计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 人工智能(2+X)
学 号 2021112001
班 级 21WL022
学 生 商柏淳
指 导 教 师 史先俊
计算机科学与技术学院
2023年4月
本文将基于本学期所学的计算机系统[1]课程内容,对Hello.c程序进行深入分析,对Hello程序的From Program to Process与From Zero-0 to Zero-0进行介绍,将C语言源文件进行预处理,编译,汇编,链接,最终形成可执行目标文件存储在磁盘当中。操作系统为其创建进程并分配内存,由CPU控制运行逻辑流的运行,最后至Hello程序结束。
关键词:P2P 020 计算机系统
目 录
2.2在Ubuntu下预处理的命令.......................................................................... - 5 -
3.2 在Ubuntu下编译的命令............................................................................. - 6 -
4.2 在Ubuntu下汇编的命令............................................................................. - 7 -
5.2 在Ubuntu下链接的命令............................................................................. - 8 -
5.3 可执行目标文件hello的格式.................................................................... - 8 -
6.2 简述壳Shell-bash的作用与处理流程..................................................... - 10 -
6.3 Hello的fork进程创建过程..................................................................... - 10 -
6.6 hello的异常与信号处理............................................................................ - 10 -
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 -
8.1 Linux的IO设备管理方法.......................................................................... - 13 -
8.2 简述Unix IO接口及其函数....................................................................... - 13 -
第1章 概述
1.1 Hello简介
Hello的P2P:P2P即From Program to Process。首先由用户在代码编辑器上编写hello.c的源程序,这便是Program。再调用C预处理器得到hello.i,再经过ccl得到汇编文件hello.o。接下来通过as汇编得到可重定位目标文件hello.o。再经过链接[2]得到可执行目标文件hello.o。在shell中输入./hello,shell对文件进行解析,再fork创建子进程并调用execeve执行代码,这便是Process。
Hello的020:020即From Zero-0 to Zero-0。在最开始,内存中并没有与hello相关的内容。这就是第一个Zero-0。运行时首先在shell中创建新的进程,再调用execve映射到虚拟内存。接着虚拟内存会对应到物理内存上。当程序返回时,进程结束,shell会对其进行回收,释放所占空间。而这个是第二个Zero-0。
1.2 环境与工具
硬件环境:Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz 2.59 GHz RTX2070
软件环境:Windows 10 64位 Ubuntu 22.04.2 LTS 64位
调试工具:Vim,edb-debugger,gdb等
1.3 中间结果
1.Hello.i 预处理获得文件
2.Hello.s ccl汇编之后的汇编文件
3.Hello.o可重定位目标文件
4.hello可执行目标文件
1.4 本章小结
本章对Hello的一生进行了分析,介绍了Hello的P2P与020。同时介绍了软硬件环境以及调试工具。并且列出了Hello的执行过程中的一些中间结果。
第2章 预处理
2.1 预处理的概念与作用
预处理的概念:在C语言的程序中包括各种以符号#开头的编译指令,这些指令称为预处理命令。预处理命令属于C语言编译器,而不是C语言的组成部分,通过预处理命令可扩展C语言程序设计的环境。
预处理的作用:1.包含文件:将源文件中以#include格式包含的文件复制到编译的源文件中,可以是头文件,也可以是其它的程序文件。2.宏定义指令:#define指令定义一个宏,#undef指令删除一个宏定义。3.条件编译:根据#ifdef和#ifndef后面的条件决定需要编译的代码。
2.2在Ubuntu下预处理的命令
命令:gcc hello.c -E -o hello.i
得到hello.i文件
图(1)
2.3 Hello的预处理结果解析
图(2)
观察可知,在主函数之前被添加了三千行内容,并且注释被删去
2.4 本章小结
本章介绍了预处理的概念以及作用,分析了hello.c到hello.i的过程和代码变化。合理的使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。
第3章 编译
3.1 编译的概念与作用
编译的概念:ccl将文本文件hello.i翻译成文本文件hello.s
编译的作用:将预处理之后的代码进行翻译,转换为汇编语言,同时又优化等功能
3.2 在Ubuntu下编译的命令
命令:gcc -S hello.i -o hello.s
图(3)
执行命令后,得到hello.s文件
3.3 Hello的编译结果解析
3.3.1初始部分
图(4)
图(5)
图(6)
3.3.2main函数分析
3.3.2.1变量
main函数中声明了i作为循环计数器,它被存储在%rbp-4的位置,此外还有数组argv和长度argc分别存储于%rsi与%edi当中。
3.3.2.2赋值
一般来说使用mov操作来进行赋值,对于不同的数据类型使用不同的赋值语句。在此程序中使用movq(8字节)和movl(4字节)
3.3.2.3算术
在源程序中有i++对i进行迭代操作,对应为
图(7)
3.3.2.4判断与控制转移
例如判断是否跳出循环,对应为:
图(8)
3.3.2.5数组
由以下代码可知,数组argv也存储在堆栈当中,它的头储存在%rbp-32位置
图(9)
3.3.2.6函数
参数传递:在32位操作系统中,参数传递通过栈实现;在64位系统中,参数传递主要通过寄存器实现,当参数超过六个时,也会使用栈来传递参数。
调用函数:call指令会将返回地址压入栈中,并且将rip的值指向所调用函数的地址,等函数执行完之后调用ret指令来弹出原先的rip并且将栈结构恢复。
函数执行:函数内部执行相应的操作
函数返回:函数返回时,如果有返回值,则先将返回值存在%rax中。再用leave与ret操作返回,将控制权还给调用函数。
Hello.c文件中调用的函数有:main(),printf(),exit(),sleep(),atoi(),getchar()。
main()即主函数
printf()通过以下语句调用
图(10)
exit()通过以下语句调用,结束进程
图(11)
sleep()通过以下语句调用
图(12)
atoi()通过以下语句调用,用来进行类型转换
图(13)
getchar()通过以下语句调用,获取一个字符
图(14)
3.4 本章小结
详细分析了hello.s中的汇编语言及其作用同时介绍了编译的概念和作用。
第4章 汇编
4.1 汇编的概念与作用
汇编的概念:汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程
汇编的作用:汇编器as将hello.s翻译成机器语言指令,将这些指令打包成一个可重定位目标程序,并将结果保存在目标文件hello.o中。
4.2 在Ubuntu下汇编的命令
命令:gcc -c hello.s -o hello.o
图(15)
4.3 可重定位目标elf格式
命令:readelf -a hello.o > hello.elf
4.3.1ELF Header[3]
图(16)
图(16)
表头中以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含了帮助链接器语法分析和解释目标文件的信息,其中包括ELF头的大小、目标文件的类型(如可重定位、可执行或者共享的)、机器类型(如x86-64)、节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是有节头部表描述的,其中目标文件中每个节都有一个固定大小的条目(entry)。
.text:已编译程序的机器代码
.rodata:只读数据
.data:已初始化的全局和静态C变量
.bss:未初始化的全局和静态C变量
.symtab:符号表
.rel.text:代码段重定位信息表
.rel.data:数据段重定位信息表
.debug:调试符号表
.line:C代码行号与机器码行号映射表
.strtab:字符串表
节头记录各节名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐
4.3.2 section header
图(17)
记录各节名称,大小,类型,地址,偏移量等信息。
4.3.3.重定位节
图(18)
保存的是.text节中需要被修正的信息。一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。具体有:printf,exit,getchar()等外部函数和.rodata中的模式串。
4.3.4符号表
图(19)
存储程序中被引用的函数名称和全局变量的信息
4.4 Hello.o的结果解析
命令:objdump -d -r hello.o
图(20)
图(21)
区别1:跳转语句不通,在反汇编语句中是先计算语句后跳转的,而hello.s中是利用L2L3L4来进行跳转的
区别2:数字表示不相同,反汇编中使用16进制,hello.s使用10进制
区别3:反汇编还未进行链接器链接。
4.5 本章小结
本章对hello.s进行了汇编,生成了hello.o可重定位目标文件,并且分析了可重定位文件的ELF头、节头部表、符号表和可重定位节,比较了hello.s和hello.o反汇编代码的不同之处,分析了汇编语言与机器语言的对应关系。
第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
图(22)
5.3 可执行目标文件hello的格式
命令:readelf -a hello > hello1.elf
获取ELF头,接下来分析ELF头中内容:
图(23)
类型为EXEC可执行文件,大小为64B,可知节头表起始位置为13560B,共27个节。
节头表:
图(24)
从这里我们可以看到各节的大小和偏移量。从elf header中可知节头表起始位置为13560B,大小为64B。
段头部表:
图(25)
描述了可执行文件连续的片到连续的内存段的映射关系,同时还包括各片大小以及对应内存段大小,offset显示出了各片在目标文件的位置。
符号表:
图(26)
记录了函数名称与全局变量
重定位节:
图(27)
记录了链接后重定位信息。
5.4 hello的虚拟地址空间
使用edb[4]加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
命令:edb –run hello
图(28)
在0040100找到代码段
图(29)
5.5 链接的重定位过程分析
分别查看经objdump得到的汇编代码和hello.o的反汇编代码
命令:objdump -d -r hello
得到部分代码如下:
图(30)
hello.o部分代码如下:
图(31)
我们可以发现在hello.o中地址从0开始,而链接后我们有了确定的地址,hello已经实现了重定位hello的反汇编代码中多出了许多其他函数的代码,包括init,printf,puts等函数,这是在链接过程中加入的。并且hello的反汇编代码中调用函数都已经变成函数确切的地址了,而条件跳转中需要跳转到的位置也已经变成了确切的地址。
5.6 hello的执行流程
ld-2.31.so!_dl_start 0x7f8e7cc34ed0
ld-2.31.so!_dl_init 0x7f8e7cc486a0
hello!_start 0x4010f0
libc-2.31.so!_libc_start_main 0x7ff825425fc0
libc-2.31.so!_cxa_atexit 0x7ff825448f60
hello!_libc_csu_int 0x4011c0
libc-2.31.so!_setjmp 0x7ff 82fdb2e00
libc-2.27.so!exit 0x7ff82fdc3bd0
5.7 Hello的动态链接分析
找到动态链接的部分,根据地址在edb中寻找
图(32)
在进行动态链接前,首先进行静态链接,生成部分链接的可执行目标文件hello。此时共享库中的代码和数据没有被合并到hello中。加载hello时,动态链接器对共享目标文件中的相应模块内的代码和数据进行重定位,加载共享库,生成完全链接的可执行目标文件。
动态链接采用了延迟加载的策略,即在调用函数时才进行符号的映射。使用偏移量表GOT+过程链接表PLT实现函数的动态链接。GOT中存放函数目标地址,为每个全局函数创建一个副本函数,并将对函数的调用转换成对副本函数调用。
图(33)
Do_int前
图(34)
Do_int后,地址发生了变化。
5.8 本章小结
本章主要介绍了链接的概念与作用,链接可分为符号定义和重定位,了解了可执行文件的ELF格式,分析了hello的虚拟地址空间,重定位过程,执行过程,动态连接过程,对链接有了更深的理解。
第6章 hello进程管理
6.1 进程的概念与作用
概念:进程是一个执行中程序的实例。系统中的每个程序都运行在某个进程的上下文中。首先是一个独立的逻辑控制流,它提供一个假象,好像我们的程序独占地使用处理器。其次他占有一个私有的地址空间,它提供一个假象,好像我们的程序独占地使用内存系统
6.2 简述壳Shell-bash的作用与处理流程
Shell 的作用:
Shell 是一个用C语言编写的交互型应用程序,代表用户运行其他程序。Shell 应用程序提供了一个界面,用户可以通过这个界面进行系统的基本操作,访问操作系统内核的服务。
Shell的处理流程大致如下:
1.从Shell终端读入输入的命令。
2.切分输入字符串,获得并识别所有的参数
3.若输入参数为内置命令,则立即执行
4.若输入参数并非内置命令,则调用相应的程序为其分配子进程并运行
5.若输入参数非法,则返回错误信息
6.处理完当前参数后继续处理下一参数,直到处理完毕
6.3 Hello的fork进程创建过程
命令:./hello 2021112001 商柏淳 6
由于hello不是内部命令,这时候shell会后fork一个子进程,父进程会给子进程复制一份相同的虚拟地址空间副本,子进程可以读取父进程中打开的任何文件。
图(35)
6.4 Hello的execve过程
execve的功能是在当前进程的上下文中加载并运行一个新程序。在执行fork得到子进程后随即使用解析后的命令行参数调用execve,execve调用启动加载器来执行hello程序。加载器执行的操作是,加删除子进程现有的虚拟内存段,并创建新的代码、数据、堆和栈段。代码和数据段被初始化为hello的代码和数据。堆和栈被置空。然后加载器将PC指向hello程序的起始位置,即从下条指令开始执行hello程序。
execve在运行时需要以下四个步骤:删除已经存在的用户区域、映射私有区域、映射共享区域、设置程序计数器。
6.5 Hello的进程执行
上下文的概念: 上下文就是内核重新启动一个被抢占的进程所需的状态。它由一些对象的值组成,这些对象包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构,比如描述地址空间的页表、包含有关当前进程信息的进程表,以及包含进程已打开文件的信息的文件表。
上下文切换: 在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程。这种决策就叫做调度(scheduling),是由内核中称为调度器(scheduler)的代码处理的。当内核选择一个新的进程运行时,我们说内核调度了这个进程。在内核调度了一个新的进程运行后,它就抢占当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程,上下文切换:
1)保存当前进程的上下文
2)恢复某个先前被抢占的进程被保存的上下文
3)将控制传递给这个新恢复的进程
开始Hello运行在用户模式,收到信号后进入内核模式,运行信号处理程序,之后再返回用户模式。运行过程中,cpu不断切换上下文,使运行过程被切分成时间片,与其他进程交替占用cpu,实现进程的调度。
6.6 hello的异常与信号处理
执行hello,程序被回收
图(36)
Ctrl+z,中止程序
输入ps,查看进程情况
图(37)
进程没有被结束,而是被挂起,PID为3135
使用jobs查看
图(38)
使用pstree可以看到具体位置
图(39)
使用fg1继续执行
图(40)
Ctrl+c
图(41)
查看pid,进程被回收
图(42)
乱按,无反应,按回车后结束。
图(43)
6.7本章小结
本章介绍了计算机中最重要的概念之一——进程。可以说现代计算机离不开进程的概念,只有充分了解进程的创建与异常流控制,才可能成为一个优秀的程序员。本章介绍了Shell的一般处理流程,调用 fork 创建新进程,调用 execve 执行 hello,hello的进程执行,hello 的异常与信号处理。
第7章 hello的存储管理
7.1 hello的存储器地址空间
1.逻辑地址:程序经过编译后出现在汇编代码中的地址。逻辑地址用来指定一个操作数或者是一条指令的地址。是由一个段标识符加上一个指定段内相对地址的偏移量,表示为 段标识符:段内偏移量。
2.线性地址:逻辑地址向物理地址转化过程中的一步,逻辑地址经过段机制后转化为线性地址,为描述符:偏移量的组合形式,分页机制中线性地址作为输入。
3.虚拟地址:就是线性地址。
4.物理地址:CPU通过地址总线的寻址,找到真实的物理内存对应地址。CPU对内存的访问是通过连接着CPU和北桥芯片的前端总线来完成的。在前端总线上传输的内存地址都是物理内存地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
逻辑地址是程序源码编译后所形成的,跟实际内存没有直接联系的地址,即在不同的机器上,使用相同的编译器来编译同一个源程序,则其逻辑地址是相同的。
线性地址=段基址*16+偏移的逻辑地址,而段基址由于不同的机器其任务不同,其所分配的段基址(线性地址)也会不相同,因此,其线性地址会不同。
7.3 Hello的线性地址到物理地址的变换-页式管理
线性地址对应到物理地址是通过分页机制,即通过页表查找来对应物理地址。
分页是CPU提供的一种机制,Linux根据这种机制的规则,利用它实现了内存管理。在保护模式下,控制寄存器的最高位PG位控制着分页管理机制是否生效,如果PG=1,分页机制生效,需通过页表查找才能把线性地址转换物理地址。
分页的基本原理是把内存划分成大小固定的若干单元,每个单元称为一页,每页包含4k字节的地址空间。这样每一页的起始地址都是4k字节对齐的。为了能转换成物理地址,我们需要给CPU提供当前任务的线性地址转物理地址的查找表,即页表。x86将线性地址通过页目录表和页表两级查找转换成物理地址。
7.4 TLB与四级页表支持下的VA到PA的变换
CPU产生虚拟地址VA,虚拟地址VA传送给MMU,MMU使用VPN高位作为TLBT和TLBI,向TLB中寻找匹配。如果命中,则得到物理地址PA。如果TLB中没有命中,MMU查询页表,CR3确定第一级页表的起始地址,VPN1确定在第一级页表中的偏移量,查询出PTE,以此类推,最终在第四级页表中找到PPN,与VPO组合成物理地址PA,添加到PLT。
7.5 三级Cache支持下的物理内存访问
首先是CPU发出一个虚拟地址给TLB里面搜索,如果命中的话就直接先发送到L1cache里面,没有命中的话就先在页表里面找到以后再发送过去,到了L1里面以后,寻找物理地址又要检测是否命中。
7.6 hello进程fork时的内存映射
当fork 函数被shell调用时,内核为hello进程创建各种数据结构,并分配给它一个唯一的PID 。并且创建hello进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。当这两个进程中的任一一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射
1.删除已经存在的用户区域。删除当前进程虚拟地址的用户部分中已存在的区域结构。
2.映射私有区域。为hello的代码、数据、bss和栈区域创建新的区域结构。
3.映射共享区域。如果hello程序与共享对象链接,比如标准C库libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域。
4.设置程序计数器(PC)。设置当前进程上下文的程序进程器,使其指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
如果程序执行过程中发生了缺页故障,则内核调用缺页处理程序。
1.检查虚拟地址是否合法,如果不合法则触发一个段错误,程序终止。
2.检查进程是否有读、写或执行该区域页面的权限,如果不具有则触发保护异常,程序终止。
3.两步检查都无误后,内核选择一个牺牲页面,如果该页面被修改过则将其交换出去,换入新的页面并更新页表。然后将控制转移给hello进程,再次执行触发缺页故障的指令。
7.9动态存储分配管理
在程序运行时程序员使用动态内存分配器获得虚拟内存,动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合,每个块要么是已分配的,要么是空闲的。分配器的类型有显示分配和隐式分配,前者要求应用显式地释放任何已分配的块,后者应用检测到已分配块不再被程序所使用,就释放这个块。
7.10本章小结
本章分析了hello的存储器空间,并给出了 TLB与四级页表支持下的VA与 PA的变换和三级Cache支持下的物理内存访问过程的介绍。
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
所有的I/ O 设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当作对相应文件的读和写来执行。这种将设备映射为文件的方式,允许Linux 内核引出一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行,这就是Unix I/O接口。
8.2 简述Unix IO接口及其函数
Linux/unix IO接口:
打开文件:返回一个小的非负整数,即描述符。用描述符来标识文件。
改变当前文件位置 从文件开头起始的字节偏移量。系统内核保持一个文件位置k,对于每个打开的文件,起始值为0。
读写文件:一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n。写操作就是从内存复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。
关闭文件:当应用完成了对文件的访问之后,它就通知内核关闭这个文件。
函数:
Open()-打开一个已经存在的文件或是创建一个新文件
Read()-从文件读取数据,执行输出
Write()-从文件中读取数据,执行输出
Close()-关闭一个被打开的文件
Lseek()-用于在指定的文件描述符中将文件指针定位到相应位置
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;
}
首先 arg 获得第二个不定长参数,即输出的时候格式化串对应的值。
查看 vsprintf 代码:
int vsprintf(char *buf, const char *fmt, va_list args)
{
char* p;
char tmp[256];
va_list p_next_arg = args;
for (p=buf;*fmt;fmt++) {
if (*fmt != '%') {
*p++ = *fmt;
continue;
}
fmt++;
switch (*fmt) {
case 'x':
itoa(tmp, *((int*)p_next_arg));
strcpy(p, tmp);
p_next_arg += 4;
p += strlen(tmp);
break;
case 's':
break;
default:
break;
}
}
return (p - buf);
}
则知道 vsprintf 程序按照格式 fmt 结合参数 args 生成格式化之后的字符串,并返回字串的长度。
在 printf 中调用系统函数 write(buf,i)将长度为i的buf输出。write 函数如下:
write:
mov eax, _NR_write
mov ebx, [esp + 4]
mov ecx, [esp + 8]
int INT_VECTOR_SYS_CALL
在 write 函数中,将栈中参数放入寄存器,ecx 是字符个数,ebx 存放第一个字符地址,int INT_VECTOR_SYS_CALLA 代表通过系统调用 sys_call,查看 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
printf()函数将变长参数的指针arg作为参数,传给vsprintf函数。然后vsprintf函数解析格式化字符串,调用write()函数。在write函数中,将栈中参数放入寄存器,ecx是字符个数,ebx存放第一个字符地址,最后,write函数调用syscall(int INT_VECTOR_SYS_CALL)。syscall将字符串中的字节从寄存器中通过总线复制到显卡的显存中,显存中存储的是字符的ASCII码。字符显示驱动子程序将通过 ASCII 码在字模库中找到点阵信息将点阵信息存 储到 vram 中。显示芯片会按照一定的刷新频率逐行读取 vram,并通过信号线向液晶显示器传输每一个点(RGB 分量),最终打印出了我们需要的字符串。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本节主要介绍了 Linux 的 IO 设备管理方法、Unix IO 接口及其函数,简单分析了 printf 函数和 getchar 函数的实现。
结论
Hello的一生经过1.编写2.预处理3.编译4.汇编5.链接6.运行。7.创建子进程8.运行程序9.结束,实现了P2P与020。
Hello作为我第一条打出来的程序,对我有非凡的意义,在经历了一学期对计算机系统这门课的学习后,我发现一条简简单单的程序后面是如此的庞大且复杂,但同时它的一生也是完美的精妙的,让我领略到计算机的魅力。
附件
列出所有的中间产物的文件名,并予以说明起作用。
hello.i:预处理生成的文本文件
hello.s:.i编译后得到的汇编语言文件
hello.o:.s汇编后得到的可重定位目标文件
hello:.o经过链接生成的可执行目标文件
hello.elf:hello的elf文件
hello1.elf:hello的elf文件
参考文献
[1] Randal E.Bryant, David O'Hallaron. 深入理解计算机系统[M]. 机械工业出版社.2018.4
[2]动态链接详解_weixin_30736301的博客-CSDN博客
[3]ELF格式解读-(1) elf头部与节头_不会写代码的丝丽的博客-CSDN博客
[4]https://blog.csdn.net/Darlingqiang/article/details/125994577