【Linux】进程控制

发布于:2024-10-17 ⋅ 阅读:(10) ⋅ 点赞:(0)

目录

进程终止

进程常见退出方法

_exit函数和exit函数的区别

进程等待

什么是进程等待

为什么需要进程等待?

进程等待的方法 

wait方法

waitpid方法

获取子进程status 

进程替换

替换函数


进程终止

进程退出有三种情况

  1. 代码运行完毕,结果正确
  2. 代码运行完毕,结果不正确
  3. 代码异常终止

进程常见退出方法

正常终止(可以通过 echo $?  查看进程退出码)

  • 从main返回
  • 调用exit
  • _exit

异常退出

  • ctrl+c,信号终止 kill -9

_exit函数和exit函数的区别

_exit函数

#include <unistd.h>
void _exit(int status);
参数: status 定义了进程的终止状态,父进程通过 wait 来获取该值

exit函数

#include <unistd.h>
void exit(int status);

这两者之间属于调用与被调用的关系,exit最后也会调用_exit,但在调用_exit之前,还做了其他工作,如下为exit函数的执行流程:

  1. 执行用户通过atexit或on_exit定义的清理函数
  2. 关闭所有打开的流,所有的缓存数据均被写入 
  3. 调用_exit

进程等待

什么是进程等待

通过系统调用wait/waitpid,来进行状态检测与回收的功能

为什么需要进程等待?

  • 子进程退出,父进程如果不管不顾,可能会造成僵尸进程的问题,僵尸进程无法被杀死,因此只有通过进程等待来杀掉该进程,进而解决内存泄漏问题。
  • 我们需要通过进程等待,获得子进程的退出情况(任务完成的怎么样了)。

进程等待的方法 

wait方法

#include <sys/types.h>

#include <sys/wait.h>

pid_t wait(int *status)

返回值:

        等待任意一个进程,有进程退出就返回,成功返回被等待进程id,失败返回-1;

参数:

        输出型参数,获取子进程退出状态,不关心则可以设置称为NULL

如果子进程不退出,父进程默认在wait的时候,调用这个系统调用的时候,也就不返回,默认叫做阻塞状态。

waitpid方法

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

获取子进程status 

wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。

status是一个整数,是32位,我们只考虑它的低16位。

status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):

  • 正常终止的情况下,低16位的前8位标识进程退出码
  • 被信号所杀的情况下,低7位标识终止信号 

进程替换

替换函数

其中有六种以exec开头的函数,统称exec函数:

#include <unistd.h>`

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

  • 函数名带l的表示一个列表,参数以列表的形式填入

int execlp(const char *file, const char *arg, ...);

  • 函数名带p的,表示PATH,execp自己会在默认的PATH环境变量中查找,传参的时候只需要传入要执行的文件就可以了

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

  • e表示enviroment环境变量,需要传入环境变量

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

  • v可以理解为vector,与l不同的是第二个参数传入字符串指针数组

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

int execve(const char *path, char *const argv[ ], char *const envp[]);

这里总结一下:

  • l(list):表示参数使用列表
  • v(vector):表示参数使用数组
  • p(path):有p自动搜索环境变量PATH
  • e (env):表示自己维护环境变量

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序,当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序启动例程开始执行,调用exec并不创建新的进程,所以调用exec前后该进程的id并未改变。

TIPS:

  • 程序替换成功之后,exec*后续的代码不会被执行,替换失败,才执行原本后续的代码,exec*函数只有失败返回值,没有成功返回值
  • Linux中形成的可执行文件被称为ELF文件,可执行文件的表头指明了程序执行入口地址
  • makefile一次要形成多个可执行文件可以引入一个伪目标all,如下:
.PHONY:all
all:otherExe mycommand

otherExe:otherExe.cpp
        g++ -o $@ $^ -std=c++11
mycommend:mycommand.c
        gcc -o $@ $^ -std=c99
.PHONY:clean
clean:
        rm -f otherExe mycommand

环境变量在进行进程替换的时候不会被替换,而是继承下去,父进程所包含的环境变量,子进程会全部继承,子进程新增的环境变量,父进程并不会拥有。