哈尔滨工业大学
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 英才学院计算学部
学 号 7203610731
班 级 2036014
学 生 李行
指 导 教 师 刘宏伟
计算机科学与技术学院
2021年5月
一个程序是如何走完他的一生的?本文将结合CSAPP课程,主要在Linux系统下,研究hello.c程序的完整生命历程,合理运用Ubuntu中的工具,将本学期所学知识融会贯通起来,以求达到对计算机系统更深入的理解
关键词:hello.c;程序的一生;Linux;Ubuntu;CSAPP
目 录
2.2在Ubuntu下预处理的命令............................................................................. - 5 -
5.3 可执行目标文件hello的格式........................................................................ - 8 -
6.2 简述壳Shell-bash的作用与处理流程........................................................ - 10 -
6.3 Hello的fork进程创建过程......................................................................... - 10 -
7.2 Intel逻辑地址到线性地址的变换-段式管理............................................... - 11 -
7.3 Hello的线性地址到物理地址的变换-页式管理.......................................... - 11 -
7.4 TLB与四级页表支持下的VA到PA的变换................................................ - 11 -
7.5 三级Cache支持下的物理内存访问............................................................. - 11 -
7.6 hello进程fork时的内存映射..................................................................... - 11 -
7.7 hello进程execve时的内存映射................................................................. - 11 -
7.8 缺页故障与缺页中断处理.............................................................................. - 11 -
8.2 简述Unix IO接口及其函数.......................................................................... - 13 -
第1章 概述
1.1 Hello简介
① P2P:From Program to Process
这是在讲一个程序如何变成一个可执行文件的过程。用高级语言(C语言)编写得到一个程序,是.c文件;随后经过预处理器,得到.i文件;对其进行编译得到.s的汇编文件;汇编器会将此文件翻译成为机器语言,并且将指令打包成.o文件;再通过链接器与函数库连接形成最终的可执行文件。
在这个可执行文件的执行过程中,操作系统会调用fork函数产生子进程,并且调用execve函数加载进程。至此就完成了从程序到进程
② 020:From Zero-0 to Zero-0
Shell 为 hello程序调用execve后映射虚拟内存,进入程序入口后调用物理内存。随后进入main函数执行目标代码。执行完成后,父进程回收子进程,内核删除相关数据结构
1.2 环境与工具
电脑 OMEN by HP Labtop 16-b0xxx
处理器 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz 2.30 GHz
机带 RAM 16.0 GB (15.6 GB 可用)
显卡 NVIDIA GeForce RTX3050 Ti Laptop GPU
虚拟机程序 VMware16
Linux操作系统 Ubuntu 20.04
工具:gcc,as,ld,vim,edb,readelf
1.3 中间结果
hello.c | c源码 |
hello.i | 预处理文件 |
hello.s | .c文件的汇编语言文件 |
hello.o | 可重定位目标文件 |
hello_o.elf | hello.o的elf文件 |
objdump_hello_o.s | hello.o的反汇编文件 |
hello | 可执行程序hello |
hello.elf | hello的elf文件 |
objdump_hello.s | hello的反汇编文件 |
1.4 本章小结
本章主要解释了P2P 020的内涵意义以及列出了实验中间结果
第2章 预处理
2.1 预处理的概念与作用
C预处理器并不属于编译器的组成部分,而是一个由编译器调用的执行程序,简单的来说它只是编译过程中的一个步骤。预处理器执行以#开头的命令行,并且有删除注释、包含其他文件以及执行宏(宏macro是一段重复文字的简短描写)替代等功能。
C语言预处理程序的作用是根据源代码中的预处理指令修改你的源代码。
2.2在Ubuntu下预处理的命令
预处理语句:gcc -E hello.c -o hello.i
图1 预处理结果
2.3 Hello的预处理结果解析
观察hello.c:
图2 hello.c
为一个总行数23行的.c文件
观察hello.i:
图3 hello.i
hello.i成为一个总行数3060行的文件,并且在第3046行找到了.c文件中的主体部分。
在此之前,为对如下三个头文件宏定义的展开:
图4 头文件
头文件的内容被引入,其中包括对数据类型的定义等等细节。
图5 头文件的引入
2.4 本章小结
本章了解了在Ubuntu下预处理语句,并分析了.i文件内部构造信息。
第3章 编译
3.1 编译的概念与作用
编译的概念:
将某一种程序设计语言写的程序翻译成等价的另一种语言的程序的程序
编译的过程:
- 词法分析:词法分析的任务是对由字符组成的单词进行处理,从左至右逐个字符地对源程序进行扫描,产生一个个的单词符号,把作为字符串的源程序改造成为单词符号串的中间程序。
- 语法分析:编译语言的语法分析器以单词符号作为输入,分析单词符号串是否形成符合语法规则的语法单位,如表达式、赋值、循环等,最后看是否构成一个符合要求的程序,按该语言使用的语法规则分析检查每条语句是否有正确的逻辑结构,程序是最终的一个语法单位。
- 中间代码:中间代码是源程序的一种内部表示,或称中间语言。中间代码的作用是可使编译程序的结构在逻辑上更为简单明确,特别是可使目标代码的优化比较容易实现中间代码
- 代码优化:是指对程序进行多种等价变换,使得从变换后的程序出发,能生成更有效的目标代码。所谓等价,是指不改变程序的运行结果。所谓有效,主要指目标代码运行时间较短,以及占用的存储空间较小。
3.2 在Ubuntu下编译的命令
命令:gcc -S hello.i -o hello.s
图6 编译指令
3.3 Hello的编译结果解析
3.3.1 ARM GNU风格的伪汇编代码
图7 伪汇编指令
伪汇编语句是指导汇编器和连接器工作的伪指令
一般以 . 为开头的都是伪汇编语言,下列为文中出现的伪汇编语言:
.file 声明源文件
.text 声明以下是代码段
.section 声明文件代码段
.rodata 声明只读文件
.align 8 声明数据对齐方式为8位对齐
.string 声明字符串.LC0和.LC1
.globl 声明全局变量
.type 声明数据类型
常见的跳转指令如下:[7]
JE :等于则跳转
JNE :不等于则跳转
JS :为负则跳转
JNS :不为负则跳转
JA :无符号大于则跳转
JG :有符号大于则跳转
JB :无符号小于则跳转
JL :有符号小于则跳转
3.3.8 引用函数指令
Call指令为调用函数,会进入到新的函数内:
3.4 本章小结
本章主要介绍了由.c文件转换成的.s文件中的一些常见汇编指令,这些是编译的结果,机器会将高级语言转换成汇编语言再转换成最基础的机器语言。汇编语言已经贴近底层,其中对寄存器的操作和处理的理解与分析可以帮助我们更好的看懂一个程序的运行流程
第4章 汇编
4.1 汇编的概念与作用
汇编的概念:
汇编语言(Assembly Language)是任何一种用于电子计算机、微处理器、微控制器或其他可编程器件的低级语言,亦称为符号语言。在汇编语言中,用助记符代替机器指令的操作码,用地址符号或标号代替指令或操作数的地址。在不同的设备中,汇编语言对应着不同的机器语言指令集,通过汇编过程转换成机器指令。特定的汇编语言和特定的机器语言指令集是一一对应的,不同平台之间不可直接移植。
汇编的作用:
完成从汇编语言文件到可重定位目标文件的转化过程。
4.2 在Ubuntu下汇编的命令
汇编语句:gcc hello.s -c -o hello.o
图8 汇编语句
4.3 可重定位目标elf格式
使用语句readelf -h hello.o我们可以得到elf头的解析:
图9 elf header
可以看到类别、数据、版本、类型、程序大小等信息。可以注意到类型为REL(可重定位文件)。
使用语句readelf -S hello.o,可以查看section header:
图10 section header
Section header里面出现了每个部分的意义,包括节的大小位置信息等等。
使用语句readelf -s hello.o 可以读取symbol table
图11 symbol table
从这个表中可以读出某个符号的编号,名称,偏移量和这个符号是本地的还是全局的。
使用语句readelf -r hello.o 可以查看重定位段的信息
图12 重定位节
可以看到本程序中存在的重定位节的偏移量、信息、类型、符号值等信息
4.4 Hello.o的结果解析
图13 反汇编
4.5 本章小结
本章主要侧重研究从hello.s到生成重定位文件hello.o的过程。其中使用readelf程序对elf header 和 section header 、symbol table、重定位节.rela.text进行了详尽的查看以及分析,并且分析了反汇编结果hello.o与原汇编程序hello.s的区别,体会了机器语言在其中的作用,为下一步生成连接做铺垫。
第5章 链接
5.1 链接的概念与作用
链接器概念:[8]
链接器(Linker)是一个程序,将一个或多个由编译器或汇编器生成的目标文件外加库链接为一个可执行文件。目标文件是包括机器码和链接器可用信息的程序模块。简单的讲,链接器的工作就是解析未定义的符号引用,将目标文件中的占位符替换为符号的地址。链接器还要完成程序中各目标文件的地址空间的组织,这可能涉及重定位工作。
链接器的作用:
简单来说,链接器就是将程序调用的各种静态链接库和动态链接库拼接在一起,成为一个可执行程序。
5.2 在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
图14 Ubuntu下生成链接的语句
5.3 可执行目标文件hello的格式
同上,打开readelf阅读现在的elf表头:
图15 elf header
可以观察到现在的文件类型变成了exec可执行程序,也可以读出这个表头的大小和程序表头大小,表头数量等一系列信息。
同理,查看节头:
图16 section header
同理,section header中可以读出名称、类型、地址、偏移量
查看symbol table:
图20 反汇编hello
通过对比与hello.o的代码,可以发现有如下变化:
- 增加了函数代码。由于已经生成一个可执行程序,将c函数库中的函数也相继调用其中,也会在机器语言上得到体现,可以看出多出了puts@plt,printf@plt,getchar@plt,exit@plt,sleep@plt等函数的执行语句代码,其中部分如下图所示:
图21 部分c函数库的反汇编部分
5.6 hello的执行流程
逐步执行程序,记录每一个call调用的函数,并调出symbolviewer进行参照
图24 操作流程
如下表所示:调用顺序为从上至下
地址 | 函数名称 |
0x401125 | hello!main |
0x401090 | hello!.plt+0x70 |
0x401030 | hello!puts@plt |
0x401020 | hello!.plt |
7f14cedae5d0 | libc_2.31.so!_IO_file_xsputn |
7f14cedafd80 | libc_2.31.so!_IO_file_overflow |
7f14cedb0e80 | libc_2.31.so!_IO_doallocbuf |
7f14ceda0c70 | libc_2.31.so!_IO_doallocate |
5.7 Hello的动态链接分析
查看表头,发现以下信息:
直接进入edb,找到0x404000地址,发现如下图所示,这是在 .init之前的地址:
图25 init之前的地址
开始执行程序,在init之后,可以发现地址变化成了如下所示:
图26 init之后的地址
动态链接是把整个程序分成很多块,拆分成相对独立的几个部分,在程序运行的时候才会进行链接拼接。
ELF在动态连接时,会用PLT(procesure linkage table)的方式来进程链接其他模块的函数。就是不会把所有的函数都链接好,而是在第一次去调用的时候去连接。
需要动态链接的函数,都会以别名的方式放在.plt这个段,他们指向的位置是.plat.got 位置。如果地址已经确定,就直接执行函数;如果没有确定好,就会回到.plt的开头位置去调用链接器ld来把地址确定好之后,再执行具体的函数了
5.8 本章小结
本章具体介绍了生成可执行程序hello后的反汇编情况,分析了其与hello.o反汇编的区别,并在函数调用具体过程、动态链接等方面进行了细致的分析。
第6章 hello进程管理
6.1 进程的概念与作用
进程的概念:[9]
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
进程的作用:
进程提供给程序两个关键的抽象:
1. 一个独立的逻辑控制流系统,能使系统产生假象,如图这个程序在独占处理器
2. 一个私有的空间地址
6.2 简述壳Shell-bash的作用与处理流程
Shell的概念:
Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言。
Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。
Shell的处理流程:
按照我们第四个实验自己编写的tinyshell可以大致得出shell的工作流程:首先对用户输入命令进行分割,划分成不同的对应块;随后判断用户输入命令行是否为内置命令,如果是内置命令则立即执行(fg、bg、jobs、quit);如果不是内置命令,则是一个可执行程序,则会调用fork函数创建子进程,再用execve函数对程序实行执行操作;shell还会对键盘输入的信号进行处理。
6.3 Hello的fork进程创建过程
当shell判断输入命令是一个可执行程序后(一般为 ./ 开头的命令),父进程会调用fork函数创建一个新的子进程。Fork()函数的返回值为一个int类型值,子进程中fork返回0,父进程中fork返回子进程的pid。Fork函数执行后,子进程会得到一个父进程虚拟地址空间相同的(但是独立的)一个副本,但是拥有不同的pid。
6.4 Hello的execve过程
execve 函数加载并运行可执行目标文件hello,execve 调用一次并从不返回
函数拥有如下的原型:
int main(intargc , char **argv , char *envp);
进程步骤大致如下:
1. 删除已存在的用户区域
2. 映射私有区:为 hello 的代码、数据、.bss 和栈区域创建新的区域结构,所有这些区域都是私有的、写时才复制的
3. 映射共享区:比如 hello 程序与共享库 libc.so 链接
4. 设置 PC:exceve() 做的最后一件事就是设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点
6.5 Hello的进程执行
6.5.1 逻辑控制流:
逻辑控制流是程序创造的第一个抽象,他让程序看起来像是在独占进程,实则不然,是多个进程轮流使用处理器,每个进程执行它的流的一部分,如果被抢占了的话就会被挂起进行其他流的处理。
6.5.2 并发流:
当一个逻辑流在时间线上与另一个正在执行的逻辑流发生重合,则成为了并发流。一个进程执行他的一个时间片段称为一个时间片
6.5.3 内核模式和用户模式:
用户模式:在用户模式中,进程不允许执行特权指令,不允许直接引用地址空间中内核区的代码和数据
内核模式:在内核模式中,可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。犹如windows系统下的执行管理员权限,和Linux系统中(ubuntu)在输入命令前加上sudo指令
6.5.4 上下文切换:
内核为每一个进程维持一个上下文。
上下文切换:
1)保存当前进程的上下文
2)回复某个先前被抢占的进程被保存的上下文
3)将控制传递到这个新恢复的进程
Hello进程的执行:
图27 hello进程的执行
6.6 hello的异常与信号处理
6.6.1 异常类型:
① 异步异常:
中断Interrupts:
·处理器外部I/O设备引起
▪ 由处理器的中断引脚指示
▪ 中断处理程序返回到下一条指令处
图28 中断
② 同步异常:
陷阱 (Traps):
·有意的,执行指令的结果(发生时间可预知)
·陷阱处理程序将控制返回到下一条指令
图29 陷阱
故障 (Faults):
·不是有意的,但可能被修复
·Examples: 缺页故障(可恢复),保护故障(protection faults,不可恢复), 浮点异常(floating point exceptions)
·处理程序要么重新执行引起故障的指令(已修复),要么终止
图30 故障
终止 (Aborts):
·非故意,不可恢复的致命错误造成
·Examples: 非法指令,奇偶校验错误(parity error),机器检查(machine check)到致命的硬件错误
·中止当前程序
图31 终止
6.6.2 hello程序受到异常的情况:
① 乱按+空格+回车:
6.7本章小结
本章详细介绍了进程管理的相关事项。从何为进程,shell的应用,具体细化到hello这个可执行文件开始执行后产生的进程情况,再到hello进程面对异常和信号的处理方式。让我们进一步了解了Linux系统下进程的开始和终结过程。
第7章 hello的存储管理
7.1 hello的存储器地址空间
现代计算机中,使用了虚拟空间技术,有如下好处:
1)有效使用主存:使用DRAM作为部分虚拟地址空间的缓存
2)简化内存管理:每个进程都使用统一的线性地址空间
3)独立地址空间:一个进程不能影响其他进程的内存
用户程序无法获取特权内核信息和代码
·逻辑地址:[10]指应用程序角度看到的内存单元(memory cell)、存储单元(storage element)、网络主机(network host)的地址。逻辑地址往往不同于物理地址(physical address),通过地址翻译器(address translator)或映射函数可以把逻辑地址转化为物理地址。
·线性地址: 线性地址(Linear Address)是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。
·虚拟地址: CPU启动保护模式后,程序运行在虚拟地址空间中。注意,并不是所有的“程序”都是运行在虚拟地址中。CPU在启动的时候是运行在实模式的
·物理地址: 真实的内存地址,在地址总线上,通过寻址访问
7.2 Intel逻辑地址到线性地址的变换-段式管理[11]
逻辑地址中分为两部分,一部分是段选择符,另一段是段内偏移量;段式管理的意义就是分段过程,也就是从逻辑地址到线性地址的一个变换
图35 逻辑地址到线性地址
逻辑地址有48位,前16位的段选择符,后32位的段内偏移量
段选择符存在寄存器中,每个进程的每一个段都有相应段的段选择符寄存器。从其中可以拿到段基址。
将段基址与段内偏移量的相应数值相加,就可以得到对应的线性地址。
这种操作叫做段式管理
7.3 Hello的线性地址到物理地址的变换-页式管理
页式管理是一种内存空间存储管理的技术。
页式管理分为静态页式管理和动态页式管理。
将各进程的虚拟空间划分成若干个长度相等的页(page),页式管理把内存空间按页的大小划分成片或者页面(page frame),然后把页式虚拟地址与内存地址建立一一对应页表,并用相应的硬件地址变换机构,来解决离散地址变换问题。
页式管理采用请求调页或预调页技术实现了内外存存储器的统一管理。
图36 分配页面
页表:存放于主存中,记录进程的逻辑页与主存中的物理块的对应关系,实现从页号到物理块号的地址映射,每一页对应一个表目,页表的长度和页表的主存起始地址存放于PCB中。
用页号去页表中寻找对应的项,由此找到物理块号,再加上本身带的页内偏移量,就可以得到物理地址
7.4 TLB与四级页表支持下的VA到PA的变换[12]
现代cpu中都有一个TLB(Translation Lookaside Buffer),可以翻译为翻译后备缓冲器,也可以译为“快表”。
VA:virtual address称为虚拟地址
PA:physical address称为物理地址
进入main函数时,CPU发出的内核信号将被MMU(memory management unit,内存管理单元)截获,MMU的作用是将这个信号从虚拟地址映射到物理地址之中。
CPU首先将VA发送来的32位信号分为三段,前两段是两次查表的索引,第三段是页内偏移量。前两次查表确定物理页面的基地址,得出该地址后再加上第三段的偏移量既可以找到相应地址并取出相应地址上的数据
图37 TLB
7.5 三级Cache支持下的物理内存访问[13]
CPU成功获得物理地址后,将物理地址拆分为标记位、组索引、块偏移三部分。首先L1cache通过组索引找到所在的组,比较每一个cacheline是否是一个有效的标记位;如果有效,则直接返回所需要的值;如果无效,则去L2 、L3、主存中继续寻找。匹配成功后,向上一级逐渐返回,直至返回至L1。
图38 三级cache
7.6 hello进程fork时的内存映射
Fork函数调用执行时,内核为新进程创建虚拟内存、创建各种数据结构、分配新的pid、创建当前子进程的mm_struct、区域结构和页表的原样副本。
它将两个进程中的每个页面都标记位只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
Fork返回时,他的虚拟内存地址和最开始创建时的虚拟内存地址相同。完成了一次内存的“映射”。
7.7 hello进程execve时的内存映射
先来看一下execve函数的结构:
int execve(char *filename, char *argv[], char *envp[])
他的作用是在当前进程中载入并运行程序
三个参数的意义及示例如下:
1)filename:可执行文件目标文件或脚本(用#!指明解释器,如 #!/bin/bash)
2) argv : 参数列表,惯例: argv[0]==filename
3) envp: 环境变量列表
·name=value" strings (e.g., USER=droh)
·getenv, putenv, printen
他的作用是覆盖当前进程的代码、数据、栈;保留相同的pid,继承已经打开的文件描述符和信号上下文,调用一次并且从不返回;设置程序计数器指向代码入口。
7.8 缺页故障与缺页中断处理
7.8.1 缺页故障的定义:
Page fault缺页: 引用虚拟内存中的字,不在物理内存中 (DRAM 缓存不命中)
图39 缺页故障
7.8.2 缺页中断的处理:
① 选择一个牺牲页
图40 牺牲页
②导致缺页的指令重新启动,导致页面命中
图41 重新启动
7.9动态存储分配管理
通常定义变量(或对象),编译器在编译时都可以根据该变量(或对象)的类型知道所需内存空间的大小,从而系统在适当的时候为他们分配确定的存储空间。这种内存分配称为静态存储分配;有些操作对象只在程序运行时才能确定,这样编译时就无法为他们预定存储空间,只能在程序运行时,系统根据运行时的要求进行内存分配,这种方法称为动态存储分配。所有动态存储分配都在堆区中进行。
7.9.1 malloc函数:
void *malloc(unsigned long size);
malloc 是一个系统函数,它是 memory allocate 的缩写。其中memory是“内存”的意思,allocate是“分配”的意思。顾名思义 malloc 函数的功能就是“分配内存”。malloc 函数只有一个形参,并且是整型。该函数的功能是在内存的动态存储空间即堆中分配一个长度为size的连续空间。函数的返回值是一个指向所分配内存空间起始地址的指针,类型为 void*型。
7.9.2 free函数:
void free(void *p);
通过malloc函数创造的动态空间,释放的方法职能由程序员手动释放。这里就要用到free函数。通过动态存储创建的空间在使用完成后一定要及时的释放,而且只能释放一次。所以free函数和malloc函数一定是成对出现。
7.10本章小结
本章主要详细介绍了hello文件的储存情况。详细阐述了段式管理、页式管理、VA到PA的变化过程、物理内存访问、fork进程、execve进程、中断操作管理、动态存储管理等细节知识。对深入了解Linux程序进程一生起到促进作用。
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
Linux 系统和其他 UNIX 系统一样,IO 管理比较直接和简洁。所有 IO 设备都被当作文件,通过在系统内部使用相同的 read 和 write 一样进行读写。
8.2 简述Unix IO接口及其函数[14]
8.2.1 函数open()和openat():
·函数原型:
int open(const char* path, int oflag, .../*mode_t mode*/);
int openat(int fd, const char* path, int oflag, .../*mode_t mode*/)
·函数功能:打开文件
其中有很多文件打开模式:
图42 open函数的打开模式
8.2.2 函数create():
·函数原型:
int create(const char *path, mode_t mode);
·函数功能:文件创建
8.2.3 函数close():
·函数原型:
int close(int fd);
·函数功能:关闭文件
8.2.4 lseek()函数:
·函数原型:
int lseek(int fd, off_t offset, int whence);
·函数功能:使用lseek()函数显式的为一个打开的文件设置偏移量。lseek仅将文件的偏移量记录在内核中,并不引起IO开销。
8.2.5 read()函数:
·函数原型:
ssize_t read(int fd, void *buf, size_t nbytes);
·函数功能:读取一个文件中期待的那么多字节数量
8.2.6 write()函数:
·函数原型:
ssize_t write(int fd, const void* buf, size_t ntyes);
·函数功能:写入期待中的那么多个字节数量
8.3 printf的实现分析[15]
Printf函数:
图43 printf函数
可以看到其中调用了一个vsprintf函数:
图44 vsprintf函数
Write函数:
图45 write函数
Printf函数想要做什么:它接受一个格式化的命令,并把指定的匹配的参数格式化输出。
Vsprintf韩硕想要做什么:vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出
8.4 getchar的实现分析
下图位getchar源码:
图46 getchar函数源码
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章介绍了unix io函数以及printf函数和getchar函数的底层实现
结论
现在来总结一下hello.c程序的一生吧:
1. 经过预处理,从hello.c变成了hello.i
2. 经过编译,从hello.i变成了hello.s
3. 经过汇编,从hello.s变成了可重定位文件hello.o
4. 经过链接,从hello.o变成了初级完全体hello的可执行文件
5. 通过shell,按照格式输入./hello 7203610731 李行 1 让程序开始执行
6. 现在轮到shell开始表演,首先分析输入命令,并作出相应操作:发现是一个可执行程序,调用fork函数生成子进程,再调用execve函数执行生成的子程序
7. CPU也没有闲着。内存管理单元、TLB、多级页表机制、三级cache协同工作,完成对地址的翻译和请求
8. 运行至printf时,会产生malloc动态内存管理的调用,还会参与到unix IO函数的调用
9. 最后父进程对子进程进行回收,hello至此也结束了他的一生
正所谓见微知著,小中见大。从一个20来行的hello.c程序在Linux系统中的一生,他也带着我们遨游了一次Linux系统下,程序普遍运行的规律。每一步都仅仅有条,井然有序,不慌不忙;就算是再复杂的程序,也会遵守相同的规律。这是秩序,这是均衡,这是现代科学思维的力量!
再见!hello!
附件
c源码 | |
hello.i | 预处理文件 |
hello.s | .c文件的汇编语言文件 |
hello.o | 可重定位目标文件 |
hello_o.elf | hello.o的elf文件 |
objdump_hello_o.s | hello.o的反汇编文件 |
hello | 可执行程序hello |
hello.elf | hello的elf文件 |
objdump_hello.s | hello的反汇编文件 |
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] 林来兴. 空间控制技术[M]. 北京:中国宇航出版社,1992:25-42.
[2] 辛希孟. 信息技术与信息服务国际研讨会论文集:A集[C]. 北京:中国科学出版社,1999.
[3] 赵耀东. 新时代的工业工程师[M/OL]. 台北:天下文化出版社,1998 [1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).
[4] 谌颖. 空间交会控制理论与方法研究[D]. 哈尔滨:哈尔滨工业大学,1992:8-13.
[5] KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.
[6] CHRISTINE M. Plant Physiology: Plant Biology in the Genome Era[J/OL]. Science,1998,281:331-332[1998-09-23]. http://www.sciencemag.org/cgi/ collection/anatmorp.
[7] 汇编指令大全 CSDN 汇编语言指令大全_Andrewniu的博客-CSDN博客_汇编指令大全
[8] 链接器 百度百科
[9] 进程 百度百科
[10] 逻辑地址、线性地址和物理地址的关系 CSDN
逻辑地址、线性地址和物理地址的关系_5G砖家的博客-CSDN博客_逻辑地址和物理地址的关系
[11] 页式存储管理和段式存储管理(学习笔记) CSDN
页式存储管理和段式存储管理(学习笔记)_所谓玄学的博客-CSDN博客_页式存储管理与段式存储管理
[12] 计组复习(四):cache,虚拟内存,页表与TLB CSDN
计组复习(四):cache,虚拟内存,页表与TLB_AkagiSenpai的博客-CSDN博客_cache和页表
[13] CUP 三级缓存L1 L2 L3 cahe详解 CSDN
CUP 三级缓存L1 L2 L3 cahe详解_步基的博客-CSDN博客_cpu的三级缓存
[14] Unix文件IO相关函数 CSDN
Unix文件IO相关函数_langzi989的博客-CSDN博客
[15] printf函数实现的深入剖析
https://www.cnblogs.com/pianist/p/3315801.html