posts - 89,comments - 0,views - 12441

标准信号

信号是事件发生时对进程的通知机制。有时也称之为软件中断。信号与硬件中断的相似之处在于打断了程序执行的正常流程,大多数情况下,无法预测信号到达的精确时间。

信号分为两大类。第一组用于内核向进程通知事件,构成所谓传统或者标准信号。Linux 中标准信号的编号范围为 1~31。另一组信号由实时信号构成。

我们可以把信号看做是进程间交流的工具,即进程间通信

举个例子,两个人分别处在不同的孤岛上,如果手机没有信号,那么他们就无法进行交流,反之,则可以进行交流

进程也是类似的,由于虚拟内存,进程之间是相互独立的,就相当于孤岛,他们可以通过信号来进行通信也就是进程间通信

有了大致的概念后,让我们具体看看信号长什么样子吧

针对每个信号,都定义了一个唯一的(小)整数,从 1 开始顺序展开。<signal.h>以 SIGxxxx形式的符号名对这些整数做了定义。

范围一般是1-31,比如说我们常用的 Ctrl+C,它对应的信号就是 SININT

信号的产生

1

发送信号的系统调用

进程间发送信号,还需要适当的权限,这里就不详细展开了,以下关注的是使用的系统调用

  • int kill(pid_t pid, int sig);

    • pid是要发送的对象的进程id(即 发送给谁)
      3

    • sig是发送的信号(即 发送什么)

  • int raise(int sig) 向自身传递信号,相当于kill(getpid(), sig)

  • int killpg(pid_t pgrp, int sig) 向某一个进程组的所有成员发送一个信号

检查进程的存在

kill()系统调用还有另一重功用。若将参数 sig 指定为 0(即所谓空信号),则无信号发送。相反,kill()仅会去执行错误检查,查看是否可以向目标进程发送信号。从另一角度来看,这意味着,可以使用空信号来检测具有特定进程 ID 的进程是否存在。若发送空信号失败,且 errno为 ESRCH,则表明目标进程不存在。如果调用失败,且 errno 为 EPERM(表示进程存在,但无权向目标进程发送信号)或者调用成功(有权向进程发送信号),那么就表示进程存在。

这段话讲的就是使用kill去判断进程是否存在,当sig=0时,就可以判断,这时候不会发送信号,然后我们可以根据kill的返回值以及错误码errno去判断进程的情况

信号集

信号集,就是信号的集合,有时候我们需要指定一组信号,而非单个信号

数据结构

可以使用sigset_t这个数据类型去定义一个信号集

  sigset_t signal_set;

初始化信号集

  • 清空信号集:int sigemptyset(sigset_t *set); 信号集中无可用信号
  • 填充信号集:int sigfillset(sigset_t *set); 将所有可用信号填充到信号集中

修改信号集

  • 向信号集添加单个信号:int sigaddset(sigset_t *set, int sig);
  • 向信号集移除单个信号:int sigdelset(sigset_t *set, int sig);

成员检测

  • 检测信号集中是否有特定信号:int sigismember(const sigset_t *set, int sig);

信号的接收

信号的阻塞状态

信号掩码

核会为每个进程维护一个信号掩码,即一组信号,并将阻塞其针对该进程的传递。如果将遭阻塞的信号发送给某进程,那么对该信号的传递将延后,直至从进程信号掩码中移除该信号,从而解除阻塞为止。

如何阻塞/移除阻塞信号

5

我们先定义两个信号集,一个存储要阻塞的信号,一个存储改动前的掩码(改动前的阻塞信号集)

  sigset_t blockSet, prevMask;
  • 阻塞一个信号
  // 阻塞信号SIGINT
  sigemptyset(&blockSet);
  sigaddset(&blockSet, SIGINT);
  sigprocmask(SIG_BLOCK, &blockSet, &prevMask);
  • 移除一个阻塞信号
    和上面的差不多,将SIG_BLOCK换成SIG_UNBLOCK就行了

  • 设置掩码信号集

  // 以下是恢复原先的阻塞信号集
  sigprocmask(SIG_SETMASK, &prevMask, NULL);

