Linux-进程信号(下)

信号捕捉

信号捕捉分析图如下
这里写图片描述

分析过程:

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程⽐较复杂,举例如下: 用户程序注册了SIGQUIT信号的处理函数sighandler。 当
前正在执行main函数,这时发生中断或异常切换到内核态。 在中断处理完毕后要返回⽤户态的main函数之前检查到有信号SIGQUIT递达。 内核决定返回用户态后不是恢复main函数的上下而继续执行,⽽是执行sighandler函 数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调⽤和被调⽤的关系,是 两个独⽴的控制流程。 sighandler函数返回后⾃动执行特殊的系统调⽤sigreturn再次进进内核态。 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。

sigaction函数:读取修改指定信号相关处理动作
#include <signal.h>

int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);

sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1。
signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针⾮非空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体:将sahandler赋值为常数SIGIGN传给sigaction表⽰忽略信号,赋值为常数SIG_DFL表⽰执⾏系统默认动作,赋值为一个函数指针表示自定义函数捕捉信号,或者说向内核注册了1个信号处理函 数,
该函数返回值为void,可以带1个int参数,通过参数可以得知当前信号的编号,这样就可以以同1个函数处理多种信号。显然,这也是1个回调函数,不是被main函数调⽤,而是被系统所调用

pause:函数:挂起进程直到有信号递达
#include <unistd.h>

int pause(void);

pause函数使调⽤进程挂起直到有信号递达。如果信号的处理动作是终⽌进程,则进程终 止,pause函数没有机会返回;如果信号的处理动作是忽略,则进程继续处于挂起状态,pause不返回;如果信号的处理动作是捕捉,则调
⽤了信号处理函数之后pause返回-1,errno设置为EINTR, 所以pause只有出错的返回值

下面我们用alarm和pause实现sleep(3)函数,称为mysleep。
1. main函数调⽤mysleep函数,后者调⽤sigaction注册了SIGALRM信号的处理函数sig_alrm。
2. 调用alarm(nsecs)设定闹钟。
3. 调用pause等待,内核切换到别的进程运⾏。
4. nsecs秒之后,闹钟超时,内核发SIGALRM给这个进程。
5. 从内核态返回这个进程的⽤户态之前处理未决信号,发现有SIGALRM信号,其处理函数 是
sig_alrm。
6. 切换到用户态执行sig_ alrm函数,进⼊sig_ alrm函数时SIGALRM信号被⾃动屏蔽, 从sig_alrm函数返回时SIGALRM信号⾃动解除屏蔽。然后⾃动执⾏系统调⽤sigreturn再次进⼊入内核,再返回用户态继续执行进程的主控制流程(main函数调用的mysleep函数)

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void wakeup(int signo)
{
      //do nothing
}
unsigned int mysleep(unsigned int t)
{
      struct sigaction new,old;
      unsigned int unsleep=0;
      new.sa_handler=wakeup;
      new.sa_flags=0;
      sigemptyset(&new.sa_mask);
      //注册一个信号处理函数
      sigaction(SIGALRM,&new,&old);
      //设置一个闹钟
      alarm(t);
      //将进程挂起,等待闹钟函数执行完毕
      pause();
      //清空闹钟
      unsleep=alarm(0);
      //回复默认信号处理动作
      sigaction(SIGALRM,&old,NULL);
      return unsleep;

}
int main(  )
{
    while( 1)
    {
        mysleep(5);
        printf(  "5s passed...\n");

    }
    return 0;
}

这里写图片描述

这样我们就实现了一个mysleep函数,但由于时序问题,这个函数存在bug.

改进版

unsigned int mysleep(unsigned int t)
{
      struct sigaction new,old;
      sigset_t newmask,oldmask,suspmask;
      unsigned int unsleep=0;
      new.sa_handler=wakeup;
      new.sa_flags=0;
      sigemptyset(&new.sa_mask);
      //注册一个信号处理函数
      sigaction(SIGALRM,&new,&old);
      sigemptyset(&newmask);
      //添加一个信号到newmask阻塞集
      sigaddset(&newmask,SIGALRM);
      //阻塞SIGALRM信号
      sigprocmask(SIG_BLOCK,&newmask,&oldmask);
      //设置一个闹钟
      alarm(t);

      suspmask=oldmask;
      sigdelset(&suspmask,SIGALRM);
      //解除信号屏蔽并挂起进程,等待闹钟响起
      sigsuspend(&suspmask);

      //清空闹钟
      unsleep=alarm(0);
      //回复默认信号处理动作
      sigaction(SIGALRM,&old,NULL);
      sigprocmask(SIG_SETMASK,&oldmask,NULL);
      return unsleep;

}
可重入函数

