2023年哈尔滨工业大学盘算机体系大作业——步伐人生-Hello’s P2P ...

农妇山泉一亩田  金牌会员 | 2024-8-26 18:06:44 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 515|帖子 515|积分 1545




图片上传不停不成功,以后有机会我再上传吧

盘算机体系大作业



题     目  步伐人生-Hello’s P2P 
专       业  数据科学与大数据技能   
学     号       2021110774        
班     级        2103501          
学       生                    
指 导 教 师        史先俊            






盘算机科学与技能学院

2023年4月

摘  要

本文详细介绍了hello.c步伐的一生,从源代码到经过预处理、编译、汇编、链接最终生成可实验目标文件hello。在Linux体系下,hello文件依次经过cpp预处理、ccl编译、as汇编、ld链接最终成为可实验目标步伐hello。通过在shell中键入启动命令后,shell为其fork,产生子历程,内核为新历程创建数据结构,hello便从可实验步伐(Program)变成为历程(Process)。同时,Hello的P2P和020形貌了hello.c文件从可实验步伐变为运行时历程的过程,以及内存中没有相关内容到步伐运行结束后回收和删除hello相关数据的过程。

关键词:盘算机体系,预处理,编译,汇编,链接,异常,历程,存储,I/O,步伐,P2P

                          










目  录


第1章 概述................................................................................................................ - 4 -
1.1 Hello简介......................................................................................................... - 4 -
1.2 环境与工具........................................................................................................ - 4 -
1.3 中间结果............................................................................................................ - 4 -
1.4 本章小结............................................................................................................. - 5-
第2章 预处理............................................................................................................ - 6 -
2.1 预处理的概念与作用........................................................................................ - 6 -
2.2在Ubuntu下预处理的命令............................................................................. - 6 -
2.3 Hello的预处理结果解析................................................................................. - 6 -
2.4 本章小结............................................................................................................ - 7 -
第3章 编译................................................................................................................ - 8 -
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的自白,利用盘算机体系的术语,简述Hello的P2P,020的整个过程。
1.2 环境与工具

列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开辟与调试工具。
硬件环境:CPU: AMD Ryzen 7 5800H with Radeon Graphics 3.20 GHz
                    RAM: 16GB
软件环境:Windows10 64位
VMware11
Ubuntu 64位
开辟与调试工具:Visual Studio 2022;
gedit,gcc,notepad++,readelf, objdump, hexedit, edb


1.3 中间结果

列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
hello.c
hello源代码
hello.i
预处理之后的文本文件
hello.s
hello的汇编代码
hellooalt.s
hello.o的反汇编代码
helloalt.s
hello的反汇编代码
hello.o
hello的可重定位文件
hello
hello的可实验文件
hello.elf
hello的elf文件
helloo.elf
hello.o的elf文件

1.4 本章小结

本章进行了hello的简介,扼要介绍了P2P与020,同时列出了完资本次论文所需要的软件、硬件环境以及开辟调试工具,也列举了hello.c生成的中间结果的名字与功能。



第2章 预处理

2.1 预处理的概念与作用

