免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 3128 | 回复: 18

[内存管理] 内存管理的逆向映射。 [复制链接]

论坛徽章:
0
发表于 2016-01-07 21:21 |显示全部楼层
通过页来遍历到每个进程映射到它的页表项。
大概的流程就是:
page->mapping, 存放struct anon_vma结构体。

anon_vma相当于一个链表表头,遍历该链表可以获取struct vm_area_struct结构体。
然而可以通过vm_area_struct 的mm,进而确定了pgd的地址。

有了pgd,只需要确定一个虚拟地址,就可以最终确定了pte.

page->index和vm_area_struct->pg_off和vm_area_struct->vm_start可以确定
映射到page的虚拟地址(address).

因此在有了pgd和addressde下,就可以获取了pte..

这样是正确的嘛?


这样的话, struct anon_vma是对应于page的,就是说每个匿名页都会有一个anon_vma的结构体?它是如何分配的?

不过struct vm_area_struct映射的区间可以是多页的,如果是多个匿名页..
该结构体的字段:
struct list_head anon_vma_node;
struct anon_vma *anon_vma;
又是如何指示的?

请教大神。。非常感谢。。

论坛徽章:
20
程序设计版块每日发帖之星
日期:2015-08-17 06:20:00程序设计版块每日发帖之星
日期:2016-07-16 06:20:00程序设计版块每日发帖之星
日期:2016-07-18 06:20:00每日论坛发贴之星
日期:2016-07-18 06:20:00黑曼巴
日期:2016-12-26 16:00:3215-16赛季CBA联赛之江苏
日期:2017-06-26 11:05:5615-16赛季CBA联赛之上海
日期:2017-07-21 18:12:5015-16赛季CBA联赛之青岛
日期:2017-09-04 17:32:0515-16赛季CBA联赛之吉林
日期:2018-03-26 10:02:16程序设计版块每日发帖之星
日期:2016-07-15 06:20:0015-16赛季CBA联赛之江苏
日期:2016-07-07 18:37:512015亚冠之萨济拖拉机
日期:2015-08-17 12:21:08
发表于 2016-01-08 13:12 |显示全部楼层
搜一下anon_vma_alloc和anon_vma_prepare

一个vma貌似只能属于一个anon_vma,一个anno_vma里可以有多个vma,具体可以参见ULK第17章的图17-1。

论坛徽章:
9
程序设计版块每日发帖之星
日期:2016-02-11 06:20:00程序设计版块每日发帖之星
日期:2016-02-14 06:20:00程序设计版块每日发帖之星
日期:2016-02-14 06:20:0015-16赛季CBA联赛之吉林
日期:2016-03-23 17:25:0015-16赛季CBA联赛之浙江
日期:2016-04-01 08:25:0615-16赛季CBA联赛之山西
日期:2016-04-01 10:09:1915-16赛季CBA联赛之广夏
日期:2016-06-03 15:58:212016科比退役纪念章
日期:2016-07-28 17:42:5215-16赛季CBA联赛之广东
日期:2017-02-20 23:32:43
发表于 2016-01-08 16:31 |显示全部楼层
通过页来遍历到每个进程映射到它的页表项。
我用一个代码例程给你讲解如何使用一个已知的 struct page 去遍历进程映射到该 struct page 的页表.
1. 映射分为文件映射和匿名映射,之间的区别简单理解为:
文件映射是在用户空间通过 open ,read,write 使用的 page 属于文件映射.
匿名映射是在用户空间通过 malloc 等分配的空间对应的 page.
2. 如何在一个 struct page 中区分该页文件映射页还是匿名映射页?
在 struct page 中有成员 struct address_space 成员,如果其值的最低位为零,则表示该页是文件映射页,如果最低位为 1,则表示该页是匿名页.
可以使用下面代码区别,
if(page->mapping & 0x01)
     /* 该页是匿名映射页 */
else
     /*  该页是文件映射页*/
