fork之後就會創建則進程,數據、堆、棧有兩份,代碼仍然爲一份但是這個代碼段成爲兩個進程的共享代碼段都從fork函數中返回,當父子進程有一個想要修改數據或者堆棧時,兩個進程真正分裂子進程在創建的時候會複製父進程的代碼。
不管是共用代碼還是複製代碼,這就相當於父進程創建了一個和自己功能完全相同的進程,這樣一來,子進程就只能進行和父進程一樣的操作動作,這樣做實際意義並不大,那怎麼讓子進程乾和父進程不一樣的事情?
程序替換:子程序通常調用exec函數,替換掉該進程的用戶空間代碼和數據,從新程序的啓動歷程開始執行,exec函數並不是創建進程,所以exec之後,進程的id並未改變
exec系列函數
需要頭文件 #include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
l(list):表示採用列表
v(vector):參數用數組
p(path):自動搜索環境變量
e(env):自己維護的環境變量
集體操作看下面代碼
#include<stdlib.h>
#include<unistd.h>
int main()
{
char* const argv[] = {"ps", "-ef", NULL};
char* const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
execl("/bin/ps", "ps", "-ef", NULL);
//帶p可以使用環境變量PATH,無需路徑
execlp("ps", "ps", "-ef", NULL);
//帶e需要自己組裝環境變量
execle("ps", "ps", "-ef", NULL, envp);
execv("/bin/ps", argv);
execvp("ps", argv);
execve("/bin/ps", argv, envp);
exit(0);
}
這個程序只需要執行一個上述 exec 函數,就可將這段代碼生成的可執行程序編程ps -ef 這條命令,上述代碼只有一個進程,換成兩個進程,基本操作也是一樣的,如下面代碼,實現最簡shell:
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
char *strs[128] = {};
int argc = 0;
void stok(char *buf, const char *ch)
{
if(buf == NULL)
{
return;
}
char *token = strtok(buf, ch);
strs[argc] = token;
while((token = strtok(NULL, ch)) != NULL)
{
argc++;
strs[argc] =token;
}
strs[argc+1] = NULL;
}
int main()
{
char buf[256] = {};
while(1)
{
pid_t pid = fork();
if(pid == 0)
{
printf("myshell#");
scanf("%[^\n]%*c", buf);
stok(buf, " "); //打斷buf字符串
execvp(strs[0], strs);
}
else
{
int status = 0;
pid_t id = waitpid(pid,&status, 0);
if(id < 0)
{
printf("wait runing error!\n");
}
}
}
return 0;
}
在這段代碼中,子程序的代碼被替換,父進程調用waitpid阻塞式等待子進程返回,子進程返回之後回收子進程的資源,while循環中使用fork,創建子進程,然後在子進程中讀取鍵盤上的輸入,並將讀進的字符在串在空格處打斷,並在每個子串的最後加上\0,讓strs數組中的每個指針都依次指向這些子串,完成之後在strs最後一個元素的後面加上一個空指針,這樣做的是爲了滿足execvp的格式要求,然後調用execvp進行程序替換,替換完完畢之後子程序退出,父進程會受到子進程的資源,然後進入下一次循環。
scanf("%[^\n]%*c", buf);
使用snanf是最後的回車也會被輸入,上面這句代碼的意思是:當敲下回車時,這個回車會被屏蔽掉,也可寫爲:
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]=0;