【第 7 周】深入理解计算机系统共读心得体会

请大家以回帖的方式将你在共读第一周的心得体会用你自己的话表达出来。

样例:

所在小组

第一组

组内昵称

张三

你的心得体会

可以基于不同知识点进行,有更新请在原回贴更新,每人每周只发一个帖子

一段自己的阐述

第二段自己的阐述

所在小组

第五组

组内昵称

锦锐

你的心得体会

ECF = Exceptional Control Flow

Concept

  • Processors, a cpu simply reads and executes a sequence of instructions.

Control Flow

For program state

  • Jumps and branches
  • Call and return

there are two ways can change the control flow . But it is insufficient for a useful system.

For system state

An exception is a transfer of control to the os kernel in response to some event.

  • exceptional control flow

it can be classify into two sets: low level mechanisms and higher level mechanisms.

concept

Process graph example

Modeling for with process graphs

  • Each vertex is the execution of a statement
  • a -> b means a happens before b
  • Edges can be labeled with current value of variables
  • printf vertices can be labeled with output
  • Each graph begins with a vertex with no inedges

Any topological sort of the graph corresponds to a feasible total ordering

reaping

  • when process terminates, it still consumes system resources.

所在小组:静默组

组内昵称:维钢、

心得体会:

信号

  • 信号术语
    • 发送信号:内核通过更新目的进程上下文中的某个状态,发送一个信号给目的进程。
    • 接收信号:当目的进程被内核强迫以某种方式对信号的发送做出反应时,它就接收了这个信号。进程可以忽略这个信号,终止或者通过执行一个称为信号处理程序的用户层函数捕获这个信号。
    • 一个发出而没有被接收的信号叫做待处理信号,一种类型至多有一个待处理信号。进程可以选择性的阻塞某种信号,当一种信号被阻塞时,它仍可以被发送,但是产生的待处理信号不会被接收,直到进程取消对此信号的阻塞。
    • 内核为每个进程在pending位向量中维护着待处理信号的集合,在blocked位向量中维护着被阻塞的信号集合。
  • 发送信号
    • 进程组:每个进程都属于一个进程组,可以使用getpgrp函数来获取当前进程的进程组ID,使用setpgid函数来改变自己或者其他进程的进程组。
    • 用/bin/kill程序发送信号,/bin/kill程序可以向另位的进程发送任意的信号。如果向负的PID中发送信号会发送到进程组PID中每个进程。
    • 从键盘发送信号。在键盘上输入Ctrl+C会导致发送一个SIGINT信号到前台进程组中的每个进程,结果是终止前台作业。
    • 用kill函数发送信号。程序可以调用kill函数发送信号给其他进程。
    • 用alarm函数发送信号。可以根据参数来定时向调用进程发送一个SIGALRM信号。
  • 接受信号
    • 当进程从内核模式切换到用户模式时,它会检查进程的未被阻塞的待处理信号的集合,如果集合非空,则会通常选择最小的信号并强制进程接收此信号。
    • 每个信号类型都有一个预定义的默认行为:
      • 进程终止。
      • 进程终止并转储内存。
      • 进程停止(挂起)直到被SIGCONT信号重启。
      • 进程忽略该信号。
    • 进程可以通过signal函数修改和信号相关联的默认行为。唯一例外的是SIGSTOP和SIGKILL,它们默认行为是不能被修改的。
    • 当一个进程捕获了一个类型为k的信号时,会调用为信号k设置的处理程序。

虚拟内存

当你虚拟化一个资源时,你向该资源的用户显示该资源的一些不同类型的视图,你通常会呈现某种抽象或某种不同的资源视图,你可以通过介入对该资源的访问过程来实现这一点。

  • 对于memory,我们通过一块称为MMU的内存管理单元的硬件来实现虚拟内存地址到物理地址到地址转换

  • 为什么要实现虚拟内存。

    • 第一,虚拟内存使用DRAM作为存储在磁盘上的实际数据的缓存。仅仅将虚拟地址空间的一部分实际存储在物理存储器中。
    • 第二,虚拟内存大大简化了内存管理。
    • 第三,它允许我们对访问内存进行保护。
  • Tool for Caching

    • 虚拟内存是存储在磁盘上的一个字节序列,物理内存可以看作是这个序列的一个缓存。

    • 从概念上讲,虚拟内存可以看作存储在磁盘上的一系列页面,这就叫做虚拟页面。一些页面缓存在物理内存中,一些页面没有缓存,它们仍然在磁盘上。大多数页都是未分配的。

    • 记录虚拟页面位置的数据结构称为页表,页表存在内存中,由内核维护,是每个进程上下文的一部分,所以每个进程都有自己的页表。 页表将虚拟页映射到物理页。

    • 在任意时刻,虚拟页面的分为三种:

      • 未分配的:VM系统还未分配(或者创建)的页。未分配的块没有任何数据和它们相关联,所以也就不占任何磁盘空间。
      • 缓存的:当前已缓存在物理内存中的已分配页。
      • 未缓存的:未缓存在物理内存中的已分配页。

      地址翻译硬件(MMU)在将虚拟地址转换为物理地址时,都会读取页表。页表每一行表示一个虚拟页面的信息,包括有效位和物理页号(磁盘地址),有效位表示此物理页是否被缓存,物理页号表示具体位置(没有分配的此栏值为null)。

    • 当访问一个虚拟页面时,操作系统通过地址翻译硬件读取页表,找到对应行后,就能知道对应空间的位置和是否缓存在物理内存中。如果有效位为0,表示缓存没有命中,我们称之为缺页。

    • 缺页会触发一个缺页异常,缺页异常调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,如果牺牲页被修改过,则牺牲页会被拷贝回磁盘,接着内核会将磁盘上需要访问的页面复制到内存牺牲页的位置上,并更新页表,这时异常处理程序执行完毕返回,返回后会重新启动导致缺页的指令,该指令会重续将虚拟地址发送给地址翻译硬件,之后由于已经缓存了目的页面,也就可以由地址翻译硬件正常处理了。

    • 当我们调用malloc分配空间时,操作系统会分配在磁盘上分配对应页面,之后会将地址更新到页表中,使页表中对应项指向磁盘上新创建的页面。所以我们在调用malloc分配空间后,这个空间实际是在磁盘上,当我们访问这个空间时,会触发一个缺页异常之后,这个空间才会被缓存到物理内存中。

  • 虚拟内存作为内存管理的工具。

    • 简化链接。操作系统为每个进程提供了一个独立的页表,因而也就是一个独立的虚拟地址空间。独立的地址空间允许每个进程的内存映像使用相同的基本格式,而不用管代码和数据实际存放在物理内存何处。
    • 简化加载。虚拟内存使得容易向内存中加载可执行文件和共享对象文件。要把目标文件中.text和.data节加载到一个新创建的进程中,Linux加载器位代码和数据段分配虚拟页,把它们标记成未缓存的,将页表条目指向目标文件中适当位置。
    • 简化共享。独立地址空间为操作系统提供了一个管理用户进程和操作系统自身之间共享的一个机制。
    • 简化内存分配。虚拟内存为向用户进程提供一个简单的分配额外内存的机制。一个用户进程中的程序要求额外的内存空间时,操作系统分配一个适当数字的连续虚拟内存页面,并将它们映射到物理内存中随机位置的任意个物理页面而不需要连续的。
  • 虚拟内存作为内存保护的工具

    我们在通过虚拟内存访问一片内存空间的时候,就会通过页表进行地址翻译来获得真正的物理地址,这样我们只需要在页表里添加几项许可位就可以通知对一个虚拟页面的访问,我们在页表里添加三个许可位,分别为:SUP,READ和WRITE。这三个许可位分别表示是否需要在内核模式下访问,是否可读和可写,这样我们在通过页表进行访问内存的时候就可以同时来检查是否满足访问的条件,如果一条指令违反了这些许可条件,那么CPU就会触发一个一般保护故障,将控制传递给一个内核中的异常处理程序,Linux shell一般将这种异常报告为“段错误(segmentation fault)”。

所在小组

第三组

组内昵称

h0n9xu

你的心得体会

参见chapter08, chapter09

  1. 物理内存可以认为是一个非常大的二进制数组,但是对现代CPU可见的内存是虚拟内存(当然C/C++和汇编程序也是),而不是物理内存。物理内存到虚拟内存的映射直接由MMU做了,CPU是无感的。

  2. 书里为什么说,将VM作为缓存工具时,从概念上讲VM是存放在磁盘上的数组,个人理解:

    • coredump都在磁盘上,当还原现场的时候,把他们当做VM的映像原封不动还原,并建立页表PTE和对应磁盘读取位置的连接,至于什么时候从磁盘读取哪个块,如果物理内存满了淘汰哪个块,OS会处理
    • 当程序请求的内存使用量大于实际的物理内存时,会发生换页,把被淘汰的页持久化到磁盘,再把新页加载进物理内存。作者认为这实际上是使用主存对程序所使用的磁盘数据进行了缓存
    • 当程序需要分配新的虚拟内存页时,只需要在磁盘的VM上创建空间并将新的PTE指向这个空间即可(当然,这是发生在请求的虚拟内存大于物理内存,虚拟内存不够用的情况下)
  3. 虚拟内存机制简化了很多实现:

    • fork,由于每个进程都有个页表,带COW的fork的核心就是复制页表,让父子进程的页表都指向相同的内存块/磁盘块(所以redis的持久化任务通过fork实现,成本低),更具体的实现书后面会讲到
    • malloc,只需要动指针就行了并返回起始地址,对malloc拿到的内存进行写入的时候会自动触发缺页中断,更具体的实现书后面会讲到
    • exec,加载器根本不需要从磁盘拷贝东西到内存,只需要将他们的代码段和数据段分配PTE并指向对应的磁盘位置就行,更具体的实现书后面会讲到
    • 通过把连续的虚拟页面映射到不连续的物理页面上和缺页中断等机制,让每个进程误以为自己可以占用了计算机的全部内存(而且还是连续的),这些复杂的内存管理对进程来说是完全透明的,简化了编程模型
  4. 除上述之外,虚拟内存还可以提供缓存磁盘文件的功能,缓存块大小就是页的大小(一般是4K)

所在小组

第七组

组内昵称

杨文

你的心得体会

  1. 异常是异常控制流的一种形式,一部分由硬件实现,一部分由操作系统实现。
  2. 异常表是一张跳转表,其中表目k 包含k的处理程序代码的地址。
  3. 异常分为四类:中断(来自I/O设备的信号)-异步发生、陷阱(有意的异常)-同步发生、故障(潜在可恢复的错误)-同步发生和终止(不可恢复的错误)-同步发生
  4. 进程总是处于三种状态:运行、停止、终止
  5. 每个信号都有预定义的默认行为:进程终止,进程终止并转储内存,进程停止(挂起)直到被 SIGCONT 信号重启,进程忽略该信号
  6. 非本地跳转,将控制直接从一个函数转义到另一个当前正在执行的函数,而不需要经过正常的调用-返回序列。
  7. 虚拟内存是硬件异常、硬件地址翻译、主存、磁盘文件和内核软件的完美交互,为每个进程提供了一个大的、一致的和私有的地址空间。
  8. 虚拟内存提供了三个重要的能力:1. 它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据。2. 为每个进程提供了一致的地址空间,从而简化了内存管理 3. 保护了每个进程的地址空间不被其他进程破坏。

所在小组

第七组

组内昵称

陈盛华

心得体会

用一个例子去理解虚拟内存,在计算机早期的时候,普通家用电脑的内存大小只有512MB,每个软件使用内存都得小心翼翼的,生怕占用内存多了被吐槽,但即使是这样子,在运行多个软件的时候,还是会出现内存不足,为此虚拟内存就被提上日程了。

虚拟内存主要的目标,分别是读取页和置换页。

  • 读取页把软件实际运行的部分页装入内存。

  • 置换页把软件部分不用的页置换到外存。

在32位的系统中,一个指针长度是4个字节,寻址范围在0x00000000~0xFFFFFFFF,但是在512MB大小的物理内存中寻址范围在0x00000000~0x1FFFFFFF。

虚拟内存实现的方式,分别是分段和分页。

  • 分段的目标是把虚拟内存分段映射到物理内存
  • 分页的目标是解决分段的效率不高,采用粒度更小的方法。一般大小是4KB或者4MB,PC大部分是4KB。

所在小组

第五组

组内昵称

chensongbin

心得体会

Chaper8

Subject: Exceptional Control Flow 异常控制流

Date: 2020-11-08

