[Linux] 信号


认识

什么是信号?
信号:软件(进程)中断,通知事件的发生。

信号有不同的种类,每个信号对应不同事件。

  • 信号的生命周期:从产生,到处理

  • Linux下信号的种类:(62种)
    1~31号继承于Unix系统,每个信号都有各自对应的事件。(非可靠信号 / 非实时信号)。
    34~64后续添加的信号,没有特定事件。(可靠信号 / 实时信号)。

非可靠 / 可靠:信号有可能会丢失 / 有多少信号就要处理多少次
实时 / 非实时:信号立刻 / 选择合适时机处理

在这里插入图片描述

指令kill -l:查看所有信号信息


生命周期

  1. 产生
  2. 注册(到进程中)
  3. 注销
  4. 处理
  5. (阻塞)暂时不处理信号。

信号的产生

硬件产生

组合键(通过硬件产生软件信号)

  • Ctrl+c(2号SIGINT中断信号)
  • Ctrl+|(3号SIGQUIT退出信号)
  • Ctrl+z(19号SIGTSTOP停止信号)

软件产生

kill -<signum> [pid]命令

  • 主动:函数
  1. kill()(man 2)
int kill(pid_t pid,int sig);

给指定进程发送指定信号,使用宏定义数字,如果要使用英文缩写要包含头文件<signal.h>
宏定义数字查看:文件处于/usr/include/bits/signum.h
在这里插入图片描述

kill实际上是15号信号SIGTERM
kill -9实际上是9号信号SIGKILL

  1. raise()
int raise(int sig);

发送给调用进程/线程(自己)发送指定信号。(使用函数需要包含头文件<signal.h>

  1. abort()
void abort(void);

相当于调用了raise接口,给调用进程发送SIGABRT信号退出(使用函数需要包含头文件<stdlib.h>

  1. alarm()
unsigned int alarm(unsigned int seconds);

定时器,seconds秒之后给调用进程发送SIGALRM信号,返回上一个定时器剩余的时间或0(如果没有定时器)。
alarm(0):参数设0表示取消定时器,并且返回剩余时间。

  • 被动:程序异常

core dump:程序异常退出时保存程序运行信息,用于事后调试,默认关闭(因为占用磁盘资源安全性考虑(例如对段错误的数据进行分析获得用户密码))。

ulimit -a:查看core dump是否开启。
ulimit -c + [num]设置核心转储文件大小,开启核心转储(num设置为unlimited表示不限制大小)
核心转储文件core.之后的数字意为pid

gdb加载程序进行调试流程:

  1. gdb + [.c程序名]
  2. core-file [corepid]
  3. bt查看调用栈
    执行结束即可查看到在那个接口发生的错误。

kill()只负责发送信号,进程在处理这个信号时,kill还没有返回,函数调用栈就到此为止了。错误并不是函数产生的,而是在函数期间收到异常信号而退出的。


信号的注册

这里讲的注册是信号在进程中的注册。

  • sigset_t结构体:
# define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
typedef struct {                                                          
	unsigned long int __val[_SIGSET_NWORDS];                   
}__sigset_t;  

其中是一个数组,对于一个进程来说,判断是否收到了某个信号,就代表进程有没有在数组中做标记。
实际上是在进程的PCB中注册标记,需要待处理。这些标记在这个结构体中记录。通过位图实现。

信号的种类有62类,按数字来标记的话是0~64,在信号的集合__sigset_t中添加一个信号,实际上是将这个信号的编号所定义的位进行置1操作。
这个数组的长度为1024 * (8 * sizeof(unsigned long int)),再加上这个数据的类型unsigned long int,应该是8*1024/8*8位,最终计算下来有1024个比特位

  1. 非可靠信号的注册:
    判断pending(信号集合位图)相应位是否为1,若为0,为信号组织sigqueue节点添加到链表中,并且pending位图置1。若为1,说明信号已经被注册过,还没有被处理,第二个相同的信号等于被丢弃。

  2. 可靠信号的注册:
    不管位图是否为1,组织节点,添加到链表中,并且位图置1,信号没有被丢弃。


信号的注销

非可靠信号(1~31号):因为非可靠信号的信号结点只有一个,因此删除结点,位图直接置0
可靠信号(34~64号):因为可靠信号的结点有可能会有多个,若还有相同信号结点,则位图依然置1,否则置0


信号的处理

signal(int signum, sighandler_t handler);	//回调函数,修改信号的处理方式
  1. 默认处理:SIG_DFL(default)
  2. 忽略处理:SIG_IGN(ignore)
    如果形如signal(2,SIG_IGN);,运行后不能通过2号信号停止,但可以通过3号信号进行停止。
  3. 自定义处理

信号的捕捉

signal接口

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
  • signum:信号编号
  • handler:函数指针
    使用handler函数替换signum 信号的处理方式。

信号自定义处理方式的捕捉流程:

  1. 在用户态因为系统调用,或中断,或异常,进入内核运行。
  2. 当完成内核功能,准备返回用户态主控流程前,先检测是否有信号待处理。
  3. 如果现在有一个信号未处理,并且是自定义处理方式的信号,此时调用do_signal(),返回用户态,找到自定制处理函数(此时为sigcb函数),去处理这个信号。
    (默认或者忽略的处理方式在内核态直接完成)
  4. 事件处理完毕,通过特殊的系统调用sigreturn()返回内核态,此时信号处理完毕,准备从内核态返回用户态,判断没有未处理信号了,通过sys_sigreturn()从主控制流程中上次被打断的位置继续向下运行。

怎么从用户态切换到内核态?系统调用中断异常

sigaction接口

使用act动作替换signum原有的处理动作,并且将原有处理动作拷贝到oldact中。

int sigaction(
	int signum, 
	const struct sigaction *act,
	struct sigaction *oldact
);
  • signum:编号。
  • act:动作,用户自己定义一个动作。
  • oldact:旧动作。

结构体:

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

sigemptyset接口

int sigemptyset(sigset_t *set);

清空一个信号集合。


信号的阻塞

阻塞:暂时阻止信号被递达,信号依然可以注册,但是暂时不处理,解除阻塞之后才会处理。

  • 信号的递达:(动作)信号的处理。
  • 信号的未决:(状态)信号的产生到处理之前,所处的状态。
  1. pending未决信号集合,每一个信号到来之后,都会在pending位图中修改本信号的位,添加本信号结点。
  2. blocked阻塞信号集合。
  3. handler动作函数数组。

信号的阻塞过程实际就是在PCBblocked信号阻塞集合中标记哪些信号到来之后暂时不处理,将blocked位图集合中对应的位置1,进行阻塞这个信号。
在这里插入图片描述

【注意】:
9号信号SIGKILL19号信号SIGSTOP无法被忽略,无法被阻塞,用户也无法对它们进行自定义处理方式。

sigprocmask接口

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • 阻塞set集合中的信号,将原来阻塞的信号保存到oldset中,不关心则置NULL
  • 返回值:成功返回0,失败返回-1
  • how的取值:
  1. SIG_BLOCK:阻塞 set 中的信号。
    计算方式:mask = mask | set
  2. SIG_UNBLOCK:对 set 中的信号解除阻塞。
    计算方式: mask = mask & (~set)
  3. SIG_SETMASK:将 set 中的信号设置为阻塞信号。
    计算方式:mask = set

sigfillset接口

int sigfillset(sigset_t *set);

将所有信号添加到set集合中。

sigaddset接口

int sigaddset(sigset_t *set,int signum);

将指定信号signum添加到set集合中。


总结
sigpromask:阻塞 / 解除阻塞信号
sigemptyset: 清空信号集合
sigfillset: 向集合中添加所有信号
sigaddset: 向集合中添加指定信号

  • sigismember:判断信号信号是否在集合中
  • sigdelset:从集合中移除执行信号
  • sigpending:获取当前进程的未决信号

竞态条件

因为运行时序而造成数据竞争,导致数据二义性
函数中所完成的操作并非原子性操作,并且操作的数据是一个全局数据。

  • 不可重入函数
    如果一个函数操作了全局性数据,并且这个操作不是原子性操作,且这个操作不受保护,则这个函数是一个不可重入函数。
  1. 不可重入函数:不可在多个时序的运行中重复调用,重复调用有可能会造成数据二义性,数据混乱。
  2. 可重入函数:在多个时序运行中重复调用,不会造成异常影响,多数为数据二义性问题。

【注】不可重入函数:malloc / free


SIGCHLD信号

子进程退出,系统通过SIGCHLD信号通知父进程,但是此信号的默认处理方式是忽略处理,导致子进程成为僵尸进程。

因此用户选择自定义SIGCHLD信号处理方式:在回调函数中执行waitpid等待子进程退出。为了将同一时间退出的子进程全部处理,防止事件丢失。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

giturtle

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值