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

简介:

本文主要介绍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插入到进程现有的线性地址空间里作为他的线性地址空间的一部分。最后对线性区间分配实际的物理页面并返回基地址。















本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sky-heaven/p/5659537.html,如需转载请自行联系原作者

相关文章
|
1月前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
70 1
|
6天前
|
消息中间件 Linux
Linux:进程间通信(共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
通过上述讲解和代码示例,您可以理解和实现Linux系统中的进程间通信机制,包括共享内存、消息队列和信号量。这些机制在实际开发中非常重要,能够提高系统的并发处理能力和数据通信效率。希望本文能为您的学习和开发提供实用的指导和帮助。
55 20
|
17天前
|
存储 运维 监控
Linux--深入理与解linux文件系统与日志文件分析
深入理解 Linux 文件系统和日志文件分析,对于系统管理员和运维工程师来说至关重要。文件系统管理涉及到文件的组织、存储和检索,而日志文件则记录了系统和应用的运行状态,是排查故障和维护系统的重要依据。通过掌握文件系统和日志文件的管理和分析技能,可以有效提升系统的稳定性和安全性。
35 7
|
26天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
94 13
|
20天前
|
监控 安全 Linux
启用Linux防火墙日志记录和分析功能
为iptables启用日志记录对于监控进出流量至关重要
|
1月前
|
SQL 运维 监控
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
|
1月前
|
调度 开发者
核心概念解析:进程与线程的对比分析
在操作系统和计算机编程领域,进程和线程是两个基本而核心的概念。它们是程序执行和资源管理的基础,但它们之间存在显著的差异。本文将深入探讨进程与线程的区别,并分析它们在现代软件开发中的应用和重要性。
62 4
|
1月前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####
|
2月前
|
缓存 算法 Linux
Linux内核的心脏:深入理解进程调度器
本文探讨了Linux操作系统中至关重要的组成部分——进程调度器。通过分析其工作原理、调度算法以及在不同场景下的表现,揭示它是如何高效管理CPU资源,确保系统响应性和公平性的。本文旨在为读者提供一个清晰的视图,了解在多任务环境下,Linux是如何智能地分配处理器时间给各个进程的。
|
2月前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
94 8