企业网站开发询问薇,安徽建设行业安全协会网站,wordpress后台乱了是怎么回事,西安软件开发培训机构文章目录 #x1f308; Ⅰ 进程创建01. fork 函数介绍02. 写时拷贝03. fork 常规用法04. fork 调用失败的原因 #x1f308; Ⅱ 进程终止01. 进程退出场景02. 常见退出方法 #x1f308; Ⅲ 进程等待01. 进程等待必要性02. 进程等待的方法2.1 wait 方法2.2 waitpid 方法 03.… 文章目录 Ⅰ 进程创建01. fork 函数介绍02. 写时拷贝03. fork 常规用法04. fork 调用失败的原因 Ⅱ 进程终止01. 进程退出场景02. 常见退出方法 Ⅲ 进程等待01. 进程等待必要性02. 进程等待的方法2.1 wait 方法2.2 waitpid 方法 03. 获取子进程状态3.1 使用位运算获取退出信息3.2 使用宏获取退出信息 04. 非阻塞轮询访问 Ⅳ 进程程序替换01. 替换原理02. 替换函数03. 函数解释04. 命名理解05. 函数用例5.1 execl 函数使用示例5.2 execlp 函数使用示例5.3 execle 函数使用示例5.4 execv 函数使用示例5.5 execvp 函数使用示例5.6 execvpe 函数使用示例 Ⅰ 进程创建
01. fork 函数介绍
fork 函数介绍
在 Linux 中可以使用 fork 函数从已经存在的进程中创建新进程。新的进程为子进程而原进程为父进程。
#include unistd.hpid_t fork(void);fork 的返回值
fork 函数有三种返回值。
返回值状态说明返回值 0表示子进程创建失败返回值 0表示当前进程为子进程返回值 0该返回值是子进程的 pid当前进程为父进程其持有子进程 pid
02. 写时拷贝
1. 什么是写时拷贝
通常情况下父子进程的代码共享父子进程在不进行写入操作时其数据也是共享的当任意一方试图写入时便会以写时拷贝的方式各自留存一份副本。 2. 为什么写时拷贝
创建子进程时子进程不一定要用到父进程的全部数据因此不需要直接将父进程的所有数据全部拷贝一份给子进程而是在要进行修改时再从父进程那拷贝这部分数据即可即可。子进程正在尝试对这部分数据进行修改但是父进程不打算修改这部分共享的数据因此子进程就必须将旧数据拷贝一份进行修改。
03. fork 常规用法
父子进程执行不同的代码段
判断 fork 的返回值从而让不同的进程去执行不同的代码段。
#include unistd.h
#include iostream
#include sys/types.husing std::cout;
using std::endl;int main()
{pid_t id fork(); // 创建进程if (0 id) // 执行子进程部分代码 cout I am child process endl; else if(id 0) // 子进程的创建失败了cout process creation failure endl;else // 执行父进程部分代码cout I am parent process endl;return 0;
}04. fork 调用失败的原因
系统中进程太多再 fork 时就会内存不足从而导致进程创建失败。实际用户的进程数超过了限制系统对每个用户能创建的进程数量是有上限的。 Ⅱ 进程终止
01. 进程退出场景
代码运行完毕结果正确。代码运行完毕结果错误。代码没执行完异常终止。
02. 常见退出方法
1. 使用 _exit 函数退出进程
函数原型 该函数是个系统调用。_exit 函数 不支持 刷新缓冲区。
#include unistd.h// 用于终止进程status 表示进程退出时的退出码
void _exit(int status);函数用例
int main()
{while (true){cout I am a process, pid: getpid() endl;_exit(3);}return 0;
}2. 使用 exit 函数退出进程
函数原型 该函数本质上是执行了系统调用的 _exit 函数。exit 函数 支持 刷新缓冲区。
#include stdlib.h// 用于终止进程status 表示进程退出时的退出码
void exit(int status);函数用例 在代码的任何地方调用 exit 函数都表示退出进程。
int main()
{while (true){cout I am a process, pid: getpid() endl;exit(2);}return 0;
}3. 使用 return 退出进程
执行 return n 等同于执行 exit(n)因为调用 main 的运行时函数会将 main 函数的返回值当做 exit 函数的参数。return 返回的值为 0 则表示进程执行成功反之则表示进程执行失败。且 0 之外的不同数字能够表示进程执行失败的不同原因。
#include cstdio
#include cstdlib
#include unistd.hint main()
{return 3;
}使用 echo $? 能够查看最近一次执行的进程的退出码。 Ⅲ 进程等待
01. 进程等待必要性
为什么要进程等待
子进程在退出时父进程如果对其不管不问就会造成僵尸问题从而造成内存泄漏。进程如果进入了僵尸状态就没人能够将其干掉无法杀死一个已经死去的进程。父进程需要知道派发给子进程的任务完成得如何结果是否正确是否正常退出等。因此父进程需要通过进程等待得方式去回收子进程所占用得资源并且获取子进程退出的相关信息。
进程等待能做什么
父进程能够通过 wait 方法回收子进程的资源 (必然)。父进程能够通过 wait 方法获取子进程的退出信息 (退出码、退出信号) (可选)。
02. 进程等待的方法
2.1 wait 方法
wait 函数原型
#include sys/wait.h
#include sys/types.hpid_t wait (int *status); // 输出型参数能通过该参数获取子进程退出结果默认置为空即可wait 函数功能
父进程阻塞等待任意一个子进程子进程不退则父进程不退。该函数能够回收子进程资源以及获取子进程的 pid。
wait 函数返回值
返回值 0返回值是所等待的子进程的 pid。返回值 0等待失败。
函数用例
演示父进程回收子进程资源。
#include iostream
#include sys/wait.h
#include sys/types.husing std::cout;
using std::endl;int main()
{// 创建子进程pid_t id fork();// 子进程执行自己的代码if (0 id) {for (size_t i 0; i 5; i){cout child process is running, pid: getpid() , ppid: getppid() endl;sleep(1);}cout 子进程准备退出马上变僵尸 endl;exit(0);}cout 父进程休眠 endl;sleep(8);cout 父进程开始回收僵尸进程 endl;// 父进程阻塞等待任意子进程pid_t rid wait(nullptr);// 等待成功rid 是子进程的 pidif (rid 0)cout wait success, rid: rid endl;cout 父进程回收僵尸进程成功 endl;sleep(1);return 0;
}2.2 waitpid 方法
waitpid 函数原型
#include sys/wait.h
#include sys/types.hpid_t waitpid (pid_t pid, int *status, int options);waitpid 函数参数
pid_t pid pid 为 -1 时等待任意一个子进程功能等同 wait。pid 0 时指定具体想要等待的那个进程。 int *status输出型参数能通过该参数获取子进程退出结果默认置为空即可int options指定父进程的等待方式为 0 则让父进程进行阻塞等待非 0 则进行非 阻塞等待。
waitpid 函数功能
回收子进程资源解决僵尸问题的同时还能够获取子进程退出信息。
waitpid 函数返回值
返回值 0返回值是所等待的子进程的 pid。返回值 0等待失败。
函数用例
#include iostream
#include sys/wait.h
#include sys/types.husing std::cout;
using std::endl;int main()
{// 创建子进程pid_t id fork();// 子进程if (0 id){for (size_t i 0; i 5; i){cout child process is running, pid: getpid() , ppid: getppid() endl;sleep(1);}cout 子进程准备退出马上变僵尸 endl;exit(1);}cout 父进程休眠 endl;sleep(8);cout 父进程开始回收僵尸进程 endl;// 用来获取子进程的退出信息 (退出码 退出信号)int status;// 父进程阻塞等待由 id 指定的子进程pid_t rid waitpid(id, status, 0);// 等待成功rid 是子进程的 pidif (rid 0){cout 等待成功, 子进程 pid: rid 子进程退出码: status endl;}sleep(1);return 0;
}这里子进程的退出码之所以不是 1 而是 256 的原因是变量 status 装着退出码和退出信号两部分信息。在 status 中只有低 16 位用于存储 退出码 退出信号。 正常退出时这 16 位的高 8 位存储退出码低 8 位默认全部存 0。异常退出时这 16 位的高 8 位不使用低 7 位 存储终止信号还有 1 位是 core dump 标志位。 当前子进程没有出异常因此为 1 的退出码在内存中是这样存储的 0000 0001 0000 0000因此按照整形的方式打印出的结果就是 256 了。 03. 获取子进程状态
3.1 使用位运算获取退出信息
获取子进程退出码将获取的状态码右移 8 位再和 0xFF 相与即可(status 8) 0xFF获取子进程退出信号将获取的状态码和 0x7F 相与即可status 0x7F
// 1. 使用位运算获取退出信息
#include iostream
#include sys/wait.h
#include sys/types.husing std::cout;
using std::endl;int main()
{// 创建子进程pid_t id fork();// 子进程if (0 id){for (size_t i 0; i 5; i){cout child process is running, pid: getpid() , ppid: getppid() endl;sleep(1);}cout 子进程准备退出马上变僵尸 endl;exit(1);}cout 父进程休眠 endl;sleep(8);cout 父进程开始回收僵尸进程 endl;// 用来获取子进程的退出信息 (退出码 退出信号)int status;// 父进程阻塞等待由 id 指定的子进程pid_t rid waitpid(id, status, 0);// 等待成功rid 是子进程的 pidif (rid 0){cout 等待成功, 子进程 pid: rid 子进程退出信号: (status 0x7F) 子进程退出码: ((status 8) 0xFF) endl;}sleep(1);return 0;
}3.2 使用宏获取退出信息
WIFEXITED(status)如果子进程是正常退出则该宏的值位真。(用以查看进程是否是正常退出)WEXITSTATUS(status)如果 WIFEXITED 的值为真则提取子进程退出码。(用以查看进程的退出码)
// 2. 使用宏获取退出信息
#include iostream
#include sys/wait.h
#include sys/types.husing std::cout;
using std::endl;int main()
{// 创建子进程pid_t id fork();// 子进程if (0 id){for (size_t i 0; i 5; i){cout child process is running, pid: getpid() , ppid: getppid() endl;sleep(1);}cout 子进程准备退出马上变僵尸 endl;exit(1);}cout 父进程休眠 endl;sleep(8);cout 父进程开始回收僵尸进程 endl;// 用来获取子进程的退出信息 (退出码 退出信号)int status;// 父进程阻塞等待由 id 指定的子进程pid_t rid waitpid(id, status, 0);// 等待成功rid 是子进程的 pidif (rid 0){// 子进程是正常退出的, 用户只需要关心退出码即可if (WIFEXITED(status)) {cout 等待成功, 子进程 pid: rid 子进程退出码: WEXITSTATUS(status) endl;}}sleep(1);return 0;
}04. 非阻塞轮询访问
父进程在阻塞等待子进程时父进程这时候什么事的做不了只能等待子进程退出才能去做自己的事效率太低非阻塞轮询访问就因此出现。
1. 非阻塞轮询访问
父进程每隔一段时间就执行一次系统调用判断子进程是否退出。如果子进程没有退出则父进程子继续执行自己的任务。如果子进程已经退出则父进程回收子进程的资源及退出信息。
2. 如何使用非阻塞等待
将 waitpid 函数的第三个参数改成非 0 值即可一般是用 WNOHANG 宏作为参数。
pid_t rid waitpid(id, status, WNOHANG);3. 非阻塞轮询访问实例
// 非阻塞轮询访问
#include iostream
#include sys/wait.h
#include sys/types.husing std::cout;
using std::endl;int main()
{// 创建子进程pid_t id fork();// 子进程if (0 id){for (size_t i 0; i 5; i){cout 子进程正在运行, pid: getpid() , ppid: getppid() endl;sleep(1);}exit(1);}// 用来获取子进程的退出信息 (退出码 退出信号)int status 0;while (true){// 父进程以非阻塞状态等待子进程退出pid_t rid waitpid(id, status, WNOHANG);if (rid 0) // 等待成功, 子进程已经退出{cout 等待成功, 子进程 pid: rid 子进程退出码: WEXITSTATUS(status) endl;break; // 不需要再执行循环等待了}else if (0 rid) // 等待成功子进程还没退出, 父进程可以执行其他任务{cout 子进程还未退出, 父进程执行其他任务 endl;// ... 父进程在等待子进程退出期间要执行的任务} else // 等待失败{perror(waitpid);break;}sleep(1); // 父进程每隔 1 秒查询一次子进程是否退出}sleep(1);return 0;
}Ⅳ 进程程序替换
01. 替换原理
1. 什么是程序替换
在用 fork 创建子进程之后子进程执行的是和父进程相同的程序 (但是有可能执行的是不同的代码分支)这样创建子进程就没多大意义了。如果创建的子进程想执行其他程序的代码需要调用 exec 系列函数去执行其他程序这种操作被称之为程序替换。当进程调用 exec 系列函数时该进程的用户空间代码和数据会完成被新的程序所替换从一个新的程序启动例程看i是执行。调用 exec 系列函数不会创建新进程因此在调用 exec 系列函数的前后该进程的 pid 不变。
02. 替换函数
exec 系列函数总共有 6 种都是以 exec 开头的这些函数统称为 exec 函数。
#include unistd.hint 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 execvpe(const char *file, char *const argv[], char *const envp[]);03. 函数解释
1. execl 函数
int execl ( // 替换失败时返回 -1成功时没有返回值const char *path, // 进程要执行的程序的所在路径const char *arg, ...); // 参数列表表示如何执行该程序 (命令行怎么写参数就怎么传), 以 NULL 结尾2. execlp 函数
int execlp (const char *file, // 要执行的程序名无需提供程序路径会自动去 PATH 环境变量中查找const char *arg, ...); // 参数列表表示如何执行该程序 (命令行怎么写参数就怎么传)3. execle 函数
int execle (const char *path, // 进程要执行的程序的所在路径const char *arg, ..., // 参数列表表示如何执行该程序 (命令行怎么写参数就怎么传)char *const envp[]); // 自己提供环境变量给子进程4. execv 函数
int execv (const char *path, // 进程要执行的程序的所在路径char *const argv[]); // 该参数是个指针数组用以存储参数表示如何执行该程序5. execvp 函数
int execvp (const char *file, // 要执行的程序的程序名无需提供程序路径char *const argv[]); // 参数数组表示如何执行该程序6. execvpe 函数
int execvpe(const char *file, // 要执行的程序的程序名无需提供程序路径char *const argv[], // 参数数组表示如何执行该程序char *const envp[]); // 自己提供环境变量给子进程04. 命名理解
解释 exec 之外的每个字母所表示的含义
l (list) : 表示参数采用列表。v (vector) : 表示参数用数组。p (path) : 有 p 表示会自动搜索环境变量 PATH。e (env) : 表示需要用户自己维护环境变量。
函数名参数格式是否带路径是否使用当前环境变量execl列表否需要自己提供程序路径是execlp列表是是execle列表否需要自己提供程序路径否需要自己配置环境变量execv数组否需要自己提供程序路径是execvp数组是是execve数组否需要自己提供程序路径否需要自己配置环境变量
05. 函数用例
5.1 execl 函数使用示例
让当前进程以 ls -a -l 的方式执行 /usr/bin/ls 路径所指定的程序。
int main()
{cout 程序替换开始 endl;// 当前进程以 ls -a -l 的方式跑去执行 /usr/bin 目录下的 ls 程序execl(/usr/bin/ls, ls, -a, -l, nullptr);cout 程序替换结束 endl;return 0;
}5.2 execlp 函数使用示例
用第一个参数作为要执行的程序的程序名无需自己补全程序路径。
int main()
{pid_t id fork();// 由子进程去进程程序替换if (0 id){cout 程序替换开始 endl;// 以 ls -a -l 的方式执行 ls 程序无需自己补全程序路径execlp(ls, ls, -a, -l, nullptr);cout 程序替换结束 endl;exit(1);}// 父进程阻塞等待子进程pid_t rid waitpid(id, nullptr, 0);if (rid 0)cout 等待成功 endl;return 0;
}5.3 execle 函数使用示例
// 演示 execle 函数
int main()
{// 定义环境变量char *const env[] { (char*)helloworld, (char*)PAHT/ };pid_t id fork();// 由子进程去进程程序替换if (0 id){ cout 程序替换开始 endl;// 以 mytest 的方式执行 当前目录下的 mytest.exe 文件execle(./mytest.exe, mytest, NULL, env);}pid_t rid waitpid(id, nullptr, 0);if (rid 0)cout 等待成功 endl;return 0;
}5.4 execv 函数使用示例
// 演示 execv 函数
int main()
{pid_t id fork();// 由子进程去进程程序替换if (0 id){ // 存储参数的指针数组char *argv[] { (char*)ls, (char*)-a, (char*)-l };cout 程序替换开始 endl;// 以 ls -a -l 的方式执行指定路径下的程序execv(/usr/bin/ls, argv);}pid_t rid waitpid(id, nullptr, 0);if (rid 0)cout 等待成功 endl;return 0;
}5.5 execvp 函数使用示例
// 演示 execvp 函数
int main()
{cout 演示 execvp 函数 endl;pid_t id fork();// 由子进程去进程程序替换if (0 id){ // 存储参数的指针数组char *argv[] { (char*)ls, (char*)-a, (char*)-l };cout 程序替换开始 endl;// 以 ls -a -l 的方式执行 ls 程序无需指定程序所在路径execvp(ls, argv);}pid_t rid waitpid(id, nullptr, 0);if (rid 0)cout 等待成功 endl;return 0;
}5.6 execvpe 函数使用示例
// 演示 execve 函数
int main()
{// 定义环境变量char *const env[] { (char*)helloworld, (char*)PAHT/ };pid_t id fork();// 由子进程去进程程序替换if (0 id){ // 存储参数的指针数组char *argv[] { mytest };cout 程序替换开始 endl;// 以 mytest 的方式执行 当前目录下的 mytest.exe 文件execve(./mytest.exe, argv, env);}pid_t rid waitpid(id, nullptr, 0);if (rid 0)cout 等待成功 endl;return 0;
}