在阅读本书前,最好先了解一下书本的结构,然后根据结构,网上查查网评。最好能找到一些最佳阅读技巧。可以给自己定一个大一点的目标,比如,期望读完这本书,可以自己设计一个操作系统。而不是仅仅学会给代码调优(这个目的会让你走火入魔)。不要企图或者期待这本书给你带来很多开箱即用的最佳实践。你不是在刷算法题。
阅读时的模式:明白技术点所要解决的问题,以及如何解决的。(问题驱动阅读)
## 第一部分 程序结构和执行
- 数据类型
- 程序指令,系统是如何执行程序指令操作数据的
- CPU结构,CPU是怎么工作的?
- 谈谈花里花俏的程序性能优化(其实还不如学算法优化实在)
- 存储,了解数据可以存在操作系统的什么地方,CPU?内存?硬盘?
前言:这一章核心,计算机怎么处理数据,计算机怎么处理指令,计算机怎么存储数据。简而言之,就是操作系统自身是怎么工作。
## 第二部分 在系统上运行程序
- 链接,怎么从文件种将杂七杂八的代码组装起来(制作图灵机的纸带?)
- 异常处理,如果用户输入垃圾代码,操作系统怎么擦屁股,收拾烂摊子?
- 操作系统,怎么提供自己的存储空间给用户呢?(聊聊地主怎么分地皮)
前言:程序是操作系统提供用户执行作业的接口。了解操作系统是怎么提供这个接口给用户。简而言之,就是了解操作系统怎么对提供服务,对就是你想的那种服务。侧重于异常和存储分配(指令这些就不管啦,CPU内部封装的,对用户不可见)。
## 第三部分 程序间的交互和通信
- 同一个操作系统,进程怎么聊
- 不同操作系统,进程怎么聊
- 操作系统是一个单例,所以怎么处理多个进程资源复用。
前言:系统是一个房子,程序就是住户,怎么通信和共同生活,也是一个问题。
本书分为3大部分:讲讲操作系统怎么工作,讲讲用户怎么让操作系统工作,讲讲怎么让操作系统有条不紊的完成用户的多个任务。
所以整本书的最终目的就是,搞清楚,怎么让CPU,内存,硬盘,帮用户做很多工作。
简化后的目录:
一,操作系统
- 数据
- 指令
- 存储
-
二,程序接口
- 编译
- 异常
- 内存
三,进程通信
- 系统IO
- 网络编程
- 并发
如果想要做操作系统,这一部分的知识就是入门材料了。
简单快读
了解概念
了解结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ogaDAsod-1588815445133)(:/076d018da076413398072526001b4a70)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gtBlVhJj-1588815445143)(:/ee8c55044e7542358d12fe96c3e8709d)]
假设我们现在已经从蓝色区域读取完了数据,接下来需要从红色区域读,首先需要寻址,把读取的指针放到红色区域所在的磁道,然后等待磁盘旋转,旋转到红色区域之后,才可以开始真正的数据传输过程。
总的访问时间 Taccess = 寻址时间 Tavg seek + 旋转时间 Tavg rotation + 传输时间 Tavg transfer
磁盘访问大部分时间花在寻址上,读取扇区是很快的过程。
寻址包括了,选盘面,选磁道,和转盘。转盘一般是4ms,选盘是4ms,选磁道也是4ms.
硬盘比 SRAM 慢 40,000 倍,比 DRAM 慢 2500 倍。
固态硬盘是闪存,他的分级为:块–》页。一页为512k,写入时页为单位,刷存时块为单位。刷存影响寿命。
在硬件上看来,所有的设备都是连接在一条总线上,由CPU通过信号驱动。CPU的信号可用触达任何存储模块。但是数据的提交过程却时层层上报的,也就是说,硬盘的数据需要加载到内存,才能被CPU读取到cache(尽管有总线,但无法直接传,因为IO速度差太大)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-182SpaCj-1588815445146)(:/ce42c1c91ad747ce87a62af2212b89b7)]
通过这种图,我们可以知道:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ngdleEly-1588815445148)(:/a4ee83cd3060425590453c1ea362c538)]
从这个图可以看出,CPU其实只需要寄存器组和运算单元就可以工作了。高速缓存SRAM只是作为加速器缓存来使用。
所有的数据最终都是来源于总线接口,总线的数据来源于内存空间。
这不是重点,重点是有没有发现这个当先流行的web服务架构一毛一样。总线接口等价于数据库IO接口,高速缓存相当于Redis等NoSQL内存存储模块,寄存器就是本机内存和硬盘了。算术逻辑单元就是我们的服务工作线程。
讲这个不是为了要用类似的思维去学习他,而是想说,不妨感受一下高速缓存的结构和意义,就像感受redis的奇妙一样。
高速缓存的结构是类似完全二叉树的树形结构,第一层是集合,第二层是行,第三层是块。块才是真正存储数据的节点。
通过一个集合都只有一行数据。等价于没有集合的概念(或者没有行的概念),这样的话,可以看做一行有很多块。只需要给出行坐标(集合坐标)以及块坐标即可定位。标准的地址是要给出集合坐标,行坐标,块坐标才可以的。
CPU怎么将写入内存?先写缓存,再写内存,需要写内存,比较慢。先写缓存,等缓存失效了再同步回内存,这种写入块,但是需要处理缓存内容过期问题(并发问题)。
缓存的和内存谁是主,是一个很重要的话题。
缓存读写策略有哪些?什么场景用什么策略呢?
缓存写入miss的策略
在写入 miss 的时候,同样有两种方式:Write-allocate: 载入到缓存中,并更新缓存(如果之后还需要对其操作,这个方式就比较好)
No-write-allocate: 直接写入到内存中,不载入到缓存
这四种策略通常的搭配是:Write-through + No-write-allocate
Write-back + Write-allocate
在内存里,数据可以是【操作符+数据】也可以只是【数据】。这是冯诺依曼体的特点。
程序内存空间从逻辑上可以分为:栈,堆,代码区。在程序被加载的时候就会分配程序空间。
那么问题来了,程序的内存空间是按照什么规则分配的?可以动态扩大或者缩小么?
按进程位单位分配的,其中栈是固定的,分配时映射了内存,堆是动态的,用的时候做映射,其他代码段,Data段,text段,库段时固定的。堆段时可以无限扩展的( 堆分为2,存放小对象的brk,和大对象的内存映射段)。
一个程序可以分配多少内存?答案是:操作系统的全部可用内存,因为操作系统分配给进程的内存是虚拟内存,或者说是逻辑内存,在进程看来,整个系统的内存都是他们可以用的。比如操作系统有4G内存,那么每个进程都可以拿到4G内存。
仅当进程对内存进行读写的那一次,才会触发物理内存映射,将内存页与虚拟内存绑定(物理内存是按页分配的)
每触发一次物理内存与虚拟内存的映射,下一个进程的可用内存都会减少。所以越往后面,新进程的可用内存就越少。除非有旧内存释放空间(接触物理内存页与虚拟内存的绑定)
根据上面的推理,可用内存大小是动态变化(堆栈的可用大小是动态变化的)。
栈有空间限制,堆没有。堆有多少就可以申请到多少。
栈的数据是有固定生命周期的,从函数创建到函数结束。堆的数据生命周期不固定。
页中断是为了让物理内存与虚拟内存建立联系。堆内存分配会触发昂贵的页中断,而栈不会,栈空间在进程初始化就分配好,并做好了页映射。
由于堆分配需要进行页中断,成本高,所以建议一次性分配多点堆空间(比如1G哈哈哈)。不要每次分配几个字节。(这是理论上来说,实际上,内存管理器预料到会这样,所以给了解决方案。)
当然为了适应玩家系统分配几个字节小内存的爱好,内存管理器,会为进程分配一个专门存放小内存的页,这个页不会随便回收,而是当空闲区比较大的时候才会回收页。所以并不会经常触发页中断。
我们把小内存映射页叫heap,把大块内存映射区叫,内存映射段 Memory Mapping Segment。
什么是Segment?进程讲虚拟内存空间划分的各种区域,这些区域就叫Segment,比如stack Segment 栈空间,head Segment,memory mapping Segment. data Segment, text Segment
操作系统的核心知识是进程管理和内存管理。进程管理涉及对算力的管理,内存管理涉及对存储的管理。进程对于操作系统,就是算力和存储的集合。重点关注【缓存局部性原理的利用】
这一章主要是讲操作系统怎么抽象出进程的概念。
认真阅读,接口产品设计,程序(进程的抽象)
- 关键词:编译,汇编,链接
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vCHPqZhe-1588815445149)(:/4da3af334c1048838d2dfb860e4615f5)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fxfcZAP5-1588815445151)(:/a8ff84645f0e419a979a188f6883d5a8)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3wOT457x-1588815445153)(:/e933d0ef75db48d3b1a5a8fef9535e7d)]
认真阅读,表面上讲异常,其实是讲操作系统怎么实现CPU共享。而下一张则是讲操作系统的内存共享
进程可能是计算机系统中最伟大的抽象。进程这个概念背后,其实隐藏着一整套系统级机制,从进程切换、用户态与内核态的转换到系统实时响应各种事件,都离不开一个相当熟悉又陌生的概念——异常。
计算机启动后就会不停的读取内核程序的指令并不停的执行指令。
除此之外,计算机还会做一件重要的事情那就是跳转。
异常控制流不止是异常,他的定义包含了用户中断和定时器中断。
我们常说的Exception,大多由硬件和操作系统触发。
进程的切换也是异常控制流ECF的结果,进程的切换实现了对CPU的共享,该中断由硬件和操作系统协助完成。
信号,只是操作系统的一种实现。
什么是非本地跳转呢?非本地跳转(Nonlocal Jumps)。
异常控制流只是一种机构上的概念吗?还是说是由于操作系统实现的一种机制?我倾向认为是前者。
异常控制流,可以从硬件到操作系统,也可以从操作系统到用户程序,可以说无处不在。
计算机从控制权的角度分为3层,硬件,操作系统,用户程序。最终的控制权位于操作系统,硬件作为服务组件为操作系统提高平台和必要的中断,操作系统监督程序运行。
我们常说异常Exception,就是由硬件或者操作系统自身触发,并利用操作系统拥有的最高控制权反映到用户程序的执行流程中。
系统会通过异常表(Exception Table)来确定跳转的位置,每种事件都有对应的唯一的异常编号,发生对应异常时就会调用对应的异常处理代码。
系统调用走的其实时异常控制流程,因为他一旦用户程序调用了系统函数,就会把CPU控制权交回给了操作系统。等系统执行完中断指令后才会返回用户空间。
科普一个概念,程序理论上可以访问所有的内存空间地址(包括虚拟内存)。
一旦用户往内存地址上写入数据,操作系统就会将与内存地址所在的内存页与用户进程绑定,表示这个页已经被某个进程映射绑定了。
内存页相对于进程来说就是一种相互竞争的物理资源。是操作系统提供给用户程序的资源。但是对于底层硬件来说,则是一种逻辑资源,页内存可以是硬盘,也可以是内存条或者是闪存。
如果页内存是硬盘资源,那么当用户访问页内存的时候操作系统就可以将页数据从硬盘加载到真正的内存提供给用户进程,这个过程对用户进程是不可见的。
提处页内存的目的有两个:
1.使得内存资源可以按批分配,提高资源的利用效率。
2.对物理资源进行虚拟,解除物理存储与用户数据的耦合关系,使得用户内存数据可以存放到硬盘,实现虚拟内存技术。
3.进一步适配各种奇奇怪怪的内存厂商标准。(也许是吧)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cxEyDV6g-1588815445156)(:/67a4a0ee316f4ac581c55bcc549988c3)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mf0XF0Py-1588815445157)(:/c0a8e011101543a58926b7f5d12390ed)]
如果站在cpu的角度看来就是这样的一堆指令:a进程指令-》切换寄存器指令-》b进程指令-》切换寄存器指令-》a进程指令。切换寄存器指令就是内核代码了。
我们说的指令重排,其实就是内核往进程代码注入内核指令的行为。
一旦发生系统调用或者硬件中断,就会触发内核上下文切换。
我刚说了进程可以理解为是虚拟计算机,那么进程的几种状态是不是和虚拟机可以对应上呢?答案是肯定的,我会会创建虚拟机new,启动虚拟机ready,运行虚拟机作业running,暂停虚拟机作业stop, 重启虚拟机作业resume,终止虚拟机terminated.
而终止虚拟机的几种方法有,直接断电,等于发送kill信号。调用exit退出函数,等价于点击关闭系统按钮,最优雅的动作。或者程序执行完所有代码(理论上不可能发送。)
说一个有趣的函数fork,fork可以字面上一颗创建子进程的函数,但是这样理解是肤浅的。对,就是肤浅的。如果学习了操作系统后,我们应该从操作系统的本质去理解,操作系统是一个资源管理系统,操作系统的服务对象是进程,操作系统的资源分为共享资源和私有资源。共享资源就是系统里存储的外设数据,比如文件描述符(字节序),或者可以笼统的理解为内核级别的资源,这种资源fork不会进行复制,另外一种资源则是进程独占的资源具体为“堆栈segment,data text segment, 库代码segment,bss segment,以及最重要的进程寄存器上下文”.这些资源会被复制一份。而页表映射绑定关系也会被复制一份,但是默认情况下不会触发页表重新映射,除非发生了写冲突,就会把原页表拷贝一份并重新绑定页内存。所以fork函数就是复制进程独占资源的函数,唯一不一样的进程独占资源就是fork函数返回寄存器值不一样,父进程的fork返回值是子进程的pid号,而子进程的fork函数返回值寄存器值是0.
当fork函数执行完后,两个进程的pc指针都指向fork函数这个内核函数的返回处。并各自开始执行自己的业务。
信号是一种内核自己发明的异常控制流,每一个进程都有内核默认的信号处理函数,比如kill -9的终止信号,默认的内核处理函数就是终止进程。又其它进程发送给另一种进程(这个信号的发送者需要有足够的系统权限)
信号作为内核进程提供给进程的通信方式,由此可见,所有利用信号进行的进程通信都需要有内核进程进行协调,内核进程就充当了代理或者是中介的角色。
信号是一种异步的通讯机制,就类似消息队列,a进程发送给b进程,信号消息会先暂存在系统的内核进程等待发送,这就意味着,信号有一种交pending的状态(内核是用set来暂存信号,所以信号不会重复)。接受者可以暂时阻塞一些特定信号(又进程的启动者对指定阻塞哪些信号),这就好比接受者在系统内核有多个快递箱,一些快递箱可以暂存不处理的信号,另一些快递箱则是存放相对实时的信号。
相比,硬件中断和系统中断,这两种异常控制流而言,系统内核发明的信号异常控制流就没那么伟大了,信号仅仅为了进程通讯,而硬件中断和系统中断则是为了实现cpu共享和协调io等外设。
进程组这个概念目前看来只是为了批量维护进程。比如可以给进程组id发送信号,这样就等于批量发送信号给进程组内的所有进程。
ctrl+z是发送挂起信号给当前前台进程。并不会终止进程。
ctrl+c是发送stop信号给当前前台进程。
kill 是用来发送信号的系统调用
每一个信号都是对应在一个状态字的一个比特位。换句话,每一个比特位都对应一个中断函数。
每个信号的中断函数是可以被重写的(重定向函数句柄),通过signal系统调用来重写。
操作系统的所有进程代码都是被内核进程注入监控指令的,以至于每个用户进程隔一段时间就会去执行内核进程的指令,看看内核进程这个顶级上司有何指示,没指示就继续做自己的事情。这样的机制就是用来实现,操作系统信号中断等异常控制流的。(有了这样的机制,进程就像是操作系统的租户。)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HPvjccY6-1588815445158)(:/3b056fb188264506bce9fac0e95836bf)]
最有趣的是操作系统内核进程指令除了会注入到用户进程意外,还会注入到自己的内核调用。有了这个满世界都是注入指令的代码,就是随时随地中断任何程序,包括自己。比如,信号处理器在处理中断信号的时候也可以被其它信号给再次中断。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sOs1ZmeF-1588815445160)(:/9a56c2f4938f4384884f9a30124504cd)]
认真阅读,资源管理算法
这一张讲用户进程怎么实现业务。
认真阅读,进程协作,学习文件系统
- 在linux里,【所有东西都是文件】(这句话有待考证)
- 文件可以看成是字节序
- 标准c文件接口都带缓存。
- 而unixI/O的文件访问接口open,close,read,write都不带缓存。
- open可以理解为调用内核函数初始化文件上下文,close就等于调用内核函数关闭文件上下文。
- read则是向设备发送字节序复制指令。
- 文件头有几个重要数据,type,inode号,inode引用次数。
- 同一个文件系统inode号表示同一个文件,inode引用次数是专门为硬链接设计的字段,用于表示该文件有多少个硬链接。
- 只有硬链接变成0才允许被删除。(一个inode可以有多个硬链接)
- 软件其实是一种文件类型,因为他有不一样的inode号,文件系统是根据inode来区分是否为同一个文件。
- 目录也是一种文件,他的文件内容保持着上一个目录的指针,以及文件索引列表。
- 每一个由Linux shell打开的进程,都会打开3个系统资源文件,即,标准输入流0.标准输出流1,标准错误流2,0、1、2 分别是这3个文件的文件描述符fd。
- write和read这两个UNIX IO接口的最小事务粒度是1个字符,即,要么读写一个字符,要么读写0个字符,没有半个字符的说法。
- 文件的数据结构很复杂,由多个文件元数据组成,其中与inode有关的元数据如下:
struct stat
{
dev_t st_dev; // Device
ino_t st_ino; // inode
mode_t st_mode; // Protection & file type
nlink_t st_nlink; // Number of hard links
uid_t st_uid; // User ID of owner
gid_t st_gid; // Group ID of owner
dev_t st_rdev; // Device type (if inode device)
off_t st_size; // Total size, in bytes
unsigned long st_blksize; // Blocksize for filesystem I/O
unsigned long st_blocks; // Number of blocks allocated
time_t st_atime; // Time of last access
time_t st_mtime; // Time of last modification
time_t st_ctime; // Time of last change
}
认真阅读,为互联网打基础,其实对于看过tcp协议和计算机网络的人来说,这一章完全可以不读的了。
认真阅读,为高并发打基础
CSAPP,第一部分讲了数据类型和汇编指令简单知识,基本上就是在可怕逻辑上的hello world入门了。第二部
,重点讲了操作系统怎么利用中断实现多进程共享算力,以及怎么定义进程虚拟内存和实现。第三部分讲了进程通信技术。
从目录结构上看,整本书核心就是讲进程,可见对于操作系统来说,进程的重要性。甚至我们可以说一切都是为了
讲明白进程而作的铺垫。
整本书就是讲:硬件怎么支持实现操作系统,操作系统怎么支持用户进程,用户进程怎么支持业务实现(从计算机的架构看来,业务的本质就是IO,计算只是为了更好的IO)。
引用声明: