【Linux】进程终止和等待和程序替换

一、进程终止

1.1 进程退出的方式

  进程终止有一下几种方法,在 main 函数中 return 返回、调用 exit_exit 函数、收到终止信号、从执行的线程中返回、异常或错误。收到终止信号和从执行线程返回我们后面再谈。在命令行中,我们可以使用 echo $ 这串指令来查看最近一个进程的退出码。而进程退出场景中,常见的有以下三种:

1、代码运行完毕,结果正确,退出码为 0
2、代码运行完毕,程序没有崩溃,但因为逻辑问题,结果不正确,退出码为 !0
3、代码没有运行完毕,程序非正常结束,包括人为终止,此时退出码没有意义

1.2 _exit 和 exit 的区别

   exit_exit 级别比 return 高,可以在程序的任意一个位置退出,而 return 只有在 main 函数才是真的退出。_exit 是内核里面的一个系统调用,而 exitC语言中的一个函数,对 _exit 函数进行了封装。在调用 _exit 之前,exit 还做了以下工作:

1、执行清理函数:exit 会执行用户通过调用 atexit 函数或 on_exit 定义的清理函数。执行顺序与函数注册的顺序相反
2、冲刷缓冲区并关闭流:很多与I/O相关的函数都提供了缓冲区,用于缓存大块数据。如果进程退出时缓冲区里面还有未冲刷的数据,可能会导致数据丢失。exit 函数在关闭流之前,会冲刷缓冲区的数据,确保缓冲区里的数据不会丢失
3、删除临时文件:如果存在临时文件,exit 函数会负责将其删除
4、调用 _exit 函数:exit函数的最后也会调用 _exit 函数来终止程序

二、进程等待

2.1、进程等待的必要性

  父进程对需要 fork 创建的子进程进行等待的原因主要有以下几点:

1、回收资源:子进程终止时,其占用的系统资源(如内存、打开的文件描述符等)不会自动释放。父进程通过等待,可以获取子进程的退出状态,并回收子进程占用的资源,避免资源泄漏。
2、获取子进程的退出状态:父进程可以通过等待获取子进程的退出码,从而了解子进程的执行结果是正常结束还是异常终止,以便做出相应的处理。
3、防止僵尸进程:如果父进程不等待子进程结束,子进程在终止后会成为僵尸进程,占用系统进程表中的资源,长期积累可能导致系统资源耗尽。
4、同步和协调:在一些复杂的程序架构中,父进程可能需要根据子进程的执行结果来决定后续的操作,通过等待可以实现进程之间的同步和协调。

2.2、wait 和 waitpid

  上面提到过,进程等待能够获取子进程的退出状态:即父进程可以通过等待获取子进程的退出码,从而了解子进程的执行结果是正常结束还是异常终止。而 waitwaitpid 两个函数都有一个共同的输出型参数来获取退出进程的相关信息,即 int* status。这个输出型参数不能简单的当作整型来看待,可以当作位图来看待,目前只研究 status 的低16位,如下图。

wait方法:

#include<sys/types.h>
#include<sys/wait.h>

pid_t wait(int* status);
功能:
	等待任意一个子进程(可以有多个),当子进程退出,wait就可以返回
返回值:
	成功则返回被等待进程的pid,失败则返回-1。
参数:
	输出型参数,获取子进程退出状态,不关心则可以设置为NULL。

waitpid方法:

pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
    当正常返回的时候waitpid返回收集到的子进程的进程ID;
    如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
    如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
    pid:
        Pid=-1,等待任一个子进程。与wait等效。
        Pid>0.等待其进程ID与pid相等的子进程。
    status:
        WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
        WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
    options:
        WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进
程的ID。 

  因为信号类型不超过64种,因此用低7位就能表示。 core dump 标志位我们暂时不讲。我们可以通过 >>& 运算来获得进程的退出码和退出信号,但是也可以使用上面系统定义好的函数的 WIFEXITED(status) 和 WEXITSTATUS(status) 来判断进程是否出异常和获取进程的退出码。在 waitpid 函数中,pid=-1,表示等待任一个子进程,与 wait 等效。而等待的方法有两种,阻塞和非阻塞。阻塞就是父进程一直等待,直到子进程退出。而非阻塞把 options 设置为 WNOHANG,若子进程尚未结束则直接返回0,父进程可以继续执行其他任务。