这里写图片描述
main函数调⽤insert函数向⼀个链表head中插⼊节点node1,插⼊操作分为两步,刚做完第⼀步的 时
候,因为硬件中断使进程切换到内核,再次回⽤户态之前检查到有信号待处理,于是切换 到sighandler
函数,sighandler也调⽤insert函数向同⼀个链表head中插⼊节点node2,插⼊操作的 两步都做完之后
从sighandler返回内核态,再次回到⽤户态就从main函数调⽤的insert函数中继续 往下执⾏,先前做第
⼀步之后被打断,现在继续做完第⼆步。结果是,main函数和sighandler先后 向链表中插⼊两个节点,
⽽最后只有⼀个节点真正插⼊链表中了。
像上例这样,insert函数被不同的控制流程调⽤,有可能在第⼀次调⽤还没返回时就再次进⼊该函数,
这称为重⼊,insert函数访问⼀个全局链表,有可能因为重⼊⽽造成错乱,像这样的函数称为 不可重⼊
函数,反之,如果⼀个函数只访问⾃⼰的局部变量或参数,则称为可重⼊(Reentrant) 函数。想⼀下,为
什么两个不同的控制流程调⽤同⼀个函数,访问它的同⼀个局部变量或参数就不会造成错乱?
如果⼀个函数符合以下条件之⼀则是不可重⼊的:
调⽤了malloc或free,因为malloc也是⽤全局链表来管理堆的。
调⽤了标准I/O库函数。标准I/O库的很多实现都以不可重⼊的⽅式使⽤全局数据结构。

SIGCHLD信号

⽤wait和waitpid函数清理僵⼫进程,⽗进程可以阻塞等待⼦进程结束,也可以⾮阻 塞地查询是否
有⼦进程结束等待清理(也就是轮询的⽅式)。采⽤第⼀种⽅式,⽗进程阻塞了就不 能处理⾃⼰的⼯作了;采⽤第
⼆种⽅式,⽗进程在处理⾃⼰的⼯作的同时还要记得时不时地轮询⼀ 下,程序实现复杂。
其实,⼦进程在终⽌时会给⽗进程发SIGCHLD信号,该信号的默认处理动作是忽略,⽗进程可以⾃ 定义
SIGCHLD信号的处理函数,这样⽗进程只需专⼼处理⾃⼰的⼯作,不必关⼼⼦进程了,⼦进程 终⽌时会通知⽗进
程,⽗进程在信号处理函数中调⽤wait清理⼦进程即可。
请编写⼀个程序完成以下功能:⽗进程fork出⼦进程,⼦进程调⽤exit(2)终⽌,⽗进程⾃定 义SIGCHLD信号的处
理函数,在其中调⽤wait获得⼦进程的退出状态并打印。
事实上,由于UNIX 的历史原因,要想不产⽣僵⼫进程还有另外⼀种办法:⽗进程调 ⽤sigaction将SIGCHLD的处
理动作置为SIG_IGN,这样fork出来的⼦进程在终⽌时会⾃动清理掉,不 会产⽣僵⼫进程,也不会通知⽗进程。
系统默认的忽略动作和⽤户⽤sigaction函数⾃定义的忽略 通常是没有区别的,但这是⼀个特例

#include<stdio.h>
#include <signal.h>
#include <stdlib.h>
void handler(int sig)
{
      pid_t id;
      while(id=waitpid(-1,NULL,WNOHANG)>0)
      {

          printf(  "wait child success:%d\n",id);

      }
      printf("  child is quit! %d\n",getpid( ));
}
int main(  )
{
      signal(SIGCHLD,handler);
      pid_t cpid;
      if(  cpid=fork()==0)
      {
            printf("child:%d\n",getpid());
            sleep(10);
            exit(1);

      }
      while(1)
      {
            printf(  " father proc is doing some thing!\n");
            sleep(1);

      }
      return 0;
}

这里写图片描述

由于时间关系,先写到这里,后续会补充

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值