Chinaunix首页 | 论坛 | 博客
  • 博客访问: 22409
  • 博文数量: 18
  • 博客积分: 810
  • 博客等级: 准尉
  • 技术积分: 205
  • 用 户 组: 普通用户
  • 注册时间: 2008-11-10 21:09
文章分类
文章存档

2009年(15)

2008年(3)

我的朋友

分类: LINUX

2009-04-20 15:37:12

 

内核需要管理进程地址空间,即系统中每个用户空间进程所看到的内存。

通过虚拟内存技术,进程可以拥有远大于物理内存的地址空间。

每个进程都拥有32位或64平坦flat:独立的连续区间)的地址空间。

不同进程可在各自地址空间的相同地址存放不同数据;

进程间可以共享地址空间,称这样的进程为线程。

可被访问的合法地址区间——内存区域

进程只能访问有效范围内的内存地址,每个内存区域也有相应进程必须遵循的特定访问属性,若进程访问违规,则返回“段错误”信息,并终止该进程。

内存区域包括:

可执行文件代码的内存映射,称为代码段

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

包含未初始化全局变量,也就是bss段的零页的内存映射;

用于进程用户空间栈的零页的内存映射;

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

任何内存映射文件;

任何共享内存段;

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

14.1 内存描述符

mm_struct结构体——表示进程的地址空间,包含与之相关的所有信息。

 

    struct mm_struct {
        struct vm_area_struct *mmap; /* list of memory areas内存区域链表 */
        struct rb_root mm_rb; /* red-black tree of VMAs 内存区域红-黑树*/
    /*mmap和mm_rb结构体描述的都是地址空间的全部内存区域,但前者以链表形式存放,后者以红-黑树形式存放。
    链表有利于简单高效的遍历所有元素,而红-黑树适合搜索指定元素*/

     struct vm_area_struct *mmap_cache; /* last used memory area 最后使用的内存区域*/
        unsigned long free_area_cache; /* 1st address space hole */
        pgd_t *pgd; /* page global directory */
        atomic_t mm_users; /* address space users正在使用该地址的进程数目 */
        atomic_t mm_count; /* primary usage counter 结构体的主使用计数*/
        int map_count; /* number of memory areas */
        struct rw_semaphore mmap_sem; /* memory area semaphore */
        spinlock_t page_table_lock; /* page table lock */
        struct list_head mmlist; /* list of all mm_structs 通过mm_list将mm_struct结构体连入双向链表,该链表的首元素是init_mm内存描述符,代表init进程的地址空间;

操作该链表时需要使用mmlist_lock锁来防止并发访问,内存描述符的总数存放于mmlist_nr全局变量,定义与*/

        unsigned long start_code; /* start address of code */
        unsigned long end_code; /* final address of code */
        unsigned long start_data; /* start address of data */
        unsigned long end_data; /* final address of data */
        unsigned long start_brk; /* start address of heap */
        unsigned long brk; /* final address of heap */
        unsigned long start_stack; /* start address of stack */
        unsigned long arg_start; /* start of arguments */
        unsigned long arg_end; /* end of arguments */
        unsigned long env_start; /* start of environment */
        unsigned long env_end; /* end of environment */
        unsigned long rss; /* pages allocated */
        unsigned long total_vm; /* total number of pages */
        unsigned long locked_vm; /* number of locked pages */
        unsigned long def_flags; /* default access flags */
        unsigned long cpu_vm_mask; /* lazy TLB switch mask */
        unsigned long swap_address; /* last scanned address */
        unsigned dumpable:1; /* can this mm core dump? */
        int used_hugetlb; /* used hugetlb pages? */
        mm_context_t context; /* arch-specific data */
        int core_waiters; /* thread core dump waiters */
        struct completion *core_startup_done; /* core start completion */
        struct completion core_done; /* core end completion */
        rwlock_t ioctx_list_lock; /* AIO I/O list lock */
        struct kioctx *ioctx_list; /* AIO I/O list */
        struct kioctx default_kioctx; /* AIO default I/O context */
};

分配内存描述符:

进程描述符的mm域存放着该进程的内存描述符,即current -> mm指向当前进程的内存描述符。

fork()函数利用copy_mm()函数复制父进程的内存描述符current -> mm域给子进程,子进程中的mm_struct是通过文件kernel/fork.c中的allocate_mm()宏从mm_cachep slab缓冲中分配得到的。

每个进程都有唯一的mm_struct结构体,即唯一的进程地址空间。

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

CLONE_VM被指定后,内核就不需要调用allocate_mm()函数,只需要在调用copy_mm()函数复制父进程的内存描述符current -> mm域给子进程。

