• Linux 进程信号深剖


    传统艺能😎

    小编是双非本科大二菜鸟不赘述,欢迎米娜桑来指点江山哦
    在这里插入图片描述
    1319365055

    🎉🎉非科班转码社区诚邀您入驻🎉🎉
    小伙伴们,满怀希望,所向披靡,打码一路向北
    一个人的单打独斗不如一群人的砥砺前行
    这是和梦想合伙人组建的社区,诚邀各位有志之士的加入!!
    社区用户好文均加精(“标兵”文章字数2000+加精,“达人”文章字数1500+加精)
    直达: 社区链接点我


    在这里插入图片描述

    概念🤔

    信号是一个生活中随处可见的概念,没什么深层隐晦的地方,一个信号包含了各种信息,比如你快递到了,你该吃饭了或者你该睡觉了等等

    首先在 Linux 系统中,我们首先需要 明 白 信 号 的 产 生 是 异 步 的 \color{red} {明白信号的产生是异步的} ,比如有以下代码:

    #include 
    #include 
    
    int main()
    {
       
    	while (1){
       
    		printf("hello signal!\n");
    		sleep(1);
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    运行结果是死循环,我们要终止死循环的话通常会直接 ctrl c:

    在这里插入图片描述
    这里的 ctrl c 动作就是一个信号,本质上是因为输入产生了一个硬中断,ctrl c 被翻译成 2 号信号送到操作系统,操作系统再送到前台进程,然后前台进程退出。

    这里所谓的 2 号信号我们可以听过 signal 函数进行捕捉,声明为:

    #include 
    typedef void (*sighandler_t)(int);
    sighandler_t signal(int signum, sighandler_t handler);
    
    • 1
    • 2
    • 3

    signum:要捕捉的信号编号  
    handler:捕捉信号的处理方法,处理方法的参数是 int,返回值是 void

    这里就以对 ctrl c 的捕捉为例:
    在这里插入图片描述
    可以看到这里系统确实收到另一个 2 号信号!

    信号是进程之间事件异步通知的一种方式,属于 软 中 断 \color{red} {软中断}

    ctrl c 产生的信号只能发给前台进程,在命令后面加 & 就可以放到后台运行,这样 Shell 不必等待进程结束就可以接收新命令,启动新的进程。当然 Shell 可以同时运行一个前台进程和任意多个后台进程,但是只有前台进程才能接到像 ctrl c 这种控制信号

    该进程的用户空间代码执行到任何地方都可能收到 SIGINT信号(ctrl c 对应的 2 号信号)而终止,所以信号相对于进程的控制流程来说是异步的。

    信号发送🤔

    我们可以通过 kill -l 命令查看 Linux 中所有的信号:

    在这里插入图片描述
    其中1-31号信号是普通信号,34-64号信号是实时信号,普通信号和实时信号各自都有31个,每个信号都有一个编号和一个宏定义名称:

    在这里插入图片描述

    信号记录🤔

    实际上进程接收一个信号,该信号是被记录在该进程的进程控制块当中的。我们都知道进程控制块本质上就是一个结构体变量,我们主要就是记录一个信号是否产生,因此我们可以用一个 32 位的位图来记录信号是否产生

    在这里插入图片描述
    其中比特位的位置代表信号的编号,而比特位的内容就代表是否收到对应信号,比如第 6 个比特位是 1 就表明收到了 6 号信号

    信号产生🤔

    首先我们要知道信号是如何产生的

    收到信号的本质就是进程内的信号位图被修改,也就是进程的数据被修改,而只有操作系统才有资格修改进程的数据,因为操作系统是进程的管理者。也就是说,信号的产生本质上就是操作系统直接去修改目标进程的 task_struct 中的信号位图。

    常见信号处理方式🤔

    对于一个信号我们有三种处理方式:

    1. 执行该信号的默认处理动作
    2. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉一个信号
    3. 直接忽略信号

    Linux 我们可以使用 man 指令来查看处理方式:

    man 7 signal
    
    • 1

    在这里插入图片描述

    终端按键产生信号🤔

    最开始我们对于死循环程序输入 ctrl c 进行终止的动作就是通过终端按键产生信号,但是其实除了 ctrl c 以外,ctrl \ 也可以终止程序:

    在这里插入图片描述
    那么问题来了, ctrl c 和 ctrl \ 的区别在哪里

    ctrl c 实际上是发送 2 号信号SIGINT,而 ctrl \ 实际上是发送3号信号SIGQUIT。查看这两个信号的默认处理动作,可以看到这两个信号的 Action 是不一样的,2 号信号是 Term,而 3 号信号是 Core

    在这里插入图片描述
    Term 和 Core 都代表终止进程,但是 Core 在终止进程时会进行 核 心 转 储 \color{red} {核心转储}

    在这里插入图片描述

    核心转储😋

    嘛是核心转储?其实可以理解为运行时报错日志,我们知道在一个代码运行结束后如果有报错,我们可以通过退出码得知错误信息,但是如果是一个运行的代码错误,我们只能通过核心转储获悉错误原因

    在运行中如果崩溃了,我们一般会直接进行 debug 调试,但是有些特殊情况下我们会用到核心转储,核心转储的本质是一个 磁 盘 文 件 \color{red} {磁盘文件} ,也叫核心转储文件,它是操作系统在进程收到信号而终止后,会将进程地址空间的内容,状态和其他信息转而存储到磁盘中,一般命名为core.pid

    我们我们通过 ulimit -a 指令查看当前资源限制设定,这里我们看到在云服务器里面,核心转储功能是默认关闭的,因为 core 文件大小为 0:

    在这里插入图片描述
    我们可以自己通过 ulimit -c 来改变 core 文件的大小:

    在这里插入图片描述
    这里有了 core 文件就证明核心转储功能已经被我们开发♂出来了,再次使用 ctrl \ 就会出现 core dump 信息(因为我的服务器搞了汉化包,这里的硬核翻译比较尴尬,吐核就是 core dump):

    在这里插入图片描述
    且在当前目录下会产生一个 core+一串数字后缀的文件,这一串数字其实就是这次核心转储的进程的 PID

    在这里插入图片描述
    ulimit 指令改变的是 shell 的 Resource Limit,这里 signal 的 PCB 也是由 shell 复制来的,所以 signal 也具有和 shell 相同的 Resource Limit

    如何调试🤔

    我们下面这个简单的除0场景为例:

    在这里插入图片描述
    崩溃后就会出现报错信息:

    在这里插入图片描述
    接下来查看 core dump 文件:

    在这里插入图片描述
    在进入 gdb 调试模式,使用core-file + core文件名命令加载core文件,即可判断出该程序在终止时收到了8号信号,并且定位到了产生该错误的具体代码:

    在这里插入图片描述
    8 号信号对应的就是算术错误。

    core dump 标识在讲 waitpid 时就有说过:

    pid_t waitpid(pid_t pid, int *status, int options);
    
    • 1

    waitpid 的第二个参数 status 是一个输出型参数,他用于获取子进程的退出状态,他是一个整型参数,但是我们不能单纯将它看成一个整数,因为他的不同比特位有不同的含义:

    在这里插入图片描述
    我们只关注 status 的 16 位,如果进程时正常退出,次低 8 位就会记录退出状态,也就是我们说的退出码,如果是因为信号异常退出,低 8 位的第一位就是 core dump,记录了核心转储

    在这里插入图片描述
    我们此时就可以开启系统的核心转储功能,编写一个代码实现父进程 fork 子进程,并在子进程里面进行野指针的实现,在 *p = 100 执行时,系统会终止并且会立即进行核心转储,我们在 waitpid 后查看对应的终止信号:

    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main()
    {
       
    	if (fork() == 0){
       
    		//child
    		printf("I am running...\n");
    		int *p = NULL;
    		*p = 100;
    		exit(0);
    	}
    	//father
    	int status = 0;
    	waitpid(-1, &status, 0);
    	printf("exitCode:%d, coreDump:%d, signal:%d\n",
    		(status >> 8) & 0xff, (status >> 7) & 1, status & 0x7f);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    效果如下:
    在这里插入图片描述
    因此 core dump 标志就是表示当前进程是否进行了核心转储

    那么还有其他组合键操作吗?我们可以通过代码捕捉所有信号,并将收到信号的默认动作改为直接打印出该信号的编号:

    #include 
    #include 
    • 1
  • 相关阅读:
    数据库系统原理题-期末
    中英文说明书丨Abbkine通用型免疫荧光工具箱(抗小鼠Dylight 488)
    两化融合企业申报奖励制度
    Python 的运算符和语句(条件、循环、异常)基本使用指南
    【UNI】底部梯形按钮组件
    TypeScript基础内容(1)
    Spring(三)IoC 相关内容
    论语第一篇-学而
    1 V 8?Mini Homer图数传链路组网测试
    Gitlab仓库部署
  • 原文地址:https://blog.csdn.net/qq_61500888/article/details/127975480