【Linux】Linux进程的理解 --- 进程描述符、状态、优先级、切换…

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

如果不改变自己就别把跨年搞的和分水岭一样记住你今年是什么吊样明年就还会是什么吊样
在这里插入图片描述

文章目录



一、冯诺依曼体系结构硬件

在这里插入图片描述

1.冯诺依曼体系结构中的存储器指的是内存带电存储具有掉电易失的特点。

2.CPU中含有能够解释计算机指令的指令集指令集又可分为精简指令集和复杂指令集这也正是为什么你的程序能够运行起来的原因因为CPU认识并理解你的二进制程序代码你的二进制程序会被CPU认为是一堆指令的集合CPU直接执行这些二进制指令就OK了。

3.某些外部设备例如磁盘、网卡等既属于输入设备又属于输出设备。

4.CPU在读取和写入的时候在数据层面只和内存打交道不和外设直接沟通这样有利于提高整个计算机的运行效率。

5.磁盘上的文件程序想要运行必须要加载到内存里面因为CPU只能从内存中访问你写的数据和代码我们平常所进行的编程其实就是在为CPU准备数据和代码等CPU过来读取这些代码并执行他这些都是冯诺依曼体系结构所决定的
在这里插入图片描述

下面从软件数据流的角度来更深入的理解冯诺依曼硬件结构体系
在这里插入图片描述
1.
如果我向我的好友发送消息从硬件结构来看前提应该是我们各自都打开了QQ程序并将QQ这个程序加载到了内存里面CPU会执行QQ程序的代码我就可以通过代码里面的scanf或cin等语句的执行利用键盘发送消息“你好呀你今天吃饭没”等等消息这些消息数据会由输入设备键盘加载到内存中的QQ程序里面然后CPU对这些消息做出处理将处理后的结果返回给内存里面然后这些消息会从内存进一步加载到外部设备网卡和显示器等里面我的朋友的笔记本的输入设备网卡会接收这些消息并将这些消息加载到他的内存中的QQ程序里面然后CPU做出信息的分析将结果返回到内存里面最后这些处理过后的信息会进一步加载到我朋友笔记本上的显示器中这样就完成了信息的发送和接收等。
2.
如果是发送文件其实就是从磁盘这样的外设中将信息加载到内存中的QQ程序里面然后通过网卡将信息传输过去我的朋友就可以用他的网卡接收到这些信息并把我发送的信息加载到内存里面最后在输出设备磁盘里面重新刷新就会出现接收到的文件信息了。

二、操作系统软件

1.操作系统是什么

1.操作系统是一个进行软硬件资源管理的软件

2.操作系统包括进程管理内存管理文件系统驱动管理这些都是操作系统对于软件的管理除了管理这些操作系统还承担管理冯诺依曼硬件体系结构。

3.为什么操作系统要进行管理呢因为操作系统可以通过合理的对于软硬件资源管理手段来为用户提供良好的稳定的、安全的、高效的执行环境目的
在这里插入图片描述

2.如何理解管理管理的本质

1.
管理者和被管理者之间是一个什么样的逻辑关系呢就像我们在大学生活中的校长我们很少直接与校长进行交互但校长依旧管理着我们这是怎么做到的呢还比如公司中的员工和CEOCEO不会和员工直接对话、嘘寒问暖等等但公司依旧可以正常运行正常的被管理这又是怎么做的呢

2.
我们可以通过这样的现象得出一个既定的事实那就是管理者和被管理者不需要直接进行交互管理者依旧能把被管理对象管理起来。

3.
首先管理者需要有重大事宜的决策能力并且决策是要有依据的同时管理者要拥有被管理对象的全部数据只要拥有了被管理对象的数据通过数据的变化和更新管理者就可以与之对应做出管理的方案。

4.所以管理的本质就是对数据做出管理

3.操作系统如何一直拿到硬件的数据驱动程序的引出

1.
我们在上面谈到过管理者和被管理者是不直接进行交互的就比如校长是不和我们大学生直接进行面对面的谈论和交流的那么校方是如何拿到每一个大学生的数据呢操作系统又是如何拿到以冯诺依曼体系为基础的所有硬件结构的数据呢

2.
这都需要一个执行者执行者负责拿到被管理者的全部信息现实中的管理者可能就是我们的班长或辅导员等等他可以拿到我们的全部数据最后将这些数据统计汇报给校方自然计算机中也需要这么一个执行者这个执行者就是驱动程序

3.
所以驱动程序会做两件事情第一件当然是和被管理者也就是底层硬件直接接触拿到被管理者的所有数据第二件事情就是执行管理者的命令也就是操作系统的命令如何执行执行什么等决策都是由管理者操作系统来决定的

4.
OS可以根据硬件的数据变化来命令驱动程序对硬件进行相应的管理这样就可以实现对硬件的管理命令中枢在OS驱动程序既要将硬件数据返回给操作系统又要执行操作系统的命令以此来管理好硬件资源。

在这里插入图片描述

4.操作系统对于庞大的软硬件的数据量如何进行管理先描述再组织

1.
我们知道计算机的底层硬件是非常多的光单个硬件的数据量其实就已经非常多了更别说所有的底层硬件的数据了那将是非常庞大的数字操作系统对于驱动程序返回来的如此庞大的数据量一定会非常的头疼如果没有合理的管理这么多的数据一定会乱起来的计算机就无法正常运行了。

2.
例如校方获得学生的庞大信息之后他一定不会拿许许多多的表格和文件来存放这些信息这样的效率非常的低所以校方会有自己的一个系统这个系统里面会存储学生的信息以这样的方式来对学生进行管理所以可以总结出来管理的方法本质逻辑就是先描述再组织校方通过一个程序例如这个程序利用了链表将学生信息存储起来那么每个节点就需要描述好每个学生入学时间高考成绩在校表现等等然后将节点组织成链表通过数据结构的方式来将学生的信息组织起来进行统一的管理。

3.
所以所有的“管理”本质逻辑都是“先描述再组织。”描述是编程语言的话题组织是数据结构的话题组织其实就是对被管理对象进行建模的过程

4.
操作系统对于硬件是先描述再组织进行管理的那对于软件的管理呢进程、文件系统、内存、驱动、系统调用接口等软件操作系统又是怎么管理呢答案还是“先描述再组织”操作系统依旧通过获得他们的数据通过类或结构体因为Linux内核是用C语言写的将这些获得的数据描述起来然后再通过链表或者其他更高效的数据结构来将这些数据组织起来然后进行管理。

5.人能管理事物人也能管理人。软件能管理硬件软件也能管理软件。

5.计算机的软硬件结构体系计算机的层状结构

1.
首先操作系统是不相信任何的用户的如果用户随意篡改操作系统的源代码那计算机就无法正常的使用并且如果这些源代码被公开这很有可能让操作系统受到 “伤害” 所以操作系统是无法分辨一个用户会不会乱来的那么操作系统就有保护自己的义务但同时操作系统又需要对上服务好用户所以可以得出来一个矛盾操作系统不可以直接开放给用户使用但是操作系统还需要对用户开放良好的服务。

2.
这时操作系统会提供给用户一些系统调用接口这些接口可以被用户所调用如果用户的操作不合法那就不能正常调用到这些系统接口我们的请求就不会被操作系统所接收如此一来这些系统调用接口就可以变相的保护操作系统并且还可以给用户提供服务。

3.
这些系统调用接口都是由C语言写出来的所以这些接口都是C式的接口说白了就是操作系统通过C语言给我们提供了一些系统级别的函数调用的接口。

4.
当然这些接口普通人是不会使用的随之在这些系统级别的接口外面一层又开放了用户操作接口用户可以通过自身的一些操作来调用这些用户操作接口以此使得操作系统能够更加先进的服务用户。

在这里插入图片描述

6.系统调用和库函数的区别上下层关系

1.
在开发角度操作系统对外会表现为一个整体但是会暴露自己的部分接口供上层开发使用这部分由操作系统提供的接口叫做系统调用接口。

2.
系统调用在使用上功能比较基础对用户的要求相对也比较高所以有心的开发者可以对部分系统调用进行适度封装从而形成库有了库就很有利于上层用户或者开发者进行二次开发许多的C/C++库其实就是系统调用接口封装得来的所以两者其实是上下层的关系。

在这里插入图片描述

三、进程

1.OS如何管理进程先描述再组织进程控制块PCB

1.
首先程序的本质其实就是文件该文件可以被永久性的存放在磁盘当中一个加载到内存中的程序我们就称之为进程在windows下我们可以看到许多进程包括正在运行的和后台运行的进程对于如此多的进程操作系统是一定要进行管理的那该如何进行管理呢

在这里插入图片描述

2.
管理的本质是对数据进行管理管理的逻辑是先描述再组织在Linux中操作系统会通过task_struct结构体
task_struct转载博客园童嫣博主的文章将每一个进程的所有属性抽象化描述起来Linux操作系统再通过双向循环链表的数据结构将数量庞大的进程进行组织这样的话管理进程就变成了对进程所对应的PCB进行相关的管理。
在这里插入图片描述

3.
进程的数据被存放在一个叫做进程控制块的数据结构当中进程控制块又可以称之为PCBprocess control block进程控制块中包含进程标识符、上下文数据、进程调度信息、进程控制信息、IO状态信息以及进程对应的磁盘代码等Linux操作系统中进程控制块其实就是struct task_struct结构体windows操作系统中进程控制块其实就是执行体进程块struct _EPROCESS

4.
进程和程序相比进程是具有动态属性的程序仅仅只是一堆代码形成的文件而已所以我们将进程提炼出来进程=进程控制块内核数据结构 struct task_struct+ 进程对应的磁盘代码

在这里插入图片描述

2.查看进程的两种方式

进程在调度运行的时候进程就具有动态属性

一、ps指令
ppid父进程idpid是进程idpgid是进程组idsid会话idTTY终端STAT状态uid用户idCOMMAND代表哪个进程

[wyn@VM-8-2-centos test_dir]$ ps ajx | head -1 && ps ajx | grep "myproc"
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
12672 15583 15583 12672 pts/1    15583 S+    1001   0:00 ./myproc
13962 16742 16741 13962 pts/0    16741 S+    1001   0:00 grep --color=auto myproc

二、ls指令
我们还可以通过根目录下的proc目录来查看进程我们的进程也可以被当作一个目录Linux下一切皆文件

ls /proc/进程的pid

在这里插入图片描述
三、如果删除掉了进程对应的磁盘上的二进制可执行程序进程还会运行吗
答案是没有任何影响完全可以继续运行所以理论上来讲一旦程序加载到内存之后进程和程序就没关系了但是进程中的可执行程序文件会冒红。
在这里插入图片描述
4.杀掉进程

kill -l --- 查看kill指令选项
kill -9 + 进程id --- 杀掉进程

在这里插入图片描述
在这里插入图片描述

3.与进程相关的系统调用

3.1 getpid() && getppid()获取进程的标识符

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 
  5 int main()
  6 {
  7     while(1)
  8     {
  9         printf("我是一个进程!,我的进程id是%d,我的父进程pid是:%d\n",getpid(),getppid());                                                                              
 10         sleep(1);
 11     }
 12     return 0;
 13 }

1.
我们可以看到进程的id随着我们的多次运行会不断的变化但是父进程的id一直不变并且我们还查看到了父进程的名字是bashshell有多种但bash是最常见的一种bash就是centos系统下的shell
Linux下有几种shell转载自cunchi4221
博主的文章
所以当前进程的父进程就是命令行解释器bashbash的进程id系统会自动给我们分配好如果我们杀掉了命令行解释器bash这个进程的话我们就会自动退出xshell工具。
在这里插入图片描述
2.
下面便可以看到父进程id始终不变我的代码对应的进程id会因为程序的多次运行而变化并且我的进程的父进程是bash所以我们可以得出结论命令行上启动的进程一般它的父进程没有特殊情况的话都是bash

在这里插入图片描述在这里插入图片描述

3.2 fork()创建进程

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 
  5 int main()
  6 {
  7     // 创建子进程 -- fork是一个函数 -- 函数执行前只有一个父进程bash派生的-- 函数执行后就会有父进程和父进程创建的子进程。
  8     fork();
  9 
 10     printf("我是一个进程!,我的进程id是%d,我的父进程pid是:%d\n",getpid(),getppid());
 11     sleep(3);
 12 
 13     return 0;                                                                                                                                                        
 14 }

1.
由程序运行结果可以看到printf被执行了两次这是怎么一回事啊这其实是因为子进程进程做了父进程一模一样的事情他把代码也执行了一遍。

下面的7088进程的父进程就是14921492实际上就是bash7088的子进程是7089这个进程就是fork函数创建出来的子进程这个子进程的父进程是7088也就是bash的子进程所以7088即是bash的儿子又是fork产生的进程7089的爹。
在这里插入图片描述

2.
通过man手册在底行中输入/加查找内容就可以查看到fork函数的返回值它的意思是创建子进程如果成功的话就会将子进程pid返回给父进程数字0会被返回给子进程如果创建失败的话返回-1给父进程没有子进程的创建。

在这里插入图片描述

下面我们再来看一段fork接口的常用形式的代码和其运行结果

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 
  5 int main()
  6 {
  7     // 创建子进程 -- fork是一个函数 -- 函数执行前只有一个父进程bash派生的-- 函数执行后就会有父进程和父进程创建的子进程。
  8     pid_t ret=fork();
  9     //fork使用后一般要用if进行分流
 10     if(ret==0)
 11     {
 12         // 子进程
 13         while(1)
 14         {
 15             printf("我是一个进程!,我的进程id是%d,我的父进程pid是:%d,ret是:%d\n",getpid(),getppid(),ret);
 16             sleep(1);
 17         }
 18 
 19     }
 20     else if(ret>0)
 21     {
 22         // 父进程
 23         while(1)
 24         {
 25             printf("我是一个进程!,我的进程id是%d,我的父进程pid是:%d,ret是:%d\n",getpid(),getppid(),ret);
 26             sleep(2);
 27         }
 28     }
 29     else 
 30     {
 31 
 32     }
 33     return 0;                                                                                                                                                        
 34 } 

在这里插入图片描述
3.
fork之后会有父进程和子进程两个进程在执行后续的代码并且后续的代码被父子进程共享我们可以通过返回值的不同让两个进程执行后续共享代码的不同部分。
我们也可以通过这样的手段来让两个进程执行不同的任务这就是所谓的并发式的编程。

四、进程状态

1.普遍的操作系统层面理解总结进程状态运行、阻塞、挂起状态

1.
计算机在开机的时候操作系统就会被加载到内存里面磁盘中的程序在运行的时候也会被加载到内存里面实际上是加载到操作系统内部受操作系统的管理我们知道程序运行的时候是需要CPU进行读取进程的代码并计算的但进程的数量一定会比CPU多那CPU该怎么一个个的读取进程代码并计算呢答案是通过运行队列数据结构来对进程的运行进行管理。

2.一个CPU匹配一个运行队列

3.
让进程入队列等待CPU资源。本质将该进程的task_struct结构体对象放入CPU的运行队列struct runqueue中。操作系统操作的不是加载到内存中的程序操作的是进程对应的PCB进程控制块内核数据结构。

4.
CPU进行进程的调度其实就是从自己的运行队列里面找到进程对应的PCB然后执行进程对应的代码和数据

在这里插入图片描述

5.
进程的运行状态指的并不是这个进程正在被运行因为这个数据是没有意义的CPU太快了几微秒就可以运行完一个进程而是指的是这个进程的PCB在CPU的运行队列runqueue当中只要这个进程在运行队列里那么这个进程的状态就是运行状态

6.
进程的状态其实就是进程内部的属性那么这个状态其实就是存在于进程对应的PCB当中状态在PCB里面其实就是一些整数每个整数对应不同的状态可以用#define R 1类似这样的表示来区分不同的进程状态如下Linux内核源代码所示

	/*
	* 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 */
};

7.
不要只以为进程只会等待占用CPU资源进程也可能随时随地占用外设资源

8.
进程或多或少都要访问硬件例如我们平常代码所写的printf、cout要访问显示器对文件进行IO要访问磁盘scanf、cin要访问键盘但这些外设的运行速度是很慢的另一方面这些外设的数量也是较少的所以很有可能出现多个进程访问一个硬件的情况但这个硬件一次只能服务一个进程所以其他进程就需要排队。

9.
当CPU调度的某个进程需要访问外设时那操作系统就会把这个进程放到硬件的结构体描述里面的等待队列task_struct * queue里面直到硬件准备就绪此时这个进程的状态就是阻塞状态表示当前进程不能直接被CPU调度需要等待某种硬件资源的就绪。值得注意的是和CPU运行队列相同操作系统操作的依旧是进程对应的PCBtask_struct结构体对象将PCB放到硬件的结构体内部的等待队列中

10.
当进程访问的硬件就绪之后表示进程又可以运行了这个时候操作系统就又把该进程对应的PCB中的状态改为R然后将PCB放到CPU的运行队列里面此时这个进程的状态就从阻塞状态改为运行状态了。

在这里插入图片描述

11.
所谓的进程的不同的状态本质上其实就是进程在不同的队列中等待某种资源。
在CPU的运行队列中的进程一般称为R运行状态进程在硬件所对应的内存中的结构体描述里面的阻塞队列里的进程称之为阻塞状态进程。

12.
当多个进程的状态是阻塞的时候这些进程都无法被立即调度也就是无法被CPU立即执行并且排队的进程其实是要等待很长的时间的因为外设的速度和CPU的速度相比简直是太慢了差的不是一星半点儿这个时候PCB和其对应的进程的代码和数据就会占用内存这些进程短期内不会使用还白白的占用着内存空间所以操作系统就把这些进程的代码和数据暂时保存到磁盘上但进程对应的PCB还留在内存里面操作系统这样的作法就可以节省内存空间。

13.
我们将代码和数据换出到磁盘的这种进程称之为挂起进程该进程的内核数据结构依旧在内存它的代码和数据被操作系统暂时换出到磁盘里面以节省内存空间给其他需要加载到内存的程序使用。

14.
等到进程对应的硬件资源就绪之后操作系统再将进程的代码和数据换入到内存当中当进程占用硬件资源结束后操作系统再将PCB放入CPU的运行队列里面使进程重新运行起来。
我们将进程的代码和数据加载到内存和暂时保存到磁盘称为内存数据的换入换出。

在这里插入图片描述
15.
阻塞不一定是挂起但挂起一定是阻塞。
如果内存空间特别够的话操作系统没有必要将阻塞的进程挂起。

2.具体的操作系统Linux下的进程状态

2.1 vim批量化注释

批量化注释 ctrl+v进入块选择模式kj上下移动光标选择你要注释的行按下大写的I并输入注释符号//输入完毕按下esc退出即可vim会自动在你选择的行首前面加上注释符号。
取消注释 ctrl+v进入块选择模式利用hjkl选择两列的//注释符号因为//是两列最后按下d删除即可取消注释。

2.2 运行状态+休眠状态阻塞状态的一种是否挂起未知这完全取决于OS不同的OS不一定将挂起暴露出来给你

下面的代码其实是仿造的一种计算密集型进程演示进程的R状态。

 1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 
  5 int main()
  6 {
  7  	                                                          
  8     while(1) 
  9     {}
 10     	
 11 }

1.
当程序代码仅仅只是一个死循环时我们将程序运行起来然后查看进程状态可以很明显的看到状态是R也就是运行状态。

在这里插入图片描述
下面的代码其实是仿造的一种IO密集型进程演示进程的S状态

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 
  5 int main()
  6 {
  7      int cnt=0;                                                               
  8      int a=0;
  9     while(1)
 10     {
 11         a=1+1;
 12         printf("a的值是:%d,running flag=%d\n",a,cnt);
 13         sleep(1);
 14     }
 15 }

2.
在代码中添加printf语句之后程序还在运行但进程的状态是S休眠状态这是为什么呢
因为我们的代码中访问了显示器显示器是外设速度非常慢CPU会飞速的运行完进程的所有代码但是我们写的进程需要占用硬件资源每一次占用硬件资源都要等显示器就绪这会花很长的时间和CPU相比大概率99%的时间是进程在等显示器就绪也就是在等IO就绪1%的时间是CPU在运行进程的代码所以我们在查看进程状态的时候极大概率上查到的都是S休眠状态。更形象化的说明就是在进程访问完毕一次显示器的时候CPU已经将这个死循环代码执行了50、60万次所以我们在查看进程状态的时候进程都是在等IO就绪的所以就会查看到进程是休眠状态这也是阻塞状态的一种。

在这里插入图片描述

3.
CPU计算的速度和IO的速度差别大概是几十万倍。

在这里插入图片描述
4.操作系统不会让你看到挂起状态因为这对你来说毫无意义操作系统不需要告诉你你也不需要知道。

2.3 停止状态stopped阻塞状态的一种是否挂起未知这完全取决于OS不同的OS不一定将挂起暴露出来给你

1.
当进程被停止的时候其实也是阻塞状态的一种因为当前没有代码再运行了。
这个进程当然也可以被挂起但这一点是未知的这完全取决于OS。

