34.1 信号特点

  • 信号的发生是随机的,但信号在何种条件下发生是可预测的
  • 进程杠开始启动时,所有信号的处理方式要么默认,要么忽略;忽略是 SIGUSR1 和 SIGUSR2 两个信号,其他都采取默认方式(大多数是终止进程)。
  • 进程在调用 exec 函数后,原有信号的捕捉函数失效
  • 子进程的诞生总是继承父进程的信号处理方式
  • 在系统层面上信号的发生是可靠的
    • 在Linux 中的可靠性只保证一次,进程在处理信号期间,若发生同类型的信号不会丢失(内核会保留),会被延迟处理
    • 但同类型信号的多次发生只会保留一次,即被处理一次。
    • 若不同类型的信号发生也会被内核保留直接会被处理,处理完后再处理原有信号
  • 用户层面可靠性依赖于信号而执行的用户代码放置在信号处理程序内部执行是可靠的,否则不一定可靠
  • 在信号发生时,慢系统调用以被中断并在信号处理后系统调用会被重启
  • 在信号发生时,用户函数可以被中断,但不能被重启,沿着中断点继续执行
    • 在用户函数中要保证数据一致性,即可重入性,不要去访问全局变量和静态变量,堆中的变量若在函数内部分配没有关系,否则会出现不可重入性

34.2 信号集和信号屏蔽函数

34.2.1 信号集

  • 信号集为一个或多个信号的集合,主要用在信号屏蔽函数中
 #include <signal.h>
int sigemptyset(sigset_t *set);
  • 函数功能:将信号集清空,对应将所有信号屏蔽字置 0
  • 函数参数:
    • set        信号集合
#include <signal.h>
int sigfillset(sigset_t *set);
  • 函数功能:将所有信号加入到信号集中,对应将所有信号屏蔽字置 1
#include <signal.h>
int sigaddset(sigset_t *set, int signo);
  • 函数功能:将某个信号加入到信号集中,对应将信号屏蔽字某位置 1
#include <signal.h>
int sigdelset(sigset_t *set, int signo);
  • 函数功能:将某个信号从信号集中删除,对应将信号屏蔽字某位置 0

  上述函数返回,若成功返回0,出错返回 -1

 #include <signal.h>
int sigismember(sigset_t *set, int signo);
  • 函数功能:测试信号集中是否包含某个信号,对应判断信号屏蔽字某位是否置 1
  • 返回值:真,返回1,假,返回0;出错返回 -1

34.2.2 信号屏蔽函数

 #include <signal.h>
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrct oset);
  • 函数功能:利用 set 去覆盖内核中信号屏蔽字, oset 存放原有的信号屏蔽字
  • 函数参数:
    • @ how

      • SIG_BLOCK:利用 set 中信号设置信号屏蔽字
      • SIG_UNBLOCK:利用 set 中信号不设置信号屏蔽字
      • SIG_SETMASK:利用 set 中信号去替换内核信号屏蔽字
  • 返回:成功,返回0;失败,返回 -1
  • 说明:
    • 进程可以暂时屏蔽信号,使得进程在执行过程中发生的相应信号暂时被阻塞,等待进程解除信号屏蔽后,再由内核或驱动将信号传递给进程
    • 信号屏蔽可屏蔽程序执行过程中的中断
 #include <signal.h>
int sigpending(sigset_t *set);
  • 函数功能:获取信号未决字的内容
  • 返回值:成功返回0;出错返回 -1

34.2.3 信号屏蔽设置

  • 信号在处理过程中是被屏蔽的(置 1),处理完毕解除屏蔽(被置 0),可在函数可重入性中使用信号屏蔽技术
  • 内核中的 task_struct 中包含两个 32 位字(记录相关的信号信息),分别为信号屏蔽字 mask 和信号未决字 pending。
    • mask:

      • 共有 31 位(代表 1~31 号信号,0 号没有意义),每一位代表一个信号,初始为 0,若这位上发生信号,会被立即处理;若为 1(设置 1 则信号被屏蔽,设置 0 则信号不被屏蔽),则在该位上发生信号不会被处理,会延迟处理
    • pengding
      • 初始为 0,若 mask 中某一位为 1,但又发生了同样的信号,则在 pending 同样的位置会被置为 1,以便让进程知道该信号又发生过而进行延迟处理
  • 若干个信号一起设置为 0 或 1 称为信号集
  • 子进程继承父进程中的信号屏蔽字,而不继承信号未决字

34.2.4 获取信号屏蔽字

 #include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h> void out_set(sigset_t set)
{
int i;
for(i = ; i < ; i++){
if(sigismember(&set, i)) {
printf("%d\n", i);
}
}
} void sig_handler(int signo)
{
printf("begin process the %d\n", signo); /** 获得正在处理信号时内核中的信号屏蔽字的内容 */
sigset_t oset;///< 放置内核屏蔽字的内容
sigemptyset(&oset);
if((sigprocmask(SIG_BLOCK, NULL, &oset)) < ) {
perror("sigprocmask error");
}
out_set(oset);
printf("finish process the %d\n", signo);
} int main(void)
{
if(signal(SIGUSR1, sig_handler) == SIG_ERR) {
perror("signal sigusr1 error");
} if(signal(SIGUSR2, sig_handler) == SIG_ERR) {
perror("signal sigusr2 error");
} sigset_t oset;///< 放置内核屏蔽字的内容
printf("before signal occured mask:\n");
/** 清空信号集 oset */
sigemptyset(&oset);
/** 在信号发生前,获得信号屏蔽字的内容 */
if((sigprocmask(SIG_BLOCK, NULL, &oset)) < ) {
perror("sigprocmask error");
}
out_set(oset);
printf("process %d wait signal...\n", getpid());
pause();///< 进程暂停等待信号 printf("after signal occured mask:\n");
sigemptyset(&oset);
/** 在信号发生后,获得信号屏蔽字的内容 */
if((sigprocmask(SIG_BLOCK, NULL, &oset)) < ) {
perror("sigprocmask error");
}
out_set(oset);
return ;
}

测试:

  

  

  在信号发生前,信号屏蔽字都为 0,然后发送 SIGUSR1 信号,信号集被设置为 10。在结束后,信号又被重新设置回 0.

34.2.5 解决函数不可重入性

 #include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> int g_v[];
int *h_v; ///< 堆中变量 void set(int val)
{
int a_v[]; int i = ;
for(; i < ; i++) {
a_v[i] = val;
g_v[i] = val;
h_v[i] = val;
sleep();
} printf("g_v:");
for(i = ; i < ; i++){
if(i != ) {
printf(", %d", g_v[i]);
}
else {
printf(", %d", g_v[i]);
}
}
printf("\n"); printf("h_v:");
for(i = ; i < ; i++){
if(i != ) {
printf(", %d", h_v[i]);
}
else {
printf(", %d", h_v[i]);
}
} printf("\n");
printf("a_v:");
for(i = ; i < ; i++){
if(i != ) {
printf(", %d", a_v[i]);
}
else {
printf(", %d", a_v[i]);
}
}
} void sig_handler(int signo)
{
if(signo == SIGTSTP){
printf("SIGTSTP occured\n");
set();
printf("\nend SIGTSTP\n");
}
} int main(void)
{
if(signal(SIGTSTP, sig_handler) == SIG_ERR){
perror("signal sigtstp error");
} h_v = (int *)calloc(, sizeof(int)); printf("begin running main\n");
//屏蔽信号(1~31)
sigset_t sigset;
sigemptyset(&sigset);
sigfillset(&sigset); ///<要屏蔽所有的信号
if(sigprocmask(SIG_SETMASK, &sigset, NULL) < 0){
perror("sigprocmask error");
}
set();
//解除信号屏蔽
if(sigprocmask(SIG_UNBLOCK, &sigset, NULL) < ){
perror("sigprocmask error");
}
printf("\nend running main\n");
return ;
}

  未加屏蔽之前的测试结果,开始运行后,按 CTRL+Z 进行中断:

  

  加入上述信号屏蔽函数后:

  

  可以看出,数值都可以正常输出。

34.2.6 查看信号未决字的内容

  在不断发同类型的信号的时候,查看信号未决字

  

 #include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h> void out_set(sigset_t set)
{
int i = ;
for(; i < ; i++){
if(sigismember(&set, i)){
printf("%d,", i);
}
} printf("\n");
} void sig_handler(int signo)
{
printf("begin the signal handler\n");
int i = ;
sigset_t sigset;
for(; i < ; i++) {
sigemptyset(&sigset);
if(sigpending(&sigset) < ) {
perror("sigpending error");
}
else {
printf("pending signal:");
out_set(sigset);
sigemptyset(&sigset);
} printf("i is %d\n", i);
sleep();
} printf("end the signal handler\n");
} int main(void)
{
if(signal(SIGTSTP, sig_handler) == SIG_ERR){
perror("signal sigtstp error");
} printf("process %d wait signal...\n", getpid());
pause();///< 进程暂停等待信号
printf("process finished\n");
}

  只发送一次中断信号的结果:

  

  信号未决字在发送一个信号的时候,再发送同类信号的时候,信号未决字才置1,

  连续发送两次信号:

   

  可以看出信号被处理了两次,未决字发生了变化。

  同样可以发送超过两次同类信号,会发现也只会处理两次。

  

