CSAPP hello‘s life

计算机系统

大作业

题     目  程序人生-Hellos P2P  

专       业  计算机科学与技术        

学     号  202111xxxx            

班   级  21xxxxx            

学       生  xxx     

指 导 教 师  xxxx            

计算机科学与技术学院

202211

摘  要

    本论文逐步分析hello程序从原始程序文件.c开始,经过预处理、编译、汇编、链接阶段生成可执行目标文件,以及该可执行目标文件是如何在shell中运行的过程,对于一个程序的一生作出较为详细且通俗的论述。其他论述内容有预处理器、编译器、汇编器、链接器在各阶段所作的工作以及之间的协同配合;计算机系统中几个重要抽象概念的具体体现;软硬件是如何配合以实现进程的并发执行和不同层次异常的处理情况等。

关键词:预处理;编译;汇编;链接;进程管理;

目  录

第1章 概述

1.1 Hello简介

1.2 环境与工具

1.3 中间结果

1.4 本章小结

第2章 预处理

2.1 预处理的概念与作用

2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析

2.4 本章小结

第3章 编译

3.1 编译的概念与作用

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析

3.4 本章小结

第4章 汇编

4.1 汇编的概念与作用

4.2 在Ubuntu下汇编的命令

4.3 可重定位目标elf格式

4.4 Hello.o的结果解析

4.5 本章小结

第5章 链接

5.1 链接的概念与作用

5.2 在Ubuntu下链接的命令

5.3 可执行目标文件hello的格式

5.4 hello的虚拟地址空间

5.5 链接的重定位过程分析

5.6 hello的执行流程

5.7 Hello的动态链接分析

5.8 本章小结

第6章 hello进程管理

6.1 进程的概念与作用

6.2 简述壳Shell-bash的作用与处理流程

6.3 Hello的fork进程创建过程

6.4 Hello的execve过程

6.5 Hello的进程执行

6.6 hello的异常与信号处理

6.7本章小结

第7章 hello的存储管理

7.1 hello的存储器地址空间

7.2 Intel逻辑地址到线性地址的变换-段式管理

7.3 Hello的线性地址到物理地址的变换-页式管理

7.4 TLB与四级页表支持下的VA到PA的变换

7.5 三级Cache支持下的物理内存访问

7.6 hello进程fork时的内存映射

7.7 hello进程execve时的内存映射

7.8 缺页故障与缺页中断处理

7.9动态存储分配管理

7.10本章小结

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

8.2 简述Unix IO接口及其函数

8.3 printf的实现分析

8.4 getchar的实现分析

8.5本章小结

结论

附件

参考文献


第1章 概述

1.1 Hello简介

P2P(From program to process):Hello的P2P指的是从program到process。Program指的是我们认为编写程序的起点,即根据不同高级语言的语法规则,从键盘敲入代码形成的原始文件,在本论文中指hello.c文件,然而,hello.c文件将经过预处理、编译、汇编、链接的阶段后才可转化为可执行目标文件,这个可执行目标文件还不是最终的process,只有通过fork函数创建子进程,才可能在子进程(process)的上下文中加载并最终运行这一可执行目标文件。

图1 从源文件到目标文件的转化过程

020(From zero to zero): Hello的一生从0到0,具体指的是Hello程序运行在进程上下文中时,有自己的虚拟地址空间。当子进程通过execve系统同调用启动加载器时,加载器会创建一组新的代码、数据、堆和栈段,新的栈和堆段被初始化为0,而通过虚拟地址空间中的页映射到可执行文件大小的页大小的片,新的代码和数据段被初始化为可执行文件的内容。事实上,直到CPU引用一个被映射的虚拟页时才会进行复制,而当程序停止运行后,OS会将子进程回收,进程从此不再存在。因此,Hello的一生从0开始而又以0结束。

1.2 环境与工具

硬件环境:AMD RYZEN 7-5000-64

软件环境:VMware, Codeblocks,Visual Studio 2019

开发与调试工具:gcc; vim/gedit; edb; objdump

1.3 中间结果

hello.i 预处理的结果——修改了的源程序

hello.s 编译的结果——汇编程序

hello.o 汇编的结果——可重定位目标文件

hello 链接的结果——可执行目标文件

aso.txt  hello.o的反汇编,用于与第3章的 hello.s进行对照分析。

ase.txt  hello的反汇编,用于与hello.o进行对照分析

