本文对hello程序的生命周期进行分析,从预处理开始对编译、汇编、链接、生成可执行文件并运行等一系列过程进行探讨,并且对进程管理、存储管理和I/O管理的全过程进行了分析。
关键词:预处理,编译,汇编,链接,程序运行,进程管理,存储管理,I/O管理
目 录
第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 -
第7章 hello的存储管理...................................................... - 11 -
7.1 hello的存储器地址空间................................................... - 11 -
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 -
7.9动态存储分配管理.............................................................. - 11 -
7.10本章小结............................................................................ - 12 -
第8章 hello的IO管理....................................................... - 13 -
8.1 Linux的IO设备管理方法................................................. - 13 -
8.2 简述Unix IO接口及其函数.............................................. - 13 -
8.3 printf的实现分析.............................................................. - 13 -
8.4 getchar的实现分析.......................................................... - 13 -
8.5本章小结.............................................................................. - 13 -
参考文献....................................................................................... - 16 -
第1章 概述
1.1 Hello简介
P2P:
1. 在IDE中编写代码,得到hello.c的源程序
2. 预处理器cpp生成hello.i
3. 编译器ccl将生成汇编程序hello.s
4. 汇编器as将hello.s生成可重定位目标文件hello.o
5. 链接器ld将hello.o和printf.o组合起来,生成可执行目标文件hello
6. 在shell中输入./hello,就可以fork一个新的子进程,调用execve函数将程序加载到新的子进程中,以此来完成P2P,即program to process
020:
- 在创建新的进程之后,使用execve将程序载入,然后进行虚拟内存映射。
- CPU为这个进程分配时间片,执行对应的逻辑控制流。
- 程序运行结束后,进程向父进程发送一个SIGCHLD信号,随后父进程会对该进程进行回收,最终释放相关内存,完成020
1.2 环境与工具
硬件环境:CPU:Intel(R) Core(TM) i7-10510U CPU @ 1.80GHz 2.30 GHz
软件环境:Windows 10;VMware® Workstation 16 Pro; Ubuntu 20.04
开发与调试工具:codeblocks;Objdump
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
中间结果:
hello.i hello.c预处理得到的文本文件
hello.s hello.i编译程序的汇编文件
hello.o hello.s得到的可重定位目标文件
hello 链接得到的可执行目标文件
1.4 本章小结
本章介绍了hello的P2P,020过程,并且对环境与工具及过程中的中间文件做了分析
第2章 预处理
2.1 预处理的概念与作用
(以下格式自行编排,编辑时删除)
概念:预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。
作用:使得程序便于阅读、修改、移植和调试,也有利于模块化程序设计
2.2在Ubuntu下预处理的命令
预处理命令:gcc -E hello.c -o hello.i
图1-预处理结果
2.3 Hello的预处理结果解析
如下图所示
图2-预处理产物内容
原来在.c文件中很少的几行在.i文件中变成3060行,因为预处理器在处理过程中实现了头文件的展开,宏的替换,并删除了注释信息。这就导致行数大大增加。而在最后才是我们原本的程序,与.c文件中的相差不大。
2.4 本章小结
本章主要介绍了预处理的概念与作用,使用预处理指令对目标文件进行处理并查看,验证了预处理中的过程,即头文件的展开、宏替换、去掉注释。
第3章 编译
3.1 编译的概念与作用
概念:编译过程以高级程序设计语言书写的源程序作为输入,而以汇编语言或机器语言表示的目标程序作为输出
作用:将预处理后的程序编译为更接近计算机语言,更容易让计算机理解的语言
3.2 在Ubuntu下编译的命令
编译命令:gcc -S hello.i -o hello.s
图3-编译结果
3.3 Hello的编译结果解析
3.3.1 数据
1.常量
(1)字符串
图4-字符串
如图,存在两个常量,均是字符串,分别是“用法: Hello 学号 姓名 秒数!\n”与“Hello %s %s\n”,他们是printf函数的参数。
2. 变量
(1)局部变量i
图5-局部变量
程序在这里定义了一个局部变量i,它被保存在-4(%rbp)内。
(2)数组变量
图6-数组变量
main函数的参数argc与argv,被存放在寄存器%edi和%esi中,接着他们被存放在-20(%rbp)和-32(%rbp)中。
3.3.2 赋值操作
1. i=0
图7-赋值操作
在这里i的值被赋为0.
2. i++
图8-赋值操作
在这里i的值随着每次循环会加上1
3.3.3 类型转换
c程序中,语句atoi(argv[3])使得字符型强制转换为整形。
3.3.4 算术操作
图9-算术操作
使用到的算术操作是i++,使用addl指令addl $1, -4(%rbp)
3.3.5 关系操作
1. argc!= 4
图10-关系操作
该语句被编译为cmpl $4, -20(%rbp)。比较完成后会设置条件码,凭借条件码判断跳转位置。
2. i<8
图11-关系操作
该语句被编译为cmpl $7, -4(%rbp),其作用是作为for循环结束的条件,比较完成后会设置条件码,凭借条件码判断跳转位置。
3.3.6 数组/指针/结构操作
1. 数组存在一个argv[],是一个指针数组
3.3.7 控制转移
1. if语句
if(argc!=4),如果满足括号内的内容,就执行if内的语句,否则跳过if内的语句。
图12-if语句
2. for循环
在for循环中,首先给i赋值为0,然后跳转到L3中。
图13-for循环
在L3内,会对i的值与8进行对比,如果满足i<8,就跳转到L4中,然后继续执行下面的指令。
图14-for循环
3.3.8 函数操作
1. main函数
main函数的参数是argc和*argv。argc存储在%edi中,argv存储在%rsi中。返回值存储在%eax中,如下图所示
图15-main函数
2. printf函数
在main函数中调用了两次printf函数,他们传入的参数各不相同。
第一个printf参数传入的是.LC0中的字符串
图16-printf函数
第二个printf传入的是.LC1处的字符串,argv[1],argv[2]的值
图17-printf函数
前面几行是在从-32(%rbp)中取argv[1],argv[2]的值,41行是传入字符串。
3.exit函数
该函数实现从main函数退出。传入的参数是1,表示非正常退出。
图18-exit函数
4. atoi函数
该函数实现将字符串类型转变成int类型,传入的参数为argv[3],前面几行在取argv[3]的值。
图19-atoi函数
5. sleep函数
该函数实现程序休眠,传入的参数为atoi(argv[3]),具体内容同上。
图20-sleep函数
6. getchar函数
getchar函数实现读取缓冲区字符,直接调用即可。call getchar@PLT
3.4 本章小结
本章解释了编译的原理与作用,并对编译出来的结果进行逐条分析,展示了编译语句的代码,对各部分编译结果有了更深一层的认识。
第4章 汇编
4.1 汇编的概念与作用
概念:把汇编语言翻译成机器语言的过程称为汇编
作用:过程将汇编代码转换为计算机能够理解并执行的二进制机器代码。
4.2 在Ubuntu下汇编的命令
汇编命令:gcc hello.s -c -o hello.o
图21-汇编结果
4.3 可重定位目标elf格式
4.3.1 ELF头
命令如下:readelf -h hello.o
图22-ELF头
开头是一个16字节的magic序列,描述了系统的字的大小和字节顺序。剩下包含帮助链接器语法分析和解释目标文件的信息。其中包括ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,以及节头部表中条目的大小和数量。
4.3.2 节头
命令如下:readelf -S hello.o
图23-节头
节头部分记录了各节的名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐等信息。使用节头表中的字节偏移信息可以得到各节在文件中的起始位置,以及各节所占空间的大小,这样方便重定位。
4.3.3 重定位节
命令如下:readelf -r hello.o
图24-重定位节
重定位节中包含了.text 节中需要进行重定位的信息,我们可以发现需要重定位的函数有: .rodata, puts, exits, printf, atoi, sleep, getchar
4.4.4 符号表
命令如下:readelf -s hello.o
图25-符号表
符号表存放了程序中定义和引用的函数和全局变量的信息
4.4 Hello.o的结果解析
hello.o的反汇编代码如下:
图26-反汇编代码
反汇编代码中,除了.s文件中已经出现过的代码,还包含了它们对应的机器语言的代码。
比如说分支转移结构中,hello.s表示为
图27-分支转移结构
而在hello.o中表示为
图28-分支转移结构
这是因为.s文件中可以用段名称L3来进行助记,而在.o文件中需要它的真实地址以便于下一步操作。
而在函数调用方面,.s文件在call后可直接跟上函数名称,如call printf@PLT,但是.o文件call 后跟的是一条重定位条目指引的信息,如callq 62 <main+0x62>
4.5 本章小结
本章介绍了汇编的概念和作用,讲述了可重定位文件的ELF头,节头,重定位节和符号表的内容。同时比较了反汇编结果与.s文件的区别。
第5章 链接
5.1 链接的概念与作用
概念:链接是将各种代码和数据片段收集并组合成为一个单一文件的过程
作用:使得分离编译成为可能。不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为更小、更好管理的模块,可以独立地修改和编译这些模块。
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
图29-链接结果
5.3 可执行目标文件hello的格式
5.3.1 ELF头
hello与hello.o的ELF头大致相同,不同之处在于hello的类型为EXEC可执行文件,有27个节
图30-ELF头
5.3.2 节头
与.o的节头不同的是,在这里每一节都有了实际地址,说明重定向已经完成。
图31-节头
5.3.3 重定位节
图32-重定位节
5.3.4 符号表
图33-符号表
5.4 hello的虚拟地址空间
从5.3的程序头来看,LOAD可加载的程序段第一段的地址为0x400000,那么虚拟内存地址从由0x401000开始,我们从Data Dump里面看,最后得到0x401ff0结束
图34- hello的虚拟地址空间
5.5 链接的重定位过程分析
使用的命令:objdump -d -r hello > hello2.txt
我们比较hello1和hello2之后可得出以下不同:
- hello1的反汇编代码的地址从0开始,而hello2的反汇编代码从0x400000开始。这说明hello1还未实现重定位,采用的是相对偏移地址,而hello2已经实现了重定位,采用的是虚拟地址。
图35-hello1反汇编代码
图36-hello2反汇编代码
- hello2中多了很多别的函数的汇编代码,比如说.init节
图37-hello2反汇编代码
重定位的过程:
- 重定位节和符号定义链接器将所有类型一致的节合并,赋予新的地址,那么程序中每条指令和全局变量都有唯一运行时的地址
- 如果遇见未知目标的引用,那么就新生成一个重定位条目。
5.6 hello的执行流程
名称 地址
ld-2.31.so!_dl_start 0x7f8e7cc34ed0
ld-2.31.so!_dl_init 0x7f8e7cc486a0
libc-2.31.so!_libc_start_main 0x7ff 825425fc0
hello!_start 0x4010f0
libc-2.31.so!_cxa_atexit 0x7ff 825448f60
hello!_libc_csu_int 0x4011c0
libc-2.31.so!_setjmp 0x7ff 82fdb2e00
libc-2.27.so!exit 0x7ff 82fdc3bd0
5.7 Hello的动态链接分析
动态链接器使用过程链接表PLT和全局偏移量表GOT实现函数的动态链接。查阅节头可以得知,.got.plt起始位置为0x404000
在调用之后该处的信息发生了变化
图38-调用前Data Dump
图39-调用后Data Dump
5.8 本章小结
本章介绍了链接的概念及作用,展示了hello的elf,分析了虚拟地址,分析了hello的重定位过程、执行流程、动态链接过程。
(以下格式自行编排,编辑时删除)
第6章 hello进程管理
6.1 进程的概念与作用
概念:进程是一个执行中的程序的实例,是系统进行资源分配和调度的基本单位。
作用:向用户提供了一种假象。 我们的程序好像是系统中当前运行的唯一程序一样,我们的程序好像是独占的使用处理器和内存。
6.2 简述壳Shell-bash的作用与处理流程
作用:Shell是一个交互型应用级程序,为使用者提供操作界面,接收命令,然后调用相应的程序。
处理流程:
1.终端进程读取命令行。
2.分析命令行,获取命令行参数,并构造传递给execve的argv
3.检查第一个命令行参数是否是一个内置的shell命令,如果不是内部命令,调用fork( )创建新进程/子进程
4.在子进程中,用步骤2获取的参数,调用execve( )执行指定程序。
5.如果用户没要求后台运行否则shell使用waitpid等待作业终止后返回。
6.如果用户要求后台运行,则shell返回。
6.3 Hello的fork进程创建过程
当在shell上输入./hello命令时,命令行会首先判断是否为内置命令,如果是内置命令则立即对其进行解释。否则会调用fork创建一个新进程并在其中执行。
6.4 Hello的execve过程
1. 删除已存在的用户区域。删除之前进程在用户部分中已存在的结构。
2. 映射私有区域。创建新的代码、数据、堆和栈段。虚拟地址空间的代码和数据区域被映射为hello文件的.txt和.data区。bss区域是请求二进制零的,映射匿名文件,其大小包含在hello文件中。栈和堆区域也是请求二进制零的,初始长度为零。
- 映射共享区域。如果hello程序与共享对象链接,比如标准C库libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域。
4. 设置程序计数器(PC)。
6.5 Hello的进程执行
hello的进程执行过程如下:
上下文信息:内核在一个进程正在运行的时候,调度了另一个新的进程运行后,它就抢占当前进程,并使用上下文切换来控制转移到新的进程。我们需要保存以前进程的上下文,恢复新恢复进程被保存的上下文,将控制传递给这个新恢复的进程来完成上下文切换。
进程时间片:进程执行它的控制流的一部分的每一时间段。
进程调度的过程:在进程执行时,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这种行为就叫做调度。
用户态与核心态转换:进程只有故障、中断或陷入系统调用时才会得到内核访问权限,否则将一直处于用户权限之中,以保证系统的安全性。
6.6 hello的异常与信号处理
出现的异常有:
图40-异常类型与处理
6.6.1 正常情况
图41-程序正常运行
6.6.2 不停乱按
1. 不包括回车
只会在屏幕上显示输入的内容
图42-运行过程中乱按
2. 回车
getchar读回车,回车前的字符串当作shell输入的命令
图43-运行过程中按回车
2.Ctrl-Z,Ctrl-C
Ctrl-Z如下图:使用SIGTSTP信号,停止前台作业
图44-运行过程中按ctrl-z
Ctrl-C如下图:使用SIGINT信号终止前台进程。
图45-运行过程中按ctrl-c
6.6.3 Ctrl-z后运行ps jobs pstree fg kill 等命令
图46- Ctrl-z后运行ps jobs pstree fg kill 等命令
6.7本章小结
本章了解了hello进程的执行过程,展示了hello进程的执行以及hello的异常和信号处理。
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:程序经过编译后出现在汇编代码中的地址,用来指定一个操作数或者是一条指令的地址。是由一个段标识符加上一个指定段内相对地址的偏移量。
线性地址:逻辑地址向物理地址转化过程中的一步
虚拟地址:就是线性地址
物理地址:CPU外部地址总线上的地址信号,是地址变换的最终结果地址
7.2 Intel逻辑地址到线性地址的变换-段式管理
逻辑地址表示为[段标识符:段内偏移量],段标识符是一个16位长的字段(段选择符)。在获得了一个逻辑地址之后,看段选择符的T1=0/1,明确待转换的段在GDT中,还是在LDT中,就可以根据相应寄存器,得到其地址和大小。
拿出段选择符中前13位,在这个数组中,查找到对应的段描述符,就可以得到Base,把Base + offset,就可以获得需要的线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
虚拟地址用VA来表示。处理虚拟地址即处理线性地址。VA分为虚拟页号(VPN)与虚拟页偏移量(VPO),CPU取出VPN,通过页表基址寄存器来定位页表条目,在有效位为1时,从页表条目中取出信息物理页号(PPN),通过将物理页号与虚拟页偏移量(VPO)结合,得到由物理地址(PPN)和物理页偏移量(PPO)组合的物理地址。
7.4 TLB与四级页表支持下的VA到PA的变换
每次CPU产生一个虚拟地址,MMU(内存管理单元)就必须查阅一个PTE(页表条目),以便将虚拟地址翻译为物理地址。在最糟糕的情况下,这会从内存多取一次数据,代价是几十到几百个周期。如果PTE碰巧缓存在L1中,那么开销就会下降1或2个周期。然而,许多系统都试图消除即使是这样的开销,它们在MMU中包括了一个关于PTE的小的缓存,称为翻译后备缓存器(TLB)。
多级页表:将虚拟地址的VPN划分为相等大小的不同的部分,每个部分用于寻找由上一级确定的页表基址对应的页表条目。VPN被分为k个部分,第一级VPN结合基址寄存器得到一个页表条目,其中存放下一级页表的基址,再结合VPN2,得到第三级页表基址,继续寻找,以此类推,直到最后确定对应的物理页号,如下图所示:
图47-多级页表
这样的话,在多级页表的情况下,不断通过索引 – 地址 – 索引 - 地址重复进行寻找就行了。
7.5 三级Cache支持下的物理内存访问
得到物理地址后,根据物理地址从cache中寻找。到了L1后,寻找物理地址检测是否命中,不命中则紧接着寻找下一级cache L2,接着L3,如果L3也不命中,则需要从内存中将对应的块取出放入cache中,直到出现命中。
7.6 hello进程fork时的内存映射
在fork被调用来创建子进程后,会为hello程序的运行创建各种数据结构,并分配一个与父进程不同的PID。同时为这个新进程创建虚拟内存,创建当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记位只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面。
7.7 hello进程execve时的内存映射
在6.4节中已经给出,如下:
1. 删除已存在的用户区域。删除之前进程在用户部分中已存在的结构。
2. 映射私有区域。创建新的代码、数据、堆和栈段。虚拟地址空间的代码和数据区域被映射为hello文件的.txt和.data区。bss区域是请求二进制零的,映射匿名文件,其大小包含在hello文件中。栈和堆区域也是请求二进制零的,初始长度为零。
3. 映射共享区域。如果hello程序与共享对象链接,比如标准C库libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域。
4. 设置程序计数器(PC)。
7.8 缺页故障与缺页中断处理
缺页故障是指:当指令引用一个相应的虚拟地址,而与改地址相应的物理页面不再内存中,会触发缺页故障。
缺页中断处理:
1)处理器生成一个虚拟地址,并将它传送给MMU
2)MMU生成PTE地址,并从高速缓存/主存请求得到它
3)高速缓存/主存向MMU返回PTE
4)PTE中的有效位是0,所以MMU出发了一次异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序。
5)缺页处理程序确认出物理内存中的牺牲页,如果这个页已经被修改了,则把它换到磁盘。
6)缺页处理程序页面调入新的页面,并更新内存中的PTE。
7)缺页处理程序返回到原来的进程,再次执行导致缺页的命令。CPU将引起缺页的虚拟地址重新发送给MMU。因为虚拟页面已经换存在物理内存中,所以就会命中。
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同的大小的块的集合来维护。每个块就是一个连续的虚拟内存片,这些内存片要么是已分配的,要么是空闲的。分配器有两种基本风格。1.显式分配器:要求应用显示的释放任何已分配的块。2.隐式分配器:要求分配器检测一个已分配块何时不再被程序使用,那么就释放这个块。
隐式空闲链表:
每个块是由一个字的头部、有效载荷、可能的额外填充以及一个字的尾部组成的,最后一位指明这个块是已分配的还是空闲的。具体的隐式空闲链表形式如下:
图48-隐式空闲链表
应用请求一个k字节的块时,分配器搜索空闲链表。查找一个足够大可以放置所请求的空闲块,找到一个匹配的空闲块时,通常将空闲块分割为两部分。第一部分变为了已分配块,第二部分变为了空闲块
显示空闲链表:
显示空闲链表是将空闲块组织为某种形式的显示数据结构。在每个空闲块中,都包含一个前驱和后继的指针。
图49-显式空闲链表
7.10本章小结
本章主要有关内存管理。介绍了hello程序的存储空间,如何得到最终的地址,介绍了hello的四级页表的虚拟地址空间到物理地址的转换,三级cashe的物理内存访问、进程 fork 时的内存映射、execve 时的内存映射、缺页故障与缺页中断处理、动态存储分配管理等内容。
第8章 helo的IO管理
8.1 Linux的IO设备管理方法
所有的I/O设备都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行,允许Linux内核引出一个简单低级的应用接口,称为Unix I/O。
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
Unix IO接口:
1.打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备,内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文件的所有信息。
2.Linux Shell创建的每个进程都有三个打开的文件:标准输入,标准输出,标准错误。
3.改变当前的文件位置。对于每个打开的文件,内核保持着一个文件位置k,初始为0,这个文件位置是从文件开头起始的字节偏移量,应用程序能够通过执行seek,显式地将改变当前文件位置k。
4.读写文件。一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n,给定一个大小为m字节的而文件,当k>=m时,触发EOF。类似一个写操作就是从内存中复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。
5.关闭文件。当应用完成了对文件的访问之后,它就通知内核关闭这个文件,作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放他们的内存资源。
Unix I/O函数:
1.int open(char* filename,int flags,mode_t mode) ,进程通过调用 open 函 数来打开一个存在的文件或是创建一个新文件的。open函数将filename 转换为一个文件描述符,并且返回描述符数字,返回的描述符总是在进程中当前没有打开的最小描述符,flags 参数指明了进程打算如何访问这个文件,mode参数指定了新文件的访问权限位。
2.int close(fd),fd 是需要关闭的文件的描述符,close 返回操作结果。
3.ssize_t read(int fd,void *buf,size_t n),read 函数从描述符为fd的当前文件位置赋值最多n个字节到内存位置 buf。返回值-1 表示一个错误,0表示EOF,否则返回值表示的是实际传送的字节数量。
4.ssize_t wirte(int fd,const void *buf,size_t n),write 函数从内存位置 buf 复制至多 n个字节到描述符为 fd的当前文件位置。
8.3 printf的实现分析
printf函数如下:
图50-printf函数
printf程序按照格式fmt结合参数args生成格式化之后的字符串,并返回字串的长度。而vsprintf函数作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。
write函数将buf中的i个元素写到终端。
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
getchar函数如下:
图51-getchar函数
当程序调用getchar时,程序等待用户按键,用户输入的字符被存放在键盘缓冲区中直到用户按回车(回车也在缓冲区中)。
当用户键入回车之后,getchar才开始从stdio流中每次读入一个字符。getchar函数的返回值是用户输入的第一个字符的ascii码,如出错返回-1,且将用户输入的字符回显到屏幕。如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。
8.5本章小结
本章主要介绍了Linux的IO设备管理方法、Unix IO接口及其函数,分析了printf和getchar函数的实现。
结论
hello的一生:
1. hello.c预处理到hello.i文本文件;
2. hello.i编译到hello.s汇编文件;
3. hello.s汇编到二进制可重定位目标文件hello.o;
4. hello.o链接生成可执行文件hello;
5.在终端里输入“./hello 7203610128 李昌昊 1”,判断输入命令是否为内置命令。经过检查后发现其不是内置命令,则shell将其当作程序执行
6. bash进程调用fork,生成子进程;
7. execve函数加载运行当前进程的上下文中加载并运行新程序
8.运行hello时,内存管理单元MMU、翻译后备缓冲器TLB、多级页表机制、三级cache协同工作,完成对地址的翻译和请求。
9. 调用sleep函数后进程休眠进入停止状态,调用完成后,内核再次进行上下文切换重新执行hello进程。
10.hello的输入输出与外界交互,与linux I/O息息相关;
11.hello最终被shell父进程回收,内核会收回为其创建的所有信息。
附件
中间产物如图所示:
hello.c:源程序
hello.i:预处理后的文本文件
hello.s:编译后的汇编文件
hello.o:汇编后的可重定位目标文件
hello1.txt:hello.o得到的反汇编文件
hello2.txt:hello得到的反汇编文件
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.