三十四、Linux 进程与信号——信号特点、信号集和信号屏蔽函数的更多相关文章

  1. 三十、Linux 进程与信号——信号的概念及 signal 函数

    30.1 信号的基本概念 信号(signal)机制是Linux 系统中最为古老的进程之间的通信机制,解决进程在正常运行过程中被中断的问题,导致进程的处理流程会发生变化 信号是软件中断 信号是异步事件 ...

  2. 第三十四天- 线程队列、线程池(map/submit/shutdown/回调函数)

    1.线程列队 queue队列 :使用import queue,用法与进程Queue一样 class queue.Queue(maxsize=0) # 先进先出: q = queue.Queue(3) ...

  3. Gradle 1.12用户指南翻译——第三十四章. JaCoCo 插件

    本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...

  4. COJ966 WZJ的数据结构(负三十四)

    WZJ的数据结构(负三十四) 难度级别:C: 运行时间限制:20000ms: 运行空间限制:262144KB: 代码长度限制:2000000B 试题描述 给一棵n个节点的树,请对于形如"u  ...

  5. NeHe OpenGL教程 第三十四课:地形

    转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...

  6. JAVA之旅(三十四)——自定义服务端,URLConnection,正则表达式特点,匹配,切割,替换,获取,网页爬虫

    JAVA之旅(三十四)--自定义服务端,URLConnection,正则表达式特点,匹配,切割,替换,获取,网页爬虫 我们接着来说网络编程,TCP 一.自定义服务端 我们直接写一个服务端,让本机去连接 ...

  7. Java进阶(三十四)Integer与int的种种比较你知道多少?

    Java进阶(三十四)Integer与int的种种比较你知道多少? 前言 如果面试官问Integer与int的区别:估计大多数人只会说到两点:Ingeter是int的包装类,注意是一个类:int的初值 ...

  8. SQL注入之Sqli-labs系列第三十四关(基于宽字符逃逸POST注入)和三十五关

    开始挑战第三十四关和第三十五关(Bypass add addslashes) 0x1查看源码 本关是post型的注入漏洞,同样的也是将post过来的内容进行了 ' \ 的处理. if(isset($_ ...

  9. spring boot 常见三十四问

    Spring Boot 是微服务中最好的 Java 框架. 我们建议你能够成为一名 Spring Boot 的专家. 问题一 Spring Boot.Spring MVC 和 Spring 有什么区别 ...

  10. “全栈2019”Java多线程第三十四章:超时自动唤醒被等待的线程

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

随机推荐

  1. nginx日志文件的定时切割与归纳

    应用环境:生产环境中的Nginx服务器,由于访问日志文件增长速度非常快,日志太大会严重影响服务器效率.同时,为了 方便对日志进行分析计算,须要对日志文件进行定时切割.定时切割的方式有按月切割.按天切割 ...

  2. Android 架构 -- Room

    gradle依赖: // add for room implementation "android.arch.persistence.room:runtime:1.1.1" // ...

  3. 如何搭建高可用redis架构?

    如何搭建高可用redis架构? 温国兵 架构师小秘圈 昨天 作者:温国兵,曾任职于酷狗音乐,现为三七互娱 DBA.目前主要关注领域:数据库自动化运维.高可用架构设计.数据库安全.海量数据解决方案.以及 ...

  4. (转)从一道面试题彻底搞懂hashCode与equals的作用与区别及应当注意的细节

    背景:学习java的基础知识,每次回顾,总会有不同的认识.该文系转载 最近去面试了几家公司,被问到hashCode的作用,虽然回答出来了,但是自己还是对hashCode和equals的作用一知半解的, ...

  5. 自制模态窗体闪烁效果: MessageBeep & FlashWindowEx

    SetFocus(hwnd_frame_preview); //设置焦点 /** 模拟模态窗口动作 **/ MessageBeep(0xFFFFFFFF); //0xFFFFFFFF SystemDe ...

  6. GUI制作仿qq窗口

    使用工具:python3.6,   pycharm 使用模块: tkinter模块:("Tk 接口")是Python的标准Tk GUI工具包的接口,位Python的内置模块,直接i ...

  7. typedef typename的用法

    我自己最后在这篇文章里理解:[C++]typedef typename什么意思? typedef typename A::a_type b_type; 其中typename是告诉编译器A::a_typ ...

  8. springboot整合jsp

    由于不想使用新模版,增加学习成本,故此延用jsp: 1.pom 文件 <!-- jsp --> <dependency> <groupId>org.apache.t ...

  9. JDBC批处理(Batch)MySQL中的表

    在数据库test里先创建表school,内容如下 向school表中一次增加多行.addBatch,executeBatch import java.sql.Connection; import ja ...

  10. Luogu P2580 于是他错误的点名开始了 Trie树 字典树

    字典树裸题.每次插入询问串,查询的时候拿出来直接查,信息保留在节点上. #include <bits/stdc++.h> using namespace std; char s[51]; ...