1. 设备驱动
1.1 设备号在系统中用dev_t来表示
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
由以上两个宏可知,dev_t就是一个无符号32位的整型
其中高12位作为主设备号,一般用major表示,主设备号表示哪类设备
其中低20位作为次设备号,一般用minor表示,次设备号表示哪个设备
1.2 设备号的操作宏
/*从设备号中分离出主设备号*/
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
/*从设备号中分离出次设备号*/
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
/*由主设备号和次设备号组成一个设备号*/
#define MKDEV(ma,mi) (((ma) << 20) | (mi))
1.3使用cdev结构体描述一个字符设备结构体,cdev结构体的定义如代码:
Linux/include/cdev.h
struct cdev {
structkobject kobj; /* 内嵌的kobject对象 */
structmodule *owner; /*所属模块*/
structfile_operations *ops; /*文件操作结构体*/
structlist_head list;
dev_tdev; /*设备号*/
unsignedint count;
};
1.4 cdev结构体的另一个重要成员file_operations定义了字符设备驱动提供给虚拟文件系统的接口函数。
- - -Linux 2.6内核提供了一组函数用于操作cdev结构体:
》》cdev_init()函数用于初始化cdev的成员,并建立cdev和file_operations之间的连接,其源代码如下:
1 void cdev_init(struct cdev *cdev, structfile_operations *fops)
2 {
3 memset(cdev, 0, sizeof *cdev);
4 INIT_LIST_HEAD(&cdev->list);
5 kobject_init(&cdev->kobj,&ktype_cdev_default);
6 cdev->ops = fops; /*将传入的文件操作结构体指针赋值给cdev的ops*/
7 }
》》cdev_alloc()函数用于动态申请一个cdev内存,其源代码如下:
---------------------------------------------------------
1 struct cdev *cdev_alloc(void)
2 {
3 struct cdev *p =kzalloc(sizeof(struct cdev), GFP_KERNEL);
4 if (p) {
5 INIT_LIST_HEAD(&p->list);
6 kobject_init(&p->kobj,&ktype_cdev_dynamic);
7 }
8 returnp;
}
cdev_add()函数和cdev_del()函数分别向系统添加和删除一个cdev,完成字符设备的注册和注销。对cdev_add()的调用通常发生在字符设备驱动模块加载函数中,而对cdev_del()函数的调用则通常发生在字符设备驱动模块卸载函数中。
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);
1.5 申请主设备号
内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。这三个函数都会调用一个共用的__register_chrdev_region() 函数来注册一组设备编号范围(即一个 char_device_struct 结构)。
register_chrdev_region( ) //分配指定的设备号范围
alloc_chrdev_region( ) //动态分配设备范围
register_chrdev( )//申请指定的设备号,并且将其注册到字符设备驱动模型中.是一个老式分配设备编号范围的函数
- - - - - - - - - 老方法2.4内核/*1. 静态申请/注册主设备号*/ - - -
ret = register_chrdev(dev_major,"dev_module",&dev_fops);
/*2. 动态申请/注册主设备号*/
dev_major = register_chrdev(0,"dev_module",&dev_fops);
__register_chrdev(major, 0, 256, name, fops);
/*表示一个字符设备*/
struct cdev *cdev;
/*申请主设备号*/
cd = __register_chrdev_region(major, baseminor, count, name);
/*分配cdev*/
cdev = cdev_alloc();
/*设备操作方法*/
cdev->owner = fops->owner;
cdev->ops = fops;
/*注册字符设备*/
err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
- - - - - - - -使用静态新方法2.6内核注册/申请主设备号- - - - - - - -
1) 静态新方法 注册/申请主设备号
/*
**param1: 设备号
**param2: 个数
**param3: 设备名称
**返回值: 失败返回非0值,成功返回0
*/
ret = register_chrdev_region(devno,1,"dev_module");
2) 涉及重要结构体
struct cdev { //表示一个字符设备
struct kobject kobj;
struct module *owner;
const struct file_operations *ops; //设备操作方法
struct list_head list;
dev_t dev; //设备号
unsigned int count; //个数
};
3) struct cdev结构体操作
/*构建struct cdev结构体*/
struct cdev *cdev;
/*为cdev分配空间*/
struct cdev *cdev_alloc(void);
/*初始化cdev,构建设备操作方法*/
void cdev_init(struct cdev *, const struct file_operations *);
/*注册字符设备*/
int cdev_add(struct cdev *, dev_t, unsigned);
/*注销字符设备*/
void cdev_del(struct cdev *);
4) 动态新方法 注册/申请主设备号
/*
**param1: 设备指针
**param2: 起始个数
**param3: 个数
**param4: 名称
**返回值: 失败返回非0值,成功返回0
*/
ret = alloc_chrdev_region(&devno,0,1,"dev_module");
/*从设备号中分离主设备号*/
dev_major = MAJOR(devno);
1.6创建设备文件
创建设备文件的步骤为,获取设备编号à创建内核类à创建设备文件。利用cat /proc/devices查看申请到的设备名,设备号。
>> 设备文件也称为设备节点
>> 如何创建设备文件
>>方法一: 手动创建
mknod /dev/xxx c major minor
>>方法二: 自动创建(由系统动态自动创建设备文件),需要同时满足两个条件
a. 要求根文件系统中,实现mdev或者udev机制
b. 在驱动中必须调用两个函数
class_create(………..);
device_create(………..);
1.7 file_operations结构体
file_operations结构体中的成员函数是字符设备驱动程序设计的主体内容,这些函数实际会在应用程序进行Linux的open()、write()、read()、close()等系统调用时最终被调用。file_operations结构体目前已经比较庞大,它的定义如代码清单6.4。
代码清单6.4 file_operations结构体
-----------------------------------
1 struct file_operations {
2 struct module *owner;
3 /* 拥有该结构的模块的指针,一般为THIS_MODULES */
4 loff_t(*llseek)(struct file *, loff_t, int);
5 /* 用来修改文件当前的读写位置 */
6 ssize_t(*read)(struct file *, char __user*, size_t, loff_t*);
7 /* 从设备中同步读取数据 */
8 ssize_t(*write)(struct file *, const char__user *, size_t, loff_t*);
9 /* 向设备发送数据*/
10 ssize_t(*aio_read)(struct kiocb *, char__user *, size_t, loff_t);
11 /* 初始化一个异步的读取操作*/
12 ssize_t(*aio_write)(struct kiocb *, constchar __user *, size_t, loff_t);
13 /* 初始化一个异步的写入操作*/
14 int(*readdir)(struct file *, void *,filldir_t);
15 /* 仅用于读取目录,对于设备文件,该字段为 NULL */
16 unsigned int(*poll)(struct file *, structpoll_table_struct*);
17 /* 轮询函数,判断目前是否可以进行非阻塞的读取或写入*/
18 int(*ioctl)(struct inode *, struct file *,unsigned int, unsigned long);
19 /* 执行设备IO控制命令*/
20 long(*unlocked_ioctl)(struct file *,unsigned int, unsigned long);
21 /* 不使用BLK的文件系统,将使用此种函数指针代替ioctl */
22 long(*compat_ioctl)(struct file *, unsignedint, unsigned long);
23 /* 在64位系统上,32位的ioctl调用,将使用此函数指针代替*/
24 int(*mmap)(struct file *, structvm_area_struct*);
25 /* 用于请求将设备内存映射到进程地址空间*/
26 int(*open)(struct inode *, struct file*);
27 /* 打开 */
28 int(*flush)(struct file*);
29 int(*release)(struct inode *, struct file*);
30 /* 关闭*/
31 int (*fsync) (struct file *, struct dentry*, int datasync);
32 /* 刷新待处理的数据*/
33 int(*aio_fsync)(struct kiocb *, intdatasync);
34 /* 异步fsync */
35 int(*fasync)(int, struct file *, int);
36 /* 通知设备FASYNC标志发生变化*/
37 int(*lock)(struct file *, int, structfile_lock*);
38 ssize_t(*sendpage)(struct file *, structpage *, int, size_t, loff_t *, int);
39 /* 通常为NULL */
40 unsigned long(*get_unmapped_area)(structfile *,unsigned long, unsigned long,
41 unsigned long, unsigned long);
42 /* 在当前进程地址空间找到一个未映射的内存段 */
43 int(*check_flags)(int);
44 /* 允许模块检查传递给fcntl(F_SETEL...)调用的标志 */
45 int(*dir_notify)(struct file *filp, unsignedlong arg);
46 /* 对文件系统有效,驱动程序不必实现*/
47 int(*flock)(struct file *, int, structfile_lock*);
48 ssize_t (*splice_write)(struct pipe_inode_info*, struct file *, loff_t *, size_t,
49 unsigned int); /* 由VFS调用,将管道数据粘接到文件 */
50 ssize_t (*splice_read)(struct file *, loff_t*, struct pipe_inode_info *, size_t,
51 unsigned int); /* 由VFS调用,将文件数据粘接到管道 */
52 int (*setlease)(struct file *, long, structfile_lock **);
53};
-----------------------------------------------------------
下面对file_operations结构体中的主要成员进行分析:
>>llseek()函数用来修改一个文件的当前读写位置,并将新位置返回,在出错时,这个函数返回一个负值。
read()函数用来从设备中读取数据,成功时函数返回读取的字节数,出错时返回一个负值。
>>write()函数向设备发送数据,成功时该函数返回写入的字节数。如果此函数未被实现,当用户进行write()系统调用时,将得到-EINVAL返回值。
>>readdir()函数仅用于目录,设备节点不需要实现它。
>>ioctl()提供设备相关控制命令的实现(既不是读操作也不是写操作),当调用成功时,返回给调用程序一个非负值。
>>mmap()函数将设备内存映射到进程内存中,如果设备驱动未实现此函数,用户进行mmap()系统调用时将获得-ENODEV返回值。这个函数对于帧缓冲等设备特别有意义。
当用户空间调用LinuxAPI函数open()打开设备文件时,设备驱动的open()函数最终被调用。驱动程序可以不实现这个函数,在这种情况下,设备的打开操作永远成功。与open()函数对应的是release()函数。
>>poll()函数一般用于询问设备是否可被非阻塞的立即读写。当询问的条件未触发时,用户空间进行select()和poll()系统调用将引起进程的阻塞。
>>aio_read()和aio_write()函数分别对与文件描述符对应的设备进行异步读、写操作。设备实现这两个函数后,用户空间可以对该设备文件描述符调用aio_read()、aio_write()等系统调用进行读写。
1.8 linux字符设备驱动框架总结
================================================
1. 包含模块头文件
#include<linux/init.h>
#include<linux/module.h>
2. 模块加载函数
a. 注册/申请主设备号
静态
动态
b. 创建设备文件
手动
自动
c. 初始化
3. 模块卸载函数
做加载函数反操作
4. 模块许可声明
5. 构建struct file_operations结构体
6. 实现xxx_open, xxx_read...函数
1.9 用户空间与内核空间数据进行交互
=======================================
1. 在系统中为指针分配空间
/*
** param1: 大小
** param2: 标号GFP_KERNEL: 表示分配不成功,则休眠
** GFP_ATOMIC:表示分配不成功,则不休眠
*/
s5pc100_dev = kmalloc(sizeof(struct dev_device),GFP_KERNEL);
2. 数据交互函数
/*获取用户空间数据*/
/*
**param1: 目标
**param2: 源
**返回值: 失败返回剩余没有拷贝成功的字节数,成功返回0
*/
ret=copy_from_user(&s5pc100_dev->val,buf,count);
/*上报数据给用户空间*/
ret=copy_to_user(buf,&s5pc100_dev->val,count);
浙公网安备 33010602011771号