编译和运行
驱动编译要用到kernel的Makefile文件 — — 也就是源码树的编译系统。所以,源码须要被配置和编译,以ubuntu自带的源码为例:linux
编译外部模块(.ko)的编译命令是:程序员
make -C <path_to_kernel_src> M=mak**e−C<pathtokernelsrc>M=PWDubuntu
也就是进入到kernel目录,利用kbuild系统来编译驱动文件。obj-m 告诉编译系统须要编译成一个module(.ko),foo.o代表须要源文件是foo.c或者foo.S,若是驱动模块包含多个文件(如: foo_main.c, foo_common.c),写法以下:并发
kbuild将编译$(foo-y)列出的全部文件,合并产生 foo.ko函数
在编译期间,模块的Makefile会被kbuild屡次读取,所以建议使用$(KERNELRELEASE)来区分Makefile的使用阶段,优化后的Makefile以下:oop
第一次运行make的时侯,$(KERNELRELEASE) 为空,所以,Makefile的 'else' 内容首先被读取,而后,执行 *‘make -C .....’*, 执行过程当中,会回读Makefile文件,此次, 'ifneq' 条件知足,两次走不一样的路径,编译系统配置不一样的变量参数。测试
若是,不使用 $(KERNELRELEASE) 区分的话,每次编译系统都会设置全部的变量和规则,可能会与kernel的Makefile变量或者规则冲突,所以,建议在(KERNELRELEASE)为空的状况下,配置driver专用的变量和规则,除了使用(KERNELRELEASE)为空的状况下,配置drive**r专用的变量和规则,除了使用(KERNELRELEASE)外,kernel还提供了一些其它的作法, 更多的kernel 编译系统信息,请参考kernel源码下的 “Documentation/kbuild/”优化
驱动模块运行相关命令ui
- insmod foo.ko —— 加载driver 到kernel去运行。
- rmmod foo —— 从kernel 移除driver.
- lsmod —— 查看当前kernel 运行的模块。
字符设备
字符设备驱动实际上就是实现一个文件接口,让设备文件能够像一个普通文件那样来访问,这样应用程序就能够使用libc库的'文件IO API(open/write/read/close 系列函数)' 来访问驱动程序,与驱动交换数据,所以,它的核心就是实现文件系统的接口 -- 文件操做。url
程序入口
宏内核与微内核的一个最大区别就是驱动程序的运行空间。微内核系统,驱动程序做为一个应用程序,运行在用户空间,它的入口就是应用程序的‘main’函数。 Linux做为一个宏内核系统,它的驱动程序与内核是一体的,运行在内核空间,它的入口是 ‘module_init’,‘module_exit’则是对应的退出函数,它们必定是成对出现的。
foo_init 执行了最基本的字符设备操做:使用 cdev_add 添加一个 'cdev'到字符设备列表(实际上是一个map结构), 这样就把foo这个字符设备托付给kernel进行管理了,当应用程序操做相应的设备文件时,kernel能调度到foo驱动程序。
foo_exit 必定要使用 cdev_del 从列表里面删除设备,否则,当kernel从列表里面查找到 cdev时,返回的将是“过期”的指针,使用它来 callback相应操做时,就会出现空指针异常,致使kernel会挂掉。切记!foo_init 和 foo_exit 必定要成对使用,执行相反的操做。
bug 实例:
- foo_exit 不执行 cdev_del 函数。
- insmod foo.ko -- OK。
- 应用程序对设备文件读写 -- OK。
- rmmod foo -- OK。
- insmod foo.ko -- OK。
- 应用程序对设备文件读写 -- core dump。
‘rmmod foo’时,会调用 foo_exit,可是,程序员忘了执行 cdev_del 函数,致使 foo.cdev 的指针没有被删除而变成了一个空指针,它仍然在字符设备列表里面。 当第二次插入foo.ko后, 读写该设备时,Kernel找的是旧的 foo.cdev 空指针,用它调用相应的文件操做时,就发生了空指针的 core dump 错误。
文件操做
setup_dev: 注册当前的设备的文件操做函数,当应用程序操做设备文件时,调用到对应的驱动函数。与用户空间交换数据,copy_from/to_user,这两个函数返回0表示函数执行成功。
- copy_from_user: 把用户写入的数据copy驱动数据buf保存起来。
- copy_to_user: copy驱动数据buf到 用户读取数据的buf。
应用程序与字符驱动的交互流程
- 建立设备文件 -- sudo mknod /dev/foodev c 500 0
- 修改设备文件权限 -- sudo chmod 766 /dev/foodev
- 应用程序使用open函数打开设备文件。
- kernel根据文件类型(字符设备文件)找到字符设备列表,并根据设备号(Major, Minor),找到对应的设备驱动模块。
- 调用设备驱动的open函数 foo_open 。
- 应用程序调用 read/write函数来读写设备文件。
- 驱动调用 foo_read/write并使用copy_from/to_user来交换数据。
常见问题
Q: 读写设备文件时,write或者 read函数返回0,不能读写数据 ? A: 这类设备文件读写失败问题,颇有多是权限问题,确认下文件读写权限,其次是数据是否符合驱动的要求。
块设备
块设备指的是存储设备,块设备驱动就是存储驱动如:HD,SSD。Linux 用 Block 子系统对它们进行管理,把应用层的IO读写请求,转变为Request ,传给相应的会设备驱动。驱动流程比较简单:
register_blkdev → alloc_disk → 处理request
Q: 文件系统与Block子系统的关系? A: Block子系统主要是提供最底层的数据读写,也就是raw io,文件系统使用它进行IO操做。
注册
注册块设备(主设备号)
注册设备(MAJOR,MINOR)
添加磁盘
这个磁盘会出如今 /dev 目录下面, 本例是 /dev/frd0,用户能够对设备文件进行格式化,分区等磁盘相关的操做。如: ‘mkfs.ext2 /dev/frd0’, ‘mount /dev/frd0 /mnt’。
初始化请求队列
处理设备请求
kernel 提供了一些宏来帮助遍历请求列表。对请求的处理策略,就是Block驱动最核心最精华的部分,开发者得根据设备的物理特性来提升访问效率,解决并发拥堵等问题。 *fr_queue_rq()* -- ‘**请求队列’**处理函数,在初始化请求队列时设置,Loop处理每一个请求:
*fr_transfer()* -- 物理设备读写数据,根据请求的上下文内容(context),进行底层数据传输,这里就是最底层的IO通信了,驱动根据物理设备的接口协议来进行数据的读写。
块设备驱动测试
执行上面命令后,frd_data_r 和 frd_data_w的内容应该是同样的。
以上就是良许教程网为各位朋友分享的Linux 设备驱动开发实例。 以上就是良许教程网为各位朋友分享的Linux相关知识。