欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

信号

程序员文章站 2022-07-12 10:29:57
...

信号

目录

  • 1、产生信号
  • 2、信号的种类
  • 3、阻塞信号
  • 4、捕捉信号

    查看系统到底有哪些信号: kill -l
    信号
    每个信号都有一个编号和一个宏定义名称,这些宏定义可以在 signal.h 中找到。
    1)SIGHUP 关闭终端
    2)SIGINT Ctrl + C
    3)SIGQUIT Ctrl + \
    5)SIGTRAP 从用户空间陷入内核
    6)SIGABRT C库的abort函数产生的,可以把一个进程不正常的终止掉
    9)SIGKILL 杀死进程
    11)SIGSEGV 段错误
    13)SIGPIPE 管道破裂
    14)SIGALRM 定时器
    15)SIGTERM kill一个进程时,默认信号
    17)SIGCHLD 子进程死亡, 给父进程发送的信号
    18)SIGCONT 继续 (比如音乐的播放和暂停)
    19)SIGSTOP 暂停

进程收到信号的三种响应方式:
1.缺省 (查看 man 7 signal)
2.忽略信号:来了信号,不采取任何操作,SIGKILL SIGSTOP 不能被忽略
3.捕获并处理:SIGKILL SIGSTOP 不能被捕获

1、产生信号

信号产生的条件:
1.按下终端按键 Ctrl + C Ctrl + \
2.硬件异常,除0,无效内存
3.kill
4.某种软条件发生,时间到,管道破裂

//信号注册函数
void (*signal(int signo, void (*handler)(int)))(int);
//参数:signo 要注册的信号
     // handler  处理信号的函数
//返回值:旧的信号处理函数
//信号处理函数
typedef void (*handler_t)(int);
//信号处理函数只带有一个参数,该参数用来指示信号类型。信号处理函数应该是可重入的,否则很容易引发一些竞态条件。

handler_t signal(int num,  //要注册的信号
                 handler_t handler)  //当信号到达,要执行的操作
//handler可以是3个值:SIG_IGN 忽略信号
                   //SIG_DFL  缺省处理方式
                   //自定义函数

例子:在屏幕上打印 . 产生SIGINT Ctrl + C 信号
信号
当信号到达:
1.保护现场
2.转去执行信号处理程序
3.恢复现场

2、信号的种类

1.可靠信号和不可靠信号

不可靠信号表现在:(编号1~31不可靠)

  • 执行完信号处理函数后,自动恢复成默认处理方式(Linux已解决)
  • 当多个信号同时抵达时,可能出现信号丢失 。或者叫信号压缩

可靠信号(编号34~64) 解决不可靠信号的两个问题 (编号32~33的信号内核使用)

2.实时信号和非实时信号

  • 非实时信号都是不可靠信号
  • 实时信号都是可靠信号
3、阻塞信号

信号在内核里的表示:

信号递达(delivery):执行信号处理动作称之为信号递达
信号未决(pending):从信号产生到信号抵达之间的状态称之为信号未决
进程可以选择阻塞(block)某个信号,被阻塞的信号产生时保持未决状态,直到解除对此信号的阻塞,才执行抵达动作

信号在内核中的表示示意图:
信号
每个信号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。
1. SIGHUP 信号未阻塞也未产生过,当它递达时执行默认处理动作。
2. SIGINT 信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
3. SIGQUIT 信号未产生过,一旦产生 SIGQUIT 信号将被阻塞,它的处理动作是用户自定义函数 sighandler。

信号集操作函数

sigset_t 类型对于每种信号用一个 bit 表示“有效”或“无效”状态,至于这个类型内部如何存储这些 bit 则依赖于系统实现。

信号集:

int sigemptyset(sigset_t *set);//清空信号集
int sigfillset(sigset_t *set);//填充
int sigaddset(sigset_t *set, int signum);//将某一个信号加入信号集
int sigdelset(sigset_t *set, int signum);//将某一信号从信号集中删除
int sigsmember(const sigset_t *set, int signum);//判断某一信号是否在信号集中

sigprocmask

调用函数 sigprocmask 可以读取或更改进程的信号屏蔽字。

int sigprocmask(int how,  // SIG_BLOCK    mask |= set      在整个进程里的信号屏蔽
                          // SIG_UNBLOCK    mask &= ~set
                          //  SIG_SETMASK    mask = set
                const sigset_t  *set,    //我们要设置的信号屏蔽
                sigset_t  *oldset);   //返回旧的信号屏蔽

如果调用 sigprocmask 解除了对当前若干个未决信号的阻塞,则在 sigprocmask返回前,至少将其中一个信号递达。

sigpending

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

