Re:Linux系统篇(四十三)信号篇·一:谁在敲进程的门?一篇带你了解信号概念与产生机制

📅 2026/7/15 20:09:05
Re:Linux系统篇(四十三)信号篇·一:谁在敲进程的门?一篇带你了解信号概念与产生机制
◆ 博主名称 小此方-CSDN博客大家好欢迎来到小此方的博客。⭐️Linux系列个人专栏 【主题曲】Linux⭐️此方的GitHub github_此方⭐️Re系列专栏我们思考 (Rethink) · 我们重建 (Rebuild) · 我们记录 (Record)文章目录概要序論一、信号的概念1.1感性理解信号的概念1.2如何查看所有的信号1.3综上得出关于信号的基础结论二、信号的产生2.1键盘产生信号2.2.1信号处理的三种抉择与自定义信号处理2.1.2如何验证键盘产生了信号以及产生了何种信号2.2目标进程是什么2.2.1如何切换进程的前后台2.2.2后台进程不响应键盘的输入2.3给进程发送信号的本质是什么插曲讲一讲前台进程组的概念2.3.1 什么是进程组2.3.2 Ctrl C 的批量传递机制2.4系统调用和系统命令产生信号2.4.1系统调用kill发送信号2.4.2系统调用raise发送信号2.4.3系统调用abort发送信号概要序論Hello大家好我是此方结束进程间通信来到了Linux的倒数第二个话题——信号。今天主要介绍信号的基础认知以及信号是如何产生的等问题。好我们开始吧。一、信号的概念1.1感性理解信号的概念我们在先前讲到过信号量的概念但是今天我们要讲的信号和信号量的关系就像老婆和老婆饼雷锋和雷锋塔——毫无关系。信号与IPC的辩证关系闹钟响了你要起床绿灯亮了你要通过在这些语境中闹钟和绿灯都是信号。信号中断了人正在做的事情——因为你有新的事情要做了那么把人换成进程信号就是中断进程当前任务的“异步通知机制”。详细解释”异步“——说的是信号的发送过程相对于进程的执行是异步的。李老师在上课突然由通知让李老师去拿卷子李老师派小刚去拿卷子。“好的孩子们我们先自习一会儿等小刚回来我们再上课”——同步。“好的孩子们我们不等小刚继续上课小刚在执行拿卷子李老师同时在执行上课”——异步。1.2如何查看所有的信号我们有哪些信号通过指令kill -l来查看。信号就是一个整数数字一个宏我们一共有62个信号。其中前31个信号是普通信号后31个是实时信号本篇重点讲解普通信号。1-31普通信号不必立即处理。34-64实时信号必须立即处理。信号在源代码中详细看到每一个信号的细节可以用man 9 signal查看从左向右依次是信号的名称信号遵循的标准版本信号的默认行为信号的行为详细信息。信号的默认行为有终止Term核心转储Core停止Stop忽略Ign。具体放在后续详细讲解。1.3综上得出关于信号的基础结论进程在信号没有产生的时候早就知道信号该如何处理了。——你知道红灯亮了要停车绿灯亮了要前进。信号的处理不是立即处理而是可以等一会在处理合适的时候进行信号的处理。——你在直播游戏外卖到了你必须记住这个信号然后在这局游戏结束后下楼取外卖。人能识别信号是提前被教育过的进程也是如此OS程序员设计的进程进程早已经内置了对于信号的识别和处理方式。生活中的信号源非常多-给进程产生信号的信号源也非常多信号源有哪些我们后面讲。于是我们接下来的三篇关于信号的文章主要以以下这条线叙事。信号产生捕捉保存处理。二、信号的产生2.1键盘产生信号信号产生的方式有很多种键盘产生信号就是其中之一我们以前使用的CTRL C 就通过产生信号来终止进程。2.2.1信号处理的三种抉择与自定义信号处理验证前须知收到信号的处理动作有默认处理自定义处理和忽略处理三种抉择。如何进行自定义处理自定义信号处理的系统调用signal通过传入函数指针进行回调。返回值成功时返回该信号上一次关联的处理函数handler的值。例如如果该信号之前的处理方式是 SIG_IGN那么返回值也会是 SIG_IGN。失败时返回 SIG_ERR并且会将全局变量 errno 设置为相应的错误原因如 EINVAL表示信号无效。科普操作系统在设计的时候为了防止恶意进程通过篡改信号处理机制使得其无法被任何手段暂停和终止。不允许9号和19号信号被设置自定义处理。2.1.2如何验证键盘产生了信号以及产生了何种信号你想怎么验证我想看到信号处理的过程——通过修改信号处理的默认操作。代码如下#includeiostream#includestring#includesignal.h#includeunistd.hvoidHander(intn){std::cout收到信号nstd::endl;exit(1);}intmain(){for(inti0;i32;i)signal(i,Hander);while(1){std::cout进程运行中。。。std::endl;sleep(1);}return0;}运行当我们按CTRLC的时候进程接收到信号2并打印当我们按CTRL \ 进程收到三号信号并打印。2.2目标进程是什么回归定义截取——”信号是发送给目标进程的“ 那么目标进程是什么呢直接说答案前台进程。后台进程可以有很多个但是前台进程只能有一个。键盘知道它自己要把信号发送给哪个进程吗——唯一指向前台进程。前台进程的一个本质就是可以从键盘中获取数据的进程。前台进程可以获取标准输入的内容。后台进程不可获取标准输入的内容。两者都可以向标准输入写入内容科普在 Windows 操作系统中我们看似能同时看到浏览器、文档等多个窗口在“前台”并存但这只是视觉上的多任务。由于键盘只有一个Windows 引入了“输入焦点”机制。本质上只有被你用鼠标点击选中的那个当前活跃窗口才是唯一真正的“前台进程”独占键盘的输入数据而其他未被选中的窗口哪怕肉眼可见在输入层面也属于“后台进程”无法获取你的键盘敲击。这与 Linux 终端下前台进程只有一个的底层逻辑完全一致。2.2.1如何切换进程的前后台方法一在运行进程的时候在后面加上取址符。# ./XXX --- 前台进程# ./YYY --- 后台进程方法二指令操作。jobs: 查看所有的后台任务。fg 任务号: 特定的进程提到前台。ctrlz进程切换到后台。暂停进程暂停的进程必须被切换到后台bg 任务号让后台进行回复运行。2.2.2后台进程不响应键盘的输入这里举两个常见的案例我们./proc执行一个死循环打印的进程此时我们载用键盘输入指令ls shell不会回应因为shell从前台切换到了后台。父进程提前退出子进程孤儿化被操作系统切换到后台进程此时响应不了键盘无法通过ctrlc杀掉。2.3给进程发送信号的本质是什么一个信号发出去不会立即处理而是存起来对不对那么存在那里呢在进程PCB中有这么一个字段structtask_struct{unsignedintsigs;}这是一个32位的位图刚好可以存放31个信号的二元状态信息。位的位置标识哪一个信号。位的0/1标识信号是否被收到。所以我们就可以知道信号发送的本质就是修改为位图。联系起来我们的键盘输入ctrlc操作系统作为硬件的管理者识别出ctrlc是发送信号。于是将这个信号写入前台目标进程的位图前台目标进程在指定的时间查看自己的位图是2号信号杀掉自己。好继续回来unsigned int sigs;在task_struct 它属于操作系统的数据结构对象修改数据本质就是修改内核数据。而修改内核数据必须要通过系统调用——不管信号如何产生在底层必须通过系统调用发送信号。所以我们引出kill跳转到2.4插曲讲一讲前台进程组的概念在理解了前台进程后我们往往会产生疑问既然前台进程“有且仅有一个”那为什么按下Ctrl C时前台的父子进程会同时挂掉其实操作系统底层的精确表述是当前终端有且仅有一个“前台进程组”。2.3.1 什么是进程组在 Linux 内核中进程是“抱团”运行的。每当我们在终端启动一个程序系统就会分配一个进程组 IDPGID组长进程由命令行直接启动的父进程担任其 PID 就是整个进程组的 PGID。组员进程父进程通过fork()创建的所有子进程默认都会自动加入该进程组共享同一个 PGID。终端在管理输入和信号时正是以“进程组”为单位进行独占和调度的。2.3.2 Ctrl C 的批量传递机制当操作系统识别出Ctrl C的 2 号信号SIGINT后它在底层并不是发给某个单一的 PID而是直接锁定当前处于前台的整个进程组。随后内核会遍历该前台进程组内的所有进程含父进程和所有子进程将信号分别写入它们各自 PCB 的信号位图中。父子进程在各自的调度时机检测到信号后同时执行退出。2.4系统调用和系统命令产生信号系统调用和系统命令是产生信号的第二种方式。主要的系统调用有killraiseabort。系统命令主要是kill。系统命令kill我们一直在用就不讲了。2.4.1系统调用kill发送信号#includeiostream#includesys/types.h#includesignal.h#includeunistd.hvoidHander(intx){std::cout捕捉到信号xstd::endl;}intmain(){for(inti1;i32;i)signal(i,Hander);for(inti1;i32;i){kill(getpid(),i);sleep(1);}return0;}2.4.2系统调用raise发送信号专门给自己发送信号的系统调用。raise(sig) 等价于kill(getpid(),sig);#includeiostream#includesys/types.h#includesignal.h#includeunistd.hvoidHander(intx){std::cout捕捉到信号xstd::endl;}intmain(){for(inti1;i32;i)signal(i,Hander);for(inti1;i32;i){raise(i);sleep(1);}return0;}2.4.3系统调用abort发送信号注意是abort不是about异常终止当前进程并产生一个 Core Dump如果系统允许abort() 会向自己发送 SIGABRT 信号。我们发现Hander无法组织abort结束进程。为什么因为POSIX 对 abort() 有明确规定如果 SIGABRT 被捕获并且处理函数返回abort() 必须仍然终止进程。Gemini对此提供了一个不错的解释还没有讲完下一篇将带大家了解另外两种信号产生机制好的本期内容就到这里如果对你有帮助还不要忘记点赞三联支持。我是此方我们下期再见。bye! Linux、C、算法持续连载中欢迎关注WeChat Official Account 【此方的技术栈】。