设备驱动充当了硬件和应用软件之间的纽带,它使得应用软件只需要调用系统软件的应用编程接口(API)就可让硬件去完成要求的工作。本文主要讲解了Linux设备驱动与硬件的关系,Linux设备驱动的开发模式以及内核中相关的重要基础数据结构。
设备驱动与硬件的关系
对设备驱动最通俗的解释就是“驱使硬件设备行动”。驱动与底层硬件直接打交道,按照硬件设备的具体工作方式,读写设备的寄存器,完成设备的轮询、中断处理、 DMA 通信,进行物理内存向虚拟内存的映射等,最终让通信设备能收发数据,让显示设备能显示文字和画面,让存储设备能记录文件和数据。
设备分类
Linux对将外设分为3类:
字符设备
字符设备指那些必须以串行顺序依次进行访问的设备,如触摸屏、磁带驱动器、鼠标等。
块设备
块设备可以用任意顺序进行访问,以块为单位进行操作,如硬盘、软驱等。
网络设备
网络设备面向数据包的接收和发送而设计,它并不对应于文件系统的节点。
总体框图
机制与策略
机制强调“提供什么能力”,而策略旨在“如何使用这些能力”。因此驱动开发需要遵守的是驱动程序的角色是提供机制, 而不是策略。
编写内核代码来存取硬件, 但是不能强加特别的策略给用户, 因为不同的用户有不同的需求。驱动应当做到使硬件可用, 将所有关于如何使用硬件的事情留给应用程序。
内核模块
Linux 提供了一种代码动态地加载到内核中机制,这种机制被称为模块(Module)。具有如下特点:
模块本身不被编译入内核映像, 从而控制了内核的大小。
模块一旦被加载,它就和内核中的其他部分完全一样。
可动态加载与移除,不需重启系统,节约开发时间。
模块放在用户空间,这部分代码可以不开源。
因此驱动多数情况以内核模块的形式加载到内核。
组成
一个 Linux 内核模块主要由如下几个部分组成:
模块加载函数(一般需要)
当通过 insmod 或 modprobe 命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作。static int __init initialization_function(void){ /* 初始化代码 */}module_init(initialization_function);
模块卸载函数(一般需要)
当通过 rmmod 命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模块卸载函数相反的功能。static void __exit cleanup_function(void){ /* 释放代码 */}module_exit(cleanup_function);
模块许可证声明(必须)
许可证( LICENSE)声明描述内核模块的许可权限,如果不声明 LICENSE,模块被加载时,将收到内核被污染 ( kernel tainted)的警告。在 Linux 2.6 内核中,可接受的 LICENSE 包括“ GPL”、“ GPL v2”、“ GPL and additional rights”、“ Dual BSD/GPL”、“ Dual MPL/GPL” 和“ Proprietary”。
模块参数(可选)
模块参数是模块被加载的时候可以被传递给它的值,它本身对应模块内部的全局变量。module_param(参数名,参数类型,参数读/写权限);module_param_array(数组名,数组类型,数组长,参数读/写权限);
模块导出符号(可选)
内核模块可以导出符号( symbol,对应于函数或变量),这样其他模块可以使用本模块中的变量或函数。EXPORT_SYMBOL(符号名);EXPORT_SYMBOL_GPL(符号名);
模块作者等信息声明(可选)
MODULE_AUTHOR(author);MODULE_DESCRIPTION(description);MODULE_VERSION(version_string);MODULE_DEVICE_TABLE(table_info);MODULE_ALIAS(alternate_name);
编译
# Makefile2.6TARGET = demo_moduleifneq ($(KERNELRELEASE),)#kbuild syntax. dependency relationshsip of files and target modules are listed here.obj-m := $(TARGET).o else# build from shell directly not in kernel rootCURDIR = $(shell pwd)KVER := $(shell uname -r)KDIR := /lib/modules/$(KVER)/buildall: $(MAKE) -C $(KDIR) M=$(CURDIR) modulesclean: $(MAKE) -C $(KDIR) M=$(CURDIR) cleaninsert: sudo insmod $(TARGET).koremove: sudo rmmod $(TARGET)endif
重要的数据结构
大部分的基础性的驱动操作包括3个重要的内核数据结构, 称为 file_operations, file, 和 inode。
file_operations
file_operations 结构体中的成员函数是设备驱动程序设计的主体内容,这些函数实际会在应用程序进行 Linux 的 open()、 write()、 read()、 close()等系统调用时最终被调用。
struct file_operations { struct module *owner; /* 拥有该结构的模块的指针,一般为 THIS_MODULES */ loff_t(*llseek)(struct file *, loff_t, int); /* 用来修改文件当前的读写位置 */ ssize_t(*read)(struct file *, char __user *, size_t, loff_t*); /* 从设备中同步读取数据 */ ssize_t(*write)(struct file *, const char __user *, size_t, loff_t*); /* 向设备发送数据*/ ssize_t(*aio_read)(struct kiocb *, char __user *, size_t, loff_t); /* 初始化一个异步的读取操作*/ ssize_t(*aio_write)(struct kiocb *, const char __user *, size_t, loff_t); /* 初始化一个异步的写入操作*/ int(*readdir)(struct file *, void *, filldir_t); /* 仅用于读取目录,对于设备文件,该字段为 NULL */ unsigned int(*poll)(struct file *, struct poll_table_struct*); /* 轮询函数,判断目前是否可以进行非阻塞的读取或写入*/ int(*ioctl)(struct inode *, struct file *, unsigned int, unsigned long); /* 执行设备 I/O 控制命令*/ long(*unlocked_ioctl)(struct file *, unsigned int, unsigned long); /* 不使用 BLK 的文件系统,将使用此种函数指针代替 ioctl */ long(*compat_ioctl)(struct file *, unsigned int, unsigned long); /* 在 64 位系统上, 32 位的 ioctl 调用,将使用此函数指针代替*/ int(*mmap)(struct file *, struct vm_area_struct*); /* 用于请求将设备内存映射 int(*open)(struct inode *, struct file*); /* 打开 */ int(*release)(struct inode *, struct file*); /* 关闭*/ int (*fsync) (struct file *, struct dentry *, int datasync); /* 刷新待处理的数据*/ int(*aio_fsync)(struct kiocb *, int datasync); /* 异步 fsync */ int(*fasync)(int, struct file *, int); /* 通知设备 FASYNC 标志发生变化*/ ...};
file
file 结构体代表一个打开的文件(设备对应于设备文件),系统中每个打开的文件在内核空间都有一个关联的 struct file。它由内核在打开文件时创建,并传递给在文件上进行操作的任何函数。在文件的所有实例都关闭后,内核释放这个数据结构。
struct file { const struct file_operations *f_op; /* 和文件关联的操作*/ unsigned int f_flags; /*文件标志,如 O_RDONLY、 O_NONBLOCK、 O_SYNC*/ fmode_t f_mode; /*文件读/写模式, FMODE_READ 和 FMODE_WRITE*/ loff_t f_pos; /* 当前读写位置*/ void *private_data; /*文件私有数据,可存储自定义数据的指针*/ ...};
inode
VFS inode 包含文件访问权限、属主、组、大小、生成时间、访问时间、最后修改时间等信息。它是 Linux 管理文件系统的最基本单位,也是文件系统连接任何子目录、文件的桥梁。
struct inode { umode_t i_mode; /* inode 的权限 */ uid_t i_uid; /* inode 拥有者的 id */ gid_t i_gid; /* inode 所属的群组 id */ dev_t i_rdev; /* 若是设备文件,此字段将记录设备的设备号 */ loff_t i_size; /* inode 所代表的文件大小 */ struct timespec i_atime; /* inode 最近一次的存取时间 */ struct timespec i_mtime; /* inode 最近一次的修改时间 */ struct timespec i_ctime; /* inode 的产生时间 */ unsigned long i_blksize; /* inode 在做 I/O 时的区块大小 */ unsigned long i_blocks; /* inode 所使用的 block 数,一个 block 为 512 byte*/ struct block_device *i_bdev; /*若是块设备,为其对应的 block_device 结构体指针*/ struct cdev *i_cdev; /*若是字符设备,为其对应的 cdev 结构体指针*/ ...};
评论
查看更多