#include "csapp.h"
#define MAXARGS 128
/* function prototypes */
void eval(char *cmdline);
int parseline(char *buf, char **argv);
int builtin_command(char **argv);
int main()
{
char cmdline[MAXLINE]; /* Command line */
while (1) {
/* Read */
printf("> ");
fgets(cmdline, MAXLINE, stdin);
if (feof(stdin))
exit(0);
/* Evaluate */
eval(cmdline);
}
}
/* $end shellmain */
/* $begin eval */
/* eval - Evaluate a command line */
void eval(char *cmdline)
{
char *argv[MAXARGS]; /* Argument list execve() */
char buf[MAXLINE]; /* Holds modified command line */
int bg; /* Should the job run in bg or fg? */
pid_t pid; /* Process id */
strcpy(buf, cmdline);
bg = parseline(buf, argv);
/*printf("%d\n",bg);*/
if (argv[0] == NULL)
return; /* Ignore empty lines */
if (!builtin_command(argv)) {
if ((pid = fork()) == 0) { /* Child runs user job */
if (execve(argv[0], argv, environ) < 0) {
printf("%s: Command not found.\n", argv[0]);
printf("fork error: %s\n",strerror(errno));
exit(0);
}
}
/* Parent waits for foreground job to terminate */
if (!bg) {
int status;
if (waitpid(pid, &status, 0) < 0)
/*unix_error("waitfg: waitpid error");*/
fprintf(stderr,"fork error: %s\n",strerror(errno));
exit(0);
}
else
printf("%d %s", pid, cmdline);
}
return;
}
/* If first arg is a builtin command, run it and return true */
int builtin_command(char **argv)
{
if (!strcmp(argv[0], "quit")) /* quit command */
exit(0);
if (!strcmp(argv[0], "&")) /* Ignore singleton & */
return 1;
return 0; /* Not a builtin command */
}
/* $end eval */
/* $begin parseline */
/* parseline - Parse the command line and build the argv array */
int parseline(char *buf, char **argv)
{
char *delim; /* Points to first space delimiter */
int argc; /* Number of args */
int bg; /* Background job? */
buf[strlen(buf)-1] = ' '; /* Replace trailing '\n' with space */
while (*buf && (*buf == ' ')) /* Ignore leading spaces */
buf++;
/* Build the argv list */
argc = 0;
while ((delim = strchr(buf, ' '))) {
argv[argc++] = buf;
*delim = '\0';
buf = delim + 1;
while (*buf && (*buf == ' ')) /* Ignore spaces */
buf++;
}
argv[argc] = NULL;
if (argc == 0) /* Ignore blank line */
return 1;
/* Should the job run in the background? */
if ((bg = (*argv[argc-1] == '&')) != 0)
argv[--argc] = NULL;
return bg;
}
/* $end parseline */
上面的代码是书中提到的通过fork和execve实现的一个简单的外壳。运行情况如下:
Fighting!hust_smartcar@:~/work/cs_code /try$./shellex
> hello sanwu
Hello sanwu!
Fighting!hust_smartcar@:~/work/cs_code /try$./shellex
> quit
其中quit是内置命令。对于内置命令,外壳进程会马上解释这个命令。对于可执行文件,如hello,外壳父进程会在一个新的子进程的上下文中加载并运行这个文件。
运行可执行目标文件p,因为p不是一个内置的外壳命令,所以外壳会通过调用某个驻留在存储器中称为加载器(loader)的操作系统代码运行p。加载器可以通过execve函数进行调用。加载器将可执行目标文件中的代码和数据从磁盘中拷贝到存储器中,然后通过跳转到程序的第一条指令或者入口点来运行程序。将程序拷贝到存储器并运行的过程叫做加载。
在32位的linux系统中,代码段从0x08048000处开始。存储器映像如上图所示。其中用户栈在运行时创建,总是从合法用户地址开始,向下增长(向低存储器地址方向增长)。从栈的上部开始的段是为操作系统驻留的存储器的部分(内核)的代码和数据保留的。
用户栈的组织结构如下如所示:
当加载器运行时,创建存储器映像,在可执行文件中段头部表的指导下,加载器将可执行文件的相关内容拷贝到代码段和数据段,接下来,加载器跳转到程序的入口点,也就是字符_start的地址,在_start地址处的启动代码是在目标文件ctrl.o中定义的,对所有的C程序都一样。
0x080480c0 <_start> /*Entry point in .text*/
call _ _libc_init_first /*Startup code in .text*/
call _init /*Startup code in .init*/
call atexit /*Startup code in .text*/
call main /*Application main routine*/
call _exit /*Returns control to OS*/
(注:上面没有显示将每个函数的参数压栈的代码)
在.text和.init节中调用了初始化里称号,启动代码调用atexit例程,这个程序附加了一系列在应用程序正常中止时应该调用的程序。exit函数运行atexit注册的函数,然后通过_exit将控制返回给操作系统。接着,启动代码调用应用程序的main程序,开始执行我们的C代码。应用程序返回之后,启动代码调用_exit程序,将控制返回给操作系统。