进程地址空间

  • 1 内存描述符
    • 分配内存描述符
    • 销毁内存描述符
    • mm_struct与内核线程
  • 2 内存区域
    • VMA标志
    • VMA操作
    • 内存区域的树形结构和内存区域的链表结构
  • 3 操作内存区域
    • find_vma()
    • find_vma_prev()
    • find_vma_intersection()
  • 4 mmap()和do_mmap():创建地址空间
    • mmap() 系统调用
  • 5 munmap()和do_munmap():删除地址空间
    • munmap()系统调用
  • 6 页表

内核除了管理本身的内存外,还必须管理进程的地址空间,也就是系统中每个用户空间地址所看到的内存。Linux操作系统采用虚拟内存计数,因此,系统中的所有进程之间以虚拟方法共享内存,对每个进程来说,它们好像都可以访问整个系统的所有物理内存。

进程地址空间由每个进程中的线性地址区组成,而且更为重要的特定是内核允许进程使用该空间中的地址。每个进程都有一个32位或64位的flat地址空间,空间的具体大小取决于体系结构,flag地址空间是指地址空间范围是一个独立的连续空间(比如,地址从0扩展到429496729位地址空间)。一些操作系统提供了段地址空间,这种地址空间并非是一个独立的线性区域,而是被分段的,但现代采用虚拟内存的操作系统通常都使用flat地址空间而不是分段式的内存模式。通常情况下,每个进程都有唯一的这种flat地址空间,而进程地址空间之间彼此互不相干,两个不同的进程可以在它们各自地址空间的相同地址内存放不同的数据。进程之间也可以选择共享地址空间,我们称这样的进程为线程。

1 内存描述符

内核使用内存描述符结构体表示进程的地址空间,该结构包含了和进程地址空间有关的全部信息。内存描述符由mm_struct结构体表示,定义在文件linux/sched.h中。

struct mm_struct {struct vm_area_struct * mmap;      /* list of VMAs */struct rb_root mm_rb;struct vm_area_struct * mmap_cache;  /* last find_vma result */unsigned long (*get_unmapped_area) (struct file *filp,unsigned long addr, unsigned long len,unsigned long pgoff, unsigned long flags);void (*unmap_area) (struct vm_area_struct *area);unsigned long mmap_base;       /* base of mmap area */unsigned long free_area_cache;       /* first hole */pgd_t * pgd;atomic_t mm_users;          /* How many users with user space? */atomic_t mm_count;         /* How many references to "struct mm_struct" (users count as 1) */int map_count;              /* number of VMAs */struct rw_semaphore mmap_sem;spinlock_t page_table_lock;        /* Protects page tables, mm->rss, mm->anon_rss */struct list_head mmlist;     /* List of maybe swapped mm's.  These are globally strung* together off init_mm.mmlist, and are protected* by mmlist_lock*/unsigned long start_code, end_code, start_data, end_data;unsigned long start_brk, brk, start_stack;unsigned long arg_start, arg_end, env_start, env_end;unsigned long rss, anon_rss, total_vm, locked_vm, shared_vm;unsigned long exec_vm, stack_vm, reserved_vm, def_flags, nr_ptes;unsigned long saved_auxv[42]; /* for /proc/PID/auxv */unsigned dumpable:1;cpumask_t cpu_vm_mask;/* Architecture-specific MM context */mm_context_t context;/* Token based thrashing protection. */unsigned long swap_token_time;char recent_pagein;/* coredumping support */int core_waiters;struct completion *core_startup_done, core_done;/* aio bits */rwlock_t        ioctx_list_lock;struct kioctx       *ioctx_list;struct kioctx       default_kioctx;
};