2.1.1预处理的概念
在预编译的过程中,重要处理源代码中的预处理指令,引入头文件,去除注释,处理全部的条件编译指令(#ifdef,#ifndef,#else,#elif,#endif),宏的更换,添加行号,保留全部的编译器指令。比方#include 命令使预处理器读取stdio.h,unistd.h,stdlib.h 等体系头文件的内容,并把这些内容直接插入到步伐文本中以及用实际值更换用#define定义的字符串。

2.1.2预处理的作用
1.预处剖析将#include后的头文件内容直接插入步伐文本,以及用实际值更换#define后面的宏定义,方便步伐后续处理。
2.预处理答应通过文件包含命令将另一个源文件的内容全部包含在此文件中,将这些内容一同编译,生产目标文件。
对步伐进行预处理后,编译器在对步伐进行编译的时候更加方便。

2.2在Ubuntu下预处理的命令

Ubuntu下通过gcc命令对hello.c文件进行预处理,生成hello.i文件


2.3 Hello的预处理结果解析

Linux下打开hello.i文件,发现hello.i已经扩展到3060行,hello.c中的main代码从3047行到3060行,前面3000+举动预处理的结果,是头文件stdio.h unistd.h stdlib.h 的依次展开,预处剖析在体系中找到#include后的文件,在步伐文本中展开,更换掉原来的#include<>,同时也会删除步伐中的多余空缺符与注释,方便进一步处理
2.4 本章小结

本章包含了预处理的概念和作用,并以Ubuntu体系下hello.c文件的预处理得到的hello.i举例,分析预处理后hello.i步伐文本的厘革。

第3章 编译

3.1 编译的概念与作用

3.1.1编译的概念
      编译器通过词法与语法分析,将指令翻译成汇编代码,将hello.i文件编译成汇编语言文件hello.s,hello.s中为机器语言的指令
3.1.2编译的作用
        将文本文件翻译成汇编语言文件,为后续处理将其转化为二进制文件做准备

3.2 在Ubuntu下编译的命令

       gcc -m64 -Og -S -no-pie -fno-PIC hello.i -o hello.s



3.3 Hello的编译结果解析

3.3.1 开头伪指令分析
      .file "hello.c"       (源文件)
      .text                            (代码段)
      .section  .rodata.str1.8,"aMS",@progbits,1          (.rodata节)
      .align 8                (对齐方式)
.LC0:
      .string    "\347\224\250\346\263\225: Hello \345\255\246\345\217\267 \345\247\223\345\220\215 \347\247\222\346\225\260\357\274\201"         (字符串)
      .section  .rodata.str1.1,"aMS",@progbits,1                                      (.rodata节)
.LC1:
      .string    "Hello %s %s\n"        (字符串)
      .text                                                 (代码段)
      .globl     main                           (全局变量名)
      .type      main, @function        (指定对象范例或函数)
      
       3.3.2 数据
              C语言的数据有:变量、常量、表达式、范例、宏
              (1)常量  大多数以立即数的情势出如今汇编代码中
                     例1:中的“1”,在hello.s中情势为

             例2:有些环境下编译器不会使用hello.i原来的常量,如循环语句 i小于5的比较在hello.s中情势为i与4比较大于则跳出循环(即i<=4)

      (2)变量  范例有全局变量、局部变量、静态变量
             已初始化的全局变量和静态变量存放在.data节,而未初始化的全局变量存放于.bss节,局部变量则在栈中。
             在hello.c文件中并无全局变量和静态变量,以是在我们hello.s中开头的伪指令里并没有.data和.bss,而是有只读数据节(.rodata),里面会存放输出的格式串
             例1:在hello.s中的代码为而.LC0中为

即我们要输出的字符串的编码情势。
      例2:在hello.s中代码为而.LC1中为


       局部变量:main函数内定义了局部变量i,存入寄存器%ebp中,在for循环内,我们可以在hello.s中找到与4大小比较与加1操纵和。
              (3) 表达式
                     hello.c中一共有3个表达式
                            例1:表达式在hello.s中为
                            例2:表达式在hello.s中为
                            例3:表达式在hello.s中为

       3.3.3 赋值
                     汇编语言中的赋值操纵通过mov指令实现,mov指令有多个情势movb、movw、movl、movq以及movabsq,传送的是不同大小的数据
例1:int范例赋值操纵在hello.s中为

       3.3.4 算术操纵
              算术操纵有+ - * / %  ++  --  取正/负+-   复合“+=”等,在汇编语言指令中为在hello.s中有使用
              例1:  i加1操纵为

       3.3.5 范例转换
              范例转换分为显示与隐式范例转换
                     例1:代码int i = 1.1,1.1赋给整型i,小数部分会向0舍入,即i的值为1,这是一个隐式范例转换。
                     例2:代码 float i = (int)2.5*2.5,结果为5,因为(int)2.5会将2.5转换成int范例的数字,即为2,这是一个显式范例转换。

       3.3.6 关系操纵
              关系操纵指令表为

其中关系操纵指令包含CMP和TEST。它们只设置条件码,并不改变任何其他寄存器。cmp指令根据两个操纵数之差来设置条件码。TEST指令的举动与AND指令一样,区别在于TEST只设置条件码而不改变目的寄存器的值。在hello.s中
在hello.s中使用了cmp指令来判定argc!=4和i<8,分别为和


       3.3.7 数组操纵
C语言中的数组是一种将标量数据聚集成更大数据范例的方式。对于数据范例T和整型常数n,声明数组a[n],起始位置表示为X,表明它在内存中分配一个K·n字节的连续区域(K为数据范例T的大小)同时,它引入了标识符A,可以用A来作为指向数组开头的指针,这个指针的值就是X,可以用0~N-1的整数索引来访间该数组元素。数组元素会被存放在所在为X+L·i上的地方。
在hello.c中的main函数有个参数为char *argv[],这是一个指针数组,每个数组元素是一个指向参数字符串的指针。在hello.c中,有对数组元素的使用:
其汇编代码为:


其中%rsi和%rdx分别向printf通报参数argv[1]和argv[2],%rdi则向sleep传送参数argv[3]。以下是argv函数的存放方式举例。



       3.3.8 控制转移 
       控制转移常用jump指令,jump指令根据条件不同也有不同的跳转方式,以下是跳转规则:

在hello.c中,有if语句的条件控制在汇编语言中如下
argc 是 main 函数的第一个参数,因此存储在 %edi 中。步伐会使用 cmpl 指令来比较 argc 和数字 4 的大小,并设置条件码。接下来,步伐会使用 jne 指令根据条件码来比较 argc 和 4 是否相等,并根据比较结果来选择是否跳转到 .L6 标签处实验一段特定的代码。假如 argc 不等于 4,步伐将跳转到 .L6 标签处实验特定的代码。假如 argc 等于 4,则步伐将继续实验接下来的指令。

在hello.c中,同样for语句的循环控制
在汇编语言中为

首先,步伐会将变量 i 初始化为 0。然后,步伐将跳转到循环判定表达式处,检测是否满意循环条件。假如满意循环条件,则步伐将跳转到循环体中实验代码,然后实验循环表达式,更新变量 i 的值。步伐将重复实验这个过程,直到循环条件不满意为止。
       3.3.9 函数操纵

两个函数A、B,A调用B,函数调用包括以下机制:

通报控制:在进入函数B时,步伐计数器必须被设置为B的代码的起始所在,然后在返回时,要把步伐计数器设置为调用B函数后面那条指令的所在。这个过程使用call和ret指令实现。
通报数据:函数A必须可以或许向B提供一个或多个参数, B必须可以或许向A返回一个值。函数A可以通过寄存器向B通报6个参数,分别存放在%rdi、%rsi、%rdx、%rcx、%r8、%r9中,超过6个的部分就要通过栈来通报。
分配和释放内存:在开始时, B大概需要为局部变量分配空间,而在返回前,又必须释放这些存储空间。
在hello.s中的函数调用有如下示例(图片为汇编语言):
1.printf("用法: Hello 学号 姓名 秒数!\n"):将唯一的参数.LC0通报到%edi中,然后调用puts函数。

2.exit(1):将唯一的参数1通报到%edi中,然后调用exit函数。

3.printf("Hello %s %s\n",argv[1],argv[2]):通报了4个参数,第一个是%edi中的.LC1,也就是在.rodata中的格式串,第2个时%eax中的0,第3个是argv[1],通过8(%rbp)盘算得到,第4个是argv[2],通过16(%rbp)盘算得到。

4.atoi(argv[3]):将唯一参数24(%rbx),即argv[3],传入%rdi中,然后调用atoi函数。

在函数调用时,call指令将返回所在压入栈中,并将PC设置为调用函数的起始所在;函数结束时,使用ret指令从栈中弹出返回所在,并将PC设为返回所在。假如需要返回值,则可以将其存储在%rax寄存器中。
3.4 本章小结

本章介绍了编译及其相关操纵。编译的目标是将高级语言源代码翻译成机器语言,而将hello.i编译成汇编文本文件hello.s是编译的一个步骤。汇编代码是机器代码的文本表示,给出步伐中的每一条指令。阅读和理解汇编代码是一项非常紧张的技能,通过理解这些汇编代码,我们可以或许更好地理解编译器的优化能力,并分析代码中隐含的低效率。

第4章 汇编

4.1 汇编的概念与作用

汇编语言是一种低级别的编程语言,它用于编写盘算机步伐。在汇编语言中,步伐员使用特别的文本标志(称为指令)来形貌盘算机中的基本操纵。这些指令通常涉及到对内存、寄存器和其他盘算机硬件的操纵。
汇编语言被广泛用于体系编程和底层硬件驱动步伐开辟,因为它可以提供对盘算机硬件的精致控制。汇编语言还可以用于优化代码,从而进步步伐的实验效率。
汇编语言相对于高级编程语言具有以下长处,如可以直接操纵硬件,实现对底层硬件的精致控制,可以实现高效的代码,从而进步步伐的实验速率和效率,和可以更好地理解和调试代码。
汇编语言虽然具有上述长处,但也存在一些缺点,如编写汇编代码需要对底层硬件有深入的理解和掌握,汇编代码的可读性较低,难以理解和维护,以及编写汇编代码的工作量大,开辟效率低。
因此,在开辟大型软件项目时,通常使用高级编程语言来进步开辟效率和可维护性。但在某些特别场合,如编写操纵体系、编写嵌入式体系等,汇编语言仍旧是必不可少的编程语言。
4.2 在Ubuntu下汇编的命令

Linux 下汇编命令gcc -m64 -Og -c -no-pie -fno-PIC hello.c -o hello.o

4.3 可重定位目标elf格式

分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。

可重定位目标文件的ELF格式如下:

通过readelf工具,详细分析可重定位目标文件hello.o,分析这些节都做些什么。
首先是ELF头,如下图:


一个ELF文件的开头是一个16字节的序列,它形貌了生成该文件的体系的字节顺序和字大小。ELF头的剩余部分包含链接器所需的语法解析和目标文件表明所需的信息。这些信息包括ELF头的大小、目标文件的范例(如可重定位、可实验或共享对象)、机器范例(如x86-64)、节头表在文件中的偏移量、以及节头表中每个条目的大小和数量。


重定位节.rela.text存放着代码的重定位条目。当链接器把这个目标文件和其他文件组适时,需要修改这些位置

每一个重定位条目的数据结构如下:

它包括offset需要被修改的引用的节偏移、symbol被修改引用应该指向的符号、type告知链接器如何修改新的引用、以及偏移调整addend
    CSAPP中给出了重定位的盘算方法:

重定位节.rela.eh_frame:eh_frame 节的重定位信息


符号表:用来存放步伐中定义和引用的函数和全局变量的信息。注意,符号表不包含局部变量的条目。


4.4 Hello.o的结果解析

以下格式自行编排,编辑时删除
objdump -d -r hello.o 


4.4.1 机器语言的构成
x86-64指令的长度可以从1到15个字节不等,常用指令和少数操纵数的指令所需字节数较少,比方pop %rbx只需一个字节5b,而那些不太常用或操纵数较多的指令则需要更多字节数。
指令格式的设计方式是,从某个给定位置开始,可以将字节唯一地解码成机器指令。比方,只有指令pushq %rbx是以字节值53开头的。假如指令中有所在或常数,则需要按小端存储顺序依次存放。
4.4.2 与第三章hello.s对照分析
反汇编器只是基于机器代码文件中的字节序列来确定汇编代码。它不需要访问该步伐的源代码或汇编代码;反汇编器使用的指令命名规则与GCC生成的汇编代码使用的有些渺小的差别。比方:省略了很多指令末了的‘q’.
在函数调用和分支跳转时,二者也是有差别的。
分支跳转时,如判定argc!=4,然后跳转

hello.s中是jump到.L6,用一个标志指示;

hello.o的反汇编是跳转到所在15

函数调用时,如printf("Hello %s %s\n",argv[1],argv[2])
hello.s中是将参数通报到寄存器之后,call后紧跟的是调用函数明

而hello.o的反汇编中call后紧跟的是45 <main+0x45>,并且另有一个重定位条目,用于链接时重定位。

需要注意的是,printf函数在只读数据段(.rodata)中的格式化字符串不再使用类似.LC1这样的符号来表示,而是使用指令mov $0x0, %esi来表示,并且后面跟随着一个重定位条目,用于在链接时进行重定位。
4.5 本章小结

本章介绍了汇编语言及其处理过程。汇编器可以将汇编代码翻译成机器指令,将这些指令打包成一个可重定位目标文件,然后将结果保存在hello.o中。hello.o是一个二进制文件,包含函数main的指令编码。多个可重定位目标文件可以在链接时合并为一个可实验目标文件。

第5章 链接

5.1 链接的概念与作用

本章介绍了链接及其过程。链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可以被加载(复制)到内存并实验。链接可以实验于编译时、加载时、运行时。比方,当hello步伐调用printf函数时,它是每个C编译器都提供的标准C库中的一个函数。printf函数存在于一个名为printf.o的单独的预编译好了的目标文件中,这个文件必须以某种方式合并到我们的hello.o步伐中。链接器(ld)就负责处理这种合并,结果得到可实验目标文件hello。

链接使得分离编译成为大概。我们不必将一个大型的应用步伐构造为一个巨大的源文件,而是可以把它分解为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需简朴地重新编译它,并重新链接,而不必重新编译其他文件。

5.2 在Ubuntu下链接的命令

ld链接命令  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的格式

分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始所在,大小等信息。

使用readelf -a hello > hello.elf 命令生成hello 步伐的ELF 格式文件。
首先是ELF头,形貌文件的总体格式,还包括步伐的入口点。

.text、.rodata 和 .data 节与可重定位目标文件中的节非常相似,除了这些节已经被重定位到它们最终的运行时内存所在。.init 节定义了一个小函数 _init,用于步伐初始化代码会调用它。因为可实验文件是完全毗连的,以是没有 .rel 节。

节头记录每个节的名称、偏移量、大小、位置等信息


步伐头部表形貌了可实验文件的连续片段如何映射到连续内存段中。每个表项提供了映射到虚拟所在空间的大小、物理所在、标志、访问权限和对齐方式。通过读取这些信息,我们可以确定各个段的起始所在。

和可重定位目标文件相比,链接过程中链接了许多其他文件,如库函数等。

5.4 hello的虚拟所在空间

使用edb加载hello,检察本历程的虚拟所在空间各段信息。

首先从0x400000开始检察,我们发现第一行竟然和ELF头中的Magic是相同的,这绝非偶然,这阐明步伐是从0x400000处开始加载的


再看数据段,它是从0x600e10的。



5.5 链接的重定位过程分析

objdump -d -r hello 分析hello与hello.o的不同,阐明链接的过程。
联合hello.o的重定位项目,分析hello中对其怎么重定位的。

使用objdump -d -r hello命令反汇编hello如下

在可实验文件中,步伐头部表形貌了将文件中的连续片段映射到连续内存段的映射关系。每个表项提供了段在虚拟所在空间中的大小、物理所在、标志、访问权限和对齐方式。通过读取步伐头部表,我们可以获取每个段的起始所在。
在步伐的链接过程中,许多函数被链接到可实验文件中,如__init、puts@plt、__printf_chk@plt等。然而,在分析main函数时,我们只需要关注它的调用方式,因为它可以或许揭示可重定位hello.o与可实验文件hello之间的差异。
我们可以发现,在调用函数时,call语句中包含函数所在,这些所在都是PC相对引用的。比方,调用strtol函数的所在为0x401040,而紧随其后的下一条指令所在为0x4011ca,两者之间的距离是0xfffffe76。根据小端法,这个距离的机器指令表示为76 fe ff ff。
对数据的重定位。printf语句中的格式串也用其所在做为参数通报,

而在hello.o中用0占位和一个重定位条目进行表示


5.6 hello的实验流程

使用edb进行单步调试,观察步伐调用的函数。在调用main之前,步伐会进行初始化工作,并调用_init函数。在_init函数之后,动态链接的重定位工作已经完成。我们可以看到一系列在步伐中所用到的库函数,好比printf、exit和atoi等。实际上,这些函数在代码段中并不占用实际的空间,它们只是一个占位的符号。这些函数的内容实际上在共享区(高所在)中。之后步伐调用_start函数,准备开始实验main函数的内容。main函数内部所调用的函数在第三章已经进行了充实的分析,这里略过main内部的函数。在实验完main函数之后,步伐会实验__libc_csu_init、__libc_csu_fini和_fini函数,最终这个步伐才结束。下面列出了各个函数的名称和所在,包括_init、puts@plt、printf@plt、getchar@plt、atoi@plt、exit@plt、sleep@plt、_start、_dl_relocate_static_pie、deregister_tm_clones、register_tm_clones、__do_global_dtors_aux、frame_dummy、main、__libc_csu_init、__libc_csu_fini和_fini函数。

5.7 Hello的动态链接分析

分析hello步伐的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容厘革。要截图标识阐明。

对于动态共享链接库中的 PIC 函数,编译器无法预测函数的运行时所在,因此需要添加重定位记录,等候动态链接器处理。GNU 编译体系使用耽误绑定技能,将过程所在的绑定推迟到第一次调用该过程时。使用耽误绑定的动机是,对于像 libc.so 这样的共享库输出的成百上千个函数,一个典型的应用步伐只会使用其中很少的一部分。将函数所在的解析推迟到实际调用的地方,可以制止动态链接器在加载时进行成百上千个实在并不需要的重定位。第一次调用过程的运行时开销很大,但是其后的每次调用都只会花费一条指令和一个间接的内存引用。
耽误绑定是通过两个数据结构的交互来实现的,这两个数据结构是 GOT(全局偏移量表)和 PLT(过程链接表)。假如一个目标模块调用定义在共享库中的任何函数,那么它就有自己的 GOT 和 PLT。GOT 是数据段的一部分,PLT 是代码段的一部分。下图介绍了 GOT 和 PLT 交互的一个例子。需要注意的是,当 GOT 和 PLT 联合使用时,GOT[0]和GOT[1]包含动态毗连器在解析函数所在时会使用的信息。GOT[2]是动态毗连器在 ld-linux.so 模块中的入口点。
5.8 本章小结

本章介绍了链接及其过程。链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并实验。链接可以由静态编译器在编译时完成,也可以在加载时和运行时由动态链接器来完成。链接器处理三种情势的目标文件,包括可重定位的、可实验的和共享的。
链接器的两个重要使命是符号解析和重定位。符号解析指的是将每个符号名与其在代码或数据片段中的所在关联起来的过程。在可重定位的和共享的目标文件中,链接器需要将全部引用符号的地方与定义符号的地方创建接洽,以便可以或许在后续的重定位过程中正确地调整这些引用符号的所在。重定位是指将一个目标文件中的所在映射到实际的内存所在的过程,同时还需要解决引用不同共享库中同名符号的标题。
链接过程的实现通常涉及到几个数据结构,如符号表、重定位表和全局偏移量表(GOT)等。符号表记录了每个符号名及其对应的所在信息,重定位表记录了需要被调整的所在和调整后的所在,GOT则用于在共享库和主步伐之间创建引用符号的所在映射关系。这些数据结构的实现可以采用不同的算法和数据结构,以进步链接器的性能和效率。
链接是编译过程中紧张的一环,它可以将多个模块组合成为一个可实验的步伐或共享库。相识链接的过程和实现原理对于开辟人员来说非常紧张,可以资助他们更好地理解编译和运行步伐的过程,同时也可以或许资助他们更好地优化代码和解决一些常见的链接标题。

第6章 hello历程管理

6.1 历程的概念与作用

6.1.1 历程的概念
一个运行中的步伐实例是体系进行资源分配和调度的基本单元,通常由文本区域、数据区域和堆栈构成。文本区域存储处理器实验的代码,数据区域存储变量和历程实验期间使用的动态分配内存,而堆栈区域存储活动过程调用的指令和本地变量。
6.1.2历程的作用
操纵体系为应用步伐提供两个关键的抽象:历程和虚拟内存。历程提供了一个独立的逻辑流,让应用步伐看起来似乎是独占地使用处理器。而虚拟内存提供了一个私有的所在空间,让应用步伐看起来似乎是独占地使用内存体系。
6.2 简述壳Shell-bash的作用与处理流程

壳(Shell)是盘算机操纵体系提供的一个用户界面,用于与操纵体系内核进行交互。Bash(Bourne-Again SHell)是一种常见的壳步伐,常用于Linux和其他类Unix体系。

Bash的作用是接收用户在命令行界面输入的命令,将其表明成操纵体系内核可以理解的格式,并交由内核实验。Bash还提供了一些内置命令和环境变量,使得用户可以方便地实验各种体系操纵和管理使命。

Bash的处理流程如下:

1.用户在命令行输入命令。

2.Bash解析用户输入的命令,并根据空格将命令和参数分离。

3.Bash查找可实验文件或内置命令,并将命令和参数通报给该步伐或命令。

4.假如命令需要读取输入或输出结果,Bash将创建相关的管道或重定向,以便将输入或输出结果通报给该命令。

该命令实验完毕后,Bash会将实验结果返回给用户,或者将结果输出到屏幕上。

此外,Bash还支持Shell脚本,这是一种将多个命令按顺序组合起来的方式,以便一次性实验多个操纵。Shell脚本可以主动化完成各种重复的体系操纵和管理使命,从而进步了效率和减少了错误。

6.3 Hello的fork历程创建过程

当步伐运行到 main() 函数中的 fork() 调用时,将会创建一个新的历程。详细的过程如下:

  • 当实验到 fork() 函数时,体系会复制当前历程的全部数据,包括代码段、全局变量、堆栈、寄存器等,生成一个新的历程,称为子历程。
  • 子历程会从 fork() 调用的下一条语句开始实验,也就是说,子历程会复制当前历程的步伐计数器,并将其指向下一条语句。
  • fork() 函数的返回值对于父历程和子历程是不同的。对于父历程,返回值是子历程的历程ID,对于子历程,返回值是0。
  • 在父历程中,fork() 函数返回子历程的历程ID。可以使用这个历程ID来识别和控制子历程。假如返回值是负数,则表示出现了错误。
  • 在子历程中,fork() 函数返回0。可以使用这个返回值来区分父历程和子历程的实验路径。
  • 子历程的代码段、数据段和堆栈与父历程完全独立,子历程不会影响父历程的运行状态,也不会受到父历程的影响。因此,父历程和子历程可以并行实验不同的使命。
  • 在 hello 步伐中,fork() 函数的调用是在主函数的开始处。因此,当步伐运行时,将会创建一个新的子历程,并在父历程和子历程中分别实验相同的代码。但由于子历程的代码段和数据段是独立的,以是子历程会从 fork() 函数下一条语句开始实验,即从循环语句开始。
6.4 Hello的execve过程

当 hello 步伐运行时,当满意一定条件时(比方,命令行参数不正确时),步伐将调用 exit() 函数来退出。假如命令行参数正确,则步伐将继续实验,并调用 execve() 函数来实验另一个步伐。

execve() 函数接受3个参数:要实验的步伐的路径、命令行参数和环境变量。当调用 execve() 函数时,当前历程的代码段和数据段将会被更换为新步伐的代码段和数据段。也就是说,当前历程将变成要实验的步伐,并重新步伐的第一条语句开始实验。

假如 execve() 函数实验成功,则不会返回,步伐将继续实验新步伐的代码。假如 execve() 函数实验失败,则会返回一个负数值,并显示错误消息。在这种环境下,当前历程将继续实验原来的步伐。在 hello 步伐中,当命令行参数正确时,步伐将调用 execve() 函数来实验 ps 命令。详细地,步伐将调用 system() 函数,该函数将会调用 execve() 函数来实验 ps 命令。在这个过程中,当前历程将会被更换为 ps 命令,从而在终端上输出体系历程信息。

6.5 Hello的历程实验

在操纵体系中,每时每刻都有许多步伐在运行,但我们通常将每个历程视为独立占用CPU、内存和其他资源。假如我们对步伐进行单步调试,我们可以观察到一系列步伐计数器(PC)的值,这些值构成了步伐的逻辑控制流程。实际上,多个步伐在盘算机内部并行实验,它们的实验是交织进行的,就像下图所示,每个步伐都会交替地运行一小段时间。在同一个处理器核心中,每个历程实验它的流程的一部分后,就会被抢占(临时挂起),然后轮到其他历程运行。

操纵体系内核使用一种称为上下文切换的较高层情势的异常控制流来实现多使命。内核为每个历程维护一个上下文,上下文包含内核重新启动被抢占历程所需的状态。它由多个对象的值构成,包括通用目的寄存器、浮点寄存器、步伐计数器、用户栈、状态寄存器、内核栈以及多种内核数据结构,如形貌所在空间的页表、历程表等。上下文切换的流程是:1.保存当前历程的上下文;2.规复某个先前被抢占的历程的保存上下文;3.将控制通报给这个新规复的历程。

为了使操纵体系内核提供一个自作掩饰的历程抽象,处理器必须提供一种机制来限定一个应用可以实验的指令以及它可以访问的所在空间范围。处理器通常使用某个控制寄存器的一个模式位提供两种模式的区分,该寄存器形貌了历程当前享有的特权。当没有设置模式位时,历程就处于用户模式中,用户模式的历程不答应实验特权指令,也不答应直接引用所在空间中内核区内的代码和数据。设置模式位时,历程处于内核模式,该历程可以实验指令会合的任何命令,并且可以访问体系中的任何内存位置。

接下来分析 "hello" 历程的历程调度。当 "hello" 显式地请求休眠并调用 "sleep" 函数时,控制权转移到另一个历程。此时计时器开始计时,当计时器到达 "argv[3]",即 1 秒时,它会产生一个中断信号来中断当前正在运行的历程,并进行上下文切换,规复 "hello" 在休眠前的上下文信息,控制权回到 "hello" 历程,继续实验。当循环结束后,"hello" 调用 "getchar" 函数,此时历程由用户模式切换到内核模式。在内核模式下,陷阱处理步伐请求来自键盘缓冲区的 DMA 传输,并实验上下文切换,将控制权通报给其他历程。当完成键盘缓冲区到内存的数据传输后,引发一个中断信号,此时内核从其他历程切换回 "hello" 历程,并实验 "return",历程终止。

6.6 hello的异常与信号处理

hello 实验过程中大概出现四类异常:中断、陷阱、故障和终止。
1. 中断是来自 I/O 设备的信号,异步发生,中断处理步伐对其进行处理,返 回后继续实验调用前待实验的下一条代码,就像没有发生过中断。
2. 陷阱是故意的异常,是实验一条指令的结果,调用后也会返回到下一条指 令,用来调用内核的服务进行操纵。资助步伐从用户模式切换到内核模式。
 3. 故障是由错误环境引起的,它大概可以或许被故障处理步伐修正。假如修正成 功,则将控制返回到引起故障的指令,否则将终止步伐。
 4. 终止是不可规复的致命错误造成的结果,通常是一些硬件的错误,处理程 序会将控制返回给一个 abort 例程,该例程会终止这个应用步伐。
以下是hello中对于各个异常和信号的处理
      1.正常运行
步伐正常实验,总共循环5次每次输出提示信息之后等候我们从命令行输入的秒数,最后需要输入一个字符回车结束步伐。

      2.中途按下ctrl-Z
内核向前台历程发送一个SIGSTP信号,前台历程被挂起,直到通知它继续的信号到来,继续实验。当按下fg 1 后,输出命令行后,被挂起的历程从停息处,继续实验。

3.中途按下ctrl-C
内核向前台历程发送一个SIGINT信号,前台历程终止,内核再向父历程发送一个SIGCHLD信号,通知父历程回收子历程,此时子历程不再存在

4.运行途中乱按
运行途中乱按后,只是将乱按的内容输出,步伐继续实验,但是我们所输入的内容到第一个回车之前会当做getchar缓冲掉,后面的输入会简朴的当做我们即将要实验的命令出如今shell的命令行处。

5.输入ps打印前台历程组
ps打印当前历程的状态

       6.pstree打印历程树

7.列出jobs当前的使命
jobs,打印历程状态信息

8.输入fg 1,继续实验前台历程1

9.输入kill
kill之后会根据不同的发送信号的值,以及要发送的历程的pid发送相应的信号,这里我们将hello杀死。

6.7本章小结

本章介绍了历程管理,其中历程是一个正在运行的步伐的实例,它提供了两个关键的抽象概念:似乎步伐在独占处理器,似乎步伐在独占内存体系。每个历程都处于某个历程上下文中,并且每个历程都有自己的上下文,用于操纵体系通过上下文切换进行历程调度。用户可以通过shell和操纵体系交互,向内核提出请求,shell使用fork函数和execve函数来运行可实验文件。

第7章 hello的存储管理

7.1 hello的存储器所在空间

本章介绍了链接及其过程。链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并实验。链接可以由静态编译器在自编译时完成,也可以由动态链接器在加载时和运行时完成。链接器处理三种情势的目标文件,包括可重定位的、可实验的和共享的。链接器的两个重要使命是符号解析和重定位。

在盘算机体系中,物理所在(physical address)用于内存芯片级的单元寻址,与处理器和CPU毗连的所在总线相对应。它表示当前CPU外部所在总线上的寻址物理内存的所在信号,是所在变更的最终结果所在。假如启用了分页机制,那么线性所在会使用页目次和页表中的项变更成物理所在。假如没有启用分页机制,那么线性所在就直接成为物理所在了。

逻辑所在(Logical Address)是指由步伐产生的和段相关的偏移所在部分,表示为[段标识符:段内偏移量]。

线性所在(Linear Address)是逻辑所在到物理所在变更之间的中间层。步伐代码会产生逻辑所在,或者说是段中的偏移所在,加上相应段的基所在就生成了一个线性所在。假如启用了分页机制,那么线性所在可以再经过变更以产生一个物理所在。假如没有启用分页机制,那么线性所在直接就是物理所在。

虚拟所在(Virtual Address)是指虚拟内存为每个步伐提供的一个大的、同等的和私有的所在空间。它是由步伐生成的,每个字节对应的所在称为虚拟所在。虚拟所在包括VPO(虚拟页面偏移量)、VPN(虚拟页号)、TLBI(TLB 索引)和TLBT(TLB 标志)。

7.2 Intel逻辑所在到线性所在的变更-段式管理

逻辑所在空间是由段所在和偏移所在构成的。通过将段所在和偏移所在相加,可以得到线性所在。

在实模式下,逻辑所在被表明为 CS:EA=CS*16+EA,转换成物理所在。而在掩护模式下,逻辑所在要先用段形貌符在 GDT/LDT 表中查找相应的段所在,再加上偏移所在得到线性所在。段内偏移量已经在链接后确定为 32 位所在,因此需要使用逻辑所在前 16 位来获得段所在,这 16 位存放在段寄存器中。

段寄存器是 16 位的,用于存储段选择符。其中,CS(代码段)存储步伐代码所在的段,SS(栈段)存储栈区所在的段,DS(数据段)存储全局静态数据区所在的段,ES、GS 和 FS 这三个段寄存器可以指向任意数据段。


段选择符中的字段寄义如下:

TI=0 表示选择全局形貌符表(GDT),TI=1 表示选择局部形貌符表(LDT)。

RPL 字段表示 CPU 的当前特权级。RPL=00 为最高级的内核态,RPL=11 为最低级的用户态。

高 13 位-8K 个索引用来确定当前使用的段形貌符在形貌符表中的位置。



段形貌符是一种数据结构,用于形貌一个段的特性,包括段的起始所在、长度、访问权限、特权级等信息。段形貌符分为两类:用户的代码段和数据段形貌符,以及体系控制段形貌符。


形貌符表实际上是段表,分为全局形貌符表(GDT)、局部形貌符表(LDT)和中断形貌符表(IDT)。GDT只有一个,用于存放体系内每个使命都大概访问的形貌符,比方,内核代码段、内核数据段、用户代码段、用户数据段以及TSS(使命状态段)等都属于GDT中形貌的段。LDT用于存放某个使命(即用户历程)专用的形貌符。IDT包含256个中断门、陷阱门和使命门形貌符,用于处理中断、异常和体系调用等事件。


逻辑所在空间表示为:段所在:偏移所在,其中段所在存储在段寄存器中。在实模式下,逻辑所在可以通过 CS:EA = CS * 16 + EA 盘算出物理所在。在掩护模式下,逻辑所在需要通过段形貌符获得段所在,然后再加上偏移所在才气得到线性所在。段形貌符中包含特权级、段限定、段基所在等信息,通过段选择符(存储在段寄存器中)和TI、RPL等字段可以选择相应的形貌符。


下图展示了逻辑所在到线性所在的转化过程:


逻辑所在由段选择符和偏移所在构成,根据段选择符选择相应的段形貌符,获得段所在和段限定,将段所在和偏移所在合成线性所在,再经过页表变更得到最终的物理所在。


7.3 Hello的线性所在到物理所在的变更-页式管理

在Linux下,虚拟所在到物理所在的转化与翻译依赖于页式管理机制,其中虚拟内存作为内存管理的关键。虚拟内存可以被看作一个由N个连续的字节大小的单元构成的数组,存放在磁盘上。这些虚拟内存块被缓存到物理内存中(DRAM cache),每个块被称为一个页,其大小为P = 2p字节。分页机制通过将虚拟内存和物理内存分为多个页,创建映射关系,从而可以高效利用内存资源并方便地进行管理。一样平常环境下,一个页面的标准大小是4KB,但有时也可以达到4MB。虚拟页面作为磁盘内容的缓存,具有全相联的DRAM缓存、大的映射函数等特点,不同于硬件对SRAM缓存更复杂精密的更换算法。虚拟页面地集合被分为三个不相交的子集:已缓存、未缓存和未分配。

页表实现从虚拟页到物理页的映射,依赖的是页表,页表就是是一个页表条目 (Page Table Entry, PTE)的数组,将虚拟页所在映射到物理页所在。这个页表是常驻与主存中的。

下图展示了页式管理中虚拟所在到物理所在的转换:       

下图a展示了当页面掷中时,CPU硬件实验的步骤:
第1步:处理器生成一个虚拟所在,并把它传送给MMU;
第2步:MMU生成PTE所在,并从高速缓存/主存请求得到它;
第3步:高速缓存/主存向MMU返回PTE;
第4步:MMU构造物理所在,并把它传送给高速缓存/主存;
第5步:高速缓存/主存返回所请求的数据字给处理器

处理缺页如图b所示:
第1~3步:和图a中的第1步到第3步相同;

第4步:假如PTE中的有效位为零,MMU将触发一次异常,并将其通报给CPU中的控制单元,控制单元将操纵体系内核的缺页异常处理步伐作为处理步伐;

第5步:缺页处理步伐将确定要换出的物理内存页,假如该页面已经被修改,则将其交换到磁盘中;

第6步:缺页处理步伐将调入新的页面,并更新内存中的PTE;

第7步:缺页处理步伐返回到原始历程,重新实验导致缺页的指令。CPU会将引起缺页的虚拟所在重新发送给MMU。由于虚拟页面如今已经缓存在物理内存中,因此会掷中。在MMU实验图b中的步骤之后,主存将返回所请求的数据字给处理器。


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

为了消除每次 CPU 产生一个虚拟所在后,MMU 需要查阅 PTE 带来的时间开销,许多体系都在 MMU 中包括了一个关于 PTE 的小的缓存,称为翻译后被缓冲器(TLB),TLB 的速率快于 L1 cache。
TLB 通过虚拟所在的 VPN 部分进行索引,分为索引(TLBI)与标志(TLBT)两个部分。这样,MMU 在读取 PTE 时会直接通过 TLB 进行查找,假如查找成功则快速访问下一级页表或者得到第四级页表中的物理页表,否则再从内存中将 PTE 复制到 TLB。同时,为了减少页表太大而造成的空间损失,可以使用条理结构的页表以压缩页表大小。比方,core i7 使用的是四级页表。
在四级页表条理结构的所在翻译中,虚拟所在被分别为 4 个 VPN 和 1 个 VPO。每个 VPNi 都是一个到第 i 级页表的索引,第 j 级页表中的每个 PTE 都指向第 j+1 级某个页表的基址,第四级页表中的每个 PTE 包含某个物理页面的 PPN,或者一个磁盘块的所在。为了构造物理所在,在可以或许确定 PPN 之前,MMU 必须访问四个 PTE。
             
综上,在四级页表下,MMU 根据虚拟所在不同段的数字通过 TLB 快速访问得到下一级页表的索引或者得到第四级页表中的物理页表,然后与 VPO 组合,得到物理所在(PA)。

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

首先,对于给定的物理所在,CPU 会根据其 s 位组索引索引到 L1 cache 的某个组,然后在该组中查找是否有某一行的标志等于物理所在的标志并且该行的有效位为 1。假如有,则阐明掷中,从该行对应物理所在的 b 位块偏移的位置取出一个字节并提供给 CPU 使用。假如不满意上述条件,则阐明不掷中,需要继续访问下一级 cache,访问原理与 L1 相同。假如三级 cache 都没有要访问的数据,则需要访问内存,从中取出数据并放入 cache。

7.6 hello历程fork时的内存映射

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

当 fork 在新历程中返回时,新历程的虚拟内存和调用 fork 时存在的虚拟内存完全相同。当这两个历程中的任意一个历程进行写操纵时,写时复制机制就会创建新的页面,从而为每个历程维护私有所在空间的概念。

7.7 hello历程execve时的内存映射

execve 函数调用会驻留在内核区域的启动加载器代码,在当前历程中加载并运行包含在可实验目标文件 hello 中的步伐,以有效地替代当前步伐。加载并运行 hello 需要以下几个步骤:
1.删除已存在的用户区域。即删除当前历程虚拟所在的用户部分中的已存在区域结构。
2.映射私有区域。为新步伐的代码、数据、bss 和栈区域创建新的区域结构。全部这些新的区域都是私有的、写时复制的。代码和数据区域被映射为 hello 文件中的.text 和.data 区。bss 区域是请求二进制零的,映射到匿名文件中,其大小包含在 hello 中。栈和堆所在也是请求二进制零的,初始长度为零。
3.映射共享区域。hello 步伐与共享对象 libc.so 链接,libc.so 是动态链接到这个步伐中的,然后再映射到用户虚拟所在空间中的共享区域内。
4.设置步伐计数器(PC)。execve 做的最后一件事就是设置当前历程上下文的步伐计数器,使之指向代码区域的入口点。

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

DRAM缓存未掷中,即缺页,指的是虚拟内存中的某个字不在物理内存中。当CPU访问虚拟页的某个字时,所在翻译硬件将读取对应虚拟页的页表条目,假如有效位推断出该页未被缓存,就会触发一个缺页异常。缺页异常会调用内核中的缺页异常处理步伐,该步伐会选择一个牺牲页,把要缓存的页缓存到该牺牲页的位置。假如这个牺牲页被修改过,就将其交换出去。缺页处理步伐返回后,CPU重新实验引起缺页的指令,该指令再次发送虚拟所在到MMU,这次MMU将可以正常地将虚拟所在翻译为物理所在。

在上图中,当VP3被引用时,由于它未被缓存,触发了一个缺页异常。缺页处理步伐选择了VP4作为牺牲页,并从磁盘上读取了VP3的副本。在缺页处理步伐返回后,CPU重新实验引起缺页的指令,该指令再次发送虚拟所在到MMU,这次MMU将可以将虚拟所在翻译为物理所在,而不再产生异常。

7.9动态存储分配管理

动态内存分配器维护一个历程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护。每个块是一个连续的虚拟内存片,可以被分配给应用步伐使用。已分配的块显式地保留给应用步伐使用,而空闲块则用于分配。一个空闲块会不停保持空闲状态,直到被应用步伐显式地分配。一个已分配的块会不停保持已分配状态,直到被释放,这个释放要么是应用步伐显式实验的,要么是内存分配器自身隐式实验的。
内存分配器可以分为两种基本风格:显式分配器和隐式分配器。显式分配器要求应用步伐显式地释放已经分配的块,而隐式分配器则会检测一个已分配块是否被应用步伐使用,假如没有,就会主动释放该块,这个过程称为垃圾收集。


7.10本章小结

本章介绍了hello操纵体系中的存储管理机制。我们讨论了虚拟所在、线性所在和物理所在之间的关系,介绍了段式管理和页式管理,以及虚拟所在到物理所在的转换,还涉及到了物理内存的访问。我们还讨论了在hello历程中进行fork和execve时的内存映射过程,以及处理缺页故障和缺页中断的过程。最后,我们还介绍了动态存储分配管理。

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

一个Linux文件是一个包含m个字节的序列:B0,B1,…,Bm-1。全部的I/O设备都被视为文件。文件的范例有以下几种:
1.普通文件:包含任何数据,分为两类:
1) 文本文件:只含有ASCII码或Unicode字符的文件
2)二进制文件:全部其他文件
2.目次:包含一组链接的文件,每个链接都将一个文件名映射到一个文件。
3.套接字:用于与另一个历程进行跨网络通信的文件。
全部的输入和输出都被当尴尬刁难相应文件的读和写来实验。这种将设备优雅地映射为文件的方式,答应Linux内核引出一个简朴、低级的应用接口,称为Unix I/O。这使得全部的输入和输出都能以一种统一且同等的方式来实验。
8.2 简述Unix IO接口及其函数

