了解并学习Linux设备驱动的基础知识

设备驱动充当了硬件和应用软件之间的纽带,它使得应用软件只需要调用系统软件的应用编程接口(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 结构体指针*/ ...};

RFID行李全流程跟踪系统设计
身份证rfid电路图设计应用
5g工业网关在智慧工厂建设中的作用是怎样的
卫星地球站对微波接力站的干扰计算步骤和具体方法分析
iQOO成为科幻巨制《沙丘》电影推广独家手机合作伙伴
了解并学习Linux设备驱动的基础知识
火焰监测器重要组成:紫外线传感器
LTCC通孔浆料的工艺研究
国芯思辰 |替代AD7606,国产16位ADC SC1467在智能电网中的应用
榕泽app:缓解电动车电量焦虑 “共享电池换电真方便
Python成为世界上最受欢迎的语言_Java定为入门语言?
dfrobot数字量I2C隔离模块简介
超低电压处理器是什么_超低电压处理器的特点
图像传感器的应用详解
自适应前馈射频功率放大器设计
小米6 小米Mix2等一大波小米新品在路上, 该不该剁手呢?
影响区块链被主流采用的障碍有哪些
四川电网将分三步打造出一张更加坚强、智慧、普惠的电网
如何在传感器领域实现突破?
为什么铁电RAM比串行SRAM更加的具有优势