Home

UnixV6分析(1) 硬件

24 Nov 2012 by LelouchHe

废话我就不多言了,背景知识请看附后的参考,我就直接介绍v6存在的硬件环境,pdp11-40

单位

pdp11-40是以8bit为1B,16bit为1word的机器,在后续讨论操作的时候,会经常用到word(字)这个单位,需要记住这个是16bit,而不是现在通用的32bit

和32有关的是一个block(块)的概念,1block是32word,也就是64B.当然,此32和x86的32不是一个数量级,看代码要随时记住我们研究的是v6.块的重要性在于pdp11-40的很多机器内部表示都是以块为单位的,所以需要记住.

地址表示

其实从单位的名称来看,很容易就看到pdp11-40是16bit的机器(话说每个硬件平台不都是以为名称的么;)),也就是说,我们可以操作的地址空间就是2^16 B,即65536B(记住,我们的地址一般指的都是字节地址)

不过很奇怪(?搞笑?)的是,pdp11-40提供的是18bit的地址线,也就是我们其实可以使用的是2^18 B的空间的,具体的实现我们之后讨论,现在的问题是我们如何表达地址.v6的代码和分析的统一的表达是使用18bit,并利用八进制三三分类,就像下面这样

字的表达

前面空余的2bit什么值呢?自然最好的情况是0了,这样就没有任何冲突了,不过实际上这里pdp11-40有个很巧的办法,对于高两位自有安排.

奇特的硬件交互

了解底层知识的人都了解,那个时候每天要打交道的就是寄存器,状态字,内存,时钟,中断和各种驱动程序,这些东西都有一些特殊的标记来使用,比如寄存器,我们使用eax,ebx等来表示通用寄存器,gdtr,ldtr等表示专用寄存器,eflag来表示状态字,0xabcd表示内存位置等.

而在pdp11-40中,有一个很奇特的设计,就是硬件的内存地址,意思就是我们不用考虑什么什么奇特的名称,pdp11-40为每个硬件都提供了一个都应的内存地址,我们直接处理这个地址的值,就相当于和这些硬件进行了交互.

比如,0xabcd表示某个时钟,那么我们填入对应的值就相当于设置了时间,读入了其中的值,就相当于读取的时钟的时间(当然,对于pdp11-40来说,处理的单位是字,就是2B的值)

这样的奇特设计,带来的好处就是不需要那么多的特殊的硬件指令了,比如我们不用in/out来表示端口输入输出,直接操作内存就完成了.唯一的缺点就是有了很多特殊的内存位置需要记住(或者靠手册)

当然,这里还有一个问题,我们能随随便便的给一个内存地址么?当然不是.那怎么办?自然是有规定的特殊位置了,此时就显示了刚才我们讨论的16bit地址和18bit地址线的神奇作用了.

pdp11-40有特殊规定,地址空间的最高4KB是留给特殊硬件的,也就是说,地址0170000~0177777我们是不能使用的,但这个16bit的地址怎么转到18bit地址线呢?自然,这些地址不能转换到对应的0170000~0177777,因为18bit的地址线下,这段地址是在整个地址中间的一段,怎么可能在这里.最合适的位置还是18bit的最高4KB,也就是地址线0770000~0777777.

看到没?

pdp11-40的聪明地方就是当我们引用最高4KB的时候,自动就把地址线最高2bit设置为1,直接就映射成了可能物理地址空间的最高4KB,保证了这个的统一性

内存管理

上面已经介绍了地址转换和特殊硬件处理的神奇,接下来就是和这个相关的内存管理了.其实和x86相比,pdp11-40的内存管理相当的简单了.

本质来看,pdp11-40的内存管理相当于简化版的8086实模式,简化的地方在于段.实模式的最大灵活性就是分段,而在pdp11-40,亦然.

pdp11-40有两种模式,基本内存模式和扩展内存模式.基本内存模式非常基本,基本就是16bit地址直接对应18bit地址(除了最高4KB),没有任何保护,没有任何限制,任何程序都可以控制整个系统.可以看到,这种模式不仅很傻很天真,而且并没有利用到地址线剩余的2bit(浪费啊~).

还有一种模式就是扩展内存模式,充分的利用了高2bit,此时的内存管理颇像8086实模式,有了段的分类.多了一个间接层,自然随之而来的就是权限和控制,还有灵活的的定位.

具体的方法策略下文会讲,此处不多言,我们需要明白两点:

  1. 内存模式的切换类似x86的保护模式一般,有专门的硬件来控制
  2. 扩展内存模式是我们重点讨论的模式.基本内存模式基本就在初始化的时候我们要处理,除去系统初始化的时候,我们不再区分,直接指的就是扩展内存模式(很像x86的保护模式吧)

寄存器