异常

  • 系统启动时候,会初始化异常表。系统中每种可能的异常都分配了异常号。a)处理器设计者分配的异常号:被零除、缺页、内存访问违法、断点、算术溢出。b)操作系统内核的设计者分配的异常号:系统调用、来自外部I/O设备的信号
  • 通过异常号是如何定位,异常处理程序的?

  • 异常处理程序运行在内核模式下,执行过程中使用的是内核栈
  • 异常的分类:
    • 中断:a) 处理器外部的I/O设备(eg:网络适配器、磁盘控制器、定时器芯片),通过向CPU上的一个引脚发信号,并将异常号放在系统总线上,来触发中断。b) CPU每次执行完指令之后,发现引脚信号变高了,就会根据异常号调用异常处理程序
    • 陷阱(系统调用是通过陷阱实现的):由指令引起的,指令syscall n,该指令根据异常表,处理器开始执行陷阱处理程序,这个程序会解析相应的参数,并调用适当的内核程序
    • 故障:由指令引起的,可能被故障处理程序修正。eg:缺页异常
    • 终止:由指令引起的,不可恢复的致命错误。eg:硬件错误,DRAM或者SRAM位损坏

思考如何自己实现一个自定义的系统调用:

  • 系统调用是通过执行syscall指令实现的
  • 该指令会触发异常,会去异常表找到对应的异常处理程序
  • 这个异常处理程序,解析相应的参数
  • 每个系统调用都会有一个唯一整数,对应内核中的跳转表的某一项,可以确定系统调用代码的地址
  • 因此我们可以在内核跳转表中增加一个条目,并在相对应的内核代码位置增加我们自己的系统调用

系统调用使用到的寄存器:a)%rax存储系统调用号,寄存器%rdi,%rsi,%rdx,%r10,%r8,%r9存储最多6个参数。b)从系统调用中返回时,寄存器%rcx和%r11都会被破坏,%rax包含返回值

再次回顾进程

  • 通过某个控制寄存器中的一个模式位来区分 用户模式和内核模式
  • 进程从用户模式到内核模式的唯一方法就是通过异常:中断、陷入系统调用、故障、终止

再次回顾上下文切换

我的理解:上下文切换是一个动词,理解它,只要回答“谁在什么时候干了什么?“

调度器(scheduler)是在执行系统调用时、或者发生中断的时候,如果满足一定条件,就执行上下文切换

  • 谁?上下文切换是通过调度器(schedule)实现的
  • 在什么时候?执行系统调用时候,可能会发生上下文切换,中断也可能引发上下文切换
  • 干了什么?1)保存当前进程的上下文 。2)恢复先前某个被抢占的进程被保存的上下文。3)将控制传递给这个新恢复的进程,也就是CPU开始执行该进程的指令

内核为每一个进程维护一个上下文:当前进程使用到的寄存器值、程序计数器、用户栈、内核栈等各种内核数据结构。eg:页表、进程表、文件描述符表

信号

  • 常见Linux信号
    • Ctrl+C 内核会发送SIGINT(号码2)
    • 子进程终止时候,内核会发送SIGCHLD(号码17)给父进程
  • 信号产生:a) 内核检测到一个事件eg:除零错误或者子进程终止。b)使用kill 系统调用
  • 信号存储:进程接收到的信号存储在pedding位向量中,blocked位向量维护着被阻塞的信号
  • 何时处理信号:进程从系统调用中返回或是完成了一次上下文切换,会检查(pedding & ~blocked),是否存在需要处理的信号

所在小组

第四组

组内昵称

Helios

心得体会

信号

Linux查看信号种类kill -l.

查看信号的解释man 7 signal

一旦信号产生,有下面三种操作:

  • 执行默认操作
  • 捕捉信号
  • 忽略信号

信号处理还有有下面三种类别(sa_flags):

  • SA_ONESHOT:捕捉到信号之后只执行一次
  • SA_NOMASK:当处理这个信号的时候来了其他信号,去处理其他信号
  • SA_INTERRUPT,清除了SA_RESTART:在进行系统调用的时候,来了信号处理完了, 返回给应用一个错误码,让人家自己去搞
  • SA_INTERRUPT,没有清除SA_RESTART:自动重启,但是有的时候比较尴尬,当用户输入的时候,系统调用处理字符的时候来了信号,再重启,还得让用户重新输入一遍

可靠信号和可靠信号

linux早期的信号是从unix继承过来的,32号一下的是不能排队的,只有32号以后才能排队,即不会丢失了。

# kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX

signal.c

编译gcc signal.c
开两个终端:
第一个终端执行:

# ./a.out
My PID is 8386

然后第二个终端连续发三个kill -SIGUSR1 8386

观察第一个终端的变化:

# ./a.out
My PID is 8386
SIGUSR1 received
SIGUSR1 received
read is interrupted by signal

我们发现丢了一个,这个很正常,符合我们的认知。

当我们把程序改为59号,也就是SIGRTMAX-5信号,看看

signal-59.c

第一个终端执行:

# ./a.out
My PID is 14232

然后第二个终端连续发三个kill -SIGRTMAX-5 14232

观察第一个终端的变化:

# ./a.out
My PID is 14232
SIGRTMAX-5 received
SIGRTMAX-5 received
SIGRTMAX-5 received
read is interrupted by signal

虚拟内存

内存表示:
virtual_memory

栈在上面,地址向下“增长”;堆在下面,地址向上增长。

 pmap 21939
21939:   ./loop
000055c1e4bfd000      4K r-x-- loop
000055c1e4dfd000      4K r---- loop
000055c1e4dfe000      4K rw--- loop
000055c1e6781000    132K rw---   [ anon ]
00007ff1130d6000   1948K r-x-- libc-2.27.so
00007ff1132bd000   2048K ----- libc-2.27.so
00007ff1134bd000     16K r---- libc-2.27.so
00007ff1134c1000      8K rw--- libc-2.27.so
00007ff1134c3000     16K rw---   [ anon ]
00007ff1134c7000    156K r-x-- ld-2.27.so
00007ff1136e4000      8K rw---   [ anon ]
00007ff1136ee000      4K r---- ld-2.27.so
00007ff1136ef000      4K rw--- ld-2.27.so
00007ff1136f0000      4K rw---   [ anon ]
00007ffc46dc3000    132K rw---   [ stack ]
00007ffc46de8000     12K r----   [ anon ]
00007ffc46deb000      8K r-x--   [ anon ]
ffffffffff600000      4K r-x--   [ anon ]
 total             4512K
# cat /proc/21939/m
map_files/  maps        mem         mountinfo   mounts      mountstats
root@iZ8vbaym9jmge8qd5hlcpiZ:~# cat /proc/21939/maps
55c1e4bfd000-55c1e4bfe000 r-xp 00000000 fc:01 1334149                    /root/linux-c/loop
55c1e4dfd000-55c1e4dfe000 r--p 00000000 fc:01 1334149                    /root/linux-c/loop
55c1e4dfe000-55c1e4dff000 rw-p 00001000 fc:01 1334149                    /root/linux-c/loop
55c1e6781000-55c1e67a2000 rw-p 00000000 00:00 0                          [heap]
7ff1130d6000-7ff1132bd000 r-xp 00000000 fc:01 1188167                    /lib/x86_64-linux-gnu/libc-2.27.so
7ff1132bd000-7ff1134bd000 ---p 001e7000 fc:01 1188167                    /lib/x86_64-linux-gnu/libc-2.27.so
7ff1134bd000-7ff1134c1000 r--p 001e7000 fc:01 1188167                    /lib/x86_64-linux-gnu/libc-2.27.so
7ff1134c1000-7ff1134c3000 rw-p 001eb000 fc:01 1188167                    /lib/x86_64-linux-gnu/libc-2.27.so
7ff1134c3000-7ff1134c7000 rw-p 00000000 00:00 0
7ff1134c7000-7ff1134ee000 r-xp 00000000 fc:01 1188163                    /lib/x86_64-linux-gnu/ld-2.27.so
7ff1136e4000-7ff1136e6000 rw-p 00000000 00:00 0
7ff1136ee000-7ff1136ef000 r--p 00027000 fc:01 1188163                    /lib/x86_64-linux-gnu/ld-2.27.so
7ff1136ef000-7ff1136f0000 rw-p 00028000 fc:01 1188163                    /lib/x86_64-linux-gnu/ld-2.27.so
7ff1136f0000-7ff1136f1000 rw-p 00000000 00:00 0
7ffc46dc3000-7ffc46de4000 rw-p 00000000 00:00 0                          [stack]
7ffc46de8000-7ffc46deb000 r--p 00000000 00:00 0                          [vvar]
7ffc46deb000-7ffc46ded000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

32位的内存分页如下:

32位用两个页就够了

所在小组:静默组

组内昵称:Tang_D

心得体会:

信号

