计算机系统大作业 程序人生-Hello’s P2P From Program to Process

计算机系统

大作业

计算机科学与技术学院
2019年12月
摘 要
本文叙述程序源代码通过预处理、编译、汇编、链接等步骤成为可执行程序、并通过进程管理、存储管理、IO管理等系统机制在操作系统中得以运行和结束的整个过程,解释了程序的“P2P”(From Program to Process)和“O2O”(From Zero-0 to Zero-0),以此回顾并串联了计算机系统各章节的知识。

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

目 录

第1章 概述 - 5 -
1.1 HELLO简介 - 5 -
1.2 环境与工具 - 5 -
1.3 中间结果 - 5 -
1.4 本章小结 - 6 -
第2章 预处理 - 7 -
2.1 预处理的概念与作用 - 7 -
2.2在UBUNTU下预处理的命令 - 7 -
2.3 HELLO的预处理结果解析 - 7 -
2.4 本章小结 - 8 -
第3章 编译 - 10 -
3.1 编译的概念与作用 - 10 -
3.2 在UBUNTU下编译的命令 - 10 -
3.3 HELLO的编译结果解析 - 10 -
3.3.1 常量 - 10 -
3.3.2 变量 - 11 -
3.3.3 赋值 - 11 -
3.3.4 运算 - 11 -
3.3.5 关系操作 - 12 -
3.3.6数组操作 - 12 -
3.3.7 控制转移 - 12 -
3.3.8 函数操作 - 13 -
3.4 本章小结 - 13 -
第4章 汇编 - 14 -
4.1 汇编的概念与作用 - 14 -
4.2 在UBUNTU下汇编的命令 - 14 -
4.3 可重定位目标ELF格式 - 14 -
4.3.1 elf头 - 15 -
4.3.2 节头部表 - 15 -
4.3.3 重定位条目 - 16 -
4.4.4 符号表 - 17 -
4.4 HELLO.O目标的结果解析 - 17 -
4.5 本章小结 - 18 -
第5章 链接 - 19 -
5.1 链接的概念与作用 - 19 -
5.2 在UBUNTU下链接的命令 - 19 -
5.3 可执行目标文件HELLO的格式 - 19 -
5.4 HELLO的虚拟地址空间 - 22 -
5.5 链接的重定位过程分析 - 24 -
5.6 HELLO的执行流程 - 24 -
5.7 HELLO的动态链接分析 - 25 -
5.8 本章小结 - 25 -
第6章 HELLO进程管理 - 26 -
6.1 进程的概念与作用 - 26 -
6.2 简述壳SHELL-BASH的作用与处理流程 - 26 -
6.3 HELLO的FORK进程创建过程 - 26 -
6.4 HELLO的EXECVE过程 - 26 -
6.5 HELLO的进程执行 - 26 -
6.6 HELLO的异常与信号处理 - 27 -
6.6.1 回车 - 27 -
6.6.2 Ctrl-Z - 27 -
6.6.3 Ctrl-C - 28 -
6.6.4不停乱按 - 28 -
6.7本章小结 - 28 -
第7章 HELLO的存储管理 - 29 -
7.1 HELLO的存储器地址空间 - 29 -
7.2 INTEL逻辑地址到线性地址的变换-段式管理 - 29 -
7.3 HELLO的线性地址到物理地址的变换-页式管理 - 29 -
7.4 TLB与四级页表支持下的VA到PA的变换 - 30 -
7.5 三级CACHE支持下的物理内存访问 - 32 -
7.6 HELLO进程FORK时的内存映射 - 32 -
7.7 HELLO进程EXECVE时的内存映射 - 33 -
7.8 缺页故障与缺页中断处理 - 34 -
7.9动态存储分配管理 - 34 -
7.10本章小结 - 35 -
第8章 HELLO的IO管理 - 36 -
8.1 LINUX的IO设备管理方法 - 36 -
8.2 简述UNIX IO接口及其函数 - 36 -
8.3 PRINTF的实现分析 - 37 -
8.4 GETCHAR的实现分析 - 37 -
8.5本章小结 - 38 -
结论 - 38 -
附件 - 39 -
参考文献 - 40 -

第1章 概述
1.1 Hello简介
P2P:
高级语言编写的程序源代码.c文件,经过预处理得到.i文件,由编译转换成汇编语言编写的.s文件,再由汇编器生成可重定位目标文件.o,和库文件链接,生成可执行程序。在shell中运行这个可执行程序,会分配进程空间,将可执行程序装入使其运行。从程序到进程,实现了P2P(From Program to Process)的过程。
O2O:
Shell中原本不存在这个程序,输入命令运行程序时,shell调用fork函数创建一个新的子进程,再调用execve使内核为它开辟空的空间和数据结构,将程序内容装入其中,这是从无到有的过程;当程序运行结束后,内核将清空它留下的数据和空间,逐步清除程序运行的痕迹,最终回到程序运行前的状态,这是从有到无的过程。从无到有再到无,实现了O2O(From Zero-0 to Zero-0)的过程。
1.2 环境与工具
硬件环境:Intel i5-4210H (4) @ 3.500GHz 8GBRAM
软件环境:Windows 10 64位,Arch Linux x86_64
开发工具:gcc,objdump,gdb ,readelf,sublime text
1.3 中间结果

  1. hello.i 预处理后的源代码文件
  2. hello.s hello.i编译后的汇编文件
  3. hello.o hello.s汇编后的可重定位目标文件
  4. hello hello.s与其他可重定位目标文件链接后的可执行程序
  5. hello.txt hello.o的反汇编代码
  6. hello1.txt hello的反汇编代码
  7. hello.elf hello.o的elf文件
  8. hello1.elf hello的elf文件
    1.4 本章小结
    本章简单描述了hello的P2P与O2O过程,说明了实验的准备工作和生成产物。

第2章 预处理
2.1 预处理的概念与作用
在计算机编程中,预处理是在进行下一步翻译之前对源代码执行处理的阶段。在c语言中,预处理是编译的第一个阶段,主要是根据代码中#开头的指令对代码进行分割、替换等处理,这些指令被称为预处理指令。预处理的结果是生成.i文件。
预处理的作用主要有以下几种。一是文件包含,即只要一条指令就相当于另一个文件中的很多内容,方便在不同源文件中调用相同的内容;二是宏定义,即define指令,把代码中的某一段文本替换成另一段文本,有减少大量重复代码、避免出现常数等作用;三是条件编译,即根据环境条件判断某段代码该不该编译。除此之外,还有删除注释、添加行号和标记、添加控制信息等作用。经过预处理的代码变得简单直接,适合进一步编译成机器语言。
2.2在Ubuntu下预处理的命令
gcc -m64 -no-pie -fno-PIC -E hello.c -o hello.i

2.3 Hello的预处理结果解析

可以看出,程序的主要部分被保留成最简单的格式,删去了所有注释,位于.i文件的第3023行;而前面包含头文件的预处理指令则变成了大段头文件本身的内容,并递归地添加头文件中又包含的文件,直到编译所需的所有代码都被包含在内。文件添加了行号便于编译时分析。
2.4 本章小结

本章解释了预处理的概念和作用,并通过hello.i这一例子展示了预处理如何对待程序源代码,体现出预处理后的文件更加简单、直接、全面,有利于进一步编译,说明了预处理的好处和重要性。

第3章 编译
3.1 编译的概念与作用
C语言程序编译过程内的“编译”步骤,是指将.i形成的简单直接的c语言码经过分析并优化,翻译成一份汇编语言代码,得到的结果为.s文件。
编译的作用是把c语言翻译成汇编语言并进行优化,使其更加接近机器二进制语言,得到更简单明了和更符合机器运作规律的逻辑,方便下一步的汇编步骤。

