拓展:
关于这个过程,涉及一些术语
(1)设备文件:linux中对硬件虚拟成设备文件,对普通文件的各种操作均适用于设备文件
(2)索引节点:linux使用索引节点来记录文件信息(如文件长度、创建修改时间),它存储在磁盘中,读入内存后就是一个inode结构体,文件系统维护了一个索引节点的数组,每个元素都和文件或者目录一一对应。
(3)主设备号:如上面的999,表示设备的类型,比如该设备是lcd还是usb等
(4)次设备号:如上面的0,表示该类设备上的不同设备
(5)文件(普通文件或设备文件)的三个结构
①文件操作:struct file_operations
②文件对象:struct file
③文件索引节点:struct inode
关于驱动程序中内存映射的实现,先了解一下open和close的流程
(1)设备驱动open流程
①应用程序调用open("/dev/mmap_driver", O_RDWR);
②Open就会通过VFS找到该设备的索引节点(inode),mknod的时候会根据设备号把驱动程序的file_operations结构填充到索引节点中(关于mknod /dev/mmap_driver c 999 0,这条指令创建了设备文件,在安装驱动(insmod)的时候,会运行驱动程序的初始化程序(module_init),在初始化程序中,会注册它的主设备号到系统中(cdev_add),如果mknod时的主设备号999在系统中不存在,即和注册的主设备号不同,则上面的指令会执行失败,就创建不了设备文件)
③然后根据设备文件的索引节点中的file_operations中的open指针,就调用驱动的open方法了。
④生成一个文件对象files_struct结构,系统维护一个files_struct的链表,表示系统中所有打开的文件
⑤返回文件描述符fd,把fd加入到进程的文件描述符表中
(2)设备驱动close流程
应用程序调用close(fd),最终可调用驱动的close,为什么根据一个简单的int型fd就可以找到驱动的close函数?这就和上面说的三个结构(struct file_operations、struct file、struct inode)息息相关了,假如fd = 3
(3)设备驱动mmap流程
由open和close得知,同理,应用程序调用mmap最终也会调用到驱动程序中mmap方法
①应用程序test.mmap.c中mmap函数
void* mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr:映射后虚拟地址的起始地址,通常为NULL,内核自动分配
length:映射区的大小
prot:页面访问权限(PROT_READ、PROT_WRITE、PROT_EXEC、PROT_NONE)
flags:参考网络资料
fd:文件描述符
offset:文件映射开始偏移量
②驱动程序的mmap_driver.c中mmap函数
上面说了,mmap的主要工作是把设备地址映射到进程虚拟地址,也即是一个vm_area_struct的结构体,这里说的映射,是一个很悬的东西,那它在程序中的表现是什么呢?——页表,没错,就是页表,映射就是要建立页表。进程地址空间就可以通过页表(软件)和MMU(硬件)映射到设备地址上了
virt_to_phys(buf),buf是在open时申请的地址,这里使用virt_to_phys把buf转换成物理地址,是模拟了一个硬件设备,即把虚拟设备映射到虚拟地址,在实际中可以直接使用物理地址。
总结
①从以上看到,内核各个模块错综复杂、相互交叉
②单纯一个小小驱动模块,就涉及了进程管理(进程地址空间)、内存管理(页表与页帧映射)、虚拟文件系统(structfile、structinode)
③并不是所有设备驱动都可以使用mmap来映射,比如像串口和其他面向流的设备,并且必须按照页大小进行映射。