//获取内核的未决状态集
int sigpending(sigset_t *set)

sigpending 读取当前进程的未决信号集,通过 set 参数传出。调用成功则返回 0,出错则返回-1。
信号
程序运行时,每秒钟把各信号的未决状态打印一遍,由于我们阻塞了 SIGINT 信号,按 Ctrl-C 将会使 SIGINT 信号处于未决状态,按 Ctrl-\仍然可以终止程序,因为 SIGQUIT 信号没有阻塞。

4、捕捉信号

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。

内核实现的信号的捕捉
信号
1. 用户程序注册了 SIGQUIT 信号的处理函数 sighandler。
2. 当前正在执行 main 函数,这时发生中断或异常切换到内核态。
3. 在中断处理完毕后要返回用户态的 main 函数之前检查到有信号 SIGQUIT递达。
4. 内核决定返回用户态后不是恢复 main 函数的上下文继续执行,而是执行sighandler 函数,sighandler 和 main 函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。
5. sighandler 函数返回后自动执行特殊的系统调用 sigreturn 再次进入内核态。
6. 如果没有新的信号要递达,这次再返回用户态就是恢复 main 函数的上下文继续执行了。

sigaction

int sigaction(int signo,//要捕获的信号类型
              const struct sigaction *set, //设置信号处理
              struct sigaction *old);//返回旧的信号

struct sigaction
{
    void (*sa_handler)(int);
    sigset_t sa_mask;//在信号处理函数期间的信号屏蔽
    int sa_flag; //0
}

例:使用sigaction
信号

pause

int pause( void );  //将当前进程挂起,转存储调度,直到有信号抵达,才唤醒该进程

pause 函数使调用进程挂起直到有信号递达。如果信号的处理动作是终止进程,则进程终止,pause 函数没有机会返回;如果信号的处理动作是忽略,则进程继续处于挂起状态,pause 不返回;如果信号的处理动作是捕捉,则调用了信号处理函数之后 pause 返回-1,errno 设置为 EINTR,所以 pause 只有出错的返回值(想想以前还学过什么函数只有出错返回值?)。错误码 EINTR 表示“被信号中断”。

例:用alarm 和pause实现sleep函数 sleep会被信号打断
信号
1. main 函数调用 mysleep 函数,后者调用 sigaction 注册了 SIGALRM 信号的处理函数 sig_alrm。
2. 调用 alarm(nsecs)设定闹钟。
3. 调用 pause 等待,内核切换到别的进程运行。
4. nsecs 秒之后,闹钟超时,内核发 SIGALRM 给这个进程。
5. 从内核态返回这个进程的用户态之前处理未决信号,发现有 SIGALRM 信号,其处理函数是 sig_alrm。
6. 切换到用户态执行 sig_alrm 函数,进入 sig_alrm 函数时 SIGALRM 信号被自动屏蔽,从 sig_alrm 函数返回时 SIGALRM 信号自动解除屏蔽。然后自动执行系统调用 sigreturn 再次进入内核,再返回用户态继续执行进程的主控制流程(main 函数调用的 mysleep 函数)。
7. pause 函数返回-1,然后调用 alarm(0)取消闹钟,调用 sigaction 恢复SIGALRM 信号以前的处理动作。

常见的信号处理:
SIGALRM(定时器)
int alarm(int sec) sec秒后,信号SIGALRM抵达
alarm(0) 清除闹钟,可用来做超时处理
信号
例子:实现一个考试的案例
信号

可重入函数和不可重入函数

可重入函数指的是可以放在信号处理函数中
不可重入函数的条件:
函数内部使用了 1.使用静态变量
2.malloc和free函数
3.标准IO

例子:可重入函数可以放在信号处理函数中,但不安全
信号

sigsuspend

sigsuspend包含了pause的挂起的功能,同时解决了竞态条件的问题,在对时序要求严格的场合下都应该调用sigsuspend而不是pause。

#include <signal.h>
int sigsuspend(const sigset_t *sigmask);
//sigsuspend = pause + 信号屏蔽

和pause一样,sigsuspend没有成功返回值,只有执行了一个信号处理函数之后sigsuspend才返回,返回值为-1,errno设置为EINTR。
调用sigsuspend时,进程的信号屏蔽字由sigmask参数指定,可以通过指定sigmask来临时解除对某个信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原来的值,如果原来对该信号是屏蔽的,从sigsuspend返回后仍然是屏蔽的。

SIGCHID信号

子进程在终止时会给父进程发 SIGCHLD 信号,该信号的默认处理动作是忽略,父进程可以自定义 SIGCHLD 信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用 wait 清理子进程即可。
信号

上一篇: 信号

下一篇: 信号