认识操纵体系的各个状态
运行状态
- 有一个在叫做调度器的东西需要包管CPU的资源被合理的利用,以是他需要维护一个运行队列。这些队列里就是历程的task_struct,用来表示每个历程。而调度器把这些task_struct链接起来,这些链接起来的历程是随时可以调度的。以是,运行状态并不是历程正在运行,而是只要是在调度队列中,随时等候被CPU调度实行即可。
阻塞状态
- 操纵体系管理硬件也是先描述再构造,以是这时候也是把硬件的task_struct放在一个叫做设备等候队列的里面。如果说我们的硬件还没准备好举行读写类似一样的工作,那么我们的task_struct就需要在设备等候队列中等候,等候硬件动作停当后,再从设备等候队列中删除,链入到CPU的运行队列里面,准备调度。(这也对应了我们上节说的,对软件和硬件的管理实际上就是对数据结构的增删查改)
- 比如说我们调用scanf函数实际上就是在等候键盘读入数据,然后我们scanf函数再从键盘那边去取数据。这也对应我们Linux下齐备皆文件的概念,我们就是从键盘这个文件去读取数据。
挂起状态
- 当操纵体系发现内存资源严重不足的时候,就需要想办法腾出空间到磁盘分区(swap)分区中,来缓解内存资源紧张的情况。我们称被置换的历程的状态叫做挂起状态
- 留意,为了方便队列对历程的管理,我们交换到swap分区中一般只是把历程的代码和数据放在swap分区中,不会把task_struct这个描述结构体交换过去。然后比及内存资源不那么紧张了,再把历程的代码和数据通过某种方法恢复到对应的task_struct中(什么方法恢复的,后面虚拟地址空间讲)
- 一个历程是否被挂起并不需要让你知道,就跟你把钱存银行里一样,你并不知道自己的钱是被干什么用了,银行并不会告诉你,只是你想要的时候他能实时给到你就好!!
运行挂起状态和阻塞挂起状态
- 一天内存资源很紧张,4GB的内存只剩下100MB了,这时候操纵体系忙得很,突然看到两个历程在等候队列里面躺着睡觉,这时候操纵体系上去问,你们两个还不赶快准备去运行在这里干啥,历程回答道:我们两个的需要资源还没停当呢,现在还不能去运行,操纵体系说:现在内存空间不足了,你两不要在这里占地方去磁盘的swap分区里面等候。如果停当了需要被运行了,你们再回来。
这就是阻塞挂起状态。
- 乃至在内存很严重不足的时候,操纵体系会把调度队列中的准备实行(CPU一次只能实行一个历程),把这些准备实行的PCB的代码和数据也交换到swap分区中,这就是运行挂起状态
Linux内核管理历程状态的方法
如何检察历程状态
ps aux / ps axj 下令
R状态
- R运行状态(running): 并不意味着历程一定在运行中,它表明历程要么是在运行中要么在运行队列中。和我们上面说的一样
S状态
- S睡眠状态(sleeping): 意味着历程在等候变乱完成(这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep))。 ——>实在就相当于是阻塞状态(由于需要跟硬件保持接洽)
- 接下来我们通过一段代码来了解一下这个状态
- 题目:为什么是s状态呢?
- 由于printf函数需要常时间和显示器这个设备建立接洽,但是由于CPU的处置惩罚太快了,显示器有点跟不上速率,以是printf输出这个历程就处于等候显示器的状态
- 还有一个题目就是,为什么叫做可中断休眠?由于处于这个状态是可以被直接杀死的,这也和我们后面的不可中断休眠的区别。
D状态
- D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的 历程通常会等候IO的竣事。
- 区分:S是浅度睡眠(可以被唤醒)、D是深度睡眠 (不相应如何需求)为了能够更好地理解他们的区别,以下会报告一个故事
- 本日呢历程有一个任务把100MB的数据写入磁盘,这个时候历程对磁盘说,磁盘啊,你帮我把这100MB的数据写入到磁盘中,磁盘说:没题目,但是我不能包管我能成功写入到里面去哦,但是不管写入成功还是失败,我都会给你效果,历程你一定要等我给你效果哈。这个时候历程说好的,于是就躺在椅子上安定等着,这个时候操纵体系由于资源不足忙里忙外的,看到历程在哪里躺着,问:你这个历程怎么回事,你没看到资源空间已经不足了吗?你还在这里啥事都不做,占着地方。于是操纵体系直接把历程杀掉了。这时候磁盘写入失败了,跑出来说,历程我写入失败了,把这100MB数据拿归去处置惩罚一下,没有声音,就一直在找历程,找了半天没找到,这100MB数据也没人管理了。假设我们是银行的体系,这个时候行长发现了本日有100MB数据丢失了,导致1w块钱的丧失,这时候就把应该存入数据的磁盘叫过来问怎么回事,磁盘说,冤枉啊,我写入失败了这100MB没历程管理啊,我叫他在哪里等我的,出来就没人影了。这时候问历程,历程说:你看我干嘛,我在哪里等得好好的,操纵体系看我不顺眼直接把我杀了。这时候问操纵体系:操纵体系说:行长,那个时候资源太紧张了,你曾经说过,如果资源太紧张了我是有杀历程的权力的。这个时候行长觉得好像都有原理,以是行长这样规定,如果历程状态是D,操纵体系不管资源是否紧张,都没有权力杀掉状态是D的历程,解决了数据丢失的题目。
T状态
- T制止状态(stopped): 可以通过发送 SIGSTOP 信号给历程来制止(T)历程。这个被暂停的历程可 以通过发送 SIGCONT 信号让历程继续运行。
指令:kill -l
- 其中9是杀历程,19是暂停历程,18是重启历程,这些东西我们在信号那个章节仔细解说,这里了解即可
- T状态存在的意义:大概是需要等候某种资源,或者是我们单纯不想让该历程运行!!
- 应用场景就是gdb,当步伐运行起来的时候遇到了我打的一个断点,然后就停下来了,这是这个过程就可以被应用于gdb这个历程在控制被调试的这个历程!!
T和t
X状态
- X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
Z状态(僵尸状态重点)
- 比如你正在公园跑步,突然瞥见一个老人走了两步就倒地上了,这时候你叫了120,120发现人已经没救了,于是走了。然后你又叫了110,但是110并不会立马清理现场,由于本质查明真相的原则,大概会需要先带着法医对尸体举行检测然后再确认效果,比如说非常死亡或者是正常死亡(由于家人需要了解情况),然后才会去清理现场。 实在这段已经死亡一直到清理现场之前的这段时间,就相当于是僵尸状态。
- 那么我们站在历程的角度考虑,一个子历程死亡后我们父历程就犹如子历程的家人一样需要关心子历程的死亡信息,以是子历程死亡后并不能马上释放,而是处于僵尸状态,等候父历程往返收资源。
- 接下来我们用一段代码来看一下,历程退出,父历程还在的情况子
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- int main()
- {
- printf("I am running...\n");
- pid_t id = fork();
- if(id == 0){ //child
- int count = 5;
- while(count){
- printf("I am child...PID:%d, PPID:%d, count:%d\n", getpid(), getppid(), count);
- sleep(1);
- count--;
- }
- printf("child quit...\n");
- exit(1);
- }
- else if(id > 0){ //father
- while(1){
- printf("I am father...PID:%d, PPID:%d\n", getpid(), getppid());
- sleep(1);
- }
- }
- else{ //fork error
- }
- return 0;
- }
复制代码
- 这里的exit函数实在就是竣事该步伐的实行,不再实行后面的代码。后面历程替换会细讲。
- 运行该代码后,我们可以通过以下监控脚本,每隔一秒对该历程的信息举行检测。
- while :; do ps axj | head -1 && ps axj | grep proc | grep -v grep;echo "######################";sleep 1;done
复制代码
- 以是只有子历程死亡了,但是父历程如果一直没有接纳子历程的资源。子历程就一直处于僵尸状态。相关的资源尤其是task_struct不能被释放,由于父历程要接纳子历程资源来知道子历程的信息。
题目1:为啥要有僵尸状态?
由于父历程很关心子历程,关心它交给子历程的任务,子历程完成的怎么样,以是需要子历程在父历程接纳它的资源之前一直不能退出。
题目2:僵尸历程的危害是什么?
- 僵尸历程的退出信息一直保持在task_struct(PCB)中,如果僵尸历程一直不退出,那么我们就需要一直维护PCB。
- 如果一个父历程创建了很多子历程,但是这些子历程最后都处于僵尸状态,父历程一直不去接纳资源,那么就会造成资源浪费,由于数据结构对象自己就要占用内存。
- 僵尸历程一直无法接纳,如果这样的历程越多,那么内存可用的资源就越少,也就是说,僵尸历程会导致内存泄露(是步伐不断地占用内存,但却不释放不再需要的内存,导致可用内存渐渐减少)。
孤儿历程
- 孤儿历程就是父历程先退出 子历程还在,导致子历程没有父历程接纳
- 例如,对于以下代码,fork函数创建的子历程会一直打印信息,而父历程在打印5次信息后会退出,此时该子历程就酿成了孤儿历程。
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- int main()
- {
- printf("I am running...\n");
- pid_t id = fork();
- if(id == 0){ //child
- int count = 5;
- while(1){
- printf("I am child...PID:%d, PPID:%d\n", getpid(), getppid(), count);
- sleep(1);
- }
- }
- else if(id > 0){ //father
- int count = 5;
- while(count){
- printf("I am father...PID:%d, PPID:%d, count:%d\n", getpid(), getppid(), count);
- sleep(1);
- count--;
- }
- printf("father quit...\n");
- exit(0);
- }
- else{ //fork error
- }
- return 0;
- }
复制代码
- 观察代码运行效果,在父历程未退出时,子历程的PPID就是父历程的PID,而当父历程退出后,子历程的PPID就酿成了1,即子历程被1号历程领养了。
题目1:为什么要领养?
- 由于孤儿历程未来也会消亡,也会被释放,不能让他一直占用内存资源
题目2:ctrl+c为什么无法中断非常历程,他的底层原理是什么??
- 本质上就是刹时父历程会被bash历程接纳掉。以是子历程也在父历程退出的刹时接纳了。以是由于子历程的PPID不是bash历程而是体系历程,以是无法中断
题目3:子历程是bash历程的孙子历程,为什么父历程消亡后不由bash历程接纳而是交由体系历程接纳???
- ——>由于bash做不到,由于孙子历程不是他去创建的!! 他没有这个权限,而体系历程可以做到,由于要将孤儿历程托孤给体系历程 固然不同的操纵体系详细的实现方法大概也不同!!
Linux到底是如何维护历程的
- 我们前面说过,历程之间的task_struct是用双链表来链接起来的,以是历程一定利用双链表这个数据结构来维护的。但是,实际场景不但如此。有大概他还嵌套放在队列,二叉树,红黑树管理。那到底是怎么实现维护task_struct之间的关系呢?
- 起首并不是整个task_struct结构体链接在一起,而是通过单独创建一个llist_head(list_head结构体两个成员next和prev)结构体来举行链接,以是实在节点都是指向该结构体中间的位置而不是头部
- 既然链表中链接的不是头部,那么我们通过节点的链接找到task_struct的其他成员呢?
- 将0强转成task_struct结构体的范例,实在就是假设在0位置都有一个task_struct结构体大小的内存,然后找到他的links节点并取他的地址,由于低地址是0,那么找到links节点的地址就相当于知道了links在task_struct中位置的偏移量——> &(task_struct*)0—>links
- 然后用当前指向的位置links地址减去links的偏移量(对于头的),就可以找到task_struct结构体的头部了。
- 最后将这个头部的地址强转成task_struct* 就可以拿到整个PCB结构体了 ,就能访问里面的其他数据
总结:( task_struct*)(links-&(task_struct*)0—>links)get到 other结构体成员
补充知识
历程退出了,内存泄露题目是否还存在?
答案是不存在,历程退出了,体系会自动接纳那块内存。那种历程最怕内存泄露,常驻内存的历程,比如操纵体系,,他一直不退出,为啥一直不退出,由于操纵体系要管理软件和硬件这些啊,除非是体系关机了才退出,如果他发生内存泄露,那么就会题目很大。
关于内核结构重复申请优化
我们在C++中,写一个类,实例化出一个对象。是不是很方便啊,由于这个类在步伐运行中一直存在,那么我们在Linux中描述信息的task_struct也可以在接纳的时候缓存在一个数据结构对象的链表中,如果我再次需要这个task_struct信息的对象,我们就可以直接从链表中拿出来实例化,不需要重新写一个task_struct
end
感谢大家的阅读,希望对你们有帮助。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |