HITICS || 2018大作业 程序人生 Hello's P2P

 

  本文通过分析一个hello.c的完整的生命周期,从它开始被编译,到被汇编、链接、在进程中运行,讲解了Linux计算机系统执行一个程序的完整过程。

  关键词:操作系统,进程,程序的生命周期

 

 

1章 概述- 4 -

1.1 Hello简介 - 4 -

1.2 环境与工具 - 4 -

1.3 中间结果 - 4 -

1.4 本章小结 - 4 -

2章 预处理- 5 -

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

2.2Ubuntu下预处理的命令 - 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 -

6hello进程管理- 10 -

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

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

6.3 Hellofork进程创建过程 - 10 -

6.4 Helloexecve过程 - 10 -

6.5 Hello的进程执行 - 10 -

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

6.7本章小结 - 10 -

7hello的存储管理- 11 -

7.1 hello的存储器地址空间 - 11 -

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

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

7.4 TLB与四级页表支持下的VAPA的变换 - 11 -

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

7.6 hello进程fork时的内存映射 - 11 -

7.7 hello进程execve时的内存映射 - 11 -

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

7.9动态存储分配管理 - 11 -

7.10本章小结 - 12 -

8helloIO管理- 13 -

8.1 LinuxIO设备管理方法 - 13 -

8.2 简述Unix IO接口及其函数 - 13 -

8.3 printf的实现分析 - 13 -

8.4 getchar的实现分析 - 13 -

8.5本章小结 - 13 -

结论- 14 -

附件- 15 -

参考文献- 16 -

1章 概述

1.1 Hello简介

P2Phello.c源程序文本先通过编译器的预处理转换为修改了的源程序文本hello.i,再经过编译变为汇编语言文本hello.s,再经过汇编器变为hello.o可重定位目标程序(二进制),最后通过链接器链接和重定位,形成可执行目标程序。然后系统在shell中执行程序,用fork产生子进程。

020:在开始时程序不占用系统资源,随着虚拟内存的访问程序开始载入物理内存,shell通过execve加载并执行hello,进程运行结束后系统回收hello进程。

1.2 环境与工具

硬件环境:

X64 CPU2GHz2G RAM256GHD Disk 以上

软件环境:

Windows7 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64

开发与调试工具:

Visual Studio 2010 64位以上;GDB/OBJDUMPDDD/EDB

1.3 中间结果

hello.i —— 修改了的源程序(文本)

hello.s —— 汇编程序(文本)

hello.o —— 可重定位目标程序(二进制)

hello —— 可执行目标程序(二进制)

1.4 本章小结

本章介绍了P2P020的过程,以及进行实验时的软硬件环境及开发与调试工具,以及在本论文中生成的中间结果文件。

2章 预处理

2.1 预处理的概念与作用

预处理是指在编译之前做的处理。

C语言程序在编译运行之前,要先对程序进行预处理。预处理主要体现在宏定义、文件包含、条件编译三个方面,预处理命令以符号“#”开头。预处理会导入宏定义与文件、头文件中的内容,使得程序能完整、正常的运行,预处理生成了hello.i的源代码文本文件。

2.2在Ubuntu下预处理的命令

通过gcc hello.c -E -o hello.i可以对hello.c进行预处理,得到hello.i

2.3 Hello的预处理结果解析

gedit打开我们得到的hello.i可以发现它的前面头文件等等被展开了,变成了很多以#开头的内容,

拉到最下面发现hello.i的最后是我们熟悉的C语言程序,而.i的文件一共有3k多行

2.4 本章小结

本章简述了在编译前进行的预处理的过程,hello.c文件经过预处理生成了hello.i文件,hello.i文件中对宏定义、文件包含、条件编译进行了展开。

3章 编译

3.1 编译的概念与作用

编译是指从 .i .s 即预处理后的文件到生成汇编语言程序的过程,生成一个hello.s的汇编语言源程序文件。

