Linux进程详解——万字总结,复习利器
Posted xiao zhou
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux进程详解——万字总结,复习利器相关的知识,希望对你有一定的参考价值。
一、图解计算机体系结构
xxxx在学习操作系统的时候,我们肯定要对计算机的体系结构有一个大体的认识,至少是在框架层次上的,作为软件开发人员,我们需要这方面的知识,但是并不是要对他十分透彻的认识,刨根问底的理解是不太需要的(当然自身知识越丰富越好)。
xxxx计算机的体系结构是一种分层结构,硬件~软件一层一层分布。下面我们来对其进行一个大体的框架认识。
xxxx接下来我们自底向上逐层来讲解一下计算机体系结构这六层。
(1)底层硬件
xxxx底层硬件就是计算机中的硬件设备,例如:网卡,键盘、硬盘、显示器……这些物理装置按系统结构的要求构成一个有机整体为计算机软件运行提供物质基础。功能是输入并存储程序和数据,以及执行程序把数据加工成可以利用的形式。
xxxx总而言之,硬件就是为我们计算机的正常运行提供必要的物质保障。
(2)驱动程序
xxxx驱动就是介于硬件与操作系统之间的一种特殊程序。由于操作系统需要对硬件进行管理(例如,什么时候读取数据,什么时候显示数据,管理内存等等等等),但是对于不同厂商生产的不同硬件,可能操作系统管理的方式是不同的,如果我们强制使用操作系统对硬件进行直接管理,那么我们甚至就需要更改操作系统的内核源代码,以适应不同的硬件,不同的计算机配一个操作系统,同一台计算机更换一次键盘、硬盘更新一下操作系统,成本是非常非常大的。这时候就出现了驱动来解决这个问题,操作系统大部分情况下只需要通过驱动与硬件交互,操作系统通过驱动向下对硬件进行管理,而驱动向上是对操作系统的统一接口。类似于适配器,这样操作系统就可以使用不同的硬件。按照我的理解,有点像Java中的JVM,达到一次编译,处处运行的感觉。操作系统只需要适应驱动的向上接口即可,对于不同硬件的适应交给驱动完成。
xxxx这样对硬件的直接管理权不在OS中,而是在驱动手中。就好像,刚插入鼠标,可能几秒之内是无法使用的,这是因为,虽然操作系统已经启动,但是驱动还没安装完成,所以无法驱动硬件。
(3)操作系统
xxxx我们经常说,安装操作系统。那么操作系统到底是什么?操作系统就是一款软件,一款搞管理的软件。在电脑上,如果没有操作系统,那么就只有硬件,就是一块可以进行逻辑算数运算的机器板子。但是有了操作系统,就是有了大脑。计算机就完成了升级。
xxxx首先我们要理解管理:什么叫做操作系统对计算机软硬件的管理呢?其实操作系统就像是一个“大脑”,处于一个支配他人管理的事务,它不需要亲力亲为,只需要站在高处统筹策划,将脏活累活交给下面的人(驱动)去做。操作系统不需要与被管理者(软硬件)之间交互,操作系统只需要进行决策。
xxxx但是操作系统依据什么进行决策呢?就是数据 / 信息。当操作系统对驱动下发指令,由驱动直接管理软硬件后,驱动也需要将软硬件的工作信息(数据)返回给操作系统,操作系统在知道工作状态后,就可以决策下面的工作行为,再次将指令发送给驱动。这样循环往复就完成了管理。保证了计算机的正常运行。
(4)System Call(系统调用)
xxxx在开发角度,操作系统会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。
xxxx对于Linux操作系统来说,他是用C语言编写的操作系统,因此系统调用接口就是C语言的一些函数。供我们在上层使用。
(5)用户操作接口
xxxx系统调用在使用上,功能比较基础,对用户的要求相对也比较高。所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。
(6)用户层
xxxx用户层就是我们日常使用的一层,我们可以下载软件(由OS进行管理)编写用户级软件等等。正是下面多层的功能,为用户提供了舒适的电脑的工作和使用环境。
二、进程
xxxx进程是一个很大的概念,进程管理也是操作系统的最重要功能之一。也是本文围绕展开的最重要话题和主线。
1、什么是进程
xxxx进程就是程序加载到内存(RAM)中运行器来的程序就变成了进程。
xxxx当我们写的C语言程序,在执行编译的时候就被加载到内存中,就形成了进程。一些软件甚至指令在使用和运行时,都会 变成进程。如图:
我们发现,其实进程不只只是我们打开的软件和正在执行的程序,还有好多我们看不见的任务在执行,每个任务都是进程。
2、创建子进程
如何创建子进程
xxxx创建子进程我们需要一个函数fork( )
在调用fork时会有两次返回。
(1)当创建子进程成功时,会给父进程返回创建子进程的pid;如果创建子进程失败会返回-1
(2)若创建子进程成功,那么就会给子进程返回0
底层
xxxx当fork创建子进程时,也就是操作系统先描述,再组织又开辟了一个task_struct给子进程。父子进程公用相同的代码(数据不是公用)
执行流
xxxx从我们开始写代码,我们大部分时间执行的都是单执行流,但是现在创建了子进程就有了多执行流。为了将多执行流区分。我们可以根据fork的返回值加以区分。例如
到底先调用子进程还是父进程由操作系统调度算法决定
3、进程的管理
xxxx回到体系结构中操作系统的话题,刚刚我们说,操作系统是一个搞管理的软件,其中有一个重要任务就是进程管理。那么对于跑起来的进程,OS是如何有效高效的进行管理的呢?
xxxx我们提到过,操作系统并不是直接对目标进行管理,而是通过对目标的信息与数据的管理而间接的管理。那这么多需要管理的对象,如此庞大的数据操作系统又该管理这些数据呢?六个字 :先描述、再组织。
(1)什么是描述
xxxx对于一个具体的进程来说,操作系统想要的不是进程本身,而是能够描述进程的数据,因此操作系统就要将进程数据化、抽象化,变成一堆数据。
(2)什么是组织
xxxx面对庞大的数据,如果想高效的管理和处理,那么必然要将数据组织起来,这样才方便管理。其实本质就是利用各种数据结构来组织数据。
xxxx那么操作系统来用于存储这些数据的就叫做PCB(Process Control Block)程序控制块。
4、PCB程序控制块
xxxx概念: PCB(process control block),进程控制块,是我们学习操作系统后遇到的第一个数据结构描述,它是对系统的进程进行管理的重要依据,和进程管理相关的操作无一不用到PCB中的内容。
xxxxPCB是不同操作系统对于管理进程数据的数据结构的总称,不同的操作系统有不同的数据结构的名字,我们讲解的所有内容都是Linux操作系统。Linux的PCB具体叫做task_struct。
(1)task_struct的内容框架
struct task_struct
{
1、pid和ppid
2、进程的优先级
3、如何找到该进程对应的数据个代码
4、时间片
5、上下文信息
6、连接信息
7、其他……
}
现在我们只需要知道task_struct是一个结构体,是操作系统对进程,先描述再组织得到的存储进程信息的结构体,听过一定的数据结构组织可以方便操作系统管理进程。
task_struct的重要内容的具体介绍会在下文提到。
(1)task_struct的内容具体介绍
① 标识符
xxxx每一个进程都有自己的标识符,其中最重要的就是pid(process ID)和ppid(parent process ID)。看到ID我们就应该想到类似于学号、身份证号码的东西,它是标识唯一性的号码。每个进程都需要这样的ID,因此在task_struct中必然会存在这样的标识符。
xxxx介绍两个系统调用接口,那就是getpid( )和getppid( ) 我们先来学习一下:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
while(1){
printf("this is a process...\\t");
printf("pid: %d\\tppid: %d\\n",getpid(),getppid());
sleep(1);
}
return 0;
}
我们来看一下调用结果
我们有三个发现:
xxxx1、我们发现每一次执行程序中pid和ppid都没有发生改变。这个很好理解,你一出生,身份证号就是定下来的。所以在一次执行中是不会发生改变的。
xxxx2、在不同执行中,pid是不同的。pid作为进程的“身份证”,你每次执行后结束,就相当于一个人出生了又死了,当你再次执行同一个程序,就像又重新出生了一个人,虽然这个人跟刚刚去世的人长得一模一样,但是身份证号码是不一样的,对于操作系统,该进程的pid就很大可能是不一样的。
xxxx3、ppid无论执行多少次,都是一样的,根据2的结论,这说明该进程的父进程一直是同一个人,并且从来没死掉过,所以他才能保持同一个pid。那么这个进程到底是谁呢?我们可以通过一定的指令来查看进程!
xxxx我们可以看到,proc(自己写的程序)进程的父进程就是-bash,bash是一种shell。shell是一个程序,可以称之为壳程序,用于用户与操作系统进行交互。用来区别与核,相当于是一个命令解析器,Shell有很多中,其中bash就是Linu预设的一种bash。那bash到底有什么功能呢?我们做一个实验,我们把bash进程终止掉,看看会发生什么情况。
xxxx我们发现一旦kill掉bash,就直接退出登录,说明我们一登录连接到云服务器,那么bash进程就会自动创建,他是用户与操作系统进行交互,是命令行解释器。bash被kill掉,整个Linux都无法使用了。
② 进程状态
睡眠状态S
xxxx睡眠状态是S(sleep)这是一种浅度睡眠,通常是进程在等待某种条件发生时所处的状态。处于该状态的进程可随时被唤醒和杀死。进程的状态并不是我们眼里所见进程的行为,而是CPU的工作状态。因为我们使用了printf,相对于打印,CPU处理数据的速度快的多得多,所以虽然一直在打印,但是CPU其实大部分时间在休眠。因此我们在查询某一时刻的进程状态的时候,CPU是在休眠的。
运行状态R
xxxx运行状态并不是指进程一直在运行,而是表示进程可以被调用的状态。处于R状态的进程会被组织在运行队列中并处于一种可被调度的状态。这就是运行状态。通过刚刚休眠的例子,我们发现既然是因为打印耗时太长导致处于S状态,那么我们不进行打印,一直空的无限循环,此时进程就是R(运行状态)。
+的含义
xxxx当初学习的时候,一直不理解状态后面的+是什么,后来查资料知道,+表征这个进程是前台进程(前台进程就是说,此时前台界面是在执行该程序的,不可以进行其他的操作。如果没有+,说明此时该进程是后台进程,前台界面是可以进行其他操作的)
xxxx如何是进程变成后台进程?如图操作:
xxxx前台进程终止可以ctrl+c。后台就不可以了,只能使用kill
深度睡眠(磁盘睡眠)D
xxxx磁盘睡眠(Disk Sleeping)处在这种状态的进程不接受外来的任何信号,这也是为什么之前我无法用kill杀掉这些处于D状态的进程,无论是”kill”, “kill -9″还是”kill -15″,因为它们压根儿就不受这些信号的支配。处于disksleeping状态的进程通常是在等待IO,比如磁盘IO,网络IO,其他外设IO。在等待的过程进程将处于休眠,但是如果此时进程被kill掉,那么IO就没有进程管理了,IO成功失败都无人注意,就可能会造成一定的危害。所以操作系统为了防止处于这种状态的进程被杀掉。就出现了disk sleeping,确保这些进程不会被杀掉。只有IO等条件满足后才会被唤醒。
暂停T(stopped)
xxxx
xxxx暂停状态,就是为了某种需要而将进程暂停的一种状态。
僵尸状态Z(zombie)
xxxx进程退出后,在系统层面,该进程曾经申请的资源不会立即被释放,而是要暂存一段时间,有父进程读取。在这个过程中,子进程(退出的进程)就处于僵尸状态。
xxxx为什么要有僵尸状态:进程被创建的目的是完成某种工作进程退出时,我们要知道这个进程的退出信息。操作系统就需要知道这个进程任务的完成情况,得到进程的退出信息。所以进程退出时不会直接彻底消失,而是进入僵尸状态仍然在task_struct中保存退出信息,让操作系统(父进程)完成对进程的处理工作。
下面我们来模拟一下僵尸状态
死亡状态X
xxxx僵尸进程的退出信息被父进程处理后变成一瞬间的退出状态退出,资源清理等等。进程彻底结束。
补充特殊的进程:僵尸进程、孤儿进程
(1)僵尸进程:刚刚我们讲解了僵尸状态,处于僵尸状态的进程是僵尸进程。那么僵尸进程有什么危害呢?
xxxx如果进程一直处于僵尸状态,那么这个进程就一直不会被释放,那么作为一个进程,一定还是会有它的PCB来维护这个进程,这就意味着,一直有PCB不被回收,一直在内存中,势必会造成内存资源的浪费,造成内存泄漏 。因此我们要避免一个进程一直保持僵尸状态的情况。
(2)孤儿进程:如果在子进程进入僵尸状态之前挂掉。那么当子进程结束任务进入僵尸进程后,它们就变成了孤儿进程(没有父进程)。那么谁来处理它的退出信息呢?因此当一个进程的父进程提前退出,它的子进程会立即被操作系统领养(被1号进程领养)。
xxxx我们可以观察到,当我们的父进程退出后,子进程立即被1号进程领养。1号进程成为子进程的父进程。
③ 优先级
xxxx优先级就是不同程序之间优先级的比较,优先级越高,越早接受CPU的处理。那为什么要有优先级?是因为,CPU资源有限,但是进程很多,所以当很多进程需要CPU处理时,就需要优先级来表示CPU处理的先后顺序。就好像根据优先级大小,进程要排队。
xxxx那么什么是进程排队?我们都知道了,每一个进程先描述再组织后,都会形成一个PCB,进程的排队本质就是PCB的排队,每个PCB都有一个优先级,根据优先级大小通过队列组织起来。优先级大的排在队头优先收到CPU的处理,优先级小的排在队尾。这样就利用了优先级形成了进程处理的先后顺序,即:进程的排队。
PRI vs NI
xxxxPRI:PRI代表了进程的优先级,PRI越小说明该进程的优先级越高。
xxxxNI:nice值,是进程优先级PRI的修正值。PRI(new) = PRI(old)+NI
通过修改NI来修改PRI改变进程优先级
xxxx使用top命令查看进程(top相当于Windows的任务管理器)。进入top后输入r,输入改变的进程的pid,然后输入你要改变的NI的值。NI取值∈【-20,19】。
如图,我们就修改了一个进程的nice值,然后修改了该进程的优先级。然后我们在此之上再做一次修改。
然后我们就发现了一个奇怪的现象,就是nice改成15后,并没有在原来PRI的基础上增加15,而是在默认值80上+15。所以我们得出结论,每次修改的nice,都会在默认的PRI的基础上进行修正。
④ 程序计数器
xxxx在进程运行时,会经常发生程序的切换,但是有些程序并没有终止由于时间片到了或者其他优先级更高的程序来了,就会被替换掉。那么我们就要保存进程执行到哪条指令了。
xxxx同时,CPU在执行指令时也要知道CPU自己取到的指令到底是从哪里来的,是属于哪一个进程的。
xxxx所以基于以上两个原因,计算机中就要有程序计数器。程序计数器的用于是储存下一条指令在内存中的地址,通常是PC、EIP这两个寄存器。这是在计算机的硬件角度。在进程角度,就需要在进程对应的PCB中留出一个字段来保存程序计数器内的数据。用于替换进程时回复数据(就好比我们看书看到一半忙其他的,我们就要记下来当前的页码,方便在之后回到这一页继续看书)。
⑤ 内存指针
xxxx包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块指针。(方便找到该进程对应的代码和数据和进程共享的内存)
⑥上下文数据
xxxx进程执行时,CPU的寄存器存放的属于该进程的数据。
xxxx为什么需要上下文数据?首先我们得明白,任何进程都有可能在任何时间点被其他进程替换掉。CPU有限,于是就会有进程属于“等待被CPU处理”这个过程。当一个进程正在运行,它的时间片到了,或者出现了一个优先级更高的进程,那么无论现在的进程是否被执行结束,都要剥离CPU换成新进程。但是我们要知道该进程执行到哪里,要保留这个信息,这样当这个进程再次被换到CPU上处理时,就可以继续接着执行。而将要被换下来时寄存器存储的数据就可以被作为上下文数据表示进程执行的位置,方便下次接着那个停止的位置执行。
⑦ 记账信息
xxxx包含处理器时间总和,使用时钟数总和,时间限制,记账号等等。
⑧ I/O状态信息
xxxx包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
⑨ 其他信息
5、环境变量
啥是环境变量
xxxx环境变量是系统级别的全局变量。环境变量是在操作系统中一个具有特定名字的对象,它包含了一个或者多个应用程序所将使用到的信息。通俗理解为:一些被指定的文件夹路径,目的是为了更快速方便的找到想要的文件和文件夹。
xxxx环境变量相当于给系统或用户应用程序设置的一些参数,比如path,是告诉系统,当要求系统运行一个程序而没有告诉它程序所在的完整路径时,系统除了在当前目录下面寻找此程序外,还应到path中指定的路径去找。
常见的环境变量
(1)PATH
<引例>
xxxx在Linux中,我们执行ls命令,其实ls也是一个可执行文件,跟我们自己写的可执行文件没有差别。但是为什么,我们却要在执行自己(不是系统的)可执行文件之前加入路径呢?— ./test。而ls却可以直接执行?
xxxx愿意就是我们没有吧自己的文件的path加入到环境变量中。如果加入了环境变量,当我们并没有提供文件的路径时,操作系统就会自己在环境变量中的所有PATH中搜索该文件的路径。
如果我们也想把自己的文件直接执行起来,该如何?
xxxx我们可以把我们想要执行的文件所在的路径加入到PATH中,这样我们在执行文件的时候,操作系统就会在PATH中寻找路径,就不需要我们自己添加了。
注:退出服务器,再次登录,PATH即可恢复原来的值,因为PATH是一个变量
(2)HOME
指定用户的主工作目录(即用户登录到Linux中的默认目录)
普通用户:
roor用户:
(3)SHELL
xxxx当前的Shell。shell就是命令行解释器,可以查看当前操作系统使用的是那种.
当前使用的是bash。
环境变量的有关指令
1、 echo:显示某个环境变量的值
2、 export:设置一个新的环境变量
3、 env:显示所有的环境变量
4、 unset:清除环境变量
5、 set:显示本地的shell变量和环境变量
访问环境变量的方式
(1)通过main函数的命令行参数
(2)通过extern导入外部变量
(3)通过系统调用接口getenv( )
6、进程地址空间
进程地址空间是指,每个进程都会有一个进程地址空间(虚拟地址)。每个虚拟地址都会通过页表(一种映射关系)指向一块内存(实际物理空间)。
(1)mm_struct
以上就是mm_struct的成员,我们可以看到都是unsigned long类型的整数。所以就有一个重要结论。
xxxx在虚拟空间中,mm_struct指向虚拟空间的具体位置(堆、栈、常量区、代码段等),不是由指针等指向,而是由整数划分范围。改变返回就是通过改变每一块区域的起始终止数值来决定的。
7、进程控制
进程创建
xxxx在上文已经提到过我们使用fork来创建子进程。现在将对fork和进程创建过程进行进一步的详细解读。
(1)关于fork的一些讨论
fork为什么会有两个返回值?
xxxx我们在调用fork后,我们就应该大体知道fork内部的实现流程。
pid_t fork()
{
①为子进程分配内存块,创建各种数据结构
②拷贝父进程的数据内容到子进程的内存与数据结构中,并稍加修改形成子进程对应的内存和数据结构
task_struct
mm_struct
页表信息
文件信息
。。。
③添加子进程到系统进程列表当中
将子进程的task_struct连接到系统的进程列表
将子进程连接到运行队列
等等操作
④return pid
在④之前,子进程已经被创建完成,所以当创建完成后就会产生两个执行流,父、子进程同时执行return
所以会有两个返回值
常见的误区就是,感觉fork函数在执行完成后,子进程才被完全创建,形成两个执行流
}
为什么给子进程返回0,给父进程返回子进程的pid?
xxxx解决这个问题还是比较有意思,比较好理解的。
xxxx对于一个父进程产生多个子进程后,每个子进程是相互独立的。子进程之间没有联系,他们不需要知道对方的信息。而子进程只有一个父进程,所以不需要去标识父进程。
xxxx但是对于父进程就不一样了。由于父进程可能有多个子进程,并且父进程创建子进程是完成一定的任务,父进程必须知道子进程的任务完成情况。所以父进程必须得到每个子进程的pid去唯一标识一个子进程。
xxxx就好像现实生活中,一个家庭有多个孩子,每个孩子只有一个爸爸,但是该父亲有多个孩子。那么就要区分孩子,就要给他们取名字。但是孩子只有一个父亲,他们不需要知道父亲的名字,每个孩子只需要喊爸爸,父亲就知道孩子在叫他。如果父亲喊儿子,那么这么多儿子,根本不知道喊的是哪个儿子。
(2)fork的使用
fork的常规用法:
xxxx①一个父进程想要复制自己,让子进程执行不同的代码段
xxxx②一个进程要执行不同的程序。
fork调用失败的场景
xxxx①系统中有太多进程了。资源不够。
xxxx②实际用户的进程超过了限制。
(2)写时拷贝
什么是写时拷贝
xxxx父子进程的代码是共享的,使用的是一套代码(可以通过if、else来分离执行流)。通常,父子代码共享,父子在不写入时,数据也是共享的,当任意一方试图写入数据,便以写时拷贝的方式各自产生一份副本。如图:
为啥要写时拷贝
①进程具有独立性,如果仍然在同一块物理空间写入数据,那么势必会影响到另一个进程的内容,这是不允许的。
②为何不在创建子进程时就拷贝?因为,很多情况下是不会写入数据的,如果每次创建子进程都拷贝,就会造成不必要的时间空间等资源的浪费。现在按需分配,极大地节省了资源。同时写时拷贝也是一种“延时分配”,在进程不需要的时候,不提前给你资源,操作系统自己留着,可以更加高效的使用内存(优先给需要的进程)。
进程退出
1、进程退出的场景
① 代码运行完毕,结果正确
② 代码运行完毕,结果错误
③ 代码执行异常
2、进程退出码
xxxx提到进程退出码,我们最常见的就是C程序中main函数的return 0。C程序跑起来也是进程,所以我们就通过C程序中main函数中的return 0 来讲解退出码。
main的返回值返回给了谁
xxxxmain函数也是函数,是函数就要被别人调用,是mainCRT_startup调用了main函数。所以main函数返回值是返回给了mainCRT_startup。本质就是返回给了操作系统。
为什么需要有返回值
xxxx因为,调用函数就是为了完成某种任务,任务的执行结果,完成与否,出现了怎样的错误,都是我们程序编写者需要知道的。所以就出现了错误码。而main函数的返回值返回的就是错误码,通过它,我们就可以知道程序是否出现问题,出现的问题具体是什么。。。
如何查看退出码
xxxx我们可以使用**echo $?**来查看最近一次进程退出时的退出码。如图:
xxxx刚刚提到程序退出的三种情况:
① 代码运行完毕,结果正确
② 代码运行完毕,结果错误
③ 代码执行异常
代码执行完毕就会返回0;0代表进程执行成功。
而进程运行异常就会返回非0的退出码,而不同的值又代表不同的意思。
*介绍一下char strerror(int errnum)**会将每个错误码转换成对应的错误信息。
进程退出的方式
① main函数的return:
xxxxmain函数是程序的入口,同时也是程序的出口,所以只有main函数的return才能时程序中止,其他的函数无法做到
② exit(退出码)<stdlib.h>
xxxxexit可以直接终止掉进程。不同于return只能使用在main函数,exit可以在程序的任何地方直接终止程序。
③ _exit(退出码)<unistd.h>
xxxxexit和_exit其实功能差不多,都可以在程序的任何地方终止程序。但是有一个区别:
exit在退出程序时会释放进程曾经占有的资源。例如:缓冲区。
但是_exit不会。接下来举一个例子。
我们发现在休眠两秒后打印出了“hello Linux!”
我们发现使用了 _exit就=以后就没有打印出“hello Linux!”这就是因为_exit退出进程后,不会释放进程曾经占用的资源等收尾工作。。。。。。。
程序的异常退出
程序可能无法正常执行完毕,异常退出。异常退出有两种情况。
① 程序出错了
② 我们使用Ctrl+C强行中止进程
xxxx当进程异常退出后,它的退出码就没有意义了。例如:
进程是死循环,我们使用ctrl+c强行终止进程,再看它的退出码130是没有任何意义的,不是strerror得出的含义。
进程终止OS做了什么
xxxx释放创建的数据结构,释放申请的内存,从各种队列等数据结构中剥离。
进程等待
xxxx之前提到过,如果子进程退出后如果父进程不对子进程进行处理,那么子进程就会一直保持僵尸状态,无法彻底杀死,会造成内存泄漏等一系列问题。所以父进程必须等待子进程 退出后对子进程进行回收子进程资源、获取子进程退出信息所以就需要父进程等待子进程。
(1)父进程等待子进程的方法
①pid_t wait(int*status)
返回值:成功返回被等待进程的pid,失败返回-1.其中status为输出型参数,获取子进程退出状态,不关心则可以设置成NULL。
在父进程用wait函数等待子进程后,子进程不会进入僵尸状态。
②pid_ t waitpid(pid_t pid, int *status, int options)
返回值:当正常返回的时候waitpid返回收集到的子进程的进程ID;
参数:
①pid:
输入等待进程的pid。若pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。若pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
②status:
输出型参数,获取等待要进程进程的退出状态
③options:
如果使用了WNOHANG(wait no hung)参数调用waitpid,如果没有子进程退出,它会立即返回0,不会像wait那样永远等下去,否则返回子进程pid。(我们称之为阻塞等待)
如果使用0,则和wait功能一样,一直等待。(我们称之为阻塞等待)。
(2)获取进程退出状态(WIFEXITED和WEXITSTATUS)
xxxx进程退出的时候,会给父进程退出状态的反馈(退出信号和退出码)
WIFEXITED(status)
xxxx若为正常终止子进程返回的状态就是真(判断进程是否正常退出)
WEXITSTATUS(status)
xxxx若WIFEXITED非零,那么就可以获取子进程的退出码。(查看退出吗,判断子进程任务完成情况)
只有WIFEXITED为真时,退出码才有意义,如果子进程是异常退出,那么它的退出码没有任何意义。
进程程序替换
(1)替换原理
xxxx用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec系列函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
举例
①替换成功
②替换失败
(2)进程替换函数
函数命名理解
l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境
总结
xxxx进程是一个非常重要的模块。需要多次复习,多敲代码练习。但最重要还是理解,这也是Linux哲学重要内容之一。(๑╹◡╹)ノ
xxxx"(T▽T)""还要继续肝啊~~
以上是关于Linux进程详解——万字总结,复习利器的主要内容,如果未能解决你的问题,请参考以下文章