需要注意的是,有两个信号是无法被阻塞的

系统将忽略试图阻塞 SIGKILL 和 SIGSTOP 信号的请求。如果试图阻塞这些信号,sigprocmask()函数既不会予以关注,也不会产生错误。

信号的等待状态

如果某进程接受了一个该进程正在阻塞的信号,那么会将该信号填加到进程的等待信号集中。当(且如果)之后解除了对该信号的锁定时,会随之将信号传递给此进程。

我的理解是,信号的阻塞是进程为了避免因某些信号的传递而中断设置的一种屏蔽措施,当这些被阻塞的信号发送时,这些信号会被存储在该进程的等待信号集,不会去中断进程,只有等进程移除了信号的阻塞(改变掩码)才能传递信号

不对信号进行排队处理

等待信号集只是一个掩码,仅表明一个信号是否发生,而未表明其发生的次数。换言之,如果同一信号在阻塞状态下产生多次,那么会将该信号记录在等待信号集中,并在稍后仅传递一次

不进行排队处理其实就是同一种信号只处理一次,不会一次处理多个同种信号

获取等待信号集

11

set参数是用来接收返回的等待信号集,然后可以使用sigismember函数来判断某个信号是否处于等待状态中

信号的处理

信号处理器程序(也称为信号捕捉器)是当指定信号传递给进程时将会调用的一个数。

以下是信号处理的流程
2

当进程收到信号时,会产生一个中断,cpu会转到信号处理器程序执行,然后在返回中断点,恢复进程的执行

不同的处理(响应)方式

信号的默认行为

  • term 表示信号终止进程
  • core 表示进程产生核心转储文件并退出
  • ignore 表示忽略该信号
  • stop 表示信号停止了进程
  • cont 表示信号恢复了一个已停止的进程

自定义行为

信号处理器函数一般具有以下格式

void signal_handler(int sig) {...};
  • signal函数

7

  // 捕获SIGINT信号,并执行信号处理器函数
  signal(SIGINT, signal_handler);
  • sigaction函数,提供了比signal函数更灵活精细的控制,可移植性更高

8

以下是struct sigaction的结构
9

sigaction中的act指向的是当前的信号处理信息,oldact指向的是旧有的信号处理信息,oldact可以是指为NULL

  struct sigaction sa; 
  sa.sa_handler = signal_handler; // 设置处理函数
  sigemptyset(&sa.sa_mask); // 清空掩码
  sa.sa_flags = 0; // 可以设置标志,用 | 进行连接

  sigaction(SIGINT, &sa, NULL); // 注册SIGINT信号处理器

等待信号

这个和信号的等待状态是不同的,这个是主动的,等待信号发送,而那个是被动的,信号已经发送了,但进程没有接收

10

借助于 pause(),进程可暂停执行,直至信号到达为止

信号的同步产生和异步产生

  • 同步:信号的产生由进程本身的执行造成的,会 立即传递
    22

  • 异步:信号的产生与进程本身的执行无关,由内核或者另一进程引发

信号传递的时机和顺序

  • 时机
  • 顺序
    按照编号,先传递小的,在传递大的,对于相同的信号,则按时间顺序进行传递
    23

实时信号

24

感觉最大的区别就是进行了排队处理

指代实时信号编号则可以采用 SIGRTMIN+x 的形式。例如,表达式(SIGRTMIN + 1)就表示第二个实时信号。

核心转储文件

所谓核心转储是内含进程终止时内存映像的一个文件。

使用命令ulimit -c unlimited生成核心转储文件,一般就是在可执行文件所在的目录下,可以使用配合调试器进行调试
21

posted on   Dylaris  阅读(49)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示