Linux 无法停止含有timeout命令的脚本

使用timeout命令时,遇到了一个问题,在此记录一下。

执行bash exec_test.sh指令后,Ctrl + C无法中断程序执行。

下面是exec_test.sh的内容:

# exec_test.sh
timeout 5s bash test.sh

test.sh的内容如下:

# test.sh
sleep 100

这是因为,Ctrl + C的信号是发送给前台进程组(foreground process group)的,而timeout命令所在的进程组不属于前台进程组,因此收不到相关信号。

  • 进程组(process group:在兼容POSIX的操作系统中,进程组表示一个或多个进程的集合。进程组通常用于控制信号的分发;当信号被发送到进程组时,组内每个进程都会收到该信号。
    • 管道两端的进程属于同一个进程组。
    • 通过fork派生的子进程,和父进程属于同一个进程组。
    • 对于执行了exec调用的进程,新进程和旧进程处于相同的进程组。
    • 以后台形式启动的进程,拥有其独立进程组。
  • 前台进程组:正在运行,并且正在接收终端输入的进程组/作业,可以通过键盘直接向其发送信号。
  • 后台进程组:正在运行,但是不能直接从终端接收输入的进程组/作业。对于尝试从终端读取输入或将输出写入终端的后台进程,shell会向它们发送SIGTTIN和SIGTTOU信号,进程收到信号后会暂停,并且返回读写错误。
  • 作业(job:在shell中,一个进程组就是一个作业。shell对作业的控制就是作业调度,其中包括挂起(suspend)、继续(resuming)、终止(terminate)等基本操作。此外,可以向作业发送信号,实现更高级的操作。shell通过作业调度,来决定哪些作业在前台,哪些在后台。
  • 会话(session:一个会话包含一个或多个进程组。有新用户登录到Linux,登录进程会为用户创建一个会话。登录进的shell就是会话的首进程。
    • 进程不能创建属于另一个会话的进程组;进程也不能假如属于另一个会话的进程组(即不能迁移到另一个会话)。

通过源码可知,timeout进程会将自己放到一个新的进程组中,进程组id(pgid)和自己的pid相同。

/* Ensure we're in our own group so all subprocesses can be killed.
   Note we don't just put the child in a separate group as
   then we would need to worry about foreground and background groups
   and propagating signals between them.  */
setpgid (0, 0);

这是为了让timeout进程的后代进程都在同一个进程组中,超时的时候可以向每个进程发送终止信号。同时,没有和父进程处于同一个进程组,timeout进程发送信号不会影响父进程继续执行。

因此,回到一开始的问题。Ctrl + C产生的信号发送给了前台进程组,也就是bash exec_test.sh所在的进程组,而timeout并不属于这个进程组,所以不会终止执行。

要使Ctrl + C产生的信号能够发送给timeout进程组,可以使用以下方法:

#!/bin/sh
trap 'kill -INT -$pid' INT
timeout 5s bash test.sh
pid=$!
wait $pid

在前台进程设置trap命令,捕获到中断信号SIGINT就向pgid=pid的进程组发送中断信号(因为timeout进程所在的进程组的pgid等于自身的pid),这样timeout进程组就能收到中断信号。pid变量的值为$!,即shell最后运行的后台进程的pid。

timeout原理

使用timeout的时候,好奇它的核心机制是怎么实现的,查看源码,发现使用了内核的alarm接口,详情见man alarm

NAME
       alarm - set an alarm clock for delivery of a signal

SYNOPSIS
       #include <unistd.h>

       unsigned int alarm(unsigned int seconds);

DESCRIPTION
       alarm() arranges for a SIGALRM signal to be delivered to the calling process in seconds seconds.

       If seconds is zero, any pending alarm is canceled.

       In any event any previously set alarm() is canceled.

RETURN VALUE
       alarm()  returns the number of seconds remaining until any previously scheduled alarm was due to be delivered, or zero if
       there was no previously scheduled alarm.

CONFORMING TO
       POSIX.1-2001, POSIX.1-2008, SVr4, 4.3BSD.

NOTES
       alarm() and setitimer(2) share the same timer; calls to one will interfere with use of the other.

       Alarms created by alarm() are preserved across execve(2) and are not inherited by children created via fork(2).

       sleep(3) may be implemented using SIGALRM; mixing calls to alarm() and sleep(3) is a bad idea.

       Scheduling delays can, as ever, cause the execution of the process to be delayed by an arbitrary amount of time.

参考

Why can’t I kill a timeout called from a Bash script with a keystroke? - Unix & Linux Stack Exchange
coreutils/timeout.c at master · coreutils/coreutils · GitHub
Process group - Wikipedia
Job control (Unix) - Wikipedia
linux之进程的基本概念(进程,进程组,会话关系)_草上爬的博客-CSDN博客_进程组
bash - Execute a shell function with timeout - Stack Overflow

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值