本篇我们将会详细讲解 Linux 中的三大类驱动:字符设备驱动、块设备驱动和网络设备驱动。
字符设备最多,从最简单的点灯到 I2C、SPI、音频等都属于字符设备驱动的类型。
块设备驱动就是存储器设备的驱动,比如 EMMC、NAND、SD 卡和 U 盘等存储设备,因为这些存储设备的特点是以存储块为基础,因此叫做块设备。
网络驱动,不管是有线的还是无线的,都属于网络设备驱动的范畴
块设备和网络设备驱动要比字符设备驱动复杂,就是因为其复杂所以半导体厂商一般都给我们编写好了,大多数情况下都是直接可以使用的。
一个设备可以属于多种设备驱动类型,比如 USB WIFI,其使用 USB 接口,所以属于字符设备,但是其又能上网,所以也属于网络设备驱动。
设备树将是本篇的重点!
字符设备驱动开发 字符设备驱动简介字符设备就是一个一个字节,按照字节流进行读写 *** 作的设备,读写数据是分先后顺序的。
Linux 驱动属于内核的一部分,因此驱动运行于内核空间。
当我们在用户空间想要实现对内核的 *** 作,比如使用 open 函数打开/dev/led 这个驱动,因为用户空间不能直接对内核进行 *** 作,因此必须使用一个叫做“系统调用”的方法来实现从用户空间“陷入”到内核空间,这样才能实现对底层驱动的 *** 作
每一个系统调用,在驱动中都有与之对应的一个驱动函数,
在 Linux 内核文件 include/linux/fs.h 中有个叫做 file_operations 的结构体,此结构体就是 Linux 内核驱动 *** 作函数集合
简单介绍一下 file_operation 结构体中比较重要的、常用的函数:
第 1589 行,owner 拥有该结构体的模块的指针,一般设置为 THIS_MODULE。 第 1590 行,llseek 函数用于修改文件当前的读写位置。
第 1591 行,read 函数用于读取设备文件。
第 1592 行,write 函数用于向设备文件写入(发送)数据。 第 1596 行,poll 是个轮询函数,用于查询设备是否可以进行非阻塞的读写。
第 1597 行,unlocked_ioctl 函数提供对于设备的控制功能,与应用程序中的 ioctl 函数对应。
第 1598 行,compat_ioctl 函数与 unlocked_ioctl 函数功能一样,区别在于在 64 位系统上
第 1599 行,mmap 函数用于将将设备的内存映射到进程空间中(也就是用户空间)
第 1601 行,open 函数用于打开设备文件。
第 1603 行,release 函数用于释放(关闭)设备文件,与应用程序中的 close 函数对应。
第 1604 行,fasync 函数用于刷新待处理的数据,用于将缓冲区中的数据刷新到磁盘中。
第 1605 行,aio_fsync 函数与 fasync 函数的功能类似。只是 aio_fsync 是异步刷新
在字符设备驱动开发中最常用的就是上面这些函数。我们在字符设备驱动开发中最主要的工作就是实现上面这些函数,不一定全部都要实现,但是像 open、release、write、read 等都是需要实现的
字符设备驱动开发步骤初始化相应的外设寄存器。我们需要按照其规定的框架来编写驱动。
1.驱动模块的加载和卸载
Linux 驱动有两种运行方式
第一种就是将驱动编译进 Linux 内核中,这样当 Linux 内核启动的时候就会自动运行驱动程序
第二种就是将驱动编译成模块(Linux 下模块扩展名为.ko),在Linux 内核启动以后使用“insmod”命令加载驱动模块。
这样我们修改驱动以后只需要编译一下驱动代码即可,不需要编译整个 Linux 代码。
模块有加载和卸载两种 *** 作,我们在编写驱动的时候需要注册这两种 *** 作函数
module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数
当使用“insmod”命令加载驱动的时候,xxx_init 这个函数就会被调用
“rmmod”命令卸载具体驱动的时候 xxx_exit 函数就会被调用。
modprobe 命令主要智能在提供了模块的依赖性分析、错误检查、错误报告等功能
modprobe 命令默认会去/lib/modules/目录中查找模块。需要自己手动创建
“modprobe -r”命令卸载驱动
2.字符设备注册与注销
对于字符设备驱动而言,当驱动模块加载成功以后需要注册字符设备
tatic inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, const char *name)
major:主设备号,Linux 下每个设备都有一个设备号,设备号分为主设备号和次设备号两部分,关于设备号后面会详细讲解。
name:设备名字,指向一串字符串。
fops:结构体 file_operations 类型指针,指向设备的 *** 作函数集合变量
要注意的一点就是,选择没有被使用的主设备号,输入命令“cat /proc/devices”可以查看当前已经被使用掉的设备号
第一列就是主设备号
3.实现设备的具体 *** 作函数
file_operations 结构体就是设备的具体 *** 作函数
static struct file_operations test_fops = {
owner = THIS_MODULE,
open = chrtest_open,
read = chrtest_read,
write = chrtest_write,
release = chrtest_release,
};
4.添加 LICENSE 和作者信息
MODULE_LICENSE() //添加模块 LICENSE 信息
MODULE_AUTHOR() //添加模块作者信息
设备号分为主设备号和次设备号
主设备号标识某一个具体的驱动,次设备号表示使用这个驱动的各个设备
Linux提供了dev_t的数据类型表示设备号,是32位的数据类型。分成了主次两部分
其中高12位为主设备,低20位为次设备
1.静态分配设备号
前面讲过,注册字符设备的时候需要指定一个设备号
。有一些常用的设备号已经被 Linux 内核开发者给分配掉了,具体分配的内容可以查看文档 Documentation/devices.txt。并不是说内核开发者已经分配掉的主设备号我们就不能用了,具体能不能用还得看我们的硬件平台运行过程中有没有使用这个主设备号,使用“cat /proc/devices”命令即可查看当前系统中所有已经使用了的设备号
2.动态分配设备号
Linux 社区推荐使用动态分配设备号,在注册字符设备之前先申请一个设备号,系统会自动给你一个没有被使用的设备号,这样就避免了冲突
用于申请设备号的函数
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
dev:保存申请到的设备号。
baseminor:次设备号起始地址,alloc_chrdev_region 可以申请一段连续的多个设备号,这些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递增。一般 baseminor 为 0,也就是说次设备号从 0 开始。
count:要申请的设备号数量。
name:设备名字。
注销字符设备之后要释放掉设备号,设备号释放函数如下:
void unregister_chrdev_region(dev_t from, unsigned count)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)