Linux——(七)进程信号

avatar
作者
筋斗云
阅读量:3

目录

注意!!!信号不是信号量

本节重点

信号:概念,信号的生命周期,函数的重入与不可重入,关键字volatile

  • 掌握Linux信号的基本概念
  • 掌握信号产生的一般方式
  • 理解信号递达和阻塞的概念,原理。
  • 掌握信号捕捉的一般方式。
  • 重新了解可重入函数的概念。

1 信号概念

1.1 概念:

是一种事件通知机制,通知进程发生了某个事件,打断进程当前的操作然后去处理这个事件。

1.2 信号种类:

kill -l 查看linux中的信号种类—62种(中间没有32/33)
在这里插入图片描述
非可靠信号/非实时信号: 1~31;(是从unix系统借鉴来的)
可靠信号/实时信号: 34~64;(linux新增加的)

2 信号的生命周期

信号的产生, 信号的注册,信号的注销,信号的处理+ 阻塞

2.1 信号的产生

2.1.1 软件产生:

(1)kill -signum pid命令(kill杀死一个进程的原理就是给进程发送一个能够让进程退出的信号。)kill默认是15号信号。
(2)int kill(pid_ t pid, int sig);给指定进程发送指定信号
(3)int raise(int sig);给自己发送指定的信号.
(4)void abort(void);给自己发送SIGABRT信号
(5)unsigned int alarm(unsigned int s);s秒后发送SIGALRM给自己

2.1.2硬件:

通过命令:vi /usr/include/bits/signum.h 打开signum.h文件查看信号

在这里插入图片描述
在这里插入图片描述
这里插一条查找命令:当我们知道文件名字以及大致在哪个目录下时候输入命令:grep -R ‘SIGKILL’ /usr/include 回车查找。即可查找到正确目录~
在这里插入图片描述

(1)ctrl+c:中断当前操作。本质是给当前进程发送一个2号中断信号Interrupt(ANSI)
在这里插入图片描述
(2)ctrl+\ :本质给进程发送3号退出信号Quit(POSIX)
在这里插入图片描述
(3)ctrl+z:发送的是20号信号 Keyboard stop (POSIX)
在这里插入图片描述

2.2 信号的注册

让进程知道自己收到了某个信号/事件需要去处理
在进程中做标记;
在pcb中有个未决信号集合(位图) ,用于标记进程信号。

  • 非可靠信号的注册:
    1~31号信号如果没有注册过则注册,已经注册过则什么都不做(信号丢弃)。(不会同时注册第二次)
  • 可靠信号的注册:
    34~64号信号,不管是否E经注册过,位图置1,向sigqueue链表中添加 一个节点。

2.3 信号的注销

信号的注销:删除待处理的未决信号(pending位图中)的信息。
一个是位图,还有一个是链表
位图:主要是为了标记是否收到了某个信号
链表:统计收到多少个相同信号

  • 非可靠信号注销:
    因为非可靠信号不会重复注册,链表中最多只有一个节点,因此删除一个节点, 并且对应位图置0
  • 可靠信号的注销:
    因为可靠信号有可能存在多个相同信号节点,因此删除一个节点后,若还有相同节点则位图不变,否则位图置0

2.4 信号的处理

信号的处理就是完成一个事件的处理,一个事件的处理其实就是一个功能的完成,一个功能的最小模块就是一个函数,信号处理说白了就是**运行信号的处理函数(**每个信号处理函数是不一样的)。信号的处理函数(处理方式)是可以修改的。

  • 信号的处理方式种类:
    (1)默认处理方式:系统中针对每个信号定义好的处理方式
    (2)忽略处理方式:收到了信号处理方式就是忽略
    (3)自定义处理方式:用户自己定义信号处理函数然后进行替换
  • 替换函数
    sighandler_t signal(int signum, sighandler_t handler);
    signum:要修改处理方式的信号的值
    handler:函数指针(SIG_ DFL-默认/ SIG_ IGN-忽略)
    typedef void (*sighandler_ t)(int);
    在这里插入图片描述
    1.程序运行因为中断/异常/系统调用从用户态切换到内核态
    2.完成内核处理功能后,在返回用户态主控流程之前检测是
    否有信号待处理,有则处理信号
    3.默认和忽略在内核中完成,自定义需要到用户态运行
    4.信号的自定义处理函数运行完毕,返回内核态