kill -19 + 进程id --- 停止运行进程
kill -18 + 进程id --- 继续运行进程

在这里插入图片描述

在这里插入图片描述

2.状态后面带+表示前台进程状态后面不带+表示后台进程。

3.
前台进程在运行的时候shell命令行无法继续获取命令行解析但是可以通过ctrl+c将进程终止掉。
后台程序在运行的时候shell命令行可以继续获取命令行解析但无法通过ctrl+c将进程终止掉需要通过kill指令 + -9信号才可以将进程终止掉也就是杀掉进程。

下面是运行状态的前台和后台进程
在这里插入图片描述
下面是休眠状态的前台和后台进程
在这里插入图片描述

2.4 磁盘休眠状态disk sleep阻塞状态的一种高IO的环境可能出现这样的状态。同样挂起是未知的这完全取决于OS不同的OS不一定将挂起暴露出来给你

1.
S状态是浅度睡眠状态是可以被终止的通过ctrl+c或kill -9 pid两种方式进行分别进行前后台终止。

2.
阻塞进程过多时操作系统会将一些进程挂起以此来解决内存空间不足的问题如果挂起依旧无法解决内存空间不足Linux就会将进程杀死但是一旦杀死进程很有可能导致进程对应的IO过程失败从而丢失大量数据这会对用户造成巨大的损失所以就出现了一个新的进程状态深度睡眠状态这样的进程无法被OS杀掉一般情况下只能等待IO过程结束让进程自己醒来重新投入CPU的运行队列重新继续运行进程。万不得已可以通过断电的方式来杀掉深度睡眠的进程

在这里插入图片描述
3.
D状态是深度睡眠状态在该状态的进程无法被OS杀掉只能通过断电或者进程自己醒来来解决

4.
当然深度睡眠的状态一般不会出现只有高IO的情况下运行某个程序时进程才有可能出现深度睡眠的状态。

5.
如果想要查看该进程状态可以了解dd指令它可以营造高IO的状态这样的状态下运行的进程有可能会出现深度睡眠状态也就是D状态。
dd指令详解转载自csdn博主远近长安博主的文章

2.5 跟踪状态tracing stop阻塞状态的一种是否挂起未知这完全取决于OS不同的OS不一定将挂起暴露出来给你

Makefile文件内容:
  1 myprocess:myprocess.c
  2     gcc -o $@ $^ -g                                                                                                                                                  
  3 .PHONY:clean
  4 clean:
  5     rm -f myprocess

$@代表冒号左侧的目标文件$^代表冒号右侧的依赖文件列表这些是makefile中的特殊符号

1.
我们在调试某个二进制程序的时候其实就是在调试该进程当进程中有断点的时候gdb中按下r进行调试运行此时就会由于断点的存在而停下来这其实表示的就是我们当前运行的进程停下来了等待我们查看当前进程的上下文数据这就是tracing stop状态跟踪状态。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
2.
Linux内核源代码中跟踪状态用的还是T表示这里为了区分跟踪和停止状态将T改为t

2.6 僵死状态zombie进程退出状态未被读取PCB依旧占用内存资源进程资源未被回收干净

1.
进程被创建的目的其实是为了完成某个任务这个任务是OS或用户布置的这也正是进程对应的内核数据结构叫做task_struct的原因。

2.
当进程完成任务之后父进程或者OS一定得知道这个任务完成的结果是怎么样的所以在进程终止的时候OS机制是不可以立即释放该进程占用的内存资源的必须保存一段时间让父进程或者OS来读取进程的结果。

3.
进程退出的信息一般都会在其对应的PCB中保存一段时间等待父进程或者OS读取。

4.只要某个进程退出但是没有被父进程或者OS回收这样的进程我们就称之为僵尸进程。

5.我们可以创建一个子进程让父进程不要退出而且什么都不做一直运行就好不要回收子进程然后再让子进程正常退出此时这个子进程就会处于一个僵死状态因为进程退出后没有人回收它。
在这里插入图片描述
下面是演示僵尸进程的代码

  1 #include  <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 
  5 int main()
  6 {
  7 
  8     pid_t id = fork();
  9 
 10     if (id==0)
 11     {
 12         printf("I am a child processpid:%dppid:%d\n",getpid(),getppid());
 13         sleep(5);
 14         exit(1);
 15     }
 16     else
 17     {
 18         while(1)
 19         {
 20             printf("I am a parent processpid:%dppid:%d\n",getpid(),getppid());
 21             sleep(1);                                                                
 22         }
 23     }
 24     return 0;
 25 }

一个可以每次间隔一秒查看进程状态的脚本

while :; do ps axj | head -1 && ps axj | grep myprocess | grep -v grep ; sleep 1 ; done

在这里插入图片描述

6.
父进程处于浅度睡眠状态因为要等显示器就绪这个我们前面说过但子进程处于Z状态就是僵死状态表示这个进程已经退出但是没有人回收它并且它的COMMAND后面加了defunct这个单词的意思是失效的不起作用的。

在这里插入图片描述
在这里插入图片描述
7.
在结束进程之后操作系统会帮我们将父进程和子进程一起回收掉。以免内存泄露的发生。

8.
进程的退出状态也属于进程的基本信息也是需要数据进行维护的所以这种信息会被保存在进程对应的PCB里面如果进程的状态一直是Z状态的话父进程一直不读取子进程的退出状态那么PCB就需要一直维护这种状态信息虽然子进程对应的代码和数据会被释放但是PCB是不会被释放的因为他需要维护进程的Z状态所以这个时候就会产生内存泄露的问题。

9.僵尸进程无法被杀掉即使通过kill和-9信号也无法杀掉因为它已经死亡了所以无法被杀掉的进程有三个深度睡眠进程僵尸进程死亡进程D状态是不能杀Z和X是无法杀因为已经死了

下面是另一种演示僵尸进程的gif动图手动杀掉子进程但父进程依旧运行不回收

在这里插入图片描述

2.7 死亡状态deadZ状态之后就是X状态PCB也已被释放

1.
当进程死亡后操作系统会立即回收进程的所有资源和数据因为这个过程是非常快的所以我们无法观察到进程的X状态但这个概念比较好理解简单来说其实就是进程被终止掉了它的PCB和对应代码和数据都退出内存不再占用内存资源此时这个进程就是实实在在的死亡状态它的所有资源都被操作系统释放掉了。

在这里插入图片描述

2.8 孤儿进程没爹的孩子OS来当这个爹

1.
子进程先退出父进程不退出继续运行且不回收子进程那么这个子进程就是僵尸进程但如果父进程先退出那么子进程就变成孤儿进程它会被1号进程回收1号进程另一个叫法是init进程init进程会回收孤儿进程剩余资源。

2.
在僵尸进程的最后部分我们手动杀掉了子进程子进程就进入了僵尸状态这里我们手动杀掉了父进程但父进程却没有进入僵尸状态而是直接进入死亡状态只不过我们看不到死亡状态这是为什么呢其实是因为bash回收了父进程的所有资源PCB+代码和数据所以父进程没有变为僵尸进程

在这里插入图片描述
3.
通过进程状态的查看发现子进程的父进程变为1号进程我们称被1号进程领养的子进程为孤儿进程1号进程其实就是操作系统所以实际上子进程是被操作系统领养。

在这里插入图片描述

在这里插入图片描述
4.
如果不领养那在子进程退出的时候子进程就会变为僵尸进程此时就没有人可以回收子进程了就会造成内存泄露所以操作系统为了管理好这些软件资源必须领养这个没爹的孩子

5.
前台进程创建的子进程如果变为孤儿进程那么这个进程会自动被切换为后台进程

在这里插入图片描述

五、进程优先级受nice值调控的priority值

1.什么是优先级

1.先还是后获得某种资源的能力被称之为优先级。

2.先获得就是优先级高后获得就是优先级低

2.为什么会存在优先级

1.
因为资源是有限的。

2.
比如现在有20多个进程要访问CPU但CPU只有一个另外20个进程要访问网卡另外40个进程要访问磁盘但是资源太少了但访问的又太多了此时就会出现优先级的情况。想要访问可以但需要排队这就出现了优先级。

3.Linux优先级特点

1.
进程的优先级和状态一样本质都是PCB里面的一个数字也可能是多个数字操作系统通过这些数字来辨别进程的状态和优先级

2.
通过ps -al指令可以查看到进程的详细信息PRI和NI的值合并在一起代表Linux进程的优先级。PRI其实就是最终优先级只不过它受NI值的调控。

在这里插入图片描述

4.修改Linux下的优先级top指令 + root身份