1.4 本章小结

   为了详细分析程序的一生中经历的各阶段:预处理、编译、汇编、链接中具体发生了什么样的变化以及计算机系统如何依靠软硬件和操作系统的配合最终能够运行此程序,使用vim/objdump/edb等调试工具对hello进行处理生成多个中间文件以便分析,更好地了解程序的一生。

(第1章0.5分)


第2章 预处理

2.1 预处理的概念与作用

(1)概念:C语言中的预处理指在正式编译之前对源程序进行一些修改,相当于编译前的准备阶段。

(2)作用:预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。C语言编译器的预处理可能会出现以下情况:

  • 如果源程序以#include包括了头文件,cpp需要读取系统头文件的内容并将其直接插入程序文本中;
  • 如果源程序存在#define,cpp用实际值替换define定义的字符串;
  • 如果源程序存在#if,那么根据其后面的条件来决定需要编译的代码。

2.2在Ubuntu下预处理的命令

预处理命令:cpp hello.c > hello.i ; 预处理结果:生成hello.i文件

2.3 Hello的预处理结果解析

hello.i的文件内容如下(由于文件内容篇幅过大,在此只展示部分内容):

可以看到hello.c源文件中主函数部分完整地保留在hello.i文件中,但是在主函数部分的代码之前插入许多如外部全局变量、函数名等,这是cpp所做的上述第一种情况的工作,即将#include后包含的stdio.h, unistd.h, stdlib.h的内容插入到程序文本中。

2.4 本章小结

    Hello.c经过预处理阶段后,比原始程序文本增加了头文件的诸多内容,为之后的编译阶段做好准备。(第2章0.5分)


第3章 编译

3.1 编译的概念与作用

(1) 概念:编译的过程是指编译器(ccl)将文本文件hello.i翻译成hello.s文本文件,hello.s中包含一个汇编语言程序。

(2)作用:将原始文本文件翻译成汇编语言程序,其中每条语句都以一种文本的格式(汇编语言)描述了一条低级机器语言指令,这里的汇编语言为不同高级语言的不同编译器提供了通用的输出语言。编译后的结果更便于汇编器的翻译。     

3.2 在Ubuntu下编译的命令

编译命令:gcc -S hello.c -o hello.s

3.3 Hello的编译结果解析

注:相同类型的操作仅举出1例进行说明

3.3.1 常量

hello.s里常量内容显示如下:

(1) 编译器将hello.c中的printf()输出的字符串常量“用法: Hello 学号 姓名 秒数!\n”放在.LC0中,并且使用3位数字编码汉字(UTF-8编码);

(2) 编译器把printf()输出格式字符串“Hello %s %s”放在.LC1中;

3.3.2局部变量

       //将i和进行比较

编译器在栈帧中分配空间用于保存局部变量,在本例中int i 就被保存在距离栈帧指针%rbp偏移量为4的空间中;编译器通过对%rsp减去一定的值来为局部变量和其他参数留出空间。

3.3.3赋值操作

编译器使用mov、leaq指令进行赋值操作如下(相似操作只举出1例不多赘述):

//使用立即数为局部变量i赋初值

   //将常量字符串赋给puts函数的第一个参数

//将%rax指向的内存中字符串赋给%rax

//将一个寄存器里的值赋值给另一个寄存器

3.3.4算术操作

编译器使用add进行算数加法:

//对i进行+1操作

//使用sub作减法操作

3.3.5关系操作

编译器通过cmp来进行比较操作:

 //将argv和常量(立即数)4进行比较

3.3.6 指针操作

编译器通过把指针保存在寄存器中,然后通过一系列mov指令对指针进行具体操作:

以上就是通过多条mov指令来把指针数组argv[i]指向的字符串赋值给不同的寄存器作为函数调用时的参数。例如,将argv[2](保存在-16(%rbp))和argv[1](保存在-24(%rbp)) 指向的字符串分别作为printf()函数的第三个和第二个参数传给相对应的寄存器里。

3.3.7 控制转移

本文件中编译器通过条件跳转指令来实现控制的转移:

以上编译器通过跳转指令实现for循环;其中-4(%rbp)保存循环变量i的值,movl指令为i赋初值,addl对其进行+1操作,cmpl和jle指令保证了当i<=8时继续循环体(.L4),若i>8, 则执行下一条指令即调用getchar()。

3.3.7 函数操作

