现在的位置: 首页 > 综合 > 正文

进程地址空间[1]

2013年04月08日 ⁄ 综合 ⁄ 共 9421字 ⁄ 字号 评论关闭
 

     内核除了管理本身的内存外,还必须管理进程的地址空间,即系统中每个用户空间进程所看到的内存。Linux采用虚拟内存技术,系统中的所有进程之间以虚拟方式共享内存。对每个进程来说,它们好像都可以访问整个系统的所有物理内存;即使单独一个进程,它拥有的地址空间也可以远远大于系统物理内存。

    进程地址空间由每个进程中的线性地址区组成,而且更为重要的特点是内核允许进程使用该空间中的地址。每个进程都有一个32或64位的flat地址空间,空间的具体大小取决于体系结构。flat描述的是地址空间范围是一个独立的连续区间。通常情况下,每个进程都有惟一的这种flat地址空间,进程地址空间之间彼此互不相干。两个不同的进程可以在鸽子地址空间的相同地址内存放不同的数据。进程之间也可以选择共享地址空间,称这样的进程为线程。

    内存地址是一个给定的值,它要在地址空间范围之内。在地址空间中,我们更为关心的是进程有权访问的虚拟内存地址区间,这些可被访问的合法地址区间被称为内存区域(memory area),通过内核,进程可以给自己的地址空间动态地添加或减少内存区域。

   进程只能访问有效范围内的内存地址。每个内存区域也具有相应进程必须遵循的特定访问属性,如果一个进程访问了不在有效范围中的地址,或以不正确的方式访问有效地址,那么内核就会终止该进程,并返回“段错误”信息。

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

1. 可执行文件代码的内存映射,称为代码段(text section)

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

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

   术语“BSS”是block started by symbol的缩写。因为未初始化的变量没有对应的值,所以不需要存放在可执行对象中。但是因为C标准强制规定未初始化的全局变量要被赋予特殊的默认值,所以内核要将未赋值的变量从可执行代码载入到内存中,然后将零页映射到该片内存上,于是这些未初始化的变量就被赋予了0值,这样避免了在目标文件中显式地进行初始化,减少空间浪费。

4. 用于进程用户空间栈(不要和进程内核栈混淆,进程的内核栈独立存在并由内核维护)的零页的内存映射。

5. 每一个诸如C库或动态连接程序等共享库的代码段,数据段和bss也会被载入进程的地址空间。

6. 任何内存映射文件

7. 任何共享内存段

