关注

HITICS 大作业 程序人生

摘  要

本文借由hello从预处理到IO管理的整个过程,简单分析了其中涉及的计算机系统的知识与内容,包括:编译与链接、进程管理、存储管理、系统级I/O等,简述了hello的一生是怎样度过的。

关键词:计算机系统;编译;进程;存储;I/O                        

目  录

第1章 概述............................................................................................................. - 4 -

1.1 Hello简介...................................................................................................... - 4 -

1.2 环境与工具..................................................................................................... - 4 -

1.3 中间结果......................................................................................................... - 4 -

1.4 本章小结......................................................................................................... - 4 -

第2章 预处理......................................................................................................... - 5 -

2.1 预处理的概念与作用..................................................................................... - 5 -

2.2在Ubuntu下预处理的命令.......................................................................... - 5 -

2.3 Hello的预处理结果解析.............................................................................. - 5 -

2.4 本章小结......................................................................................................... - 5 -

第3章 编译............................................................................................................. - 6 -

3.1 编译的概念与作用......................................................................................... - 6 -

3.2 在Ubuntu下编译的命令............................................................................. - 6 -

3.3 Hello的编译结果解析.................................................................................. - 6 -

3.4 本章小结......................................................................................................... - 6 -

第4章 汇编............................................................................................................. - 7 -

4.1 汇编的概念与作用......................................................................................... - 7 -

4.2 在Ubuntu下汇编的命令............................................................................. - 7 -

4.3 可重定位目标elf格式................................................................................. - 7 -

4.4 Hello.o的结果解析...................................................................................... - 7 -

4.5 本章小结......................................................................................................... - 7 -

第5章 链接............................................................................................................. - 8 -

5.1 链接的概念与作用......................................................................................... - 8 -

5.2 在Ubuntu下链接的命令............................................................................. - 8 -

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

5.4 hello的虚拟地址空间.................................................................................. - 8 -

5.5 链接的重定位过程分析................................................................................. - 8 -

5.6 hello的执行流程.......................................................................................... - 8 -

5.7 Hello的动态链接分析.................................................................................. - 8 -

5.8 本章小结......................................................................................................... - 9 -

第6章 hello进程管理................................................................................... - 10 -

6.1 进程的概念与作用....................................................................................... - 10 -

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

6.3 Hello的fork进程创建过程..................................................................... - 10 -

6.4 Hello的execve过程................................................................................. - 10 -

6.5 Hello的进程执行........................................................................................ - 10 -

6.6 hello的异常与信号处理............................................................................ - 10 -

6.7本章小结....................................................................................................... - 10 -

第7章 hello的存储管理................................................................................ - 11 -

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 -

7.9动态存储分配管理....................................................................................... - 11 -

7.10本章小结..................................................................................................... - 12 -

第8章 hello的IO管理................................................................................. - 13 -

8.1 Linux的IO设备管理方法.......................................................................... - 13 -

8.2 简述Unix IO接口及其函数....................................................................... - 13 -

8.3 printf的实现分析........................................................................................ - 13 -

8.4 getchar的实现分析.................................................................................... - 13 -

8.5本章小结....................................................................................................... - 13 -

结论......................................................................................................................... - 14 -

附件......................................................................................................................... - 15 -

参考文献................................................................................................................. - 16 -

第1章 概述

1.1 Hello简介

首先,程序员敲代码敲出一个hello.c源文件,然后进行预处理、编译、汇编、链接等步骤生成可执行文件hello。

在shell里输入./hello以运行这个程序。Shell先给它fork一个进程,然后通过execve将hello程序加载到内存中。虚拟内存机制通过mmap为hello进程分配了虚拟空间,调度器为它分配时间片。(P2P)

CPU逐条从.text段取指令,syscall让进程陷入内核,执行write。

最后通过waitpid,内核回收该进程,所占用的处理器及内存资源被释放。(020)。

1.2 环境与工具

硬件环境:处理器11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz

软件环境:Windows、Linux Ubuntu

