Chinaunix首页 | 论坛 | 博客
  • 博客访问: 498864
  • 博文数量: 196
  • 博客积分: 115
  • 博客等级: 民兵
  • 技术积分: 364
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-13 23:19
文章分类

全部博文(196)

文章存档

2019年(5)

2018年(11)

2017年(16)

2016年(13)

2015年(46)

2014年(81)

2013年(22)

2012年(2)

分类: LINUX

2013-12-07 09:00:26

上一节讲了内核如何管理物理内存,其实内核除了管理本身的内存外,还必须管理用户空间中进程的内存,这就是进程地址空间,也就是系统中每个用户空间进程所看到的内存。

Linux采用虚拟内存技术,系统中所有进程之间以虚拟方式共享内存,对一个进程而言,可以访问整个系统的所有物理内存,其拥有地址空间也可以远远大于系统物理内存。

1.地址空间

进程地址空间由进程可寻址的虚拟内存组成。每个进程都有一个32bit64bit的平坦地址空间,空间具体大小取决于体系结构。平坦(flat)指的是地址空间范围是一个独立的连续空间(比如,地址从02 32-1).一些操作系统提供了段地址空间,这种地址空间并不是一个独立的线性区域,而是被分段的。现代采用虚拟内存的操作系统都使用平坦地址空间。

每个进程都有唯一的这种平坦地址空间,并且不同进程之间,彼此互不相干,地址空间完全独立。

尽管一个进程可以寻址4GB虚拟内存(32bit),但这并不代表它就有权访问所有虚拟内存,在地址空间中,更关心的是一些可以合法访问的虚拟内存的地址空间,这个空间称为“内存区域(memory areas)”,进程只能访问有效内存区域内的内存地址,每个内存区域也具有相关权限,如可读、可写,可执行属性。如果一个进程访问了不在有效范围的内存区域,或以不正确的方式访问了有效地址,内核就会终止该进程,并返回“段错误”。

内存区域可以包含各种内存对象,比如:

代码段(text section):可执行文件代码的内存映射

数据段(data section): 可执行文件的已初始化全局变量的内存映射

bss:包含未初始化全局变量,也就是bss段的零页 (页面中的信息全部为0,可用于映射bss段等目的) 的内存映射

栈:用户进程用户空间的栈(不要和进程内核栈混淆,进程的内核栈独立存在并且由内核维护)每个诸如C库或动态链接程序等共享库的代码段、数据段和BSS也会被载入进程的地址空间。

任何内存映射文件

任何共享内存段

任何匿名的内存映射,比如malloc()分配的内存;

进程地址空间中的任何有效地址都只能位于唯一的区域,这些内存区域不能相互覆盖;在执行的进程中,每个不同的内存片段都对应一个独立的内存区域:栈、对象代码、全局变量、被映射的文件等。

2.内存描述符

内核使用内存描述符来表示进程的地址空间,该结构体包含了和进程地址空间有关的官不信息,mm_struct结构体,定义在

