Linux编程–信号处理

1、pause函数

  • pause函数的本质是将当前进程挂起,等待信号的触发
  • 解除休眠一:传递信号终止进程
  • 解除休眠二:调用信号捕获函数
  • 返回值:仅在捕捉信号并且捕获函数执行结束后返回-1并且将errno赋值为EINTR;

pause函数+alarm函数模拟sleep函数的实现

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <signal.h>
#include <errno.h>

typedef void (*sighandler_t)(int);
// 信号捕获后的回调函数
void sigalarmcatch(int sigal){
    printf("catch fuction....\n");
    sleep(1);
}
// 模拟实现sleep函数的功能主体
unsigned int mysleep(unsigned int seconds){
    // 1.1、创建sigaction结构体
    struct sigaction sa, old_sa;
    sigemptyset(&sa.sa_mask);
    sa.sa_handler = sigalarmcatch;
    sa.sa_flags = 0;
    // 1.2、捕获闹钟信号,恢复原进程继续执行
    sigaction(SIGALRM, &sa, &old_sa);
    // 2、闹钟定时,时间到期后会自动唤醒原进程
    alarm(seconds);
    // 这个位置很关键!!!!!这是一个风险点,如果系统负荷很重,有可能很长时间后才能再次回来继续执行
    // 如果程序运行此处时失去了cpu的使用权,待重新获取cpu后,
    // 时钟已到期,此时进程处理时钟到期信号,处理结束后才能执行pause函数,
    // 进程被挂起,无法再次被唤醒
    int ret = pause();
    if (ret == -1 && errno == EINTR){
        printf("pause success\n");
    }
    ret = alarm(0);
    // 3、恢复原信号的默认处理方式
    sigaction(SIGALRM, &old_sa, NULL);
    return ret;
}

int main(){
    while(1){
        printf("begin....\n");
        mysleep(1);
    }
    return 0;
}
  • 以上自定义sleep函数中存在一个漏洞,即在闹钟开启后到进程挂起前这个时间间隙里,如果CPU被其他进程抢占,闹钟信号发送之后才重新拥有cpu使用权,那进程会先处理信号,再执行pause挂起进程,此时已错过信号唤醒时机,此后进程不会再次被唤醒
  • 重点在于:信号不能在进程挂起之前被处理掉,否则闹钟失效!
  • pause函数无法解决这个问题,这是系统层面的机制
  • sigsuspend函数为此而生,解决以上问题的思路是:
    • 先将闹钟信号阻塞
    • 将解除阻塞的信号屏蔽字传给sigsuspend函数
    • 解除阻塞与挂起进程捆绑成原子操作
    • 确保进程处理信号在挂起进程之后
    • 这个就叫时序竞态

2、sigsuspend函数

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

// 信号捕捉回调函数
void sigcatch(int signal){
    printf("I am catched signal %d\n", signal);
}

// 自定义sleep函数
unsigned int mysleep(unsigned int seconds){
    // 闹钟+pause
    // 1、捕获闹钟信号
    struct sigaction sigact, old_sigact;
    sigact.sa_handler = sigcatch;
    sigemptyset(&sigact.sa_mask);
    sigact.sa_flags = 0;
    sigaction(SIGALRM, &sigact, &old_sigact);

    // 2、阻塞闹钟信号
    sigset_t sig, old_sig, sus_sig;
    sigemptyset(&sig);
    sigaddset(&sig, SIGALRM);
    sigprocmask(SIG_BLOCK, &sig, &old_sig);

    // 3、开启闹钟
    alarm(seconds);

    // 4、解除阻塞挂起进程
    sus_sig = old_sig;
    sigdelset(&sus_sig, SIGALRM);
    sigsuspend(&sus_sig);

    // 5、还原信号捕获、还原信号屏蔽字
    int unslept = alarm(0);
    sigprocmask(SIG_SETMASK, &old_sig, NULL);
    sigaction(SIGALRM, &old_sigact, NULL);
    return unslept;
}

int main(){
    int i = 0;
    while(++i){
        printf("%d:\n", i);
        mysleep(1);
    }
    return 0;
}

3、时序竞态的事例

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

int num, flag;
// 父进程信号捕捉回调函数
void father(int signal){
    if (flag == 1){
        //sleep(1);
        printf("father: %d\n", num+=2);
        flag = 0;
    }
}
// 子进程信号捕捉回调函数
void child(int signal){
    if (flag == 1){
        //sleep(1);
        printf("child:  %d\n", num+=2);
        flag = 0;
    }
}

int main(){
    pid_t pid = fork();
    if (pid < 0){
        perror("fork error");
        exit(1);
    }else if (pid > 0){
        // 父进程,捕捉信号SIGUSR2
        struct sigaction act;
        sigemptyset(&act.sa_mask);
        act.sa_handler = father;
        act.sa_flags = 0;
        sigaction(SIGUSR2, &act, NULL);

        num—;// num = 1
        sleep(1); // 等待子进程注册捕捉回调函数,因为是相互发送信号,必须要等到对方的信号才能发送下一个信号
        while(1){
            if (flag == 0){
                // 给对方发送一个信号,对方接收到后,会立即回发一个,收到回发信号修改了flag值才能再次给对方发送
                // 发送信号-->对方回信号-->修改falg值为0
                int ret = kill(pid, SIGUSR1);
                if (ret == -1){
                    perror("kill error");
                    exit(1);
                }
                // 如果flag=1执行的时间比较慢,在对方回送信号后(回调函数中将flag修改为0)才执行,flag赋值为1
                // 下一次while循环不会再进这里来,当前进程后续不会再发信号,对方也会一直处于等待状态
                flag = 1;
            }
        }
    }else{
        // 子进程
        struct sigaction act;
        sigemptyset(&act.sa_mask);
        act.sa_handler = child;
        act.sa_flags = 0;
        sigaction(SIGUSR1, &act, NULL);
        sleep(1);// 等待父进程注册好捕捉回调函数,因为是相互发送信号,并且等到对方的信号才能发送下一个信号
        while(1){
            if (flag == 0){
                int ret = kill(getppid(), SIGUSR2);
                if (ret == -1){
                    perror("kill error");
                    exit(1);
                }
                flag = 1;
            }
        }
    }

    return 0;
}

