曾经被面试过一个问题就是“从终端输入一个命令到显示出结果,中间的过程是怎样的”,这个问题大致要从终端、shell的定位以及linux进程树方面来回答。
有关shell的原理,可以从下面的代码看出来(摘自APUE)
#include<sys/wait.h>
#include "apue.h"
int main(void){
char buf[MAXLINE];
pid_t pid;
int status;
printf("%% ");
while(fgets(buf,MAXLINE,stdin)!=NULL){
if (buf[strlen(buf)-1] == '\n'){
buf[strlen(buf)-1] = 0;
}
if((pid = fork())<0){
err_sys("fork error");
}else if(pid == 0){
printf("%s",buf);
execlp(buf,buf,(char*)0);
err_ret("couldn't execute: %s",buf);
exit(127);
}
/*parent*/
if ((pid=waitpid(pid,&status,0))<0){
err_sys("waitpid error");
}
printf("%% ");
}
exit(0);
}
首先fgets读取一行命令,然后fork出一个新进程来执行命令,自身等待子进程执行完毕后返回。下面说一些细节。
1. fgets当读到ctrl+D(文件结束符)时候返回NULL
2. linux的新进程产生(spawn)分为了创建和替换进程映像两部分,分别由fork和execlp完成。
fork执行之后会返回两次,一次给调用fork的主进程,返回子进程的pid,一次给子进程,返回0. 写程序的时候就可以根据这一点写各自的逻辑了。
3. execlp原型是int execlp(const char * file,const char * arg,...,(char *)0); 其中file是执行的命令,从第二个形参开始就是命令所需要的参数,从args[0]开始,所以第二个参数程序中也是buf。execlp找命令结尾是'\0'不是'\n',因此buf需要在结尾处做一下调整。execlp执行成功之后就替换了子进程的程序文件,因此后面的err_ret也不会再执行了,如果没有执行成功,就会接着执行,最后exit(127)退出。
常见错误码如下:
1: Catchall for general errors
2: Misuse of shell builtins (according to Bash documentation)
126: Command invoked cannot execute
127: "command not found"
128: Invalid argument to exit
128+n: Fatal error signal "n"
如果要让本程序能接收命令参数,还需要解析buf。不过由于参数个数是不固定的,我们可以使用execlp同源的另外几个函数:
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[]);
带l的表示参数是变长的,最后以(char*)0结尾,不带l的可以用数组的形式传参。
带p的会从系统PATH中搜素命令,不带p的则需要给出绝对路径。