(1)参数传递:编译器通过指定寄存器进行参数传递:

已知源程序中printf("Hello %s %s\n",argv[1],argv[2]); 编译器将格式字符串传给了%rdi, 将argv[1]指向的字符串传给了%rsi,将argv[2]指向的字符串传给了%rdx

通过分析上述printf()各参数的传递情况可知:第一个参数用%rdi,第二个参数用%rsi,第三个参数用%rdx。

(2)函数调用:编译器通过call指令调用函数

(3)函数返回:编译器指定%rax保存返回值

3.4 本章小结

在编译阶段,编译器做了许多工作,包括但不限于对C语言各操作的汇编指令的转换,为局部变量分配栈帧,用跳转指令实现控制结构、为每个函数调用作传参准备等,为之后的汇编阶段提供诸多便利。

(第3章2分)


第4章 汇编

4.1 汇编的概念与作用

(1)概念:汇编是指把汇编语言翻译成机器语言的过程。

(2)作用:汇编器将hello.s文件翻译成机器指令,并打包成可重定位目标文件,汇编器产生重定位条目便于链接器进行重定位。

4.2 在Ubuntu下汇编的命令

汇编指令:as hello.s -o hello.o

4.3 可重定位目标elf格式

(1) hello.o的ELF格式:使用readelf -a hello.o命令产生的hello.o的ELF格式主要包括有:ELF Header、Section Headers、Relocation section、和Symbol table。针对hello.o来说,没有section groups、program headers、dynamic section和version information。此外,unwind sections由于设备不支持不予显示。

(2) elf各节具体内容如下:

  • ELF头

图2 可重定位目标文件elf格式ELF头

其中包括了字节顺序、ELF头的大小、目标文件的类型、机器类型、节表的偏移以及节头部表中条目的大小和数量。

  • 节头部表

图3 可重定位目标文件elf格式节头部表

其中包含了各节的大小、偏移、类型以及各种标志。

  • 重定位条目

图4 可重定位目标文件elf格式重定位条目

其中包含了所有需要重定位的符号,如图所示,offset代表需要被修改的引用的节偏移量、type告知链接器如何修改新的引用,addend 代表在重定位某些类型的值时需要做偏移调整,Sym.标识被修改引用应当指向的符号。在hello.o中,我们可以看到,需要进行重定位的符号包括函数调用puts、exit、printf、atoi、sleep、getchar等,还包括位于.rodata节的两个字符串常量。

  • 符号表:

图5 可重定位目标文件elf格式符号表

符号表中包括了value(距定义目标的节的起始位置的偏移),size是目标的大小,type通常是数据或函数,bind是表示符号是本地的或是全局的,ndx表示符号被分配到的节。

4.4 Hello.o的结果解析

使用objdump -d -r hello.o得到的结果如下:

图6  hello.o反汇编结果

可见机器语言是由二进制数构成的,以16进制如图左半部分所示;每条机器指令对应一条汇编语言,但是机器指令对应的汇编语言与hello.s中的汇编语言存在着以下几个方面的不同:

类型

截图

不同之处

反汇编

跳转指令后跟的是下一条要执行的指令的地址

hello.s

跳转指令后跟的是节的名字

反汇编

对字符串常量寻址时%rip+0,并插入重定位条目;指令lea;

hello.s

对字符串常量寻址用的是节.LC0;指令为leaq;

反汇编

函数调用call指令后是下一条要执行的指令地址,同时插入了重定位条目

hello.s

函数调用call指令后直接加函数名

反汇编

jmp指令后跟要跳转的地址值

hello.s

jmp指令后直接跟要跳转的节的名字

  

4.5 本章小结

经过汇编,程序从文本文件转换成二进制文件,并增加了重定位条目、符号表等可用于后续链接过程。无论程序最终运行时的地址如何,经过汇编之后各符号、函数的相对位置以及如何链接,组装起来的方式已经确定。hello程序做好了为可执行所作的准备。(第4章1分)


5链接

5.1 链接的概念与作用

(1) 链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载到内存并执行。

(2) 作用:链接器使得分离编译成为可能,不必将一个大型的应用程序组织成一个巨大的源文件,而是可以把其分解为更小的模块,可以独立地修改和编译这些模块,改变诸多模块中的一个时,只是简单地重新编译并重新链接应用,而不必编译其他文件。

注意:这儿的链接是指从 hello.o 到hello生成过程。

