哈尔滨工业大学计算机系统大作业2024春

计算机系统

大作业

题     目  程序人生-Hellos P2P  

专       业       网络空间安全     

学     号        2022112521      

班   级         2203901        

学       生         黄文艺     

指 导 教 师          史先俊     

计算机科学与技术学院

20245

摘  要

本论文旨在通过运用在CSAPP课程中所学知识,分析hello.c程序从预处理,编译,汇编,链接,加载到执行到回收的一系列步骤和过程。通过使用Linux系统下的工具,来分析hello程序的一生,并借此了解c语言程序的执行流程和执行时各硬件的工作情况。

关键词:计算机系统;存储管理;进程管理;                            

(摘要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过程强调了hello是如何从源代码文件变成执行中的进程。P2P可以分为编译过程和执行过程。在编译过程中,hello.c被预处理,编译,汇编和链接,生成了可执行文件hello。在执行过程中,父进程通过fork创建子进程,并调用execve函数加载hello,完成了P2P。

020过程强调了hello从加载到回收的执行过程。 shell为hello的execve映射虚拟内存,并跳转到程序入口后开始载入物理内存,执行目标代码。当Hello执行结束时,shell作为父进程将对其进行回收,释放器占用的资源。实现了020。

1.2 环境与工具

硬件环境:12th Gen Intel(R) Core(TM) i7-12700H 2.30 GHz; 16 GB RAM

操作系统:Windows11;Ubuntu22.04

开发工具:g++,gdb,objdump,edb,VScode

1.3 中间结果

名称

作用

hello.i

预处理生成的文件

hello.s

编译汇编生成的文件

hello.o

汇编生成的elf可重定位目标文件

hello

链接hello和其他库产生的elf可执行目标文件

hellorel.s

hello.o反汇编得到的汇编代码

helloexec.s

hello反汇编得到的汇编代码

hellorel.txt

readelf从hello.o中读取的信息

helloexec.txt

readelf从hello中读取的信息

1.4 本章小结

本章简介了hello的p2p过程和020过程,并说明了实验环境和结果信息。

(第1章0.5分)


第2章 预处理

2.1 预处理的概念与作用

预处理通过预处理器cpp完成,负责处理C语言源代码文件中如宏定义、文件包含,条件编译等代码,即#开头的代码,也会删除注释,更换换行和缩进等,其结果以.i为后缀名。

宏定义在预处理的过程中会进行宏替换,例如#define。在不带参数的宏定义中,要用实际值替换用定义的字符或字符串;而在带参数的宏定义中,不仅仅要进行实际值的替换,还要将参数进行代换。

文件包含指的是对代码中出现的语句进行处理,例如#include。即把引用的源文件直接插入到预处理后的文件中。

条件编译指的是针对例如#if等语句进行的处理。条件编译能够根据#if的不同条件决定需要进行编译的代码,#endif是结束这些语句的标志。

总而言之,预处理是编译的准备工作,经过预处理,源代码中适合人类识别的格式被转换为适合计算机识别的格式,为之后的编译流程做好了准备。

2.2在Ubuntu下预处理的命令

cpp hello.c hello.i (直接调用预处理器)

或 gcc -E hello.c hello.i (使用gcc命令)

2.3 Hello的预处理结果解析

预处理后,hello.i文件的长度为3093行,其中前3078行为头文件的内容,3079~3093行为main函数的内容。可以看到,文件中的注释被删除,缩进也被修改,并把头文件的内容导入了进来。

头文件导入时也递归导入了头文件导入的文件。

2.4 本章小结

本章介绍了预处理的概念和作用,并且以hello.c为例演示了预处理的结果验证了预处理后的文件的内容。

(第2章0.5分)


第3章 编译

3.1 编译的概念与作用

编译程序会进行词法分析,语法分析和源代码优化,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码,其结果以.s为后缀名。

词法和语法分析指编译程序的词法和语法分析器以单词符号作为输入,分析词素和抽象语法树是否正确。

代码优化指对程序进行多种等价变换,使得从变换后的程序出发,能生成更有效的目标代码,可能使用循环优化、死代码消除、常量折叠等技术。

这些操作过后,代码转变成十分接近机器语言的汇编指令。

3.2 在Ubuntu下编译的命令

gcc -m64 -Og -no-pie -fno-stack-protector -fno-PIC -S hello.c -o hello.s

3.3 Hello的编译结果解析

3.3.1 数据

3.3.1.1 常量

常量包括数字和字符串

一方面,在源代码中,比较if(argc != 5)时,对应汇编指令为cmpl $5, %edi,数字常量在汇编中为直接值

另一方面,在源代码中,一共输出了两次字符串

字符串会存储在.Rodata区域,如第一句的标签为.LC0,第二句为.LC1

调用时需要调用标签,如movl $.LC0, %edi

字符串中的中文字符需要用三个字节表示,而ASCII码内的字符直接表示

3.3.1.2 变量

全局变量存储在数据区,而局部变量存储在栈里或寄存器里

Hello.c中的变量主要是循环的局部变量i和main函数参数argc和argv

函数的参数共两个为int型,按顺序存储在%edi,%esi中,而变量i存在寄存器%ebp中

3.3.2 赋值

hello.c中出现了赋值语句i = 0,由于i是一个局部变量,保存在寄存器%ebp中,因此所对应的汇编语句为“movl $0, %ebp”

3.3.3 算数操作

hello.c中出现了自增运算符“++”,由于i是一个局部变量,保存在寄存器%ebp中,因此所对应的汇编语句为“addl $1, %ebp”。

3.3.4 关系操作

hello.c中出现了两个判断“argc!=5”与“i<10”。在汇编语言中, cmp命令比较会将两个操作数相减,但不会改变操作数的值,只会修改标志位。在执行完cmp命令后,通过判断标志位的值,即可得知两个操作数的关系,这个关系一般用于跳转指令。

如语句if(argc!=5)的指令cmpl $5, %edi,如果5和edi存储的值相等,则ZF标志位会置0,此时再执行下一句汇编指令jne .L6,即ZF不为0时跳转,否则继续执行下一条语句。对于i<10同样如此。

3.3.5 数组操作

hello.c中main函数参数argv为一个字符指针数组的头指针,64位运行环境下,每个指针长度位8个字节。

如图所示,在执行“movq %rsi, %rbx”后,argv地址保存到rbx寄存器中,调用argv[1],argv[2],argv[3]时,采取相对寻址方式,直接找8(%rbx)、16(%rbx)、24(%rbx)即可。

3.3.6 控制转移

hello.c内有if与for两个流程控制语句。if语句块内的代码从标签.L6开始,如果“argc!=5”为真,那么就跳转.L6开始执行,该语句块以exit函数结束,因此执行完毕后会直接退出程序。

for语句块内的代码从.L3开始,当执行到“cmpl $9, %ebp”时,如果%ebp仍然小于等于9,就跳转回.L3再执行一遍循环体;如果%ebp的值大于9,则不会往回跳转,而是按顺序继续执行接下来的代码。

3.3.8 函数操作

在hello.c中,没有自定义函数,但对于系统函数(头文件内的函数如puts,exit,getc)的调用。

函数调用首先要把参数存在寄存器%rdi、%rsi、%rdx、%rcx、%r8、%r9中,多于6个参数的函数剩下的参数需要保存在栈中。

调用函数时,新建一个栈帧,首先保存返回地址(call指令),再保存上个函数栈帧的栈顶(push %rbp,mov %rsp,%rbp),ebp中存储新栈顶,接着需要预留缓冲空间给函数内部的局部变量(例如sub $8,%rsp)。执行完函数后,如果需要返回值,则把返回值放入寄存器rax中,并恢复rbp,rsp,(pop,ret指令)

3.4 本章小结

本章讨论了hello.c的汇编过程,并且分析了hello的一些汇编代码。对于C语言中的各个常见的要素,本章简要介绍了它们在汇编语言中是如何表示,执行的。

(第3章2分)


第4章 汇编

4.1 汇编的概念与作用

汇编过程将上一步的汇编代码文件(.s)转换为二进制机器码,产生.o目标文件,这个过程是通过汇编器as完成的。

经过汇编之后,代码已经从可读的汇编语言转换为最底层的机器语言。

4.2 在Ubuntu下汇编的命令

gcc -m64 -Og -no-pie -fno-stack-protector -fno-PIC -c hello.c -o hello.o

4.3 可重定位目标elf格式

4.3.1  ELF头

ELF头描述了文件的类型、系统、架构等基本信息,并给出了各个部分的开始地址、节头部的大小、节头数量等信息。

4.3.2 节头部表

节头表描述了ELF文件中每个节的名称、类型、地址、文件偏移、大小、标志等信息。

4.3.3 重定位表

重定位表是按节存储的,对于.text节则为.rela.text。重定位表主要用于告诉机器在链接过程中该如何修改位置,如指出寻址的类型等。需要重定位的有:.rodata中的模式串,puts,exit,printf,slepsecs,sleep,getchar等符号。

4.3.4 符号表

符号表存放了函数中定义和调用的函数和全局变量。在链接过程中,不同的目标文件可能相互引用变量和函数,链接器将检查这些调用是否冲突,并实现这些调用。

4.4 Hello.o的结果解析

在数的表示上,hello.s中的操作数表现为十进制,而hello.o反汇编代码中的操作数为十六进制。

在控制转移上,hello.s使用.L2和.LC1等段名称进行跳转,而反汇编代码使用目标代码的虚拟地址跳转。不过目前留下了重定位条目,跳转地址为零。它们将在链接之后被填写正确的位置。

在函数调用上,hello.s。对于符号的引用直接通过函数名或变量名表示,helloelf.s中这些符号的地址暂时以0代替,并且给出重定向条目和方法。

4.5 本章小结1·

本章讨论了hello的汇编过程。在汇编过程后,hello.s转换为可重定位目标文件hello.o。通过对于hello.o的分析,可以得到elf文件的基本结构,对hello.o反汇编后,可以了解编译阶段和汇编阶段对应的汇编代码的区别。

(第4章1分)


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

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

5.3.1 ELF头

与hello.o的elf头相比,可以发现其类型从可重定向目标文件变成了可执行目标文件。同时,程序主入口点、程序头部等原本为空的数值也已经被填入。

5.3.2 节头部表

与hello.o的节头部表相比,数量更多(如.interp,.dynamic等,用于动态链接相关的内容)

5.3.3 程序头表

Hello.o中不含程序头表,程序投标主要描述磁盘上可执行文件的布局,以及可执行文件是如何被映射到内存中的。

5.3.4 动态段

5.3.5 重定向节

虽然链接器已经处理了大部分重定向段,但由于动态链接库的存在,有的库需要程序启动才链接。

5.3.6 符号表

5.4 hello的虚拟地址空间

    

可以看到,虚拟地址空间为0x400000-0x405000

对于.text段,根据5.3中内容可知从0x4010f0开始。

对于.rodata段,从0x402000开始,里面存储了字符串,符合实际。

5.5 链接的重定位过程分析

5.5.1 不同

hello反汇编的结果有更多函数和节而hello.o只有main函数

且hello中的地址变为了真实的虚拟地址,而hello.o所有地址均以0代替

5.5.2 重定向过程

例如这一句的提示是R_X86_64_32即应填入.rodata.str1.8的直接地址,查看5.4节的地址知为0x402008,再根据小端法写入文件

符合实际结果,对于其他需要链接的指令也同此过程.

5.6 hello的执行流程

在加载hello的过程中,加载器根据hello的程序头部表将hello的各段映射到内存当中。然后将PC指向Hello的入口点,开始执行hello。hello的执行过程中线调用_init、_start进行初始化,然后进入hello的主流程。

hello的主流程中先检查命令函参数是否正确,然后重复输出十遍Hello 学号 姓名,输出之间等待指定的秒数。

这些工作完成后,hello通过一个getchar()等待输入,接收到输入之后退出。

子程序名

程序地址

hello!_init

0x0000000000401000

hello!_start

0x00000000004010f0

hello!main

0x00000000004011d6

hello!__printf_chk@plt

0x0000000000401050

hello!puts@plt

0x0000000000401030

hello!sleep@plt

0x0000000000401070

hello!getc@plt

0x0000000000401080

hello!exit@plt

0x0000000000401060

5.7 Hello的动态链接分析

在调用共享库函数时,链接器会对这个函数生成一个重定位条目。动态链接器将在程序加载时对于该条目进行解析。GNU编译系统使用延迟绑定,将绑定地址过程推迟到第一次调用该过程时。绑定是通过GOT和PLT实现的。

执行_init之前的GOT与PLT:

执行_init之后的GOT与PLT:

5.8 本章小结

本章展示了链接前后可重定位目标文件与可执行目标文件的区别,可执行目标文件如何使用虚拟内存的以及动态链接器是如何把符号动态加载到hello中的。

(第5章1分)


6hello进程管理

6.1 进程的概念与作用

进程是一个正在运行的程序的实例。进程的代码、数据以及支持进程运行的环境合称为进程的上下文。

进程给应用程序提供了两个关键抽象:

(1)每个应用程序似乎独立地使用CPU

(2)每个应用程序似乎独占地使用内存系统

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

shell是一个交互型应用级程序,代表用户运行其他程序,其处理流程如下:

(1)从键盘读取并解析指令;

(2)如果指令是内部命令,则立即处理它;

(3)如果指令是可执行文件,则在子进程的上下文中加载和运行它;

(4)如果指令以“&”结尾,则让作业在后台执行,继续接收下一个命令;否则在前台执行作业,等待作业结束再接收下一个命令。

6.3 Hello的fork进程创建过程

在shell中执行Hello时,父进程(shell进程)通过调用fork函数创建一个新的运行的子进程。新创建的子进程拥有与父进程用户级虚拟空间相同但独立的一份副本,包括代码和数据段、堆、共享库以及用户栈.

6.4 Hello的execve过程

在创建子进程之后,子进程会调用execve函数。该函数在当前上下文中加载一个新程序,加载过程包括以下几步:

(1)删除子进程现有的虚拟内存段(注意是写时复制,需要先复制一份再删除修改);

(2)将新程序的代码、数据、bss段映射到虚拟内存中,将栈与堆初始化为0;

(3)映射共享区:Hello程序与标准C库libc.so链接,这些对象都是动态链接到Hello的,然后设置到虚拟地址空间中的共享区域内。

(4)让PC指向新程序的入口点

在这个过程完成之后,子进程会开始执行新加载的程序。

6.5 Hello的进程执行

hello进程具有独立的逻辑控制流,这通过计时器实现,当计时器减为0,控制从进程切换到内核,内核将进行上下文切换,将控制转移给其他进程。此时hello进程暂停执行。当控制切换回hello进程时,hello进程就可以继续执行了。在这种调度方式下,尽管同时只有一个进程在cpu上运行(单核处理器),但可以近似地认为进程是并行执行的。

此外,在hello进程遇到异常时,控制权也会转移到内核,由内核进行系统调用操作。

6.6 hello的异常与信号处理

hello执行过程中可能出现的异常包括中断、陷阱、故障、终止等。

6.6.1 正常运行

需要getchar()终止

6.6.2 乱按

输入的字符会储存到缓冲区,没有被hello处理的输入会被当做是shell的输入,作为命令处理。

6.6.3 ctrl-c

Ctrl-c给shell发送SIGINT信号,shell将此信号转发给前台进程组的每个进程,hello的默认信号处理程序会终止hello进程的执行。

6.6.4 ctrl-z

Ctrl-z给shell发送SIGTSTP信号,shell将此信号转发给前台进程组的每个进程,hello的默认信号处理程序会停止hello进程的执行。

6.6.4 ctrl-z+ps-au

6.6.4 ctrl-z+jobs

6.6.4 ctrl-z+pstree

进程树

6.6.4 ctrl-z+fg %1

切换到前台执行

6.6.4 ctrl-z+kill %1

终止进程

6.7本章小结

本章主要讨论了hello进程的创建,以及其运行(作业)的流程,对于遇到的异常和信号处理也进行了分析.

(第6章1分)


7hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:逻辑地址由段标志符与段内偏移量组成,是一种相对位置,如hello.o内的地址。

线性地址:逻辑地址的段地址+偏移地址得到线性地址;线性地址空间是非负整数地址的有序集合。

虚拟地址:虚拟地址与线性地址是相似的;虚拟地址空间是一个拥有2的n次方个地址的有序集合,hello运行在虚拟地址空间中。

物理地址:每个物理地址与系统物理内存的一个字节相对应。

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

在保护模式下,以段描述符作为下标,到GDT或者LDT中查表获得段地址。段地址+偏移地址=线性地址。

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

虚拟地址分为两个部分:VPN(虚拟页号)和VPO(虚拟页偏移量)。由于页大小相同,因此VPO=PPO.要得到PPN需要查询当前进程的页表,一方面通过VPN查找页表中的项,看是否缓存了该地址,另一方面找到后需要判断其有效位是否为1.

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

VPN分为TLBT和TLBI,按照类似的方式看TLB中是否缓存了PTE,如果有则和VPO组成PA访问Cache,否则需要进行四级页表查询,查询到后写入TLB,再访问缓存取数据.

四级页表主要是减小页表的大小,节省内存空间。

在四级页表中,虚拟地址的VPN被分为4个,第i个VPN是第i级页表的索引。前三级页表的条目是一个指向低一级的页表的索引,而第四级页表的条目包含了有效位、PPN等信息。由于某个页表条目不存在时,他所指向的更低级的页表也无需存在,因此可以节省储存页表的内存空间。

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

在完成了从VA到PA的转化之后,就可以通过PA访问物理内存了。

Core i7有三级缓存L1,L2,L3 Cache,其中的L1 Cache为组相联高速缓存,总共有64组、每组8路、块大小64kb。

物理地址PA被划分为标记、组索引、块偏移。

首先通过组索引来定位到L1 Cache中的某组,然后将标记位与L1 Cache中每路的标记位比较。如果匹配成功且对应路的有效位为1,即为命中,可以通过块偏移从高速缓冲块中取出相应的数据。

若没有匹配成功或者匹配成功但是标志位为1,即为缓存不命中。需要向下一级缓存请求数据(L2 Cache、L3 Cache、主存)。取出数据后会写入或替换当前缓存。

7.6 hello进程fork时的内存映射

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

当两个进程中的任意一个对虚拟内存的某个页面进行写操作时,会触发保护故障,此时写时复制机制会在内存中创建一个对应页面的副本,并更新页表条目指向副本。此时进程对于内存的修改就会反应在副本页面上。

7.7 hello进程execve时的内存映射

在通过execve加载新进程时,需要以下几个步骤:

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

映射私有区域。为新程序(即hello)的代码、数据、bss和栈区域等创建新的区域结构。所有这些区域都是私有的、写时复制的。

映射共享区域。如果hello程序与共享对象(或目标)链接(如标准C库),那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。

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

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

缺页故障的产生:在对于虚拟地址进行翻译时,如果发现对应的PTE的有效位未设置,说明虚拟地址对应的内容还没有缓存在物理内存中,此时会触发一个缺页故障。

在发生了缺页故障时,会调用内核中的缺页异常处理程序,如果缺页原因是该页还未缓存到高速缓存中,则该程序会选择一个空白页或是牺牲页,用当前请求的内容替换之,并修改牺牲页对应的页表条目。如果磁盘内该页本身有问题,则报错中断程序.

7.9动态存储分配管理

动态内存分配器维护着一个进程的虚拟内存区域,称为堆。对每个进程,内核维护一个全局变量brk指向堆顶。

分配器将堆视为一组不同大小的块的集合来维护。每个块是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。一个已分配的块保持已分配的状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。

分配器的具体操作过程如下:

(1)放置已分配块:当一个应用请求一个k字节的块时,分配器搜索空闲链表。查找一个足够大可以放置所请求的空闲块.

(2)分割空闲块:一旦分配器找到了匹配的空闲块,需要决定分配这个空闲块中多少空间。可以选择用整个块,但会造成额外的内部碎片;也可以选择将空闲块分割为两部分,第一部分变成已分配块,剩下的变成新的空闲块。

(3)获取额外的堆内存:如果分配器不能为请求块找到空闲块,分配器通过调用函数向内核请求额外的堆内存.

(4)合并空闲块:分配器释放一个已分配块时,要合并相邻的空闲块。

7.10本章小结

本章主要介绍了Hello的虚拟内存空间是如何转换成物理内存空间的。同时也介绍了这种储存机制在fork、execve时的应用,以及动态分配器的原理。

(第7章 2分)


8hello的IO管理

8.1 Linux的IO设备管理方法

Linux将文件所有的I/O设备都模型化为文件,甚至内核也被映射为文件。这种称为Unix I/O。Linux就是基于Unix I/O实现对设备的管理。

8.2 简述Unix IO接口及其函数

打开文件:

open函数将filename转换为一个文件描述符,并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符。flags参数指明了进程打算如何访问这个文件;mode参数指定了新文件的访问权限位。

读写文件:

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

关闭文件:

fd是需要关闭的文件描述符,成功返回0,错误返回-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;

}

首先,printf创建一个缓冲区,然后调用vsprintf函数对于格式串进行解析,将待输出的内容存进buf变量中,将待输出的字节数存入i中。

然后调用write函数,该函数将触发一个系统调用,将控制转移到内核并将buf在标准输出上输出。

在UNIX IO中,标准输出stdout也被作为文件处理,当需要将stdout输出到屏幕上时,需要字符显示驱动子程序读取stdout中的数据(UTF-8编码),并从字模库中取出对应的字模加载到显示vram(存储每一个点的RGB颜色信息)。

显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量),便完成了字符串到屏幕的输出过程.

8.4 getchar的实现分析

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

当getchar被调用时,它会试图缓冲区中读取一个字符。如果缓冲区为空,getchar将调用read系统函数来从键盘缓冲区中读取数据到自身的缓冲区(直到接收到回车键才返回)。当缓冲区填充完成之后,getchar函数从缓冲区中读取一个字符后返回。

8.5本章小结

本章介绍了Unix IO的设计理念以及其提供的接口和函数,并通过printf和getchar 两个函数分析了IO函数是如何与Unix IO协同工作的。

结论

Hello的历程:

编写:编写hello.c文件;

预处理:C预处理器对hello.c进行宏替换得到hello.i;

编译:C编译器将hello.i编译为汇编语言文件hello.s

汇编:汇编器将汇编语言文件hello.s文件汇编为二进制的可重定位目标文件hello.o。

链接:链接器将hello.o与其他库链接得到可执行目标文件hello

运行:父进程(Shell等)通过fork创建子进程,通过execve加载hello程序。

运行中:hello的代码和数据从磁盘加载到主存和Cache;内核对hello进行调度,hello的逻辑控制流在数个时间片中运行(并行);hello在运行过程中对接受到的信号作出响应。

终止:hello终止后,由hello的父进程负责回收hello,清除hello的痕迹。

感悟:

计算机系统是由硬件和软件组成的,他们共同工作来运行程序.虽然系统的实现方法一直在变化,但其内在的概念们没有改变.所有的系统都有相似的硬件和软件,也执行着相似的功能,了解这些功能对于程序员来说不仅是了解程序是怎样运行的,代码会经过几次处理才能实现想要的效果,更重要的是能根据这种实现方式解决代码可能的安全漏洞,或是对代码进行优化处理,加速程序的运行,又或是对出现的报错有更深层次的理解,能迅速给出解决方案.

通过对这门课程的学习,对于这些技巧有了一定的了解,但是只有在实践中才能更熟练的使用这些技巧!

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


附件

名称

作用

hello.i

预处理生成的文件

hello.s

编译汇编生成的文件

hello.o

汇编生成的elf可重定位目标文件

hello

链接hello和其他库产生的elf可执行目标文件

hellorel.s

hello.o反汇编得到的汇编代码

helloexec.s

hello反汇编得到的汇编代码

hellorel.txt

readelf从hello.o中读取的信息

helloexec.txt

readelf从hello中读取的信息

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


参考文献

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

[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] https://zhuanlan.zhihu.com/p/384701815

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

  • 9
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值