Linux进程(一)---深入了解进程的概念及状态

目录

什么是进程

如何管理进程

描述进程

PCB到底是什么?

PCB的内容分类

组织进程 

查看进程

ps命令

通过系统调用获取进程标示符

getpid()

getppid()

通过系统调用创建进程-fork初识

进程状态

运行态(R)

阻塞态(S)

阻塞态

 挂起态

和就绪态的区别

停止状态(T)

僵尸状态(Z)


什么是进程

进程我们虽然没有特地讲过,但是平常我们也一定在时刻接触着.例如打开任务管理器.

这些都是进程,例如QQ,浏览器,壁纸软件等,这些都是一个进程.

也是说:我们启动一个软件,本质就是启动了一个进程.

在Linux下,运行一条命令或者./xxx,其实都是在系统层面创建了一个进程.

进程概念:

课本概念:程序的一个执行实例,正在执行的程序等.

内核观点:担当分配系统资源(CPU时间,内存)的实体.

如何管理进程

Linux是可以同时加载多个程序,即Linux是可以同时存在大量的进程在系统中的.

我们必须把这些进程管理起来,如何管理呢?

先描述,再组织。

我非常建议大家可以看看我上一章所讲的内容,什么是先描述后组织,举了我们一些常见的例子来类比讲解,大家看完上一章再来看这些,看待问题的角度会有很大的不同.

描述进程

大家先来简单理解下这张图:

磁盘中有很多可执行程序,我们知道要运行程序必须先加载到内存,注意是把文件(可执行程序本质就是个文件)的内容(代码+数据)加载到内存, 为了以后方便管理,我们在操作系统内部,定义了一个结构PCB用来描述这个进程的全部属性数据.

注意,这个属性数据和文件内容关系不大.

所以此时的任务从对进程的管理,变成了对PCB结构体链表的增删查改.

所以此时再对进程有一个更深层次的定义.

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

之前一直说PCB,那么这

PCB到底是什么?

我们知道这是用来描述一个进程的全部属性信息的,当有一个新的可执行文件加载到内存时,对应的PCB也会新加载一份.

进程信息被放在一个叫做进程控制块(PCB)的数据结构中,可以理解为进程属性的集合。
课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct.

在Linux中描述进程的结构体叫做task_struct,每个系统平台的叫法可能不一样.,但统称为PCB.
task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息.

PCB的内容分类

这是PCB里面的的一些属性信息,我们会在后面分别讲解对应的重点部分.

标示符: 描述本进程的唯一标示符,用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。(区别于权限,优先级是区分先后,权限是区分能不能做).
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据[CPU,寄存器]。重点讲的.
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息

组织进程 

我们前面说了,描述进程的属性信息是PCB,在Linux下叫做task_struct.

所有运行在系统里的进程都是以task_struct双链表的形式存在内核里.

对进程的操作转化为对链表进行操作.

查看进程

有三种方式可以查看,分别为

ls /proc/进程PID【以文件的方式查看进程】

ps

top【top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程资源占用状况】

这三个命令.我们一般使用ps来查看

ps命令

ps 命令用于显示当前进程的状态,类似于 windows 的任务管理器。

用法如下:

ps [options]

常用选项ajx.意思就是显示全部进程信息.

 我们写一份源文件,命名为myproc.c

输入以下内容:

 即创建一个死循环。然后复制一个窗口,一个窗口执行,另一个窗口监视该进程. 

因为ps ajx是显示所有进程,所以我们需要在后面加上grep来挑选出我们需要的进程:

ps -ajx | grep 'myproc'

这样我们发现右边程序在运行的时候,左边也能看到对应程序运行的状态.

那么多进程,我们该怎么标识唯一呢?

通过系统调用获取进程标示符

每个进程都会有一个id,我们称之为PID,用来标识自身.还有一个PPID,是用来表示该进程的父进程的ID.后面会讲.

调用函数:getpid().

getpid()

