🐶博主主页:@ᰔᩚ. 一怀明月ꦿ
❤️🔥专栏系列:线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C++,linux
🔥座右铭:“不要等到什么都没有了,才下定决心去做”
🚀🚀🚀大家觉不错的话,就恳求大家点点关注,点点小爱心,指点指点🚀🚀🚀
目录
非阻塞等待
当父进程进行非阻塞等待的时候,父进程完成其他的任务,比如download、printlog、show
进程等待的必要性
进程程序替换
execl
execlp
execv
execvp
替换c++程序
替换shell语言程序
exec族函数为什么可以替换不同语言的程序?
当一个程序运行的整个过程
非阻塞等待
在 Linux 中,可以使用非阻塞方式等待子进程退出或状态改变。这通常通过设置
waitpid()
函数的选项参数中的WNOHANG
标志来实现。这样,waitpid()
函数将立即返回,而不会阻塞当前进程。事例
[BCH@hcss-ecs-6176 11_7]$ cat myprocess.c #include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> void Worker(int cnt) {printf("I am a child ,pid: %d, cnt:%d\n",getpid(),cnt);} int main() {pid_t id=fork();if(id==0)//child{int cnt=5;while(cnt--){Worker(cnt);sleep(2);}exit(0);}while(1){//fateherint status=0;pid_t rid=waitpid(id,&status,WNOHANG);if(rid>0){printf("child quit success,exit code:%d exit sign:%d\n",(status>>8)&0xFF,status&0x7F);}else if(rid==0){printf("father do other thing......\n");}else{printf("wait fail\n");}sleep(1);}return 0; }[BCH@hcss-ecs-6176 11_7]$ ./myprocess father do other thing...... I am a child ,pid: 20429, cnt:4 father do other thing...... I am a child ,pid: 20429, cnt:3 father do other thing...... father do other thing...... I am a child ,pid: 20429, cnt:2 father do other thing...... father do other thing...... I am a child ,pid: 20429, cnt:1 father do other thing...... father do other thing...... I am a child ,pid: 20429, cnt:0 father do other thing...... father do other thing...... child quit success,exit code:0 exit sign:0 wait fail wait fail wait fail wait fail wait fail wait fail wait fail wait fail[BCH@hcss-ecs-6176 ~]$ while :; do ps ajx | grep myprocess | grep -v grep ; sleep 1 ; echo "================================="; done ================================= ================================= =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess 20428 20429 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess 20428 20429 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess 20428 20429 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess 20428 20429 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess 20428 20429 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess 20428 20429 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess 20428 20429 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess 20428 20429 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess 20428 20429 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess 20428 20429 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess =================================2499 20428 20428 2499 pts/4 20428 S+ 1000 0:00 ./myprocess
当父进程进行非阻塞等待的时候,父进程完成其他的任务,比如download、printlog、show
[BCH@hcss-ecs-6176 11_7]$ cat myprocess.c #include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h>//定义父进程的任务的个数 #define TASK_NUM 5//重定义函数指针类型 typedef void(*task_t)();//这里的这些功能并没有真正实现,而是输出一段字符串 //执行下载的任务 void download() {printf("this is a download task is running!\n"); }//执行写入的任务 void printlog() {printf("this is a write log task is running!\n"); }//执行查看的任务 void show() {printf("this is a info task is runing!\n "); }//将函数指针数组初始化 void initTask(task_t tasks[]) {int i=0;for(;i<TASK_NUM;i++){tasks[i]=NULL;} }//添加任务到,函数指针数组中 int addTask(task_t tasks[],task_t t) {int i=0;for(;i<TASK_NUM;i++){if(tasks[i]==NULL){tasks[i]=t;return 1;}}return 0; }//执行函数指针数组里的任务 void executeTask(task_t tasks[],int num) {int i=0;for(;i<num;i++){if(tasks[i])tasks[i]();} }//子进程的执行的任务 void Worker(int cnt) {printf("I am a child ,pid: %d, cnt:%d\n",getpid(),cnt);}int main() {//创建函数指针数组,用于存储各种任务(函数)task_t tasks[TASK_NUM];initTask(tasks);addTask(tasks,download);addTask(tasks,printlog);addTask(tasks,show);//创建子进程,id>0执行父进程,id==0执行子进程pid_t id=fork();if(id==0)//child{int cnt=5;while(cnt--){Worker(cnt);sleep(1);}_exit(0);}while(1){//fateherint status=0;//记录子进程退出的退出码和信号pid_t rid=waitpid(id,&status,WNOHANG);//通过waitpid系统调用去等待子进程,采用WNOHANG非阻塞的方式等待if(rid>0){//wait success, child quit nowprintf("child quit success,exit code:%d exit sign:%d\n",(status>>8)&0xFF,status&0x7F);}else if(rid==0){printf("##############################################################################################\n");//wait success, but child not quitprintf("father do other thing......\n");executeTask(tasks,TASK_NUM);//也可以在内部进行自己移除&&新增对应的任务printf("##############################################################################################\n");}else{//wait failed, child unknowprintf("wait fail\n");}sleep(1);}return 0; }[BCH@hcss-ecs-6176 11_7]$ ./myprocess ############################################################################################## father do other thing……//说明此时rid==0,父进程开始执行其他任务 this is a download task is running! this is a write log task is running! this is a info task is runing!############################################################################################## I am a child ,pid: 23948, cnt:4 ############################################################################################## father do other thing...... this is a download task is running! this is a write log task is running! this is a info task is runing!############################################################################################## I am a child ,pid: 23948, cnt:3 ############################################################################################## I am a child ,pid: 23948, cnt:2 father do other thing...... this is a download task is running! this is a write log task is running! this is a info task is runing!############################################################################################## I am a child ,pid: 23948, cnt:1 ############################################################################################## father do other thing...... this is a download task is running! this is a write log task is running! this is a info task is runing!############################################################################################## I am a child ,pid: 23948, cnt:0 ############################################################################################## father do other thing...... this is a download task is running! this is a write log task is running! this is a info task is runing!############################################################################################## ############################################################################################## father do other thing...... this is a download task is running! this is a write log task is running! this is a info task is runing!############################################################################################## child quit success,exit code:0 exit sign:0//rid>0,此时子进程退出,父进程接收到子进程的退出信息和退出码 wait fail//子进程结束,waitpid捕获不到该子进程的信息,所以rid<0 wait fail
进程等待的必要性
如果不进行进程的等待,有可能形成僵尸进程
进程等待我们现在一般使用的阻塞等待,因为阻塞等待足够简单!
进程程序替换
我们所创建的所有子进程,执行的代码,都是父进程代码的一部分!
如果我们想让子进程执行新的程序呢???执行全新的代码和访问全新的数据,不在和父进程有瓜葛
那需要程序替换
execl
execl 是一个系统调用,用于在当前进程中执行一个新的程序。它的原型如下:
#include <unistd.h> int execl(const char *path, const char *arg0, const char *arg1, ..., const char *argn, (char *) NULL);path 参数是要执行的新程序的路径。 arg0, arg1, ..., argn 是新程序的命令行参数,最后一个参数必须是空指针 (char *) NULL,表示参数列表的结束。
execl 函数会取代当前进程的内存映像,加载并执行指定路径的新程序,新程序的参数由 arg0 到 argn 指定。执行 execl 后,当前进程的代码、数据、堆栈等内容都被替换为新程序的内容,新程序开始执行。如果 execl 函数执行成功,它不会返回;如果发生错误,返回值为 -1,并且设置全局变量 errno 表示具体的错误类型。
这个函数在创建子进程后常用于在子进程中执行其他程序,因为在子进程中执行 execl 后,子进程的内容就被替换为新程序的内容,从而达到执行其他程序的效果。
事例
我们可以用“语言”调用其他程序 [BCH@hcss-ecs-6176 11_7_1]$ cat test.c #include<stdio.h> #include<unistd.h> int main() {printf("pid:%d,excel command begin\n",getpid());execl("/usr/bin/ls","ls","-a","-l",NULL);//execl调用的ls程序printf("pid:%d,excel command end\n",getpid());return 0; }[BCH@hcss-ecs-6176 11_7_1]$ ./test pid:4066,excel command begin 总用量 24 drwxrwxr-x 2 BCH BCH 4096 11月 10 18:05 . drwx------ 26 BCH BCH 4096 11月 10 18:04 .. -rwxrwxr-x 1 BCH BCH 8464 11月 10 18:05 test -rw-rw-r-- 1 BCH BCH 208 11月 10 18:04 test.c
替换的底层原理
证明程序替换并没有创建新进程
[BCH@hcss-ecs-6176 11_7]$ cat myprocess.c #include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> int main() {pid_t id=fork();if(id==0){//childprintf("pid:%d,excel command begin\n",getpid());sleep(3);execl("/usr/bin/ls","ls","-a","-l",NULL);printf("pid:%d,excel command end\n",getpid());}else{//fatherpid_t rid=waitpid(-1,NULL,0);if(rid>0){printf("wait success,rid:%d\n",rid);}}return 0; }[BCH@hcss-ecs-6176 11_7]$ ./myprocess pid:11527,excel command begin 总用量 56 drwxrwxr-x 2 BCH BCH 4096 11月 10 18:08 . drwx------ 26 BCH BCH 4096 11月 10 18:04 .. -rw-rw-r-- 1 BCH BCH 1928 11月 10 16:30 1 -rw-rw-r-- 1 BCH BCH 74 11月 9 22:13 Makefile -rwxrwxr-x 1 BCH BCH 8624 11月 10 17:59 myprocess -rw-rw-r-- 1 BCH BCH 2672 11月 10 17:59 myprocess.c -rw-r--r-- 1 BCH BCH 12288 11月 10 18:08 .myprocess.c.swo -rw-r--r-- 1 BCH BCH 12288 11月 8 23:05 .myprocess.c.swp wait success,rid:11527pid和rid的值一样,可以说明,execl的时候没有创建新的进程
当父进程创建子进程,子进程发生了替换的时候,这里发生的写时拷贝,写时拷贝的同时不仅要拷贝数据还要拷贝代码,这样子进程发生程序替换的时候,不会影响父进程
子进程怎么知道,要从新的程序的最开始执行?
他怎么知道最开始的地方在哪里呢?
Eip寄存器:虽然cpu中只有一个eip寄存器,但是可以存储多组数据,也就是每一个进程都都会有一组数据
当替换进来的程序,eip会找到entry,可执行程序的入口地址
注意:当通过execl程序替换成功了,则后续代码没有机会再执行了!因为被替换掉了!
如果替换失败,会有一个返回值-1,替换成功,不会返回
#include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> int main() {pid_t id=fork();if(id==0){//childprintf("pid:%d,excel command begin\n",getpid());sleep(3);int n=execl("/usr/bin/lsss","lsss","-a","-l",NULL);//execl程序替换错误时,n接收返回值printf("pid:%d,excel: n=%d command end\n",getpid(),n);}else{//fatherpid_t rid=waitpid(-1,NULL,0);if(rid>0){printf("wait success,rid:%d\n",rid);}}return 0; }[BCH@hcss-ecs-6176 11_7]$ ./myprocess pid:29364,excel command begin pid:29364,excel: n=-1 command end wait success,rid:29364
execlp
execlp 是一个系统调用,与 execl 类似,用于在当前进程中执行一个新的程序,但它可以搜索 PATH 环境变量指定的路径来寻找可执行文件。它的原型如下:
#include <unistd.h> int execlp(const char *file, const char *arg0, const char *arg1, ..., const char *argn, (char *) NULL); file 参数是要执行的新程序的文件名或路径。如果 file 中不包含斜杠 /,则 execlp 函数会在 PATH 环境变量指定的路径中搜索与 file 匹配的可执行文件。 arg0, arg1, ..., argn 是新程序的命令行参数,最后一个参数必须是空指针 (char *) NULL,表示参数列表的结束。
与 execl 类似,execlp 函数也会取代当前进程的内存映像,加载并执行指定路径的新程序,新程序的参数由 arg0 到 argn 指定。执行 execlp 后,当前进程的代码、数据、堆栈等内容都被替换为新程序的内容,新程序开始执行。如果 execlp 函数执行成功,它不会返回;如果发生错误,返回值为 -1,并且设置全局变量 errno 表示具体的错误类型。
execlp 常用于在当前进程中执行其他程序,且无需指定程序的绝对路径,只需指定程序的名称即可,因为它会在 PATH 环境变量指定的路径中搜索可执行文件。
事例
[BCH@hcss-ecs-6176 11_7]$ cat myprocess.c #include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> int main() {pid_t id=fork();if(id==0){//childprintf("pid:%d,excel command begin\n",getpid());sleep(3);//execl("/usr/bin/ls","ls","-a","-l",NULL);execlp("ls","ls","-a","-l",NULL);//第一个ls是文件名,第二个ls是命令行调用ls指令printf("pid:%d,excel command end\n",getpid());}else{//fatherpid_t rid=waitpid(-1,NULL,0);if(rid>0){printf("wait success,rid:%d\n",rid);}}return 0; }[BCH@hcss-ecs-6176 11_7]$ ./myprocess pid:13889,excel command begin 总用量 56 drwxrwxr-x 2 BCH BCH 4096 11月 11 00:05 . drwx------ 26 BCH BCH 4096 11月 11 00:05 .. -rw-rw-r-- 1 BCH BCH 1928 11月 10 16:30 1 -rw-rw-r-- 1 BCH BCH 74 11月 9 22:13 Makefile -rwxrwxr-x 1 BCH BCH 8624 11月 11 00:05 myprocess -rw-rw-r-- 1 BCH BCH 2710 11月 11 00:05 myprocess.c -rw-r--r-- 1 BCH BCH 12288 11月 10 23:44 .myprocess.c.swo -rw-r--r-- 1 BCH BCH 12288 11月 8 23:05 .myprocess.c.swp wait success,rid:13889
execv
execv 是一个系统调用,用于在当前进程中执行一个新的程序。它的原型如下:
#include <unistd.h> int execv(const char *path, char *const argv[]); path 参数是要执行的新程序的路径。 argv 是一个以 NULL 结尾的字符串数组,其中第一个元素是要执行的新程序的名称,后续元素是新程序的命令行参数。
execv 函数会取代当前进程的内存映像,加载并执行指定路径的新程序,新程序的参数由 argv 指定。执行 execv 后,当前进程的代码、数据、堆栈等内容都被替换为新程序的内容,新程序开始执行。
如果 execv 函数执行成功,它不会返回;如果发生错误,返回值为 -1,并且设置全局变量 errno 表示具体的错误类型。
execv 与 execl 和 execlp 的不同之处在于它接受一个字符串数组作为参数,而不是逐个列出参数。这使得在运行时动态构建参数列表更为方便。
事例
[BCH@hcss-ecs-6176 11_7]$ cat myprocess.c #include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> int main() {pid_t id=fork();if(id==0){//child//char* const argv[]={"ls","-a","-l",NULL};printf("pid:%d,excel command begin\n",getpid());sleep(3);//execl("/usr/bin/ls","ls","-a","-l",NULL);//execlp("ls","ls","-a","-l",NULL);execv("/usr/bin/ls",argv);printf("pid:%d,excel command end\n",getpid());}else{//fatherpid_t rid=waitpid(-1,NULL,0);if(rid>0){printf("wait success,rid:%d\n",rid);}}return 0; }[BCH@hcss-ecs-6176 11_7]$ ./myprocess pid:1798,excel command begin 总用量 56 drwxrwxr-x 2 BCH BCH 4096 11月 11 00:15 . drwx------ 26 BCH BCH 4096 11月 11 00:15 .. -rw-rw-r-- 1 BCH BCH 1928 11月 10 16:30 1 -rw-rw-r-- 1 BCH BCH 74 11月 9 22:13 Makefile -rwxrwxr-x 1 BCH BCH 8624 11月 11 00:15 myprocess -rw-rw-r-- 1 BCH BCH 2824 11月 11 00:15 myprocess.c -rw-r--r-- 1 BCH BCH 12288 11月 10 23:44 .myprocess.c.swo -rw-r--r-- 1 BCH BCH 12288 11月 8 23:05 .myprocess.c.swp wait success,rid:1798
execvp
execvp 是一个系统调用,与 execv 类似,用于在当前进程中执行一个新的程序,但它可以搜索 PATH 环境变量指定的路径来寻找可执行文件。它的原型如下:
#include <unistd.h> int execvp(const char *file, char *const argv[]); file 参数是要执行的新程序的文件名或路径。如果 file 中不包含斜杠 /,则 execvp 函数会在 PATH 环境变量指定的路径中搜索与 file 匹配的可执行文件。 argv 是一个以 NULL 结尾的字符串数组,其中第一个元素是要执行的新程序的名称,后续元素是新程序的命令行参数。
与 execv 类似,execvp 函数也会取代当前进程的内存映像,加载并执行指定路径的新程序,新程序的参数由 argv 指定。执行 execvp 后,当前进程的代码、数据、堆栈等内容都被替换为新程序的内容,新程序开始执行。如果 execvp 函数执行成功,它不会返回;如果发生错误,返回值为 -1,并且设置全局变量 errno 表示具体的错误类型。
execvp 常用于在当前进程中执行其他程序,且无需指定程序的绝对路径,只需指定程序的名称即可,因为它会在 PATH 环境变量指定的路径中搜索可执行文件。
替换c++程序
我们用自己写的c语言程序,去程序替换成我们自己编写的C++程序
[BCH@hcss-ecs-6176 11_7]$ cat test.cc #include<iostream> using namespace std; int main() {cout<<"hello c++"<<endl;cout<<"hello c++"<<endl;cout<<"hello c++"<<endl;cout<<"hello c++"<<endl;return 0; }[BCH@hcss-ecs-6176 11_7]$ cat myprocess.c #include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> int main() {pid_t id=fork();if(id==0){//childprintf("pid:%d,excel command begin\n",getpid());sleep(3);excel("./mytest" , "mytest",NULL);printf("pid:%d,excel command end\n",getpid());}else{//fatherpid_t rid=waitpid(-1,NULL,0);if(rid>0){printf("wait success,rid:%d\n",rid);}}return 0; }[BCH@hcss-ecs-6176 11_7]$ ./myprocess pid:32180,excel command begin hello c++ hello c++ hello c++ hello c++ wait success,rid:32180
替换shell语言程序
test.sh是shell下面的脚本语言
[BCH@hcss-ecs-6176 11_7]$ cat test.sh #!/usr/bin/bash //开头必须以#!,后面跟上系统指令或者自己写的程序路径echo "hello world" touch file1 file2 file3 echo "hello done"命令行运行的时候 1)bash test.sh2)也可以chmod +x test.sh ./test.sh
我们用自己写的c语言程序,去程序替换成我们自己编写的shell脚本程序
[BCH@hcss-ecs-6176 11_7]$ cat test.sh #!/usr/bin/bash //开头必须以#!,后面跟上系统指令或者自己写的程序路径echo "hello world" touch file1 file2 file3 echo "hello done"[BCH@hcss-ecs-6176 11_7]$ cat myprocess.c #include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> int main() {pid_t id=fork();if(id==0){//childprintf("pid:%d,excel command begin\n",getpid());sleep(3);excel("/usr/bin/bash" , "bash", "test.sh",NULL);printf("pid:%d,excel command end\n",getpid());}else{//fatherpid_t rid=waitpid(-1,NULL,0);if(rid>0){printf("wait success,rid:%d\n",rid);}}return 0; }[BCH@hcss-ecs-6176 11_7]$ ./myprocess pid:31838,excel command begin hello world//脚本语言运行成功 hello done wait success,rid:31838
exec族函数为什么可以替换不同语言的程序?
exce*(*是通配符)
不管用什么语言编写的程序(C/C++、bash、java、python等),只要在os中都可以进行进程替换
exec
系列函数可以替换不同语言的程序,是因为它们是操作系统级别的系统调用,与编程语言无关。这些函数在操作系统层面实现了程序的加载和执行,不依赖于特定的编程语言。当调用
exec
系列函数时,操作系统会负责加载指定的可执行文件,并在当前进程的上下文中执行该文件。这意味着,无论是用 C、Python、Java 还是其他编程语言编写的程序,只要它们是可执行文件,并符合操作系统的执行要求,就可以被exec
函数加载和执行。因此,
exec
系列函数是跨语言的,可以用于替换任何可执行文件,而不仅仅局限于特定语言的程序。这也使得在一个编程环境中,通过调用exec
函数,可以方便地与其他编程语言的程序进行交互和整合。当一个程序运行的整个过程
一个程序被运行(形成进程),首先第一步,是要创建内核数据结构(pcb、页表等),然后第二步,将程序的代码和数据加载到内存中,页表再一一映射
有时,有些程序没有被运行时,只需要创建内核数据结构(pcb、页表等),页表没有映射物理内存
🌸🌸🌸如果大家还有不懂或者建议都可以发在评论区,我们共同探讨,共同学习,共同进步。谢谢大家! 🌸🌸🌸