这里开始分析hello中的一个寻址过程的实现。当然现在的情景是:(当然可能只是一小部分加载到了内存中,大部分的需要使用缺页异常处理来实现内存分配)。
在sys_exece()函数中,在内存ram中保存了命令行参数,环境参数,但是代码段,数据段,bss段,可执行文件的其他段提供”映射“(映射的具体含义参见"深入理解Hello World 3"),此时文件到虚存的映射仅仅是建立了一种映射关系,也就是说,虚存页面到物理页面之间的映射还没有建立。
在说明内存寻址 之前,先来看看进程是如何管理虚拟地址,然后开始说明在没有缺页的情况下,保护模式下的寻址,最后开始hello的情景分析,当hello程序开始寻址内存的某个位置时,到底发生了什么(还是从hello刚被加载到内存中)?
1.进程的地址空间及管理
该部分参见的是《深入理解linux内核。
内核中的函数通过相当直接了当的方式来得到动态内存。内核对于内存的分配时采用不同的方式,对于内核而言,总是立即分配内存,但是对于用户进程而言,内核总是在“不能再推迟的情况下”才分配内存。linux下的进程总是维护自己的虚拟地址空间(linux为每个用户进程 分配4DB虚拟地址空间),而虚拟内存到物理内存的实际转换的话,是采用的"页表“的机制来实现的。下面说明相关的数据结构:
在内核源码include/linux/sched.h中定义了stask_struct结构,用来标记每个进程:
struct task_struct {
...
struct mm_struct *mm, *active_mm;
...
}
进程的task_struct中的mm指向mm_struct用来维护进程管理的虚拟内存,哪些虚拟内存地址使用,而那些虚拟内存地址还没有使用,mm_struct定义如下:
struct mm_struct {
unsigned long start_code, end_code, end_data;
...
struct rb_toor mm_root;
struct vm_area_struct * mmap;
struct vm_area_struct * mmap_avl;
..
}
所有的mm_struct都是存放在一个链表中,链表的第一个元素是init_mm元素,init_mm是初始化进程0使用的内存描述符。定义:
#define INIT_MM { \
0, \
0, 0, 0, \ 0, 0, 0, 0, \ 0, 0, 0, 0, \ 0, \ */ 0, 0, 0, 0, \ 0, \ */ 0, 0, 0, 0, \ &init_mmap, &init_mmap
}
vm_area_struct定义如下:
/* * This struct defines a memory VMM memory area. There is one of these * per VM-area/task. A VM area is any part of the process virtual memory * space that has a special rule for the page-fault handlers (ie a shared * library, the executable area etc). */ struct vm_area_struct { struct mm_struct * vm_mm; /* VM area parameters */ unsigned long vm_start; unsigned long vm_end; /* linked list of VM areas per task, sorted by address */pgprot_t vm_page_prot; unsigned short vm_flags; /* AVL tree of VM areas per task, sorted by address */ ... /* For areas with inode, the list inode->i_mmap{,_shared}, for shm areas, * the list of attaches, otherwise unused. */ ... struct vm_operations_struct * vm_ops; unsigned long vm_offset; struct file * vm_file; unsigned long vm_pte; /* shared mem */ };
上面的vm_area_struct 中需要特别强调的是vm_file,vm_ops,vm_mm,vm_mm指向进程的mm字段, vm_file执行虚拟地址映射的文件的
file对象,vm_ops字段指向vm_operations_struct结构,该结构中 存放的是作用域线性区的方法,常见的有:open,close,nopage,
populate。
总结上面可得:
process --> task_struct --> mm(mm_struct) --> vm_area_struct list来管理进程虚拟地址空间
了解了上面的数据结构之后,开始看进程的虚拟地址空间是如何创建的?如何删除的?在这里hello的情景中,进程是由sys_execve()系统调用来
创建的,在sys_execve函数中,首先是分配一个页框(物理地址)填充进程的命令行参数,环境变量,将这个页框分配各这个进程(设置页表),
然后开始映射可执行文件的.text段,这里”映射text段“所做的工作只是分配虚拟地址,将可执行文件的一部分和它对应上,在实际的物理内存中是
不存在该“段”的内容,如果需要访问的话 ,产生"缺页异常",调用异常处理函数将可执行文件对应的部分读入到内存中,并设置相关的页表(这一步
其实才建立了虚拟地址和物理地址的映射,也就是说映射其实分为两个部分,虚拟地址 <--> 文件 虚拟地址 <-->物理地址,具体的映射部分参见
第四部分)
删除进程的地址空间使用的是exit_mm。具体的参见“深入理解Hello World程序卸载”部分。
2.保护模式下寻址
计算机中引入了”虚拟内存“的东西,好处是很大,但是还是给程序的理解带还很多的麻烦啊。我学习时主要疑问是:
1.哪些是虚拟地址?哪些是实际的物理地址?
如果开启了分页机制的话,在应用程序中使用的所有地址都是虚拟地址,将虚拟地址转换成物理地址的工作是有硬件来完成,同时使用软件来初始化
一些table,显然必须存在一些”东西“来记录实际的物理地址,这个工作是寄存器中写入物理地址的值。
2.linux为每个活动的进程分配i、一个页目录项。
至于具体的转换过程,在很多操作系统课本中讲的有。
3.hello情景分析
依旧使用上面的假设:hello程序刚刚被加载到内存中,下面需要寻址一个text段内容,由于在sys_execve中做的仅是映射,那么虚拟地址在转换
成物理地址时,所访问的页表项是空,产生缺页异常,调用缺页异常处理函数,读入该块美容,然后读取页表,cpu的地指线上就是该物理地址的值。然后得到该物理地址中的值,最终开始执行这条指令
下一步开始解决的是“映射”的实际含义?这其中设计到了文件系统的相关东西,好像雪球越来越大了,继续。。。
哎,顺便说上一句,内核庞大的代码,查找一个函数或者是数据结构的定义是在是很难,推荐使用的是google code search来完成相关搜索。