我们看一下用法:

 其中pid_t是一种数据类型,代表无符号整数.

我们简单使用一下,打开myproc.c文件,输入:

 

然后和刚才的操作一样,编译成功文件后,复制一个窗口,然后一个窗口运行,另一个窗口监视.

 这样程序便获取到了自己的pid并输出了出来.

我们如果此时想关掉这个运行程序可以怎么做呢?

1.Ctrl+C   2.kill -p PID

一号不用多说,看下第二种方法效果.

 这样运行中的进程就被kill掉了,这个后面再讲.

除了可以获取子进程ID,也可以获得父进程的ID.用法同上,只不过是getppid().

getppid()

我们在原文件中加上这个用法:

 然后相同的操作:

 

此时自身的id和父进程的id都获取到了,那么有一个问题,这个父进程是谁呢?

 

我们发现父进程是bash,那这bash是什么呢?

bash是一个命令处理器, 运行在文本窗口中, 并能执行用户直接输入的命令.

通过系统调用创建进程-fork初识

我们先看一下fork是什么.

 可以看到fork作用是创建一个子进程.然后看一下返回值.

 说如果成功的话,子进程的PID会返回给父进程,0返回给新创建的子进程.

如果失败的话,-1会被返回给父进程,没有子进程会被创建.

这就很不符合常规了,哪有一个函数有两个返回值的?

我们可以看一下下面的代码:

 编译运行:

 发现一个printf竟然输出了两次,而且还是两个返回值各不一样.

其实这是因为父进程遇到fork()之后新加了一个子进程,两个进程都经过这个printf,导致输出了两次,一次是返回给父进程的子进程PID,另一个是子进程自己拿到的0.

 相当于由一个执行流变成了两个执行流.

然而我们在真正使用的时候都是会分模块的,子进程执行自己的任务,父进程执行它自己的任务,这样互不干扰.

 因为如果创建成功,子进程id会等于0,这样会走第二个if,而父进程会得到子进程id,一定是大于0的,所以会走第三条if.

这样可以看到确实走了不同的分支了. 

还是在总结一下,为什么fork()会有两个返回值?

1.因为fork()内部,父子会各自执行一次return语句,返回了两次当然有两个返回值.

