首页 > 代码库 > 0725------Linux基础----------信号

0725------Linux基础----------信号

1.基础知识巩固

  1.1 中断分为两类:

    a)中断,就是通常所说的中断,中断处理程序运行在内核态,需要一定的硬件支持;

    b)中断,是在软件层次上对中断的一种模拟,就是常说的信号,它的处理程序运行在用户态。它是软件级别的,不需要特定的硬件支持。

  1.2 常见的信号:(用kill -lman 7 signal 命令可查看)

    a)SIGINT : CTRL+C , 2

    b)SIGQUIT: CTRL+\ , 3

    c) SIGKILL: kill -9 pid 命令 ,9

    d)SIGPIPE: 与关闭的TCP连接有关,13

    e)SIGCHLD: 子进程消亡,出现僵尸进程,17

    f)SIGALARM: alarm 函数,14

  1.3 关于信号的术语:

    a)发送信号;

    b)接收信号。

  1.4 对信号的处理方式有三种(这里注意,杀死一个进程的方法是,kill -9 pid 或者 pkill filaname)

    a)自行编写handler

    b)忽略

    c)采用系统默认的行为

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <signal.h>#define ERR_EXIT(m)     do {         perror(m);        exit(EXIT_FAILURE);    }while(0)/* * 处理 ctrl + C 信号  * 忽略 ctrl + \  信号 */void handler(int signum){    printf("    catch Ctrl + C\n");}int main(int argc, const char *argv[]){    if(signal(SIGINT, handler) == SIG_ERR){        ERR_EXIT("signal");    }    if(signal(SIGQUIT, SIG_IGN) == SIG_ERR){ //ctrl +         ERR_EXIT("signal");    }    for(;;)        pause(); //暂停程序直到收到信号    return 0;}

