CSAPP - Chapter7 链接

7.2 静态链接 为了构造可执行文件,链接器必须完成两个主要任务: 符号解析。将每个符号引用和一个符号定义连接起来。 重定位。编译器和汇编器生成从 0 地址开始的代码和数据节。连接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节。然后修改所有对这些符号的引用,使得他们指向这个内存位置。 7.3 目标文件 可重定位目标文件 .o, .a(.a 就是一堆打包的.o) 可执行目标文件 elf 共享目标文件 .so 7.4 可重定位目标文件 .text 代码段 .rodata 只读数据段,保存字符串,const 数据等。 .data 数据段。全局和静态变量。 .bss 未初始化和初始化为 0 的全局和静态变量。不占空间。 .symtab 符号表,存放程序中定义和引用的函数和全局变量信息。 .rel.text .text 节中需要重定位的代码段。 .rel.data .data 节中需要重定位的数据段,被模块引用或定义的所有全局变量。 .debug 调试符号表。包含程序中定义的局部变量和类型定义,定义和引用的全局变量,原始的 C 源文件。 .line 原始 C 文件中的行号和.text 节中机器指令的映射。 .strtab 字符串表。 7.5 符号和符号表 由当前模块定义并能被其他模块引用的全局符号 其他模块定义并被当前模块引用的全局符号,称为外部符号 只被当前模块定义和引用的的局部符号 下面两个局部静态变量: int f() { static int x = 1; return x; } int g() { static int x = 2; return x; } 两个 x 分别在各自的函数中可见,这两个 x 都会保存到....

2024-08-07 · 5 min

CSAPP - Chapter6 存储器层次结构

6.1 存储技术 6.1.1 随机访问存储器 RAM SRAM DRAM ROM 6.1.2 磁盘存储 6.1.3 固态硬盘 大约十万次写会导致块损坏。 6.2 局部性 6.4 高速缓冲存储器 // todo: 添加截图 高速缓存确定一个请求是否命中,然后抽出被请求字的过程分为三步: 组选择 行匹配 字抽取 6.4.2 直接映射高速缓存 每组只有一行(E=1)。 // todo: 添加截图 6.4.3 组相连高速缓存 1<E<C/B 组选择 和直接映射高速缓存相同。 行匹配和字选择 每组的任何一行都可以包含任何映射到这个组的内存块。 所以高速缓存必须搜索组中的每一行,寻找一个有效行,其标记与地址中的标记相匹配。 行替换 行替换算法: LFU: Least frequently used. 替换过去某个时间窗口引用次数最少的一行。 LRU: Least recently used. 替换最后一次访问时间最久远的一行。 6.4.4 全相连高速缓存 //todo: 插图 E=CB,一个组包含所有行。 组选择 只有一个组,没有组索引。 行匹配和字选择 构建一个又大又快的相连高速缓存很困难,而且很昂贵。因此全相连高速缓存只适合做小的高速缓存,例如虚拟内存中的 TLB。 6.4.5 有关写的问题 写命中 假设我们要写一个已经缓存了的字 w(写命中)。 首先都会先更新高速缓存中的字 w,接着更新低一级的内存中的数据,有两种方式: write-through 直写。立即将 w 的高速缓存块写回到低一层中。 wirte-back 写回。尽可能地推迟更新,只有当替换算法要驱逐这个更新过的块时,才写到低一层去。 写回可以显著减少总线流量,但增加了复杂性,需要额外维护一个 dirty bit,表示这个高速缓存快块是否修改过。...

2024-07-28 · 1 min

CSAPP - Chapter5 优化程序性能

5.1 优化编译器的能力和局限性 gcc 优化等级: -O0:这是默认的优化等级,基本上不进行任何优化,以便于调试。源代码会被尽可能地转化为目标代码,而不会对程序的执行速度进行优化。 -O1:这是一个适度的优化等级,它会在编译时执行一些简单的优化,如常量折叠、死代码删除、循环展开等,但不会影响编译时间太多。这个等级的优化通常不会显著改变程序的行为。 -O2:这是最常见的优化等级,它会执行更多的优化,包括函数内联、循环优化、寄存器分配等,可能会稍微增加编译时间,但是通常可以产生更快的可执行文件。 -O3:这是最高级别的优化,它包含了-O2 中的所有优化,并且会执行更激进的优化策略,如更多的内联、循环展开等。这可能会显著增加编译时间,但同时也可能产生执行效率最高的代码。 -Os:这个优化等级专注于减少输出文件的大小,同时仍然保持一定的运行时性能。它会使用一些特定的技巧来减小代码和数据的大小,适合用于嵌入式系统或资源有限的环境。 -Og:这个优化等级主要用于调试,它会生成更多的调试信息,同时尽量保持源代码和目标代码的一致性,以便于调试。 -Ofast:这是为了追求极致性能的优化等级,它会启用所有可用的优化,并且可能会违反 C/C++语言标准的一些规则,比如浮点运算的精度可能会有所损失。 两种指针可能指向同一个内存位置的情况称为内存别名使用。 比如 void twiddle1(long *xp, long *yp) { *xp += *yp; *xp += *yp; } void twiddle2(long *xp, long *yp) { *xp = 2* *yp; } 在xp和yp指针不指向同一地址时,twiddle1可以被优化成twiddle2,但如果xp == yp,那结果就不一样了,twiddle1该地址的值会变成 4 倍,而twiddle2只是两倍。所以编译器对这种情况不能进行优化。 提供一个 combine1 函数,看看我们能如何来优化它。 // for add #define IDENT 0 #define OP + // for multiply #define INENT 1 #define OP * void combine1(vec_ptr v, data_t *dest) { long i; *dest = IDENT; for (i = 0; i < vec_length(v); i++) { data_t val; get_vec_element(v, i, &val); *dest = *dest OP val; } } int get_vec_element(vec_ptr v, long index, data_t *dest) { if (index < 0 || index >= v->Len) return 0; *dest = v->data[index]; return 1; } long vec_length(vec_ptr v) { return v->len; } 5....

2024-07-21 · 2 min

CSAPP - Chapter3 程序的机器级表示

3.3 数据格式 Intel用术语字来表示16位数据类型。 32位:双字。 64位:四字。 3.4 访问信息 %rbp是帧指针。 生成1字节和2字节数字的指令会保持剩下的字节不变; 生成4字节数字的指令会把高位的4个字节置为0。 3.4.1 操作数指示符 3.4.2 数据传送指令 MOV S, D: S->D 传送指令和的两个操作数不能都指向内存地址。 movabsq: 常规的movq指令只能以表示为32位补码数字的立即数作为源操作数。然后把这个值符号扩展得到64位的值,放到目的位置。 movabsq指令能够以任意64位立即数作为源操作数,并且只能以寄存器作为目的。 MOVZ S, R: 零扩展(S)->R MOVS S, R: 符号扩展(S)->R 这两个指令目的只能是寄存器。 cltq: 符号扩展(%eax)->%rax, 把%eax符号扩展到%rax。 3.4.4 压入和弹出栈数据 练习题 3.2 内存引用总是以4字节寄存器给出,比如%rax。 练习题 3.3 movl %eax, %rdx: 目的寄存器%rdx长度不匹配,应该为%edx。 3.5算术和逻辑操作 图3-10: 3.5.1 加载有效地址 如果%rdx保存的值为x leaq 7(%rdx, %rdx, 4), %rax: 将%rax寄存器的值设为5x+7。 指令形式是从内存读数据到寄存器,但实际上根本没有引用内存。 3.5.3 移位操作 移位量可以是一个立即数,或者放在单字节寄存器%cl中。(只允许以这个特定的寄存器) 3.5.5 特殊的算术操作 //TODO: 3.6 控制 3.6.1 条件码 CF: 进位标志。 ZF: 零标志。...

2024-06-07 · 3 min

CSAPP - Chapter2 信息的表示和处理

2.1.1 十六进制表示法 2的整数幂转化为16进制: 多少次方就有多少个零。 \(2048 = 2^{11}\) \(n = 11 = 3 +4*2\) 因此十六进制表示为0x800。 2.1.2 字数据大小 计算机字长等于指针数据的长度,32/64。 2.1.3 寻址和字节顺序 typedef unsigned char *byte_pointer; void show_bytes(byte_pointer start, size_t len) { size_t i; for (int i = 0; i < len; i++) printf("%.2x\n", start[i]); } int val = 0x87654321 byte_pointer valp = (byte_pointer)&val; show_bytes(valp, 1); // 结果是0x21, 小端法低地址保存的是数据低有效位 const char *s = "12345"; show_bytes((byte_pointer)s, strlen(s)); // 结果是31 32 33 34 35。没有字符串结尾的00,因为strlen不统计结束符。 // 注意字符串会以顺序排列,和字节序无关。 2....

2024-05-25 · 1 min

Operating Systems Three Easy Pieces(OSTEP) - Concurrency

Chapter26 Concurrency: Introduction 线程和进程类似,但多线程程序,共享address space和data。 多线程各自独享寄存器组,切换进程的时候需要上下文切换。 进程上下文切换的时候把上一个进程的寄存器组保存到PCB(Process Control Block)中,线程切换需要定义一个或更多新的结构体TCBs(Thread Control Blocks)来保存每个线程的状态。 线程切换Page table不用切换,因为线程共享地址空间。 线程拥有自己的栈。 26.1 Why use threads? Parallelism。单线程程序只能利用一个CPU核,多线程程序可以并行利用多个CPU核,提高效率。 防止I/O操作导致程序block。即使是单核CPU,多线程程序可以在一个线程执行I/O操作的时候,其他线程继续利用CPU。 如果是CPU密集型工作,单核CPU跑多线程对效率提升不大 26.2~26.4 Problem of Shared data 创建两个线程,分别对全局变量counter加1e7, 最后结果会不等于2e7。 #include <stdio.h> #include <pthread.h> #include "common.h" #include "common_threads.h" static volatile int counter = 0; void *mythread(void *arg) { printf("%s: begin\n", (char *) arg); int i; for (i = 0; i < 1e7; i++) counter = counter + 1; printf("%s: done\n", (char *) arg); return NULL; } int main(int argc, char *argv[]) { pthread_t p1, p2; printf("main: begin (counter = %d)\n", counter); Pthread_create(&p1, NULL, mythread, "A"); Pthread_create(&p2, NULL, mythread, "B"); // join waits for the threads to finish Pthread_join(p1, NULL); Pthread_join(p2, NULL); printf("main: done with both (counter = %d)\n", counter); return 0; } pthread_create: 创建线程。 pthread_join: 阻塞等待线程终止。...

2024-04-01 · 11 min

Operating Systems Three Easy Pieces(OSTEP) - Memory Virtualization

Chapter14 Memory API 14.2 The malloc() call 分配字符串长度的时候要注意:malloc(strlen(s) + 1),多分配一个byte给\0。strlen函数计算长度不会统计\0。 14.4 Common Errors Not Allocating Enough Memory char *src = "hello"; char *dst = (char *) malloc(strlen(src)); // wrong!!! char *dst = (char *) malloc(strlen(src) + 1); strcpy(dst, src); // work properly strcpy拷贝的时候会把\0也拷贝过去,所以分配长度的时候要strlen()+1。 否则多拷贝的一个byte,会写到heap的其他变量所属的地址,可能会导致fault。 Forgetting to Initialize Allocated Memory malloc分配出来的内存,需要初始化,否则可能是随机内存值。 Forgetting To Free Memory malloc分配出来的内存需要free掉。不然长时间运行的进程会导致内存泄漏。 短时间运行就结束的进程可能不会有这个问题,在进程结束后系统会自动回收内存,但free掉不使用的内存仍然是一个好习惯。 Freeing Memory Before You Are Done With It Freeing Memory Repeatedly Chapter15 Address Translation 地址转换 Chapter16 Segmentation 分段 16....

2024-03-27 · 1 min

Operating Systems Three Easy Pieces(OSTEP) - CPU Virtualization

这篇文章是阅读 OSTEP 3~11 的笔记,主要讲的是 Virtualization 中 Virtualize CPU 的部分。 Chapter4 Process 4.2 Process API 一般 OS 会提供以下的进程 API 来操作进程: Create: 创建进程。 Destory: 结束进程。 Wait: Wait a process to stop running. 等待进程结束。 Miscellaneous Control: Suspend/Resume… 休眠,唤醒等等。 Status: 查看进程状态。 4.3 Process Creation 首先 OS 将存储在 disk or SSD 的 program 程序加载进 memory 内存。 这边有两种方式,一种是在运行前把 code 和 static data 全部加载进内存。现代操作系统一般会使用第二种方式,懒加载,只加载即将使用的 code 和 data。 分配栈。 分配堆。 分配三个文件描述符,标准输入 0,标准输出 1,错误 2。 4.4 Process Status 进程的状态有:...

2024-03-21 · 5 min

I2C spec

Reference I2C 为什么要用开漏输出,而不能用推挽输出。 防止短路。两个 push pull 输出相连,如果一个输出高,一个输出低,会短路。 线与。开漏输出有一个输出端输出低,则整条线都为低,所有输出高才为高。 https://www.zhihu.com/question/534999506 https://www.eet-china.com/mp/a87499.html 以上都是针对 multiple masters 多个输出端的,如果是 single master config 的话,推挽输出是否可以? 2 I2C-bus features Only two bus lines are required; a serial data line (SDA) and a serial clock line (SCL). It is a true multi-controller bus including collision detection and arbitration to prevent data corruption if two or more controllers simultaneously initiate data transfer. Serial, 8-bit oriented, bidirectional data transfers can be made at up to 100 kbit/s in the Standard-mode, up to 400 kbit/s in the Fast-mode, up to 1 Mbit/s in Fast-mode Plus, or up to 3....

2023-09-12 · 2 min

Cortex-M3权威指南

Reference 《Cortex-M3权威指南》 《ARMv7-M Architecture Reference Manual》 《Cortex-M3 Technical Reference Manual》 Chapter1 介绍 Cortex-M3处理器(CM3)采用ARMv7-M架构,它包括所有的16位Thumb指令集和基本的32位Thumb-2指令集架构,Cortex-M3处理器不能执行ARM指令集。 CM3的出现,还在ARM处理器中破天荒地支持了“非对齐数据访问支持”。 Chapter2 CM3概览 2.2 Registers CM3有R0-R15,16个registers。R13作为堆栈指针SP。SP有两个,但在同一时刻只能有一个可以看到,这也就是所谓的“banked”寄存器。 绝大多数16位Thumb指令只能访问R0-R7,而32位Thumb-2指令可以访问所有寄存器。 Cortex-M3拥有两个堆栈指针,然而它们是banked,因此任一时刻只能使用其中的一个。 主堆栈指针(MSP):复位后缺省使用的堆栈指针,用于操作系统内核以及异常处理例程(包括中断服务例程) 进程堆栈指针(PSP):由用户的应用程序代码使用。 R14 LR:当调用一个子程序时,由R14存储返回地址。 R15 PC: 指向当前的程序地址。如果修改它的值,就能改变程序的执行流。 2.3 操作模式和特权级别 两种操作模式:handler mode(异常服务程序代码),thread mode(应用程序代码)。 两种特权级别:特权级,用户级。 在CM3运行主应用程序时(线程模式),既可以使用特权级,也可以使用用户级;但是异常服务例程必须在特权级下执行。复位后,处理器默认进入线程模式,特权极访问。 用户级的程序不能简简单单地试图改写CONTROL寄存器就回到特权级,它必须先“申诉”:执行一条系统调用指令(SVC)。这会触发SVC异常,然后由异常服务例程(通常是操作系统的一部分)接管,如果批准了进入,则异常服务例程修改CONTROL寄存器,才能在用户级的线程模式下重新进入特权级。 2.5 存储器映射 总体来说,Cortex-M3支持4GB存储空间,如图2.6所示地被划分成若干区域。 从图中可见,不像其它的ARM架构,它们的存储器映射由半导体厂家说了算,Cortex-M3预先定义好了“粗线条的”存储器映射。 2.6 总线接口 2.9 中断和异常 11种系统异常+5个保留档位+240个外部中断。 编号 类型 优先级 简介 0 N/A N/A 没有异常在运行 1 复位 -3(最高) 复位 2 NMI -2 不可屏蔽中断(来自外部NMI输入脚) 3 硬(hard) fault -1 所有被disable的fault,都将“上访”成硬fault 4 MemManage fault 可编程 存储器管理fault,MPU访问犯规以及访问非法位置 5 总线fault 可编程 总线错误(预取流产(Abort)或数据流产) 6 用法(usage)Fault 可编程 由于程序错误导致的异常 7-10 保留 N/A N/A 11 SVCall 可编程 系统服务调用 12 调试监视器 可编程 调试监视器(断点,数据观察点,或者是外部调试请求 13 保留 N/A N/A 14 PendSV 可编程 为系统设备而设的“可悬挂请求”(pendable request) 15 SysTick 可编程 系统滴答定时器 16-255 IRQ #0~239 可编程 外中断#0~#239 2....

2023-09-06 · 2 min