- 论坛徽章:
- 0
|
内核需要管理进程地址空间,即系统中每个用户空间进程所看到的内存。
通过虚拟内存技术,进程可以拥有远大于物理内存的地址空间。
每个进程都拥有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,先检查是否有缓存,否则再通过页表索引物理地址)
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/85048/showart_1904511.html |
|