Chinaunix首页 | 论坛 | 博客
  • 博客访问: 489325
  • 博文数量: 153
  • 博客积分: 3010
  • 博客等级: 中校
  • 技术积分: 1724
  • 用 户 组: 普通用户
  • 注册时间: 2008-12-08 11:55
文章分类

全部博文(153)

文章存档

2011年(1)

2010年(55)

2009年(88)

2008年(9)

我的朋友

分类: LINUX

2010-11-25 11:54:43


有些内容不太准确,但内容比较系统,收藏先。。。



http://tauruspdj.blog.163.com/blog/static/431250062008112843423140/


linux系统 2008-12-28 16:34:23 阅读398 评论0   字号: 

我先前的日志中已经转了“Linux内存管理”上、下两篇网文,
而这篇则是参考了陈莉君老师的博客,对linux的内存管理进行了一个简要概述。

我们首先从进程的角度,来看linux中的内存空间。
****************************************************************************************************************************************
Linux中的地址空间(一)
有这么一系列的问题,是否在困扰着你:用户程序编译连接形成的地址空间在什么范围内?内核编译后地址空间在什么范围内?要对外设进行访问,I/O的地址空间又是什么样的?
先回答第一个问题。Linux最常见的可执行文件格式为elf(Executable and Linkable Format)。在elf格式的可执行代码中,ld总是从0x8000000开始安排程序的“代码段”,对每个程序都是这样。至于程序执行时在物理内存中的实际地址,则由内核为其建立内存映射时临时分配,具体地址取决于当时所分配的物理内存页面。
我们可以用Linux的实用程序objdump对你的程序进行反汇编,从而知晓其地址范围。
例如:假定我们有一个简单的C程序Hello.c
  # include
  greeting ( )  {
              printf(“Hello,world!\n”);
  }
  main()   {
         greeting();
 }
之所以把这样简单的程序写成两个函数,是为了说明指令的转移过程。我们用gcc和ld对其进行编译和连接,得到可执行代码hello。然后,用Linux的实用程序objdump对其进行反汇编:
$objdump –d hello
得到的主要片段为:
08048568 :
   8048568:     pushl  %ebp
   8048569:     movl  %esp, %ebp
   804856b:     pushl  $0x809404
   8048570:     call    8048474  <_init+0x84>
   8048575:     addl   $0x4, %esp
   8048578:     leave
   8048579:     ret
   804857a:     movl  %esi, %esi
   0804857c

:
   804857c:     pushl  %ebp
   804857d:     movl  %esp, %ebp
   804857f:     call    8048568 
   8048584:     leave
   8048585:     ret
   8048586:     nop
   8048587:     nop

其中,像08048568这样的地址,就是我们常说的虚地址(这个地址实实在在的存在,只不过因为物理地址的存在,显得它是“虚”的罢了)。

虚拟内存、内核空间和用户空间

   Linux虚拟内存的大小为2^32(在32位的x86机器上),内核将这4G字节的空间分为两部分。最高的1G字节(从虚地址0xC0000000到0xFFFFFFFF)供内核使用,称为“内核空间”。而较低的3G字节(从虚地址0x00000000到0xBFFFFFFF),供各个进程使用,称为“用户空间”。因为每个进程可以通过系统调用进入内核,因此,Linux内核空间由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟地址空间(也叫虚拟内存)。
  
    每个进程有各自的私有用户空间(0~3G),这个空间对系统中的其他进程是不可见的。最高的1GB内核空间则为所有进程以及内核所共享。另外,进程的“用户空间”也叫“地址空间”,在后面的叙述中,我们对这两个术语不再区分。
