测试程序
int fd;
struct stat st;
void *mem;
void processMem(void)
{
char ch = *((char*)mem);
printf("%c\n", ch);
}
int main(void)
{
fd = open("./test", O_RDONLY);
fstat(fd, &st);
mem = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
processMem();
}
进入MMAP
- exp调用mmap
最终变成syscall(9)
sys_mmap()
- 进入entry_SYSCALL_64切换内核保存用户栈, 然后通过sys_call_table调用到sys_mmap()函数
- 进行一些简单检查后进入sys_mmap_pgoff()
sys_mmap_pgoff()
- 就干了两件事: 根据fd找到文件对象, 然后调用vm_mmap_pgoff
vm_mmap_pgoff()
- vm_mmap_pgoff()
- 获得mmap_sem这个信号量
- 调用do_mmap_pgoff()
- 如果需要的话调用mm_populate()
do_mmap_pgoff()
- 这是个do_mmap()的包装函数
do_mmap()
- 进行一些准备工作:
- 首先获取当前进程的内存描述符
- 然后处理一下基地址和内存权限
- 再调用get_unmapped_area()从当前进程的虚拟地址空间中找到一片空闲区域
- 这里找到的空闲地址是0x00007ffff7ffa000
- 接着处理标志, 并进行一些简单的检查
- 如果要映射到文件, 那么根据映射类型进行一些文件相关的检查
- file->f_op对于文件对象的方法集合, 这里其mmap方法对应shmem_mmap函数
- 接着调用mmap_region()进行真正的映射工作, 并处理populate标志
mmap_region()
- mmap_region()首先进行三个小检查:
- 这个进程的虚拟地址空间还够不够映射
- 如果这片区域已经有映射了, 那么就取消掉
- 如果是私有可写入映射, 检查下内存还够不够进行写时映射
- 接着会申请一个VMA对象用于描述映射的虚拟内存区域
- 可以直接扩展一个已有的VMA, 比如权限相同地址相邻, 那么就不用分配一个新的VMA对象了
- 否则从内核中分配一个00初始化的VMA对象并初始化
- 接着对新的VMA对象进行一些文件相关初始化操作, 最重要的是两步
- 获取一个文件对象, 增加引用计数: get_file()
- 调用文件对象的f_op->mmap()方法设置VMA
- 对于./test文件, 会调用shmem_file_operations.mmap, 也就是shmem_mmap()函数, 这个函数做两件事:
- touch一下文件, 表示访问过
- 设置这片虚拟内存的标准操作为shmem_vm_ops
- vma->vm_ops是这片VMA上的各种操作集合, 对应struct vm_operations_struct结构体
- shmem_vm_ops则是文件对象实现的, 当VMA发生某些事件时需要调用的函数
- 其中最重要的就是缺页异常处理函数shmem_fault()
- 缺页异常会在后面说到, 限制只要知道如果访问这片新建的VMA发生缺页异常, 就会调用vma->vm_ops->fault(), 也就是调用shmem_fault()函数
- 然后把新建的VMA对象插入到mm内部从而完成VMA的创建工作
- 最后进入out部分, 进行一些收尾工作并设置这片虚拟内存中每一页的属性(vm_page_prot)后结束映射
退出系统调用
- 首先所有函数栈一路回退到entry_SYSCALL_64, 这部分就是正常的C回退, 只是在内核地址空间发生而已
- 回退到entry_SYSCALL_64后, 先把SyS_mmap()的返回值写入到内核栈上保存的pt_regs->rax中
pt_regs用来保存陷入内核时的CPU状态, 离开内核态时就用pt_regs恢复用户态的执行环境, 从而继续执行 - 把pt_regs->rax设置为返回值, 就可以在恢复用户的执行环境时设置rax寄存器为返回值, 从而符合C的函数调用约定
- 由于退出系统调用的指令sysret会使用%rcx设置%rip, %r11设置EFLAGS, 因此要用内核栈中pt_regs->RIP设置%rcx, pt_regs->EFLAGS设置%r11
- 然后用pt_regs恢复其余通用寄存器
- 然后用pt_regs->rsp设置%rsp, 从而恢复用户态的栈
- 最后swapgs指令从MSR寄存器中换出用户态的gs, 并保存内核态的gs, 最后调用sysret恢复到用户态的执行
为什么没有读文件?
- 到这里我们会发现一个问题, mmap只是根据参数在进程的mm中插入了一个VMA对象, 并没有真正的把文件中的信息读入, 这是为什么?
- 这其实是请求调页与写时复制的结果, 虽然映射到了文件,但不代表全部都会用到, 当你真正读写刚刚映射的内存区域时, MMU会发出一个缺页异常给CPU, CPU调用内核的缺页异常处理函数, 这时候再真正的分配物理内存或者把文件的内容读到物理内存中, 实现按需分配
- 后续留到下一个文章中细说
发表评论
您还未登录,请先登录。
登录