0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

为什么Ctrl-C会导致当前运行程序退出呢?

dyquk4xk2p3d 来源:ytcoode 2023-09-08 09:23 次阅读

0x01 问题演示

下面是用rust写的一段测试程序,逻辑非常简单,就是读取用户输入,然后将其输出。

c8b0d366-4dd7-11ee-a25d-92fbcf53809c.png

运行这个程序,然后按Ctrl-C:

c8c6ff06-4dd7-11ee-a25d-92fbcf53809c.png

由上图可见,该程序没有收到任何输入,当然也没有任何输出,这个程序就退出了。

为什么Ctrl-C会导致当前运行程序退出呢?

0x02 程序退出原因

上面的测试程序之所以会退出,是因为Ctrl-C会告诉linux内核,让其发送SIGINT信号给当前运行程序,该信号的默认行为是杀掉目标进程,所以就有了上面的现象。

但是,SIGINT信号,以及其他的各种信号,都是可以捕获的,这样我们就可以修改信号的默认行为,比如将SIGINT信号的默认行为,修改成输出一些日志,而不是杀掉当前进程。

将上面的测试代码改成下面的样子:

c8ddab8e-4dd7-11ee-a25d-92fbcf53809c.png

上图中我们捕获了SIGINT信号,并且在收到该信号后,输出 got SIGINT。

运行看下:

c8f71c68-4dd7-11ee-a25d-92fbcf53809c.png

这次再按Ctrl-C,程序就不会退出了,而且还会输出 got SIGINT。

由上可见,Ctrl-C导致程序退出,确实是因为该按键使内核发送了SIGINT信号到目标进程,进而导致目标进程被杀死。

那为什么Ctrl-C会触发SIGINT信号呢?

在回答这个问题之前,我们要先了解下terminal emulator,即终端模拟器,的运行机制。

0x03 Terminal Emulator的运行机制

我当前使用的terminal emulator为 alacritty,后面如果涉及到terminal emulator的源码分析,就是基于这个项目。

当然,以下讲的terminal emulator的运行机制,对于其他terminal emulator也同样适用。

当我们在图形化界面,打开一个terminal emulator时,terminal emulator会调用openpty函数,向linux内核申请一个pty数据通道,当该pty数据通道创建成功后,linux内核会返回两个文件描述符,即两个fd,给terminal emulator,这两个fd,就代表了新创建pty数据通道的两端,分别为master端和slave端。

当向master端的fd写数据时,该数据就可以从slave端的fd读出来,当向slave端的fd写数据时,该数据就能从master端的fd读出来。

当pty数据通道创建完毕后,terminal emulator就会调用fork函数,启动一个子进程,该子进程用来运行shell程序,比如bash、zsh等,同时会将该shell的标准输入,标准输出,标准错误输出,都设置为上面通过openpty函数获取的slave端的fd。

shell在启动成功后,会一直等待着从标准输入,即slave fd,里接收要执行的命令。

当terminal emulator启动成功后,我们就可以在其内部输入命令了,我们输入的命令,会被terminal emulator写入到master fd里,这样在shell子进程中,就可以通过标准输入,即slave fd,接收到这个命令,并开始执行。

shell在执行接收到的命令时,也是通过fork函数,创建一个子进程,然后在子进程里执行该命令对应的程序。

不过,这里需要注意的是,子进程中运行的命令程序,其标准输入,标准输出、标准错误输出都是继承自shell,即它的标准输入,标准输出、标准错误输出的值都是slave fd。

这样,当命令程序写日志到标准输出时,其实际上是写到了slave fd里,如此,在terminal emulator里,就可以通过master fd,读取到这些日志信息,并在terminal emulator里显示出来。

同样的道理,当我们此时在terminal emulator里输入内容时,该内容会被terminal emulator写入到master fd,进而就可以被命令程序进程,从标准输入,即slave fd,里读出来。

这里大家可能会有个疑问,即shell进程和当前运行的命令程序进程,他们的标准输入都是slave fd,那为什么我们写入到terminal emulator里的内容,是被命令程序进程读出来,而不是被shell进程读出来呢?

这个就涉及到terminal emulator使用权的概念了。

当shell进程刚启动成功后,terminal emulator的使用权自然是shell的,此时我们在terminal emulator里输入的内容,会被shell从标准输入,即slave fd,里读出来。

当shell启动一个子进程,并用该进程运行命令程序时,它会把terminal emulator的使用权,转交给该命令程序进程,此时我们在terminal emulator里输入的内容,会被该命令程序进程从它的标准输入,即slave fd,里读出来的。

当该命令程序进程退出后,linux内核会通知shell进程,告知它启动的子命令进程已经结束了,此时shell会把terminal emulator的使用权转回给自己,进而shell又可以开始从terminal emulator接收新的命令了。