3.2 在Ubuntu下编译的命令
gcc -m64 -no-pie -fno-PIC -S hello.c -o hello.i

3.3 Hello的编译结果解析
3.3.1 常量

.section .rodata说明接下来的是只读数据rodata段。本程序的.rodata段中存储了两个字符串常量,即.LC0和.LC1,分别对应源程序的两个printf。可以看到和源程序相比缺失了一个\n,这是编译器优化成用puts函数输出的结果。.rodata还可能存放const全局变量、switch跳转表等。

程序中间出现的数字常量都以前面加$的立即数的形式写在了汇编代码中。
3.3.2 变量

Hello.c中不存在全局变量,上图为一个局部变量的例子。可以看到定义局部变量时是直接在栈中开辟一块空间,值也是直接被存在栈中的,%rbp就是一个典型的用于作为栈的寄存器。
3.3.3 赋值

还以局部变量i的赋值为例,直接向栈的寄存器中存入32位整型(对应movl的l后缀),即int i = 0。
3.3.4 运算

Hello.c中只有一个运算,即循环中的++运算。实现++运算所用的汇编代码是自增函数addl,直接访问栈来对i进行+1运算。
3.3.5 关系操作
Hello.c中有两个关系运算,分别是!=和<。

在汇编中,!=比较操作采用了简单的cmp函数实现。

这里可以注意到,<8被编译器优化成了<=7。这样优化后就也可以简化为用cmp实现了。
3.3.6数组操作
汇编语言中并没有数组的概念,而是将数组简化为其本质:一串相连的存储空间。要想访问数组中的某一个元素,就用数组的基地址加上偏移量来得到元素地址。

这里是访问数组的三条语句。第一条是获得数组argv的基地址,也就是argv[0],第二条在基地址上加了偏移量,第三条则访问了加偏移量后的地址,也就是argv[1],这里存储着输入的第一个字符串。
3.3.7 控制转移
Hello.c中有两处控制转移,分别是if和for。

上图是if的条件转移,判断相等。J后面加不同的字母是汇编中判断是否相等的语句,其中je是判断相等,如果相等则跳转到.L2处的代码,即if中的代码;否则接着执行下面的代码。

上图是for循环的条件转移,jle是判断小于等于,如果小于等于7则跳到.L4,而.L4在这行上方的循环体开始处,从而开启又一轮循环。
3.3.8 函数操作
函数在汇编中通过寄存器实现,其中返回值存储在%eax中,参数则存储在rdi, rsi, rdx, rcx, r8, r9这六个寄存器中进行调用,如果需要更多参数还要利用栈帧存入栈中,但hello.c中没有用到。调用函数时用到的汇编语句是call,返回时则用ret。以下举例说明几处函数调用。

输出第一个字符串,编译器将原本的printf函数优化成了puts函数,先传入参数即.LC0的字符串,再调用puts。

这段代表exit(1)。
还有atoi、getchar、printf、sleep等函数,传入参数和调用的步骤都大致相同。

以上的函数都是c语言的库函数,没有出现ret。这个ret出现在if的条件结构中,是用于结束if的。
3.4 本章小结
本章解释了c程序编译过程中“编译”步骤的概念和作用,并通过hello.s这一例子展示了编译得到的汇编语言代码,从中可以看到从c代码到汇编代码的翻译方式,也体现出并不是直接死板地翻译,而是要经过修整以适合机器的运作规律,从而离下一步的翻译成二进制机器语言又更进一步。

第4章 汇编
4.1 汇编的概念与作用

汇编是将汇编语言代码翻译成二进制机器语言,并生成可重定位目标文件的过程。
得到的二进制机器语言是机器可以直接理解并运行的,只要再经过链接就可以得到能够运行的完整程序了。
4.2 在Ubuntu下汇编的命令
gcc -m64 -no-pie -fno-PIC -c hello.s -o hello.o

4.3 可重定位目标elf格式

以上是elf的基本格式。下面逐一解析这些节分别的作用和意义。
4.3.1 elf头

Elf头开始是一个16字节序列,前四个字节是elf格式固定的开头,然后的三个字节依次代表64位、小端序和文件头版本。Elf头含有文件的最基本信息,是在链接时读取并理解这个文件所必不可少的。
4.3.2 节头部表
节头部表列出了各节的大小、类型、地址、偏移量等信息,方便查找各节。

4.3.3 重定位条目
文件中有一些内存地址或引用,这些地方在链接前是待定的,需要视链接的情况指定确切的地址。因此,需要对这些地址进行重定位。每个代码段或数据段都对应一个重定位表,记录了段中的这些位置,方便对它们进行查找和操作。

4.4.4 符号表

.symtab是符号表,它列举了程序中用到的函数和全局变量。

4.4 Hello.o目标的结果解析

反汇编得到的代码和之前的汇编代码主要有三方面差别:一是操作数,反汇编代码中的操作数从十进制变为了十六进制;二是分支转移,.L2、.L3等段名称在反汇编代码中变成了以偏移量表示的内存地址;三是函数调用,也从函数名变成了以偏移量表示的内存地址。这样的变化,主要是由于机器码中没有符号的概念,所有的符号都要变成具体可查的数字。但内存地址又是偏移量而不是具体的数值,这是因为还没有链接,无法确定使用的内存地址,这部分要留给重定位来解决。
4.5 本章小结
本章解释了c程序编译过程中“汇编”步骤的概念和作用,并以hello.o的elf格式和反汇编代码为例展示了二进制机器语言文件的格式和特性。从汇编代码到二进制机器语言的过程是有迹可循、有理可依的,它为下一步的链接又提供了更方便的条件。

第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
ld -o hello -dynamic-linker /lib64/ld-linux-x86-64.so.2 /lib/crt1.o /lib/crti.o hello.o /lib/libc.so /lib/crtn.o

5.3 可执行目标文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
Elf头:

节头部表:

程序头表:

符号表:

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

运行main后的memory regions:

对比节头部表,得到节映射到的段信息。其中虚拟地址减偏移量的值是0x400000则为代码段,0x600000则为数据段。
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。

这是两者反汇编的对比,主要有以下几项区别:以0开头的虚拟地址变成了具体的内存地址;函数的调用也变成了内存地址;增加了.init和.plt节;增加了getchar等库函数。由此可以知道,链接的过程就是将不同.o文件的内容按合理顺序拼接在一起使得彼此能够配合的过程。在重定位时,链接器需要整理符号表中的条目,分配出内存地址。先将每个同类节合并成同一个节,然后为它们赋予内存地址,使指令和变量有唯一的内存地址。最后将重定位节中的符号引用改为内存地址。

5.6 hello的执行流程
ld-2.27.so!_dl_start
ld-2.27.so!_dl_setup_hash
ld-2.27.so!_dl_sysdep_start
ld-2.27.so!_dl_init
libc-2.27.so!_cxa_atexit
libc-2.27.so!_new_nextfn
hello!_init
hello!main
hello!printf@plt
hello!atoi@plt
hello!sleepp@plt
hello!getchar@plt
5.7 Hello的动态链接分析
GOT表,即全局偏移表,对应了所有外部定义的符号,这些符号是需要在程序运行中才能重定位的,称为动态链接。通过.plt表查找数据,再跳转到got表,got表的值会改变,从0变为真实地址。
5.8 本章小结
本章解释了c程序编译过程中“链接”步骤的概念和作用,并以可执行程序的elf格式和反汇编代码为例展示了链接前后的区别,推导出链接的过程。