点击(此处)折叠或打开

  1. struct mm_struct {
  2.     struct vm_area_struct * mmap;        /* 内存区域链表list of VMAs */
  3.     struct rb_root mm_rb;                /* VMA形成的红黑树*/
  4.     struct vm_area_struct * mmap_cache;    /* 最近使用的内存区域last find_vma result */
  5. #ifdef CONFIG_MMU
  6.     unsigned long (*get_unmapped_area) (struct file *filp,
  7.                 unsigned long addr, unsigned long len,
  8.                 unsigned long pgoff, unsigned long flags);
  9.     void (*unmap_area) (struct mm_struct *mm, unsigned long addr);
  10. #endif
  11.     unsigned long mmap_base;        /* base of mmap area */
  12.     unsigned long task_size;        /* size of task vm space */
  13.     unsigned long cached_hole_size;     /* if non-zero, the largest hole below free_area_cache */
  14.     unsigned long free_area_cache;        /* 地址空间第一个空洞first hole of size cached_hole_size or larger */
  15.     pgd_t * pgd; /*页全局目录*/
  16.     atomic_t mm_users;            /* 使用地址空间的用户数How many users with user space? */
  17.     atomic_t mm_count;            /* 主使用计数器How many references to "struct mm_struct" (users count as 1) */
  18.     int map_count;                /* number of VMAs */
  19.     struct rw_semaphore mmap_sem; /*内存区域的信号量*/
  20.     spinlock_t page_table_lock;        /* 页表锁Protects page tables and some counters */

  21.     struct list_head mmlist;        /*所有mm_struct形成的双向链表 List of maybe swapped mm's.    These are globally strung
  22.                          * together off init_mm.mmlist, and are protected
  23.                          * by mmlist_lock
  24.                          */


  25.     unsigned long hiwater_rss;    /* High-watermark of RSS usage */
  26.     unsigned long hiwater_vm;    /* High-water virtual memory usage */

  27.     /*全部页面数目,上锁的页面数目*/
  28.     unsigned long total_vm, locked_vm, shared_vm, exec_vm;
  29.     unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
  30.     /*代码段的开始地址,结束地址;数据段的首地址和结束地址*/
  31.     unsigned long start_code, end_code, start_data, end_data;
  32.     /*堆的首地址,堆的尾地址*/
  33.     unsigned long start_brk, brk, start_stack;
  34.     /*命令行参数的首地址和结束地址,环境变量的首地址和结束地址*/
  35.     unsigned long arg_start, arg_end, env_start, env_end;

  36.     unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */

  37.     /*
  38.      * Special counters, in some configurations protected by the
  39.      * page_table_lock, in other configurations by being atomic.
  40.      */
  41.     struct mm_rss_stat rss_stat;

  42.     struct linux_binfmt *binfmt;

  43.     cpumask_t cpu_vm_mask;

  44.     /* Architecture-specific MM context */
  45.     mm_context_t context;

  46.     /* Swap token stuff */
  47.     /*
  48.      * Last value of global fault stamp as seen by this process.
  49.      * In other words, this value gives an indication of how long
  50.      * it has been since this task got the token.
  51.      * Look at mm/thrash.c
  52.      */
  53.     unsigned int faultstamp;
  54.     unsigned int token_priority;
  55.     unsigned int last_interval;

  56.     unsigned long flags; /* Must use atomic bitops to access the bits */

  57.     struct core_state *core_state; /* coredumping support */
  58. #ifdef CONFIG_AIO
  59.     spinlock_t        ioctx_lock;
  60.     struct hlist_head    ioctx_list;
  61. #endif
  62. #ifdef CONFIG_MM_OWNER
  63.     /*
  64.      * "owner" points to a task that is regarded as the canonical
  65.      * user/owner of this mm. All of the following must be true in
  66.      * order for it to be changed:
  67.      *
  68.      * current == mm->owner
  69.      * current->mm != mm
  70.      * new_owner->mm == mm
  71.      * new_owner->alloc_lock is held
  72.      */
  73.     struct task_struct *owner;
  74. #endif

  75. #ifdef CONFIG_PROC_FS
  76.     /* store ref to file /proc/<pid>/exe symlink points to */
  77.     struct file *exe_file;
  78.     unsigned long num_exe_file_vmas;
  79. #endif
  80. #ifdef CONFIG_MMU_NOTIFIER
  81.     struct mmu_notifier_mm *mmu_notifier_mm;
  82. #endif
  83. };

mm_users域记录正在使用该地址的进程数目,mm_count表示mm_struct结构体的主引用计数,当mm_users值减少为0(所有使用该地址空间的线程都退出)mm_count变为0;当mm_count等于0,说明已经咩有人和指向该mm_stuct结构体的引用了,这时该结构体会被撤销。

mmapmm_rb描述同一个对象:该地址空间中的全部内存区域。Mmap以链表形式存放,mm_rb以红-黑树形式存放。内核通常会避免用两种数据结构组织同一种数据,但此处这种冗余派的上用场,mmap链表,利于简单、高效地遍历所有元素;而mm_rb结构更适合搜索指定的元素。覆盖树上的链表并用这两个结构体同时访问相同的数据集,有时候这种操作称为线索树。

