在linux中,进程是资源分配的基本单位,而线程才是调度的基本单位。
一个在计算机上运行的程序,需要一些基本的硬件资源才能正常运行起来,包括CPU,内存,存储设备(文件),还有对所有文件进行管理的文件系统。
在linux里面是用struct task_struct
来描述一个进程资源的分配情况(线程也是用这个结构来描述,具体后面介绍)。
进程生命周期
在linux中,这几种状态以bit位表示
TASK_RUNNIG
正在执行的进程和位于就绪队列上的进程都为TASK_RUNNIG
态,通过调度器从就绪队列里面选出一个合适的进程放到CPU上执行。
TASK_INTERRUPTIBLE & TASK_UNINTERRUPTIBLE
当这个进程由于执行过程中需要得到某个资源,例如读串口,但是这个时候串口里面没数据,那么这个进程就会被设置为TASK_INTERRUPTIBLE或者TASK_UNINTERRUPTIBLE的状态并且调用schedule()
触发调度器进行一次调度(由串口驱动做这些动作,可以看看宋宝华驱动书里面globalfifo驱动源码)。
一段时间后,串口里面有数据来了,在串口收数据那个部分,就会wake_up等待队列上的进程。
wake_up_interruptible(&dev->r_wait);
TASK_STOPPED
一个正在执行的进程如果收到信号SIGSTOP
(可以通过ctrl+z发出此信号,SIGSTOP
和SIGKILL
一样,不可被捕获也不可被忽略),它会停止执行,直到收到信号SIGCONT
。例如有如下程序,一个死循环
main()
{
while(1);
}
编译运行后,ps aux
查看其运行状态
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
baohua 20413 26.3 0.0 2024 276 pts/5 R+ 18:24 1:52 ./a.out
终端输入CTRL+Z后,a.out停止了运行,并且ps里面它的状态由R+
变为了T
,T表示进程收到SIGSTOP, SIGSTP, SIGTIN, SIGTOU信号后停止运行运行。
发送一个SIGCONT给a.out
kill -CONT `pidof a.out`
ps aux
再次查看其运行状态,变为了R
,没了+
,+表示进程位于前端的进程组(+ is in the foreground process group)。
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
baohua 20591 85.1 0.0 2024 276 pts/5 R 18:47 1:43 ./a.out
我在网上查这个状态时,绝大部分都是说
+
表示位于后台进程组,虽然这个概念无关紧要,但是总觉得怪怪的。前台不是占据着一个终端运行的程序吗?我的a.out最开始在终端上运行,后面SIGCONT唤醒后跑后台运行了。按道理是前台转后台,没+
表示后台进程,所以感觉网上一些人写错了。例如
https://blog.csdn.net/ZWE7616175/article/details/79703903
https://blog.csdn.net/MaxineZhou/article/details/80468608
https://zhidao.baidu.com/question/520824519127538445.html
在man ps
里面找到了答案
PROCESS STATE CODES
Here are the different values that the s, stat and state output
specifiers (header "STAT" or "S") will display to describe the state of
a process:
D uninterruptible sleep (usually IO)
R running or runnable (on run queue)
S interruptible sleep (waiting for an event to complete)
T stopped, either by a job control signal or because it is
being traced
W paging (not valid since the 2.6.xx kernel)
X dead (should never be seen)
Z defunct ("zombie") process, terminated but not reaped by
its parent
For BSD formats and when the stat keyword is used, additional
characters may be displayed:
< high-priority (not nice to other users)
N low-priority (nice to other users)
L has pages locked into memory (for real-time and custom IO)
s is a session leader
l is multi-threaded (using CLONE_THREAD, like NPTL pthreads
do)
+ is in the foreground process group
也可以在a.out启动的那个终端输入fg
来把后台进程唤醒,拉到前台来。在用vim编辑文件时经常这样用,在串口终端里面用vim编辑到一半的文件,突然想敲个其他命令。直接CTRL+Z
,将vim切至后台,然后敲完命令,fg回到vim。
TASK_ZOMBIE
一个进程退出的最后阶段会调用exit()系统调用,在内核态执行的就是do_exit(),它会释放进程描述结构struct task_struct
里面指向的各种资源,例如内存,打开的文件等等。但是这个调用结束后,strcut task_struct
这个结构本身还有进程pid这种资源是没有释放掉的,这个时候进程就是僵死态了,它不可能被调度,因为分配给它的内存在上一步释放光了,也就无法执行这个进程的任何代码,也就无法做到自己释放自己的struct task_struct
,pid等。所以这些未被释放完的资源需要其它人帮它释放,一般是它的父进程,父进程调用waitpid这一系列的函数来阻塞的等待子进程的退出,并回收子进程的尸体struct task_struct
, PID
这些资源。如果父进程先于子进程死,则找init进程(1号进程)做父。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char** argv)
{
pid_t pid;
pid = fork();
if ( pid < 0 )
{
printf("fork error
");
exit(-1);
}
else if ( pid > 0)
{
printf("i am parent, my child is %d
", pid);
sleep(200);
}
else
{
printf("i am child
");
}
return 0;
}
父进程阻塞200秒,子进程立即结束,子进程此时就是僵死态struct task_struct
结构,pid也被占据着,最终导致pid耗尽。
baohua 23207 0.0 0.0 2028 276 pts/21 S+ 20:37 0:00 ./a.out
baohua 23208 0.0 0.0 0 0 pts/21 Z+ 20:37 0:00 [a.out] <defunct>
僵死态的进程是无法杀死的,因为杀死进程的本质是进程通信,发送kill信号给要杀死的进程。僵死态进程名存实亡,没有进程执行的实体了。ps只不过是拿了/proc/pid/
下的信息进行展示。