Linux信号产生与处理机制学习笔记(二)
来源:互联网 发布:日剧 商务 知乎 编辑:程序博客网 时间:2024/05/16 06:36
接着Linux信号产生与处理机制学习笔记(一) 来说。
一、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信号产生与处理机制学习笔记(一)
- linux信号signal处理机制(二)
- Linux信号机制与信号处理
- Linux信号机制与信号处理
- Linux信号机制与信号处理
- Linux程序设计学习笔记——异步信号处理机制
- 信号处理学习笔记(二)Decimation信号抽取
- linux系统编程之信号(二):信号处理流程(产生、注册、注销、执行)
- linux系统编程之信号(二):信号处理流程(产生、注册、注销、执行)
- linux系统编程之信号(二):信号处理流程(产生、注册、注销、执行)
- Linux信号机制学习笔记-----Linux信号机制的疑问?????
- Linux 信号机制 (二)
- (转)Linux网络编程(3):信号处理与定时机制简要学习
- Linux网络编程(3):信号处理与定时机制简要学习
- Linux信号处理机制
- linux信号处理机制
- Linux 信号处理机制
- 51Nod 1596 搬货物
- js变量
- HDU 3449 Consumer 详细题解(依赖背包)
- CDOJ 1324 卿学姐与公主 分块法
- 第十一次笔记
- Linux信号产生与处理机制学习笔记(二)
- XML的约两种约束——DTD、Schema
- LeetCoder_____Median of Two Sorted Arrays
- python ast 语法分析
- 《JAVA与模式》之调停者模式
- PAT 1018. Public Bike Management
- 安卓开发颜色增加透明度
- css基础3新知识点
- Struts2(随笔) 17-2-20