云计算百科
云计算领域专业知识百科平台

linux 进程信号 (上)

博客一:Linux进程信号(上)——信号基础与产生

一、信号快速认识

1.1 生活角度的信号

用快递来类比信号处理:

  • 识别快递:你在网上买了很多商品,即便快递没到,你也知道快递来临时该怎么处理——你能"识别快递"
  • 异步性:快递员到楼下通知你,但你正在打游戏,需5min后才能去取——取快递的行为不一定要立即执行,可以"在合适的时候去取"
  • 时间窗口:从收到通知到拿到快递期间,你"记住了有一个快递要去取"
  • 处理方式:
  • 执行默认动作(打开使用)
  • 执行自定义动作(送给女朋友)
  • 忽略快递(扔掉继续打游戏)
  • 快递到来整个过程是异步的,你不能准确断定快递员什么时候打电话

基本结论:

  • 识别信号是内置的,进程识别信号是内核程序员写的内置特性
  • 信号的处理方法,在信号产生之前已经准备好了
  • 处理信号不是立即处理,而是在合适的时候
  • 信号处理流程:信号产生 → 信号保存 → 信号处理
  • 处理方式:a.默认 b.忽略 c.自定义(后续都叫信号捕捉)

1.2 技术应用角度的信号

样例代码:

// sig.cc
#include <iostream>
#include <unistd.h>

int main()
{
while(true){
std::cout << "I am a process, I am waiting signal!" << std::endl;
sleep(1);
}
}

运行过程:

  • 用户输入命令,在Shell下启动一个前台进程
  • 用户按下Ctrl+C,键盘输入产生一个硬件中断,被OS获取,解释成信号,发送给目标前台进程
  • 前台进程因为收到信号,进而引起进程退出

signal系统函数:

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

  • signum:信号编号
  • handler:函数指针,表示更改信号的处理动作,当收到对应的信号,就回调执行handler方法

验证Ctrl+C本质是SIGINT(2号信号):

#include <iostream>
#include <unistd.h>
#include <signal.h>

void handler(int signumber)
{
std::cout << "我是:" << getpid()
<< ",我获得了一个信号:" << signumber << std::endl;
}

int main()
{
std::cout << "我是进程:" << getpid() << std::endl;
signal(SIGINT/*2*/, handler);
while(true){
std::cout << "I am a process, I am waiting signal!" << std::endl;
sleep(1);
}
}

  • 运行后按Ctrl+C,进程不退出,而是执行自定义handler
  • 说明信号处理是自己处理

生活例子与Ctrl+C信号处理过程结合:

  • 进程就是你,操作系统就是快递员,信号就是快递,发信号的过程就类似给你打电话

注意:

  • signal函数仅仅是设置了特定信号的捕捉行为处理方式,并不是直接调用处理动作。如果后续特定信号没有产生,设置的捕捉函数永远也不会被调用
  • Ctrl+C产生的信号只能发给前台进程。一个命令后面加&可以放到后台运行
  • Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像Ctrl+C这种控制键产生的信号
  • 前台进程在运行过程中用户随时可能按下Ctrl+C而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到SIGINT信号而终止,所以信号相对于进程的控制流程来说是**异步(Asynchronous)**的
  • 可以渗透&和nohup

1.3 信号概念

信号:进程之间事件异步通知的一种方式,属于软中断。

查看信号:

$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 ... 64) SIGRTMAX

宏定义(在signal.h中):

#define SIGINT 2 /* Interactive attention signal. */
#define SIGILL 4 /* Illegal instruction. */
#define SIGABRT 6 /* Abnormal termination. */
#define SIGFPE 8 /* Erroneous arithmetic operation. */
#define SIGSEGV 11 /* Invalid access to storage. */
#define SIGTERM 15 /* Termination request. */
#define SIGHUP 1 /* Hangup. */
#define SIGQUIT 3 /* Quit. */
#define SIGTRAP 5 /* Trace/breakpoint trap. */
#define SIGKILL 9 /* Killed. */
#define SIGBUS 10 /* Bus error. */
#define SIGSYS 12 /* Bad system call. */
#define SIGPIPE 13 /* Broken pipe. */
#define SIGALRM 14 /* Alarm clock. */
// …

  • 编号34以上的是实时信号,本章只讨论编号34以下的信号
  • 详细说明:man 7 signal