3. 为什么需要上面这么区分呢?
因为文件映射和匿名映射在映射时不同的数据结构进行管理,所以通过 page 找到进程的页表方式就采用不同的方法.
那么我就分别将两种方法如何找到页表.


1) 匿名页找到对应的进程页表.见下面代码:
  struct page *page; // page 已知.
  struct address_space *mapping = page->mapping;  //此时 mapping 指向匿名结构体 struct anon_vma,
  struct anon_vma *anon_vma = (strcut anon_vma *)mapping;  // 内核使用 strruct anon_vma , struct anon_vma_chain , struct vm_area_struct 三个结构体来沟通匿名页和虚拟空间之间的联系,其使用方法如下:
  struct anon_vma_chain *vma_chain;
  
    // 遍历所有使用该匿名页的 struct anon_vma_chain.
   list_for_each_entry(vma_chain,&anon_vma->head,same_anon_vma) {
        struct vm_area_struct *vma;
      
        // 遍历所有在 anon_vma_chain 上的 vma.
        list_for_each_entry(vma,&vma_chain->same_vma,anon_vma_chain) {
                    // 现在 vma 就是使用该匿名页的虚拟空间.
                   struct vm_area_struct *loop_vma;
                   pgd_t *pgd;
                   struct mm_struct *mm = vma->vm_mm; // 获得虚拟空间对应的内存空间结构体 struct mm_struct.

                   // 获得进程的全局页表.
                   pgd = mm->pgd;
                  // 遍历进程使用的页表.
                  for(loop_vma = mm->mmap ; loop_vma->vm_next != NULL ; loop_vma = loop_vma->vm_next) {
                           unsigned long start;
                           unsigned long end;
                  
                           for(start = loop_vma->vm_start ; start < loop_vma->vm_end ; start++) {
                                     pgd_t *pgd;
                                     pmd_t *pmd;
                                     pud_t *pud;
                                     pte_t  *pte;

                                    pgd = pgd_offset(loop_vma->vm_mm,start); // 一级页表.
                                    pmd = pmd_offset(pgd,start);    // 二级页表
                                    pud  = pud_offset(pmd,start);  // 三级页表
                                    pte    = pte_offset(pud,start);  // 四级页表
                           }
                  }

        }
      
}


先写到这里,老板来了,后面有问题继续补上!!!!

论坛徽章:
20
程序设计版块每日发帖之星
日期:2015-08-17 06:20:00程序设计版块每日发帖之星
日期:2016-07-16 06:20:00程序设计版块每日发帖之星
日期:2016-07-18 06:20:00每日论坛发贴之星
日期:2016-07-18 06:20:00黑曼巴
日期:2016-12-26 16:00:3215-16赛季CBA联赛之江苏
日期:2017-06-26 11:05:5615-16赛季CBA联赛之上海
日期:2017-07-21 18:12:5015-16赛季CBA联赛之青岛
日期:2017-09-04 17:32:0515-16赛季CBA联赛之吉林
日期:2018-03-26 10:02:16程序设计版块每日发帖之星
日期:2016-07-15 06:20:0015-16赛季CBA联赛之江苏
日期:2016-07-07 18:37:512015亚冠之萨济拖拉机
日期:2015-08-17 12:21:08
发表于 2016-01-11 19:44 |显示全部楼层
anno_vma_chain是2.6.30+以后引入的?有这个结构确实方便多了。

论坛徽章:
0
发表于 2016-01-19 09:57 |显示全部楼层
多谢你的回复,有些天没来论坛了。
按照你的说法就是一个vm_area_struct只能属于一个anon_vma。一个anon_vma属于一个page。而这个anon_vma的链表元素都是包含该匿名page的vm_area_struct。
比较疑惑的是,一个vm_area_struct的start和end可以包含多个页(匿名页)。
然而多个page,每个page都会有自己的anon_vma吧?
这样的话,包含多个匿名页的vm_area_struct是作为哪个anon_vma的链表元素。。

