请大家以回帖的方式将你在共读第一周的心得体会用你自己的话表达出来。
样例:
所在小组
第一组
组内昵称
张三
你的心得体会
可以基于不同知识点进行,有更新请在原回贴更新,每人每周只发一个帖子
一段自己的阐述
第二段自己的阐述
…
请大家以回帖的方式将你在共读第一周的心得体会用你自己的话表达出来。
样例:
第一组
张三
可以基于不同知识点进行,有更新请在原回贴更新,每人每周只发一个帖子
…
第五组
锦锐
there are two ways can change the control flow . But it is insufficient for a useful system.
An exception is a transfer of control to the os kernel in response to some event.
it can be classify into two sets: low level mechanisms and higher level mechanisms.
Any topological sort of the graph corresponds to a feasible total ordering
心得体会:
当你虚拟化一个资源时,你向该资源的用户显示该资源的一些不同类型的视图,你通常会呈现某种抽象或某种不同的资源视图,你可以通过介入对该资源的访问过程来实现这一点。
对于memory,我们通过一块称为MMU的内存管理单元的硬件来实现虚拟内存地址到物理地址到地址转换。
为什么要实现虚拟内存。
Tool for Caching
虚拟内存是存储在磁盘上的一个字节序列,物理内存可以看作是这个序列的一个缓存。
从概念上讲,虚拟内存可以看作存储在磁盘上的一系列页面,这就叫做虚拟页面。一些页面缓存在物理内存中,一些页面没有缓存,它们仍然在磁盘上。大多数页都是未分配的。
记录虚拟页面位置的数据结构称为页表,页表存在内存中,由内核维护,是每个进程上下文的一部分,所以每个进程都有自己的页表。 页表将虚拟页映射到物理页。
在任意时刻,虚拟页面的分为三种:
地址翻译硬件(MMU)在将虚拟地址转换为物理地址时,都会读取页表。页表每一行表示一个虚拟页面的信息,包括有效位和物理页号(磁盘地址),有效位表示此物理页是否被缓存,物理页号表示具体位置(没有分配的此栏值为null)。
当访问一个虚拟页面时,操作系统通过地址翻译硬件读取页表,找到对应行后,就能知道对应空间的位置和是否缓存在物理内存中。如果有效位为0,表示缓存没有命中,我们称之为缺页。
缺页会触发一个缺页异常,缺页异常调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,如果牺牲页被修改过,则牺牲页会被拷贝回磁盘,接着内核会将磁盘上需要访问的页面复制到内存牺牲页的位置上,并更新页表,这时异常处理程序执行完毕返回,返回后会重新启动导致缺页的指令,该指令会重续将虚拟地址发送给地址翻译硬件,之后由于已经缓存了目的页面,也就可以由地址翻译硬件正常处理了。
当我们调用malloc分配空间时,操作系统会分配在磁盘上分配对应页面,之后会将地址更新到页表中,使页表中对应项指向磁盘上新创建的页面。所以我们在调用malloc分配空间后,这个空间实际是在磁盘上,当我们访问这个空间时,会触发一个缺页异常之后,这个空间才会被缓存到物理内存中。
虚拟内存作为内存管理的工具。
虚拟内存作为内存保护的工具
我们在通过虚拟内存访问一片内存空间的时候,就会通过页表进行地址翻译来获得真正的物理地址,这样我们只需要在页表里添加几项许可位就可以通知对一个虚拟页面的访问,我们在页表里添加三个许可位,分别为:SUP,READ和WRITE。这三个许可位分别表示是否需要在内核模式下访问,是否可读和可写,这样我们在通过页表进行访问内存的时候就可以同时来检查是否满足访问的条件,如果一条指令违反了这些许可条件,那么CPU就会触发一个一般保护故障,将控制传递给一个内核中的异常处理程序,Linux shell一般将这种异常报告为“段错误(segmentation fault)”。
物理内存可以认为是一个非常大的二进制数组,但是对现代CPU可见的内存是虚拟内存(当然C/C++和汇编程序也是),而不是物理内存。物理内存到虚拟内存的映射直接由MMU做了,CPU是无感的。
书里为什么说,将VM作为缓存工具时,从概念上讲VM是存放在磁盘上的数组,个人理解:
虚拟内存机制简化了很多实现:
除上述之外,虚拟内存还可以提供缓存磁盘文件的功能,缓存块大小就是页的大小(一般是4K)
第七组
杨文
第七组
陈盛华
用一个例子去理解虚拟内存,在计算机早期的时候,普通家用电脑的内存大小只有512MB,每个软件使用内存都得小心翼翼的,生怕占用内存多了被吐槽,但即使是这样子,在运行多个软件的时候,还是会出现内存不足,为此虚拟内存就被提上日程了。
虚拟内存主要的目标,分别是读取页和置换页。
读取页把软件实际运行的部分页装入内存。
置换页把软件部分不用的页置换到外存。
在32位的系统中,一个指针长度是4个字节,寻址范围在0x00000000~0xFFFFFFFF,但是在512MB大小的物理内存中寻址范围在0x00000000~0x1FFFFFFF。
虚拟内存实现的方式,分别是分段和分页。
第五组
chensongbin
Subject: Exceptional Control Flow 异常控制流
Date: 2020-11-08
系统调用使用到的寄存器:a)%rax存储系统调用号,寄存器%rdi,%rsi,%rdx,%r10,%r8,%r9存储最多6个参数。b)从系统调用中返回时,寄存器%rcx和%r11都会被破坏,%rax包含返回值
我的理解:上下文切换是一个动词,理解它,只要回答“谁在什么时候干了什么?“
调度器(scheduler)是在执行系统调用时、或者发生中断的时候,如果满足一定条件,就执行上下文切换
内核为每一个进程维护一个上下文:当前进程使用到的寄存器值、程序计数器、用户栈、内核栈等各种内核数据结构。eg:页表、进程表、文件描述符表
第四组
Helios
Linux查看信号种类kill -l
.
查看信号的解释man 7 signal
一旦信号产生,有下面三种操作:
信号处理还有有下面三种类别(sa_flags):
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
编译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信号,看看
第一个终端执行:
# ./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
内存表示:
栈在上面,地址向下“增长”;堆在下面,地址向上增长。
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位用两个页就够了
心得体会:
通过改变进程上下文中的某个状态,将信号传递给进程,进程有一个pending,和一个blocked来对接收到的信号进行处理,收到信号后,进程如果没有设置对这个信号的屏蔽等操作,会调用信号处理程序,来对这个信号进行处理。
信号的发送的机制是基于信号组的,信号组的标识和信号组的父进程保持一致。可以通过自定义的signal对信号进行自定义处理
需要注意的是信号是不排队的,如果有一个信号正在处理,另一个相同信号进入,并不会排队,而是简单丢弃。
虚拟内存,通过对物理内存的抽象给了程序员一块连续的内存空间,同时也使得多个进程互不干扰,主要两个部分,一个是pagetable,一个是mmu,当你代码中读取一个地址时,由mmu进行翻译,但是翻译参照的是pagetable中的内容,也就是操作系统负责处理虚拟内存与物理内存的映射,具体使用虚拟内存时,由mmu负责。
虚拟内存的好处首先时提供了内存的保护,一个用户级进程无法访问内核的地址,其次,使用虚拟内存便于我们管理物理内存,使得内存碎片减少。
pagetable中的PTE有几个标识位,如可读,可写,可运行,有效,然后剩余的bit作为物理地址。
第五组
张学广
第七章-链接
概念 :链接就是将各种代码和数据片段整合到一个文件中。
整体结构 :
整体对编译后的链接过程进行了详细的介绍,以链接库的不同分为两大部分:静态库链接和共享库链接。
静态库链接介绍的重点为符号的解析和重定位,共享库链接的重点是动态链接器,共享库是被加载到内存中的,动态链接器的作用就是通过重定位完成链接任务(注:动态链接器本身就是个动态库,这个涉及到一个自举的过程)。
后面还介绍了位置无关代码和库打桩的机制,位置无关代码是对动态链接的一种空间优化,而库打桩是我们的调试利器,这里mark一下,以后用到再拿来实践。
其他内容:
第三组
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函数来提供。
虚拟内存是对于物理内存的抽象。支持虚拟内存的处理器,通过使用虚拟寻址
这种间接方法来引用物理内存。
处理器产生一个虚拟地址,在被发送到物理内存之前,这个地址被翻译成一个物理地址。
虚拟内存提供了三个重要的功能
页表
中加入保护位
,从而简化了内存保护
。大多数页表条目位于L1高速缓存中,但是TLB的页表条目的片上高速缓存,通常会消除访问在L1上的页表条目的开销。
内存映射就是,把虚拟内存和磁盘映射起来,从而提高性能。
虽然我们可以使用mmap函数
或者munmap函数
来创建和删除虚拟内存,但是使用动态内存分配
更方便,也有更好的移植性。
第二组
李显良
信号可能会中断进程内的系统调用,当然linux会自动重启被中断的系统调用;也可以在应用程序中处理或者通过自定义信号处理函数
子进程拷贝父进程的虚拟地址空间;使用了copy on write的技术,在读的情况下两者的物理内存是一致性的;当有写入时,会引起子进程页表缺页异常,拷贝真正需要的数据
虚拟存储器是由系统自动提供的,它也是一种有限的存储器资源,作为程序员需要深入了解原理才能写高效果程序
第一组
张仁杰
信号处理的原则
虚拟内存是对主存的抽象的概念
虚拟内存的重要能力:
由物理寻址转向虚拟寻址:
在虚拟内存中,都会提供某些手段来控制对内存系统的访问,每次CPU生成一个地址时,地址翻译硬件都会读一个PTE,这个PTE会提供几个许可在虚拟页面内容上,如果违反了这些许可条件,此时会报告为“段错误”
地址翻译的过程 (页面命中)
所在小组
第二组
组内昵称
梁广超
心得体会
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外,常用的还有: