嵌入式Linux设备驱动程序:编写内核设备驱动程序
嵌入式Linux设备驱动程序:编写内核设备驱动程序
Embedded Linux device drivers: Writing a kernel device driver
编写内核设备驱动程序
最终,当您用尽了之前所有的用户空间选项后,您将发现自己必须编写一个设备驱动程序来访问连接到设备上的硬件。字符驱动程序是最灵活的,应该能满足你90%的需求;网络驱动程序适用于使用网络接口,而块驱动程序用于大容量存储。编写内核驱动程序的任务很复杂,超出了本文的范围。最后有一些参考资料可以帮助你。概述一下与驱动程序交互时可用的选项,这是一个通常不会涉及的主题,并向您展示角色设备驱动程序的基本知识。
设计字符驱动接口
主字符驱动程序接口基于字节流,就像使用串行端口一样。然而,许多设备并不符合这种描述:例如,机器人手臂的控制器需要功能来移动和旋转每个关节。幸运的是,除了读写之外,还有其他与设备驱动程序通信的方法:
ioctl:ioctl函数允许您将两个参数传递给您的驱动程序,这两个参数可以具有您喜欢的任何含义。按照惯例,第一个参数是一个命令,它选择驱动程序中几个函数中的一个,第二个参数是指向结构的指针,它充当输入和输出参数的容器。一个像画布这样的程序可以让你设计任何一个空白的界面。当驱动程序和应用程序紧密链接并由同一个团队编写时,这种情况很常见。但是,ioctl在内核中是不推荐使用的,而且您会发现很难在上游使用ioctl的任何驱动程序得到接受。内核维护者不喜欢ioctl,因为它使内核代码和应用程序代码过于相互依赖,而且很难在内核版本和体系结构中保持两者同步。
sysfs:这是现在的首选方式,前面描述的GPIO接口就是一个很好的例子。它的优点是,只要为文件选择描述性名称,它就有点自文档化。它也是可编写脚本的,因为文件内容通常是文本字符串。另一方面,如果需要一次更改多个值,则每个文件都必须包含一个值,这使得实现原子性变得很困难。相反,ioctl在单个函数调用中传递结构中的所有参数。
mmap:通过将内核内存映射到用户空间,绕过内核,可以直接访问内核缓冲区和硬件寄存器。内核和DMA可能仍然需要处理一些代码中断。uio文档中有更多的uio驱动程序,例如,在文档中有更多的uio。
sigio:您可以使用名为kill_fasync()的内核函数从驱动程序发送信号,以通知应用程序输入准备就绪或接收到中断等事件。按照惯例,使用SIGIO信号,但可以是任何信号。您可以在UIO驱动程序drivers/UIO/UIO.c和RTC驱动程序drivers/char/RTC.c中看到一些示例。主要问题是很难在用户空间中编写可靠的信号处理程序,因此它仍然是一个很少使用的工具。
debugfs:这是另一个伪文件系统,它将内核数据表示为文件和目录,类似于proc和sysfs。主要区别在于debugfs不能包含系统正常运行所需的信息;它只包含调试和跟踪信息。它挂载为mount-t debugfs debug/sys/kernel/debug。内核文档documentation/filesystems中对debugfs有很好的描述/调试文件.txt.
proc:proc文件系统对于所有新代码都是不推荐使用的,除非它与进程相关,这是文件系统最初的目的。但是,您可以使用proc发布您选择的任何信息。而且,与sysfs和debugfs不同,它可用于非GPL模块。
netlink:这是一个socket协议族。AFüNETLINK创建一个将内核空间链接到用户空间的套接字。它最初是为了让网络工具可以与Linux网络代码通信来访问路由表和其他细节。udev也使用它将事件从内核传递到udev,这在一般设备驱动程序中很少使用。
在内核源代码中有许多前面提到的文件系统的例子,您可以为您的驱动程序代码设计真正有趣的接口。唯一的普遍规则是最小惊奇原则。换言之,使用驱动程序的应用程序编写者应该发现,一切都以逻辑方式工作,没有任何怪癖或怪癖。
设备驱动程序的剖析 ‘
现在是时候通过查看一个简单的设备驱动程序的代码来绘制一些线程。下面是一个名为dummy的设备驱动程序,它创建了四个通过
dev/dummy0 to /dev/dummy3 .
驱动程序的完整源代码如下:您将在
MELP/chapter_09/dummy-driver :
#include #include #include #include #include #define DEVICE_NAME "dummy"#define MAJOR_NUM 42#define NUM_DEVICES 4static struct class *dummy_class;static int dummy_open(struct inode *inode, struct file *file){ pr_info("%sn", __func__); return 0;}static int dummy_release(struct inode *inode, struct file *file){ pr_info("%sn", __func__); return 0;}static ssize_t dummy_read(struct file *file, char *buffer, size_t length, loff_t * offset){ pr_info("%s %un", __func__, length); return 0;}static ssize_t dummy_write(struct file *file, const char *buffer, size_t length, loff_t * offset){ pr_info("%s %un", __func__, length); return length;}struct file_operations dummy_fops = { .owner = THIS_MODULE, .open = dummy_open, .release = dummy_release, .read = dummy_read, .write = dummy_write,};int __init dummy_init(void){ int ret; int i; printk("Dummy loadedn"); ret = register_chrdev(MAJOR_NUM, DEVICE_NAME, &dummy_fops); if (ret != 0) return ret; dummy_class = class_create(THIS_MODULE, DEVICE_NAME); for (i = 0; i < NUM_DEVICES; i++) { device_create(dummy_class, NULL, MKDEV(MAJOR_NUM, i), NULL, "dummy%d", i); } return 0;}void __exit dummy_exit(void){ int i; for (i = 0; i < NUM_DEVICES; i++) { device_destroy(dummy_class, MKDEV(MAJOR_NUM, i)); } class_destroy(dummy_class); unregister_chrdev(MAJOR_NUM, DEVICE_NAME); printk("Dummy unloadedn");}module_init(dummy_init);module_exit(dummy_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("Chris Simmonds");MODULE_DESCRIPTION("A dummy driver");
At the end of the code, the macros called module_init and module_exit specify the functions to be called when the module is loaded and unloaded. The three macros named MODULE_* add some basic information about the module, which can be retrieved from the compiled kernel module using the modinfo
When the module is loaded, the dummy_init() function is called. You can see the point at which it becomes a character device when is makes the call to register_chrdev , passing a pointer to struct file_operations , which contains pointers to the four functions that the driver implements. While register_chrdev tells the kernel that there is a driver with a major number of 42, it doesn't say anything about the class of driver, and so it will not create an entry in /sys/class . Without an entry in /sys/class , the device manager cannot create device nodes. So, the next few lines of code create a device class, dummy and four devices of that class called dummy0 to dummy3 . The result is that the /sys/class/dummy directory is created when the driver is initialized, containing subdirectories dummy0 to dummy3 . Each of the subdirectories contains a file, dev , with the major and minor numbers of the device. This is all that a device manager needs to create device nodes: /dev/dummy0 to /dev/dummy3 .
The dummy_exit function has to release the resources claimed by dummy_init , which here means freeing up the device class and major number.
The file operations for this driver are implemented by dummy_open() , dummy_read(), dummy_write(), and dummy_release() and are called when a user space program calls open(2), read(2), write(2), and close(2). They just print a kernel message so that you can see that they were called. You can demonstrate this from the command line using the echo command:
# echo hello > /dev/dummy0
dummy_open
dummy_write 6
dummy_release
In this case,
the messages appear because I was logged on to the console, and kernel messages
are printed to the console by default. If you are not logged onto the console,
you can still see the kernel messages using the command dmesg .
The full source
code for this driver is less than 100 lines, but it is enough to illustrate how
the linkage between a device node and driver code works, how the device class
is created, allowing a device manager to create device nodes automatically when
the driver is loaded, and how the data is moved between user and kernel spaces.
Next, you need to build it.
Compiling kernel modules
At this point,
you have some driver code that you want to compile and test on your target
system. You can copy it into the kernel source tree and modify makefiles to
build it, or you can compile it as a module out of tree. Let's start by
building out of tree.
You need a
simple makefile which uses the kernel build system to do the hard work:
LINUXDIR :=
$(HOME)/MELP/build/linux
obj-m := dummy.o
all:
make ARCH=arm
CROSS_COMPILE=arm-cortex_a8-linux-gnueabihf-
-C
$(LINUXDIR) M=$(shell pwd)
clean:
make -C
$(LINUXDIR) M=$(shell pwd) clean
Set LINUXDIR to the
directory of the kernel for your target device that you will be running the
module on. The obj-m
:= dummy.o code will invoke the kernel build rule to take the source
file, dummy.c , and create kernel module, dummy.ko . I will
show you how to load kernel modules in the next section.
If you want to
build a driver in the kernel source tree, the procedure is quite simple. Choose
a directory appropriate to the type of driver you have. The driver is a basic
character device, so I would put dummy.c in drivers/char . Then,
edit the makefile in the directory, and add a line to build the driver
unconditionally as a module, as follows:
obj-m += dummy.o
Or add the
following line to build it unconditionally as a built-in:
obj-y += dummy.o
If you want to
make the driver optional, you can add a menu option to
the Kconfig file and make the compilation conditional on the configuration
option, as I described in Chapter 4, Configuring and Building the Kernel, in the section,
Understanding kernel configuration .
Loading kernel modules
You can load,
unload, and list modules using the simple insmod, lsmod, and rmmod commands.
Here they are shown loading the dummy driver:
# insmod /lib/modules/4.8.12-yocto-standard/kernel/drivers/dummy.ko
# lsmod
Tainted:
G
dummy 2062 0 - Live 0xbf004000 (O)
# rmmod dummy
If the module is
placed in a subdirectory in /lib/modules/ , you can create a modules dependency database using the
command, depmod -a:
# depmod -a
# ls /lib/modules/4.8.12-yocto-standard
kernel modules.alias
modules.dep modules.symbols
The information
in the module.* files is used by the modprobe command to locate a module by name
rather than the full path. modprobe has many other features, which are described on the manual
page modprobe(8) .
The next article in this series will describe how to discover the system's
hardware configuration.
嵌入式Linux设备驱动程序:编写内核设备驱动程序的更多相关文章
- 嵌入式linux加载引导内核和根文件系统的方法
总体来说,嵌入式Linux内核和根文件的引导与PC机差不多.嵌入式linux内核和根文件系统可以存放在各种可能的存储设备中,一般情况下我 们将内核和根文件系统直接烧入到Flash中(包括NOR和NAN ...
- 嵌入式Linux驱动学习之路(十七)驱动程序分层分离概念-平台设备驱动
平台设备驱动: 包含BUS(总线).DEVICE.DRIVER. DEVICE:硬件相关的代码 DRIVER:比较稳定的代码 BUS有一个driver链表和device链表. ①把device放入bu ...
- 嵌入式linux开发uboot启动内核的机制(二)
一.嵌入式系统的分区 嵌入式系统部署在Flash设备上时,对于不同SoC和Flash设备,bootloader.kernel.rootfs的分区是不同的.三星S5PV210规定启动设备的分区方案如下: ...
- 作为一个新人,怎样学习嵌入式Linux,(韦东山)
很早以前在网上看到的韦东山老师写的文章,复制到自己的博客,方便自己以后看. 在学习嵌入式Linux之前,肯定要有C语言基础.汇编基础有没有无所谓(就那么几条汇编指令,用到了一看就会). C语言要学到什 ...
- 作为一个新人,如何学习嵌入式Linux?
作为一个新人.如何学习嵌入式Linux?我一直在问太多次,特写文章来回答这个问题. 在学习嵌入式Linux之前.肯定要有C语言基础.汇编基础有没有无所谓(就那么几条汇编指令,用到了一看就会).C语言要 ...
- 作为一个新人,怎样学习嵌入式Linux
作为一个新人,怎样学习嵌入式Linux?被问过太多次,特写这篇文章来回答一下. 在学习嵌入式Linux之前,肯定要有C语言基础.汇编基础有没有无所谓(就那么几条汇编指令,用到了一看就会). C语言要学 ...
- (转)作为一个新人,怎样学习嵌入式Linux?(韦东山)
被问过太多次,特写这篇文章来回答一下. 在学习嵌入式Linux之前,肯定要有C语言基础.汇编基础有没有无所谓(就那么几条汇编指令,用到了一看就会).C语言要学到什么程度呢?越熟当然越好,不熟的话也 ...
- 作为一个新人,怎样学习嵌入式Linux?
作为一个新人,怎样学习嵌入式Linux? 在学习嵌入式Linux之前,肯定要有C语言基础.汇编基础有没有无所谓(就那么几条汇编指令,用到了一看就会).尝试着写一些C语言竞赛的题目.它们是纯 ...
- 作为一个新人,怎样学习嵌入式Linux?(韦东山)
这篇文章是引用韦老师的部分关于新人怎么学习嵌入式Linux的经验,引用如下: 1.电脑一开机,那些界面是谁显示的?是BIOS,它做什么?一些自检,然后从硬盘上读入windows,并启动它. 类似的, ...
随机推荐
- Linux中数据库的安装和配置(MySQL与Maria DB)
目录 MySQL和Maria DB的介绍 MySQL和Maria DB的安装 yum源安装MySQL(Centos6.5+Mysql5.1) 源码包安装MySQL yum源安装Maria DB 源码包 ...
- 【vue-05】vue-cli
Vue-router官网 安装 vue-router是一个插件包,所以我们还是需要用npm 来进行安装.打开命令行工具,进入你的项目目录,输入下面命令. npm install vue-router ...
- MarkDown写ppt
首先给你的VSCode安装插件 MarkDown语法 例子 --- marp: true paginate: true theme: default class: - lead - invert si ...
- python-内置函数-callable,chr,ord,bytes,随机验证码生成
s="老男人" bytes(s,encoding="utf-8") 随机验证码的实现方法: 大写字母: li = [] for i in range(6): t ...
- JVM什么叫安全检测点
[deerhang] 在JVM的垃圾回收阶段,GC线程首先要进行对象的可达性分析.为了避免多线程对可达性分析的影响引出了安全点检测的概念 当GC线程进行GC前,需要等待其他线程进入安全点.例如JVM调 ...
- SwiftUI 简明教程之指示器
本文为 Eul 样章,如果您喜欢,请移步 AppStore/Eul 查看更多内容. Eul 是一款 SwiftUI & Combine 教程 App(iOS.macOS),以文章(文字.图片. ...
- Excel-宏与VBA-数据类型
学习视频,本文是观看前视频时做的笔记,手动感谢up. 数据类型 案例 声明一个变量并且赋值 Sub 变量() ' 声明一个变量用Dim,格式就是 Dim 变量名 As 数据类型 Dim Score A ...
- 面试侃集合 | ArrayBlockingQueue篇
面试官:平常在工作中你都用过什么什么集合? Hydra:用过 ArrayList.HashMap,呃-没有了 面试官:好的,回家等通知吧- 不知道大家在面试中是否也有过这样的经历,工作中仅仅用过的那么 ...
- Codeforces Round #712 (Div. 2)
A. Déjà Vu 题意:就是问能否加上字母a,使得字符串不中心对称 思路:只有一种情况不能加入,就是全部是a,剩下的都可以满足,找a的位置就找哪个字母不是a,然后让它的对称位置是新加的这个a 代码 ...
- DirectX渲染时Clear无效的原因(造成叠影)
最近在开发D3D程序的过程中,发现一件很奇怪的事情,就是在Render的时候,纹理总是留有"残影"(即上次Render后的帧):如上图,是一副纹理绕中心点旋转的向日葵,但是可以看到 ...