用户空间不是进程共享的,而是进程隔离的每个进程最大都可以有3GB的用户空间。一个进程对其中一个地址的访问,与其它进程对于同一地址的访问绝不冲突。比如,一个进程从其用户空间的地址0x1234ABCD处可以读出整数8,而另外一个进程从其用户空间的地址0x1234ABCD处可以读出整数20,这取决于进程自身的逻辑。
任意一个时刻,在一个CPU上只有一个进程在运行。所以对于此CPU来讲,在这一时刻,整个系统只存在一个4GB的虚拟地址空间,这个虚拟地址空间是面向此进程的。当进程发生切换的时候,虚拟地址空间也随着切换。由此可以看出,每个进程都有自己的虚拟地址空间,只有此进程运行的时候,其虚拟地址空间才被运行它的CPU所知。在其它时刻,其虚拟地址空间对于CPU来说,是不可知的。所以尽管每个进程都可以有4 GB的虚拟地址空间,但在CPU眼中,只有一个虚拟地址空间存在。虚拟地址空间的变化,随着进程切换而变化

从上面我们知道,一个程序编译连接后形成的地址空间是一个虚拟地址空间,但是程序最终还是要运行在物理内存中。因此,应用程序所给出的任何虚地址最终必须被转化为物理地址,所以,虚拟地址空间必须被映射到物理内存空间中,这个映射关系需要通过硬件体系结构所规定的数据结构来建立。这就是我们所说的段描述符表和页表,Linux主要通过页表来进行映射
于是,我们得出一个结论,如果给出的页表不同,那么CPU将某一虚拟地址空间中的地址转化成的物理地址就会不同。所以我们为每一个进程都建立其页表,将每个进程的虚拟地址空间根据自己的需要映射到物理地址空间上。既然某一时刻在某一CPU上只能有一个进程在运行,那么当进程发生切换的时候,将页表也更换为相应进程的页表,这就可以实现每个进程都有自己的虚拟地址空间而互不影响。所以,在任意时刻,对于一个CPU来说,只需要有当前进程的页表,就可以实现其虚拟地址到物理地址的转化。

Linux内核空间(二)

内核空间到物理内存的映射 

   内核空间对所有的进程都是共享的,其中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据,不管是内核程序还是用户程序,它们被编译和连接以后,所形成的指令和符号地址都是虚地址(参见2.5节中的例子),而不是物理内存中的物理地址。
虽然内核空间占据了每个虚拟空间中的最高1GB字节,但映射到物理内存却总是从最低地址(0x00000000)开始的,如图4.2所示,之所以这么规定,是为了在内核空间与物理内存之间建立简单的线性映射关系。其中,3GB(0xC0000000)就是物理地址与虚拟地址之间的位移量,在Linux代码中就叫做PAGE_OFFSET。
                  
我们来看一下在include/asm/i386/page.h头文件中对内核空间中地址映射的说明及定义:

#define __PAGE_OFFSET           (0xC0000000)
……
#define PAGE_OFFSET             ((unsigned long)__PAGE_OFFSET)
#define __pa(x)                 ((unsigned long)(x)-PAGE_OFFSET)
#define __va(x)                 ((void *)((unsigned long)(x)+PAGE_OFFSET))
对于内核空间而言,给定一个虚地址x,其物理地址为“x- PAGE_OFFSET”,给定一个物理地址x,其虚地址为“x+ PAGE_OFFSET”。

这里再次说明,__pa()仅仅把一个内核空间的虚地址映射到物理地址,而决不适用于用户空间,用户空间的地址映射要复杂得多,它通过分页机制完成


 内核空间为3GB~4GB,这1GB的空间分为如下几部分,如图1所示:



           图1  从PAGE_OFFSET开始的1GB地址空间 

 先说明图中符号的含义:

PAGE_OFFSET: 0XC0000000,即3GB

high_memory: 这个变量的字面含义是高端内存,到底什么是高端内存,Linux内核规定,RAM的前896为所谓的低端内存,而896~1GB共128MB为高端内存。如果你的内存是512M,那么high_memory是多少?是3GB+512M,也就是说,物理地址x<=896M,就有内核地址0xc0000000+x,否则,high_memory=0xc0000000+896M
或者说high_memory实际值为0xc0000000+x,但最大不能超过0xc0000000+896M

在源代码中函数mem_init中,有这样一行:
high_memory = (void *) __va(max_low_pfn * PAGE_SIZE);
其中,max_low_pfn为物理内存的最大页数。

所以在图中,PAGE_OFFSET到high_memory 之间就是所谓的物理内存映射只有这一段之间物理地址与虚地址之间是简单的线性关系
  还要说明的是,要在这段内存分配内存,则调用kmalloc()函数。反过来说,通过kmalloc()分配的内存,其物理页是连续的
void *kmalloc(size_t size, gfp_t flags);
 * The @flags argument may be one of:
 * %GFP_USER - Allocate memory on behalf of user.  May sleep.
 * %GFP_KERNEL - Allocate normal kernel ram.  May sleep.
 * %GFP_ATOMIC - Allocation will not sleep.

VMALLOC_START:非连续区的的起始地址。
VMALLOC_END:非连续区的的末尾地址
在非连续区中,物理内存映射的末端与第一个VMalloc之间有一个8MB的安全区,目的是为了“捕获”对内存的越界访问。处于同样的理由,插入其他4KB的安全区来隔离非连续区。
非连续区的分配调用vmalloc()函数
void *vmalloc(unsigned long size);

vmalloc()与 kmalloc()都是在内核代码中用来分配内存的函数,但二者有何区别?
   从前面的介绍已经看出,这两个函数所分配的内存都处于内核空间,即从3GB~4GB;但位置不同,kmalloc()分配的内存处于3GB~high_memory之间,这一段内核空间与物理内存的映射一一对应,而vmalloc()分配的内存在VMALLOC_START~4GB之间,这一段非连续内存区映射到物理内存也可能是非连续的。

   vmalloc()工作方式与kmalloc()类似, 其主要差别在于前者分配的物理地址无需连续,而后者确保页在物理上是连续的(虚地址自然也是连续的)。

尽管仅仅在某些情况下才需要物理上连续的内存块,但是,很多内核代码都调用kmalloc(),而不是用vmalloc()获得内存。这主要是出于性能的考虑。vmalloc()函数为了把物理上不连续的页面转换为虚拟地址空间上连续的页,必须专门建立页表项。还有,通过vmalloc()获得的页必须一个一个的进行映射(因为它们物理上不是连续的),这就会导致比直接内存映射大得多的缓冲区刷新。因为这些原因,vmalloc()仅在绝对必要时才会使用——典型的就是为了获得大块内存时,例如,当模块被动态插入到内核中时,就把模块装载到由vmalloc()分配的内存上。

vmalloc()函数用起来比较简单:
char *buf;
buf = vmalloc(16*PAGE_SIZE);  /*获得16页*/
if (!buf)
    /* 错误!不能分配内存*/

在使用完分配的内存之后,一定要释放它:
vfree(buf);

****************************************************************************************************************************************
下面的内容参考LDD3,十五章内存映射

在内核空间中,kmalloc分配的空间往往被称为内核逻辑地址(必须与物理地址的映射是线性的和一一对应的),vmalloc分配的空间被称为内核虚拟地址(不必和物理地址线性对应)。

所有的内核逻辑地址都是内核虚拟地址,但是许多内核虚拟地址不是逻辑地址
内核逻辑地址通常保存在unsigned long或者void*类型的变量中;内核虚拟地址通常保存在指针变量中。

物理内存中,低端内存存在于内核逻辑地址上,且与内核逻辑地址线性对应;而高端内存是不存在内核逻辑地址上的,只处于内核虚拟地址之上的。所有的物理内存都是通过page结构来描述的。
page结构中有个void *virtual域,表示如果页面被映射,则指向页的内核虚拟地址,如果未被映射则为NULL。低端内存页面总是被映射,而高端内存通常不被映射。

page结构和内核地址的转换
struct page *virt_to_page(void *kaddr); /* 内核逻辑地址->page结构指针 */
struct page *pfn_to_page(int pfn); /* 页帧号->page结构指针,应在传递前用pfn_valid检查页帧号的合法性 */
void *page_address(struct page *page); /* page结构指针-> 内核虚拟地址,大多数情况用kmap替代 */

