Linux之进程信号(上)(二)

简介: Linux之进程信号(上)(二)

野指针——发送11号信号

文件test1.cc

1 #include<iostream>
  2 using namespace std;
  3 #include<unistd.h>
  4 #include<signal.h>
  5 int main(int argc, char* argv[])
  6 {
  7         while(true)
  8         {
  9                 cout<<"i am running..."<<endl;
 10                 sleep(1);
 11                 int* p;
 12                 *p = 10;
 13         }
 14         return 0;
 15 }

野指针的使用,导致程序崩溃,进程收到了来自OS的11号信号。

文件test1.cc

1 #include<iostream>
  2 using namespace std;
  3 #include<unistd.h>
  4 #include<signal.h>
  5 void catchSig(int signo)
  6 {
  7         cout<<"获取到一个信号,信号编号是:"<<signo<<endl;
  8 }
  9 int main(int argc, char* argv[])
 10 {
 11         signal(11, catchSig);//捕捉任意信号
 12         while(true)
 13         {
 14                 cout<<"i am running..."<<endl;
 15                 sleep(1);
 16                 int* p;
 17                 *p = 10;
 18         }
 19         return 0;
 20 }

OS会给当前进程发送11号信号,11号信号代表非法的内存引用。

OS怎么知道野指针?

访问野指针会导致虚拟地址到物理内存之间转化时对应的MMU报错,进而OS识别到报错,转化成信号。

4.软件条件

当软件条件被触发时,OS会发送对应的信号。

管道——13号信号SIGPIPE

我们之前在了解管道时,有讲到一种情况:当读端关闭时,OS会立即终止写端。OS是向写端进程发送13号信号,即当管道的读端关闭软件条件触发是,OS会向进程发送13号信号。

定时器——4号信号SIGALRM

定时器软件条件:alarm():设定闹钟。

调用alarm函数可以设定一个闹钟,即告诉内核在seconds秒后给当前进程发送SIGALRM信号,该信号的默认处理动作时终止当前进程。

该函数的返回值是:0或者之前设定的闹钟事件剩余的秒数。

例如,早上设定的6:30的闹钟,但是在6:20被人吵醒了,想着再睡一会睡到6:40,于是将闹钟设定为20分钟后再响。

于是,重新设定的闹钟为20分钟后响,以前设定的闹钟还剩余的时间为10分钟。如果seconds值为0,表示取消之前设定的闹钟,函数返回值仍然是之前设定的闹钟的剩余秒数。

这份代码的意义是统计1s左右,我们的计算机可以将数据累积多少次。但,实际上这种方式效率较低,因为打印在屏幕上是需要访问外设的,而外设的运行速度较慢。

如果不进行打印:

文件test.cc

1 #include<iostream>
  2 using namespace std;
  3 #include<unistd.h>
  4 #include<signal.h>
  5 int cnt = 0;
  6 void catchSig(int signo)
  7 {
  8         cout<<"获取到一个信号,他的编号是:"<<signo<<",在1秒内计算机累加了:"<<cnt<<"次"<<endl;
  9 }
 10 int main()
 11 {
 12         signal(SIGALRM, catchSig);
 13         alarm(1);//软件条件
 14         while(1)
 15         {
 16                 cnt++;
 17         }
 18         return 0;
 19 }

理解闹钟是软件条件

“闹钟”其实就是用软件实现的,任意一个进程都可以通过alarm系统调用在内核中设置闹钟。OS内可能会存在很多的“闹钟”,因此需要对“闹钟”进行管理:先描述,再组织。因此,在OS内部设置闹钟时,需要为闹钟创建特定的数据结构对象

OS会周期性检查这些闹钟

curr_timestamp > alarm.when;//超时了,OS会发送SIGALRM ->alarm.p

struct alarm{
  uint64_t when;  //未来到的超时时间
  int type;   //闹钟类型(一次性、周期性)
  task_struct* p;
  stryct alarm* next;
  //…
};

管理闹钟

内核管理闹钟的数据结构是堆:大堆或者小堆。例如,100个闹钟,可以根据100个闹钟的when建小堆,最小的在堆顶。只要堆顶的没有超时,其他的闹钟自然也没有超时,所以只需要检查堆顶即可管理好这100个闹钟。

小结

  1. 上面说的所有产生信号的方式,本质都是由OS来执行发送信号的工作,因为OS是进程的管理者。
  2. 信号是在合适的时间进行处理,如果不是被立即处理,那么该信号就需要被记录下来,记录在进程PCB中
  3. 一个进程在未收到信号之前就知道自己应该如如何对该信号进行处理,这是程序员默认在系统中编写好的。
  4. OS向进程发送信号的本质是修改目标进程PCB中的信号位图。

四、捕捉信号的方法

1.signal

通过signum方法设置回调函数,来设置某一个信号的对应动作

练习:

文件test.cc

1 #include<iostream>
  2 using namespace std;
  3 #include<sys/types.h>
  4 #include<unistd.h>
  5 #include<signal.h>
  6 void catchSig(int signo)
  7 {
  8         cout<<"获取到一个信号,信号编号是:"<<signo<<endl;
  9 }
 10 int main(int argc, char* argv[])
 11 {
 12         signal(2, catchSig);//捕捉任意信号
 13         while(true)
 14         {
 15                 cout<<"i am running...,my pid = "<<getpid()<<endl;
 16                 sleep(1);
 17         }
 18         return 0;
 19 }

可以看到此时向进程发送2号信号或者按ctrl + c都能捕捉到信号(之所以在发送信号时没有终止进程,是因为我们将默认动作改为自定义动作,如果想让进程也终止,可以加上exit(0);或者直接kill -9 ,注意killl -9的对应动作是不会被修改的)

