哈尔滨工业大学计算机系统大作业

 

计算机系统

大作业

题     目  程序人生-Hello’s P2P   

专       业     计算机科学与技术   

学     号        2021110826      

班     级         2103103        

学       生          于哲         

指 导 教 师          刘宏伟         

计算机科学与技术学院

2022年5月

摘  要

本文以hello.c为源文件,通过预处理,编译,汇编,链接等操作,将生成.i , .s , .o 到hello可执行程序的各个阶段一一展示出来,最后在shell中执行hello程序,通过本文我们可以很好的了解hello的一生。通过hello的一生,加强我们对计算机系统有关知识的深入理解和掌握。

关键词:hello;预处理;编译;汇编;链接;shell;进程;信号。                           

目  录

第1章 概述................................................................. - 4 -

1.1 Hello简介.......................................................... - 4 -

1.2 环境与工具......................................................... - 4 -

1.3 中间结果............................................................. - 4 -

1.4 本章小结............................................................. - 4 -

第2章 预处理............................................................. - 5 -

2.1 预处理的概念与作用......................................... - 5 -

2.2在Ubuntu下预处理的命令.............................. - 5 -

2.3 Hello的预处理结果解析.................................. - 5 -

2.4 本章小结............................................................. - 5 -

第3章 编译................................................................. - 6 -

3.1 编译的概念与作用............................................. - 6 -

3.2 在Ubuntu下编译的命令................................. - 6 -

3.3 Hello的编译结果解析...................................... - 6 -

3.4 本章小结............................................................. - 6 -

第4章 汇编................................................................. - 7 -

4.1 汇编的概念与作用............................................. - 7 -

4.2 在Ubuntu下汇编的命令................................. - 7 -

4.3 可重定位目标elf格式..................................... - 7 -

4.4 Hello.o的结果解析........................................... - 7 -

4.5 本章小结............................................................. - 7 -

第5章 链接................................................................. - 8 -

5.1 链接的概念与作用............................................. - 8 -

5.2 在Ubuntu下链接的命令................................. - 8 -

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

5.4 hello的虚拟地址空间...................................... - 8 -

5.5 链接的重定位过程分析..................................... - 8 -

5.6 hello的执行流程.............................................. - 8 -

5.7 Hello的动态链接分析...................................... - 8 -

5.8 本章小结............................................................. - 9 -

第6章 hello进程管理........................................ - 10 -

6.1 进程的概念与作用........................................... - 10 -

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

6.3 Hello的fork进程创建过程.......................... - 10 -

6.4 Hello的execve过程...................................... - 10 -

6.5 Hello的进程执行............................................ - 10 -

6.6 hello的异常与信号处理................................ - 10 -

6.7本章小结........................................................... - 10 -

结论............................................................................. - 11 -

附件............................................................................. - 12 -

参考文献..................................................................... - 13 -

第1章 概述

1.1 Hello简介

Hello.c的一生,可以看成P2P, 020的整个过程。

P2P:即From Promgram to Process,program指hello.c源代码文件,而process指hello可执行文件创建的程序实例。这个过程就是hello.c在Linux系统下经过一系列的操作最终变成可执行文件并执行的过程。首先,hello.c经过预处理器cpp生成hello.i,然后经过编译器ccl生成hello.s,再经过汇编器as生成hello.o,最后经过链接器ld生成hello可执行程序。在Linux的shell中执行该程序,系统会调用fork函数为其生成子进程。

020:即From Zero-0 to Zero-0,第一个zero指程序创建之前,一切还不存在,在经过fork创建子进程后,系统又调用execve函数将hello程序加载到内存空间,同时为其分配资源。在一切执行完以后,shell会回收该子进程,同时释放它所占用的内存空间和各种资源,一切又回到了zero。

1.2 环境与工具

硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上

软件环境:Windows7/10 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位 以上

开发与调试工具:Visual Studio 2010 64位以上;CodeBlocks 64位;vi/vim/gedit+gcc