Unix I/O接口提供了以下几种操纵:
1.打开文件:步伐可以请求内核打开文件,并获得一个小的非负整数(即形貌符),用于标识该文件。通过记录形貌符,步伐就能记录打开文件的全部信息。
2.为历程打开三个文件:在历程启动时,shell会为其打开三个文件:标准输入、标准输出和标准错误输出。
3.改变当前文件位置:对于每个打开的文件,内核都会保存一个文件位置k,初始值为0,表示从文件开头开始的字节偏移量。应用步伐可以使用seek操纵显式地设置文件的当前位置为k。
4.读写文件:读操纵从文件中复制n个字节到内存,从当前文件位置k开始,并将k增长到k+n。对于一个大小为m字节的文件,当k>=m时,实验读操纵将触发一个称为EOF的条件。应用步伐可以检测到该条件,但在文件末了处并没有明确的EOF符号。
5.关闭文件:内核会释放打开文件时创建的数据结构和占用的内存资源,并将形貌符规复到可用的形貌符池中。无论历程因何种原因终止,内核都会关闭全部打开的文件并释放它们的内存资源。
Unix I/O函数提供了以下几种操纵:

1.int open(char *filename, int flags, mode_t mode);
2.open函数将文件名filename转换为文件形貌符,并返回形貌符数字。返回的形貌符总是当前历程中未打开的最小形貌符。flags参数指定历程计划如何访问该文件,mode参数指定新文件的访问权限位。
3.int close(int fd);
4.close函数关闭一个打开的文件。
5.ssize_t read(int fd, void *buf, size_t n);
6.read函数从形貌符为fd的当前文件位置将最多n个字节复制到内存位置buf中。返回值-1表示错误,0表示EOF,否则返回的值表示实际传输的字节数量。
7.ssize_t write(int fd, const void *buf, size_t n);
8.write函数将内存位置buf中最多n个字节复制到形貌符fd的当前文件位置。
8.3 printf的实现分析

