进程信号

news2025/7/17 17:42:00

目录

信号的产生方式

程序的崩溃

通过键盘产生

进程异常产生

系统调用产生

软件条件产生

信号产生中

函数介绍

sigset_t(信号集)

sigprocmask函数

sigpending函数

信号处理 

用户态和内核态的理解

处理信号的过程

信号捕捉

sigaction函数

volatile关键字

SIGCHLD信号


信号和信号量就相当于老婆和老婆饼的关系,其实没有实质联系。

1、信号具有识别信号并处理信号的能力,远远早于信号的产生

2、进程收到某种信号的时候,并不算是立即处理,而是在合适的时候

3、进程收到信号之后,有时需要先保存起来,以供合适的时候处理

4、信号的本质是数据,信号的发送就是向进程的task_struct内写入信号数据

5、无论任何信号的发送,本质都是在底层通过OS发送的

进程收到信号的处理方案有以下几种

1、默认动作---------->一部分是终止自己,暂停等

2、忽略动作---------->是一种信号处理的方式,只不过动作就是什么都不干

3、(信号捕捉)自定义动作-->使用signal方法修改信号的处理动作。默认动作->自定义动作

该函数用于修改指定信号的默认行为。

信号的产生方式

程序的崩溃

在阐述信号的产生方式之前,我们需要对程序的崩溃重塑以下系统层面的认识。

ps:浮点异常的情况中的计算会加载到cpu中运算,其中除数为0,引发cpu错误。野指针访问的情况,我们需要知道页表映射虚拟地址和物理地址。但是这里的p是一个null指针,没有虚拟地址,何谈映射物理地址,引发错误。 

程序上的错误,通常会体现在硬件或者其他软件上边。

OS作为硬件的管理者,当发现硬件错误的时候,就会寻找引发错误的进程,对其发送信号,终止进程。便引起了进程的崩溃。

通过键盘产生

写入一个死循环

键盘输入ctrl+c,终止进程

验证该操作是由异常引起的。编写以下代码

void handler(int signo){
  switch(signo){
     case 2:
      printf("hello Linux,get a signal:%d\n", signo);
      break;
     case 3:
       printf("hello world,get a signal:%d\n", signo);
       break;
     case 9:
       printf("hello ...,get a signal:%d\n", signo);
       break;
     default:                                                                                                            
       printf("hello signal:%d\n", signo);
       break;
   }
   exit(1);
}
int main(){
   //捕捉所有信号
   int cnt = 1;
   for( ; cnt < 31; cnt++){                                                                                              
     signal(cnt, handler);//将cnt号信号的默认动作修改成自定义动作
   }
 
   while(1){
     printf("hello linux!\n");
     sleep(1);
   }
   return 0;
}

执行结果:

进程异常产生

执行下边的代码,具有浮点溢出错误

void handler(int signo){
  switch(signo){
     case 2:
      printf("hello Linux,get a signal:%d\n", signo);
      break;
     case 3:
       printf("hello world,get a signal:%d\n", signo);
       break;
     case 9:
       printf("hello ...,get a signal:%d\n", signo);
       break;
     default:                                                                                                            
       printf("hello signal:%d\n", signo);
       break;
   }
   exit(1);
}
int main(){
   //捕捉所有信号
   int cnt = 1;
   for( ; cnt < 31; cnt++){                                                                                              
     signal(cnt, handler);//将cnt号信号的默认动作修改成自定义动作
   }
 
   int a = 10;
   a /= 0;//浮点异常

   while(1){
     printf("hello linux!\n");
     sleep(1);
   }
   return 0;
}

执行结果

当我们想找到具体崩溃在哪一行的时候。需要用到进程等待的知识。

在Linux中,当一个进程退出的时候,它的退出码和退出信号都会被设置(正常情况)。

当进程异常的时候,进程的退出信号会被设置,表明当前进程退出的原因。 

如果必要,OS会设置退出信息中的core dump标志位,并将进程在内存中的数据转存到磁盘中,方便后期调试。

默认情况下,云服务器上的core dump服务是被关掉的。

使用ulimit -a可以查看系统资源。

我们需要使用ulimit -c + 大小命令将服务其打开。

 然后再运行程序。如下:

