linux字符设备驱动

字符驱动设备相关的数据结构

cdev结构体

1
2
3
4
5
6
7
8
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
} __randomize_layout;

以上结构体是对字符设备的抽象,其结构非常简单,包含了sysfs相关的kobj,驱动相关的owner,操作结构相关的ops,设备号dev和次设备号count,同时有一个双向链表用于链接所有此类字符设备。

字符设备相关操作接口

1
2
3
MAJOR(dev_t dev)
MINOR(dev_t dev)
MKDEV(int major, int minor)

以上宏用于从设备号获得主设备号与次设备号、从主设备号与次设备号或的设备号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/* 参数:字符设备指针,文件操作指针
* 功能:初始化传入的字符设备,包括复制文件操作指针与调用kobject和链表的初始化接口,其它部分初始化为0
**/
void cdev_init(struct cdev *, const struct file_operations *);

/* 参数:void
* 功能:创建一个cdev结构体并简单初始化后返回其指针
**/
struct cdev *cdev_alloc(void);

/* 参数:字符设备指针
* 功能:减少该字符设备相关的引用计数,包括kobject和module
**/
void cdev_put(struct cdev *p);

/* 参数:字符设备指针,主设备号,次设备号
* 功能:添加一个字符设备到系统
**/
int cdev_add(struct cdev *, dev_t, unsigned);

/* 参数:字符设备指针,kobj
* 功能:为一个字符设备设置一个父kobject,在sysfs上表现为一个父目录
**/
void cdev_set_parent(struct cdev *p, struct kobject *kobj);

/* 参数:字符设备指针,设备指针
* 功能:添加一个字符设备到系统和添加相应设备到系统
**/
int cdev_device_add(struct cdev *cdev, struct device *dev);

/* 参数:字符设备指针,设备指针
* 功能:删除一个字符设备从系统和删除相应设备从系统
**/
void cdev_device_del(struct cdev *cdev, struct device *dev);

/* 参数:字符设备指针
* 功能:从系统删除相应字符设备
**/
void cdev_del(struct cdev *);

/* 参数:inode节点
* 功能:从inode中将i_devices从链表中删除,i_cdev赋值为0,i_mapping=i_data
**/
void cd_forget(struct inode *);

/* 参数:起始设备设备号,次设备号,设备或驱动名
* 功能:注册一个设备号
**/
int register_chrdev_region(dev_t from, unsigned count, const char *name);

/* 参数:设备号指针,起始次设备号,次设备号,设备或驱动名
* 功能:分配一个设备号
**/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);

/* 参数:主设备号,次设备号
* 功能:注销一个设备号
**/
void unregister_chrdev_region(dev_t from, unsigned count);

file_operations结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
struct file_operations {
struct module *owner;
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 (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iopoll)(struct kiocb *kiocb, bool spin);
int (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
unsigned long mmap_supported_flags;
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
loff_t, size_t, unsigned int);
loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
struct file *file_out, loff_t pos_out,
loff_t len, unsigned int remap_flags);
int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;

file_operations相当于一个接口类,定义了一个文件所应具备的所有操作接口,不同的文件或设备通过继承来实现对应的接口函数,就能够通过这种统一标准的接口向上层通过系统调用来提供服务。

字符设备驱动的组成

字符设备驱动的加载和卸载

任何设备驱动都必须要实现加载和卸载函数,其实现的具体过程可能不同,但这两个接口是必须要有的,这是将设备驱动投入内核和从内核中离开的统一接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct xxx_dev_t {
struct cdev cdev;
...
} xxx_dev;

static int __init xxx_init(void)
{
...
cdev_init(&xxx_dev.cdev, &xxx_ops);
xxx_dev.cdev.owner = THIS_MODULE;

if (xxx_major) {
register_chrdev_region(xxx_dev_no, 1, DEV_NAME);
} else {
alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME);
}

ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1);
...
}

static int __exit xxx_exit(void)
{
unregister_chrdev_region(xxx_dev_no, 1);
cdev_del(&xxx_dev.cdev);
...
}

字符设备驱动的file_operations接口实现

file_operations结构体中的成员函数是字符设备驱动于内核虚拟文件系统的接口,是用户空间进行文件相关系统调用的最终落实者,其中read(),write(),ioctl()是三个最常见的接口,也是大多数字符设备都会实现的。在驱动中实现这些接口的形式如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
ssize_t xxx_read(struct file *filp, char __user *buf, size_t count, loff_t *f_ops)
{
...
copy_to_user(buf, ..., ...);
...
}

ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_ops)
{
...
copy_from_user(..., buf, ...);
...
}

long xxx_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
...
switch(cmd) {
case XXX_CMD1:
...
break;
case XXX_CMD2:
...
break;
default:
return - ENOTTY;
}
return 0;
}

struct file_operations xxx_fops = {
.owner = THIS_MODULE,
.read = xxx_read,
.write = xxx_write,
.unlocked_ioctl = xxx_ioctl,
...
}

以上这些接口的实现中,关键部分是内核空间于用户空间的界面,这儿包含了两种不同内存空间的数据交换和合法性检查。

其中包含合法性检查的内存数据交换接口包括:

1
2
3
4
5
get_user(x, ptr)
put_user(x, ptr)

static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n);
static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n);

不包含合法性检查的接口:

1
2
__get_user(x, ptr)
__put_user(x, ptr)

对于不包含合法性检查的接口在使用前必须进行合法性检查,即如下所示:

1
2
3
4
5
...
if (!access_ok(addr, size))
return -EFAULT;
__put_user(x, addr);
...

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 yxhlfx@163.com

文章标题:linux字符设备驱动

本文作者:红尘追风

发布时间:2016-05-07, 17:53:18

原始链接:http://www.micernel.com/2016/05/07/linux%E5%AD%97%E7%AC%A6%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录