1.
最终优先级=老的优先级固定为80+nice值Linux支持进程在运行中通过修改nice值的改变来进行进程优先级的调整。

2.
PRI的值越小代表优先级越高输入sudo top指令用管理员的身份去做然后按下r输入对应的进程pid表示你要修改哪个进程的优先级然后输入整数值这个值就是你要修改的nice值。

3.
优先级过度设置会导致CPU调度失衡所以nice值是有范围的不能让你随意过度的调整nice值的范围是-20到19一共40个优先级级别所以最终优先级的范围就是60到99

在这里插入图片描述
4.
下面是nice值分别调整为-100和100后进程的优先级分别为60和99的结果这也就说明OS不会让我们过度随意调整nice值nice值的调整是有一个限度范围的。

在这里插入图片描述
在这里插入图片描述

六、四个补充的进程概念注意区分并行和并发

竞争性: 系统进程数目众多而CPU资源只有少量甚至1个所以进程之间是具有竞争属性的。为了高效完成任务更合理竞争相关资源便具有了优先级值得注意的是进程排队的时候OS操作的是进程对应的PCB而不是进程对应的代码和数据。

独立性: 多进程运行需要独享各种资源多进程运行期间互不干扰就算是父子进程在运行时也是互不干扰的。例如下面的子进程挂掉变为僵尸进程但父进程依旧可以运行所以进程间运行是互不干扰的。

在这里插入图片描述

并行: 多个进程在多个CPU下分别同时进行运行这称之为并行。有几个CPU就可以同时运行几个进程。

并发: 多个进程在一个CPU下采用进程切换的方式来实现多个进程的运行不是同时运行在一个时间段内让多个进程都得以推进称之为并发。

如果只有一个CPU那么在任何时刻只能有一个进程正在运行多个进程的运行依靠的是进程切换呈现给使用者的就是我们好像看到多个进程在同时运行但其实不是这样的任何时刻只能有一个进程在运行。

不是一个进程必须在CPU上跑完才能被CPU拿下来当代计算机采用的是时间片轮转的策略一个进程可以跑一会儿就换到下一个进程在一个时间段内每个进程都可以跑多次呈现出来的现象就是多个进程同时运行例如我的计算机现在可以打开画图工具还可以开直播和别人聊天还可以打开xshell进行linux的学习但这些进程并不是同时在一个CPU下运行的只是采用了进程切换的方式来完成的。

CPU可以在几毫秒的时间内就将每个进程都运行好几次CPU读取代码的速度非常快

时间片轮转百度百科

七、进程切换进程的上下文保护和恢复

1.
一个CPU会有大量的寄存器虽然寄存器很多但是只有一套寄存器寄存器分为用户可见的寄存器和用户不可见的寄存器状态寄存器、权限寄存器等对于寄存器的深入了解可以看看
寄存器详谈转载自知乎博主轩辕之风的文章

2.
CPU永远做三件事情取指令代码被编译器翻译成二进制指令分析指令执行指令在CPU中有一个叫做eip的寄存器专门用来标识下一次应该从当前进程的具体的位置读取相应的代码和指令

3.
当进程在运行的时候一定会产生非常多的临时数据这些临时数据只属于当前进程虽然CPU内部只有一套寄存器硬件但是寄存器里面保存的数据是属于当前进程的寄存器硬件和寄存器内的数据是两码事

4.
进程在运行的时候的确是占有CPU的但进程并不是一直占有CPU直到进程运行结束这个我们前面说过计算机有时间片轮转的策略每一个进程在运行的时候都有自己的时间片所以在进程重新被CPU调度的时候CPU必须知道上一次这个进程运行到什么时候了当进程被换下去的时候进程的运行信息会被存在操作系统里面具体什么位置这里不详谈以便下次CPU重新调度时进程能够正常运行这叫做进程的上下文保护。当进程被CPU重新调度上来时首先要做的第一件事情就是读取操作系统中进程运行的相关数据这叫做进程的上下文恢复

5.
在任何时刻CPU中的寄存器虽然被所有进程共享但寄存器内的数据是每个进程私有的这些寄存器内的数据就是进程的上下文数据这些寄存器内的数据是时刻在不断变化的OS会实时加载当前运行进程的上下文数据到寄存器里面

在这里插入图片描述

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux

“【Linux】Linux进程的理解 --- 进程描述符、状态、优先级、切换…” 的相关文章