未加星标

Linux内核剖析之进程地址空间(一)

字体大小 | |
[系统(linux) 所属分类 系统(linux) | 发布者 店小二04 | 时间 20140804 | 作者 路人甲 ] 0人收藏点击收藏
绪论

内核获取内存方式——直接了当:

1. 从分区页框分配器获取内存(__get_free_pages()或alloc_pages());

2. 使用slab分配器为专用或通用对象分配内存(kmem_cache_alloc()或kmalloc());

3. 使用vmalloc或vmalloc_32获取一块非连续内存区。

如果申请的内存得以满足,这些函数返回一个页描述符地址或线性地址。

*内核申请内存使用这些简单方法基于以下两个原因:

1.内核是操作系统中优先级最高的成分。如果内核申请动态内存,那么必定有正当理由。因此,没有理由推迟处理这个请求。

2.内核信任自己。所有内核函数都是假定没有错误的,因此内核函数不必插入针对编程错误的任何保护措施。

*当给用户态进程分配内存时,情况完全不同:

1.进程对动态内存的请求被认为是不紧迫的。内核总是尽量推迟给用户态进程分配动态内存。

2.用户进程是不可信任的,因此,内核必须能随时准备捕捉用户进程引起的所有寻址错误。

主要内容:

*内核如何实现对进程动态内存的推迟分配?

——使用一种新的资源(线性地址区)

当用户进程请求动态内存时,并没有获得请求的页框,而是获得了一个新的线性地址的使用权。这线性地址区域就成为进程地址空间的一部分。

*为什么要推迟分配?

*进程怎样看待动态内存。

*进程地址空间的基本组成。

*缺页异常处理程序在推迟给进程分配页框中所起的作用。

*内核怎样创建和删除进程的整个地址空间?

*与进程的地址空间管理有关的API和系统调用。

====>>>>>

1. 进程地址空间

2. 内存描述符

3. 线性区

4. 缺页异常处理程序

5. 创建和删除进程的地址空间

6. 堆的管理

进程地址空间

进程的地址空间(Address Space)由允许进程使用的全部线性地址组成。一个进程使用的进程地址空间与另一个进程使用的进程地址空间之间没有关系。如果进程之间共享相同的地址空间,则被称为线程。

内核可以通过增加或删除某些线性地址区间来动态修改进程的地址空间。

内核通过所谓线性区的资源来表示线性地址空间,线性地址空间由起始线性地址、长度以及相应访问权限来描述。

起始地址和长度都是4096(一页)的整数倍——提高效率。

*进程创建新的线性区的典型情况(六种):

程序执行

exec()函数

缺页异常处理程序

内存映射

IPC共享内存

malloc()函数—heap

*与创建、删除线性区相关的系统调用:


系统调用



说明



brk()



改变进程堆的大小



execve()



装入可执行文件,从而改变进程的地址空间



_exit()



结束当前进程并撤销它的进程地址空间



fork()



创建一个新的进程,并创建新的地址空间。



mmap(),mmap2()



为文件创建一个内存映射,从而扩大进程的地址空间



mremap()



扩大或缩小线性区



remap_file_pages()



为文件创建非线性映射



munmap()



撤销对文件的内存映射,从而缩小进程的地址空间



shmat()



创建一个共享线性区



shmdt()



撤销一个共享线性区


确定进程所拥有的线性地址区是内核的任务,这使得缺页处理程序能够有效的处理以下两种无效的线性地址:

1. 由编程错误引发的非法地址访问(如数组越界、非法指针);

2. 由缺页(物理页)引发的无效线性地址,即使这个线性地址属于进程地址空间,但是内核还未分配物理页。——请求调页。

内存描述符

与进程地址空间的全部信息都包含在一个叫做内存描述符(Memory Descriptor)的数据结构中,这个结构的类型是mm_struct,进程描述符的mm字段指向此结构。

