转自:http://blog.csdn.net/bullbat/article/details/7106094

版权声明:本文为博主原创文章,未经博主允许不得转载。

本文主要介绍linux内核中进程地址空间的数据结构描述,包括mm_struct/vm_area_struct。进程线性地址区间的分配流程,并对相应的源代码做了注释。

内核中的函数以相当直接了当的方式获得动态内存。当给用户态进程分配内存时,情况完全不同了。进程对动态内存的请求被认为是不紧迫的,一般来说,内核总是尽量推迟给用户态进程分配内存。由于用户进程时不可信任的,因此,内核必须能随时准备捕获用户态进程引起的所有寻址错误。当用户态进程请求动态内存时,并没有获得请求的页框,而仅仅获得对一个新的线性地址区间的使用权,而这一线性地址区间就成为进程地址空间的一部分。

进程地址空间由允许进程使用的全部线性地址组成。内核可以通过增加或删除某些线程地址区间来动态地修改进程的地址空间。内核通过所谓线性去得资源来标示线性地址区间,线性区是由起始线性地址、长度和一些访问权限来描述的。进程获得新线性区的一些典型情况:

1.但用户在控制台输入一条命令时,shell进程创建一个新的进程去执行这个命令。结果是,一个全新的地址空间(也就是一组线性区)分配给新进程。

2.正在运行的进程有可能决定装入一个完全不同的程序。这时,进程描述符不变,可是在装入这个程序以前所有的线性区却被释放,并有一组新的线性区被分配给这个进程。

3.正在运行的进程可能对一个文件执行内存映像。

4.进程可能持续向他的用户态堆栈增加数据,知道映像这个堆栈的线性区用完为止,此时,内核也许会决定扩展这个线性区的大小。

5.进程可能创建一个IPC共享线性区来与其他合作进程共享数据。此时,内核给这个进程分配一个新的线性区以实现这个方案。

6.进程可能通过调用类似malloc这样的函数扩展自己的动态堆。结果是,内核可能决定扩展给这个堆所分配的线性区。

数据结构描述

进程描述符task_struct中的mm字段描述了进程地址空间

  1. struct mm_struct {
  2. struct vm_area_struct * mmap;       /* list of VMAs */
  3. struct rb_root mm_rb;
  4. struct vm_area_struct * mmap_cache; /* last find_vma result */
  5. unsigned long (*get_unmapped_area) (struct file *filp,
  6. unsigned long addr, unsigned long len,
  7. unsigned long pgoff, unsigned long flags);
  8. void (*unmap_area) (struct mm_struct *mm, unsigned long addr);
  9. unsigned long mmap_base;        /* base of mmap area */
  10. unsigned long task_size;        /* size of task vm space */
  11. unsigned long cached_hole_size;     /* if non-zero, the largest hole below free_area_cache */
  12. unsigned long free_area_cache;      /* first hole of size cached_hole_size or larger */
  13. pgd_t * pgd;
  14. atomic_t mm_users;          /* How many users with user space? */
  15. atomic_t mm_count;          /* How many references to "struct mm_struct" (users count as 1) */
  16. int map_count;              /* number of VMAs */
  17. struct rw_semaphore mmap_sem;
  18. spinlock_t page_table_lock;     /* Protects page tables and some counters */
  19. struct list_head mmlist;        /* List of maybe swapped mm's.  These are globally strung
  20. * together off init_mm.mmlist, and are protected
  21. * by mmlist_lock
  22. */
  23. /* Special counters, in some configurations protected by the
  24. * page_table_lock, in other configurations by being atomic.
  25. */
  26. mm_counter_t _file_rss;
  27. mm_counter_t _anon_rss;
  28. unsigned long hiwater_rss;  /* High-watermark of RSS usage */
  29. unsigned long hiwater_vm;   /* High-water virtual memory usage */
  30. unsigned long total_vm, locked_vm, shared_vm, exec_vm;
  31. unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
  32. unsigned long start_code, end_code, start_data, end_data;
  33. unsigned long start_brk, brk, start_stack;
  34. unsigned long arg_start, arg_end, env_start, env_end;
  35. unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */
  36. struct linux_binfmt *binfmt;
  37. cpumask_t cpu_vm_mask;/*用于懒惰TLB交换的位掩码*/
  38. /* Architecture-specific MM context */
  39. mm_context_t context;
  40. /* Swap token stuff */
  41. /*
  42. * Last value of global fault stamp as seen by this process.
  43. * In other words, this value gives an indication of how long
  44. * it has been since this task got the token.
  45. * Look at mm/thrash.c
  46. */
  47. unsigned int faultstamp;
  48. unsigned int token_priority;
  49. unsigned int last_interval;
  50. unsigned long flags; /* Must use atomic bitops to access the bits */
  51. struct core_state *core_state; /* coredumping support */
  52. #ifdef CONFIG_AIO
  53. spinlock_t      ioctx_lock;
  54. struct hlist_head   ioctx_list;/*一步IO上下文链表*/
  55. #endif
  56. #ifdef CONFIG_MM_OWNER
  57. /*
  58. * "owner" points to a task that is regarded as the canonical
  59. * user/owner of this mm. All of the following must be true in
  60. * order for it to be changed:
  61. *
  62. * current == mm->owner
  63. * current->mm != mm
  64. * new_owner->mm == mm
  65. * new_owner->alloc_lock is held
  66. */
  67. struct task_struct *owner;
  68. #endif
  69. #ifdef CONFIG_PROC_FS
  70. /* store ref to file /proc/<pid>/exe symlink points to */
  71. struct file *exe_file;
  72. unsigned long num_exe_file_vmas;
  73. #endif
  74. #ifdef CONFIG_MMU_NOTIFIER
  75. struct mmu_notifier_mm *mmu_notifier_mm;
  76. #endif
  77. };

关于mm_users字段和mm_count字段

mm_users字段存放共享mm_struct数据结构的轻量级进程的个数。mm_count字段是内存描述符的主使计数器,在mm_users次使用计数器中的所有用户在mm_count中只作为一个单位,每当mm_count递减时,内核都要检查他是否变为0,如果是,就要解除这个内存描述符,因为不再有用户使用他。

用一个例子解释mm_users和mm_count之间的不同。考虑一个内存描述符由两个轻量级进程共享。他的mm_users字段通常存放的值为2,而mm_count字段存放的值为1(两个所有者进程算作一个)。如果把内存描述符在一个长操作的中间不被释放,那么,就应该增加mm_users字段而不是mm_count字段的值。最终结果是相同的,因为mm_users的增加确保了mm_count不变为0,即使拥有这个内存描述符的所有轻量级进程全部死亡。

内核线程仅运行在内核态,因此,他们永远不会访问低于TASK_SIZE(等于PAGE_OFFSET,通常为0xc0000000)的地址。与普通进程相反,内核线程不用线性区,因此,内存描述符的很多字段对内核线程是没有意义的。也就是说,当创建内核线程时,内核线程的active_mm共享父进程的mm,但是只使用mm中部分数据与变量。

线性区

linux通过类型为vm_area_struct的对象实现线性区,它的字段为

  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;   /* 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;
  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;
  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_node; /* Serialized by anon_vma->lock */
  38. struct anon_vma *anon_vma;  /* Serialized by page_table_lock */
  39. /* Function pointers to deal with this struct. */
  40. const struct vm_operations_struct *vm_ops;
  41. /* Information about our backing store: */
  42. unsigned long vm_pgoff;     /* Offset (within vm_file) in PAGE_SIZE
  43. units, *not* PAGE_CACHE_SIZE */
  44. struct file * vm_file;      /* File we map to (can be NULL). */
  45. void * vm_private_data;     /* was vm_pte (shared mem) */
  46. unsigned long vm_truncate_count;/* truncate_count or restart_addr */
  47. #ifndef CONFIG_MMU
  48. struct vm_region *vm_region;    /* NOMMU mapping region */
  49. #endif
  50. #ifdef CONFIG_NUMA
  51. struct mempolicy *vm_policy;    /* NUMA policy for the VMA */
  52. #endif
  53. };

进程所拥有的线性区从来不重叠,并且内核尽力把新分配的线性区与邻接的现有线性区进行合并。如果两个相邻区的访问权限相匹配,就能把他们合并在一起。

操作

线性区的处理

