Linux信号产生与处理机制学习笔记(一)

来源:互联网 发布:点播软件哪家好 编辑:程序博客网 时间:2024/05/01 07:21

一、信号基本概念:

中断:就是终止当前代码转而执行其他代码,中断有软件中断与硬件中断。

信号的本质:是一系列非负整数(操作系统就是这么爱用整数)。UNIX常用信号1–48,Linux常用信号1–64。每个信号都有一个宏名称(宏变量),宏变量都是以SIG(signal)开头。信号的产生时间是无规律的,不知道什么时候回来,因此对于信号的处理采取异步处理方式(函数指针信号中断)。信号是不连续的;有些信号是不存在的;不同的操作系统,对应的信号的值时不同的(宏名是一样的),故在开发时应使用宏而不是使用数字以更好的兼容不同版本LINUX/UNIX。

eg:(LINUX)SIGINT是信号2,Ctrl+c产生的信号就是SIGINT,交由操作系统处理。

kill -l:查看信号

这里写图片描述

31个(1-31)为UNIX经典信号,后31个(34-64)为硬件驱动开发的实时信号man 7 signal查看信号章节对各信号的介绍eg:(对应上图)Ctrl + c:2   SIGINT 默认终止进程Ctrl + z:20  SIGTSTP 停止(fg + id继续运行,dg + id后台运行)Ctrl + \:3   SIGQUIT 默认终止进程段错误:11     SIGSEGV 非法操作内存总线错误:7    SIGBUS 非法操作文件映射浮点数例外:8  SIGFPE CPU不能除0(致命错误)管道信号:13   SIFPIPE 向一个没有读端的管道写操作

二、用kill函数实现kill命令:

1、kill()函数介绍:

kill函数:int kill(pid_t pid, int signal);pid > 0:指定pid进程pid == 0:与发送信号同组的进程pid < 0:发送给指定组gid = |pid|pid == -1:向所有权限发送信号的进程发送信号

还用一种用法:(kill -0 pid测试)指定pid,并发送0,用来测试当前用户是否拥有给某个进程发送信号的权限,如图(普通用户不具备给init进程发送信号的权力,称作为:不允许的操作):

这里写图片描述

2、kill命令实现:

用kill函数实现简单的kill命令:#include<stdio.h>#include<stdlib.h>#include<signal.h>#include<sys/types.h>int main(int argc, char *argv[]){    if(argc < 3){        printf("./mykill signal pid");        exit(EXIT_FAILURE);    }    if(kill((pid_t)atoi(arv[2]), atoi(argv[1])) < 0){        perror("kill");        exit(EXIT_FAILURE);     }    return 0; }/*Ctrl + z:进程暂停放到后台,或者启动时直接加&便启动到后台运行fg num:从后台启动到前台运行bg num:启动到后台运行*/

3、测试结果如图:

我们向一个死循环的后台进程发送SIGSEGV(11)信号,并且fg重新调到前台时发现报了个“段错误(核心已转储)”,其实是因为我们发送的是SIGSEGV信号而已,并非是因为段错误。
这里写图片描述

三、信号发送与处理方式:

1、信号发送方式:

我们可以在键盘上Ctrl+c、Ctrl+\来发送一些信号,在程序出错(段错误、总线错误、浮点数例外)时也会发送一些信号,另外我们常用的发送命令的方式除了键盘还有使用kill命令/kill()函数发送,当然发送信号除了以上一些方式,还有一些函数也可以实现:

int raise(int sig);函数:只能向自己发送信号void abort(void);函数:向自己发送指定信号SIGABRT(6)alarm()函数:可以发送信号,但不是为了发送信号而诞生的函数。
#include<unistd.h>unsigned int alarm(unsigned int seconds);/*只能给当前进程(调用alarm()函数的进程)发送一个SIGALRM信号的函数。*/

alarm(n); n秒之后会发送一个闹钟信号SIGALRM。闹钟函数alarm()不阻塞,SIGALRM默认处理方式是打印“闹钟”并结束进程。如果多次调用alarm(),新的闹钟会替代原有闹钟。当参数为0,表示闹钟取消。
eg:
alarm(10); //从现在起10秒后发送一个闹钟信号,默认打印“闹钟”
alarm(2); //从现在起2秒钟后发送一个信号,取消原有10中后发送
alarm(0); //取消原有闹钟
//所以最后不会打印“闹钟”两字

