進程的程序替換與shell的實現

  在Linux系統中,⽤用fork創建⼦子進程後執⾏行的是和⽗父進程相同的程序(但有可能執⾏行不同的代碼分⽀支),⼦進程往往 要調⽤用⼀一種exec函數以執⾏行另⼀一個程序。當進程調⽤用⼀一種exec函數時,該進程的⽤用戶空間代碼和數 據完全被新程序替換,從新程序的啓動例程開始執⾏行。調⽤用exec並不創建新進程,所以調⽤用exec前後該進程的id並未改變。

Linux操作系統中的shell就是運用這個原理處理客戶請求的,不是每個請求都是shell親力親爲的,所以shell會創建子程序替換他,在實現shell的過程中我們會用到exec函數,所以我們先了解一下exec函數族並對其每個的用法用代碼實現一遍。

其實有六種以exec開頭的函數,統稱exec函數:

#include <unistd.h>

int execl(const char *path, const char *arg, ...);

       參數:路徑,操作,以NULL結尾

int execlp(const char *file, const char *arg, ...);

       參數:文件名,操作,NULL

int execle(const char *path, const char *arg, ..., char *const envp[]);

       參數:路徑,操作,環境變量

int execv(const char *path, char *const argv[]);

       參數:路徑,argv

int execvp(const char *file, char *const argv[]);

       參數:文件名,argv[]

int execve(const char *path, char *const argv[], char *const envp[]);

     最標準的系統調用,參數:路徑,argv,環境變量

這些函數如果調⽤用成功則加載新的程序從啓動代碼開始執⾏行,不再返回,如果調⽤用出錯則返回-1, 所以exec函數只有出錯的返回值⽽而沒有成功的返回值。exec函數族的特點是誰調用他他就替換誰,只要exec函數調用成功,後續代碼全部失效。

這些函數原型看起來很容易混,但只要掌握了規律就很好記。

不帶字母p (表⽰示path)exec函數 第⼀一個參數必須是程序的相對路徑或絕對路徑,例如

"/bin/ls""./a.out",⽽而不能 是"ls""a.out"。對於帶字母p的函數: 如果參數中包含/,

將其視爲路徑名。 否則視爲不帶路徑的程序名,PATH環境變量的⽬目錄列表中搜索這

個程序。

帶有字母l( 表⽰示list)exec函數要求將新程序的每個命令⾏行參數都當作⼀一個參數傳給

,命令⾏行 參數的個數是可變的,因此函數原型中有...,...中的最後⼀一個可變參數應該是

NULL, sentinel的作⽤用。

帶有字母v( 表⽰示vector)的函數,則應該先構造⼀一個指向各參數的指針數 組,然後將該數

組的⾸首地址當作參數傳給它,數組中的最後⼀一個指針也應該是NULL,就像main函數 的

argv參數或者環境變量表⼀一樣。

對於以e (表⽰示environment)結尾的exec函數,可以把⼀一份新的環境變量表傳給它,其他

exec函數 仍使⽤用當前的環境變量表執⾏行新程序。

下面我們用代碼驗證exec函數族:

源代碼:

Makefile的編寫:

.PHONY:all

all:other myexec

other:other.c

gcc -o other other.c

myexec:myexec.c

gcc -o myexec myexec.c

.PHONY:clean

clean:

rm -f myexec other

代碼實現:

Myexec.c

#include<stdio.h>

#include<stdlib.h>

#include<unistd.h>

int main()

{

    //創建子進程

pid_t id=fork();

if(id<0)

{

printf("new process is faild\n");

return 1;

}

else if(id==0)

{

//child

printf("I am a process\n");

sleep(1);

char* const myenv[]={"MYPATH=aa/bb/cc/dd/hello/world",NULL};

//char* const myargv[]={"ls","-l","-a",NULL};

//execl("/bin/ls","ls","-l","-i","-n","-a",NULL);

//execv("/bin/ls",myargv);

//execlp("ls","ls","-l","-i","-n","-a",NULL);

//execvp("ls",myargv);

execle("./other","other",NULL,myenv);

//替換失敗,退出碼爲2

exit(2);

}

else

{

//father

pid_t ret=waitpid(id,NULL,0);

int status=0;

if(ret>0)

{

            //打印退出碼,獲取時儘量用宏,不要用移位

printf("wait success,exet code:%d\n",WEXITSTATUS(status));

}

else{

printf("wait failed\n");

return 3;

}

}

return 0;

}

Other.c

#include<stdio.h>

#include<unistd.h>

#include<stdlib.h>

int main()

{

printf("I am another proc,I am running,MYPATH : %s\n",getenv("MYPATH"));

return 0;

}

exec函數族熟悉了之後,爲了實現shell,我們還必須瞭解一個函數就是read,對read函數的返回值一定要理解:

Read函數一共有三種返回值

read的返回值大於0時,表示讀取成功了並且讀取成功的數值小於等於sizeofbuf-1

read的返回值等於0時,表示讀取的文件已經讀到了文件尾。

read的返回值小於0時,表示讀取出錯。

對以上知識點掌握之後,我們就來實現自己的shell

源碼:

Makefile的實現:

 

.PHONY:myshell

myshell:myshell.c

gcc -o myshell myshell.c

.PHONY:clean

clean:

rm -f myshell

Shell.c

#include<stdio.h>

#include<unistd.h>

#include<stdlib.h>

#include<sys/types.h>

#include<sys/wait.h>

int main()

{

char cmd[128];

    while(1)

{

    //打印命令行的提示符(包括用戶名,主機名等)

printf("[test@my-host-name myshell]#");

    fflush(stdout);

ssize_t _s=read(0,cmd,sizeof(cmd)-1);

if(_s>0)

{

        //把最後一個賦爲\0

cmd[_s-1]='\0';

}

else

{

perror("read");

return 1;

}

char * _argv[32];

_argv[0]=cmd;

int i=1;

char *start=cmd;

while(*start)

{

        //把用戶輸入的命令中的空格用\0代替

if(isspace(*start))

{

          *start='\0';

start++;

_argv[i]=start;

i++;

}

else

start++;

}

_argv[i]=NULL;

pid_t id=fork();

if(id<0)

{

perror("fork");

}

else if(id==0)

{

//child

        //程序替換

execvp(_argv[0],_argv);

exit(1);

}

else

{

//father

int status=0;

pid_t ret=waitpid(id,&status,0);

if(ret>0&&WIFEXITED(status))

{}

else

{

perror("wait");

}

}

}

return 0;

}

運行結果:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章