进程的程序替换与shell的实现

来源:互联网 发布:淘宝大学是不是骗局 编辑:程序博客网 时间:2024/06/05 18:08

  在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;

}

运行结果:

 

2 0
原创粉丝点击