通过改变进程上下文中的某个状态,将信号传递给进程,进程有一个pending,和一个blocked来对接收到的信号进行处理,收到信号后,进程如果没有设置对这个信号的屏蔽等操作,会调用信号处理程序,来对这个信号进行处理。
信号的发送的机制是基于信号组的,信号组的标识和信号组的父进程保持一致。可以通过自定义的signal对信号进行自定义处理
需要注意的是信号是不排队的,如果有一个信号正在处理,另一个相同信号进入,并不会排队,而是简单丢弃。

虚拟内存

虚拟内存,通过对物理内存的抽象给了程序员一块连续的内存空间,同时也使得多个进程互不干扰,主要两个部分,一个是pagetable,一个是mmu,当你代码中读取一个地址时,由mmu进行翻译,但是翻译参照的是pagetable中的内容,也就是操作系统负责处理虚拟内存与物理内存的映射,具体使用虚拟内存时,由mmu负责。
虚拟内存的好处首先时提供了内存的保护,一个用户级进程无法访问内核的地址,其次,使用虚拟内存便于我们管理物理内存,使得内存碎片减少。
pagetable中的PTE有几个标识位,如可读,可写,可运行,有效,然后剩余的bit作为物理地址。

所在小组

第五组

组内昵称

张学广

阅读笔记

第七章-链接

概念 :链接就是将各种代码和数据片段整合到一个文件中。

整体结构

整体对编译后的链接过程进行了详细的介绍,以链接库的不同分为两大部分:静态库链接和共享库链接。

静态库链接介绍的重点为符号的解析和重定位,共享库链接的重点是动态链接器,共享库是被加载到内存中的,动态链接器的作用就是通过重定位完成链接任务(注:动态链接器本身就是个动态库,这个涉及到一个自举的过程)。

后面还介绍了位置无关代码和库打桩的机制,位置无关代码是对动态链接的一种空间优化,而库打桩是我们的调试利器,这里mark一下,以后用到再拿来实践。

其他内容

  1. .bss端的作用:未初始化的全局变量为0,这里作为占位符,在目标文件中不占据实际的磁盘空间(节省空间)。
  2. 符号对应着函数、全局变量或者静态变量
  3. 符号解析就是将符号引用和符号定义关联。
  4. 共享库的目的是为了节省内存空间,多个进程共享内存中的共享代码。
  5. 库打桩:用来追踪调试,可以理解为将共享库的调用替换成你的代码,代码中增加对引用次数以及输入输出值的追踪。

所在小组

第三组

组内昵称

wyhqaq

你的心得体会

  • 信号

    作用:提供一种机制,告诉用户进程发生了异常。

    一个待处理信号最多只能被接受一次,内核会为进程维护一个待处理信号向量集合。

    每个信号类型都有预定义的默认行为:1.进程终止 2.进程终止并转储内存 3.进程停止直到被SIGCONT信号重启 4.进程忽略该信号

    信号处理难点:1.处理程序与主程序并发运行,共享同样的全局变量 2.如何以及何时接受信号的规则有违直觉 3.不同系统有不同的信号处理语义

    安全的信号处理:1.处理程序尽可能简单 2.处理程序中只调用异步信号安全的函数 3.保存和恢复errno 4.阻塞所有的信号,保护对共享全局数据结构的访问 5. 用volatile声明全局变量 6.用Sig_atomic_t声明标志

  • 非本地跳转

    将控制直接从一个函数转移到另一个当前正在执行的函数,而不需要正常的调用-返回序列。类似ijava中的try-catch

  • 虚拟内存

    作用:1. 高效使用主存 2.为每个进程提供一致的地址空间 3.保护每个进程的地址空间不被破坏

    使用:CPU生在一个虚拟地址访问主存,访问之前通过MMU的专用硬件做地址翻译

    核心:

    如果主存不够时,就会产生不停的换页也叫做抖动。利用Linux的getrusage可以检测缺页的数量。

所在小组

第三组

组内昵称

hy

心得体会

所在小组

第六组

组内昵称

黄永平

你的心得体会

