【進程控制二】進程替換
- 1.exec系列接口
- 2.execl系列
- 2.1execl接口
- 2.2execlp接口
- 2.3execle
- 3.execv系列
- 3.1execv
- 3.2總結
- 4.實現一個bash解釋器
- 4.1內建命令
通過fork創建的子進程,會繼承父進程的代碼和數據,因此本質上還是在執行父進程的代碼
進程替換可以將別的進程的代碼替換到自己的代碼區,讓自己去執行別人的代碼
進程替換是通過exec系列系統調用接口實現的
1.exec系列接口
先看看man手冊中的exec接口:
這些接口健壯度很高,就算錯誤地使用了接口,結果也不容易出錯
2.execl系列
execl隸屬于exec系列,加上l代表list,表示參數采用列表
2.1execl接口
int execl(const char *pathname, const char *arg, ...);
pathname
:指定用于替換的進程的路徑arg
:以何種方式運行進程...
:以何種方式運行該進程NULL
:當參數列表list結束,必須以NULL結尾- 返回值:如果調用成功,該函數不會返回,因為當前進程的映像被替換
我們現在要替換ls指令到自己的進程中,ls指令在/usr/bin/ls中
我們希望以ls -l -a的形式來調用這個進程,因此我們的三個參數 “ls”, “-l”, "-a"就是這個指令拆分出來的三個字符串
最后以NULL結尾
#include<unistd.h>
#include<stdio.h>int main()
{printf("程序替換前\n");execl("/usr/bin/ls", "ls", "-l", "-a", NULL);//執行ls -l并替代當前進程printf("程序替換后\n"); return 0;
}
輸出結果:
我們成功在當前進程中替換成了ls指令,并以ls -l -a的形式調用
但沒有打印“程序替換后”
,因為進程替換是用別的進程的代碼區覆蓋掉自己原先的代碼區,所以execl一旦執行,整個進程的代碼都被替換了,那么printf(“程序替換后\n”);
就會被覆蓋掉,最后不輸出
2.2execlp接口
int execlp(const char* file, const char* arg, ... );
file
:指定替換的進程名稱(不用指明路徑,會自動去環境變量PATH指定的路徑中查找)arg
:以何種方式運行進程...
: 運行該進程的選項- 最后以
NULL
結尾 - 返回值:如果調用成功,該函數不會返回,因為當前進程的映像被替換
int main()
{ printf("程序替換前\n"); execlp("ls","-ls","-l","-a",NULL); printf("程序替換后\n"); return 0;
}
2.3execle
int execle(const char *pathname, const char *arg, ... ,char *const envp[] );
pathname
:指定用于替換的進程的路徑arg
:以何種方式運行進程...
:以何種方式運行該進程NULL
:當參數列表list結束,必須以NULL結尾- envp:指針數組存儲環境變量,用于設置新程序的環境變量,數組必須以 NULL 結束
- 返回值:如果調用成功,該函數不會返回,因為當前進程的映像被替換
int main()
{ const char* _env[] = {"My_env = 666666666666666666666",NULL};printf("程序替換前\n"); execlp("/usr/bin/ls","-ls","-l","-a",NULL,_env); printf("程序替換后\n"); return 0;
}
execle可以給替換后的進程指定環境變量表
3.execv系列
v就是vector,以數組的形式,把選項都存在數組中,將整個數組傳入
3.1execv
int execv(const char *pathname, char *const argv[]):
pathname
:指定用于替換的進程的路徑argv
:指定以何種方式調用進程,將這些選項存儲在一個數組中
int main()
{ char* set[] = {"ls","-a","-l",NULL};printf("程序替換前\n"); execv("/usr/bin/ls",set); printf("程序替換后\n"); return 0;
}
將我們要執行程序的方法用數組存起來再把數組傳過去
3.2總結
其他接口就不一一演示了
健壯度演示:
int main()
{ char* set[] = {"ls","-a","-l",NULL};printf("程序替換前\n"); execvp("/usr/bin/ls",set); //自動查找可執行文件并執行,但我們主動傳遞了文件路徑也不會出錯printf("程序替換后\n"); return 0;
}
雖然使用的是execvp,但我們主動傳遞了文件路徑也不會出錯
4.實現一個bash解釋器
接下來要把字符串以空格為分割進行打散,strtok函數可以幫助我們實現
代碼如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>#define NUM 1024//輸入命令行字符串
#define SIZE 64 //打散后的命令行字符串
#define SEP " " //字符串分隔符int lastcode = 0;//上個進程的退出碼const char* getUsername()
{const char* name = getenv("USER");if(name) return name;else return "none";
}const char* getHostname()
{const char* hostname = getenv("HOSTNAME");if(hostname) return hostname;else return "none";
}const char* getCwd()
{const char* cwd = getenv("PWD");if(cwd) return cwd;else return "none";
}int GetUserCommand(char* command,int num)
{printf("[%s@%s %s]#",getUsername(),getHostname(),getCwd());fgets(command,num,stdin);//在fgets()函數的眼里,換行符’\n’也是它要讀取的一個普通字符而已。在讀取鍵盤輸入的時候會把最后輸入的回車符也存進數組里面,即會把’\n’也存進數組里面command[strlen(command) - 1] = '\0';//將輸入的\n清除掉return strlen(command);
}void CommandSplit(char* in,char* out[])
{int argc = 0;out[argc++] = strtok(in,SEP);while(out[argc++] = strtok(NULL,SEP));
}int execute(char* argv[])//執行命令
{pid_t id = fork();if(id < 0) return -1;else if(id == 0)//child process{execvp(argv[0],argv);//程序替換}else//father process{int status = 0;pid_t rid = waitpid(id,&status,0);if(rid > 0){lastcode = WEXITSTATUS(status);//刷新退出碼}}return 0;
}int main()
{ while(1){char UserCommand[NUM];//用于保存即將輸入的命令行字符串char* argv[SIZE];//保存將會被打散的字符串//GetUserCommand(UserCommand,sizeof(UserCommand));//打印提示符&&獲取用戶命令字符串CommandSplit(UserCommand,argv);//分割字符串execute(argv);//執行命令}return 0;
}
4.1內建命令
我們實現bash后,可能會遇見一個問題:cd指令進入某個文件夾似乎沒用
因為指令cd是進入某個文件夾,而進入此文件夾當然是由當前的父進程進入
如果由子進程去執行,由于寫時拷貝的原因父進程并不會進去
對于像cd這樣的指令我們稱為內建命令,也就是不能讓子進程來完成的命令,只能父進程親自執行
我們需要主動添加內建命令的判斷
char cwd[1024];//父進程要進入的文件路徑char* homepath()
{char* home = getenv("HOME");if(home) return home;else return (char*)".";
}
void cd(const char* path)
{chdir(path);//切換當前的工作目錄char tmp[1024];getcwd(tmp,sizeof(tmp));sprintf(cwd,"PWD=%s",tmp);putenv(cwd);
}
int doBuildin(char* argv[])
{if(strcmp(argv[0], "cd") == 0){char *path = NULL;if(argv[1] == NULL) path = homepath();else path = argv[1];cd(path);return 1;}return 0;
}
內建命令不止cd,像export,kill和history等等也是內建命令
完整代碼
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>#define NUM 1024//輸入命令行字符串
#define SIZE 64 //打散后的命令行字符串
#define SEP " " //字符串分隔符int lastcode = 0;//上個進程的退出碼
char cwd[1024];//父進程要進入的文件路徑const char* getUsername()
{const char* name = getenv("USER");if(name) return name;else return "none";
}const char* getHostname()
{const char* hostname = getenv("HOSTNAME");if(hostname) return hostname;else return "none";
}const char* getCwd()
{const char* cwd = getenv("PWD");if(cwd) return cwd;else return "none";
}int GetUserCommand(char* command,int num)
{printf("[%s@%s %s]#",getUsername(),getHostname(),getCwd());fgets(command,num,stdin);//在fgets()函數的眼里,換行符’\n’也是它要讀取的一個普通字符而已。在讀取鍵盤輸入的時候會把最后輸入的回車符也存進數組里面,即會把’\n’也存進數組里面command[strlen(command) - 1] = '\0';//將輸入的\n清除掉return strlen(command);
}void CommandSplit(char* in,char* out[])
{int argc = 0;out[argc++] = strtok(in,SEP);while(out[argc++] = strtok(NULL,SEP));
}char* homepath()
{char* home = getenv("HOME");if(home) return home;else return (char*)".";
}
void cd(const char* path)
{chdir(path);//切換當前的工作目錄char tmp[1024];getcwd(tmp,sizeof(tmp));sprintf(cwd,"PWD=%s",tmp);putenv(cwd);
}
int doBuildin(char* argv[])
{if(strcmp(argv[0], "cd") == 0){char *path = NULL;if(argv[1] == NULL) path = homepath();else path = argv[1];cd(path);return 1;}return 0;
}int execute(char* argv[])//執行命令
{pid_t id = fork();if(id < 0) return -1;else if(id == 0)//child process{execvp(argv[0],argv);//程序替換}else//father process{int status = 0;pid_t rid = waitpid(id,&status,0);if(rid > 0){lastcode = WEXITSTATUS(status);//刷新退出碼}}return 0;
}int main()
{ while(1){char UserCommand[NUM];//用于保存即將輸入的命令行字符串char* argv[SIZE];//保存將會被打散的字符串//GetUserCommand(UserCommand,sizeof(UserCommand));//打印提示符&&獲取用戶命令字符串CommandSplit(UserCommand,argv);//分割字符串int n = doBuildin(argv);//判斷是否是內建命令并執行if(n) continue;execute(argv);//執行命令}return 0;
}