mm_users域记录正在使用该地址的进程数目。比如,有两个进程共享该地址空间,那么mm_users的值便等于2;mm_count是mm_struct的主引用计数,只要mm_users不为0,那么mm_count值就等于1。当mm_users的值减为0时,mm_count域的值才为0,如果mm_count的值等于0,说明已经没有任何指向该mm_struct结构体的引用了,这个时候该结构体会被销毁。
mmap和mm_rb这两个数据结构描述的对象是相同的:该地址空间中的全部内存区域。但是mmap是以链表形式存放而后者以红-黑树形式存放。mmap结构体作为链表,利于简单、高效地遍历所有元素,而mm_rb结构体更适合搜索指定元素。
所有的mm_struct结构体都通过自身的mmlist域连接在一个双向链表中,该链表的首元素是init_mm内存描述符,它代表init进程的地址空间,内存描述符的总数存放在mmlist_nr全局变量中,该变量定义在kernel/fork.c中。

分配内存描述符

在进程的struct task_struct进程描述符中,mm域存放着该进程使用的内存描述符,所以current->mm便指向当前进程的内存描述符。fork函数利用copy_mm()函数复制父进程的内存描述符,也就是current->mm域给其子进程,而子进程中的mm_struct结构体实际是通过文件kernel/fork.c中的allocate_mm()宏从mm_cachep slab缓存中分配得到的。

如果父进程希望和其子进程共享地址空间,可以在调用clone()时,设置CLONE_VM标志。我们把这样的进程称作线程。当CLONE_VM被指定后,内核就不再需要调用allocate_mm()函数了,而仅仅需要在调用copy_mm()函数中将mm域指向其父进程的内存描述符就可以了。

销毁内存描述符

当进程退出时,内核会调用exit_mm函数,该函数执行一些常规的销毁工作,同时更新一些统计量。其中,该函数会调用mmput()函数减少内存描述符的mm_users用户计数,如果mm_users降到0,继续调用mmdrop()函数,减少mm_count,如果mm_count也等于0了,说明该内存描述符不再有任何使用者了,那么调用free_mm宏通过kmem_cache_free()将mm_struct结构体归还到mm_cachep_slab缓存中。

mm_struct与内核线程

内核线程没有进程地址空间,也没有相关的内存描述符,所以内核线程对应的进程描述符中mm域为空。

当一个进程被调度时,该进程的mm域指向的地址空间被装载到内存,进程描述符中的active_mm域会被更新,指向新的地址空间。内核线程没有地址空间,所以mm域为NULL,于是当一个内核线程被调用时,就会保留前一个进程的地址空间,随后内核更新内核线程对应的进程描述符中的active_mm域,使其指向前一个进程的内存描述符。

2 内存区域

内存区域由vm_area_struct结构体描述,定义在文件linux/mm.h中,内存区域在内核中也经常被称作虚拟内存区域或VMA。
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_end;      /* The first byte after our end addresswithin vm_mm. *//* linked list of VM areas per task, sorted by address */struct vm_area_struct *vm_next;pgprot_t vm_page_prot;       /* Access permissions of this VMA. */unsigned long vm_flags;        /* Flags, listed below. */struct rb_node vm_rb;/** For areas with an address space and backing store,* linkage into the address_space->i_mmap prio tree, or* linkage to the list of like vmas hanging off its node, or* linkage of vma in the address_space->i_mmap_nonlinear list.*/union {struct {struct list_head list;void *parent;   /* aligns with prio_tree_node parent */struct vm_area_struct *head;} vm_set;struct prio_tree_node prio_tree_node;} shared;/** A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma* list, after a COW of one of the file pages.  A MAP_SHARED vma* can only be in the i_mmap tree.  An anonymous MAP_PRIVATE, stack* or brk vma (with NULL file) can only be in an anon_vma list.*/struct list_head anon_vma_node; /* Serialized by anon_vma->lock */struct anon_vma *anon_vma; /* Serialized by page_table_lock *//* Function pointers to deal with this struct. */struct vm_operations_struct * vm_ops;/* Information about our backing store: */unsigned long vm_pgoff;      /* Offset (within vm_file) in PAGE_SIZEunits, *not* PAGE_CACHE_SIZE */struct file * vm_file;        /* File we map to (can be NULL). */void * vm_private_data;      /* was vm_pte (shared mem) */#ifdef CONFIG_NUMAstruct mempolicy *vm_policy; /* NUMA policy for the VMA */
#endif
};