1.3 中间结果

hello.c:源文件。

hello.i:预处理文件。(ASCII码)

hello.s:汇编语言文件。

hello.o:可冲定位目标文件。

1.4 本章小结

       hello.c先通过编译生成可执行文件,然后操作系统来执行可执行文件,包括为程序创建进程、分配内存、分配CPU占用、I/O流、回收等,体现了hello的P2P和020。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

概念:预处理是指在系统对源程序(.c文件)进行编译之前,对程序中某些特殊的命令行进行处理的过程。预处理程序会根据这些预处理指令对源代码进行修改或调整,然后生成一个预处理后的文件(以.i为扩展名),供后续的编译阶段使用。

作用:移除注释、文件包含(如处理#include)、宏定义替换(处理#define)、优化代码格式等。

2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析

生成了hello.i文件,发现#include<stdio.h>等被替换成了stdio.h等的代码。

2.4 本章小结

预处理主要进行宏替换,hello.c文件经过预处理生成了hello.i文件。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

概念:编译是指利用编译器将与处理后的代码转换为汇编代码的过程。

作用:词法分析、语义检查、中间代码生成、优化(删除冗余代码、公共子表达式消除等)等。

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析

3.3.1 数据

3.3.1.1.整型常量被编译为立即数。如argc!=5中的5以及exit(1)中的1。如下图:

3.3.1.2.字符型常量被保存在只读代码段.rodata,在使用的时候取其地址传参。

3.3.1.3.局部变量i在定义的时候没赋初值,编译器没管它。

3.3.2 赋值

在for循环中,先对i赋初值0,如下图:

3.3.3 类型转换

atoi(argv[4])返回的类型是int,而sleep()要求参数为unsigned int,发生隐式类型转换。但在汇编语言中没有体现。atoi的返回值在rax,rax把值给edi作为sleep的参数。如下图:

3.3.4 算术操作

for循环中的i++,编译器在栈中给i加1,如下图:

3.3.5关系操作

编译器通常通过cmp + jum 语句组合进行比较、转移。如下图:

argc!=5:

;i<9:

3.3.6 数组操作

hello中数组为参数,在栈中。取值时使用mov,取地址时使用lea。

printf("Hello %s %s %s\n",argv[1],argv[2],argv[3]);语句对应的汇编代码体现了取数组元素的过程。如下图:

每次取-32(%rbp)以后加24/16/8对应的是argv[3/2/1]的地址。

3.3.7 控制转移

       控制转移通常和关系比较一同出现,hello中有:if(argc!=5)和for(i=0;i<9;i++)两种跳转。如下图:

argc!=5:

;i<9:

3.3.8 函数操作

hello中的函数操作有:printf 、exit 、sleep 、atoi 、getchar。编译器先向rdi,rsi,rdx,rcx等寄存器传递参数,然后call。如下图:

3.4 本章小结

编译器高级程序语言编译为汇编语言,并进行优化。本章分析了编译器对各种操作的实现,包括数据定义、赋值、类型转换、算术操作、关系操作、数组操作、控制转移、函数操作等。

(第32分)

第4章 汇编

4.1 汇编的概念与作用

汇编器将汇编语言程序转换为二进制机器码程序,把指令打包成可重定位目标程序的格式,生成.o文件。

4.2 在Ubuntu下汇编的命令

4.3 可重定位目标elf格式

ELF头:包含了magic(确认这是一个ELF文件)、class(这是一个64位的ELF文件)、data(说明小端序)、type(可重定位文件)等。如下图:

节头:展现所有节的名称、大小、文件内的位置、类型等信息。如下图:

重定位节(.rela.text 和 .rela.eh_frame):Offset指出.text节中需要重定位的地址偏移;Info包含符号表索引和重定位类型;Type中R_X86_64_PC32和R_X86_64_PLT32,分别表示32位基于PC的相对重定位和过程链接表(PLT)的重定位;Sym. Value和Sym. Name:指出哪个符号需要重定位以及它的当前值;Addend用于调整重定位值的额外偏移。

从.rela.text中,可以看到有多个外部函数(如puts、exit、printf等)需要重定位。这意味着在链接阶段,链接器需要找到这些函数的实际地址,并将它们插入到.text节的相应位置。如下图:

符号表:列出文件中所有的符号,包括函数、变量和外部引用。如下图:

4.4 Hello.o的结果解析

不同:

1).cfi等指令在.s文件中存在,而在反汇编中消失。

