一.进程的创建和终止
1.1 fork函数
1.1.1 fork函数的使用
cpp
pid_t fork(void);
在之前, 曾经讲过fork函数的使用方式, 这里不再过多赘述。
1.1.2 fork函数的原理
当fork函数被调用时候, 进程切换到内核态进行以下操作:
- 分配新的内存块和内核数据结构给子进程
- 将父进程部分数据结构拷贝给子进程
- 添加子进程到系统进程列表中
- fork返回, 开始调度器的调度
1.1.3 写时拷贝
在子进程中, 父子代码通常是共享的, 父和子进程都不再写入时, 数据也是共享的, 但是当任何一方出访时写入, 便以写诗拷贝的方式各自持有一份副本。
1.2 exit和_exit函数
1.2.1 exit和_exit函数的用法
cpp
void exit(int status);
void _exit(int status);
- exit和_exit函数常用于进程终止退出。
参数:status 定义了进程的终止状态,父进程通过wait来获取该值
1.2.2 exit和_exit的区别
- exit函数通常会执行用户定义的清理函数, 刷新缓冲区, 关闭流等操作, 然后再调用_exit函数
- _exit则是直接终止该进程(类似于直接拔电源)
1.3 wait和waitpid函数
1.3.1 wait和waitpid函数的用法
cpp
pid_t wait(int*status);
返回值:
- 成功返回被等待进程pid,失败返回-1。
参数:
- 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
cpp
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。
1.3.2 阻塞等待和非阻塞等待
阻塞等待
cpp
#include <sys/wait.h>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cerrno>
int main()
{
pid_t pid;
pid = fork();
if(pid < 0){
return 1;
}
else if( pid == 0 ){ //child
printf("child is run, pid is : %d\n",getpid());
sleep(5);
exit(257);
}
else{
int status = 0;
pid_t ret = waitpid(-1, &status, 0);//阻塞式等待,等待5S
printf("this is test for wait\n");
if( WIFEXITED(status) && ret == pid ){
printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));
}
else{
printf("wait child failed, return.\n");
return 1;
}
}
return 0;
}
非阻塞等待
cpp
#include <sys/wait.h>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cerrno>
int main()
{
pid_t pid;
pid = fork();
if(pid < 0){
return 1;
}
else if( pid == 0 ){ //child
printf("child is run, pid is : %d\n",getpid());
sleep(5);
exit(1);
}
else{
int status = 0;
pid_t ret = 0;
do
{
ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待
if( ret == 0 ){
printf("child is running\n");
}
sleep(1);
} while(ret == 0);
if( WIFEXITED(status) && ret == pid ){
printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));
}
else{
printf("wait child failed, return.\n");
return 1;
}
}
return 0;
}
二.进程替换
2.1 什么是进程替换
新的程序替换当前正在运行的进程。这个过程通常涉及到终止当前进程的执行,并加载新的程序到内存中,然后开始执行新程序。
2.2 进程替换的原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
2.3 进程替换函数
cpp
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
- e(env) : 表示自己维护环境变量
cpp
#include <sys/wait.h>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cerrno>
#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);
}
事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve。
感谢大家观看,不妨点赞支持一下吧喵~
如有错误,随时纠正喵~