再使用gdb调试

 

 ps:并不是所有信号都会引发core dump标志位的重置。

系统调用产生

 这里的kill就是一个向进程发送信号的接口。

软件条件产生

alarm接口设置seconds秒后向进程发送SIGALRM信号。

执行以下代码

#include<stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
 
int count = 0;
void HandlerAlarm(int signo){
   printf("hello: %d\n", count);
   exit(1);
}                                                                                                                     
int main(){//统计一下1s的时间,服务器能对count递增多少
  signal(SIGALRM, HandlerAlarm);
  alarm(1);//没有设置alarm信号的捕捉动作(即没有自定义),执行默认动作
  while(1){
    count++;
    //printf("hello: %d\n",count++);//这里比较慢,因为有IO
  }
  return 0;
}

执行结果

 总结:信号产生的方式有4种。1、键盘产生2、进程异常产生3、系统调用产生4、软件条件产生。这四种方式最终都是通过OS向目标进程发送的信号

信号产生中

进程的task_struct中存放着进程的各种属性,其中采用位图结构来标识进程是否收到信号。OS向进程发送信号也就能理解成本质是向进程的task_struct中的信号位图中对应比特位写为1,即完成信号的发送。

实际执行信号的处理动作称为信号抵达:可分为自定义捕捉,忽略和默认三种情况。

信号从产生到抵达之间的状态称为信号未决:本质是这个信号被暂存再task_struct信号为途中。

进程可以选择阻塞某个信号:本质是OS允许进程暂时屏蔽指定的信号。

进程抵达中的忽略与阻塞是完全不同的概念。忽略只是进程抵达的一种方式,而阻塞是没有抵达,是一种独立的状态。忽略和阻塞是不同阶段的概念。

task_struct中存在指向了存放这三张表结构的变量,如下图

其中block位图用于表示进程是否阻塞。pending位图用于是否有信号写入。handler函数指针数组用于进程执行何种动作(其中存放的是动作函数指针,每个信号的编号就是其数组下标)。

这三张表必须横向同时看,也就是说识别一个信号是一个三元组。比如:

第一行表示该进程未被阻塞,但是并未写入0号信号,执行SIG_DFL(默认)动作。

第二行表示该进程被阻塞,且写入了1号信号,执行SIG_IGN(忽略)动作。

第三行表示该进程被阻塞,但是未写入2号信号,执行自定义动作。

每一行的执行逻辑,可参考下边伪代码

int IsHandler(int signo){
    if(block & signo){//该信号被block
        · · · · ·
        return 0;
    }
    else{//未被block
        if(signo & pending){//未被block,且已被写入
            handler_arr[signo](signo);
            return 0;
        }
    }
}

函数介绍

sigset_t(信号集)

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

sigset_t类型对于每种信号用一个比特位表示“有效”或“无效”状态,至于这个类型内部如何存储这些比特位则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释

#include<signal.h>

        int sigemptyset(sigset_t *set);//初始化信号集,置0

        int sigfillset(sigset_t *set);//初始化信号集,置1

        int sigaddset (sigset_t *set, int signo);//将信号集的某一信号置为1

        int sigdelset(sigset_t *set, int signo);//将信号集的某一信号置为0

        int sigismember(const sigset_t *set, int signo);//判断某一信号在信号集中是否为1

ps:

        函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。

        函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号。

注意,在使用sigset_ t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的 状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号

sigprocmask函数

sigprocmask函数可以读取或更改进程的信号屏蔽字(阻塞信号集)

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

sigpending函数

sigpending函数可以读取当前进程的未决信号集,通过set参数传出

现在我们使用sigprocmask函数和sigpending函数完成一个函数。思路是,

1、对2号信号写入信号集

2、将该信号集使用sigprocmask函数将2号信号block住

3、我们键盘输入ctrl+c,产生2号信号,发送至该进程。

4、使用sigpending函数输出pending位图。

ps:因为我们将2号进程block住了,该信号并不能抵达。我们再向其发送2号信号,信号被block住了并不会影响信号的写入。因此我们再发送2号进程之前pending位图应该全为0,发送2号进程之后会看见pending位图发送由0至1的变化。

