进程的概念

认识冯诺依曼体系

✳️认识组件
1.输入:键盘,话筒,摄像头,磁盘,网卡

2.输出:显示器,音响,磁盘,网卡
(输入和输初给我们人提供某种读取和写入数据的能力)

3.存储器:指的就是内存;
❓为什么要存在内存?
✳️解答:a.技术角度:CPU的运算速度>寄存器的速度>;l1-l3>内存>>外设(磁盘)>>光盘磁带,由木桶原理,数据不是直接交给CPU的,而是通过内存交给CPU再有CPU交给内存由其交 给外设。
从数据角度:外设几乎不和CPU打交道,直接和内存打交道,CPU也同样如此。
因为只和内存打交道,整个体系的效率和性能主要以内存为核心标准,引入了存储器这样设备,让我们整机效率以存储器为标准;第二点因为存储器有暂存数据的能力,能够预装数据,使得软件有更大的生存空间。

✳️理解预装数据:我们自己写的软件,编译好之后,要运行,必须先加载到内存中!可是为什么呢?因为CPU是一个读取和分析指令,CPU不断和设备进行交互时,要读取指令和数据,数据必须得在内存里,CPU只和存储器打交道,但是我编译好的文件是exe可执行程序,编译好之后是在磁盘上的,而我们CPU分析和读取指令要通过存储器,所以要求你要运行程序则必须将程序加载到内存,因为CPU只会从存储器读取数据,所以也是体系结构所决定!(当你内存和外设进行交涉的时候,CPU在干其她事情,这样可以把你内存加载过程和CPU计算过程并行起来);操作系统也会加载到内存中,我们刚开始读还没读完有些数据早就加载到内存中去了,就是这个意思;
✳️局部性原理:你怎么保证你加载上来的数据立马或者最短时间内会被CPU和程序所使用呢?活来科学家大量研究所得出来局部性原理,即为你当前可能执行第10行,我能推断接下来执行的代码一定在其附近,则我把数据提前加载进来;
✳️解答:内存在我们看来,就是体系结构的一个大缓存,适配外设和CPU速度不均的问题
b.成本角度:寄存器价格较为昂贵,若想要大众使用必须得有效和便宜✳️解答:使用较低的钱的成本,能够获得较高的性能

4.运算器+控制器【CPU】:当代计算器没有直接把数据交给CPU,也没有直接由CPU打入到输出设备;运算器一般执行算数计算➕逻辑运算;控制器 ,一般外设把数据交给内存,中央处理器就可以读取数据了,虽然我们一直说外设不和CPU在数据上有交互,但并不代表他们之间没有交互,不是彻底割裂开的,我们所说预装载不一定百分百加载进去,则中央处理器就要和外设交互一下,达到协调数据流向,流向哪里流多少问题,其实就是由控制器来控制外设,将数据尽可能搬运到内存当中;所以处理器一般要能给计算,另一方面也要能够协调步骤让我们把数据从外设到内存和从内存到外设;
中央处理器CPU其实只是一个具有运算和控制能力的提线木偶,真正让我们中央处理器去计算和完成某些控制的是计算机的大脑-----最具代表性“操作系统”!
几乎所有的硬件,只能被动的完成某种功能,不能主动完成某种功能,一般都是要配合软件完成的(OS➕CPU) 请添加图片描述
❓两个计算如何通信呢?
我们把qq都加载到了内存,我们从键盘中输入,输入的数据放到内存当中,通过中央处理器读取和分析重新回到内存,刷到网卡,通过网络,到对方的网卡当中,对方网卡吧数据交给内存,中央处理器读取和分析再交给内存,再刷到显示器上面。请添加图片描述
文件的话其实就是从磁盘中把数据刷到内存里。

操作系统(软件上)

✳️操作系统是一款软件,管理软件;
❓那什么叫做管理?如何理解呢??
例子来说明:人做事1.决策,2.执行
校长:是管理者,做学校相关的一切决策。那校长连我的面都不见,如何管理我呢?管理你要和你打交道?要和你见面吗?那么他是怎么做到的?
✳️所以所谓管理的本质:不是对被管理对象进行直接管理,而是只要拿到被管理对象的所有相关数据,我们对数据的管理,就可以体现对人的管理!!!
那么他又是如何拿到我们的数据呢?辅导员可以拿到!辅导员是执行者

✳️所以校长相当于操作系统(决策);辅导员相当于驱动(执行);学生相当于硬件(被管理)。
所以管理的本质:对数据做管理!

✳️管理的理解二:数据是有多少的分别的!❓那如何管理好大量的数据呢?
程序员校长:虽然数据量很大,但有很多属性是重复的(比如学号,年龄…),所以想到以学生为单位组织信息,然后我们拿一个学生信息就方便很多。
人认识世界的方式:通过属性认识世界的!
由C++可知道一切皆对象!
一切事物都可以通过抽取对象的属性,来达到描述对象的目的
所以校长抽取所有同学的属性,描述对应的同学,在C中,有没有一种数据类型,能够达到描述某种对象的功能??----➡️struct!
struct student{
学生的基本信息(身高,姓名,年纪,电话)
在校基本信息(宿舍号,专业,年纪,班级)
考试成绩(C,C++,Java…)
},然后校长又可以将一个个学生节点弄成一个链表(数据结构)!

✳️对学生的管理(数据),变成了对链表的增删查改(数据结构)!!!

