文章目录

  • 1.信号的基本概念
  • 2.利用 kill 命令发送信号
  • 3.信号处理的相关动作
  • 4.信号与 signal 函数
    • 4.1 signal 函数示例一
    • 4.2 signal 函数示例二
  • 5.利用 sigaction 函数进行信号处理
  • 6.利用信号处理技术消灭僵尸进程

1.信号的基本概念

发送信号是进程之间常用的通信手段。信号用来通知某个进程发生了某一个事情,事情、信号都是突发事件,信号是异步发生的,信号也被称为“软件中断”。

信号如何产生:

  • 某个进程发送给另外一个进程或者发送给自己
  • 由内核发送给某个进程
    • 通过在键盘上输入命令,如 Ctrl + Ckill
    • 内存访问异常、除数为 0 0 0 等等,硬件都会检测到并且通知内核

UNIX 以及类 UNIX 操作系统支持的信号数量各不相同。信号有名字,都是以 SIG 开头,如终端断开信号 SIGHUP。其实,信号就是一些宏定义的正整数常量(从数字 1 1 1 开始)。

查找 signal.hSIGHUP 的命令如下:

sudo find / -name "signal.h" | xargs grep -in "SIGHUP"

2.利用 kill 命令发送信号

创建一个 test.c 文件,其内容如下:

#include <stdio.h>
#include <unistd.h>int main(int argc, char* const* argv)
{printf("你好,世界!\n");for (;;){sleep(1);printf("休息1秒\n");}printf("程序退出,再见!\n");return 0;
}

编译 test.c 的命令如下:

gcc test.c -o test

运行 test 的命令如下:

./test

查看 bash 进程和 test 进程的命令如下:

ps -eo pid,ppid,pgid,sid,tty,comm | grep -E 'bash|PID|test'

跟踪 test 进程的命令如下:

sudo strace -e trace=signal -p 1268

kill 1268 命令就是往 test 进程发送 SIGTERM 终止信号:

kill -2 1365 命令就是往 test 进程发送 SIGINT 中断信号:

关于 kill 命令及 Linux 系统支持的部分信号:https://wker.com/linux-command/kill.html

3.信号处理的相关动作

上面提到的 kill 命令只是发个信号,而不是单纯的杀死的意思。

当某个信号出现时,我们可以按三种方式之一进行处理:

  • 执行系统默认动作,绝大多数信号的默认动作是杀死这个进程;
  • 忽略该信号;
  • 捕捉该信号,即自己写个处理函数,当信号来的时候,就调用处理函数来处理。

注意:SIGKILLSIGSTOP 信号既不能被忽略,也不能被捕捉。

4.信号与 signal 函数

进程:“嘿,操作系统!如果我之前创建的子进程终止,就帮我调用 zombie_handler 函数。”

操作系统:“好的!如果你的子进程终止,我会帮你调用 zombie_handler 函数,你先把该函数要执行的语句编好!”

上述对话中进程所讲的相当于“注册信号”过程,即进程发现自己的子进程结束时,请求操作系统调用特定函数。该请求通过 signal 函数调用完成,因此称 signal 函数为信号注册函数。

#include <signal.h>void (*signal(int signo, void (*func)(int)))(int);// 为了在产生信号时调用,返回之前注册的函数指针
// 函数名:signal
// 参数:int signo, void(*func)(int)
// 返回类型:参数为int型,返回void型函数指针

调用上述函数时,第一个参数为特殊情况信息,第二个参数为特殊情况下将要调用的函数的地址值(指针)。发生第一个参数代表的情况时,调用第二个参数所指的函数。

// 子进程终止则调用mychild函数
signal(SIGCHLD, mychild);
// 常数SIGCHLD定义了子进程终止的情况,应成为signal函数的第一个参数
// 此时mychild函数的参数应为int,返回值类型应为void,只有这样才能成为signal函数的第二个参数
// 已到通过alarm函数注册的时间,请调用timeout函数
signal(SIGALRM, timeout);
// 输入CTRL+C时调用keycontrol函数
signal(SIGINT, keycontrol);

以上就是信号注册过程。注册好信号后,发生注册信号时(注册的情况发生时),操作系统将调用该信号对应的函数。

4.1 signal 函数示例一

下面首先介绍 alarm 函数。

#include <unistd.h>unsigned int alarm(unsigned int seconds);// 返回0或以秒为单位的距SIGALRM信号发生所剩时间

如果调用该函数的同时向它传递一个正整型参数,相应时间后(以秒为单位)将产生 SIGALRM 信号。若向该函数传递 0 0 0,则之前对 SIGALRM 信号的预约将取消。如果通过该函数预约信号后未指定该信号对应的处理函数,则(通过调用 signal 函数)终止进程,不做任何处理。

接下来给出信号处理相关示例。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>// 定义信号处理函数,这种类型的函数称为信号处理器(Handler)
void timeout(int sig)
{if (sig == SIGALRM)puts("Time out!");// 为了每隔2秒重复产生SIGALRM信号,在信号处理器中调用alarm函数alarm(2);
}// 定义信号处理函数,这种类型的函数称为信号处理器(Handler)
void keycontrol(int sig)
{if (sig == SIGINT)puts("CTRL+C pressed");
}int main(int argc, char *argv[])
{int i;// 注册SIGALRM、SIGINT信号及相应处理器signal(SIGALRM, timeout);signal(SIGINT, keycontrol);// 预约2秒后发生SIGALRM信号alarm(2);// 为了查看信号产生和信号处理器的执行,提供每次100秒、共3次的等待时间,在循环中调用sleep函数。// 也就是说,再过300秒、约5分钟后终止程序,这是相当长的一段时间,但实际执行时只需不到10秒。for (i = 0; i < 3; i++){puts("wait...");sleep(100);}return 0;
}

编译运行:

gcc signal.c -o signal
./signal

输出结果:

上述是没有任何输入时的运行结果。

下面在运行过程中输入 CTRL+C,可以看到输出“CTRL+C pressed”字符串。

有一点必须说明:“发生信号时将唤醒由于调用 sleep 函数而进入阻塞状态的进程。”

调用函数的主体的确是操作系统,但进程处于睡眠状态时无法调用函数。因此,产生信号时,为了调用信号处理器,将唤醒由于调用 sleep 函数而进入阻塞状态的进程。而且,进程一旦被唤醒,就不会再进入睡眠状态。即使还未到 sleep 函数中规定的时间也是如此。所以,上述示例运行不到 10 10 10 秒就会结束,连续输入 CTRL+C 则有可能 1 1 1 秒都不到。

4.2 signal 函数示例二

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>// 信号处理函数
void sig_usr(int signo)
{if (signo == SIGUSR1){printf("收到了SIGUSR1信号!\n");}else if (signo == SIGUSR2){printf("收到了SIGUSR2信号!\n");}else{printf("收到了未捕捉的信号%d!\n", signo);}
}int main(int argc, char* const* argv)
{// 系统函数,第一个参数是个信号,第二个参数是个函数指针,代表一个针对该信号的捕捉处理函数if (signal(SIGUSR1, sig_usr) == SIG_ERR){printf("无法捕捉SIGUSR1信号!\n");}if (signal(SIGUSR2, sig_usr) == SIG_ERR){printf("无法捕捉SIGUSR2信号!\n");}for (;;){sleep(1);printf("休息1秒\n");}printf("再见!\n");return 0;
}

5.利用 sigaction 函数进行信号处理

sigaction 函数,它类似于 signal 函数,而且完全可以代替 signal 函数,也更稳定。之所以稳定,是因为:“signal 函数在 UNIX 系列的不同操作系统中可能存在区别,但 sigaction 函数完全相同。”

实际上现在很少使用 signal 函数编写程序,它只是为了保持对旧程序的兼容。下面介绍 sigaction 函数,但只讲解可替换 signal 函数的功能,因为全面介绍会给各位带来不必要的负担。