void show_pending(sigset_t* set){
  int i = 1;
  for(i; i<= 31; i++){
    if(sigismember(set, i)){
      printf("1");
    }
    else{
      printf("0");
    }
  }
  printf("\n");
}
void handler(int signo ){
  printf("%d号信号被抵达了,已经处理完成!\n", signo);
}
int main(){

  signal(2, handler);//捕捉2号信号,完成自定义动作
  sigset_t iset, oset;

  //对集合做清空
  sigemptyset(&iset);
  sigemptyset(&oset);

  sigaddset(&iset, 2);//将2号信号写入iset集合

  //1、设置当前进程的屏蔽字
  //2、获取当前进程老的屏蔽字
  sigprocmask(SIG_SETMASK, &iset, &oset);

  int count = 0;
  sigset_t pending;
  while(1){
    sigemptyset(&pending);

    sigpending(&pending);

    show_pending(&pending);
    sleep(1);

    count++;
    if(count == 10){
      sigprocmask(SIG_SETMASK, &oset, NULL);
      //2号信号执行自定义动作
      printf("恢复2号信号,可以被抵达了\n");
    }
  }
  return 0;
}

运行结果如下

信号处理 

进程从内核态返回到用户态的时候,进行信号检测和处理

用户态和内核态的理解

 关于进程如何切换,我们都能找到同一个OS,因为使用同一张内核页表

关于系统调用:就是进程的身份转化为内核态,然后根据内核页表找到系统函数,进行执行。

大部分情况下,我们的OS都是可以在进程的上下文中直接运行。

处理信号的过程

ps:用户态的时候执行用户代码,在调用系统接口或者进程切换的时候切换到内核态,查看进程的三张表。(信号捕捉过程)当需要执行自定义动作的时候,切回用户态执行自定义动作,自定动作完成之后,再切到内核态执行sys_sigreturn()函数,最后再切回用户态中用户代码的下一行,继续执行。当执行默认动作或者忽略动作的时候,直接可以再内核中完成,完成之后切回用户态中的用户代码的下一行,继续执行。

信号捕捉

sigaction函数

sigaction函数可以读取和修改与指定信号相关联的处理动作

signo 是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非 空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体

 当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。

示例

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
void handler(int signo){
  while(1){
    printf("get a signo: %d\n", signo);
    sleep(1);
  }
}
int main(){
  struct sigaction act;
  memset(&act, 0, sizeof(act));

  act.sa_handler = handler;//执行自定义动作
  //act.sa_handler = SIG_IGN;
  //act.sa_handler = SIG_DFL;

  sigemptyset(&act.sa_mask);
  sigaddset(&act.sa_mask, 3);

  sigaction(2, &act, NULL);//本质是修改当前进程的handler函数指针数组特定内容

  while(1){
    printf("hello linux!\n");
    sleep(1);
  }

  return 0;
}

上边的代码代码对2号信号执行自定义动作,并将3号信号添加到了block位图中,因此无论我们输入ctrl+c(向该进程发送2号信号)还是ctrl+\(向该进程发送3号信号)都无法终止进程,只能发送9号管理员信号终止。执行结果如下:

volatile关键字

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

int flag = 0;

void handler(int signo){
  flag = 1;
  printf("change flag 0 to 1\n");
}
int main(){
  signal(2, handler);

  while(!flag);

  printf("这个进程是正常退出的!\n");
  return 0;
}

我们使用gcc  -O3的方式优化上述代码。

无法出现我们预期的结果。原因如下图。 

ps:我们使用gcc优化的方式,全局变量flag的值会直接缓存在cpu中的寄存器里,当flag值发生变化的时候,cpu也不会从内存中再次读取flag,而是使用缓存的flag值。

volatile的作用则是:告诉编译器,不要对该变量做任何优化,必须贯穿式的读取内存,不要读取中间缓冲区寄存器中的数据。

在flag变量前加上volatile关键字的运行结果。

SIGCHLD信号

子进程在终止时会给父进程发送SIGCHLD信号,该信号的默认处理动作是忽略。验证如下:

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

void handler(int signo){
  printf("get a signal: %d, pid: %d\n", signo, getpid());
}

