linux内核之进程地址空间
1、地址空间
进程的地址空间由进程可寻址的虚拟内存组成。每个进程都有自己独立的地址空间,两个进程之间没有关系,互不影响。这些虚拟内存区域包含各种对象,常见的有如下:
代码段:可执行文件代码的内存映射
数据段:可执行文件的已初始化全局变量的内存映射
Bss段:包含未初始化的全局变量,也称bss段零页的内存映射
用户进程用户空间栈的零页内存映射,栈大小编译时指定
每个共享库的代码段、数据段和bss段也会被载入进程的地址空间
任何内存映射文件
任何共享内存段
任何匿名的内存映射,比如malloc
进程地址空间中任何有效地址都只能位于唯一的区域,这些内存区域不能相互覆盖。
2、内存描述符
(1)数据结构
内核使用内存描述符结构体标识进程的地址空间,该数据结构包含了和进行地址空间相关的全部信息。
struct task_struct {
…
struct mm_struct *mm,*active_mm; //进程任务描述符指向内存描述符,mm为进程地址空间所有信息,active_mm则为当前执行的地址空间信息
…
}
struct mm_struct {
struct vm_area_struct * mmap; //内存区域链表
struct rb_root mm_rb; //内存区域形成的红黑树
struct vm_area_struct * mmap_cache; //最近使用的内存区域
#ifdef CONFIG_MMU
unsigned long (*get_unmapped_area) (struct file *filp,
unsigned long addr,unsigned long len,
unsigned long pgoff, unsignedlong flags);
void (*unmap_area) (struct mm_struct *mm, unsigned long addr);
#endif
unsigned long mmap_base; /*base of mmap area */
unsigned long task_size; /*size of task vm space */
unsigned long cached_hole_size; /* if non-zero, the largest hole below free_area_cache */
unsigned long free_area_cache; /* first hole of size cached_hole_size or larger */
unsigned long highest_vm_end; /* highest vma end address */
pgd_t * pgd;
atomic_t mm_users; /* Howmany users with user space? */
atomic_t mm_count; /* Howmany references to "struct mm_struct" (users count as 1) */
int map_count; /*number of VMAs */
spinlock_t page_table_lock; /*Protects page tables and some counters */
struct rw_semaphore mmap_sem;
struct list_headmmlist; /* List of maybe swappedmm's. These are globally strung
* together off init_mm.mmlist, andare protected
* by mmlist_lock
*/
unsigned long hiwater_rss; /*High-watermark of RSS usage */
unsigned long hiwater_vm; /*High-water virtual memory usage */
unsigned long total_vm; /*Total pages mapped */
unsigned long locked_vm; /*Pages that have PG_mlocked set */
unsigned long pinned_vm; /*Refcount permanently increased */
unsigned long shared_vm; /*Shared pages (files) */
unsigned long exec_vm; /*VM_EXEC & ~VM_WRITE */
unsigned long stack_vm; /*VM_GROWSUP/DOWN */
unsigned long def_flags;
unsigned long nr_ptes; /*Page table pages */
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 saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */
/*
* Special counters, in some configurations protected by the
* page_table_lock, in other configurations by being atomic.
*/
struct mm_rss_stat rss_stat;
structlinux_binfmt *binfmt;
cpumask_var_t cpu_vm_mask_var;
/* Architecture-specific MM context */
mm_context_t context;
unsigned long flags; /* Must use atomic bitops to access the bits */
structcore_state *core_state; /* coredumping support */
…
}
mmap和mm_rb指向的是相同数据对象(vm_area_stuct虚拟内存区域),只是组织方式不一样。mmap以链表形式存放,利于简单、高效的遍历所有元素;mm_rb以红黑树的形式存放,更适合搜索指定元素。
pgd为页全局目录,实现虚拟地址和物理内存的映射,在switch_mm时将pgd首地址放入CPU寄存器供mmu(硬件实现)使用。TLB则是pgd的一个子集,存放已经使用过的映射,加速地址映射查找。
mm_users和mm_count:mm_users记录正在使用该内存的进程数目;mm_count域是mm_struct结构体的主引用计数。区别:mm_users一个进程创建多个线程时会增加,mm_count当内核线程共享进程地址空间时增加,初始化是1。
所有的mm_struct结构体都通过自身的mmlist域连接在一个双向链表中,该链表的首元素是init_mm内存描述符,它代表进程0的地址空间。
见后面关联图说明。
(2)分配与撤销
A.分配
在进程创建一节提到过,do_fork的copy_process()完成大部分工作。copy_process()中的copy_mm()实现复制父进程的内存描述符,源码解析如下:
static intcopy_mm(unsigned long clone_flags, struct task_struct *tsk)
{
struct mm_struct *mm, *oldmm;
int retval;
tsk->min_flt = tsk->maj_flt = 0;
tsk->nvcsw = tsk->nivcsw = 0;
/* 把子进程初始化成NULL */
tsk->mm = NULL;
tsk->active_mm = NULL;
/* 由于内核线程没有独立的地址空间mm=NULL,所以当前current(即父进程)的mm为NULL,则表示父进程是内核线程,子进程上面已经初始化为NULL,这里直接返回 */
oldmm = current->mm;
if (!oldmm)
return 0;
/* 如果设置了CLONE_VM共享地址空间标识,则子进程直接指向父进程的内存描述符,使用计数+1,一般用户创建线程使用 */
if (clone_flags & CLONE_VM) {
atomic_inc(&oldmm->mm_users);
mm = oldmm;
goto good_mm;
}
retval = -ENOMEM;
/* 其他情况则是通过allocate_mm()申请自己的mm_struct,拷贝父进程的内容,再进行自己独立的初始化(包括pgd页表申请等)
allocate_mm()是kmem_cache_alloc的宏,从mm_cashep slab缓存中分配得到 */
mm = dup_mm(tsk);
if (!mm)
goto fail_nomem;
good_mm:
tsk->mm = mm;
tsk->active_mm = mm;
return 0;
fail_nomem:
return retval;
}
B.撤销
当进程退出时,内核会调用exit_mm()函数,该函数执行一些常规的撤销工作,同时更新一些统计量。其中,该函数会调用mmput减少mm_users计数,如果到0,调用mmdrop函数减少mm_count计数,如果到0,那么调用free_mm()宏通过kmem_cache_free()函数将mm_struct结构体归还到mm_cachep slab缓存中。
(3)内核线程mm_struct
上面分配一节已经看到,内核线程对应的进程描述符中mm域为空,这是因为内核线程在用户空间中没有任何页,所以不需要有自己的内存描述符和页表,所有内核线程共享内核地址空间。
尽管mm域为空,即使访问内核内存,内核线程也还是需要使用一些数据的。为了避免内核线程为内存描述符和页表浪费内存,避免向新地址空间进程切换浪费处理器周期,内核线程将直接使用前一个进程的内存描述符,进程切换源码如下:
static inline void context_switch(struct rq*rq, struct task_struct *prev,
struct task_struct *next)
{
struct mm_struct *mm, *oldmm;
…
mm = next->mm; //下一个将执行进程的mm
oldmm = prev->active_mm; //当前执行进程的active_mm
/*mm=NULL则为内核线程,内核线程的active_mm指向当前执行进程的active_mm,同时mm_count+1
mm != NULL则为用户进程,通过switch_mm实现内存描述符的切换,主要完成将pgd首地址放入CPU寄存器供mmu(硬件实现)使用
*/
if (!mm) {
next->active_mm = oldmm;
atomic_inc(&oldmm->mm_count);
enter_lazy_tlb(oldmm, next);
}else
switch_mm(oldmm, mm, next);
/*如果当前的进程为内核线程,则将active_mm=NULL,下次运行再重新赋值*/
if (!prev->mm) {
prev->active_mm = NULL;
rq->prev_mm = oldmm;
}
…
}
3、虚拟内存区域
(1)数据结构
虚拟内存区域(VMA)由vm_area_struct结构体描述,指定地址空间内连续区间上的一个独立内存范围。每一个VMA作为一个单独的内存对象管理,具有一致的属性(比如访问权限等),因此,一个VMA就代表了一种类型的内存区域(如内存映射文件、进程用户空间栈等)。结构体如下:
struct vm_area_struct {
unsignedlong vm_start; //内存区间的收地址
unsigned longvm_end; //内存区间的尾地址
//VMA双向链表,按照地址顺序排序
struct vm_area_struct *vm_next, *vm_prev;
struct rb_nodevm_rb; //放在红黑树上的节点
unsigned longrb_subtree_gap;
struct mm_struct *vm_mm; //指向内存描述符
pgprot_t vm_page_prot; //访问权限
unsigned long vm_flags; //标志
union {
struct {
struct rb_node rb;
unsigned long rb_subtree_last;
} linear;
struct list_head nonlinear;
const char __user *anon_name;
} shared;
struct list_head anon_vma_chain; //匿名VMA对象链表
struct anon_vma *anon_vma; //匿名VMA对象
const structvm_operations_struct *vm_ops; //相关的操作表
unsigned long vm_pgoff; //文件中的偏移量
struct file * vm_file; //被映射的文件
void * vm_private_data; //私有数据
#ifndefCONFIG_MMU
struct vm_region *vm_region; /* NOMMU mapping region */
#endif
};
在同一个地址空间内的不同内存区间不能重叠。
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);
当没有出现在物理内存中的页面被访问时,该函数被页面故障处理调用
int (*page_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf);
当某个页面为只读页面时,该函数被页面故障处理调用
int (*access)(struct vm_area_struct *vma, unsigned long addr,
void *buf, int len, int write);
当get_user_pages()函数调用失败时,该函数被access_process_vm()函数调用
int(*remap_pages)(struct vm_area_struct *vma, unsigned long addr,
unsigned long size, pgoff_tpgoff);
};
(2)相关操作
内核时常需要在某个内存区域上执行一些操作,比如某个指定地址是否包含在某个内存区域中。这类操作非常频繁,也是mmap例程的基础。为了方便执行这类对内存区域的操作,内核定义了许多的辅助函数。
A. find_vma
structvm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr)
为了找到一个给定的内存地址属于哪一个内存区域。该函数在指定的地址空间搜索第一个vm_end大于addr的内存区域。如果没有发现这样的区域,返回NULL,否则指向匹配的内存区域vm_area_struct结构体指针。返回的结果被缓存在内存描述符mmap_cache域中,有相当好的命中率,提高速度。该函数通过红黑树查找。
B.find_vma_prev
和find_vma类似,但是它返回第一个小于addr的VMA。
C.find_vma_intersection
返回第一个和指定地址区间相交的VMA,也是调用find_vma
D.mmap
创建一个新的线性地址区间。如果新创建的VMA和已经存在的地址区间相邻,并且相同的访问权限,两个区域将合并为一个;如果不能合并就算是一个新的VMA。创建之后,会将该地址空间加入到进程的地址空间中。
Mmap系统调用对应的内核函数do_mmap_pgoff:
unsigned long do_mmap_pgoff(struct file*file, unsigned long addr,
unsigned long len, unsigned long prot,
unsigned long flags, unsigned long pgoff,
unsigned long *populate)
file为NULL且pgoff为0,代表没有和文件相关,称为匿名映射
如果指定了文件名和偏移量,称为文件映射
E.munmap
从特定的进程地址空间中删除指定地址空间,munmap系统调用对应的内核函数do_munmap:
int do_munmap(struct mm_struct *mm,unsigned long start, size_t len)
4、缺页异常处理
Linux缺页异常处理程序必须区分两种情况:
由编程错误引起的异常
由引用属于进程地址空间但还尚未分配物理页框的页所引起的异常。
内核由static int __kprobes do_page_fault(unsigned long addr, unsigned intfsr, struct pt_regs *regs)实现。
总体方案如下图:
5、关联图
进程、线程之间内存描述符关系
进程A和B是独立进程,进程B由A创建,T1是进程A的一个线程。上图展示说明如下:
A.进程A fork进程B,进程B会复制父进程A的task_struct和mm_struct,进行相应的初始化,有各自的页表目录。
B.进程A pthread_create 线程T1,复制父进程A的task_struct,但是共享mm_struct,使用同一个页表目录
C.进程A和进程B的虚拟地址通过mm_struct的pgd页表映射到实际物理内存,对于可读页(如C库等),两个进程是共享实际内存,对于可写页(如堆栈、变量等)则有各自的物理内存
D.进程A和线程T1共享mm_struct,因此也共享pgd页表,当线程需要自己的可写页时,就会加入到进程A的共享页表里。
进程的虚拟内存管理
VMA:虚拟内存区域
mm_rb:为红黑树,图中用链表示意,表示mmap和mm_rb都指向虚拟内存区域
每个进程都有一个task_struct,所有进程通过链表连接起来,链表头为init_task。每个task_struct又都有一个mm_struct结构,所有的mm_struct通过mmlist连接在一个双向链表中,该链表的首元素是init_mm。每个进程的各个虚拟内存区域通过mmap和mm_rb组织起来,然后通过pgd页表,映射到实际的物理内存。
linux内核之进程地址空间相关推荐
- 【Linux 内核】进程管理 ( 进程与操作系统 | 进程与程序 | 进程与线程 | 虚拟地址空间 )
文章目录 一.进程与操作系统 二.进程与程序 三.进程与线程 四.虚拟地址空间 一.进程与操作系统 操作系统与硬件的关系 : 操作系统 使用 硬件 提供的资源 , 如 CPU , 内存 , 磁盘 , ...
- 【Linux 内核】进程管理 ( 内核线程概念 | 内核线程、普通进程、用户线程 | 内核线程与普通进程区别 | 内核线程主要用途 | 内核线程创建函数 kernel_thread 源码 )
文章目录 一.内核线程概念 二.内核线程.普通进程.用户线程 三.内核线程.普通进程区别 四.内核线程主要用途 五.内核线程创建函数 kernel_thread 源码 一.内核线程概念 直接 由 Li ...
- 【Linux 内核】进程管理 ( 进程特殊形式 | 内核线程 | 用户线程 | C 标准库与 Linux 内核中进程相关概念 | Linux 查看进程命令及输出字段解析 )
文章目录 一.进程特殊形式 ( 内核线程 | 用户线程 ) 二.C 标准库与 Linux 内核中进程相关概念 三.Linux 查看进程命令及输出字段解析 一.进程特殊形式 ( 内核线程 | 用户线程 ...
- 【Linux 内核】进程管理 ( 进程状态 | 进程创建 | 进程终止 | 调用 exit 系统调用函数主动退出 | main 函数返回自动退出 | kill 杀死进程 | 执行异常退出 )
文章目录 一.进程状态 二.进程创建 三.进程终止 ( 调用 exit 系统调用函数主动退出 | main 函数返回自动退出 | kill 杀死进程 | 执行异常退出 ) 一.进程状态 Linux 进 ...
- 【Linux 内核】进程管理 task_struct 结构体 ⑤ ( files 字段 | nsproxy 字段 | 信号处理相关字段 | 信号量和共享内存相关字段 )
文章目录 一.task_struct 结构体字段分析 1.files 字段 2.nsproxy 字段 3.信号处理相关字段 4.信号量和共享内存相关字段 在 Linux 内核 中 , " 进 ...
- 【Linux 内核】进程管理 task_struct 结构体 ④ ( comm 字段 | 进程优先级字段 | cpus_ptr 字段 | mm、active_mm 字段 | fs 字段 )
文章目录 一.task_struct 结构体字段分析 1.comm 字段 2.进程优先级字段 3.cpus_ptr 字段 4.mm.active_mm 字段 5. fs 字段 在 Linux 内核 中 ...
- 【Linux 内核】进程管理 task_struct 结构体 ③ ( real_parent 字段 | parent 字段 | group_leader 字段 | real_cred、cred字段 )
文章目录 一.task_struct 结构体字段分析 1.real_parent 字段 2.parent 字段 3.group_leader 字段 4.real_cred 字段 5.cred 字段 在 ...
- 【Linux 内核】进程管理 task_struct 结构体 ② ( state 字段 | stack 字段 | pid 字段 | tgid 字段 | pid_links 字段 )
文章目录 一.task_struct 结构体字段分析 1.state 字段 2.stack 字段 3.pid字段 4.tgid 字段 5.pid_links 字段 在 Linux 内核 中 , &qu ...
- 【Linux 内核】进程管理 task_struct 结构体 ① ( task_struct 结构体引入 | task_struct 代码示例 )
文章目录 一.task_struct 结构体 二.task_struct 结构体代码示例 一.task_struct 结构体 在 Linux 操作系统 中 , 进程 作为 调度的实体 , 需要将其抽象 ...
- 【Linux 内核】进程管理 ( Linux 内核中的进程状态 | TASK_RUNNING | TASK_INTERRUPTIBLE | __TASK_STOPPED | EXIT_ZOMBIE )
文章目录 一.Linux 内核中的进程状态 二.TASK_RUNNING 状态 三.TASK_RUNNING 状态 四.TASK_UNINTERRUPTIBLE 状态 五.__TASK_STOPPED ...
最新文章
- 【微信小程序】:小程序,新场景
- 恩智浦AI视觉组之逐飞岁末彩蛋
- python【力扣LeetCode算法题库】面试题 01.06-字符串压缩
- python编程设计大学ppt_Python程序设计-清华大学出版社-董付国第5章-函数的设计和使用PPT...
- 让你了解什么是内存屏障
- Yii 框架里数据库操作详解-[增加、查询、更新、删除的方法](转)
- HierarchicalBeanFactory接口
- 启动spark shell
- python 装饰器
- 编辑距离Edit distance
- linux docker 安装sql,CentOS7使用Docker安装SQL Server 2017
- 协作多智能体强化学习中的回报函数设计
- Android 加载天地图
- mac删除默认ABC输入法,mac删除自带ABC输入法
- 深度学习在音乐信息检索(MIR)方向的应用介绍(上)
- 3Dtouch开发内容
- 最全解读】各种金融机构的产品分析(银行、证券、基金、信托...)
- 0528班宋ww:回顾刚来的那一天还历历在目,不禁感概一番
- IT外包:中国跃跃欲试,准备一鸣惊人——2012国际外包峰会所见所感(下)
- 汽车估损师跟二手车评估师的区别及鉴定方法