我们举一个常用的find_vma函数,是一个从rb树中查找指定的线性区间。其他的函数不再举例。

  1. /* Look up the first VMA which satisfies  addr < vm_end,  NULL if none. */
  2. //deal with searching the virtual address space for mapped and free regions.
  3. //The two parameters are the top-level mm_struct that is to be searched and the address the caller is interested in
  4. struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr)
  5. {
  6. //Defaults to returning NULL for address not found.
  7. struct vm_area_struct *vma = NULL;
  8. //Makes sure the caller does not try to search a bogus mm.
  9. if (mm) {
  10. /* Check the cache first. */
  11. /* (Cache hit rate is typically around 35%.) */
  12. //mmap_cache has the result of the last call to find_vma().
  13. //This has a chance of not having to search at all through the red-black tree
  14. vma = mm->mmap_cache;
  15. //If it is a valid VMA that is being examined, this checks to see if the address being searched is contained within it. If it is,
  16. //the VMA was the mmap_cache one, so it can be returned. Otherwise, the tree is searched.
  17. if (!(vma && vma->vm_end > addr && vma->vm_start <= addr)) {
  18. //Starts at the root of the tree.
  19. struct rb_node * rb_node;
  20. rb_node = mm->mm_rb.rb_node;
  21. vma = NULL;
  22. //This block is the tree walk.
  23. while (rb_node) {
  24. struct vm_area_struct * vma_tmp;
  25. //The macro, as the name suggests, returns the VMA that this tree node points to.
  26. vma_tmp = rb_entry(rb_node,
  27. struct vm_area_struct, vm_rb);
  28. //Checks if the next node is traversed by the left or right leaf
  29. if (vma_tmp->vm_end > addr) {
  30. vma = vma_tmp;
  31. //If the current VMA is what is required, this exits the while loop
  32. if (vma_tmp->vm_start <= addr)
  33. break;
  34. rb_node = rb_node->rb_left;
  35. } else
  36. rb_node = rb_node->rb_right;
  37. }
  38. //If the VMA is valid, this sets the mmap_cache for the next call to find_vma().
  39. if (vma)
  40. mm->mmap_cache = vma;
  41. }
  42. }
  43. //Returns the VMA that contains the address or, as a side effect of the tree walk,
  44. //returns the VMA that is closest to the requested address.
  45. return vma;
  46. }

分配线性地址区间

do_mmap函数为当前进程创建并初始化一个新的线性区。不过,分配成功之后,可以把这个新的线性区与进程已有的其他线性区进行合并。

  1. /*创建并初始化一个新的线性地址区间,
  2. 不过,分配成功之后,可以把这个新的先行区间
  3. 与已有的其他线性区进行合并;
  4. file和offset:如果新的线性区将把一个文件映射到内存
  5. 则使用文件描述符指针file和文件偏移量offset
  6. addr:这个线性地址指定从何处开始查找一个
  7. 空闲的区间;
  8. len:线性区间的长度;
  9. prot:指定这个线性区所包含页的访问权限,
  10. 比如读写、执行;
  11. flag:指定线性区间的其他标志
  12. */
  13. static inline unsigned long do_mmap(struct file *file, unsigned long addr,
  14. unsigned long len, unsigned long prot,
  15. unsigned long flag, unsigned long offset)
  16. {
  17. unsigned long ret = -EINVAL;
  18. /*对offset的值进行一些初步的检查*/
  19. if ((offset + PAGE_ALIGN(len)) < offset)
  20. goto out;
  21. if (!(offset & ~PAGE_MASK))
  22. ret = do_mmap_pgoff(file, addr, len, prot, flag, offset >> PAGE_SHIFT);
  23. out:
  24. return ret;
  25. }

