Linux信号产生与处理机制学习笔记(二)
2017-02-20 23:26
621 查看
接着Linux信号产生与处理机制学习笔记(一) 来说。
signal()与sigaction()功能比较:
sigaction()可以注册信号的处理方式,功能和signal一样,但sigaction是增强版的signal,且signal底层是调用sigaction的。另外signal()为C标准库函数,sigaction()为Linux系统函数,signal()接口简单且跨平台,但不能设置信号屏蔽字;sigaction()则只能用于UNIX/LINUX系统,但能设置屏蔽字并允许附带信号之外的数据。
flags参数:
SA_SIGINFO:当sa_flags中包含SA_SIGINFO时,则使用第二个函数指针,其它情况使用第一个函数指针(两个不可能同时用)。
SA_NODEFER,SA_NOMASK:解除了在信号函数执行期间,对自身注册信号的原始屏蔽(只屏蔽sa_mask的屏蔽设置的信号);若不设置该flags,则会在信号函数执行期间,屏蔽注册信号相同信号与sa_mask设置的信号。
SA_RESETHAND:第一次信号处理为自定义处理,之后的恢复SIG_DFL默认处理
那么我们就对与signal()类似的结构体第一个成员:sa_handler及其使用作以简单的测试:
测试结果:
(1)、sigqueue()函数:
类似于函数kill(),可以发送信号,并且在发送信号的同时附加数据。
(2)、sa_sigaction成员与struct siginfo_t:
注意:联合体是sigqueue发送时参数,而siginfo_t结构体是信号处理函数的参数,一个是发送参数一个是接收参数。
(3)、简单测试:
测试结果:
等5秒后,父进程成功发送“hello myson”中断子进程。而在5秒内Ctrl+c中断子进程,在信号处理函数中打印的附加内容为NULL,且是由0号进程发送的信号(进程0:Linux引导中创建的第一个进程,完成加载系统后,演变为进程调度、交换及存储管理进程):
说到0号进程与内核,这里我们就得说一下信号的处理过程是怎样的了,首先信号是由内核负责产生发送的,而父进程只是向内核发送请求,子进程收到的也是请求的结果。信号处理过程如图所示:
注意:信号并非能产生立刻中断,当程序在执行过程中,在其拥有的时间片内,接收到一个信号,CPU只知道收到一个信号,不去关心是谁发给谁的,所以CPU不会马上处理,而等待当前时间片到达回收CPU资源,并分给下一个抢占CPU资源的进程。所以先将信号放入内核队列等待,当时间片到,CPU发现有一个发给时间片已经结束的进程的信号时,而该进程暂时被回收资源,只能等待下一次时间片到。当下一次时间片轮转到之前保存现场的进程时,该进程的时间片先用来处理之前等待的信号,到用户空间执行自定义处理,再返回内核,当处理完信号返回内核,时间片还未完就继续到用户空间执行该进程的代码。但是由于信号等待处理的时间很短,所以基本可以认为是实时响应的。
另外:SIGUSR1和SIGUSR2这两个信号,系统并未定义这两个信号具体用来干什么,由用户来自己处理,默认是终止进程。虽然其他大多信号也可以注册用以其他功能,但是由于系统已经为这些信号制定了特定的功能,所以我们在自己的使用过程中,凡是要对信号自定义处理的一般都用SIGUSR1和SIGUSR2实现,一般用来实现父子进程间信息同步(我们为了测试方便采用SIGINT信号)。
SIGCHLD信号是在子进程状态变化时,由系统自动发送给子进程的父进程的信号,父进程采用wait/waitpid中status参数接收,并根据该信号来进行相应操作。
SIGCHLD处理(默认忽略Ign):
SIGCHLD产生条件有以下三种:
①子进程终止时,SIGCHLD告诉父进程进行子进程收尸(回收资源)。
②子进程接收到SIGSTOP信号停止时,父进程不予理睬。
③子进程处在停止态,接受到SIGCONT后唤醒时,父进程不予理睬。
(2)、status处理方式:
之前在进程中我们有提过status参数,但是说的不是很透彻,接下来我们就这一点详细总结一下(以waitpid为例):
pid参数可参考: 进程创建与相关函数等知识点总结
options参数:
WNOHANG:没有子进程结束立刻返回
WUNTRAECD:如果子进程由于被停职产生的SIGCHLD,waitpid则立即返回
WCONTIBUED:如果子进程由于被SIGCONT唤醒而产生SIGCHLD,waitpid则立即返回
status参数:
status参数中存储了,由父进程获取子进程状态改变时系统发送的信号,以及子进程退出结束返回值(如果是因为子进程返回致使系统发送SIGCHLD)等信息。而获取其中的信息可以使用以下实现:
①WIFEXITED(status);
子进程正常exit终止返回真,而我们可以使用WEXITSTATUS(status)函数获取退出值:返回子进程正常退出值
②WIFSIGNALED(status)
子进程被信号终止返回真,WIERMSIG(status)返回终止子进程的信号值
③WIFSTOPED(status)
子进程被停止返回真,WSTOPSIG(status)返回停止子进程的信号值
④WIFCONTINUED(status)
子进程停止态转为就绪态返回真
以上宏函数的使用,对应了SIGCHLD信号的三种产生方式。
(3)简单测试:
测试一(按上面代码测试):
测试二(将上面代码信号处理时的while循环等待改为不加循环等待,并将父子进程的睡眠时间增大以便测试):
对于僵尸进程的产生,可以多创建几个子进程并以非循环等待处理,而子进程在sleep后加上一个空循环,ps -aux便会看到僵尸态。
一、sigaction()信号注册函数于struct sigaction结构体:
1、sigaction()函数:
int sigaction(int signum, const struct sigaction * act, struct sigaction * oldact); /*参数: int signum:信号编号 const struct sigaction:信号新的动作 struct sigaction * oldact:信号原有动作 一般第三个参数用不到置为NULL即可 */
signal()与sigaction()功能比较:
sigaction()可以注册信号的处理方式,功能和signal一样,但sigaction是增强版的signal,且signal底层是调用sigaction的。另外signal()为C标准库函数,sigaction()为Linux系统函数,signal()接口简单且跨平台,但不能设置信号屏蔽字;sigaction()则只能用于UNIX/LINUX系统,但能设置屏蔽字并允许附带信号之外的数据。
2、sigaction()的第二个参数struct sigaction结构体:
这个结构体据决定了信号屏蔽字的设置,以及是使用附带额外数据还是不附带额外数据。struct sigaction{ void (*sa_handler)(int);/*与signal()用法相似:设置为ISG_DFL为默认,SIG_IGN为忽略,设为函数名为自定义*/ void (*sa_sigaction)(int, siginfo_t *, void *);/*可以附带额外数据与sigqueue()联合使用*/ sigset_t sa_mask;//设置阻塞信号(临时信号集) int sa_flags;//指定是使用第一个函数指针,还是第二个函数指针(这两个函数指针互斥,一次只能用一个) void (*sa_restorer)(void);//废弃掉 };
flags参数:
SA_SIGINFO:当sa_flags中包含SA_SIGINFO时,则使用第二个函数指针,其它情况使用第一个函数指针(两个不可能同时用)。
SA_NODEFER,SA_NOMASK:解除了在信号函数执行期间,对自身注册信号的原始屏蔽(只屏蔽sa_mask的屏蔽设置的信号);若不设置该flags,则会在信号函数执行期间,屏蔽注册信号相同信号与sa_mask设置的信号。
SA_RESETHAND:第一次信号处理为自定义处理,之后的恢复SIG_DFL默认处理
那么我们就对与signal()类似的结构体第一个成员:sa_handler及其使用作以简单的测试:
#include<stdio.h> #include<signal.h> void deaL_sig(int sig) { printf("----in deal_sig----"); printf("sig = %d\n", sig); sleep(1);//模拟保证处理信号时间够长,以屏蔽其它同类信号 } int main(void){ struct sigaction act; act.sa_handler = deal_sig;/*设置处理方式,我们采用自定义函数处理方式*/ sigemptyset(&act.sa_mask);/*设置信号屏蔽字为均不阻塞*/ act.sa_flags = 0;//默认属性置零:使用第一个函数指针 /*当SIGINT信号注册后且收到SIGINT信号后处于处理阶段时, 就只屏蔽SIGINT(系统自动设置,即接收第一个SIGINT,屏蔽第一个处理期间的其它同类信号), 处理完成后恢复不阻塞的设置*/ sigaction(SIGINT, &act, NULL);//为SIGINT注册 while(1){ printf("----in main----\n"); sleep(1); } return 0; }
测试结果:
3、struct sigaction的sa_aigaction成员及使用:
上面说到sa_sigaction可以附带额外数据,而sigqueue()函数就是专门用来与sa_sigaction配合使用在发送信号的同时收发附加数据的。并且,这种附带数据的功能只能在有血缘关系的进程间使用。因为union sigval{}中有一个指针类型,对于无血缘关系的不同进程,其使用的虚拟内存地址不同,而传递的指针也无效。(1)、sigqueue()函数:
类似于函数kill(),可以发送信号,并且在发送信号的同时附加数据。
int sigqueue(pid_t pid,int sig, const union sigval value); union sigval{ int sival_int; void * sival_ptr;/*所有类型的指针*/ };
(2)、sa_sigaction成员与struct siginfo_t:
void (*sa_sigaction)(int, siginfo_t *, void); siginfo_t结构体(结构体成员较多,只列一部分): struct siginfo_t{ int si_signo; int si_errno; int si_code; int si_trapno; pid_t si_pid;/*发送信号的进程ID*/ uid_t si_uid;/*发送信号的进程所属组ID*/ int si_status; clock_t si_utime; clock_t si_stime; sigval_t si_value;/*与sigqueue()的value相对应*/ .... };
注意:联合体是sigqueue发送时参数,而siginfo_t结构体是信号处理函数的参数,一个是发送参数一个是接收参数。
(3)、简单测试:
#include<stdio.h> #include<stdlib.h> #include<signal.h> void deal_sig(int sig, siginfo_t *message,void * ptr){ printf("I'am %d process, I accept a signal %d and other message is %s from %d process.\n", getpid(), sig, message->si_value, message->si_pid); exit(EXIT_FAILURE); } int main(void) { pid_t pid = fork(); if(pid < 0){ perror("fork"); exit(EXIT_FAILURE); } struct sigaction act; act.sa_sigaction = deal_sig; act.sa_flags = SA_SIGINFO; sigemptyset(&act.sa_mask); if(pid == 0){ sigaction(SIGINT, &act, NULL);/*在子进程中注册信号SIHINT(2)*/ while(1){ printf("I'am child\n"); sleep(1);/*5秒内不会被信号中断*/ } }else{ union sigval message; message.sival_ptr = "hello myson!"; sleep(5);/*5秒后发送信号中断子进程死循环*/ printf("I'am father %d process, I send the message of %s for my son is %d process\n",getpid(), message.sival_ptr, pid); sigqueue(pid, SIGINT, message);//发送信号以及附加信息 } }
测试结果:
等5秒后,父进程成功发送“hello myson”中断子进程。而在5秒内Ctrl+c中断子进程,在信号处理函数中打印的附加内容为NULL,且是由0号进程发送的信号(进程0:Linux引导中创建的第一个进程,完成加载系统后,演变为进程调度、交换及存储管理进程):
说到0号进程与内核,这里我们就得说一下信号的处理过程是怎样的了,首先信号是由内核负责产生发送的,而父进程只是向内核发送请求,子进程收到的也是请求的结果。信号处理过程如图所示:
注意:信号并非能产生立刻中断,当程序在执行过程中,在其拥有的时间片内,接收到一个信号,CPU只知道收到一个信号,不去关心是谁发给谁的,所以CPU不会马上处理,而等待当前时间片到达回收CPU资源,并分给下一个抢占CPU资源的进程。所以先将信号放入内核队列等待,当时间片到,CPU发现有一个发给时间片已经结束的进程的信号时,而该进程暂时被回收资源,只能等待下一次时间片到。当下一次时间片轮转到之前保存现场的进程时,该进程的时间片先用来处理之前等待的信号,到用户空间执行自定义处理,再返回内核,当处理完信号返回内核,时间片还未完就继续到用户空间执行该进程的代码。但是由于信号等待处理的时间很短,所以基本可以认为是实时响应的。
二、进程间信号处理:
fork()创建的子进程会继承父进程的信号屏蔽字与对信号的处理动作(action)(代码区共享,不存在子进程找不到父进程自定义的信号处理函数的情况)。而vfork()+exec创建的子进程:父进程忽略,子进程也会会略,但父进程自定义,子进程会改为默认(因为exec改变代码区之后,找不到父进程中自定义的处理函数)。另外:SIGUSR1和SIGUSR2这两个信号,系统并未定义这两个信号具体用来干什么,由用户来自己处理,默认是终止进程。虽然其他大多信号也可以注册用以其他功能,但是由于系统已经为这些信号制定了特定的功能,所以我们在自己的使用过程中,凡是要对信号自定义处理的一般都用SIGUSR1和SIGUSR2实现,一般用来实现父子进程间信息同步(我们为了测试方便采用SIGINT信号)。
1、status参数与SIGCHLD信号处理:
(1)SIGCHLD信号:SIGCHLD信号是在子进程状态变化时,由系统自动发送给子进程的父进程的信号,父进程采用wait/waitpid中status参数接收,并根据该信号来进行相应操作。
SIGCHLD处理(默认忽略Ign):
SIGCHLD产生条件有以下三种:
①子进程终止时,SIGCHLD告诉父进程进行子进程收尸(回收资源)。
②子进程接收到SIGSTOP信号停止时,父进程不予理睬。
③子进程处在停止态,接受到SIGCONT后唤醒时,父进程不予理睬。
(2)、status处理方式:
之前在进程中我们有提过status参数,但是说的不是很透彻,接下来我们就这一点详细总结一下(以waitpid为例):
pid_t waitpid(pid_t pid, int * status, int options);
pid参数可参考: 进程创建与相关函数等知识点总结
options参数:
WNOHANG:没有子进程结束立刻返回
WUNTRAECD:如果子进程由于被停职产生的SIGCHLD,waitpid则立即返回
WCONTIBUED:如果子进程由于被SIGCONT唤醒而产生SIGCHLD,waitpid则立即返回
status参数:
status参数中存储了,由父进程获取子进程状态改变时系统发送的信号,以及子进程退出结束返回值(如果是因为子进程返回致使系统发送SIGCHLD)等信息。而获取其中的信息可以使用以下实现:
①WIFEXITED(status);
子进程正常exit终止返回真,而我们可以使用WEXITSTATUS(status)函数获取退出值:返回子进程正常退出值
②WIFSIGNALED(status)
子进程被信号终止返回真,WIERMSIG(status)返回终止子进程的信号值
③WIFSTOPED(status)
子进程被停止返回真,WSTOPSIG(status)返回停止子进程的信号值
④WIFCONTINUED(status)
子进程停止态转为就绪态返回真
以上宏函数的使用,对应了SIGCHLD信号的三种产生方式。
(3)简单测试:
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<signal.h> #include<sys/types.h> void sys_err(const char * ptr) { perror(ptr); exit(EXIT_FAILURE); } void deal_sig(int signo) { int status; pid_t pid; //waitpid(0,,)循环等待同组所有进程,可以保证回收所有子进程资源, while( (pid = waitpid(0, &status, WNOHANG)) > 0 ){ if(WIFEXITED(status)) printf("child %d exit and return %d\n", pid, WEXITSTATUS(status)); else if(WIFSIGNALED(status)) printf("child %d cancle signal %d\n", pid, WTERMSIG(status)); } // exit(EXIT_FAILURE); } int main(void) { pid_t pid; int i; //先阻塞SIGCHLD(不用设置,SIGCHLD默认忽略) for(i = 0; i< 5; i++){/*创建5个子进程*/ if((pid = fork()) == 0) break; else if(pid < 0) sys_err("fork"); } if(pid == 0){ printf("I'am child of %d\n", getpid()); sleep(1); return i; } else if(pid > 0){ //先注册信号,设置捕捉再解除对SIGCHLD的阻塞 struct sigaction act; act.sa_handler = deal_sig; act.sa_flags = 0; sigaction(SIGCHLD, &act, NULL); while(1){ printf("I'am parent and process id is %d\n", getpid()); sleep(2); } } return 0; }
测试一(按上面代码测试):
测试二(将上面代码信号处理时的while循环等待改为不加循环等待,并将父子进程的睡眠时间增大以便测试):
对于僵尸进程的产生,可以多创建几个子进程并以非循环等待处理,而子进程在sleep后加上一个空循环,ps -aux便会看到僵尸态。
相关文章推荐
- Linux信号产生与处理机制学习笔记(一)
- 【拔苗计划】linux学习笔记——信号处理机制及kill、du、df命令
- Linux程序设计学习笔记——异步信号处理机制
- Linux网络编程(3):信号处理与定时机制简要学习
- [Linux学习笔记]信号处理
- Linux信号机制学习笔记-----Linux信号机制的疑问?????
- Linux内核学习笔记五——中断推后处理机制
- [离散时间信号处理学习笔记] 12. 连续时间信号的离散时间处理以及离散时间信号的连续时间处理
- 【linux高级程序设计】(第十章)Linux异步信号处理机制 2
- Linux 信号signal处理机制(ZZ)
- linux 系统编程-学习笔记8--信号/线程
- Linux中断处理学习笔记
- 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】深入剖析Linux中断机制之三--Linux对异常和中断的处理
- JavaScript学习笔记之事件处理机制
- (转)Linux网络编程(3):信号处理与定时机制简要学习
- linux 进程学习笔记-信号semaphore
- linux学习笔记----目录处理命令----mkdir
- Linux学习笔记---目录处理命令---删除文件或目录
- linux信号signal处理机制(一)
- UNIX环境编程学习笔记(23)——信号处理初步学习