回复 2# nswcfd


   

论坛徽章:
0
发表于 2016-01-19 10:10 |显示全部楼层
回复 3# Buddy_Zhang1

多谢你的帮助。

在最后一个
for(start = loop_vma->vm_start ; start < loop_vma->vm_end ; start++)
就是去遍历了整个vm_area_struct的地址区间,然后去找到该区间所有的pte了,而不是有参数page指定的对应的pte咯。

在获取到了pgd之后,我在1L的时候有提到过,
page->index和vm_area_struct->pg_off和vm_area_struct->vm_start可以确定
映射到page的虚拟地址(address).

因此这样,就可以找到了由page对应的pte了。

问题的困惑,可以看下5L的回复哦。

另外,不知道内存管理你了解不。
函数:   bootmem_init_node

for_each_nodebank(i, mi, node) {   //遍历所有的bank,找到bank号为node的bank。。。
        struct membank *bank = &mi->bank;
        unsigned long start, end;

        start = bank_pfn_start(bank);
        end = bank_pfn_end(bank);
        
//更新start_pfn和end_pfn的值,遍历该node号的所有bank,记录该node号的内存起始和结束。计算的结果以页为单位了。
        if (start_pfn > start)
            start_pfn = start;
        if (end_pfn < end)
            end_pfn = end;
//这里我挺奇怪,如果bank是高端地址的话,也是直接映射咯?没有进行处理?
        map_memory_bank(bank); //函数就是创建映射,映射到虚拟地址。(直接映射)
    }

我看了下,所有的内存信息都包含在bank的数据结构里面,好像并没有特别对待。
for_each_nodebank就遍历所有的bank.......
   

论坛徽章:
0
发表于 2016-01-19 13:51 |显示全部楼层
回复 2# nswcfd

翻看了下ULK的这个章节的内容,算是大概明白了。

论坛徽章:
0
发表于 2016-01-19 14:05 |显示全部楼层
回复 3# Buddy_Zhang1

对于这个匿名映射,我现在大概明白了。
原来page和anon_vma并不是一一映射的关系吧。
而是多个page可能都会指向同一个anon_vma。

ULK中有这么一句话:
即使一个匿名线性区存有不同的页,也始终只有一个反向映射链表用于该区域的所有的页框。
   

至于分配的动作则是:
当为一个匿名线性区分配第一页的时候,内核创建一个新的anon_vma数据结构。那么该线性区的页框都指向这个anon_vma。
后面如果有需要指向这些匿名页框的,则把它的vm_area_struct添加到anon_vma的链表中。

这样的一个结果可能出现,一个page,然后找到它的anon_vma,接着利用anon_vma可以遍历所有的vm_area_struct的数据结构,
但是,并不是遍历到的vm_area_struct就包含了该page的(线性地址就在vm_area_struct的线性区内),这种情况下返回SWAP_AGAIN。

static int try_to_unmap_one(struct page *page, struct vm_area_struct *vma, int migration)
{
        struct mm_struct *mm = vma->vm_mm;
        unsigned long address;
        pte_t *pte;
        pte_t pteval;
        spinlock_t *ptl;
        int ret = SWAP_AGAIN;

        address = vma_address(page, vma);  //根据page和vma可以得到线性地址.
        if (address == -EFAULT)
                goto out;
        //判断是不是在线性区内,如果不是,则直接退出。。返回值正是SWAP_AGAIN
        pte = page_check_address(page, mm, address, &ptl, 0);
        if (!pte)
                goto out;
        ....
        ....
out:
        return ret;
}

至于那个bank在高端内存上的处理,还请指点迷津。多谢。~