我们看do_mmap_pgoff函数做的实际工作

  1. unsigned long do_mmap_pgoff(struct file *file, unsigned long addr,
  2. unsigned long len, unsigned long prot,
  3. unsigned long flags, unsigned long pgoff)
  4. {
  5. struct mm_struct * mm = current->mm;
  6. struct inode *inode;
  7. unsigned int vm_flags;
  8. int error;
  9. unsigned long reqprot = prot;
  10. /*下面主要是对参数的基本检查,所提的请求
  11. 是否能满足要求*/
  12. /*
  13. * Does the application expect PROT_READ to imply PROT_EXEC?
  14. *
  15. * (the exception is when the underlying filesystem is noexec
  16. *  mounted, in which case we dont add PROT_EXEC.)
  17. */
  18. if ((prot & PROT_READ) && (current->personality & READ_IMPLIES_EXEC))
  19. if (!(file && (file->f_path.mnt->mnt_flags & MNT_NOEXEC)))
  20. prot |= PROT_EXEC;
  21. if (!len)
  22. return -EINVAL;
  23. if (!(flags & MAP_FIXED))
  24. addr = round_hint_to_min(addr);
  25. error = arch_mmap_check(addr, len, flags);
  26. if (error)
  27. return error;
  28. /* Careful about overflows.. */
  29. len = PAGE_ALIGN(len);
  30. if (!len || len > TASK_SIZE)
  31. return -ENOMEM;
  32. /* offset overflow? */
  33. if ((pgoff + (len >> PAGE_SHIFT)) < pgoff)
  34. return -EOVERFLOW;
  35. /* Too many mappings? */
  36. if (mm->map_count > sysctl_max_map_count)
  37. return -ENOMEM;
  38. if (flags & MAP_HUGETLB) {
  39. struct user_struct *user = NULL;
  40. if (file)
  41. return -EINVAL;
  42. /*
  43. * VM_NORESERVE is used because the reservations will be
  44. * taken when vm_ops->mmap() is called
  45. * A dummy user value is used because we are not locking
  46. * memory so no accounting is necessary
  47. */
  48. len = ALIGN(len, huge_page_size(&default_hstate));
  49. file = hugetlb_file_setup(HUGETLB_ANON_FILE, len, VM_NORESERVE,
  50. &user, HUGETLB_ANONHUGE_INODE);
  51. if (IS_ERR(file))
  52. return PTR_ERR(file);
  53. }
  54. /* Obtain the address to map to. we verify (or select) it and ensure
  55. * that it represents a valid section of the address space.
  56. */
  57. /*获得新线性区的线性地址区间*/
  58. addr = get_unmapped_area(file, addr, len, pgoff, flags);
  59. if (addr & ~PAGE_MASK)
  60. return addr;
  61. /* Do simple checking here so the lower-level routines won't have
  62. * to. we assume access permissions have been handled by the open
  63. * of the memory object, so we don't do any here.
  64. */
  65. /*通过把存放在prot和flags参数中的值进行组合
  66. 来计算新线性区描述符的标志*/
  67. vm_flags = calc_vm_prot_bits(prot) | calc_vm_flag_bits(flags) |
  68. mm->def_flags | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;
  69. if (flags & MAP_LOCKED)
  70. if (!can_do_mlock())
  71. return -EPERM;
  72. /* mlock MCL_FUTURE? */
  73. if (vm_flags & VM_LOCKED) {
  74. unsigned long locked, lock_limit;
  75. locked = len >> PAGE_SHIFT;
  76. locked += mm->locked_vm;
  77. lock_limit = current->signal->rlim[RLIMIT_MEMLOCK].rlim_cur;
  78. lock_limit >>= PAGE_SHIFT;
  79. if (locked > lock_limit && !capable(CAP_IPC_LOCK))
  80. return -EAGAIN;
  81. }
  82. inode = file ? file->f_path.dentry->d_inode : NULL;
  83. if (file) {
  84. switch (flags & MAP_TYPE) {
  85. case MAP_SHARED:
  86. if ((prot&PROT_WRITE) && !(file->f_mode&FMODE_WRITE))
  87. return -EACCES;
  88. /*
  89. * Make sure we don't allow writing to an append-only
  90. * file..
  91. */
  92. if (IS_APPEND(inode) && (file->f_mode & FMODE_WRITE))
  93. return -EACCES;
  94. /*
  95. * Make sure there are no mandatory locks on the file.
  96. */
  97. if (locks_verify_locked(inode))
  98. return -EAGAIN;
  99. vm_flags |= VM_SHARED | VM_MAYSHARE;
  100. if (!(file->f_mode & FMODE_WRITE))
  101. vm_flags &= ~(VM_MAYWRITE | VM_SHARED);
  102. /* fall through */
  103. case MAP_PRIVATE:
  104. if (!(file->f_mode & FMODE_READ))
  105. return -EACCES;
  106. if (file->f_path.mnt->mnt_flags & MNT_NOEXEC) {
  107. if (vm_flags & VM_EXEC)
  108. return -EPERM;
  109. vm_flags &= ~VM_MAYEXEC;
  110. }
  111. if (!file->f_op || !file->f_op->mmap)
  112. return -ENODEV;
  113. break;
  114. default:
  115. return -EINVAL;
  116. }
  117. } else {
  118. switch (flags & MAP_TYPE) {
  119. case MAP_SHARED:
  120. /*
  121. * Ignore pgoff.
  122. */
  123. pgoff = 0;
  124. vm_flags |= VM_SHARED | VM_MAYSHARE;
  125. break;
  126. case MAP_PRIVATE:
  127. /*
  128. * Set pgoff according to addr for anon_vma.
  129. */
  130. pgoff = addr >> PAGE_SHIFT;
  131. break;
  132. default:
  133. return -EINVAL;
  134. }
  135. }
  136. error = security_file_mmap(file, reqprot, prot, flags, addr, 0);
  137. if (error)
  138. return error;
  139. error = ima_file_mmap(file, prot);
  140. if (error)
  141. return error;
  142. /*实际工作*/
  143. return mmap_region(file, addr, len, flags, vm_flags, pgoff);
  144. }

