当前课程知识点:Linux 内核分析与应用 >  第4章 内存管理 >  4.6 动手实践-Linux内存映射实现(中) >  Video

返回《Linux 内核分析与应用》慕课在线视频课程列表

Video在线视频

Video

下一节:Video

返回《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

好了 接下来让我们看一下

有关于用户测试进程还有测试进程的演示

Linux 内核分析与应用课程列表:

第1章 概述

-1.1 Linux操作系统概述

--1.1 Linux 操作系统概述

-1.2 Linux内核结构以及内核模块编程

--Video

-1.3 Linux内核源码中的双链表结构

--Video

-1.4 源码分析-内核中的哈希表

--Video

-1.5 动手实践-Linux内核模块的插入和删除

--Video

-第1章 概述--章节测验

-第1章导学--引领你进入Linux内核的大门

第2章 内存寻址

-2.1 内存管理之内存寻址

--Video

-2.2 段机制

--Video

-2.3分页机制

--Video

-2.4 动手实践-把虚拟地址转换成物理地址

--Video

-第2章 内存寻址--章节测验

-第二章导学-从零打造自己的操作系统

第3章 进程管理

-3.1 进程概述

--Video

-3.2 Linux进程创建

--Video

-3.3 Linux进程调度

--Video

-3.4 动手实践-打印进程描述符task_struct中的字段

--Video

-3.5工程实践-基于内核模块的负载监控

--Video

-第3章 进程管理--章节测验

-第三章导学-进程背后琳琅满目的宝贝到哪里挖?

第4章 内存管理

-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章 中断

-5.1 中断机制概述

--Video

-5.2 中断处理机制

--Video

-5.3 中断下半部处理机制

--Video

-5.4 时钟中断机制

--Video

-5.5 动手实践-中断上半部的代码分析及应用

--Video

-5.6 动手实践-中断下半部的代码分析及应用

--Video

-第5章 中断--章节测验

第6章 系统调用

-6.1 Linux中的各种API

--Video

-6.2 系统调用机制

--Video

-6.3 动手实践-添加系统调用(系统调用日志收集系统)

--Video

-第6章 系统调用--章节测验

第7章 内核同步

-7.1 内核同步概述

--Video

-7.2 内核同步机制

--Video

-7.3 动手实践-内核多任务并发实例(上)

--Video

-7.4 动手实践-内核多任务并发实例(下)

--Video

-第7章 内核同步--章节测验

第8章 文件系统

-8.1 虚拟文件系统的引入

--Video

-8.2 虚拟文件系统的主要数据结构

--Video

-8.3 文件系统中的各种缓存

--Video

-8.4 页高速缓存机制以及读写

--Video

-8.5 动手实践-编写一个文件系统(上)

--Video

-8.6 动手实践-编写一个文件系统(中)

--Video

-8.7 动手实践-编写一个文件系统(下)

--Video

-第8章 文件系统--章节测验

第9章 设备驱动

-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

直播视频:从Linux内核学习到自主操作系统研发

-从Linux内核学习到自主操作系统研发

附录:实验代码、课件以及相关素材

-各章实验代码

-《Linux内核分析与应用》课件

-《Linux操作系统原理与应用》教材课堂视频

Video笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。