int main(){

  signal(SIGCHLD, handler);
  //signal(SIGCHLD, SIG_IGN);//显式设置忽略SIGCHLD信号,当进程退出后,自动释放僵尸进程

  pid_t id = fork();
  if(id == 0){
    int cnt = 5;
    while(cnt){
      printf("I am child: %d\n", getpid());
      sleep(1);
      cnt--;
    }
    exit(0);
  }
  while(1);
  return 0;
}

运行结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/619496.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

安全测试13款免费的测试工具!

目录 前言&#xff1a; 1. Excercise in a Box 2. Needle 3. DevSlop 4. 移动安全框架(Mobile Security Framework) 5. Frida 6. Nishang 7. Tamper 8. InSpec 9. Faraday 10. Pocsuite 11. Taipan 12. Pacu 13. Secure Guild 前言&#xff1a; 首先&#xff0c;我想强调一…

策略模式——实践:在业务逻辑中理解设计模式

一般定义 策略模式(Strategy Pattern)&#xff1a;定义一系列算法&#xff0c;将每一个算法封装起来&#xff0c;并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化&#xff0c;也称为政策模式(Policy)。 主要角色 Context: 环境类 Strategy: 抽象策略类 Concr…

CnOpenData电商平台交易数据数据

一、数据简介 电子商务是网络化的新型经济活动&#xff0c;是推动“互联网&#xff0b;”发展的重要力量&#xff0c;是新经济的主要组成部分。通过电商平台获取的电商交易平台数据可以及时统计、监测数据&#xff0c;全方位跟踪分析电子商务市场的发展情况和发展环境&#xff…

mysql中的group by 和 having使用

mysql中的group by 和 having 使用 理论 –sql中的group by 用法解析&#xff1a; – Group By语句从英文的字面意义上理解就是“根据(by)一定的规则进行分组(Group)”。 –它的作用是通过一定的规则将一个数据集划分成若干个小的区域&#xff0c;然后针对若干个小区域进行数…

【Nginx+Tomcat的7层代理和四层代理】

目录 一、NginxTomcat负载均衡、动静分离1、正向代理2、反向代理3、Nginx动静分离实现原理Nginx静态处理优势 二、实战1.部署Nginx 负载均衡器2.部署2台Tomcat 应用服务器3.动静分离配置&#xff08;1&#xff09;Tomcat1 server 配置&#xff08;2&#xff09;Tomcat2 server …

【华为OD机试真题2023B卷 JAVAJS】评论转换输出

华为OD2023(B卷)机试题库全覆盖,刷题指南点这里 评论转换输出 时间限制:1s 空间限制:256MB 限定语言:不限 题目描述: 在一个博客网站上,每篇博客都有评论。每一条评论都是一个非空英文字母字符串。 评论具有树状结构,除了根评论外,每个评论都有一个父评论。 当评论保…

ciso模拟器配置RIP2

