深入浅出Hello World 3
这里开始分析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来完成相关搜索。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?