《深入理解计算机系统》第八章 (二)程序的加载与运行

原创 2013年12月03日 09:42:52
/* $begin shellmain */
#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程序,将控制返回给操作系统。



Linux操作系统分析(2)- 进程的创建与可执行程序的加载

学号:sa×××310 姓名:××涛 环境:Ubuntu13.04  gcc4.7.3 1.进程管理        Linux中的进程主要由kernel来管理。系统调用是应用程序与内核交互的一种...
  • qp120291570
  • qp120291570
  • 2013年05月26日 20:13
  • 2693

《深入理解计算机系统》第八章 (四)信号

信号是软件层面上的异常 (一)Linux信号 可以通过man 7 signal 查看Linux提供的标准信号。其中描述的信号的编号在不同的处理器体系结构的差异有所不同。 (二)信号传送的步骤 传送一个...
  • sanwu2010
  • sanwu2010
  • 2013年12月04日 17:24
  • 556

《深入理解计算机系统》笔记(三)链接知识【附图】

概述         ●该章节主要讲解的是ELF文件的结构。             ●静态库的概念         ●动态库(又叫共享库)的概念,一般用于操作系统,普通应用程序作用不大。    ...
  • hherima
  • hherima
  • 2013年05月23日 16:19
  • 3606

《深入理解计算机系统-CSAPP》练习题笔记(一)

《深入理解计算机系统-CSAPP》练习题笔记(一)
  • niaolianjiulin
  • niaolianjiulin
  • 2016年07月28日 17:54
  • 1854

手动构造完全与`char *argv[]` 等价的参数

场景在main函数中的两个参数(int argc , char *argv[]),都是系统构造的。通常来说,我们只需要去解析即可,不需要去构造这样一个参数。然而,今天写代码时却不得不构造这样一个参数。...
  • x5942110
  • x5942110
  • 2016年05月08日 15:52
  • 1266

《深入理解计算机系统》笔记(二)内存和高速缓存的原理【插图】

《深入计算机系统》笔记(一)主要是讲解程序的构成、执行和控制。接下来就是运行了。我跳过了“处理器体系结构”和“优化程序性能”,这两章的笔记继续往后延迟!     《深入计算机系统》的一个很大的用处是...
  • hherima
  • hherima
  • 2013年05月17日 16:50
  • 5153

int main(int argc,char* argv[])详解,以及与int main()有什么区别

K&R C 上5.10 命令行参数 中介绍的int main(int argc,char* argv[ ]) 在 d:\ 编写 程序,并命名为 c.c   #include int ma...
  • hopeneversleep
  • hopeneversleep
  • 2017年02月19日 09:15
  • 2781

深入理解计算机系统第二章家庭作业答案(2.58-2.67)

2.58 bool is_little_endian() { unsigned int x = 1; return *((unsigned char*)&x); } 2.59 ...
  • phx_storm
  • phx_storm
  • 2014年07月15日 17:03
  • 1095

《深入理解计算机系统》并发编程——读书笔记

现在操作系统提供了三种基本的构造并发程序的方法: 1、进程。每个逻辑控制流都是一个进程,由内核来调度和维护; 2、I/O多路复用。 3、线程。 一、基于进程的并发编程                 ...
  • zhanghaodx082
  • zhanghaodx082
  • 2013年10月04日 15:10
  • 1328

《深入理解计算机系统》--并发编程

这也是一本很出名的书,在很早的时候读过一些,这次从后面开始读,看有没有新的体会。     如果逻辑流在时间上重叠,那么他们就是并发的,硬件异常处理程序、进程和UNIX信号处理程序都是熟悉的例子。并发...
  • yusiguyuan
  • yusiguyuan
  • 2013年10月13日 14:09
  • 2112
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:《深入理解计算机系统》第八章 (二)程序的加载与运行
举报原因:
原因补充:

(最多只允许输入30个字)