三、进程替换

3.1、替换原理

  用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种 exec 函数
以执行另一个程序。当进程调用一种 exec 函数时,该进程的用户空间代码和数据完全被新程序替换(环境变量不会被替换,会被继承下去),开始执行新程序。即进程替换后,替换代码行之后的代码不会再被执行,不然也就不需要进程替换了,直接使用 fork 创建子进程就能执行另一个程序。同时调用 exec 并不创建新进程,所以调用 exec 前后该进程的 id 并未改变。exec 系类函数提供了一个加载器的效果,将磁盘上的可执行程序加载到内存

3.2、替换方法

  下面的六个函数,可以实现进程的替换。第一个为 Linux 提供的系统接口函数。剩下的函数的底层都是通过封装 execve 来实现的,这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。如果调用出错则返回-1,所以exec函数只有出错的返回值而没有成功的返回值。

int execve(const char* file, char* const argv[], char* const env[])
int execl(const char* path, const char* argv, …)
int execv(const char* path, char* const argv[])
int execlp(const char* file, const char* argv, … )
int execvp(const char* file, char* const argv[] )
int execle(const char* path, const char* argv, … , char* const env[])

  这些函数原型看起来很容易混,但只要掌握了规律就很好记。 l(list): 表示参数采用列表。 v(vector): 参数用数组。p(path): 有p自动搜索环境变量PATH。e(env) : 表示自己维护环境变量 。 execlexecle 函数需要指定可执行程序文件的完整路径或者确保可执行文件在系统的搜索路径中,两者区别在于 execle 函数可以自定义设置环境变量,而 execl 函数使用当前进程的环境变量。execlp 函数和 execl 函数的区别在于 execlp 会在环境变量 PATH 指定的目录中查找可执行文件。由上面可以看出,最好用的替换函数就是 execlp,下面是记忆方法和相关使用例子

#include <unistd.h>
 
int main()
{
    char *const argv[] = {"ps", "-ef", NULL};
    char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
 
    execl("/bin/ps", "ps", "-ef", NULL);
 
    // 带p的,可以使用环境变量PATH,无需写全路径
    execlp("ps", "ps", "-ef", NULL);
 
    // 带e的,需要自己组装环境变量
    execle("ps", "ps", "-ef", NULL, envp);
 
    execv("/bin/ps", argv);
    
    // 带p的,可以使用环境变量PATH,无需写全路径
    execvp("ps", argv);
 
    // 带e的,需要自己组装环境变量
    execve("/bin/ps", argv, envp);
}
  • 13
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Linux进程管理和进程通信是操作系统中非常重要的概念。我可以为您提供一些关于这两个主题的基本信息。 1. 进程管理: 在Linux中,进程是指在执行中的程序实例。进程管理包括创建、调度、终止和监控进程的活动。以下是一些关于进程管理的关键概念: - 进程标识符(PID):每个进程都有一个唯一的PID,用于标识和跟踪进程。 - 进程状态:进程可以处于运行、就绪、阻塞或终止等不同状态。 - 进程调度:操作系统根据特定算法决定哪个进程应该在给定时间内运行。 - 进程创建:通过fork()系统调用创建子进程,或者通过exec()系列系统调用加载新程序镜像并替换当前进程。 - 进程终止进程可以自愿终止,或者由其他进程(如父进程或操作系统)强制终止。 - 进程监控:可以使用系统工具(如ps、top)或编程接口(如proc文件系统)来监控和管理进程。 2. 进程通信: 进程通信是指进程之间交换数据和信息的机制。在Linux中,有多种方法可以进行进程间通信,包括: - 管道(Pipe):用于在父子进程或具有亲缘关系的进程之间进行通信。 - 信号(Signal):用于向进程发送简单的通知或中断信号。 - 共享内存(Shared Memory):允许多个进程共享同一块物理内存,以便快速高效地进行数据交换。 - 消息队列(Message Queue):进程可以通过消息队列传递和接收消息。 - 信号量(Semaphore):用于实现进程间的互斥和同步操作。 - 套接字(Socket):适用于网络编程,允许不同主机上的进程进行通信。 这只是对Linux进程管理和进程通信的简要介绍,如果您有任何具体问题或深入了解的需求,请随时提问!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杰瑞的猫^_^

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值