Linux源码(0.11)学习02---内核写时复制 verify_area write_verify

前言

  在进程创建之初,父子进程的数据段和代码段共享并且设置为只读,直到他们之一要将代码和数据段进行修改时才会进行复制即写时复制。但是,这种判断条件只能用于用户态,因为8086cpu, 在执行特权0代码时不会理会用户空间中页面是否为有保护,用户空间中数据页面保护标志不起任何作用的。这样将违背了进程的独立性。
用户态的写时复制:
          在对页面进程修改时会受到用户空间页面标志的影响。在用户态上的写时复制是由硬件支持的,写时当你把页表项是的属性设为只读的话,如果对页表所指向的这段内存空间执行了写操作(具本说就是写数据的指令,比如说 mov ) ,CPU 就会自动发现,然后进行一个陷阱中,去执行你事先设定好的处理程序,在这个处理程序中你自己把数据拷一份给写数据的进程,给这个进程分配真正的物理空间, 然后再改页表,让内存可写,这时候重新执行这条写指令就行了~~~~~~~~这里纯粹是硬件的机制问题,只是软件利用了这种机制而已
内核态的写时复制:
        为了保证进程的独立性,在内核态时,需要执行写前检测。[(verify_area(void &*addr, int size)) ],由于在实行写前验证是通过调用write_verify() 实现的,而对于该函数是以页为单位的,所以在verify_area需要得到addr所在的页面的首地址。

verify_area

void verify_area(void * addr,int size)
{
    unsigned long start;

    start = (unsigned long) addr;
// 由于start调整,size的大小也会变大
    size += start & 0xfff;
// start调整为所在页的起始地址
    start &= 0xfffff000;
// 这里的size是逻辑地址,线性地址需要加上段的起始地址,ldt[2]表示数据和堆栈段
    start += get_base(current->ldt[2]);
// 下面循环验证页,如果不可写,则复制页面
    while (size>0) {
        size -= 4096;
        write_verify(start);
        start += 4096;
    }
}

verify_area验证内存的起始位置和范围的调整示意图。主要是因为内存是以页为单位操作的

write_verify
void write_verify(unsigned long address)
{
    unsigned long page;
// 判断页目录项是否存在,这里为啥移动20,而不是22?下面解释
    if (!( (page = *((unsigned long *) ((address>>20) & 0xffc)) )&1))
        return;
    page &= 0xfffff000;
// 页表地址
    page += ((address>>10) & 0xffc);
// 页面不可写,写时复制
    if ((3 & *(unsigned long *) page) == 1)  /* non-writeable, present */
        un_wp_page((unsigned long *) page);
    return;
}

  • 分页机制
page = *((unsigned long *) ((address>>20) & 0xffc 要看懂这句,需要回顾一下分页机制

看上图,正常来说右移22位正好是目录号,移动20位是什么鬼?下面要搞清楚两个方面:
  • 目录号是从1开始的,不能从0开始
  • 目录号和指向目录的指针之间有个联系,实际上,看下图目录表从0x0000开始,可以一个指针占4个字节,目录号*4刚好等于指针地址,也是目录号左移动2位,这个等于 (address>>20) & 0xffc

  •  页表项

 P--位0是存在(Present)标志,用于指明表项对地址转换是否有效。P=1表示有效;P=0表示无效。在页转换过程中,如果说涉及的页目录或页表的表项无效,则会导致一个异常。如果P=0,那么除表示表项无效外,其余位可供程序自由使用,如图4-18b所示。例如,操作系统可以使用这些位来保存已存储在磁盘上的页面的序号。

R/W--位1是读/写(Read/Write)标志。如果等于1,表示页面可以被读、写或执行。如果为0,表示页面只读或可执行。当处理器运行在超级用户特权级(级别0、1或2)时,则R/W位不起作用。页目录项中的R/W位对其所映射的所有页面起作用。
U/S--位2是用户/超级用户(User/Supervisor)标志。如果为1,那么运行在任何特权级上的程序都可以访问该页面。如果为0,那么页面只能被运行在超级用户特权级(0、1或2)上的程序访问。页目录项中的U/S位对其所映射的所有页面起作用。
A--位5是已访问(Accessed)标志。当处理器访问页表项映射的页面时,页表表项的这个标志就会被置为1。当处理器访问页目录表项映射的任何页面时,页目录表项的这个标志就会被置为1。处理器只负责设置该标志,操作系统可通过定期地复位该标志来统计页面的使用情况。
D--位6是页面已被修改(Dirty)标志。当处理器对一个页面执行写操作时,就会设置对应页表表项的D标志。处理器并不会修改页目录项中的D标志。
AVL--该字段保留专供程序使用。处理器不会修改这几位,以后的升级处理器也不会。
所以(3 & *(unsigned long *) page) == 1这里是判断R/W标志位是否为0,为0则表示只读,这个是父进程的数据段,需要复制

参考:

  1. 0.11 内核 在内核态上如何实现‘写时复制’ verify_area write_verify
  2. 《linux 内核完全剖析》上帝为什么是右移20,而不是22! dir = (unsigned long *) ((from>>20) & 0xffc)
  3. Linux 内核完全注释
原文地址:https://www.cnblogs.com/vczf/p/12635839.html