进程与作业


Linux 启明道场.

计算机通过执行软件来完成任务。 在现代计算环境中,软件分为两类:操作系统内核(我们会在后面的课程中学习)和本模块要讨论的进程。 Linux 启动时,会先启动一个 init 进程(init 是 initializer 的缩写,意为初始化程序)。它接着启动一批其他进程,这些进程又启动更多进程,直到你最终看到命令行 shell——shell 本身也是一个进程! shell 当然也会根据你输入的命令启动进程。

本模块将带你学习多种查看进程和与进程交互的有趣方式!


首先,我们来学习用 ps 命令列出正在运行的进程。 对于 ps 的含义,有人认为是 "process snapshot"(进程快照),也有人认为是 "process status"(进程状态);它的作用就是列出进程。 默认情况下,ps 只列出当前终端中运行的进程,说实话,这样的用处不大:

hacker@dojo:~$ ps
    PID TTY          TIME CMD
    329 pts/0    00:00:00 bash
    349 pts/0    00:00:00 ps
hacker@dojo:~$

上面的例子列出了 shell(bash)和 ps 进程本身,这就是该终端中运行的所有进程。 我们还能看到,每个进程都有一个数字标识符(进程 ID,简称 PID),用于在 Linux 环境中唯一标识一个正在运行的进程。 输出还显示了命令运行所在的终端(本例中为 pts/0),以及进程到目前为止消耗的CPU 时间总量(这些进程的计算需求很低,消耗的时间连 1 秒都不到!)。

多数情况下,直接运行 ps 只能看到这些信息。 要让它更有用,我们需要传入一些参数。

ps 是一个历史悠久的工具,它的用法也因此有些杂乱。 指定参数有两种方式。

“标准”语法:在这种语法中,可以用 -e 列出所有进程,用 -f 以完整格式输出,包括命令的参数。 这两个选项可以合并为一个参数 -ef。

“BSD”语法:在这种语法中,可以用 a 列出所有用户的进程,用 x 列出没有在终端中运行的进程,用 u 生成便于用户阅读的输出。 这三个选项可以合并为一个参数 aux。

这两种方式,即 ps -ef 和 ps aux,输出略有不同,但显示的信息大体可以相互对应。

在 dojo 环境中试试看:

hacker@dojo:~$ ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
hacker         1       0  0 05:34 ?        00:00:00 /sbin/docker-init -- /bin/sleep 6h
hacker         7       1  0 05:34 ?        00:00:00 /bin/sleep 6h
hacker       102       1  1 05:34 ?        00:00:00 /usr/lib/code-server/lib/node /usr/lib/code-server --auth=none -
hacker       138     102 11 05:34 ?        00:00:07 /usr/lib/code-server/lib/node /usr/lib/code-server/out/node/entr
hacker       287     138  0 05:34 ?        00:00:00 /usr/lib/code-server/lib/node /usr/lib/code-server/lib/vscode/ou
hacker       318     138  6 05:34 ?        00:00:03 /usr/lib/code-server/lib/node --dns-result-order=ipv4first /usr/
hacker       554     138  3 05:35 ?        00:00:00 /usr/lib/code-server/lib/node /usr/lib/code-server/lib/vscode/ou
hacker       571     554  0 05:35 pts/0    00:00:00 /usr/bin/bash --init-file /usr/lib/code-server/lib/vscode/out/vs
hacker       695     571  0 05:35 pts/0    00:00:00 ps -ef
hacker@dojo:~$

这里可以看到用于初始化挑战环境的进程(docker-init)、为节省计算资源而在超时后自动结束挑战的进程(sleep 6h,6 小时后超时)、VSCode 环境的进程(几个 code-server 辅助进程)、shell(bash),以及刚刚运行的 ps -ef 命令。 使用 ps aux 看到的情况基本一样:

hacker@dojo:~$ ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
hacker         1  0.0  0.0   1128     4 ?        Ss   05:34   0:00 /sbin/docker-init -- /bin/sleep 6h
hacker         7  0.0  0.0   2736   580 ?        S    05:34   0:00 /bin/sleep 6h
hacker       102  0.4  0.0 723944 64660 ?        Sl   05:34   0:00 /usr/lib/code-server/lib/node /usr/lib/code-serve
hacker       138  3.3  0.0 968792 106272 ?       Sl   05:34   0:07 /usr/lib/code-server/lib/node /usr/lib/code-serve
hacker       287  0.0  0.0 717648 53136 ?        Sl   05:34   0:00 /usr/lib/code-server/lib/node /usr/lib/code-serve
hacker       318  3.3  0.0 977472 98256 ?        Sl   05:34   0:06 /usr/lib/code-server/lib/node --dns-result-order=
hacker       554  0.4  0.0 650560 55360 ?        Rl   05:35   0:00 /usr/lib/code-server/lib/node /usr/lib/code-serve
hacker       571  0.0  0.0   4600  4032 pts/0    Ss   05:35   0:00 /usr/bin/bash --init-file /usr/lib/code-server/li
hacker      1172  0.0  0.0   5892  2924 pts/0    R+   05:38   0:00 ps aux
hacker@dojo:~$