所有mm_stuct都通过自身的mmlist域链接在一个双向链表中,该链表首元素是init_mm内存描述符,它代表init进程的地址空间,另外注意,操作该链表是需要使用mmlist_lock来防止并发访问。

2.1 分配内存描述符

在进程的进程描述符task_struct中,mm域存放着该进程使用的内存描述符,所以current->mm指向当前进程的内存描述符。

fork()函数利用copy_mm()函数复制父进程的内存描述符,而子进程中的mm_struct结构体实际是通过文件kernel/fork.c中的allocate_mm()宏从mm_cachep slab缓存中分配得到的。通常每个进程都有唯一的mm_struct结构体,即唯一的进程地址空间。

如果父进程希望和子进程共享地址空间,可以调用clone()时,设置CLONE_VM标志,这样的进程称作线程,Linux中所谓的线程和进程的本质区别,就是是否共享地址空间。

CLONE_VM被指定后,内核就不再需要调用allocate_mm()函数,而仅仅需要在调用copy_mm()函数中将mm域指向其父进程的内存描述符就可以了:

点击(此处)折叠或打开

  1. if (clone_flags & CLONE_VM) {
  2.     //current 是父进程,而tsk在fork()指向期间是子进程
  3.     atomic_inc(&current->mm->mm_users);
  4.     tsk->mm = current->mm;
  5. }

2.2 撤销内存描述符

进程退出时,内核会调用exit_mm(),该函数执行一些常规撤销工作,同时更新一些统计量。

该函数会调用mmput()减少内存描述符中的mm_users用户基数,如果用户计数降到0,将调用mmdrop()函数,减少mm_count使用计数。如果使用计数也等于零,说明内存描述符不再有任何使用者了,那么调用free_mm()宏通过kmem_cache_free()mm_struct结构体归还到mm_cachep slab缓存中。

2.3 mm_struct与内核线程

内核线程没有进程地址空间,也没有相关的内存描述符,所以内核线程对应的进程描述符mm域为空,事实上,这也正是内核线程的真实含义它们没有用户上下文。

为了避免内核线程为内存描述符和页表浪费内存,也为了当新内核线程运行时,避免浪费处理器周期向新地址空间进行切换,内核线程将直接使用前一个进程的内存描述符。

当一个进程被调度时,该进程的mm域指向的地址空间被载入内存,进程描述符中的active_mm域会被更新,指向新的地址空间。内核线程没有地址空间,mm域为NULL,于是,当一个内核线程被调度时,内核发现它的mm域为NULL,就会保留前一个进程的地址空间,随后更新内核线程的进程描述符的active_mm域,使其指向前一个进程的内存描述符。所以需要时,内核线程便可以使用前一个进程的页表。

3.虚拟内存区域

内存区域由vm_area_struct结构体描述,定义在中,内存区域在Linux内核中经常称作虚拟内存区域(virtual memoryAreas, VMAs).

vm_area_struct描述了指定地址空间内连续区间上的一个独立内存范围,内核将每个内存区域作为一个单独的内存对象管理,每个内存区域都拥有一致的属性,比如访问权限等,相应的操作也应该一致。按这种方式,每个VMA就可以代表不同类型的内存区域(比如内存映射文件或者进程用户空间栈)