一个用户进程无法直接访问内核空间,只能通过系统调用进行访问,当程序通过系统调用访问内核空间的过程称之为运行在内核态。
程序运行从用户态切换到内核态:系统调用接口,异常,中断

  • 笔试题:以下那种方式可以切换到内核态运行?
    memcpy(库函数、不会), fwrite(库函数、不会), read(系统调用,会切换), 10/0(除零异常,会切换)。如果当前运行的是用户自己写的代码或者库函数就运行在用户态,如果是系统调用,或者中断/异常的处理就是内核态。

2.5 信号的阻塞

2.5.1 定义

  • 信号的阻塞:阻止当前未决信号的处理.
    有信号注册了但是当前不处理,等到解除阻塞之后再处理。——在pcb中,标记有哪些信号到来了但是暂时不处理。在pcb中也有一个block阻塞信号集合。 将哪个信号添加到block集合中,就表示哪个信号就算到来了也暂时不处理。

2.5.2 接口函数

int sigprocmask(int how,sigset_t * new,sigset_t * old);

  • 功能:对调用进程的阻塞信号集合进行操作
  • how:
    SIG BLOCK —— block |= new :向block集合添加new信号
    SIG UNBLOCK——block &= ~ new:从block中移除new中信号
    SIG SETMASK—— block = new:将new设置为block
  • new:要添加/移除阻塞的信号集合;
  • old:用于接收修改前block中的信号(多数用于还原)

2.5.3 示例

思路:
1.先修改指定信号的处理方式 signal(sig, handler)
2.阻塞指定的/所有信号 sigprocmask(SIG_ BLOCK)
3.等待用户按下回车 getchar()
4.解除阻塞 sigprocmask(SIG_ UNBLOCK)
sigset_ t new;
会用到的辅助接口:
sigemptyset(sigset_ t *set); 清空
sigaddset(sigset_ t *set, int sig); 添加指定信号到集合
sigillset(sigset_ t *set);添加所有信号到集合中
sigdelset(sigset_ t *set, int sig);从集合中移除指定信号
sigismember(sigset
t *set, int sig)判断信号是否在集合中

代码:

vi mask.c

 1 #include <stdio.h>   2 #include <unistd.h>   3 #include <stdlib.h>   4 #include <signal.h>                                                                                                             5    6 void sigcb(int signo)   7 {   8     printf("recv signo:%d\n", signo);   9 }  10 int main()  11 {  12     signal(SIGINT, sigcb);//修改信号处理方式  13     signal(40, sigcb);  14   15     sigset_t set;//定义信号集合  16     sigemptyset(&set);//将集合清空  17     //sigaddset(&set, SIGINT);//将SIGINT信号添加到集合中  18     //sigaddset(&set, 40);  19     sigfillset(&set);//将所有信号添加到set集合中  20     //将set集合中的信号添加到block集合中进行阻塞  21     sigprocmask(SIG_BLOCK, &set, NULL);  22   23     printf("输入回车继续运行...\n");  24     getchar();//等待一个回车,没有回车就一直卡在这里  25   26     sigprocmask(SIG_UNBLOCK, &set, NULL);//解除阻塞,观察信号处理  27   28     return 0;  29 }  

makefile 内容

在这里插入代码片 

make一下

在进程中,有两个信号是不可被阻塞,不可被修改处理方式的: SIGKILL SIGSTOP
一个进程kill杀不死原因是什么?1.僵尸进程; 2.信号被修改处理方式;3.信号被阻塞

  • 应用:
    1.僵尸进程的处理- -进程等待
    子进程退出之后,父进程为什么没有关心到子进程退出状态
    本质:子进程退出之后实际上给父进程发送了一个信号-SIGCHLD
    然而SIGCHLD信号的默认处理方式就是忽略
    相当于父进程实际收到了通知但是什么都没做
    sigcb(){ while(waitpid(-1, NULL, WNOHANG)> 0); }
    signal(SIGCHLD, SIG_ IGN);//用户的显式忽略
    2.所有管道读端被关闭则write触发异常–SIGPIPE

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!