哈尔滨工业大学计算机系统大作业程序人生-Hello’s P2P

摘 要

文章以C语言程序设计经典案例hello.c为研究对象,系统解析程序在计算机系统中的完整生命周期。剖析源代码通过预处理、编译、汇编、链接四阶段演化为可执行目标程序的编译系统工作机制,继而从进程视角揭示程序运行时计算机体系结构的协同运作:处理器执行指令流、存储器层次架构支撑数据存取,操作系统通过进程管理实现资源调度与状态维护。通过深度追踪hello.c从静态代码到动态进程的转化路径,构建对程序存储、加载、执行等计算机系统核心机制的完整认知框架。

在整个实验中使用泰山服务器进行。

关键词:预处理;编译;汇编;链接;进程;存储;计算机系统

目 录

[++++第1章 概述++++](#第1章 概述)

[++++1.1 Hello简介++++](#1.1 Hello简介)

[++++1.2 环境与工具++++](#1.2 环境与工具)

[++++1.3 中间结果++++](#1.3 中间结果)

[++++1.4 本章小结++++](#1.4 本章小结)

[++++第2章 预处理++++](#第2章 预处理)

[++++2.1 预处理的概念与作用++++](#2.1 预处理的概念与作用)

++++2.2在Ubuntu下预处理的命令++++

[++++2.3 Hello的预处理结果解析++++](#2.3 Hello的预处理结果解析)

[++++2.4 本章小结++++](#2.4 本章小结)

[++++第3章 编译++++](#第3章 编译)

[++++3.1 编译的概念与作用++++](#3.1 编译的概念与作用)

[++++3.2 在Ubuntu下编译的命令++++](#3.2 在Ubuntu下编译的命令)

[++++3.3 Hello的编译结果解析++++](#3.3 Hello的编译结果解析)

[++++3.4 本章小结++++](#3.4 本章小结)

[++++第4章 汇编++++](#第4章 汇编)

[++++4.1 汇编的概念与作用++++](#4.1 汇编的概念与作用)

[++++4.2 在Ubuntu下汇编的命令++++](#4.2 在Ubuntu下汇编的命令)

[++++4.3 可重定位目标elf格式++++](#4.3 可重定位目标elf格式)

[++++4.4 Hello.o的结果解析++++](#4.4 Hello.o的结果解析)

[++++4.5 本章小结++++](#4.5 本章小结)

[++++第5章 链接++++](#第5章 链接)

[++++5.1 链接的概念与作用++++](#5.1 链接的概念与作用)

[++++5.2 在Ubuntu下链接的命令++++](#5.2 在Ubuntu下链接的命令)

[++++5.3 可执行目标文件hello的格式++++](#5.3 可执行目标文件hello的格式)

[++++5.4 hello的虚拟地址空间++++](#5.4 hello的虚拟地址空间)

[++++5.5 链接的重定位过程分析++++](#5.5 链接的重定位过程分析)

[++++5.6 hello的执行流程++++](#5.6 hello的执行流程)

[++++5.7 Hello的动态链接分析++++](#5.7 Hello的动态链接分析)

[++++5.8 本章小结++++](#5.8 本章小结)

[++++第6章 hello进程管理++++](#第6章 hello进程管理)

[++++6.1 进程的概念与作用++++](#6.1 进程的概念与作用)

[++++6.2 简述壳Shell-bash的作用与处理流程++++](#6.2 简述壳Shell-bash的作用与处理流程)

[++++6.3 Hello的fork进程创建过程++++](#6.3 Hello的fork进程创建过程)

[++++6.4 Hello的execve过程++++](#6.4 Hello的execve过程)

[++++6.5 Hello的进程执行++++](#6.5 Hello的进程执行)

[++++6.6 hello的异常与信号处理++++](#6.6 hello的异常与信号处理)

++++6.7本章小结++++

[++++第7章 hello的存储管理++++](#第7章 hello的存储管理)

[++++7.1 hello的存储器地址空间++++](#7.1 hello的存储器地址空间)

[++++7.2 Intel逻辑地址到线性地址的变换-段式管理++++](#7.2 Intel逻辑地址到线性地址的变换-段式管理)

[++++7.3 Hello的线性地址到物理地址的变换-页式管理++++](#7.3 Hello的线性地址到物理地址的变换-页式管理)

[++++7.4 TLB与四级页表支持下的VA到PA的变换++++](#7.4 TLB与四级页表支持下的VA到PA的变换)

[++++7.5 三级Cache支持下的物理内存访问++++](#7.5 三级Cache支持下的物理内存访问)

[++++7.6 hello进程fork时的内存映射++++](#7.6 hello进程fork时的内存映射)

[++++7.7 hello进程execve时的内存映射++++](#7.7 hello进程execve时的内存映射)

[++++7.8 缺页故障与缺页中断处理++++](#7.8 缺页故障与缺页中断处理)

++++7.9动态存储分配管理++++

++++7.10本章小结++++

[++++第8章 hello的IO管理++++](#第8章 hello的IO管理)

[++++8.1 Linux的IO设备管理方法++++](#8.1 Linux的IO设备管理方法)

[++++8.2 简述Unix IO接口及其函数++++](#8.2 简述Unix IO接口及其函数)

[++++8.3 printf的实现分析++++](#8.3 printf的实现分析)

[++++8.4 getchar的实现分析++++](#8.4 getchar的实现分析)

++++8.5本章小结++++

++++结论++++

++++附件++++

++++参考文献++++

第1章 概述

1.1 Hello简介

(1)P2P过程

代码阶段:hello.c作为文本文件(高级语言程序),通过预处理(处理#include等指令)、编译(转为汇编代码hello.s)、汇编(生成机器码hello.o)、链接(合并库函数生成可执行文件hello),最终成为磁盘上的二进制程序。

进程阶段;

用户在Shell中输入./hello后,操作系统通过fork()创建子进程,execve()加载hello到内存,CPU开始执行指令:

存储管理:MMU将虚拟地址(VA)转为物理地址(PA),TLB和页表加速映射。

CPU调度:进程在时间片内运行(取指、译码、执行),可能因中断或时间片耗尽切换。

I/O交互:printf通过系统调用write将字符串输出到屏幕,getchar等待键盘输入。

(2)020过程

Zero→运行:

进程从"无"到被OS创建,分配资源(内存、文件描述符等),进入就绪队列。

运行→Zero:

正常终止:main返回后,OS回收内存、关闭文件,父进程(如Shell)通过wait获取退出状态。

异常终止:用户按Ctrl-C(SIGINT)或Ctrl-Z(SIGTSTP)时,OS发送信号强制结束进程,清理资源。

1.2 环境与工具

硬件环境:x64 CPU;3.20GHz;16G RAM

软件环境:Windows 11 64位;MobaXterm

开发与调试工具:gcc,objdump,readelf

1.3 中间结果

原始代码hello.c

预处理后的代码hello.i

编译后的汇编语言代码hello.s

可重定位目标文件hello.o

可执行文件hello

1.4 本章小结

本章主要介绍了hello.c程序P2P,020的过程。列出了本次实验所需的环境和工具以及过程中所生成的中间结果。

第2章 预处理

2.1 预处理的概念与作用

预处理的概念:是在数据正式处理或分析前进行的预备性操作,旨在提升数据质量、统一格式或提取关键信息,为后续步骤提供更高效的输入。

预处理的作用:缩短计算时间、提升模型精度、增强数据可靠性,是机器学习和数据分析中不可或缺的环节。例如,图像预处理(去噪、裁剪)可提高AI识别准确率,文本预处理(分词、去停用词)能优化自然语言处理效果。

2.2在Ubuntu下预处理的命令

图2.1预处理命令展示

2.3 Hello的预处理结果解析

图2.2hello.i文件部分展示

(1)头文件包含展开:

#include <stdio.h> 会被替换为stdio.h头文件的内容

#include <unistd.h> 会被替换为unistd.h头文件的内容

#include <stdlib.h> 会被替换为stdlib.h头文件的内容

图2.3头文件预处理部分截图

(2)注释处理:原始代码中的注释大作业的 hello.c 程序等会被移除

图2.4移除注释后的原程序

(3)头文件内容:

stdio.h会提供printf、getchar等函数的声明

unistd.h会提供sleep函数的声明

stdlib.h会提供exit、atoi等函数的声明

(4)预处理指令消失:所有的#include、#define等预处理指令都会被处理掉,不会出现在预处理后的代码中

2.4 本章小结

预处理是在数据正式处理或分析前进行的预备性操作,旨在提升数据质量、统一格式或提取关键信息,为后续步骤提供更高效的输入。

在linux环境下使用 gcc -E 命令对源代码进行预处理,生成预处理后的代码(.i 文件)。

对C语言程序进行预处理中头文件包含展开、注释处理、预处理指令消失生成更干净的代码供编译器使用。

第3章 编译

3.1 编译的概念与作用

注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序

编译的概念:

编译是指将预处理后的源代码(.i 文件,即经过宏展开、头文件包含、去注释等处理后的代码)转换为汇编代码(.s 文件)的过程。

输入预处理后的 C 代码(.i 文件)输出:汇编语言代码(.s 文件)

编译的作用:

将高级的C语言转换为低级语言更接近机器指令,优化代码结构,为后续汇编阶段做准备

3.2 在Ubuntu下编译的命令

图3.1编译的命令

3.3 Hello的编译结果解析

图3.2hello.s文件展示

3.3.1 基本数据类型处理

整数类型局部变量 i:

图3.3基本数据类型处理

int 为 32 位,使用 w 系列寄存器(w0~w30)。

存储在栈偏移量 44 处(sp 为栈指针)。

指针类型参数访问 argv[1]:

图3.4基本数据类型处理

3.3.2控制结构处理

条件分支(if(argc!=5))

图3.5条件分支处理

cmp 指令比较后,beq(branch if equal)实现条件跳转。

循环 (for(i=0;i<10;i++))

图3.6循环处理

通过 ble(branch if less or equal)和标签实现循环逻辑。

3.3.3 函数调用处理

printf 调用

图3.7printf调用

前 8 个参数通过 x0~x7 传递,printf 的格式字符串地址在 x0,后续参数依次为 x1~x3。

adrp 和 add 组合用于加载全局地址(位置无关代码)。

sleep 和 atoi 调用

图片同图3.7

atoi 返回的 int 存放在 w0,直接作为 sleep 的参数。

3.3.4 内存访问与栈管理

栈帧分配

图3.8栈帧分配

stp(store pair)指令保存 x29(帧指针)和 x30(返回地址),同时调整栈指针 sp。

栈空间用于存储局部变量(如 i 在 [sp, 44])和参数。

参数存储 (argc, argv)

图3.9参数存储的处理

函数入口参数 argc 和 argv 通过 w0 和 x1 传入,需保存到栈。

3.3.5 其他操作

字符串常量处理

图3.10字符串常量处理

系统调用 (getchar)

图3.11系统调用

getchar 的返回值(输入字符的 ASCII 码)通过 w0 返回。

3.4 本章小结

本章详细分析了从预处理后的 .i 文件到汇编代码 .s 的编译过程,重点解析了 hello.c 程序在 ARM64 架构下的编译结果 hello.s。通过分析汇编代码,可以清晰地看到编译器如何将高级 C 语言转换为低级的机器指令表示。

第4章 汇编

4.1 汇编的概念与作用

注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。

汇编的概念:

汇编是将汇编语言(.s 文件)转换为机器语言(二进制 .o 文件)的过程。在这一阶段,汇编器(如 as)逐行解析汇编代码,将其中的指令(如 mov、add)、标签和伪指令(如 .section)翻译为对应的机器码,并生成目标文件。目标文件包含可被计算机直接执行的二进制指令,同时保留符号表、重定位信息等元数据,以便后续链接器处理。这一过程是编译流程中连接高级语言与机器硬件的关键环节,直接生成处理器可识别的低级代码。

汇编的作用:

汇编的核心作用是将人类可读的汇编指令转换为机器可执行的二进制指令,为程序运行提供底层支持。它通过解析符号、地址和指令,生成与特定硬件架构(如 x86、ARM)匹配的目标文件,确保代码能直接在 CPU 上运行。同时,汇编阶段会处理代码分段(如 .text、.data)、解决局部符号引用,并为链接器预留重定位信息,使得多模块程序能正确合并。最终,汇编输出的目标文件是生成可执行程序或库的基础,直接影响程序的性能和硬件兼容性。

4.2 在Ubuntu下汇编的命令

图4.1汇编的命令

4.3 可重定位目标elf格式

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

图4.2 ELF 文件头

关键信息:文件类型(REL 表示可重定位目标文件)、架构(AArch64)。

图4.3查看所有节

关键节:

.text:存放代码。

.rodata:只读数据(如字符串常量)。

.rela.text:代码段的重定位信息。

图4.4查看符号表

关键信息:

main 是已定义的函数(FUNC,位于 .text 节)。

puts、printf 等是未定义符号(UND),需在链接时解析。

图4.5查看重定位节

关键字段:

Offset:需要修改的代码/数据在 .text 中的偏移。

Type:重定位类型(如 R_AARCH64_ADR_PREL_PG_HI21 表示 AArch64 的 PC 相对地址高 21 位)。

Sym. Name:依赖的符号(如 printf、.rodata)。

图4.6结合 objdump 查看代码与重定位

外部函数调用

如 puts、printf 的调用(R_AARCH64_CALL26 类型)需在链接时填充实际地址.

数据引用:

如 .rodata 中的字符串地址(R_AARCH64_ADR_PREL_PG_HI21)需在链接时计算。

未定义符号:

符号表中标记为 UND 的符号需由其他目标文件或库提供。

4.4 Hello.o的结果解析

以下是关键代码段的对比(以 main 函数为例):

(1) 函数入口(栈帧设置)

a9be7bfd:stp 指令的编码,操作数为 x29, x30, [sp, #-48]!

910003fd:mov 指令的编码,将 sp 的值赋给 x29。

操作数通过寄存器编号(如 x29=29, x30=30)和立即数偏移(如 #28)编码到机器码中。

(2) 条件分支与重定位

54000140:b.eq 的机器码,0140 是偏移量(计算目标地址 PC + 0x40)。

由于 hello.o 未链接,40 是临时偏移,链接后会修正为实际地址。

(3) 函数调用与重定位

adrp 和 add 的立即数(#0x0)是占位符,链接时会通过重定位修正。

bl 指令的跳转目标(puts)在重定位表(R_AARCH64_CALL26)中记录,链接时填充实际地址。

(4) 数据引用(字符串常量)

字符串以二进制形式存储在 .rodata 节,地址通过 adrp/add 组合加载。

机器语言与汇编语言的映射关系

(1) 指令编码

每条汇编指令对应固定的机器码(如 stp → a9be7bfd)。

操作数(寄存器、立即数)通过位域编码到机器码中:

例如 str w0, [x29, #28] 的 #28 被编码为 12 位有符号立即数。

(2) 分支与函数调用

分支偏移:

汇编中的标签(如 .L2)在机器码中转换为相对当前 PC 的偏移。

未链接时偏移是临时的,链接后修正(如 b.eq 40 中的 40)。

函数调用:

bl 指令的目标地址在重定位表中记录(如 R_AARCH64_CALL26 puts)。

链接时动态库的地址被填充到调用点。

图片参考第三章第四章

4.5 本章小结

本章围绕汇编过程及其在程序生成中的核心作用展开,系统性地分析了从汇编语言(.s文件)到可重定位目标文件(.o文件)的转换机制。汇编作为连接高级语言与机器硬件的关键环节,通过汇编器将人类可读的指令(如mov、bl)转换为机器码,同时处理符号引用、分段管理(如.text、.rodata)并生成重定位信息,为后续链接奠定基础。通过分析hello.o的ELF格式,我们深入理解了目标文件的结构:文件头标识其可重定位属性(REL)与架构(AArch64),节头表管理代码与数据的分区,而重定位条目(如R_AARCH64_CALL26)则记录了链接时需要修正的地址依赖(如外部函数puts的调用)。反汇编与汇编代码的对照进一步揭示了机器语言的构成逻辑------指令通过二进制编码固定化,操作数则嵌入寄存器编号或立即数,而分支与函数调用的地址在未链接时仅为临时值,需依赖重定位动态修正。这一过程凸显了工具链的协作:汇编器生成带元数据的中间文件,链接器解析重定位以绑定实际地址,最终生成可执行程序。本章内容不仅阐明了汇编的底层转换规则,也展现了程序从源码到二进制运行的全链路依赖关系,为理解计算机系统的软件-硬件协同提供了坚实基础。

5 链接

5.1 链接的概念与作用

链接是将一个或多个目标文件(如 `hello.o`)及所需的库文件合并生成最终可执行程序(如 `hello`)的关键过程。在这一阶段,链接器(如 `ld`)负责解析目标文件中的符号引用,将分散的代码段和数据段按需组合,并完成地址空间的重新分配。具体到 `hello.o` 到 `hello` 的转换,链接器首先会处理重定位信息,例如修正 `hello.o` 中对 `puts`、`printf` 等外部函数的调用地址,将这些未定义符号绑定到动态库(如 `libc.so`)或静态库中的实际实现;同时合并所有输入文件的 `.text`、`.data` 等节,并确定它们在最终程序中的内存布局。此外,链接器还会添加程序头信息,设置入口点(如 `_start`),并生成动态链接所需的元数据(如 PLT/GOT 表),使程序能在加载时与操作系统交互。通过链接,原本独立的机器码片段被整合为一个完整的可执行映像,不仅解决了模块间的符号依赖问题,还优化了存储空间和运行时效率,最终生成可直接由操作系统加载执行的二进制文件 `hello`。这一过程是程序从源码到运行的最后一步,直接决定了程序能否正确启动并与系统环境无缝衔接。

5.2 在Ubuntu下链接的命令

以下格式自行编排,编辑时删除

使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件

图5.1链接的命令

实际链接过程隐含调用了 ld,并链接了默认的启动文件(crt1.o)和库(libc.so)。

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

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

图5.2 查看 ELF 文件头(架构/入口点)

关键字段:

Type: EXEC(可执行文件)

Entry point address: 0xxxxx(程序入口地址)

Machine: LoongArch(架构)

图5.3列出所有段(Segments)及内存布局

关键信息:

VirtAddr:段在内存中的虚拟地址(如 0x400000)

MemSiz:段在内存中的大小

Flags:权限(R=读,W=写,E=执行)

图5.4列出所有节(Sections)及地址

关键字段:

Addr:节的加载地址(如 .text 通常位于 0x400000 附近)

Size:节的大小

Flg:属性(A=可分配,X=可执行,W=可写)

图5.5查看动态段(依赖库)

作用:确认程序依赖的动态库(如 libc)。

5.4 hello的虚拟地址空间

图5.6虚拟地址空间各段信息

在泰山服务器中没有edb相关,缺少权限无法下载,于是使用pgrep和pmap指令查看虚拟地址空间。

通过 readelf 分析 静态ELF结构,再对比 运行时内存布局:

  1. 虚拟地址空间分析(运行时)

通过 edb 或 /proc/[pid]/maps 查看运行时的内存布局,可观察到以下典型段:

代码段(.text):权限为 r-xp(可读可执行),对应程序中 main 函数的机器指令。

数据段(.data/.rodata):

.rodata(只读数据):存储字符串常量(如 "Hello %s %s %s\n"),权限为 r--p。

.data(可写数据):若程序有全局变量,权限为 rw-p。

堆(Heap):动态分配的内存(如 malloc),权限为 rw-p,地址范围由低向高增长。

栈(Stack):存储局部变量和函数调用链,权限为 rw-p,地址范围由高向低增长。

动态库映射:如 libc.so 的代码和数据段,权限为 r-xp(代码)或 rw-p(数据)。

  1. 可执行文件(hello)格式分析

通过 readelf -S hello 查看静态文件中的节(Section)信息,主要包含:

.text 节:与运行时代码段对应,存储程序的指令(如 main 函数的汇编指令)。

.rodata 节:与运行时只读数据段对应,存储字符串常量(如 LC0 和 LC1)。

.data 节:若存在初始化全局变量,会在此节定义。

.bss 节:未初始化全局变量,文件中不占空间,运行时扩展为 rw-p 内存。

  1. 对照分析关键差异

地址映射:

静态文件中节的地址(如 .text 的 Addr)是相对地址,运行时由加载器重定位为绝对虚拟地址(如 0xaaaaaaaaa000)。

动态链接库(如 libc.so)在文件中仅记录依赖项,运行时才映射到内存。

权限扩展:

静态文件的 .text 节标记为 AX(可分配可执行),运行时扩展为 r-xp。

.rodata 在文件中为只读(A),运行时同样为 r--p。

动态段补充:

运行时新增 [stack]、[heap] 等匿名映射段,这些在静态文件中无对应节。

  1. 动态链接与重定位

PLT/GOT:

调用 printf、sleep 等函数时,静态文件通过 .rela.plt 节记录重定位项,运行时由动态链接器填充真实地址。

地址随机化(ASLR):

运行时段的基地址(如 0xaaaaaaaaa000)与静态文件中的预设地址不同,这是操作系统的安全特性。

5.5 链接的重定位过程分析

图5.7objdump指令部分截图

在程序从源文件到可执行文件的转换过程中,链接阶段起着至关重要的作用。通过分析 hello.c 编译生成的 hello.o 目标文件与最终链接生成的 hello 可执行文件,可以清晰地看到链接过程中对符号引用和地址分配的处理方式。

目标文件 hello.o 是一个可重定位文件,其中包含了程序的机器代码和数据,但尚未完成所有地址的最终确定。在 hello.o 中,对外部函数(如 printf、sleep、atoi 等)的调用以及全局数据的引用都是通过重定位项来标记的。这些重定位项指示链接器在最终生成可执行文件时需要修正的地址位置。例如,当 hello.o 调用 printf 函数时,具体的函数地址并未确定,而是由重定位项记录下需要修正的位置和方式。

链接器在生成 hello 可执行文件时,会处理这些重定位项,完成以下几个关键步骤:首先,链接器将所有目标文件的代码段、数据段等按需合并,并为它们分配最终的运行时内存地址;其次,链接器解析所有未定义的符号引用,确保它们指向正确的地址,例如将 printf 的调用指向动态链接库中的实际实现;最后,链接器生成程序头信息,指导操作系统如何加载和执行该程序。

在 hello 可执行文件中,所有的函数调用和数据访问都已完成了地址绑定。原本在 hello.o 中通过重定位项标记的位置,现在已经被替换为具体的虚拟地址或动态链接相关的跳转指令(如通过过程链接表 PLT 实现延迟绑定)。动态链接库中的函数地址会在程序加载时由动态链接器进一步解析填充。

通过这一过程,链接器将零散的目标文件整合为一个完整的可执行文件,确保了程序运行时能够正确访问所有函数和数据。从 hello.o 到 hello 的变化,本质上是从未链接的独立代码片段到地址完全解析的可执行程序的转变,这一过程依赖于重定位信息的精确指导。

5.6 hello的执行流程

以下格式自行编排,编辑时删除

使用gdb/edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程(主要函数)。请列出其调用与跳转的各个子程序名或程序地址。

图5.8gdb执行hello

程序加载与启动(_start)

当执行 ./hello 时,操作系统加载器(如 ld-linux)负责将程序载入内存,并跳转到入口点 _start(由链接器默认设置)。该过程涉及:

加载动态链接库(如 libc.so),解析依赖项。

初始化程序环境(栈、全局变量等)。

调用 __libc_start_main(由 _start 调用),负责:

设置 argc 和 argv。

调用全局构造函数(如 .init 段)。

最终调用 main 函数。

主函数执行(main)

main 函数的调用链如下:

参数检查

检查 argc != 5 时,调用 printf 输出用法提示,随后调用 exit(1) 终止。

循环输出

调用 printf 打印格式化字符串(参数为 argv[1]、argv[2]、argv[3])。

调用 atoi 将 argv[4] 转换为整数,传递给 sleep 函数。

等待输入

调用 getchar 等待用户输入。

返回退出

return 0 触发 main 的退出逻辑。

程序终止

main 返回后,控制权交还给 __libc_start_main,依次执行:

调用全局析构函数(如 .fini 段)。

调用 _exit 系统调用(或 exit_group),释放资源并终止进程。

图5.9main函数的地址

5.7 Hello的动态链接分析

图5.10gdb调试并设置断点

图5.11GOT初始值与PLT条目

图5.12链接后GOT项

(1)定位关键地址

使用info functions printf@plt获取PLT条目地址(如0x4005f0)

通过objdump -R hello或PLT指令计算GOT项地址(如0x411040)

(2)观察初始状态

反汇编PLT条目(disas 0x4005f0),确认其跳转逻辑

检查GOT项初始值(x/gx 0x411040),应指向PLT解析代码

(3)触发动态链接

执行到printf调用(continue或step)

动态链接器解析libc地址并更新GOT项

(4)验证绑定结果

再次检查GOT项(x/gx 0x411040),确认指向libc(如0x7ffff7e3d490)

通过info sharedlibrary验证地址在libc映射范围内

5.8 本章小结

本章主要介绍了链接的概念与作用,在泰山服务器下链接的命令,可执行目标文件hello的格式,包括ELF头、节头、重定位节、符号表,通过对比hello与hello.o进行链接的过程分析和重定位过程分析,对hello的执行流程和动态链接进行分析。

6 hello进程管理

6.1 进程的概念与作用

(1)进程的概念:

进程的经典定义就是一个执行中程序的实例。

(2)进程的作用:

每次用户向shell输入一个可执行目标文件的名字,运行程序时,shell就会创建一个新的进程,然后在这个新进程的上下文中运行这个可执行目标文件。应用程序也能够创建新进程,并且在这个新进程的上下文运行它们自己的代码或其他应用程序。进程提供给应用程序两个关键抽象:

①一个独立的逻辑流,它提供一个假象,好像我们的程序独占地使用处理器。

②一个私有的地址空间,它提供一个假象,好像我们的程序独占地使用内存系统。

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

作用:Shell是信号处理的代表,负责各进程创建与程序加载运行及前后台控制,作业调用,信号发送与管理等。

shell的处理流程一般为:提示符显示;读取命令;解析命令;查找命令;创建子进程;执行命令;等待命令完成;显示输出;返回提示符

6.3 Hello的fork进程创建过程

当程序通过`fork()`系统调用创建子进程时,操作系统会生成一个与父进程高度相似但独立的新进程。子进程会获得父进程用户空间内存的完整副本,包括程序代码、数据段、堆内存、共享库以及用户栈空间,所有这些内容都是独立拷贝而非共享。此外,子进程还会继承父进程所有已打开的文件描述符,这意味着它可以像父进程一样读写相同的文件资源。父进程与子进程之间最本质的区别在于它们拥有各自独立的进程ID(PID),这是操作系统区分两个进程的关键标识。其他诸如寄存器状态、内存映射等资源在创建初期完全一致,但后续执行过程中会因各自的操作而逐渐产生差异。

6.4 Hello的execve过程

当子进程调用execve()系统调用时,操作系统会彻底重构该进程的用户空间。首先,原有的代码段、数据段、堆栈等用户区域会被完全清除。随后,内核通过虚拟内存机制将新可执行文件hello的各个段(如代码段、数据段)精确映射到进程地址空间的对应位置。同时,动态链接器ld-2.31.so等依赖的共享库也会以内存映射方式加载到进程空间。最终,处理器指令指针会跳转到hello程序的入口地址(通常是_start符号),从此开始执行新程序的指令流。这一过程彻底替换了进程的执行映像,仅保留原进程的PID和文件描述符等少量属性。

6.5 Hello的进程执行

进程调度是操作系统通过管理进程上下文和时间片实现多任务并发的核心机制。每个进程的运行都依赖于完整的上下文环境,包括内存中的代码段、数据段、栈结构、寄存器状态以及打开的资源描述符等关键信息。这些上下文构成了进程执行的现场,必须保证在调度过程中不被破坏。

当系统通过时钟中断触发调度时,会引发精确的状态转换:正在CPU上执行的进程(如hello)会因硬件中断立即从用户态切换至内核态,此时处理器特权级提升,控制权移交操作系统。内核首先将当前进程的完整上下文保存至进程控制块(PCB),包括程序计数器、寄存器堆等关键状态。随后调度器根据时间片轮转算法选择就绪队列中的下一个进程(如进程B),将其保存的上下文重新载入CPU,完成状态恢复后切换至用户态继续执行。

时间片机制是调度公平性的保障,它限定了每个进程占用CPU的最长连续时段。当进程主动调用系统调用(如sleep)时,会通过陷阱指令触发从用户态到内核态的同步转换。内核处理sleep请求时会将进程置为阻塞状态,并启动计时器。在此期间,调度器会选取其他就绪进程投入运行。当计时器中断到达时,内核在中断处理流程中解除进程阻塞状态,待下次调度时恢复其上下文继续执行。这种基于中断驱动的状态保存与恢复机制,实现了多进程间的无缝切换,既保证了系统响应速度,又维持了各进程上下文的完整性。

6.6 hello的异常与信号处理

hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。

程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。

Hello执行过程中会出现中断(异步),陷阱,故障,终止异常

中断,陷阱总是返回到下一条指令

故障可能返回到当前指令

终止不会返回

(1)在程序正常运行时,打印10次提示信息,以输入回车为标志结束程序,并回收进程。

图6.1程序正常运行

(2)按下Ctrl-Z后显示进程暂停状态,暂停进程并转入后台。

图6.2Ctrl-Z

(3)按下Ctrl-C后直接终止hello进程。

图6.3Ctrl-C

(4)按下Ctrl-Z后输入jobs -l指令,查看当前的后台进程

图6.4jobs指令

(5)随后输入pstree指令可以显示进程及其父进程(如bash)的树状结构。

图6.5pstree指令

(6)利用fg指令使进程恢复运行,继续输出Hello信息。

图6.6fg指令

(7)直接终止进程

图6.7kill指令

  1. 乱按

可以看出输入字符有效,对于程序运行没有影响。

图6.8乱按

6.7本章小结

本章深入探讨了hello程序的进程管理机制,系统性地阐述了进程的核心概念及其在计算机系统中的重要作用。通过分析shell环境的工作原理和处理流程,揭示了命令行程序的执行机制。研究重点剖析了hello程序运行过程中涉及的fork系统调用实现进程创建的完整过程,以及execve函数进行程序映像替换的关键细节。同时,本章还全面分析了程序执行期间可能触发的各类异常情况,包括中断、陷阱和故障等,并对相应的信号处理机制进行了详细说明,完整呈现了从进程创建、执行到异常处理的完整生命周期管理。

7 hello的存储管理

7.1 hello的存储器地址空间

在hello程序的执行过程中,地址转换涉及四个层次的关键概念。逻辑地址是程序直接使用的段内偏移地址,如hello.c编译后生成的函数和变量地址,它由段选择符和偏移量共同构成。线性地址作为中间层,是通过将逻辑地址的偏移量与段基址相加得到的过渡形式。

操作系统通过虚拟内存机制为hello程序提供虚拟地址空间,使得程序可以透明地使用连续的内存地址,而无需关心物理内存的实际分布情况。例如,main函数中的指令地址就是虚拟地址的具体表现。最终,当程序实际访问内存时,虚拟地址会通过MMU转换为物理地址,这是真正在内存总线上传输的地址信号,对应着DRAM芯片中的具体存储单元。

物理地址直接对应硬件层面的内存访问,无论是读取指令还是写入数据,CPU都通过物理地址与内存芯片交互。这种多级地址转换机制,既保证了程序的内存访问安全性,又实现了物理内存的高效共享和管理。在hello程序从编译到运行的整个生命周期中,这些地址概念共同协作,完成了从源代码到硬件执行的无缝转换。

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

在x86架构的段式内存管理机制中,CPU通过"段选择符:偏移地址"形式的逻辑地址进行寻址。具体转换过程如下:16位的段选择符用于索引全局描述符表(GDT)或局部描述符表(LDT),从中获取对应的段描述符。该描述符包含了段的基地址、界限和访问权限等关键信息。CPU将段基地址与32位偏移地址相加,最终生成线性地址。需要特别指出的是,GDT和LDT的建立与管理完全由操作系统内核负责,这包括段的划分、权限设置以及表的维护等工作,从而确保内存访问的安全性和隔离性。这种段式管理机制构成了保护模式下内存寻址的基础层。

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

现代操作系统通过分页机制管理虚拟地址空间,将地址空间划分为固定大小的页。CPU在地址转换时,首先提取线性地址的高位作为页表索引,通过查询多级页表结构(如x86-64的四级页表)定位到对应的页表项。该页表项中存储了目标物理页框的起始地址,CPU将此物理页基址与线性地址的低位(页内偏移量)相加,最终得到实际的物理地址。这一转换过程由内存管理单元(MMU)硬件加速完成,操作系统负责维护页表内容,包括设置权限位、修改映射关系等核心功能,从而实现了虚拟内存到物理内存的高效安全映射。分页机制不仅支持灵活的内存分配,还通过页表项中的权限控制位实现了内存保护。

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

现代处理器通过TLB(Translation Lookaside Buffer)加速虚拟地址到物理地址的转换。当CPU生成虚拟地址后,MMU首先查询TLB获取页表项(PTE)。若命中,则立即完成地址转换;若未命中(TLB缺失),则需通过多级页表机制进行转换。

x86-64架构采用四级页表结构,将虚拟页号(VPN)划分为四个索引字段。MMU依次查询各级页表:

  1. 使用VPN1索引PGD(页全局目录)

  2. 使用VPN2索引PUD(页上级目录)

  3. 使用VPN3索引PMD(页中间目录)

  4. 使用VPN4索引PT(页表)

最终获取物理页号(PPN)后,与页内偏移量组合形成物理地址。多级页表通过按需分配页表结构,显著减少了内存占用,而TLB则通过缓存热点PTE来提升转换效率,二者协同工作实现了高效的内存地址转换。

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

现代计算机系统通过高速缓存(Cache)机制优化物理内存访问性能,其工作流程可分为四个关键步骤:

首先,处理器根据虚拟地址中的组索引位确定目标缓存组,将二进制索引转换为无符号整数定位具体组位置。接着进行行匹配操作,将地址标记位与组内所有缓存行的标记位逐一比对,当发现有效位为1且标记匹配时即触发缓存命中。命中后,系统利用块偏移位精确定位数据块中的目标字节,完成数据读取并返回CPU处理。

若发生缓存未命中,系统将启动补救机制:从下级存储层次(如主存)提取目标数据块,并根据放置策略将其存入缓存。对于组内存在空闲行的情况直接写入;当组已满时则触发替换机制,通常采用最近最少使用(LRU)等算法淘汰旧数据。这种分层缓存体系通过空间局部性和时间局部性原理,显著提升了内存访问效率。

7.6 hello进程fork时的内存映射

当进程调用fork()系统调用时,操作系统会执行以下精细的进程复制操作:

  1. 内核首先为新进程构建完整的进程控制结构,包括分配唯一的进程标识符(PID);

  2. 采用写时复制(COW)技术高效复制父进程的虚拟内存空间:

  • 完整复制mm_struct内存管理结构

  • 复制所有虚拟内存区域(vma)描述符

  • 建立与父进程相同的页表结构

  1. 将所有共享内存页标记为只读权限;

  2. 将所有虚拟内存区域标记为私有COW属性;

在fork()返回时,子进程拥有与父进程调用fork()时刻完全一致的虚拟内存视图。这种设计的关键优势在于:

  • 初始阶段仅复制元数据,物理内存页保持共享

  • 当任一进程尝试写入共享页时触发页错误异常

  • 内核捕获异常后分配新物理页,执行实际复制

  • 更新页表项,恢复进程的写入权限

这种写时复制机制实现了两个重要目标:

  1. 极大优化了fork性能,避免不必要的内存拷贝

  2. 确保进程间内存空间的隔离性,维护私有地址空间抽象

整个过程对应用程序完全透明,程序员无需关心底层的内存复制细节,只需理解fork()后两个进程将拥有独立但初始内容相同的地址空间。

7.7 hello进程execve时的内存映射

execve函数通过内核加载器在当前进程空间内加载并执行目标程序hello,其核心操作流程如下:

首先,内核会彻底清理进程现有的用户空间:解除所有原用户区域的虚拟内存映射,释放相关数据结构。随后建立新的内存布局:为程序的代码段(.text)、初始化数据(.data)、未初始化数据(.bss)以及运行时栈等区域创建私有且支持写时复制的映射。其中代码段和数据区直接映射到hello文件的对应节区,.bss和堆栈则初始化为零填充的匿名映射。

对于动态链接场景,内核会将共享库(如libc.so)映射到进程的共享内存区域,确保所有依赖库都能被正确加载。最后,内核重置进程的执行上下文,将指令指针设置为程序入口点(通常是_start符号地址),完成从旧进程到新程序的平滑转换。这种机制既保证了进程资源的彻底重置,又维持了高效的执行环境切换。

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

现代计算机系统的虚拟内存管理采用硬件与操作系统协同工作的机制。当处理器生成虚拟地址后,内存管理单元(MMU)会自动查询页表项(PTE),这一过程完全由硬件电路实现。若PTE有效位为1,则直接完成地址转换,此过程称为页面命中,全程无需操作系统介入。

当发生缺页异常时,系统进入硬件与内核协作处理流程:

  1. MMU检测到无效PTE后触发缺页异常

  2. CPU控制权移交操作系统缺页处理程序

  3. 内核执行页面置换算法选择牺牲页

  4. 若牺牲页被修改则写回磁盘

  5. 从磁盘调入目标页面至物理内存

  6. 更新页表项并设置有效位

  7. 异常返回后重新执行触发指令

该机制通过硬件加速常规地址转换,同时利用操作系统处理异常情况,既保证了内存访问的高效性,又实现了虚拟内存的透明管理。最终,当指令重新执行时,虚拟地址能够顺利转换,完成预期的内存访问操作。

7.9动态存储分配管理

动态内存分配器管理着进程虚拟地址空间中的堆区域。在典型的内存布局中,堆是一个从.bss段末尾开始向上扩展的匿名内存区域,内核通过brk指针标记堆空间的当前顶端边界。

分配器将堆空间组织为离散的内存块集合,每个块代表一段连续的虚拟地址范围,其状态分为:

  1. 已分配块:明确分配给应用程序使用的内存区域

  2. 空闲块:可供分配使用的可用内存区域

内存块的生命周期遵循严格的状态转换规则:空闲块仅在被显式分配时转为已分配状态,而已分配块必须通过显式释放或自动回收机制才能重新变为空闲块。

现代分配器主要分为两大类型:

显式分配器(如malloc/free)要求程序员手动管理内存释放,这种方案控制精准但易引发内存泄漏。隐式分配器(垃圾回收器)通过引用计数、标记-清除等算法自动识别并回收不可达内存块,虽然减轻了程序员负担,但需要额外的运行时开销来跟踪对象引用关系。两种设计哲学在内存安全性和执行效率之间形成了典型权衡。

7.10本章小结

本章系统性地阐述了计算机存储管理的核心机制与实现策略。首先深入剖析了存储器地址空间的组织结构,详细讲解了Intel架构下从逻辑地址到线性地址的段式转换机制,以及线性地址到物理地址的页式映射原理。在地址转换优化方面,重点分析了TLB加速技术和四级页表结构如何协同完成虚拟地址到物理页的高效转换,并探讨了三级缓存体系对物理内存访问的性能优化。

在进程管理层面,详细解读了fork操作采用的写时复制机制和execve过程的内存映射重构策略。针对存储异常处理,深入阐述了缺页故障的触发条件与中断处理流程。最后,系统介绍了动态存储分配管理的两种范式:显式分配器的精确控制与隐式垃圾回收的自动化管理。

这些内容全面揭示了程序存储管理的底层原理,包括地址转换机制、内存映射策略、异常处理流程以及动态分配方案,为理解现代操作系统如何高效安全地管理存储器资源提供了完整的理论框架和实践指导。

8 hello的IO管理

8.1 Linux的IO设备管理方法

现代操作系统采用统一的文件抽象模型来管理各类I/O设备,这一创新性设计将物理设备与标准文件操作完美融合。具体实现包含两个关键层面:

  1. 设备抽象化机制

操作系统将网络接口、磁盘驱动器、终端设备等所有I/O资源都抽象为特殊的文件对象,每个设备文件都具有标准的文件属性(如inode、权限位等)。通过这种抽象,系统实现了:

  • 统一的命名空间管理

  • 标准化的访问控制

  • 一致的读写语义

  1. Unix I/O接口规范

内核基于文件抽象层提供的系统调用接口具有以下特性:

  • 原子性操作保障(open/read/write/close)

  • 同步/异步访问模式支持

  • 设备无关的编程模型

  • 统一的错误处理机制

这种设计哲学使得应用程序无需关心底层设备差异,通过标准文件操作即可实现:

  • 磁盘文件的顺序/随机访问

  • 网络套接字的数据收发

  • 终端设备的输入输出

  • 特殊设备的控制命令传递

该架构不仅简化了系统设计,还大幅提升了I/O子系统的可扩展性,新设备的接入只需实现对应的文件操作接口即可融入现有体系。这种"一切皆文件"的设计理念已成为Unix-like系统的核心特征之一。

8.2 简述Unix IO接口及其函数

Unix I/O 接口采用统一文件模型来管理所有输入输出操作,通过一组简洁而强大的系统调用实现对各类设备的访问控制。其核心函数包括 open()用于创建或打开文件描述符、read()/write()执行基础数据读写、close()释放文件资源,以及 lseek()实现随机访问定位。这些系统调用为磁盘文件、终端设备、网络套接字等提供了统一的访问抽象,所有I/O操作都通过文件描述符这个整型句柄来完成。特别地,ioctl()函数为设备特定操作提供了扩展接口,而fcntl()则支持描述符属性的动态配置。该设计通过将设备抽象为文件,使得应用程序无需关心底层硬件差异,仅通过标准文件操作就能处理包括块设备、字符设备和网络设备在内的各类I/O请求,这种"一切皆文件"的哲学既保证了接口的简洁性,又确保了系统的可扩展性,成为Unix系统持久影响力的关键特征之一。

8.3 printf的实现分析

在printf函数的实现中,其核心工作流程展现了用户态到内核态的完整I/O处理链条。该函数首先通过可变参数机制收集格式化参数,调用vsprintf进行字符串解析和格式化处理,这个关键步骤不仅完成格式转换,还精确计算输出字符串长度。随后调用write系统调用触发内核态执行,此时CPU通过中断指令(如x86的int 0x80或syscall)实现特权级切换,将控制权移交操作系统。

在内核层面,字符输出驱动负责将ASCII编码转换为显示设备可识别的字模数据,这个转换过程需要查询预先存储的字形库。转换后的像素信息被写入视频存储器(VRAM),其中每个像素点的RGB色彩值都被精确记录。显示控制器则按照固定刷新频率(如60Hz)自动扫描VRAM内容,通过视频信号线将逐像素的色彩数据实时传输至显示设备,最终完成视觉信息的呈现。这个从格式化字符串到物理像素显示的完整流水线,体现了计算机系统软硬件协同工作的精妙设计。

8.4 getchar的实现分析

getchar()函数实现了一个从标准输入流同步读取字符的完整处理链条。当这个库函数被调用时,它最终会通过read系统调用进入内核态,等待用户输入。在硬件层面,键盘控制器负责将物理按键动作转化为扫描码,并触发处理器中断机制。键盘中断服务程序作为异步事件的处理核心,首先从键盘接口寄存器获取扫描码,经过转码表将其转换为ASCII字符,并存入输入缓冲区。值得注意的是,getchar()采用行缓冲模式,只有在检测到回车键输入时才会将整行字符从内核缓冲区返回用户空间,这种设计既减少了系统调用次数,又符合终端交互的自然特性。整个处理过程涉及硬件中断响应、内核驱动处理、系统调用转换以及用户态缓冲管理等多个层级的协同工作,展现了操作系统对输入设备管理的典型实现方式。

8.5本章小结

输入输出操作表面简单,实则蕴含着复杂的系统级交互过程。从应用程序发起I/O请求到最终设备响应,整个流程涉及用户态与内核态的多次切换、硬件中断处理、设备驱动调度等多个关键环节。这种跨层级的协作机制虽然为程序员提供了简洁的接口抽象,但在执行效率方面存在固有开销,常常成为程序性能的瓶颈所在。同时,由于需要协调软件栈各层级和硬件设备的时序关系,I/O操作也成为系统稳定性的关键风险点,任何环节的异常都可能导致整个流程失败。这就要求开发者在实现功能时,必须深入理解不同I/O函数的内在特性,根据具体场景审慎选择同步/异步、缓冲/非缓冲等机制,在功能需求与性能可靠性之间取得平衡。这种对I/O操作的精确把控,往往能显著提升程序的健壮性和执行效率。

结论

  1. 程序编译与链接

预处理:展开 #include 头文件,处理宏定义,生成 hello.i。

编译:将 C 代码(hello.i)翻译为汇编指令(hello.s),包含 main、printf、sleep 等函数的低级实现。

汇编:将汇编代码转换为机器码(hello.o),生成可重定位目标文件,包含逻辑地址和未解析的符号(如 printf)。

链接:动态链接器(ld)合并 hello.o 和 libc.so,解析符号地址,生成可执行文件 hello,并标记动态链接依赖。

  1. 进程创建与加载

shell 解析命令:输入 ./hello 后,shell 调用 fork() 创建子进程,子进程通过 execve() 加载 hello。

fork():复制父进程(shell)的虚拟内存空间(写时复制),分配新 PID。

execve():清除原进程空间,按 ELF 格式加载 hello 的代码段(.text)、数据段(.data)、BSS 段(清零),映射共享库(如 libc.so),设置入口点为 _start。

动态链接:若首次运行,加载器 (ld.so) 解析 printf、sleep 等函数在 libc.so 中的实际地址,填充 PLT/GOT 表。

  1. 程序执行与系统交互

CPU 执行流程:

_start:初始化栈帧,调用 __libc_start_main。

main 函数:

检查 argc,若参数不足,调用 printf 输出提示并触发 exit(1)。

循环 10 次:调用 printf 打印参数,通过 atoi 转换参数为整数,调用 sleep 挂起进程。

调用 getchar() 等待输入。

系统调用:

printf 通过 write 系统调用(syscall)将缓冲区数据写入标准输出(文件描述符 1)。

sleep 通过 nanosleep 系统调用挂起进程,触发进程调度。

getchar 通过 read 系统调用(文件描述符 0)等待键盘输入。

  1. 硬件与内核协作

地址转换:

逻辑地址(main 中的指令地址)→ 线性地址(段式管理)→ 物理地址(MMU 查询页表,TLB 加速)。

若缺页,内核分配物理页,可能触发页面置换(如将旧页写入交换区)。

I/O 处理:

终端输出:write 系统调用将数据从用户缓冲区拷贝至内核,字符设备驱动将 ASCII 码转换为字模,写入显存(VRAM),由显卡逐帧渲染。

键盘输入:按键触发中断,键盘驱动将扫描码转为 ASCII 码,read 系统调用从内核缓冲区返回字符。

  1. 进程终止

main 返回:返回值 0 传递给 __libc_start_main。

资源释放:内核关闭打开的文件描述符,释放内存页,撤销进程结构。

父进程(shell)回收:通过 wait() 获取子进程状态,显示新的提示符。

最初编写hello程序时,只觉得它不过是一个简单的打印语句,对计算机而言理应轻而易举。然而通过这次深入的剖析,我才惊觉这短短一行文字背后竟隐藏着如此精密的系统级协作 - 从预处理展开宏定义到编译器生成中间代码,从链接器解析符号到加载器构建内存映像,从CPU流水线执行到系统调用处理,最后经由显卡驱动完成像素渲染。每一个环节都像瑞士钟表里的齿轮般严丝合缝,让我深刻体会到计算机科学中"简单源于复杂"的哲学智慧。这种认识彻底改变了我对编程的认知维度:今后无论是开发应用程序还是研究系统优化,我都会铭记此刻的震撼,以体系结构的思维审视每一行代码的生命周期,在抽象与实现之间寻找创新的可能。计算机系统的精妙之处,正在于用层层精密的工程实现,最终呈现出令人惊叹的简洁之美。

附件

原始代码hello.c

预处理后的代码hello.i

编译后的汇编语言代码hello.s

可重定位目标文件hello.o

可执行文件hello

参考文献

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.

相关推荐
Sapphire~2 小时前
Linux-07 ubuntu 的 chrome 启动不了
linux·chrome·ubuntu
伤不起bb2 小时前
NoSQL 之 Redis 配置与优化
linux·运维·数据库·redis·nosql
广东数字化转型2 小时前
nginx怎么使用nginx-rtmp-module模块实现直播间功能
linux·运维·nginx
啵啵学习2 小时前
Linux 里 su 和 sudo 命令这两个有什么不一样?
linux·运维·服务器·单片机·ubuntu·centos·嵌入式
半桔3 小时前
【Linux手册】冯诺依曼体系结构
linux·缓存·职场和发展·系统架构
网硕互联的小客服3 小时前
如何利用Elastic Stack(ELK)进行安全日志分析
linux·服务器·网络·安全
Felven4 小时前
C. Basketball Exercise
c语言·开发语言
可乐鸡翅好好吃4 小时前
通过BUG(prvIdleTask、pxTasksWaitingTerminatio不断跳转问题)了解空闲函数(prvIdleTask)和TCB
c语言·stm32·单片机·嵌入式硬件·bug·keil
冰橙子id4 小时前
linux——磁盘和文件系统管理
linux·运维·服务器