嵌入式Linux设备驱动程序:编写内核设备驱动程序

时间:2022-09-07 23:43:30

嵌入式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驱动学习之路&lpar;十七&rpar;驱动程序分层分离概念-平台设备驱动

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

  3. 嵌入式linux开发uboot启动内核的机制&lpar;二&rpar;

    一.嵌入式系统的分区 嵌入式系统部署在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?&lpar;韦东山)

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

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

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

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

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

随机推荐

  1. browser-sync

    引入 大家写网页的时候,肯定都遇到这种情况,每次用sublime写完都要返回浏览器,刷新页面,而这个工具正好解决了这个问题,提高前端开发效率,这是一个npm的包 browser-sync browse ...

  2. 软件开发常用设计模式—单例模式总结(c&plus;&plus;版)

    单例模式:就是只有一个实例. singleton pattern单例模式:确保某一个类在程序运行中只能生成一个实例,并提供一个访问它的全局访问点.这个类称为单例类.如一个工程中,数据库访问对象只有一个 ...

  3. JavaScript -- 小试牛刀

    //var a = parseInt(window.prompt("请输入一个数字!","")); //switch(a) { // case 1 : // c ...

  4. 关于synchronized 影响可见性的问题

    问题来自于学习thinking in java的时候的一个示例,先上代码吧 public class StopThread { private static boolean stop = false; ...

  5. C&num;浅拷贝与深拷贝区别

    也许会有人这样解释C# 中浅拷贝与深拷贝区别: 浅拷贝是对引用类型拷贝地址,对值类型直接进行拷贝. 不能说它完全错误,但至少还不够严谨.比如:string 类型咋说? 其实,我们可以通过实践来寻找答案 ...

  6. django1&period;6之创建用户

    如何你运行python manager.py syncdb(数据库同步成功) 则运行一下代码 >>> from django.contrib.auth.models import U ...

  7. 40个Android问题

    1. Android的四大组件是哪些,它们的作用? 答:Activity:Activity是Android程序与用户交互的窗口,是Android构造块中最基本的一种,它需要为保持各界面的状态,做很多持 ...

  8. 2快速掌握OMD

    我们已经知道使用ArcGIS Engine开发,也就意味着我们要和接口打交道,ArcGIS Engine中提供的接口和类加起来估计上万,但是用过ArcGIS Engine的人,知道这个数字不为过.Ar ...

  9. codeforces-1144 &lpar;div3&rpar;

    赛后经验:div3过于简单,以后不做了 A.存在以下情况即为NO 1.存在相同字母 2.最大字母-最小字母 != 字符串长度 #include <map> #include <set ...

  10. 关于angular 的路由title设置

    在主模块下 constructor( private router: Router, private activatedRoute: ActivatedRoute, ) {} this.router. ...