Chinaunix首页 | 论坛 | 博客
  • 博客访问: 157198
  • 博文数量: 152
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 0
  • 用 户 组: 普通用户
  • 注册时间: 2016-07-08 15:46
文章分类

全部博文(152)

文章存档

2016年(152)

我的朋友

分类: LINUX

2016-06-02 14:04:24

    前面学习了内核内存管理的基本概念,现在再来看看内核用怎样的数据结构来管理这些物理内存和映射。
    内核源码:Linux-3.0
一、物理页在内存中的数据结构及内存映射
    由于历史原因, 内核使用逻辑地址来引用物理内存页。 但后来增加了对高内存的支持,这就暴露出一个问题:高端内存无法通过逻辑地址引用. 因此, 内核中处理内存的函数后来趋向使用指向 struct page 的指针来解决这个问题(定义在 中). 这个数据结构用来保存内核需要的所有关于物理内存的信息.
    系统中每一个物理页有一个 struct page 结构相对应. 这个结构定义如下(驱动需要关心的用红色标出):
  1. /*
  2.  * 系统中的每一个物理页都有一个 struct page 结构体与之相对应,
  3.  * 用于跟踪当前我们对这个物理页的使用。
  4.  * 注意:我们无法跟踪到哪个进程使用了物理页,
  5.  * 但如果它是一个pagecache页,rmap结构可以告诉我们是谁映射了它。
  6.  * 
  7.  */
  8. struct page {
  9.     unsigned long flags;     /* 描述页状态的一组原子位标志, 可能异步更新。其中包括:
  10.                               * PG_locked, 指示该页已被加锁,  
  11.                               * PG_reserved,防止内存管理系统使用该页。 */
  12.     atomic_t _count;        /* 使用计数,若为0,则该页被移至空闲链表 */
  13.     union {
  14.         atomic_t _mapcount;    /* Count of ptes mapped in mms,
  15.                      * to show when page is mapped
  16.                      * & limit reverse map searches.
  17.                      */
  18.         struct {        /* SLUB */
  19.             u16 inuse;
  20.             u16 objects;
  21.         };
  22.     };
  23.     union {
  24.      struct {
  25.         unsigned long private;        /* Mapping-private opaque data:
  26.                           * usually used for buffer_heads
  27.                          * if PagePrivate set; used for
  28.                          * swp_entry_t if PageSwapCache;
  29.                          * indicates order in the buddy
  30.                          * system if PG_buddy is set.
  31.                          */
  32.         struct address_space *mapping;    /* If low bit clear, points to
  33.                          * inode address_space, or NULL.
  34.                          * If page mapped as anonymous
  35.                          * memory, low bit is set, and
  36.                          * it points to anon_vma object:
  37.                          * see PAGE_MAPPING_ANON below.
  38.                          */
  39.      };
  40. #if USE_SPLIT_PTLOCKS
  41.      spinlock_t ptl;
  42. #endif
  43.      struct kmem_cache *slab;    /* SLUB: Pointer to slab */
  44.      struct page *first_page;    /* Compound tail pages */
  45.     };
  46.     union {
  47.         pgoff_t index;        /* Our offset within mapping. */
  48.         void *freelist;        /* SLUB: freelist req. slab lock */
  49.     };
  50.     struct list_head lru;        /* Pageout list, eg. active_list
  51.                      * protected by zone->lru_lock !
  52.                      */
  53.     /*
  54.     * 在所有内存都映射到内核地址空间的设备中(没有高端内存),
  55.     * 我们可以简单的计算出虚拟地址。而在拥有高端内存的设备中
  56.     * (一些内存被动态地映射到内核虚拟地址空间),
  57.     * 我们需要一个成员来保存这个虚拟地址。
  58.     * 注意:这个地址在x86上可以是16bit
  59.     *
  60.      *
  61.      * Architectures with slow multiplication can define
  62.      * WANT_PAGE_VIRTUAL in asm/page.h
  63.      */
  64. #if defined(WANT_PAGE_VIRTUAL)
  65.     void *virtual;            /* 如果改页被映射,则代表它的内核虚拟地址; 否则为NULL.
  66.                                *低端内存页一直被映射; 高端内存页则不是. 
  67.                              *这个成员并不在所有体系上出现; 它只在页的内核虚拟地址不易计算时被编译. 
  68.                              *如果你想查看这个成员, 正确的方法是使用 page_address 宏。
  69.                              *Kernel virtual address (NULL if not kmapped, ie. highmem) */
  70. #endif /* WANT_PAGE_VIRTUAL */
  71. #ifdef CONFIG_WANT_PAGE_DEBUG_FLAGS
  72.     unsigned long debug_flags;    /* Use atomic bitops on this */
  73. #endif

  74. #ifdef CONFIG_KMEMCHECK
  75.     /*
  76.      * kmemcheck wants to track the status of each byte in a page; this
  77.      * is a pointer to such a status block. NULL if not tracked.
  78.      */
  79.     void *shadow;
  80. #endif
  81. };
    内核维护一个或多个 struct page 结构数组来跟踪系统中所有物理内存. 在某些系统, 用了一个单独的数组称为 mem_map. 而在另一些系统中, 情况更加复杂。非一致内存访问( NUMA )系统和有很多不连续物理内存的系统可能有多个内存映射数组, 因此从移植性考虑,代码应当避免直接访问这些数组.幸运的是, 通常只需使用 struct page 指针, 而不用担心它们是怎么来的.

    以下是与 struct page 结构相关的内核函数:

   (1)在 struct page 指针和虚拟地址之间转换:
  1. struct page *virt_to_page(void *kaddr);
    这个宏, 定义在 (ARM构架在), 采用一个内核逻辑地址并返回相关联的 struct page 指针. 注意:她针对逻辑地址, 不能使用来自 vmalloc 的内存或者高内存.(看源码就可知道原因)

  1. struct page *pfn_to_page(int pfn);
    给定页帧号,返回 struct page 指针. 如果需要,在传递给 pfn_to_page 之前可使用 pfn_valid 来检查页帧号的有效性.

  1. void *page_address(struct page *page);
   返回这个页的内核虚拟地址,高低端内存均可使用。对于高端内存, 仅当这个页已被映射,地址才存在. 这个函数在 中定义. 


   (2)创建地址映射:

  1. #include <linux/highmem.h>
  2. void *kmap(struct page *page);
  3. void kunmap(struct page *page);
    kmap 为系统中的任何页返回一个内核虚拟地址. 
    对于低端内存页,它只返回页的逻辑地址; 
    对于高端内存页, kmap在内核地址空间的一个专用部分中创建一个特殊的映射. 这样的映射数目是有限, 因此最好不要持有过长的时间. 
    使用 kmap 创建的映射应当使用 kunmap 来释放;    
    kmap 调用维护一个计数器, 因此若2个或多个函数都在同一个页上调用kmap也是允许的. 
    注意:当没有映射可用时,kmap 可能睡眠.

  1. #include <linux/highmem.h>
  2. #include <asm/kmap_types.h>
  3. void *kmap_atomic(struct page *page, enum km_type type);
  4. void kunmap_atomic(void *addr, enum km_type type);
     kmap_atomic是kmap的一种高性能版本. 每个体系都给原子kmap维护一个slot(专用的页表入口); kmap_atomic的调用者必须在type参数中告知系统使用哪个slot. 
    对驱动有意义的slot是 KM_USER0 和 KM_USER1 (在用户空间的直接运行的代码), 以及 KM_IRQ0 和 KM_IRQ1(对于中断处理). 
    注意:原子kmap必须被原子地处理,代码不能在持有一个原子kmap时睡眠。另外内核中没有什么可以阻止2个函数试图使用同一个slot并且相互干扰的机制(尽管每个CPU有独特的一套slot)。实际上,对原子kmap slot的竞争似乎不会有什么问题.

二、页表
  在现代系统中, 处理器必须有一个机制来实现虚拟地址到物理地址的硬件转换,这个硬件模块就是MMU(Memory Management Unit的缩写,内存管理单元).他的功能是将内核与应用程序产生的虚拟地址转换为物理地址。这使得操作系统可以灵活管理内存,以下列举几个优势:
1、重定位:为解决主存不足,引入从外存换入换出的概念。但是必须解决换入时必须放在以前相同存储区的限制,内存管理可实现重定向解除限制;
2、保护:解决不同进程间的互相干扰和破坏;同时可以通过权限位保护内核代码不受应用程序的破坏
3、共享:解决不同进程间的资源共享;同时还可以实现内核与应用程序的内存共享。
此外还有别的用处,这里不再列举

   而要实现MMU的功能,必须在内存中告诉MMU映射信息。页表就是在内存中的提供映射信息的数据结构,他是多级树型结构数组, 包含了虚拟地址到物理地址的映射和一些关联的标志位. 即便在没有直接使用这样页表的体系上,Linux内核也会维护一套页表.

   而页表的数据结构是怎样的呢?这随着构架的变化而不同!这是因为页表是提供给CPU中的硬件模块MMU去读取的,不同构架的MMU结构不尽相同,所以MMU识别的信息结构也是不同的。
    如果想了解X86的页表结构,除了可以google之外,我推荐看看《ULK3》中关于分页机制的章节。
    如果想了解ARM的页表结构,推荐看看网上的一份PDF《ARM MMU中文详解》(就是ARM手册中MMU部分的翻译)
    如果想了解MIPS的页表结构,推荐看看《See MIPS Run 第二版》的中文版。

    操作系统使用MMU的大致过程:在CPU上电启动的时候默认关闭MMU,程序直接使用物理地址。在bootloader成功引导操作系统后,操作系统会将内存的某个区域作为页表的入口,并写入初始化页表。之后将这个入口的物理地址告诉MMU,之后就可以启动MMU。在启动MMU后,操作系统和应用程序使用的所用地址都是虚拟地址了。
   
    但是映射过的内存并不代表有使用,是否被使用是依据上面的struct page来决定的。虽然在通常的ARM系统下,几乎所有的内存都被映射到了内核逻辑地址空间,但是内核不可能都用了吧!剩下的内存可以给内核动态使用及应用程序使用。当一个应用程序需要内存的时候,他需要向内核申请,内核会使用系统调用来获取内存,并修改页表,将用户层的虚拟地址和物理地址对应起来,最后将这个虚拟地址返回给应用程序。
    由于一块物理内存可以同时映射到不同的虚拟地址,所以这样就可以实现应用程序间的内存共享及内核与应用程序间的内存共享。
    设备驱动通常可以做的许多操作来修改页表。幸运的是对于驱动作者, 2.6 内核已经去掉了直接操作页表的需求.所以这里不描述它们的任何细节,有兴趣的可以看看《ULK3》中关于分页机制的章节。

