嵌入式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=(LINUXDIR) M=(LINUXDIR)M=(shell pwd)
clean:
make -C (LINUXDIR)M=(LINUXDIR) M=(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设备驱动程序:编写内核设备驱动程序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 分离内核和虚拟机支持安全的关键任务边缘计
- 下一篇: 管道:实用程序服务和数据结构