进程地址空间
15.1 地址空间
15.2 内存描述符
内核用内存描述符,表示进程的地址空间,描述了与进程地址有关的所有信息
mm_struct结构体,定义在<linux/sched.h>中
struct mm_struct{
struct vm_area_struct *mmap; /*内存区域链表*/
struct rb_root mm_rb; /*VMA形成的红黑树*/
...
pgd_t *pgd;
struct list_head mmlist; /*所有mm_struct形成的链表*/
...
atomic_t mm_users; /*使用地址空间的用户数*/
atomic_t mm_count; /*主使用计数器*/
}
mm_users:如果两个线程共享地址空间,则等于2, mm_count为增加量,则为1
9个线程共享,mm_count仍为1
只有mm_users减为0,mm_count才为0,结构体撤销
mmap和mm_rb描述的对象相同,地址空间中所有的内存区域
前者以链表形式,后者以红黑树
冗余的意义:链表适合便利,mm_rb适合搜索
mm_struct:通过 mmlist域连接在双向链表,首元素是init_mm内存描述符,代表init进程的地址空间
操作时使用mmlist_lock,定义在kernel/fork.c
15.2.1 分配内存描述符
进程描述符task_struct中,mm域存放进程使用的内存描述符,current->mm
fork()函数利用copy_mm复制父进程的内存描述符
mm_struct实际由fork.c中的allocate_mm()宏,从mm_cachep slab缓存中分配得到‘
通常,每个进程有唯一的mm_struct,即唯一进程地址空间
如果父进程希望和子进程共享地址空间,调用clone()时设置CLONE_VM标志,这样的进程为线程
CLONE_VM仅需要在调用copy_mm()函数将mm域指向父进程的mm_struct
if(clone_flags & CLONE_VM){
/*
* current是父进程而tsk在fork()执行期间是子进程
*/
atomic_inc(¤t->mm->mm_users);
tsk->mm = current->mm;
}
15.2.2 撤销内存描述符
进程退出时,内核调用定义在kernel/exit.c中的exit_mm()函数
更新统计量,调用mmput()减少mm_users计数
调用mmdrop()减少mm_count计数
调用free_mm()宏,归还到mm_cachep slab缓存
15.2.3 mm_struct域内核线程
内核线程没用进程地址空间,没有内存描述符,所以进程描述符中mm为空
内核线程不需要访问任何用户空间的内存
不需要自己的内存描述符和页表
为了避免切换地址空间,内核线程直接使用前一个进程的内存描述符
当进程调度时,mm域指向的地址空间被装载到内存,进程描述符中的active_mm域被更新
内核mm域为空,当内核线程被调度时,会保留前一个进程的地址空间,内核更新进程描述符中的active_mm为前一个进程的mm
需要时,内核线程可使用前一个进程的页表,仅使用地址空间中和内核相关的信息(存在可以访问用户空间信息的可能)
15.3 虚拟内存区域
内存区域由vm_area_struct结构体描述,定义在<linux/mm_types.h>
虚拟内存区域 virtual memeoryAreas, VMAs
vm_area_struct描述了连续区间的独立内存范围
每个VMA可代表不同的内存区域:内存映射文件,进程用户空间栈
struct vm_area_struct{
struct mm_struct *vm_mm;
unsigned long vm_start;
unsigned long vm_end;
struct vm_area_struct *vm_next;
pgprog_t vm_page_prot;
unsigned long vm_flags;
...
struct vm_operations_struct &vm_ops;
...
}
每个VMA对其相关的mm_struct都是唯一的
15.3.1 VMA标志
<linux/mm.h>
包含在vm_flags域内
反映了内核处理页面需要遵守的行为准则,而不是硬件要求
标志 | 对VMA以及页面的影响 |
---|---|
VM_READ,VM_WRITE,VM_EXEC | |
VM_SHARED | 页面可共享 |
VM_MAYREAD,… | READ,WRITE,EXEC可设置 |
VM_GROWSDOWN | 可向下增长 |
VM_SHM | 可做共享内存 |
VM_IO | 页面映射设备IO空间 |
VM_SEQ_READ | 可能被连续访问 |
VM_DONTCOPY | 不能在fork时被拷贝 |
VM_RESERVED | 区域不能被换出 |
页面的权限还要和访问权限配合
VM_IO常在驱动设备执行的mmap()函数中设置,同时表示内存区域不能被包含在任何进程的core dump中
VM_SEQ_READ,VM_RAND_READ决定内核是否有必要预读取文件
15.3.2 VMA操作
struct vm_operations_struct{
//内存区域被加入地址空间
void(*open)(struct vm_area_struct *);
//内存区域被删除
void(*close)(struct vm_area_struct *);
//没有出现在物理内存的页面
int(*fault)(struct vm_area_struct *, struct vm_fault *);
//只读被写
int(*page_mkwrite)(struct vm_area_struct *, struct vm_fault *);
//get_user_pages()调用失败,access_process_vm()调用
int(*access)(struct vm_area_struct *,unsigned long, void*, int, int);
}
15.3.3 内存区域的树形和链表结构
15.3.4 实际使用中的内存区域
cat /proc/1426/maps可查看内存区域
不可写的内存区域可以只保留一份映射,所有进程的libc库在物理内存中实际只需要一份
这样的方式能节约大量的内存
没有映射文件的内存区域设备标志为00:00,索引节点标志也为0,这就是零页——零页映射的内容全0
15.4 操作内存区域
15.4.1 find_vma()
内存地址属于哪个区域
先检查mmap_cache,再搜索红黑树
15.4.2 find_vma_prev()
返回第一个小于addr的VMA
15.4.3 find_vma_intersection()
返回第一个和指定区间相交的VMA
15.5 mmap()和do_mmap():创建地址区间
do_mmap:创建一个新的线性地址区间
unsigned long dommap(struct file*file, unsigned long addr, unsigned long len, unsigned long prot, unsigned long flag, unsigned long offset)
file取NULL,offset=0,这次映射没有文件相关:匿名映射,否则文件映射
addr:可选
用户空间通过mmap()系统调用获取do_mmap()功能
15.6 mummap()和do_mummap()
删除地址空间
15.7 页表
内核正确设置页表的前提下,硬件才能操作他们
每个进程字节的页表,内存描述符的pgd域指向页全局目录,然后三级页表
页表对应的结构体依赖于具体的体系结构,asm/page.h
页表的管理时内核的关键部分,不断改进,2.6:
- 高端内存分配页表
- 写时拷贝共享页表,(不改就不分配新的)