人丑就得多读书,最近不得不承认,我也得多读书,读书就得多总结,不然会忘。
今天是Christmas Eve(写完估计就圣诞了),同时也是我的生日(写完估计就不是了),其实我是过农历的,所以今天才是真正官方认证的生日,之前公历那个就算欺骗大家感情了。
值此双喜之日,我终于把《Linux C一站式编程》看完了,可是有很多不明白的地方,想写点读书笔记,梳理一下。今天就和大家一起简单的梳理一下,关于计算机体系结构基础的一些概念,我们整天谈我们了解计算机,但是我们真正了解多少呢。我们一起梳理一下吧,以下以x86架构为例。
声明:内容在发出之前,尽量做到仔细校订,若有覆盖不全,或者明显错误的地方,请帮我指出来,帮助我进步。
CPU
那就先从CPU开始讲吧,CPU最核心的部件,包括以下几个:
寄存器
寄存器就是CPU内部的高速存储器,像内存一样可以存取数据,但是速度比内存快很多,为什么快呢,可以看看阮一峰的[这篇](http://www.ruanyifeng.com/blog/2013/10/register.html)科普文章。
CPU内部的寄存器通常分为两种,一种是通用寄存器,比如eax,可以用于运算和读写内存的过程中;有的是特殊寄存器,比如eip,用于程序计数器。
程序计数器(PC)
在x86体系结构中,通常是eip,这里面保存这CPU的取指地址,CPU从PC中读取指令地址,然后去内存中取指令执行,然后PC中保存的地址自动加上该条指令的长度,指向内存中的下一条指令,CPU一直在重复这项工作。
指令译码器(Instruction Decoder)
CPU根据PC中的地址,从内存中读取到某条指令以后,指令的内容其实就是0和1的各种组合,有些位的组合表示一个寄存器,有些位的组合表示一种操作,ID负责解释这条指令的含义,然后调用相关的执行单元去执行。
算数逻辑单元(ALU)
上面讲到的执行单元的一种,如果译码器将指令解释为运算指令,就调动ALU去运算,指令中会指定结果保存在什么地方,运算结果可能保存在寄存器或者是内存单元中,这都不是事。
三总线
CPU和内存之间通过三条总线相连,分别是地址总线,数据总线和控制总线,每条总线上的状态根据电势高低,分为0和1。如果在指令执行过程中,指令需要读取内存某个地址数据到寄存器中,整个过程可以想象成一下这个样子
* CPU将寄存器的每个位,对接到数据总线的每位上,等待接收数据
* 然后CPU将地址通过地址总线发送给内存单元
* 然后用控制总线发送读数据请求
* 内存收到读请求和地址以后,就将对应地址的存储单元对接到数据总线的另一端,这样每一位的状态就到达了寄存器中相应的位,完成了数据传输
32/64位
一般来说,地址线、数据线和CPU寄存器的位数是一致的,因为寄存器读数据需要对接数据线,寄存器有时又需要保存地址,比如寄存器间接寻址,所以三者是一致的,一般32位体系结构就是32位,64位也是一样,处理器的位数又叫做字长。32位计算机共有32根地址线,可寻址空间位4G,64位更大。
以上算是把CPU内部的一些主要部件讲完了,但是有一些需要注意的地方,上面讲的总线都是内总线,直接和执行单元相连接,内总线经过MMU和总线接口的转换引出到芯片引脚才是外总线,外总线的位数有可能和内总线不一样,所以32位处理器的外地址总线可寻址范围是可以大于4G的。至于内外地址线的转换以及MMU的功能,等我请教完老师傅,继续看点书再和大家掰掰,先按下不表了。
外设
讲完了CPU的内件,接下来就走出CPU,看看外面的世界,讲讲外设吧。
计算机强大之处,以及被应用到各个场景里面的一个很重要的原因就是可以接很多奇奇怪怪的外部设备,老一点的比如键盘、鼠标,流行一点的各种体感设备,数位板等。那各式各样的外设如何与计算机交互,如何于CPU连接的呢?我们就来探探。
本来想先放一张图镇楼的,可是找不到好的图床,就先干巴巴的说吧。
设备寄存器
有些设备和内存芯片一样直接架在总线上,但是不同的设备需要占用不同的地址空间,访问这种设备就像访问内存一样按照地址读写,往地址发数据,数据不一定会被保存,而读取也不一定是保存在设备中的数据,可能是设备当前的状态,设备中可供读写的单元称为设备寄存器,操作过程就是读写这些设备的寄存器,比如串口设备。
内存映射I/O
还有一些设备是继承在处理器芯片中的,从CPU核心引出的总线分为两端,一端是经总线接口到引脚了,还有一段接在了芯片内部的设备上,无论内部总线设备还是外部总线设备,都有各自的地址范围,即都可以像内存一样访问,这种方式成为内存映射IO,比如ARM体系结构就是实用的这种方式。
端口I/O
对于另外一些体系结构,需要从CPU核心单独引出额外的地址线来接到内部设备上,访问这些内部设备寄存器的时候,需要使用特殊的指令in/out,这种方式称为端口I/O,x86就是这种体系架构
设备总线和设备控制器
对于CPU而言,操作设备的方式只有内存映射和端口两种方式,但是外部设备的种类有很多,需求也很不一样,比如有的需要高性能读写,有的需要支持热插拔等,为了满足各种不同的需求,出现了各种不同的设备总线,比如PCI、AGP、USB和SATA等。这些总线并不是直接与CPU连接的,CPU通过内存映射或者是端口方式访问设备控制器,设备控制器再去访问挂在设备总线上的各种设备。
比如在x86系统上,硬盘通常挂载在ATA、SATA或者SCSI总线上,保存在硬盘上的指令是不能被CPU直接取指的,操作系统执行程序的时候,会将指令从硬盘拷贝到内存,然后从内存中取指,这个过程称为『加载』,程序加载到内存,成为操作系统可调度执行的任务,成为进程。
操作系统内核也是一样,计算机启动的时候执行一段固定的代码,固定指的是地址固定,称为启动代码(bootloader),这是一段很短小精悍的代码,当年我的老师这么和我说的。这段代码首先把操作系统从磁盘加载到内存中,然后执行操作系统中的代码,把用户需要的程序加载到内存中,操作系统最核心的功能就是资源管理,包括进程调度,内存管理等,这部分功能代码叫做内核。
中断(Interrupt)
设备和内存有一个很不一样的地方,就是内存一般来说只是数据保存,相对本机而言不会产生新的数据,他不会主动的提供数据给CPU。但是设备却不同,通常都会自己产生数据,需要主动通知CPU来取这些数据,并做响应的处理,例如敲击键盘,活动的网卡等。这个通知就是通过中断机制来实现的,每个设备都有一个中断线,通过中断控制器连接到CPU,当设备有数据产生,就会触发一个中断,通过中断总线和中断控制器通知到CPU,CPU当前正在执行的指令被打断,程序计数器被设置成一个固定的地址,CPU跳转到这个地址,开始执行中断服务程序(ISR),完成以后跳转会原先执行指令的地址。
中断服务程序(ISR)
ISR通常是由内核提供的一段代码,实现加载到一个固定的地址,当出现中断请求的时候,CPU跳转过来执行。这段中首先判断是哪个设备引发了中断,然后调用该设备注册的中断处理函数做进一步处理,突然想起大学的时候,我和小伙伴在ACM实验室,搞了一晚的基,才让键盘引发中断的时候,调用我们写的中断处理程序,在屏幕上打印对应的键码。
驱动程序
由于每种设备的中断处理方法都不一样,每种设备都需要提供对应的驱动程序,应用广泛的操作系统,就需要有大量的驱动程序,设备驱动程序通常就是对设备寄存器的读写操作,有些设备需要在ISR注册中断处理程序。
好了,今天差不多就总结了这么多,搬了一些东西加上自己的理解,虽然不够细致,但也差不多弄清楚了从CPU内,到CPU外的架构和通信,后续会深入到内存里面去,看看虚拟内存,以及计算机程序从汇编角度的理解、二进制文件的内容以及加载到内存中的模样,不过得我从老师傅那里取经回来。
最后,还是值此圣诞佳节和我的生日,祝大家圣诞快乐,Merry Christmas.
多说一句,知乎常年招收Antispam工程师,地址是这里,简历可以发到hdd at zhihu dot com里面。