5.2 在Ubuntu下链接的命令

Ubuntu下链接的命令:ld -o hello -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o hello.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o

5.3 可执行目标文件hello的格式

(1) hello的ELF格式包括ELF Header、Section Headers、Program Headers、Dynamic Section, Relocation Section、Symbal Table、Version symbols section。可执行文件中额外包括了段头部表。

(2) 用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

图7 可执行文件elf格式段头部表

其中offset为可执行目标文件中的偏移量,由此来定位到段的起始位置;VirtAddr为分配的虚拟内存地址;PhysAddr为分配的真实物理地址;FileSiz为要加载的数据节的大小;MemSiz表示申请的内存空间;Flags表示段的属性,比如Type是LOAD类型的第一行代码段的R E表示可读可执行,第二行数据段的RW表示可读可写。

5.4 hello的虚拟地址空间

使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。在edb中查看data dump如下图所示。  

图8 hello虚拟地址空间

分析:从上图可以看出,本进程虚拟地址空间地址从0x400000开始,起始地址处是ELF头,而由5.3节中的图分析可知,.interp段的起始地址是0x400200,该段的大小是0x1c,相对于开始地址的偏移量是0x00000200。在datadump中,我们可以发现.interp段的起始地址为0x400200,与虚拟空间起始地址0x400000相差0x200,这与5.3节的图是一致的,同时该节的大小是0x1c,那么在datadump中显示该节于下一行的04处结束。同理,对于,init节,其起始地址是0x4004c0,该段的大小是0x17,偏移量是0x000004c0,对应于datadump中该段的起始地址为0x4004c0,到0x4004d7的位置结束;从地址0x4004e0开始即为.plt节,这与5.3中.plt节的起始位置0x4004e0也是相对应的。

5.5 链接的重定位过程分析

(1) 使用objdump -d -r hello命令得到hello的反汇编文件如下:

分析hello与hello.o的不同,说明链接的过程。

图9 hello反汇编结果

通过与hello.o对比分析,其主要的不同点在于:

  • hello.o反汇编后得到的文件中只包含main函数对应的机器指令及其对应的汇编语言;而hello反汇编后得到的文件中包含有.init .plt .text多个节的机器指令和及其对应的汇编语言;
  • hello.o反汇编得到的结果中main函数的起始地址从0x0开始;而hello反汇编之后各个节的起始位置不是从0开始,而是运行时地址,不同节的地址各不相同。
  • hello.o反汇编得到的结果中对于各函数和符号的引用没有定位到具体的位置,例如对于常量字符串的引用,采用相对寻址时偏移量是0,0x0(%rip);以及在调用函数时,call指令后跟的是顺序执行的下一条指令的地址(相对起始地址的偏移量),并非是被调用函数的实际地址;在hello反汇编后得到的结果中链接器把每个符号定义与一个内存位置关联起来,重定位所有节,为每个节分配了运行时地址,然后修改所有对于全局符号(字符串常量以及函数)的引用,使得这些引用指向内存位置。例如,对于printf字符串常量的引用,采用lea 0xfa(%rip) %rdi指令对printf应该输出的常量字符串"用法: Hello 学号 姓名 秒数!\n”进行引用,该字符串的地址位于相对于%rip偏移量为0xfa的位置;同理,对于函数的调用,每个函数的定义对应一个地址,hello反汇编后得到的所有call指令后跟的是要调用的函数的入口地址。以下表格展示了main函数中call指令的调用情况。

call指令

函数入口

call指令

函数入口

call指令

函数入口

call指令

函数入口

call指令

函数入口

call指令

函数入口

  • Hello.o反汇编得到的文件对控制转移条件跳转指令后面跟的是要跳转的位置相对于函数起始位置的偏移量;而hello反汇编中跳转指令后面跟的是要跳转的位置的运行时地址。具体差别如下图:

可见,二者要跳转的位置相对于起始地址偏移量是相同的,不同点在于一个是运行时地址,一个是相对地址。

  • Hello.o反汇编中机器指令没有对引用地址的说明,而在hello反汇编文件中,对于存在重定位条目的机器指令二进制位做了修改。(具体修改方式见(2)重定位分析)。

由上述对比可知链接的过程包括:

  • 符号解析:将每个符号引用和一个符号定义关联起来
  • 重定位:链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使得它们指向这个内存位置。链接器使用汇编器产生的重定位条目的详细指令,不加甄别地执行这样的重定位。

