zoukankan      html  css  js  c++  java
  • Linux源码(0.11)学习03---复制内存页表 copy_mem copy_page_tables

    前言

    先看看分页机制里面的页目录表、页表、页之间的关系。分页机制是用于将一个线性地址转换为一个物理地址。

    在I32 CPU环境里面,首先通过设置CR0寄存器,打开保护模式、开启分页机制。然后将页目录表的物理地址基址给CR3寄存器。开启分页机制后,I32将全部的物理内存空间、线性地址空间划分为一个个的页。每个页可以是4KB或者4MB。

    页目录表里面存放页目录表项,每个页目录表项指向页表。其中页目录表项的高20位为对应页表的物理地址的高20位。低12位为属性位。
    页表里面存放着页表项,每个页表项指向页。其中页表项的高20位为对应页的物理地址的高20位,低12为属性位。
    经过上面这三个结构,CPU就有了将线性地址转换为物理地址的基础。

    当CPU拿到一个线性地址后,需要将其转换为物理地址。其中一个32位的线性地址分为三部分:最高10位,中10位,低12位。
    其中高10位表示这个物理地址属于页目录表的那一项管辖,通过这个页目录表项就可以得到对应指向的页表的物理基址。
    中10位表示这个物理地址属于上一步得到的页表里面第几项管辖。通过这个页表项就可以得到这个物理地址所在的页的物理基址。
    低12位表示这个物理地址在所属的页(上一步确定了这个页的物理基址)里面的偏移。

    页目录项和页表项

     

    上图就是页目录项和页表项的格式。可以看出,由于页表或者页的物理地址都是4KB对齐的(低12位全是零),所以上图中只保留了物理基地址的高20位(bit[31:12])。低12位可以安排其他用途。

    【P】:存在位。为1表示页表或者页位于内存中。否则,表示不在内存中,必须先予以创建或者从磁盘调入内存后方可使用。 
    【R/W】:读写标志。为1表示页面可以被读写,为0表示只读。当处理器运行在0、1、2特权级时,此位不起作用。页目录中的这个位对其所映射的所有页面起作用。 
    【U/S】:用户/超级用户标志。为1时,允许所有特权级别的程序访问;为0时,仅允许特权级为0、1、2的程序访问。页目录中的这个位对其所映射的所有页面起作用。 
    【PWT】:Page级的Write-Through标志位。为1时使用Write-Through的Cache类型;为0时使用Write-Back的Cache类型。当CR0.CD=1时(Cache被Disable掉),此标志被忽略。对于我们的实验,此位清零。 
    【PCD】:Page级的Cache Disable标志位。为1时,物理页面是不能被Cache的;为0时允许Cache。当CR0.CD=1时,此标志被忽略。对于我们的实验,此位清零。 
    【A】:访问位。该位由处理器固件设置,用来指示此表项所指向的页是否已被访问(读或写),一旦置位,处理器从不清这个标志位。这个位可以被操作系统用来监视页的使用频率。 
    【D】:脏位。该位由处理器固件设置,用来指示此表项所指向的页是否写过数据。 
    【PS】:Page Size位。为0时,页的大小是4KB;为1时,页的大小是4MB(for normal 32-bit addressing )或者2MB(if extended physical addressing is enabled). 
    【G】:全局位。如果页是全局的,那么它将在高速缓存中一直保存。当CR4.PGE=1时,可以设置此位为1,指示Page是全局Page,在CR3被更新时,TLB内的全局Page不会被刷新。 
    【AVL】:被处理器忽略,软件可以使用。

    copy_mem
    // 复制内存页表
    // 新进程仅设置自己的页目录项和页表项,没有分配物理内存页面
    int copy_mem(int nr,struct task_struct * p)
    {
        unsigned long old_data_base,new_data_base,data_limit;
        unsigned long old_code_base,new_code_base,code_limit;
    //选择器
    //15                                                           3     2 1    0 //                                                    索引   TI RPL // 0x0f : 1111 11:用户级 1:局部描述符表 1:索引1 代码段选择符 // 0x0f : 10111 11:用户级 1:局部描述符表 10:索引2 数据段选择符 code_limit=get_limit(0x0f); data_limit=get_limit(0x17); old_code_base = get_base(current->ldt[1]); old_data_base = get_base(current->ldt[2]); // linux0.11内核不支持代码和数据段分立的情况 if (old_data_base != old_code_base) panic("We don't support separate I&D"); if (data_limit < code_limit) panic("Bad data_limit"); // 新进程的线性地址 = 64M * 任务号 new_data_base = new_code_base = nr * 0x4000000; p->start_code = new_code_base; // 利用基地址设置数据段和代码段局部描述符 set_base(p->ldt[1],new_code_base); set_base(p->ldt[2],new_data_base); // 复制父进程的页目录项和页表项 if (copy_page_tables(old_data_base,new_data_base,data_limit)) { printk("free_page_tables: from copy_mem "); free_page_tables(new_data_base,data_limit); return -ENOMEM; } return 0; }

    这里给子进程分配了新的线性地址,但是这个线性地址对应的页目录项对应的页表还没有分配,访问会出问题的,所以需要把父进程的页表复制给子进程,注意这里只复制对应下页表,没有分配实际的物理内存,这里通过copy_page_tables完成。

    copy_page_tables

    int copy_page_tables(unsigned long from,unsigned long to,long size)
    {
        unsigned long * from_page_table;
        unsigned long * to_page_table;
        unsigned long this_page;
        unsigned long * from_dir, * to_dir;
        unsigned long nr;
    
    // 查看form和to对应的页表项是不是为第一项
        if ((from&0x3fffff) || (to&0x3fffff))
            panic("copy_page_tables called with wrong alignment");
    
    // 获取from 和to页目录项地址
        from_dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
        to_dir = (unsigned long *) ((to>>20) & 0xffc);
    
    // size表示有多少个页表,不满4M的补足
        size = ((unsigned) (size+0x3fffff)) >> 22;
        for( ; size-->0 ; from_dir++,to_dir++) {
            // P位:1表示页表存在 0表示不存在
            if (1 & *to_dir)
                panic("copy_page_tables: already exist");
            if (!(1 & *from_dir))
                continue;
    
            // 页目录项高20位存放着页表项的起始地址,这里得到页表的物理地址
            from_page_table = (unsigned long *) (0xfffff000 & *from_dir);
            // 申请新的页表,返回页表的物理地址
            if (!(to_page_table = (unsigned long *) get_free_page()))
                return -1;    /* Out of memory, see freeing */
            // 填充to的页目录项内容
            *to_dir = ((unsigned long) to_page_table) | 7;
            nr = (from==0)?0xA0:1024;
            // 取出from_page_table的内容,修改一下特权给to_page_table
            for ( ; nr-- > 0 ; from_page_table++,to_page_table++) {
                this_page = *from_page_table;
                // P位:1表示页存在 0表示不存在
                if (!(1 & this_page))
                    continue;
                // 修改读写权限 只读不可写
                this_page &= ~2;
                *to_page_table = this_page;
                // 当大于LOW_MEM,修改夫进程权限,防止夫进程修改,子进程读写脏数据
                if (this_page > LOW_MEM) {
                    *from_page_table = this_page;
                    this_page -= LOW_MEM;
                    this_page >>= 12;
                    mem_map[this_page]++;
                }
            }
        }
        invalidate();
        return 0;
    }

    参考:

    1. 页目录项和页表项

    2. Linux 内核完全注释

    3. copy_page_tables函数分析

  • 相关阅读:
    VS Code 调试报错
    Nginx反向代理设置
    Nginx 的配置文件
    Nginx 的常用的命令
    CentOS7安装Nginx
    Docker配置
    Centos7 安装MySQL 5.7
    限制Redis使用的最大内存
    C#操作Redis
    Font Awesome 字体图标
  • 原文地址:https://www.cnblogs.com/vczf/p/12644216.html
Copyright © 2011-2022 走看看