✳️管理的本质是对数据的做管理—➡️对数据的管理–➡️对某种数据结构的管理

✳️管理的核心理念:先描述,再组织!!
(对数据做管理你必须把它数据结构化,描述起来后,再把它组织成特定的数据结构,然后才能对他做管理。所以写C++代码上来就是class struct定义结构体/类; 这六个字给我们写代码提供了顶层的理论依据,也能够让我们去解释很多现象和行为;回过头想想写的扫雷,通讯录,五子棋,就拿扫雷上来就是先定义一个二维数组;你先写的通讯录是先struct表示某个人,对人做管理,再弄一个大数组,把一个个人放进去;所以我们先用struct描述一个对象,再把所有struct对象放在数组里是管理对象,这便是先描述再组织)

进程

什么叫进程

✳️操作系统: 内存管理 进程管理 文件管理 驱动管理
✳️进程是一个运行起来的程序
我们写好的代码经过编译形成.exe可执行程序放在磁盘中,❓程序是文件吗?是的!文件在磁盘。由冯诺依曼体系,我们的程序要由CPU运行则必须得先从磁盘加载到内存中,则这个过程叫做程序运行起来了。❓那么他叫进程吗?这是比较狭隘且肤浅的。因为把程序从磁盘搬到内存还是程序,不能够称之为进程。(就如同我去了你学校就是你的学生吗?肯定不是)
学校里面有很多被管理对象,人太多,而我们的管理本质是对数据做管理,则为了方便管理则必须先把他们先描述再组织起来。
操作系统里面可能同时存在大量的进程!❓操作系统要不要将所有的进程管理起来了呢?对进程的管理本质上是不是就是对进程数据的管理?-----➡️所以我们要**“先描述,再组织”**!!
将来会有很多进程在内存中,虽然各不相同,但他们都是进程,一定能够抽取出共同的属性,所以我可以把所有的进程先描述起来,当进程太多没办法一个个管,则就要组织起来。
当一个程序加载到内存的时候不要只以为操作系统就只是把它的代码和数据加载到内存,操作系统为了管理该进程,还创建了对应的数据结构!描述一个事物肯定是用struct tast_struct----➡️包含了进程的所有属性数据;
❓所以什么叫做进程?答:所谓的进程就是我们将进程描述的结构体struct tast_struct和进程的代码合起来才叫做一个进程。
✳️PCB(进程控制块process ctrl block)—➡️struct struct_task ;操作系统要管理对应的进程,就要将进程先描述再组织起来,实际上是吧描述进程的进程控制块组织起来。
❓为什么管理进程要由PCB(tast_struct)?答:这个结构是操作系统为了管理进程而描述进程所设计的结构类型,当以后有个进程加载到操作系统时,操作系统在内核里面一定会为其创建tast_struct结构体变量,并且将该变量链到全局的链表当中。所以以后操作系统之要对链表进行遍历和管理就能达到对进程的管理。
什么是进程?进程 = 可执行程序 ➕ 该进程对应的内核数据结构
进程我们学什么? 要学进程控制块里面有什么属性! 请添加图片描述

什么是OS(操作系统)

✳️概念
a.驱动软件层:比如我们鼠标USB插入电脑接口,一开始没反应,但过了一会电脑下面弹出软件驱动成功,便可以用鼠标了(同理键盘);
b.操作系统: 对上给用户提供一个,良好的,稳定的,搞笑的运行环境;
对下要管理好底层的软硬件资源;
OS是一款软件,用来进行对软硬件资源进行管理的软件,管理目的对上,对下,整体上达到提高操作效率
c.什么叫Linux操作系统呢?----是上面操作系统的一种;

✳️操作系统的管理结构:因为有操作系统存在,能够把所有软硬件资源以进程的方式对外提供服务
我们见到所有的银行,都是一个封闭体,暴露出来一些窗口,银行不是相信任何人的,银行给所有人提供服务的方式是通过窗口提供的。
操作系统也是不相信任何人的,它既要防止少数人,又要给多数人提供服务,操作系统是通过给用户提供接口的方式!内核使用C语言写的!接口:用C语言,给我们提供函数调用!-----➡️系统调用

OS为什么要给我们提供服务

✳️我们调用的printf/cout,向显示器打印,显示器是硬件,所谓的打印,本质是将数据写到硬件,那么自己的C程序有资格向硬件写入吗?–没资格!可数据写到了显示器了,其实是贯穿操作系统做到的,我们的请求是经过无数层软件层请求做到的;因为用户的需求很多,本质上我们上层应用直接或间接一定要访问某种硬件来完成自己的某种功能,上层用户有这种需求,就好比银行有客户求存钱和取钱的需求,那么此时一定就有银行系统必须对外提供服务,这也是其价值体现所在,若没这个价值那么你就没有资格去做管理工作,没资格管理硬件。
所以计算机和OS是为了给人提供服务的!所以必须给我们提供服务。

OS如何对外提供服务

✳️操作系统是不相信任何人的,不会直接暴露自己的任何数据结构,代码逻辑,其他数据相关的细节!
操作系统是通过系统调用的方式,对外提供接口服务的!(Linux系统使用C语言写的,这里所谓的“接口”,本质上就是C函数!我们学习系统编程,本质就是学习这里的系统接口!)
C语言中的printf/cin其实就是C标准库和C++标准库,给我们提供的lib可以让我们去去链接,换而言之我们要用全新的视角去理解C++了,我们要清楚了解到我们学习到的语言其实是用操作接口层的,我们的链接的时候是去链接库,但是库里面一定有对应的方法底层去调用系统接口,只不过用户不知道罢了!
请添加图片描述

