进程是一个独立的程序正在处于运行的状态,它是一个占用CPU、内存、IO资源的实体。
Linux 系统启动后,不会直接管理进程,而是会交给 init 进程管理进程,在一些 Linux 发行版中,比如 CentOS,这个 init 进程就是 systemd。
进程是通过 fork 函数生成的,父进程 fork 出子进程。
如果一个进程的父进程退出了,那么它的父进程就会变成 systemd。
systemd 会管理进程的生命周期,包括处理进程退出。
如果在进程退出时,它的父进程没有管它,它就一直处于僵尸状态,这就是僵尸进程的出现原因。
Linux 系统里有几种不同类型的进程:用户进程(User processes)、守护进程(Deamon processes)和内核进程(Kernel processes)。
操作系统的角度来说,进程描述为一个结构体---->PCB(在Linux下PCB位task_struct)
task_struct是Linux内核的一种数据结构,他会被装载到RAM(内存)里并且包含着进程的信息:
- 标示符 pid:描述本进程的唯一标示符,用来区别其他进程
- 状态:任务状态,退出代码,退出信号等
- 优先级:相对于其他进程的优先级
- 程序计数器:程序中即将被执行的下一条指令的地址
- 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据:进程执行时处理器的寄存器中的数据
- I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表
- 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等
- 其他信息
$ ls /proc/
$ ps aux
$ ps ef
另外在 top
命令中可以看到各种状态的进程的数量。
进程的七大状态:(linux内核里有时候也叫任务)
- R--->运行状态
- S--->睡眠状态(可中断睡眠状态)
- D--->磁盘休眠状态(不可中断睡眠状态)
- T--->停止状态
- X--->死亡状态
- t --->追踪状态
- Z--->僵尸状态(很重要,必须理解)
其中运行状态包括可运行状态
进程获取了所有所需资源,正等待 CPU 时,就会进入可运行状态。处于可运行状态的进程在 ps
的输出中,也已 R
标识。
举例来说,一个正在 I/O 的进程并不立即需要 CPU。当进程完成 I/O 操作后,就会触发一个信号,通知 CPU 和调度器将该进程置于运行队列(由内核维护的可运行进程的列表)。当 CPU 可用时,该进程就会进入正在运行状态。
和正在运行状态一样,进程的状态被设置为 TASK_RUNNING
当进程所需的资源暂不可用时,就会进入睡眠状态。此时,进程要么主动进入睡眠状态,要么被内核置于睡眠状态(不管你想不想睡,反正内核会让你睡;因此,后者又称为「进程被内核睡了」)。进入睡眠状态的进程,会立即交出 CPU 的使用权。
当进程所需的资源可用时,CPU 会收到一个信号。于是,当调度器下次调度该进程时,会将它置为正在运行或可以运行状态。
以 login shell 进程为例,它
- 在你键入命令时进入睡眠状态,同时等待一个特定的事件(取决与你键入执行的命令);
- Shell 进程睡眠时,会进入一个特定的等待通道(
WCHAN
, wait channel,同样取决于你键入执行的命令); - 当 Shell 进程等待的事件发生时(例如,收到一个来自键盘的中断
^C
),在该等待通道的所有进程都会苏醒。
执行 ps -l
可看到与当前 shell 关联的进程,执行 ps -el
则可看到系统上所有进程。如果进程处于睡眠状态,ps
输出中的 WCHAN
字段会显示进程在等待什么系统调用。
$ ps -l | more
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 R 867 12085 27779 0 80 0 - 27029 - pts/480 00:00:00 ps
0 S 867 12086 27779 0 80 0 - 25779 pipe_w pts/480 00:00:00 more
0 S 867 27779 35146 0 80 0 - 27721 do_wai pts/480 00:00:01 bash
例如,在这里,我们执行了 ps -l | more
这个命令。输出中,more
和 bash
都处于睡眠状态。前者是在等待管道输入,即 pipe_wait
,因为 ps
输出时,more
还没有接到内容。后者是在 等待 ps -l | more
执行完毕,即等待 do_wait
系统调用。
除了等待资源之外,进程也可以主动进入睡眠状态并持续一段时间。例如,sleep()
函数接收一个时间长度(以秒为单位,比如 10 秒)的参数,然后调用该函数的进程就会进入睡眠状态,并持续 10 秒。当睡眠时间结束后,调度器再次调度到该进程时,会将其设置为可运行状态。之后,当 CPU 空闲时,进程会重新进入正在运行状态。
由此可见,
sleep(10)
并不能保证「恰好」睡眠 10 秒,它只保证睡眠时间不少于 10 秒。
部分进程永远不会终止,而是不断地在睡眠、唤醒干活的状态中循环。每次循环开始时,进程进入睡眠状态,然后等待某个特定的事件。当事件发生时,进程被唤醒(进入正在运行或者可以运行状态),然后处理任务。
睡眠状态也分可中断之睡眠状态和不可中断之睡眠状态。
可中断之睡眠状态表示进程在等待时间片段或者某个特定的事件。一旦事件发生,进程会从可中断之睡眠状态中退出。ps
命令的输出中,可中断之睡眠状态标识为 S
。
系统会为可中断之睡眠状态的进程设置进程运行状态为:
p->state = TASK_INTERRUPTABLE;
不可中断之睡眠状态的进程不会处理任何信号,而仅在其等待的资源可用或超时时退出(前提是设置了超时时间)。
不可中断之睡眠状态通常和设备驱动等待磁盘或网络 I/O 有关。在内核源码 fs/proc/array.c
中,其文字定义为 "D (disk sleep)", /* 2 */
。当进程进入不可中断之睡眠状态时,进程不会处理信号,而是将信号都积累起来,等进程唤醒之后再处理。在 Linux 中,ps
命令使用 D
来标识处于不可中断之睡眠状态的进程。
系统会为不可中断之睡眠状态的进程设置进程运行状态为:
p->state = TASK_UNINTERRUPTABLE;
由于处于不可中断之睡眠状态的进程不会处理任何信号,所以 kill -9
也杀不掉它。解决此类进程的办法只有两个:
- 对于怨妇,你还能怎么办,只能满足它啊:搞定不可中断之睡眠状态进程所等待的资源,使资源可用。
- 如果满足不了它,那就只能 kill the world——重启系统。