Linux——进程(下)

很楠不爱 2024-10-04 09:07:04 阅读 69

一.进程创建

进程调用

fork

,当控制转移到内核中的

fork

代码后,内核做:

分配新的内存块和内核数据结构给子进程 将父进程部分数据结构内容拷贝至子进程 添加子进程到系统进程列表当中 fork返回,开始调度器调度


二.进程终止

1.为什么终止

释放曾经的代码和数据所占据的空间。释放内核数据结构。


2.终止的三种情况

代码跑完,结果正确代码跑完,结果不正确代码执行时,出现了异常,提前退出了。

echo $?指令:父进程bash获取到最近一个的子进程的退出码,要知道子进程的退出情况。

结果是否正确可以通过进程的退出码决定,退出码为0表示退出成功,非0则表示失败

非0的情况有多种,每个退出码都代表失败的不同原因,可以使用下述函数,通过退出码来打印退出信息:

char *strerror(int errnum); 

父进程bash要通过退出码来获取子进程的退出情况(成功或失败,失败的原因),为用户负责。 

进程的异常退出,本质是进程收到了OS发出的进程信号

想知道进程如何异常退出,可以看进程退出时的退出信号是多少

衡量一个进程退出的情况,只需要两个数字,退出码和退出信号


3.如何终止

main函数中直接return,表示进程终止(非main函数,return表示函数结束,而非进程退出)。代码调用exit函数。在代码的任意位置调用exit,都代表进程终止。_exit() -- 系统指令。

exit和_exit的区别:exit会在进程退出时冲刷缓冲区,_exit不会。


三.进程等待

1.为什么等待

任何子进程,在退出情况下,一般必须要被父进程进行等待。等待的原因如下:

父进程通过等待,解决子进程退出的僵尸问题,回收系统资源(必须考虑)。获取子进程的退出信息,知道子进程是因为什么原因退出的(非必须)。


2.怎么等待

函数

pid_t wait(int *status):等待父进程中,任意一个子进程退出,等待成功返回子进程的pid。pid_t waitpid(pid_t pid,int *status,int options)

在子进程退出之前,父进程会一直进行阻塞等待,而不会执行自己的代码。 


参数

pid:

pid = -1 ,表示等待任一个子进程,与wait等效。pid>0,等待其进程ID与输入的pid相等的子进程。

status:

输出型参数,用于得到子进程的退出信息。退出信息包括:退出码和退出信号

如果父进程不关心子进程的退出信息,则可以设为NULL。 

int类型的status由32位组成,其中高16位不使用,对于低16位,高8位为进程的退出码,低七位为进程的退出信号。 

WIFEXITED(status)函数:若为正常终止子进程返回的状态,则为真。(查看进程是否正常退出)WEXITSTATUS(status)函数:若WIFEXITED函数返回值非零,提取子进程退出码。(查看进程退出码)

options:

子进程没有退出,而父进程在执行waitpid等待子进程,就会导致阻塞等待,即options默认为0。

将options改为WNOHANG,则为非阻塞等待

阻塞等待下,父进程调用waitpid函数会一直等待子进程的状态,直到其达到某种情况(如退出)才会停止等待

非阻塞等待下,父进程调用waitpid函数只会确认一次子进程的状态,而不进行等待,可以干其他事。此时,父进程需要循环调用waitpid函数来判断子进程是否退出,

非阻塞等待 + 循环 = 非阻塞轮询 


返回值

阻塞等待:

当正常返回的时候,waitpid返回收集到的子进程的进程ID;如果调用中出错,则返回-1,这时erron会被设置成相应的值以示错误所在。

非阻塞等待:

pid_t > 0:等待成功的,子进程退出了,并且父进程回收成功。pid_t < 0:等待失败了。pid_t == 0:检测是成功了,只不过子进程还没完全退出,需要你下一次进行重复等待。


四.进程替换

使用exec*系列的函数在原本的程序中执行新的程序,称为程序替换。 

其本质是exec*系列的函数类似于linux系统上的加载函数将新的程序加载到内存中

exec*系列的函数,执行完之后,后边的代码不会再被执行,因为被替换了。

这些函数的返回值不用关心只要替换成功,就不会向后运行,如果向后运行,代表替换失败

进程 = 内核数据结构 + 代码和数据,而进程替换即替换掉代码和数据,没有创建新的进程,而是复用原本的虚拟地址空间和页表,重新建立页表映射关系

当父进程创建子进程,通过进程替换让子进程去执行一个新的进程时,父进程的代码和数据都将进行写时拷贝,子进程将享有新的代码和数据,实现与父进程的完全独立


1.替换函数

exec*是整个替换函数系列的开头,后边追加的字符则代表不同的含义


(1)execl         

int execl(const char *path,const char *arg,...)

“l”代表list,即列表,表示该函数可以按列表方式执行程序。path是程序所在的路径,执行程序必须要给出其所在的路径。arg表示程序名(指令名)。"..."表示该函数为变参函数,用于追加多个后缀指令。参数必须以NULL结尾。

例子:

execl("/usr/bin/ls","ls","-l","-a",NULL);

ls -l -a即为一个列表

执行ls -l -a指令。


(2)execv

int execv(const char *path, char *const argv[])

"v"表示数组vector,说明该函数调用的指令需要从数组中获取。argv即为要存放指令的数组。

例子:

char *const argv[] = ("ls","-l","-a",NULL);

execv("/usr/bin/ls",argv);


 execlp/execvp

int execlp(const char *flie,const char *arg,...)

int execvp(const char *file, char *const argv[])

"p"表示环境变量PATH。

这两个函数与上述两种函数功能完全相同,但是使用这两个函数,第一个参数可以不传要执行的程序所在的路径,而只需传入该程序的名字,而后函数会从系统的PATH环境变量下找到该程序并执行


execle/execvpe

int execle(const char *flie,const char *arg,...,char *const envp[])

int execvpe(const char *file, char *const argv[],char *const envp[])

e表示环境变量,使用这两种函数,用户替换程序的环境变量

本质是用新的环境变量替换程序原本的环境变量, 使其成为新环境变量下的程序



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。