点击(此处)折叠或打开

  1. /*
  2.  * This struct defines a memory VMM memory area. There is one of these
  3.  * per VM-area/task. A VM area is any part of the process virtual memory
  4.  * space that has a special rule for the page-fault handlers (ie a shared
  5.  * library, the executable area etc).
  6.  */
  7. struct vm_area_struct {
  8.     struct mm_struct * vm_mm;    /* 相关的mm_struct结构体The address space we belong to. */
  9.     unsigned long vm_start;        /* 区间首地址Our start address within vm_mm. */
  10.     unsigned long vm_end;        /* 区间尾地址The first byte after our end address
  11.                      within vm_mm. */

  12.     /* linked list of VM areas per task, sorted by address */
  13.     struct vm_area_struct *vm_next, *vm_prev; //VMA链表

  14.     pgprot_t vm_page_prot;        /* 访问控制权限Access permissions of this VMA. */
  15.     unsigned long vm_flags;        /* 标志:内存区域标志的信息和行为Flags, see mm.h. */
  16.     struct rb_node vm_rb; //树上该VMA的节点

  17.     /*
  18.      * For areas with an address space and backing store,
  19.      * linkage into the address_space->i_mmap prio tree, or
  20.      * linkage to the list of like vmas hanging off its node, or
  21.      * linkage of vma in the address_space->i_mmap_nonlinear list.
  22.      */
  23.     union {
  24.         struct {
  25.             struct list_head list;
  26.             void *parent;    /* aligns with prio_tree_node parent */
  27.             struct vm_area_struct *head;
  28.         } vm_set;

  29.         struct raw_prio_tree_node prio_tree_node;
  30.     } shared;

  31.     /*
  32.      * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
  33.      * list, after a COW of one of the file pages.    A MAP_SHARED vma
  34.      * can only be in the i_mmap tree. An anonymous MAP_PRIVATE, stack
  35.      * or brk vma (with NULL file) can only be in an anon_vma list.
  36.      */
  37.     struct list_head anon_vma_chain; /* Serialized by mmap_sem &
  38.                      * page_table_lock */
  39.     struct anon_vma *anon_vma;    /* Serialized by page_table_lock */

  40.     /* Function pointers to deal with this struct. */
  41.     const struct vm_operations_struct *vm_ops; //相关的操作链表

  42.     /* Information about our backing store: */
  43.     unsigned long vm_pgoff;        /*文件中偏移量 Offset (within vm_file) in PAGE_SIZE
  44.                      units, *not* PAGE_CACHE_SIZE */
  45.     struct file * vm_file;        /* 被映射的文件File we map to (can be NULL). */
  46.     void * vm_private_data;        /* 私有数据was vm_pte (shared mem) */
  47.     unsigned long vm_truncate_count;/* truncate_count or restart_addr */

  48. #ifndef CONFIG_MMU
  49.     struct vm_region *vm_region;    /* NOMMU mapping region */
  50. #endif
  51. #ifdef CONFIG_NUMA
  52.     struct mempolicy *vm_policy;    /* NUMA policy for the VMA */
  53. #endif
  54. };

每个内存描述符都对应于进程地址空间中的唯一区间,vm_end-vm_start大小就是内存区间的长度。在同一地址空间内的不同内存区间不能重叠。

vm_mm域指向和VMA相关的mm_struct结构体,每个VMA对其相关的mm_struct结构体来说都是唯一的,如果两个线程共享一个地址空间,那么它们也同时共享其中所有的vm_area_struct结构体。

3.1VMA标志

VMA是一种位标志,它包含在vm_flags域内,标志了内存区域所包含的页面行为和信息。VMA标识反映了内核处理页面所需遵守的行为准则,而不是硬件要求。vm_flags包含了内存区域中每个页面的信息或内存区域的整体信息,而不是具体的独立页面。


几个重要的标志(这些标志可以按需求组合)

VM_READVM_WRITEVM_EXEC标志了区域中页面的读、写和执行权限。

VM_SHARD:指明内存区域包含的映射是否可以在多进程间共享,

VM_IO:标志内存区域中按对设备I/O空间的映射,该标志通常在设备驱动程序执行mmap()函数进行I/O空间映射时才被设置。

VM_SEQ_READ:标志内核应用程序对映射内容执行有序的(线性和连续的)读操作,这样内核可以有选择地执行预读文件。

3.2 VMA操作

Vm_area_struct结构体中的vm_ops域指向与指定内存区域相关的操作函数

