Linux之信号(下)

317 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

信号的捕捉

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

sigfileset接口

int sigfileset(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等待子进程退出。为了将同一时间退出的子进程全部处理,防止事件丢失。