查看自己的进程

第一种查看方式

✳️命令:#ps ajx
#ps axj |greep ‘mytest’—➡️可以看到自己写的mytest进程
#ps axj | grep ‘mytest’ | grep -v grep请添加图片描述
✳️我们自己写的代码,编译成为可执行程序,启动之后就是一个进程!

✳️那么别人写的呢?启动之后是不是进程呢?—肯定算!
(ls pwd touch grep…都是别人写的都会变成进程)

当前路径解释

✳️当前路径就是当前进程所在的路径!
进程自己会维护!!

✳️pid,当前路径这些东西都在哪里呢?-----都是进程内部的属性,都在进程的进程控制块PCB(task_struct)结构体中
请添加图片描述

第二种查看方式

✳️到proc目录去找:是内存文件系统,里面放的是当前系统实时的进程信息!
命令:#ls /proc----➡️有进程PID
#ps ajx | head -1—➡️提出属性名称
#ps ajx | head -1 && ps ajx | grep ‘mytest’ | grep -v grep–➡️(&&表示前面命令执行完了在执行后面的命令)

✳️每一个进程在系统中,都会存在一个唯一的标识符PID!就如同同学在学校每个学生都有一个学号

获取进程的PID

✳️我们要查看一个进程的PID则一定要让这个进程运行起来。

getpid【系统调用接口】

请添加图片描述
✳️pid_t其实就是无符号整数,每个系统都有自己的pid,当创建了一个进程,系统就会为他分配一个pid。

✳️使用

printf("helloworld.Mypid:%d\n",getpid());

✳️创建进程有很多方法,./你的程序就是创建方法之一

获得父进程PID–PPID

✳️pit_t getppid(void)
请添加图片描述✳️mytest被创建多次可是父进程PID一直不变-----➡️bash
✳️几乎我们在命令后上所执行的所有的指令(你的cmd),都是bash进程的子进程

kill 【杀掉进程】

#kill -9 ➕pid的编码

创建子进程:fork()

#include<uinstd.h>
pid_t fork(void);

✳️fork函数是用来创建子进程的,它有两个返回值(??)
父进程返回子进程的pid,给子进程返回0

✳️fork()之后是两个进程。
✳️❓同一个id值,使用打印,没有修改,却打出来了不同的值???
回答不了!进程地址空间,再来回答 请添加图片描述
C语言上if和eles可以同时执行吗?C语言中,有没有课能两个以上的死循环同时运行??–可以
✳️a.fork之后,父进程和子进程会共享代码,一般都会执行后续的代码–printf为什么会打印两次的问题
b.fork之后,父进程和子进程返回值不同,可以通过不同的返回值,判断,让父子执行不同的代码块
请添加图片描述
❓为什么给父进程返回子进程的pid,却给子进程返回0??
解答:a.在现实生活中,父亲:儿子 = 1 :n;所以父进程必须得有标识子进程的方法,则方法就是fork之后,给父进程返回子进程的pid!
b.子进车个最重要的是要知道想自己被创建成功了,因为子进程找父进程成本非常低!我可以通过getppid()获得父进程。

❓fork如何做到会有不同的返回值??即为什么fork会返回两次??
解答:一定是曾经被返回两次!fork()是函数由操作系统提供即系统调用,操作系统内部一定会有fork的实现,那么fork之后OS做了什么?我们目前知道进程就是tast_struct+进程代码和数据,需要操作系统先描述再组织;创建进程是由父进程创建的,父进程也是进程它也有自己的tast_struct结构体和代码数据,那么创建进程系统就是多了一个进程,本质上就是task_struct➕子进程代码和数据,那么子进程的task_struct对象内部的数据从哪里来呢?应该也有pid,ppid,有当前路径等等属性,那么这些是从哪里来的呢?基本是从父进程继承(拷贝)下来的!
子进程执行代码,计算数据的,那么子进程的代码是从哪里来的呢?—只能是和父进程执行同样的代码,**fork之后,父子进程代码共享!**但是数据会各自独立(后面讲)。因为代码共享,所以我们可以通过不同的返回值,让不同的进程执行不同的代码!所以fork之后父子进程代码共享,因为返回值不一样,那么就可以通过判断让不同的返回值当中的父子进程执行不同的代码,这样的话呢我们就可以让父子进程干不一样的事情,这是fork之后的初步理解。
所以在系统调用当中,fork之后本质上是系统多了一个进程,则就多了一个task_struct,那么这个进程控制块会完全或几乎继承父进程,代码呢会在fork之后代码共享,数据会各自私有(后面说),我们现在知道fork之后会执行很多的代码逻辑,那么fork呢会有两个返回值,那么一定是它曾经返回了两次,那么返回两次一定会在里面调用return pid(return pid之前的代码就是父子共享的代码),那么调用一个函数,当这个函数准备return的时候,这个函数核心功能完成了吗?—已经完成了!
如果已经完成则 a.子进程已经被创建了。
❓如何理解进程被运行?比如我们去一家公司面试hr怎么知道我的能力不错叫我去面一下,很简单本质上是因为曾经向这家公司投递过简历,你就相当于一个代码和数据,你的简历就相当于描述一个进程的控制块,而公司内部人员发现了你的简历,发现你不错,然后想把你面一下,本质上就是想执行一下你的代码,这个过程就叫做调度一下你,让你这个进程放在CPU上去运行,但是不光只有我投递,我的简历一会儿在公司人才池放着,也有可能是面试官或hr桌子上,换而言之你的简历在哪里,你的进程就在哪里,我们之前讲的每个进程都会被放到双链表当中,用来在系统层面上管理进程,但并不代表整个进程pcb只能在一个双链表当中,也可能放在其他结构当中。我们操作系统当中有调度器,在Linux内核当中我们每个CPU都会有一个运行队列(runqueue)的东西,里面放的全是task_struct,并且每个task_struct都有指向自己的代码和数据,所谓调度器调度,就是从运行队列当中选择一个合适的进程被CPU去执行,选择的时候肯定是根据优先级去选择,确认好调度哪个了本质上就是CPU去访问其对应的代码和数据,便能执行这个进程,我们创建子进程,要给它创建好task_struct然后控制块也要指向它父进程的代码和数据,然后我们也要将它放到运行队列当中,此时供我们CPU去调度
b.将子进程放入运行队列
那么a 和 b有return之前的代码完成了,那么return是代码吗?是的!那么当我们走到这里,证明父进程已经有了子进程也已经有了并放入到运行队列当中了,fork之后代码共享,当然父子进程都会去执行共享的return代码(因为return pid之前子进程有了!有了之后它由于代码共享也会执行父进程代码中的return!),所以父进程执行一次return,子进程也执行一次return不就是返回两次吗!返回两次不就是会有两个返回值吗!
那么id是同一个变量呀,为啥会有不同的值呢?因为父子进程数据私有,会发生写实拷贝!(讲地址空间的时候会说!)
请添加图片描述请添加图片描述

进程的状态

✳️以后凡是说进程,必须先想到进程的task_struct,我们操作系统内部做管理压根不看代码和数据,无非就是我要执行就让CPU去执行一下,我要管理你压根不是把你的数据拿过来拿过去,而是我要把你的进程描述符task_struct拿过来,我们让进程去排队,实际上就是让进程控制块去排队。
✳️进程状态在内核当中就是一个整数(int status)我们可以类似于用#define RUN 1…等,进程状态在进程的task_struct中;

进程运行态

✳️ 运行态:是进程正在CPU上运行,还是进程只要在运行队列中叫做运行态呢?
每个CPU都会维护一个运行队列,每一个task_struct一定能找到对应的代码和数据,我们让进程排队,就是让进程控制块pcb在运行队列里排队。
解答:所谓运行态就是只要在运行队列里就是运行态,不代表我正在运行,而是代表我已经准备好了,随时可以调度!!

进程终止态

✳️ 终止态:是不是这个进程已经被释放,就叫做终止态?还是该进程还在,只不过永远不运行了,随时等待被释放!
理论上一个进程被终止了,第一把自己曾经占据内存加载进来的代码和数据free掉,第二为了方便管理进程所为其创建的控制块task_struct也给释放掉。理论和实际上就是这样的。
终止态答案就是: 终止态是该进程还在,只不过永远不运行了,随时等待被释放!
那我们很好奇,进程都终止了,为什么不立马释放对应的资源,而要维护一个终止态??----释放要花时间吗?有没有可能当前你的操作系统很忙呢?相当于一个进程退出了,你能保证退出立马释放你吗,如果能立马释放为什么还要有终止态,不要就完了嘛。没有办法立即释放,就要维护终止态,来告诉操作系统我已经退出了,你不要来调度我了,等你不忙了来释放我。

进程阻塞

✳️阻塞态:1.一个进程,使用资源的时候,可不仅仅是再申请CPU资源!
2.进程可能申请更多的其他资源:磁盘,网卡,显卡,声卡…
(比如我们申请下载,当我们下载的时候CPU资源肯定是要申请的,因为需要CPU去执行下载逻辑,它还申请了网络资源,带宽兆宽等资源也是要被申请的!)

✳️ 如果我们申请CPU资源,暂时无法得到满足,需要排队的—运行队列;
✳️ 那么如果我们申请其他慢设备的资源呢?(比如说我们想读写网卡,可是带宽被其他占满了,导致我们不能读写了;比如我们现在想访问磁盘,可是磁盘正在被其他进程访问,磁盘也很忙,你现在没办法…)----也是需要排队的!(task_struct在进程排队)

我们操作系统为了管理软件必须先描述再组织,我们所看到的软件在系统里一定有对应的数据结构来描述它,其中我们就可以接下来每一个都对应的有自己的资源,那么请问我今天一个资源正在被CPU所调度,它呢说我要读数据,读1g的数据到内存,那么读数据的时候CPU就要去执行它读数据的代码,可是磁盘当前正在忙着呢,忙不过来怎么办?磁盘就要叫进程等一等,那么此时这个进程他要IO读取时,可是磁盘数据没有就绪,请问这个进程怎么办?难道要他占着CPU让它在CPU上等吗?—绝对不会!操作系统会把该进程放到磁盘的等待队列当中!让CPU去处理其他进程,若其他进程想要读网卡,可网卡还没就绪,也会将该进程放到网卡的等待队列当中去的。
✳️当进程访问某些资源(磁盘,网卡),该资源如果暂时没有准备好没或者正在给其他进程提供服务,此时:1.当前进程要从runqueue中移除;2. 将当前进程放入对应设备的描述结构体中的等待队列中!—该系列动作是由操作系统完成的!那么操作系统是不是正在执行对进程的管理任务呢?—是的!