点击(此处)折叠或打开

  1. struct vm_operations_struct {
  2. //当指定的内存区域被加入到一个地址空间时,该函数被调用
  3.     void (*open)(struct vm_area_struct * area);
  4. //当指定的内存区域从地址空间删除时,该函数调用
  5.     void (*close)(struct vm_area_struct * area);
  6. //当没有出现在屋里内存中的页面被访问时,该函数被页面故障处理调用
  7.     int (*fault)(struct vm_area_struct *vma, struct vm_fault *vmf);

  8.     /* notification that a previously read-only page is about to become
  9.      * writable, if an error is returned it will cause a SIGBUS */
  10. //当某个页面为只读页面时,该函数被页面故障处理调用
  11.     int (*page_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf);

  12.     /* called by access_process_vm when get_user_pages() fails, typically
  13.      * for use by special VMAs that can switch between memory and hardware
  14.      */
  15. //当get_user_pages()函数调用失败时,该函数被access_process_vm()函数调用
  16.     int (*access)(struct vm_area_struct *vma, unsigned long addr,
  17.          void *buf, int len, int write);
  18.     …
  19. };

3.3 内存区域的树型结构和内存区域的链表结构

mmapmm_rb,独立地指向与内存描述符相关的全体内存区域对象,它们包含完全相同的vm_area_struct结构体指针,仅仅方法不同。

mmap域使用单独的链表链接所有的内存区域对象,每个vm_area_struct结构体通过自身vm_next域被连入链表,mmap域指向链表中的一个内存区域,链中最后一个结构体指针指向空

mm_rb域使用红-黑树链接所有内存区域对象,mm_rb指向红-黑树根节点,地址空间中每个vm_area_struct通过自身的vm_rb连接到树中。

链表用于需要遍历全部节点的时候,而红黑树适用于在地址空间中定位特定内存区域的时候,内核为了内存区域上的各种不同操作都能获得高性能,所以同时使用了这两种数据结构。

3.4 实际使用中的内存区域

可使用/proc文件系统和pmap工具查看给定进程的内存空间和其中所含的内存区域。

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. int main(void)
  3. {
  4.      printf("hello,world!\r\n");
  5.      while (1);
  6.      return 0;
  7. }

该程序执行pid2874,那么

leon@ubuntu:~$ cat /proc/2874/maps

08048000-08049000 r-xp 00000000 08:01 131477     /home/leon/a.out

08049000-0804a000 r--p 00000000 08:01 131477     /home/leon/a.out

0804a000-0804b000 rw-p 00001000 08:01 131477     /home/leon/a.out

b7589000-b758a000 rw-p 00000000 00:00 0

b758a000-b772e000 r-xp 00000000 08:01 524970     /lib/i386-linux-gnu/libc-2.15.so

b772e000-b7730000 r--p 001a4000 08:01 524970     /lib/i386-linux-gnu/libc-2.15.so

b7730000-b7731000 rw-p 001a6000 08:01 524970     /lib/i386-linux-gnu/libc-2.15.so

b7731000-b7734000 rw-p 00000000 00:00 0

b7744000-b7747000 rw-p 00000000 00:00 0

b7747000-b7748000 r-xp 00000000 00:00 0          [vdso]

b7748000-b7768000 r-xp 00000000 08:01 524935     /lib/i386-linux-gnu/ld-2.15.so

b7768000-b7769000 r--p 0001f000 08:01 524935     /lib/i386-linux-gnu/ld-2.15.so

b7769000-b776a000 rw-p 00020000 08:01 524935     /lib/i386-linux-gnu/ld-2.15.so

bfb5b000-bfb7c000 rw-p 00000000 00:00 0          [stack]

每行数据格式如下:

内存地址开始-结束 访问权限 偏移 主设备号:次设备号 i节点 文件

或者用pmap命令查看

leon@ubuntu:~$ pmap 2874

2874:   ./a.out

08048000      4K r-x--  /home/leon/a.out

08049000      4K r----  /home/leon/a.out

0804a000      4K rw---  /home/leon/a.out

b7589000      4K rw---    [ anon ]

b758a000   1680K r-x--  /lib/i386-linux-gnu/libc-2.15.so

b772e000      8K r----  /lib/i386-linux-gnu/libc-2.15.so