1.3 中间结果

hello.i:源代码文件经过预处理后的结果。

hello.s:编译后结果,汇编语言文本文件。

hello.o:汇编后结果,可重定位目标文件。

hello:可执行文件。

helloelf.txt:hello.o的elf格式文件。

hello_obj.txt:hello可执行程序的objdump反汇编结果。

hello_o_obj.txt:hello.o的objdump反汇编结果。

1.4 本章小结

本章介绍了hello的一生所经过的P2P和020过程。以及实验所处的软硬件环境和开发与调试工具,最后列举出了实验所产生的中间结果文件及其作用。

第2章 预处理

2.1 预处理的概念与作用

概念:预处理也称为预编译,它为编译做预备工作,主要进行代码文本的替换工作,用于处理#开头的指令,其中预处理器产生编译器的输出。经过预处理器处理的源程序会有所不同,在预处理阶段所进行的工作只是纯粹的替换和展开,没有任何计算功能。

作用:

  1. 处理代码文件中所有的#include,将头文件内容嵌入在该位置。
  2. 将所有的宏定义(#define)替换为特定的值。
  3. 对所有的条件编译如(#ifndef, #endif, #if)进行处理,选择哪些要编译,哪些不需要编译。

2.2在Ubuntu下预处理的命令

 

2.3 Hello的预处理结果解析

以下为hello.i程序部分内容的截图。

 

 

如图我们可以看到hello.i程序足足有3060行,远多于hello.c程序。而且我们发现在hello.i程序的最后即为原来.c程序中main函数的内容。而前面的大段内容为将#include中的头文件内容加载进来的结果。同时我们在.c文件中注释的内容也消失不见。由此我们可以看出,预处理的作用有删除所有的注释内容,并且将include的头文件内容加载到相应的位置。

2.4 本章小结

本章我们首先介绍了预处理的概念以及它的作用,然后讲解了一个具体的例子,即在Ubuntu下通过命令行生成hello.i文件,并且通过将hello.i文件中的内容与hello.c中的内容进行比较,从而感受预处理阶段给源文件带来的改变,更好的了解它的功能。

第3章 编译

3.1 编译的概念与作用

编译的概念:编译是指将预处理阶段的.i文件通过编译器翻译为.s文件,其为一个汇编语言文本文件。

编译的作用:编译生成的汇编语言文件属于低级语言,更加贴近与计算机硬件底层结构,方便于被计算机理解和执行。同时在将高级语言翻译成汇编语言时,编译器还会进行语法检查和语义分析,并优化代码结构,产生高效的汇编代码。

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析

以下为编译生成的hello.s文件。

      .file "hello.c" 

      .text

      .section  .rodata

      .align 8

.LC0:

      .string    "\347\224\250\346\263\225: Hello \345\255\246\345\217\267 \345\247\223\345\220\215 \347\247\222\346\225\260\357\274\201"

.LC1:

      .string    "Hello %s %s\n"

      .text

      .globl     main

      .type      main, @function

main:

.LFB6:

      .cfi_startproc

      endbr64

      pushq    %rbp

      .cfi_def_cfa_offset 16

      .cfi_offset 6, -16

      movq     %rsp, %rbp

      .cfi_def_cfa_register 6

      subq      $32, %rsp

      movl      %edi, -20(%rbp)

      movq     %rsi, -32(%rbp)

      cmpl      $4, -20(%rbp)

      je    .L2

      leaq .LC0(%rip), %rdi

      call puts@PLT

      movl      $1, %edi

      call exit@PLT

.L2:

      movl      $0, -4(%rbp)

      jmp .L3

.L4:

      movq     -32(%rbp), %rax

      addq      $16, %rax

      movq     (%rax), %rdx

      movq     -32(%rbp), %rax

      addq      $8, %rax

      movq     (%rax), %rax

      movq     %rax, %rsi

      leaq .LC1(%rip), %rdi

      movl      $0, %eax

      call printf@PLT

      movq     -32(%rbp), %rax

      addq      $24, %rax

      movq     (%rax), %rax

      movq     %rax, %rdi

      call atoi@PLT

      movl      %eax, %edi

      call sleep@PLT

      addl $1, -4(%rbp)

.L3:

      cmpl      $8, -4(%rbp)

      jle   .L4

      call getchar@PLT

      movl      $0, %eax

      leave

      .cfi_def_cfa 7, 8

      ret

      .cfi_endproc

.LFE6:

      .size       main, .-main

      .ident     "GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0"

      .section  .note.GNU-stack,"",@progbits

      .section  .note.gnu.property,"a"

      .align 8

      .long      1f - 0f

      .long      4f - 1f

      .long      5

0:

      .string    "GNU"

1:

      .align 8

      .long      0xc0000002

      .long      3f - 2f

2:

      .long      0x3

3:

      .align 8

4:

3.3.1数据

3.3.1.1常量

在hello.s文件的前面,.rodata节中已经存储了文件中的两个字符串常量,分别是.LC0:   .string       "\347\224\250\346\263\225: Hello \345\255\246\345\217\267 \345\247\223\345\220\215 \347\247\222\346\225\260\357\274\201"

.LC1:         .string    "Hello %s %s\n"

而在之后main函数的汇编代码中也引用了这两个字符串。即下图中用红框圈住的两处指令。分别调用了两处字符串常量的首地址.LC0,.LC1。

3.3.1.2变量

hello程序中的变量主要为局部变量,包括由main函数的参数传入的argc,argv,以及在main函数中定义的变量i。如下图所示,图中第一个红框框住的是由main函数的参数列表传入的变量argc,argv。它们分别有寄存器%edi和%rsi保存。这里将它们的内容压入栈中。图中第二个红框处即对应hello.c中int i = 0的指令表示,将i这个局部变量保存在%rbp-4指向的内存单元里。

 

3.3.2赋值操作

赋值操作可用mov+n指令来实现n取b, w, l, q分别对应于字节,字,双字,四字。

例如在本程序中,给i赋初值0:movl $0, -4(%rbp),因为i为int型,故采用l。

3.3.3算术操作

算术运算包含对变量的各种操作,如+ - * / %  ++  --     取正/负+-   复合“+=”等。在本程序中,有对i进行++的操作。

 

这里通过指令addl   $1, -4(%rbp)实现该操作。将局部变量i的数值加1,实现了每循环一次,i+1的功能。

3.3.4关系操作

关系操作有很多种,例如==  !=   >  <   >=   <=它们常常用来作为条件进行分支跳转。在本程序中有两处关系操作。分别为!=和<。

它们在汇编语言中对应的实现如下图所示。先用cmp a b指令通过计算b-a设置条件码,然后再根据要判断的两数关系使用不同条件码进行跳转,即je, jle,在这里要比较从i<9,实际上用i<=8来等价替代。

 

 

       3.3.5数组操作

要想从数组中取出某个元素的值,首先要取出数组的首地址作为基址,然后将基址再加上你要取元素的偏移值即可得到待取数据的地址。

例如在我们的hello.s中,在-32(%rbp)中保存的是argv[]首地址,将改地址值赋给%rax,通过对%rax进行加8,加16的操作,来获取argv[1]和argv[2]的地址。

 

       3.3.6控制转移

控制转移使得程序执行可以不是顺序执行,而可以跳转到任意位置。有关控制转移的关键字有if/else switch for while  do/while  ?:       continue  break。

在hello程序中用到了if判断和for循环。

对于if判断,采用cmp指令进行比较,然后根据比较的结果决定是否跳转。如果相等则跳转到if代码块后面忽略这段代码,否则顺序执行代码块中内容。

 

对于for循环,红框区域实现初始条件,为i赋值0,并跳转到条件判断区域绿框,在该区域判断i<=8,若条件成立,则跳转到蓝框区域,此处为为for循环的循环体。当循环体快结束时执行i++操作,更新循环变量。再次进入绿框条件判断区域进行判断实现循环。

 

 

3.3.7函数操作

在hello程序中的main函数里共进行了6次函数调用分别是。

 

 

 

 

 

 

参数传递:

如果用函数参数进行值传递,参数传递用寄存器实现,而最多可用寄存器传入6个参数,按顺序依次用%rdi、%rsi、%rdx、%rcx、%r8、%r9来传递,对于多余的参数则用栈来传递值。

如果用函数参数进行地址传递,往往是传递数组或指针,这时想要改变参数本身,则要使用栈进行传递,将地址值压入栈中。通过地址找出引用的数进行操作。

这里对第三个函数的传参情况进行分析。传入三个参数,分别保存在%rdi,%rsi,%rdx中。对应printf("Hello %s %s\n",argv[1],argv[2])

 

函数返回:

函数返回保存在寄存器%rax中。

3.4 本章小结

本章主要介绍了编译的概念和作用,通过对hello.s文件汇编代码的各类操作的详细分析让我们感受到汇编语言与高级语言及底层硬件之间的联系,汇编语言是联系高级语言代码与底层硬件之间的桥梁。

第4章 汇编

4.1 汇编的概念与作用

汇编的概念:hello.s经过汇编器as翻译成为机器语言的过程。生成可重定位目标文件hello.o

汇编的作用:将人类能够看懂的文本文件形式的汇编语言翻译成为便于机器理解的机器语言文件,其内容为01字符串,方便机器的识别和处理。

4.2 在Ubuntu下汇编的命令

 

4.3 可重定位目标elf格式

   

 

通过readelf查看ELF格式。

ELF头:

如下图,ELF头以一个16字节大小的序列开始,描述字大小和字节顺序。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。

节头部表:

如下图,包含不同节的位置和大小,记录了旗标,链接,信息,对齐等数据。

 

 

可重定位节:

包含了.rel.text节,它表示一个.text节中位置的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。从图中我们可以看出,需要重定位的信息有普通,exit,printf,atoi,sleep,getchar以及.rodata中需要重定位的字符串常量。它将每一个需要重定位的信息写成一个结构体的形式,称此为ELF可重定位条目,每个条目包含偏移量,信息,类型,符号值,符号名称+加数。表示一个必须被重定位的引用,并指明如何计算被修改的引用。

 

.symtab

 

.symtab节,一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。符号表中每一个符号对应一个结构体形成的条目,它的内容包括value,即距定义目标的节的起始位置的偏移;name,对应的字符串表中的字节偏移;size,目标的大小;type,表示是数据还是函数;binding,表示符号是本地的(local)还是全局的(global)。在hello程序的符号表中,共包含了18个条目组成的数组。

4.4 Hello.o的结果解析

运用objdump反汇编的结果,保存在文件hello_o_obj.txt中

 

在hello.o的反汇编结果中我们可以发现机器语言即为用二进制代码描述的一种指令的集合,它表明了指令的类型,所用到的操作数以及寄存器。通过上图结果我们发现机器代码和汇编指令是一一对应的关系,每一条汇编指令唯一对应一条机器代码。

操作数方面:如上图红框圈中的部分,在hello.s文件中用十进制数-32表示而在objdump反汇编hello.o中,用十六进制数-0x20表示。

分支转移方面:hello.s文件中采用代码段助记符作为分支跳转的目标如下图中跳转的地点用代码段助记符.L2来表示。

 

而在hello.o的反汇编当中,分支跳转的目标用.text的位置加上偏移值来表示。即上图中用蓝框框住的部分,用main+0x2f表示跳转到的地点。

函数调用方面:hello.s文件中直接用call指令加上助记符来表示要调用的目标函数。如下图puts@PLT

而在hello.o的反汇编中,对应函数调用如图中绿框框住部分所示,因为目前处于汇编阶段,还没有进入链接阶段,所以要跳转到的位置是未知的,地址用00 00 00 00来表示,为了方便在链接阶段计算出跳转位置的信息,在call指令的下一行,给出了可重定位条目,链接器可根据该条目计算出偏移地址或直接地址。

4.5 本章小结

本章主要讲述了汇编的概念和作用,即将由.s文件生成.o文件的过程。接着通过readelf来获取hello.o的ELF格式,对其中各部分内容进行了分析。最后通过objdump反汇编hello.o来分析其中机器语言与汇编语言表示的区别和联系。

第5章 链接

5.1 链接的概念与作用

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

链接的作用:链接器将分离编译成为可能。我们可以将一个大型的应用程序分解为更小、更好管理的模块,可以独立地修改和编译这些模块。如果改变其中一个模块,只需重新编译这个模块,并重新链接应用,不需要重新编译其他文件。

5.2 在Ubuntu下链接的命令

 

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

 

图5.3.1 可执行目标文件elf格式

通过readelf -a hello来查看hello的elf格式文件。

hello的ELF:由图中可看出hello程序中节的数量为27个,同时ELF头中加入了程序运行时第一条指令存放地址即入口点地址0x4010f0。同时,通过类型可看出,这是可执行文件。

图5.3.2 ELF头

节头表:

节头表包含了27个节的名称,类型,地址,以及偏移量等信息。

图5.3.3节头表1

 

图5.3.4节头表2

程序头表:

程序头表是一个结构体数组,它体现出可执行程序中各片在文件中的偏移,在内存中的地址,运行时访问权限以及对齐要求。它是可执行文件的连续的片到连续的内存段的映射。

图5.3.5 程序头表

程序的节段映射:

 

图5.3.6 节段映射

5.4 hello的虚拟地址空间

使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。

从图中可以看出,edb加载hello虚拟空间的首地址为0x00400000

 

5.4.1 hello的虚拟地址空间首地址

然后我们按顺序查看.interp段信息。

由5.3中图知.interp段的起始地址为0x004002e0

 

通过edb查找对应内存地址存储信息如下图

 

5.4.2 .interp段信息

接下来我们选择查看.text段存储信息。

首先由5.3图知.text段的起始地址为0x004010f0

 

通过edb查找对应内存地址存储信息如下图

图5.4.3 .text段内存信息

最后我们再来查找.rodata段的信息。该段对应的地址为0x00402000

 

通过edb查找对应内存地址存储信息如下图

图5.4.4 .rodata段信息

剩余内容,查找方法类似,就不再一一列举出来。

5.5 链接的重定位过程分析

首先用objdump -d -r hello > hello_obj.txt对hello进行反汇编,产生结果如下。

 

图5.5.1 hello反汇编结果

接着我们再来看之前对hello.o反汇编的结果。如下图。

 

图5.5.2 hello.o反汇编结果

发现在hello.o反汇编结果中红框圈住的需要重定位的信息已经在hello反汇编结果中红框圈中区域计算出相应的地址。

这里我们以地址为0x401145处的call指令为例,解释重定位的过程。

首先我们由2图中蓝框框住部分找到puts的重定位条目,发现它是相对寻址,并且偏移量为0x21。则重引用地址refaddr = ADDR(s)+ offset = 0x401125 + 0x21 = 0x401146,则重引用值*refptr = unsigned(ADDR(puts)+ r.addent - refaddr)= 0x401090 – 0x4 - 0x401146 = 0xffffff46,则用小端法,将重定位值填入即为46 ff ff ff,如图1中绿框所示。

图5.5.3 puts函数位置信息

5.6 hello的执行流程

使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。

子进程地址       子进程名

0x7ffff7de3f30   <_init>

0x4010f0        <_start>

0x4011f1        <in __libc_csu_init>

0x401000        <_init>

0x401125        <main>

0x4010a0        printf@plt

0x4010c0        atoi@plt

0x4010e0        sleep@plt

0x4010b0        getchar@plt

0x7ffff7e06a70    <exit>

5.7 Hello的动态链接分析

动态链接器使用过程链接表PLT+全局偏移量表GOT实现函数的动态链接,GOT中存放函数目标地址,PLT使用GOT中地址跳转到目标函数。

首先找到.got的地址。由下图知,.got.plt的地址为0x404000

图5.7.1 .got.plt的位置

然后在edb中找到相应位置,在dl_init前后观察存储内容的变化。

图5.7.2 edb中dl_init前.got.plt内容

图5.7.3 edb中dl_init后.got.plt内容

5.8 本章小结

本章首先介绍了链接的概念和作用,然后用objdump反汇编hello,与之前用objdump反汇编hello.o进行比较。最后又分析了链接的重定位过程,hello的执行过程以及动态链接的过程。

第6章 hello进程管理

6.1 进程的概念与作用

进程的概念:一个执行程序中的实例,它提供给我们一种假象,我们的程序好像是系统中当前运行的唯一的程序,我们的程序独占地使用处理器和内存,处理器好像是无间断的执行我们程序中的指令,我们程序的代码和数据好像是系统中内存唯一的对象。

进程的作用:提供给应用程序的两个关键抽象。

(1)一个独立的逻辑控制流,它提供一个假象,好像我们的程序独占地使用处理器。

(2)一个私有的地址空间,它提供一个假象,好像我们的程序独占地使用内存系统。

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

Shell-bash的作用: Linux系统中,Shell是一个交互型应用级程序,代表用户运行其他程序(是命令行解释器,以用户态方式运行的终端进程)。其基本功能是解释并运行用户的指令。Bash则是Linux操作系统缺省的shell。

处理流程:

(1)终端进程读取用户由键盘输入的命令行。

(2)分析命令行字符串,获取命令行参数,并构造传递给execve的argv向量

(3)检查第一个(首个、第0个)命令行参数是否是一个内置的shell命令

(3)如果不是内部命令,调用fork( )创建新进程/子进程

(4)在子进程中,用步骤2获取的参数,调用execve( )执行指定程序。

(5)如果用户没要求后台运行(命令末尾没有&号)否则shell使用waitpid(或wait...)等待作业终止后返回。

(6)如果用户要求后台运行(如果命令末尾有&号),则shell返回;

6.3 Hello的fork进程创建过程

首先在命令行中输入./hello + 参数。Shell判断该命令不是一个内置命令,是一个可执行程序,于是调用fork函数为其创建一个子进程。创建得到的子进程得到与其父进程地址空间相同的一个独立的副本,他们共享文件,即子进程可以打开父进程打开的文件。但子进程与父进程最大的不同是,他们的pid不同,父进程的fork返回子进程的pid,而子进程的fork返回0。

6.4 Hello的execve过程

当hello程序的进程被创建之后,他就会调用execve函数来加载程序。而execve函数的功能是在当前创建的进程的上下文中加载并运行一个新程序。具体的执行过程如下:

1.删除已存在的用户区域。删除当前进程虚拟地址的用户部分中已存在的区域结构。

2.映射私有区域。为新程序的代码、数据、.bss和栈区域创建新的区域结构。所有这些区域结构都是私有的,写时复制的。虚拟地址空间的代码和数据区域被映射为hello文件的.txt和.data区。.bss区域是请求二进制零的,映射匿名文件,其大小包含在hello文件中。栈和堆区域也是请求二进制零的,初始长度为零。

3.映射共享区域。如果hello程序与共享对象链接,比如标准C库libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域。

4.设置程序计数器(PC)。Exceve函数做的最后一件事就是设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。下一次调用这个进程时,它将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。

5.如果execve函数调用成功,这不返回,因为原先程序已经不在了。若没有调用成功,则返回原先的程序。

6.5 Hello的进程执行

首先,我们来看一下上下文信息和进程时间片的定义。

上下文信息:内核为每个进程维持一个上下文。上下文就是内核重新启动一个被抢占的进程所需的状态。

进程时间片:进程时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。

接下来,我们来分析一下hello的进程执行过程。首先,hello进程调用了execve函数将hello程序的内容加载到内存空间中。开始运行后会打印要输出的字符串,然后调用sleep函数进入休眠状态。这时,会进入内核模式,然后保存该进程的上下文,通过调度器来讲控制权传递给其他进程,恢复其他进程的上下文然后让其他进程在用户模式下接着运行,完成了一次上下文切换。当休眠的时间到了后,再通过上下文切换将控制权还给hello进程。

6.6 hello的异常与信号处理

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

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

异常包括四种情况:

  1. 中断:异步异常。来自I/O设备的信号导致。总是返回到下一条指令。
  2. 陷阱:同步异常。有意的异常。总是返回到下一条指令。
  3. 故障:同步异常。潜在可恢复的异常。可能返回到当前指令。
  4. 终止:同步异常。不可恢复的错误。不会返回。

正常情况。

第一种情况,在程序运行过程中多次按回车。这时,在最后会多出几行输入命令的提示,这是因为多输入的回车存在缓存区里,在程序运行完后被shell进行识别处理。

第二种情况,输入ctrl+c。输入Ctrl+c以后键盘向前台进程组发出SIGINT信号,它的默认行为是终止前台正在运行的hello进程。

第三种情况,输入Ctrl+z。输入Ctrl+z以后键盘向前台进程组发出SIGTSTP信号,它的默认行为是停止前台正在运行的hello进程。

输入ps,显示当前时刻系统中各个进程的信息。

输入jobs。显示当前被暂停的进程。

输入pstree。查看进程树。

输入fg %1。将hello进程转到前台运行。

输入kill,杀死hello进程。

6.7本章小结

在本章中,我们围绕hello进程展开,分析了hello进程运行时,execve函数的作用,上下文切换,以及模式切换。最后分析了hello程序运行时的异常与信号处理。

结论

hello程序的一生:

  1. 编辑,用文本编辑器编写hello.c源程序文件。
  2. 预处理,用预处理器(cpp)由hello.c生成hello.i文本文件。
  3. 编译,用编译器(ccl)由hello.i翻译成hello.s汇编语言文本文件。
  4. 汇编,用汇编器(as)由hello.s翻译成机器语言构成的可重定位目标文件hello.o。
  5. 链接,用链接器(ld)由hello.o和库文件相链接生成可执行目标文件hello。
  6. 在shell中输入命令,调用fork函数为hello程序生成子进程,并调用execve函数加载程序到进程中,开始执行,如执行中传入信号,则调用信号处理程序对信号进行处理。执行完后回收子进程,释放内存。

就这样,hello正式的走完了自己平凡而又伟大的一生。

通过本次的学习,我对计算机系统有了更加深入的理解,以一个程序的视角,走完了计算机执行的过程。感受到计算机系统设计的精密和巧妙。

附件

hello.c:hello的源程序文件。

hello.i:源代码文件经过预处理后的结果。

hello.s:编译后结果,汇编语言文本文件。

hello.o:汇编后结果,可重定位目标文件。

hello:可执行文件。

helloelf.txt:hello.o的elf格式文件。

hello_obj.txt:hello可执行程序的objdump反汇编结果。

hello_o_obj.txt:hello.o的objdump反汇编结果。

参考文献

[1]  《深入理解计算机系统》 Randal E.Bryant  David R.O’Hallaron 机械工业出版社

[2] C语言预处理命令详解:

C语言预处理命令详解_折竹丶的博客-CSDN博客

[3]  ELF格式分析:https://blog.csdn.net/edonlii/article/details/8779075

[4]刘宏伟,计算机系统课程PPT,哈尔滨工业大学

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值