(2) 重定位分析

   通过和hello.o反汇编文件的对比,发现区别的根源在于链接器对于可重定位目标文件中所有的引用作了重定位。下面结合重定位条目,以

59: e8 00 00 00 00        callq  5e <main+0x5e>

5a: R_X86_64_PLT32 printf-0x4

为例进行重定位的具体说明。

(需要注意的是:较新的 GCC 版本使用 R_X86_64_PLT32 而不是R_X86_64_PC32 来标记 32 位 PC 相对寻址。如果函数是在本地定义的,链接器始终可以将 PLT32 重定位减少到PC32。)

  • 确定各量的对应值。由重定位条目定义可知:0x5a为需要被修改的引用的节偏移,在本例中即为机器指令e8后面的字节;重定位类型是R_X86_64_PLT32,采用PC相对寻址;printf为需要引用的符号名;-0x4是偏移调整量。
  • 计算相对引用的地址:由公式refaddr=ADDR(s)+r.offset ref=(ADDR(r.symbol) + r.addend - refaddr; 首先需计算出要修改的地址的运行时地址,由于main的起始地址为0x400582,所以要重定位的字节位于0x400582+0x5a=0x4005dc处,printf函数的地址为0x400500,因此计算出的相对引用的地址为0x400500+(-0x4)-0x4005dc=0xffffff20。因此将e8之后的字节修改为20 ff ff ff(小端序)。

5.6 hello的执行流程

(1) hello的执行流程:

  • 使用edb执行hello,控制首先转移到dl_start(通过call ld-2.27.so! dl_start实现);
  • 然后程序进入dl_init(通过call ld-2.27.so! dl_init);
  • 程序进入<libc-2.27.so!__libc_start_main>main函数,执行main函数中的指令;
  • 在main函数执行过程中先后调用了puts函数,如果用户输入命令行参数,则接续调用printf()、atoi()、sleep()等函数,循环结束后调用getchar函数;程序返回值为0,终止。
  • 若没有命令行参数,调用exit函数,程序终止。

(2) 请列出其调用与跳转的各个子程序名或程序地址。

使用step over得到程序运行过程如下:

图10 程序运行过程

5.7 Hello的动态链接分析

通过运行时程序过程分析可知,hello程序动态链接了linux-x86-64.so这一共享库,而编译器在产生共享模块代码时产生的是位置无关代码(可以加载而无需重定位。由于本程序调用了由共享库定义的函数,然而编译器没有办法预测这个函数运行时的地址,因为定义函数的共享模块可以在运行时加载到任意的位置。为了解决这一问题,GNU编译系统采用延迟绑定技术,把函数地址的解析推迟到它实际被调用的地方,这一技术依赖的数据结构是GOT和PLT。

通过readelf查看got地址如下:

执行_dl_init之前got在内存中如图:

执行_dl_init之后got在内存中如图:

可以发现,在dl_init之前,地址0x600ff0起始后的8个字节为全0;而在dl_init之后,0x600ff0起始后的字节变为a0 ab 28 61 03 7f 00,也即地址0x7f036128aba0。同时,地址0x601000的后8个字节也从全0变成了70 51 88 61 03 7f 00 00,也即地址0x7f0361885170, 地址0x601010的前8个字节由全零变成f0 18 67 61 03 7f 00 00也即地址0x7f03616718f0。

5.8 本章小结

    通过链接,可重定位目标文件中的所有引用的外部定义的变量或是函数都被精准定位,并且通过修改机器指令中的二进制数据使得程序在引用这些函数时能够链接到相对应的位置。在链接过程中,链接器和编译器、汇编器相配合,不仅为每个函数分配运行时地址,还可以通过生成位置无关代码的方式来实现共享库的链接,避免其在加载时进行成千上万个并不需要的重定位,这样做进一步提高了链接的效率。

(第5章1分)


6hello进程管理

6.1 进程的概念与作用

(1) 概念:进程的定义就是一个正在运行的程序的实例。系统中的每个程序都运行在某个进程的上下文中。而上下文是由程序正确运行所需要的状态组成的。

(2) 作用:进程提供给应用程序两个关键抽象:一个独立的逻辑控制流和一个私有的地址空间。逻辑控制流提供一个假象,好像程序独占地使用处理器;私有地址空间提供的假象是程序独占地使用内存系统。

6.2 简述壳Shell-bash的作用与处理流程

(1) 作用:shell是一个命令行解释器,可以解释用户从键盘输入的命令行。shell也可以理解成是一个交互型应用级程序,代表用户运行其他程序;shell执行一系列的读/求值步骤,然后终止。读步骤读取来自用户的一个命令行,求值步骤解析命令行,并代表用户运行程序。

(2) 处理流程:

  • 终端进程读取用户从键盘输入的命令行;
  • 分析命令行字符串,获取命令行参数,并构造传递给execve的argv向量;
  • 检查首个命令行参数是否为内置的shell命令;
  • 如果不是则调用fork()创建子进程;
  • 子进程调用2中的参数,execve执行特定程序;
  • 若用户没有要求后台运行(即结尾没有&),shell使用waitpid或wait等待作业终止后返回。
  • 如果用户要求后台运行(即结尾有&),shell就返回

6.3 Hello的fork进程创建过程

当我们在命令行输入./hello,时,shell会读取输入的这个命令行并分析命令行字符串,发现首个命令行参数不是内置的shell命令,于是调用fork()创建子进程。这个子进程是新的运行的子进程,它得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈,子进程还或的与父进程任何打开文件描述符相同的副本,这意味着子进程可以读写父进程中打开的任何文件。

6.4 Hello的execve过程

shell在分析命令行时,不仅会获取命令行参数,还会构造传给execve函数的argv数组,argv数组是指针数组,数组里的每个元素都是指向一个字符串的指针,argv[0]即为程序的名字。而main函数的参数还包括argc,这一参数为int 型,指明了命令行参数的总数,子进程会调用argv参数,并调用execve函数加载并运行可执行目标文件hello。execve函数的参数包括文件名、参数列表和环境变量列表。在其加载了文件名之后,会调用启动代码,由启动代码设置栈(包括参数列表和环境变量列表),并将控制传递给主函数。

图11 启动代码设置的栈

6.5 Hello的进程执行

进程的上下文保存着内核重新启动一个被抢占的进程所需的状态,包括通用目的寄存器,浮点寄存器,程序计数器,用户栈,状态寄存器,内核栈和各种内核的数据结构。在时间片中,一个进程执行它的控制流的一部分,由于进程与进程之间是并发执行的,所以在hello程序运行过程中,很可能被其他进程抢占,这是由内核决定的。内核调度另一个进程开始执行之前,将保存hello的上下文,恢复另一个进程被保存的上下文,然后把控制传递给新的进程;当然,hello程序也可以作为新的进程来抢占其他进程的执行。对于系统调用函数sleep的执行,当程序执行这一指令时,内核同样也会进行上下文切换,让hello休眠。hello程序必须通过系统调用sleep来进行休眠,这是因为用户模式中的进程不允许执行特权指令,也不允许直接引用地址空间中内核区的代码和数据,因此用户程序必须通过系统调用接口间接地访问内核代码和数据。而一个运行在内核模式下的进程可以执行指令集中的任何指令并访问系统中的任何内存位置。

6.6 hello的异常与信号处理

(1) hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。

异常

信号

处理

来自键盘的中断

SIGINT/SIGQUIT

终止

系统调用

调用内核程序

用户定义的信号,例如kill

SIGKILL

终止

(2) 程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps  jobs  pstree  fg  kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。

操作

现象

分析

按回车

按下回车不影响程序的执行过程

Ctrl-z

键入ctrl-z进程停止,仍在后台运行;

Ctrl-c

键盘键入ctrl-c,内核发送SIGINT给前台进程组的进程,终止hello进程

ps

显示hello进程并没有终止

jobs

当前作业中hello被停止

pstree

以树形结构显示程序与进程的关系

fg

hello可在前台运行

kill

内核发送SIGKILL信号给hello进程终止进程

6.7本章小结

本章分析了程序是如何在进程中运行的,以shell为载体,从创建子进程开始到调用execve来加载程序文件到程序最终的执行。操作系统内核通过使用上下文切换的较高层形式的异常控制流来实现多任务,此外,针对程序运行过程中可能遇到的异常,计算机系统引入信号作为更高层的软件形式的异常来允许内核和进程中断其他进程。通过分析各阶段获可见,软件和硬件的充分配合,以及几个重要的抽象概念,使得大量的进程以并发的形式在计算机中运行。

(第6章1分)

结论

本论文中hello程序主要经过了预处理、编译、汇编、链接、进程管理的阶段;在预处理阶段,预处理器cpp主要做的工作就是将#include包含的头文件的内容插入到程序的文本中;在编译阶段,程序由高级语言翻译成汇编语言,汇编语言和机器指令一一对应的,在这一阶段,编译器为计算常量表达式等,为全局变量分配节,为局部变量分配栈中的空间,决定寄存器的使用情况,为程序中的控制转移产生相应的汇编指令以实现控制,对于函数的调用分配用于传参的寄存器和返回值;在汇编阶段,程序由文本文件转换成可重定位的二进制目标文件,汇编器为每一个需要重定位的符号生成重定位条目;在链接阶段,为不同段分配运行时地址,并且进行符号解析和重定位过程,以程序中引用到的外部函数或全局变量地址来修改机器指令;对于共享库,采用GOT和PLT两种数据结构来实现对共享库位置无关代码的定位。在进程管理中,操作系统和硬件相互配合协作,为多个进程能够并发地执行提供机制基础,同时,针对不同层面的异常涉及不同处理机制包括信号等。

从hello.c到最终在进程的上下文中运行,程序经历了复杂而又精细的处理过程。从中得以窥见计算机系统里所包含的伟大的抽象与庞大的知识体系结构。计算机系统的各个部分是紧密联系的,无论是不同硬件单元之间的物理连接,还是存储器体系结构之间传递,无论是操作系统涉及的各个管理机制,还是操作系统与硬件之间的配合,都不是脱离了其他而单独存在的,各个体系,各个层面都有着深刻且广泛的联系。而这一切的背后离不开几个伟大的概念:表格、虚拟内存、进程等等。或许未来的计算机系统可以在当前的基础上继续优化,在并行度的提高以及不同计算机之间如何尽可能消除移植方面遇到的问题等。


附件

列出所有的中间产物的文件名,并予以说明起作用。

hello.i 预处理的结果,用于分析预处理阶段发生的变化

hello.s 编译的结果,用于分析编译阶段发生的变化

hello.o 汇编的结果,反汇编后用于分析汇编器所作的处理

aso.txt  hello.o的反汇编,用于与第3章的 hello.s进行对照分析。

ase.txt  hello的反汇编,用于与hello.o进行对照分析

hello 可执行程序,用于运行时测试不同shell指令以及edb调试


参考文献

[1]  Bryant,R.E. 深入理解计算机系统系统(第三版). [M], 2016

[2]  Shell简介:Bash的功能与解释过程(一) Shell简介 - 知乎

[3]  R_386_PC32和R_X86_64_PC32在link(GNU ld)重定位过程中有什么区别? - IT宝库

[4]  readelf命令使用说明_木虫下的博客-CSDN博客_readelf

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Proxy(代理)是一种在计算机网络中广泛应用的中间服务器,用于连接客户端和目标服务器之间的通信。Proxy csapp是一个与计算机系统应用(Computer Systems: A Programmer's Perspective)相关的代理服务器。 Proxy csapp的设计目的是为了提供更高效的网络通信,增强系统的安全性,并提供更好的用户体验。在Proxy csapp中,客户端的请求首先会被发送到代理服务器,然后由代理服务器转发给目标服务器,并将目标服务器的响应返回给客户端。这种中间层的机制可以提供很多功能,如缓存、负载均衡、安全认证等。 在csapp中,Proxy csapp可以被用于优化网络数据传输的效率。代理服务器可以对客户端请求进行调度和协商,以减少网络延迟和数据传输量。通过缓存常用的数据和资源,代理服务器可以减少重复的数据传输和目标服务器的负载,提高网络性能和响应速度。 此外,Proxy csapp还可以提供安全的网络通信环境。代理服务器可以拦截和过滤网络流量,用于检测和阻止恶意攻击、垃圾邮件等网络安全威胁。代理服务器还可以对用户进行身份验证和授权,保护敏感数据的安全性。 最后,通过Proxy csapp可以实现更好的用户体验。代理服务器可以根据用户的需求进行个性化的服务,如按地理位置提供更快的网络连接、提供访问限制和控制等。代理服务器还可以对网络流量进行压缩和优化,提高网络传输效率,减少用户的等待时间。 总之,Proxy csapp在计算机系统应用中是一个重要的代理服务器,它可以提供高效的网络通信、增强系统的安全性,并带来更好的用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值