8. 任何匿名的内存映射,比如由malloc()分配的内存。

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

  • 内存描述符

  内核使用内存描述符结构体表示进程的地址空间,该结构包含了和进程地址空间相关的全部信息:

 


  1. 在<include/linux/sched.h>中
  2. struct mm_struct {
  3.     struct vm_area_struct * mmap;       /* list of VMAs */
  4.     struct rb_root mm_rb;  /* 虚拟内存区域红黑树 */
  5.     struct vm_area_struct * mmap_cache; /* last find_vma result */
  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.     unsigned long mmap_base;        /* base of mmap area */
  11.     unsigned long task_size;        /* size of task vm space */
  12.     unsigned long cached_hole_size;         /* if non-zero, the largest hole below free_area_cache */
  13.     unsigned long free_area_cache;      /* first hole of size cached_hole_size or larger */
  14.     pgd_t * pgd;
  15.     atomic_t mm_users;          /* How many users with user space? */
  16.     atomic_t mm_count;          /* How many references to "struct mm_struct" (users count as 1) */
  17.     int map_count;              /* number of VMAs */
  18.     struct rw_semaphore mmap_sem;
  19.     spinlock_t page_table_lock;     /* Protects page tables and some counters */
  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.     /* Special counters, in some configurations protected by the
  25.      * page_table_lock, in other configurations by being atomic.
  26.      */
  27.     mm_counter_t _file_rss;
  28.     mm_counter_t _anon_rss;
  29.     unsigned long hiwater_rss;  /* High-watermark of RSS usage */
  30.     unsigned long hiwater_vm;   /* High-water virtual memory usage */
  31.     unsigned long total_vm, locked_vm, shared_vm, exec_vm;
  32.     unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
  33.     unsigned long start_code, end_code, start_data, end_data;
  34.     unsigned long start_brk, brk, start_stack;
  35.     unsigned long arg_start, arg_end, env_start, env_end;
  36.     unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */
  37.     cpumask_t cpu_vm_mask;
  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 char dumpable:2;
  51.     /* coredumping support */
  52.     int core_waiters;    /*内核转储等待线程 */
  53.     struct completion *core_startup_done,/*core 开始完成 */ core_done/*core结束完成 */;
  54.     /* aio bits */
  55.     rwlock_t        ioctx_list_lock;   /* AIO IO链表锁*/
  56.     struct kioctx       *ioctx_list;  /* AIO IO链表*/
  57. };

   内核同时使用mm_count和mm_users这两个计数器是为了区别主使用计数器(mm_count)和使用该地址空间的进程数目(mm_users)。

   mmap和mm_rb这两个不同的数据结构体描述的对象是相同的:该地址空间中的全部内存区域。mmap是以链表形式存放的,这样利于简单高效地遍历所有元素;而mm_rb以红黑树形式存放,适合搜索指定元素。

   所有mm_struct结构体通过自身的mmlist域连接成一个双向链表中,首元素是init_mm内存描述符,它代表init进程的地址空间。操作该链表的时候,需要使用mmlist_lock锁(定义在kernel/fork.c中)来防止并发访问。

   内存描述符的总数存放在mmlist_nr全局变量(定义在kernel/fork.c中)中。

  • 分配内存描述符

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

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

   如果父进程希望和子进程共享地址空间,可以在调用clone()时,设置CLONE_VM标志。我们把这样的进程称为线程。

    1. /* SLAB cache for mm_struct structures (tsk->mm) */
    2. static struct kmem_cache *mm_cachep;
    3. #define allocate_mm()   (kmem_cache_alloc(mm_cachep, GFP_KERNEL))
    4. static int copy_mm(unsigned long clone_flags, struct task_struct * tsk)
    5. {
    6.     struct mm_struct * mm, *oldmm;
    7.     int retval;
    8.     tsk->min_flt = tsk->maj_flt = 0;
    9.     tsk->nvcsw = tsk->nivcsw = 0;
    10.     tsk->mm = NULL;
    11.     tsk->active_mm = NULL;
    12.     /*
    13.      * Are we cloning a kernel thread?
    14.      *
    15.      * We need to steal a active VM for that..
    16.      */
    17.     oldmm = current->mm;
    18.     if (!oldmm)
    19.         return 0;
    20.     if (clone_flags & CLONE_VM) {
    21.         atomic_inc(&oldmm->mm_users);
    22.         mm = oldmm;
    23.         goto good_mm;
    24.     }
    25.     retval = -ENOMEM;
    26.     mm = dup_mm(tsk);
    27.     if (!mm)
    28.         goto fail_nomem;
    29. good_mm:
    30.     /* Initializing for Swap token stuff */
    31.     mm->token_priority = 0;
    32.     mm->last_interval = 0;
    33.     tsk->mm = mm;
    34.     tsk->active_mm = mm;
    35.     return 0;
    36. fail_nomem:
    37.     return retval;
    38. }
  • 销毁内存描述符

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

  1. 在<kernel/Exit.c>中
  2. /*
  3.  * Turn us into a lazy TLB process if we
  4.  * aren't already..
  5.  */
  6. static void exit_mm(struct task_struct * tsk)
  7. {
  8.     struct mm_struct *mm = tsk->mm;
  9.     mm_release(tsk, mm);
  10.     if (!mm)
  11.         return;
  12.     /*
  13.      * Serialize with any possible pending coredump.
  14.      * We must hold mmap_sem around checking core_waiters
  15.      * and clearing tsk->mm.  The core-inducing thread
  16.      * will increment core_waiters for each thread in the
  17.      * group with ->mm != NULL.
  18.      */
  19.     down_read(&mm->mmap_sem);
  20.     if (mm->core_waiters) {
  21.         up_read(&mm->mmap_sem);
  22.         down_write(&mm->mmap_sem);
  23.         if (!--mm->core_waiters)
  24.             complete(mm->core_startup_done);
  25.         up_write(&mm->mmap_sem);
  26.         wait_for_completion(&mm->core_done);
  27.         down_read(&mm->mmap_sem);
  28.     }
  29.     atomic_inc(&mm->mm_count);
  30.     BUG_ON(mm != tsk->active_mm);
  31.     /* more a memory barrier than a real lock */
  32.     task_lock(tsk);
  33.     tsk->mm = NULL;
  34.     up_read(&mm->mmap_sem);
  35.     enter_lazy_tlb(mm, current);
  36.     task_unlock(tsk);
  37.     mmput(mm);  //减少内存描述符中的mm_users用户计数
  38. }
  39. 在fork.c中
  40. /* Please note the differences between mmput and mm_release.
  41.  * mmput is called whenever we stop holding onto a mm_struct,
  42.  * error success whatever.
  43.  *
  44.  * mm_release is called after a mm_struct has been removed
  45.  * from the current process.
  46.  *
  47.  * This difference is important for error handling, when we
  48.  * only half set up a mm_struct for a new process and need to restore
  49.  * the old one.  Because we mmput the new mm_struct before
  50.  * restoring the old one. . .
  51.  * Eric Biederman 10 January 1998
  52.  */
  53. void mm_release(struct task_struct *tsk, struct mm_struct *mm)
  54. {
  55.     struct completion *vfork_done = tsk->vfork_done;
  56.     /* Get rid of any cached register state */
  57.     deactivate_mm(tsk, mm);
  58.     /* notify parent sleeping on vfork() */
  59.     if (vfork_done) {
  60.         tsk->vfork_done = NULL;
  61.         complete(vfork_done);
  62.     }
  63.     /*
  64.      * If we're exiting normally, clear a user-space tid field if
  65.      * requested.  We leave this alone when dying by signal, to leave
  66.      * the value intact in a core dump, and to save the unnecessary
  67.      * trouble otherwise.  Userland only wants this done for a sys_exit.
  68.      */
  69.     if (tsk->clear_child_tid
  70.         && !(tsk->flags & PF_SIGNALED)
  71.         && atomic_read(&mm->mm_users) > 1) {
  72.         u32 __user * tidptr = tsk->clear_child_tid;
  73.         tsk->clear_child_tid = NULL;
  74.         /*
  75.          * We don't check the error code - if userspace has
  76.          * not set up a proper pointer then tough luck.
  77.          */
  78.         put_user(0, tidptr);
  79.         sys_futex(tidptr, FUTEX_WAKE, 1, NULL, NULL, 0);
  80.     }
  81. }
  82. /*
  83.  * Decrement the use count and release all resources for an mm.
  84.  */
  85. void mmput(struct mm_struct *mm)
  86. {
  87.     might_sleep();
  88.     if (atomic_dec_and_test(&mm->mm_users)) {
  89.         exit_aio(mm);
  90.         exit_mmap(mm);
  91.         if (!list_empty(&mm->mmlist)) {
  92.             spin_lock(&mmlist_lock);
  93.             list_del(&mm->mmlist);
  94.             spin_unlock(&mmlist_lock);
  95.         }
  96.         put_swap_token(mm);
  97.         mmdrop(mm);
  98.     }
  99. }
  100. /*
  101.  * Called when the last reference to the mm
  102.  * is dropped: either by a lazy thread or by
  103.  * mmput. Free the page directory and the mm.
  104.  */
  105. void fastcall __mmdrop(struct mm_struct *mm)
  106. {
  107.     BUG_ON(mm == &init_mm);
  108.     mm_free_pgd(mm);
  109.     destroy_context(mm);
  110.     free_mm(mm);
  111. }
  112. 在sched.h中
  113. static inline void mmdrop(struct mm_struct * mm)
  114. {
  115.     if (atomic_dec_and_test(&mm->mm_count))
  116.         __mmdrop(mm);
  117. }

 

  • mm_struct与内核线程

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

  内核线程并不需要访问任何用户空间的内存,以为内核线程在用户空间没有任何页,所以它们并不需要有自己的内存描述符和页表。尽管如此,即使访问内核内存,内核线程饿还是需要使用一些数据的,比如页表。为了避免内核线程为内存描述符和页表浪费内存,也为了当新内核线程运行时,避免浪费处理器周期向新地址空间进行切换,内核系拿出将直接使用前一个进程的内存描述符。

  当一个进程被调度时,该进程的mm域执行的地址空间被装载到内存,进程描述符中的active_mm域会被更新,指向新的地址空间。内核线程没有地址空间,所以mm为空。当内核线程被调度时,内核发现它的mm为空,就会保留前一个进程的地址空间,随后内核更新内核线程对应的进程描述符中的active_mm域,使其指向前一个进程的内存描述符,在需要时,内核线程可以使用前一个进程的页表。因为内核线程不访问用户空间的内存,所以它们仅仅使用地址空间中的和内核内存相关的信息。

  

 

抱歉!评论已关闭.