#include
void *kmap(struct page *page);  /* page结构指针-> 内核虚拟地址 ,不同于page_address若是高端内存,则会创建映射,不要持有映射过长时间。会休眠*/
void kunmap(struct page *page); /* 释放kmap创建的映射 */
#include
#include
void *kmap_atomic(struct page *page, enum km_type type); /* kmap的原子版本,type指定使用的槽 */
void kunmap_atomic(void *addr, enum km_type type);
The only slots that make sense for drivers are KM_USER0 and KM_USER1 (for code running directly from a call from user space), and KM_IRQ0 and KM_IRQ1 (for interrupt handlers).

前面提到了每个进程都各自维护一个页表,来实现内存映射关系。2.6内核中删除了对页表直接操作的需求。
虚拟内存区(VMA)用于管理进程地址空间中不同区域的内核数据结构。查看/proc//maps文件就能了解进程的内存区域。 /proc/self是一个特殊的文件,它始终指向当前进程。

# cat /proc/1/maps    look at init
        start-end                pern       offset       major:minor   inode          image
08048000-0804e000 r-xp 00000000   03:01      64652      /sbin/init  text
0804e000-0804f000 rw-p 00006000   03:01      64652      /sbin/init  data
0804f000-08053000 rwxp 00000000   00:00      0          zero-mapped BSS
40000000-40015000 r-xp 00000000   03:01      96278      /lib/ld-2.3.2.so  text
40015000-40016000 rw-p 00014000   03:01     96278      /lib/ld-2.3.2.so  data
40016000-40017000 rw-p 00000000   00:00     0          BSS for ld.so
42000000-4212e000 r-xp 00000000   03:01     80290      /lib/tls/libc-2.3.2.so  text
4212e000-42131000 rw-p 0012e000  03:01     80290      /lib/tls/libc-2.3.2.so  data
42131000-42133000 rw-p 00000000  00:00     0          BSS for libc
bffff000-c0000000     rwxp 00000000  00:00     0          Stack segment
ffffe000-fffff000          ---p  00000000  00:00     0          vsyscall page
perm 内存区域的读写和执行权限的位掩码,p表示私有,s表示共享
上面这些字段和vm_area_struct结构中的重要成员对应。
vm_operations_struct结构包含了处理进程的内存需求的操作。
mm_struct结构,包含了虚拟内存区域链表、页表以及其它大量内存管理信息等待。

mmap设备操作

在现代Unix操作系统中,内存映射是最吸引人的特征。对于驱动程序来说,内存映射可以提供给用户程序直接访问设备内存的能力。例如:X-windows服务的有关显卡的/dev/mem映射。
映射一个设备意味着将用户空间的一段内存与设备内存关联。
不是所有设备都能进行mmap抽象。如串口和其它面向流的设备就不能做这样的抽象。
mmap必须以PAGE_SIZE为单位进行映射,即只能在页表一级上对虚拟地址进行管理。
mmap方法是file_operations结构的一部分,
int (*mmap) (struct file *filp, struct vm_area_struct *vma);
为了执行mmap,驱动程序只需要为该地址范围建立合适的页表,并将vma->vm_ops替换为一系列的新操作即可。

有两种方法建立页表:
1使用remap_pfn_range函数一次全部建立,2或者通过nopage VMA方法每次建立一个页表。
int remap_pfn_range(struct vm_area_struct *vma,
                                   unsigned long virt_addr,      /*重新映射时,起始用户虚拟地址*/
                                   unsigned long pfn,  /*页帧号,phys_addr>>PAGE_SHIFT,更多用vma->vm_pgoff的值*/
                                   unsigned long size,     /*以字节为单位*/
                                   pgprot_t prot);            /*保护属性,用vma->vm_page_prot的值*/