1.4 信号处理三种方式

1. 忽略此信号:

signal(SIGINT, SIG_IGN); // 设置忽略信号的宏

  • 输入Ctrl+C毫无反应

2. 执行该信号的默认处理动作:

signal(SIGINT, SIG_DFL);

  • 输入Ctrl+C,进程退出

3. 提供一个信号处理函数(自定义捕捉):

signal(SIGINT, handler);

  • 内核在处理该信号时切换到用户态执行这个处理函数

源码:

#define SIG_DFL ((__sighandler_t) 0) /* Default action. */
#define SIG_IGN ((__sighandler_t) 1) /* Ignore signal. */
typedef void (*__sighandler_t) (int);
// 其实SIG_DFL和SIG_IGN就是把0,1强转为函数指针类型


二、产生信号

2.1 通过终端按键产生信号

Ctrl+C (SIGINT):已经验证过

Ctrl+\\ (SIGQUIT):发送终止信号并生成core dump文件,用于事后调试

signal(SIGQUIT/*3*/, handler);

  • 按Ctrl+\\,执行自定义handler
  • 注释掉后按Ctrl+\\,进程退出,显示Quit

Ctrl+Z (SIGTSTP):发送停止信号,将当前前台进程挂起到后台

signal(SIGTSTP/*20*/, handler);

  • 按Ctrl+Z,执行自定义handler
  • 注释掉后按Ctrl+Z,显示Stopped,可用jobs查看