编译的作用是将高级语言转变为更易于计算机读懂的汇编语言,同时它还可以进行语法检查、程序优化。

3.2 在Ubuntu下编译的命令

通过gcc hello.i -S -o hello.s可以对.i文件进行汇编,得到hello.s

3.3 Hello的编译结果解析

我们得到的hello.s就是编译后得到的汇编语言源程序文件,通过gedit查看它,发现它的头部声明了全局变量和它们存放的节段,接下来是将源程序的命令汇编得到的代码,接下来对它们进行分析。

3.3.1 全局变量

hello.c中有一个全局变量sleepsecs,它被定义成int型,但在编译器编译的过程中将它优化为了long型,这里编译器进行了隐式的类型转换,我们给它赋值为2.5,查看sleepsecs的值时发现sleepsecs = 2,它被存放在.rotate节中。

3.3.2 局部变量

通过观察这里,我们发现在.L2中声明了一个局部变量i,将其存储在-4(%rbp)中,可以得知在处理局部变量时,编译到当前位置才去申请这样一个内存空间的。

3.3.3 赋值

对于全局变量的赋值直接在处理时在.data节声明,sleepsecs为值为2long型变量。

对于局部变量的赋值使用movx语句完成,在栈或寄存器内分配空间,由于i4个字节的int型数据,因此采用movl

3.3.4 类型转换

hello.c中定义全局变量sleepsecs时将其定义为int型,但在查看时发现它的类型是long,由于我们在程序中并没有类型转换这个操作,因此可以发现编译器会进行隐式的类型转换,以对程序起到优化的作用。

3.3.5 算术操作

汇编语言中加减乘除四则运算是通过语句来实现的:

加法 x = x + y  ->  addq y, x

减法 x = x - y   ->  subq y, x

以及在hello中没有体现的乘除

乘法  x = x * y  ->  imulq y, x

除法  x = x / y  ->  divq y, x

3.3.6 关系操作

关系操作就是比较两个变量的大小情况,通过cmpl来执行,在cmpl中比较两个数的大小,用后一个数减去前一个数得到结果的情况来设置标志位,接下来可以通过设置的标志位进行跳转等操作。

3.3.7 控制转移之条件语句

通过cmpl进行比较,根据比较的结果通过jx进行跳转,跳转方式可以通过查看跳转表得到

     

3.3.8 控制转移之循环语句

这里就是一个循环语句的开始,可以发现我们的循环条件是i < 10,在这里被优化为了i <= 9,每次将计数器的值与9进行比较,若小于等于则跳转到循环内部.L4执行

循环内部语句如下,在每次执行.L4结束后都将-4(%rbp)1,因此它是i,起到一个计数器的作用

3.3.9 函数操作

·参数传递:在函数的参数传递中使用不同的寄存器来保存第x个参数

·函数调用:使用call语句来实现函数的调用

  

·函数返回:函数的返回值保存在%rax中,将需要返回的变量值存在%rax中,在进行函数的操作之后ret即可返回%rax中的值

3.4 本章小结

本章简述了在预处理之后编译器进行编译的过程,通过.i文件生成.s文件,得到汇编语言源代码文本,通过分析文本中的每一部分了解了C语言中的不同操作在汇编语言中的实现方法。

4章 汇编

4.1 汇编的概念与作用

汇编是指将汇编语言翻译成机器语言的过程。

作用:用汇编语言写的程序,机器不能直接识别,要将.s生成一个机器语言二进制的.o文件,才能使得机器能够识别。

4.2 在Ubuntu下汇编的命令

通过as hello.s -o hello.o进行汇编,得到hello.o

4.3 可重定位目标elf格式

通过readelf -a hello.o语句查看hello.oelf格式

4.3.1 ELF Header

ELF Header中描述了elf文件总的信息

4.3.2 Section Headers

这部分描述了hello.o中出现的各个节的类型、位置、所占空间大小等信息

4.3.3 .rela.text

这部分描述了.text节中需要重定位的信息,这些信息在生成可执行文件时就会被重定位,通过查看可以发现在hello.o中需要被重定位的有.radata, puts, exit, printf, sleepsecs, sleep, getchar

4.4 Hello.o的结果解析

通过objdump -d -r hello.o得到hello.o的反汇编,与 hello.s进行对照分析,发现hello.o的比hello.s在左侧多了一些十六进制的数字,这些就是机器码,其余的具体内容与hello.s并无巨大差异,只是多了一些跳转的位置。

4.4.1 分支转移

在汇编语言中,分支转移的跳转位置都是用.L3, .L4来表示的,但在机器语言中它们被具体的跳转地址所替代。

   

4.4.2 函数调用

在汇编语言中,函数的调用都是用函数名来跳转的的,但在机器语言中它们被具体的跳转地址所替代。

  

4.5 本章小结

本章简述了将hello.s生成hello.o,从汇编代码生成机器代码的过程,比较了汇编代码与机器代码的异同。

5 链接

5.1 链接的概念与作用

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

链接使得分离编译成为可能,能够将一个大型的应用程序分解成为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其他文件。

5.2 在Ubuntu下链接的命令

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

通过readelf查看hello的信息

5.3.1 ELF Header

这部分描述了elf文件总的信息

5.3.2 Section Headers

这部分描述了hello中出现的各个节的类型、载入到虚拟内存后的地址(Address、节头表所对应字节大小(Size)以及这个节的地址偏移量(Offset等信息

5.4 hello的虚拟地址空间

使用edb可以查看本进程虚拟地址的空间,从0x400000开始,到0x400fff结束,这一部分连续存放的是elf中的section headers中的address

   

5.5 链接的重定位过程分析

通过objdump -d -r hello,比较hellohello.o发现hellohello.o要多了很多函数:

_init

puts@plt

printf@plf等等如同xxx@plt

_start

_fini

在链接时,_init用来做初始化,_start是程序的入口,它调用main

libc.so是动态链接共享库,其中定义了putsgetcharprintfexit等函数

链接器将共享库中的函数加入到程序中。

同时我们可以发现,在链接后.text.plt的相对位置确定,因此在跳转时无需再利用偏移量,可以直接通过:目标地址 = PC + 偏移量,来计算出函数地址来进行跳转。

在重定位过程中由于目标地址 = PC + 偏移量,因此重定位地址 = 目标地址- PC,可以得到重定位的值。

5.6 hello的执行流程

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

执行过程中调用的子程序名:

_dl_start

_dl_init

_start 

_libc_start_main                      

_init      

_main

_printf

_exit

_sleep

_getchar

_dl_runtime_resolve_xsave

_dl_fixup

_dl_lookup_symbol_x

exit

5.7 Hello的动态链接分析

在动态链接的过程中,程序会生成一个共享库,它可以加载到任意的内存地址,并和一个内存中的程序链接起来,它在加载和运行时由动态链接器完成。

在处理全局函数时,对于x函数会生成一个x@plt函数,这个函数对应重定位后的跳转地址。

_dl_init执行前偏移量中全为0,在它执行后偏移量变为了相应的偏移值,因此可以发现_dl_init操作是加载计算当前内存地址的偏移量。

5.8 本章小结

本章简述了链接器的工作,分析了静态链接和动态链接两种形式,以及链接器的符号解析和重定位的过程,通过链接和重定位,得到了可执行的二进制文件。

6 hello进程管理

6.1 进程的概念与作用

进程时计算机程序需要对数据集合进行操作所进行的一次活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

进程提供给应用程序两个关键抽象,逻辑控制流和私有地址空间:

逻辑控制流进程使得每个程序似乎独占地使用CPU,它由通过OS内核的上下文切换机制提供。

私有地址空间使得每个程序似乎独占地使用内存系统,它由OS内核的虚拟内存机制提供。

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

Shell是用户和Linux内核之间的接口程序,用户输入的命令通过shell解释,然后传给Linux内核,然后将内核的处理结果翻译给用户。

处理流程:首先shell读取用户输入的命令并进行解析,得到参数列表,然后检查这条命令是否是内核命令,如果是则直接执行,如果不是则fork子进程,启动加载器在当前进程中加载并运行程序。

6.3 Hello的fork进程创建过程

父进程通过调用fork函数创建一个新的运行的子进程:

pid_t fork(void);

fork子进程时,系统创建一个与父进程几乎但不完全相同的子进程,子进程得到与父进程用户级虚拟地址空间相同但独立的一份副本,包括代码、数据段、堆、共享库以及用户栈,子进程获得与父进程任何打开文件描述符相同的副本,这就意味着当父进程调用fork时,子进程可以读写父进程中的内容,但它们有着不同的PID,在父进程中,fork返回子进程的PID,在子进程中,fork返回0

6.4 Hello的execve过程

execve函数加载并运行可执行目标文件:

int execve(const *filename, const char *argv[], const char *envp[]);

其中filename是可执行目标文件,argv是参数列表,envp是环境变量列表

它调用一次,从不返回,只有出现错误时execve才会返回到调用程序

Loader删除子进程现有的虚拟内存段,创建一组新的段(栈与堆初始化为0),并将虚拟地址空间中的页映射到可执行文件的页大小的片chunk,新的代码与数据段被初始化为可执行文件的内容,然后跳到_start,新程序启动后的栈结构如下:

6.5 Hello的进程执行

上下文:进程的物理实体(代码和数据等)和支持进程运行的环境合称为进程的上下文;由进程的程序块、数据块、运行时的堆和用户栈(两者通称为用户堆栈)等组成的用户空间信息被称为用户级上下文,用户级上下文地址空间和系统级上下文地址空间一起构成了一个进程的整个存储器映像。

进程时间片:进程时间片即CPU分配给每个进程的时间

Hello进程调度的过程:首先shell通过加载器加载可执行目标文件hello,操作系统进行上下文切换,切换到hello的进程中,这是我们在用户态,接下来hello调用sleep函数,进入内核态,当sleep的时间到时定时器发送一个中断信号,通过信号处理函数处理,通过上下文切换再进入hello进程,回到用户态。

6.6 hello的异常与信号处理

6.6.1 正常运行

6.6.2 ctrl+z

ctrl+z向进程发送了一个SIGTSTP信号,将当前进程暂时挂起

ctrl+z后可运行其他指令:

·ps:通过ps可以发现hello进程还没有被回收。

·jobs:可以看到当前挂起的进程

·fg:可以使后台挂起的进程继续运行

6.6.3 ctrl+c

ctrl+c向进程发送SIGINT信号,直接结束当前进程并进行回收,通过ps可以发现hello进程已经不在了

6.7本章小结

本章简述了进程的概念与作用,以及shell的执行流程,总结了forkexecve的运行过程,以及在上下文切换中用户态和内核态的切换,探究了在进程运行过程中不同信号的作用。

7 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:指机器语言指令中,用来指定一个操作数或者是一条指令的地址。一个逻辑地址,是由一个段标识符加上一个指定段内相对地址的偏移量。

线性地址(虚拟地址):跟逻辑地址类似,它也是一个不真实的地址,假设逻辑地址是相应的硬件平台段式管理转换前地址的话,那么线性地址则相应了硬件页式内存的转换前地址。

物理地址:用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应,是内存单元的真正地址

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

一个逻辑地址由两部分组成,段标识符和段内偏移量。段标识符由一个16位长的字段组成,称为段选择符其中前13位是一个索引号后面3位包含一些硬件细节,段选择符各字段含义如图:

通过TI可以判断这个段描述的是局部段描述符还是全局段描述符,然后根据段标识符的前13索引,在段描述符表中找到一个具体的段描述符,可以得到基地址,然后再将其与段内偏移量结合,即可得到线性地址,如图:

7

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

CPU中的一个控制寄存器,页表基址寄存器指向当前页表,n位的虚拟地址由虚拟页面偏移(VPO, n位)和虚拟页号(VPN, n - p位)组成。MMU利用VPN来选择适当的PTE(页表条目),将页表条目中的物理页号(PPN)与虚拟地址的页面偏移量(VPO)串联起来,就得到相应的物理地址。

页面命中时,CPU硬件执行的步骤如下:

1) 处理器生成一个虚拟地址,并将其传送给MMU

2) MMU生成PTE地址,并从高速缓存/内存中请求得到它

