Linux之进程

目录

一、冯诺依曼体系结构

二、进程

1、关于进程

关于PCB结构体

2、查看进程

①ps

 ②/proc

3、getpid

4、getppid

5、fork()

fork基本用法

6、进程状态

7、孤儿进程

8、进程优先级

修改nice值:top

9、进程的几个特性


一、冯诺依曼体系结构

冯诺依曼体系结构如下图所示:

Linux之进程_第1张图片

而上面的输入设备,输出设备,存储器,运算器,控制器又是什么呢?

存储器:内存

输入设备:键盘,摄像头,话筒,磁盘,网卡...

输出设备:显示器,音响,磁盘,网卡...

CPU(中央处理器):包括运算器、控制器

运算器:算术运算,逻辑运算

控制器:CPU是可以响应外部事件的,协调外部就绪事件


首先说说运行速度:

CPU&&寄存器  >  内存 > 磁盘/SSD(固态硬盘) > 光盘 > 磁带

那既然CPU运行速度是最快的,为什么冯诺依曼体系结构中还要在输入输出设备中间用存储器呢,直接用CPU处理输入输出岂不是更好用,速度更快。

当然可以这样用,但是如果真的这样使用,就会造成昂贵的成本,原本几千快的电脑,如果全部使用CPU处理各种数据,可能会花费十几万甚至几十万的成本,这是十分不划算的,并且我们大众需要的是性价比高的,即价钱便宜性能好,所以这里引用了内存即存储器,速度比磁盘快,价钱也比CPU便宜,可以很好解决这方面问题。

存储器可以将数据缓存在存储器中,进而CPU在存储器中读取数据时,不用再访问外设,可以在一定程度上提高效率


CPU读取数据(数据+代码),都是从内存中读取的。站在数据角度上,CPU不和外设直接交互

CPU要处理数据,需要先将外设中的数据加载到内存。站在数据角度上,外设只和内存直接交互


有句话是:程序要运行,必须先被加载到内存中

现在我们可以用冯诺依曼体系结构知识解释,因为CPU读数据都是从内存中读取的,所以程序要运行,必须先被加载到内存中


下面举个例子,能更深入的理解冯诺依曼体系结构的知识:

小王和小张在两个不同的城市,他们用QQ联系,那么QQ发送一句话的过程大概是怎样的呢?

小王通过输入设备(键盘)将数据输入到存储器(内存)中,CPU通过内存分析数据后,再将数据写回给内存,之后内存再将数据给到输出设备(网卡),接着就发送到网络(后面会说到,这里只涉及冯诺依曼相关知识),然后到了小张那边用输入设备(网卡)接收消息,把数据从网卡读到内存里,继续刚刚的过程,将数据交给CPU计算分析,然后CPU再将数据写回内存,最后再将数据刷新到输出设备(显示器)

具体步骤如下图所示,紫色箭头就是上述全部过程:

Linux之进程_第2张图片


二、进程

操作系统:先描述,再组织

1、关于进程

Windows下,我们自己电脑上启动一个软件,本质就是启动了一个进程

Linux下,运行一条命令,在运行的时候,其实就是在系统层面创建了一个进程

Linux是可以同时加载多个程序的,是可以同时存在大量的进程在系统中的(OS里就是内存中)


下面对进程下定义:

进程 = 对应的代码和数据 + 进程对应的PCB结构体

当把数据加载到内存中,就变为进程了,这时会生成一个struct结构体PCB,包含了该进程的所有属性,这时对进程的管理,就变为了对进程结构体PCB链表的增删查改


关于PCB结构体

PCB全称是:process control block,在不同的操作系统中,PCB的名字不同

在Linux中,PCB结构体是task_struct,它会被装载到内存中并且包含进程的属性信息


task_struct的内容:

①标识符:描述进程的唯一标识符,用来区分其他进程

②状态:任务状态,退出代码,退出信号等

③优先级:相对于其他进程的优先级

④程序计数器:程序中即将被执行的下一条指令的地址

⑤内存指针:包括程序代码和进程相关的数据的指针

⑥上下文数据:进程执行时处理器的寄存器中的数据

⑦IO状态信息⑧记账信息等等


2、查看进程

①ps

ps是查看进程的命令

首先打开两个页面,都是我自己路径下

Linux之进程_第3张图片

在test.c文件里写了死循环打印hello world的程序,然后编译运行

Linux之进程_第4张图片

在左边的页面内输入ps,ps只能查看当前终端的进程:

Linux之进程_第5张图片

ps axj是查看所有的进程:

Linux之进程_第6张图片

而我们要查看的是自己刚刚的进程,所以输入:ps axj | grep 'test'

再把头部带上,即输入:ps axj | head -1 && ps axj | grep 'test'

Linux之进程_第7张图片

这样查看,我们可以发现,./test是在运行的,所以有./test进程,又因为grep在查看进程,所以还有一个grep进程

