| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 系统运维 -> 【linux】——信号详解和实操代码 -> 正文阅读 |
|
[系统运维]【linux】——信号详解和实操代码 |
目录 信号的概念
信号是进程之间事件异步通知的一种方式,属于软中断。
kill -l 查看进程所有的信号 1.每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到,例如其中有定 义 #define SIGINT 2 2.编号1~31是普通信号,编号34以上是实时信号,在这里我们不讨论实时信号。其中每个普通信号都有对应的默认动作,可以在man 7 signal中查看。???????? 信号捕捉初始当信号要处理时,每个信号都有一个默认处理动作,有的信号是直接终止进程,有的信号是直接进程core dump(下面会详解),当然,我们也可以自定义函数,修改信号执行的方式。 signal函数? ? ? ?
示例: ???????? 结果:? 其中按键【ctrl 】?+ 【 c 】 发送的是2号信号。【 ctrl 】 + 【 \?】发送的是3号信号。 发送信号????????????????
通过按键产生信号。例如: 【ctrl】+【c】给前台进程发送第2号信号,即SIGINT信号 【ctrl】+【\】给前台进程发送第3号信号,即SIGQUIT信号 【ctrl】+【z】给前台进程发送第20号信号,即SIGTSTP信号。
kill命令?
kill -9 13717 给pid为13717的进程发送9号信号。 ?kill函数
实现一个kill命令: ?编译生成mykill程序 将我们当前路径导入到我们的PATH环境变量中,这样我们运行mykill就不用在前面加上./ ?运行mykill,?mykill 2 10204是给pid为10204的进程发送2号信号 raise函数
?示例 运行结果:? abort函数
abort是SIGABRT信号,为第6信号。无论该信号是否呗被signal捕捉,则它都会使该当前进程退出。捕捉后再退出。? 编译生成myraise程序 ,运行结果:遇到abort则进程退出,不会运行后面的代码。 ? ?alarm函数
示例: ?编译生成myalarm程序,该程序的功能是1秒内在不断的计数,1秒到时被SIGNAL进程终止。 信号保存的原理进程收到信号可能不会够立即的去处理信号,需要等到合适的时间才会去处理我们的信号,那么我们的进程就需要将该信号保存起来,那么进程是如何记录这些信号的? 信号是保存咋我们进程的pcb中,也就是task_struct变量中。 ?每个信号都有两个标志位,一个是阻塞位图(block)和未决位图(pending),还有一个函数指针数组handler,这是信号处理的方式。 其中我们先来看一下 pending的含义:
? block含义
ps:
在block也是一个信号位图,其中位图的编号为信号的编号,位图的内容代表的是信号是否被阻塞。 如下图:?
handler的含义:
有了一些基本概念后,我们再重新看这张图。 在上面的例子中:
信号发送的本质信号的发送本质是由操作系统直接去修改进程task_struct变量中的pending的bit位,使该信号的bit位在pending位图中是有效的,只有操作系统才有权力去修改我们进程的task_struct变量。不管是我们的快捷键还是kill命令给我们的进程发送信号,它的底层都会去调用操作系统,让操作系统去修改该进程的位图。 由以上的知识,我们可以总结出?信号的产生到信号递达的整个过程。 sigset_t类型? ?在上面的概念中,每个信号只有一个bit的未决状态,非0即1,不管该信号产生了多少次,阻塞也是同样道理,所以未决和阻塞我们可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,sigset_t可以用来表示信号是否处于有效或无效的状态。????????这个类型在未决的信号集中表示”有效“和”无效“的含义是否处于未决状态,在阻塞的信号集中表示”有效的含义表示该信号是否被阻塞。 sigprocmask函数
set如果set是非空指针,则更改进程的信号屏蔽字。
进程是通过set变量进行增加或者删除我们的进程屏蔽字的,所以我们只要修改set就能修改我们的进程的屏蔽字,但我们不能直接对set进行修改,我们需要通过以下接口修改我们的set,sigset_t变量是需要调用以下接口来进行修改的。
注意:当
sigset_t变量被第一次使用的时候,需要先用sigemptyset或者sigfillset初始化sigset_t变量,使sigset_t为一个确定值。
how
指示进程如何更改进程的屏蔽字,how有三个选项可以让我们选,如下:
假设当前的信号的屏蔽字为mask
即
?如果想要给进程的信号集中的第三号信号设置为阻塞,则需要将set的第3位的bit位设置为有效,然后将sigprocmask函数的参数how设置为SIG_BLOCK,这样就可以给我们的进程设置第3号信号为阻塞。如下图所示: ?如果想要解除进程的信号集中的第4号信号的阻塞,则需要将set的第4位设置为有效,然后将sigprocmask函数的参数设置为SIG_UNBLOCk,这样就可以将解除第4号信号的阻塞。 osetoset是非空指针,则将修改前的信号屏蔽字保存到我们oset中,如果为NULL,则不对oset进行任何操作。 sigpending函数
示例: 阻塞进程的2号信号,然后我们给进程发送2号信号,看进程是否会执行我们的2号信号,并将进程未决图给打印出来 ?结果: 当我们阻塞2号信号后,给进程发送2号信号发现进程没有处理该信号。 ????????Core DumpSIGINT信号和SIGQUIT信号的区别 SIGINT的默认处理动作是终止进程 SIGQUIT的默认处理动作是Core Dump,有些信号的默认动作是core dump。 Core Dump是在进程触动某些异常时,它会把进程在内存的核心数据和引发异常的信息保存到磁盘中的一个文件中,这个文件名称为core。我们可以通过调试器在这个文件定位到引发异常的代码和异常异常的原因。进程能够产生多大core文件的取决于于进程的Resource Limit(这个信息保存 在PCB中)。 我们可以用命令ulimit -a查看我们进程是否能够产生core文件。默认情况下是不会产生core文件的。如果 core file size的大小为0,则代表产生的core文件是0,即不能产生core?文件 ? ulimit -c 10240 设置进程能够产生最大的core文件为10240kb。 ?测试: 产生core文件中,后面的数字是进程的id。 接下来我们用gdb调试core? pid_ t waitpid(pid_t pid, int *status, int options); wait和waitpid中有一个参数status,这个参数是输出型参数,将子进程的退出信息保存在这个变量中,如果忽略子进程的退出信息,则把status设置为NULL。 我们不能将status看作一个整形变量,它需要把它看作一个位图来看待,也就是说把这个变量看作是由32个比特位组成。其中我们只研究status的低16位。 ?当进程正常终止的时候,低8位是0,次第8位组成一个是进程退出码,这个我在【linux】——进程控制这篇文章讲过。 当进程异常终止时,也就是被信号所杀了,低7位是终止信号的编号,第8位代表的是有没有发生core dump,也就是有没有产生core文件,当产生core文件的时候,该bit位将记录为1. 实例代码:
输出: ?我们发现进程发生了core dump,并且收到了第11号的信号。 我们可以kill -l查看第11号信号是什么。 ?我们kill -l发现第11号信号对应的宏定义是SIGSEGV。 man 7 signal 查看对应的信号发生的错误信息。 引发 第11信号的对应的是invalid memory reference,翻译为错误的内存访问,也就是指针越界。 什么是用户态?什么是内核态?我曾经说过,信号只有在合适的时间里才会被处理,那么这个合适的时间里究竟是什么时候? 其实这个合适的时间是内核态切换回用户态的时候。 那么问题来了,什么是用户态?什么是内核态? ?我们先来看一张进程的虚拟内存 在进程的虚拟地址空间中,0G~3G是用户空间,这块空间通过用户的页表映射的是进程本身的代码和数据。3G~4G的虚拟内存空间是内核空间,这个内核空间是通过内核页表映射内存中的操作系统的代码和数据,也就是说,每个进程都能通过虚拟地址空间看到操作系统的代码和数据,但是每个进程不一定能够访问这个内核操作系统的代码和数据。 在intel cpu提供Ring0-Ring3三种级别的运行模式,Ring0级别最高,Ring3最低,Ring0状态是执行操作系统的状态,也就是内核态,内核态可以运行用户空间和内核空间上的代码和数据,Ring3是用户态,而运行于用户态的代码则要受到处理器的诸多检查,它们只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址,如果运行操作系统的代码,则会被cpu给终止掉,也就是说用户态只能运行我们用户空间上的代码和数据,不能进入内核运行操作系统的代码和数据。
用户态:当进程执行自己的代码的时候,则该状态称为用户态。???????? 内核态:当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态),当进程处于内核态时,执行的内核代码会使用当前进程的内核空间。 。 无论什么进程都是可以都能看到操作系统的代码和数据,所以进程是随时可以进入内核空间的,用户态和内核态是随时都有可能进行切换的。 什么时候用户态切换到我们的内核态?
什么时候内核态会切换到我们的用户态?
信号处理的过程当我们的进程在执行用户主控制流程上用户空间上的代码和数据时遇到系统调用或者进程的时间片到了等动作进入到内核空间,则cpu会切换到内核态去进入内核空间去调用系统接口或者切换进程等动作,当在内核态处理完这些动作后,在准备切换为用户态之前,操作系统是会去去处理可以递达的信号,信号的处理有三种方式,它们处理的过程是不一样的。 如果信号的处理方式是自定义行为,先将进程的pending上相对应的有效bit为由0该为1,然后将cpu的运行状态切换到用户态,去用户空间上执行自定义函数,当自定义函数执行完毕的时候,会执行特殊的系统调用sigreturn,执行完后自定义函数最后再执行特殊函数调用sigretum再次切换到内核态进入到内核当中,然后再次进入我们内核中,再从内核空间返回到我们用户态上的主控制流,继续执行上次中断的地方。 ?printf是需要往显示器上打印的,它封装的是系统调用接口,所以printf调用的时候是会切换到内核中,执行内核中的代码,执行完毕时,在检查我们pending,看看是否有信号要被处理,则操作系统会处理该信号。 如果处理信号的方式默认行为,直接在内核态执行,因为我们默认行为是要么是终止进程要么是core dump,这两个行为是在操作系统上完成的。 如果处理信号的方式是忽略,在内核态种则直接将pending上相对应的bit为由1变为0即可。 信号捕捉函数我们之前已经学了一个信号捕捉的信号,将捕捉到的信号执行自定义的行为,接下来我们将学习另一个捕捉函数sigaction
struct sigaction变量
在struct sigaction中,我们重点关注两个变量:一个是sa_handler变量,另一个是sa_sigaction变量。 sa_handler:
sa_mask
示例:将2号信号的处理动作设定为handler,执行handler函数后恢复2号信号上一次的执行动作。? ?运行结果: ? 好啦,今天的内容就到这里,喜欢的朋友给个三连呗,码字不易,你的三连将是我最大的鼓励。 往期linux文章 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/16 0:30:57- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |