当你在BASH中按下Ctrl-C时会发生什么?(提示,它不仅仅是发送一个SIGINT信号)

11

首先介绍一下背景-在我使用apt-get install从公司的互联网下载时,前10秒钟左右会提供高速(400-500KB/s),然后速度就会降到十分之一(40-50KB/s),几分钟后就会变得非常慢(4-5KB/s)。这让我想到系统管理员已经实施了某种网络限制方案。

现在我知道网络不仅仅是不稳定的,因为如果我开始一个apt-get install foo,并在10秒钟后按Ctrl-C,然后立即再次运行apt-get install foo(通过使用bash历史记录中的向上箭头和输入键),并且重复这个过程几分钟直到所有的软件包都被下载完成,我甚至可以快速下载大型软件包。特别地,即使使用Ctrl-C中止下载,apt-get似乎也能够在下一次调用中恢复下载。

当然,盯着屏幕每10秒钟做Ctrl-C Up Enter变得非常无聊,所以我写了一个shell脚本-

#!/bin/sh
for i in `seq 1 100` ; do
    sudo apt-get install foo -y &
    sleep 10
    sudo kill -2 $!
done

这似乎能够工作。它启动apt-get,运行10秒钟,然后通过发送SIGINT来杀死它,然后再次启动它。然而,它并不能真正地工作,因为现在在随后的调用中apt-get不会恢复下载!

作为一个实验,我从一个终端中运行sudo apt-get install foo,然后从另一个终端中运行kill -2 <PID of apt-get>. 即使在这种情况下,当我重新启动apt-get时,它仍不会恢复下载。

所以很明显Ctrl-C并不等同于SIGINT。当我手动做Ctrl-C时,还有其他事情发生了,给apt-get一个保存下载状态的机会。问题是-这是什么?

编辑

迄今为止我收到的建议如下,但都无法解决问题:

  1. sudo kill -2 $!上,信号可能被发送到sudo而不是apt-get。这不是原因,因为如上所述,我也尝试了专门向apt-get的PID发送SIGINT,即使那样也会阻止apt-get保存其状态。

  2. Sudo捕获信号并向apt-get发送其他信号。我尝试了向apt-get发送所有我能想到的信号!它仍然不能恢复下载。只有在我Ctrl-C手动杀死时,它才会恢复下载。

  3. 如果来自脚本而不是交互式shell,则apt-get会以不同的方式处理SIGINT。再次,“实验”证明这不是真的。


有人可能会推测apt-get安装了一个SIGINT处理程序,该处理程序只是根据它所运行的是否为交互式 shell,以不同的方式处理其信号... - Linus Kleen
那肯定不止这个问题,因为在一个shell中运行apt-get,在另一个shell中杀死它仍然应该允许它捕获SIGINT并执行任何必要的操作以保存下载状态。 - Anupam Jain
3个回答

5

好的,谜团解决了!感谢印度Linux用户组提供的帮助。

这里的答案有两个方面 -

首先,apt-get调用另一个名为http的程序来下载数据。

[~] ➔ file /usr/lib/apt/methods/http

/usr/lib/apt/methods/http: ELF 32-bit LSB executable, Intel 80386, version 1
(SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15,
stripped

请注意,这是一个可执行文件,甚至不是脚本,可能是为了在系统安装过程中支持下载文件,当时perl/python/ruby等都还不可用。
其次,在运行apt-get后按Ctrl-C时,SIGINT被发送到http而不是apt-get。当http接收到SIGINT时,它会保存下载状态然后关闭。
下面是完美工作的更新脚本-
#!/bin/sh
for i in `seq 1 100` ; do
    sudo apt-get install foo -y &
    sleep 10
    sudo kill -2 `ps -ae | grep " http" | awk '{print $1}'`
done

5
提示,它不仅仅是发送一个SIGINT信号。是的,只是发送一个SIGINT信号 :-) 减速是正在发生的事情,你说得对。我猜测正在发生以下情况:
某些东西正在限制连接的带宽。为了跟踪连接,它还包括源端口(在我看来是个坏主意)等其他参数。
当你杀死apt-get并重新启动它时,它自然会获得一个新的TCP源端口,而减速你的邪恶实体会想:“哦,这是一个新的连接”,实际上确实是这样。
那么如何加快速度?好的解决方案是使用多个并行下载。我从未使用过它,但我听说过一个叫做“apt-fast”的工具(实际上是一个bash脚本),它做了类似的事情。
编辑:再次阅读问题后,我怀疑信号没有被发送给apt-get。
sudo apt-get install foo -y &
sudo kill -2 $! # sends signal to sudo, which sends whatever it wants to `apt-get`

因此,我相信 sudo 捕获了信号并向 apt-get 发送了其他信号(sigterm?sighup?)。


那么为什么在使用bash历史记录时,按上箭头和回车键下载会恢复呢? - Thaddee Tyl
因为它正在启动一个新进程,选择一个新的源端口,建立一个新的TCP连接,以及一个新的“节流”启动时间。请再次阅读我的答案 :-) - cnicutar
1
我的意思是,apt-get 是否在某个地方存储它下载的缓存?它通常会恢复下载吗?我不知道,因为我不经常使用它。我想知道为什么从另一个终端启动时它不会恢复下载。 - Thaddee Tyl
@Thaddee Tyl,我不知道apt-get如何恢复下载(说实话我也不关心),但我怀疑一个新的TCP连接是所有问题的原因。 - cnicutar
@Anupam Jain,抱歉,我的眼睛看到“apt-get特定的东西”就停止阅读了。只有标题是误导性的,我承认。不过,我不明白为什么要投反对票 :-/ 也许你应该再次阅读你问题的标题? - cnicutar
显示剩余6条评论

0

好的,就像所说的那样,这只是发送了一个SIGINT信号。 现在,关键在于以下内容:

我怀疑信号没有发送到apt-get

这是真的。让我解释一下。

运行sudo foo会启动一个sudo进程,然后(在你输入密码之后)调用foo;它的参数。一旦sudo接受密码并调用foo,你就进入了foo的“空间”。在等待foo完成时所做的任何操作都是在foo上执行的,而不是在sudo上执行的。

因此,在等待apt完成其工作时发送CtrlC,该信号将被发送到apt

现在,如果您启动sudo,它的pid将存储在$!变量中。向该pid / var发送kill信号将发送kill信号到sudo,而不是稍后由sudo启动的apt。如果您想向apt发送kill信号,则可能需要使用pidof实用程序。

自行测试:

sudo do-something
echo $!
pidof do-something

输出应该是两个不同的pid
希望这能有所帮助。


你所说的可能是一个因素。这就是为什么我在我的问题中提到的进行了“实验”的原因。即使我获得了apt-get的PID(而不是sudo的PID),并从另一个终端kill它,它仍然会阻止apt-get保存状态。显然还有其他问题。 - Anupam Jain

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接