b7730000      4K rw---  /lib/i386-linux-gnu/libc-2.15.so

b7731000     12K rw---    [ anon ]

b7744000     12K rw---    [ anon ]

b7747000      4K r-x--    [ anon ]

b7748000    128K r-x--  /lib/i386-linux-gnu/ld-2.15.so

b7768000      4K r----  /lib/i386-linux-gnu/ld-2.15.so

b7769000      4K rw---  /lib/i386-linux-gnu/ld-2.15.so

bfb5b000    132K rw---    [ stack ]

 total     2004K

分别表示程序和C库的代码段、数据段、bss

进程全都地址空间大约2004KB,但只有大概不到200KB的内存区域是可写或私有的。如果一片内存范围是共享的或不可写的,那么内核只需要在内存中为文件保留一份映射,比如C库的代码,只读入一次是安全的。

由于内存未被共享,所以只要一有进程写该处数据,那么该处数据就将被拷贝出来(写时拷贝),然后才被更新。

每个和进程相关的内存区域都对应于一个vm_area_strcut结构体。

4.操作内存区域

内核时常需要在某个内存区域上执行一些操作,这些操作非常频繁,它们也是mmap()例程的基础,为了方便这类对内存区域的操作,内核定义了许多辅助函数声明在

4.1 查找一个给定的内存地址属于哪一个内存区域: find_vma()

点击(此处)折叠或打开

  1. /* Look up the first VMA which satisfies addr < vm_end, NULL if none. */
  2. struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr)
  3. {
  4.     struct vm_area_struct *vma = NULL;

  5.     if (mm) {
  6.         /* Check the cache first. */
  7.         /* (Cache hit rate is typically around 35%.) */
  8.         vma = mm->mmap_cache;
  9.         if (!(vma && vma->vm_end > addr && vma->vm_start <= addr)) {
  10.             struct rb_node * rb_node;

  11.             rb_node = mm->mm_rb.rb_node;
  12.             vma = NULL;

  13.             while (rb_node) {
  14.                 struct vm_area_struct * vma_tmp;

  15.                 vma_tmp = rb_entry(rb_node,
  16.                         struct vm_area_struct, vm_rb);

  17.                 if (vma_tmp->vm_end > addr) {
  18.                     vma = vma_tmp;
  19.                     if (vma_tmp->vm_start <= addr)
  20.                         break;
  21.                     rb_node = rb_node->rb_left;
  22.                 } else
  23.                     rb_node = rb_node->rb_right;
  24.             }
  25.             if (vma)
  26.                 mm->mmap_cache = vma;
  27.         }
  28.     }
  29.     return vma;
  30. }

该函数在指定地址空间中搜索的一个vm_end大于addr的内存区域,这样返回的VMA首地址可能大于addr,所以指定的地址并不一定就包含在返回的VMA中。

因为很有可能在执行某个VMA操作后,其他操作还会对该VMA进行操作,所以find_vma()函数返回的结果被缓存在内存描述符的mmap_cache域中,实践证明,被缓存的VMA有相当好的命中率(30~40%),检查被缓存的VMA速度会很快,如果指定的地址不在缓存中,那么必须搜索和内存描述符相关的所有内存区域,这种搜索通过红黑树进行。

4.2 查找第一个和指定地址区间相交的VMAfind_vma_intersection()

点击(此处)折叠或打开

  1. /* Look up the first VMA which intersects the interval start_addr..end_addr-1,
  2.    NULL if none. Assume start_addr < end_addr. */
  3. static inline struct vm_area_struct * find_vma_intersection(struct mm_struct * mm, unsigned long start_addr, unsigned long end_addr)
  4. {
  5.     struct vm_area_struct * vma = find_vma(mm,start_addr);

  6.     if (vma && end_addr <= vma->vm_start)
  7.         vma = NULL;
  8.     return vma;
  9. }

mm:要搜索的地址空间

start_addr:区间的起始地址

end_addr:区间尾地址

如果find_vma()返回NULL,那么find_vma_intersection()返回NULL;

