linux内核分析之进程地址空间【转】

转自: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字段描述了进程地址空间

[cpp] view plain copy

 
 print?

  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.   
  20.     struct list_head mmlist;        /* List of maybe swapped mm's.  These are globally strung 
  21.                          * together off init_mm.mmlist, and are protected 
  22.                          * by mmlist_lock 
  23.                          */  
  24.   
  25.     /* Special counters, in some configurations protected by the 
  26.      * page_table_lock, in other configurations by being atomic. 
  27.      */  
  28.     mm_counter_t _file_rss;  
  29.     mm_counter_t _anon_rss;  
  30.   
  31.     unsigned long hiwater_rss;  /* High-watermark of RSS usage */  
  32.     unsigned long hiwater_vm;   /* High-water virtual memory usage */  
  33.   
  34.     unsigned long total_vm, locked_vm, shared_vm, exec_vm;  
  35.     unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;  
  36.     unsigned long start_code, end_code, start_data, end_data;  
  37.     unsigned long start_brk, brk, start_stack;  
  38.     unsigned long arg_start, arg_end, env_start, env_end;  
  39.   
  40.     unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */  
  41.   
  42.     struct linux_binfmt *binfmt;  
  43.   
  44.     cpumask_t cpu_vm_mask;/*用于懒惰TLB交换的位掩码*/  
  45.   
  46.     /* Architecture-specific MM context */  
  47.     mm_context_t context;  
  48.   
  49.     /* Swap token stuff */  
  50.     /* 
  51.      * Last value of global fault stamp as seen by this process. 
  52.      * In other words, this value gives an indication of how long 
  53.      * it has been since this task got the token. 
  54.      * Look at mm/thrash.c 
  55.      */  
  56.     unsigned int faultstamp;  
  57.     unsigned int token_priority;  
  58.     unsigned int last_interval;  
  59.   
  60.     unsigned long flags; /* Must use atomic bitops to access the bits */  
  61.   
  62.     struct core_state *core_state; /* coredumping support */  
  63. #ifdef CONFIG_AIO  
  64.     spinlock_t      ioctx_lock;  
  65.     struct hlist_head   ioctx_list;/*一步IO上下文链表*/  
  66. #endif  
  67. #ifdef CONFIG_MM_OWNER  
  68.     /* 
  69.      * "owner" points to a task that is regarded as the canonical 
  70.      * user/owner of this mm. All of the following must be true in 
  71.      * order for it to be changed: 
  72.      * 
  73.      * current == mm->owner 
  74.      * current->mm != mm 
  75.      * new_owner->mm == mm 
  76.      * new_owner->alloc_lock is held 
  77.      */  
  78.     struct task_struct *owner;  
  79. #endif  
  80.   
  81. #ifdef CONFIG_PROC_FS  
  82.     /* store ref to file /proc/<pid>/exe symlink points to */  
  83.     struct file *exe_file;  
  84.     unsigned long num_exe_file_vmas;  
  85. #endif  
  86. #ifdef CONFIG_MMU_NOTIFIER  
  87.     struct mmu_notifier_mm *mmu_notifier_mm;  
  88. #endif  
  89. };  

关于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的对象实现线性区,它的字段为

