Linux 0.11 内核在内核空间创建进程时不使用写时复制技术

来源:互联网 发布:唯美的爱情动作片 知乎 编辑:程序博客网 时间:2024/06/05 07:12

       在Linux0.12内核完全剖析中,有这样一段话:"由于创建新进程的过程是通过完全复制父进程代码段和数据段的方式实现,因此在首次使用fork()创建新进程init()时,为了确保新进程用户态栈中没有进程0的多余信息,要求进程0在创建第一个新进程(进程1)之前不要使用其用户态堆栈,即要求任务0不要调用函数。"

       一直对这段话不太理解,今天研究了一下午,总算有点眉目了。

       首先,内核在内核地址空间并不使用写时复制技术,这是因为在系统内核代码的实现中,写时复制机制代码限定了写时复制只能用于用户空间,具体代码流程如下:fork()-->copy_mem()->copy_page_tables()。在copy_page_tables()函数中有这样一段代码:

      nr = (from==0)?0xA0:1024;
        for ( ; nr-- > 0 ; from_page_table++,to_page_table++) {
            this_page = *from_page_table;
            if (!this_page)
                continue;
            if (!(1 & this_page)) {
                if (!(new_page = get_free_page()))
                    return -1;
                read_swap_page(this_page>>1, (char *) new_page);
                *to_page_table = this_page;
                *from_page_table = new_page | (PAGE_DIRTY | 7);
                continue;
            }
            this_page &= ~2;
            *to_page_table = this_page;
            if (this_page > LOW_MEM) {
                *from_page_table = this_page;
                this_page -= LOW_MEM;
                this_page >>= 12;
                mem_map[this_page]++;
            }
        }

       在调用写时复制机制相关函数时,系统会判断要复制的地址空间是否位于内核代码地址空间,即nr = (from==0)?0xA0:1024;若位于内核地址空间,则肯定是系统第一次调用fork()复制内核地址空间,即任务0创建任务1,由于任务0的特殊性,只需要复制640KB空间就可以了。关键是if (this_page > LOW_MEM) 这段代码,系统判断要复制的地址空i安是否属于内核地址空间,若是,则什么也不做,若不是,则进行写时复制机制,将原页表项所指内存页也设为只读,如此而来,则不论是父进程还是子进程,只要有一个进程写共享的物理内存,就会引起缺页异常,导致写时复制。若是内核地址空间,则只有子进程即任务1所指的内存页面是只读的,子进程对内存页面的写操作仍会导致写时复制,但父进程可随时读写其地址空间,不会引起写时复制。

       补充一下对"内核空间不使用写时复制机制"的本质理解:参看另一篇博客:http://blog.csdn.net/yihaolovem/article/details/37958351(在这片博客之后很长时间写的)

       因为任务0和任务1这两个进程比较特殊,他们的进程虚拟地址空间不同,但是引用的却是完全相同的内核代码区内(小于1M的物理内存)相同的代码和物理内存页面(640KB)只是执行的代码不在一处。由于引用的是相同的一段物理内存地址空间,而执行的代码不在一处,相当于一段物理内存空间中有两个进程,但两个进程共享着同一个用户堆栈,由于每个进程都有自己的私有内核堆栈(存在于任务描述符结构体中),所以这两个进程各自有自己的内核堆栈。

       在进程0创建新进程1之前应当禁止使用其用户态堆栈,以保持其干净的状态,因为进程0必然要创建进程1,所以其用户态堆栈必然在创建进程1的过程中由两个进程共享,而不能独立成进程0的私有用户态堆栈。若进程0弄脏了用户堆栈,则进程1被fork后,用户堆栈中也会存在进程0中遗留下来的脏东西,而他们虽然共享同一段内核代码,但执行的地址不同,故用户堆栈不可苟同。

        由于内核空间并不使用写时复制机制(我想,可能是因为内核是全局的,由所有进程共享的,故不需要进行写时复制,还有就是安全性方面的考虑还有进程0和1的特殊性所致),所以进程0创建进程1后,其代码数据地址空间仍是共享的,但进程1如要执行函数调用,则会读写其用户堆栈,引起写时复制(必须保证此时的用户堆栈干净),在主内存分配一页内存作为其私有用户堆栈,至此,进程0和进程1的用户堆栈独立开来。由于内核调度程序是随即的,所以创建进程1后,仍不能确定进程0和1的执行顺序,若进程0先执行但弄脏了用户堆栈,则仍是有问题的,会导致进程1因写用户堆栈引起写时复制时复制脏了的用户堆栈,所以要保证进程0不可调用函数而弄脏用户堆栈。

       综上所述,由于进程0的特殊性,导致其fork进程1时要保证用户堆栈的干净,就要保证其不调用函数,所以他的fork().pause()函数都是内联函数。

       另外,进程0执行内联的系统调用fork(),pause()时也是一种函数调用,但是这是系统调用,使用的是系统堆栈,故不会影响其用户堆栈。我觉得其实内核fork还是使用了写时复制的,即进程1写操作会引起写时复制操作。追踪内核代码也可以看到,写时复制调用的函数也只是对内核空间的写时复制打印一句警告,但仍会最终执行un_wp_page()函数为其在主内存分配一页内存,并取消写保护。

1 0
原创粉丝点击