下面我们来看一些具体的例子:

c90ce0de-4dd7-11ee-a25d-92fbcf53809c.png

上图是用ps命令输出的信息,看图中的选中行,第一行为alacritty进程,即我们最开始启动的terminal emulator,第二行为alacritty启动的子进程,在该子进程中,运行的是bash程序,第三行为bash启动的子进程,在该子进程中,运行的是我们文章最开始时使用的测试程序hello。

这三个进程的层级关系,和我们上面的描述是一致的。

我们再来看下alacritty启动bash子进程的相关代码:

c92467ae-4dd7-11ee-a25d-92fbcf53809c.png

上图中make_pty函数内会调用openpty函数获取master fd和slave fd,在获取到master fd和slave fd后,slave fd被赋值到builder的stdin, stdout, stderr里,这样,在下面执行builder.spawn函数启动shell子进程时,其标准输入、标准输入、标准错误输出就都指向slave fd了。

另外,在上图中,master fd被保存到了Pty里,并和其他信息一起返回给该函数的调用方,这样,alacritty如果想要发送数据给shell时,就从Pty里获取到master fd,然后将数据写入到master fd里就好了。

0x04 Ctrl-C是如何处理的

上面我们讲过,我们在terminal emulator中输入的内容,会被terminal emulator写到内核的pty数据通道中,进而这些数据会被转发给shell进程,或者是在shell中运行的子进程。

那在terminal emulator里按Ctrl-C,也是这么处理的吗?

首先,Ctrl-C确实是被当作一个字符来处理的,且terminal emulator在接收到这个字符后,会直接写入到内核的pty数据通道,并不做特殊处理。

但是,在内核的pty数据通道里,有一个组件叫做 line discipline,它会检查要被传输的字符,如果字符流中包含Ctrl-C,它就会把Ctrl-C这个特殊字符从字符流中移除掉,并生成一个SIGINT信号,发送给目标进程。

如果目标进程没有捕获该信号,内核就会执行该信号的默认行为,即杀掉目标进程。

以下是生成SIGINT信号的内核代码:

c944312e-4dd7-11ee-a25d-92fbcf53809c.png

上图中,光标所在行就是在判断该字符是否是Ctrl-C,如果是,则发送SIGINT信号给目标进程。

由上图我们还可以看到,其实不止Ctrl-C这个特殊字符会转化成信号,QUIT字符Ctrl-,SUSP字符Ctrl-Z等,都会被转化成对应的信号。

0x05 精致全景图

以上讲了很多理论,下面我们来画一幅图,来梳理下完整流程。

wKgaomT6eBWAEzM5AABvYuVspz8583.jpg

首先,在terminal emulator启动成功后,我们在其中输入./hello命令,该命令沿着terminal emulator中的第一个输出箭头,即第一条虚线,经由内核pty数据通道,到达bash进程的stdin。

然后,bash从标准输入中读取到要执行的命令./hello,fork一个新的子进程,并在子进程中开始执行hello程序,此时bash也把terminal emulator的使用权交给了hello进程。

hello程序在开始运行后,就尝试从标准输入中读取数据。

接着,我们在terminal emulator中再输入hello world,该数据会沿着terminal emulator中的第二个输出箭头,即第一条实线,经由pty数据通道,到达hello进程的stdin。

hello进程从标准输入中读到hello world字符串,然后直接将其写入到了标准输出,该数据又经由内核pty数据通道,到达terminal emulator的master fd端。

terminal emulator从master fd中读取到hello world字符串,并将其显示在界面中。

hello进程在写完hello world字符串后,自己主动退出,bash检测到hello进程退出后,又把terminal emulator的使用权转回给自己。

bash写命令提示符>到标准输出,该数据再经由pty数据通道到达terminal emulator的master fd端。

terminal emulator从master fd中读取到bash的命令提示符,并将其显示在界面上,提示用户可以输入下一条命令了。

以上就是在terminal emulator中执行hello程序的完整流程。

从上图中我们还可以看到,假设我们在terminal emulator中按Ctrl-C,该数据在到达内核pty数据通道时,line discipline组件会将其转换成SIGINT信号,并发给目标进程。

这个也解答了我们此篇文章的疑问,现在你应该豁然开朗了吧。

另外,内核中 line discripline 组件的能力也是可以被修改的,比如我们可以修改成按Ctrl-B触发SIGINT信号,甚至是直接关闭SIGINT信号的生成,具体方式,可以查看stty命令的man文档。







审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 触发器
    +关注

    关注

    14

    文章

    2000

    浏览量

    61142
  • 模拟器
    +关注

    关注

    2

    文章

    875

    浏览量

    43218
  • LINUX内核
    +关注

    关注

    1

    文章

    316

    浏览量

    21646
  • Shell
    +关注

    关注

    1

    文章

    365

    浏览量

    23364
  • rust语言
    +关注

    关注

    0

    文章

    57

    浏览量

    3009

