当前课程知识点:Linux 内核分析与应用 > 第4章 内存管理 > 4.6 动手实践-Linux内存映射实现(中) > Video
我们现在来进入源码 看看内存映射具体的实现过程
首先我这里使用的Linux内核版本是4.15.0
大家可以使用其他的版本 所以我这里提到的函数
它的名称或者用法呢可能会有所差异
让我们来看一下我们的驱动程序的源码
我们的驱动程序大致由这么三部分组成
一模块的装载卸载函数
二file_operations结构体和mmap函数
三 vm_operations_struct和fault函数
下面我给大家来做一一的介绍
首先是模块的装载/卸载函数
这里模块的装载函数 它所要完成
的工作有两个 一是设备注册 二是在内核中为设备申请一块内存
设备的注册由register_chrdev这个函数来实现
在这里我们需要指定
设备的主设备号
设备的名称还有它所链接的file_operations结构
这里呢如果我们为主设备号零的话
该函数将自己分配一个主设备号
返回给我们的result
如果返回的值为零则表示成功
返回值为负就表示注册设备失败
接下来呢是我们的申请内存
申请内存 我们这里使用的是
vmalloc函数该函数的特点是申请的内存区域
它的内核线性地址是连续的 但是物理地址
是不连续的
这里我们看到
我们还需要对申请到的物理页框的pagereserved标志位置位
这样做是告诉系统 该物理页框已经被我们使用了
下面是模块的卸载函数 在模块的卸载函数中 我们要做的正相反
首先是清理pagereserved标志位
接着释放我们申请的
vmalloc线性区的线性地址
最后注销掉我们的这个设备
接下要给大家介绍的是file_operations结构体
和mmap函数
这里是我们在驱动模块中定义的file_operations结构体
在开始分析它之前呢我希望大家了解的是
内核源码中有关file_operations结构体的完整定义
一个设备的读取 写入
保存等等这些操作,都是由存储在file_operations结构
中的这些函数指针来处理的
这些函数指针所指向的函数都需要我们在驱动模块将其实现
在这里我们可以看到file_operations结构体中提到了许多的
函数指针包括read write mmap open等等等等
但是呢我们可以不全使用它们
对于那些指向未实现函数的指针可以简单的设置为空
操作系统将负责实现该功能
好了我们现在回到源码中
我们的驱动模块中只实现了三个函数
owner是用来指向该驱动module结构的指针
open函数在这里 我用它来打印了调用该模块进程的pid
最后就mmap函数 我们来看一下它的代码
在开始分析这个函数之前呢我还要补充的是
mmap系统调用的执行过程
在这里一个用户进程
它调用课mmap系统调用后
它需要依次执行这么多的函数
它们都是用来做什么的呢
我们来看一下 首先呢一个用户进程在调用mmap
系统调用后 系统会为其在当前进程的虚拟地址空间中
寻找一段连续的空闲地址
这是通过遍历vm_area_struct链表实现的
当找到这样一个合适的地址区间后呢 为其建立一个vm_area_struct结构
完成这些之后,该进程就有了一个专门用于mmap映射的虚拟内存区了
但是这样还不够 因为在进程页表中呢
这个区域中的线性地址都没有对应的物理页框
接着系统会调用内核空间的系统调用函数mmap
也就是需要我们在file operatios结构中定义的这个mmap
它将要完成对vm_area_struct结构中的虚拟地址
为它们建立其相应的页表项
而建立页表项的方法呢
有两种:一种叫做remap_pfn_range的方法
一种呢叫做fault的方法
两者的区别是前者
它是在内核函数mmap
也就是我们在file_operations中要完成的mmap函数
它在被调用时一次性的为vm_area_struct结构中的
这些线性地址建立页表项的
所以这也就要求了这些页表项所要映射的
物理地址是连续的
后者 fault函数呢则不同
它是在进程访问到这个映射空间中
的虚拟地址时 发现该虚拟地址的页表项为空
引起缺页时 才被调用的
所以呢它更适合于我们今天
我们对vmalloc分配的这些不连续的物理地址
来进行映射 所以我们今天使用的是fault方法
了解了这些之后 我们再回到驱动程序中
我们看一下代码 前面这些判断 都是一些对异常情况的判断
首先 MAPLEN是我们在上面定义的一个宏
它是用来表示在内核中申请的物理页框的大小的
如果进程中用来映射的线性地址区
大于我们申请的物理地址
那么就可能访问到一些
不该访问的地方
所以我们需要防止这种情况的发生
接着这个判断呢 它是如果
我们的vm区呢
它是可写入的 它的标志位是可写入的
这个vm_flags呢它是用来保存
进程对该虚存空间的访问权限的
如果我们当前的vma是可写入的话 那么它也就
必须有另一个标志位VM_SHARED 也就是可共享的
如果没有可共享的标志位的话 那么写入操作就应当是非法的
我们也要把这种情况判断出来
在这里呢 我们可以看到我们给vm_flags标志位增加了一个
标志位叫做VM_LOCKONFAULT
它是用来锁住该区所映射的这些物理页框的
让它不要被操作系统交换出去
看了这些之后其实我们整个mmap函数中最重要的一其实是这里
它将我们在驱动模块中定义的
vm_operations_struct结构的地址呢赋给了
当前vma的vm_ops指针
也就是说它用我们在驱动模块中
定义的vm_operations结构体 替换掉了
当前进程vma中的这样一个
结构体 为什么它要这么做呢
想想我们说到的fault方法
我们的Fault方法在建立页表的时候呢 它是在
进程访问到线性地址产生缺页时才建立页表的
所以呢我们在mmap函数被调用的时候
可能我们的用户进程还没有进行访问 所以呢我们建立页表
还不用现在就立即建立
所以将这个任务交给它的下一环节
也就是vm_operations结构体
最后要给大家介绍的是vmoperation_struct结构体和fault函数
这是我们在驱动模块中定义的vm_operations_struct结构体
同样的在开始之前我希望大家了解
结构体在内核源码中的完整定义
这里是4.15内核中vm_operations_struct结构体中的定义
我们可以看到与file_operations结构体非常类似
它其中也定义了许多的函数指针
我们可以有选择的使用他们
我们今天主要实现fault函数
关于fault函数的功能前面已经给大家解释过了
它是用来为进程中那些用来映射的线性地址建立相应的页表项的
这里我们可以看到他传入的参数是一个指针
在这里,我希望大家对比一下不同的内核版本中有关fault函数的定义
在3.6内核中呢我们可以看到
fault函数它需要的参数有两个它多了一个指向vma
结构体的这样一个指针
而在更早的2.6.15的内核中是没有fault函数的
在这里它使用的是一个叫做nopage的函数指针
所以大家在查阅资料时可能找不到fault函数
那是因为他们使用的源码比较早了
所以我们这里可以去查阅一下nopage函数看看它的一些解释
其实这两个函数它实际上功能是非常类似的
我们回到4.15的内核中
我们看到fault函数它需要的参数是一个指向
vm_fault结构体的一个指针
这里我们就想知道这个vm_fault它又是什么呢
我们需要在内核源码中找到它的定义
这里是4.15内核中有关于vm_fault结构体的定义
我们可以看到vm_fault它就是用来存放
缺页有关的这些参数的
这里呢我们可以看到首先呢
映射区的这个vma指针
address它就是产生缺页的线性地址
pmd pud就是这个线性地址所对应的页
目录项pte就是它所对应的页表项了
这个结构体中
最重要的就是page字段配置字段
我们知道我们在fault函数中我们要为这个
用了映射的这个
线性地址建立页表项 那么我们首先要找到它所对应的物理地址
找到这个物理地址后我们取到它的页描述符
将其填写到vm_fault结构体的page字段中
剩下的建立页表项的工作就可以交由操作系统自动完成了
了解了这些后呢回到驱动的代码中
们可以看到fault函数在驱动中是这样一个名字 我们去找到它
我们这个驱动程序的目的呢就是
想要将内核空间的线性地址所对应的物理地址
映射到用户空间的某一个线性地址中
所以我们首先需要找到这些
物理页框 那么我们怎么找呢 用到的方法就是
这样两个函数vmalloc_to_pfn和vmalloc_to_page
顾名思义这两个函数可以将
内核vmalloc区线性地址所指向的那个物理页框
它找到 我们想要找到的是页描述符 那么我们就用vmalloc_to_page函数
我们想要找到页帧号 那么我们就用vmalloc_to_pfn函数
好了我们从头开始看fault函数 首先这个offset呢它是
我们产生缺页的线性地址它在这
它在这个vma映射区中的偏移量
我们用当前的缺页的地址减去
vma的起始地址得到的它offset
接下来这个virt_strat它是
内核中的线性地址
我们用的是vmalloc_area 也就是我们在
模块装载函数中申请到的那个
线性区的起始地址让它加上每一页的偏移 就得到了
内核中每一页的线性地址
这个pfn_start就是这些对应的
内核中的线性地址所对应的那些物理
页框的页帧号这里我们使用的是vmlloc_to_pfn函数来找到它们
下面呢就是一些对异常状况的判断 比如我们
当前进程的vma为空
我们申请到的内核中内存区域内核中的
内存区域为空 那么肯定是一种异常状况我们将它判断出来
接着这个判断是 如果我们当前产生缺页的线性地址
它超过了我们在内核中用于映射的物理页框的大小
那么肯定是一种异常状况 我们将它判断出来
这里呢 这个page_ptr呢
我们看一下 它是内核中线性地址
其实跟virt_start的值是一样的
这里我们使用vmalloc_area也就是它的起始地址
加上offset每一页的偏移量得到的
这里,page,因为我们要取的是页描述符
所以我们就要用vmalloc_to_page函数
内核中的线性地址
找到它所对应的物理页框的页描述符
接着我们需要将每一页 我们找到这个每一页
理页框增加它的引用次数 用get_page这样一个函数来实现
最后我们将找到的页描述符填写到
vmf的page字段中 就完成了我们对一个线性地址
一个产生缺页的线性地址
它的页表项的建立了 后面呢 最后呢我们实际上这些
内核中的线性地址 所对应的物理地址
还有映射后的这个
进程中的线性地址等等这些字段都打印出来
好了到这里就是有关于驱动模块的主要内容了 在结束之前呢
再跟大家看一下前面有关一些宏的定义
在这里 我们在内核空间中申请了
10个页面大小的内存空间
我们的主设备号是240 设备名称是mapnopage
这里在我们的设备注册函数中
我们不光申请了
10个页面的内存 并且呢在这里
在每一页的内存中都写入了一个
test字符串
从第一页开始依次是test1、test2、test3一直到test10
好了 接下来让我们看一下
有关于用户测试进程还有测试进程的演示
-1.1 Linux操作系统概述
-1.2 Linux内核结构以及内核模块编程
--Video
-1.3 Linux内核源码中的双链表结构
--Video
-1.4 源码分析-内核中的哈希表
--Video
-1.5 动手实践-Linux内核模块的插入和删除
--Video
-第1章 概述--章节测验
-2.1 内存管理之内存寻址
--Video
-2.2 段机制
--Video
-2.3分页机制
--Video
-2.4 动手实践-把虚拟地址转换成物理地址
--Video
-第2章 内存寻址--章节测验
-3.1 进程概述
--Video
-3.2 Linux进程创建
--Video
-3.3 Linux进程调度
--Video
-3.4 动手实践-打印进程描述符task_struct中的字段
--Video
-3.5工程实践-基于内核模块的负载监控
--Video
-第3章 进程管理--章节测验
-4.1 Linux内存管理机制
--Video
-4.2 进程用户空间管理机制
--Video
-4.3 物理内存分配与回收机制(上)
--Video
-4.4 物理内存分配与回收机制(下)
--Video
-4.5 动手实践-Linux内存映射基础(上)
--Video
-4.6 动手实践-Linux内存映射实现(中)
--Video
-4.7 动手实践-Linux内存映射测试(下)
--Video
-4.8 初学者对内存管理的常见疑惑
-第4章 内存管理--章节测验
-5.1 中断机制概述
--Video
-5.2 中断处理机制
--Video
-5.3 中断下半部处理机制
--Video
-5.4 时钟中断机制
--Video
-5.5 动手实践-中断上半部的代码分析及应用
--Video
-5.6 动手实践-中断下半部的代码分析及应用
--Video
-第5章 中断--章节测验
-6.1 Linux中的各种API
--Video
-6.2 系统调用机制
--Video
-6.3 动手实践-添加系统调用(系统调用日志收集系统)
--Video
-第6章 系统调用--章节测验
-7.1 内核同步概述
--Video
-7.2 内核同步机制
--Video
-7.3 动手实践-内核多任务并发实例(上)
--Video
-7.4 动手实践-内核多任务并发实例(下)
--Video
-第7章 内核同步--章节测验
-8.1 虚拟文件系统的引入
--Video
-8.2 虚拟文件系统的主要数据结构
--Video
-8.3 文件系统中的各种缓存
--Video
-8.4 页高速缓存机制以及读写
--Video
-8.5 动手实践-编写一个文件系统(上)
--Video
-8.6 动手实践-编写一个文件系统(中)
--Video
-8.7 动手实践-编写一个文件系统(下)
--Video
-第8章 文件系统--章节测验
-9.1 设备驱动概述
--Video
-9.2 I/O空间管理
--Video
-9.3 设备驱动模型
--Video
-9.4 字符设备驱动程序简介
--Video
-9.5 块设备驱动程序简介
--Video
-9.6 动手实践-编写字符设备驱动程序
--Video
-9.7工程实践-编写块设备驱动的基础(上)
--Video
-9.8 工程实践-块设备驱动程序分析(中)
--Video
-9.9 工程实践-块设备驱动程序实现(下)
--Video
-第9章 设备驱动--章节测验
-致谢与说明
--Video