[cpp] view plain copy

 
 print?

  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.   
  13.     /* linked list of VM areas per task, sorted by address */  
  14.     struct vm_area_struct *vm_next;  
  15.   
  16.     pgprot_t vm_page_prot;      /* Access permissions of this VMA. */  
  17.     unsigned long vm_flags;     /* Flags, see mm.h. */  
  18.   
  19.     struct rb_node vm_rb;  
  20.   
  21.     /* 
  22.      * For areas with an address space and backing store, 
  23.      * linkage into the address_space->i_mmap prio tree, or 
  24.      * linkage to the list of like vmas hanging off its node, or 
  25.      * linkage of vma in the address_space->i_mmap_nonlinear list. 
  26.      */  
  27.     union {  
  28.         struct {  
  29.             struct list_head list;  
  30.             void *parent;   /* aligns with prio_tree_node parent */  
  31.             struct vm_area_struct *head;  
  32.         } vm_set;  
  33.   
  34.         struct raw_prio_tree_node prio_tree_node;  
  35.     } shared;  
  36.   
  37.     /* 
  38.      * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma 
  39.      * list, after a COW of one of the file pages.  A MAP_SHARED vma 
  40.      * can only be in the i_mmap tree.  An anonymous MAP_PRIVATE, stack 
  41.      * or brk vma (with NULL file) can only be in an anon_vma list. 
  42.      */  
  43.     struct list_head anon_vma_node; /* Serialized by anon_vma->lock */  
  44.     struct anon_vma *anon_vma;  /* Serialized by page_table_lock */  
  45.   
  46.     /* Function pointers to deal with this struct. */  
  47.     const struct vm_operations_struct *vm_ops;  
  48.   
  49.     /* Information about our backing store: */  
  50.     unsigned long vm_pgoff;     /* Offset (within vm_file) in PAGE_SIZE 
  51.                        units, *not* PAGE_CACHE_SIZE */  
  52.     struct file * vm_file;      /* File we map to (can be NULL). */  
  53.     void * vm_private_data;     /* was vm_pte (shared mem) */  
  54.     unsigned long vm_truncate_count;/* truncate_count or restart_addr */  
  55.   
  56. #ifndef CONFIG_MMU  
  57.     struct vm_region *vm_region;    /* NOMMU mapping region */  
  58. #endif  
  59. #ifdef CONFIG_NUMA  
  60.     struct mempolicy *vm_policy;    /* NUMA policy for the VMA */  
  61. #endif  
  62. };  

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

操作

线性区的处理

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

 

[cpp] view plain copy

 
 print?

  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.   
  21.             rb_node = mm->mm_rb.rb_node;  
  22.             vma = NULL;  
  23. //This block is the tree walk.  
  24.             while (rb_node) {  
  25.                 struct vm_area_struct * vma_tmp;  
  26. //The macro, as the name suggests, returns the VMA that this tree node points to.  
  27.                 vma_tmp = rb_entry(rb_node,  
  28.                         struct vm_area_struct, vm_rb);  
  29. //Checks if the next node is traversed by the left or right leaf  
  30.                 if (vma_tmp->vm_end > addr) {  
  31.                     vma = vma_tmp;  
  32. //If the current VMA is what is required, this exits the while loop  
  33.                     if (vma_tmp->vm_start <= addr)  
  34.                         break;  
  35.                     rb_node = rb_node->rb_left;  
  36.                 } else  
  37.                     rb_node = rb_node->rb_right;  
  38.             }  
  39.             //If the VMA is valid, this sets the mmap_cache for the next call to find_vma().  
  40.             if (vma)  
  41.                 mm->mmap_cache = vma;  
  42.         }  
  43.     }  
  44. //Returns the VMA that contains the address or, as a side effect of the tree walk,   
  45. //returns the VMA that is closest to the requested address.  
  46.     return vma;  
  47. }  

 

分配线性地址区间

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

[cpp] view plain copy

 
 print?

  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函数做的实际工作

[cpp] view plain copy

 
 print?

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

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

[cpp] view plain copy

 
 print?

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

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

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

[cpp] view plain copy

 
 print?

  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,其他一个类似。

[cpp] view plain copy

 
 print?

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

接着上面的调用mmap_region函数

[cpp] view plain copy

 
 print?

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

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

时间: 2024-10-26 21:07:42

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

Linux内核剖析 之 进程地址空间(一)

绪论     内核获取内存方式--直接了当:     1. 从分区页框分配器获取内存(__get_free_pages()或alloc_pages()):     2. 使用slab分配器为专用或通用对象分配内存(kmem_cache_alloc()或kmalloc()):     3. 使用vmalloc或vmalloc_32获取一块非连续内存区.     如果申请的内存得以满足,这些函数返回一个页描述符地址或线性地址.     *内核申请内存使用这些简单方法基于以下两个原因:     1.内

Linux内核剖析 之 进程地址空间(三)

本节主要讲述缺页异常处理程序和堆的管理等内容. 缺页异常处理程序 触发缺页异常程序的两种情况: 1. 由编程错误引起的异常(如访问越界,地址不属于进程地址空间). 2. 地址属于线性地址空间,但内核还未分配相应的物理页,导致缺页异常. 缺页异常处理程序总体方案: 线性区描述符可以让缺页异常处理程序非常有效的完成它的工作. do_page_fault()函数是80x86上的缺页中断服务程序,它把引起缺页的线性地址和当前进程的线性区相比较,从而根据具体方案选择适当的方法处理此异常. 标识符vmall