2).s文件中操作数是10进制,反汇编中是16进制。如下图:

     

—>

3).s文件在分支跳转的时候以标签的形式,而反汇编以地址的形式。如下图:

     

 —>

4).s文件在函数调用时以函数名形式,而反汇编以地址形式。如下图:

     

—>

4.5 本章小结

分析了汇编后可重定位文件的ELF格式及内容,并观察了同样是汇编语言的.s文件与反汇编的不同。

(第41分)

5章 链接

5.1 链接的概念与作用

链接将多个编译后的目标文件或库文件组合成一个可执行文件或库文件。主要有符号解析和重定位两个任务。

5.2 在Ubuntu下链接的命令

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

hello.o的ELF头:

hello的ELF头:

发现program headers和section headers的内容增多了。

各段信息:

5.4 hello的虚拟地址空间

通过5.3中各段的虚拟空间中位置信息,可以用edb中data dump查看相应内容。如:

 

5.5 链接的重定位过程分析

5.5.1地址序号不同

objdump -d -r hello 序号为虚拟地址从0x400000开始,而objdump -d -r hello.o序号是从0开始的。如下图:

5.5.2 字符串常量地址不同

在objdump -d -r hello.o中是可重定位条目,地址用0替代;在objdump -d -r hello中虚拟地址被填充上(重定位了)。如下图:

5.5.3函数调用地址不同

与字符串常量同理,被重定位了。如下图:

5.5.4 链接过程

链接包括符号解析和重定位这两个核心步骤。

符号解析的目的是将每个符号引用与其对应的符号定义关联起来。对于局部符号,链接器通常不需要进行特殊处理,因为这些符号的作用域仅限于其所在的目标文件。对于全局符号,规定:不允许由同名强符号;强弱同名取强;弱弱同名任取一个。

重定位的目的是确定每个符号定义的运行时内存地址,并修改对这些符号的引用,使之指向正确的内存地址。链接器首先将所有相同类型的节合并成一个新的节,然后为每个合并后的节确定运行时内存地址,并为其中的符号定义分配内存地址。然后链接器修改所有对这些符号的引用(依赖于目标文件中的重定位信息)。以R_X86_64_PC32为例:

主函数开始地址:

未定位:

重定位:

查看节头表知.rodata在402000 :

查看重定位条目:

0x4010f0+0x00001c=0x40110c(运行时地址)

0x402000+(-4)-0x40110c=0xef0

写成小端模式:f0 0e 00 00,即重定位后的偏移地址(相对于PC)。

5.6 hello的执行流程

Hello!_start:0x4011a0

libc.so.6!__libc_start_main:0x000076ec23c29dc0

libc.so.6!__cxa_atexit:0x000076ec23c458c0

5.7 Hello的动态链接分析

在动态链接过程中,会生成一个共享库(动态库),该库能够在程序运行时被加载到任意的内存地址,并与主程序进行链接。这一过程主要由动态链接器负责完成,它在程序启动并准备执行main函数之前进行必要的符号解析和重定位工作。

通过观察.got.plt节的变化,观察动态链接的过程。

通过readelf找到.got.plt节在地址为0x404000的地方开始,大小为0x48。

 

5.8 本章小结

分析了执行链接操作的结构基础:ELF格式。

分析了连接过程符号解析及重定位的过程以及动态链接的过程。

(第51分)

6章 hello进程管理

6.1 进程的概念与作用

进程是操作系统中用于管理和执行程序的基本单位,它是一个正在运行的程序实例。进程为程序提供两个假象:独占处理器(逻辑控制流)、独占内存系统(私有地址空间)。

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

Shell是一个命令解释器,它为用户提供了一个向Linux内核发送请求以便运行程序的界面系统级程序。

其基本功能是解释并运行用户的指令,重复处理如下过程:

  1. 终端进程读取用户由键盘输入的命令行。
  2. 分析命令行字符串,获取参数,并构造传递给execve的argv向量。
  3. 如果是内置命令,立即执行;如果不是,调用Fork()创建子进程。
  4. 在子进程中,用2)的参数,调用execve执行指定程序。
  5. 若用户没要求后台运行(没&)则waitpid等待作业终止后返回。
  6. 若要求后台运行,则返回。

6.3 Hello的fork进程创建过程

Shell执行fork函数,创建一个与当前进程几乎完全相同的子进程。这个子进程会获得父进程当前执行到的位置、打开的文件描述符、当前的工作目录等环境的副本。

fork进程创建过程:系统为新进程分配一个唯一的进程标识符(PID),为新进程分配所需的内存空间,包括程序、数据和用户栈。

6.4 Hello的execve过程

execve函数用于加载并执行一个新的可执行文件,将当前进程替换为新的程序。操作系统将hello加载到当前进程的内存空间中,替换掉当前进程的代码段、数据段、堆栈等,将argv和envp参数指定的命令行参数和环境变量设置为新进程的相应内容,从新程序的入口点(main)开始执行代码。

6.5 Hello的进程执行

当hello进程创建时,操作系统会为hello进程分配时间片,hello与其他进程轮流使用处理器,并发执行,执行各自的逻辑控制流。

当执行系统调用(如hello中的sleep,printf)或者当操作系统认为hello进程运行得足够久时,会发生上下文切换,程序将由用户态转换至核心态。内核中的调度器决定进程如何调度并进行上下文切换,将当前的上下文信息保存到内核中,恢复某个先前被抢占的进程的上下文,然后再由核心态转换至用户态,将控制传递给这个新恢复的进程。

6.6 hello的异常与信号处理

6.6.1 正常运行后随便按一个键

打印结束后getchar()等待用户输入一个键,之后程序结束运行,Shell回收hello进程。如下图:

6.6.2 ctrl +c

内核发送SIGINT信号给Shell前台的所有进程,所有进程接收信号并终止运行。如下图:

6.6.3 crtl+z

内核发送SIGTSTP信号给Shell前台的所有进程,所有进程接收信号并挂起。如下图:

ps可以查看到当前hello被挂起:

jobs查看被挂起的hello进程的jid及状态标识:

pstree查看hello进程的继承关系:

systemd──systemd──gnome-terminal-──bash──hello

fg让hello回到前台继续运行:

Kill发送信号给进程,这里发送SIGKILL信号(序号9)终止程序:

6.6.4 乱按

      不按到上述几个就没影响。

6.7本章小结

在shell中运行hello可执行程序,shell先为它fork,再为它execve,分配时间片和内存,同时内核检测键盘给出的信息(硬件异常),为进程发送相应的信号,hello在收到信号由信号处理程序执行相应操作。

(第61分)

7章 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址是指从应用程序角度看到的内存单元或存储单元的地址,它通常是由程序产生的段内偏移地址。

线性地址是逻辑地址到物理地址变换之间的中间层。在分段部件中,逻辑地址加上基地址(段基址)就构成了线性地址。

虚拟地址是程序运行在虚拟地址空间中的地址,它与实地址模式下的分段地址类似,也可以写为“段:偏移量”的形式。

物理地址是内存中各存储单元的编号,即存储单元的真实地址。它是可识别、可寻址并实际存在的。

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

逻辑地址通常由段选择符和偏移量两部分组成。段选择符用于选择特定的段,而偏移量则用于确定该段内的具体位置。

计算:根据逻辑地址中的段选择符,从全局或局部描述符表中选择对应的段描述符,然后从选中的段描述符中提取段的基地址,再将逻辑地址中的偏移量与基地址相加,得到线性地址。

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

