Linux-驱动开发
- ■ Linux-应用程序对驱动程序的调用流程
- ■ Linux-file_operations 结构体
- ■ Linux-驱动模块的加载和卸载
- ■ 1. 驱动编译进 Linux 内核中
- ■ 2. 驱动编译成模块(Linux 下模块扩展名为.ko)
- ■ Linux-
- ■ Linux-
- ■ Linux-设备号
- ■ Linux-设备号-分配
- ■ 静态分配设备号
- ■ 动态分配设备号
- ■ Linux-驱动分类
- ■ 字符设备:
- ■ 字符设备-注册与注销函数
- ■ 字符设备-具体操作函数
- ■ 字符设备-LICENSE 和作者信息
- ■ 示例一:
- ■
- ■
- ■
■ Linux-应用程序对驱动程序的调用流程
■ Linux-file_operations 结构体
在 Linux 内核文件 include/linux/fs.h 中有个叫做 file_operations 的结构体,此结构体就是 Linux 内核驱动操作函数集合,
1588 struct file_operations {
1589 struct module *owner; // 拥有该结构体的模块的指针,一般设置为 THIS_MODULE
1590 loff_t (*llseek) (struct file *, loff_t, int); //llseek 函数用于修改文件当前的读写位置。
1591 ssize_t (*read) (struct file *, char __user *, size_t, loff_t*); // read 函数用于读取设备文件。
1592 ssize_t (*write) (struct file *, const char __user *, size_t,loff_t *); //write 函数用于向设备文件写入(发送)数据。
1593 ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
1594 ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
1595 int (*iterate) (struct file *, struct dir_context *);
1596 unsigned int (*poll) (struct file *, struct poll_table_struct*); //是个轮询函数,用于查询设备是否可以进行非阻塞的读写。
1597 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //unlocked_ioctl 函数提供对于设备的控制功能,与应用程序中的 ioctl 函数对应。
1598 long (*compat_ioctl) (struct file *, unsigned int, unsigned long); //compat_ioctl 函数与 unlocked_ioctl 函数功能一样,区别在于在 64 位系统上,
32 位的应用程序调用将会使用此函数。在 32 位的系统上运行 32 位的应用程序调用的是
unlocked_ioctl。
1599 int (*mmap) (struct file *, struct vm_area_struct *); //mmap 函数用于将设备的内存映射到进程空间中(也就是用户空间),一般帧缓
冲设备会使用此函数,比如 LCD 驱动的显存,将帧缓冲(LCD 显存)映射到用户空间中以后应用
程序就可以直接操作显存了,这样就不用在用户空间和内核空间之间来回复制。
1600 int (*mremap)(struct file *, struct vm_area_struct *);
1601 int (*open) (struct inode *, struct file *); //open 函数用于打开设备文件。
1602 int (*flush) (struct file *, fl_owner_t id);
1603 int (*release) (struct inode *, struct file *); //release 函数用于释放(关闭)设备文件,与应用程序中的 close 函数对应。
1604 int (*fsync) (struct file *, loff_t, loff_t, int datasync); //fasync 函数用于刷新待处理的数据,用于将缓冲区中的数据刷新到磁盘中
1605 int (*aio_fsync) (struct kiocb *, int datasync); //aio_fsync 函数与 fasync 函数的功能类似,只是 aio_fsync 是异步刷新待处理的
数据。
1606 int (*fasync) (int, struct file *, int);
1607 int (*lock) (struct file *, int, struct file_lock *);
1608 ssize_t (*sendpage) (struct file *, struct page *, int, size_t,loff_t *, int);
1609 unsigned long (*get_unmapped_area)(struct file *, unsigned long,unsigned long, unsigned long, unsigned long);
1610 int (*check_flags)(int);
1611 int (*flock) (struct file *, int, struct file_lock *);
1612 ssize_t (*splice_write)(struct pipe_inode_info *, struct file *,loff_t *, size_t, unsigned int);
1613 ssize_t (*splice_read)(struct file *, loff_t *, structpipe_inode_info *, size_t, unsigned int);
1614 int (*setlease)(struct file *, long, struct file_lock **, void**);
1615 long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);
1617 void (*show_fdinfo)(struct seq_file *m, struct file *f);
1618 #ifndef CONFIG_MMU
1619 unsigned (*mmap_capabilities)(struct file *);
1620 #endif
1621 };
序号 | 描述 |
---|---|
1 | 驱动加载成功以后会在“/dev”目录下生成一个相应的文件 |
2 | 比如现在有个叫做/dev/led 的驱动文件,此文件是 led 灯的驱动文件。应用程序使用 open 函数来打开文件/dev/led,使用完成以后使用close 函数关闭/dev/led 这 个文件。 open和 close 就是打开和关闭 led 驱动的函数,如果要点亮或关闭 led |
3 | 应用程序运行在用户空间,而 Linux 驱动属于内核的一部分,因此驱动运行于内核空间 |
4 | |
5 | |
6 | |
7 | |
8 |
■ Linux-驱动模块的加载和卸载
■ 1. 驱动编译进 Linux 内核中
这样当 Linux 内核启动的时候就会自动运行驱动程序。
■ 2. 驱动编译成模块(Linux 下模块扩展名为.ko)
在Linux 内核启动以后使用“insmod”命令加载驱动模块。在调试驱动的时候一般都选择将其编译为模块,这样我们修改驱动以后只需要编译一下驱动代码即可,不需要编译整个 Linux 代码。
module_init(xxx_init); //注册模块加载函数 当使用“insmod”命令加载驱动的时候, xxx_init 这个函数就会被调用
module_exit(xxx_exit); //注册模块卸载函数 当使用“rmmod”命令卸载具体驱动的时候, xxx_exit 函数就会被调用。
示例一:
第15行,调用函数module_init 来声明 xxx_init 为驱动入口函数,当加载驱动的时候 xxx_init函数就会被调用。
第16行,调用函数module_exit来声明xxx_exit为驱动出口函数,当卸载驱动的时候xxx_exit函数就会被调用。
关键字功能 | 作用 | 说明 |
---|---|---|
加载驱动 | insmod | insmod 是最简单的模块加载命令 例如 insmod drv.ko , insmod 命令不能解决模块的依赖关系, 比如 drv.ko 依赖 first.ko 这个模块,就必须先使用insmod 命令加载 first.ko 这个模块,然后再加载 drv.ko 这个模块。 |
加载驱动 | modprobe | modprobe 会分析模块的依赖关系,然后会将所有的依赖模块都加载到内核中, 因此modprobe 命令相比 insmod 要智能一些。 推荐使用 modprobe 命令来加载驱动。 modprobe 命令默认会去/lib/modules/目录中查找模块, |
卸载驱动 | modprobe -r | 例如 modprobe -r drv.ko 使用 modprobe 命令可以卸载掉驱动模块所依赖的其他模块,前提是这些依赖模块已经没有被其他模块所使用,否则就不能使用 modprobe 来卸载驱动模块。 |
卸载驱动 | rmmod | 例如 rmmod drv.ko 推荐使用 rmmod 命令。 |
■ Linux-
■ Linux-
■ Linux-设备号
Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成,
主设备号表示某一个具体的驱动,
次设备号表示使用这个驱动的各个设备。
dev_t 其实就是 unsigned int 类型,是一个 32 位的数据类型。高 12 位为主设备号, 低 20 位为次设备号。
typedef unsigned int __u32;
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t; dev_t 其实就是 unsigned int 类型,是一个 32 位的数据类型。高 12 位为主设备号, 低 20 位为次设备号。
在include/linux/kdev_t.h 中提供了几个关于设备号的操作函数(本质是宏),如下所示:
#define MINORBITS 20 宏 MINORBITS 表示次设备号位数,一共是 20 位。
#define MINORMASK ((1U << MINORBITS) - 1) 宏 MINORMASK 表示次设备号掩码。
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) 宏 MAJOR 用于从 dev_t 中获取主设备号,将 dev_t 右移 20 位即可。
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) 宏 MINOR 用于从 dev_t 中获取次设备号,取 dev_t 的低 20 位的值即可。
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) 宏 MKDEV 用于将给定的主设备号和次设备号的值组合成 dev_t 类型的设备号。
■ Linux-设备号-分配
■ 静态分配设备号
具体分配的内容可以查看文档 Documentation/devices.txt。
看硬件平台运行过程中有没有使用这个主设备号,使用“cat /proc/devices” 命令即可查看当前系统中所有已经使用了的设备号。
■ 动态分配设备号
静态分配设备号很容易带来冲突问题, 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) //设备号释放函数
from:要释放的设备号。
count: 表示从 from 开始,要释放的设备号数量。
■ Linux-驱动分类
■ 字符设备:
字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、 IIC、 SPI,LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。
■ 字符设备-注册与注销函数
static 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: 主设备号,
name:设备名字,指向一串字符串。
fops: 结构体 file_operations 类型指针,指向设备的操作函数集合变量。
■ 字符设备-具体操作函数
打开
关闭
读
写 操作
■ 字符设备-LICENSE 和作者信息
MODULE_LICENSE() //添加模块 LICENSE 信息
MODULE_AUTHOR() //添加模块作者信息
■ 示例一:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#define CHRDEVBASE_MAJOR 200 //主设备号
#define CHRDEVBASE_NAME "chrdevbase" //名字
static char readbuf[100]; /*读缓冲 */
static char writebuf[100]; /* 写缓冲 */
static char kerneldata[] = {"kernel data!"};
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
// printk("chrdevbase_open\r\n");
return 0;
}
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
// printk("chrdevbase_release\r\n");
return 0;
}
static ssize_t chrdevbase_read(struct file *filp, __user char *buf, size_t count,
loff_t *ppos)
{
int ret = 0;
//printk("chrdevbase_read\r\n");
memcpy(readbuf, kerneldata, sizeof(kerneldata));
ret = copy_to_user(buf, readbuf, count);
if(ret == 0) {
} else {
}
return 0;
}
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
int ret = 0;
//printk("chrdevbase_write\r\n");
ret = copy_from_user(writebuf, buf, count);
if(ret == 0) {
printk("kernel recevdata:%s\r\n", writebuf);
} else {
}
return 0;
}
/*
* 字符设备 操作集合
*/
static struct file_operations chrdevbase_fops={
.owner = THIS_MODULE,
.open = chrdevbase_open,
.release = chrdevbase_release,
.read = chrdevbase_read,
.write = chrdevbase_write,
};
static int __init chrdevbase_init(void)
{
int ret = 0;
printk("chrdevbase_init\r\n");
/* 注册字符设备 */
ret = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
if(ret < 0) {
printk("chrdevbase init failed!\r\n");
}
return 0;
}
static void __exit chrdevbase_exit(void)
{
printk("chrdevbase_exit\r\n");
/* 注销字符设备 */
unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
}
/*
模块入口与出口
*/
module_init(chrdevbase_init); /* 入口 */
module_exit(chrdevbase_exit); /* 出口 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");