进程状态
进程状态
任何一个进程都要有自己的代码和数据一样,每一个进程都有自己的状态。
1、Linux中的进程状态
所谓的进程状态也就是Linux系统中struct task_struct结构体中的各个属性/变量所表示的状态的综合。
在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 */
};
其中task_struct用数组的形式来表示进程的各种状态。
1、1、进程状态R+和S+
执行代码源码
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
while(1)
{
printf("I am a process,pid:%d\n",getpid());
}
return 0;
}
当我们执行一个可执行程序的时候发现,此时的进程状态是S,可是不对啊,上面说的S不是sleep吗,运行为什么查到的确实sleep的休眠状态。
我们换一个代码运行试试呢。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
while(1)
{
//printf("I am a process,pid:%d\n",getpid());
}
return 0;
}
好奇怪为什么这时候运行却又显示的状态是R呢?
首先先介绍一下R状态就是表示的是进程运行的状态。S状态是进程的休眠状态
可是最不理解的是加上一个printf的时候不应该也是在运行吗,可是为什么那个时候的状态是S呢?
那是因为printf是把信息打印在屏幕上。根据冯诺依曼体系结构,由于屏幕是外设是一个硬件,所以CPU处理完的信息要打印到屏幕上之前要先存储在内存之中。但是由于CPU的速度大大快于屏幕,所以进程没有在一直进行,因为要等待屏幕的输出结束才能够CPU再处理数据。所以我们查看状态的时候大多数都是S状态,而不是R状态。 所以此时如果不需要走外设的这条路的话,直接查询能够查到的就只会是R状态了。可想而知,外设的速度和CPU相比较而言相差的速度是多么的离谱。
所以此时的S+休眠状态更准确的描述应该是:进程再等待“资源”就绪。此外这种状态也能够随时中断睡眠。
实际上呢
./testStatus &:如果这样操作的话,进程就会在后端运行,此时S状态而不是S+状态了
所以其中的“+”代表的就是在前台的意思,如果有这个符号,那就说明是在前台,如果没有,那就是说明是在后台。
1、2、进程状态T/t
为了能够观察到进程是否能够处在T状态,我们需要利用kill来帮助我们。kill中的19号信号是signal stop的含义,暂停进程的意思。
此时进程暂停了,等待进一步唤醒。
18号信号SIGCONT,signal continue表示信号继续。发送完之后就可以看到,此时的进程又跑起来了。
kill -9/-19/-18:杀掉进程,暂停进程,继续进程
进程暂停常见吗?很常见其实大多数写过代码的都用过暂停,那就是调试代码时候的断点。
当我们没有开始run的时候只存在gdb的S,但是当我们打上断点,然后run的时候就出现了从S到R的gdb,本身文件的属性也从没有到 t。为什么变成t?因为程序碰到断点,此时就需要暂停。
1、3、进程状态D
Linux系统特有的状态。其中D看源码代表的disk sleep那这到底是啥意思也还是没这么明白。
如果进程中内存严重不足的情况下,Linux的操作系用有权利杀掉进程来释放空间。如果说我们需要存储一个非常重要的1GB的资源到硬盘上,由于之前讲过硬件的速度远远的小于CPU的处理速度,所以说此时存在于内存中的1GB的数据可能大多数情况都是处在S状态,那正巧此时内存严重不足,操作系统一看,这么大内存就站着不用,手起刀落,直接把你kill掉,当硬盘处理完回来看数据的时候发现找不到了,没办法,硬盘只能继续再干其余要存储数据的工作了。那此时重要的数据丢失了,应该怪谁呢?如果操作系统不采取强烈的措施的话,可能会导致系统的宕机到时候可能就不只是1GB的数据了。如果是进程的话,可是进程什么都没做就被杀掉了。可是如果是硬盘的话,硬盘也确实在十分努力的工作存储数据,他应该也没有什么问题啊。 ** 所以为了防止以后再出现这种情况,我们需要给进程家伙是那个一个状态,凡是要给磁盘写数据的进程,即使是操作系统也不能删除,应该删除别的一些进程。所以以后的进程凡是进行数据I/O的需要加上D状态,避免被操作系统删除。
D状态表示为深度睡眠,不可被暂停。**
那么此时进程该怎么取消呢?1、重启(断电) 2、自己醒来
1、4、进程状态X和Z
X状态是死亡状态比较难以查看,而且也是一个比较瞬时的状态。
怎么说呢,此时的状态比较像现实生活中的查案时候,当有了案子的时候,警察来到现场,不是先收拾现场,而是保持现场,只有当一切流程都走完了,把信息都取证了之后,才能够把现场收拾了。
类似进程,一个进程结束了呢,并不会立刻的退出,而是处于一种僵尸状态,如果父进程不对我们进行回收的话,我们将会一直处于僵尸的状态Z,不会X。
2、僵尸进程
这样的代码的含义就是父进程一直处于while循环之中,但是子进程只会执行5次,此时子进程执行结束之后,父进程还没有结束,所以此时的子进程处于没有被回收的状态,也就是子进程现在是僵尸进程。
其中defunct表示失效的,不存在的。这也就意味着子进程确实是退出了,没了数据和代码,但是还存在PCB结构体。
僵尸进程表示:已经运行完毕,但是需要维持自己的推出信息,在自己的进程task_struct会记录自己的退出信息,在未来会让父进程来进行读取。如果没有父进程的读取,此时僵尸进程将会一直存在。
3、孤儿进程
顾名思义,这个进程就是父进程先退出了,但是子进程还没有结束。其中有一个最典型的表现
父进程如果先退出,子进程就会变成孤儿进程,孤儿进程都会被1号进程(OS)领养,为了能够保证孤儿进程能够X掉。这也就是为什么孤儿进程要被OS领养。
4、bash概括
我们以前所有已经启动的进程,为什么从来没有关心过僵尸进程和内存泄漏?
因为我们直接在命令行中启动的子进程,他的父进程是bash,bash会自动回收新进程的Z。
5、进程的阻塞,挂起和运行
5、1、运行
当我们的一个个task_struct排成队列放在CPU中的时候,那么此时该进程就称为R状态(运行状态)。
一个进程一旦持有一个CPU,会一直运行到这个进程结束吗?
不会。因为是基于时间片进行调用,如果一个进程时间超过规定的时间了就需要跳过这个进程,先执行下一个,就这样一个一个的按照固定时间片循环进行。
基于时间片,让多个进程按照切换的方式进行调度,在一个时间内同时得以推荐代码,叫做并发。正是这样,我们能够解决遇到死循环的情况,防止出现CPU一直被占用却没推进。
当是多个CPU的时候,才能够实现真正的并行算法调度。
可是Linux不是这么调度的!这只是OS调度算法中的一种
5、2、阻塞
问题
1、我们在C语言中使用scanf的时候,如果我当前不对键盘进行输入的话,那么此时的进程处于什么状态。scanf在等待什么资源?
此时scanf的时候,就是在阻塞,在Linux中相当于就是S+状态,此时就是在等待键盘资源是否就绪(键盘是否输入数据)。
2、操作系统如何对硬件做管理呢?
操作系统对于硬件的管理并不是直接管理硬件,而是管理其的数据。每一个硬件都有自己的结构体给操作系统来控制。为什么能够让程序等待硬件说明硬件中的结构体能够有程序的等待队列。
当我们进行进程的时候,当一个进程队列中有scanf的时候,此时需要检查硬件是否已经准备好了,如果没有准备好的话,就需要将进程队列中的这个进程单独剥离下来,排到硬件的队列中,等待硬件的数据输入成功。
所以不只是CPU有自己运行队列,各种硬件也有自己的运行队列。
此时这种进程的状态就被称为阻塞。
当我们对硬件有操作之后,之后操作系统就要将硬件的结构体中队列中的特定的进程中释放,重新链回对于的进程的运行队列中。
5、3、挂起
磁盘中存在swap分区。由于有的时候可能OS操作系统的内存吃的特别紧的情况的话,我们就需要把一些不在运行队列中的进程的代码和数据暂时唤出磁盘的swap分区中。因为此时的进程还在硬件的排序的队列中,数据和代码暂时不会访问。这样的话如果数据和代码比较多的话,能够相对而言优化一下内存的容量。当读取数据结束,重新回到运行队列中的时候,再从swap分区中唤出相对应的代码和数据。
此时进程还存在,只不过是数据和代码放在磁盘中而已。这种情况就称为阻塞挂起态。
但是频繁的唤出唤入会降低进程的效率。