3) 高速缓存/内存向MMU返回PTE(即MMU 使用内存中的页表生成PTE

4) MMU构造物理地址,将其传送给高速缓存/主存

5) 高速缓存/主存返回所请求的数据字给处理器

页面不命中时,CPU硬件执行的步骤如下:

2) 1) 处理器生成一个虚拟地址,并将其传送给MMU

2) MMU生成PTE地址,并从高速缓存/内存中请求得到它

3) 高速缓存/内存向MMU返回PTE(即MMU 使用内存中的页表生成PTE

4) PTE中的有效位为零, 因此 MMU 触发缺页异常

5) 缺页处理程序确定物理内存中牺牲页 (若页面被修改,则换出到磁盘)

6) 缺页处理程序调入新的页面,并更新内存中的PTE

7) 缺页处理程序返回到原来进程,再次执行导致缺页的指令

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

在这里要用到翻译后备缓冲器(TLB),每个VA被分为VPNVPO,每个VPN又分为三段,根据TLB标记(TLBT)和TLB索引(TLBI)到TLB中找对应的虚拟页号(PPN),找到的PPN+VPO即为物理地址。

TLB模式下,如果能直接命中即可直接得到PPN,若发生缺页则要去页表里再进行查找,VPN被分为了4段,在查找时通过每一级页表一级一级往下找,最后找到相应的PPN,加上虚拟页面偏移量VPO即可得到物理地址。

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

在三级cache下,将物理地址分成CT(标记)+CI(索引)+CO(偏移量)首先在一级cache下找,若发生不命中miss则到下一级缓存即二级cache下找,若不命中则到三级cache下访问。

7.6 hello进程fork时的内存映射

fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。

fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同,当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间。

7.7 hello进程execve时的内存映射

execve函数在当前进程中加载并运行新程序a.out时:

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

·创建新的区域结构,这些新的区域都是私有的、写时复制的,代码和初始化数据映射到.text.data区,.bss和栈堆映射到匿名文件。

·映射共享区域。如果a.out程序与共享对象链接,那么这些对象都是动态链接到这个程序的,再映射到用户虚拟地址空间中的共享区域内。

·设置程序计数器(PC)。设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。

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

当出现缺页故障时,即DRAM缓存不命中,此时调用缺页处理程序,内存会确定一个牺牲页,若页面被修改,则换出到磁盘,再将新的目标页替换牺牲页写入,缺页处理程序返回到原来的进程,重启导致缺页的指令。

7.9动态存储分配管理

7.9.1 隐式空闲链表

隐式空闲链表通过头部中的大小字段隐含地连接所有块

7.9.2 显式空闲链表

显式空闲链表在空闲块中使用指针连接空闲块。它将空闲块组织为某种形式的显式数据结构,只保留空闲块链表,而不是所有块。在每个空闲块中,都包含一个前驱(pred)和后继(succ)指针。

维护显式空闲链表有两种方式,分别为后进先出(LIFO)和按照地址顺序来维护。后进先出的顺序维护将新释放的块放置在链表的开始处,使用LIFO的顺序和首次适配的放置策略,分配器会最先检查最近使用过的块。按照地址顺序来维护链表时,链表中每个块的地址都小于它的后继的地址,按照地址排序的首次适配比LIFO排序的首次适配有更高的内存利用率,接近最佳适配的利用率。