每个内存描述符都对应于进程地址空间的唯一区间,vm_start域指向区间的首地址,vm_end域指向区间的尾地址之后的第一个字节,vm_end~vm_start的大小便是内存区间的长度,内存区域的位置就在[vm_start,vm_end]之中,注意,在同一个地址空间内的不同内存区间不能重叠。

vm_mm域指向和VMA相关的mm_struct结构体,注意每个VMA对其相关的mm_struct来说都是唯一的,所以即使两个独立的进程将同一个文件映射到各自的地址空间,它们分别都会有一个vm_area_struct结构体标志自己的内存区域,但是如果两个线程共享一个地址空间,那么它们也同时共享其中所有的vm_area_struct结构体。

VMA标志

VMA标志是一种位标志,其定义在linux/mm.h中,它包含在vm_flags域内,标志了内存区域所包含的页面的行为和信息,和物理页的访问权限不同,VMA标志反映了内核处理页面所需要遵守的行为准则,而不是硬件要求。

VMA操作

vm_area_struct结构体中的vm_ops指向与指定内存区域相关的操作函数表,内核使用表中的方法操作VMA。vm_area_struct作为通用对象代表了任何类型的内存区域,而操作表描述针对特定的对象实例的特定方法。
操作函数表由vm_operations_struct结构体表示,定义在文件linux/mm.h中

/** These are the virtual MM functions - opening of an area, closing and* unmapping it (needed to keep files on disk up-to-date etc), pointer* to the functions called when a no-page or a wp-page exception occurs. */
struct vm_operations_struct {void (*open)(struct vm_area_struct * area);void (*close)(struct vm_area_struct * area);struct page * (*nopage)(struct vm_area_struct * area, unsigned long address, int *type);int (*populate)(struct vm_area_struct * area, unsigned long address, unsigned long len, pgprot_t prot, unsigned long pgoff, int nonblock);
#ifdef CONFIG_NUMAint (*set_policy)(struct vm_area_struct *vma, struct mempolicy *new);struct mempolicy *(*get_policy)(struct vm_area_struct *vma,unsigned long addr);
#endif
};

内存区域的树形结构和内存区域的链表结构

上面说过,可以通过内存描述符中的mmap和mm_rb域之一访问内存区域,这两个域各自独立地指向与内存描述符相关的全部内存区域对象vm_area_struct。

mmap使用单独链表连接所有的内存区域对象vm_area_struct,每一个vm_area_struct结构体通过自身的vm_next域被连入链表,所有的区域按地址增长的方向排序,mmap域指向链表中第一个内存区域,链中最后一个VMA结构体指针指向空。

mm_rb域使用红-黑树连接所有内存区域对象,mm_rb域指向红-黑树的根结点,地址空间中每一个vm_area_struct结构体通过自身的vm_rb域连接到树中。

链表用于需要遍历全部结点的时候,而红-黑树适用于在地址空间中定位特定内存区域的时候。内核为了内存区域上的各种不同操作都能获得高性能,所以同时使用了这两种数据结构。

3 操作内存区域

内核定义了许多内存区域操作函数,它们都声明在文件linux/mm.h中

find_vma()

find_vma()函数定义在mm/mmap.c中。

