计算机系统
计算机科学与技术学院
2023年4月
本论文深入探讨了操作系统中的关键概念和机制,重点集中在进程管理、内存管理、IO设备管理以及Unix IO函数的使用方面。首先,我们介绍了进程的概念和作用,并详细分析了通过fork和execve函数创建和加载运行新进程的过程。随后,我们探讨了逻辑地址到物理地址的转换过程,以及缺页故障和缺页中断处理。接着,我们讨论了动态内存管理的基本方法和策略,以及Unix IO函数的用法、参数含义和函数功能。此外,我们还介绍了将IO设备模型化为文件的概念,以及在Linux中统一且一致地执行输入输出操作的Unix IO接口。最后,我们通过重新表述一些经典函数的源代码,用诗意的语言描绘了程序员与操作系统之间的交互过程。本研究为读者提供了对操作系统中关键概念和机制的深入理解,并为进一步研究和应用操作系统提供了基础和指导。
关键词:操作系统、进程管理、内存管理、IO设备管理、Unix IO函数、fork、execve、逻辑地址、物理地址、缺页故障、缺页中断、动态内存管理、Unix IO接口、文件模型化、输入输出操作、源代码、交互过程;
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P:from program to process:一个hello程序从开始编写到其结束的全过程,其包含以下的步骤:
编写代码:首先使用c来编写HelloWorld程序的源代码。这段代码并不复杂,甚至可以说是非常简单,只是需要打印HelloWorld!这个字符串。
预处理:在编译之前,所编写的源代码需要经过首先预处理。预处理器会处理一些预处理指令,像头文件包含、宏定义等。在这个阶段中,预编译命令会被处理。可以在代码中引入其他库或文件。
编译:预处理后的代码将会被送到编译器进行编译。编译器将源代码翻译成机器可执行的二进制的目标文件,这一般是是以机器码或者字节码的形式呈现的。在这个步骤汇中生成的目标文件通常是二进制(.bin)文件。
汇编:在这个阶段中,将由汇编器来把目标文件转换成机器码指令的文本表示形式。这些文本表示形式的指令是计算机硬件能够理解和执行的指令。
链接:在链接阶段,链接器将汇编生成的目标文件与所需的库文件进行链接,生成最终的可执行文件。对于Hello World程序,通常不需要链接其他库文件,因此这一步骤可能会比较简单。
运行:在这个时候,生成的可执行文件其实已经可以被操作系统加载和执行了。当我们运行Hello World程序时,操作系统会为其创建一个进程(fork)。
fork:在操作系统层面,使用fork系统调用创建一个新的进程,这个新进程是HelloWorld程序的副本。
execve:新的进程通过调用execve的系统调用来加载HelloWorld可执行文件的内容,并且通过此来替换自身的内存空间。
mmap:在执行过程中,操作系统会使用mmap系统调用函数来将可执行文件映射到进程的虚拟地址空间中,经过这样的操作以后,程序能够访问其指令和数据。
执行:一旦进程准备就绪,操作系统将控制权交给它。HelloWorld程序开始执行,将字符串"HelloWorld!"打印到屏幕上。
1.2 环境与工具
Windows平台:visual studio
Linux平台(通过ssh连接):codeblocks gcc等
1.3 中间结果
在使用gcc编译hello.c文件的过程中,第一部将通过预处理器生成文件hello.i
随后将经过编译器生成机器可执行的目标文件(hello.o)。再经过汇编器将目标文件转换为机器码的形式(.o)最后将通过链接器把此文件转换成机器指令的.s文件,最后将生成可执行文件.exe
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
1.4 本章小结
本章说明了本次大作业所用到的工具,并且分析了接下来几章节即将分析的内容
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
预处理是指在编译过程中对源代码进行文本替换和宏展开等操作的过程。它主要处理以#开头的预处理指令,并根据指令的要求对代码进行相应的处理。
下面是预处理的举例:比如在嵌入式c编程的过程中,在各个外设的库中,我们经常需要在多处同时include同一个包比如systick.h,这样容易产生重复引用的问题,所以我们需要使用预处理命令#ifndef来防止重复编译。
另一个例子,使用#define能方便程序的移植。
预处理的作用主要有宏展开,头文件包含,条件编译,去除注释,符号定义,错误检查等。
2.2在Ubuntu下预处理的命令
gcc -E hello.c -o hello.i
2.3 Hello的预处理结果解析
在hello.i文件中,打开查看可以看到文件的内容仍为C语言程序文本文件,主要的变化为:1.宏被展开(#define等)2.头文件包含了内容3.条件编译结果生成4.注释被去除5.符号被定义
2.4 本章小结
本章简单介绍了预处理这一部分,并且观察hello.i文件并对其进行了解释。预处理阶段为编译过程奠定了基础,使得源代码更加灵活、高效和可维护。通过预处理,我们可以实现代码的模块化、重用和跨平台适配,同时提高代码的可读性和维护性。了解预处理的概念和作用对于理解编译过程以及源代码的处理过程非常重要。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:编译是将高级语言代码转换为底层机器语言的过程。它将程序员编写的源代码作为输入,经过一系列的处理和转换,生成可执行文件。编译过程旨在优化和转换源代码,使其能够在目标计算机上正确地执行。
作用:编译器是执行编译过程的软件工具。它接受源代码作为输入,并将其转换为可执行文件。编译器通常由以下几个组件组成:
1.词法分析器:把源代码分解为单个的词法单元
2.语法分析器:根据语法规则分析词法单元的结构和关系,并构建抽象语法树表示程序的结构。
3.语义分析器:对语法树进行语义分析,检查变量声明、类型匹配、函数调用等语义错误。
4.中间代码生成
3.2 在Ubuntu下编译的命令
gcc -S hello.c
3.3 Hello的编译结果解析
首先给出完整的hello.s的源代码:
.file "hello.c"
.text
.section .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>.LC1:
.string "Hello %s %s\n"
.text
.globl main
.type main, @function
main:
.LFB6:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
cmpl $4, -20(%rbp)
je .L2
leaq .LC0(%rip), %rax
movq %rax, %rdi
call puts@PLT
movl $1, %edi
call exit@PLT
.L2:
movl $0, -4(%rbp)
jmp .L3
.L4:
movq -32(%rbp), %rax
addq $16, %rax
movq (%rax), %rdx
movq -32(%rbp), %rax
addq $8, %rax
movq (%rax), %rax
movq %rax, %rsi
leaq .LC1(%rip), %rax
movq %rax, %rdi
movl $0, %eax
call printf@PLT
movq -32(%rbp), %rax
addq $24, %rax
movq (%rax), %rax
movq %rax, %rdi
call atoi@PLT
movl %eax, %edi
call sleep@PLT
addl $1, -4(%rbp)
.L3:
cmpl $4, -4(%rbp)
jle .L4
call getchar@PLT
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE6:
.size main, .-main
.ident "GCC: (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
以下是解析:
3.3.1 总体
.file "hello.c":指定源代码文件名为"hello.c"。
.text:标识接下来的指令为代码段
.section .rodata:标识接下来的指令为只读数据段
.align 8:标识当前位置对其到8字节的边界
3.3.2 数据类型
.LC0:定义一个标签,以便后续的引用
在其中,"\347\224\250\346\263\225: Hello \345\255\246\345\217\267 \345\247\223\345\220\215 \347\247\222\346\225>
标识了一个以utf-8编码的字符串常量
.LC1:与上文类似,另一个标签
在LC1中,不仅有"Hello %s %s\n这样的让printf函数引用的字符串,还有main函数的入口
对于这两个标签,其将字符串常量存储在了只读数据段中,在嵌入式编程中通常为flash(相对于sram)
对于整数常量:即代码中使用的常数,编译器通常将常数直接编码为汇编指令中的立即数。
对于变量(如此文件中使用的变量i)通常是在栈中分配一段空间来存储。
算数操作:对于加法,比较等操作,编译器将算术操作转化为相应的汇编指令,对寄存器和内存中的数据进行操作如(addq,cmpl)等指令。
类型转换:程序中使用了atoi函数进行字符串到整数的转换。编译器会根据函数的定义,将字符串类型的参数转换为整数类型。
addq $24, %rax
movq (%rax), %rax
movq %rax, %rdi
赋值操作:在代码中有这样的操作:
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
它将函数参数赋值给了本地变量。
函数操作:在程序中多次用到了类似下列的函数操作:
call puts@PLT
call exit@PLT
call printf@PLT
它能使得指定的函数得到调用
控制转移:在此程序中控制转移使用jle.命令,它能在达成某些条件时进行某些操作。
3.4 本章小结
本章着重介绍了编译的过程,并且对生成的hello.s文件进行了解析。首先,预处理阶段通过对源代码的处理,展开宏定义、包含头文件等,生成了经过预处理的代码文件。然后,编译器将预处理后的代码文件转换为汇编代码,进行语法分析、语义分析、类型检查等操作,生成汇编语言表示的中间代码。最后,汇编器将汇编代码转化为机器代码,生成可执行文件。
编译器在处理过程中会根据语法规则和语义规则进行各种检查,包括语法检查、类型检查等,以确保代码的正确性和安全性。此外,编译器还会进行代码优化,以提高程序的性能和效率。
通过编译过程,我们可以将高级的C语言代码转换为机器可执行的代码,使得计算机能够理解和执行我们编写的程序。编译器在其中起到了关键的作用,将程序员编写的代码转化为计算机可以理解和执行的指令序列。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
将汇编代码转换成机器指令。汇编器会将汇编代码翻译成机器可执行的指令,并生成一个目标文件(.o),通常以 .o 扩展名表示。
在这个过程中,编译器和汇编器会对 C 语言的数据类型、操作符、控制流等进行转换和处理。
数据类型:编译器会根据 C 语言中定义的数据类型(如整数、浮点数、字符等)将其映射到汇编语言中相应的寄存器或内存位置。不同的数据类型可能需要不同的指令和操作。
操作符:编译器会将 C 语言中的操作符(如赋值操作、算术操作、逻辑操作等)转换为相应的汇编指令,进行相应的计算和操作。
控制流:编译器会将 C 语言中的控制流语句(如条件语句、循环语句等)转换为相应的汇编指令,控制程序的执行流程。
数组和指针:编译器会将 C 语言中的数组和指针操作转换为相应的汇编指令,进行内存的访问和操作。
函数调用:编译器会将 C 语言中的函数调用转换为相应的汇编指令,包括参数传递、栈帧的设置和清理等。
通过这些转换和处理,编译器将 C 语言代码转化为汇编代码,汇编器将汇编代码转化为机器可执行的目标文件,为后续的链接和最终生成可执行文件做准备。
4.2 在Ubuntu下汇编的命令
gcc -c hello.c -o hello.o
4.3 可重定位目标elf格式
使用命令readelf -S hello.o来打开hello.o
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
There are 14 section headers, starting at offset 0x420:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000098 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 000002d0
00000000000000c0 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 000000d8
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 000000d8
0000000000000000 0000000000000000 WA 0 0 1
[ 5] .rodata PROGBITS 0000000000000000 000000d8
0000000000000033 0000000000000000 A 0 0 8
[ 6] .comment PROGBITS 0000000000000000 0000010b
000000000000002c 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 00000137
0000000000000000 0000000000000000 0 0 1
[ 8] .note.gnu.pr[...] NOTE 0000000000000000 00000138
0000000000000020 0000000000000000 A 0 0 8
[ 9] .eh_frame PROGBITS 0000000000000000 00000158
0000000000000038 0000000000000000 A 0 0 8
[10] .rela.eh_frame RELA 0000000000000000 00000390
0000000000000018 0000000000000018 I 11 9 8
[11] .symtab SYMTAB 0000000000000000 00000190
0000000000000108 0000000000000018 12 4 8
[12] .strtab STRTAB 0000000000000000 00000298
0000000000000032 0000000000000000 0 0 1
[13] .shstrtab STRTAB 0000000000000000 000003a8
0000000000000074 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
D (mbind), l (large), p (processor specific)
若使用readelf -a hello.o >hello.elf命令,可将其结果保存至hello.elf文件中
在此文件中我们可以看到其elf头(head),其内容如下:
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
Version: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: REL (可重定位文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x0
程序头起点: 0 (bytes into file)
Start of section headers: 1056 (bytes into file)
标志: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 14
Section header string table index: 13
由此可得到的信息有:
Magic:ELF 文件的前几个字节是固定的魔数,用于标识该文件格式。在此例中,魔数为 7f 45 4c 46 02 01 01 00,对应 ELF64 文件。
类型:64位可执行文件
数据:表明其采用补码表示,并且采用小段。
类型:该 ELF 文件是一个可重定位文件,这表明了此文件是一个尚未链接的目标文件。
入口点地址:该 ELF 文件没有指定入口点地址,因为它是一个可重定位文件,而不是一个可执行文件。
程序头起点:在该 ELF 文件中,程序头表的起始位置为 0 字节(文件的开头)。
节头表起点:节头表的起始位置在文件中的偏移量为 1056 字节。
标志:标志字段为 0x0,表示没有特殊标志被设置。
此头的大小:ELF 头的大小为 64 字节。
程序头大小:该字段的值为 0,表示该 ELF 文件中不存在程序头表。
程序头数量:该字段的值为 0,表示该 ELF 文件中不存在程序头表。
节头大小:节头的大小为 64 字节。
节头数量:节头的数量为 14。
节头字符串表索引:该字段的值为 13,表示节头字符串表在节头表中的索引。
.rela.text 节包含重定位信息,它的类型为 RELA,偏移量为 0x2d0,大小为 0xc0,链接到节号为 11 的 .symtab 节,其中包含 1 个重定位项(位于Info 列)。
.rela.eh_frame 节(节号 10)也包含重定位信息,它的类型为 RELA,偏移量为 0x390,大小为 0x18,链接到节号为 11 的 .symtab 节,其中包含 9 个重定位项。
这些重定位节用于指示链接器在将目标文件与其他模块链接时如何进行符号重定位。它们记录了需要修改的位置和相关的符号信息。
4.4 Hello.o的结果解析
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
执行此命令后可得到以下输出:
hello.o: 文件格式 elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
0: f3 0f 1e fa endbr64
4: 55 push %rbp
5: 48 89 e5 mov %rsp,%rbp
8: 48 83 ec 20 sub $0x20,%rsp
c: 89 7d ec mov %edi,-0x14(%rbp)
f: 48 89 75 e0 mov %rsi,-0x20(%rbp)
13: 83 7d ec 04 cmpl $0x4,-0x14(%rbp)
17: 74 19 je 32 <main+0x32>
19: 48 8d 05 00 00 00 00 lea 0x0(%rip),%rax # 20 <main+0x20>
1c: R_X86_64_PC32 .rodata-0x4
20: 48 89 c7 mov %rax,%rdi
23: e8 00 00 00 00 call 28 <main+0x28>
24: R_X86_64_PLT32 puts-0x4
28: bf 01 00 00 00 mov $0x1,%edi
2d: e8 00 00 00 00 call 32 <main+0x32>
2e: R_X86_64_PLT32 exit-0x4
32: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
39: eb 4b jmp 86 <main+0x86>
3b: 48 8b 45 e0 mov -0x20(%rbp),%rax
3f: 48 83 c0 10 add $0x10,%rax
43: 48 8b 10 mov (%rax),%rdx
46: 48 8b 45 e0 mov -0x20(%rbp),%rax
4a: 48 83 c0 08 add $0x8,%rax
4e: 48 8b 00 mov (%rax),%rax
51: 48 89 c6 mov %rax,%rsi
54: 48 8d 05 00 00 00 00 lea 0x0(%rip),%rax # 5b <main+0x5b>
57: R_X86_64_PC32 .rodata+0x22
5b: 48 89 c7 mov %rax,%rdi
5e: b8 00 00 00 00 mov $0x0,%eax
63: e8 00 00 00 00 call 68 <main+0x68>
64: R_X86_64_PLT32 printf-0x4
68: 48 8b 45 e0 mov -0x20(%rbp),%rax
6c: 48 83 c0 18 add $0x18,%rax
70: 48 8b 00 mov (%rax),%rax
73: 48 89 c7 mov %rax,%rdi
76: e8 00 00 00 00 call 7b <main+0x7b>
77: R_X86_64_PLT32 atoi-0x4
7b: 89 c7 mov %eax,%edi
7d: e8 00 00 00 00 call 82 <main+0x82>
7e: R_X86_64_PLT32 sleep-0x4
82: 83 45 fc 01 addl $0x1,-0x4(%rbp)
86: 83 7d fc 04 cmpl $0x4,-0x4(%rbp)
8a: 7e af jle 3b <main+0x3b>
8c: e8 00 00 00 00 call 91 <main+0x91>
8d: R_X86_64_PLT32 getchar-0x4
91: b8 00 00 00 00 mov $0x0,%eax
96: c9 leave
97: c3 ret
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
对比反汇编代码和hello.s,我们可以得出以下几点区别:
分支转移和跳转标签:在汇编代码中,分支转移和跳转标签通常使用助记符(例如.L0)表示;而在反汇编代码中,分支转移和跳转使用相对于主函数的偏移量进行表示。
函数调用:汇编代码中的函数调用直接使用函数的名称(例如printf、atoi、sleep等);而反汇编代码中的函数调用使用主函数名加上相对偏移量的方式进行调用(例如main+0x28、main+0x32、main+0x68等)。
全局变量访问:汇编代码中访问全局变量时使用类似.LC0(%rip)的表示方式;而反汇编代码中使用相对于指令指针寄存器(RIP)的偏移量表示(例如0x0(%rip))。
数字表示方式:汇编代码中使用十进制表示数值,而反汇编代码使用十六进制表示数值。这是因为机器语言和汇编语言之间的映射关系,机器语言中的操作数表示通常使用二进制或十六进制,而汇编语言更接近人类可读的形式,使用十进制表示更为直观。
这些区别展示了机器语言和汇编语言之间的映射关系。机器语言是计算机可以直接执行的二进制指令,它使用特定的编码来表示不同的操作和操作数。汇编语言是机器语言的人类可读形式,通过助记符和符号表示,将机器指令和操作数翻译为更容易理解的形式。
在反汇编过程中,机器语言指令被转换回汇编语言形式,通过识别指令和操作数的编码规则,以及对应的符号表和地址计算,将机器指令转换为汇编代码。由于机器语言和汇编语言的表示方式略有不同,因此在反汇编代码中,我们会看到一些数值和跳转地址的表示形式与汇编代码中的表示略有差异。
4.5 本章小结
本章主要涵盖了汇编语言的概念和作用,并通过对hello.o文件的转换和内容解读进行了实例演示。随后,我们使用objdump工具对hello.o文件进行反汇编,并将其与汇编文件进行对比,从中得出了一些反汇编代码和汇编代码之间的区别。这些区别包括分支转移和函数调用的表示方式、全局变量的访问方式以及使用的进制表示等。通过这些对比,我们加深了对机器语言构成及其与汇编语言的映射关系的理解。汇编语言作为一种低级编程语言,能够直接控制计算机硬件,对理解计算机底层工作原理和进行性能优化具有重要意义。
第5章 链接
5.1 链接的概念与作用
链接是将多个目标文件(或可重定位文件)合并为一个可执行文件或共享库的过程。它包括符号解析、地址重定位和符号表的处理等步骤,最终生成可执行文件或共享库,使得程序能够在运行时正确地链接并执行。
链接的作用是解决符号引用的问题。当一个程序在多个源文件中引用了同一个符号(如函数或变量)时,链接器负责将这些引用与符号的定义进行匹配,以确保程序能够正确地找到并使用这些符号。
5.2 在Ubuntu下链接的命令
(以下格式自行编排,编辑时删除)
ld -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 /usr/lib/gcc/x86_64-linux-gnu/7/crtbegin.o hello.o -lc /usr/lib/gcc/x86_64-linux-gnu/7/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o -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、/usr/lib/gcc/x86_64-linux-gnu/7/crtbegin.o、/usr/lib/gcc/x86_64-linux-gnu/7/crtend.o 和 /usr/lib/x86_64-linux-gnu/crtn.o 是一些标准的启动文件和终止文件,它们提供了程序运行所需的基本功能。
hello.o 是要链接的目标文件。
-lc 指定要链接的 C 标准库(libc)。
-z relro 启用了 Relocation Read-Only(RELRO)机制,增加了程序的安全性。
-o hello 指定生成的可执行文件名为 hello。
5.3 可执行目标文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
使用readelf -a hello > hello.elf命令来获取elf文件:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400318 00000318
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.gnu.pr[...] NOTE 0000000000400338 00000338
0000000000000030 0000000000000000 A 0 0 8
[ 3] .note.gnu.bu[...] NOTE 0000000000400368 00000368
0000000000000024 0000000000000000 A 0 0 4
[ 4] .note.ABI-tag NOTE 000000000040038c 0000038c
0000000000000020 0000000000000000 A 0 0 4
[ 5] .gnu.hash GNU_HASH 00000000004003b0 000003b0
0000000000000024 0000000000000000 A 6 0 8
[ 6] .dynsym DYNSYM 00000000004003d8 000003d8
00000000000000f0 0000000000000018 A 7 1 8
[ 7] .dynstr STRTAB 00000000004004c8 000004c8
000000000000007e 0000000000000000 A 0 0 1
[ 8] .gnu.version VERSYM 0000000000400546 00000546
0000000000000014 0000000000000002 A 6 0 2
[ 9] .gnu.version_r VERNEED 0000000000400560 00000560
0000000000000040 0000000000000000 A 7 1 8
[10] .rela.dyn RELA 00000000004005a0 000005a0
0000000000000048 0000000000000018 A 6 0 8
其中:[0] NULL:
类型: NULL
地址: 0x0000000000000000
偏移量: 0x00000000
大小: 0x0000000000000000
[1] .interp:
类型: PROGBITS
地址: 0x0000000000400318
偏移量: 0x00000318
大小: 0x000000000000001c
[2] .note.gnu.pr[...]:
类型: NOTE
地址: 0x0000000000400338
偏移量: 0x00000338
大小: 0x0000000000000030
[3] .note.gnu.bu[...]:
类型: NOTE
地址: 0x0000000000400368
偏移量: 0x00000368
大小: 0x0000000000000024
[4] .note.ABI-tag:
类型: NOTE
地址: 0x000000000040038c
偏移量: 0x0000038c
大小: 0x0000000000000020
[5] .gnu.hash:
类型: GNU_HASH
地址: 0x00000000004003b0
偏移量: 0x000003b0
大小: 0x0000000000000024
[6] .dynsym:
类型: DYNSYM
地址: 0x00000000004003d8
偏移量: 0x000003d8
大小: 0x00000000000000f0
[7] .dynstr:
类型: STRTAB
地址: 0x00000000004004c8
偏移量: 0x000004c8
大小: 0x000000000000007e
[8] .gnu.version:
类型: VERSYM
地址: 0x0000000000400546
偏移量: 0x00000546
大小: 0x0000000000000014
[9] .gnu.version_r:
类型: VERNEED
地址: 0x0000000000400560
偏移量: 0x00000560
大小: 0x0000000000000040
[10] .rela.dyn:
类型: RELA
地址: 0x00000000004005a0
偏移量: 0x000005a0
大小: 0x0000000000000048
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
从截图可以看出,其地址范围为0x0000000000401000-0x0000000000402000
5.5 链接的重定位过程分析
(以下格式自行编排,编辑时删除)
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
5.6 hello的执行流程
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
1加载hello可执行文件:
操作系统将可执行文件加载到进程的虚拟内存空间中,建立相应的内存映射。
通常,加载过程会包括将程序的各个段(如代码段、数据段等)映射到适当的虚拟地址空间中。
2执行程序的入口点_start:
_start是程序的入口点,它通常由启动代码(如crt1.o)提供。
_start负责执行一些初始化操作,如设置栈、设置环境变量等,然后跳转到程序的主函数。
3跳转到main函数:
一般情况下,启动代码(如crt1.o)会在执行一些初始化工作后,调用main函数。
跳转到main函数的过程可能涉及一些函数调用约定和参数传递,具体实现会根据编译器和平台而有所不同。
4main函数的执行:
一旦跳转到main函数,程序将开始执行main函数中的指令。
main函数通常包含程序的主要逻辑和功能。
5程序终止:
一旦main函数执行完毕或遇到return语句,程序将开始执行退出流程。
退出流程可能包括清理资源、返回退出码等操作。
在最终退出之前,可能还会执行一些与资源清理相关的函数。
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
在进行动态链接之前,需要进行静态链接,生成部分链接的可执行目标文件 hello。该文件中只包含代码和数据,没有共享库的内容。在加载 hello 时,动态链接器会对共享目标文件中的相应模块进行重定位,从而生成完全链接的可执行目标文件。
为了避免在运行时修改调用模块的代码,链接器采用延时绑定的策略。它通过全局偏移量表(GOT)和过程链接表(PLT)的协同工作来实现函数的动态链接。PLT+GOT 是实现动态链接的基础。
当程序需要调用一个动态链接的函数时,PLT 和 GOT 提供了一种机制来确保函数能够正确地被加载和调用。PLT 是一个代码段,其中包含一系列的跳转指令,每个指令对应一个动态链接的函数。GOT 是一个全局偏移量表,保存了函数的地址信息。
具体的动态链接过程如下:
当程序第一次调用一个动态链接的函数时,PLT 中的跳转指令会被执行。
PLT 中的跳转指令首先跳转到相应的 PLT 槽,该槽中保存了一个间接跳转指令和一个对应函数在 GOT 中的入口地址。
第一次执行时,间接跳转指令会触发动态链接器,它会查找并加载函数的实际地址,并将其写入 GOT 中的对应槽位。
之后,PLT 中的跳转指令会直接跳转到 GOT 中保存的函数地址,实现函数的调用。
通过延时绑定和 PLT+GOT 机制,动态链接器能够在程序运行时动态加载和链接所需的共享库,并实现函数的动态调用,从而实现了动态链接的特性。这种机制允许程序在运行时利用共享库提供的功能,同时减少了可执行文件的大小和启动时间。
5.8 本章小结
本章主要介绍了链接的概念和作用。通过将hello.o进行链接操作,我们生成了可执行目标文件hello。在此过程中,我们验证了hello的虚拟地址空间与ELF头中的各个节的信息是相对应的。同时,我们还分析了hello的执行流程和动态链接的过程。
链接是将多个目标文件或库文件组合在一起,形成一个可执行文件或共享库的过程。通过链接,我们可以解决符号引用、地址重定位和库依赖等问题,将各个模块或库文件连接在一起,形成最终可执行的程序或库。
在本章中,我们使用链接器对hello.o进行链接操作,生成了可执行目标文件hello。通过读取ELF头的信息,我们可以了解到hello的虚拟地址空间中各个节的起始地址、大小等信息,这些信息与ELF头中的节头对应。
此外,我们还深入分析了hello的执行流程和动态链接过程。hello在执行时,会按照一定的顺序调用不同的函数和模块。动态链接是指在程序运行时将共享库动态加载到内存中,并通过符号解析和重定位实现函数的动态调用。我们了解到动态链接的关键是延时绑定、全局偏移量表(GOT)和过程链接表(PLT)的机制,它们协同工作来实现函数的动态链接和调用。
通过本章的学习,我们对链接的概念、作用以及动态链接的原理有了更深入的理解,这将为我们理解和构建复杂的程序和库文件提供重要的基础。
第6章 hello进程管理
6.1 进程的概念与作用
进程的概念:进程是计算机中正在运行的程序的实例。它是操作系统进行任务调度和资源分配的基本单位。每个进程都有自己的地址空间、执行状态、上下文和相关资源。
其作用有:并发执行,任务调度,实现资源管理,数据保护,进程通信和异常处理等
6.2 简述壳Shell-bash的作用与处理流程
shell是操作系统和用户交互的接口,它是一个命令行解释器,负责解释用户输入的命令并将其传递给操作系统内核执行。
Bash的处理流程通常包括以下几个步骤:
1提示符显示:Bash会显示一个提示符,通常是一个特定的字符或字符串,表示等待用户输入命令的就绪状态。
2读取命令:Bash等待用户输入命令,并通过标准输入(通常是键盘输入)获取用户输入的命令。
3解析命令:Bash会对用户输入的命令进行解析,识别命令本身、参数、重定向符号等。
4执行命令:Bash将解析后的命令传递给操作系统内核执行。根据命令的类型和参数不同,操作系统可能执行程序、创建新进程、管理文件等。
5输出结果:执行命令后,Bash会将命令的执行结果输出到标准输出(通常是终端屏幕),供用户查看。
6循环处理:Bash会不断循环执行上述步骤,等待用户输入新的命令,直到用户选择退出或终止Shell。
6.3 Hello的fork进程创建过程
当我们创建一个fork时,父进程调用fork函数,随后操作系统将创建子进程。
在之后的时间,子进程继续执行。在最后返回时通过返回的进程id区分不同进程。
6.4 Hello的execve过程
在Hello程序中,execve函数用于执行指定的可执行文件,并将当前进程替换为新的进程。下面是Hello的execve过程的简要描述:
程序准备参数:在Hello程序中,首先需要准备一个包含可执行文件路径和命令行参数的参数数组。通常使用一个字符串数组来表示这些参数。
调用execve函数:接下来,程序调用execve函数,将可执行文件的路径和参数数组作为参数传递给该函数。execve函数是一个系统调用,它会请求操作系统执指定的可执行文件。
操作系统加载新程序:当调用execve函数后,操作系统会加载并执行指定的可执行文件。操作系统会替换当前进程的代码和数据,并将控制权转移到新的程序。
执行新程序:一旦新程序被加载,它将从其入口点开始执行。新程序将获得操作系统分配的资源,并开始执行其代码。
6.5 Hello的进程执行
并发执行多个流是一种常见现象,被称为并发。多任务是指一个进程与其他进程轮流运行的概念,每个进程在执行其控制流的一部分时被称为时间片。因此,多任务也被称为时间分片。操作系统通过分配时间片给各个进程,实现多个进程在CPU上轮流执行,从而实现并发执行的效果。
上下文切换:
操作系统内核通过一种称为上下文切换的高级形式的异常控制流来实现多任务。内核为每个进程维护一个上下文,该上下文包含了进程的状态信息。当内核决定抢占当前正在执行的进程,并重新开始之前被抢占的进程时,就会进行上下文切换。上下文切换的过程主要包括以下步骤:
保存当前进程的上下文,包括寄存器、用户栈、内核栈、程序计数器以及其他内核数据结构的值。
恢复先前被抢占进程保存的上下文。
将控制权传递给恢复的进程,使其继续执行。
通过上下文切换,操作系统能够实现多个进程的轮流执行,使得每个进程都能获得执行的机会。
6.6 hello的异常与信号处理
错误异常:当程序遇到错误或异常情况时,会生成错误信号,如SIGSEGV(段错误)表示访问非法内存,SIGILL(非法指令)表示执行非法指令等。这些异常通常由操作系统捕获并终止程序的执行。
中断信号:按键盘产生的中断信号可以被Hello程序捕获并处理。常见的中断信号有SIGINT(Ctrl-C)和SIGTSTP(Ctrl-Z)。SIGINT信号用于终止程序的执行,而SIGTSTP信号用于将程序置于后台挂起。
子进程状态变化:如果Hello程序创建了子进程,当子进程发生状态变化时(如终止或停止),会产生SIGCHLD信号。
6.7本章小结
本章介绍了进程的概念和作用,简单的描述了shell的原理,并分析了执行hello进程以及hello进程运行时的异常/信号处理过程,使用fork以及execve运行hello。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:逻辑地址是程序中使用的地址空间,它是相对于程序本身而言的,通常是指程序中的变量、函数或指令在编译和链接过程中的地址。在hello程序中,逻辑地址可以表示为源代码中变量或函数的相对位置。
线性地址:线性地址是逻辑地址经过转换后得到的地址,它是一个连续的地址空间,对应于程序执行时的虚拟地址空间。在hello程序中,线性地址可以表示为程序在虚拟内存中的位置。
虚拟地址:虚拟地址是指程序在执行过程中使用的地址,它是相对于虚拟内存而言的。虚拟地址是由线性地址经过分页机制、地址映射和转换得到的。在hello程序中,虚拟地址可以表示为程序在虚拟内存中的偏移量。
物理地址:物理地址是指实际的内存地址,它是处理器在访问内存时发送到内存总线上的地址。物理地址是由虚拟地址经过内存管理单元的地址转换机制得到的。在hello程序中,物理地址表示程序在物理内存中的位置。
7.2 Intel逻辑地址到线性地址的变换-段式管理
在Intel平台下,逻辑地址(logical address)由段选择符(selector)和段内偏移量(offset)组成,通常表示为"selector:offset"的形式。其中,selector是CS寄存器的值,offset是EIP寄存器的值。逻辑地址需要经过一定的转换过程才能映射到实际的物理地址。
在段式内存管理机制中,通过使用段选择符去全局描述符表(GDT)或局部描述符表(LDT)中查找相应的段描述符。段描述符是一个数据结构,包含了段的基地址(segment base address)和段的大小等信息。段选择符的前13位用于在描述符表中定位到对应的段描述符。
通过从段描述符中获取段的基地址,然后将其与段内偏移量相加,就可以得到线性地址(linear address)。线性地址是一个虚拟的地址空间,它还需要进一步的转换才能映射到物理地址。
需要注意的是,逻辑地址到线性地址的转换仅涉及段式内存管理的一部分,还需要进行后续的页式管理等转换才能得到最终的物理地址。这个转换过程由处理器和操作系统的内存管理单元(MMU)完成
7.3 Hello的线性地址到物理地址的变换-页式管理
从线性地址到物理地址的转换是通过地址翻译的过程。线性地址可以分为虚拟页号(VPN,Virtual Page Number)和虚拟页偏移量(VPO,Virtual Page Offset)两部分。
在地址翻译过程中,系统使用控制寄存器 CR3 存储了页表的基址(Page Table Base Address)。通过访问 CR3 寄存器,系统可以获取页表的起始地址。
内存管理单元(MMU)根据线性地址中的 VPN,通过查找页表中相应的页表项,可以得到对应的物理页号(PPN,Physical Page Number)。同时,由于线性地址的 VPO 和物理地址的 PPO(Physical Page Offset)是相同的,因此将获取的物理页号和 VPO 连接起来,就得到了对应的物理地址。
7.4 TLB与四级页表支持下的VA到PA的变换
每当CPU生成一个虚拟地址时,MMU(内存管理单元)需要查找对应的PTE(页表条目),将虚拟地址转换为物理地址。在最糟糕的情况下,这个过程可能需要额外访问内存,导致几十到几百个时钟周期的延迟。然而,为了减少这种开销,许多系统引入了一个小型的PTE缓存,称为翻译后备缓存器(TLB,Translation Lookaside Buffer)。
TLB是一个小型的虚拟寻址缓存,其中的每一行都存储着一个PTE块。TLB通常具有高度的相联度,这意味着每个虚拟地址都有多个可能的物理地址对应,从而提高了查找命中率。当CPU访问一个新的虚拟地址时,MMU首先会在TLB中查找对应的PTE。如果TLB中存在该条目(即TLB命中),则可以直接获取物理地址,避免了访问页表的开销,大大加快了地址转换的速度。
然而,如果TLB中没有找到对应的PTE(即TLB未命中),那么MMU将不得不访问内存中的页表来获取所需的PTE。这种情况下,会产生额外的延迟。为了最大程度地提高TLB的命中率,系统通常采用各种策略,如增大TLB的容量、优化TLB的替换算法等。
7.5 三级Cache支持下的物理内存访问
在访问缓存的过程中,首先将物理地址分为三部分:CT(Cache Tag,高速缓存标记)、CI(Cache Index,组缓存索引)和CO(Cache Offset,块偏移)。这样的划分是为了在多级缓存中进行有效的地址映射和缓存访问。
接下来,根据CI的值在L1缓存中找到对应的组。每个组包含多个缓存块。然后,根据CT在该组中查找匹配的缓存块。如果某个缓存块的标记与CT相同且有效位已被设置,即表示缓存命中,可以直接返回所需的数据。
如果在当前级别的缓存中未命中,即缓存未找到所需的数据,那么就需要继续访问下一级缓存,例如L2缓存或L3缓存。同样的过程会重复进行,即根据CI在下一级缓存的对应组中查找匹配的缓存块,直到找到所需的数据或达到最后一级缓存。
如果在最后一级缓存(通常是L3缓存)中仍然未能找到所需的数据,那么就需要从主存(内存)中读取数据,并将其加载到缓存中。这将涉及到更长的延迟,因为主存的访问速度相对较慢。
7.6 hello进程fork时的内存映射
在Shell中输入命令行后,当内核调用fork创建子进程时,会为子进程创建一个新的上下文,并为其分配一个与父进程不同的进程标识符(PID)。同时,为子进程创建了独立的虚拟内存空间。
在创建子进程时,内核会复制父进程的mm_struct(内存描述结构)、区域结构和页表,创建它们的副本。这样,新进程和父进程的虚拟内存布局和映射关系是相同的。为了保持进程间的隔离性,内核会将这两个进程中的每个页面标记为只读,并将每个区域结构标记为私有的写时复制。
当fork在新进程中返回时,新进程的虚拟内存空间与调用fork时父进程的虚拟内存空间是完全相同的。这意味着新进程和父进程共享同样的内存映射和数据内容。但是,当这两个进程中的任何一个进行写操作时,写时复制机制就会发挥作用,创建新的页面。这样,每个进程都会保持自己的私有地址空间的抽象概念。
通过写时复制机制,子进程和父进程可以共享大部分内存空间,减少了内存的复制开销。只有在需要修改数据时才会进行实际的复制操作,确保进程间的数据隔离性。
这种方式使得fork创建的子进程可以在独立的环境中运行,并且对于每个进程来说,它们都认为自己拥有独立的内存空间。这样,每个进程可以独立地执行操作,而不会影响其他进程的数据和状态。
7.7 hello进程execve时的内存映射
在execve执行期间,会进行以下内存映射的操作:
清除原有的虚拟地址空间映射:在执行execve之前,原有的虚拟地址空间映射会被清除。
创建新的虚拟地址空间映射:根据新加载的可执行文件的特性和要求,操作系统会为hello进程创建新的虚拟地址空间映射。
加载可执行文件到内存:新的可执行文件的代码段、数据段等将被加载到进程的内存中。这些段会在虚拟地址空间中被映射到适当的位置。
建立堆和栈:在新的虚拟地址空间中,会为进程分配堆和栈空间。堆用于动态内存分配,栈用于函数调用和局部变量。
建立动态链接库映射:如果可执行文件依赖于动态链接库,操作系统会加载和映射这些库到进程的内存中。
7.8 缺页故障与缺页中断处理
(2)判断内存是否有空闲可用帧?若有,则获取一个帧号No,转(4) 启动I/O过程。若无,继续(3)
(3)腾出一个空闲帧,即:
(3)-1调用置换算法,选择一个淘汰页PTj。
(3)-2 PTj(S)=0 ; //驻留位置0
(3)-3 No= PTj (F); //取该页帧号
(3)-4 若该页曾修改过,则
(3)-4-1 请求外存交换区上一个空闲块B ;
(3)-4-2 PTj(D)=B ;//记录外存地址
(3)-4-3启动I/O管理程序,将该页写到外存上。
(4)按页表中提供的缺页外存位置,启动I/O,将缺页装入空闲帧No中。
(5)修改页表中该页的驻留位和内存地址。PTi(S)=1 ; PTi(F) =No。
(6)结束。
通过缺页中断处理,操作系统能够动态地将需要的页面从辅存加载到主存中,提供了虚拟内存的支持,并确保程序能够正常执行。-
7.9动态存储分配管理
动态内存管理的基本方法包括堆和栈分配。堆内存是由程序员手动申请和释放的,而栈内存是由编译器自动管理的。常见的动态内存分配策略有首次适应、最佳适应、最差适应和快速适应。
总的来说,动态内存管理的目标是高效地分配和释放内存,确保内存的合理利用,并避免内存泄漏问题。
7.10本章小结
本章介绍了hello程序的存储管理,包括各类地址空间的转换,段式管理和页式管理等,介绍了VA到PA的转换,物理内存访问。以及调用fork函数和execve函数时的内存映射,发生缺页故障后的处理方法和动态储存的分配管理方法。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
Linux内核将所有的IO设备都模型化为文件,这包括磁盘驱动器、网络接口、串口、键盘、显示器等等。这种设计思想被称为"一切皆文件"的概念。
通过将设备映射为文件,Linux内核提供了一种简单、统一的接口,称为Unix I/O。这个接口使得应用程序可以使用相同的操作方式来处理文件和设备。它包括以下基本操作:
打开文件(open):通过指定文件路径和打开模式(读、写、追加等),应用程序可以打开文件或设备。
改变当前文件位置(lseek):应用程序可以使用lseek函数在文件移动当前的读写位置,以便定位到需要读取或写入的位置。
读写文件(read和write):通过read函数从文件中读取数据或使用write函数向文件中写入数据。这些函数接受文件描述符和数据缓冲区作为参数。
关闭文件(close):应用程序使用close函数来关闭打开的文件或设备,释放相关的资源。
通过这种统一的接口,应用程序可以以一致的方式对待文件和设备,无需关心底层的物理细节。这种设计使得应用程序编写更加简单、灵活,并提供了高度的可移植性。
8.2 简述Unix IO接口及其函数
open():打开文件或设备,并返回文件描述符。它接受文件路径和打开模式作为参数,可以指定读、写、追加等操作。
close():关闭文件或设备,并释放相关资源。它接受文件描述符作为参数。
read():从文件中读取数据,并将数据存储到指定的缓冲区中。它接受文件描述符、缓冲区和读取字节数作为参数。
write():将数据写入文件或设备。它接受文件描述符、数据缓冲区和写入字节数作为参数。
lseek():改变文件的当前读写位置。它接受文件描述符、偏移量和起始位置作为参数,可以用于在文件中定位到指定位置。
fcntl():对文件描述符执行各种控制操作。例如,设置文件状态标志、非阻塞模式等。
ioctl():执行设备特定的控制操作。它接受文件描述符、控制命令和参数作为参数,用于与设备进行交互。
dup() 和 dup2():复制文件描述符。dup() 复制给定文件描述符,并返回新的文件描述符;dup2() 复制给定文件描述符到指定的文件描述符。
8.3 printf的实现分析
该函数接受一个格式化字符串 fmt 和变长参数 ...。它首先创建一个字符数组 buf 作为输出缓冲区。然后使用 vsprintf 函数,根据格式字符串和变长参数将格式化的输出存储到缓冲区 buf 中。接下来,使用 write 函数将缓冲区中的内容写入输出。最后,函数返回输出字符的总数。
在具体实现中, va_list 类型的 arg 变量用于处理变长参数。通过 va_start 宏初始化 arg,将其指向 fmt 参数之后的可变参数列表。在使用完变长参数后,使用 va_end 宏结束对 arg 的处理。
最终,通过调用适当的系统函数(如 int 0x80 或 syscall)来将格式化的字符串显示出来。字符显示驱动程序将处理字符转换为相应的字模,并将字模信息写入显示存储器中(vram),然后通过信号线将每个点的颜色信息传输到液晶显示器,按照刷新频率逐行读取并显示。从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
该函数用于从标准输入中获取一个字符。它维护了一个静态字符数组 buf 作为输入缓冲区,以及一个指向当前字符的指针 bb 和计数器 n。
首先,函数检查计数器 n 的值是否为0。如果为0,说明输入缓冲区为空,需要从标准输入中读取更多的字符。它使用 read 函数从文件描述符为0(标准输入)的输入流中读取字符,并将其存储到缓冲区 buf 中。然后,将指针 bb 指向缓冲区的起始位置,并更新计数器 n 为读取的字符数量。
接下来,函数返回下一个字符。它首先将计数器 n 减1,然后检查计数器是否大于等于0。如果是,则返回指针 bb 当前位置的字符,并将指针向后移动一位;否则,返回 EOF 表示已经到达输入的末尾。
异步异常处理程序(键盘中断处理子程序)将处理键盘中断,并将按键的扫描码转换为对应的 ASCII 码,并将其保存到系统的键盘缓冲区中。当调用 getchar 函数时,它会调用底层的 read 系统函数来进行系统调用,从键盘缓冲区中读取 ASCII 码。函数会一直读取,直到接收到回车键(表示输入结束),然后返回读取到的字符。
8.5本章小结
本章简要介绍了Linux中对IO设备进行管理的方法以及UnixIO函数的使用、参数含义和功能。
在Linux中,所有的IO设备都被模型化为文件,这使得IO设备的管理变得统一且一致。通过使用UnixIO函数,可以对这些IO设备进行读取和写入操作,就像对文件进行读写一样。
UnixIO函数提供了一套简单的、低级的应用接口,可以打开文件、改变当前文件位置、读取和写入文件数据,并且关闭文件。它们被广泛用于处理输入和输出操作,无论是与标准输入输出进行交互,还是与其他文件进行数据交换。
这些函数包括open、read、write、lseek、close等,它们接受不同的参数来指定文件名、文件描述符、读写位置、数据缓冲区等。通过调用这些函数,可以实现对文件和IO设备的读写操作。
(第8章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
编者在纸上舞动着灵巧的手指,代码的诗篇在编辑器中诞生,嗯,它就是那个名为hello.c的作品。
带着一种神秘的力量,预处理器轻抚着hello.c,将它转变成了一个新的存在,一个被命名为hello.i的幻梦。
而后,hello.i沉醉于编译器的拥抱,一次次地跳跃,舞动着优美的语汇,成为了hello.s,一首绚丽的汇编之歌。
在光影的映射下,hello.s穿越了时空的门槛,被汇编器迎入了一个神秘的领域,它化身为了hello.o,一个闪烁着二进制光芒的可重定向的艺术品。
然后,链接器登场了,它以巧妙的技法,将hello.o与其他代码纽带在一起,缔结成一个名为hello的壮丽交响曲。
在一个神奇的壳中,shell孕育了一个新生命,称为子进程。子进程踏入了execve的仪式,新的hello被呼唤而生。
在壳的翱翔中,hello与众多同行并驱,共舞于上下文的舞台。时而系统的调用,时而切换的节拍,使得他们的旋律交错变幻。
当hello渴望触摸内存的时候,他穿越了逻辑地址和物理地址的迷宫,终于来到了缓存、主存或者磁盘的宫殿中,寻找那些流转的数据之谜。
在旅途中,hello时而被来自不同设备或者进程的信号所触摸。当他接收到这些信号的时候,他呼唤信号处理程序,用心灵的手笔绘制出最美的应对策略。
在hello的内心深处,他温暖地呼唤着printf和getchar这两位奇迹的存在。他们借助write和read等咒语,通过系统调用的魔力,将文字的韵律转化为真实的存在。
最后,当hello的旅程终结,他的使命完成,shell温柔地拥抱着他,将他的身躯回归于虚无,hello的奇妙生命画上了完美的句点。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
列出所有的中间产物的文件名,并予以说明起作用。
hello.c hello源程序
hello.i hello.c预处理生成的文件
hello.s hello.i编译编后生成的文件
hello.o hello.s汇编后可重定位的目标程序
hello.or hello.o的反汇编文件
hello.elf hello.o的elf文件(生成了文件但是被覆盖,其源文件见上文报告)
hello hello.o链接后得到的可执行文件
hello.elf hello的elf文件
(附件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.
(参考文献0分,缺失 -1分)