Linux:进程控制(创建、终止、等待、替换)

进程控制(创建、终止、等待、替换)

进程创建(fork)

fork之后系统里多了一个进程,也就意味着多了一套PCB。

fork(用于创建子进程)

#include <unistd.h>
pid_t fork(void);

fork函数返回值:子进程返回0,父进程返回子进程pid,出错返回-1

进程调用fork,当控制转移到内核中的fork代码后,内核做:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度
    在这里插入图片描述
    也就是说子进程继承了父进程的代码,父子进程数据以写时拷贝的方式各自私有一份。
    写时拷贝:
    父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。
    在这里插入图片描述

fork常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

fork调用失败的原因

  • 系统中有太多的进程
  • 实际用户的进程数超过了限制

进程终止(exit)

进程退出一共有三种情况:

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

进程的退出方法:

  1. 从main函数return返回
  2. 调用exit
  3. _exit
exit函数
#include <unistd.h>
void exit(int status);

参数:status 定义了进程的终止状态(可以用0或非0表示),父进程通过wait来获取该值

_exit函数
#include <unistd.h>
void _exit(int status);

参数:status 定义了进程的终止状态(可以用0或非0表示),父进程通过wait来获取该值

exit和_exit的区别如下图:
在这里插入图片描述

进程等待(wait、waitpid)

进程等待必要性

  • 当子进程退出时,我们想要知道交给子进程的任务它完成的如何,因此我们需要等待子进程来获取子进程的退出信息。
  • 如果子进程退出,父进程不管它,那么就会造成僵尸子进程的问题,如果有无数个进程都这样做,那么就会产生无数的僵尸进程,僵尸进程要占用资源,要进行数据维护,那么就会产生内存泄漏问题。
  • 通过进程等待的方式来回收子进程资源。当父进程等待成功并拿到子进程的退出信息时,子进程就可以被操作系统回收了。

具体怎么等待呢?
当进程在运行时进程状态通常是R状态,如果此时父进程需要等待子进程,那么操作系统就会把父进程的状态设置为非R状态,然后把父进程的PCB放到在该进程下的等待队列中进行等待。那如果现在子进程运行完了,父进程此时就要被唤醒了,那么怎么唤醒呢?很简单,操作系统把父进程的PCB从等待队列中拿出来,然后把父进程的状态由非R状态变为R状态即可。(注:这种情况是阻塞式等待)

进程等待方法

wait

wait:等待任意一个子进程

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);

返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

waitpid

waitpid: 可以等待指定id的进程,也可以等待任意一个子进程

#include <sys/types.h>
#include <wait.h>
pid_t waitpid(pid_t pid,int *status,int options)

返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在。
参数:
pid: 要等待的子进程的id(如果将该值设置为-1,则等待任意一个子进程)
status: 子进程的退出信息(输出型参数)

宏:WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出) 位操作:status&0x7F
宏:WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码) 位操作:(status>>8)&0xFF

options: 等待方式(默认是阻塞等待)

WNOHANG(非阻塞等待): 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

waitpid里的status参数:
父进程按理说是不可以拿到子进程的退出信息的,但是父进程想通过status拿到子进程的退出信息,却是可以的,这是为什么呢?因为waitpid是系统调用,在进行调用waitpid函数时,父进程会陷入内核,权限会提升,操作系统就可以帮父进程拿到子进程的退出信息,waitpid刚好传了一个status参数,操作系统就把子进程的退出信息放在status参数里,进行传出,所以status是输出型参数。

0-7比特位表示是否正常退出,或者收到什么信号
8-15比特位表示退出时的退出状态(退出码),如果被信号所杀则没有此部分的数据
在这里插入图片描述

进程程序替换(exec*函数)

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

#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
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(有p的函数默认会去PATH路径下查找)
e(env) : 表示自己维护环境变量(说明需要主动的添加一些自己定义的环境变量)

注意事项:

  1. 有六个函数,但是execve才是系统调用接口,其他的五个都是封装的
  2. 一旦进行了程序替换,那么说明以前的代码和数据都被替换掉了,也就是说程序替换没有成功时的返回值

简单的进程程序替换函数使用:

#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);
	exit(0);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章