ps -ef 与 ps aux 有许多共同之处:两者都会显示用户(USER 列)、PID、TTY、进程启动时间(STIME/START)、累计消耗的 CPU 时间(TIME),以及命令(CMD/COMMAND)。 ps -ef 还会输出父进程 ID(PPID),也就是启动当前进程的那个进程的 PID;ps aux 则会输出当前进程占用的系统 CPU 和内存百分比。 此外还有一些其他信息,我们暂时不展开。

好了! 来练习一下。 在这一关中,我们再次将 /challenge/run 重命名为一个随机文件名,而且这次你无法用 ls 查看 /challenge 目录! 不过,我们也已经启动了它,所以你可以在运行中的进程列表中找到它,确定文件名,然后直接再次运行它来获取 flag! 祝你好运!

注意:ps -ef 和 ps aux 都会按照终端宽度截断命令列表(这也是上面的示例在屏幕右侧对齐得如此整齐的原因)。 如果无法看清进程的完整路径,你可能需要把终端加宽(或者把输出重定向到其他地方,以避免截断)! 也可以将 w 选项传入两次(例如 ps -efww 或 ps auxww),来禁用截断。

你已经学会启动进程、查看进程,现在要学习终止进程了! 在 Linux 中,这项操作使用名字颇具攻击性的 kill 命令完成。 使用默认选项时(本关只介绍默认用法),kill 会请求进程终止,让它有机会在退出前完成清理工作。

假设你在另一个终端中启动了一个碍事的 sleep 进程(sleep 只是等待命令行中指定的秒数,本例中是 1337 秒),如下所示:

hacker@dojo:~$ sleep 1337

怎样让它结束呢? 把进程标识符(即 ps 输出中的 PID)作为参数传给 kill,即可终止它:

hacker@dojo:~$ ps -e | grep sleep
 342 pts/0    00:00:00 sleep
hacker@dojo:~$ kill 342
hacker@dojo:~$ ps -e | grep sleep
hacker@dojo:~$

现在,来终止你的第一个进程吧! 在这一关中,只要 /challenge/dont_run 还在运行,/challenge/run 就会拒绝运行! 你必须找到 dont_run 进程,并用 kill 终止它。 如果任务失败,pwn.college 将否认曾知晓你的任务。 祝你好运。

你已经学会用 kill 命令终止其他进程,但有时你只想结束那个占着终端的进程! 好在终端提供了一个快捷键:Ctrl-C(也就是按住 Ctrl 键,再按 C)。它会向当前等待终端输入的应用发送“中断”信号,通常会使应用正常退出。

在这里试试看! 只有在你中断 /challenge/run 后,它才会给你 flag。 祝你好运!


如果想进一步了解,可以阅读这篇介绍终端和“控制码”的文章(例如 Ctrl-C)。

有时,行为异常的进程会干扰你的工作。 这些进程可能就需要被终止……

在这一关中,一个干扰进程占用了关键资源:位于 /tmp/flag_fifo 的命名管道(FIFO)。和管道练习模块中的 FIFO 挑战一样,/challenge/run 希望将 flag 写入这个管道。 你需要用 kill 终止这个进程。

大致步骤如下:

  1. 查看当前正在运行的进程。
  2. 在列表中找到 /challenge/decoy,并确定它的进程 ID。
  3. 用 kill 终止它。
  4. 运行 /challenge/run,不受干扰信息刷屏的影响,拿到 flag(无需重定向它的输出;它会自行写入 FIFO)。

祝你好运!


注意: 即使已经终止了干扰进程,你也可能还会看到几个假 flag。 这是因为 Linux 管道具有缓冲区:可以把管道想象成一段有长度的通道,数据在其中流动;终止干扰进程时,管道中可能仍有数据。 这些已经进入管道的数据会继续到达另一端,也就是你的 cat。 稍等片刻,你就会看到干扰输出停止,然后便可以运行 /challenge/run,完成挑战!

你已经学会用 Ctrl-C 中断进程,但还有更温和的方式可以让你重新使用终端! 你可以将进程暂停并放到后台,快捷键是 Ctrl-Z。 本关将介绍这一操作,而下一关会带你学习如何恢复这些被暂停的进程!