int io_remap_page_range(struct vm_area_struct *vma, 
                                      unsigned long virt_addr, 
                                      unsigned long phys_addr,
  
      /*指向IO内存*/
                                      unsigned long size, 
                                      pgprot_t prot);

简单的示例:
static int simple_remap_mmap(struct file *filp, struct vm_area_struct *vma)
{
    if (remap_pfn_range(vma, vma->vm_start, vm->vm_pgoff,
                                    vma->vm_end - vma->vm_start,
                                    vma->vm_page_prot))
         return -EAGAIN;
    vma->vm_ops = &simple_remap_vm_ops;
    simple_vma_open(vma);
    return 0;
}

然后便是为VMA添加新操作:
void simple_vma_open(struct vm_area_struct *vma)
{
    printk(KERN_NOTICE "Simple VMA open, virt %lx, phys %lx\n",
            vma->vm_start, vma->vm_pgoff << PAGE_SHIFT);
}
void simple_vma_close(struct vm_area_struct *vma)
{
    printk(KERN_NOTICE "Simple VMA close.\n");
}
static struct vm_operations_struct simple_remap_vm_ops = {
    .open =  simple_vma_open,
    .close = simple_vma_close,
};

最后是在mmap中将默认的vm_ops指针替换为自己的操作:
vma->vm_ops = &simple_remap_vm_ops;

如果要支持mremap系统调用,就必须实现nopage函数。
struct page *(*nopage)(struct vm_area_struct *vma, 
                                  unsigned long address,   /*包含引起错误的虚拟地址*/
                                  int *type);

当用户需要访问VMA,而该页又不在内存中时,将调用相关的nopage函数。
remap_pfn_range函数的一个限制是:它只能访问保留页和超出物理内存的物理地址。(Linux中,在内存映射时,物理地址页被标记为reserved,表示内存管理对其不起作用。如在PC中,640KB-1MB之间的内存被标记为保留的,因为这个范围位于内核自身代码的内部。)所以remap_pfn_range不允许重新映射常规地址。包括调用get_free_page函数所获得的地址。相反,他能映射零内存页。
nopage函数可用于重新映射RAM。

直接执行I/O访问
直接I/O开销巨大,又没有使用缓存I/O的优势。无论如何在字符设备中执行直接I/O是不可行的。
只有确定设置缓冲I/O的开销特别巨大,才使用直接I/O。2.6内核的一个例子是SCSI磁带机驱动程序,因为磁带机传输通常是面向记录的。
通过下面的函数实现直接I/O:
int get_user_pages(struct task_struct *tsk, 
                   struct mm_struct *mm, 
                   unsigned long start,
                   int len, 
                   int write, 
                   int force, 
                   struct page **pages, 
                   struct vm_area_struct **vmas);

如果调用成功,调用这就会拥有一个指向用户空间缓冲区的页数组,它将被锁在内存中。为了能够直接操作缓冲区,内核空间代码必须用kmap函数将每个page结构指针转换成内核虚拟地址。
一旦直接I/O操作完成,就必须释放用户内存页。

直接内存访问(DMA)
the software asks for data (via a function such as read), the steps involved can be summarized as follows:

  1. When a process calls read, the driver method allocates a DMA buffer and instructs the hardware to transfer its data into that buffer. The process is put to sleep.

  2. The hardware writes data to the DMA buffer and raises an interrupt when it's done.

  3. The interrupt handler gets the input data, acknowledges the interrupt, and awakens the process, which is now able to read data.

hardware asynchronously pushes data to the system,The steps are slightly different:

  1. The hardware raises an interrupt to announce that new data has arrived.

  2. The interrupt handler allocates a buffer and tells the hardware where to transfer its data.

  3. The peripheral device writes the data to the buffer and raises another interrupt when it's done.

  4. The handler dispatches the new data, wakes any relevant process, and takes care of housekeeping.

高效的DMA处理依赖于中断报告!

分配的DMA缓冲区须使用线性物理内存区,用kmalloc及get_free_page函数,使用GFP_DMA标志。