函数printf接受一个格式化字符串fmt以及可变数量的参数,使用vsprintf函数将它们格式化成一个字符串,并将结果写入到一个缓冲区中。然后使用体系调用write将该字符串输出到屏幕上,并返回该字符串的长度。

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;
}
函数vsprintf的作用是将格式化字符串fmt和对应的可变参数转换成一个字符串,并返回该字符串的长度。转换的过程中需要将参数按照格式化字符串的格式进行解析和处理。

将输出字符串显示到屏幕上需要经过以下步骤:字符显示驱动子步伐将ASCII码转换成字模库中对应的字模,然后将字模的数据存储到显示缓存区中。显示芯片按照一定的刷新频率逐行读取显示缓存区中的数据,并通过信号线向液晶显示器传输每一个像素的RGB颜色信息,从而实现字符在屏幕上的显示。这个过程中大概需要使用体系调用int 0x80或syscall等来实现对底层硬件的访问。
8.4 getchar的实现分析

当用户在键盘上按下某个键时,键盘接口将生成一个代表该键的键盘扫描码,并产生一个中断请求。中断请求会中断当前历程的实验,并运行键盘中断子步伐。键盘中断子步伐首先从键盘接口获取该键的扫描码,然后将其转换为相应的 ASCII 码,并将其存储到体系的键盘缓冲区中。
如今来看函数 getchar 的代码。该函数定义如下:
int getchar(void) {
  static char buf[BUFSIZ];
  static char* bb = buf;
  static int n = 0;

  if (n == 0) {
    n = read(0, buf, BUFSIZ);
    bb = buf;
  }
  return (--n >= 0) ? (unsigned char)*bb++ : EOF;
}
这段代码实现了从标准输入读取一个字符的功能。在实验过程中,getchar 调用了 read 函数,read 函数通过体系调用将键盘缓冲区中的数据读取出来,直到读到回车符为止,然后返回读取到的整个字符串。而在 getchar 函数中,只有第一个字符被返回,别的的字符被存储在输入缓冲区中,等候下一次读取。在这个过程中,假如缓冲区中没有数据,则会调用 read 函数从标准输入中读取数据,并将其存储到缓冲区中。
8.5本章小结

