本節重點
- 理解進程替換的相關概念與原理
- 掌握相關程序替換接口
- 程序替換與進程創建的區別
- 程序替換的注意事項
一、概念與原理
進程程序替換是操作系統中實現多任務和資源復用的關鍵機制,允許進程在運行時動態加載并執行新程序。
1.1 定義
進程程序替換是指用新程序的代碼、數據和堆棧完全替換當前進程的地址空間(加載新程序到內存、更新頁表映射、初始化虛擬地址空間,并將進程控制塊(PCB)指向新程序),使進程執行新程序的邏輯,而進程ID(PID)保持不變。
1.2 與進程創建的區別
- 進程創建(fork):創建新進程,并分配PID
- 進程程序替換:不創建新進程,更改原程序的代碼與數據
二、實現方法 exec函數(6+1)
2.1 語言封裝(6)
2.1.1 execl
函數原型:
int execl(const char *pathname, const char *arg, ...);
參數:
pathname:新程序的路徑+文件名
arg:傳遞給新程序的參數
代碼示例:
#include<iostream>
#include<cstdio>
#include<unistd.h>
int main()
{printf("進程替換之前!\n");int ret=execl("/bin/ls","ls","-a","-l",NULL);//進程替換成功后后續代碼不會執行//只有進程替換出錯后才會執行后續代碼,并設置錯誤碼(void)ret;printf("進程替換之后!\n");return 0;
}
運行結果:
2.1.2 execlp
int execlp(const char *file, const char *arg, ...);
?參數解析:
file:新程序的程序名
arg:傳遞給新程序的參數/執行的方法
代碼示例:
#include<iostream>
#include<cstdio>
#include<unistd.h>int main()
{printf("進程替換之前!:\n");int ret=execlp("ls","ls","-a","-l",NULL);(void)ret;if(ret==-1)printf("execlp fail!\n");printf("進程替換之后!:\n");return 0;
}
運行結果:
2.1.3 execle
int execle(const char *pathname, const char *arg, char *const envp[]);
?參數解析:?
pathname:新程序的路徑+文件名
arg:傳遞給新程序的參數/執行新程序的方法
envp:傳遞給新程序的環境變量表(以NULL結尾)
代碼示例:
code.cc
#include<iostream>
#include<cstdio>
#include<unistd.h>int main()
{printf("進程替換之前!:\n");char* const env[]={(char* const)"other=12345",(char* const)"n1=45612",(char* const)"n2=56784",(char* const)"n3=12034",(char* const)"n4=yuejianhua",(char* const)"n5=jinzhiqi",NULL,};int ret=execle("./text","text",NULL,env);if(ret==-1)printf("execlp fail!\n");printf("進程替換之后!:\n");return 0;
}
text.cc?
#include<iostream>
#include<cstdio>
#include<unistd.h>int main(int argc,char* argv[],const char* env[])
{//打印環境變量表:int i=0;while(env[i]){std::cout<<env[i]<<std::endl;i++;}i=0;//打印命令行參數列表:while(argv[i]){std::cout<<argv[i]<<std::endl;i++;} return 0;
}
運行結果:
2.1.4 execv
int execv(const char *pathname, char *const argv[]);
?參數解析:?
pathname:新程序的路徑+文件名
argv:傳遞給新程序的命令行參數列表(以NULL結尾)
代碼示例:
#include<iostream>
#include<cstdio>
#include<unistd.h>int main()
{printf("進程替換之前!:\n");char* const argv[]={(char* const)"ls",(char* const)"-a",(char* const)"-l",NULL,};int ret=execv("/bin/ls",argv);if(ret==-1)printf("execlp fail!\n");printf("進程替換之后!:\n");return 0;
}
運行結果:
2.1.5 execvp
int execvp(const char *file, char *const argv[]);
?參數解析:?
file:新程序的文件名
argv:傳遞給新程序的命令行參數表(以NULL結尾)
代碼示例:
code.cc
#include<iostream>
#include<cstdio>
#include<unistd.h>int main()
{printf("進程替換之前!:\n");char* const argv[]={(char* const)"yuejianhua",(char* const)"jinzhiqi",NULL,};int ret=execvp("./text",argv);if(ret==-1)printf("execlp fail!\n");printf("進程替換之后!:\n");return 0;
}
text.cc
#include<iostream>
#include<cstdio>
#include<unistd.h>int main(int argc,char* argv[],char* env[])
{//打印環境變量表:int i=0;//打印命令行參數列表:while(argv[i]){std::cout<<argv[i]<<std::endl;i++;} return 0;
}
?運行結果:
2.1.6 execvpe
int execvpe(const char *file, char *const argv[],char *const envp[]);
?參數解析:?
file:新程序的函數名
argv:傳遞給新程序的命令行參數列表(以NULL結尾)
envp:傳遞給新程序的環境變量表(以NULL結尾)
code.cc
#include<iostream>
#include<cstdio>
#include<unistd.h>int main()
{printf("進程替換之前!:\n");char* const argv[]={(char* const)"yuejianhua",(char* const)"jinzhiqi",NULL,};char* const env[]={(char* const)"other=12345",(char* const)"n1=45612",(char* const)"n2=56784",(char* const)"n3=12034",NULL,};int ret=execvpe("./text",argv,env);if(ret==-1)printf("execlp fail!\n");printf("進程替換之后!:\n");return 0;
}
text.cc
#include<iostream>
#include<cstdio>
#include<unistd.h>int main(int argc,char* argv[],char* env[])
{//打印環境變量表:int i=0;while(env[i]){std::cout<<env[i]<<std::endl;i++;} i=0;//打印命令行參數列表:while(argv[i]){std::cout<<argv[i]<<std::endl;i++;} return 0;
}
運行結果:
2.2 系統調用(1)
2.2.1 execve
int execve(const char *pathname, char *const argv[],char *const envp[]);
參數解析:?
pathname:新程序的路徑+文件名
argv:傳遞給新程序的命令行參數表(以NULL結尾)
envp:傳遞給新程序的環境變量表(以NULL結尾)
代碼示例:
code.cc
#include<cstdio>
#include<iostream>
#include<unistd.h>
int main()
{std::cout<<"這是進程替換之前"<<std::endl;//傳遞自己的命令行參數與環境變量表char* const argv[]={(char* const)"yuejianhua",(char* const)"jinzhiqi",NULL,};char* const env[]={(char* const)"n1=12345",(char* const)"n2=45678",(char* const)"n3=lut",NULL,};int ret=execve("./text",argv,env);//替換成功后續代碼不會執行if(ret<0){std::cout<<"進程替換失敗!"<<std::endl;}std::cout<<"進程替換之后"<<std::endl;return 0;
}
text.cc
#include<cstdio>
#include<iostream>int main(int argc,char* argv[],char* env[])
{int i=0;while(argv[i]){std::cout<<argv[i]<<std::endl;i++;}i=0;while(env[i]){std::cout<<env[i]<<std::endl;i++;}return 0;
}
?運行結果:
2.3 總結?
函數 | 參數傳遞方式 | 環境變量 | 路徑搜索 | 示例調用 |
---|---|---|---|---|
execl?? | 可變參數列? ? ? ? ? ? ?表? ? ? | 繼承? | 需完整路? ? ? ? ? ? ?徑 | execl("/bin/ls", "ls", "-l", NULL) |
execlp | 可變參數列表 | 繼承 | 支持?PATH | execlp("ls", "ls", "-l", NULL) |
execle | 可變參數列表 | 顯式傳遞 | 需完整路徑 | execle("/bin/ls", "ls", "-l", NULL, envp) |
execv | 參數數組 | 繼承 | 需完整路徑 | char *argv[] = {"ls", "-l", NULL}; execv("/bin/ls", argv); |
execvp | 參數數組 | 繼承 | 支持?PATH | execvp("ls", argv); |
execve | 參數數組 | 顯式傳遞 | 需完整路徑 | execve("/bin/ls", argv, envp); |
?知識點:
進程程序替換所關聯的exec族函數都有一個顯著特征就是exec+參數傳遞的方式,每個字母代表特定的傳參方法,以下是關于這一類型的總結:
- l (list)? ? ? :表示給新進程傳參需要一個個傳
- p(PATH):表示索引新進程可以只傳遞文件名,但是要是自己的代碼文件必須指明路徑
- v(vector):表示給新進程傳參可以直接使用自定義命令行參數列表
- e(env)? ? :表示可以給新進程傳遞自定義環境變量表
這里需要注意的是命令行參數列表與環境變量表必須都以NULL結尾。?
exec族函數在底層都封裝了系統調用execve。
當在進程替換的時候顯式地給新進程傳遞環境變量表時傳遞的環境變量表會覆蓋默認的環境變量表,可以參考execle的代碼演示。
三、進程替換的用途
在后期我們可以通過fork+exec機制創建子進程利用進程替換機制使子進程執行定義好的代碼文件。
如:Shell執行命令(簡易版):
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>int main() {pid_t pid = fork();if (pid == 0) {// 子進程:替換為 ls 命令char *argv[] = {"ls", "-l", NULL};execvp("ls", argv); // 自動搜索 PATHperror("execvp failed");exit(1);} else if (pid > 0) {// 父進程:等待子進程結束wait(NULL);printf("Command executed.\n");} else {perror("fork failed");}return 0;
}
fork+exec機制使用后要注意資源回收的問題,使用進程等待的方式回收或使用信signal(SIGCHLD, SIG_IGN)
?自動回收(需謹慎)。