理解OS如何得知键盘有数据:

  • 键盘按下
  • 向CPU发送硬件中断
  • CPU识别自身针脚具有硬件中断信息(高电压)
  • CPU执行操作系统中处理键盘数据的代码
  • OS停下来当前工作,将数据从外设读入内存,等待进一步处理
  • 初步理解信号起源:

    • 信号其实是从纯软件角度,模拟硬件中断的行为
    • 只不过硬件中断是发给CPU,而信号是发给进程
    • 两者有相似性,但是层级不同

    2.2 调用系统命令向进程发信号

    #include <iostream>
    #include <unistd.h>
    #include <signal.h>

    int main()
    {
    while(true){
    sleep(1);
    }
    return 0;
    }

    $ g++ sig.cc -o sig # step1
    $ ./sig& # step2
    $ ps ajx | head -1 && ps ajx | grep sig # step3
    PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
    211805 213784 213784 211805 pts/0 213792 S 1002 0:00 ./sig
    $ kill -SIGSEGV 213784
    $
    [1]+ Segmentation fault ./sig

    • 213784是sig进程的pid
    • 之所以要再次回车才显示Segmentation fault,是因为在213784进程终止掉之前已经回到了Shell提示符等待用户输入下一条命令,Shell不希望Segmentation fault信息和用户的输入交错在一起,所以等用户输入命令之后才显示
    • 指定发送某种信号的kill命令可以有多种写法,上面的命令还可以写成kill -11 213784,11是信号SIGSEGV的编号
    • 以往遇到的段错误都是由非法内存访问产生的,而这个程序本身没错,给它发SIGSEGV也能产生段错误

    2.3 使用函数产生信号

    kill函数:给一个指定的进程发送指定的信号

    #include <sys/types.h>
    #include <signal.h>
    int kill(pid_t pid, int sig);
    // 成功返回0,出错返回-1

    样例:实现自己的kill命令:

    // mykill -signumber pid
    int main(int argc, char *argv[])
    {
    if(argc != 3)
    {
    std::cerr << "Usage: " << argv[0] << " -signumber pid" << std::endl;
    return 1;
    }
    int number = std::stoi(argv[1]+1); // 去掉-
    pid_t pid = std::stoi(argv[2]);
    int n = kill(pid, number);
    return n;
    }

    raise函数:给当前进程发送指定的信号(自己给自己发信号)

    #include <signal.h>
    int raise(int sig);
    // 成功返回0,失败返回非零

    样例:

    void handler(int signumber) {
    std::cout << "获取了一个信号:" << signumber << std::endl;
    }

    int main() {
    signal(2, handler); // 先对2号信号进行捕捉
    while(true) {
    sleep(1);
    raise(2); // 每隔1s,自己给自己发送2号信号
    }
    }

    abort函数:使当前进程接收到信号而异常终止

    #include <stdlib.h>
    void abort(void);
    // abort函数总是会成功的,所以没有返回值

    样例:

    signal(SIGABRT, handler);
    while(true) {
    sleep(1);
    abort();
    }
    // 实验可以得知,abort给自己发送的是固定6号信号,虽然捕捉了,但是仍然会终止
    // Aborted

    2.4 由软件条件产生信号

    SIGPIPE:是一种由软件条件产生的信号(向已关闭的管道写数据)

    alarm函数和SIGALRM信号:

    #include <unistd.h>
    unsigned int alarm(unsigned int seconds);
    // 返回值:0或以前设定的闹钟时间还余下的秒数

    • 调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号,该信号的默认处理动作是终止当前进程
    • 如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数

    基本alarm验证——体会IO效率问题:

    // IO多
    int count = 0;

    void handler(int signumber)
    {
    std::cout << "count : " << count << std::endl;
    exit(0);
    }

    int main()
    {
    signal(SIGALRM, handler);
    alarm(1);
    while (true) {
    count++;
    }
    return 0;
    }
    // 输出:count : 107148 (IO多,效率低)

    // IO少
    int count = 0;

    void handler(int signumber)
    {
    std::cout << "count : " << count << std::endl;
    exit(0);
    }

    int main()
    {
    signal(SIGALRM, handler);
    alarm(1);
    while (true) {
    count++;
    }
    return 0;
    }
    // 输出:count : 492333713 (IO少,效率高)

    结论:

    • 闹钟会响一次,默认终止进程
    • 有IO效率低

    设置重复闹钟:

    #include <iostream>
    #include <unistd.h>
    #include <signal.h>
    #include <vector>
    #include <functional>

    using func_t = std::function<void()>;
    int gcount = 0;
    std::vector<func_t> gfuncs;

    void handler(int signo)
    {
    for(auto &f : gfuncs) {
    f();
    }
    std::cout << "gcount : " << gcount << std::endl;
    int n = alarm(1); // 重设闹钟,会返回上一次闹钟的剩余时间
    std::cout << "剩余时间:" << n << std::endl;
    }

    int main()
    {
    alarm(1); // 一次性的闹钟,超时alarm会自动被取消
    signal(SIGALRM, handler);
    while (true) {
    pause();
    std::cout << "我醒来了…" << std::endl;
    gcount++;
    }
    }

    pause函数:等待信号

    #include <unistd.h>
    int pause(void);
    // pause()使调用进程(或线程)睡眠,直到一个信号被递送,该信号要么终止进程,要么导致调用信号捕获函数
    // 只有当信号被捕获且信号捕获函数返回时,pause()才返回。在这种情况下,pause()返回-1,errno设置为EINTR

    如何理解软件条件:

    • 在操作系统中,信号的软件条件指的是由软件内部状态或特定软件操作触发的信号产生机制
    • 包括但不限于:定时器超时(如alarm函数设定的时间到达)、软件异常(如向已关闭的管道写数据产生的SIGPIPE信号)等
    • 当这些软件条件满足时,操作系统会向相关进程发送相应的信号,以通知进程进行相应的处理

    如何简单快速理解系统闹钟:

    • 系统闹钟,其实本质是OS必须自身具有定时功能,并能让用户设置这种定时功能,才可能实现闹钟这样的技术
    • 现代Linux是提供了定时功能的,定时器也要被管理:先描述,再组织
    • 内核中的定时器数据结构:

    struct timer_list {
    struct list_head entry;
    unsigned long expires;
    void (*function)(unsigned long);
    unsigned long data;
    struct tvec_t_base_s *base;
    };

    • 定时器超时时间expires和处理方法function
    • 操作系统管理定时器,采用的是时间轮的做法,但是为了简单理解,可以把它组织成为"堆结构"

    2.5 硬件异常产生信号

    硬件异常被硬件以某种方式检测到并通知内核,然后内核向当前进程发送适当的信号。

    例如:

    • 当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程
    • 当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程

    模拟除0:

    #include <stdio.h>
    #include <signal.h>

    void handler(int sig)
    {
    printf("catch a sig : %d\\n", sig);
    }

    int main()
    {
    // signal(SIGFPE, handler); // 8) SIGFPE
    sleep(1);
    int a = 10;
    a /= 0;
    while(1);
    return 0;
    }

    模拟野指针:

    // 默认行为
    #include <stdio.h>
    #include <signal.h>

    void handler(int sig)
    {
    printf("catch a sig : %d\\n", sig);
    }

    int main()
    {
    // signal(SIGSEGV, handler);
    sleep(1);
    int *p = NULL;
    *p = 100;
    while(1);
    return 0;
    }
    // 输出:Segmentation fault (core dumped)

    // 捕捉行为
    signal(SIGSEGV, handler);
    // 输出:catch a sig : 11
    // catch a sig : 11
    // catch a sig : 11

    注意:

    • 通过上面的实验,我们可能发现:发现一直有8号信号产生被我们捕获,这是为什么呢?
    • 上面我们只提到CPU运算异常后,如何处理后续的流程,实际上OS会检查应用程序的异常情况,其实在CPU中有一些控制和状态寄存器,主要用于控制处理器的操作,通常由操作系统代码使用
    • 状态寄存器可以简单理解为一个位图,对应着一些状态标记位、溢出标记位。OS会检测是否存在异常状态,有异常存在就会调用对应的异常处理方法
    • 除零异常后,我们并没有清理内存,关闭进程打开的文件,切换进程等操作,所以CPU中还保留上下文数据以及寄存器内容,除零异常会一直存在,就有了我们看到的一直发出异常信号的现象
    • 访问非法内存其实也是如此

    子进程退出core dump:

    #include <iostream>
    #include <string>
    #include <unistd.h>
    #include <stdlib.h>
    #include <signal.h>
    #include <sys/wait.h>

    int main()
    {
    if (fork() == 0)
    {
    sleep(1);
    int a = 10;
    a /= 0;
    exit(0);
    }
    int status = 0;
    waitpid(–1, &status, 0);
    printf("exit signal: %d, core dump: %d\\n", status&0x7F, (status>>7)&1);
    return 0;
    }

    status结构:

    • 15-8位:正常终止退出状态 / 异常被信号杀掉退出信号(是否正常结束)
    • 7位:core dump标志

    man 7 signal:

    SIGABRT P1990 Core Abort signal from abort(3)
    SIGALRM P1990 Term Timer signal from alarm(2)
    SIGBUS P2001 Core Bus error (bad memory access)
    SIGCHLD P1990 Ign Child stopped or terminated
    SIGCLD – – A synonym for SIGCHLD
    SIGCONT P1990 Cont Continue if stopped
    SIGEMT – – Emulator trap
    SIGFPE P1990 Core Floating-point exception
    SIGHUP P1990 Term Hangup detected on controlling terminal
    or death of controlling process
    SIGILL P1990 Core Illegal Instruction
    SIGINFO – – A synonym for SIGPWR
    SIGINT P1990 Term Interrupt from keyboard
    SIGIO – Term I/O now possible (4.2BSD)
    SIGIOT – Core IOT trap. A synonym for SIGABRT
    SIGKILL P1990 Term Kill signal
    SIGLOST – Term File lock lost (unused)
    SIGPIPE P1990 Term Broken pipe: write to pipe with no readers; see pipe(7)
    SIGPOLL P2001 Term Pollable event (Sys V).

    Core Dump:

    • SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且Core Dump
    • Core Dump:当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump
    • 进程异常终止通常是因为有Bug,比如非法内存访问导致段错误,事后可以用调试器检查core文件以查清错误原因,这叫做Post-mortem Debug(事后调试)
    • 一个进程允许产生多大的core文件取决于进程的Resource Limit(这个信息保存在PCB中)。默认是不允许产生core文件的,因为core文件中可能包含用户密码等敏感信息,不安全
    • 在开发调试阶段可以用ulimit命令改变这个限制,允许产生core文件

    $ ulimit -c 1024
    $ ulimit -a
    core file size (blocks, -c) 1024
    data seg size (kbytes, -d) unlimited
    scheduling priority (-e) 0
    file size (blocks, -f) unlimited
    pending signals (-i) 7643
    max locked memory (kbytes, -l) 65536
    max memory size (kbytes, -m) unlimited
    open files (-n) 65535
    pipe size (512 bytes, -p) 8
    POSIX message queues (bytes, -q) 819200
    real-time priority (-r) 0
    stack size (kbytes, -s) 8192
    cpu time (seconds, -t) unlimited
    max user processes (-u) 7643
    virtual memory (kbytes, -v) unlimited
    file locks (-x) unlimited

    测试:

    // test.c
    #include <stdio.h>
    int main()
    {
    printf("pid is : %d\\n", getpid());
    while(1);
    return 0;
    }

    • 前台运行这个程序,然后在终端键入Ctrl+C(貌似不行)或Ctrl+\\(介个可以)
    • ulimit命令改变了Shell进程的Resource Limit,test进程的PCB由Shell进程复制而来,所以也具有和Shell进程相同的Resource Limit值,这样就可以产生Core Dump了

    2.6 总结思考

    • 上面所说的所有信号产生,最终都要有OS来进行执行,为什么?OS是进程的管理者
    • 信号的处理是否是立即处理的?在合适的时候
    • 信号如果不是被立即处理,那么信号是否需要暂时被进程记录下来?记录在哪里最合适呢?
    • 一个进程在没有收到信号的时候,能否能知道,自己应该对合法信号作何处理呢?
    • 如何理解OS向进程发送信号?能否描述一下完整的发送处理过程?

    三、保存信号

    3.1 信号其他相关常见概念

    • 实际执行信号的处理动作称为信号递达(Delivery)
    • 信号从产生到递达之间的状态,称为信号未决(Pending)
    • 进程可以选择阻塞(Block)某个信号
    • 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作
    • 注意:阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作

    3.2 在内核中的表示

    信号在内核中的表示示意图:

    task_struct
    |
    +– block pending handler
    | SIGHP(1) 0 SIG_DFL
    | SIGINT(2) 1 SIG_IGN
    | SIGQUIT(3) 0 void sighandler(int signo)
    | …… …… ……

    • 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作
    • 信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志
    • 在上图的例子中:
      • SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作
      • SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞
      • SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler
    • 如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里

    内核结构 >:

    struct task_struct {
    /* signal handlers */
    struct sighand_struct *sighand;
    sigset_t blocked;
    struct sigpending pending;
    }

    struct sighand_struct {
    atomic_t count;
    struct k_sigaction action[_NSIG]; // #define _NSIG 64
    spinlock_t siglock;
    }

    struct __new_sigaction {
    __sighandler_t sa_handler;
    unsigned long sa_flags;
    void (*sa_restorer)(void);
    __new_sigset_t sa_mask;
    }

    struct k_sigaction {
    struct __new_sigaction sa;
    void __user *ka_restorer;
    }

    typedef void (*__sighandler_t)(int);

    struct sigpending {
    struct list_head list;
    sigset_t signal;
    }

    3.3 sigset_t

    • 每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的
    • 因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储
    • sigset_t称为信号集,这个类型可以表示每个信号的"有效"或"无效"状态
    • 在阻塞信号集中"有效"和"无效"的含义是该信号是否被阻塞
    • 在未决信号集中"有效"和"无效"的含义是该信号是否处于未决状态
    • 阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的"屏蔽"应该理解为阻塞而不是忽略
    • 类似权限哪里的umask

    3.4 信号集操作函数

    #include <signal.h>

    int sigemptyset(sigset_t *set); // 初始化set所指向的信号集,使其中所有信号的对应bit清零
    int sigfillset(sigset_t *set); // 初始化set所指向的信号集,使其中所有信号的对应bit置位
    int sigaddset(sigset_t *set, int signo); // 在该信号集中添加某种有效信号
    int sigdelset(sigset_t *set, int signo); // 在该信号集中删除某种有效信号
    int sigismember(const sigset_t *set, int signo); // 判断一个信号集的有效信号中是否包含某种信号

    • sigemptyset:初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号
    • sigfillset:初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号
    • 注意:在使用sigset_t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的状态
    • 这四个函数都是成功返回0,出错返回-1
    • sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1

    sigprocmask:读取或更改进程的信号屏蔽字(阻塞信号集)

    #include <signal.h>
    int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
    // 返回值:若成功则为0,若出错则为-1

    • 如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出
    • 如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改
    • 如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字

    how参数可选值:

    how说明
    SIG_BLOCK set包含了我们希望添加到当前信号屏蔽字的信号,相当于mask=mask|set
    SIG_UNBLOCK set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask&~set
    SIG_SETMASK 设置当前信号屏蔽字为set所指向的值,相当于mask=set
    • 如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递交

    sigpending:读取当前进程的未决信号集

    #include <signal.h>
    int sigpending(sigset_t *set);
    // 读取当前进程的未决信号集,通过set参数传出
    // 调用成功则返回0,出错则返回-1

    实验代码:

    #include <iostream>
    #include <unistd.h>
    #include <cstdio>
    #include <sys/types.h>
    #include <sys/wait.h>

    void PrintPending(sigset_t &pending)
    {
    std::cout << "curr process[" << getpid() << "]pending: ";
    for (int signo = 31; signo >= 1; signo—)
    {
    if (sigismember(&pending, signo)) {
    std::cout << 1;
    } else {
    std::cout << 0;
    }
    }
    std::cout << "\\n";
    }

    void handler(int signo)
    {
    std::cout << signo << "号信号被递达!!!" << std::endl;
    std::cout << "———————–" << std::endl;
    sigset_t pending;
    sigpending(&pending);
    PrintPending(pending);
    std::cout << "———————–" << std::endl;
    }

    int main()
    {
    // 0. 捕捉2号信号
    signal(2, handler); // 自定义捕捉
    // signal(2, SIG_IGN); // 忽略一个信号
    // signal(2, SIG_DFL); // 信号的默认处理动作

    // 1. 屏蔽2号信号
    sigset_t block_set, old_set;
    sigemptyset(&block_set);
    sigemptyset(&old_set);
    sigaddset(&block_set, SIGINT); // 我们有没有修改当前进行的内核block表呢???1
    // 1.1 设置进入进程的Block表中
    sigprocmask(SIG_BLOCK, &block_set, &old_set); // 真正的修改当前进行的内核block表,完成了对2号信号的屏蔽!

    int cnt = 15;
    while (true)
    {
    // 2. 获取当前进程的pending信号集
    sigset_t pending;
    sigpending(&pending);

    // 3. 打印pending信号集
    PrintPending(pending);
    cnt—;

    // 4. 解除对2号信号的屏蔽
    if (cnt == 0)
    {
    std::cout << "解除对2号信号的屏蔽!!!" << std::endl;
    sigprocmask(SIG_SETMASK, &old_set, &block_set);
    }

    sleep(1);
    }
    }

    运行结果:

    curr process[448336]pending: 000000000000000000000000000000000000000000000000000000000000001
    curr process[448336]pending: 000000000000000000000000000000000000000000000000000000000000001
    ^Ccurr process[448336]pending: 000000000000000000000000000000000000000000000000000000000000001
    curr process[448336]pending: 000000000000000000000000000000000000000000000000000000000000001
    …

    • 程序运行时,每秒钟把各信号的未决状态打印一遍,由于我们阻塞了SIGINT信号,按Ctrl+C将会使SIGINT信号处于未决状态,按Ctrl+\\似乎可以终止程序,因为SIGQUIT信号没有阻塞

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » linux 进程信号 (上)
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!