头部有一个PID,代表进程ID,它代表当前进程的ID值

这时我们将右边的死循环Ctrl + c终止了,这时再执行ps axj | head -1 && ps axj | grep 'test'命令,会发现只有一个grep进程了


 ②/proc

有一个系统文件夹是/proc,存放的是进程信息

Linux之进程_第8张图片

这时我们运行./test,然后查看进程:

Linux之进程_第9张图片

发现test的进程PID是21599,然后我们在proc这个系统文件夹中也可以看到当前的进程PID21599(-l是显示属性-d是只显示路径)

而当我们Ctrl + c终止死循环进程时,再查看就没有这个进程信息了

所以我们可以发现proc这个文件夹是动态的


3、getpid

getpid是获得我们的进程PID的,下面是用man查看getpid的介绍

Linux之进程_第10张图片

需要包两个头文件,其中返回值pid_t其实就是无符号整型

接下来将test.c改造一下,使用getpid这个函数:

Linux之进程_第11张图片

然后右边窗口运行test后,左边窗口查看进程:

Linux之进程_第12张图片

可以发现getpid获得的PID和我们ps查看的PID相同

而我们如果想终止这个死循环,可以Ctrl + c终止,也可以kill -9 PID终止

Ctrl + c终止:

Linux之进程_第13张图片

kill -9 PID终止:


4、getppid

getppid是获得父进程的PID

下面是通过man查看getppid

Linux之进程_第14张图片

和getpid一样,同样是包含两个头文件,同样返回值是pid_t

接下来将test.c里改变一下,将ppid也打印出来:

Linux之进程_第15张图片

通过观察得知:

运行出来的PID和PPID与我们grep查看的一样

我们终止后再重新运行,如下图:

Linux之进程_第16张图片

却发现,PID变了,但是PPID却一直是21147,那我们用ps查看一下21147,如下图:

我们发现PID是21147的进程是bash,而前面我们说过,bash的shell外壳程序

所以在执行命令时,所有的命令最终都是以bash的子进程的方式去运行的


5、fork()

fork()是创建一个子进程

下面是man查看fork

Linux之进程_第17张图片

fork在头文件unistd.h中

需要注意的是fork函数的返回值:

失败的时候:返回-1

成功的时候:①给父进程返回子进程的pid  ②给子进程返回0

下面使用fork给大家示范一下返回值的场景:

Linux之进程_第18张图片

在fork函数前面打印一下进程的pid,然后执行fork函数后,分别打印fork函数的返回值ret,与对应的pid和ppid

Linux之进程_第19张图片

大家观察可知,fork函数执行前,是父进程在执行,pid是22942,执行fork函数后,成功的话会有两个返回值①给父进程返回子进程的pid  ②给子进程返回0

所以ret是0的就是子进程,而子进程的pid是22943

ret是子进程pid(22943)的就是父进程,而父进程的pid和fork执行前的pid一样,都是22942

通过上面的示例,可以更清楚的理解fork的两个返回值


fork基本用法

fork之后代码是父子进程共享的

下面例子可以清楚显示:

Linux之进程_第20张图片

在fork函数后,有if和else if语句,分别判断fork的两个返回值,且都是死循环,下面看结果:

Linux之进程_第21张图片

一个父进程一个子进程,交叉进行死循环,让父子进程在fork函数后面执行不同的代码,所以fork之后有两个不同的执行流,可以有两个while(1)同时执行,而ret这个返回值,在父进程里面是子进程的pid,在子进程里面是0

并且可以清楚观察到,子进程的ppid就是父进程的pid


一个父进程可能有多个子进程,而一个子进程只能有一个父进程

所以父进程:子进程 = 1 :n

所以给父进程返回子进程的pid用于区分子进程,而子进程只有一个父进程,并不需要区分,所以返回0

而在创建一个子进程的时候,操作系统需要新建一个task_struct(PCB结构体),而这个新建的task_struct内部属性大部分是以父进程为模板的


fork函数实现时,运行到return前面就说明核心代码已经执行结束了

这时子进程已经被创建出来了,可能会已经被放到运行队列尾部了

所以这时父子进程一起执行,父进程被调度时会return,然后当子进程被调度时,return也会执行,所以fork函数会有两个返回值


6、进程状态

操作系统进程的状态:

新建:PCB资源刚分配好,还没有放到运行队列中就是新建状态

运行:task_struct 结构体在运行队列中排队,就叫做运行态

阻塞:等待非CPU资源就绪,这个状态就叫做阻塞状态

挂起:当内存不足的时候,OS通过适当的置换进程的代码和数据到磁盘,此时进程的状态就叫做挂起状态

退出:退出状态


Linux操作系统进程的状态:

R运行状态:表明进程要么在运行中,要么在运行队列里,对应上面的运行态

