🐶博主主页:@ᰔᩚ. 一怀明月ꦿ
❤️🔥专栏系列:线性代数,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) { //fateher int 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) { //fateher int status=0;//记录子进程退出的退出码和信号 pid_t rid=waitpid(id,&status,WNOHANG);//通过waitpid系统调用去等待子进程,采用WNOHANG非阻塞的方式等待 if(rid>0) { //wait success, child quit now printf("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 quit printf("father do other thing......\n"); executeTask(tasks,TASK_NUM);//也可以在内部进行自己移除&&新增对应的任务 printf("##############################################################################################\n"); } else { //wait failed, child unknow printf("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) { //child printf("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 { //father pid_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:11527 pid和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) { //child printf("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 { //father pid_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) { //child printf("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 { //father pid_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 { //father pid_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) { //child printf("pid:%d,excel command begin\n",getpid()); sleep(3); excel("./mytest" , "mytest",NULL); printf("pid:%d,excel command end\n",getpid()); } else { //father pid_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.sh 2)也可以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) { //child printf("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 { //father pid_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、页表等),页表没有映射物理内存
🌸🌸🌸如果大家还有不懂或者建议都可以发在评论区,我们共同探讨,共同学习,共同进步。谢谢大家! 🌸🌸🌸