目录
什么是进程
如何管理进程
描述进程
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种状态:
运行状态(Running):进程正在运行或者正在等待CPU资源。
就绪状态(Ready):进程已经准备好运行,但是还没有得到CPU资源。
阻塞(可中断睡眠)状态(Sleeping):进程正在等待某个事件的发生,例如等待输入输出完成、等待信号量、等待锁等。
停止状态(Stopped):进程被暂停了,例如由于收到了SIGSTOP信号。
僵尸状态(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+代表是前台运行程序.