返回值:如果以前没有设置闹钟,或者以前设置闹钟已经结束,那么现在(新的)alarm()函数返回0,;如果以前设置alarm()没有结束,那么新的alarm()函数返回之前alarm()到发送信号剩余的时间(类似于sleep返回的剩余时间)。

2、信号处理的方式:

SIG_IGN:忽略
SIG_DEL:默认
a signal handling function:捕捉,自定义处理函数

(1)、默认处理方式:

默认处理五种方式:
①Term : Default action is to terminate the process.
默认动作是终止进程
②Ign : Default action is to ignore the signal.
默认动作是忽略信号(与SIG_IGN不是一个层级的)
③Core : Default action is to terminate the process and dump core (seecore(5)).
默认动作是终止这个进程并产生一个core文件(Core Dump用于GDB调试)
④Stop : Default action is to stop the process.
默认动作是暂停进程
⑤Cont : Default action is to continue the process if it is currently stopped.
默认动作是继续进程(如果当前这个进程被暂停)

(2)、自定义捕捉信号:
signal、sigaction函数处理(先说signal):

#incldue<signal.h>typedef void (*sighandler_t)(int);sighandler_t signal(int signum,sighandler_t handler);

参数:
参数与返回值都是自定义函数指针(函数名作为指针),signum是具体的信号值。handler(处理方式)除了可以设为自定义函数指针,还可以设定为SIG_IGN(忽略信号)、SIG_DFL(默认处理方式)。

返回值:
成功返回信号处理的前值(return previous value of the signal handler),错误失败返回宏SIG_ERR。

3、alarm()与signal()测试:

alarm()函数在5秒后发送一个SIGALRM(14)信号,而死循环中注册了一个SIGALRM信号,并以自定义处理方式注册。循环中每打印一次alarm n就睡眠一秒,所以5秒钟打印了5次,之后就被信号中断执行自定义信号处理函数,并且直接退出,所以返回值为exit()退出码,而不是main函数中return值:

这里写图片描述

四、信号集与信号集操作函数:

1、PCB中维护的信号集:

PCB中为信号维护了两个信号集PEND未决信号集Block阻塞信号集。未决信号集初始值每一位均为0,如果产生一个信号则对应bit位置为1,产生的信号如果阻塞信号集对应位置为1,则阻塞。如果不再阻塞进行下一步处理(忽略、默认、自定义)时,处理完毕则未决信号对应bit位会被重新置为0。未决信号集对应位为1时,信号状态为未决态(信号产生,没有被响应);递达态:信号产生并且被相应(不阻塞)。未决信号集由内核自动设置,用户可读,而阻塞信号集用户可以自己设置一屏蔽某些信号。信号SIGKILL和SIGSTOP不能被忽略/阻塞。并且前三十一个信号不支持排队机制,后三十二个支持排队机制。

关于PEND未决信号集Block阻塞信号集 的图解如图所示:
这里写图片描述

2、信号集处理函数:

sigset_t为信号集类型,sizeof(sifset_t)=128,每个信号站一个bit位,剩余的为预留位置,程序员可以操作的信号集为阻塞信号集,又叫做信号屏蔽字)。

信号屏蔽:在执行某些核心代码,我们不希望被信号意外中断,可采用屏蔽信号的方法(信号到了但是延时处理),防止写如等操作被意外终止。解除信号屏蔽以后再处理来过的被屏蔽的信号。

信号集的功能函数:

①清空、删除所有信号(全置为0)
int sigemptyset(sigset_t * set);
②将所有信号全加入(全置为1)
int sigfillset(sigset_t * set);
③增加1个信号
int sigaddset(sigset_t * set,int signum);
④删除一个信号
int sigdelset(sigset_t * set,int signum);
⑤查找元素(判断是否是现有成员)
int sigismember(const sigset_t * set,int signum);
有返回1、无返回0

