进程信号——捕捉信号和可重入函数及SIGCHLD

捕捉信号

一、内核实现信号的捕捉

    操作系统会在适当的时候给进程发送信号,但是什么时候是最适当的呢?在上图中,在内核态到用户态的时候会对信号进行处理,同时进行检测,如果这时候发现异常,操作系统就会发送信号。

sighandler和main函数是使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。

二、sigaction

int sigaction(int signo,const struct sigaction* act,struct sigaction* oact);
struct sigaction {  
               void     (*sa_handler)(int);//信号的处理动作  
               void     (*sa_sigaction)(int, siginfo_t *, void *);  
               sigset_t   sa_mask;//当正在执行信号处理动作时,希望屏蔽的信号。当处理结束后,自动解除屏蔽  
               int        sa_flags;//一般为0  
               void     (*sa_restorer)(void);  
           };  

sigaction函数可以读取和修改与指定信号相关联的处理动作。成功为0,失败为-1.

signo是指定信号的编号。

若act指针非空,则根据act修改该信号的处理动作。

若oact指针非空,则通过oact传出该信号原来的处理动作。

3.pause

#include<unistd.h>
int pause(void);

pause函数使调用进程挂起知道有信号递达。

若信号的处理动作是终止进程,则pause函数是没有机会返回的。

若信号的处理动作是忽略,则进程继续处于挂起状态,pause不返回。

若信号的处理动作是捕捉,则调用了信号处理函数之后,pause返回-1。

以下用sigaction pause sleep实现一个mysleep程序

#include<stdio.h>
#include<unistd.h>
#include<signal.h>


void sig_alrm(int signo)
{
    //DO NOTHING
}

unsigned int mysleep(unsigned int nsecs)
{
struct sigaction new,old;
unsigned int unslept = 0; new.sa_handler = sig_alrm;
sigemptyset(&new.sa_mask);//在执行闹钟信号的同时, 不屏蔽其他信号
new.sa_flags = 0; sigaction(SIGALRM,&new,&old);//捕捉闹钟信号 alarm(nsecs); //nsec秒后向进程发送这个信号 pause(); unslept = alarm(0); sigaction(SIGALRM,&old,NULL); return unslept; } int main() { while(1) { mysleep(5); printf("5 seconds passed\n"); } return 0; }
[root@du mysleep]# ./mysleep 
5 seconds passed
5 seconds passed
^C
 
 
2号信号为自定义。
void handler(int signo)
{
    printf("this signo is: %d\n",signo);
}
int main()
{
    signal(2,handler);
    int ret =  mysleep(5);
    printf("ret = %d\n",ret);
    return 0;
}
[root@du mysleep]# ./mysleep 
^Cthis signo is: 2
ret = 4

可重入函数

若一个函数被不同的控制流程调用,用可能在第一次调用还没有返回的时候就再次进入了该函数,这称为重入,

如果该函数访问一个全局链表,有可能因为重入而造成错乱,这样称为不可重入。

但如果一个函数只访问自己的局部变量或参数,称为可重函数。

注:如果一个函数符合以下条件之一,则不可重入:

1.调用了malloc和free,因为malloc也是用全局链表来管理堆的。

2.调用了标准I/O库函数,I/O库中好多的实现都是以不可重入的方式使用全局数据结构。

volatite:保证内存的可见性,始终从内存中查看。

SIGCHLD

字进程在终止时,会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD的处理函数,这样父进程只需处理自己的工作,子进程终止时会通知父进程,父进程在信号处理函数中调用wait清理进程即可。

其实要想不产生僵尸进程还有方法:父进程调用sigaction将SIGCHLD的处理方式设置为SIG_ING这样fork出来的子进程在终止时将会自动清理。但此方法只适合用于linux。

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
void handler(int sig)
{
    pid_t id;
    while((id = waitpid(-1,NULL,WNOHANG))>0)
    {
        printf("wait child success: %d\n",getpid());
    }
}


int main()
{
    signal(SIGCHLD,handler);
    pid_t cid;
    if((cid = fork()) == 0)
    {
        printf("child : %d\n",getpid());
        sleep(3);
        exit(1);
    }
    while(1)
    {
        printf("father proc is doing some thing!\n");
        sleep(1);
    }
    
    return 0;
}


################
  2676   9054   9054   2676 pts/1      9054 S+       0   0:00 ./sigchld
  9054   9055   9054   2676 pts/1      9054 S+       0   0:00 ./sigchld
################
  2676   9054   9054   2676 pts/1      9054 S+       0   0:00 ./sigchld
  9054   9055   9054   2676 pts/1      9054 S+       0   0:00 ./sigchld
################
  2676   9054   9054   2676 pts/1      9054 S+       0   0:00 ./sigchld
  9054   9055   9054   2676 pts/1      9054 S+       0   0:00 ./sigchld
################
  2676   9054   9054   2676 pts/1      9054 S+       0   0:00 ./sigchld
################
  2676   9054   9054   2676 pts/1      9054 S+       0   0:00 ./sigchld


猜你喜欢

转载自blog.csdn.net/duckyloser/article/details/80018167