💓博主CSDN主頁:杭電碼農-NEO💓
?
?專欄分類:Linux從入門到精通?
?
🚚代碼倉庫:NEO的學習日記🚚
?
🌹關注我🫵帶你學更多操作系統知識
? 🔝🔝
進程程序替換
- 1. 前言
- 2. exec系列函數的認識
- 3. execl系列函數
- 4. execv系列函數
- 5. 程序替換的使用場景
- 6. 自我實現一個bash解釋器
- 7. 內建命令的特殊性
- 8. 總結以及拓展
1. 前言
本篇文章是進程控制的最后一篇文章
有時我們遇見這種場景:子進程被創建
出來后并不想執行父進程的代碼,而是
想去執行其他程序的代碼來完成任務,
于是在這種場景下,程序替換顯得很重要!
本章重點:
本篇文章著重講解進程程序替換
的exec系列函數的用法(一共六個),
并且自主實現一個bash解釋器.
最后拓展如何使用C調用其他語言的程序
2. exec系列函數的認識
在fork之后如果子進程想要執行一個
全新的程序,就需要用到此系列函數!
這里一共有六個函數,但是它們都有
一定的規律,并不難記憶!它們都是失敗
返回-1首先先介紹一個最簡單的函數:
execl
它的參數分別代表:要執行的程序的路徑
以及名字,和如何執行此程序
先使用再解釋:
int main()
{ printf("我要進行程序替換了...\n"); int n = execl("/usr/bin/ls","ls","-a","-l",NULL); if(n==-1) { perror("execl"); } printf("程序替換完畢!\n"); return 0;
}
現象:
可以發現,在打印完:我要進行程序替換
后,就去執行了ls程序了,并且執行完后
并沒有打印"程序替換完成"!
表面execl后,會將當前進程的代碼和數據
進行替換,包括還沒執行的代碼!
3. execl系列函數
首先,execl隸屬于exec系列,加上l,
l就是list,相當于要把執行的程序的路徑
給列舉出來,execl系列中還有
execlp和execle,現在來講解這兩個
execlp函數解析:
int main()
{ printf("我要進行程序替換了...\n"); int n = execlp("ls","-l",NULL); if(n==-1) { perror("execl"); } printf("程序替換完畢!\n"); return 0;
}
可以發現,使用execlp函數即使不加上
路徑也可以找到程序,并且允許它,但是
這是為什么呢?帶上p,p也就是PATH
環境變量,所以系統會去環境變量PATH
中找路勁,若找到就直接執行它!
execle函數解析:
int main()
{ const char* _env[]={"MY_ENV=666",NULL}; printf("我要進行程序替換了...\n"); int n = execle("/usr/bin/ls","ls","-l",NULL,_env);//自己定義一個環境變量MY_ENV=666傳遞給要去執行的程序 if(n==-1) { perror("execl"); } printf("程序替換完畢!\n"); return 0;
}
execle可以在執行其他程序前,
傳入自己定義的環境變量,方便后續
程序的執行!e也就是env的簡寫
4. execv系列函數
execv系列函數即為將l換成了v,
v就是vector,數組,也就是利用
數組來傳參
execv函數解析:
int main() { char* const set[]={"ls","-a","-l",NULL}; printf("我要進行程序替換了...\n"); int n = execv("/usr/bin/ls",set); if(n==-1) { perror("execl"); } printf("程序替換完畢!\n"); return 0; }
將我們要執行程序的方法用數組存起來
如何再把數組傳過去!后面的execvp和
execvpe函數也就很好理解了,加上p無非
就是去環境變量PATH中找路徑,加上e也
就是給要去執行的程序傳入環境變量,僅此而已!
5. 程序替換的使用場景
其實一般情況下,程序替換都不是將
自己替換掉,而是創建子進程去替換,
讓子進程去干活,而父進程當"監工"
在這種場景下,我們可以很自然的想到
bash解釋器的工作原理可能就是創建
子進程去執行任務,而bash父進程本身
就需要獲取指令,并傳達命令即可!
首先,bash解釋器一定是一個while
死循環,因為它會不斷給我們打印信息:
當然這個消息你可以自定義處理
int main()
{while(1){//打印提示信息printf("[kwy@localhost myshell]# ");fflush(stdout);......}return 0;
}
我們先把bash的整體結構分析一下,
然后在一步一步的實現它:
-
首先我們需要定義兩個數組A和B
A用來存放用戶輸入的所有字符串
B用來存放以空格打散后的字符串 -
第二步,獲取用戶輸入的字符串后,
將字符串以空格為分割打散 -
第三步,創建子進程使用exec系列
函數去執行用戶輸入的指令
而bash本身充當監工的角色等待子進程死亡
6. 自我實現一個bash解釋器
首先先創建兩個數組備用
然后再接收用戶的輸入
#define NUM 1000
#define SIZE 16
char cmd_line[NUM];//保存完整的命令行字符串
char* my_argv[SIZE];//保存打散后的字符串
if(fgets(cmd_line,sizeof cmd_line,stdin)==NULL)//用fgets將標準輸入輸入到數組中continue;
cmd_line[strlen(cmd_line)-1] = '\0';//將輸入的換行符給清除掉
接下來就是將字符串以空格為分割打散了
在C語言的學習時有strtok函數可以幫助
我們解決這個問題,它的功能如下:
這里默認大家知道這個函數的用法了
所以我直接將分割字符串的代碼寫出來:
//命令行字符串解析:以空格為分割打散
my_argv[0]=strtok(cmd_line," ");//提出第一部分
int index=1;
while(my_argv[index++] = strtok(NULL," "));//第二次調用strtok時若還想解析第一次調用的字符串,則傳NULL
這段代碼寫完后,字符串就已經被我們
分割成了幾個小字符串了,比如用戶輸入
“ls -a -l"就轉換成了"ls”,“-a”,"-l"了,接下來
只需創建子進程完成任務即可!
//shell運行原理:通過子進程執行命令,父進程等待&&解析命令
//命令行解釋器是一個常駐程序
#define NUM 1000
#define SIZE 16
char cmd_line[NUM];//保存完整的命令行字符串
char* my_argv[SIZE];//保存打散后的字符串int main()
{while(1){//打印提示信息printf("[kwy@localhost myshell]# ");fflush(stdout);memset(cmd_line,'\0',sizeof cmd_line);//獲取用戶的鍵盤輸入if(fgets(cmd_line,sizeof cmd_line,stdin)==NULL)continue;cmd_line[strlen(cmd_line)-1] = '\0';//將輸入的換行符給清除掉//命令行字符串解析:以空格為分割打散my_argv[0]=strtok(cmd_line," ");//提出第一部分int index=1;while(my_argv[index++] = strtok(NULL," "));//第二次調用strtok時若還想解析第一次調用的字符串,則傳NULL//fork后子進程去完成任務pid_t id=fork();if(id == 0)//子進程{printf("下面的功能讓子進程執行\n");//當執行cd等命令時,改變的是子進程的路徑,而父進程的路徑沒變execvp(my_argv[0],my_argv);exit(1);//執行失敗就返回1}//父進程的代碼,當監工int status = 0;pid_t ret = waitpid(-1,&status,0);if(ret>0) printf("exit code: %d\n",WEXITSTATUS(status));}return 0;
}
關于代碼的解釋都在注釋中
如果你還有哪個地方不懂,歡迎私信
7. 內建命令的特殊性
在實現bash時,可能會遇見一個問題:
就是cd指令進入某個文件夾似乎沒用
這一點其實很好理解,因為指令cd是
進入某個文件夾,而進入此文件夾當然
是當前進程進入了,如果創建了子進程
去進去文件夾,由于寫時拷貝的原因,父
進程并不會進去,所以對于像cd這樣的
指令我們稱為內建命令,也就是不能讓
子進程來完成的命令,只能父進程親自動手!
if(strcmp(my_argv[0],"cd")==0)
{if(my_argv[1]!=NULL)chdir(my_argv[1]);continue;//直接跳到while(1)處
}
chdir即為切換當前的工作目錄
內建命令不止cd,像export,kill
和history等等也是內建命令!
8. 總結以及拓展
進程程序替換可以幫助我們完成很多
任務,制作一個簡易的bash解釋器只是
眾多應用中的一個,隨著我們學習的深入
你還會發現新大陸!
對于程序替換的拓展:
在Linux下,C語言程序不僅可以替換成
C語言程序去執行,還可以替換成python
或Java甚至是bash等程序去執行它們
語言的代碼:
比如python腳本:
#! /usr/bin/python3.6
print("hello Python/n")
運行命令: python test.py
將進程替換為Python程序:execlp("python", "python", "test.py", NULL);
這樣就可以直接在C程序上執行python代碼了!