信号的基本概念
操作系统内部的交互机制。
- 信号相对于进程的控制流程来说是异步,即信号在任何时间点都可能产生。
- 进程通过位图知道自己收到了几号信号,位图放在PCB中。
- 比特位代表信号编号,比特位的内容代表该信号是否被阻塞。
- 操作系统通过修改pending表位图中的某一位来给进程发送信号,进程收到信号后先把信号保存起来,再在合适的时间去处理信号。
- 处理信号的方式有忽略(9号信号SIGKILL、19号信号SIGSTOP不能忽略),默认,自定义。
产生信号
1.通过终端按键产生信号
Ctrl+c、Ctrl+\
SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且Core Dump
Core Dump叫做核心转储,当一个进程要异常终止时(通常是有Bug),可以选择把进程的用户空间内存数据全部保存到磁盘上。文件名叫做core。线上的服务器是不允许产生core文件的。
2.调用系统函数向进程发信号
使用kill命令,kill命令是调用kill函数实现的。kill函数可以给一个指定的进程发送指定的信号。raise函数可以给当前进程发送指定信号(自己给自己发送信号)。
3.由软件条件产生信号
管道读端关闭产生SIGPIPE,也是一种软件条件产生的信号。
4.硬件异常产生
CPU:零作为除数,会产生SIGFPE信号。
mmu:访问内存发现一个内存非法或内存越界,mmu设备会发现错误并通知操作系统内核发一个11号信号,就会产生段错误。
阻塞信号
信号递达:实际执行信号的处理动作
信号未决:信号从产生到递达之间的状态。
进程可以选择阻塞某个信号。
阻塞和忽略的区别:
只要信号被阻塞就不会递达,忽略是在递达之后可选的一种处理动作。
信号在内核中表示
每个信号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作。
sigset_t:信号集,非零即一。阻塞信号集也叫做当前进程的信号屏蔽字。
pause:调用进程挂起直到有信号递达
#include <unistd.h>
int pause(void);
sigprocmask
读取或更改进程的信号屏蔽字
#include<signal.h>
int sigprocmask(int how,const sigset_t *set,sigset_t *oset);
//返回值:若成功为0,出错为-1
pending
读取当前进程的未决信号,只能读不能改。
#include<signal.h>
sigpending(sigset_t *set);
经典信号处理
SIGALARM时钟信号
#include <unistd.h>
unsigned int alarm(unsigned int seconds);//时间以秒为单位
代码实现捕捉信号,读取信号屏蔽字。
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void myhander(int sig)
{
printf("sig=%d\n",sig);
}
void Printsigset(sigset_t* set){
int i=1;
for(;i<=31;i++){
if(sigismember(set,i)){
printf("1");
}else{
printf("0");
}
}
printf("\n");
}
int main()
{
//1.捕捉SIGINT信号
signal(SIGINT,myhander);
//2.把SIGINT信号屏蔽
sigset_t set;
sigset_t oset;
sigemptyset(&set);
sigaddset(&set,SIGINT);
sigprocmask(SIG_BLOCK,&set,&oset);
//3.循环读取未决信号集
int count=0;
while(1){
++count;
if(count>=5){
count=0;
//解除信号屏蔽字,再设置上
printf("解除信号屏蔽字!\n");
sigprocmask(SIG_SETMASK,&oset,NULL);
sleep(1);
printf("再次设置信号屏蔽字!\n");
sigprocmask(SIG_BLOCK,&set,&oset);
}
sigset_t pending_set;
sigpending(&pending_set);
Printsigset(&pending_set);
sleep(1);
}
return 0;
}
可重入函数
同一个函数在多个执行流中同时调用,没有逻辑问题。如果一个函数访问自己的局部变量或参数,叫做可重入函数。
不可重入函数
同一个函数在多个执行流中同时调用,有逻辑问题。如果函数访问一个全局链表,则可能会因为重入而发生错乱。
volatile限定符
关键字,防止编译器对代码过度的优化
竞态条件
运行时序并不像我们写程序所设想的那样,由于异步事件在任何时候都有可能发生(异步事件有时候指出现更高优先级的进程),由于时序问题导致错误,叫做竞态条件。
SIGCHLD
为避免产生僵尸进程
- 父进程可以阻塞等待子进程结束。--父进程阻塞饿就不能处理自己的工作
- 轮询方式---父进程非阻塞查询是否有子进程等待清理。----父进程还要时不时的轮询一下,程序实现复杂。
- 子进程在终止时会给父进程发送SIGCHLD信号,信号的默认处理动作是忽略,所以父进程就可以处理自己的工作,不必关心子进程。
代码实现:
void handler(int sig_id){
if(waitpid(-1,NULL,WNOHANG)>0){
printf("wait child success!\n");
}
printf("child exit!\n");
}
int main(){
signal(SIGCHLD,handler);
pid_t id=fork();
if(id==0){
//child
printf(" %d,i am child\n",getpid());
exit(1);
}else if(id>0){
//father
while(1){
printf("i am father!\n");
sleep(1);
}
}else{
perror("fork\n");
}
return 0;
}