在80386中,CR系列寄存器被称为控制寄存器,用于改变或控制CPU或其他数字设备的一般行为。控制寄存器执行的常见任务包括中断控制、切换寻址模式、分页控制和协处理器控制。
CR寄存器有32位二进制数,CR0主要控制保护模式的开启以及分页模式的开启等等,其中,第十六位bit被称为"WP"位,即 Write Protection 写保护位,这一位的功能比较模糊,许多材料上也没有进行具体解释。如 MIT 6.828 课程给出的参考资料80386 Programmer’s Reference Manual – Section 6.5 (mit.edu),其中对于权限为 Supervisor 时,读写保护的检查情况为:
when the combined U/S attribute is S, the R/W attributeis not checked.
即 Supervisor 模式下,将无视页的读写保护,但这种说法并不完全正确。
在Intel手册中有详尽的解释:
When the processor is in supervisor mode and the WP flag in register
CR0 is clear (its state following reset initialization), all pages
are both readable and writable (write- protection is ignored). When
the processor is in user mode, it can write only to user- mode pages
that are read/write accessible. User-mode pages which are read/write
or read-only are readable; supervisor-mode pages are neither
readable nor writable from user mode. A page-fault exception is
generated on any attempt to violate the protection rules. Starting
with the P6 family, Intel processors allow user-mode pages to be
write- protected against supervisor-mode access. Setting CR0.WP = 1
enables supervisor- mode sensitivity to user-mode, write protected
pages. Supervisor pages which are read-only are not writable from
any privilege level (if CR0.WP = 1)
总结
WP = 1时,Supervisor不能写R/W没有置位的页。
WP = 0时,Supervisor可以写任何页。
实例
错误理解
在实现OS的分页管理时,需要实现物理页面分配的功能,其中可能需要对分配的页面进行零初始化,即:
memset((void*)page2pa(page_free_list), 0, PGSIZE);
其中page_free_list是将要分配的物理页元信息的虚拟地址,page2pa将元信息所代表的物理页的物理地址提取出来。执行这个操作后,QEMU仿真器报了处理器级别的错误,停机了。根据gdb信息,我发现是memset在执行第一个参数的处理时出现了问题。
直觉地,我以为是传参出了问题,猜测进程运行过程中都进行虚拟地址级别的处理,如果传入一个物理地址,那么memset将会报错。于是我进行了修改,将第一个参数转化为虚拟地址。
memset(page2kva(page_free_list), 0, PGSIZE);
此时,程序可以正常运行。
正确理解
在继续进行实验的过程中,我意识到对于函数传进的地址,处理器都会进行页表寻址的操作,而不会检查你传入的地址类型。也就是说,就算我传入了一个物理地址,处理器并不会直接抛出错误,而是会根据传入的物理地址当做虚拟地址进行寻址。
根据实验预先的页表设置,如下:
虚拟地址范围 物理地址范围 权限
00000000-00400000 00000000-00400000 -r-
f0000000-f0400000 00000000-00400000 -rw
即将高地址和低地址的两端长度为4MB的虚拟地址区域,直接映射到了物理地址的低4MB区域,这是方便内核进行物理地址访问而设计的。根据映射关系,我发现传入一个高位置的虚拟内存,在寻址后得到的物理内存即为pa = va - 0xf0000000,这和直接将这个物理地址当成虚拟地址寻址后的结果相等,因为还是自己。那么,之前那种理解是错误的,不管memset第一个参数接受到的是一个地址的虚拟地址还是物理地址,寻址后都会得到相同的结果。
那么,区别可能在于权限访问上。查阅参考资料,发现内核模式在读写时会无视权限保护,这与实践结果不一致,于是我进一步查阅更详细的资料。通过查阅Intel手册,我发现是因为WP的置位导致了内核运行也会对读写权限敏感,而参考资料上的说法是在WP=0时才正确。
于是,我在实验设置上更改了WP的置位进行测试:
orl $(CR0_PE|CR0_PG|CR0_WP), %eax
改为
orl $(CR0_PE|CR0_PG), %eax
之后,即便是使用最开始的函数调用:
memset((void*)page2pa(page_free_list), 0, PGSIZE);
处理器也没有再报错。这证明了这次错误是因为内核使用memset,在WP置位的情况下对一个写保护的页进行写操作导致的。理解上是因为对WP置位的理解不够深入,不能简单地用“”传入一个非虚拟地址“”这样浅层的理由去解释。如果我们置位了,那么内核写一个不可写的页面就会报错,而非内核无视读写保护。内核需要对写保护敏感主要是为了之后的写时复制机制所设置的,会更加方便。