【Linux】信号

什么是信号?

  信号在我们生活中是非常常见的,比如路口的红绿灯等等。信号给我们传递了一种信息,让我们做出相应的处理动作。那么在Linux中的,信号也是类似的。比如:Ctrl+C可以给前台进程发送信号,终止进程。
  
  可以使用kill -l命令查看Linux中的信号列表。
这里写图片描述
1. 可以看到每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到。
2. 这里没有32,33号信号,所以共有62个信号;其中1-31是普通信号,34-62是实时信号。

  对于信号的理解,我们可以同现实生活中类比,这样就会比较容易理解。举一个栗子:当老师给我们布置的作业,首先我们需要将作业记录下来,接下来我们需要在空余的时候完成作业,最后在交作业。那么在Linux也是类似的,首先需要产生一个信号,然后在一个适当的时间,进程去处理信号,作出相应的处理。

产生信号的方式

1.用户在终端下按下某些键时,终端驱动程序会发送信号给前台进程。譬如:Ctrl+C产生SIGINT信号,Ctrl+\产生SIGQUIT信号,Ctrl+Z产生SIGSTP信号。
2.硬件异常信号,当硬件检测到异常时通知内核,由内核向当前进程发送适当的信号。比如:当进程出现除以0时,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。
3.调用系统函数想进城发送信号。
(1)一个进程调用kill函数可以发送信号给另一个进程,也可以用kill命令发送信号,kill命令也是调用kill函数实现的
(2)raise函数可以给当前进程发送信号,自己给自己发信号。

#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);
//都是成功返回0,错误返回-1

(3) absort使当前进程接受到信号而异常终止

#include <stdlib.h>
void absort(void);
//没有返回值,总会调用成功。

4.软件条件产生信号
(1)SIGPIPE是一种由软件产生的信号,
在匿名管道中,如果读端的文件描述符关闭了,那么写端就会发送信号SIFPIPE进而导致write进程退出
(2)alarm函数可以设定一个闹钟,在seconds秒之后给当前进程发送一个SIGALARM信号,该信号的默认处理时终止当前进程。

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
//返回值是0或者以前设定的闹钟还剩余下的秒数。如果seconds值为0,表示取消以前设定的闹钟,
//函数的返回值仍是以前设定闹钟的余下秒数。
信号处理的常见方式

  1. 忽略此信号
  2. 执行该信号的默认处理动作(一般都是自动退出)
  3. 注册一个信号处理函数,要求内核在处理该信号时切换到用户态执行。
阻塞信号

1.先了解几个概念

  1. 实际执行信号的处理动作称之为信号的递答(Delivery)
  2. 信号从产生到递达之间的状态,称之为信号未决(Pending)
  3. 进程可以选择阻塞(Block)某个信号,被阻塞的信号产生时将保持在未决状态
  4. 直至进程解决对此信号的阻塞,才执行递达动作。

注意:阻塞与忽略是不同的,被阻塞的信号根本不会递达,而忽略是在递达之后的一种处理动作。
2.信号在内核中的表示
这里写图片描述
  从上图中可以看到,对于未决标志和阻塞标志,每个信号只有一个bit的0或1来表示,因此,用相同的数据类型sigset_t来存储,sigset称为信号集。我们可以发现:其实向进程发送信号的本质是修改进程PCB中相关位置的一个比特位。
  阻塞信号也叫做当前进程的信号屏蔽字。

3.信号集操作函数
sigset_t类型对于每种信号用一个bit表示有效或者无效状态,至于这个类型内部如何存储这些bit则依赖于系统实现,我们只关注调用以下函数来操作sigset_t变量。

#include <signal.h>
int sigemptyset(sigset_t *set);
//初始化set所指向的信号集,使所有的信号对应的bit清零,表示该信号集不包含任何有效信号
int sigfillset(sigset_t *set);
//初始化set所指向的信号集,使其中所有信号对应对的bit置一
int sigaddset (sigset_t *set, int signo);
//在该信号集中添加某种有效信号
int sigdelset(sigset_t *set, int signo);
//在该信号集中删除某种有效信号
//以上四个函数都是成功返回0,出错返回-1
int sigismember(const sigset_t *set, int signo);
//是一个布尔函数,判断一个信号集的有效信号中是不是包含某种信号,包含返回1, 不包含返回0,出错返回-1

sigprocmask
读取或更改进程的信号屏蔽字

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);

参数:

  • how:指定信号修改的方式,有三种
    • SIG_BLOCK 添加到当前信号屏蔽字的信号
    • SIG_UNBLOCK 从当前信号屏蔽字中解除阻塞的信号
    • SIG_SETMASK 设置当前信号屏蔽字为set所指向的值
  • set:指向新设的信号集的指针,如果set非空,则更改进程的信号屏蔽字
  • oset:指向原来信号集的指针,如果oset非空,则读取进程的信号屏蔽字,通过oset参数传出

返回值:若成功则为0,若出错则为-1

sigpending
读取当前进程的未决信号集,通过set参数传出。

#include <signal.h>
int sigpending(sigset_t *set);

调用成功返回0,出错返回-1

信号的捕捉

这里写图片描述
1.内核如何实现信号的捕捉?
  如果信号的处理动作是用户自定义的函数,在信号递达时就嗲用这个函数,这称为捕捉信号。举一个例子:用户程序注册了SIGQUIT信号的处理函数sighandler,当前在执行main函数,这时发生终端或异常切换到内核态,在处理终端完后要返回用户态main之前,检测到sigquit递达,内核决定返回用户态执行sighandler函数(sighandler和main函数使用不同的堆栈空间,时两个独立的控制流),sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入你日和态,如果没有新的信号递达,再次返回用户态,恢复main函数的上下文继续执行。
2.signal

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
//修改或指定信号的处理动作,第一个参数为信号编号,第二个参数为信号处理函数

3.sigaction
读取和修改与指定信号相关联的处理动作,与signal函数相似

#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);

参数:

  • signo:指定信号的编号
  • act:若指针非空,则根据act修改该信号的处理动作
  • oact:若指针非空,则通过oact传出该信号原来的处理动作
  • act和oact指向sigaction结构体
    这里写图片描述
    调用成功返回0, 出错返回-1

4.pause
pause函数使调用进程挂起直到有信号递达

#include <unistd.h>
int pause(void);
  • 如果信号的处理动作是终止进程,则进程终止,函数没有机会返回
  • 如果信号的处理动作是忽略,则进程继续处于挂起状态,函数不反悔
  • 如果信号的处理动作是捕捉,则调用信号处理函数之后pause返回-1,error设置为EINTR,这个函数只有出错返回值。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值