S睡眠状态:也可以称为可中断睡眠,表明进程在等待事件完成,对应上面的阻塞状态,

D磁盘睡眠状态:深度睡眠,不可以被中断,不可以被被动唤醒

t调试状态:调试时的状态

T暂停状态:单纯的暂停状态        

X终止状态:瞬时性非常强

Z僵尸状态:一个进程已经退出,还不允许被OS释放,处于一个被检测状态;维持该状态,是为了让父进程或OS来进行回收

只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程就进入僵尸状态

僵尸进程会造成资源泄露,必须使用wait/waitpid接口进行等待处理


7、孤儿进程

子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程称为“僵尸进程”

而如果父进程先退出,子进程还在运行,子进程就被称为“孤儿进程”

孤儿进程会被“领养”,被1号进程领养(init,即系统本身)

之所以要被领养,因为父进程退出后,未来子进程如果退出了,父进程早已不在,没有人来回收它,需要领养的进程进行回收

下面是test.c的代码,父进程执行三次就结束,而子进程一直死循环:

Linux之进程_第22张图片

继续分为左右两个窗口,右边窗口运行,左边窗口观察进程:

Linux之进程_第23张图片

可以观察到,右边开始运行以后,ps观察进程,蓝线划出来的就是子进程,这时父进程还没有退出,子进程的ppid还是父进程的pid,等父进程三次运行完退出后,子进程的ppid变为1,表示被1号进程领养


8、进程优先级

之所以要有优先级,是因为CPU是有限的,但是进程太多了,所以需要用这种方式竞争资源

优先级就是确定谁先获得资源,谁后获得资源

优先级是选择将谁放在CPU上执行的重要调度指标

Linux中具体的优先级做法:

优先级 = 老的优先级 + nice值  

下面具体演示:

创建两个窗口,右边窗口先写一个test.c,代码是死循环打印hello world,再加上打印pid的值,然后运行;在左边窗口观察进程信息,输入 ps -al | head -1 && ps -al | grep test:

在还没有运行程序时:

运行后:

可以看到进程信息中pid和打印的pid相同,所以就是正在运行的进程,并且蓝色圈圈出来的PRI就是优先级,NI就是nice值

PRI表示这个进程被执行的优先级,值越小越早被执行

NI是nice值,表示进程可被执行的优先级的修正数值

所以加入nice值后,新的优先级 = 老的优先级 + nice值,当nice值为负数时,该程序的优先级值会变小,优先级会变高,即越快被执行

修改nice值:top

首先重复刚刚的操作,可以看到当前是PRI是80,nice值是0

top相当于Windows中的任务管理器

再打开一个窗口,输入top

Linux之进程_第24张图片

然后输入r

相当于告诉你renice就是修改nice值,而刚刚运行的pid是27103,所以再输入27103

现在就可以输入nice值,我们输入30,再用ps打印观察进程信息

可以发现优先级值变为了99,而nice值变为了19,那为什么不是30呢

因为Nice值的取值范围是-20 ~ 19,超过19自动当做19

所以Nice值为19,而优先级值也由刚刚的80变为了99

而我们如果想将优先级调高点,那就是nice值调低,将nice值输入-100,必须以sudo运行top:

这时nice值变为最小值-20,同时PRI也由80变为60,为什么不是刚刚的99-20变为79呢?其实每次设置优先级值都是由80开始设置,即从进程最开始的优先级开始设置


9、进程的几个特性

①竞争性:进程数量多,而CPU资源很少,所以进程直接是有竞争属性的,所以就有了优先级

②独立性:多进程运行,要独享各种资源,多进程运行期间互不干扰

③并行:多个进程在多个CPU下分别、同时进行运行,称之为并行

④并发:多个进程在一个CPU下采用进程切换的方式,在一段时间内,让多个进程都得以推进,称之为并发(里面有时间片、抢占与出让的概念)

时间片:每一个进程在CPU执行时都会有一段时间,比如10ms,运行完就该下一个进程进CPU

抢占与出让:有可能进程a在CPU中运行的时间是10ms,而5ms就进行完了,这时进程a就相当于在出让CPU资源;而抢占就是指优先级高的可以抢占优先级低的进程的CPU资源


切换:

CPU中有很多寄存器,如果此时进程a正在运行,那么CPU中的寄存器里面,保存的一定是进程a的临时数据,而寄存器中存储的进程a的临时数据,就叫做进程a的上下文数据

当进程a由于并发、时间片的约束,暂时被切下来时,需要进程a带走自己的上下文数据

带走进程a的上下文数据暂时保存的目的是:下次回CPU运行时,能够重新恢复上去,就能继续按照之前执行的逻辑继续向后执行,就如同之前没有中断过一样

CPU中的寄存器只有一份,但是上下文数据可以有多份,分别用于对应不同的进程


你可能感兴趣的:(Linux,linux,运维,服务器)