2. 发送和接收信号

  2.1 发送信号的方法:

    a)kill -signum pid 命令:向pid进程发送signum信号,例如,kill -9 pid 就是向某进程发送SIGKILL信号杀死进程,kill -2 pid就是向某个进程发送CTRL + C 信号,使进程终止;

    b)使用系统调用kill 函数;

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <signal.h>#define ERR_EXIT(m)     do {         perror(m);        exit(EXIT_FAILURE);    }while(0)/* * kill 函数  向一个进程发送某个信号 */void handler(int signum){    printf("    catch Ctrl + C\n");}int main(int argc, const char *argv[]){    if(signal(SIGINT, handler) == SIG_ERR){        ERR_EXIT("signal");    }    sleep(3); //睡三秒后向本进程发送 SIGINT 信号    if(kill(getpid(), SIGINT) == -1){        ERR_EXIT("kill");    }    for(;;)        pause(); //暂停程序直到收到信号    return 0;}

    c)alarm 函数,给自己发送一个SIGALRM 信号,注意每个进程只能有一个alarm计时器,新的会取代旧的。

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <signal.h>#define ERR_EXIT(m)     do {         perror(m);        exit(EXIT_FAILURE);    }while(0)/* * alarm 函数 给自己发送 SIGALAM 函数 */void handler(int signum){    printf("    catch sigalrm\n");}int main(int argc, const char *argv[]){    if(signal(SIGALRM, handler) == SIG_ERR){        ERR_EXIT("signal");    }    alarm(3);    for(;;)        pause(); //暂停程序直到收到信号    return 0;}

  

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <signal.h>#define ERR_EXIT(m)     do {         perror(m);        exit(EXIT_FAILURE);    }while(0)/* * alarm 计时器 */void handler(int signum){    static int beeps = 0;    printf("beep\n");    if(++beeps < 5){        alarm(1);    }    else{        printf("boom\n");        exit(EXIT_SUCCESS);    }}int main(int argc, const char *argv[]){    if(signal(SIGALRM, handler) == SIG_ERR){        ERR_EXIT("signal");    }    alarm(3);    for(;;)        pause();    return 0;}  

  

 

  2.2 sleep函数可以被信号中断

    2.2.1 如本例,程序先睡10s,若在这10s内按了ctrl + C 即向程序发送了该信号,信号处理完毕后,此时程序不会再重新执行sleep,而是从sleep 的下一行开始执行。

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <signal.h>#define ERR_EXIT(m)     do {         perror(m);        exit(EXIT_FAILURE);    }while(0)/* * sleep 函数能被 信号打断 */void handler(int signum){    printf("    catch Ctrl + C\n");}int main(int argc, const char *argv[]){    if(signal(SIGINT, handler) == SIG_ERR){        ERR_EXIT("signal");    }    sleep(10);
   printf("after handler\n); return 0;}

 

    2.2.2 因为sleep函数返回剩余还没有睡眠的秒数,因此,可以使用一个循环,使得程序被打断后继续睡眠,直到结束。

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <signal.h>#define ERR_EXIT(m)     do {         perror(m);        exit(EXIT_FAILURE);    }while(0)/* * sleep 返回剩余没有睡眠的秒数 * 因此可以使用一个循环 来继续被打断的sleep */void handler(int signum){    printf("    catch Ctrl + C\n");}int main(int argc, const char *argv[]){    if(signal(SIGINT, handler) == SIG_ERR){        ERR_EXIT("signal");    }    //sleep(10); //sleep 期间若有信号 直接结束    int n = 10;    do{        n = sleep(n);        printf("interupted left %d seconds\n", n);    }while(n > 0);    return 0;}
    

   2.3 已经发送但是还未被接收的信号称为待处理信号。系统采用一个向量表示待处理信号的集合,所以每种类型的信号最多只有一个待处理信号。一个待处理信号最多只能被接收一次。

3.信号处理

  3.1 信号处理带来的一些问题:

    a)待处理信号会被阻塞:如果程序正在处理 SIGINT,那么此时到达一个SIGINT,会被阻塞,成为待处理信号,一直到上一个SIGINT处理程序返回。

    b)待处理信号不会排队等待:每种类型的信号最后只有一个待处理信号,如果程序中已经有一个SIGINT待处理信号,此时到达一个SIGINT,会被丢弃。

    c)系统调用可以被handler打断,即EINTR,例如,read(fd...)是一个慢速的系统调用,此时如果有信号到来,会切换到该信号的handler中,当从该handler返回的时候,read也返回 -1, errno 置为 EINTR。

  3.2 例子。父进程产生 10 个子进程,每个子进程执行完一些操作后自动退出,此时父进程收到 10 个 SIGCHLD 信号(这10个信号几乎是同一时间发出的)。然后父进程开始处理第一个 SIGCHLD 的handler,里面sleep 2s,在这期间,第二个信号到来,被阻塞为待处理信号,后面的信号都被简单的丢弃。因此,本程序后面的子进程都变成了僵尸进程。

 

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <signal.h>#define N 10#define ERR_EXIT(m)     do {         perror(m);        exit(EXIT_FAILURE);    }while(0)/* *  待处理信号只能有一个 *  其他的 会被丢弃 *///处理 sigchld 信号,防止产生僵尸进程void handler(int signum){    pid_t pid;    if((pid = waitpid(-1, NULL, 0)) < 0){        ERR_EXIT("WAITPID");    }    printf("handler process child %d\n", pid);    sleep(2); //故意阻塞别的SIGCHLD 信号,只有一个SIGCHLD信号处理完毕才能处理下一个}int main(int argc, const char *argv[]){    if(signal(SIGCHLD, handler) == SIG_ERR){        ERR_EXIT("signal");    }    pid_t pid;    int i;    for(i = 0; i < N; i++){        if((pid = fork()) < 0){            ERR_EXIT("fork");        }        else if(pid == 0){            printf(" child pid: %d\n", getpid());            sleep(2);            exit(EXIT_SUCCESS);        }    }    for(;;)        pause(); //暂停程序直到收到信号    return 0;}

 

  3.3 解决以上问题的关键思想:

    a)信号对应的向量置为 1,说明至少存在一个该信号;

    b)当处理 SIGCHLD 信号时,应该尽可能多处理子进程。

  3.4 例子。对上例的改进。程序分析:

    a)10个子进程几乎同时消亡,发出10个 SIGCHLD 信号;

    b)父进程接收到一个SIGCHLD,执行waitpid,回收第一个子进程,然后睡眠2 s;

    c)在这 2 s 内,第二个 SIGCHLG 变成待处理,其余丢失;

    d)睡眠结束,此时存在9个僵尸进程,然后执行 9+1 次while 循环,全部处理完毕;

    e)以上都属于第一个 SIGCHLD 的handler;

    f)处理第二个 SIGCHLD,此时没有子进程,waitpid 失败,退出。

 

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <signal.h>#include <unistd.h>#define N 3#define ERR_EXIT(m)     do {         perror(m);        exit(EXIT_FAILURE);    }while(0)/* * 多处理 sigchld 信号 */void handler(int signum){    pid_t pid;    while((pid = waitpid(-1, NULL, 0)) > 0){        printf("handler process child %d\n", pid);        sleep(2);    }    printf("end\n");}int main(int argc, const char *argv[]){    if(signal(SIGCHLD, handler) == SIG_ERR){        ERR_EXIT("signal");    }    pid_t pid;    int i;    for(i = 0; i < N; i++){        if((pid = fork()) < 0){            ERR_EXIT("fork");        }        else if(pid == 0){            printf(" child pid: %d\n", getpid());            sleep(2);            exit(EXIT_SUCCESS);        }    }    int n;    char buf[1024] = {0};    //阻塞在这里    //Linux 自动重启系统调用    if((n = read(STDIN_FILENO, buf, 1024)) < 0){        ERR_EXIT("read");    }    printf("parents process input\n");    return 0;}

 

  3.5 处理僵尸进程:

    a)在父进程中编写同步代码,可以按照顺序或者异步的方式逐个回收子进程这种方法的缺陷是需要准确把握子进程的消亡时机。

    b)处理SIGCHLD信号,这个方法提供了一种异步处理能力。其中要注意:处理SIGCHLD信号时,应该使用while循环尽可能多去处理子进程,这里主要是防止信号的阻塞和丢失问题。