论坛徽章:
9
程序设计版块每日发帖之星
日期:2016-02-11 06:20:00程序设计版块每日发帖之星
日期:2016-02-14 06:20:00程序设计版块每日发帖之星
日期:2016-02-14 06:20:0015-16赛季CBA联赛之吉林
日期:2016-03-23 17:25:0015-16赛季CBA联赛之浙江
日期:2016-04-01 08:25:0615-16赛季CBA联赛之山西
日期:2016-04-01 10:09:1915-16赛季CBA联赛之广夏
日期:2016-06-03 15:58:212016科比退役纪念章
日期:2016-07-28 17:42:5215-16赛季CBA联赛之广东
日期:2017-02-20 23:32:43
发表于 2016-01-19 14:13 |显示全部楼层
你好,我使用基于 ARM 架构的内存管理和您交流一下 bootmem_init_node 函数.
内核使用 meminfo 表示物理内存,meminfo 的成员 struct membank 表示一块物理内存块.
内核在启动期间会去初始化 meminfo 结构,其可以使用 uboot 传递的 ATAG 参数初始化,或者使用机器特定的 struct machine_desc 的 fixup() 函数去初始化 meminfo 结构.
正如您所描述的,分情况讨论
1. 如果我们使用一块物理内存为 1024M,那么初始化之后 meminfo->bank[0].size = 1024M,至于 meminfo->bank[0].start 的值是根据 PHYS_OFFSET 定义的.
2. 如果我们使用多块物理内存构成 1024M,那么每一块物理内存对应的一个 struct membank.
内核在 setup_arch() 函数中初始化 meminfo 之后,然后会使用 sort() 函数根据每个 membank 的起始地址从低到高的排序,其实现在 arm_memblock_init() 函数内,
排序之后 meminfo 里面的 membank 就从低地址到高地址排序.
接着内核会调用 parse_early_param() 函数,该函数要实现很多功能,其中和内存有关的作用就是设置 vmalloc_min 的值,该值代表 VMALLOC 的起始地址,该值可以通过内核参数 "vmalloc=" 来设置其大小.
设置了内核参数 "vmalloc" 的值之后就可以通过和 VMALLOC_END 获得 vmalloc_min 的具体值,
vmalloc_min 代表低端内存的最大虚拟地址,所以内核低端内存虚拟地址范围就是: PAGE_OFFSET 到 vmalloc_min .
接着内核在 paging_init() 函数的 sanity_check_meminfo() 函数中,会根据 vmalloc_min 的值,对 meminfo 中所有的 membank 进行检测.
!!!!!! 如果您启用 高端内存,也就是 CONFIG_HIGHMEM 启用,按下面情况处理.
1. membank 的范围低于 vmalloc_min 的,其 struct membank 的 highmem 成员一直为 0.
2. membank 的范围有一部分和 vmalloc_min 重叠,那么内核就将这个 membank 分作两个 membank,其中一个范围为[membank->start , vmalloc_min] ,
另外一个范围为 [vmalloc_min , (membak->start + membank->size)],低地址的 membank 的 highmem = 0,高地址的 membank 的 highmem = 1.
3. membank 的起始地址大于或等于 vmalloc_min,那么就将其 membank 的 highmem = 1.
!!!!!! 如果没有启用 高端内存,那么按下面处理.
1. membank 的范围低于 vmalloc_min 的,其 struct membank 的 highmem 成员一直为 0.
2. membank 的范围有一部分和 vmalloc_min 重叠,那么内核就将大于 vmalloc_min 的地址忽略,其 membank 的范围变成 [membank->start , vmalloc_min].
3. membank 的起始地址大于或等于 vmalloc_min,直接忽略.

通过 sanity_checnk_meminfo() 函数之后,在支持高端内存的系统中,membank 被分作了 highmem = 0 的低端内存 和 highmem = 1 的高端内存.
处理完 meminfo 之后,内核就开始映射页表.
初始化页表时,只有第一块 membank 可以进行映射!!!!尽管其他属于低端内存的 membank,但其不进行页表映射.