页式管理将内存划分为固定大小的页面(通常是4KB)。每个页面都有一个唯一的页号,用于标识其在内存中的位置。页式管理通过页表将线性地址中的页号映射到物理地址中的页面,从而实现地址的转换。

处理器首先根据线性地址的高位部分(即页号)在页表中查找对应的页表项。如果页表项有效,则处理器从找到的页表项中提取页面的物理地址。然后,处理器将线性地址的低位部分(即页内偏移量)与提取的物理地址相加,得到最终的物理地址。

为了提高地址转换的效率,处理器通常配备了一个TLB。TLB是页表的缓存,存储了最近使用的PTE。

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

当处理器需要访问某个虚拟地址时,它首先会在TLB中查找PTE。如果TLB命中,则处理器可以直接使用该条目进行地址转换,无需再访问页表。如果TLB未命中),则处理器需要访问四级页表来查找对应的物理地址,并将新的PTE添加到TLB中。

四级页表包含4个级别的页表项。前三级的页表项都指向下一个级别的页表,最后一级页表项指向最终的物理页面。处理器逐级查找,直到找到最终物理页面。

多级页表通过分级索引的方式,对于未使用的虚拟地址空间,可以不在页表中为其分配页表项,通常会节省大量页表空间。

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

在MMU成功计算出物理地址后,该地址被送往L1缓存进行匹配。L1缓存利用PA中的标记和组索引信息来查找对应的数据块。若此时缓存命中(即找到匹配项且有效位为1),则根据块偏移量直接提取数据并返回给CPU。若缓存未命中,则系统会按照L1-L2-L3-主存的顺序逐级向下查询。一旦在某一级缓存或主存中找到所需数据,该数据将被返回给CPU,并依据特定的替换策略,将相应的数据块缓存至当前的L1缓存中,以备后续快速访问。

7.6 hello进程fork时的内存映射

当fork被调用时,内核会为子进程创建一个新的虚拟内存空间,这个空间几乎完全复制了父进程的虚拟内存空间,包括代码段、数据段、堆、栈等各个部分,但物理内存并不是立即复制的。父子进程共享相同的物理内存分页,这些分页被标记为只读,当父子进程中的任何一个试图写入这些共享的分页时,会触发一个写时复制,内核为写入进程创建一个新的物理内存分页,并将所需的数据从原始分页复制到新的分页中。然后,内核会更新写入进程的页表,使其指向新的分页,并允许写入操作继续。

7.7 hello进程execve时的内存映射

execve读取hello可执行文件,解析文件以获取创建新内存映像所需的信息,包括代码段、数据段等的起始地址和大小。然后,系统会基于这些信息为新进程构建一个新的内存映像,这个映像将覆盖当前进程的整个虚拟地址空间。这个过程中,当前进程的所有内存映射,包括之前打开的文件、建立的内存映射等,都会被新的内存映像所替换。

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

当进程访问的页面不存在于物理内存中时,就会触发缺页故障,操作系统暂停当前进程的执行,并触发一个缺页中断,中断处理程序随后接管控制权,开始处理缺页中断。

中断处理程序会先保存当前CPU的状态,然后确定哪个页面缺失并需要被调入内存,从磁盘中调入缺失的页面,将其加载到物理内存中。如果物理内存已满,那么中断处理程序会采用页面置换算法来选择一个页面牺牲。在页面被成功调入内存后,中断处理程序会更新页表,将缺失页面的虚拟地址映射到新的物理地址上。最后中断处理程序恢复之前保存的CPU状态,将控制转移回被中断的进程。进程重新访问之前发生缺页中断的地址,并且命中。

7.9动态存储分配管理

       动态存储分配管理通过操作系统或语言运行时库提供的内存分配函数来实现。这些方法允许程序在运行时根据需要动态地申请或释放内存空间。

隐式空闲链表通过每个块的头部中存放的信息来定位下一个块的位置。头部一般包含本块的大小及使用情况(分配或空闲)。当接收到一个内存分配请求时,从头开始遍历堆,找到一个空闲的满足大小要求的块。若有剩余,将剩余部分变成一个新的空闲块,并更新相关块的控制信息。在释放内存时,仅需把使用情况标记为空闲即可,但可能涉及合并空闲块的问题。合并操作通常推迟进行,以减少系统开销。

显式空闲链表显式地把所有的空闲块通过链表的形式维护起来。每个空闲块中包含两个指针字段,一个指向前面的空闲块,一个指向后面的空闲块。当需要分配内存时,从链表中找到一个合适的空闲块进行分配。如果找不到满足要求的空闲块,可能需要向操作系统申请新的内存空间。在释放内存时,将释放的块重新插入到链表中,并可能需要合并相邻的空闲块。

7.10本章小结

本章说明了逻辑地址、线性地址、虚拟地址、物理地址的概念,以及它们的联系。说明了逻辑地址到线性地址的变换、线性地址到物理地址的变换是如何完成的。分析了TLB与四级页表支持下的VA到PA的变换。说明了三级Cache支持下的物理内存访问的流程。分析了fork与execve时的内存映射。介绍了缺页故障与缺页中断的处理,然后分析了动态存储分配管理。

(第7 2分)

8章 hello的IO管理

8.1 Linux的IO设备管理方法

Linux把所有IO设备都当作文件来处理,通过在系统内部使用相同的read和write函数进行读写操作。这些设备文件通常位于/dev目录下,包括磁盘、打印机、网络设备等。Linux通过设备驱动来实现IO设备的操作,设备驱动为操作系统和硬件分别预留接口,通过设备驱动来屏蔽操作系统和硬件的差异。

8.2 简述Unix IO接口及其函数

打开文件: open(char *path, int flags, mode_t mode);

关闭一个一打开的文件: close(int fd);

从文件中读取数据: read(int fd, void *buf, size_t count);

向文件中写数据: write(int fd, const void *buf, size_t count);

8.3 printf的实现分析

1. 用户态的格式化字符串处理

printf 函数首先会解析格式字符串,并根据格式字符串中的格式说明符(如 %d、%s、%f 等)以及后续提供的参数,生成要输出的显示信息。这一步骤通常由 vsprintf来完成,它会将格式化的字符串存储在一个缓冲区中。

2. 系统调用

生成了格式化的字符串后,printf需要将这个字符串输出到标准输出设备。在Unix-like系统中,这通常是通过 write系统调用来实现的。write系统调用会将缓冲区中的数据写入到文件描述符所指向的文件中,对于标准输出,其文件描述符通常是 1。在进行系统调用时,用户态的程序会触发一个陷阱或中断,以切换到内核态来执行系统调用的处理函数。

3. 内核态的系统调用处理

当系统调用被触发后,CPU会跳转到内核态,并执行系统调用的处理函数。对于write系统调用,内核会检查文件描述符的有效性,确定要写入的文件,并将用户态缓冲区中的数据复制到内核缓冲区中。然后,内核会根据文件的类型和状态,将数据写入到相应的设备中。

对于标准输出,数据通常会被写入到终端设备的驱动程序中。

4. 字符显示驱动子程序

终端设备的驱动程序会接收内核传递过来的字符数据,并将其转换为显示芯片可以理解的格式。这通常涉及到将ASCII码转换为字模库中的字模数据,并将这些数据写入到视频内存(VRAM)中。

字模库是一个包含所有可打印字符的点阵数据的集合,每个字符都由一个固定大小的点阵来表示。驱动程序会根据字符的ASCII码,在字模库中找到对应的点阵数据,并将其写入到VRAM中相应的位置。

5. 显示芯片和液晶显示器

最后,显示芯片会按照刷新频率逐行读取VRAM中的数据,并通过信号线将这些数据传输到液晶显示器上。液晶显示器会根据接收到的RGB分量信息,控制每个像素点的颜色和亮度,从而显示出字符和图像。