原文标题:为什么Ctrl-C会中断当前运行程序

文章出处:【微信号:良许Linux,微信公众号:良许Linux】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Linux C语言获取当前程序名称的三种方式

    如果需要在程序内部获取当前运行程序的名称,可以使用如下 4 种方式:
    发表于 08-24 16:03 2238次阅读
    Linux <b class='flag-5'>C</b>语言获取<b class='flag-5'>当前程序</b>名称的三种方式

    这个程序怎样通过布尔量退出循环?

    本帖最后由 lazybear 于 2013-1-17 14:12 编辑 一旦运行程序 就无法退出了 怎么能够解决这个问题
    发表于 01-17 14:10

    运行程序出问题啦!!!

    我以前生成的运行程序都能正常运行,最近不知为什么生成的运行程序不是代码不全就是调用的时候会出现重置,有事调用程序都调不出来......我做了一个登入界面,然后登入到主
    发表于 07-29 15:52

    Ctrl-C在使用OGL的应用程序中不起作用是怎么样?

    我们有一个使用 OpenGL 的应用程序。该应用程序为一些信号(包括 SIGINT)设置了一个信号处理程序。我们注意到 Ctrl-C 信号处理程序
    发表于 03-27 08:08

    步进电机加速-匀速-减速运行程序(C51源程序)

    步进电机加速-匀速-减速运行程序(C51) ME300系列单片机开发系统+步进电机模块演示程序 功能:
    发表于 12-28 10:10 6951次阅读

    步进电机加速-匀速-减速运行程序(ASM)

    步进电机加速-匀速-减速运行程序(ASM) 功能:    步进电机以加速方式启动,转速达到程序规定的最快速度后保持一段时间
    发表于 12-28 10:12 5033次阅读

    为什么区块链1.0不能运行程序

    那么,DAPP究竟是什么?在区块链上运行程序到底是怎么一回事? 在之前我们说到:BTC用区块链记账和使用UTXO,而ETH用区块链运行程序代码和使用账户余额制。 区块链2.0, 是可编程区块链,我们可以在区块链2.0上面,
    发表于 11-25 11:45 950次阅读

    关于MCU怎么在扩展的SDRAM上运行程序

    MCU怎么在扩展的SDRAM上运行程序
    的头像 发表于 03-01 13:17 2203次阅读
    关于MCU怎么在扩展的SDRAM上<b class='flag-5'>运行程序</b>?

    在STVDCOSMIC在RAM中运行代码stm8 ram中运行程序

    在STVDCOSMIC在RAM中运行代码stm8 ram中运行程序(电源技术期刊主编)-在STVDCOSMIC在RAM中运行代码stm8 ram中运行程序         
    发表于 09-17 17:12 12次下载
    在STVDCOSMIC在RAM中<b class='flag-5'>运行</b>代码stm8 ram中<b class='flag-5'>运行程序</b>

    vcu-ctrl-sw里decoder的退出机制

    有工程师询问vcu-ctrl-sw里decoder的退出机制。 下面的内容,根据vcu-ctrl-sw 2020.2分析。
    发表于 08-02 17:39 583次阅读

    Linux下运行后台任务的几种方法

    程序员最不能容忍的是在使用终端的时候往往因为网络,关闭屏幕,执行CTRL+C等原因造成ssh断开造成正在运行程序退出,使得我们的工作功亏一篑。
    的头像 发表于 12-05 11:13 849次阅读

    linux中防止运行程序退出的几种执行方法

    其背后的主要原因在于上述的相关操作,shell默认会发送中断信号给该终端session关联的进程,从而导致进程跟随终端退出,为了弄清这个问题我们首先要了解两种中断信号:
    发表于 07-03 11:37 2427次阅读

    OOM导致JVM虚拟机退出

    熟悉Java开发的人,应该会经常遇到的异常:OOM,那么这个异常会导致 JVM 虚拟机退出吗? 1、结论 Java虚拟机(JVM)在运行Java应用时,可能遇到内存不足的情况,从而抛
    的头像 发表于 09-30 10:14 794次阅读

    运行c程序的基本步骤

    运行C程序的基本步骤可以分为以下四个主要步骤:编写程序、编译程序、链接程序
    的头像 发表于 11-27 16:21 5638次阅读

    idea如何多次运行程序

    在计算机编程的世界中,我们通常需要多次运行程序来测试其性能、调试错误或者进行大规模的数据处理。但是有些编程初学者可能困惑于如何多次运行程序的问题。本文将会详细介绍如何多次运行程序的不
    的头像 发表于 12-06 14:59 1495次阅读