目錄
相關博客:
進程相關概念: 戳鏈接( ̄︶ ̄)↗https://blog.csdn.net/qq_41071068/article/details/103213364
進程創建(fork):戳鏈接( ̄︶ ̄)↗https://blog.csdn.net/qq_41071068/article/details/103302804
進程退出:戳鏈接( ̄︶ ̄)↗https://blog.csdn.net/qq_41071068/article/details/103302874
進程等待(wait()/waitpid()):戳鏈接( ̄︶ ̄)↗https://blog.csdn.net/qq_41071068/article/details/103302883
進程程序替換
爲什麼要進行進程替換 ?
前面說到, fork()創建子進程, fork創建的子進程要麼和父進程執行一樣的代碼, 要麼執行不同的代碼分支(通過fork的返回值控制),
但這樣還是不夠靈活. 假如要有很多的功能已經用別的程序實現好了, 那麼就不需要在父進程中控制父子進程執行不同的代碼分
支, 讓子進程在自己的分支中完成這些功能, 而是可以直接拿一個已有的程序替換掉子進程. 使子進程的代碼完全變成所替換程序
的代碼. 這樣就方便了很多, 而且在子進程需要完成較爲複雜的功能或是多項功能時, 分支就顯得力不從心了.
所以進程往往要調用exec族中的某一個函數來進行程序替換, 讓一個進程來執行另一個程序. 當進程調用一種exec函數時,該進程
的用戶空間代碼和數據完全被新程序替換,從新程序的啓動例程開始執行。調用exec族中的函數並不會創建新進程,所以調用exec
前後被替換進程的iD並未改變。
進程替換的原理
如上圖, 進程替換時, 替換的是PCB映射在內存中的代碼和數據. 這樣, 該進程PID雖然沒有變, 但已經物是人非, 已經不是原來的那
個進程了.
如進行進程程序替換? 前面也說到了, exec族函數
替換函數exec族函數
exec族函數共有六個, 功能都是進程程序替換, 但多個不同的函數接口使得使用更加靈活.
其中exceve()是系統調用接口, 其餘5個底層都封裝了execve().
函數原型如下 :
int execl(const char *path, const char *arg0, ... /*, (char *)0 */);
int execv(const char *path, char *const argv[]);
int execle(const char *path, const char *arg0, ... /*,(char *)0, char *const envp[]*/);
int execve(const char *path, char *const argv[], char *const envp[]);
int execlp(const char *file, const char *arg0, ... /*, (char *)0 */);
int execvp(const char *file, char *const argv[]);
頭文件: unistd.h
返回值 :
- 這六個函數返回值相同.
- 當函數調用失敗, 返回 -1.
- 當調用成功, 即加載新的程序, 替換後的進程啓動開始執行,exec族函數不再返回 .
- 特殊的地方是, 函數調用成功, 不返回
參數:
雖然有六個, 不好記, 但好在有規律. 可以發現, 函數名都是在exce的基礎上, 加上l, v, e, p形成新的函數名, 加哪個字母都有各自的
含義 , 如下所示:
- l / v 必須有一個, 也只能有一個, 帶l參數格式是列表, 帶 v 參數格式是 數組
- p 有p, 會自動搜索環境變量PATH, 則可以不帶路徑, 只要文件名(但文件必須放在PATH環境變量指定路徑). 沒有p, 則必須指定文件路徑
- e 有e, 則不使用當前環境變量, 需要自己設置環境變量, 沒帶e, 則使用當前環境變量, 無需設置環境變量
如下表:
函數名 | 參數格式 | 函數是否自帶路徑(通過PATH) | 是否使用當前環境變量 |
---|---|---|---|
execl | 列表 | 不帶, 需要制定文件路徑 | 使用 |
execlp | 列表 | 帶, 但文件必須的放在指定目錄 | 使用 |
execle | 列表 | 不帶, 需要制定文件路徑 | 不使用, 需要自己設置環境變量 |
execv | 數組 | 不帶, 需要制定文件路徑 | 使用 |
execvp | 數組 | 帶, 但文件必須的放在指定目錄 | 使用 |
execve | 數組 | 不帶, 需要制定文件路徑 | 不使用, 需要自己設置環境變量 |
來看一下這六個函數具體如何使用.
1. execv(參數格式是數組)
#include<stdio.h>
#include<unistd.h>
int main() {
char* arg[] = {"ls","-a","-l","/",NULL };//參數數組;//參數數組
execv("/bin/ls", arg);
printf("hello world!\n");
return 0;
}
可以看到進程被ls程序替換後, 只會執行ls的代碼, 並不會再輸出 hello world!
如下, 還可以通過main函數的參數傳入, 我們用Shell調用ls等命令就是這個原理.
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(int argc, char* argv[]){
char* arg[] = {"ls","-a","-l",NULL };//參數數組
pid_t pid = fork();
if(pid == -1){
perror("fork");
}
else if(pid == 0){
printf("替換子進程\n");
execv("/bin/ls", argv);
}
else{
sleep(1);
printf("替換父進程\n");
execv("/bin/ls", arg);
}
printf("hello world!\n");
return 0;
}
2.execl(參數格式是列表)
#include<stdio.h>
#include<unistd.h>
int main(){
execl("/bin/ls", "ls", "/", NULL);
printf("hello world!\n");
return 0;
}
3.execvp / execlp(不帶替換程序的路徑)
execvp
#include<stdio.h>
#include<unistd.h>
int main(int argc, char* argv[]){
execvp("ls", arg);
printf("hello world!\n");
return 0;
}
execlp
#include<stdio.h>
#include<unistd.h>
int main(){
char* arg[] = {"ls","-a","-l",NULL };//參數數組
execlp("ls", "-a", "-l", NULL);
printf("hello world!\n");
return 0;
}
4.execle / execve(需要自己設置環境變量)
execve
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(int argc, char* argv[]){
char* envp[] = {"PATH=/home/test", NULL};
execve("/bin/env", argv, envp);
return 0;
}
execle
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){
char* envp[] = {"PATH=/home/test", NULL};
execle("/bin/env", "", NULL, envp);
return 0;
}
學習完進程的創建和替換後, 就可以利用這些知識, 寫一個自己的Shell了
進程的創建在另一篇博客, 戳鏈接( ̄︶ ̄)↗https://blog.csdn.net/qq_41071068/article/details/103302804
具體代碼, 持續更新