如果find_vma()返回有效VMAfind_vma_intersection()只有在该VMA的起始位置于给定的地址区间结束位置之前,才将其返回,否者返回NULL

5.mmap()do_mmap():创建地址区间

内核使用do_mmap()函数创建一个新的线性地址区间。如果这个新的VMA与相邻地址区间具有相同访问权限的话,将合并为一个VMA,如果不能合并,就确实需要创建一个新的VMA了。

无论如何,do_mmap()函数都会将一个地址区间加入到进程的地址空间中,无论是扩展已存在的内存区域还是创建一个新的区域。

中定义

static inline unsigned long do_mmap(struct file *file, unsigned long addr,

       unsigned long len, unsigned long prot,

       unsigned long flag, unsigned long offset)

该函数映射由file指定的文件,具体映射从文件偏移ofset开始,长度为len字节。如果file参数是NULL并且offset0,那么代表这次映射没有和文件相关,这叫做匿名映射(anonymous mapping)。否则叫文件映射(file-backed mapping)

addr是可选参数,它指定搜索空闲区域的起始位置。

prot参数指定内存区域中页面的访问权限。


flag参数指定VMA标志,这些标志指定类型并改变映射的行为


如果系统调用do_mmap()的参数中有无效参数,它返回一个负值;否者,就会在虚拟内存中分配额一个合适的新内存区域(有可能从slab中获取)

在用户空间通过调用mmap()系统调用获取内核do_mmap()的功能。

void *mmap2(void *addr,

size_t length,

int prot,

                     int flags,

int fd,

off_t pgoffset)

该系统调用是mmap()调用的第二种变种,所以起名为mmap2(),原始的mmap()方法的调用最后一个参数是字节偏移量,而mmap2()使用页面偏移量;mmap()调用由POSIX定义,C库中任然作为mmap()方法使用,但新内核中已经没有对应实现了,mmap()方法的调用是通过将字节偏移转化为页面偏移,从而转化为对mmap2()函数的调用来实现的。

6.mummap()do_mummap():删除地址区间

do_mummap()从特定的进程地址空间中删除指定地址区间,定义在

int do_munmap(struct mm_struct *mm, unsigned long start, size_t len)

mm指定的用户空间,删除从地址start开始,长度为len字节的地址区间。

Itn munmap(void *start, size_t length)

该系统调用定义在文件mm/mmap.c中,它是对do_mummap()函数的一个简单封装:

点击(此处)折叠或打开

  1. asmlinkage long sys_munmap(unsigned long addr,size_t len)
  2. {
  3.     int ret;
  4.     struct mm_struct *mm;

  5.     mm = current->mm;
  6.     down_write(&mm->mmap_sem);
  7.     ret = do_munmap(mm,addr,len);
  8.     up_write(&mm->mmap_sem);
  9.     
  10.     return ret;
  11. }

7.页表

虽然应用程序操作的对象是对应虚拟内存,但处理器直接操作的却是物理内存,当应用程序访问一个虚拟地址时,首先必须将虚拟地址转化成物理地址,然后处理器才能解析地址访问请求。

Linux使用三级页表管理完成地址转换,可按需求在编译简化使用两级,用三级是利用“最大公约数”的思想--- 一种设计简单的体系结构。

每个进程都有自己的页表(线程会共享页表),内存描述符的pgd域指向的就是进程的页全局目录,注意,操作和检索页表时必须使用page_table_lock锁,该锁在相应进程的内存描述符中,防止竞争条件。

页表对应的结构体依赖于具体的体系结构,定义在


由于几乎每次对虚拟内存的页面访问都必须先解析它,从而得到物理地址,所以页表操作的性能非常关键。但不幸的是搜索内存中的物理地址速度很有限,为了加快搜索,多数体系结构实现了一个翻译后缓存器(translate lookaside buffer, TLB)TLB缓存虚拟地址到物理地址的映射,如果访问的虚拟地址在缓存中命中,物理地址立刻返回;否者就需要再通过页表搜索需要的物理地址。

阅读(802) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~
评论热议
请登录后评论。

登录 注册