[Linux]Processes
Processes
1 ps
进程是指在机器上运行的程序,由内核进行管理。每个进程都有一个与之关联的标识符,称为进程ID(PID)。PID按照进程创建顺序分配。
使用ps
命令可以查看当前正在运行的进程列表:
1 |
|
输出示例:
1 |
|
这里各列含义如下:
- PID:进程ID。
- TTY:与进程相关的控制终端。
- STAT:进程状态代码。
- TIME:总CPU使用时间。
- CMD:可执行文件或命令名称。
ps
命令有多种选项,不同风格(BSD、GNU或Unix)有不同的参数。其中,BSD风格较为常用。
使用以下命令获取更详细的进程信息:
1 |
|
a
:显示所有用户的全部进程。u
:显示关于进程的详细信息。x
:列出没有控制终端的进程,这些进程在TTY字段中显示为?
,通常为系统启动时自动运行的守护进程。
这将展示更多的字段,包括但不限于:
- USER:有效用户。
- %CPU:进程使用的CPU时间占总运行时间的比例。
- %MEM:进程使用的物理内存比率。
- VSZ:整个进程的虚拟内存使用量。
- RSS:常驻集大小,即任务已使用的非交换物理内存。
- START:进程启动时间。
使用top
命令实时监控
top
命令提供了一个动态的实时视图,显示系统中各个进程资源使用情况,默认每10秒刷新一次。这对于监控占用大量系统资源的进程非常有用。
1 |
|
2 终端类型与控制终端
在ps
命令的输出中,存在一个TTY字段,它表示执行命令的终端。终端分为两种类型:常规终端设备和伪终端设备。
常规终端设备 vs 伪终端设备
常规终端设备:指的是可以直接输入并发送输出到系统的本地终端设备。例如,通过按下Ctrl-Alt-F1组合键进入的第一个虚拟控制台(TTY1),这里仅显示终端,没有图形界面等。要退出此模式,可以使用Ctrl-Alt-F7组合键。
伪终端设备:是我们通常使用的终端模拟器,如shell窗口中的终端,标记为PTS(Pseudo Terminal Slave)。通过再次运行
ps
命令,您可以看到自己的shell进程位于pts/*之下。
控制终端及其重要性
一般而言,进程通常绑定到一个控制终端。例如,在shell窗口中运行如find
这样的程序时,如果关闭了该窗口,则相关进程也会随之终止。
然而,有些特殊进程,如守护进程(daemon),它们对于保持系统正常运行至关重要。这类进程通常在系统启动时开始运行,并在系统关机时终止。由于这些进程在后台运行且需要持续工作而不受用户操作的影响,因此它们不绑定到任何控制终端。在ps
命令的输出中,这类进程的TTY字段将显示为?
,表示没有控制终端。
3 深入理解进程
什么是进程?
进程是系统上运行的程序实例。具体来说,它是操作系统分配内存、CPU和I/O资源以使程序得以运行的结果。为了更好地理解这一点,您可以尝试以下实验:打开三个终端窗口,在其中两个窗口中运行cat
命令(不带任何选项),因为cat
命令等待标准输入(stdin),所以它将保持进程开启状态。然后,在第三个窗口中执行ps aux | grep cat
命令。您会发现有两个名为cat
的进程存在,尽管它们调用的是相同的程序。
内核的角色
内核负责管理所有进程。当我们运行一个程序时,内核会将程序代码加载到内存中,确定并分配必要的资源,并持续跟踪每个进程的状态。内核掌握的信息包括:
- 进程的状态
- 进程使用的资源及接收情况
- 进程的所有者
- 信号处理
- 以及其他相关信息
资源分配与管理
所有进程都在竞争系统资源,而内核的任务就是根据进程的需求合理分配这些资源。当一个进程结束时,它所占用的资源会被释放出来供其他进程使用。内核确保每个进程都能获得适当数量的资源,从而维持系统的高效运行。
4 进程创建与管理
创建新进程
当创建一个新进程时,现有进程使用所谓的“fork”系统调用(基本上复制了自己。Fork系统调用会生成一个几乎完全相同的子进程,该子进程将获得一个新的进程ID(PID),而原始进程则成为其父进程,并拥有一个称为父进程ID(PPID)的标识符。之后,子进程可以选择继续使用与其父进程相同的程序,但更常见的情况是使用execve系统调用来启动一个新程序。此系统调用会销毁内核为原进程设置的内存管理,并为新程序建立新的内存管理。
我们可以通过以下命令观察这一过程:
1 |
|
l
选项提供了一个“长格式”或更加详细的运行进程视图。您会看到一列标记为PPID的字段,这是父进程ID。
所有进程的起源
既然每个进程都需要一个父进程,并且它们都是通过相互克隆形成的,那么必然存在一个所有进程的源头。确实如此,当系统启动时,内核会创建一个名为init的进程,它的PID为1。除非系统关闭,否则init进程无法被终止。它以root权限运行,负责执行许多维持系统运作的进程。
5 进程终止与管理
终止进程
进程可以通过_exit系统调用退出,这将释放该进程占用的资源以供重新分配。当一个进程准备终止时,它会通过所谓的“终止状态”告知内核其终止原因。通常情况下,状态码0表示进程成功结束。然而,这还不足以完全终止一个进程。父进程必须使用wait系统调用来确认子进程的终止,并检查子进程的终止状态。
另一种终止进程的方式是通过信号,之后会讨论。
孤儿进程
如果父进程在子进程之前终止,内核会意识到不会收到对该子进程的wait调用。因此,这些子进程会被标记为“孤儿”,并由init进程接管。最终,init进程会对这些孤儿进程执行wait系统调用,以便它们能够被彻底清除。
僵尸进程
当子进程终止而父进程尚未调用wait时,会发生什么呢?内核会将其转换为僵尸进程。子进程使用的资源已被释放用于其他进程,但在进程表中仍保留有这个僵尸进程的条目。僵尸进程无法被杀死,因为它们实际上已经是“死亡”的状态,所以不能通过信号来消灭它们。最终,若父进程调用了wait系统调用,僵尸进程将会消失,这一过程被称为“收割”。如果父进程没有执行wait调用,init进程将收养这些僵尸进程,并自动执行wait来移除它们。过多的僵尸进程可能会占据进程表的空间,影响新进程的运行,因此这是应当避免的情况。
6 信号
信号是一种通知机制,用于告知进程某事件已经发生。
为何使用信号?
- 用户可以通过输入特定的终端字符(如Ctrl-C或Ctrl-Z)来终止、中断或挂起进程。
- 硬件问题出现时,内核可能需要通知相关进程。
- 软件问题发生时,内核也可能需要通知进程。
- 它们是进程间通信的一种方式。
信号处理过程
当某个事件产生一个信号后,该信号会被传递给相应的进程,并处于待处理状态直到被接收。如果进程正在运行,信号将会被传递。然而,进程可以通过设置信号掩码来阻止某些信号的传递。当信号被传递时,进程可以执行以下操作之一:
- 忽略该信号。
- “捕获”信号并执行特定的处理程序。
- 进程终止。
- 根据信号掩码阻塞信号。
常见信号
每个信号都由整数定义,并具有形式为SIGxxx的符号名。一些常见的信号包括:
- **SIGHUP (1)**:挂断
- **SIGINT (2)**:中断
- **SIGKILL (9)**:强制结束进程
- **SIGSEGV (11)**:段错误
- **SIGTERM (15)**:软件终止请求
- SIGSTOP:暂停进程
信号编号可能会有所不同,因此通常通过其名称引用。值得注意的是,某些信号无法被阻塞,例如SIGKILL,它将直接销毁进程。
7 kill
在Linux系统中,kill
命令用于向进程发送信号以终止其运行。例如:
1 |
|
这里的12445是目标进程的PID(进程标识符)。默认情况下,kill
命令发送的是SIGTERM
信号,请求进程正常终止并释放资源、保存状态。
也可以通过kill
命令指定发送不同的信号,如使用-9
选项发送SIGKILL
信号来强制结束进程:
1 |
|
这将立即杀死进程,不给予任何清理机会。
常见信号及其区别
尽管以下信号听起来相似,但它们之间存在显著差异:
- **SIGHUP (挂断)**:当控制终端关闭时发送给进程。例如,如果你关闭了一个正在运行某个进程的终端窗口,则该进程会收到一个SIGHUP信号。
- **SIGINT (中断)**:可通过按下Ctrl-C触发,通知系统尝试优雅地终止进程。
- **SIGTERM (软件终止)**:请求进程终止,并允许它进行必要的清理操作。
- **SIGKILL (强制终止)**:无条件地终止进程,不做任何清理工作,常被称为“强制杀死”。
- **SIGSTOP (暂停)**:停止或挂起进程的执行。
8 niceness
当计算机同时运行多个程序,如Chrome、Microsoft Word或Photoshop时,这些程序看似并行执行,但实际上它们是通过分时共享CPU来实现的。每个进程仅在短时间内——即所谓的时间片内使用CPU资源,然后暂停几毫秒以让位于其他进程。操作系统默认采用轮询调度算法分配时间片,确保所有进程均得到足够的处理时间。此过程由内核管理,并且大多数情况下能够高效地完成。
进程优先级与Nice值
进程无法自行决定何时以及多久可以占用CPU;若无干预,各进程大致会获得相等的CPU时间。然而,Linux系统允许用户通过设置“nice”值来影响内核的调度决策。“Nice”值决定了进程获取CPU资源的优先级:较高的数值意味着较低的优先级(更“友善”的进程),而较低甚至负数的值则表示较高优先级(较“不友善”的进程)。
查看进程的“nice”值可以通过top
命令:
1 |
|
在显示的结果中,“NI”列表示的就是各个进程的“nice”级别。
调整进程优先级
使用nice
和renice
命令可调整进程的优先级:
nice
命令用于启动新进程时设定其优先级:1
$ nice -n 5 apt upgrade
renice
命令则用于修改已存在进程的优先级:1
$ renice 10 -p 3245
9 进程状态
执行ps aux
命令后,在“STAT”列中可以看到各种状态代码,它们代表Linux进程中可能处于的不同状态。以下是常见的状态代码及其含义:
- R:运行或可运行状态。这意味着进程正在运行或等待CPU处理。
- S:中断休眠状态。进程正在等待某个事件完成,例如来自终端的输入。
- D:不可中断休眠状态。这些进程无法被信号杀死或中断,通常需要通过重启系统或解决问题来清除。
- Z:僵尸状态。这是指已终止但仍在等待其状态被收集的进程。
- T:已停止状态。表示进程已被挂起或停止。
10 /proc 文件系统
Linux中的进程与/proc文件系统
在Linux中,所有事物都被视为文件,这包括进程。进程信息存储在一个特殊的文件系统:/proc文件系统中。
执行以下命令列出/proc目录下的内容:
1 |
|
该目录下包含多个子目录,每个运行中的进程ID(PID)都有一个对应的子目录。通过查看ps
命令的输出,可以找到特定进程的PID,并在/proc目录下对应地访问其信息。
例如,要查看PID为12345的进程状态,可以使用cat
命令读取其状态文件:
1 |
|
这将展示有关该进程的状态信息及更详细的资料。通过/proc目录,内核提供了对系统状态的视图,因此这里的信息比ps
命令显示的更为详尽和全面。
11 job control
在单一终端窗口中管理长时间运行的进程
当在单一终端窗口中执行一个耗时较长的命令,该命令会占用整个shell直到完成。为了继续使用机器,可以将进程置于后台运行。以下是如何通过作业(jobs)控制进程运行的方式:
将任务发送到后台
在命令后添加一个&符号,可以让命令在后台运行,同时释放shell以便进行其他操作。示例如下:
1 |
|
查看所有后台作业
要查看刚刚发送到后台的任务,可以使用jobs
命令:
1 |
|
输出示例:
1 |
|
这显示了每个作业的ID、状态及所运行的命令。带有+号标记的作业是最新的后台作业,而-号标记的则是次新作业。
将现有作业发送到后台
对于已经启动但未设为后台运行的任务,无需终止它重新开始。首先通过Ctrl-Z暂停作业,然后使用bg
命令将其置于后台:
1 |
|
再次使用jobs
命令确认更新后的状态。
将后台作业移至前台
要将后台作业移到前台,指定相应的作业ID即可。如果直接运行fg
不带参数,则默认将最新的后台作业(即带有+号标记的作业)带回前台:
1 |
|
终止后台作业
类似地,可以通过指定作业ID来终止后台进程:
1 |
|