7.9.3 寻找空闲块

·首次适配 (First fit):从头开始搜索空闲链表,选择第一个合适的空闲块。

·下一次适配 (Next fit):从链表中上一次查询结束的地方开始搜索空闲链表,选择第一个合适的空闲块。

·最佳适配 (Best fit):查询链表,选择一个最好的空闲块。

7.9.4 分配空闲块——分割

当分配块比空闲块小时,我们可以把空闲块分割成两部分,并将多余的空间重新加到空闲链表中,以减少内部碎片。

7.9.5 空闲块的合并——带边界标记的合并

合并后面的空闲块时,当前块的头部指向下一个块的头部,可以检查这个指针以判断下一个块是否是空闲的,如果是,就将它的大小简单地加到当前块头部的大小上,使得两个块在常数的时间内被合并。

合并前面的空闲块时,在每个块的结尾处添加一个脚部,它是头部的一个副本,分配器可以通过检查它的脚部判断前一个块的起始状态和状态。

7.10本章小结

本章简述了在计算机中的虚拟内存管理,虚拟地址、物理地址、线性地址、逻辑地址的区别以及它们之间的变换模式,以及段式、页式的管理模式,在了解了内存映射的基础上重新认识了共享对象、forkexecve,同时认识了动态内存分配的方法与原理。

8 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:所有IO设备都被模型化为文件,所有的输入和输出都能被当做相应文件的读和写来执行。

设备管理:Linux内核有一个简单、低级的接口,成为Unix I/O,是的所有的输入和输出都能以一种统一且一致的方式来执行。

8.2 简述Unix IO接口及其函数

打开文件:int open (char *filename, int flags, mode_t  mode);

关闭文件:int close (int fd);

读文件:ssize_t read (int fd, void *buf, size_t n);

写文件:ssize_t write (int fd, const void *buf, size_t n);

8.3 printf的实现分析

printf接受一个格式化的命令,并把指定的匹配的参数格式化输出

printf中调用了两个函数:vsprintwrite

观察vsprintf它接受确定输出格式的格式字符串fmt,将所有参数内容格式化后存入buf,返回格式化数组的长度

观察write,它将buf中的i个元素的值写到终端

vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80syscall.

字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。

显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

getchar调用read函数返回字符,read将整个缓存区内容读入buf,然后返回缓冲区长度。然后对buf的长度进行判断,若buf长度为0,则调用read函数,否则直接返回buf前面的元素。

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。

getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结

本章节简述了Linux系统下I/O的机制,了解了有关打开、关闭与读写文件的操作,分析了printfgetchar这两个我们常用的函数的实现过程。

结论

hello首先在计算机内被其他程序进行了一番翻译,经过预处理变为hello.i,再经过编译器变为汇编程序hello.s,再经过汇编器变为可重定位的二进制目标程序hello.o,然后经过链接器生成hello可执行的二进制目标程序;

shell中经过了forkexecve,把hello加载到其中;

然后在磁盘中去读取它,在运行的过程中接受键盘的信号shell对其做出不同的处理,映射虚拟内存进行访问,进行动态内存分配;

最后进程终止,被shell回收。

Hello一路走来很不容易呐,我们在面前只看到了它简单的运行结果,但其实后面有编译器汇编器链接器等等在一起工作,使它能完美的呈现在我们面前^ ^

写完大作业对Linux下的各种命令更加熟悉了,学习了一下edb的使用,虽然感觉它不如gdb好用(

计算机系统是个很神奇很伟大的东西,它需要我们花更多的时间和精力去深入理解它……光是这学期学的知识还远远不够,以后也要深入理解计算机系统哇

/*

所有图片没法复制。。要一个一个慢慢插入qwq

别急啊别急啊窝忙完这波就插图片顺便好好排个版嘤嘤嘤

*/

猜你喜欢

转载自www.cnblogs.com/pinkglightning/p/10226684.html