操作系统自用5 $$ trap kill sleep wait命令实操

OS Programme Lecture #5

1. Scripting for Process Management

(1) BASH - Process pause/continuation with the kill command

在第一个控制台中运行脚本loop.sh

切换到第二个控制台,找到loop.sh进程的PID

我们现在用kill来中止进程,kill -STOP PID

回到第一个控制台,此时loop进程中止,非终止,仍然在就绪状态READY STATE

切换到第二个控制台,ps aux | grep bash,可以看到loop进程没有消失

要继续该进程,kill -CONT PID

最后用kill PID终止执行

(2) BASH - Search and kill process

在第一个控制台中打开一个应用程序,例如网页浏览器firefox,输入firefox

切换到第二个控制台,搜索firefox的进程PID,kill该进程

(3) Processes in Windows

回到Windows系统中,同样写一个脚本实现无限循环,loop.ps1,参考代码如下:

$num=1

while ($true){

$square=$num*$num

echo "$num $square"

$num+=1

}

echo "Programme completed ..."

在第一个控制台中执行脚本loop.ps1

打开一个新的控制台,尝试查找loop进程的PID,Get-Process -name power*,记录该进程PID

在这个控制台中,终止该进程,Stop-Process PID

看一下脚本是否被终止?

2. Parent and Children Processes

在BASH脚本里,我们可以通过$$获得一个进程的PID,例如echo $$,用ps aux | grep bash命令行查看与echo $$输出的PID是否有一致的?

编写以下脚本test.sh:

#!/bin/bash

trap "echo signal received ..." SIGINT

echo "The script PID is $$"

sleep 30

用whatis sleep查看sleep的意思

在控制台运行该脚本,查看最终输出情况

在控制台运行该脚本,用Ctrl+C终止该进程,查看输出情况

在控制台运行该脚本,在另一个控制台用kill终止该脚本,查看输出情况

为什么输出会不一样?!

回答这个问题前,必须了解一下什么是进程组(process groups),前台(forground)和后台(background)任务!

同一个进程组的进程共享一个pgid(process group id)

当进程组的成员创建子进程时,该进程成为同一进程组的成员。每个进程组都有一个领导者;我们可以很容易地识别它,因为它的 pid 和 pgid 是相同的!

我们可以使用 ps 命令可视化正在运行的进程的 pid 和 pgid。命令的输出可以自定义,以便只显示我们感兴趣的字段:在本例中为 CMD、PID 和 PGID。我们通过使用 -o 选项来做到这一点,提供一个逗号分隔的字段列表作为参数:

$ps -a -o pid,pgid,cmd

如果我们执行test.sh,在脚本运行期间在另一个控制台$ps -a -o pid,pgid,cmd,观察结果!

结论:主进程的sleep 30创建了一个子进程,两个进程在一个组!

当我们在启动test.sh脚本的终端上按下 CTRL-C 时,信号不仅发送到父进程,而且发送到整个进程组。

哪个进程组?终端的前台进程组。该组的所有进程成员都称为前台进程,所有其他进程都称为后台进程。

#前台和后台进程#:

为了便于实现作业控制的用户界面,操作系统维护了当前终端进程组 ID 的概念。

该进程组的成员(进程组 ID 等于当前终端进程组 ID 的进程)接收键盘生成的信号,例如 SIGINT。

这些进程在前台。后台进程是那些进程组 ID 与终端不同的进程;这样的进程不受键盘产生的信号的影响。

相反,当我们使用 kill 命令发送 SIGINT 信号时,我们只针对父进程的 pid;

当 Bash 在等待程序完成时收到信号时,它会表现出一种特定的行为:该信号的“陷阱代码”在该进程完成之前不会执行。

这就是为什么只有在 sleep 命令退出后才会显示“收到信号”消息的原因。

要出现当我们使用 kill 命令在终端中按 CTRL-C 发送信号时发生的情况,我们必须以进程组为目标。

$kill -2 -(pid of the process leader)

3. 从脚本内部管理信号传播

考虑一个问题:现在,假设我们从非交互式 shell 启动一个长时间运行的脚本,并且我们希望所述脚本自动管理信号传播,

以便当它接收到诸如 SIGINT 或 SIGTERM 之类的信号时,它会终止其可能长时间运行的子脚本,

最终执行一些清理退出前的任务。我们如何做到这一点?

就像我们之前所做的那样,我们可以处理在陷阱中接收到信号的情况;

然而,正如我们所见,如果在 shell 等待程序完成时收到信号,则“陷阱代码”仅在子进程退出后才会执行。

这不是我们想要的:我们希望在父进程收到信号后立即处理陷阱代码。

为了实现我们的目标,我们必须在后台执行子进程:我们可以通过在命令后放置 & 符号来实现。

稍微改写test.sh脚本:

#!/bin/bash

trap "echo signal received ..." SIGINT

echo "The script PID is $$"

sleep 30 &

观察程序输出,$ps -a -o pid,pgid,cmd

这样写仍然有一个问题:父进程将在执行 sleep 30 命令后立即退出,让我们没有机会在它结束或被中断后执行清理任务。

我们可以使用shell内置的wait来解决这个问题。

#等待#:

等待由 ID 标识的每个进程,该 ID 可以是进程 ID 或作业规范,并报告其终止状态。

如果未给出 ID,则等待所有当前活动的子进程,返回状态为零。

脚本改写如下:

#!/bin/bash

trap "echo signal received ..." SIGINT

echo "The script PID is $$"

sleep 30 &

wait $!

在我们设置一个进程在后台执行后,我们可以将$!作为参数传递给 wait 让父进程等待它的子进程。

但是还有一个问题:接收到在脚本内部的陷阱中处理的信号,导致等待内置函数立即返回,而不是实际等待后台命令的终止。

最终我们可以改变脚本如下:

#!/bin/bash

cleanup() {

echo "cleaning up..."

# Our cleanup code goes here

}

trap 'echo signal received!; kill "${child_pid}"; wait "${child_pid}"; cleanup' SIGINT SIGTERM

echo "The script pid is $"

sleep 30 &

child_pid="$!"

wait "${child_pid}"

在脚本中,我们创建了一个清理函数,我们可以在其中插入清理代码,并使我们的陷阱也捕获 SIGTERM 信号。

以下是当我们运行此脚本并向其发送这两个信号之一时发生的情况:

(1) 启动脚本并在后台执行 sleep 30 命令;

(2) 子进程的pid“存储”在child_pid变量中;

(3) 脚本等待子进程终止;

(4) 脚本接收到 SIGINT 或 SIGTERM 信号;

(5) wait命令立即返回,不等待child终止。

此时陷阱trap被执行:

(1) SIGTERM 信号(kill 默认值)被发送到 child_pid;

(2) 我们等待以确保子进程在收到此信号后终止;

(3) 等待返回后,我们执行清理功能。

此为广大操作系统实践课课件总结,版权归广大所有

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值