#include <signal.h>int sigaction(int signo, const struct sigaction *act, struct sigaction *oldact);// 成功时返回0,失败时返回-1。
// signo:与signal函数相同,传递信号信息。
// act:对应于第一个参数的信号处理函数(信号处理器)信息。
// oldact:通过此参数获取之前注册的信号处理函数指针,若不需要则传递0。

声明并初始化 sigaction 结构体变量以调用上述函数,该结构体定义如下:

struct sigaction
{void (*sa_handler)(int);sigset_t sa_mask;int sa_flags;
};

此结构体的 sa_handler 成员保存信号处理函数的指针值(地址值)。sa_mask 和 sa_flags 的所有位均初始化为 0 0 0 即可。这 2 2 2 个成员用于指定信号相关的选项和特性,而我们的目的主要是防止产生僵尸进程,故省略。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>// #define _XOPEN_SOURCE 700void timeout(int sig)
{if (sig == SIGALRM)puts("Time out!");alarm(2);
}int main(int argc, char *argv[])
{int i;// 为了注册信号处理函数,声明sigaction结构体变量并在sa_handler成员中保存函数指针值struct sigaction act;act.sa_handler = timeout;// 调用sigemptyset函数将sa_mask成员的所有位初始化为0sigemptyset(&act.sa_mask);// sa_flags成员同样初始化为0act.sa_flags = 0;// 注册SIGALRM信号的处理器。调用alarm函数预约2秒后发生SIGALRM信号sigaction(SIGALRM, &act, 0);alarm(2);for (i = 0; i < 3; i++){puts("wait...");sleep(100);}return 0;
}

编译运行:

gcc sigaction.c -o sigaction
./sigaction

输出结果:

6.利用信号处理技术消灭僵尸进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>// #define _XOPEN_SOURCE 700void read_childproc(int sig)
{int status;pid_t id = waitpid(-1, &status, WNOHANG);if (WIFEXITED(status)){printf("Removed proc id: %d\n", id);printf("Child send: %d\n", WEXITSTATUS(status));}
}int main(int argc, char *argv[])
{pid_t pid;// 注册SIGCHLD信号对应的处理器。若子进程终止,则调用第7行中定义的函数。// 处理函数中调用了waitpid函数,所以子进程将正常终止,不会成为僵尸进程。struct sigaction act;act.sa_handler = read_childproc;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGCHLD, &act, 0);// 创建子进程pid = fork();if (pid == 0)    // 子进程执行区域{puts("Hi! I'm child process");sleep(10);return 12;}else    // 父进程执行区域{printf("Child proc id: %d\n", pid);// 创建子进程pid = fork();if (pid == 0)    // 另一个子进程执行区域{puts("Hi! I'm child process");sleep(15);exit(24);}else{int i;printf("Child proc id: %d\n", pid);// 为了等待发生SIGCHLD信号,使父进程共暂停5次,每次间隔5秒。// 发生信号时,父进程将被唤醒,因此实际暂停时间不到25秒。for (i = 0; i < 5; i++){puts("wait...");sleep(5);}}}return 0;
}

编译运行:

gcc remove_zombie.c -o zombie
./zombie

输出结果:

可以看出,子进程并未变成僵尸进程,而是正常终止了。

