程序人生-Hello’s P2P

**

程序人生-Hello’s P2P

**

摘 要

本文主要运用本学期所学的计算机系统有关知识,在Linux系统下完整的跟踪hello的P2P、020过程,以加深对系统运行程序的认识。

关键词:程序生命周期;P2P;020

第1章 概述

1.1 Hello简介

根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。

1.2 环境与工具

硬件:x64 CPU;2.30GHz;8G RAM

软件:Microsoft Windows10 家庭中文版;Ubuntu18.04
LTS

工具:objdump、gdb、edb

1.3 中间结果

在这里插入图片描述

1.4 本章小结

本章对为完成本次作业所做的准备做了介绍。

第2章 预处理

2.1 预处理的概念与作用

预处理:预处理是指程序在被编译前执行的处理。C语言的预处理主要有以下几个方面:1.宏定义 2.文件包含 3.条件编译

对于宏定义,预处理过程又叫宏展开,即将宏名替换为文本,带参数的宏定义除了一般的字符串替换外还要做参数代换。总之,对于宏定义,预处理的工作就是一个“换”,在对相关命令或语句的含义和功能做具体分析之前就要换。

文件包含是指一个文件包含了另一个文件的内容,预处理对文件进行包含处理,编译时以包含处理后的文件为编译单位,被包含文件是源文件的一部分。被包含的文件又被称为“标题文件”或“头部文件”、“头文件”,且常用.h做扩展名。

使用条件编译可以使目标程序变小,运行时间变短。

2.2在Ubuntu下预处理的命令

gcc –E hello.c –o hello.i

图2-1 预处理命令

2.3 Hello的预处理结果解析

图2-2 预处理结果(部分)

由图2-2,预处理后的文件仍然是一个C程序,所不同的是文件大小发生了变化,文件由原来的28行变为3118行,这是进行了宏展开和文件包含处理的结果。经过预处理,原C程序中包含的头文件被插入。

2.4 本章小结

对于宏定义和文件包含,预处理主要是“换”和“插入”,即将宏定义替换为相应的文本,有参数的还要进行参数代换;将包含的头文件插入到程序文本中。而条件编译则是选出需要进行编译的部分保留到程序文本中。

本阶段完成了对hello.c的预处理生成了hello.i文件,为后续的编译工作做了准备。

第3章 编译

3.1 编译的概念与作用

编译器将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。这个过程称为编译,同时也是编译的作用。

编译器的构建流程主要分为3个步骤:

1.词法分析器,用于将字符串转化成内部的表示结构。

2.语法分析器,将词法分析得到的标记流(token)生成一棵语法树。

3.目标代码的生成,将语法树转化成目标代码。

3.2 在Ubuntu下编译的命令

gcc –S hello.c –o hello.s
图3-1 编译命令
3.3 Hello的编译结果解析3.3.

图3-2全局变量和全局函数

hello.c中定义了全局变量sleepsecs与全局函数main(int argc,char* argv[])。编译后,sleepsecs被存放在了.rodata节,main函数中的字符串常量也被存放在数据节。

3.3.2 隐式类型转换

图3-3 源程序中对sleepsecs的声明与赋值
图3-4 编译后的sleepsecs

在hello.c中将sleepsecs声明为int型,但用2.5为其赋值,根据隐式类型转换的规则,赋值运算符右边的数据类型必须转换成赋值号左边的类型,若右边的数据类型的长度大于左边,则要进行截断或舍入操作,因此在编译时sleepsecs被赋值为2。

3.3.3 主函数的参数

图3-5 主函数的参数
主函数的参数列表中有两个参数,分别为int argc与char *argv[],结合后面的cmpl语句知参数argc被保存在了地址为R[%rbp] - 20的内存单元中;argv[]作为一个指针数组,其首地址被保存在了地址为R[%rbp] - 32的内存单元中。

3.3.4 局部变量