三、虚拟内存区
    虚拟内存区( VMA )用来管理进程地址空间中不同区域的内核数据结构。一个 VMA 代表一个进程的虚拟内存的一个同类区域: 一个有相同权限标志和被相同对象(如, 一个文件或者交换空间)访问的连续虚拟地址范围. 它对应于一个"段"的概念, 更好的描述为"拥有自身属性的内存对象". 一个进程的内存映射由下列区组成:

1、程序的可执行代码(text)区.
2、多个数据区, 包括初始化数据(在执行开始时,有一个明确值)、未初始化数据(BBS)以及程序堆栈.
3、已激活的内存映射的区域.

    查看VMA信息
一个进程的虚拟内存区可通过 /proc//maps看到。 (/proc/self 是一个的特殊文件,它指当前进程. )以下是我PC的一个maps:
  1. tekkaman@tekkaman-desktop:~/development/research/linux-3.0/linux-3.0$ sudo cat /proc/self/maps
  2. 00261000-00262000 r-xp 00000000 00:00 0 [vdso]
  3. 00262000-003b5000 r-xp 00000000 08:01 137348 /lib/tls/i686/cmov/libc-2.11.1.so
  4. 003b5000-003b6000 ---p 00153000 08:01 137348 /lib/tls/i686/cmov/libc-2.11.1.so
  5. 003b6000-003b8000 r--p 00153000 08:01 137348 /lib/tls/i686/cmov/libc-2.11.1.so
  6. 003b8000-003b9000 rw-p 00155000 08:01 137348 /lib/tls/i686/cmov/libc-2.11.1.so
  7. 003b9000-003bc000 rw-p 00000000 00:00 0
  8. 00a6b000-00a86000 r-xp 00000000 08:01 130582 /lib/ld-2.11.1.so
  9. 00a86000-00a87000 r--p 0001a000 08:01 130582 /lib/ld-2.11.1.so
  10. 00a87000-00a88000 rw-p 0001b000 08:01 130582 /lib/ld-2.11.1.so
  11. 08048000-08054000 r-xp 00000000 08:01 1047687 /bin/cat
  12. 08054000-08055000 r--p 0000b000 08:01 1047687 /bin/cat
  13. 08055000-08056000 rw-p 0000c000 08:01 1047687 /bin/cat
  14. 0942a000-0944b000 rw-p 00000000 00:00 0 [heap]
  15. b755a000-b7599000 r--p 00000000 08:01 304358 /usr/lib/locale/zh_CN.utf8/LC_CTYPE
  16. b7599000-b76eb000 r--p 00000000 08:01 304360 /usr/lib/locale/zh_CN.utf8/LC_COLLATE
  17. b76eb000-b76ec000 rw-p 00000000 00:00 0
  18. b76f1000-b76f2000 r--p 00000000 08:01 303954 /usr/lib/locale/zh_CN.utf8/LC_NUMERIC
  19. b76f2000-b76f3000 r--p 00000000 08:01 304359 /usr/lib/locale/zh_CN.utf8/LC_TIME
  20. b76f3000-b76f4000 r--p 00000000 08:01 304361 /usr/lib/locale/zh_CN.utf8/LC_MONETARY
  21. b76f4000-b76f5000 r--p 00000000 08:01 304362 /usr/lib/locale/zh_CN.utf8/LC_MESSAGES/SYS_LC_MESSAGES
  22. b76f5000-b76f6000 r--p 00000000 08:01 303960 /usr/lib/locale/zh_CN.utf8/LC_PAPER
  23. b76f6000-b76f7000 r--p 00000000 08:01 304363 /usr/lib/locale/zh_CN.utf8/LC_NAME
  24. b76f7000-b76f8000 r--p 00000000 08:01 304364 /usr/lib/locale/zh_CN.utf8/LC_ADDRESS
  25. b76f8000-b76f9000 r--p 00000000 08:01 304365 /usr/lib/locale/zh_CN.utf8/LC_TELEPHONE
  26. b76f9000-b76fa000 r--p 00000000 08:01 303964 /usr/lib/locale/zh_CN.utf8/LC_MEASUREMENT
  27. b76fa000-b7701000 r--s 00000000 08:01 452414 /usr/lib/gconv/gconv-modules.cache
  28. b7701000-b7702000 r--p 00000000 08:01 304366 /usr/lib/locale/zh_CN.utf8/LC_IDENTIFICATION
  29. b7702000-b7704000 rw-p 00000000 00:00 0
  30. bfb17000-bfb2c000 rw-p 00000000 00:00 0 [stack]
