计算机科学与技术学院
2025年5月
摘要:本论文以“Hello’s P2P”程序为研究对象,通过分析其从源代码到进程执行的完整生命周期,深入探讨了计算机系统的编译、链接、进程管理、存储管理及I/O管理等核心机制。研究基于Ubuntu 22.04环境,使用GCC工具链对程序进行预处理、编译、汇编和链接,生成可执行文件;结合GDB调试工具和系统工具(如readelf、strace),对程序的执行流程、虚拟地址空间、动态链接、异常信号处理等进行了详细分析。通过实验验证了程序在进程创建(fork/execve)、存储管理(页式地址转换、TLB与Cache优化)及I/O操作(printf/getchar系统调用)中的具体实现。研究结果表明,计算机系统通过分层抽象和硬件-软件协同机制,实现了程序的高效执行与资源管理。本工作不仅深化了对计算机系统底层原理的理解,还为后续系统级编程和优化提供了实践基础。
关键词:计算机系统;编译链接;进程管理;虚拟内存;系统调用
目 录
第1章 概述
1.1 Hello简介
Hello的P2P(Program to Process)过程:
(1)预处理:将hello.c展开头文件、宏替换,生成hello.i。
(2)编译:将hello.i翻译为汇编代码hello.s。
(3)汇编:将hello.s转换为机器指令,生成可重定位目标文件hello.o。
(4)链接:将hello.o与库文件链接,生成可执行文件hello。
(5)执行:Shell通过fork创建子进程,execve加载hello,CPU调度执行。
020(Zero to Zero)过程:
程序从无到有(源码到进程),最终被回收(内存释放、进程终止)。
1.2 环境与工具
(1)硬件:VMware虚拟机,4核CPU,4GB内存。
(2)软件:Ubuntu 22.04 LTS,GCC 11.3.0,GDB 12.1,readelf 2.38。
(3)工具链:预处理器(cpp)、编译器(gcc)、汇编器(as)、链接器(ld)
1.3 中间结果
文件 | 作用 |
hello.i | 预处理后的C代码 |
hello.s | 汇编代码 |
hello.o | 可重定位目标文件 |
hello | 可执行文件 |
1.4 本章小结
本章概述了Hello的P2P过程,列出了开发环境和中间文件,为后续章节的详细分析奠定基础。
第2章 预处理
2.1 预处理的概念与作用
预处理是编译的第一步,主要任务包括:
1.宏替换:将#define定义的常量或函数替换为实际值。
2.头文件包含:递归插入#include文件内容(如stdio.h中的函数声明)。
3.条件编译:根据#ifdef、#if等指令选择性地保留代码块。
4.注释删除:移除所有注释以减少编译输入体积。
2.2在Ubuntu下预处理的命令
hello.i 完整程序附在压缩包里了!
2.3 Hello的预处理结果解析
2.3.1关键变化分析:
- 头文件展开(截图为stdio.h中的一部分):
#include <stdio.h>被替换为标准I/O函数的声明(700余行)。
类似地,unistd.h和stdlib.h的内容也被完整插入,引入sleep、exit等函数。
(2)注释删除:
原始代码中的中文注释“//大作业的 hello.c 程序”被完全删除。
(3)宏处理:
原始代码中未定义宏,但头文件中的宏(如#define __USE_XOPEN)被保留。
(4)条件编译:
头文件中通过#ifdef过滤平台相关代码:
2.3.2预处理结果特点:
文件总行数从hello.c的约30行扩展至hello.i的约2000行。
所有外部依赖的函数声明和类型定义均已内联。
2.4 本章小结
预处理阶段生成hello.i,为编译阶段提供纯净的C代码
第3章 编译
3.1 编译的概念与作用
编译器将预处理后的C代码(.i)转换为汇编代码(.s),主要步骤包括:
1.词法/语法分析:生成抽象语法树(AST)。
2.语义分析:检查类型匹配、作用域规则。
3.中间代码生成:生成与机器无关的中间表示(如LLVM IR)。
4.代码优化:删除冗余指令,优化控制流。
5.目标代码生成:输出机器相关的汇编代码。
3.2 在Ubuntu下编译的命令
hello.s 完整代码附在压缩包里了!
3.3 Hello的编译结果解析
3.3.1 数据:常量、变量、表达式
- 常量
字符串常量.LC1被存储在.rodata节,通过RIP相对寻址加载。
- 局部变量
局部变量i存储在栈帧偏移-4(%rbp)处,通过movl和addl操作。
3.3.2赋值与操作符
- 赋值=
直接赋值立即数0到i的内存位置
- 复合赋值+=
等效于i += 1
3.3.3 类型转换
显示转换
atoi将字符串指针转换为整数,结果存入%eax
3.3.4 类型与size of
1.类型声明
int类型对应汇编中的32位操作(如movl)。
2.size of操作
编译器在编译期直接计算,不生成运行时指令。
3.3.5算数与位操作
1.算术操作(+)
i = i + 1
2.位操作:
代码中未使用位操作,但&、|等对应andl、orl指令。
3.3.6 逻辑与关系操作
1.逻辑判断(argc!=5)
比较argc与5,相等则跳转(逻辑非)
2.关系操作(i<=9)
比较i与9,i <= 9则跳转
3.3.7数组/指针操作
数组访问
指针运算通过偏移量实现(argv + 8对应argv[1])。
3.3.8控制转移
- for循环
循环条件检查与跳转。
- If条件分支
检查argc != 5,相等则跳过错误处理, 加载错误提示字符串,调用puts输出错误
3.3.9 函数操作
1.参数传递
参数按顺序存入寄存器:%rdi, %rsi, %rdx等。
2.函数返回
返回值存入%eax
- 局部变量存储
3.4 本章小结
编译阶段生成hello.s,将C代码转换为机器相关的汇编指令。
第4章 汇编
4.1 汇编的概念与作用
汇编语言是直接操作硬件的低级语言,用于系统底层开发、性能优化及安全研究;典型场景包括操作系统、嵌入式系统和逆向工程。将汇编代码转换为机器指令,生成可重定位目标文件。
4.2 在Ubuntu下汇编的命令
hello.o程序附在压缩包里了!
4.3 可重定位目标elf格式
1.ELF表头
(前四个字节)ELF Magic---ELF的魔数 ---用于确定文件类型是否正确
ELF头紧接着的是段头表(可执行文件):页面大小,虚拟地址内存段,段大小
2.节头表
(1).text section
存放已经编译好的机器代码
(objdump(反汇编) 将机器代码转换为汇编代码)
(2).data section
存放已初始化(*)的全局变量或静态变量的值
(3).bss section---注意:bss section并不占用实际空间 仅仅是一个占位符 区分是否初始化
存放未初始化(*)或初始化为0(*)的全局变量和静态变量
(4).rodata section
存放只读数据(printf格式串/switch跳转表)
(5).symtab符号表中,main函数被标记为全局(GLOBAL)。
4.4 Hello.o的结果解析
1.通过objdump -d -r hello.o反汇编:
未解析符号:printf和sleep的地址为0x0,需在链接阶段填充。
定位条目:R_X86_64_PLT32类型指示动态链接符号的PLT偏移
2.关键机器指令
跳转指令:
偏移量0x38表示跳转到循环体起始地址。
3.将其与Hello.s相比:
原来的伪指令消失。在函数调用处,标准库中的puts,exit,printf,atoi,sleep消失了,被0替代。字符常量的引用被改成了有效地址计算,但立即数都被设置成全0。另外,分支转移不再以标签为操作数,改成使用长度为一个字节的PC相对地址。因为输出的文件是二进制,因此十进制的立即数都变成了二进制。
4.5 本章小结
汇编生成hello.o,包含机器指令和重定位信息,但未解析外部符号。
第5章 链接
5.1 链接的概念与作用
链接是将多个目标文件(.o)和库文件合并生成可执行文件的过程,主要解决符号引用和地址重定位问题。链接分为静态链接和动态链接。
5.2 在Ubuntu下链接的命令
参数说明:
-dynamic-linker: 指定动态链接器路径(如/lib64/ld-linux-x86-64.so.2)。
crt1.o, crti.o, crtn.o: C运行时初始化文件。
-lc: 链接C标准库libc.so。
-o hello: 输出可执行文件。
5.3 可执行目标文件hello的格式
程序头表:代码段(R E)和数据段(RW)的加载地址和权限。
节头表:代码段(.text)和数据段(.data)的具体位置。
动态段:依赖的动态库(libc.so.6)和初始化代码地址。
5.4 hello的虚拟地址空间
使用gdb/edb加载hello,查看本进程的虚拟地址空间各段信息:
代码段(0x400000):可读可执行,存放程序指令。
数据段(0x7ffff7dbf000):可读可写,存放全局变量和静态数据。
动态库映射(0x7ffff7fad000):C标准库的地址范围。
5.5 链接的重定位过程分析
hello与hello.o的关键差异:
未解析符号:hello.o中printf和sleep的调用地址为0x0。
重定位后:hello中printf和sleep指向PLT(过程链接表)条目:
链接的过程:
链接的步骤:
(1)符号解析
(2)重定位
重定位节和符号定义
重定位符号引用(以hello.o为例):
通过可重定位条目计算地址
修改符号引用
5.6 hello的执行流程
步骤 1:编译并启动调试
步骤 2:设置断点并运行
步骤 3:跟踪执行流程
1.入口点 _start:
2.调用 __libc_start_main:
_start 调用 __libc_start_main,传入 main、argc、argv 等参数:
__libc_start_main 负责初始化环境并调用 main。
3.进入 main 函数:
4.执行 main 函数体:
参数检查、循环打印、调用 sleep 和 getchar。
5.返回并调用 exit:
main 返回后,控制权回到 __libc_start_main,最终调用 exit:
关键函数调用链
步骤 | 地址 | 函数/操作 |
1. 程序入口 | 0x4010f0 | _start |
2. 调用初始化函数 | 0x401118 | call __libc_start_main |
3. 初始化环境 | 0x7ffff7dfd0d0 | __libc_start_main |
4. 进入用户代码 | 0x4011d6 | main |
5. 调用库函数 | 0x401251 | call printf@plt |
6. 主函数返回 | 0x401285 | retq |
7. 清理并退出 | 0x7ffff7e05a40 | exit |
总结:
通过 gdb 跟踪,hello 的执行流程如下:
加载阶段:由 _start 初始化环境,调用 __libc_start_main。
主逻辑:__libc_start_main 调用 main,执行用户代码。
终止阶段:main 返回后,__libc_start_main 调用 exit 清理资源并退出。
关键地址与函数调用完全符合预期,验证了程序从启动到终止的完整生命周期。
5.7 Hello的动态链接分析
PLT条目:printf@plt通过GOT(全局偏移表)间接跳转到动态库中的实际函数地址。
5.8 本章小结
链接生成可执行文件hello,完成符号解析和地址重定位。
第6章 hello进程管理
6.1 进程的概念与作用
进程是程序在内存中的动态执行实例,包含资源与状态;其作用是支持多任务并发执行,隔离程序运行环境并提升系统资源利用率。
6.2 简述壳Shell-bash的作用与处理流程
Shell(Bash)的作用:
1.用户交互接口
Shell 是用户与操作系统内核之间的桥梁,接收用户输入的命令并执行,返回结果。
2.命令解释器
解析用户输入的命令(如 ls -l),将其转换为系统可理解的指令。
3.脚本编程支持
提供脚本语言功能(如条件判断、循环),允许自动化任务(如 if 语句、for 循环)。
4.环境管理
维护环境变量(如 PATH)、别名(alias)和配置文件(如 .bashrc),定制用户工作环境。
5.I/O 重定向与管道
支持输入输出重定向(>、<)和管道(|),实现数据流的灵活处理。
Shell(Bash)的处理流程:
- 接收用户输入:
Bash从标准输入接收用户输入的命令,这些命令可以是单独的命令,也可以是一系列命令组成的脚本。
- 解释执行:
Bash解释用户输入的命令,并根据命令的类型进行相应的处理。例如,如果是内置命令,Bash会直接在自身中执行;如果是外部命令,Bash会通过系统调用来执行对应的可执行文件。
- 系统调用:
对于外部命令,Bash会使用系统调fork来创建新的进程,并加载并执行对应的可执行文件。
6.3 Hello的fork进程创建过程
Hello的fork进程创建过程:
- 调用fork函数创建新进程---在代码段中添加fork函数
- 检查fork返回值
fork函数在执行时会返回两次。在父进程中,fork函数返回子进程的PID(进程ID),而在子进程中,fork函数返回0。
6.4 Hello的execve过程
execve替换进程镜像,加载hello的代码和数据段。
调用execve需要传入3个参数,无返回值。
观察execve的参数传递:
6.5 Hello的进程执行分析进程的调度与状态切换
6.5.1. 进程调度机制
(1) 进程调度
进程中内核可以决定抢占当前进程,重新开始先前被抢占的进程的决策---由内核的调度器决定---内核调度通过上下文切换的机制转移到新的进程。
- 上下文切换
保存当前进程的上下文
恢复某个先前被抢占进程的上下文
将控制传递给这个新恢复的进程
6.5.2. 进程状态切换
进程在生命周期中会经历以下状态:
状态 | 描述 |
创建 | 进程被创建(如通过 fork()),但尚未就绪。 |
就绪 | 进程已准备好运行,等待 CPU 调度。 |
运行 | 进程正在 CPU 上执行。 |
阻塞 | 进程因等待 I/O、信号等事件暂停(如 printf 输出可能涉及系统调用阻塞)。 |
终止 | 进程执行完毕或被终止,释放资源。 |
监控进程状态:
查看CPU占用(因sleep(3),CPU占用率接近0%)
查看进程状态:
状态为"S"(睡眠,因sleep(3))
6.6 hello的异常与信号处理
6.6.1 hello运行会面对的异常
1.中断--异步(由IO设备(CPU外部)产生)
处理过程:
2.陷阱--同步(CPU执行当前指令的结果)
陷阱是故意触发的异常--指令结果
陷阱最重要的用途为用户程序和操作系统内核之间提供一个类似函数的接口
(系统调用指令syscall)
3.故障--同步
错误情况--可由故障处理程序修复(非故意)
4.终止--同步
不可恢复的错误--通常由硬件引起(存储器异常)
6.6.2测试信号处理行为,运行程序并发送信号:
1.回车--没有影响
2.Ctrl-Z---挂起
3.查看挂起进程(jobs)
4.恢复进程(fg)
5.kill---终止挂起的作业
6.Ctrl-C---立即终止进程
(以下格式自行编排,编辑时删除)
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
6.7本章小结
使用 strace 确认 fork 和 execve 的调用流程。
通过 pstree 和 ps 查看进程父子关系和状态。
通过信号控制进程生命周期。
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:逻辑地址是指程序中使用的地址。逻辑地址是相对于进程而言的,每个进程都有自己独立的逻辑地址空间。
线性地址:线性地址是CPU生成的地址,它是经过内存管理单元(MMU)处理后的地址。
虚拟地址:虚拟地址是操作系统管理的地址空间,它包含了所有进程的逻辑地址空间。每个进程都拥有自己的虚拟地址空间,这样可以使得每个进程都认为自己在访问一个独立的内存空间,从而提高了进程之间的隔离性。
物理地址:物理地址是实际存储在内存芯片上的地址。当程序执行时,虚拟地址最终需要被映射到物理地址上,才能进行实际的内存读写操作。
在计算机系统中,内存管理单元(MMU)负责逻辑地址到物理地址的转换工作,它利用页表等数据结构来实现虚拟地址到物理地址的映射。这种映射过程涉及到虚拟内存、分页、分段等技术,从而实现了对内存的灵活管理和保护。
运行hello并查看内存映射:
其中:
00400000-00401000 r--p ...... 是代码段
7f66d14ab000-7f66d14cd000 r--p ...... 是数据段
7.2 Intel逻辑地址到线性地址的变换-段式管理
逻辑地址转换公式:线性地址 = 段基址 + 偏移量。
- 查看段寄存器(利用gdb)
- 段式地址转换
(1) 段选择符解析
根据段选择符的 TI位 确定使用GDT还是LDT。
根据 索引 计算描述符在表中的偏移:偏移 = 索引 × 8字节。
从GDTR/LDTR寄存器获取GDT/LDT的基地址,定位到具体段描述符。
(2) 段描述符加载
将段描述符加载到CPU的段描述符缓存(隐藏部分)中,包括:
基地址(Base)
段限长(Limit)
属性字段(如DPL、G、D/B等)
(3) 地址有效性检查
特权级检查:比较当前CPL(当前特权级)与段描述符的DPL(描述符特权级)和RPL。
段限长检查:确保偏移量 ≤ 段限长(可能受粒度位影响)。
存在位检查:若P=0,触发段不存在异常。
(4) 线性地址计算
线性地址 = 基地址(Base) + 偏移量(Offset)
7.3 Hello的线性地址到物理地址的变换-页式管理
MMU通过页表可以计算物理地址,页大小通常为4KB。
7.4 TLB与四级页表支持下的VA到PA的变换
将虚拟地址分为5部分(以48位地址为例):
(1)PML4索引(9位)
(2)PDPT索引(9位)
(3)PD索引(9位)
(4)PT索引(9位)
(5)页内偏移(12位)
2. 四级页表查找:
CR3寄存器:提供PML4表基地址。
逐级查找:
用PML4索引找到PDPT表基地址。
用PDPT索引找到PD表基地址。
用PD索引找到PT表基地址。
用PT索引找到物理页框基地址。
3. 计算物理地址:
物理地址 = 物理页框基地址 + 页内偏移
4. TLB加速
命中TLB:直接返回物理地址。
未命中TLB:执行完整四级页表查找,并更新TLB。
5. 现代优化
大页:减少TLB压力(如2MB/1GB页)。
透明巨页:自动合并小页为大页。
扩展页表:支持虚拟化环境。
7.5 三级Cache支持下的物理内存访问
分体设计:L1通常分为指令缓存(L1i)和数据缓存(L1d),减少指令与数据竞争。
命中时间:1-3个时钟周期,容量通常为32KB-64KB。
2.L2/L3逐级查找:
L2缓存:私有或共享,容量256KB-1MB,命中时间约10周期。
L3缓存:多核共享,容量可达数十MB,命中时间约30-50周期。
3.主存访问:
总线传输:通过内存控制器访问DRAM,延迟约100纳秒以上。
预取机制:CPU预测访问模式,提前加载数据到缓存(如顺序流预取)。
4.替换策略:
伪LRU:近似算法平衡复杂度与准确性,如树形PLRU。
缓存行:固定大小(通常64字节)的数据块,减少标签匹配次数。
7.6 hello进程fork时的内存映射
fork创建子进程时,复制父进程的页表,写时复制(Copy-on-Write)。
Copy-On-Write(COW):父进程页表项标记为只读,子进程共享物理页。
内核数据结构:复制父进程的mm_struct和vm_area_struct,但页表项指向同一物理页。
2.写操作触发复制:
缺页异常:任一进程写COW页时触发异常,内核分配新页。
复制内容:将原页内容复制到新页,更新进程页表项为独立可写。
3.进程独立性:
文件映射:文件映射区域(如mmap)保持共享,直到文件内容被修改。
7.7 hello进程execve时的内存映射
execve调用:替换当前进程镜像为hello,加载代码段和数据段。
1.清空地址空间:
释放资源:关闭打开的文件描述符(除STDIN/OUT/ERR),释放堆、栈、映射区域。
保留凭证:保留进程的UID、GID等安全上下文。
2.加载可执行文件:
解析ELF头:读取程序头表(Program Header Table),确定各段(Segment)的虚拟地址和大小。
按需加载:仅加载必要段(如.text、.data),.bss段初始化为零。
3.建立内存映射:
匿名映射:为堆栈分配匿名内存,设置初始的栈帧(包含argc、argv、envp)。
动态链接:若需动态链接(如glibc),映射ld.so并解析依赖库。
4.跳转入口点:
设置EIP:将CPU指令指针指向ELF头中e_entry字段指定的入口地址。
7.8 缺页故障与缺页中断处理
非法地址:访问未映射的虚拟地址(如空指针解引用)。
合法但未驻留:页面在磁盘交换区或文件映射中未加载。
2.内核介入:
保存上下文:保存寄存器、程序计数器等现场信息。
安全检查:验证访问权限(如用户态是否可访问内核页)。
3.分配物理页:
页面置换:若物理内存不足,运行LRU等算法选择淘汰页。
写时复制:处理COW页的分离(如fork后的写操作)。
4.更新页表:
设置PTE:将物理页框地址填入页表项,标记P=1(存在)、R/W权限。
填充数据:若为文件映射缺页,从磁盘读取数据到物理页。
5.恢复执行:
重试指令:返回用户态,重新执行触发缺页的指令(此时TLB已更新)。
7.9动态存储分配管理
brk系统调用:调整程序断点(Program Break),扩展堆的末尾。
mmap匿名映射:大内存分配(如超过128KB)直接通过mmap创建私有匿名映射。
2.空闲块管理:
显式链表:维护空闲块链表,记录大小和状态(如glibc的ptmalloc)。
隐式空闲链表:通过边界标记(Boundary Tags)快速遍历空闲块。
3.分配策略:
首次适应:从堆起始位置查找第一个足够大的空闲块。
最佳适应:查找最接近请求大小的空闲块,减少碎片。
4.合并碎片:
立即合并:释放块时检查前后块是否空闲,若空闲则合并。
延迟合并:在分配时合并,减少频繁操作的开销。
5.线程缓存(TCache):
局部缓存:每个线程维护小对象(如<=64KB)的缓存,减少锁竞争。
批量分配:从全局堆批量获取对象,提高多线程性能。
7.10本章小结
Hello的内存管理依赖虚拟内存机制,通过页表和TLB高效转换地址。
结论
Hello程序从编写到执行的完整生命周期深刻体现了计算机系统各层次的高效协同与抽象管理机制。其过程可总结如下:
预处理:通过宏替换、头文件展开和条件编译,将hello.c转换为纯净的hello.i,消除外部依赖,为后续编译提供基础。
编译:编译器将hello.i转换为汇编代码hello.s,完成语法分析、中间代码生成与优化,生成与机器指令紧密相关的低级表示。
汇编:汇编器将hello.s转换为可重定位目标文件hello.o,包含机器指令和符号表,但尚未解决外部引用。
链接:链接器将hello.o与动态库(如libc.so)结合,完成符号解析与地址重定位,生成可执行文件hello,确保程序能够在虚拟地址空间中正确运行。
进程管理:Shell通过fork创建子进程,execve加载hello至内存,CPU调度执行,最终由操作系统回收资源,完成020(Zero to Zero)过程。
这一过程中,计算机系统通过分层抽象(如虚拟内存、进程隔离)与硬件-软件协同(如TLB加速地址转换、动态链接优化)实现了高效资源管理。例如,页式存储与写时复制(COW)技术显著减少了进程创建的开销;动态链接库(PLT/GOT)则平衡了内存占用与灵活性。
创新理念:未来可探索基于编译期预链接(Pre-linking)优化动态库加载效率,或引入更智能的页表缓存策略(如基于机器学习的TLB预取),以进一步提升系统性能。
附件
文件名 | 作用 |
hello.i | 预处理后的C代码,包含展开的头文件与宏替换结果。 |
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.
[7] Randal E. Bryant, David R. O'Hallaron. Computer Systems: A Programmer's Perspective (3rd Edition). Pearson, 2016.
[8] Brian W. Kernighan, Dennis M. Ritchie. The C Programming Language (2nd Edition). Prentice Hall, 1988.
[9] GCC Manual. GNU Compiler Collection. GCC online documentation- GNU Project
[10] Intel® 64 and IA-32 Architectures Software Developer Manuals. Volume 3: System Programming Guide. 2023.