我们get_unmapped_area函数获得新的线性地址区间

  1. /*
  2. The parameters passed are the following:
  3. file The file or device being mapped
  4. addr The requested address to map to
  5. len The length of the mapping
  6. pgoff The offset within the file being mapped
  7. flags Protection flags
  8. */
  9. //When a new area is to be memory mapped, a free region has to be found that is large enough to contain the new mapping.
  10. /*查找进程地址空间以找到一个可以使用的
  11. 线性地址区间,函数根据线性地址区间是否应该
  12. 用于文件内存映射或匿名内存映射,调用两个
  13. 方法(get_unmapped_area文件操作和内存描述符的
  14. get_unmapped_area方法)中的一个*/
  15. unsigned long
  16. get_unmapped_area(struct file *file, unsigned long addr, unsigned long len,
  17. unsigned long pgoff, unsigned long flags)
  18. {
  19. unsigned long (*get_area)(struct file *, unsigned long,
  20. unsigned long, unsigned long, unsigned long);
  21. get_area = current->mm->get_unmapped_area;
  22. if (file && file->f_op && file->f_op->get_unmapped_area)
  23. get_area = file->f_op->get_unmapped_area;
  24. addr = get_area(file, addr, len, pgoff, flags);/*调用对应的函数*/
  25. if (IS_ERR_VALUE(addr))
  26. return addr;
  27. if (addr > TASK_SIZE - len)
  28. return -ENOMEM;
  29. if (addr & ~PAGE_MASK)
  30. return -EINVAL;
  31. /*x86 ia-32直接返回地址*/
  32. return arch_rebalance_pgtables(addr, len);
  33. }

我们看不使用文件的一个,对于和文件相关的一个,在文件系统中再来分析

对于内存相关的get_unmapped_area函数在如下函数中设置

  1. /*
  2. * This function, called very early during the creation of a new
  3. * process VM image, sets up which VM layout function to use:
  4. */
  5. void arch_pick_mmap_layout(struct mm_struct *mm)
  6. {
  7. if (mmap_is_legacy()) {
  8. mm->mmap_base = mmap_legacy_base();
  9. mm->get_unmapped_area = arch_get_unmapped_area;
  10. mm->unmap_area = arch_unmap_area;
  11. } else {
  12. mm->mmap_base = mmap_base();
  13. mm->get_unmapped_area = arch_get_unmapped_area_topdown;
  14. mm->unmap_area = arch_unmap_area_topdown;
  15. }
  16. }

我们直接看arch_get_unmmapped_area,其他一个类似。

  1. unsigned long
  2. arch_get_unmapped_area(struct file *filp, unsigned long addr,
  3. unsigned long len, unsigned long pgoff, unsigned long flags)
  4. {
  5. struct mm_struct *mm = current->mm;
  6. struct vm_area_struct *vma;
  7. unsigned long start_addr;
  8. if (len > TASK_SIZE)
  9. return -ENOMEM;
  10. if (flags & MAP_FIXED)
  11. return addr;
  12. if (addr) {
  13. addr = PAGE_ALIGN(addr);
  14. /*从现有地址空间中查找地址*/
  15. vma = find_vma(mm, addr);
  16. /*当地址合法,现有进程地址空间中没有
  17. vma或者该地址不属于现有进程地址空间中已经
  18. 的vma中(也就是说现有地址空间有vma存在)*/
  19. if (TASK_SIZE - len >= addr &&
  20. (!vma || addr + len <= vma->vm_start))
  21. return addr;/*返回地址*/
  22. }
  23. /*达到这里表示addr为0或者前面的搜索失败*/
  24. /*cached_hole_size表示在free_area_cache下面地址中最大
  25. 的一个空洞,所以从free_area_cache开始搜索,
  26. 这样提高搜索效率*/
  27. if (len > mm->cached_hole_size) {
  28. start_addr = addr = mm->free_area_cache;
  29. } else {/*设置搜索起点为用户态地址空间的三分之一
  30. 处*/
  31. start_addr = addr = TASK_UNMAPPED_BASE;
  32. mm->cached_hole_size = 0;
  33. }
  34. full_search:
  35. /*逐个访问查找从addr开始的vma*/
  36. for (vma = find_vma(mm, addr); ; vma = vma->vm_next) {
  37. /* At this point:  (!vma || addr < vma->vm_end). */
  38. if (TASK_SIZE - len < addr) {
  39. /*
  40. * Start a new search - just in case we missed
  41. * some holes.
  42. */
  43. if (start_addr != TASK_UNMAPPED_BASE) {
  44. addr = TASK_UNMAPPED_BASE;
  45. start_addr = addr;
  46. mm->cached_hole_size = 0;
  47. goto full_search;
  48. }
  49. return -ENOMEM;
  50. }
  51. /*满足没映射的要求*/
  52. if (!vma || addr + len <= vma->vm_start) {
  53. /*
  54. * Remember the place where we stopped the search:
  55. */
  56. mm->free_area_cache = addr + len;
  57. return addr;
  58. }/*更新cached_hole_size,这里每次会更新cached_hole_size
  59. 变量,因为查找len长度为从低地址到高地址
  60. 依次开始查找的,所以第一个满足要求的肯定
  61. 满足比这个地址更低的地址中没有比他的空洞
  62. 更大的了,同时这里的每次更新和上面的
  63. free_area_cache变量的更新可以对应上*/
  64. if (addr + mm->cached_hole_size < vma->vm_start)
  65. mm->cached_hole_size = vma->vm_start - addr;
  66. addr = vma->vm_end;/*更新addr为本次搜索先行区间的末*/
  67. }
  68. }