2.sigaction

它的作用域和signal一样,对特定的信号设置特定的回调方法。

一个正在运行的进程,势必会收到大量同类型的信号。那么问题来了,如果收到同类型的信号,但当前进程正在处理相同的信号,此时会出现什么情况?OS是否会运行频繁进行重复的信号提交?

文件test.cc

1 #include<iostream>
  2 using namespace std;
  3 #include<stdio.h>
  4 #include<sys/types.h>
  5 #include<unistd.h>
  6 #include<signal.h>
  7 void Count(int cnt)
  8 {
  9         while(cnt)
 10         {
 11                 printf("cnt:%2d\r", cnt);
 12                 fflush(stdout);
 13                 cnt--;
 14                 sleep(1);
 15         }
 16         printf("\n");
 17 }
 18 void handler(int signo)
 19 {
 20         cout<<"get a signo:"<<signo<<"正在处理中…"<<endl;
 21         Count(20);
 22 }
 23 int main(int argc, char* argv[])
 24 {
 25         cout<<"i am "<<getpid()<<endl;
 26         struct sigaction act, aact;
 27         act.sa_handler = handler;
 28         act.sa_flags = 0;
 29         sigemptyset(&act.sa_mask);
 30         sigaction(SIGINT, &act, &aact);
 31         while(true) sleep(1);
 32         return 0;
 33 }

  1. 当我们在传递一个信号的期间,它同类型的信号是无法传递的
    例子中,当前信号正在被捕捉,系统会自动将当前信号加入到进程的信号屏蔽字,即在block表中自动将2号信号屏蔽。
  2. 当系统完成对当前信号的捕捉,会自动解除对该信号的屏蔽
    一般信号被解除屏蔽,如果该信号已经被pending的话,系统会自动传递当前屏蔽信号,否则就不做任何动作。
  3. 进程处理信号的原则是:串行的处理同类型信号,不允许递归式处理。
  4. 特殊的:如果我们在屏蔽了一个信号的同时还想屏蔽另一个信号,则可以像下面的例子:
sigemptyset(&act.sa_mask);当前我们正在处理当前信号的同时,我们还想同时屏蔽另一个信号,例如3号信号,可以用下面的代码
sigaddset(&act.sa_mask, 3);

总结

以上就是今天要讲的内容,本文从现实中的信号引入,介绍了进程信号的部分内容,包括进程信号的基本概念、进程中有什么信号,如何查看进程中的信号、信号是如何产生的、如何捕捉信号(信号的自定义动作)等相关知识。

本文作者目前也是正在学习Linux相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。

最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!

相关文章
|
20天前
|
缓存 监控 Linux
linux进程管理万字详解!!!
本文档介绍了Linux系统中进程管理、系统负载监控、内存监控和磁盘监控的基本概念和常用命令。主要内容包括: 1. **进程管理**: - **进程介绍**:程序与进程的关系、进程的生命周期、查看进程号和父进程号的方法。 - **进程监控命令**:`ps`、`pstree`、`pidof`、`top`、`htop`、`lsof`等命令的使用方法和案例。 - **进程管理命令**:控制信号、`kill`、`pkill`、`killall`、前台和后台运行、`screen`、`nohup`等命令的使用方法和案例。
71 4
linux进程管理万字详解!!!
|
11天前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
53 8
|
8天前
|
Linux
如何在 Linux 系统中查看进程占用的内存?
如何在 Linux 系统中查看进程占用的内存?
|
20天前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
57 4
|
21天前
|
缓存 负载均衡 算法
Linux内核中的进程调度算法解析####
本文深入探讨了Linux操作系统核心组件之一——进程调度器,着重分析了其采用的CFS(完全公平调度器)算法。不同于传统摘要对研究背景、方法、结果和结论的概述,本文摘要将直接揭示CFS算法的核心优势及其在现代多核处理器环境下如何实现高效、公平的资源分配,同时简要提及该算法如何优化系统响应时间和吞吐量,为读者快速构建对Linux进程调度机制的认知框架。 ####
|
22天前
|
消息中间件 存储 Linux
|
29天前
|
运维 Linux
Linux查找占用的端口,并杀死进程的简单方法
通过上述步骤和命令,您能够迅速识别并根据实际情况管理Linux系统中占用特定端口的进程。为了获得更全面的服务器管理技巧和解决方案,提供了丰富的资源和专业服务,是您提升运维技能的理想选择。
37 1
|
1月前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
【10月更文挑战第9天】本文将深入浅出地介绍Linux系统中的进程管理机制,包括进程的概念、状态、调度以及如何在Linux环境下进行进程控制。我们将通过直观的语言和生动的比喻,让读者轻松掌握这一核心概念。文章不仅适合初学者构建基础,也能帮助有经验的用户加深对进程管理的理解。
26 1
|
1月前
|
消息中间件 Linux API
Linux c/c++之IPC进程间通信
这篇文章详细介绍了Linux下C/C++进程间通信(IPC)的三种主要技术:共享内存、消息队列和信号量,包括它们的编程模型、API函数原型、优势与缺点,并通过示例代码展示了它们的创建、使用和管理方法。
32 0
Linux c/c++之IPC进程间通信
|
1月前
|
Linux C++
Linux c/c++进程间通信(1)
这篇文章介绍了Linux下C/C++进程间通信的几种方式,包括普通文件、文件映射虚拟内存、管道通信(FIFO),并提供了示例代码和标准输入输出设备的应用。
30 0
Linux c/c++进程间通信(1)
下一篇
无影云桌面