一台电脑一般只有一个CPU、一个磁盘(无论一台电脑有几个CPU、磁盘,数量都是远少于进程的,这里举例用一个)。
运行状态
而一个CPU同时只能调度一个进程,所以操作系统就维护起了一个运行队列runqueue,在这个队列中排队的进程就称为运行(R)状态,并不是正在被CPU调度的进程称为运行(R)状态。
#include <stdio.h>
int main()
{
while(1);
return 0;
}
阻塞状态
假设我们的某些进程想要访问磁盘,而由于CPU的速度非常快,磁盘的速度非常慢,那么当CPU执行完5个进程,磁盘连一个进程都没执行完,所以就会导致,CPU执行完的需要访问磁盘的进程就需要排队,所以操作系统又维护了一个等待队列waitqueue,在磁盘中排队的都进程都会在这个队列中,当进程访问完磁盘后,该进程又会重新去runqueue排队。而在这个队列中排队的进程就称为阻塞(S)状态。
#include <stdio.h>
#include <unistd.h>
int main()
{
while(1)
{
sleep(1);
printf("hello world\n");
}
return 0;
}
这里有一个很违反直觉的现象,输出hello world怎么每次都是S状态?这是因为CPU的速度太快了!而磁盘的速度太慢了!所以当ps命令去查该进程状态的时候会有99%的几率查到的都是S状态,因为99%的时间都在等待IO。
挂起状态
有一个状态在Linux中未显式写出来,但教材中会经常提到,就是挂起状态。假设现在有很多进程都在访问磁盘,但是内存中的空间不够了,于是操作系统就会将一些在waitqueue的进程的程序代码和数据保存到磁盘上(PCB仍在waitqueue中),而这样的进程状态就称为挂起状态,该状态其实更应该被称为阻塞挂起状态,因为挂起状态可以和任意状态组合,比如运行挂起状态、就绪挂起状态、暂停挂起状态等等。
磁盘睡眠状态
假设有大量的进程在等待IO,而此时内存空间严重不足,操作系统挂起了很多进程也无济于事,这时操作系统很容易崩溃,为了防止操作系统杀掉这些在等待IO的进程(可能有重要的数据),于是在这种情况下的这种进程就会转为磁盘睡眠(D)状态,在该状态下的进程是不会被杀掉的,只能通过重启或者断电的方式。
暂停状态
#include <stdio.h>
#include <unistd.h>
int main()
{
while(1)
{
sleep(1);
printf("hello world\n");
}
return 0;
}
一个进程是可以被暂停的,在Linux下使用kill -19 目标进程PID
命令可以使目标进程暂停,此时进程的状态会转为暂停(T)状态。
kill -18 目标进程PID
可以解除T状态。
这时会发现,原本的状态是S+,后来经过T状态后,变为了S。其实带有+号的状态是前台进程,不带的是后台进程,而后台进程是无法用ctrl+c信号来终止的,只能通过kill -9命令。
追踪停止状态
当我们调试程序的时候,该进程也总是处于暂停状态的,但并不是上面的使用kill命令导致的暂停状态,在Linux中该状态称为追踪停止(t)状态。
僵尸状态
进程可以派生子进程,一个子进程在结束时,需要告诉父进程我的任务完成的怎么样,而父进程需要去接收该信息(终止状态),如果父进程不接收终止状态,则会导致子进程的PCB一直无法释放(代码和数据释放了),而在这段时间内,该子进程的状态就为僵尸(Z)状态,也称为僵尸进程,该进程会导致内存泄漏。
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
//child
printf("I am child, pid = %d, ppid = %d\n", getpid(), getppid());
}
else if(id > 0)
{
//parent
printf("I am parent, pid = %d, ppid = %d\n", getpid(), getppid());
while(1)
{
//死循环,不接收子进程的终止状态
}
}
else
{
//error
}
return 0;
}
死亡状态
Linux中还有一个死亡(X)状态,处在该状态的进程就表明该进程马上就执行完了,没什么好说的。
孤儿状态
上面说到的僵尸状态是由于子进程执行完了,而父进程没有及时接收子进程的终止状态导致的,那么如果父进程先执行完,子进程后执行完的话,这时由于父进程已经执行完了,所以无法接受子进程的终止状态,但子进程的终止状态必须有人来接受,所以此时操作系统(1号Init进程)就会领养该子进程,以便能够接受子进程的终止状态,而处于被操作系统领养的进程所处的状态就称为孤儿状态, 也称为孤儿进程。
Linux内核进程状态源代码
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
/* states in TASK_REPORT: */
"R (running)", /* 0x00 */
"S (sleeping)", /* 0x01 */
"D (disk sleep)", /* 0x02 */
"T (stopped)", /* 0x04 */
"t (tracing stop)", /* 0x08 */
"X (dead)", /* 0x10 */
"Z (zombie)", /* 0x20 */
};