接着上面的调用mmap_region函数

  1. unsigned long mmap_region(struct file *file, unsigned long addr,
  2. unsigned long len, unsigned long flags,
  3. unsigned int vm_flags, unsigned long pgoff)
  4. {
  5. struct mm_struct *mm = current->mm;
  6. struct vm_area_struct *vma, *prev;
  7. int correct_wcount = 0;
  8. int error;
  9. struct rb_node **rb_link, *rb_parent;
  10. unsigned long charged = 0;
  11. struct inode *inode =  file ? file->f_path.dentry->d_inode : NULL;
  12. /* Clear old maps */
  13. error = -ENOMEM;
  14. munmap_back:
  15. /*确定处于新区间之前的线性区对象的位置,
  16. 以及在红黑树这两个新线性区的位置*/
  17. vma = find_vma_prepare(mm, addr, &prev, &rb_link, &rb_parent);
  18. /*检查是否还存在于新区建重叠的线性区*/
  19. if (vma && vma->vm_start < addr + len) {
  20. if (do_munmap(mm, addr, len))/*删除新的区间*/
  21. return -ENOMEM;
  22. goto munmap_back;
  23. }
  24. /* Check against address space limit. */
  25. /*检查插入新的线性区是否引起进程地址空间的
  26. 大小超过上限*/
  27. if (!may_expand_vm(mm, len >> PAGE_SHIFT))
  28. return -ENOMEM;
  29. /*
  30. * Set 'VM_NORESERVE' if we should not account for the
  31. * memory use of this mapping.
  32. */
  33. if ((flags & MAP_NORESERVE)) {
  34. /* We honor MAP_NORESERVE if allowed to overcommit */
  35. if (sysctl_overcommit_memory != OVERCOMMIT_NEVER)
  36. vm_flags |= VM_NORESERVE;
  37. /* hugetlb applies strict overcommit unless MAP_NORESERVE */
  38. if (file && is_file_hugepages(file))
  39. vm_flags |= VM_NORESERVE;
  40. }
  41. /*
  42. * Private writable mapping: check memory availability
  43. */
  44. if (accountable_mapping(file, vm_flags)) {
  45. charged = len >> PAGE_SHIFT;
  46. if (security_vm_enough_memory(charged))
  47. return -ENOMEM;
  48. vm_flags |= VM_ACCOUNT;
  49. }
  50. /*
  51. * Can we just expand an old mapping?
  52. */
  53. /*检查是否可以和前一个线性区进行合并*/
  54. vma = vma_merge(mm, prev, addr, addr + len, vm_flags, NULL, file, pgoff, NULL);
  55. if (vma)/*合并成功*/
  56. goto out;
  57. /*
  58. * Determine the object being mapped and call the appropriate
  59. * specific mapper. the address has already been validated, but
  60. * not unmapped, but the maps are removed from the list.
  61. */
  62. /*程序运行到这里表示新区将建立为新区间
  63. 分配一个vma结构*/
  64. vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);
  65. if (!vma) {
  66. error = -ENOMEM;
  67. goto unacct_error;
  68. }
  69. /*初始化新区对象*/
  70. vma->vm_mm = mm;
  71. vma->vm_start = addr;
  72. vma->vm_end = addr + len;
  73. vma->vm_flags = vm_flags;
  74. vma->vm_page_prot = vm_get_page_prot(vm_flags);
  75. vma->vm_pgoff = pgoff;
  76. if (file) {
  77. error = -EINVAL;
  78. if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP))
  79. goto free_vma;
  80. if (vm_flags & VM_DENYWRITE) {
  81. error = deny_write_access(file);
  82. if (error)
  83. goto free_vma;
  84. correct_wcount = 1;
  85. }
  86. vma->vm_file = file;
  87. get_file(file);
  88. error = file->f_op->mmap(file, vma);
  89. if (error)
  90. goto unmap_and_free_vma;
  91. if (vm_flags & VM_EXECUTABLE)
  92. added_exe_file_vma(mm);
  93. /* Can addr have changed??
  94. *
  95. * Answer: Yes, several device drivers can do it in their
  96. *         f_op->mmap method. -DaveM
  97. */
  98. addr = vma->vm_start;
  99. pgoff = vma->vm_pgoff;
  100. vm_flags = vma->vm_flags;
  101. }
  102. /*如果该区间是一个共享匿名区*/
  103. else if (vm_flags & VM_SHARED) {
  104. /*初始化,共享匿名区主要用于进程间通信*/
  105. error = shmem_zero_setup(vma);
  106. if (error)
  107. goto free_vma;
  108. }
  109. if (vma_wants_writenotify(vma))
  110. vma->vm_page_prot = vm_get_page_prot(vm_flags & ~VM_SHARED);
  111. /*将新区间插入到进程的线性地址空间中*/
  112. vma_link(mm, vma, prev, rb_link, rb_parent);
  113. file = vma->vm_file;
  114. /* Once vma denies write, undo our temporary denial count */
  115. if (correct_wcount)
  116. atomic_inc(&inode->i_writecount);
  117. out:
  118. perf_event_mmap(vma);
  119. /*增加total_vm字段大小*/
  120. mm->total_vm += len >> PAGE_SHIFT;
  121. vm_stat_account(mm, vm_flags, file, len >> PAGE_SHIFT);
  122. if (vm_flags & VM_LOCKED) {
  123. /*
  124. * makes pages present; downgrades, drops, reacquires mmap_sem
  125. */
  126. /*连续分配线性区的所有页,并将他们
  127. 锁在RAM中*/
  128. long nr_pages = mlock_vma_pages_range(vma, addr, addr + len);
  129. if (nr_pages < 0)
  130. return nr_pages;    /* vma gone! */
  131. mm->locked_vm += (len >> PAGE_SHIFT) - nr_pages;
  132. } else if ((flags & MAP_POPULATE) && !(flags & MAP_NONBLOCK))
  133. /*连续分配线性区的所有页*/
  134. make_pages_present(addr, addr + len);
  135. return addr;/*返回新线性区地址*/
  136. unmap_and_free_vma:
  137. if (correct_wcount)
  138. atomic_inc(&inode->i_writecount);
  139. vma->vm_file = NULL;
  140. fput(file);
  141. /* Undo any partial mapping done by a device driver. */
  142. unmap_region(mm, vma, prev, vma->vm_start, vma->vm_end);
  143. charged = 0;
  144. free_vma:
  145. kmem_cache_free(vm_area_cachep, vma);
  146. unacct_error:
  147. if (charged)
  148. vm_unacct_memory(charged);
  149. return error;
  150. }

