OS状态介绍
在操作系统中,会有大量的进程被创建,但是并不是所有的进程都在运行,于是便有了进程的状态的区分。
在操作系统教材里面所描述的状态是所有操作系统都有的状态,但是名字可能不同,具体的实现还要看操作系统具体的实现方案。
上图就是操作系统教材介绍的进程的几种状态。
但是Linux的进程的状态分类就要比上图多。
Linux中的状态
为了知道Linux进程状态都有哪些,可以翻看其源码得到:
下面的状态在kernel源代码里定义:
/*
* 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[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
而上面的各种属性,就是Linux的pcb,也就是里面task_struct的一个内部属性int status
。各种字母表示状态,也就是定义成了宏。
类似:
#define S 1
#define D 2
#define T 4
#define t 8
...
kill -信号
通过kill - l
命令可以查看kill的信号
通过kill可以给指定的进程发信号
可以看到上面的信号都是大写字母,其实也就是定义的宏
kill -9:杀死某个进程
kill -19:暂停某个进程
kill -18:继续某个进程
Linux具体状态
R状态
R状态表示运行状态,并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列,
代表可被调度。进程只有是R状态才可被调度,其他状态要先转为R状态,才能被OS调度。
通过下面的代码去观察此状态
int main()
{
while (1)
{
//printf("i am parent, pid:%d ,ppid:%d \n", getpid(), getppid());
}
return 0;
}
S状态
意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。
通过下面的代码去观察此状态
int main()
{
while (1)
{
printf("i am parent, pid:%d ,ppid:%d \n", getpid(), getppid());
}
return 0;
}
为什么我们一直打印,状态一直是S+呢而不是R状态。
因为我们的系统中有很多个进程,很多个进程都要运行。而当前进程是在屏幕上打印。屏幕,属于一个外设。屏幕这个外设的速度和CPU差距很大。代码执行的很快,所以当一个循环执行完要往屏幕上打印的时候,屏幕外设还没有就绪,要等待屏幕这个外设就绪。这也是为什么R状态的演示要把printf注释掉。而此时CPU就要执行其他进程。在等待的这个过程中,此进程就是一个S状态。绝大部分时间,这个进程都在S状态。
但是为什么上述代码的状态不是S,而是S+呢
S+:表示进程在前台运行
S:表示进程在后台运行
我们在命令后面带一个 &
符号就可以让他运行在后台了。
处于S+的进程在不停的执行的时候可以通过crtl + c终止运行,但是S状态的不可以
必须使用kill + pid才能结束进程
T状态
可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
可以观察到我们的进程通过我们的信号变成了暂停状态就是T状态。
再通过kill -18,让进程继续运行。此时进程自动从体状态变成S状态。通过T状态变为S状态的进程自动在后台运行。
t状态
t状态也是暂停状态。t状态表示因为被追踪而变为的暂停。因为被追踪而被暂停的状态可以理解为debug。当要调试一个代码的时候,遇到断点时,这时的进程就会被暂停变为t状态。
D状态
D 状态是Linux中特有的一个状态,也叫不可中断睡眠状态(uninterruptible sleep),和上面的S状态对比,S是可中断的睡眠状态。在这个状态的进程通常会等待IO的结束。
举个例子:
当我们的系统运行了大量进程的时候。如果此时操作系统压力特别大,内存资源严重不足时,操作系统就会有权利杀死某些进程。但是它不能把任何的进程都给杀死,因为有的进程,比如说正在往磁盘写入数据。此时这样的进程就不能被杀死。所以就有了D状态。D状态就表示这个进程不能被杀死,他正在执行一个很重要的操作。
其实D状态也是一种S状态,是一种深度睡眠状态,不可中断睡眠。只有进程自己醒来或者重启断电才能中断他。
僵尸进程和Z状态
通过下面代码介绍僵尸进程和Z状态
int main()
{
int f = fork();
if (f == 0)
{
while (1)
{
printf("i am child, pid:%d ,ppid:%d \n", getpid(), getppid());
sleep(1);
break;
}
}
else
{
int cnt = 5;
while (cnt--)
{
printf("i am parent, pid:%d ,ppid:%d \n", getpid(), getppid());
sleep(1);
}
}
return 0;
}
代码表示子进程运行一秒退出,父进程运行5秒。
可以看到,父进程没运行完之前,子进程由S状态变为了Z状态,此时的进程就是僵尸进程。此时进程已经运行完毕,但是需要维持自己的退出信息在自己的进程task_struct。会记录自己的退出信息未来让父进程来进行读取。
如果父进程不读取僵尸进程就会一直存在。但是,结束掉父进程僵尸进程也就结束了。
僵尸进程不能被kill -9杀掉
而且可以发现进程后面多了defunct,这个单词的意思就是 失效的,僵尸。
为什么会有僵尸进程:
因为我们必须得保证一个进程跑完,启动这个进程的父进程或是操作系统必须得知道这个进程退出时,把我们交代得任务完成得怎么样了,成功还是失败了。必须要知道子进程得运行结果。当子进程退出的时候,它的信息不会立即释放,会存在PCB中,没有被读取,这个状态不会被释放掉,这个状态就是僵尸状态。
僵尸进程的危害:
维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说, Z状态一直不退出, PCB一直都要维护
一个父进程创建了很多子进程,就是不回收,就会造成内存资源的浪费,因为数据结构对象本身就要占用内存,而且Linux内核中PCB中东西很多。此时就会造成内存泄漏。
我们自己的进程为什么不会是僵尸进程呢?
自己写的程序会由操作系统自动回收。
X死亡状态
这个状态只是一个返回状态,你不会在任务列表里看到这个状态。当父进程读取子进程的返回结果时,子进程立刻释放资源。死亡状态是非常短暂的,几乎不可能通过ps命令捕捉到。
孤儿进程
孤儿进程是当一个进程的父进程结束时,但是它自己还没有结束,那么这个进程将会成为孤儿进程。
孤儿进程没有父进程,那么结束的时候就没有进程可以回收他,所有会被1号进程领养。此时在子进程结束时也会由1号进程完成对它的回收工作。
可以简单的将1号进程理解为操作系统。
通过下面代码观察:
int main()
{
int f = fork();
if (f == 0)
{
while (1)
{
printf("i am child, pid:%d ,ppid:%d \n", getpid(), getppid());
sleep(1);
}
}
else
{
int cnt = 5;
while (cnt--)
{
printf("i am parent, pid:%d ,ppid:%d \n", getpid(), getppid());
sleep(1);
}
}
return 0;
}
可以看到父进程结束以后,子进程的ppid变为了1。
Linux状态与操作系统状态
上述图片可以看作是操作系统的一个指导思想。
终止状态就是我们上述的Z和X状态,R状态就是执行(运行)状态。
Linux操作系统中每个进程的task_struct,在CPU的运行队列里面,只要在运行队列里面的进程都称为R状态。
Linux中没有直接的就绪队列,可以将就绪和运行看作一个状态。也可以将在运行队列里面,但是不在CPU上执行的进程看作就绪状态。
S和D都算阻塞状态,在等待资源是否就绪。操作系统有很多的阻塞队列,当前进程哪个资源阻塞了,就进入哪个队列。
如果需要等待的资源不止一个,假如需要等待屏幕和键盘两个资源,那么谁先阻塞进谁的队列。当此资源就绪了,如果其他资源在阻塞,就进入其他资源的阻塞队列。
挂起态
当因为等待某种资源就绪,进程对应PCB由运行队列转至资源下的等待队列时,考虑到内存空间紧张,CPU会将因为等待而暂时无法运行的进程对应的代码和数据先由内存转移到磁盘的swap分区中,此时进程即为挂起状态,等到该进程可以被运行时再将对应的代码和数据由磁盘转移回内存中。
这个过程是操作系统完成的,我们并不能感受到。
程序怎么执行的
当程序运行的时候,操作系统将其pcb中的关于程序的信息,比如程序指针,当前程序运行到了哪里,等各种信息加载到CPU的各种寄存器里面,
CPU一步一步的执行,当时间片到了以后,CPU在加载另一个进程的信息。
pcb里面的信息就保存了,当前程序运行到了哪一步
上图结构体是Linux中task_struct中的一个成员,保存的就是各寄存器的值,程序的上下文,在下一次执行的时候,将这些数据在加载到cpu中。