这篇文章谈谈linux中的进程管理。
一周爆肝,创作不易,望支持!
希望对大家有所帮助!记得收藏!
要理解进程管理,重要的是周边问题,一定要知其然,知其所以然。看下方目录就知道都是干货!
目录
1.什么是进程管理?
2.为什么要有进程管理?
3.如何对进程进行管理?
(1)描述进程
(2)组织进程
4.查看系统中的进程
(1)命令查看
(2)文件查看
5.创建进程的两种方式
(1)./运行一个程序
(2)通过代码创建
6.父子进程与fork()
(1)父子关系
(2)系统调用fork()
(3)fork()原理
1.fork()干了什么事?
2.为什么fork()会有两个返回值?
3.为什么fork()给父进程返回子进程pid,给子进程返回0?
4.fork()之后,父子进程谁先运行?
5.如何理解同一个变量会有不同的值?
6.重新理解fork()
7.进程状态
(1)一般操作系统的进程状态
1.运行状态
2.阻塞状态
3.阻塞挂起和swap分区
(2)linux的进程状态
linux进程的休眠状态
linux僵尸进程和孤儿进程
8.进程状态切换
CUP运行队列中的进程如何调度?
9.进程的性质
1.进程的竞争性
2.进程的独立性
3.进程的并行、并发
10.进程的优先级和时间片
11.进程间切换
12.linux内核2.61CPU运行队列窥视(综合理解!)
OS内核中有内存管理,进程管理,文件管理,驱动管理,OS将这四样管理起来才基本可以实现硬件和数据的自由调控,才可成为一个优质内核。
要知道为什么要有进程管理,首先要知道进程是什么东西,这里给出进程的定义。
进程的定义:程序的一个执行实例,加载到内存中的程序,即正在执行的程序。进程是操作系统执行任务的基本单元,也是分配资源、处理数据和被调度的基本单元。更加通俗的理解:操作系统要做事情,进程就是一件又一件的"事情"。
在哲学的范畴,管理的本质是对数据的管理,而非对具体对象的管理。管理的策略是:先描述,再组织。即先将被管理对象进行建模描述为抽象类型,再想好如何将每个被管理对象组织起来。
操作系统对进程的管理也是如此:
描述进程、组织进程
在操作系统中,经常将描述进程的类称为PCB(process control block)。进程的属性有很多:比如pid,ppid(父进程pid),stat(进程状态),进程优先级等等。后续慢慢学习······
struct PCB
{
int pid;
int ppid;
int stat;
......
};
进程的组织就是用高效的数据结构将进程对象组织起来,操作系统一般用双向链表将进程的PCB对象组织起来。
ps ajx //查看当前所有进程
ps ajx | head -1 && ps ajx | grep mybin//查看指定进程mybin
在根目录下的proc目录中有当前存在的所有进程,因此这个目录是动态的。
这个目录里的各个数字目录,就是以各个进程的pid命名的。目录里面放的就是进程PCB对象的各种属性。
本质:将磁盘上的可执行程序代码和数据拷贝到内存当中,为这个进程创建PCB对象,将PCB对象链入内存中管理PCB对象的双链表中。(重点)
使用fork()函数创建当前进程的子进程。
进程是有父子关系的,在一个进程中创建出新的进程,这个新的进程就被叫做那个进程的子进程。那个进程叫做新的进程的父进程。
fork()是一个进程通过代码创建子进程的时候,所调用的系统接口。通过fork()函数,在fork()下一行处会创建一个子进程,这个子进程和父进程共同执行接下来的代码。
如图:
体现出了fork()之后, 子进程和父进程两个执行流分别执行接下来的代码。
fork()函数
头文件#include
返回值:父进程返回子进程pid,子进程返回0。
现象:fork()是系统接口,fork()之后会为父进程创建子进程,父子进程两个执行流共同执行fork()之后的代码。
本质:进程的创建,本质是将可执行程序代码和数据加载到内存中,创建PCB对象,再将PCB对象链接到管理PCB对象的双向链表中。父进程中使用fork()创建进程本质是:以父进程PCB为模板创建子进程PCB,由于子进程没有从磁盘中加载代码和数据,子进程的PCB只能指向父进程的代码和数据,因此父子进程共享代码子进程可以看到父进程的全部代码,由于进程的独立性,数据会以写时拷贝的方式给到子进程。
fork()函数在return x 之前,子进程就被创建出来了,所以不要把fork()看成一个整体,return之后父子进程两个执行流共同执行接下来的代码,return也被父子进程以不同的值返回两次。
给父进程返回子进程的pid,是为了让父进程较好的控制管理子进程,所以返回子进程的pid让父进程进行控制。子进程只需要确认自己创建成功与否,所以返回0。
fork()后,父子进程都会进入CPU的执行队列中被调度,需要依赖进程PCB的调度优先级和调度器算法。
父子进程共享代码,但是要保证进程的独立性,数据不能共享,所以数据会以写时拷贝的方式拷贝给子进程,return两个值本质是写两次数据,子进程的数据就要写时拷贝。因此父子进程都有自己存储返回值的变量。
进程的状态指的是一个进程处在内存中某个PCB队列中所体现出来的状态。进程的状态是为了更好的让操作系统得知每个进程的实时情况所设定的。进程可以在很多PCB队列中进行排队。进程状态是PCB中的一个属性,是一个int类型的值,来表示这个进程目前的状态。
我们把进程的PCB在CPU的运行队列中排队的状态认定为运行状态。在CPU运行队列的进程准备被调度(取决于调度器算法)。
当我们的进程在CPU的运行队列中被调度的时候,这个进程开始让CPU来执行它的可执行程序,这个可执行程序势必或多或少会访问到操作系统中的软硬件资源,如果此时要访问的软硬件资源还没有到位,那么这个进程的PCB就会从CPU的运行队列上剥离,去到软硬件资源的等待队列中排队,此时这个进程的状态被设置为阻塞状态!
硬件阻塞
当我们的进程处于阻塞状态,进程的PCB在对应软硬件资源的等待队列中排队,此时这个进程的代码数据和PCB都在内存中占用着资源,但是这个进程暂时不需要被调度,如果此时操作系统的内存资源严重不足,就会把这个进程的代码和数据交换到磁盘中的swap分区中,为内存腾出空间。此时进程就处于阻塞挂起状态。当软硬件资源就绪时,操作系统再把这部分代码和数据唤回内存中,此时进程再从软硬件资源的等待队列中剥离到CPU的运行队列中排队。
软件阻塞
用linux调试代码工具gdb来解释一下就是,gdb在调试程序的过程中要根据断点来追踪、暂停进程,要想调试一个程序的代码,gdb和这个程序必须都加载到内存当中成为进程,CPU执行代码遇到断点进程就被阻塞了,需要等待gdb这个进程的继续操作,此时这个进程就需要等待软件资源gdb的响应,而gdb又要等待诸如键盘的响应,所以进程的PCB到gdb这个进程的PCB里的等待队列里排队,gdb这个进程PCB又到键盘的等待队列里排队。当键盘响应gdb,gdb的PCB又到CPU的运行队列里排队,待调试代码的进程也到CPU的运行队列里排队。
linux进程状态主要有一下几种:
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 */ //僵尸状态
};
本质就是阻塞状态(可以随时被终止)。这里还有一个深度休眠状态disk sleep(磁盘休眠)。当一个进程在向磁盘上写入数据的时候,这个进程需要等待磁盘写入完毕,并把写入结果返回给进程PCB,由于这个过程中进程不可被终止(防止数据丢失)此时这个进程的状态就是深度休眠状态。
当一个进程的可执行程序已经在CPU上跑完了,这个进程的生命周期就结束了,此时操作系统会先将这个进程的代码和数据进行释放,暂时不会释放这个进程的PCB。为什么?由于进程退出时的退出信息会保存在进程的PCB中,而操作性系统或者父进程要得知进程的退出原因(任务完成情况),所以要在父进程或者操作系统读取到退出信息之后,进程才能完全销毁,将进程PCB也释放。linux规定:进程在退出后,进程的PCB还没有被父进程或者操作系统读取到,进程还没有完全退出的进程状态为僵尸状态。如果进程一直处于僵尸状态,会导致内存泄露的问题。
上面所说,进程在退出时,PCB是由父进程来回收的。那么,如果进程退出前,父进程就已经退出了,那这个进程的PCB就没有相应的进程来回收了,此时进程的状态成为孤儿状态。
切换本质 : 进程的状态切换,本质就是进程的PCB在不同队列中做不停的跳转。
系统进程众多,而CPU资源只有少数,甚至一个,所以进程之间是具有竞争属性的。为了高效完成任务,合理竞争相关资源,便有了进程优先级和时间片已经调度器算法。
各个进程运行期间互不干扰,各自独立。
并行:多个进程分别在多个CPU上同时运行。
并发:多个进程在单个CPU上采取进程切换的方式,在一段时间内让多个进程都得以推进。
不要用人的感知来感受CPU的处理速度,例如每个进程的时间片是0.0001秒,CPU就执行每个进程0.0001秒,在一秒的时间内,上百个进程,每个都被CPU推进了100次!CPU的运行速度之快,决定了进程并发的可行性。
linux进程的优先级范围是60-99,默认优先级为80。优先级由默认优先级和nice值共同决定,例如:创建一个进程,这个进程的默认优先级为80,同时把nice设置为10,进程的最终优先级为80+nice=90。
时间片决定了一个进程每次使用CPU资源的最大时间限度。当一个进程使用CPU资源的时间超过时间片,进程就会自动剥离,等待下次被调度。
进程间切换指的是进程PCB在CPU运行队列中,上、下的反复过程。下去的进程要打包带走自己的寄存器数据,上来的进程要将自己的寄存器数据拷贝给寄存器硬件。
现在执行一个可执行程序mybin。
./mybin
一个进程的一生开始了!
1.此时,操作系统在内存中创建这个可执行程序的PCB,并把磁盘上的代码和数据拷贝到内存中。
2.PCB放入CPU运行队列中的过期队列中等待被调度。需要等待活跃队列中的PCB全部剥离,PCB剥离需要同时带走自己的寄存器数据保存在自己的PCB中。
3.活跃队列中的PCB全部剥离,swap两队列的指针,开始按照优先级执行活跃队列(原过期队列)中的进程。每个进程让CPU执行,需要把PCB内的寄存器数据拷贝给寄存器。
4.该你执行了,CPU开始执行代码,代码中要访问软硬件资源但没有就绪,阻塞!PCB进入对应的等待队列。(剥离时带走自己的寄存器数据)
5.软硬件资源就绪,PCB被唤回CPU运行队列中的过期队列中等待被调度。
6.又轮到你了,将寄存器数据交给寄存器,继续从上次阻塞的地方开始执行。
7.时间片到了,剥离到过期队列中。
8.活跃队列中的PCB全部剥离,再swap两队列的指针。CPU开始执行过期队列中的进程。
9.又轮到你了,这次CPU在时间片结束时间内把代码执行完了,进程将要结束了。
10.父进程把你的代码和数据释放,将PCB内的退出数据读取完毕,把PCB也释放。进程Z状态。
本次分享就到这里,如果对大家有用的话,希望程序猿们可以三连支持一下,会继续分享知识,加油!