《Linux内核设计与实现》读书笔记——进程地址空间

进程地址空间

内核除了管理本身的内存之外,还需要管理用户空间的内存。

用户空间中进程的内存称为进程地址空间,也就是系统中每个用户空间进程所看到的内存。

Linux采用虚拟内存技术,因此系统中所有的进程之间以虚拟方式共享内存。

对一个进程而言,它好像都可以访问整个系统的所有物理内存。

进程地址空间由进程可寻址的虚拟地址组成,它是一个32位或者64位平坦地址空间。

“平坦”指的是地址空间范围是一个独立的连续区间。

现代采用虚拟内存的操作系统通常使用平坦地址空间而不是分段式的内存模式。

尽管一个进程可以寻址4G(32位)虚拟内存,但是并不代码它有权访问所有的虚拟地址。

可被访问的合法地址空间称为内存区域

内存区域具有相关权限,如对相关进程可读、可写、可执行等。

进程访问了不在有效范文内的内存区域,或者以不正确的方式访问了内存区域,内核就会终止该进程,并返回段错误。

内存区域包含各种内存对象:

  • 代码段;
  • 数据段;
  • BSS段;
  • 用户空间栈;
  • 共享库的代码段、数据段和BSS段;
  • 内存映射文件;
  • 共享内存段;
  • 匿名的内存映射(比如有malloc()分配的内存);

内存描述符

内存描述符结构体表示进程的地址空间,位于include\linux\mm_types.h。

task_struct结构体中的mm成员表示进程使用的内存描述符。

进程的mm_struct结构体使用allocate_mm()宏从mm_cachep slab缓存中分配得到:

/*
 * Allocate and initialize an mm_struct.
 */
struct mm_struct * mm_alloc(void)
{
    struct mm_struct * mm;
    mm = allocate_mm();
    if (mm) {
        memset(mm, 0, sizeof(*mm));
        mm = mm_init(mm, current);
    }
    return mm;
}

进程的内存描述符的内容来自通过copy_mm()复制来的父进程的内存描述符。

static int copy_mm(unsigned long clone_flags, struct task_struct * tsk)

对于线程,由于内存描述符是公用的,所以不需要新的内存描述符:

    if (clone_flags & CLONE_VM) {
        atomic_inc(&oldmm->mm_users);
        mm = oldmm;
        goto good_mm;
    }

撤销内存描述符使用:

static void exit_mm(struct task_struct * tsk);

内核线程对应进程描述符中的mm成员是空,因为内核线程不需要进程地址空间,所以使用进程描述符有点浪费。

但是内核线程也需要用到内核描述符中的某些内容,比如页表,因此内核线程选择使用前一个进程的内存描述符。

虚拟内存区域

vm_area_struct结构体用来描述内存区域,位于include\linux\mm_types.h。

内存区域在Linux内存中也常被称为虚拟内存区域(Virtual Memory Areas,VMA)。

vm_area_struct结构体描述了指定地址空间内连续区间上的一个独立内存范围。

每个内存区域都拥有一致的属性。

同一个地址空间内的不同内存区域不能重叠。

vm_area_struct结构体包含VMA标志,用来描述内存区域包含的页面的行为和信息,它是内核处理页面所需要遵守的行为准则,而不是硬件要求。

VMA标志如下:

vm_area_struct结构体包含vm_ops成员,指向与指定内存区域相关的操作函数:

struct vm_operations_struct {
    void (*open)(struct vm_area_struct * area);
    void (*close)(struct vm_area_struct * area);
    int (*fault)(struct vm_area_struct *vma, struct vm_fault *vmf);
    /* notification that a previously read-only page is about to become
     * writable, if an error is returned it will cause a SIGBUS */
    int (*page_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf);
    /* called by access_process_vm when get_user_pages() fails, typically
     * for use by special VMAs that can switch between memory and hardware
     */
    int (*access)(struct vm_area_struct *vma, unsigned long addr,
              void *buf, int len, int write);
#ifdef CONFIG_NUMA
    /*
     * set_policy() op must add a reference to any non-NULL @new mempolicy
     * to hold the policy upon return.  Caller should pass NULL @new to
     * remove a policy and fall back to surrounding context--i.e. do not
     * install a MPOL_DEFAULT policy, nor the task or system default
     * mempolicy.
     */
    int (*set_policy)(struct vm_area_struct *vma, struct mempolicy *new);
    /*
     * get_policy() op must add reference [mpol_get()] to any policy at
     * (vma,addr) marked as MPOL_SHARED.  The shared policy infrastructure
     * in mm/mempolicy.c will do this automatically.
     * get_policy() must NOT add a ref if the policy at (vma,addr) is not
     * marked as MPOL_SHARED. vma policies are protected by the mmap_sem.
     * If no [shared/vma] mempolicy exists at the addr, get_policy() op
     * must return NULL--i.e., do not "fallback" to task or system default
     * policy.
     */
    struct mempolicy *(*get_policy)(struct vm_area_struct *vma,
                    unsigned long addr);
    int (*migrate)(struct vm_area_struct *vma, const nodemask_t *from,
        const nodemask_t *to, unsigned long flags);
#endif
};

内存描述符中有两个成员mmap和mm_rb,用于访问内存区域。这两个成员指向的内存区域是一致的,但是前者是链表,遍历快;后者是红黑树,搜索元素快。

内存区域操作函数:

/* Look up the first VMA which satisfies  addr < vm_end,  NULL if none. */
extern struct vm_area_struct * find_vma(struct mm_struct * mm, unsigned long addr);
extern struct vm_area_struct * find_vma_prev(struct mm_struct * mm, unsigned long addr,
                         struct vm_area_struct **pprev);
static inline struct vm_area_struct * find_vma_intersection(struct mm_struct * mm, unsigned long start_addr, unsigned long end_addr)

创建地址区间

内核使用do_mmap()创建一个新的线性地址区间,并将该地址区间加入到进程的地址空间:

static inline unsigned long do_mmap(struct file *file, unsigned long addr,
    unsigned long len, unsigned long prot,
    unsigned long flag, unsigned long offset)

用户空间可以使用mmap()系统调用获取内核函数do_mmap()的功能。

对应的是do_munmap()函数,从特定的进程地址空间中删除指定地址区间:

extern int do_munmap(struct mm_struct *, unsigned long, size_t);

页表

内存描述符中的pgd成员指向全局目录:

Linux使用三级页表完成地址转换。

猜你喜欢

转载自blog.csdn.net/jiangwei0512/article/details/106151043