每行的字段含义如下:
start-end perm offset major:minor inode image 
在 /proc/*/maps 中的每个字段(除映象名) 都对应 struct vm_area_struct 中的一个成员:

start/end :内存区开始和结束的虚拟地址.

perm:内存区的读,写和执行权限的位掩码,描述进程可以对属于此区的页做什么操作. 
      成员的最后一个字符 p表示"私有",s表示"共享"。

offset:内存区的起始对于被映射的文件中的位置偏移. 
        0 偏移意味着内存区开始对应文件的开始.

major:minor  :拥有已被映射文件的设备主次编号. 
               对于设备映射, 主次编号指的是持有被用户打开的设备特殊文件的磁盘分区, 不是设备自身.

inode :被映射文件在文件系统中的 inode 号.

image :被映射的文件名

   vm_area_struct 结构
    当一个用户空间进程调用 mmap 来映射设备内存到它的地址空间, 系统通过一个新 VMA 代表那个映射来响应. 一个支持 mmap 的驱动(其中实现 mmap 方法)需要来帮助那个进程来完成那个 VMA 的初始化. 因此, 为支持 mmap,驱动编写者应当至少对 VMA有基本的理解.所以必须研究一下 struct vm_area_struct ( 在 中定义). 
    注意:为优化查找方法,内核维护 VMA 的链表和树型结构,且 vm_area_struct 的几个成员被用来维护这个结构. 因此, 驱动不能任意创建VMA或改变这个结构. 

    一个进程的虚拟地址空间主要由两个数据结来描述。一个是最高层次的:mm_struct,另一个是较高层次的:vm_area_structs。
    mm_struct结构描述了一个进程的整个虚拟地址空间。
    而vm_area_truct描述了虚拟地址空间的一个区(VMA)。
    mm_struct的指针可以在task结构中找到; 驱动需要访问它时, 通常的方法是使用 current->mm. 
    注意:mm_struct可在进程之间共享; Linux就是用这种方法实现线程的。 

    首先我们来初略地看看mm_struct结构( 在 中定义):

  1. struct mm_struct {
  2.     struct vm_area_struct * mmap;          /* 指向虚拟内存区(VMA)的链表 */
  3.     struct rb_root mm_rb;                  /* 指向red_black树 */
  4.     struct vm_area_struct * mmap_cache;    /* 指向最近找到的虚拟区间 */
  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;        /* mmap区域的基地址 */
  12.     unsigned long task_size;        /* 进程虚拟地址空间的大小 */
  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;             /* 用户空间中的有多少用户? */
  17.     atomic_t mm_count;             /* 本数据结构的引用计数 (users count as 1) */
  18.     int map_count;                 /* 虚拟内存区(VMA)的计数 */

  19.     spinlock_t page_table_lock;        /* 页表和计数器的保护自旋锁 */
  20.     struct rw_semaphore mmap_sem;

  21.     struct list_head mmlist;           /* 用于mm结构体的双向链表,
  22.                                         * 系统中所有的mm结构体通过这个成员串成链表。
  23.                                         * 链表头是init_mm.mmlist,此成员通过mmlist_lock保护
  24.                                         */


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

  27.     unsigned long total_vm, locked_vm, shared_vm, exec_vm;
  28.     unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
  29.     unsigned long start_code, end_code, start_data, end_data; /* 代码段和数据段的起始地址和中止地址 */
  30.     unsigned long start_brk, brk, start_stack;                /* 堆和栈的起始地址和结束地址 */
  31.     unsigned long arg_start, arg_end, env_start, env_end;     /* 命令行参数和环境变量的起始地址和结束地址 */

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

  33.     /*
  34.      * Special counters, in some configurations protected by the
  35.      * page_table_lock, in other configurations by being atomic.
  36.      */
  37.     struct mm_rss_stat rss_stat;

  38.     struct linux_binfmt *binfmt;

  39.     cpumask_var_t cpu_vm_mask_var;

  40.     /* Architecture-specific MM context */
  41.     mm_context_t context;

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

  52.     /* How many tasks sharing this mm are OOM_DISABLE */
  53.     atomic_t oom_disable_count;

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

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

  73.     /* store ref to file /proc/<pid>/exe symlink points to */
  74.     struct file *exe_file;
  75.     unsigned long num_exe_file_vmas;
  76. #ifdef CONFIG_MMU_NOTIFIER
  77.     struct mmu_notifier_mm *mmu_notifier_mm;
  78. #endif
  79. #ifdef CONFIG_TRANSPARENT_HUGEPAGE
  80.     pgtable_t pmd_huge_pte; /* protected by page_table_lock */
  81. #endif
  82. #ifdef CONFIG_CPUMASK_OFFSTACK
  83.     struct cpumask cpumask_allocation;
  84. #endif
  85. };
    接下来重点看看struct vm_area_struct ( 在 中定义). 
  1. /*
  2.  * 这个结构体定义了一个虚拟内存管理(VMM)的内存区(VMA)。每个进程中任意一个虚拟内存区
  3.  * 都对应一个此结构体。一个VMA是进程虚拟内存空间的一部分,
  4.  * 他有特定的页故障处理规则 (比如共享库、可执行区等)
  5.  *
  6.  */
  7. struct vm_area_struct {
  8.     struct mm_struct * vm_mm;    /* 所属的进程虚拟地址空间的结构体指针,
  9.                                   * 就是我们上面看到的结构体 */

  10.     /* 被该VMA 覆盖的虚拟地址范围. 也就是在 /proc/*/maps中出现的头 2 个字段 */
  11.     unsigned long vm_start;        /* vm_mm内的起始地址 */
  12.     unsigned long vm_end;          /* 在vm_mm内的结束地址之后的第一个字节的地址 */

  13.     /* 用来链接进程的VMA结构体的指针, 按地址排序 */
  14.     struct vm_area_struct *vm_next, *vm_prev;

  15.     pgprot_t vm_page_prot;        /* 改VMA的访问权限 */
  16.     unsigned long vm_flags;        /* 描述这个区的标志位集. 对设备驱动较重要的标志是 VM_IO 和 VM_RESERVUED.
  17.                                     * VM_IO标志一个VMA作为内存映射的I/O区,以阻止这个区被包含在进程核心转储中.
  18.                                     * VM_RESERVED告知内存管理系统不要交换出这个VMA; 它应当在大部分设备映射中设置. */

  19.     struct rb_node vm_rb;

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

  32.         struct raw_prio_tree_node prio_tree_node;
  33.     } shared;

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

  43.     /* 内核可能会调用来操作这个内存区函数集。
  44.      * 它的存在指示内存区是一个内核"对象", 类似struct file. */
  45.     const struct vm_operations_struct *vm_ops;

  46.     /* Information about our backing store: */
  47.     unsigned long vm_pgoff;        /* 文件( vm_file)中该区的偏移 , 以页为单位
  48.                                     * 当一个文件和设备被映射时, 他是该区中被映射的第一页在文件中的位置. */
  49.     struct file * vm_file;         /* 一个指向关联的 struct file 结构的指针.(如果有一个) */
  50.     void * vm_private_data;        /* 驱动可以用这个成员来存储自身信息 */

  51. #ifndef CONFIG_MMU
  52.     struct vm_region *vm_region;    /* NOMMU mapping region */
  53. #endif
  54. #ifdef CONFIG_NUMA
  55.     struct mempolicy *vm_policy;    /* NUMA policy for the VMA */
  56. #endif
  57. };
    上面的数据结构中,比较重要的是vm_operations_struct(定义于 )。 它包括下面列出的操作. 这些操作只用来处理进程的内存需要, 后面将学习如何实现这些函数。

  1. /*
  2.  * 虚拟内存管理的操作函数 - 对VMA做打开、关闭和
  3.  * 取消映射操作, (需要保持文件在磁盘上的同步更新等),
  4.  * 当一个缺页或交换页异常发生时,这些函数指针指向实际的函数调用。
  5.  */
  6. struct vm_operations_struct {
  7.     void (*open)(struct vm_area_struct * area);
  8.    /* 被内核调用以实现 VMA 的子系统来初始化一个VMA.
  9.     * 当对 VMA 产生一个新的引用时( 如fork进程时),则调用这个方法
  10.     * 唯一的例外是当该 VMA 第一次被 mmap 创建时;
  11.     * 在这个情况下, 则需要调用驱动的 mmap 方法.
  12.     */

  13.     void (*close)(struct vm_area_struct * area);
  14.    /* 当一个VMA被销毁时, 内核调用此操作.
  15.     * 注意:由于没有相应的 VMA使用计数; VMA只被每个使用它的进程打开和关闭一次.
  16.     */

  17.     int (*fault)(struct vm_area_struct *vma, struct vm_fault *vmf);
  18.    /* 重要的函数调用,当一个进程试图存取使用一个有效的、但当前不在内存中 VMA 的页,
  19.     * 自动触发的缺页异常处理程序就调用该方法。
  20.     * 将对应的数据读取到一个映射在用户地址空间的物理内存页中(替代原有的nopage)
  21.     */


  22.     /* 通知一个之前只读的页将要变为可写,
  23.      * 如果返回错误,将会引发SIGBUS(总线错误) */
  24.     int (*page_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf);

  25.     /* 当get_user_pages() 失败,被access_process_vm调用,一般用于
  26.      * 特殊的VMA(可以在硬件和内存间交换的VMA)????
  27.      */
  28.     int (*access)(struct vm_area_struct *vma, unsigned long addr,
  29.          void *buf, int len, int write);

  30. #ifdef CONFIG_NUMA /* 非一致性内存访问使用 */
  31.     /*
  32.      * set_policy() op must add a reference to any non-NULL @new mempolicy
  33.      * to hold the policy upon return. Caller should pass NULL @new to
  34.      * remove a policy and fall back to surrounding context--i.e. do not
  35.      * install a MPOL_DEFAULT policy, nor the task or system default
  36.      * mempolicy.
  37.      */
  38.     int (*set_policy)(struct vm_area_struct *vma, struct mempolicy *new);

  39.     /*
  40.      * get_policy() op must add reference [mpol_get()] to any policy at
  41.      * (vma,addr) marked as MPOL_SHARED. The shared policy infrastructure
  42.      * in mm/mempolicy.c will do this automatically.
  43.      * get_policy() must NOT add a ref if the policy at (vma,addr) is not
  44.      * marked as MPOL_SHARED. vma policies are protected by the mmap_sem.
  45.      * If no [shared/vma] mempolicy exists at the addr, get_policy() op
  46.      * must return NULL--i.e., do not "fallback" to task or system default
  47.      * policy.
  48.      */
  49.     struct mempolicy *(*get_policy)(struct vm_area_struct *vma,
  50.                     unsigned long addr);
  51.     int (*migrate)(struct vm_area_struct *vma, const nodemask_t *from,
  52.         const nodemask_t *to, unsigned long flags);
  53. #endif
  54. };
这里我们初略了解了Linux内存管理数据结构. 现在可以继续学习Linux内存映射的实现.
阅读(575) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~