寄存器是个好东西,我记得大学时做编译器的移植时,深深感慨arm系列的寄存器真的比x86的又多又好用

pdp11-40有多种类型的寄存器,下面就一一进行介绍

状态寄存器(PS)

字如其意,pdp11-40的PS表示了当前处理器的状态,这个状态类似与x86中的eflag和cs的结合.其具体的含义如下:

状态寄存器

条件码的含义是:

T则是陷入位(Trap),提供了一种对运行程序进行中断控制的方法(具体方法后说)

中间的3bit表示了当前处理器的优先级,一般说来,这个值越大,当前的优先级越高,越不能被中断(这个也后说)

最高的4bit表示了两种模式,当前模式和前模式.为什么提供两个模式呢?我想,这大概和不同模式间的数据互通有关,pdp11-40提供了两条特别的指令,mfpi和mtpi,都是用于把前模式的数据和当前模式的数据进行传递和变换.其实,这个可能与下文马上要介绍的堆栈寄存器有关,这些模式的设置和mfpi和mtpi的指令,可以有效的将不同模式的堆栈进行关联.

通用寄存器

在pdp11-40中,我们能看到的通用寄存器只有8个,即r0~r7,但实际上,系统里一共有9个通用寄存器,如刚才我们说的,不同模式有不同的堆栈寄存器,由于pdp11-40只使用了两种模式(kernel和user),所以堆栈寄存器就两个,如下:

通用寄存器

r6就是那个神奇的堆栈寄存器(以后我们称之为sp),不同模式下只能看到其中的一个,这也是pdp11-40提供前模式的作用,因为当前模式下无法使用别的模式的堆栈,因此只能设置前模式并通过mfpi/mtpi来进行交互.

r7就是我们通常见到的pc(以后就叫pc啦)

而剩余的6个寄存器基本就是通用的了,除了一些我们编程上的约定外,基本没有什么使用限制(约定也可以不管,因为我们在写OS嘛,约定还不是我们自己写的;))

至于在v6内部使用的一些约定,在后面分析代码的时候再谈,否则现在空谈木有代码等于没谈

段寄存器

还记得x86的段寄存器和段说明符么?下图很好的展示了pdp11-40中类似的结构:

段寄存器

首先要了解段寄存器是干什么的,上面我们提到了和x86的类比,其实他们的作用是一样的,也就是用来映射和控制地址的转换的.再提一句,此时我们讨论的是扩展内存模式,在这种情况下,pdp11-40就是利用段寄存器来将16bit的虚拟地址转换为18bit的真实物理地址

段寄存器指的就是图上的一组APR(Active Page Registers),一个段寄存器指的就是如上的八个APR(APR0~APR7)(段寄存器就是个统称而已啦)

段寄存器控制的是和八个和Page(页)相关的寄存器,也就是说,每个段寄存器代表了8个页.请不要和x86中的段和页混淆,这里的段其实是统称,没有实际的含义滴

还记得ps的15,14bit表示什么么?对了,就是当前模式.从图上其实可以看到,段寄存器其实也是分成了两种,左边的是kernel模式下的,右边的则是user模式下的

与上面提过的sp不同,不同模式下的段寄存器都是能被看到的,也就是说,我们看不到不同的sp,但是能操作不同的段寄存器,这种区分其实很神奇,在我看来,既然这样,还不如让sp也能被不同模式看到,然后通过类似段寄存器的方式进行控制,不过这是后话了

先来看看APR的组成的

PAR(Page Address Register)

好吧,这个很像x86的段说明符的一部分,都是提供一个基址,之后加上虚拟地址的偏移来得到物理地址(这个后说).其结构如下:

PAR

其中PAF(Page Address Field)就是这个基址,不过这个基址是以为单位的.还记得的概念么?1块=32字=64B(看到了吧,这就是块的使用)

比如如果PAF是02222(看清楚,我们是12bit),实际上我们的基址是02222-00(1块是64B,正好是低位6bit),正好组成了18bit的物理地址(请类比x86中的段地址到线性地址的转换)

PAR的最高4bit是保留不使用的,请善良的置0吧,少年

PDR(Page Descriptor Register)

这个很像x86段说明符的另一部分,也就是权限控制的部分,其结构如下:

PDR

如果有x86的经验的话,能很容易的看出二者的一致(当然,在上古时代,pdp是经典中的王者,其他架构不可能没有从中汲取经验的)

ACF(Access Control Field)表示了对这个页的权限,含义如下:

ED(Expansion Direction)指的是该页的扩展方向

