程序人生-Hello’s P2P

计算机系统

大作业

题     目  程序人生-Hellos P2P  

专       业      计算机类          

学     号      1190200407        

班   级       1936602          

学       生      林棋珺      

指 导 教 师       刘宏伟        

计算机科学与技术学院

20216

摘  要

本文以hello程序的编译过程再到执行过程作为引子,展示了机器执行程序的全过程以及在此期间机器内部的维持运转、各个部位的工作。

包括了hello程序的预处理、编译、汇编再到链接的过程,以及运行时hello 的进程管理、存储管理和IO管理,通过提出概念并以hello作为例子详细刨析。

关键词:预处理、编译、汇编语言、机器语言、可重定位目标文件ELF格式、链接、重定位、动态链接、shell、进程、Cache、页表、TLB;                            

(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分

目  录

第1章 概述

1.1 Hello简介

1.2 环境与工具

1.3 中间结果

1.4 本章小结

第2章 预处理

2.1 预处理的概念与作用

2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析

2.4 本章小结

第3章 编译

3.1 编译的概念与作用

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析

3.4 本章小结

第4章 汇编

4.1 汇编的概念与作用

4.2 在Ubuntu下汇编的命令

4.3 可重定位目标elf格式

4.4 Hello.o的结果解析

4.5 本章小结

第5章 链接

5.1 链接的概念与作用

5.2 在Ubuntu下链接的命令

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

5.4 hello的虚拟地址空间

5.5 链接的重定位过程分析

5.6 hello的执行流程

5.7 Hello的动态链接分析

5.8 本章小结

第6章 hello进程管理

6.1 进程的概念与作用

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

6.3 Hello的fork进程创建过程

6.4 Hello的execve过程

6.5 Hello的进程执行

6.6 hello的异常与信号处理

6.7本章小结

第7章 hello的存储管理

7.1 hello的存储器地址空间

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

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

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

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

7.6 hello进程fork时的内存映射

7.7 hello进程execve时的内存映射

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

7.9动态存储分配管理

7.10本章小结

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

8.2 简述Unix IO接口及其函数

8.3 printf的实现分析

8.4 getchar的实现分析

8.5本章小结

结论

附件

参考文献


第1章 概述

1.1 Hello简介

P2P(From Program to Process):指的是hello.c经过预处理、编译、汇编、链接后得到一个可执行程序hello

O2O(From Zero-0 to Zero-0):

    一开始0

    输入./hello 学号 姓名

    Shell调用fork()申请子进程

子进程调用execve传入argc, argv, environ(参数和环境变量)

映射虚拟内存,载入物理内存

执行hello

运行结束后,Shell接受子进程信号回收

内核删除子进程

回到了0

1.2 环境与工具

硬件环境:lntel(R) Core(TM) i7-8750H CPU @2.20GHz 2.21 GHz;8GB RAM

软件环境:Windows 10 64位 ,Vmware 14;Ubuntu 18.04 LTS 64位

开发工具:gcc + gedit , Codeblocks , edb,objdump,readelf

1.3 中间结果

hello.i 预处理后的文件

hello.s 汇编语言文件

hello.o 可重定位目标文件

hello 可执行文件

1.4 本章小结

    这一章是hello的程序人生的开始,了解hello P2P和O2O的过程,让我们重回初恋,了解更多关于hello的一切。

(第1章0.5分)


第2章 预处理

2.1 预处理的概念与作用

预处理是在源代码编译之前所做的修改源码的操作

主要的行为是删去#注释的代码,不同的注释作用如下:

*将源文件中以“#include”格式包含的文件复制到编译的源文件中。

*用实际值替换用“#define”定义的字符串。

*根据“#if”后面的条件决定需要编译的代码。

...

此外删除了其他//和/* */的注释

2.2在Ubuntu下预处理的命令

输入该命令调用gcc预处理

2-2 预处理命令

2.3 Hello的预处理结果解析

图2-3 hello.i内容(未截全)

上面是hello.i最后的内容,因为内容过多所以直接了最后和hello.c的主函数一样的部分,可以看见main函数基本没有变化,说明预处理时还没有编译。

这里可以看到预处理的作用之一就是把头文件转换成源代码。

同时也有修改宏定义和条件编译的作用

2.4 本章小结

每次写代码都会写的头文件,不知道有什么意义,现在终于明白了。

宏定义,拓展和条件编译,所有的#注释 // /**/注释都要删掉,才能让程序编程机器能懂的语言。

没想到短短的hello程序居然也可以包含如此多的内容,但是头文件的代码都是曾经的程序员一点一滴积累的,只有前人不断地开发,今后的代码才能越来越简练。

(第2章0.5分)


第3章 编译

3.1 编译的概念与作用

编译是指预处理后的文件到生成汇编语言程序的过程,将文件hello.i转换成hello.s,获得一个汇编语言文件

    编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。

3.2 在Ubuntu下编译的命令

图3-2 编译命令

3.3 Hello的编译结果解析

图3-3 hello.s 文件

3.3.1 数据

        hello.s里出现的数据如下:

        由图3-3-1-1可以看见main函数给自己的栈帧申请了32个字节

        并将argc存在%rbp-20中(sizeof(argc)=4)

        argv的数组指针为%rbp-32,通过偏移可得:

argv[1]地址为argv+8(%rbp-24)

argv[2]地址为argv+16(%rbp-16)

(sizeof(argv)=8)

        argc和argv是main函数带入的参数,也是调用hello命令时输入的

  

图3-3-1-1 数据argc和argv

        

         全局变量sleepsecs放在.data节中

         sizeof(sleepsecs) = 4

          

图3-3-1-2 数据sleepsecs

       

         printf的两个表达式(LC),放在.rodata节中

图3-3-1-3 表达式

    由这几句可以看出局部变量i存放在(%rbp)-4中

       sizeof(i)=4

图3-3-1-3 变量i

3.3.2 操作

   3.3.2.1 赋值操作

               用movl语句给寄存器i赋值

           

图3-3-2-1 赋值操作

   3.3.2.2 算术操作

               用addl语句执行i++

           

图3-3-2-2 算术操作

3.3.2.3 关系操作

               程序带有4个标志位,ZF,OF,SF,CF

               在执行cmpl时会改变,然后根据这4个标志位的情况可以决定j**和cmov**是否执行,从而产生分支

               例如判断(argc!=3)和(i<10)

           

                

图3-3-2-3 关系操作

 3.3.2.4 算术操作

               main在.text节中,声明为全局变量和函数类型

           

图3-3-2-4-1 函数操作-main

其余函数都是用call函数调用,一般传入的参数会放进%rdi, %rsi, %rdx, %rcx, %r8, %r9(按顺序优先)或栈中(前面的寄存器占用后),返回值一般放进%rax中

printf把表达式放入%rdi中

             

图3-3-2-4-2 函数操作-printf

把参数放入%rdi中后调用sleep

           

图3-3-2-4-3 函数操作-sleep

           

图3-3-2-4-4 函数操作-getchar

图3-3-2-4-5 函数操作-put

图3-3-2-4-6 函数操作-exit

3.3.3 控制转移

            

图3-3-3 for语句

          通过上面提到的关系操作,判断i<=9的条件,满足就返回.L4(循环块)

           

3.4 本章小结

麻雀虽小五脏俱全,小小的hello.s却包含了各种数据类型和各种操作

但是分析其中的结构和实现过程对我们了解汇编语言至关重要

(第3章2分)


第4章 汇编

4.1 汇编的概念与作用

汇编是编译后的文件转换至机器语言二进制程序的过程。将文件hello.s转换成hello.o,获得一个可重定位目标文件。

4.2 在Ubuntu下汇编的命令

图4-2 汇编命令

4.3 可重定位目标elf格式

图4-3 elf格式

4.3.1 ELF头

命令输入readelf -h hello.o,得到elf头,存放整个程序的基本信息

包括机器类型;操作系统版本;节头部表的开始位置、大小;文件大小等等

图4-3-1 ELF头

4.3.2 节头表

        命令输入readelf -S hello.o,得到文件(图4-3 elf格式 中所述)各个节的信息(包括 名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐)

  

图 4-3-2 节头表

4.3.3 symtab

         输入命令readelf -s hello.o,得到符号表(程序中所有函数名有全局或者static属性的变量名

对于每一个条目的不同属性的说明:

Value: 偏移量

Size: 大小(字节数)

Type: 类型

Bind: 绑定属性——是全局符号还是本地(局部)符号

Ndx: 节索引

Vis:

Name: 符号名称

图4-3-3 符号表

4.3.4 重定位条目

        上文提到的偏移量会在此处加以叙述。

重定位就是将符号定义和符号引用进行连接的过程,通过偏移量计算它们运行时所处的内存地址。这一步需要依赖hello.o中的重定位条目

输入命令 readelf -r hello.o,得到重定位节

偏移量 指向需要进行重定位操作的位置。
信息指定必须对其进行重定位的符号表索引以及要应用的重定位类型。
加数指定常量加数,用于计算存储在可重定位字段中的值。

图4-3-4 可重定位节

4.4 Hello.o的结果解析

图4-4 hello.o的反汇编

说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。

4.4.1 机器语言的构成

     1、单字节指令

   操作码本身就隐含了操作数的信息,不需再加操作数。如retq,leaveq等

2、双字节指令

    首字节为操作码,第二个字节为操作数或操作数地址。如callq,push等

3、三字节指令

   首字节为操作码,后两个字节为操作数或操作数地址。如addl,mov,cmpl等等

     与汇编语言的映射关系:操作码对应一个字节,根据操作码的不同,操作码后面会添加操作数或者操作数地址等等组成一个完整的指令

     不同:汇编语言中用的是十进制,而机器语言(反汇编语言)用的是十六进制;汇编语言中跳转和调用函数用的是自定义段或者函数名字,而机器语言(反汇编语言)用的是相对寻址的方法(跳转到对应的rip)

      

4.5 本章小结

程序转成了可重定位目标文件,已经是机器语言了,只差最后一步就可以执行了。

这一章节我们学习到了elf头和机器语言两大部分,都是链接后成为可执行文件的基础。

(第4章1分)


5链接

5.1 链接的概念与作用

链接过程将多个可重定位目标文件合并以生成可执行目标文件。

作用:

-模块化

一个程序可以分成很多源程序文件;

可构建公共函数库,如数学库,标准C库等。以便代码重用,提高开 发效率。

-效率高

时间上,可分开编译:只需要重新编译修改的源程序文件,然后重新 链接;

空间上,无需包含共享库所有代码:源文件中无需包含共享库函数的 源码,只要直接调用即可(如,只要直接调用printf() 函数,无需包含其 源码),另外,可执行文件和运行时的内存中只需包含所调用函数的代码, 而不需要包含整个共享库。

5.2 在Ubuntu下链接的命令

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

图5-3-1 ELF头

   

 

图5-3-2 节头表

5.4 hello的虚拟地址空间

   

图5-3-3 edb中的.init段

图5-3-4 edb中的.text段

        对照elf头的位置和偏移亮可以看到,readelf中所有节的地址位置是和edb中显示的一一对应的

5.5 链接的重定位过程分析

objdump -d -r hello运行结果如下:

可以看到hello比起hello.o,多了很多函数的代码,是在链接时引用的

并且在跳转和调用函数时,用的是绝对寻址,说明重定位完成,数据和代码都放进了地址空间中。

图5-5 hello的反汇编代码

hello的重定位过程

1.合并相同的节(数据节和代码节)

2.确定「定义符号」在虚拟空间的绝对地址

3.用上述「定义符号」的绝对地址修改 .text 和 .data 节中「引用符号」的地址

5.6 hello的执行流程

图5-6 hello各函数地址

0x400520            hello!_start

->0x400607            main

->0x4004d0            hello!puts@plt

->0x400500            hello!exit@plt

->0x4004e0            hello!printf@plt

->0x400510            hello!sleep@plt

->0x4004f0            hello!getchar@plt

->0x400704            hello!_fini 

5.7 Hello的动态链接分析

hello程序的动态链接项目:global_offset表

图5-7-1 运行dl_init前的GOT表

图5-7-2 运行dl_init后的GOT表

执行了dl_init后的global offset表内的数据都发生了变化。

5.8 本章小结

至此,我们的程序hello完成了P2P

只有一个函数只有一段代码是无法完成hello的全部工作的

需要与依赖的函数链接,将函数重定位,找到每个符号在虚拟空间中的绝对地址,才能真正执行hello

(第5章1分)


6hello进程管理

6.1 进程的概念与作用

进程是具有以下特征的活动单元:一组指令序列的执行,一个当前状态和相关的系统资源集计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

作用:进程提供一个假象,好像我们的程序是在独占使用处理器和内存。

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

shell是一个用户跟操作系统之间交互的命令解释器,可以合并编程语言以控制进程和文件,以及启动和控制其他程序。

处理流程:

-输入的命令行

-将命令用空格分开

-判断是否为内置命令,如果是那就执行内部函数

-调用fork和execve函数来运行程序

6.3 Hello的fork进程创建过程

Shell解析命令,调用fork函数获得一个进程

获得命令输入的参数argv argc,构造envp。

子进程自己创建一个进程组,并把父进程的信息私有的写时复制下来,这样调用hello时就不会影响到shell进程。

子进程调用execve函数进入hello的代码。

6.4 Hello的execve过程

execve函数加载并运行可执行目标文件hello,且带参数列表argv和环境变量列表envp。如果没有出现错误,execve正常加载运行hello,调用后就不会返回。

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

*映射私有区域:为hello的代码、数据、bss和栈区域创建新的数据结构。私有的、写时复制。

*映射共享区域:将hello与共享对象动态链接,再映射到用户虚拟地址空间中的共享区域内

*设置程序计数器(PC)设置hello上下文中的程序计数器

6.5 Hello的进程执行

以下格式自行编排,编辑时删除

结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。

6.5.1 进程上下文

    操作系统内核使用一种成为上下文切换的较高层形式的异常控制流来实现多任务:

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

6.5.2 进程时间片

是操作系统分配给每个正在运行的进程的一段CPU时间

由操作系统内核的调度程序分配给每个进程。

6.5.3 进程调度的过程

      刚开始控制在hello进程中,hello调用sleep函数时,会将控制权转交给内核进程,内核保存hello的上下文后,在sleep的时间片内执行进入进程的上下文将控制传递给该进程,此时可以接受来自键盘的信息,当sleep的时间片结束后,控制转移回hello保存的上下文,控制传递给hello。

6.5.4 用户态与核心态转换

    可以通过中断,陷阱,故障,终止等异常情况,程序会从用户态转换到内核态,内核保存进程的上下文,恢复下一个进程的上下文来重新启动该进程,控制转交给进程,从核心态转换到用户态

6.6 hello的异常与信号处理

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

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

6.6.1 可能出现的异常和信号

         中断:ctrl+z会发送SIGTSTP信号->进程中断,切换至别的进程

         终止:Ctrl+C会发送SIGINT信号->终止回收进程

     hello可能会接受其他进程调用kill发送的信号

6.6.2 可能出现的情况

6-6-2-1 正常情况

胡乱按键盘对进程没有影响

6-6-2-2 胡乱按键盘

6-6-2-3 Ctrl+C

按下Ctrl+Z后会发送SIGTSTP信号停止hello进程,此时可以输入其他指定

6-6-2-4 Ctrl+Z+(ps,pstree,jobs)

在中断时可以用kill发送信号(上图为SIGKILL,下图为SIGCONT)

6-6-2-4 Ctrl+Z+kill

6.7本章小结

进程是重要的概念。可以说我们能看到程序的运行靠的就是进程,了解进程利用进程也是十分重要的一项技能。

(第6章1分)


7hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:在机器语言中用来确定一个指令或者是操作数的地址。逻辑地址包含段和偏移量,而偏移量是相对偏移,而段则确定了偏移开始的地方,这样就能通过段和偏移来确定地址。在hello中指hello.o中的相对偏移地址

线性地址:逻辑地址与物理地址之间的桥梁。用偏移加上段的地址就能得到线性地址,也就是虚拟内存地址。在hello中指hello中的虚拟内存地址

虚拟地址:虚拟地址和线性地址一样。

物理地址:加载到内存地址寄存器中的地址,内存单元的真正地址。hello的虚拟地址通过地址翻译器可转换成物理地址。

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

逻辑地址由段地址和偏移地址组成。

计算机中共有4个段寄存器,用于存放数据、代码、堆栈、辅助4段的基地址,段选择符共计16位,前13位为索引位,用于确定段描述符在描述符表中的位置。

第14位为Tl位,Tl=0时选择全局描述符表,Tl=1时选择局部描述符表。

最后两位用于描述段的状态

被选中的描述符先被送至描述符Cache,每次从描述符Cache中取32位基地址,与32位段内偏移量(有效地址)相加得到线性地址

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

Linux的线性地址到物理地址的转换,是通过页式管理完成的。一组固定长度的线性地址的集合被称为页面,一般来说页面有4KB大小。

线性地址的后12位是页内偏移(VPO)

前面36位是虚拟页号(VPN),通过VPN可以找到相应的物理地址所在的页,如果有多级页表,VPN将会被分成多份。第i个VPN作为第i级页表的索引指向第i+1级页表的基址。最后一级页表中的PTE包含每个物理页面的页号(PPN)

转换的具体过程如下:

1、CPU产生一个虚拟地址,将虚拟地址传递给主存

2、MMU从TLB中取出相应的PTE,通过PTE找出VPN对应的物理页号

3、与页内偏移量结合构成物理地址

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

TLB称为翻译后备缓冲器,是关于PTE的高速缓存。

如果TLB有T=2^t组,那么TLB索引(TLBI)是由VPN的最低t位组成,而TLB标记(TLBT)是由VPN剩余的位组成的

将VA的后12位作为VPO,前36位作为VPN

MMU会先从TLB中寻找PTE,根据索引找到对应的组,如果组内存在标记相同切有效为为1的pte那么获得pte中的ppn。

如果没找到,内核就会通过内存和cache寻找。

将32位的vpn分为4个9位的vpn(1,2,3,4)

通过一个固定寄存器中的值获得以及页表的基地址,然后由vpn1作为索引找到第二级页表的基址,然后由vpn2作为索引,以此类推,直到vpn4指向的就是对应的ppn的值。

最后将ppn和VPO合成得到PA

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

Cache的工作原理:内核通过内存地址的组索引找到对应的组。找到组内标记相同且有效位为1的块,返回块内的值。如果没有找到就是不命中,就需要从下一级cache中寻找,或者是内存(如果在第三级cache中不命中)。

找到数据后会对不命中的cache进行替换,放置在组内的空闲块或者驱逐最久没被使用的有效块(如果组内已满)

7.6 hello进程fork时的内存映射

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

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

7.7 hello进程execve时的内存映射

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

*映射私有区域:为hello的代码、数据、bss和栈区域创建新的数据结构。私有的、写时复制。

*映射共享区域:将hello与共享对象动态链接,再映射到用户虚拟地址空间中的共享区域内

*设置程序计数器(PC)设置hello上下文中的程序计数器

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

在虚拟内存的习惯说法中,DRAM缓存不命中称为缺页(page fault) 。

当发生缺页中断时,操作系统会调用缺页处理子程序。

程序会先判断虚拟地址A是否合法(否则出现段错误:访问一个不存在的页面),然后判断试图进行的内存访问是否合法(否则发生保护异常:例如,违反许可,写一个只读的页面)。

如果内核知道了这个缺页是由于对合法的虚拟地址进行合法的操作造成的,就会选择一个牺牲页面,如果这个页面被修改过,那么就将该页交换出去,换入新的页面并更新页表。然后返回程序中断处的当前指令,再次发送A到MMU,这次就可以正常翻译了。

7.9动态存储分配管理

动态内存分配器维护着一个进程的虚拟内存区域,称为堆(heap)。

分配器将堆视为一组大小不同的块的集合进行维护,每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。

已分配的块显式地保留为供应用程序使用,空闲块可以用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已经分配的块保持已分配状态直到它被释放,释放要么由应用程序显式执行,要么由内存分配器隐式执行。

malloc函数为显式分配器,可以从堆中申请一块内存并返回指向该块的指针。

隐式空闲链表:

    块的结构为头部:块大小+标志位(a已分配/f空闲)、(有效载荷+填充)和尾部(等于头部),通过头部存储的值将所有块连接起来。

显式空闲链表:

    空闲块的结构为头部:块大小+标志位(a已分配/f空闲)、前驱指针、后继指针和尾部(等于头部),通过前后指针将空闲块用链表串联起来。

    不同的空闲块会根据大小分类串到不同的链表中

分配:

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

下一次适配:每一次都从上一次搜索结束的地方开始搜索,选择第一个合适的空闲块。

最佳适配:对堆进行全面搜索,找到最合适的块。

增加堆的空间:

通过调用sbrk函数,申请额外的存储器空间,插入到空闲链表中。

合并空闲块:当分配器释放一个已分配块时,可能有其他空闲块与这个新释放的空闲块相邻,造成假碎片现象。即相邻的块被切割成了小块而无法使用。因此,合并空闲块的目的即为消除假碎片现象,提升内存利用率。

策略:

1、立即合并:释放完内存就合并

2、推迟合并:直到发生某种情况需要合并时再合并

7.10本章小结

所有的进程都需要调用内存,hello也不例外,然而hello自认为调用所有内存工作,实际上只是内存的“替身”罢了。

了解虚拟内存的工作原理,以及高速缓存、缺页异常和动态分配等等,揭开内存的真面目,才能再今后的计算机工作以应万变。

(第7章 2分)


8hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件

设备管理:unix io接口

一个linux文件就是一个m个字节的序列:

B0 , B1 , … , Bk , … , Bm-1

所有的I/ O 设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当作对相应文件的读和写来执行。这种将设备优雅地映射为文件的方式,允许Linux 内核引出一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行。

8.2 简述Unix IO接口及其函数

Unix IO接口:

打开文件:一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备。内核返回一个小的非负整数,作为描述符,它会在对文件所有操作中标识这个文件。

Linux shell创建的每个进程开始时都有三个打开的文件:标准输入(描述符为0)、标准输出(描述符为1),标准错误(描述符为2)。头文件定义了常量STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO,它们可用来代替显式的描述符值。

改变当前的文件位置:内核保持这一个文件位置k,初始为0。文件位置是从文件开头起始的字节偏移量,应用程序能够通过seek操作,显式地设置文件的当前位置。

读写文件:一个读操作是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n。(当k>=m时执行读操作会触发EOF条件,m为文件字节大小)写操作是从内存复制n>0个字节到文件,从当前文件位置开始,然后更新k。

关闭文件:当应用完成对文件的访问之后,通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。

函数:

open():

函数原型:int open(char *filename,int flags,mode_t mode)

open函数将filename转换为一个文件描述符,并且返回描述符数字。若出错返回-1,否则返回新文件描述符,返回的描述符总是在进程中当前没有打开的最小描述符。flags指明进程打算如何访问文件:

O_RDONLY:只读

O_WRONLY:只写

O_RDWR:可读可写

flags也可以是一个或更多位掩码的或,提供额外的指示:

O_CREATE:文件不存在则创建

O_TRUNC:文件存在则截断

O_APPEND:写操作时设置到文件的结尾处

mode参数制定了新文件的访问权限位

close():关闭一个打开的文件,若成功返回0,出错时返回-1

函数原型:int close(int fd)

参数:fd文件描述符

read():read函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf,返回值-1表示一个错误,返回值为0表示EOF,否则,返回值表示的是实际传送的字节数量

函数原型:ssize_t read(int fd,void *buf,size_t n)

其中fd为文件描述符,buf缓冲区,n为传送字节数

write():write函数从内存位置buf复制最多n个字节到描述符为fd的当前文件位置,返回值-1表示错误,否则,返回值表示写的字节数量

函数原型:ssize_t write(int fd,const void *buf,size_t n)

其中fd为文件描述符,buf缓冲区,n为传送字节数

lseek:显式地修改当前文件的位置

8.3 printf的实现分析

https://www.cnblogs.com/pianist/p/3315801.html

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

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

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

8.4 getchar的实现分析

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

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

8.5本章小结

机器的工作是在纷繁复杂的二进制码中计算和运转,但是在显示屏上显示的,在键盘上输入的,却是人类能懂的语言文字,这就要归功于计算机功能完备的IO系统。

实际上所有输入输出设备都被模型化为文件,在Unix I/O接口中完成用户和机器的沟通,可谓十分的巧妙。

(第8章1分)

结论

hello.c

预处理->hello.i 预处理后的文件

编译->hello.s 汇编语言文件

汇编->hello.o 可重定位目标文件

链接->hello 可执行文件

通过深入理解计算机系统,我对于该如何编写对编译器友好的代码,优化程序的性能有了更多的想法。

(结论0分,缺失 -1分,根据内容酌情加分)


附件

hello.c :hello源代码

hello.i :预处理后的文本文件

hello.s :编译后的汇编文件

hello.o :汇编后的可重定位目标文件

hello :链接后的可执行文件

(附件0分,缺失 -1分)


参考文献

为完成本次大作业你翻阅的书籍与网站等

  1. https://blog.csdn.net/dlutbrucezhang/article/details/8753765
  2. https://blog.csdn.net/zycdeCSDN/article/details/102084045
  3. https://www.jianshu.com/p/b7e44f749211
  4. https://www.jianshu.com/p/3e3218ef0bcf
  5. https://www.cnblogs.com/pianist/p/3315801.html

(参考文献0分,缺失 -1分)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值