操作系统进程的状态
进程状态:一个进程的生命周期可以划分为一组状态,这些状态刻画了整个进程。进程状态即体现一个进程的生命状态。
一个可执行程序加载到内存中,操作系统会创建一个PCB里面存放着各种属性、PID、PPID等;进程的状态也就是一个字段/变量在PCB中,不同的值代表不同的状态。
操作系统进程的主要状态包括,运行状态、阻塞状态、挂起阻塞状态;
运行状态
当我们的可执行程序加载到内存中,需要CPU进行数据运算时会从内存调度到CPU中;当然CPU的处理数据的能力是有限的,而当有很多的程序被调度时候,会将每个可执行程序的PCB使用链表建模依次等待CPU的调度;也就是我们之前提到的进程在”排队“;因此只要在CPU运行的队列中的进程,都是运行状态。
阻塞状态
我们自己编写的可执行程序难免会对系统中的资源进行访问,像我们需要使用输入函数cin/scanf从键盘输入一些数据时候就是可执行程序从键盘中读取数据;那么我们一直不输入数据,键盘中的数据就一直没有准备就绪,也就是进程访问的资源没有就绪,那么后序的代码就执行不了了。
当然我们的操作系统此时可不止这一个进程需要对系统中某个设备的资源进行访问,这就有需要一个队列建模需要访问资源的进程的PCB,此时这个队列就不再等待CPU调度的队列中了;因此我们把进程PCB链入非CPU的运行队列而把PCB链入到各种设备所维护的等待队列当中去等待资源就绪时的状态叫做阻塞状态。
· 进程的PCB从CPU调度的等待队列中转移到各种设备的等待队列中叫做该进程阻塞了。
· 当从设备中拿到数据是PCB会从设备的等待队列转移到CPU的调度队列这个过程叫做将该进程唤醒。
进程阻塞的现象
· 进程卡住了
· PCB不在运行状态中&&状态不是running,进程不在被CPU调度
挂起阻塞状态
挂起阻塞状态时阻塞状态的一种特殊情况,当我们的PCB在阻塞状态时操作系统的内存严重不足时,会将次PCB对应加载到内存中的程序移出内存放回磁盘,当PCB读取到数据时需要被CPU调度时又将程序加载到内存中。
总结
进程状态变化的本质:
· 更改PCB中status的整数变量
· 将PCB链入不同的队列中
· 只和进程的PCB有关,和进程的代码数据无关。
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 */ };
1 #include<stdio.h> 2 int main() 3 { 4 while(1) 5 { 6 printf("我是一个进程\n"); 7 } 8 return 0; 9 }
因为我们的进程在疯狂的printf时,本质是往显示器上面打印,进程是在内存中是将内存中的数据向外刷新;当刷新时,显示器不一定准备好,因此在程序运行中大部分是S状态。
R(running状态)运行状态
1 #include<stdio.h> 2 int main() 3 { 4 while(1) 5 { 6 // printf("我是一个进程\n"); 7 } 8 return 0; 9 }
这样我们就可以查到R状态的进程了;
S(sleeping)休眠状态
浅度睡眠
1 #include<stdio.h> 2 #include<unistd.h> 3 int main() 4 5 { 6 int a=0; 7 printf("请输入一个数字:"); 8 scanf("%d",&a); 9 printf("%d\n",a); 10 11 return 0; 12 }
浅度休眠状态可以被终止,可以被kill掉;并且会对外部状态做出相应。
D(desk sleep)磁盘休眠
深度睡眠
· 针对磁盘来设计的,不可能被kill掉,操作系统也不可以;
· 我们很难查到D状态,如果被用户查到计算机几乎快要宕机了。
T(stopped)
暂停一个进程。
t(tracing stop)
适用于我们debug模式下调试代码追踪我们的进程暂停下来。
等待软件条件。
x(dead)
这个状态只是一个返回状态,不会查看到这个进程的。
僵尸进程(zombie)
z(zombie)
可执行程序加载到内存中成为进程,操作系统创建PCB,内存里的进程肯定是完成某种任务得到某种结果的;当进程退出时总要返回一些信息告诉操作系统任务完成的怎样;就像我们写的代码总要在最后一行return 0;退出信息会由操作系统写入到PCB中,此时内存中进程的代码和数据被释放,但是不允许PCB释放;需要操作系统读取PCB进程的退出信息,得知进程退出的原因后PCB才会被释放。因此当进程被释放后而PCB没有被释放的这段空窗期成为僵尸进程。
· 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
· 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
· 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
僵尸状态演示
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<stdlib.h> 4 int main() 5 { 6 pid_t id =fork(); 7 if(id<0) 8 { 9 return 1; 10 } 11 else if(id==0) 12 { 13 int n=5; 14 while(n) 15 { 16 printf("我是一个子进程:%d\n",n--); 17 sleep(1); 18 } 19 exit(2); 20 } 21 else{ 22 while(1) 23 { 24 printf("我是父进程\n"); 25 sleep(1); 26 } 27 } 28 return 0; 29 }
僵尸进程的危害
· 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!
· 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护?是的!
· 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
· 内存泄漏!!!
孤儿进程
· 孤儿进程和僵尸进程刚好相反,父进程先退出,子进程称之为”孤儿进程“。
· 孤儿进程会被系统进程”领养”,最中由init进程回收,init进程为我们所有进程的起源。
孤儿进程演示
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<stdlib.h> 4 int main() 5 { 6 pid_t id =fork(); 7 if(id<0) 8 { 9 return 1; 10 } 11 else if(id==0) 12 { 13 while(1) 14 { 15 printf("我是一个子进程\n"); 16 sleep(1); 17 } 18 } 19 else{ 20 int n=5; 21 while(n) 22 { 23 printf("我是一个父进程:%d\n",n--); 24 sleep(1); 25 } 26 exit(2); 27 } 28 return 0; 29 }
前台进程和后台进程
当我们运行我们编译好的代码后无论输入任何指令系统都不会有任何反应,但是可以通过Ctrl+C来终止 。并且状态带有+ ,这就是前台状态。
在编译好的代码后加上&操作符进行执行,代码不仅可以执行而且输入的指令也可以识别,但是不可以使用CTRL+C终止掉,这就是后台状态。