目录
1、孤儿僵尸2:41:50讲解如何通过结构体的某一个成员的地址找到结构体的地址
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家
一、引言
首先进程不是一直在运行的,就算是将进程放到CPU上也不会一直在运行(有时间片的影响)。
cpp1 #include<stdio.h> 2 int main() 3 { 4 int a = 0; 5 scanf("%d",&a); 6 printf("%d",a); 7 return 0; 8 }
比如运行上述代码时:
此时就会卡住,因为有scanf函数的存在,这个进程就需要等待键盘资源。
只要进程在排队,那一定是在等待某种"资源",并且只要是在排队,那一定是进程的task_struct进行排队。
1、孤儿僵尸2:41:50讲解如何通过结构体的某一个成员的地址找到结构体的地址
一个task_struct可以被连入多种数据结构中,这是如何办到的?
实际是将某种数据结构的对象作为task_struct的成员变量,然后链接的时候,是链在这个对象上面,然后通过这个对象的地址和偏移量就可以找到task_struct结构体的地址:
对象变量地址 = 结构体地址 + 偏移量
二、进程状态
1、状态是什么?
在很多教材中对进程状态的表示一般是:运行、阻塞、挂起。
实际上进程状态其实就是一个整型变量,并且是在task_struct中的一个整型变量
2、状态决定了什么?
状态决定了进程的后续动作:Linux中可能会存在多个进程都要根据它的状态执行后续动作(比如进程排队)。
3、运行状态
每个CPU都有一个对应的运行队列。处于该运行队列中的进程都在等待CPU调度,此时进程的状态就是运行状态
4、阻塞状态
比如在上述的例子中:
cpp1 #include<stdio.h> 2 int main() 3 { 4 int a = 0; 5 scanf("%d",&a); 6 printf("%d",a); 7 return 0; 8 }
当运行到scanf语句的时候,该进程就需要等待键盘的资源,此时进程的状态就由运行状态转换为阻塞状态。
当我们的进程在进行等待软硬件资源(比如上述等待键盘资源)的时候,资源如果没有就绪。进程的task_struct只能做两件事:
(1)、将自己设置为阻塞状态;
(2)、将自己的PCB连入等待的资源提供的等待队列中(比如上述进程会连入键盘的等待队列中);
每个设备都有一个自己的等待队列(例如键盘),其次CPU也属于设备,它有一个运行队列。进程状态的变化,引起的是PCB会被操作系统变迁到不同的设备的等待队列中。
问题:硬件的就绪状态只有谁最清楚?
操作系最清楚,因为操作系统是软硬件的管理者。
5、挂起状态
挂起状态有个前提:此时计算机资源已经比较吃紧了。
通俗来讲,当一个进程阻塞了,如果此时计算机的内存特别的吃紧,那么就会将该进程的代码和数据拷贝到外设磁盘,然后把内存中的数据释放掉(这个过程叫唤出,但注意内存中释放的是代码和数据,不会释放该进程的PCB,因为PCB要进行管理),此时进程的状态就是挂起状态(也叫阻塞挂起),在内存需要的时候,又将代码和数据从外设磁盘拷贝到内存(这个过程叫唤入)。
注意事项:
三、具体介绍Linux中的进程状态
这是Linux内核源码内容:
下面依次介绍
1、R状态
R:运行状态(running),并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
通过代码查看R状态:
cpp//通过代码查看R状态 6 while(1) 7 { 8 printf("i am a process\n"); 9 sleep(1); 10 }
运行这段代码:
通过指令查看进程信息:
cppps ajx | head -1 && ps ajx | grep myprocess
STAT一栏就表示进程的状态,但我们已经运行了该程序,为什么还是S+(睡眠)状态呐?
如下:
此时如果我们把多余的代码全部注释,只剩下一个while循环,此时就不访问任何外设,所以看到状态就为R
2、前台进程和后台进程
(1)、前台进程:在上面我们看到状态的后面都有一个 + 号,如R+
这代表此时这个进程叫前台进程,前台进程与用户直接交互,会占用当前终端,在它运行期间,用户无法在同一终端输入其他命令(如pwd),直到该进程结束。
前台进程可以通过键盘输入(如
Ctrl+C
组合键)来终止进程,也可以使用Ctrl+Z
组合键将其暂停并放入后台。(2)、后台进程:当在程序时,在后面加上一个"&",这样形成的进程就叫后台进程,在后台运行,不会占用终端,用户可以在同一终端继续输入和执行其他命令:
通常使用
kill
命令来管理,如kill -9 <PID>
可强制终止指定进程,键盘组合键是终止不掉的。
3、S状态
S:睡眠状态(sleeping)意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠)
cpp//查看S状态 6 int a = 0; 7 scanf("%d",&a); 8 printf("%d\n",a);
当等待scanf输入时,此时进程就是S+状态:
所以其实S状态就是阻塞状态,又因为ctrl+c可以中断S状态的进程,所以S状态也叫可中断睡眠(浅层睡眠)。
4、D状态
D:磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
有时候把操作系统逼急了,是会直接杀掉进程的,而D状态相当于给进程赋予了一个"免死金牌",是该进程不会被操作系统杀掉。(注意D状态也是属于阻塞状态的一种)。
5、T状态
T:停止状态(stopped) 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
首先通过 kill -l可以查看kill的信号分类:
然后可以看到信号19,信号19就可以使进程暂停,即进入T状态:
信号18可以重新启动一个进程:
此时可以注意到S+变成了S,也就是说通过暂停和启动这一个过程,一个前台进程会默认变成后台进程。
6、t状态:
t状态也是暂停的意思:
当我们调试的时候,程序运行到断点处,此时程序对应的进程就是t状态
7、Z状态:僵尸状态
Z状态:也叫僵尸状态,一个进程已经执行完毕,但父进程还没有获取它的退出信息,此时进程的状态就叫僵尸状态。
查看僵尸状态的示例代码:
cpp//查看僵尸状态的代码 8 pid_t id = fork(); 9 if(id == 0) 10 { 11 int cnt = 5; 12 while(cnt) 13 { 14 printf("i am a child,pid: %d,ppid:%d\n]",getpid(),getppid()); 15 sleep(1); 16 cnt--; 17 } 18 exit(0);//让子进程直接退出 19 } 20 //父进程 21 while(1) 22 { 23 printf("i am a father, pid:%d,pptd:%d\n",getpid(),getppid()); 24 sleep(1); 25 }
首先创建一个子进程,然后通过exit让子进程直接退出,但后面父进程并没有读取子进程的退出信息,所以子进程就变为了Z+状态。
8、为什么要有Z状态?
因为我们创建进程是希望这个进程帮用户完成相关工作,子进程必须得有PCB中的结果和数据。什么是Z?进程已经退出,但是当前进程的状态还需要自己维持住,供上层读取,必须是Z状态。此时代码和数据可以被释放,但PCB不能被释放。
如果父进程不读取退出信息,僵尸状态的进程会一直存在,task_struct对象也就会一直存在,但我们并不需要,这就会占据内存,造成内存泄漏。
9、X状态
X状态就是父进程读取了子进程的退出信息后,子进程彻底终止,最终死亡。
10、孤儿进程
孤儿进程:通俗讲就是父进程比子进程优先终止的子进程,这样该子进程的退出信息就没人读取,这样该进程就不会最终退出,从而造成内存泄漏,这样的进程也叫孤儿进程。
操作系统为了防止孤儿进程造成的内存泄漏,所以有了如下处理方法:
查看孤儿进程的示例代码:
cpp//查看孤儿进程 8 pid_t id = fork(); 9 if(id == 0) 10 { 11 int cnt = 500; 12 while(cnt) 13 { 14 printf("i am a child,pid: %d,ppid:%d\n]",getpid(),getppid()); 15 sleep(1); 16 cnt--; 17 } 18 exit(0);//让子进程直接退出 19 } 20 //父进程 21 int cnt = 5; 22 while(cnt) 23 { 24 cnt--; 25 printf("i am a father, pid:%d,pptd:%d\n",getpid(),getppid()); 26 sleep(1); 27 } 28