关于DMA的更多操作和介绍参看LDD3的DMA部分。


下面两篇翻译则是转自陈莉君老师的博客
****************************************************************************************************************************************
Linux执行以及虚拟内存之用(Linux Execution and Virtual Memory Utilization

When Linux boots, it starts with the MMU disabled, so initially it deals only with physical
memory. The kernel image is copied to physical address 0x8000 in DRAM and executed. First a master page table is created and placed just before the kernel, which describes all available DRAM. The MMU is then switched on, mapping all of DRAM to virtual address 0xC0000000. The kernel reserves 64K of virtual memory for the interrupt handler code (usually at the very top of virtual memory), and sets up all the mappings for the hardware registers (UART, USB, LCD, GPIO, etc). Once kernel space is established and all drivers are initialized, the linux kernel moves on to establishing user space. This involves reading in the file system and actually executing processes.

当Linux启动时,是以MMU禁用来开启它的旅途的,因此,它起初只关注物理内存。内核映像(kernel image)被拷贝到DRAM的物理地址 0x8000处,并得到执行权。第一个主页表( master page table)从而诞生,并紧挨着内核映像存放,这个页表是对全部可用DRAM的描述。此后,MMU被启用,从而把全部DRAM映射到从0xC0000000开始的虚地址。内核为中断处理程序保留64K虚拟内存(通常在虚拟内存的顶端),然后为所有的硬件寄存器(UART, USB, LCD, GPIO, etc)建立映射。一旦内核空间建立起来,并且所有的驱动程序都被初始化,则Linux内核转向建立用户空间。这涉及到在文件系统中读取并实际执行进程。

Each process that runs on the system does so in its own memory “context”. Each context
has its virtual memory space established and maintained by the kernel using a separate page table. So each process can “see” the entire user space, but its underlying physical memory is different from the other processes. Multiple processes and their contexts are run in time slices. This means that each process executes for a time, then is halted, and execution is passed to the next process in a rotating queue. The act of passing execution is called “context switching” and its frequency varies, depending on the kernel configuration and the current system load.

运行在系统中的每个进程都的确在它自己的内存“上下文”中执行。每个上下文都有自己的虚拟内存空间,这一空间的建立和维护是内核通过独立的页表进行的。因此,每个进程可以“看到”整个用户空间,但是,其对应的物理内存互不重叠。多个进程及其上下文以时间片轮流执行。这就意味着,每个进程执行一段时间,然后停下来,从而把执行权传递给轮流队列中的下一进程。传递执行权的行为就是所谓的“上下文切换”,传递的频率随内核的配置及当前负载而变化。

Each context includes the mappings for both user and kernel space because each new
page table is initialized with the master page table contents. So, code executing in kernel space has full access to the entire memory map for the current process. Switching back and forth between kernel and user space execution does not require a page table swap.

每个上下文既包含用户空间的映射,也包含内核空间的映射,这是因为,每个新的页表都是用主页表内容进行初始化的。因此,对当前进程而言,在内核空间执行的代码具有对整个内存映射访问的权利。在内核空间和用户空间之间来回切换根本无需切换页表

When a process is executed, the kernel creates a new context, and maps just enough
physical memory to copy the executable image into DRAM (starting at least a page above virtual address 0, because 0 is reserved for faults). The executable is broken into the same sections as the kernel: code and data (uninitialized global data, zero-initialized global data). Once the kernel schedules the process for execution, it sets the stack to grow down from the top of user space, and the heap to grow up from the end of the executable image. This way the chances of the two spaces colliding is minimized. Any dynamic libraries the process pulls in are placed at virtual address 0x40000000.

当一个进程执行时,内核创建新的上下文,并为把可执行映像拷贝到DRAM仅映射足够用的物理内存(从虚地址0之上的某个地址开始一个页,因为0地址留作存放出错信息)。可执行映像如内核一样也被划分为两部分:代码和数据(未初始化数据和初始化为0的全局数据)。一旦内核调度进程执行,它就设置栈和堆,前者从用户空间的顶部向下增长,后者从可执行映像的尾部向上增长。以这种方式,两个空间冲突的机会就大大减少。进程调用的动态库都存放在 0x40000000开始的地址处。

The kernel reserves virtual memory whenever a process or driver requests it, but it doesn’t
actually map in the underlying physical memory until the memory is accessed (copy-on-write). So, the physical DRAM is used only when it absolutely has to be, and the kernel controls the allocation through a global memory management scheme. Ideally, the kernel tries to allocate contiguous blocks of physical memory as contiguous memory is most likely to evenly fill physically tagged caches. Thus, the kernel organizes memory to grow inward from the ends of physical memory, maximizing contiguous memory in the middle. The physical memory map is described in detail in the next section.

只要进程或驱动程序请求内存,内核就留出虚拟内存来,但,并不实际映射到真正的物理内存,直到要访问相应的内存时才进行映射(写时复制)。因此,只有在万不得已时才用到物理DRAM,内核通过全局内存管理模式控制分配。理想的情况下,内核尽量分配连续的物理块,这是因为连续内存很可能均匀地填充物理上紧挨的高速缓存。因此,内核对内存的组织采用从物理内存的末端向内方向增长,以图在中间最大化连续内存。物理内存的映射在下一部分将详细给予描述。

嵌入式Linux物理内存映射
**********************************************************************************************************************
The physical memory map for Linux is completely independent from the virtual map and is designed to maximize contiguous space. Given that the kernel image will always be at the start of DRAM, the Linux kernel maximizes contiguous space by allocating runtime memory from the end of physical DRAM moving downward.

The kernel starts by breaking available memory out into large, contiguous blocks (typically 4MB or more).It then maintains memory using the buddy system, where physical memory is always allocated in combinations of blocks of 2^n pages (where n is the order, that is, 4K is a 0 order block, 8K is a 1st order block, 16K is a 2nd order block, etc).


    Linux物理内存的映射完全独立于虚拟内存的映射,而且尽可能映射到连续的空间。假定内核映像总是位于DRAM的开始处,Linux内核尽可能让空间连续,这是通过从物理DRAM的末端向下移动来分配运行时内存而达到的。
   内核一开始把可用内存分割为大而连续的块(通常为4MB 或更多)。此后,内核利用伙伴算法来管理内存,这里,物理内存的分配总是2^n个页所形成块的组合(这里,n是幂,4k就是幂为0的块,8K是幂为1块,16K为幂为2的块,如此等等)
linux内存及IO管理概述(转) - tauruspdj - tauruspdj的博客
When physical memory is allocated, that is, on process start, or when malloc’ed memory is written to (copy-on-write), the kernel scans for the smallest order block that will fill fit starting from the top of DRAM. As the number of running processes increases, or new allocations are spawned from drivers or processes, the physical memory used grows downward.

当物理内存分配时,也就是在进程刚开始时,或者说当分配的内存被写入时(写时复制),内核扫描最小幂次的块(从DRAM顶部开始寻找最合适的)。随着进程数的增加,或者来自驱动程序或进程新分配的蔓延,所使用的物理内存向下延伸。

When a process exits or a large enough memory block is freed, its DRAM space is unmapped and becomes a gap in memory. This process is called memory fragmentation and becomes more and more prevalent the longer a device is used and the more frequently the use case changes (such as checking email, playing an mp3, watching a video, etc). As new processes and allocations are created, the gaps are filled whenever possible, but fragmentation is still an inevitable part of any OS memory map.

   当进程退出或者大块内存块被释放时,DRAM空间的映射被解除,从而在内存中形成空白区。这样的过程就是所谓内存碎片,设备使用的时间越长,使用场景变化越频繁(例如收发邮件,播放MP3,观看视频等等),碎片会越来越多。


阅读(616) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2010-11-25 16:23:20

很好的, 收藏了 推荐一个博客,提供很多免费软件编程电子书下载: http://free-ebooks.appspot.com