✳️ 当我们对应的设备就绪,所谓就绪就是准备好了,再硬件层面上准备好了,它一定会通过某种方式让操作系统知道(什么方式在后面会说),操作系统知道后如磁盘或网络资源好了,然后再把该进程的PCB再放入到运行队列当中去 ,那么CPU就可以快速处理这个进程然后让该进程读取对应的网络资源了!
换而言之,当我们的进程此时在等待外部资源的时候,该进程的代码,不会被执行了(那么为什么不会被执行了?巨简单,因为它的代码从当前的CPU拿下去了,它不会再会被去执行相应的代码了,而是相应资源的等待队列当中去等待 ,若该资源就绪,它PCB又会被放到运行队列当中去,然后CPU回去调度它)那么上层用户看到的就是:我的进程卡住了!
我们把这样的等待某种资源,让当前进程去等待某种资源就绪的时候并不运行时所处的状态就叫做-----进程阻塞!

✳️所有的资源,我们进程都是需要排队的,所谓排队本质上是PCB进程控制块在排,每个设备在操作系统都要被管理都有对应的数据结构,所以操作系统对硬件的管理,变成了对数据的管理,很好理解,每一个里面都包含task_struct* queue,把进程列入其中。

✳️比如说我们用的fopen,fwrite读写磁盘,读写文件就是在用资源,当对应的设备就绪时,所谓把进程由阻塞状态变为运行态,就是将PCB从我们当前等待资源的队列当中,再放入到我们的运行队列当中,那么CPU就会继续执行它!

✳️再回看进程阻塞:进程等待某种资源(非CPU),而资源没有就绪的时候,进程需要再该资源的等待队列中进行排队,此时进程的代码并没有运行,进程所处的状态就叫做阻塞!小白在上层看来就是卡住了!请添加图片描述

进程挂起

✳️首先进程挂起,它不是进程运行,那么也就决定了一个进程被挂起,绝对不会去申请CPU资源,就和阻塞一样,除非自己从挂起变为运行态。当处于挂起状态的时候,它不会申请CPU资源,也就意味着该进程处于非运行状态,从上层看来就是被卡住了。
✳️ 挂起和阻塞最本质的不一样就是,我们目前对进程的理解呢就是他有自己的PCB,可是一个进程有自己的代码和数据的,那么曾经在磁盘当中有各种可执行程序被加载到内存,然后操作系统让各自PCB指向自己的代码和数据 ,所以说让PCB维护在一张双链表里,那么此时他就可以转化为对进程的管理变为对双链表的管理,所谓进程运行,它就会有运行队列,阻塞的话呢就会有阻塞队列,所以操作系统行使对进程的管理职责是通过将对应进程的PCB放入不同的队列来完成不同的功能,这是理解思路。
对于挂起它必须得面临的就是:如果内存不足了怎么办??----操作系统就要帮我们进行辗转腾挪!(比如我的CPU只有一个也就意味着能被运行的进程也就只有一个,说大点能在短期内被运行的都在运行队列里,向那些在阻塞队列里比如在等网络,等磁盘,可是磁盘和网络已经相当忙了,那么磁盘和网络短期内根本就不会就绪,你还必须得磁盘上等,更重要的是你占着PCB,你进程的代码和数据还依旧在内存里占着,你又不干事,短期内又不会被执行,你的代码和数据还占着磁盘和空间,而我的内存又不够了怎么办?)很简单—操作系统会将短期内不会被调度(你等的资源,短期内不会就绪)的进程,它的代码和数据依旧在内存中就是白白的浪费空间,操作系统就会将进程的代码和数据置换到磁盘上!操作系统就能通过这样的方式在内存当中让进程短暂的存留PCB,剩下的数据全部置换到磁盘上,此时这样的进程就叫做:进程的挂起!
✳️进程挂起:一个进程对应的代码和数据被操作系统因为资源不足,而导致操作系统将该进程的代码和数据临时置换到磁盘当中,此时叫做进程挂起!请添加图片描述

Linux进程状态

R状态:就是运行态!

S状态:就是阻塞状态,我们把S状态也叫做浅度睡眠,也叫做可中断睡眠。

✳️现象:你往显示器打hello world虽然是while执行死循环,你说打就打吗?我们显示器本身是外设,它非常慢,即便它闲着准备好让你去刷新也是要花时间,虽然这个死循环打印的进程看似在执行printf,但是它百分之90情况都在等!都在等显示器就绪!所以我们查进程的状态大部分是S状态。

D(disk sleep)状态:也是一种阻塞状态,一般而言在LInux中,如果我们等待的是磁盘资源,我们进程阻塞所处的状态就是D状态。

(举个例子:我们进程需要去访问磁盘资源,然后磁盘帮进程去写数据,此时进程就把自己设为S状态等待某种资源就绪,它在等磁盘把数据写完告诉他,在它等期间,进程越来越多,操作系统越来越忙,操作系统扫描路过该进程的时候,由于内存严重不够,则操作系统将该进程杀掉(服务器压力过大,OS是会终止用户进程的) ,磁盘写完之后要进行报告是否写成功,若写失败了则会出问题!则操作系统设计者认为是操作系统有杀掉进程的权利,那么设计者就引入了一种状态,叫做D状态)D状态是深度睡眠,不可被中断睡眠!

X状态

X状态:就是死亡状态,就是终止状态;

Z状态

Z状态:是僵尸状态,其实是一种已经死亡的状态,但是指的是死了之后我们要等一等 ,不要让Z直接进入X状态(男主人公在路上看到一名跑步者倒下,然后主人公报了警之后,法医和警察吧所有问题排除了倒下者才被抬走,那么倒下者在地上躺的期间法医和警察要排查原因,弄清楚情况,所以倒下者已经死亡了但此时是僵尸状态,警察排出完了后被抬走才是死亡状态)

✳️当一个Linux中的进程退出的时候,一般不会直接进入X状态(死亡,资源可以立马回收),而是进入Z状态。
❓为什么先进入Z呢?进程为什么被创建出来呢?—一定是因为要有任务让这个进程被执行,当该进程退出的时候,我们怎么知道,这个进程把任务给我们完成的如何了呢??—需要将进程的执行结果告知给父进程/OS
🌟解答:子进程退出,维护Z状态,就是为了维护退出信息!让父进程或者操作系统读取。该退出信息会被写入task_struct中 。
(今天有几个话题暂时谈不了:进程退出的情况有哪些?怎么退出的进程?这是要进程控制那块才会讲;
然后你说退出信息能够让操作系统读,操作系统就不说了PCB是它创建的肯定有办法可以读,但是父进程是怎么去读的呢?包括如何让子进程状态由Z变为X,像这样的过程今天也讲不了,这是由下个主题控制当中进程等待要解决的!)
我们父进程或操作系统是怎么读到僵尸进程的退出信息呢?是通过进程等待来读取的,至于进程如何等待?要到后面讲!
我们写main()函数总会有一个返回值,返回到哪里呢,最后到哪里保存呢,其实我们main()函数返回值就是会到PCB的退出信息中。
请添加图片描述

如何模拟僵尸进程?

如果创建子进程,子进程退出了,父进程不退出,也不等待子进程(这个动作怎么做我们暂时不讲,)那么此时子进程退出之后所处的状态就是Z状态(我们来模拟模拟)

