嵌入式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:


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设备驱动程序:编写内核设备驱动程序的更多相关文章

  1. 嵌入式linux加载引导内核和根文件系统的方法

    总体来说,嵌入式Linux内核和根文件的引导与PC机差不多.嵌入式linux内核和根文件系统可以存放在各种可能的存储设备中,一般情况下我 们将内核和根文件系统直接烧入到Flash中(包括NOR和NAN ...

  2. 嵌入式Linux驱动学习之路(十七)驱动程序分层分离概念-平台设备驱动

    平台设备驱动: 包含BUS(总线).DEVICE.DRIVER. DEVICE:硬件相关的代码 DRIVER:比较稳定的代码 BUS有一个driver链表和device链表. ①把device放入bu ...

  3. 嵌入式linux开发uboot启动内核的机制(二)

    一.嵌入式系统的分区 嵌入式系统部署在Flash设备上时,对于不同SoC和Flash设备,bootloader.kernel.rootfs的分区是不同的.三星S5PV210规定启动设备的分区方案如下: ...

  4. 作为一个新人,怎样学习嵌入式Linux,(韦东山)

    很早以前在网上看到的韦东山老师写的文章,复制到自己的博客,方便自己以后看. 在学习嵌入式Linux之前,肯定要有C语言基础.汇编基础有没有无所谓(就那么几条汇编指令,用到了一看就会). C语言要学到什 ...

  5. 作为一个新人,如何学习嵌入式Linux?

    作为一个新人.如何学习嵌入式Linux?我一直在问太多次,特写文章来回答这个问题. 在学习嵌入式Linux之前.肯定要有C语言基础.汇编基础有没有无所谓(就那么几条汇编指令,用到了一看就会).C语言要 ...

  6. 作为一个新人,怎样学习嵌入式Linux

    作为一个新人,怎样学习嵌入式Linux?被问过太多次,特写这篇文章来回答一下. 在学习嵌入式Linux之前,肯定要有C语言基础.汇编基础有没有无所谓(就那么几条汇编指令,用到了一看就会). C语言要学 ...

  7. (转)作为一个新人,怎样学习嵌入式Linux?(韦东山)

    被问过太多次,特写这篇文章来回答一下.   在学习嵌入式Linux之前,肯定要有C语言基础.汇编基础有没有无所谓(就那么几条汇编指令,用到了一看就会).C语言要学到什么程度呢?越熟当然越好,不熟的话也 ...

  8. 作为一个新人,怎样学习嵌入式Linux?

        作为一个新人,怎样学习嵌入式Linux?   在学习嵌入式Linux之前,肯定要有C语言基础.汇编基础有没有无所谓(就那么几条汇编指令,用到了一看就会).尝试着写一些C语言竞赛的题目.它们是纯 ...

  9. 作为一个新人,怎样学习嵌入式Linux?(韦东山)

    这篇文章是引用韦老师的部分关于新人怎么学习嵌入式Linux的经验,引用如下: 1.电脑一开机,那些界面是谁显示的?是BIOS,它做什么?一些自检,然后从硬盘上读入windows,并启动它. 类似的, ...

随机推荐

  1. hdu1572 水搜索

    题意: 中文的不解释; 思路:           其实就是一个水的搜索,直接搜索不会超时,还有别跑最短路了,题目没要求跑最短路,别读错题,刚开始自己嘚嗖的跑了一边最短路 wa了 ,正好最近看了STL ...

  2. hdu2100 26进制加法

    题意:       给你两个26进制数,让你算出他们两个的和. 思路:      水题不解释了,注意这样的数据 AAA AAA 输出A #include<stdio.h> #include ...

  3. Windows驱动派遣函数的学习

    //派遣处理例程的介绍: //IPR简介: //IRP全称(I/O Request Package),即输入输出请求包.他是windows驱动的重要概念,用户模式下所有对驱动程序的I/O请求,全部由操 ...

  4. Hook android系统调用研究(一)

    本文的博客链接:http://blog.csdn.net/qq1084283172/article/details/55657300 一.Android内核源码的编译环境 系统环境:Ubuntu 14 ...

  5. POJ1135比较有意思的对短路(多米骨牌)

    题意:      有一个骨牌游戏,就是推到一个后所有的牌都会被退到的那种游戏,起点是1,有两种骨牌,一种是关键牌,另一种是普通牌,普通牌是连接关键牌用的,给你一些边a b c的意思是关键牌a倒之后c时 ...

  6. 手撸了一个starter,同事直夸我666~

    Spring Boot starter原理 Spring Boot 将常见的开发功能,分成了一个个的starter,这样我们开发功能的时候只需要引入对应的starter,而不需要去引入一堆依赖了!st ...

  7. Java 正则表达式实例操作

    Regular Expression正则表达式,简称RegExp,常规通用的表达式,在多个开发语言中都有它的实现,可以通过正则表达式来快速的检索.匹配.查找.替换字符串中的文本. 简单实例 匹配网址 ...

  8. [bug] HDFS:hdfs missing blocks. The following files may be corrupted

    原因 HDFS数据块丢失,需要删除丢失块的元信息 bin/hadoop fsck / -delete 参考 https://blog.csdn.net/lixgjob/article/details/ ...

  9. KVM性能优化

    一.KVM为什么要调优 性能的损耗是关键.KVM采用全虚拟化技术,全虚拟化要由一个软件来模拟硬件,故有一定的损耗,特别是I/O,因此需要优化.KVM性能优化主要在CPU.内存.I/O这几方面.当然对于 ...

  10. 进入单用户模式修改root密码

    进入单用户模式修改root密码 1.进入引导菜单界面2.按e进入grub,在linux或linux16那行结尾加上 rw init=/bin/bash,按Ctrl+x或F103.进入bash-4.3# ...