设置信号屏蔽字(屏蔽信号集):
int sigprocmask(int how,sigset_t * new,sigset_t * old);
参数:
how是屏蔽的方式,有三种:
SIG_BLOCK:相当于或运算,在原有的基础上加上新的屏蔽信号
SIG_UNBLOCK:相当于与运算,在原有的基础上去除屏蔽的信号
SIG_SETMASK:直接重新赋值,与原有的无关
new:新的设置的信号屏蔽字,old保存之前的信号屏蔽字,如果old为NULL就是不保存原有的信号屏蔽字。当保存了原有信号屏蔽字到old,核心代码执行完毕重新设置为old信号屏蔽字时,就是取消屏蔽的过程。

获取未决信号集
程序员虽然不能操作未决信号集,但是能够进行未决信号集的读取:
int sigpending(sigset_t * set);

3、信号屏蔽测试:

/*sigprocmask.c*/#include<stdio.h>#include<signal.h>#include<sys/types.h>void print_sigset(const sigset_t * sig_get){    int i;    for(i=1; i<32; i++){        if(sigismember(sig_get, i) == 1)            putchar('1');        else            putchar('0');    }    printf("\n");}int main(void){    sigset_t set, get;    printf("sizeof(sigset_t) = %d\n",sizeof(sigset_t));    sigemptyset(&set);//屏蔽字清空,即全部置为0    sigaddset(&set, SIGINT);//可以被阻塞    sigaddset(&set, SIGQUIT);//可以被阻塞    sigaddset(&set, SIGKILL);//不可以被阻塞,设置无效    sigprocmask(SIG_BLOCK, &set, NULL);    while(1){        sigpending(&get);        print_sigset(&get);        sleep(1);    }    return 0;}

测试结果:
这里写图片描述

注意:一般,我们不对操作系统赋予了实际意义的信号进行自定义操作,而SIGUSR1SIGUSR2这两个信号可用于用户自定义操作,因为这两个信号并没有实际意义,由程序员赋予他们意义,他们一般用于父子进程间通信,这点以后会提到。

五、可靠信号与不可靠信号:

信号分类:
1~64这62个信号分为两类:可靠信号不可靠信号

①1–31都是不可靠信号,这种信号不支持排队,有可能丢失,是非实时信号
②34–64都是可靠信号,这种信号不支持排队,不会丢失,是实时信号

关于信号会丢失这点,我们可以做个测试:

/* *模拟一个需要屏蔽某种信号的环境 *运行代码 *重新打开一个终端 *kill -sig pid给该进程发送信号测试 * */#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<signal.h>void accept_signal(int sig){    printf("接收到信号%d\n",sig);}int main(void){    /*2,3为不可靠信号,会丢失一部分*/    signal(SIGINT,accept_signal);    signal(SIGQUIT,accept_signal);    /*50为可靠信号,自行排队等待*/    signal(50,accept_signal);    printf("pid = %d\n",getpid());    printf("执行普通代码,不屏蔽信号,但是未忽略的信号会中断sleep\n");    int ret = sleep(30);    printf("ret = %d\n",ret);    sigset_t set_new, set_old;    sigaddset(&set_new,SIGINT);     sigaddset(&set_new,SIGQUIT);    sigaddset(&set_new,50);    sigprocmask(SIG_SETMASK, &set_new, &set_old);/*设置屏蔽信号集*/    printf("执行关键代码,屏蔽信号,sleep不会被屏蔽的信号中断\n");    ret = sleep(30);    printf("ret = %d\n",ret);    printf("关键代码执行完毕,解除屏蔽\n");    sigprocmask(SIG_SETMASK, &set_old, NULL);/*解除屏蔽*/    return 0;}

存放来过的阻塞信号的数据结构,类似于一个栈(当然这个数据结构不是栈,因为如果取消屏蔽的是2而不是3和50,那么2号信号也出不来),对于不可靠信号产生,如果还未处理(屏蔽阻塞状态),之后再产生的同类信号则会丢失;而可靠信号将所有未处理的信号都保存,当解除屏蔽时再取出来。结果如下:

这里写图片描述

0 0
原创粉丝点击