本文为ciso模拟器配置RIP2 操作笔记 (供新手参考&#xff09; 思科路由器设置ip地址怎么设置(思科模拟器中怎样给路由器配置ip地址) 方法一&#xff1a; 物理配置 https://www.luyouqi.com/shezhi/39347.html 方法二&#xff1a; 路由 CLI 配置 https://blog.csdn.net/qq_6…

Tomcat ServletConfig和ServletContext接口概述

ServletConfig是一个接口&#xff0c;是Servlet规范中的一员 WEB服务器实现了ServletConfig接口&#xff0c;这里指的是Tomcat服务器 一个Servlet对象中有一个ServletConfig对象&#xff0c;Servlet和ServletConfig对象是一对一 ServletConfig对象是Tomcat服务器创建的&#xf…

你用过的低代码都装备了这四大引擎吗?

低代码开发是一种通过图形化界面和少量编码来快速构建应用程序的方法。尽管增删改查是低代码开发中常见的基本功能&#xff0c;但仅仅通过这些功能的配置&#xff0c;往往只能实现数据的输入和输出&#xff0c;无法满足实际的业务需求。 增删改查功能主要用于对数据进行操作&a…

第11章:SpringMVC注解配置

一、注解配置SpringMVC 目的是&#xff1a;使用配置类和注解代替web.xml和Spring.MVC配置文件的功能 1.创建初始化类&#xff0c;代替web.xml 在Servlet3.0环境中&#xff0c;容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类&#xff0c;如果找…

c++的概述(二)

新增bool类型 bool的变量只能赋值为true (非0) 或false (0) #include <iostream>using namespace std;int main(int argc, char const *argv[]) {bool num;num true;cout<<"true "<<true<<endl;cout<<"false "<<…

移动端布局之flex布局3:案例-携程网首页案例制作(曾经的版本)2

移动端布局之flex布局3 案例&#xff1a;携程网首页案例制作(曾经的版本)背景线性渐变index.htmlindex.css 侧导航栏index.htmlindex.css 热门活动模块制作index.htmlindex.css 案例&#xff1a;携程网首页案例制作(曾经的版本) 背景线性渐变 background:linear-gradient(起始…

简单三步,教你快速接入淘宝开放平台,调用官方API

淘宝开放平台是为了方便开发者接入淘宝平台&#xff0c;进行商品、订单等信息的管理和交互而设计的。接入淘宝开放平台需要经过一系列审核和申请流程&#xff0c;而在API权限包审核时&#xff0c;一定要提供真实有效的证件和资料&#xff0c;并满足相应的条件&#xff0c;才能顺…

Redis未授权访问漏洞复现与利用

目录 一、漏洞简介及危害 1.1什么是redis未授权访问 1.2漏洞的危害&#xff1a; 1.3漏洞影响&#xff1a; 二、漏洞复现&#xff1a; 三、未授权访问漏洞测试 3.1 利用redis写webshell 3.2 利用"公私钥"认证获取root权限 3.3 利用crontab反弹shell 四、脚本…

自动化10年+经验给你10条建议,让你在自动化界占据一片地!

目录 前言&#xff1a; 1、哪一刻&#xff0c;让你想起了自动化 1.1 执行回归测试 1.2 压测场景执行并发 1.3 UI稳定&#xff0c;接口不断升级 2、七问&#xff1a;是否了解自动化风险 2.1 团队成员的资历 2.2 自动化成本投入产出比 2.3 慎重对待UI级自动化 2.4 自动化…

OpenAI | Let’s Verify Step by Step详细解读

一、概述 title&#xff1a;Let’s Verify Step by Step 论文地址&#xff1a;https://arxiv.org/abs/2305.20050 代码&#xff1a;GitHub - openai/prm800k: 800,000 step-level correctness labels on LLM solutions to MATH problems 1.1 Motivation 近期大模型的出现极…

青岛科技大学|物联网工程|物联网定位技术(第一讲)|6.7

目录 物联网定位技术&#xff08;第一讲&#xff09; 1. 什么是物联网定位技术&#xff1f; 2. 物联网定位技术主要有哪些&#xff1f; 3. 简述卫星定位系统的发展历史以及GPS的发展概况&#xff1f; &#xff08;1&#xff09;卫星定位的由来和发展 &#xff08;2&…

【Mysql】InnoDB 中 B+ 树索引的注意事项

一、根页面万年不动 在之前的文章里&#xff0c;为了方便理解&#xff0c;都是先画存储用户记录的叶子节点&#xff0c;然后再画出存储目录项记录的内节点。 但实际上 B 树的行成过程是这样的&#xff1a; 每当为某个表创建一个 B 树索引&#xff0c;都会为这个索引创建一个根…

Vue.js 中的服务端渲染和客户端渲染的区别

Vue.js 中的服务端渲染和客户端渲染的区别 Vue.js 是一个流行的前端框架&#xff0c;它提供了一种简单而强大的方式来构建交互式用户界面。Vue.js 可以在客户端和服务端执行渲染&#xff0c;这两种方式有不同的优势和劣势。本文将介绍 Vue.js 中的服务端渲染和客户端渲染的区别…

安全测试:13款免费的安全测试工具,抓紧白嫖不看后悔

目录 1. Excercise in a Box 2. Needle 3. DevSlop 4.移动安全框架(Mobile Security Framework) 5. Frida 6. Nishang 7.Tamper 8.InSpec 9. Faraday 10. Pocsuite 11. Taipan 12.Pacu 13. Secure Guilld 总结 1. Excercise in a Box Excercise in a Box是由英国…