Linux内核剖析 之 进程地址空间(二)

//接前一章,本节主要介绍线性区以及相关线性区的操作. 线性区 Linux通过类型为vm_area_struct的对象实现线性区. vm_area_struct: struct vm_area_struct { struct mm_struct * vm_mm; /* The address space we belong to. */ unsigned long vm_start; /* Our start address within vm_mm. */ unsigned long vm_e

Linux内核分析(四)----进程管理|网络子系统|虚拟文件系统|驱动简介

原文:Linux内核分析(四)----进程管理|网络子系统|虚拟文件系统|驱动简介 Linux内核分析(四) 两天没有更新了,上次博文我们分析了linux的内存管理子系统,本来我不想对接下来的进程管理子系统.网络子系统.虚拟文件系统在这个阶段进行分析的,但是为了让大家对内核有个整体的把握,今天还是简单的介绍一下剩余的几个子系统,我们对这几个子系统的分析,只要了解其作用和部分内容即可,不必深究,等我们写上几个驱动,到时候按照驱动再来分析这几个子系统我们就清晰多了. 在http://www.cnbl

Linux内核分析(五)----字符设备驱动实现

原文:Linux内核分析(五)----字符设备驱动实现 Linux内核分析(五) 昨天我们对linux内核的子系统进行简单的认识,今天我们正式进入驱动的开发,我们今后的学习为了避免大家没有硬件的缺陷,我们都会以虚拟的设备为例进行学习,所以大家不必害怕没有硬件的问题. 今天我们会分析到以下内容: 1.      字符设备驱动基础 2.      简单字符设备驱动实现 3.      驱动测试   l  字符设备基础 1.       字符设备描述结构 在linux2.6内核中,使用cdev结构体描

Linux内核分析(三)----初识linux内存管理子系统

原文:Linux内核分析(三)----初识linux内存管理子系统 Linux内核分析(三) 昨天我们对内核模块进行了简单的分析,今天为了让我们今后的分析没有太多障碍,我们今天先简单的分析一下linux的内存管理子系统,linux的内存管理子系统相当的庞大,所以我们今天只是初识,只要对其进行简单的了解就好了,不会去追究代码,但是在后面我们还会对内存管理子系统进行一次深度的分析. 在分析今天的内容之前,我们先来看出自http://bbs.chinaunix.net/thread-2018659-2

LINUX内核分析第四周学习总结——扒开应用系统的三层皮(上)【转】

转自:http://www.cnblogs.com/lalacindy/p/5276874.html 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.知识概要 (一)用户态.内核态和中断处理过程 (二)系统调用概述 系统调用概述和系统调用的三层皮 (三)使用库函数API和C代码中嵌入汇编代码触发同一个系统调用 使用库函数API获取系统当前时间 C代码中嵌入汇编代码的方法(复习

Linux内核分析(一)---linux体系简介|内核源码简介|内核配置编译安装

原文:Linux内核分析(一)---linux体系简介|内核源码简介|内核配置编译安装 Linux内核分析(一) 从本篇博文开始我将对linux内核进行学习和分析,整个过程必将十分艰辛,但我会坚持到底,同时在博文中如果那些地方有问题还请各位大神为我讲解. 今天我们会分析到以下内容: 1.      Linux体系结构简介 2.      Linux内核源码简介 3.      Linux内核配置.编译.安装   l  Linux体系结构简介 1.       Linux体系结构(linux系统构

深入理解linux内核之(二)进程

                                      深入理解linux内核之(二)进程       程序是静态的,进程是正在执行的程序的一个实例,一个程序可以由多个进程组成.进程是资源分配的实体.在进程被创建出来之后,该子进程几乎和父进程一样.子进程复制了父进程的地址空间,从fork()之后的第一条指令开始执行,和父进程有同样的程序可执行代码(exec调用除外).尽管子进程和父进程具有同样的程序执行代码,但是子进程拥有自己的stack和heap,因此,子进程对数据的修改对