1、异常指的是把控制交给系统内核来响应某些事件(例如处理器状态的变化),包括异步异常(中断)、同步异常。
2、异步异常称之为中断(Interrupt),是由处理器外面发生的事情引起的。比较常见的中断有两种:计时器中断和 I/O 中断。
3、同步异常(Synchronous Exception)是因为执行某条指令所导致的事件,分为陷阱(Trap)、故障(Fault)和终止(Abort)三种情况。
4、处理器提供一种机制,限制一个应用程序可以执行的指令以及它可以访问的地址空间范围。这就是用户模式和内核模式。
5、进程从用户模式变为内核模式的唯一方式就是通过诸如中断、故障或陷入系统调用这样的异常。当异常发生时,控制传递给异常处理程序,处理器将模式从用户模式变为内核模式;当返回到应用程序时,处理器将模式从内核模式转变为用户模式。
6、C语言提供了一种用户级异常流控制流形式,称为非本地跳转。非本地跳转是通过setjmp和longjmp函数来提供。

  1. 所在小组:第二组
  2. 组内昵称:跃山
  3. 心得体会:

虚拟内存

物理和虚拟寻址

虚拟内存是对于物理内存的抽象。支持虚拟内存的处理器,通过使用虚拟寻址这种间接方法来引用物理内存。

处理器产生一个虚拟地址,在被发送到物理内存之前,这个地址被翻译成一个物理地址。

  1. 物理寻址
  2. 虚拟寻址

地址空间

虚拟内存的作用

虚拟内存提供了三个重要的功能

  1. 在物理内存中自动缓存最近使用的存放磁盘上的虚拟地址空间的内容。(虚拟内存缓存中的块叫做页)。对磁盘上页的引用会触发缺页,缺页将控制转移到操作系统中的一个缺页处理程序。缺页处理程序将页面从磁盘复制到物理内存缓存。
  2. 虚拟内存简化了内存管理,进而简化了链接,在进程间共享数据,进程的内存分配以及程序加载。
  3. 虚拟内存通过在每条页表中加入保护位,从而简化了内存保护

虚拟内存作为缓存的工具

  1. DRAM缓存的组织结构
  2. 页表
  3. 页命中
  4. 缺页
  5. 分配页面

虚拟内存作为内存管理的工具

虚拟内存作为内存保护的工具

地址翻译

大多数页表条目位于L1高速缓存中,但是TLB的页表条目的片上高速缓存,通常会消除访问在L1上的页表条目的开销。

  1. 结合高速缓存和虚拟内存
  2. 利用TLB加速地址翻译
  3. 多级页表端到端的地址翻译

内存映射

内存映射就是,把虚拟内存和磁盘映射起来,从而提高性能。

  1. 共享对象
  2. fork函数
  3. execve函数
  4. 使用mmap函数的用户级内存映射(如mongoDB及pgsql等db都是通过mmap机制(借助mmap函数实现)来提高性能。)

动态内存分配

  1. 显式分配器。要求应用显式地释放他们的内存块。
  2. 隐式分配器(GC)。自动释放任何未使用或者不可达的块。

为什么要使用动态内存分配?

虽然我们可以使用mmap函数或者munmap函数来创建和删除虚拟内存,但是使用动态内存分配更方便,也有更好的移植性。

分配器的要求和目标

隐式空闲链表

GC

所在小组

第二组

组内昵称

李显良

你的心得体会

信号

信号可能会中断进程内的系统调用,当然linux会自动重启被中断的系统调用;也可以在应用程序中处理或者通过自定义信号处理函数

信号处理的几个问题

  1. 一个进程处理多个信号时,会出现阻塞
  2. 待处理信号不会排队,任意类型信号只有一个待处理,其他待处理的同类型信息会被丢弃
  3. 系统调用可以被中断,如read write accpet这样的系统调用潜在的会阻塞一段较长的时间,被中断的的系统调用在信号处理程序完成时不再继续,而是返回一个错误;当然linux系统会
    自动重启被中断的系统调用,为了编写可移植信号处理,可以自定义信号中断重启处理

fork的高效实现

子进程拷贝父进程的虚拟地址空间;使用了copy on write的技术,在读的情况下两者的物理内存是一致性的;当有写入时,会引起子进程页表缺页异常,拷贝真正需要的数据

虚拟存储

虚拟存储器是由系统自动提供的,它也是一种有限的存储器资源,作为程序员需要深入了解原理才能写高效果程序

虚拟存储器的作用

  1. 主内存中自动缓存最近使用的存放在磁盘上的虚拟地址空间内容
  2. 虚拟存储器简化了存储器管理,进而简化了链接和进程间的数据共享
  3. 虚拟存储器通过在每条页表条目上加入保护位,从而保护了各进程间的安全性

所在小组

第一组

组内昵称

张仁杰

心得体会

