X

曜彤.手记

随记,关于互联网技术、产品与创业

吉 ICP 备10004938号

《CSAPP 第三版》读书笔记(第 6-8 章)


第六章、存储器层次结构

  1. (Page:400)随机访问存储器(RAM):

  1. (Page:402)DRAM 芯片的数据读取过程:

  1. (Page:402)内存模块:由多个 DRAM 芯片组成。

  1. (Page:403)增强的 DRAM:
  1. (Page:405)总线结构:

  1. (Page:408)磁盘容量的计算公式:

  1. (Page:414)固态硬盘(SSD):

  1. (Page:418)局部性(locality)原理:

- 一个空间局部性很差的典型例子

// 非“行优先”的顺序扫描数组,导致数据引用的位置不连续(C 中数组在内存中是以行优先的形式存放的),因此导致“空间局部性”很差;
int sum(int a[M][N]) {
  int i, j, sum = 0;
  for (j = 0; j < N; ++j) {
    for (i = 0; i < M; ++i) {  // bad: column first.
      sum += a[i][j];
    }
  }
  return sum;
}

- 评价局部性的基本原则

  1. (Page:422)存储器层次结构中各层之间的通信:对于每个 k,位于 k 层的更快更小的存储设备作为位于 k+1 层更大更慢存储设备的缓存。数据总是以“块大小”为传送单元(带来了“空间局部性”优势),在两个层次之间来回复制。通常来说,层次结构中较低层的设备访问时间较长,因此为了补偿这些较长的访问时间,倾向于使用较大的块

- 当程序需要第 k+1 层的某个数据对象时

  1. (Page:425)现代计算机系统中的缓存体系

  1. (Page:426)高速缓存存储器(L1~L3)的组织结构:

- 地址到“标记、索引、偏移”位的映射示例

  1. (Page:431)高速缓存“抖动”:即高速缓存反复地加载和驱逐相同的高级缓存块的组。解决方法:由于地址与高速缓存具体组的映射关系是固定的,因此可以通过适当调整引用数据地址之间的相互关系,来避免同一缓存组被来回驱逐的情况。
  2. (Page:433)组相联高速缓存:每个组保存有多于一个的高速缓存行(1 < E < 高速缓存容量/B)。
  1. (Page:438)“写回”与“写分配”:

-原则:*保持局部性,优先保障高层次高速缓存内的数据更新。一般而言,高速缓存越往下层,越可能使用写回而不是直写。

  1. (Page:438)一个真实的高速缓存层次结构(Intel Core i7):

  1. (Page:440)编写高速缓存友好代码的原则:
  1. (Page:446)存储器山:可用于描述存储系统的性能。
  2. (Page:448)一个性能优化实例:

- 利用局部性

第七章、链接

(*下文中“节”可以与“段”互换)

  1. (Page:464)链接:将各种代码和数据片段收集并组合成一个单一文件的过程
  1. (Page:465)一个示例程序:
// main.c
int sum(int *a, int n);
int array[2] = {1, 2};
int main() {
  int val = sum(array, 2);
  return val;
}

// sum.c
int sum(int *a, int n) {
  int i, s = 0;
  for (i = 0; i < n; i++) {
    s += a[i];
  }
  return s;
}
  1. (Page:466)静态链接:

- 两个主要任务

  1. (Page:467)ELF 可重定位目标文件:

  1. (Page:469).symtab 符号表:

- C 源代码

// main.c
// CLI: clang -O0 -c main.c -o main.o && readelf -a main.o
extern int add(int x, int y);
int global_uninitialized;
int global_initialized_zero = 0;
int global_initialized_non_zero = 10;

int minus(int x, int y) {
  return x - y;
}
int main() {
  static int local_static_uninitialized;
  return add(1, 2) + minus(1, 2);
}

- 对应上述代码的 .symtab 符号表结构

  1. (Page:471)链接器解析多重符号定义的规则:在编译时,编译器向汇编器输出每个全局符号,或是强或是弱。而汇编器把这个信息隐含地编码在可重定位目标文件的符号表中。函数和已初始化的全局变量是强符号,未初始化的全局变量是弱符号

- 一个由于强弱符号规则引发的运行时错误代码

// main.c
#include <stdio.h>
void f(void);
int y = 15212;
int x = 15213;  // 强符号;
int main() {
  f();
  printf("x = 0x%x y = 0x%x \n", x, y);
  return 0;
}
// lib.c
double x;
void f() {
  x = -0.0
}
  1. (Page:476)静态库
  1. (Page:483)ELF 可执行文件:

- Linux X86-64 运行时内存映像

  1. (Page:486)共享库

- GOT 与 PLT 协作流程

  1. (Page:492)库打桩:允许截获对共享库函数的调用,取而代之执行自己的代码。

第八章、异常控制流

  1. (Page:501)异常控制流(Exceptional Control Flow,ECF):现代系统通过使控制流发生突变来对各种情况(异常)做出反应。当处理器检测到有事件发生时,它就会通过一张叫做“异常表”的跳转表(其起始地址存放在“异常表基址寄存器”中),进行一个间接过程调用,到一个专门设计用来处理这类事件的子程序中。异常处理完毕后,根据异常事件类型,会发生:
  1. (Page:504)异常的类别:

* 0~31 的异常号对应的是由 Intel 架构师定义的异常,这些异常通常会被分别映射到 Linux 内核的不同信号上(比如 “SIGSEGV” 信号导致的 “Segmentation Fault”)。

  1. (Page:508)进程:

- 提供给应用程序的抽象

- 用户模式与内核模式

- 上下文切换

- 进程的三种状态

  1. (Page:512)系统调用的错误处理
// strerror 函数返回一个文本串,描述了某个错误的详细信息;
void unix_error(char *msg) {
  fprintf(stderr, "%s: %s\n", msg, strerror(errno));
  exit(0);
}
  1. (Page:513)进程控制:

- 获取进程 IDgetpid/getppid):

#include <sys/types.h>
#include <unistd.h>

pid_t getpid(void);  // 获得调用进程的 PID;
pid_t getppid(void);  // 获得调用进程父进程的 PID;

- 创建和终止进程fork):

int main() {
  pid_t pid;
  int x = 1;
  pid = fork();  // 创建进程;
  if (pid == 0) {
    /* 子进程 */
    printf("child: x=%d\n", ++x);  // "child: x=2".
    exit(0);
  }
  /* 父进程 */
  printf("parent: x=%d\n", --x);  // "parent: x=0".
  exit(0);
}

- 回收子进程waitpid / wait):

- 让进程休眠sleep / pause):

- 加载并运行文件程序execve):

- 新程序用户栈的典型结构,参数和环境变量getenv / setenv / unsetenv):

  1. (Page:526)信号:允许进程和内核中断其他进程。一个信号就是一条消息,它通知进程系统中发生了一个某种类型的事件。

- 发送信号

- 接收信号

  1. (Page:546)非本地跳转:C 语言提供的一种用户级的异常控制流形式,它将控制直接从一个函数转移到另一个当前正在执行的函数,而不需经过正常的“调用-返回”序列。非本地跳转是通过 setjmplongjmp 函数来提供的。


这是文章底线,下面是评论
  暂无评论,欢迎勾搭 :)