信号、signal 函数、sigaction 函数相关推荐

  1. Linux信号详解:signal与sigaction函数【2】

    我们已经讨论了Linux操作系统中"信号"."中断"."僵尸进程"'等重要概念.结合C语言中的setjmp.longjump.sigsetj ...

  2. linux 信号signal和sigaction理解

    转载:http://blog.csdn.net/beginning1126/article/details/8680757 今天看到unp时发现之前对signal到理解实在浅显,今天拿来单独学习讨论下 ...

  3. linux下sigaction函数,sigaction函数解析

    sigaction函数的功能是检查或修改与指定信号相关联的处理动作(可同时两种操作). 他是POSIX的信号接口,而signal()是标准C的信号接口(如果程序必须在非POSIX系统上运行,那么就应该 ...

  4. 信号编程之sigaction函数和sigqueue函数

    转https://blog.csdn.net/lzjsqn/article/details/53545631 转载于:https://www.cnblogs.com/ming-michelle/p/1 ...

  5. 信号的用法,signal函数、sigaction函数及信号集(sigemptyset,sigaddset)操作函数

    信号是与一定的进程相联系的.也就是说,一个进程可以决定在进程中对哪些信号进行什 么样的处理.例如,一个进程可以忽略某些信号而只处理其他一些信号:另外,一个进程还可以选择如何处理信号.总之,这些总与特定 ...

  6. linux中signal函数返回值,signal函数、sigaction函数及信号集操作函数

    信号是与一定的进程相联系的.也就是说一个进程可以决定在进程中对哪些信号进行什 么样的处理.例如一个进程可以忽略某些信号而只处理其他一些信号另外一个进程还可以选择如何处理信号.总之这些总与特定的进程相联 ...

  7. 信号捕捉(signal、sigaction)

    信号的基本属性:软中断,由内核发送,内核处理.某个进程通过内核向另一个进程发送信号时(引起信号产生的五个因素),另一个进程将会陷入内核进行中断处理,未决信号集中相应信号置1,当递达后,置0.如果阻塞信 ...

  8. 2022-1-25 牛客C++ 项目 —— sigaction 函数

    sigaction 函数 sigaction 函数和 signal 函数 都是信号处理函数,但是由于 signal 函数在不同版本的 Linux 系统下的实现方式不一样,可能出现的行为也不一样.所以尽 ...

  9. Linux signal、sigaction的使用总结

    目录 一.什么是信号 二.信号的种类 三.信号的处理 -- signal()函数 四.信号处理 -- sigaction()函数(扑获信号) 五.扑获信号示例 一.什么是信号 信号是UNIX和Linu ...

  10. linux signal函数用法,linux信号机制之sigaction构造体浅析,signal 函数,信号捕捉.

    来自:http://hi.baidu.com/phenix_yw/blog/item/6eb4ca391d1479f23a87ce19.html 信号安装函数sigaction(int signum, ...

最新文章

  1. 6-5-树的双亲表示法-树和二叉树-第6章-《数据结构》课本源码-严蔚敏吴伟民版...
  2. ElementUI中的el-table怎样实现绑定对象数组时每一列不同控件的动态数据绑定
  3. 【科普】五分钟快速了解代码复杂度
  4. 1.3.3 错题整理(组成原理)
  5. 如何批量转.jpg/.bmp图片
  6. 你知道void和Void的区别吗
  7. echarts 其他样式 折线 重叠_echarts 折线图 areaStyle颜色重叠问题
  8. python压缩文件操作_python处理zip压缩文件 | 学步园
  9. ~~双链表(数据结构)
  10. sql collection内容_非关系数据库复习|SQL到mongoDB查询语句转换
  11. *管家病毒查杀模块逆向分析
  12. Python Revisited Day 09 (调试、测试与Profiling)
  13. cxGrid使用笔记
  14. 2022年全新UI聚合支付系统四方源码更新完美版
  15. 知乎引流实操:日吸200精准粉丝玩法分享
  16. 雷电模拟器手游辅助制作教程
  17. TLSF 内存分配算法详解
  18. 软件外包平台列表(转载-知乎网)
  19. Android自定义控件(高手级)--JOJO同款能力分析图,这份字节跳动历年校招Android面试真题解析
  20. 佳驰电子冲刺科创板:年利润3.2亿 源峰磐钰与国家产投是股东

热门文章

  1. e成科技人岗匹配中的匹配模型
  2. ThinkPad E545连WiFi教程(系统:ubuntu-20.04.3-live-server,无线网卡:BCM34142)
  3. nextdate函数白盒测试问题 软件测试_NextDate函数测试用例 -
  4. localhost解释
  5. elment-ui的el-select选择器blur事件失效 bug解决
  6. SFC模拟器coming soon
  7. cubeIDE开发, stm32独立看门狗IWDG的CubeMX配置及HAL库底层实现分析
  8. fpga电平约束有什么作用_Xilinx FPGA的约束设计和时序分析总结
  9. 第六十七篇:opencv中KeyPoint与point2f之间相互转换
  10. 删除数据库表中重复的记录