销毁内存描述符:

进程退出时,内核调用exit_mm()函数:

(1) 调用mmput()函数减少mm_users用户计数

(2) 若用户计数降到0,调用mmdrop()函数,减少mm_count使用计数

(3) 使用计数也降为0,调用free_mm()宏通过kmem_cache_free()函数将mm_struct街头体归还到mm_cachep slab缓存中。

内核线程没有进程地址空间,也没有相关的内存描述符,所以内核线程对应的进程描述符中mm域为空,内核线程会直接使用前一个进程的内存描述符。

14.2 内存区域

内存区域也称为虚拟内存区域或VMA

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

 

struct vm_area_struct {
        struct mm_struct *vm_mm; /* associated mm_struct */
        unsigned long vm_start; /* VMA start, inclusive */
        unsigned long vm_end; /* VMA end , exclusive */
        struct vm_area_struct *vm_next; /* list of VMA's */
        pgprot_t vm_page_prot; /* access permissions */
        unsigned long vm_flags; /* flagsVMA标志 */
        struct rb_node vm_rb; /* VMA's node in the tree */
        union { /* links to address_space->i_mmap or i_mmap_nonlinear */
                struct {
                        struct list_head list;
                        void *parent;
                        struct vm_area_struct *head;
                } vm_set;
                struct prio_tree_node prio_tree_node;
        } shared;
        struct list_head anon_vma_node; /* anon_vma entry */
        struct anon_vma *anon_vma; /* anonymous VMA object */
        struct vm_operations_struct *vm_ops; /* associated ops相关的操作函数表 */
        unsigned long vm_pgoff; /* offset within file */
        struct file *vm_file; /* mapped file, if any */
        void *vm_private_data; /* private data */
};

内存区间长度vm_end-vm_start

vm_mm域指向和VMA相关的mm_struct结构体;

两个独立的进程将同一个文件映射到各自的地址空间,分别有vm_area_struct来标志各自的内存区域;

两个线程共享一个地址空间,它们同时共享其中所有的vm_area_struct结构体。

VMA标志:

反映的是内核处理页面所需要遵守的行为准则,而不是硬件要求,也包含内存区域中页面的信息。

Flag

Effect on the VMA and its pages

VM_READ

Pages can be read from

VM_WRITE

Pages can be written to

VM_EXEC

Pages can be executed

VM_SHARED

Pages are shared共享映射/私有映射

VM_MAYREAD

The VM_READ flag can be set

VM_MAYWRITE

The VM_WRITE flag can be set

VM_MAYEXEC

The VM_EXEC flag can be set

VM_MAYSHARE

The VM_SHARE flag can be set

VM_GROWSDOWN

The area can grow downward

VM_GROWSUP

The area can grow upward

VM_SHM

The area is used for shared memory

VM_DENYWRITE

The area maps an unwritable file

VM_EXECUTABLE

The area maps an executable file

VM_LOCKED

The pages in this area are locked

VM_IO

The area maps a device's I/O space

VM_SEQ_READ

The pages seem to be accessed sequentially

VM_RAND_READ

The pages seem to be accessed randomly

VM_DONTCOPY

This area must not be copied on fork()

VM_DONTEXPAND

This area cannot grow via mremap()

VM_RESERVED

This area must not be swapped out

VM_ACCOUNT

This area is an accounted VM object

VM_HUGETLB

This area uses hugetlb pages

VM_NONLINEAR

This area is a nonlinear mapping

操作VMA

 

    struct vm_operations_struct {
        void (*open) (struct vm_area_struct *);
        void (*close) (struct vm_area_struct *);
        struct page * (*nopage) (struct vm_area_struct *, unsigned long, int);/*要访问的页不在物理内存中,被页错误处理程序调用*/
        int (*populate) (struct vm_area_struct *, unsigned long, unsigned long,
                         pgprot_t, unsigned long, int);/*被系统调用remap_pages()调用为将要发生的缺页中断预映射一个新映射*/
};

内存区域的树型结构和链表结构:

可用内存描述符的mmap域或mm_rp域访问内存区域,它们包括相同的vm_area_struct结构体的指针,组织方法不同。

mmap域使用单独链表连接所有内存区域对象,,mmap域指向链表的第一个内存区域,每个vm_area_struct结构体通过vm_next域连入链表;

mm_rb域使用红-黑树连接所有内存区域对象,mm_rb域指向根节点,每个vm_area_struct结构体通过vm_rb域连接到树中。

实际使用中的内存区域:

/proc//maps显示了该进程地址空间的全部内存区域;

pmap工具将信息以更方便阅读的形式输出。

 

14.3 操作内存区域

find_vma()——寻找第一个包含addr的内存区域,定义在

 

    struct vm_area_struct * find_vma(struct mm_struct *mm, unsigned long addr)
{
        struct vm_area_struct *vma = NULL;

        if (mm) {
                vma = mm->mmap_cache;//先检查mmap_cache

                if (!(vma && vma->vm_end > addr && vma->vm_start <= addr)) {//否则搜索红-黑树

                        struct rb_node *rb_node;

                        rb_node = mm->mm_rb.rb_node;
                        vma = NULL;
                        while (rb_node) {
                                struct vm_area_struct * vma_tmp;

                                vma_tmp = rb_entry(rb_node,
                                                   struct vm_area_struct, vm_rb);
                                if (vma_tmp->vm_end > addr) {
                                        vma = vma_tmp;
                                        if (vma_tmp->vm_start <= addr)
                                                break;
                                        rb_node = rb_node->rb_left;
                                } else
                                        rb_node = rb_node->rb_right;
                        }
                        if (vma)
                                mm->mmap_cache = vma;
                }
        }

        return vma;
}

find_vma_prev()——寻找第一个小于addr的内存区域

 

struct vm_area_struct * find_vma_prev(struct mm_struct*mm,        

                                      unsigned long addr,
                                    struct vm_area_struct **pprev)

find_vma_intersection()——返回第一个与指定地址区间相交的VMA

 

    static inline struct vm_area_struct *
find_vma_intersection(struct mm_struct *mm,
                      unsigned long start_addr,
                      unsigned long end_addr)
{
        struct vm_area_struct *vma;

        vma = find_vma(mm, start_addr);
        if (vma && end_addr <= vma->vm_start)
                vma = NULL;
        return vma;
}

14.4 mmap()do_mmap():创建地址区间

内核使用do_mmap()函数创建一个新的线性地址区间

 

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

do_mmap()函数将一个地址区间加入进程的地址空间,如果与已存在的地址空间相邻且具有相同的访问权限,则与之合并,即扩展已存在的内存区域,否则创建一个新的区域。

prot参数指定内存区域中页面的访问权限,

Flag

Effect on the Pages in the New Interval

PROT_READ

Corresponds to VM_READ

PROT_WRITE

Corresponds to VM_WRITE

PROT_EXEC

Corresponds to VM_EXEC

PROT_NONE

Page cannot be accessed

 

flag参数指定了VMA标志,

Flag

Effect on the New Interval

MAP_SHARED

The mapping can be shared

MAP_PRIVATE

The mapping cannot be shared

MAP_FIXED

The new interval must start at the given address addr

MAP_ANONYMOUS

The mapping is not file-backed, but is anonymous

MAP_GROWSDOWN

Corresponds to VM_GROWSDOWN

MAP_DENYWRITE

Corresponds to VM_DENYWRITE

MAP_EXECUTABLE

Corresponds to VM_EXECUTABLE

MAP_LOCKED

Corresponds to VM_LOCKED

MAP_NORESERVE

No need to reserve space for the mapping

MAP_POPULATE

Populate (prefault) page tables

MAP_NONBLOCK

Do not block on I/O

 

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

mmap的系统调用:

 

void * mmap2(void *start,
              size_t length,
              int prot,
              int flags,
              int fd,
              off_t pgoff)

14.5 munmap()do_munmap():删除地址区间

int do_munmap(struct mm_struct *mm, unsigned long start, size_t len)
munmap()为用户空间提供从地址空间删除指定地址空间的方法。

int munmap(void *start, size_t length)
系统调用:

 

    asmlinkage long sys_munmap(unsigned long addr, size_t len)
{
        int ret;
        struct mm_struct *mm;

        mm = current->mm;
        down_write(&mm->mmap_sem);
        ret = do_munmap(mm, addr, len);
        up_write(&mm->mmap_sem);
        return ret;
}

14.6 页表

应用程序操作的对象是映射到物理内存之上的虚拟内存,但是处理器直接操作的却是物理内存。

当用程序访问一个虚拟地址时,需要首先将虚拟地址转换成物理地址,然后处理器才能解析地址访问请求。

三级页表完成地址转换。

页全局变量(PGD);

中间页目录(PMD);

页表(PTE)。

多数体系结构中,搜索页表的工作由硬件完成——翻译后缓冲器(TLB,先检查是否有缓存,否则再通过页表索引物理地址)

阅读(530) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~