信号处理的原则

  • 处理程序要尽可能简单
  • 处理程序中只调用异步信号安全的函数
  • 保存和恢复errno
  • 阻塞所有的信号,保护对共享全局数据结构的访问
  • 用volatile声明全局变量
  • 使用sig_atomic_t声明标志

虚拟内存是对主存的抽象的概念

虚拟内存的重要能力:

  1. 它将主存看作存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传递数据
  2. 它为每个进程提供了一致的地址空间,从而简化了内存管理
  3. 它保护了每个进程的地址空间不被其他的进程破坏

由物理寻址转向虚拟寻址:

在虚拟内存中,都会提供某些手段来控制对内存系统的访问,每次CPU生成一个地址时,地址翻译硬件都会读一个PTE,这个PTE会提供几个许可在虚拟页面内容上,如果违反了这些许可条件,此时会报告为“段错误”

地址翻译的过程 (页面命中)

  1. CPU生成一个虚拟地址,并传递给MMU
  2. MMU生成一个PTE地址,并从高速缓存中得到它
  3. 高速缓存向MMU返回一个PTE
  4. MMU构造物理地址,并把它传送给你高速缓存/内存
  5. 高速缓存/主存返回所有请求的数据字给CPU

所在小组
第二组

组内昵称
梁广超

心得体会
1、虚拟内存作为磁盘的缓存
2、虚拟内存被分割位为大小固定的虚拟页,与物理内存的页帧大小相同
3、虚拟页面分为三个状态:未分配、已缓存以及未缓存
4、虚拟内存是通过页表来判断一个虚拟页面是否已经缓存,如果一个pte存在且其标记位为1则缓存命中
5、DRMA缓存是全相联的,任何虚拟页都可以放在任何物理页中
6、缓存不命中会触发缺页
7、虚拟内存作为内存管理工具可以:
简化链接,简化加载,简化共享
8、虚拟内存作为内存的保护工具,通过标记位,确定一个虚拟地址的操作权限

所在小组

第五组

组内昵称

王传义

你的心得体会

  • 用户自定信号处理函数,回跑到内核去执行吗?内核看不到用户数据呀?

    当进程从内核态切换到用户态的时(内核触发),恢复正常执行之前,

    执行信号处理函数( 用户态执行),信号处理程序又是可中断的。

  • 信号处理程序和主进程上面不是有顺序,为什么还说并发执行?

如果主程序执行系统调用,上下文切换等操作, 有可能信号处理触发执行。

一个函数在执行,中断 执行操作过程中

信号也在执行。从时间断上看是同时在执行

都会操作全局数据,不安全的。

可重入,不能被中断 才是安全的函数。 new printf都不是。

for( 1){i++} ,过程中可能发现信号处理。

  • 虚拟内存 存储在磁盘上,不应该存储在内存吗?

    联想:进程装载可执行程序分配虚拟空间,进程切换时候,这片虚拟空间肯定放到磁盘上,虚拟内存和磁盘挂钩了。

    将之前学到的进程,cpu中断,编译器/链接器,存储器都联系在一起一起了。

    这样体现了 内存是磁盘的缓存的概念,

所在小组

静默组

组内昵称

Han

心得体会

信号处理函数用于与外部程序交互的一种方式,通常会使用SIGUSR1或者SIGUSR2,程序定义好接收到这两种信号后的行为,当外部条件发生变化时,可以通过发送预定义好的信号给程序来执行特定的操作,比如:

  • 软重启所有子进程
  • 重新加载部分资源文件
  • 等等等等

信号处理函数带来的隐藏bug是非常难以定位的,书中提到的errno的问题,在实际项目中有遇到过,程序的异常行为与实际引发问题的地方完全没有关联,导致寻找的方向完全不在正确的逻辑上,其实要是知道这个知识点,很容易往正确的路径上去定位到根本原因。

共享对象大大节约了进程的内存使用,内核的机制保证了复用共享对象(动态库等),其实只会占用一块物理空间,在使用工具命令查看进程占用内存时也要注意,要找到实际占用物理内存。

# cat /proc/<pid>/status   # VmRSS (包含了共享库的内存)
# cat /proc/<pid>/smaps    # Pss (把共享库大小分摊到所有映射的进程)

对于需要频繁进行内存分配和回收的程序比如NoSQL,好的内存分配器直接影响到程序的性能,除了malloc外,常用的还有:

  • jemalloc (facebook)
  • tcmalloc (google)