第6章 hello进程管理
6.1 进程的概念与作用
进程是一个执行中程序的实例,它的上下文提供了程序运行所需的状态。
进程为程序提供逻辑控制流和私有地址空间。
6.2 简述壳Shell-bash的作用与处理流程
Shell是用户与操作系统内核之间的交互程序。
Shell分析用户输入的字符串,先检查是否为内部命令,如果是则执行,不是再查找是否有程序文件,如果有则建立进程和上下文,加载这个程序文件,让系统内核运行这个程序。程序运行后等待其退出并回收进程。
6.3 Hello的fork进程创建过程
父进程调用fork函数,创建pid不同但虚拟地址空间相同的一个副本子进程。子进程和父进程的代码、数据段、堆、共享库以及用户栈均相同,可以访问父进程的文件。Fork函数调用一次返回两次,在父进程中返回子进程的pid,在子进程中返回0.
6.4 Hello的execve过程
Exceve调用加载器,把子进程存在的区域清空,映射新程序自身的私有区域,将代码、数据、共享库和用户栈都加载到上下文中,映射共享区域。最后设置程序计数器指向_start,进而调用main函数。
6.5 Hello的进程执行
一个进程运行时有两种模式,分别是由应用程序控制的用户模式和由内核控制的内核模式。正常情况下是用户模式,拥有仅供正常运行的权限,而故障、暂停、休眠等异常状态时交给系统内核控制,也因此有了更多系统级的权限。在同时运行多个并发进程时,系统将会进行调度,轮流把处理器分配给每个进程以用户模式使用,其他进程就会暂时中断,进入内核模式。进程以用户模式占用处理器的一段时间就成为时间片,多个时间片接续起来成为控制流。进程的上下文中存储了它从中断状态中恢复运行所需的一切信息,因此每个时间片能连接上,就像从未中断过一样顺利完成工作。中断的时间是由sleep函数控制的,当sleep的计时到达预设的时间时就发送信号,让内核重新唤醒这个进程。
6.6 hello的异常与信号处理
6.6.1 回车

正常结束,hello进程已经不再运行了。
6.6.2 Ctrl-Z

按下ctrl-z之后内核将发送一个SIGSTP信号给shell父进程。信号处理函数将hello进程挂起但并不结束,因此ps命令还能看到hello。使用fg命令可以把被挂起的hello重新调度到前台运行,继续将剩下的步骤运行完。
6.6.3 Ctrl-C

按下ctrl-c之后内核将发送一个SIGINT信号给shell父进程。信号处理函数将hello进程挂起并结束,因此ps命令不显示hello,说明hello已经不再运行了。
6.6.4不停乱按

Shell随时用一个getchar函数监听输入的字符,并读取分析回车键后输入的字串,如果是符合语法的命令则在程序结束后将会执行,如果并不是设定好的命令则不会做出任何反应,程序继续运行。
6.7本章小结
本章解释了c程序运行时进程从创建到回收的全过程,说明了多并发进程被内核调度切换的方式、shell和内核如何调度进程并对信号和命令做出反应,并试验了程序运行时几种信号的结果。

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:相对于当前程序进程段的地址偏移量,也是经过汇编后程序中写的地址,表示操作数或指令的地址与段基地址之间的距离。
线性地址:32位无符号整数,用段的基地址加上逻辑地址也就是偏移量,就得到了线性地址。
虚拟地址:每个进程拥有4G虚拟内存,高2G属于内核,低2G属于进程独占。虚拟内存是cpu假定的内存空间,只有向地址写入内容时才会为该地址分配真正的物理内存。
物理地址:物理地址是32位或36位无符号整数,是地址寄存器中存放的地址,它是与内存芯片上的单元一一对应的,可以通过它从总线上找到唯一的内存单元。
7.2 Intel逻辑地址到线性地址的变换-段式管理
段映射是从逻辑地址到线性地址的映射,用段的基地址加上逻辑地址也就是偏移量,就得到了线性地址。逻辑地址由两部分组成,前16位为段标识符,后面为偏移量。通过段标识符中的前13位可以在一个叫段描述符表的表中找到其对应的三个信息,分别是段基地址、段限长、段属性(可读可写性等)。段描述符表又有全局描述符表(GDT) 和局部描述符表 (LDT)两种。因此,通过一个逻辑地址就可以找出基地址和偏移量,算出唯一的线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理