int main()
{
    pid_t id = fork();
    //id:0 子进程, >0:父进程
    if(id == 0)
    {
        //child
        while(1)
        {
            printf("我是子进程,我的pid: %d,我的父进程是: %d\n", getpid(), getppid());
            sleep(1);
        }
    }
    else{
        //parent
        while(1)
        {
            printf("我是父进程,我的pid: %d,我的父进程是: %d\n", getpid(), getppid());
            sleep(1);
        }
    }
长时间僵尸会有问题吗?

✳️如果没有人回收子进程的僵尸,该状态会一直维护!该进程的相关资源(task_struct)不会被释放!会导致内存泄漏!一般必须要求父进程进行回收(后面会说)

T/t状态

✳️T/t:都是暂停状态,就是让运行的进程停下来(比如我们播放视频,播放音乐,停止下载都是暂停状态)所以暂停状态具备功能性!
✳️模拟:可以发送19号信号让自己写的进程暂停(T)
用gdb调试代码,当进程被调试的时候,遇到断点的状态就是t

孤儿进程

✳️子进程一直不退出,我们让父进程先比子进程先退出。通过代码可以模拟
❓当父进程不见了,那么父进程为什么没有处于Z状态呢?----父进程有自己的父进程,它是bash,所以父进程被bash回收了!父进程先走了,那么此时谁来管我这个子进程呢?
如果父进程先退出,则其子进程会被1号进程领养,1号进程就是操作系统!!
✳️我们把被领养的进程称之为孤儿进程。

✳️我们发现孤儿进程无法被contrl➕c杀掉,是因为此时孤儿进程状态后面没有+号,此是后台进程的标志,可以用kill -9去杀掉后台进程。有+号是前台进程。

进程的优先级

✳️优先级是进程获取资源的先后顺序。(你能,只不过是先还是后)

什么是优先级vs权限

✳️食堂有些是给教职工才能吃的,而学生不可以;
所以权限是能还是不能的问题。
优先级是,你能,只不过是先还是后的问题。

为何会存在优先级?

✳️排队的本质就是确认优先级,那么为什么要排队呢?因为资源不够
系统里面永远都是进程占大多数,而资源占少数。这就直接决定了,进程竞争资源是常态,所以一需要确认先后。

Linux下的优先级的相关概念和操作(怎么做到的)

如何用命令查看优先级

✳️#ps -l只会显示当前终端下进程的相关信息
#ps -la就能看到自己创建的进程了

✳️优先级是由:priority ➕ nice共同决定的
priority的默认值是80

✳️要更改进程的优先级,需要更改不是pri,而是NI,nice
nice:进程优先级的修正数据请添加图片描述
✳️top命令可以改变优先级
先输入top命令,然后会叫你输入想要修改的进程的PID,然后会让你输入值;
但是我们要有超级用户的权限才能去设置优先级
Linux不允许进程无节制的设置优先级,Linux中设置nice值的最小为-20,最大到19。
实际上Linux一共有140个优先级,而其中很多优先级,并不是给我们这些普通用户去用的,像60-99才是给用户层的,有些操作系统是实时操作系统,也有分时操作系统。我们现在用的是分时操作系统,有点像操作系统那里的时间片的概念,这个问题我们后面说;实时进程一个进程优先级设置好后,一直会执行完,一旦这个资源被调度,那就立马得被调度,不能等,这叫实时。

✳️prio值 = prio_old值 + nice值
每次设置nice值,prio都会被自动重新回到80

其他概念

1

✳️PCB内部应该包含什么呢?目前像我们提到的PID,当前路径,优先级,状态,包括内存指针字段指向我们PCB代码和数据等等;

✳️竞争属性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级。

✳️进程运行的本质是具有独立性的:需要在讲完进程地址空间后才能理解,但现在可以感性理解一下:我们在手机上刷抖音和qq,一些原因qq挂掉了,但它并不会影响抖音;我们在下载的时候也打开着播放器放视频,也浏览着网页,突然 浏览器闪退了,它并不会影响我下载和播放器播放等等例子
所以进程具有独立性,不会因为一个进程挂掉或异常,导致其他进程出现问题。
进程无外乎由内核数据结构➕代码和数据组成,你所谓的独立性相当于在操作系统内每个进程都有一套自己的内核结构,第二个呢每个进程都有自己的代码和数据,所以你qq有你的代码和数据,我迅雷有我的代码和数据,每种app都有自己的代码和数据。但我们之前讲过fork()之后产生父子进程,它们之间又是如何做到的呢,所以我们最终所谓独立应该体现在内核结构➕代码和数据这两方面;
❓那么操作系统是如何做到进程具有独立性呢??(与之前残留的问题:fork之后同一个id怎么会有两个不同的值)—要在进程地址空间才能讲清楚。

✳️并行:假设计算机有两个CPU,曾经讲过操作系统会为每一个CPU维护调度队列,你现在有两个CPU也就会维护两个run队列,CPU1可以把一个PCB位给自己,同理CPU2;换而言之,如果存在多个CPU的情况,在任何时刻,都有两个进程同时运行(就是在CPU运行)就是所谓的并行。

❓我的是单CPU,但是我的电脑程序有各种进程都在跑啊!-----多个进程都在你的系统中运行 !=多个进程都在你的系统中同时运行
不要以为进程一旦占有CPU就会一直执行到结束,才会释放CPU资源!不要认为一个系统实际调度我们进程的时候,它只要占有了CPU就一直让他跑,所谓一直让他跑,就是你的进程不退出,我就一直让你占用CPU,直到你把代码执行完,这种情况是不存在的!
我们遇到的大部分操作系统都是分时的!
操作系统会给每一个进程,再一次调度周期中,赋于一个“时间片”的概念!
假设有进程1 2 3 4 5,我要调度你的进程1,但是我放上去后,只给他10ms时间,时间到了之后,好坏都不让你运行了,此时直接把你从CPU上剥离下来,然后调度进程2,调度进程2的时候也同样赋予10ms,这样的话1秒中之内有1000ms,那么就是说这5个进程会被调度20次,没次调度都是自己代码得以推进的时候
✳️并发:
那么我们就有一个概念:在一个时间段内,多个进程都会通过切换交叉的方式,让多个进程代码,在一段时间内都得到推进,这种现象,我们叫做并发。
并发的真相呢就是,我们一旦给你一段时间去跑,跑完还是没跑完我不管,跑完了最好,没有跑完也要把你剥离下来,让你等一等,让其他进程运行,那么谁把你剥离下来,又是谁把你放上去的呢?谁给我赋予这么长时间呢?那么这个工作是有操作系统里面的软件—调度器!
在这里插入图片描述

✳️
我想问你几个问题?我们从CPU上面剥离下来了,我被从CPU剥离下了,我下次被放在运行队列当中等待着被调度,你说有一个时间片轮转,可是我们的操作系统就是简单的根据队列来进行先后的调度的吗?有没有可能来了一个优先级更高的进程??如果来了一个优先级更高的不就是谁优先级越高越优先去占用CPU资源吗,把当前的进程剥离了,现在要拿上来一个新进程,肯定要拿优先级更高的,另外我当前正在运行,同时又来了一个优先级更高的进程,就真的让这个优先级更高的去排队吗??
实际上当代操作系统都支持“抢占式内核”。意思就是说进程资源不像你想的那么和谐,该到谁就到谁,有时候我们正在运行的时候 ,你给该进程10ms,但是2ms或者3ms突然新增一个优先级更高的进程,那么操作系统极有可能基于更高优先级进程到来,把正在运行的进程直接给你剥离下来,你没跑完没关系,你一会儿再来跑吧,现在来了优先级更高的进程,那么此时把运行的进程从上面剥离下来,然后把新进程放上去,这个过程就叫做进程抢占!
所谓抢占式内核就是正在运行的低优先级进程,但如果来了优先级更高的进程,我们的调度器会直接把我们的进程从CPU上剥离下来,放上优先级更高的进程这就是进程抢占!
请添加图片描述

2

✳️现在讲讲Linux是如何维护所有的进程的。 帮助我们去理解内部是如何维护进程的概念。
进程的优先级 | 队列:假设我们现在有5种优先级,一定要意识到两点
a.操作系统内是允许不同优先级进程的存在的。我们系统现在存在各种的优先级,各种优先级是不是就按优先级的方式在这里排队呢?那这样排队的话再有进程来,它的优先级我得确认它,就得从队列里找个位置吧节点插入进去,我们不要忘了数据结构队列是先进先出!你不能从中间随便插入,那么请问你不能随便在中间插入,优先级是如何体现出来的??如果只有一个队列在逻辑上是不自洽的,会发现优先级在这里是毫无用处的,你排队不是确认好优先级了吗,你干嘛还要排队,所以这里逻辑不自洽就要来提一提了。
首先第一点我们系统存在不同优先级的进程存在

b.相同优先级的进程是可能存在多个的!那么Linux当中是怎么做的呢?我们通常说CPU只有一个队列是不对的,假如我们现在有5种优先级,如果是我去设计调度算法的话,我会定义一个task_struct* queue[5],5个优先级队列,你的优先级属于哪个就插入到那个对应的队列当中。
所以Linux根据不同的优先级,将待定的进程放入不同的队列当中!
其实就是一张简单的哈希表,哈希表后面链的就是一个个队列,我们根据不同优先级是几,索引的时候直接把你索引到哈希表对应的队列指针当中,然后根据优先级把你放到优先级队列当中。
实际上哈希表是很大的,每次找都要遍历,最差情况是线性遍历,对操作系统来讲线性遍历成本太高,所以Linux还设计了一个位图!而是用位图做检测,若哪个位置为1说明上面有进程,由优先级高到低检测,一旦位置为1的话就说吗对应的队列当中有我们的进程就把进程拿出来了。
操作系统还做了其他工作,Linux内核不仅用位图来表示对应的队列哈希是否有队列,队列当中是否为空,为空就是0,不为空就是1,所以通过扫描位图方式就能确定数组里哪个优先级上还有进程,就直接拿着对应的索引,位图所在的位置然后再哈希表的位置去找,找到之后就去拿进程,该进程一定是优先级最高的,也一定是这次调度时候最合适哈的进程。
请添加图片描述
除此之外呢这个机制是有两份 ,一份呢struct_runqueue实际上不是我们想的那样,把结构直接放进去,而是定义了一个指针,假如我们把这个结构当作hash_queue* active
hah_queue* old
假如新的进程来了,不是我们想的那样直接就入到老的hash_queue,而是把它链入到另一个一摸一样的结果当中,当我们CPU做完一套动作后,会执行swap(acticve,old),只要将指针交换,active就会指向新的结构,然后再有新的进程来了就放入到另一个队列里,换句话说swap就是把两套结构交换一下这是O(1)请添加图片描述

进程间切换

1.CPU实际上是一个组件,里面有许多的寄存器,寄存器的主要功能是可以临时的存储数据,但非常少却很重要!
2.寄存器分为:可见寄存器
不可见寄存器
3,当一个进程运行的时候是以时间片在CPU上面跑,当进程在被执行的过程中一定会存在大量的临时数据,会暂存在CPU内的寄存器当中!
✳️ 寄存器上面数据的重要性:所谓进程切换不是嘴说把进程从CPU上剥离下来,没那么容易,你把下个进程放上去的时候,要把剥离的进程的历史数据拿走。(比如你去当兵则学校要为你保留学籍,要保留你曾经学到哪门课,哪个专业的,你是哪个床铺的等等其他所有的临时数据都要保留好,当回来的时候,也要恢复学籍。)学校就是CPU,你在学校所产生的临时数据就叫做你自己的上下文数据学校就是CPU,你在学校所产生的临时数据就叫做你自己的上下文数据,你呢相当于进程,你自己当兵被招走了,就叫做你被操作系统切换下去了,当被切换时,你不能直接走,而是在CPU产生的临时数据保存好,保存的目的是为了恢复
我们把进程在运行中产生的各种寄存器数据,我们叫做进程的硬件上下文数据
当进程被剥离的时候:需要保存上下文数据
当进程恢复的时候:需要将曾经保存的上下文数据恢复到寄存器中

相当于就是说一个进程他运行的时候是根据时间片被切换了,他在时间片切换过程中因为更高优先级的进程或者自己时间片到了,则CPU一定会存在当时进程所产生的临时数据,那么临时数据都在CPU内的寄存器保存,这个进程你被剥离走,还想下次运行,不要着急请把你的数据带走好吗,就叫做上下文保存,当你回来的时候,又会把你的数据恢复上去,这就叫作上下文的保存与恢复。
❓上下文在哪里呢?(相当于我作为进程被切走,要被保存起来,首先CPU里面肯定是不能呆了,寄存器也不让我去存数据,它是要给别人用的,那么我自己的数据应该在哪里保存呢 ?)
✳️解答:task_struct!我们会把CPU的各种数据暂存在task_struct当再次被调度的时候,又会从PCB里面恢复进去!

✳️准确区分:寄存器vs寄存器内的数据,我们所说将上下文保存起来,本质上你不可能从CPU上把寄存器拆走,而是把寄存器里面的数据保存起来,寄存器只有一套,不代表只能保存一份数据,而是可以保存多份数据。所谓进程保存上下文,是保存寄存当中的数据。举个例子:我们在学校里上自习,有个座位特别喜欢,这个座位只有一个,但可能曾经被上千上万的人坐过,当想坐的时候就把自己的书笔记本等放上去,当走的时候不是人就走了,而是要把自己的东西全部带走,把桌子空出来,让别人去使用。当你再回来的时候,把自己东西再恢复上去。那么每一个寄存器就如同图书馆的一个座位,就将相当于把图书馆比作CPU,里面的座位比作寄存器,上面的一系列动作就是上下文数据的保存和恢复。

✳️实际上一个进程时间片或被抢占的时候,当数据被切走的时候,实际上是把寄存器上的值保存起来,保存起来把你进程task_struct控制块重新放到运行队列里,再拿新的进程把它数据恢复到CPU上,这样CPU就可以执行另一个进程这就是进程切换。
请添加图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值