[Linux:]信号

送分小仙女□ 提交于 2020-02-17 05:15:31

1.如何理解信号

例如在生活中,快递员告诉我们快递到了,那么我们会终止手上的事,去取快递,这就是一种信号。
站在操作系统的角度,再来理解信号:
1.用户输入命令,在Shell下启动一个前台进程。
2. 用户按下 Ctrl-C ,这个键盘输入产生一个硬件中断,被OS获取,解释成信号,发送给目标前台进程
3. 前台进程因为收到信号,进而退出。
所以,显而易见,信号是操作系统与用户通信的一种方式,也可以是进程间通信的方式。

2. Linux下信号机制

  • 信号是进程间时间异步通知的一种方式,属于软中断。
    在Linux下我们可以使用kill -l命令查看系统定义的信号列表。

2.1 信号常见的处理方式

  • 忽略此信号。
  • 执行该信号的默认处理动作。
  • 提供一个信号处理函数,要求内核在处理信号时切换到用户态执行这个处理函数,这种处理方式称之为捕捉一个信号。

2.2 产生信号的方式

  • 通终端按键产生信号(如常用的Ctrl+z)
  • 调用系统函数向进程发信号
  • 由软件条件产生信号
  • 硬件异常产生信号

3. Linux下信号阻塞

先来了解一些信号相关常见的概念:
信号递达(Delivery):实际执行信号的处理动作
未决信号(Pending):信号从产生到传递之间的状态。
阻塞信号(Blocking):被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
在内核中,每个信号都有两个标志位表示阻塞和未决,还有一个函数表示处理动作,信号产生时,内核在PCB中设置该信号的未决标志,知道信号递达才清除该标志。

3.1sigprocmask

调用sigprocmask函数可以读取或者更改进程信号屏蔽字(阻塞信号集)

#include <signal.h> 
int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 
返回值:若成功则为0,若出错则为-1 

来看下面的demo来体会使用sigprocmask阻塞信号

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


void sigcb(int signum)
{
    printf("recv a signal:%d\n",signum);
}
int main()
{
    signal(SIGINT,sigcb);
    signal(40,sigcb);
    sigset_t set,old;
    sigemptyset(&set);   //清空信号集合
    sigemptyset(&old);
    sigfillset(&set);    //将所有信号都添加到set集合
    sigprocmask(SIG_BLOCK,&set,&old);  //阻塞所有信号
    printf("enter to continue\n");
    getchar();
    sigprocmask(SIG_UNBLOCK,&set,NULL); //解除阻塞
    return 0;
}

可以看到执行程序后,按下CTRL+c ,并不能退出程序,因为SIHGINT信号被阻塞。

4.捕捉信号

4.1 内核如何实现信号的捕捉

以SIGQUIT信号为例

  1. 用户注册了SIGQUIT信号的处理函数sighandler,当前正在执行 main函数,这时发生中断或异常切换到内核态。
  2. 在中断处理完毕后要返回用户态的main函数之前检查到有信号 SIGQUIT递达。
  3. 内核执行sighandler函数,其与main函数是两个独立的控制流程。
  4. sighandler函数返 回后自动执行特殊的系统调用sigreturn再次进入内核态。
  5. 如果没有新的信号要递达,这次再返回用户态就是恢复 main函数的上下文继续执行。

4.2 sigaction函数的演示

#include <signal.h> 
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact); 
// signo --- 指定信号的编号
   act   --- 自定义动作
   oact  --- 保存信号原有处理动作

再来看看sigaction结构体:

struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_fla 5gs;
               void     (*sa_restorer)(void);
           };

sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1。

以下demo演示sigaction函数的使用

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
void sigcb(int signum)
{
  printf("recv signum : %d\n",signum);
}

int main()
{
    struct sigaction newact;
    struct sigaction oldact;
    newact.sa_handler = sigcb;
    newact.sa_flags = 0;
    sigemptyset(&newact.sa_mask);
    sigaction(SIGINT,&newact,&oldact);
    //sigaction修改信号的处理动作作为newact,原来的动作使用oldact保存
    while(1){
        printf("hello\n");
        sleep(1);
    }
    return 0;
}

5.SIGCHLD信号

我们清理僵尸进程的方式有wait和waitpid函数,调用wait时,父进程会阻塞的等待子进程结束,后者调用时,是轮询查询是否有子进程退出。
但其实子进程退出时会向父进程发送SIGCHLD信号,该信号的默认处理方式是忽略,父进程可以定义SIGCHLD信号的处理函数,父进程在处理函数中调用wait函数即可。
下面的demo我们可以体会下SIGCHLD信号的处理及作用:

#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<stdlib.h>
#include<sys/wait.h>
void sigcb(int signo)
{
    while(waitpid(-1,NULL,0) > 0);
}
int main()
{
    signal(SIGCHLD,sigcb);
    pid_t pid = fork();
    if(pid == 0){
        sleep(5);
        exit(0);
    }
    while(1){
        printf("-------------\n");
        sleep(1);
    }
    return 0;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!