页映射是从线性地址到物理地址的映射,由cpu的页式管理单元负责将线性地址翻译成物理地址。
物理内存的最小分配单位是帧,页是虚拟内存中相当于帧的单位。线性地址的后半部分是页偏移量,而前半部分就是页号了,标志着其属于虚拟内存的哪一页。而物理内存中还有一个页表,它存储着虚拟内存页和物理内存帧的对应关系。这样,通过页号-》页表-》物理内存帧,再加上页偏移量,就得到了物理内存的地址。
7.4 TLB与四级页表支持下的VA到PA的变换

四级页表指的是虚拟页号被等分为四段,用每段页号依次查找,第一级页表中找到相应的第二级页表,以此类推。这样做的好处是节约了页表的总大小,因为如果第一级页表未分配就不用存储相应的第二级页表,节省了空间。上图以两级页表为例展示了页表不同层级之间的关系。

即使这样,页表也是庞大繁琐的,查询起来耗费时间。因此为页表建立一个缓存,称为tlb,查询过的页表项就存入tlb,下次查询时如果tlb中有就不用再依次繁琐地查找页表,节省了时间。
7.5 三级Cache支持下的物理内存访问
Cache是主存储器的缓存,它的原理是把之前调用过的主存数据缓存起来,下次如果缓存中有就不用再到主存中查找了。三级cache的原理相同,先在第一级中查找,没有再到第二级查找。采用分组的方式,固定数目的路为一组,在cache中取高位作为组号,每个组内都是一个全相联cache,一般采用最近最少用规则使被调用多的块留下来,增添效率。
7.6 hello进程fork时的内存映射

内核为子进程创建与父进程mm_struct、区域结构和页表相同的副本,将两个进程的所有页面标记为只读,区域结构标记为写时复制。这样两个进程就能读取到完全相同的虚拟内存。但进程对内存进行写操作时,由于标记了写时复制,将为这个进程复制一份新的页面,这样就与另一个进程互不干扰了。
7.7 hello进程execve时的内存映射
如图,总共分为四步:删除存在的用户区域、映射私有区域、映射共享区域、设置程序计数器。
7.8 缺页故障与缺页中断处理
总共分为三步。第一步要确认地址是否合法,如果不合法则返回段错误。第二步读写指令等是否符合该页的读写权限,如果不符合则属于非法访问,触发保护异常。若进行到第三步,说明是正常的缺页,从页表中选一个页替换为当前页,这样就更新了页表。
7.9动态存储分配管理

如图,动态内存分配器负责维护堆结构,brk指针指向堆顶。堆被动态分配器视为块的集合,块有已分配和空闲两种状态。分配器有显式和隐式,而malloc就是显式的分配方法,是应用要求释放时才能释放的。隐式则会自动释放不使用的块,称为垃圾收集。空闲的块由称为空闲链表的数据结构记录,它也有显式和隐式之分。
7.10本章小结
本章解释了c程序运行时分配存储空间的操作和过程,讲解了各种地址的翻译计算方式,说明了各级存储器是怎样互相配合的、有怎样的层级关系。最后讲解了进程如何具体地调用这些存储器的数据结构,并如何达到高效快速的效果。

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:
Unix文件是一个m字节的序列。I/O设备都被视为文件,输入与输出都被视为读写文件,这样可以使所有设备的输入输出具有统一的格式和执行方式,方便管理与使用。为执行这种操作而设计的接口就是unix I/O接口。
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
1.open()函数:
功能描述:用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等各种参数。
函数原型:int open(const char *pathname,int flags,int perms)
参数:pathname:被打开的文件名(可包括路径名如"dev/ttyS0")flags:文件打开方式。
返回值:成功:返回文件描述符;失败:返回-1。
2.close()函数:
功能描述:用于关闭一个被打开的的文件。
所需头文件:#include <unistd.h>
函数原型:int close(int fd)
参数:fd文件描述符。
返回值:0成功,-1出错。
3.read()函数:
功能描述:从文件读取数据。
所需头文件:#include <unistd.h>
函数原型:ssize_t read(int fd, void *buf, size_t count);
参数:fd:将要读取数据的文件描述词。buf:指缓冲区,即读取的数据会被放到这个缓冲区中去。count: 表示调用一次read操作,应该读多少数量的字符。
返回值:返回所读取的字节数;0(读到EOF);-1(出错)。
4.write()函数:
功能描述:向文件写入数据。
所需头文件:#include <unistd.h>
函数原型:ssize_t write(int fd, void *buf, size_t count);
返回值:写入文件的字节数(成功);-1(出错)。
5.lseek()函数:
功能描述:用于在指定的文件描述符中将将文件指针定位到相应位置。
所需头文件:#include <unistd.h>,#include <sys/types.h>
函数原型:off_t lseek(int fd, off_t offset,int whence);
参数:fd;文件描述符。offset:偏移量,每一个读写操作所需要移动的距离,单位是字节,可正可负(向前移,向后移)。
返回值:成功:返回当前位移;失败:返回-1。
8.3 printf的实现分析
int printf(const char *fmt, …)
{
int i;
char buf[256];

va_list arg = (va_list)((char*)(&fmt) + 4);   
i = vsprintf(buf, fmt, arg);   
write(buf, i);   
  
return i;   

}
、https://www.cnblogs.com/pianist/p/3315801.html
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
int getchar(void)
{
static char buf[BUFSIZ];
static char* bb=buf;
static int n=0;
if(n==0)
{
n=read(0,buf,BUFSIZ);
bb=buf;
}
return(–n>=0)?(unsigned char)*bb++:EOF;
}
Getchar调用了read函数,它从缓冲区将buf的大小读满,发生异常时再监听键盘输入读取缓冲区,读取时获得读入的字节数,如果大于0则返回缓冲区第一个字节,否则返回eof。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章解释了unix系统的io管理,列举了其io接口及相关函数,并通过hello中两个函数的具体实现作为例子展现了i/o的过程。

结论
程序源代码通过预处理、编译、汇编、链接等步骤成为可执行程序、并通过进程管理、存储管理、IO管理等系统机制在操作系统中得以运行和结束的整个过程,这样的过程被详细地称作“P2P”(From Program to Process)和“O2O”(From Zero-0 to Zero-0)。通过梳理P2P和O2O的过程,我们也串联回顾了计算机系统所学的知识,使知识结构更加清晰。

附件
列出所有的中间产物的文件名,并予以说明起作用。
9. hello.i 预处理后的源代码文件
10. hello.s hello.i编译后的汇编文件
11. hello.o hello.s汇编后的可重定位目标文件
12. hello hello.s与其他可重定位目标文件链接后的可执行程序
13. hello.txt hello.o的反汇编代码
14. hello1.txt hello的反汇编代码
15. hello.elf hello.o的elf文件
16. hello1.elf hello的elf文件

参考文献
[1] 《深入理解计算机系统》第三版,兰德尔 E.布莱恩特,大卫 R.奥哈拉伦
[2] http://www.jeepxie.net/article/29810.html
[3] x86/x86_64 CPU中逻辑地址、线性地址与物理地址 – Linux Kernel Exploration https://ilinuxkernel.com/?p=414
[4] TLB的作用及工作原理 - AlanTu - 博客园 https://www.cnblogs.com/alantu2018/p/9000777.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值