您的位置:首页 > 运维架构 > Linux

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

2017-02-20 23:26 621 查看
接着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