无论是内核线程还是用户进程,对于内核来说,都是task_struct这个数据结构的一个实例,task_struct被称为进程描述符(process descriptor),因为它记录了这个进程所有的上下文(context)。其中有一个被称为“内存描述符”(memory descriptor)的数据结构 mm_struct,该结构抽象并描述了linux视角下管理进程地址空间的所有信息。

Linux内核剖析之进程地址空间(一)
[start_code,end_code)表示代码段的地址空间范围。
[start_data,end_data)表示数据段的地址空间范围。
[start_brk,brk)分别表示heap段的起始空间和当前的heap指针。
[start_stack,end_stack)表示stack段的地址空间范围。

mmap_base表示memory mapping段的起始地址。

具体结构图:

Linux内核剖析之进程地址空间(一)

内存描述符:

mm_strcut定义:

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;
unsigned long hiwater_rss; /* High-water RSS usage */
unsigned long hiwater_vm; /* High-water virtual memory usage */
};
mm_struct字段:

类型



字段



说明



struct vm_area_struct*



mmap



指向线性区域对象的链表头



struct rb_root



mm_rb



指向线性区对象的红黑树的根



struct vm_area_struct*



mmap_cache



指向最后一个引用的线性区对象



unsigned long (*) ()



get_unmapped_area



在进程地址空间中搜索有效线性地址区



void (*) ()



unmap_area



释放线性地址区间时调用的方法



unsigned long



free_area_cache



内核从这个地址开始搜索进程地址空间中线性地址的空闲区域



pgd_t *



pdg



指向页全局目录



atomic_t



mm_users



次使用计数器



atomic_t



mm_count



主使用计数器



int



map_count



线性区的个数



struct rw_semaphore



mmap_sem



线性区的读/写信号量



spinlock_t



page_table_lock



线性区的自旋所和页表的自旋锁



struct list_head



mmlist



指向内存描述符链表中的相邻元素



unsigned long



start_code



可执行代码的起始地址



unsigned long



end_code



可执行代码的最后地址



unsigned long



start_data



已初始化数据的起始地址



unsigned long



end_data



已初始化数据的最后地址



unsigned long



start_brk



堆的起始地址



unsigned long



brk



堆的当前最后地址



unsigned long



start_stack



用户堆栈的起始地址



unsigned long



arg_start



命令行参数的起始地址



unsigned long



arg_end



命令行参数的最后地址



unsigned long



env_start



环境变量的起始地址



unsigned long



env_end



环境变量的最后地址



unsigned long



rss



分配给进程的页框数



unsigned long



anon_rss



非配给匿名内存映射的页框数



unsigned long



total_vm



进程地址空间的大小(页数)



unsigned long



locked_vm



锁住而不能换出的页的个数



unsigned long



shared_vm



共享文件内存映射中的页数



unsigned long



exec_vm



可执行内存映射中的页数



unsigned long



stack_vm



用户堆栈中的页数



unsigned long



reserved_vm



在保留区中的页数或在特殊线性区中的页数



unsigned long



def_flags



线性区默认的访问标志



unsigned long



nr_ptes



进程的页表数



unsigned long []


saved_auxv



开始执行ELF程序时使用



unsigned int



dumpable



表示是否可以产生内存转储信息的标志



cpumask_t



cpu_vm_mask



用于惰性TLB交换的位掩码



mm_context_t



context



指向有关特定体系结构信息的表



unsigned long



swap_token_time



进程在这个时间将有资格获得交换标志



char



recent_pagein



最近发生了主缺页,则设置该标志



int



core_waiters



正在把进程地址空间的内容转储到core文件中的轻量级进程的数目



struct completion *



core_startup_done



指向创建内存转储文件的补充原语



struct completion



core_done



指向创建内存转储文件的补充原语



rwlock_t



ioctx_list_lock



用于保护异步I/O上下文链表的锁



struct kioctx *



ioctx_list



异步I/O上下文链表



struct kioctx



default_kioctx



默认的异步I/O上下文



unsigned long



hiwater_rss



进程所拥有的最大页框数



unsigned long



hiwater_vm



进程线性区中的最大页数


所有的内存描述符存放在一个双向链表中。每个内存描述符在mmlist字段存放链表相邻元素的地址。链表的第一个元素是init_mm的mmlist字段,init_mm是初始化阶段进程0使用的内存描述符。

注意理解和比较两个字段:mm_users和mm_count字段。

# mm_users字段存放共享mm_struct数据结构的轻量级进程的个数。

# mm_count字段是内存描述符的主使用计数器,在mm_users次使用计数器中的所有用户在mm_count中只作为一个单元。每当mm_count递减时,内核都要检查它是否变为0,如果是,就要解除这个内存描述符,因为不再有用户使用它。

===>为什么要设置mm_count字段?见下文。

内核线程的内存描述符

内核线程就能运行在内核态,因此,它们永远不会访问低于TASK_SIZE(等于PAGE_OFFSET)的地址。与普通进程相反,内核线程不用线性区。因此描述符的很多字段对内核线程是没有意义的。

内核线程使用前一个进程的内存描述符。

在每个进程描述符中包含了两种内存描述符指针:mm和active_mm。

进程描述符中的mm字段指向进程所拥有的内存描述符,而active_mm字段指向进程运行时所使用的内存描述符。对于普通进程,这两个字段存放相同的指针。但是,对于内核线程,由于内核线程不拥有任何内存描述符,因此,它们的mm字段总是NULL。当内核线程运行时,它的active_mm字段被初始化为前一个运行进程的active_mm值。

这里,可以解释上面mm_users字段和mm_count字段的区别:

<<<====>>>

mm_users字段记录共享该内存描述符的普通进程的个数,mm_count的目的在于支持内核线程级别。

对Linux来说,用户进程和内核线程(kernel thread)都是task_struct的实例(tsk),唯一的区别是内核线程是没有进程地址空间的,内核线程也没有mm内存描述符,所以内核线程的tsk->mm域(其中,struct task_struct * tsk)是空(NULL)。当内核scheduler在执行进程上下文切换(context switching)时,会根据tsk->mm判断即将调度的进程是用户进程还是内核线程。虽然内核线程不访问用户进程地址空间,但是仍然需要通过page table来访问内核线程的内核地址空间。幸运的是,对于任何用户进程来说,它们的内核空间都是完全相同的(3G-4G),所以内核可以“借用”上一个被调用的用户进程的内存描述符mm中的页表来访问内核地址,并将此mm记录在active_mm。简而言之,对于用户进程,tsk->mm == tsk->active_mm;而对于内核线程,tsk->mm == NULL表示自己内核线程的身份,而tsk->active_mm是使用上一个用户进程的mm,通过此mm的page table来访问内核空间。

为了支持内核线程级别,mm_struct里面引入了另外一个counter,主使用计数器mm_count。前面有mm_users表示这个进程地址空间被多少普通进程共享或者引用,而mm_count则表示这个地址空间被内核线程引用的次数+1(这里的1,指的是在mm_users次使用计数器中的所有用户在mm_count中只作为一个单元)。内核不会因为mm_users == 0而销毁此mm_struct,内核只会当mm_count == 0时才会释放mm_struct,因为这个时候既没有用户进程使用这个地址空间,也没有内核线程引用这个地址空间。

本文系统(linux)相关术语:linux系统 鸟哥的linux私房菜 linux命令大全 linux操作系统

tags: mm,unsigned,long,struct,地址,内核,进程,内存,线程,vm,描述符,线性,gt,start,count
分页:12
转载请注明
本文标题:Linux内核剖析之进程地址空间(一)
本站链接:http://www.codesec.net/view/39211.html
分享请点击:


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 系统(linux) | 评论(0) | 阅读(636)