HIT计算机系统大作业:程序人生——Hello‘s P2P

HIT计算机系统大作业:程序人生——Hello‘s P2P 题 目程序人生-Hello’s P2P专 业人工智能学 号2023130048学 生LIANG FRANCIS指 导 教 师刘松波摘 要本文将一个很短的 hello.c 当作主角追踪它从源文件到进程、再从进程退出到资源被回收的完整过程。表面上看程序只是在循环中打印命令行参数、睡眠并在最后等待一次输入但在系统层面它要经过预处理、编译、汇编、链接、装载、调度、虚拟内存映射、动态链接、标准 I/O 和信号处理等环节。本文围绕 Hello 的“P2P”Program to Process和“020”From Zero-0 to Zero-0生命周期展开并结合本机生成的 hello.i、hello.s、hello.o、hello以及 objdump、readelf 等工具的分析结果说明程序从源文件生成可执行文件、成为进程并最终终止回收的完整过程。本文的重点是解释那些命令背后的系统行为为什么 argc 必须等于 5为什么 printf 在反汇编中变成 __printf_chkplt为什么 hello.o 里还保留重定位项为什么 fork 后还要 execve以及为什么一个普通的 getchar() 会让进程停在终端输入上。通过这个小程序可以看到 CSAPP 中抽象层次的连接方式C 语言语句落到寄存器和栈帧目标文件落到 ELF 段和符号表进程落到页表、TLB、Cache 和 Unix I/O。Hello 很短但它走过的路并不短。关键词HelloP2P020ELF进程虚拟内存Unix I/O第1章 概述1.1 Hello简介​图1-1 Hello 的 P2P 与020生命周期​1Hello 的 P2PHello 的 P2P 指的是从 Program 到 Process 的过程。最开始Hello 只是磁盘上的一个 C 源文件 hello.c它没有地址空间、没有寄存器上下文也不会占用 CPU。经过编译系统翻译后它变成可执行目标文件 hello。当用户在 Bash 中输入类似./hello 2023130048 LIANG FRANCIS 18845159061 1Shell 会创建子进程并在子进程中执行 execve 装入 hello。从这一刻开始Hello 才真正成为一个进程它拥有自己的虚拟地址空间、页表、用户栈、打开文件描述符和内核维护的进程控制块。随后它被调度运行执行 main在循环里打印十次最后等待 getchar()。当程序返回或被信号结束时内核回收进程资源Shell 继续等待下一条命令。2Hello 的链接过程Hello 从可重定位目标文件 hello.o 生成可执行目标文件 hello需要经过链接过程。hello.o 已经包含机器指令但外部符号 puts、exit、__printf_chk、strtol、sleep、getc 和 stdin 等仍未解析.text 中也保留着重定位信息。链接器将 hello.o、C 运行时启动文件和动态链接信息组合起来完成符号解析与重定位最终生成可由内核装载的 ELF 可执行文件 hello。本机生成的 hello 入口地址为 0x4010f0main 地址为 0x4011d6。​图1-2 从 hello.c 到 hello 的四步翻译​3Hello 的 020常把 Hello 的一生概括为 “020”从 0 开始到 0 结束。开始的 0 是磁盘上没有运行生命的文本文件中间的 2 可以理解为两条主线翻译线和运行线最后的 0 是进程终止后用户态地址空间、内核对象和打开文件引用被释放Hello 又只剩下磁盘上的文件。这个说法有些俏皮但它提醒我们程序不是天生就在 CPU 上运行的进程也不是永久存在的。1.2 环境与工具课程计算机系统基础题目程序人生-Hellos P2P作者信息课程作业公开版个人信息已省略运行参数中的秒数为手机号模 5 的结果本次实验取 1。实验环境Ubuntu 22.04x86-64GCC 11.4.0​图1-3 GCC 环境与 hello.c 源程序截图​源程序hello.c本文主要使用的开发、分析与调试工具包括 gcc、readelf、objdump、gdb、bash、ps、jobs、pstree、fg、kill 等。1.3 中间结果为分析 Hello 的完整生命周期本文使用了以下中间文件​图1-4 hello 相关中间文件与文件类型截图​build/hello.i、build/hello.s、build/hello.o、build/hello、build/hello_o_disasm.txt、build/hello_disasm.txt、build/hello_o_readelf.txt、build/hello_readelf.txt、figures/*.svg1.4 本章小结本章确定了 Hello 的两条分析主线P2P 说明程序如何成为运行中的进程020说明 Hello 从磁盘文件开始经过生成、装载、执行和终止最终回到非运行状态。后续章节将结合实际生成的文件和工具输出分析其完整生命周期。第2章 预处理2.1 预处理的概念与作用预处理是编译系统处理源文件的第一步。它主要完成头文件展开、宏替换、条件编译处理和注释删除。hello.c 中包含了三个头文件#include stdio.h#include unistd.h#include stdlib.h这些头文件提供了 printf、getchar、sleep、atoi、exit 等函数声明。预处理之后源文件会膨胀成一个更大的文本文件 hello.i里面除了原始 main 函数还包含大量系统头文件声明和类型定义。2.2 在 Ubuntu 下预处理的命令本作业使用的命令为gcc -m64 -Og -no-pie -fno-stack-protector -fno-PIC -E hello.c -o build/hello.i得到的 hello.i 大小约为 117817 字节远大于原始 hello.c。这说明预处理并没有生成机器代码而是生成了供后续编译器分析的完整 C 文本。2.3 Hello 的预处理结果解析在 hello.i 中原程序的主体仍然保留int main(int argc,char *argv[]){int i;if(argc!5){printf(用法: Hello 学号 姓名 手机号 秒数\n);exit(1);}for(i0;i10;i){printf(Hello %s %s %s\n,argv[1],argv[2],argv[3]);sleep(atoi(argv[4]));}getchar();return 0;}但函数声明已经来自头文件。例如 stdio.h 让编译器知道 printf 和 getchar 的参数与返回值stdlib.h 给出 atoi、exit 的声明unistd.h 给出 sleep 的声明。没有这些声明编译器仍可能勉强生成代码但类型检查和调用约定都会变得不可靠。2.4 本章小结预处理阶段没有改变 Hello 的核心逻辑而是把依赖头文件的源程序展开为独立的编译输入。hello.i 仍是 C 文本但已包含系统库接口信息。第3章 编译3.1 编译的概念与作用编译阶段把预处理后的 C 程序翻译成汇编程序。这里的关键工作包括语法分析、语义分析、控制流和数据流分析、寄存器分配、指令选择等。对 Hello 来说编译器需要把 if、for、函数调用、数组访问 argv[i]、局部变量 i 等 C 语言结构改写成 x86-64 指令和寄存器操作。3.2 在 Ubuntu 下编译的命令命令为gcc -m64 -Og -no-pie -fno-stack-protector -fno-PIC -S hello.c -o build/hello.s其中 -Og 保留一定优化但便于调试-no-pie 和 -fno-PIC 让最终地址更稳定便于在反汇编中观察固定地址。3.3 Hello 的编译结果解析hello.s 中 main 的开头如下main:endbr64pushq %rbppushq %rbxsubq $8, %rspcmpl $5, %edijne .L6movq %rsi, %rbxmovl $0, %ebpjmp .L2按照 x86-64 System V 调用约定main 的第一个参数 argc 位于 %edi第二个参数 argv 位于 %rsi。cmpl $5, %edi 对应 if(argc!5)不等则跳转到错误处理分支 .L6。编译器把 argv 保存到被调用者保存寄存器 %rbx把循环变量 i 放到 %ebp这是 -Og 下比较容易读懂的一种安排。打印循环的关键指令为.L3:movq 16(%rbx), %rcxmovq 8(%rbx), %rdxmovq 24(%rbx), %r8movl $.LC1, %esimovl $1, %edimovl $0, %eaxcall __printf_chkmovq 32(%rbx), %rdimovl $10, %edxmovl $0, %esicall strtolmovl %eax, %edicall sleepaddl $1, %ebp这里 8(%rbx)、16(%rbx)、24(%rbx)、32(%rbx) 分别取 argv[1] 到 argv[4]。由于 argv 是指针数组每个元素在 64 位机器上占 8 字节。源程序写的是 atoi(argv[4])汇编中出现了 strtol这是系统头文件和编译选项共同作用后的结果atoi 可以被实现为对 strtol 的包装或被优化替换。printf 也表现为 __printf_chk这与 glibc 的 _FORTIFY_SOURCE 相关是带检查的 printf 入口。循环判断为.L2:cmpl $9, %ebpjle .L3源程序写 i 10汇编中等价为 i 9。最后movq stdin(%rip), %rdicall getc对应 getchar()。getchar 本质上从标准输入流读取一个字符因此程序打印十次以后不会马上退出而是等待终端输入。综合来看源程序中的主要C语言操作都能在汇编中找到对应实现argc ! 5由比较和条件跳转完成i 0和i分别对应寄存器赋值与加法指令argv[i]通过基址加8字节倍数偏移进行寻址函数参数主要通过%rdi、%rsi、%rdx、%rcx、%r8等寄存器传递函数返回值通常保存在%eax中。由此可以看到C语言中的变量、数组、关系运算、循环和函数调用最终都被转换为具体的寄存器操作、访存指令和控制转移指令。3.4 本章小结编译阶段是抽象下降最明显的一步。C 代码中的参数、循环和库函数调用变成了寄存器、栈、跳转和 call 指令。Hello 的控制逻辑很短但它已经足以展示调用约定、数组寻址、循环翻译和库函数替换。第4章 汇编4.1 汇编的概念与作用汇编器把 hello.s 翻译成可重定位目标文件 hello.o。这个文件已经包含二进制机器指令但其中的符号地址还没有完全确定。也就是说hello.o 知道自己需要调用 sleep、getc 等函数却不知道这些函数在最终进程中的地址。4.2 在 Ubuntu 下汇编的命令命令为gcc -m64 -Og -no-pie -fno-stack-protector -fno-PIC -c hello.c -o build/hello.o本机 size build/hello.o 的结果为text data bss dec hex filename295 0 0 295 127 build/hello.o4.3 可重定位目标 ELF 格式readelf -a build/hello.o 显示hello.o 中有 .text、.rela.text、.rodata.str1.8、.rodata.str1.1、.symtab 等节。.text 保存机器指令.rodata 保存字符串常量.symtab 保存符号表.rela.text 保存需要链接器修正的位置。符号表中可以看到main FUNC GLOBAL DEFAULT .textputs UNDexit UND__printf_chk UNDstrtol UNDsleep UNDstdin UNDgetc UNDmain 已经由本目标文件定义而其他库函数和对象仍是未定义符号 UND等待链接阶段解析。4.4 Hello.o 的结果解析readelf 给出的 .rela.text 关键项包括R_X86_64_PLT32 puts - 4R_X86_64_PLT32 exit - 4R_X86_64_PLT32 __printf_chk - 4R_X86_64_PLT32 strtol - 4R_X86_64_PLT32 sleep - 4R_X86_64_PC32 stdin - 4R_X86_64_PLT32 getc - 4这些条目解释了为什么 hello.o 还不能直接运行。对于函数调用汇编器先留下相对位移的“空位”并记录需要链接器以后填补。对于 stdin 这样的数据对象也需要在最终装载布局确定后修正引用。​图4-1 hello.o 反汇编与重定位信息截图​4.5 本章小结汇编阶段把 Hello 从文本变成了二进制但这个二进制还带着“欠条”外部函数和全局对象的地址需要链接器解决。目标文件的节、符号表和重定位表正是链接器完成符号解析和重定位的依据。第5章 链接5.1 链接的概念与作用链接把多个目标文件和库组合成一个可执行目标文件。对 Hello 来说链接器不仅要处理 hello.o还要加入 C 运行时启动代码并为动态链接准备 .plt、.got、动态符号表、重定位表等结构。最终生成的 hello 是一个 ELF 可执行文件可以被内核装载。5.2 在 Ubuntu 下链接的命令命令为gcc -m64 -Og -no-pie -fno-stack-protector -fno-PIC hello.c -o build/hello本机结果text data bss dec hex filename1755 584 16 2355 933 build/hellohello 文件大小为 16192 字节比 hello.o 大得多因为它包含 ELF 头、程序头、动态链接信息、PLT/GOT、启动代码等内容。5.3 可执行目标文件 hello 的格式readelf -a build/hello 显示Entry point address: 0x4010f0.text 0x4010f0.rodata 0x402000main 0x4011d6stdin 0x404060入口地址不是 main而是 _start。这是因为 Linux 进程启动时先进入 C 运行时入口 _start它负责整理参数和运行环境再调用 __libc_start_main最后间接调用用户写的 main。​图5-1 hello 可执行文件 ELF 头和程序头截图​5.4 hello 的虚拟地址空间hello 使用 -no-pie 编译因此代码段地址比较稳定。反汇编中 main 位于 0x4011d6格式串地址位于 .rodata 附近例如 0x402038。这使我们能直接把机器指令与 ELF 节对应起来40120f: be 38 20 40 00 mov $0x402038,%esi40121e: e8 8d fe ff ff call 4010b0 __printf_chkplt第一条指令把格式串地址送入参数寄存器第二条通过 PLT 调用 glibc 的 __printf_chk。5.5 链接的重定位过程分析hello.o 中的 call 目标还没有确定依赖重定位项。链接后的 hello 中调用已经变成具体 PLT 地址4011f4 call 401090 putsplt4011fe call 4010c0 exitplt40121e call 4010b0 __printf_chkplt401231 call 4010a0 strtolplt401238 call 4010d0 sleepplt40124c call 4010e0 getcpltPLT 像一层跳板。第一次调用库函数时动态链接器可能解析真实地址并写入 GOT之后同一函数调用就可以通过 GOT 更快跳到实际实现。5.6 hello 的执行流程_start 的任务不是完成业务逻辑而是把进程带到适合 C 程序运行的状态。它准备参数、对齐栈、调用 __libc_start_main。main 返回后C 运行时还会处理退出流程最终进入系统调用让内核结束进程。5.7 Hello 的动态链接分析Hello 使用动态链接方式调用 glibc 中的库函数。从反汇编结果可以看到程序调用的是 putsplt、__printf_chkplt、strtolplt、sleepplt 和 getcplt而不是直接跳转到共享库中的真实函数地址。这说明可执行文件通过PLT和GOT完成库函数调用。PLT表项首先根据GOT中保存的地址进行间接跳转在启用延迟绑定时函数首次调用会进入动态链接器由动态链接器在共享库中查找真实地址并更新对应的GOT表项。以后再次调用该函数时程序便可以通过PLT和GOT直接跳转到已经解析的函数地址。该机制避免了把完整库函数复制到可执行文件中同时使不同进程能够共享只读的库代码页。5.8 本章小结链接阶段将 hello.o 转换为可装载、可运行的 hello。从反汇编可以看到外部函数调用都被导向 PLT程序入口由 main 前移到 _startELF 文件也从简单的可重定位目标文件扩展为包含装载和动态链接信息的完整可执行文件。第6章 hello 进程管理6.1 进程的概念进程是正在运行的程序实例。它不仅包含代码和数据还包含寄存器上下文、虚拟地址空间、打开文件、信号状态、调度信息等。程序是静态文件进程是动态活动。Hello 从磁盘文件变成进程依赖 Shell 和内核共同完成。6.2 Shell-bash 的作用Bash 读取用户输入解析命令行参数定位可执行文件并决定如何创建新进程。对于前台命令./hello 2023130048 LIANG FRANCIS 18845159061 1Bash 会调用 fork 创建子进程。子进程起初几乎复制父进程的上下文但它随后调用 execve用 hello 的程序映像替换自己的地址空间。父进程 Bash 则等待前台作业结束或状态改变。6.3 Hello 的 forkfork 后会出现父子两个执行流。Linux 使用写时复制减少开销父子进程最初共享物理页只要没有写入就不必立刻复制整片内存。对 Hello 来说fork 只是成为独立进程的起点真正让它运行自身代码的是后面的 execve。6.4 Hello 的 execveexecve 接收可执行文件路径、参数数组和环境变量数组。内核检查 ELF 头建立新的虚拟地址空间把代码段、只读数据段、数据段、堆和栈映射好并把 argc、argv、环境变量放到用户栈上。随后控制权转到 ELF 入口 _start。6.5 Hello 的进程执行Hello 开始运行后先判断 argc 是否等于 5。若参数不正确打印用法并 exit(1)。若参数正确进入十次循环1. 从 argv[1]、argv[2]、argv[3] 取出学号、姓名、手机号。2. 调用 printf 输出一行。3. 把 argv[4] 转为整数。4. 调用 sleep 主动让出 CPU直到定时器唤醒。这段逻辑使 Hello 在运行中频繁经历“运行态 - 阻塞态 - 就绪态 - 运行态”的切换。实验中将秒数设为 1 后Hello 每打印一行就调用 sleep(1)因此在终端中可以稳定观察到它处于运行态、阻塞态和就绪态之间切换。用 ps 查看时hello 是 bash 创建的前台作业用 pstree 可以看到 bash 与 hello 的父子关系这对应 Shell 先 fork、子进程再 execve 的过程。按 Ctrl-Z 时终端驱动向前台进程组发送 SIGTSTPhello 不会正常退出而是进入停止状态jobs 会显示该作业已停止执行 fg 后bash 把该作业重新放回前台并发送 SIGCONT使 hello 继续运行。若按 Ctrl-C则发送 SIGINT默认动作是终止前台进程。若使用 kill -SIGTERM PID本质上是由另一个进程显式向 hello 发送终止信号。​图6-1 hello 运行、进程树与 Ctrl-Z 停止状态截图​6.6 hello 的异常与信号信号为操作系统提供了一种异步控制进程的方法。按下Ctrl-Z时终端向前台进程组发送SIGTSTP使hello进入停止状态但进程并未终止执行fg后Bash将作业重新放回前台并发送SIGCONT使其继续运行。按下Ctrl-C时终端发送SIGINT其默认动作是终止前台进程。通过kill -SIGTERM PID也可以由其他进程向hello发送终止信号。以上过程说明进程的暂停、恢复和终止不仅由程序自身控制也可能由内核和外部信号改变。6.7 本章小结Hello 成为进程不是简单地“运行文件”。Bash、fork、execve、内核装载器、调度器和信号机制一起参与了它的生命过程。sleep 和 getchar 让 Hello 的状态变化更容易观察因此它很适合作为进程管理实验对象。第7章 hello 的存储管理7.1 hello 的存储器地址空间Hello 运行时看到的是虚拟地址空间。对进程来说0x4011d6 这样的地址像是真实可用的地址但硬件和操作系统会把它翻译到物理内存。这样每个进程都能拥有独立、连续、受保护的地址视图。结合 readelf -l build/hello 的结果hello 的可装载段可以更具体地对应到虚拟地址空间只读头部和动态链接信息从 0x400000 附近开始可执行代码段 LOAD 映射到 0x401000包含 .init、.plt、.text 和 .fini只读数据段 LOAD 映射到 0x402000包含 .rodata 和异常处理表可读写段 LOAD 映射到 0x403e10 附近包含 .dynamic、.got、.data 和 .bss。这些地址说明进程看到的 0x4011d6、0x402000、0x404060 不是随意数字而是链接器按照 ELF 段布局组织出的虚拟地址。execve 时内核并不是把整个文件简单复制到内存而是按程序头建立带权限的映射代码页可读可执行常量页只读数据页可读写栈由内核另行创建。​图7-1 hello 进程的典型虚拟地址空间​7.2 Intel 逻辑地址到线性地址在现代 x86-64 Linux 用户程序中分段机制基本被弱化用户态常用的代码段、数据段基址近似为 0。因此逻辑地址到线性地址这一步通常不改变地址值。课程里仍然讨论它是因为它解释了 x86 体系结构从分段到分页的历史层次。7.3 Hello 的线性地址到物理地址Hello使用的线性地址需要经过分页机制转换为物理地址。在常见的4 KiB页面配置下线性地址的低12位作为页内偏移其余高位用于索引多级页表。处理器首先查询TLB若TLB命中可以直接获得物理页号并与页内偏移组合成物理地址。若TLB未命中则由硬件按照多级页表逐级查找页表项。页表项除保存物理页号外还记录存在位、读写权限、用户权限、访问位和脏位等状态信息。7.4 TLB 与多级页表TLB是页表项的高速缓存用于减少地址翻译过程中访问内存的次数。在常见的x86-64四级页表配置中处理器依次查询页全局目录、页上级目录、页中间目录和页表最终得到物理页号。如果页表项表明页面尚未装入内存则产生缺页异常由内核分配物理页或从可执行文件、共享库及交换区域中装入相应内容。完成地址翻译后处理器再依次查询L1、L2、L3 Cache未命中时才访问主存。7.5 Cache 对 Hello 的影响Cache 缓存近期指令和数据。Hello 循环体短argv、格式串和栈帧会被反复访问具有较好局部性sleep 后重新调度时缓存内容可能被替换但只影响性能不影响正确性。7.6 hello fork 时的内存映射fork 后父子进程通过写时复制共享物理页只在写入时复制数据页。Hello 随后立即 execve因此避免了复制 Bash 地址空间的开销。7.7 hello execve 时的内存映射execve 丢弃子进程原地址空间并依据 hello 的 ELF 程序头重新映射代码段、只读数据段和可写数据段同时创建用户栈并映射动态链接器与 libc。7.8 缺页异常与动态分配Hello 未显式调用 malloc但 glibc 的 I/O 缓冲、动态链接和运行时初始化可能分配内存。首次访问尚未装入的代码页或数据页时会触发缺页异常内核处理后继续执行。7.9 printf 的存储访问printf 读取 .rodata 中的格式串和用户栈上 argv 指向的字符串必要时使用 glibc 用户态缓冲最终通过 write 等系统调用进入内核。7.10 本章小结Hello 的内存行为体现了虚拟内存的隔离、保护、共享和按需分配。短程序同样会经过 TLB、页表、Cache、缺页异常和共享库映射。第8章 hello 的 I/O 管理8.1 Linux 的 I/O 设备管理方法Linux 把设备、管道、终端、普通文件等都抽象为文件进程通过文件描述符访问它们。Hello 默认拥有三个文件描述符标准输入 0、标准输出 1、标准错误 2。printf 主要使用 stdoutgetchar 主要使用 stdin。​图8-1 printf 与 getchar 的 I/O 路径​8.2 简述 Unix I/O 接口及其函数底层 I/O 通过 read、write、open、close 等系统调用完成。用户态函数不能直接操作终端设备必须通过系统调用进入内核。系统调用会切换到内核态内核根据文件描述符找到对应文件对象和设备驱动再完成读写。8.3 printf 的实现分析源程序调用printf(Hello %s %s %s\n, argv[1], argv[2], argv[3]);在本机汇编中对应 __printf_chk。它首先解析格式串并将三个字符串参数组织成输出字节序列。当标准输出连接终端时stdout 通常采用行缓冲方式因此格式串末尾的换行符会促使缓冲区刷新。glibc 随后通过 write 等系统调用把输出内容写入文件描述符1内核再将数据交给终端驱动并显示在屏幕上。8.4 getchar 的实现分析getchar() 从标准输入读取一个字符。若输入缓冲为空进程会阻塞等待终端输入。用户按下按键并回车后终端驱动把字符交给内核read 返回glibc 再把字符作为 getchar 的返回值传回用户程序。Hello 没有使用这个返回值只是借它在结束前停一下。8.5 本章小结I/O 表面是两行库函数底层则是标准流、缓冲区、文件描述符、系统调用和终端设备协作。printf 展示了从格式化到 write 的输出路径getchar 展示了从键盘到 read 的输入路径。结论Hello 的代码只有十几行但它完整走过了一个程序在计算机系统中的典型人生。预处理让它获得头文件声明编译把 C 语言结构降到汇编汇编生成带符号和重定位信息的目标文件链接把这些未决关系整理成可执行 ELF。运行时Shell 通过 fork 和 execve 让它成为进程内核为它建立地址空间并调度执行动态链接器把库函数接上虚拟内存系统为每次取指和访存提供地址翻译Unix I/O 把终端输入输出统一成文件读写。这次分析最有价值的地方是把课本上分散的概念放回同一个程序里看。argc 检查对应寄存器 %ediargv 访问对应 8(%rbx) 这样的寻址printf 对应 PLT 和 glibcsleep 对应进程阻塞getchar 对应标准输入和终端驱动。Hello 不是复杂程序但它经过的系统层次很完整。理解它就能更踏实地理解“程序为什么能运行”这个问题。附录hello.cbuild/hello.ibuild/hello.sbuild/hello.obuild/hellohello_o_disasm.txthello_disasm.txthello_o_readelf.txthello_readelf.txt参考文献[1] 兰德尔·E·布莱恩特, 大卫·R·奥哈拉伦. 深入理解计算机系统第三版[M]. 北京: 机械工业出版社, 2016.[2] Florian. printf背后的故事[EB/OL]. 博客园, 2014.printf背后的故事 - Florian - 博客园[3] 沐浴凌风. Linux链接详解——动态链接库[EB/OL]. 博客园, 2017.Linux 链接详解----动态链接库 - 沐浴凌风 - 博客园[4] m0_74985290. 操作系统中的设备管理Linux下的I/O[EB/OL]. CSDN博客.操作系统中的设备管理Linux下的I/O_linux字符设备和io设备的关联-CSDN博客[5] 刁海威. linux2.6内存管理——逻辑地址转换为线性地址[EB/OL]. 博客园, 2016.linux2.6 内存管理——逻辑地址转换为线性地址逻辑地址、线性地址、物理地址、虚拟地址 - 刁海威 - 博客园