2.返回两次,并不意味着会保留两次.(比如用同一个id判断,怎么可能做到判断两次,每次都是不同的值呢(同一个id怎么可以走两个执行流)这个问题后面会说.        

fork()子进程被创建出来之后,哪个进程先执行呢?

这个不一定,谁先运行,这个是由操作系统调度器决定的.

关于fork()的认识大家可以先停到这里,后面会再次深入讲解原理.

进程状态

进程一共有5种状态:

  1. 运行状态(Running):进程正在运行或者正在等待CPU资源。

  2. 就绪状态(Ready):进程已经准备好运行,但是还没有得到CPU资源。

  3. 阻塞(可中断睡眠)状态(Sleeping):进程正在等待某个事件的发生,例如等待输入输出完成、等待信号量、等待锁等。

  4. 停止状态(Stopped):进程被暂停了,例如由于收到了SIGSTOP信号。

  5. 僵尸状态(Zombie):进程已经结束了,但是其父进程还没有来得及处理它的退出状态信息,因此它被称为僵尸进程。

这是它们之间的关系图:

下面是状态转化的几种场景:

就绪->运行:进程调度
运行->就绪:时间片到或者被强行占用
运行->阻塞:请求服务后等待响应,或者等待某个信号的到来
阻塞->就绪:请求的服务已经完成,或者等待的信号已经到来

我们将分别讲解它.

运行态(R)

运行状态(Running):进程正在运行或者正在等待CPU资源。

注意,运行态不一定是在运行,linux下 task_struct结构体只要在运行队列(调度队列)中排队,就叫做运行态.

就好比在食堂,你在排队打饭,当别人问你在干什么,你会说我在吃饭,但此时饭并没有真正到嘴里.就是这个意思.

阻塞态(S)

阻塞态

阻塞状态(Waiting):进程正在等待某个事件(非CPU资源)的发生,例如等待输入输出完成、等待信号量、等待锁等。

我们首先要知道,系统中是一定 存在各种资源的(不仅仅是CPU),还有网卡,显卡,磁盘等设备.

所以系统中不只是存在一种队列!不只有CPU的运行队列,还有磁盘,网卡等相关队列.

例如CPU有一个运行队列在运行,磁盘也有一个队列也有很多进程在准备访问磁盘.此时CPU正在执行的这个进程遇到fread需要到磁盘中读取数据,这个时候便把这个进程从CPU的运行队列放到磁盘的等待队列中,这个等待的过程就叫做阻塞态 ,这个等待的队列就叫做阻塞队列.

 挂起态

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

当进程处于挂起态时,它暂时放弃了CPU的执行权,将进程的状态标记为“不可执行”。这通常是因为进程需要等待某些事件的发生,例如:I/O操作,信号接收,资源不足等等.

当进程是挂起态时,内存内部只有task_struct这个结构体,而没有代码和数据.

我们一开始提到了,这个阻塞态又叫做可中断睡眠状态.什么叫可中断睡眠态呢?

看下面代码:

我们让这段程序休眠100秒,然后运行,另一个窗口观察.

 

 此时发现程序进入了睡眠状态,此时我们给它发送信号,让它醒过来.

发送19号信号之后,就发现状态改变了,说明还是“理你的”.

就是说你给它信号,它还会回你,就类似于我在睡觉,有事随时叫醒我.这就是可中断睡眠.

与此对应的是D状态(磁盘休眠状态),又叫做深度睡眠状态,这是不可被唤醒的.类似于我在睡觉,有事勿扰.

和就绪态的区别

就绪态(Ready State)是指进程已经准备好执行,并满足了调度所需的所有条件,包括获取所需的资源。在就绪态中,进程等待被调度执行,但并没有获得CPU的执行权。它处于一种可以立即执行的状态,只需等待系统调度器将其选中并分配CPU的执行时间。

等待态(Waiting State)是指进程暂时无法执行,因为它正在等待某些事件、条件或资源的发生。进程进入等待态时,它放弃了CPU的执行权,并等待外部事件的触发或者特定条件的满足。在等待态中,进程暂时不能再进行任何运算,直到等待的事件或条件发生,或者所需的资源可用。

总的来说,就是就绪态资源已经准备就绪,就等被调用;等待态是因为资源没有就绪,然后放弃了被调用.

停止状态(T)

停止状态(Stopped):进程被暂停了,例如由于收到了SIGSTOP信号。

通常,暂停态用于暂停或中止进程的执行,例如在调试过程中暂停某个进程以进行调试操作,或者由系统管理员在必要时暂停进程的执行。进程可以从暂停态恢复到就绪态,并继续执行它原来被暂停的操作。

需要注意的是,暂停态与挂起态(睡眠/阻塞态)不同。挂起态表示进程暂时无法执行,等待某些条件或事件的发生。而暂停态是人为操作或特定信号导致进程主动停止执行,暂时中止进程的运行。

等待接收到对应的信号(如 SIGCONT)或使用命令(如 kill -CONT <pid>)来恢复到就绪态。

僵尸状态(Z)

僵尸状态(Zombie):进程已经结束了,但是其父进程还没有来得及处理它的退出状态信息,因此它被称为僵尸进程。

僵尸进程的主要特点是进程已经结束执行,但是相关的资源并没有完全释放,包括进程的PCB、内存和其他打开的文件描述符等。尽管僵尸进程不再执行,但其存在占用了系统的一些资源。

 可以看到子进程睡眠5秒就会结束离开,而父进程一直在运行,这个时候便会出现僵尸进程.

 可以发现子进程变成了Z僵尸状态,Z+代表是前台运行程序.

猜你喜欢

转载自blog.csdn.net/weixin_47257473/article/details/131694790