自定义闹钟、自定义sleep、完善上述功能

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/time.h>

unsigned int myalarm(unsigned int useconds){
    // 闹钟
    struct itimerval value, old_val;
    value.it_value.tv_sec = 0;
    value.it_value.tv_usec = useconds;
    value.it_interval.tv_sec = 0;
    value.it_interval.tv_usec = 0;
    setitimer(ITIMER_REAL, &value, &old_val);
    return old_val.it_value.tv_sec * 1000 * 1000 + old_val.it_value.tv_usec;
}
void mysleephandler(int second){}
unsigned int mysleep(unsigned int useconds){
    // 闹钟+sigsuspend
    // 0、捕捉闹钟信号
    struct sigaction sigact, old_act;
    sigact.sa_handler = mysleephandler;
    sigemptyset(&sigact.sa_mask);
    sigact.sa_flags = 0;
    sigaction(SIGALRM, &sigact, &old_act);

    // 1、阻塞闹钟信号
    sigset_t sig, old_sig, sus_sig;
    sigemptyset(&sig);
    sigaddset(&sig, SIGALRM);
    sigprocmask(SIG_BLOCK, &sig, &old_sig);
    // 2、开启闹钟
    myalarm(useconds);
    // 3、创建信号屏蔽字
    sus_sig = old_sig;
    sigdelset(&sus_sig, SIGALRM);
    sigsuspend(&sus_sig);
    // 4、恢复
    unsigned int unalarms = myalarm(0);
    sigprocmask(SIG_SETMASK, &old_sig, NULL);
    sigaction(SIGALRM, &old_act, NULL);
    return unalarms;
}

int num, flag;
void father(int signal){
    if (flag == 1){
        mysleep(20);
        printf("father: %d\n", num+=2);
        flag = 0;
    }
}
void child(int signal){
    if (flag == 1){
        mysleep(20);
        printf("child:  %d\n", num+=2);
        flag = 0;
    }
}
int main(){
    pid_t pid = fork();
    if (pid < 0){
        perror("fork error");
        exit(1);
    }else if (pid > 0){
        // 父进程
        struct sigaction act;
        sigemptyset(&act.sa_mask);
        act.sa_handler = father;
        act.sa_flags = 0;
        sigaction(SIGUSR2, &act, NULL);

        num--;
        sleep(1);
        while(1){
            if (flag == 0){
                int ret = kill(pid, SIGUSR1);
                if (ret == -1){
                    perror("kill error");
                    exit(1);
                }
                flag = 1;
            }
        }
    }else{
        // 子进程
        struct sigaction act;
        sigemptyset(&act.sa_mask);
        act.sa_handler = child;
        act.sa_flags = 0;
        sigaction(SIGUSR1, &act, NULL);
        sleep(1);

        while(1){
            if (flag == 0){
                int ret = kill(getppid(), SIGUSR2);
                if (ret == -1){
                    perror("kill error");
                    exit(1);
                }
                flag = 1;
            }
        }
    }

    return 0;
}

4、SIGCHLD信号的捕获

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

// 信号捕获函数
void sigcatch(int signal){
    int wstatus;
    pid_t wpid = waitpid(0, &wstatus, WNOHANG);
    while(wpid > 0){
        printf("waitpid %u, wstatus: %d\n", wpid, WEXITSTATUS(wstatus));
        wpid = waitpid(0, &wstatus, WNOHANG);
    }
}
int main(){
    // 开启子进程
    int i = 10;
    pid_t pid;
    while(i--){
        pid = fork();
        if (pid == 0) break;
    }
    if (pid > 0){
        // 父进程
        // 1、阻塞SIGCHOD信号
        sigset_t sig, old_sig;
        sigemptyset(&sig);
        sigaddset(&sig, SIGCHLD);
        sigprocmask(SIG_BLOCK, &sig, NULL);
        // 2、注册捕获函数
        struct sigaction sigact, old_act;
        sigact.sa_handler = sigcatch;
        sigemptyset(&sigact.sa_mask);
        sigact.sa_flags = 0;
        sigaction(SIGCHLD, &sigact, &old_act);
        // 3、解决SIGCHLD信号阻塞
        sigprocmask(SIG_UNBLOCK, &sig, NULL);
        while(1){
            pause();
            //printf("---------------------------\n");
        }
    }else{
        // 子进程
        //printf("child getpid:%u, return %d\n", getpid(), i);
        //printf("child %u, index %d\n", getpid(), 10-i);
        return 10-i;
    }
    return 0;
}

Leave a Reply