本章重要介绍了Linux操纵体系中I/O设备的管理方法,包括Unix I/O接口和相关函数的使用。其中,详细讲解了printf函数的实现原理以及getchar函数的详细实现方式。通过本章的学习,读者可以更深入地相识Linux体系的输入输出机制,以及在编写C语言步伐时如何利用I/O函数进行数据的读写和输出。
结论

从一个简朴的hello.c步伐,竟然可以或许产生这么多复杂的过程,这阐明任何步伐都不是简朴的。这些步伐需要经过多重磨练,从源代码变成可实验目标文件并不是通过简朴地按下运行按钮就能完成的。只有学完ICS这门神课才气真正理解其中的艰苦。

让我们简朴回首一下hello.c步伐的生命周期。首先,它会被C预处理器(cpp)处理,拓展带#的内容,变成hello.i文本文件;接着被编译器(ccl)转化成汇编文本。但是,汇编文本还无法被机器直接处理,因为它只能识别01序列。因此,需要由汇编器将hello.s生成可重定位目标文件hello.o。但是标题又来了,hello步伐调用的printf等库函数并不在hello.o文件中,以是无法运行。因此,链接器通过静态或动态链接最终生成了可实验目标文件hello。你大概以为hello的使命完成了,但它只是在磁盘上悄悄地待着,还需要很多"神仙"的资助才气运行,如fork创建新历程,execve加载映射等等。在运行过程中还大概会遇到异常环境,需要异常处理步伐才气解决。hello的一生可谓惊险而刺激!

盘算机体系是一个非常精致和巧妙的体系,好比流水线处理器准确到每个几微秒周期;它还引入了缓存的概念。我不由得赞叹盘算机体系设计者的高超智慧。在这门课中我学到了很多东西,感谢老师一学期以来的辛勤付出和耐心解答。虽然像hello一样,我只是履历了预处理的一小步,但我信赖在未来的路上,另有很长的路要走。




附件

hello.c
hello源代码
hello.i
预处理之后的文本文件
hello.s
hello的汇编代码
hellooalt.s
hello.o的反汇编代码
helloalt.s
hello的反汇编代码
hello.o
hello的可重定位文件
hello
hello的可实验文件
hello.elf
hello的elf文件
helloo.elf
hello.o的elf文件


参考文献


  • 大卫R.奥哈拉伦,兰德尔E.布莱恩特. 深入理解盘算机体系[M]. 机器工业出版社,2016.
  •  CSDN(www.csdn.net)


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

农妇山泉一亩田

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表