综上所述,printf 函数的实现涉及从用户态到内核态的多个层次,包括格式化字符串处理、系统调用、内核态处理、字符显示驱动以及硬件级别的显示控制。这些步骤共同协作,实现了将格式化的数据输出到显示设备上的功能。

8.4 getchar的实现分析

当 getchar 被调用时,它首先检查系统的键盘缓冲区中是否有字符可用。如果缓冲区中有字符,getchar 会从缓冲区中取出下一个字符并返回它。否则,getchar调用read系统函数,键盘中断触发异常处理子程序,接受按键扫描码转成ascii码,保存到系统的键盘缓冲区,直到接受到回车键才返回。

8.5本章小结

本章讨论了Linux系统中Unix I/O的形式以及实现的函数。对printf和getchar两个函数的实现进行了的探究。

(第81分)

结论

1.起始:编写源代码

一切始于一个简单的C语言源文件——hello.c。

2.预处理阶段

编写完成后,源文件进入编译流程的第一步——预处理。预处理器(通常是cpp)处理所有的预处理指令,比如#include、#define等。它展开宏定义,包含头文件,并可能生成一些额外的代码,比如行号信息等,最终生成一个预处理后的文件,通常带有.i后缀。

3.编译阶段

接下来,预处理后的代码被传递给编译器。编译器将C语言源代码转换为汇编语言代码。这一步骤涉及词法分析、语法分析、语义分析、中间代码生成、代码优化等多个子步骤。编译器输出的汇编代码文件通常带有.s后缀。

4.汇编阶段

汇编器(如as)接收汇编代码文件,将其转换为机器码,即目标代码。目标代码是特定于平台的二进制指令集,但还不是可直接执行的程序。汇编过程还生成符号表和其他信息,用于后续的链接阶段。生成的目标文件通常带有.o后缀。

5.链接阶段

多个目标文件(如果有多个源文件)以及库文件(如C标准库libc)需要通过链接器(如ld)链接在一起,形成最终的可执行文件。链接器解析符号引用,将各个目标文件中的代码和数据段组合成一个单一的、可执行的文件。在hello的例子中,我们得到的是名为hello的可执行文件。

6.执行阶段

在Shell中,用户通过输入./hello命令来运行这个程序。Shell首先通过fork系统调用创建一个新的进程,该进程是Shell进程的子进程。然后,通过execve系统调用,新进程加载并执行hello程序。

7.虚拟内存与进程管理

操作系统利用虚拟内存机制为hello进程分配一个独立的地址空间。这包括代码段(.text)、数据段(.data)、未初始化数据段(.bss)等。mmap系统调用被用来映射这些段到物理内存或交换空间。同时,调度器负责分配CPU时间片给hello进程,使其有机会在CPU上执行。

8.指令执行与系统调用

CPU从hello进程的代码段中逐条取出指令执行。当程序执行到printf函数调用时,它实际上触发了一个系统调用(syscall),导致进程从用户态切换到内核态。内核中的write系统调用处理这个请求,将字符串“Hello 2023110013 qhy 15204658623”写入到标准输出。

9.进程结束与资源回收

当hello程序执行完毕,它通常通过调用exit函数来结束。Shell通过waitpid系统调用等待并收集这个子进程的退出状态。内核负责回收hello进程所占用的所有资源,包括CPU时间、内存(通过解除映射)、文件描述符等。这样,系统资源得以高效利用,不会因为已终止的进程而浪费。


通过上述步骤,hello.c源文件便经历了从编写到执行再到回收的完整生命周期。

附件

hello.i:预处理程序。

hello.s:汇编语言程序。

hello.o:可重定位文件。

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

参考文献

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

[1]    《深入理解计算机系统》

[2]  HITICS大作业:HELLO——程序的一生_机带ram16.0 gb (15.4 gb 可用)-CSDN博客

[3]    https://blog.csdn.net/m0_65601072/article/details/124650579

转载自CSDN-专业IT技术社区

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/qhy323/article/details/144856796

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--