粗谈linux内存管理

Linux内存管理
本文解析了Linux内存管理系统,包括物理内存的分区与初始化、伙伴系统的工作原理、任务内存区域的分配及虚拟内存映射机制。详细介绍了从物理内存到虚拟内存的映射过程,以及程序加载时的数据段和栈的分配。

先转一张图,图出自chinaunix论坛,but具体找不到是哪个帖子。



      写在最前面:linux每个进程都有4G虚拟内存空间,而虚拟内存可以这么理解,就相当于我用3公里的铁轨(物理内存),让火车(程序当前需要的物理内存数)从北京跑到上海(虚拟内存),  只要铁轨铺的够及时,火车始终能在铁轨上跑。

       在应用层看到的内存地址全部都是虚拟地址。

一:

       在图上面,观察到物理内存是分区的,分别分成DMA,NORMAL,HIGHMEM。这个分区过程是在start_kernel的时候,setup_arch ->arch_mem_init(cmdline_p)  ->paging_init() ->free_area_init(zones_size);->free_area_init_core  这一系列的流程完成了物理内存的分区。but一般嵌入式系统中,只有ZONE_DMA一个区。

       分区完毕后,需将这些区通过链表连接起来,这时候就出现了zonelist,同样是在start_kernel中 build_all_zonelists ->__build_all_zonelists->build_zonelists,通过这三个不同的zone,链接在zonelist->zone上面。

        start_kernel接下来关于内存部分的,就该轮到mem_init了,mem_init其实也没什么花头,就是统计一下有多少空闲页,然后将空闲的页放入zone->free_area[order]->free_listpage->lru在这个链表里面),zone->free_area[order]->nr_free是记录空闲页数量的。这个函数可以这么理解,对物理内存进行了以及比较系统的初始化,一页一页的比较清晰了。这时候已经精确到页了。

    salb接下来就是这个蛋疼的伙伴系统了,其实也很容易理解,这个系统就是用来分配物理内存的,比如我申请一个32字节的内存,它就会给我去32字节的那个链表的物理页里寻找空间,然后分配给我。 kmem_cache_init这个start_kernel函数就是slab功能的初始化。而这个slab也就先搞几个大小不等的(32,64,128…………)的空间页表出来,再者有三链结构,slabs_partial; slabs_full;slabs_free;这个三链是用来管理分配物理内存的,当其中某个特定大小的三链被填满后,就会重新分配出新的一页,page_alloc。start_kernel经过这一步骤后,就可以使用各种kmalloc类似的函数了。到这里结束,物理内存已经被有效的管理起来了。

         内核部分暂时告一段落。

二:

         接下来就是蛋疼的task->mm,这个东西怎么出来的呢,揭露过程有点羞涩,中间可能存在一些错误观点。

         一个程序第一件事情就是do_execve(只有第一次载入镜像的时候才会有此过程),这时候就是去调用mm_alloc,初始化current->mm

int do_execve(char * filename,
	char __user *__user *argv,
	char __user *__user *envp,
	struct pt_regs * regs)
{
	……
	bprm->p = PAGE_SIZE*MAX_ARG_PAGES-sizeof(void *);

	bprm->file = file;
	bprm->filename = filename;
	bprm->interp = filename;
	bprm->mm = mm_alloc();	//这里malloc出mm
	……

	retval = copy_strings(bprm->argc, argv, bprm);
	if (retval < 0)
		goto out;

	retval = search_binary_handler(bprm,regs);	//这个
        ……
}
      上面的代码做大的简化,就留了几段对我们理解有用的。其中mm_alloc最最重要的是生成了pgd这个目录项,这个目录项最后可用于物理地址的查找,由于pgd分配的是物理地址,所以每个进程的pgd是不同的。

    因为我们的执行文件是elf,所以search_binary_handle中最为关键的load_elf_binary。在load_elf_binary这个函数里,实现了执行文件code的载入,数据段的载入,以及栈顶位置的分配。

          比较有用的载入信息是:1、函数里的静态变量存放在数据段里2、全局变量在BSS里面,由程序编译完链接的时候定义3、函数里的参数,存放在栈里,栈顶set_arg_pages(bprm,randomize_stack_topSTACK_TOP,executable_table)是随机分配的

下面通过一个例子来简单说明这载入信息。

#include <stdio.h>
#include <malloc.h>
int z=0;
static int y=0;
int main()
{
	unsigned int i;
	i=0;
	static int j=1;
	char *c;
	c = (char *)malloc(1024*1024);
	printf("i:%lx\n",&i);
	printf("global_address:%lx\n",&z);
	printf("static g_address:%lx\n",&j);
	printf("static:%lx\n",&y);
	printf("malloc:%lx\n",c);
	
	free(c);
	return 0;
}
运行结果:

star: 0x440a74 end:0x440b30 stack:0x7fad4e80//这个是在内核打印的,跟程序无关,start和end都是data数据段的

i:7fad4d9c

g_address:440b50

staticg_address:440b54

static:440ac0

malloc:2abe1008

       这还比较云里雾里,but有一点是确定的,非全局变量的静态数据存放在data数据段里。

    接下来用readelf -a来看编译完后的信息。

ELF Header:

……

Section Headers:

  [Nr] Name              Type            Addr     Off   Size   ES Flg Lk Inf Al

  ……

  [21] .bss              NOBITS          00440b30 000b30 000030 00  WA 0   0 16

  ……

There are no sectiongroups in this file.

Program Headers:

  Type          Offset   VirtAddr   PhysAddr  FileSiz MemSiz  Flg Align

  ……

  LOAD           0x000a74 0x00440a74 0x00440a740x000bc 0x000ec RW  0x1000

……

There are norelocations in this file.

There are no unwindsections in this file 

Symbol table'.dynsym' contains 23 entries:

   Num:   Value  Size Type    Bind  Vis      Ndx Name

     0: 00000000     0 NOTYPE LOCAL  DEFAULT  UND

……

    18: 00440b50     4 OBJECT GLOBAL DEFAULT   21 z

……

Symbol table'.symtab' contains 80 entries:

   Num:   Value  Size Type    Bind  Vis      Ndx Name

  ……

    50: 00440b54     4 OBJECT LOCAL  DEFAULT   21 y

  ……

    62: 00440b50     4 OBJECT GLOBAL DEFAULT   21 z

 ……

 

      这时候是不是已经发觉了,全部变量的虚拟地址也是在编译的时候给定义好了。这时候其实很容易想到了这些编译完后就存在的信息,都存放在mm->vma里面,一个个vma链表。数据段,bss段这些vma,是程序一开启就挂载上去的,malloc出来的vma,只要不去释放,也是挂在那里的,而栈的vma就随时在各种删除添加。至于VMA,它有一个红黑树和单向链表,这个查找起来和插入进去都很方便。

三:

    那么程序跑起来后是怎么调用物理地址的呢?do_page_fault(缺页中断)中将虚拟内存转换成物理内存,首先要找到虚拟内存所在的vma,接下来就是需找物理内存pgd是一个4k(4096)表,虚拟内存的bit 31-22pgd的索引,得到pmd,因为采用二级页表,所以pmd也就是pgd,再根据pmd,结合虚拟地址的bit 21-12 得到pte,再感觉pte得到物理内存的前20 bit,虚拟地址的后12bit结合,得到相应的物理地址。其实pgdpte可以这么理解,都是1024字节的表,1024*1024这是这两张表结合起来的大小,再加上偏移量是4k。也就是4k*1024*1024=4G,刚好进程虚拟地址的大小。


本人水平有限,只能大致分析至此。这部分东西很多很羞涩难懂。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值