图3-6 源程序中的局部变量
图3-7 编译后局部变量的位置
在主函数中仅声明了一个局部变量int i作为for循环的循环控制变量,因此根据编译后找到更新及检查循环控制变量的地方,得到i存放在地址为R[%rbp] – 4的内存单元。

3.3.5 条件分支语句

图3-8 源程序中的if语句

图3-9 编译后的if语句

进入main函数后,首先使用if进行了条件判断,编译后的程序if的条件判断通过cmpl及相应的条件跳转实现。如果满足条件,则继续执行,否则输出提示信息并以1为参数调用exit退出程序。

3.3.6 循环控制语句

图3-10 编译后的for循环语句

if判断成功后就进入了for循环语句。.L2中的语句是将循环控制变量i初始化为0,并将控制转移给负责判断循环是否进行的.L3,接着如果条件满足,.L3将控制交给循环体.L4执行循环体内的命令,.L4的最后一句更新循环控制变量,接着控制又交给了.L3。如此循环一直到循环控制变量i>9时循环结束,执行后面的语句。

3.3.7 数组访问

图3-11 对数组argv的访问

循环体内的操作是输出命令行中的后两个参数,因此需要访问argv[1]与argv[2]指向的字符串。图3-11中的13行即取出了argv[2]的值存在了三号参数寄存器rdx中;46行取出了argv[1]的值存在了二号参数寄存器rsi中,然后将printf的格式字符串的首地址存在一号参数寄存器rdi中,这就完成了调用printf前的参数准备。

3.3.7 函数操作

1.printf函数的调用

printf函数的第一次调用是在输出提示信息时,参数是要输出字符串的首地址。(此时printf函数被优化为puts)

printf第二次调用是在循环体中输出命令行参数时,此时printf的参数是格式字符串的首地址、argv[1]、argv[2]。

2.主函数

在主函数的最后一句将返回值赋为0,使函数正常退出将控制交给内核。

3.4 本章小结

本阶段完成了对hello.i扩展文件的编译。在Ubuntu下使用编译指令即可完成对.i文件的编译。本章通过比较源程序代码与生成的汇编代码,完成了对汇编代码的解析工作

4.1 汇编的概念与作用

把汇编语言翻译成机器语言的过程称为汇编。汇编过程中输入的是用汇编语言书写的源程序,输出的是用机器语言表示的目标程序。汇编语言是为特定计算机或计算机系列设计的一种面向机器的语言,由汇编执行指令和汇编伪指令组成。

汇编算法采用的基本策略是简单的。通常采用两遍扫描源程序的算法。第一遍扫描源程序根据符号的定义和使用,收集符号的有关信息到符号表中;第二遍利用第一遍收集的符号信息,将源程序中的符号化指令逐条翻译为相应的机器指令。

4.2 在Ubuntu下汇编的命令

gcc –c hello.s –o hello.o

图4-1 汇编命令

4.3 可重定位目标elf格式

图4-2 hello.o的ELF头

使用readelf –h hello.o观察hello.o的elf头,如图4-2,hello.o为可重定位文件,有13个节。

使用readelf –S hello.o观察hello.o各个节的内容,得到的信息如图4-3
图4-3 hello.o节头表

通过图4-3所示的表,能看出各节的大小、地址及可执行的操作等。。在文件头中得到节头表的信息,然后再使用节头表中的字节偏移信息得到各节在文件中的起始位置,以及各节所占空间的大小,就可以基本定位要找的部分在目标文件的位置。另外,由于是可重定位文件,各节的地址都是0以用于重定位。

图4-4 hello.o符号表

另外,从符号表里可以看出,由于还没有与库进行链接,因此调用的库中的函数puts、exit、printf、sleep、getchar的类型都是“无类型”。

4.4 Hello.o的结果解析

图4-5 hello.o文件反汇编

与汇编语言相比,反汇编后得到的结果在逻辑结构与处理流程并无太大出入,主要有以下几个不同:

1.操作数:汇编语言中的操作数采用对人友好的十进制,而.o文件中的操作数采用了对机器友好的十六进制。

2.分支转移:在汇编语言中跳转的目的地是通过像是“.L2”、“.L4”这样的伪地址标识的;而在反汇编结果中,是通过相对于所在函数的偏移量标识。

3.函数调用:在汇编语言中直接用“call 函数名”,在反汇编结果中,每有一次引用,无论是变量还是函数,都会在引用后加一行重定位条目

4.5 本章小结

本阶段完成了对.s文件的汇编,生成了可重定位文件,为下一步的链接并生可执行文件做了准备。此外,本章还比较了.o文件的反汇编结果与.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

图5-1 链接命令

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

图5-2 hello文件的ELF头
由图5-2文件格式为可执行文件。
图5-3 hello文件节头表(1)
图5-3 hello文件节头表(2)

5.4 hello的虚拟地址空间

图5-4 hello虚拟地址空间
使用edb加载hello查看本进程虚拟地址空间,根据5.3中的信息可查看各段的二进制信息。代码段的信息如图5-4标记所示,开始于0x400520,大小为0x1e2。

5.5 链接的重定位过程分析
图5-5 hello文件反汇编结果(部分)

与hello.o文件的反汇编结果相比,hello文件中不是以.text节开始,而是以.init节开始;而且每条语句的地址也不再是在文件内的偏移,而是相应的虚拟地址;同时,函数中的对函数的调用也都是用函数的首地址标识的。

链接的过程:

1.符号解析

链接器将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来。对于那些定义和引用在相同模块中的局部符号引用,符号解析十分简单明了。对于外部引用,链接器有如下过程解析。

先说明三个集合:E 将被合并可以组成可执行文件的所有目标文件集合

            U 当前所有为解析的引用符号集合

            D 当前所有定义符号的集合

按照命令行给出的顺序扫描.o文件和.a文件。

扫描期间将当前为解析的引用记录到一个列表U中

每遇到一个新的.o或.a中的模块,都试图用来解析U中的符号

如果扫描到最后,U中还有未被解析的符号,则发生错误。

到此,符号解析完成

2.重定位

首先合并相同的节,即将集合E中的所有目标模块中相同的节合并成新节

然后,链接器将运行时内存的地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号。

对引用符号进行重定位。

5.6 hello的执行流程

main之前:

<ld-2.27.so!_dl_start>

<ld-2.27.so!_dl_init>

<hello!_start>

<libc-2.27.so!_libc_start_main>

<libc-2.27.so!_cxa_atexit>

<libc-2.27.so!_new_exitfn>

<libc-2.27.so!_libc_csu_init>

< libc-2.27.so!
_setjmp >

<libc-2.27.so!_sigsetjmp>

<libc-2.27.so!_sigjmp_save>

main之后:

<ld-2.27.so!_dl_runtime_resolve_xsave>

<ld-2.27.so!_dl_fixup>

<ld-2.27.so!_dl_lookup_symbol_x>

<ld-2.27.so!_do_lookup_x>

5.7 Hello的动态链接分析

dl_init运行前后GOT的内容发生了变化。
图5-6 GOT地址

在运行dl_init之前,GOT各项内容都为零,运行之后,都被赋上了相应的偏移量。

5.8 本章小结

本阶段完成了对hello.o文件的链接工作,生成了可直接运行的hello可执行目标文件。本章对链接后的结果与之前的结果比较,突出了连接的作用。对动态链接做了简要的分析。

第6章 hello进程管理

6.1 进程的概念与作用

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

作用:对正在运行的程序过程的抽象

  清晰地刻画动态系统的内在规律,有效管理和调度进入计算机系统主存储器运行的程序。

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

shell作为UNIX的一个重要组成部分,是它的外壳.也是用户与UNIX系统的交互作用界面.Shell是一个命令解释程序.除此,它还是一个高级程序设计语言.

流程:

(1).读取输入的命令行。

(2).解析引用并分割命令行为各个单词,各单词称为token。其中重定向所在的token会被保存下来,直到扩展步骤(5)结束后才进行相关处理,如进行扩展、截断文件等。

(3).检查命令行结构。主要检查是否有命令列表、是否有shell编程结构的命令,如if判断命令、循环结构的for/while/select/until,这些命令属于保留关键字,需要特殊处理。

(4).对第一个token进行别名扩展。如果检查出它是别名,则扩展后回到(2)再次进行token分解过程。如果检查出它是函数,则执行函数体中的复合命令。如果它既是别名,又是函数(即命令别名和函数同名称的情况),则优先执行别名。在概念上,别名的临时性最强,优先级最高。

(5).进行各种扩展。扩展顺序为:大括号扩展;波浪号扩展;参数、变量和命令替换、算术扩展(如果系统支持,此步还进行进程替换);单词拆分;文件名扩展。

(6).引号去除。经过上面的过程,该扩展的都扩展了,不需要的引号在此步就可以去掉了

(7).搜索和执行命令

(8).返回退出状态码

6.3 Hello的fork进程创建过程

在输入./hello命令后,shell调用fork函数创建一个子进程,之后内核为hello进程创建各种数据结构,并分配给它们一个唯一的PID。

内核还创建当前进程的mm_struct、区域结构和页表的原样副本,它将父、子进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。这样,hello进程得到了与父进程用户级虚拟空间相同的(但是独立的)一份副本,包括数据段、堆、共享库以及用户栈。

子进程还获得与父进程任何打开文件描述符相同的副本,这就意味着父进程调用fork时子进程可以读写父进程中打开的任何文件。

shell进程与hello进程最大的区别在于两者PID不同。同时fork调用一次,返回两次,在父进程中返回子进程PID,在子进程中返回0.

6.4 Hello的execve过程

execve函数在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。有几个步骤:

删除已存在的用户区域

映射私有区域

映射共享区域

设置程序计数器

6.5 Hello的进程执行

上下文:内核重新启动一个被抢占的进程所需的状态。它由一些对象的值构成,这些对象包括目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构。

调度:在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这种决策就叫调度。

时间片:是分时操作系统分配给每个正在运行的进程微观上的一段CPU时间。

Linux下每个程序都运行在一个上下文中。Hello起初运行在用户模式中,当调用sleep函数时,内核进行调度执行上下文切换,hello进入内核模式,内核将上下文控制权交给其他进程,计时器开始计时。当时间到两秒后,定时器发送一个中断信号,内核执行中断处理,又将上下文的控制权交给了hello,hello继续运行自己的逻辑流。

6.6 hello的异常与信号处理

1.正常结束:由于最后是一个getchar函数,必须得从键盘输入,因此这种情况不太可能发生。

2.来自I/O的异常

乱按:不会影响运行,输入的字符都被getchar读到缓冲区。但在乱按回车后,第一个回车被getchar读入,剩下的就被当作是shell的命令。

Ctrl-Z:

图6-1 Ctrl-Z
图6-2 fg
图6-3kill
Ctrl-C:
图6-4 Ctrl-C
图6-5 pstree(部分)

6.7本章小结

本章简述了进程的定义和作用,fork与execve的作用,大致了解了其原理。还了解了异常的处理以及进程上下文的切换

第7章 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址(LogicalAddress)是指由程序产生的与段相关的偏移地址部分

线性地址(Linear Address)是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。

物理地址:在存储器里以字节为单位存储信息,为正确地存放或取得信息,每一个字节单元给以一个唯一的存储器地址,称为物理地址(Physical Address),又叫实际地址或绝对地址。

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

一个逻辑地址是由一个段标识符加上一个指定段内相对地址的偏移量构成,表示为[段标识符:段内偏移量]。

首先看段选择符的T1 = 0还是1,知道当前要转换的时是全局段描述符表中的段还是局部段描述符表中的段,再根据相应寄存器得到其地址和大小

拿出段选择符中前13位的索引在相应的数组中查找到对应的段描述符,这样就找到了它的基地址。

基地址 + 段内偏移量就是要转换的线性地址。

图7-1 段式管理

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

线性地址被分成了3部分,由低位到高位依次为VPO、TLB索引、TLB标记。其中TLB是位于MMU中的关于PTE的缓存,称为翻译后被缓冲器。

进行地址翻译时,现根据TLB索引及标记找到相应PPN,若标记位的有效位为0,则直接到PTE中去找,将PPN与VPO串联起来,就得到了相应的物理地址。

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

图7-2给出了Core i7MMU如何使用四级的页表来将虚拟地址翻译成物理地址。36位的VPN被划分成四个9位的片,每个片被用作到一个页表的偏移量。CR3寄存器包含L1页表的物理地址。VPN1提供到一个L1 PTE的偏移量,这个PTE包含L2页表的基地址。VPN2提供到一个L2 PTE的偏移量,以此类推。

图 7-2 Core i7页表翻译

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

首先CPU生成一个虚拟地址,经过地址翻译后将其转化为物理地址,然后根据物理地址的各字段访问L1 Cache请求数据,若发生不命中,则向下一级缓存中请求数据(L2 ->L3->主存)
图7-3 Core i7内存系统
图7-4 Core i7地址翻译

7.6 hello进程fork时的内存映射

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

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

7.7 hello进程execve时的内存映射

execve函数在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。有几个步骤:

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

2.映射私有区域:为新程序的代码、数据、bss和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data区。bss区域是请求二进制0的,映射到匿名文件,其大小包含在hello文件中。栈和堆区域也是请求二进制0的,初始长度为0.

3.映射共享区域:与hello程序链接的共享对象动态链接到程序,然后再映射到用户虚拟地址空间中的共享区域内。

4.设置程序计数器:使PC指向代码区域的入口点。

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

缺页中断就是要访问的页不在主存,需要操作系统将其调入主存后再进行访问。在这个时候,被内存映射的文件实际上成了一个分页交换文件。

缺页中断处理:

如图7-5所示,CPU引用VP3的一个字,地址翻译硬件从内存读取PTE3,从有效位判断出VP3为被缓存,触发缺页异常。缺页异常调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,在本例中为存放在PP3中的VP4。
图7-5 VM缺页(之前)
接下来,内核从磁盘复制VP3到内存中的PP3,更新PTE3。随后返回
图7-6 VM缺页(之后)

7.9动态存储分配管理

分配器将堆视为一组不同大小的块 (blocks)的集合来维护,每个块要么是已分配的,要么是空闲的。

记录空闲块:隐式空闲链表,显示空闲链表,红黑树等

放置以分配的块:

首次适配:从链表开始寻找适合的空堆块,直到找到为止

下一次适配:从上一次的分配点开始找,直到找到适合的空堆块为止

最佳适配:查询整个链表,找到最合适(浪费最小)的空堆块

块的分割:当前的空堆块大小大于所需要大小且剩余部分若满足最小要求,则将块分两为两部分

获取额外的堆存储器:

当链表中不能满足申请要求的堆块空间的时候,1)通过合并相邻的堆块空间,形成单个尽量大的堆块空间 2)实在没有其他办法了,分配器通过sbrk函数向内核申请格外的堆空间,分配器将堆空间插入到链表中,然后提供给申请空间的块。

7.10本章小结

本章主要借助hello这一具体进程介绍了Unix的存储管理,包括段式管理、页式管理,有介绍了地址翻译与访存,并简单介绍了C语言中的显示动态内存分配。

(第7章 2分)

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

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

设备的模型化:文件

类型:

 普通文件

 目录

 套接字

设备管理:unix io接口

所有的I/O设备都被模型化为文件,而所有的输入输出都被当作对相应文件的读和写来执行。这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O。

8.2 简述Unix
IO接口及其函数

Unix IO接口:

1.打开文件:一个应用程序通过要求内核打开相应文件来宣告他想要访问一个I/O设备。内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件内核记录有关这个文件的所有信息。应用程序只需记住这个描述符。

2.Linux shell创建的每个进程开始时都有三个打开的文件:标准输入(描述符为0)、标准输出(描述符为1)和标准错误(描述符为2)。

3.改变当前文件的位置。对于每个打开的文件,内核保持这一个文件位置k,初始为0。应用程序能够通过执行seek操作,显式地设置文件的当前位置为k。

4.读写文件。一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n。类似地,写操作就是从内存复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。

5.关闭文件。当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。

函数:

1.打开文件

int
open(char *filename , int flags , mode_t mode);

open函数将filename转换成一个文件描述符,并且返回描述符数字,出错则返回-1

参数flags指明了进程打算如何访问这个文件,也可以是一个或者更多位掩码的或,为写提供一些额外的指示

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

2.关闭文件

int
close(int fd);

成功返回0,出错返回-1

3.读文件

ssize_t
read(int fd , void *buf , size_t n);

返回:成功则为读的字节数,若EOF则为0,若出错为-1

4.写文件

ssize_t write(int fd , const void *buf ,
size_t n);

返回:若成功则为写的字节数,出错则为-1

8.3 printf的实现分析

首先看看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;

}

函数的定义过程中使用了可变参数。

其中,fmt是指向字符的指针,指向第一个参数,这个参数是固定的,可以通过这个参数的位置及C语言函数参数入栈的特点来引用其他可变参数。

vsprintf函数返回打印字符串的长度,它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。

之后调用write(buf,i),查看write汇编代码

write:

 mov eax, _NR_write 

 mov ebx, [esp + 4] 

 mov ecx, [esp + 8] 

 int INT_VECTOR_SYS_CALL

找到INT_VECTOR_SYS_CALL的实现:

init_idt_desc(INT_VECTOR_SYS_CALL,DA_386IGate,
sys_call, PRIVILEGE_USER);

通过系统调用了sys_call,猜测就是这个函数驱动了显示器,在看它的代码:

sys_call:

 call save 

 push dword [p_proc_ready] 
 
 sti 

 push ecx 

 push ebx 

 call [sys_call_table + eax * 4] 

 add esp, 4 * 3 

mov [esi + EAXREG - P_STACKBASE], eax 

cli 
ret

最终将格式化了的字符串显示在屏幕上。以看出代码里面的call是访问字库模板并且获取每一个点的RGB信息最后放入到eax也就是输出返回的应该是显示vram的值,然后系统显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

当程序调用getchar时.运行程序时就等着用户从按键输入,用户输入的字符被存放在键盘缓冲区中.直到用户按回车为止(回车字符也放在缓冲区中),当用户键入回车之后,getchar才开始从输入流中每次读入一个字符,输入的字符不只一个的时候,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完之后,才等待用户按键,getchar函数输入数字也按字符处理,单个的getchar函数输入多于一个字符时,只接收第一个字符.

8.5本章小结

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

结论

1.hello.c通过I/O键入,存入主存。

2.利用预处理器,对.c文件预处理,生成hello.i文件

3.利用编译器,将文本文件hello.i翻译成包含一个汇编语言程序的文本文件hello.s

4.利用汇编器将hello.s翻译成机器语言指令,生成了可重定位目标文件hello.o

5.利用链接器将系统的库与hello.o链接,生成可执行目标文件hello

6.在命令行中键入“./hello”,shell调用fork函数为hello创建子进程

7.shell调用execve函数创造一个新进程即将hello安排进去

8.CPU为hello分配时间片,在内核的调度下,hello在自己的上下文中运行自己的逻辑流。

9.对于一些异常和中断信号hello可以正常处理

10.hello运行结束后,shell会对其进行回收,内核删除在创建hello进程时建立的记录。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值