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;
}