/* 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);

该函数在指定的地址空间中搜索第一个vm_end大于addr的内存区域。换句话说,该函数寻找第一个包含addr或首地址大于addr的内存区域,如果没有发现这样的区域,该函数返回NULL。否则返回指向匹配的内存区域的vm_area_struct结构体指针,返回的结构会被缓存在内存描述符的mmap_cache域中,所以find_vma会先在缓存中查找,如果指定的地址不在缓存中,那么必须搜搜和内存描述符相关的所有内存区域,这种搜索通过红-黑树进行。

find_vma_prev()

find_vma_prev()函数和find_vma()工作方式相同,但是它返回第一个小于addr的VMA。该函数定义和声明分别在文件mm/mmap.c中和文件linux/mm.h中

extern struct vm_area_struct * find_vma_prev(struct mm_struct * mm, unsigned long addr,struct vm_area_struct **pprev)

pprev参数存放指向先于addr的VMA指针。

find_vma_intersection()

find_vma_intersection()返回第一个和指定地址区间相交的VMA。因为该函数和内联函数,所以定义在文件linux/mm.h中:

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 = find_vma(mm,start_addr);if (vma && end_addr <= vma->vm_start)vma = NULL;return vma;
}

第一个参数是要搜索的地址空间,start_addr是区间的开始首位置,end_addr是区间的尾位置,

4 mmap()和do_mmap():创建地址空间

内核使用do_mmap()函数创建一个新的线性地址区间。如果创建的地址区间和一个已经存在的地址区间相邻,并且它们具有相同的访问权限的话,那么两个区间将合并为一个。do_mmap()函数会将一个地址区间加入到进程的地址空间中。

do_mmap()函数定义在linux/mm.h中

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

该函数映射由file指定的文件,具体映射的是文件中从偏移量offset处开始,长度为len字节的范围内的数据。如果file参数是NULL并且offset参数也是0,那么就代码这次映射没有和文件相关,该情况被称作匿名映射,如果指定了文件名和偏移量,那么该映射被称为文件映射。

addr是可选参数,它指定搜索空闲区域的起始位置。
prot参数指定内存区域中页面的访问权限。访问权限标志定义在文件asm/mman.h中。
flag参数指定了VMA标志,这些标志也定义在文件asm/mman.h中

mmap() 系统调用

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

5 munmap()和do_munmap():删除地址空间

do_munmap()函数从特定的进程地址空间中删除指定地址区间,该函数定义在文件linux/mm.h中:

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

第一个参数指定要删除区域所在的地址空间,删除从地址start开始,长度为len字节的地址区间,如果成功,返回0.

munmap()系统调用

系统调用munmap()给用户空间程序提供了一种从自身地址空间删除指定区间的方法。

int munmap(void *start ,size_t length)

该系统调用定义在mm/mmap.c中,它是对do_munmap的一个简单封装

6 页表

虽然应用程序操作的对象是映射到物理内存之上的虚拟内存,但是处理器直接操作的却是物理内存,所以当应用程序访问一个虚拟地址时,首先必须将虚拟地址转化为物理地址,然后处理器才能解析地址访问请求。地址的转换工作是通过查询页表完成的。
页表对应的结构体依赖具体的体系结构,所以定义在文件asm/page.h中

Linux内核设计与实现---进程地址空间相关推荐

  1. linux内核的进程管理,Linux内核设计与实现——进程管理

    主要内容 进程 进程描述符及任务结构 进程创建 线程在linux中的实现 进程终结 1. 进程 进程不仅仅是一段可执行程序代码,还包含其他资源,如打开的文件,挂起的信号,内核内部数据,处理器状态,一个 ...

  2. linux内核设计与实现---进程管理

    进程管理 1 进程描述符及任务结构 分配进程描述符 进程描述符的存放 进程状态 设置当前进程状态 进程上下文 进程家族树 2 进程创建 写时拷贝 fork() vfork() 3 线程在Linux中的 ...

  3. Linux内核如何私闯进程地址空间并修改进程内存

    进程地址空间的隔离 是现代操作系统的一个显著特征.这也是区别于 "古代"操作系统 的显著特征. 进程地址空间隔离意味着进程P1无法以随意的方式访问进程P2的内存,除非这块内存被声明 ...

  4. 《Linux内核设计与实现》读书笔记(十五)- 进程地址空间(kernel 2.6.32.60)

    进程地址空间也就是每个进程所使用的内存,内核对进程地址空间的管理,也就是对用户态程序的内存管理. 主要内容: 地址空间(mm_struct) 虚拟内存区域(VMA) 地址空间和页表 1. 地址空间(m ...

  5. linux内核对孤儿进程寻父,读薄「Linux 内核设计与实现」(2) - 进程管理和调度

    这篇文章是<读薄「Linux 内核设计与实现」>系列文章的第 II 篇,本文主要讲了以下问题:进程管理的任务.进程管理与其他模块的依赖关系.进程描述符和任务队列.进程的创建.线程的实现.进 ...

  6. 《Linux内核设计与实现》之进程

    文章目录 1 进程 1.1 两个虚拟化 1.2 任务队列 1.3 task_struct 1.4 进程家族树 1.5 进程创建 1.5.1 fork() 函数 1.6 进程终结 1.6.1 do_ex ...

  7. 《Linux内核设计与实现》读书笔记 - 目录 (完结)

    读完这本书回过头才发现, 第一篇笔记居然是 2012年8月发的, 将近一年半的时间才看完这本书(汗!!!). 为了方便以后查看, 做个<Linux内核设计与实现>读书笔记 的目录: < ...

  8. 读《Linux内核设计与实现》我想到了这些书

          从题目中可以看到,这篇文章是以我读<Linux内核设计与实现>而想到的其他我读过的书,所以,这篇文章的主要支撑点是<Linux内核>.       开始读这本书已经 ...

  9. 读 Linux内核设计与实现 我想到了这些书

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴!     ...

最新文章

  1. Codeforces Round #742 (Div. 2) E. Non-Decreasing Dilemma (线段树维护区间连续问题)
  2. [老贴]《asp.net高级编程》读书笔记(2)
  3. [教程]MongoDB 从入门到进阶 (概要 以及 高级索引篇 TimeToLive GeoNear)
  4. Access数据库审计工具mdbtools
  5. 几种任务调度的 Java 实现方法与比较--转载
  6. 【不会吧不会吧,不会有人真的三分钟入门Python了吧?】Python编程基础
  7. sublime配置c++环境
  8. wordpressQQ登陆php代码_WordPress实现前台登录功能
  9. mysql pdo insert_PDO数据库操作类——插入数据的实现
  10. k8s 安装_二进制安装k8s集群总结
  11. 最全“Java面试宝典+Java核心知识集”
  12. Navicat12.0 激活
  13. matlab前馈仿真,前馈-反馈控制系统的具体分析及其MATLAB/Simulink.PDF
  14. 愚人节里的巧合与必然:BAT等亮出的AI招牌故事
  15. springmvc(表现层/Web层框架)
  16. Windows如何强制关闭电脑全部代理
  17. 设计性思考维模型及步骤(上)
  18. 如何将程序打包成exe
  19. elemntui icon 大小_vue-elementui 引入第三方iconfont图标 并使用彩色icon
  20. List的contains方法老是返回false的解决法

热门文章

  1. css实现左(右)侧固定宽度,右(左)侧宽度自适应 ---清除浮动
  2. 点击文本框后页面变大
  3. margin赋值为负值的几种效果(负值像素,负值百分数)
  4. Mysql远程登录及常用命令
  5. Xcode 升级后,常常遇到的遇到的警告、错误,解决方法(转)
  6. bzoj1588 [HNOI2002]营业额统计
  7. Function类型(JS高程3)—— JS学习笔记2015-6-29(第70天)
  8. 我的2015年读书计划,每两周读完一本书!
  9. 工作经常使用的SQL整理,实战篇(一)
  10. paip.最新的c++ qt5.1.1环境搭建跟hello world