文章目录
信号阻塞和未决信号
信号集相关API函数
操作流程
代码示例
未决信号
代码示例
pause的使用
代码示例
信号传送处理过程
可重入函数
代码示例
信号处理函数的继承
代码示例
setitimer实现定时器
代码示例
信号阻塞和未决信号
进程可以设置对某个信号的阻塞(屏蔽),需要用到sigset_t(信号集)数据类型。
信号集相关API函数
sigemptyset(3)
#include <signal.h> int sigemptyset(sigset_t *set); 功能:将信号集清空 参数: set:指定要清空的信号集 返回值: 0 成功 -1 错误
int sigfillset(sigset_t *set);
功能:将信号集设置为满 参数: set:指定要置满的信号集 返回值: 0 成功 -1 错误
int sigaddset(sigset_t *set, int signum);
功能:给信号集添加信号 参数: set:指定信号集 signum:指定要添加的信号 返回值: 0 成功 -1 错误
int sigdelset(sigset_t *set, int signum);
功能:删除信号集中的某个信号 参数: set:指定信号集 signum:指定要删除的信号 返回值: 0 成功 -1 错误
int sigismember(const sigset_t *set,int signum);
功能:测试信号是否是信号集中的一个成员 参数: set:指定的信号集 signum:指定信号 返回值: -1 错误 0 信号不是信号集中的一个成员 1 信号集中有这个信号
操作流程
如果希望设置进程对2号信号阻塞,就需要设置阻塞信号集
- sigset_t block_set;
- 将block_set集合中的所有成员清空;
- 将2好信号添加到block_set集合中;
- 将block_set设置为这个进程的阻塞信号集。sigprocmask函数使用。
sigprocmask(2)
#include <signal.h> int sigprocmask(int how, const sigset_t *set, \ sigset_t *oldset); 功能:检测或改变阻塞信号 参数: how: SIG_BLOCK 当前信号集和set指定信号集的并集 SIG_UNBLOCK 将set集合里的成员从当前进程阻塞信号集中移除 SIG_SETMASK 将set指定为当前进程的阻塞信号集 set:指定的新的信号集 oldset:之前的信号集保存到oldset中。 返回值: 0 成功 -1 错误
代码示例
blocked.c #include <stdio.h> #include <signal.h> #include <unistd.h> void doit(int n){ printf("recv %d signal...\n",n); return; } int main(void){ sigset_t block; printf("pid:%d\n",getpid()); //为2号信号添加处理函数 signal(64,doit); //将block清空 sigemptyset(&block); //将2号信号添加到block集合中 sigaddset(&block,64); //将block设置为进程的阻塞信号集 sigprocmask(SIG_SETMASK,&block,NULL); //while(1){ sleep(10); //} //解除信号阻塞 sigprocmask(SIG_UNBLOCK,&block,NULL); return 0; }
- 执行结果
未决信号
在信号则是期间,可以查看未决信号。
如何查看未决信号。使用sigpending(2)查看
#include <signal.h> int sigpending(sigset_t *set); 功能:检测未决信号 参数: set:值-结果参数,用于返回进程的未决信号掩码 返回值: 0 成功 -1 错误
代码示例
- pending.c
#include <stdio.h> #include <signal.h> int main(void){ //定义信号集变量 sigset_t b,p; //清空b信号集 sigemptyset(&b); //将2号信号添加到b信号集中 sigaddset(&b,2); sigaddset(&b,3); //设置进程的信号屏蔽掩码为b sigprocmask(SIG_SETMASK,&b,NULL); while(1){ sleep(1); //清空信号集p sigemptyset(&p); //获取未决信号掩码 sigpending(&p); //检测信号集p中有哪些未决信号 int f=sigismember(&p,2); if(f==-1){ perror("sigismember"); return 1; } if(f==0){ printf("not 2\n"); }else{ printf("2号信号未决\n"); } int f3=sigismember(&p,3); if(f3==-1){ perror("sigismember"); return 1; } if(f3==0){ printf("not 3\n"); }else{ printf("3号信号未决\n"); } } return 0; }
- 执行结果
pause的使用
int pause(void);
#include <unistd.h> int pause(void); 功能:等待一个信号 参数: void 返回值: -1 错误 errno被设置为EINTR
代码示例
- pause.c
#include <stdio.h> #include <signal.h> #include <unistd.h> void doit(int n){ printf("recv %d signal\n",n); return; } int main(void){ signal(2,doit); //等待信号到来 int f=pause(); /*if(f==-1){ perror("pause"); return 1; }*/ printf("pause after...%d\n",f); return 0; }
- 执行结果
- 使用alarm和pause完成sleep的功能,mysleep.c
#include <stdio.h> #include <signal.h> #include <unistd.h> void doit(int n){ return; } unsigned int mysleep(unsigned int seconds){ signal(SIGALRM,doit); //设置闹钟 int l=alarm(seconds); //暂停 pause(); return l; } int main(void){ while(1){ mysleep(4); printf("jjjjjjj\n"); } return 0; }
- 执行结果
信号传送处理过程
- 用户输入命令,在bash下启动一个前台作业。
- 用户按下ctrl+c键,这个键盘输入产生一个硬件中断。
- 如果CPU正在执行这个进程的代码,则该进程的用户空间代码暂停执行。CPU从用户态切换到内核态处理硬件中断。
- 终端驱动程序将ctrl+c解释成一个SIGINT信号,记录在该进程的PCB中。
- 当某个时刻,进程从内核态切换会用户态的时候,首先处理PCB中记录的信号,如果PCB中有未处理信号,找到信号对应的信号处理程序进行处理。
- 信号处理函数处理完毕,调用sigreturn(2),继续返回到进程的内核态。再次循环到第五步。
可重入函数
函数使用到的变量的空间全部分配在栈桢中,那这样的函数称为可重入函数。否则称为不可重入函数。
信号的处理函数尽量保证为可重入函数。
代码示例
- reenterable.c
#include <stdio.h> #include <signal.h> void doit(int n){ int v; static int c=0; v=c; v++; usleep(5000); c=v; //usleep(5000); printf("c=%d\n",c); return; } int main(void){ signal(2,doit); while(1) doit(333); return 0; }
- 执行结果
信号处理函数的继承
子进程会继承父进程的信号处理函数,信号属于进程资源,在进程的PCB中会有信号的记录。
代码示例
- p_signal.c
#include <stdio.h> #include <signal.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> void doit(int n){ printf("recv %d signal\n",n); return ; } int main(void){ pid_t pid; //注册2号信号的处理函数为用户自定义 signal(2,doit); //创建子进程 pid=fork(); if(pid ==-1){ perror("fork"); return 1; } if(pid==0){ printf("child pid %d\n",\ getpid()); while(1); }else{ wait(NULL); } return 0; }
- 执行结果
setitimer实现定时器
系统计时器,运行一个进程的时候,进程所消耗的时间包括三个部分:
- 用户时间:进程消耗在用户态的时间
- 内核时间:进程消耗在内核态的时间
- 睡眠时间:进程消耗在等待I/O、睡眠等不被调度的时间。
内核为每个进程都维护了三个计时器:
- 真实计时器:统计进程的执行时间。
- 虚拟计时器:统计进程的用户时间。
- 实用计时器:统计进程的用户时间和内核时间之和。
执行时间 = 用户时间 + 内核时间 + 睡眠时间。
这三个计时器除了统计进程的各种时间以外,还可以按照各自的计时规则,以定时器的方式工作,向进程周期性的发送不同的信号。
SIGALRM 真实定时器 SIGVTALRM 虚拟定时器 SIGPROF 实用定时器
通过使用setitimer(2)设置、启动、关闭定时器
#include <sys/time.h> int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value); 功能:设置一个间隔时间的定时器 参数: which: ITIMER_REAL:SIGALRM ITIMER_VIRTUAL:SIGVTALRM ITIMER_PROF:SIGPROF new_value:定时器新值 old_value:定时器原来的值 返回值: 0 成功 -1 错误 errno被设置
- 补充
struct itimerval{ /*间隔时间*/ struct timeval it_interval; /* next value */ /*初值时间*/ struct timeval it_value; /* current value */ }; struct timeval{ long tv_sec; /* seconds */ long tv_usec; /* microseconds */ };
代码示例
- timer.c
#include <stdio.h> #include <signal.h> #include <sys/time.h> #include <unistd.h> void doit(int n){ printf("recv signal...\n"); return ; } int main(void){ struct itimerval new; //设置计时器的初始时间 new.it_value.tv_sec=5; new.it_value.tv_usec=0; //设置定时器的间隔时间 new.it_interval.tv_sec=0; new.it_interval.tv_usec=500000; signal(SIGALRM,doit); //设置定时器 setitimer(ITIMER_REAL,&new,NULL); while(1) pause(); return 0; }
- 执行结果