其实一直没搞懂扩展的意思,今天突然想通了,比如我们的页基址是02222-00,我们的偏移是033(具体是什么下面会说),那我们的物理地址是多少呢?是02222-33么?当然不是,这里就考虑到了ED了,如果ED为0,表示偏移向高位走,那么物理地址自然是02222-33,沿着高位走了.但如果ED为1,则要向下扩展了,那么物理地址就是02221-45,看,是不是还是偏移033.ED就是拿来做这种事情的

其实系统里,使用ED为1的只有堆栈了,理由大家懂的.不过注意,虽然堆栈是向下扩展,但这仅仅是虚拟地址转换物理地址的规则,具体出栈入栈的操作,+/-还是得按照规则滴.

W(Write)则是页的dirty位,表示该页曾被修改过,这个就和换页操作什么的有联系了

PLF(Page Length Field)表示该页的长度,单位也是块(=32字=64B),页长度从1块到128块,不能超出限制,其中PLF的值如下:

其实可以看到,与其说PLF是页长度,不如说它是页尾偏移(以块为单位),即PAF+PLF就是最后一个block的位置.ED=0的时候好理解,ED=1的时候,为什么128要减呢?大家看PLF,7bit,128不就是2^7 么?这就等于求了下补码,最后PAF+PLF就等于PAF-len+1,这就变成了尾块的位置.

但这个地址是怎么来的呢?等我们说完最后一组寄存器再来谈这个问题

错误寄存器

pdp11-40里还有两个寄存器用来表示程序遇到故障时的机器状态,分别是SR0和SR2(Status Register,另外,SR1存在于pdp11-45,pdp11-40中没有)

SR0

SR0除了有很多表示出错原因的bit之外,另一个极其重要的作用就是控制是否启用扩展内存模式,下面就是其结构:

SR0

很多位都是显而易见的,而且除了0bit外,都是在出错的时候处理器自己设置的,包括错误原因(15,14,13bit),出错时内存模式(8bit,1表示扩展内存模式),出错时模式(6,5bit),出错时页位置(3,2,1bit,表示出错使用的某个APR).

0bit很特殊,是我们自己设置了,如果为1,表示开启扩展内存模式.这个和8bit有区别,8bit是指出错时候的模式,但0bit是当前设置的模式,二者在某些情况下不太一样

SR2

SR2只有一个用途,就是表示出错时的pc值,即出错时的指令位置

虚拟地址到物理地址

前面我们说了一堆16bit到18bit的故事,但一直没说怎么转换,下面就来自己说说这个转换

转换分为2类,第一类自然是基本内存模式下的转换(即上面说的SR0的0bit为0),此时的转换是直接的映射,也就是16bit的虚拟地址直接对应为18bit的低16bit,高2bit根据16bit的位置添入不同的值,亦即如果16bit处于最高的4KB,高2bit添入1,否则添入0.

第二类就是扩展内存模式(SR0的0bit为1),此时的地址转换类似于x86的转换,如下图:

虚拟地址到物理地址

这里面就涉及到一个判断越界的问题,就和我们上面讨论的PLF有些关系了

在PAF和BlockNo相加之前,需要进行一个判断是否越界,这里有两种情况:

  1. 该页的ED=0时,需要保证BlockNo<=PLF
  2. 该页的ED=1时,需要保证BlockNo>=PLF

ED=1时,有两种方式来理解BlockNo和PLF的关系,一种是把这个理解成正数,把每页都想成从页基址到低128块的空间,而BlockNo和PLF则表示从最低的0block向上的块数,将PAF-BlockNo就得到对应的块了,此时,自然BlockNo>=PLF就是在页空间里了(记住PLF表示尾块位置);第二种方法是将其理解为负数,BlockNo>=PLF,表示-BlockNo<=-PLF,将PAF+BlockNo就是对应的块,自然也在页空间了

(此处画一个图就很好理解了,不过目前没有方便的工具,以后我会调整补上的)

硬件部位的内存地址

(此处会补上内存位置映射表)

为什么讲这一章

在我看来,需要深入的了解硬件,才能充分的了解基础软件,比如OS这种构建在硬件上的东东,也只有在这个的基础上,才能有更好的抽象,才能最终让OS方便简洁的运行在各种迥然不同的硬件之上

而且,这篇说的主要是一些我们不太主动参与的部件,除了设置其值外,我们很少参与这些部件的管理,比如地址的转换,比如越界和各种权限控制,只需要我们设置好值,处理器就会自己处理,我们之后要做的就是和处理器通信,即下一篇会提到的,和软件很相关的中断,因为涉及到代码了,所以就放到下次说

总结

pdp11-40是非常经典的架构,现在的很多设计都有从此处借鉴的痕迹

在看v6的代码时,如果有一些莫名其妙的地方不是很了解,就应该翻开PDP11-40手册查查是不是哪些硬件没搞懂,因为pdp11-40毕竟是老机器,很多东西是不一样的

继续吧