一.字符设备和驱动
- 字符设备是指使用字节流进行操作的设备。
- 驱动是位于内核空间的,所以用户空间(应用程序)要想操作某个设备时,则需要通过系统调用(如c库函数)的方法,实现对驱动的调用,从而实现操作设备。
- Linux驱动有两种运行方式
- 直接编译进Linux内核,随操作系统启动而自动运行
- 将驱动编译成模块,动态地加载、卸载驱动模块
二. 当动态地加载卸载驱动模块时,需要向Linux内核注册驱动模块加载卸载的操作函数
-
module_init(xxx_init); //注册驱动模块加载函数xxx_init module_exit(xxx_exit); //注册驱动模块卸载函数xxx_exit
三. 实现设备的具体操作函数——驱动的实现
-
即实现file_operations结构体中的函数,该结构体在内核文件include/linux/fs.h中。常用的几个函数如下:
/* 参数: inode:传递给驱动的inode值 filp :设备文件 buf :数据缓冲区 cnt :数据长度 offt :相对于文件首地址的偏移 */ //打开设备 static int chrdevbase_open(struct inode *inode, struct file *filp); //从设备读取数据 static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt); //向设备写数据 static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt); //关闭/释放设备 static int chrdevbase_release(struct inode *inode, struct file *filp); /*定义file_operations结构体,并初始化*/ static struct file_operations chrdevbase_fops = { .owner = THIS_MODULE, .open = chrdevbase_open, .read = chrdevbase_read, .write = chrdevbase_write, .release = chrdevbase_release };
四. 注册/注销字符设备
-
加载了驱动,还需要向Linux内核注册设备;同理卸载驱动时,也需要向Linux内核注销设备;Linux内核中使用cdev结构体来表示一个字符设备,结构体在文件include/linux/cdev.h中定义;相关函数如下
/*定义好后cdev字符设备变量后,要使用cdev_init函数对其进行初始化*/ void cdev_init(struct cdev *cdev, const struct file_operations *fops); /*使用cdev_add函数向Linux内核添加/注册 cdev变量表示的字符设备. dev是设备号,count是要添加/注册的设备数量 */ int cdev_add(struct cdev *p, dev_t dev, unsigned count); /*卸载驱动时,使用cdev_del函数从Linux内核 删除/卸载 cdev变量表示的字符设备*/ void cdev_del(struct cdev *p);
-
设备号:Linux设备号由主设备号和次设备号组成,主设备号表示一个具体的驱动,次设备号表示驱动下的设备;用一个unsigned int类型表示设备号,其中高12位为主设备号,低20位为次设备号。相关函数如下:
/*向Linux内核申请未使用的设备号;
参数dev是返回的主/次设备号; baseminor是起始次设备号;
count是要申请的数量; name是设备名字*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
/*使用完后释放设备号;
参数from是注销的起始设备号; count是注销的设备数量*/
void unregister_chrdev_region(dev_t from, unsigned count);
- 设备节点文件:用户空间下操作设备的入口文件。
*
- 实现在驱动加载、设备注册后,自动在 /dev目录下创建设备节点文件;借助class结构体实现,在include/linux/device.h文件下定义,相关函数如下:
/* 创建class结构体变量;
owner参数一般为THIS_MODULE;name是变量名字;返回值为class指针*/
struct class *class_create (struct module *owner, const char *name);
/* 删除class结构体变量;
cls为要删除的class结构体*/
void class_destroy(struct class *cls);
/* 创建设备节点文件;
参数class表示设备节点文件需要创建在哪个class结构体下面;
parent表示父设备,NULL表示没有;devt表示设备号;
drvdata表示需要使用到的数据,一般为NULL;
fmt表示创建dev/fmt设备节点文件 */
struct device *device_create(
struct class *class,
struct device *parent,
dev_t devt,
void *drvdata,
const char *fmt, ...);
/* 删除设备节点文件;
class表示要删除的设备节点所处的结构体,devt是指设备号 */
void device_destroy(struct class *class, dev_t devt);
五. 添加驱动的License信息和作者信息
-
MODULE_LICENSE(); //添加模块LICENSE 信息 MODULE_AUTHOR(); //添加模块作者信息
六. 其他函数
- 因为用户空间不能直接操作内核空间内存,内核空间不能直接操作用户空间,所以要使用内核提供的函数完成这一操作
/*成功返回0,失败返回负数*/
//从内核空间拷贝数据至用户空间
static inline long copy_to_user(void __user *to, const void *from, unsigned long n);
//从用户空间拷贝数据至内核空间
static inline long copy_from_user(void *to, const void __user *from, unsigned long n);
以上是我在学习过程中的总结,不当之处请在评论区指出。