本关的 run 希望看到自己的另一个实例已经启动,并且使用同一个终端。 怎么做到呢? 先在终端中启动它,再将其暂停,然后在第一个实例仍处于暂停状态时,启动第二个实例!

通常,暂停进程之后,你会希望在某个时候恢复它。 否则,为什么不直接终止它呢? 为了恢复进程,shell 提供了内置命令 fg。它会恢复被暂停的进程,并将其重新放回终端的前台。

来试试看! 本关的 run 要求你先暂停它,再恢复它。 祝你好运!

你已经在前台恢复过进程,使用的是 fg 命令。 要在后台恢复进程,则可以使用 bg 命令! 这样既能让进程继续运行,又能让你重新使用 shell,在此期间执行更多命令。

本关的 run 希望看到自己的另一个实例正在运行,而不是处于暂停状态,并且使用同一个终端。 怎么做到呢? 先在终端中启动它,再将其暂停,然后让它在后台运行,这一步使用 bg。在第一个实例运行于后台时,再启动第二个实例!


深入探究: 如果你对更深入的细节感兴趣,可以看看如何区分进程是暂停在后台,还是正在后台运行! 下面演示一下。 首先,暂停一个 sleep 进程:

hacker@dojo:~$ sleep 1337
^Z
[1]+  Stopped                 sleep 1337
hacker@dojo:~$

sleep 进程现在已经被暂停在后台。 使用 ps 的 -o 选项显示 stat 列,就能看到这一点:

hacker@dojo:~$ ps -o user,pid,stat,cmd
USER         PID STAT CMD
hacker       702 Ss   bash
hacker       762 T    sleep 1337
hacker       782 R+   ps -o user,pid,stat,cmd
hacker@dojo:~$ 

看到那个 T 了吗? 它表示进程被我们用 Ctrl-Z 暂停了。 bash 的 STAT 列中的 S 表示 bash 正在休眠,等待输入。 ps 那一行中的 R 表示它正在运行,而 + 表示它位于前台!

看看在后台恢复 sleep 后会发生什么:

hacker@dojo:~$ bg
[1]+ sleep 1337 &
hacker@dojo:~$ ps -o user,pid,stat,cmd
USER         PID STAT CMD
hacker       702 Ss   bash
hacker       762 S    sleep 1337
hacker      1224 R+   ps -o user,pid,stat,cmd
hacker@dojo:~$

看! sleep 现在显示的是 S。 它正在执行等待操作,所以处于休眠状态,但并没有被暂停! 它也位于后台,所以没有 +。

假设一个进程正在后台运行,而你想继续与它交互。 该怎么做呢? 和恢复暂停的进程一样,你可以用 fg 将后台运行的进程切回前台! 这一关将带你练习这项操作!

当然,要让进程在后台运行,不一定非得先暂停它:你可以从一开始就在后台启动它! 操作很简单,只需在命令末尾加上 &,如下所示:

hacker@dojo:~$ sleep 1337 &
[1] 1771
hacker@dojo:~$ ps -o user,pid,stat,cmd
USER         PID STAT CMD
hacker      1709 Ss   bash
hacker      1771 S    sleep 1337
hacker      1782 R+   ps -o user,pid,stat,cmd
hacker@dojo:~$ 

这里,sleep 正在后台运行,并没有被暂停。 现在轮到你练习了! 在后台启动 /challenge/run,获取 flag!

每条 shell 命令,包括所有程序和内置命令,在运行结束并终止时,都会返回一个退出码。 shell 或 shell 的使用者(也就是你!)可以根据退出码,判断进程是否成功完成了任务(当然,如何判断成功,取决于这个进程原本应该做什么)。

特殊变量 ? 保存着最近结束的命令的退出码(读取它的值时,别忘了在前面加上 $!):

hacker@dojo:~$ touch test-file
hacker@dojo:~$ echo $?
0
hacker@dojo:~$ touch /test-file
touch: cannot touch '/test-file': Permission denied
hacker@dojo:~$ echo $?
1
hacker@dojo:~$

如你所见,执行成功的命令通常返回 0;失败的命令通常返回非零值,最常见的是 1,有时也会返回用于标识具体失败原因的错误码。

在这一关中,你必须取得 /challenge/get-code 返回的退出码,然后把这个错误码作为参数,运行 /challenge/submit-code。 祝你好运!


30天排行榜:

此排行榜反映本模块在道场上线后各挑战的解出情况。

排名 黑客 徽章 得分
暂无解题排行数据 —— 注册账号并完成挑战即可登上榜单!