到这里分配线性地址空间就算走完了,主要完成的工作依次由根据地址和长度在进程地址空间中查找一个未添加进来的线性区间,如果这个区间可以和当前进程线性地址空间的线性区间可以合并,则合并之。如果不能合并,创建一个线性区间,将这个线性区间vma插入到进程现有的线性地址空间里作为他的线性地址空间的一部分。最后对线性区间分配实际的物理页面并返回基地址。

linux内核分析之进程地址空间【转】的更多相关文章

  1. linux内核分析之进程地址空间管理

    1.struct task_struct 进程内核栈是操作系统为管理每一个进程而分配的一个4k或者8k内存大小的一片内存区域,里面存放了一个进程的所有信息,它能够完整的描述一个正在执行的程序:它打开的 ...

  2. 【转载】linux内核笔记之进程地址空间

    原文:linux内核笔记之进程地址空间 进程的地址空间由允许进程使用的全部线性地址组成,在32位系统中为0~3GB,每个进程看到的线性地址集合是不同的. 内核通过线性区的资源(数据结构)来表示线性地址 ...

  3. LINUX内核分析第八周学习总结:进程的切换和系统的一般执行过程

    韩玉琪 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.进程切换的关 ...

  4. linux内核分析作业8:理解进程调度时机跟踪分析进程调度与进程切换的过程

    1. 实验目的 选择一个系统调用(13号系统调用time除外),系统调用列表,使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用 分析汇编代码调用系统调用的工作过程,特别是参数的传递的方 ...

  5. 《Linux内核分析》第八周 进程的切换和系统的一般执行过程

    [刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK EIGHT ...

  6. Linux内核分析——理解进程调度时机跟踪分析进程调度与进程切换的过程

    20135125陈智威 +原创作品转载请注明出处 +<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 实验 ...

  7. Linux内核分析之理解进程调度时机跟踪分析进程调度与进程切换的过程

    一.原理分析 1.调度时机 背景不同类型的进程有不同的调度需求第一种分类I/O-bond:频繁的进行I/O:通常会花费很多时间等待I/O操作的完成CPU-bound:计算密集型:需要大量的CPU时间进 ...

  8. 《Linux内核分析》第六周 进程的描述与创建

    [刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK SIX(3 ...

  9. 分析Linux内核中进程的调度(时间片轮转)-《Linux内核分析》Week2作业

    1.环境的搭建: 这个可以参考孟宁老师的github:mykernel,这里不再进行赘述.主要是就是下载Linux3.9的代码,然后安装孟宁老师编写的patch,最后进行编译. 2.代码的解读 课上的 ...

随机推荐

  1. Color Length UVA - 1625 DP

    题目:题目链接 题意:输入两个长度分别为n和m的颜色序列,要求按顺序合并成同一个序列,即每次可以把一个序列开头的颜色放到新序列的尾部.对于每个颜色c来说,其跨度L(c)等于最大位置和最小位置之差,输出 ...

  2. 笔记-python lib-pymongo

    笔记-python lib-pymongo 1.      开始 pymongo是python版的连接库,最新版为3.7.2. 文档地址:https://pypi.org/project/pymong ...

  3. python基础之继承派生、组合、接口和抽象类

    类的继承与派生 经典类和新式类 在python3中,所有类默认继承object,但凡是继承了object类的子类,以及该子类的子类,都称为新式类(在python3中所有的类都是新式类) 没有继承obj ...

  4. Java线程和多线程(七)——ThreadLocal

    Java中的ThreadLocal是用来创建线程本地变量用的.我们都知道,访问某个对象的所有线程都是能够共享对象的状态的,所以这个对象状态就不是线程安全的.开发者可以通过使用同步来保证线程安全,但是如 ...

  5. ST表学习

    啊谈不上学习了.复习一下原理留一下板子. $f\left[i,j \right]$表示以$i$为起点,区间长度为${2}^{j}$的区间最值.以最小值为例,即 $min\left(a\left [ k ...

  6. Javascript进阶:对象实例属性和方法

    Ecmascript中,Object类型是所有它的实例的基础.换句话说,Object类型所具有的任何属性和方法也同样存在于更具体的对象中. Object的每个实例都具有以下属性和方法,这些都能方便于我 ...

  7. Android学习记录(6)—将java中的多线程下载移植到Android中(即多线程下载在Android中的使用)③

    在这一节中,我们就来讲多线程下载以及断点续传在android中怎么使用,前两节是为本节做准备的,没有看前两节的同学,最好看完前面的两篇文章再来看这篇.其实在android端的应用和java基本上是差不 ...

  8. DOS程序员手册(十)

    终于到(十)了~~~ 503页 ES:DI       指向未更新且未打开的FCB的指针 注释:该功能最初用来从命令行中析取文件,并以正确的格式来保存此文件 以便打开FCB.为了实现这个目的,可首先将 ...

  9. iOS笔记061 - 二维码的生成和扫描

    二维码 生成二维码 二维码可以存放纯文本.名片或者URL 生成二维码的步骤: 导入CoreImage框架 通过滤镜CIFilter生成二维码 1.创建过滤器 2.恢复滤镜的默认属性 3.设置内容 4. ...

  10. freemaker示例

    第一步  创建一个User.java文件 来两个变量 public class User {      private String userName;       private String us ...