论坛徽章:
0
发表于 2016-01-19 14:35 |显示全部楼层
回复 9# Buddy_Zhang1

我查看的内核版本是2.6.30的。这几个数据结构的定义如下:
struct membank {
        unsigned long start;
        unsigned long size;
        int           node;
};

struct meminfo {
        int nr_banks;
        struct membank bank[NR_BANKS];
};
之前有大概看了过更高点的版本是好像是有一个highmem的字段。不过就我当前版本的来看,并看不出来所有的bank中哪个是高端内存。


此外,初始化的函数调用过程大概如下:
void __init bootmem_init(void)
{
        struct meminfo *mi = &meminfo;
        unsigned long memend_pfn = 0;
        int node, initrd_node;

       for_each_node(node) { //遍历所有的内存结点。
          unsigned long end_pfn = bootmem_init_node(node, mi);
          ...
          ...
}
static unsigned long __init bootmem_init_node(int node, struct meminfo *mi)
{
        unsigned long start_pfn, end_pfn, boot_pfn;
        unsigned int boot_pages;
        pg_data_t *pgdat;
        int i;

        start_pfn = -1UL;
        end_pfn = 0;


        for_each_nodebank(i, mi, node) { //遍历该内存结点的所有bank
                struct membank *bank = &mi->bank;
                unsigned long start, end;

                start = bank_pfn_start(bank);
                end = bank_pfn_end(bank);

                if (start_pfn > start)
                        start_pfn = start;
                if (end_pfn < end)
                        end_pfn = end;

                map_memory_bank(bank);  //对该bank做映射。
        }
}
上面宏的定义:
#define for_each_nodebank(iter,mi,no)                        \
        for (iter = 0; iter < (mi)->nr_banks; iter++)        \
                if ((mi)->bank[iter].node == no)

就刚才假设的1024M的内存,sanity_check_meminfo函数中会进行拆分。
就Linux2.6.30版本,该函数如下:
#define VMALLOC_MIN        (void *)(VMALLOC_END - vmalloc_reserve)

static void __init sanity_check_meminfo(void)
{
        int i, j;

        for (i = 0, j = 0; i < meminfo.nr_banks; i++) {
                struct membank *bank = &meminfo.bank[j];
                *bank = meminfo.bank;

#ifdef CONFIG_HIGHMEM
                /*
                 * Split those memory banks which are partially overlapping
                 * the vmalloc area greatly simplifying things later.
                 */
                if (__va(bank->start) < VMALLOC_MIN && bank->size > VMALLOC_MIN - __va(bank->start)) {
                        if (meminfo.nr_banks >= NR_BANKS) {
                                printk(KERN_CRIT "NR_BANKS too low, "
                                                 "ignoring high memory\n");
                        } else if (cache_is_vipt_aliasing()) {
                                printk(KERN_CRIT "HIGHMEM is not yet supported "
                                                 "with VIPT aliasing cache, "
                                                 "ignoring high memory\n");
                        } else {
                      //这里会进行把1024M的一个bank,拆分成2个。
                                memmove(bank + 1, bank, (meminfo.nr_banks - i) * sizeof(*bank));
                                meminfo.nr_banks++;
                                i++;
                                bank[1].size -= VMALLOC_MIN - __va(bank->start);
                                bank[1].start = __pa(VMALLOC_MIN - 1) + 1;
                                j++;
                        }
                        bank->size = VMALLOC_MIN - __va(bank->start);
                }
#else
           //假设开启高端内存。
#endif
                j++;
        }
        meminfo.nr_banks = j;
}
拆分之后,对2个bank来说,1个是低端内存,1个是高端内存。但是在bootmem_init_node函数中,好像都会对
2个bank进行map_memory_bank的调用,进行映射。

最后,
初始化页表时,只有第一块 membank 可以进行映射!!!!尽管其他属于低端内存的 membank,但其不进行页表映射.

是如何确定的。。。
望指点。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP