侧边栏壁纸
博主头像
如此肤浅博主等级

但行好事,莫问前程!

  • 累计撰写 18 篇文章
  • 累计创建 8 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

信号

如此肤浅
2022-10-07 / 0 评论 / 0 点赞 / 57 阅读 / 3,672 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-10-13,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

信号是 Linux 进程间通信的最古老的方式。信号是软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式 。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。

Linux 信号可由如下条件产生:
① 对于前台进程,用户可以通过输人特殊的终端字符来给它发送信号。比如输人 Ctrl+C 通常会给进程发送一个中断信号。
② 系统异常。比如浮点异常和非法内存段访问。
③ 系统状态变化。比如 alarm 定时器到期将引起 SIGALRM 信号。
④ 运行 kill 命令或调用 kill 函数。

概述

发送信号

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);

功能:

  • 把信号 sig 发送给进程 pid

pid

  • pid > 0:信号发送给 PID 为 pid 的进程
  • pid = 0:信号发送给本进程组内的其他进程
  • pid = -1:信号发送给除 init 外的所有进程
  • pid < -1:信号发送给组 ID 为 -pid 的进程组中的所有成员

sig

  • 要发送的信号

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno,errno 如下:
    • EINVAL:无效信号
    • EPERM:该进程没有权限发送信号给任何一个目标进程
    • ESRCH:目标进程或进程组不存在

注意事项一:(发送空信号)

  • Linux定义的信号值都大于 0,如果 sig 取值为0,则 kill 函数不发送任何信号
  • 但将 sig 设置为 0 可以用来检测目标进程或进程组是否存在,因为检查工作总是在信号发送之前就执行
  • 不过这种检测方式是不可靠的,因为:
    • 由于进程 PID 的回绕,可能导致被检测的 PID 不是我们期望的进程的 PID
    • 这种检测方法不是原子操作

信号处理方式

#include <signal.h>

typedef void (*__sighandler_t)(int);

功能

  • 目标进程收到信号后,执行该信号处理函数。

参数

  • 信号的编号,这里可以填数字编号,也可以填信号的宏定义

注意事项

  • 信号处理函数应该是可重入的,否则很容易引发一些竞态条件,所以在信号处理函数中严禁调用一些不安全的函数

Linux 信号

掌握下面列出的信号即可,其它的了解。

信号 信号值 默认行为 含义
SIGHUP 1 Term 控制终端挂起
SIGINT 2 Term 键盘输入以中断进程(Ctrl+C)
SIGQUIT 3 Core 键盘输入使进程退出(Ctrl+)
SIGKILL 9 Term 终止一个进程,该信号不可被捕获或者忽略
SIGSEGV 11 Core 非法内存段引用
SIGPIPE 13 Term 往读端被关闭的管道或者 socket 连接中写数据
SIGALRM 14 Term 由 alarm 或 setitimer 设置的实时闹钟超时引起
SIGTERM 15 Term 终止进程,kill 命令默认发送的信号就是 SIGTERM
SIGCHLD 20,17,18 Ign 子进程状态发生变化(退出或者暂停)
SIGURG 23 Ign socket 连接上接收到紧急数据
  • Term
    缺省动作是终止进程。
  • Core
    缺省动作是终止进程并进行内核映像转储(core dump)。当程序在执行的时候,由于编写的失误或未经过充分的测试,程序对系统构成威胁,就可能会导致核心转储。内核映像转储是指将进程数据在内存的映像和进程在内核结构中的部分内容以一定格式转储到文件系统,以作为调试之用,并且进程退出执行。
  • Ign
    缺省动作是忽略此信号,将该信号丢弃,不做处理。

signal 系统调用

#include <signal.h>

sighandler_t signal(int sig, sighandler_t handler);

功能:

  • 为一个信号设置处理函数

sig

  • 要捕获的信号

handler

  • 信号 sig 的处理函数,有 3 种情况:
    • SIG_IGN:忽略 sig 所指的信号
    • SIG_DFL:恢复 sig 所指信号的处理方式为默认值
    • 自定义的信号处理函数,信号的编号为这个自定义函数的参数

返回值

  • 不用关心~~

sigaction 系统调用

#include <signal.h>

int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact);

功能

  • 为一个信号设置处理函数(更健壮)

sig

  • 要捕获的信号

act

  • 信号的处理方式

oldact

  • 输出信号先前的处理方式(如果不为 NULL 的话)

返回值

  • 成功返回 0,失败返回 -1,并设置 errno
struct sigaction {
	void (*sa_handler)(int); 
    void (*sa_sigaction)(int, siginfo_t*, void*); 
    sigset_t sa_mask;
    int sa_flags;
    
    void (*sa_restorer)(void); // 已弃用
}

sa_handler、sa_sigaction

  • 信号处理函数指针,和 signal() 里面的函数指针用法一样,应根据情况给 sa_handler、sa_sigaction 两者之一赋值。

sa_mask

  • 信号掩码(信号阻塞集),指定哪些信号不能发送给本进程,临时屏蔽指定的信号。
  • 每个进程都有一个阻塞集,创建子进程时子进程将继承父进程的阻塞集。
  • 所谓阻塞并不是禁止传送信号, 而是暂缓信号的传送。

sa_flags

  • 设置程序收到信号时的行为,通常设为 0 ,表示使用默认属性。
sa_flags 选项 含义
SA_NOCLDSTOP 如果 sigaction 的 sig 参数是 SIGCHLD,则设置该标志表示子进程暂停时不生成 SIGCHLD 信号
SA_NOCLDWAIT 如果 sigaction 的 sig 参数是 SIGCHLD,则设置该标志表示子进程结束时不产生僵尸进程
SA _SIGINFO 使用 sa_sigaction 作为信号处理函数(而不是默认的 sa_handler),它给进程提供更多相关的信息
SA_ONSTACK 调用由 sigaltstack 函数设置的可选信号栈上的信号处理函数
SA_NODEFER 当接收到信号并进入其信号处理函数时,不屏蔽该信号。默认情况下,我们期望进程在处理一个信号时不再接收到同种信号,否则将引起一些竞态条件
SA_RESETHAND 信号处理函数执行完以后,恢复信号的默认处理方式
SA_INTERRUPT 中断系统调用
SA_RESTART 重新调用被该信号终止的系统调用
SA_NOMASK 同 SA_NODEFER
SA_ONESHOT 同 SA_RESETHAND
SA_STACK 同 SA_ONSTACK

信号集

信号集函数

Linux 使用数据结构 sigset_t 来表示一组信号,其实际上是一个长整型数组,数组的每个元素表示一个信号。Linux 提供了如下一组函数来设置、修改、删除和查询信号集:

#include <signal.h>
int sigemptyset(sigset_t *_set); // 清空信号集
int sigfillset()(sigset_t *_set); // 在信号集中设置所有信号
int sigaddset()(sigset_t *_set, int _signo); // 将信号_signo添加至信号集中
int sigdelset()(sigset_t *_set, int _signo); // 将信号_signo从信号集中删除
int sigismember()(_const sigset_t *_set, int _signo); // 测试_signo是否在信号集中

进程信号掩码

前文提到,我们可以利用 sigaction 结构体的 sa_mask 成员来设置进程的信号掩码。此外,如下函数也可以用于设置或查看进程的信号掩码:

#include <signal.h>

int sigprocmask(int _how, _const sigset_t *_set, sigset_t *oset);

_how

  • 如果 _set 不为 NULL,指定给进程号设置掩码的方式

_set

  • 指定新的信号掩码
  • 如果 _set 为 NULL,则进程信号掩码不变,此时我们仍然可以利用 _oset 参数来获得进程当前的信号掩码

_oset

  • 输出原来的信号掩码(如果不为NULL的话)

返回值

  • 成功返回 0,失败返回 -1,并设置 errno
_how 参数 含义
SIG_BLOCK 新的进程信号掩码是其当前值和 _set 指定信号集的并集
SIG_UNBLOCK 新的进程信号掩码是其当前值和 ~_set 信号集的交集,因此 _set 指定的信号集将不被屏蔽
SIG_SETMASK 直接将进程信号掩码设置为 _set

被挂起的信号

设置进程信号掩码后,被屏蔽的信号将不能被进程接收。如果给进程发送一个被屏蔽的信号,则操作系统将该信号设置为进程的一个被挂起的信号。如果我们取消对被挂起信号的屏蔽,则它能立即被进程接收到。

如下函数可以获得进程当前被挂起的信号集:

#include <signal.h>

int sigpending(sigset_t *set);

set

  • 保存被挂起的信号集

返回值

  • 成功返回 0,失败返回 -1,并设置 errno

fork 调用产生的子进程将继承父进程的信号掩码,但具有一个空的挂起信号集。

0

评论区