Fork me on GitHub

linux驱动:[2]字符设备驱动memdev(cdev结构解析)

Linux 内存模拟字符设备 驱动程序

测试平台: Xunlong Orange Pi Zero

代码一览(解析见下方)

驱动程序以及Makefile如下:

  • memdev.c:
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#ifndef MEMDEV_MAJOR
#define MEMDEV_MAJOR 254
#endif
#ifndef MEMDEV_NR_DEVS
#define MEMDEV_NR_DEVS 2
#endif
#ifndef MEMDEV_SIZE
#define MEMDEV_SIZE 4096
#endif
struct mem_dev {
char *data;
unsigned long size;
};
static int mem_major = MEMDEV_MAJOR;
module_param(mem_major, int, S_IRUGO);
struct mem_dev *mem_devp;
struct cdev cdev;
int mem_open(struct inode *inode, struct file *filp)
{
struct mem_dev *dev;
int num = MINOR(inode->i_rdev);
if (num >= MEMDEV_NR_DEVS)
return -ENODEV;
dev = &mem_devp[num];
filp->private_data = dev;
return 0;
}
int mem_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *poss)
{
unsigned long p = *poss;
unsigned int count = size;
int ret = 0;
struct mem_dev *dev = filp->private_data;
if (p >= MEMDEV_SIZE)
return 0;
if (count > MEMDEV_SIZE-p)
count = MEMDEV_SIZE-p;
if(copy_to_user(buf, (void*)(dev->data + p), count)) {
ret = -EFAULT;
} else {
*poss += count;
ret = count;
printk(KERN_INFO "read %d bytes from %lu\n", count, p);
}
return ret;
}
static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *poss)
{
unsigned long p = *poss;
unsigned int count = size;
int ret = 0;
struct mem_dev *dev = filp->private_data;
if (p >= MEMDEV_SIZE)
return 0;
if (count > MEMDEV_SIZE-p)
count = MEMDEV_SIZE - p;
if (copy_from_user(dev->data + p, buf, count)) {
ret = -EFAULT;
} else {
*poss += count;
ret = count;
printk(KERN_INFO "write %d bytes from %lu\n", count, p);
}
return ret;
}
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
{
loff_t newpos;
switch (whence) {
case 0:
newpos = offset;
break;
case 1:
newpos = filp->f_pos + offset;
break;
case 2:
newpos = MEMDEV_SIZE - 1 + offset;
break;
default:
return -EINVAL;
}
if ((newpos < 0) || (newpos > MEMDEV_SIZE))
return -EINVAL;
filp->f_pos = newpos;
return newpos;
}
static const struct file_operations mem_fops =
{
.owner = THIS_MODULE,
.llseek = mem_llseek,
.read = mem_read,
.write = mem_write,
.open = mem_open,
.release = mem_release,
};
static int memdev_init(void)
{
int result;
int i;
dev_t devno = MKDEV(mem_major, 0);
if (mem_major)
result = register_chrdev_region(devno, 2, "memdev");
else {
result = alloc_chrdev_region(&devno, 0, 2, "memdev");
mem_major = MAJOR(devno);
}
if (result < 0)
return result;
cdev_init(&cdev, &mem_fops);
cdev.owner = THIS_MODULE;
cdev.ops = &mem_fops;
cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);
mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
if (!mem_devp) {
result = -ENOMEM;
goto fail_malloc;
}
memset(mem_devp, 0, MEMDEV_NR_DEVS * sizeof(struct mem_dev));
for (i = 0; i < MEMDEV_NR_DEVS; i++) {
mem_devp[i].size = MEMDEV_SIZE;
mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
memset(mem_devp[i].data, 0, MEMDEV_SIZE);
}
printk("memdev init success\n");
return 0;
fail_malloc:
unregister_chrdev_region(devno, 2);
return result;
}
static void memdev_exit(void)
{
cdev_del(&cdev);
kfree(mem_devp);
unregister_chrdev_region(MKDEV(mem_major, 0), 2);
printk("memdev exit success\n");
}
MODULE_AUTHOR("Ziping Chen <techping.chan@gmail.com>");
MODULE_LICENSE("GPL");
module_init(memdev_init);
module_exit(memdev_exit);
  • Makefile:
1
2
3
4
5
6
7
8
9
10
obj-m := memdev.o #编译进模块
KERNELDIR := /lib/modules/4.11.0-rc4-00064-g89970a0-dirty/build #此处为linux内核库目录
PWD := $(shell pwd) #获取当前目录
OUTPUT := $(obj-m) $(obj-m:.o=.ko) $(obj-m:.o=.mod.o) $(obj-m:.o=.mod.c) modules.order Module.symvers
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf $(OUTPUT)

在shell中使用以下命令装载驱动程序: (这里以主设备号为181进行测试)

1
2
3
$ make
$ insmod memdev.ko mem_major=181
$ mknod /dev/memdev0 c 181 0

使用linux c进行测试:

  • memapp.c:
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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main()
{
int fd;
char buf[4096];
strcpy(buf,"memory simulate char device test...\n");
printf("original buf:%s\n",buf);
fd = open("/dev/memdev0",O_RDWR);
if (fd == -1) {
printf("open memdev failed!\n");
return -1;
}
write(fd, buf, sizeof(buf));
lseek(fd, 0, SEEK_SET);
strcpy(buf, "nothing here");
read(fd, buf, sizeof(buf));
printf("read buf:%s\n", buf);
return 0;
}

进行编译、测试:

1
$ gcc -o memapp memapp.c

实验成功!


代码解析:

一、分配设备号

1
2
3
4
5
6
if (mem_major)
result = register_chrdev_region(devno, 2, "memdev");
else {
result = alloc_chrdev_region(&devno, 0, 2, "memdev");
mem_major = MAJOR(devno);
}

如果定义的参数mem_major不为0(上面测试用了181),则进行静态分配

1
register_chrdev_region(devno, 2, "memdev");//静态分配设备号为devno的设备

如果mem_major为0则进行动态分配

1
alloc_chrdev_region(&devno, 0, 2, "memdev");//动态分配主设备号为devno,次设备号为0的设备

二、初始化cdev结构

/linux/include/linux/cdev.h:

1
2
3
4
5
6
7
8
struct cdev {
struct kobject kobj;//每个 cdev 都是一个 kobject
struct module *owner;//指向实现驱动的模块
const struct file_operations *ops;//操纵这个字符设备文件的方法
struct list_head list;//与 cdev 对应的字符设备文件的 inode->i_devices 的链表头
dev_t dev;//起始设备编号
unsigned int count;//设备范围号大小
};

一个 cdev 一般它有两种定义初始化方式:静态的和动态的。

静态内存定义初始化:

1
2
3
struct cdev my_cdev;
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;

动态内存定义初始化:

1
2
3
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &fops;
my_cdev->owner = THIS_MODULE;

两种使用方式的功能是一样的,只是使用的内存区不一样,一般视实际的数据结构需求而定。

源码分析:

1
2
3
4
5
6
7
8
9
struct cdev *cdev_alloc(void)
{
struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
if (p) {
INIT_LIST_HEAD(&p->list);
kobject_init(&p->kobj, &ktype_cdev_dynamic);
}
return p;
}
1
2
3
4
5
6
7
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}

可见,两个函数完成都功能基本一致。

三、添加cdev

初始化 cdev 后,需要把它添加到系统中去。为此可以调用 cdev_add() 函数。传入 cdev 结构的指针,起始设备编号,以及设备编号范围。

1
2
3
4
5
6
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
p->dev = dev;
p->count = count;
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}

简单地说,设备驱动程序通过调用cdev_add把它所管理的设备对象的指针嵌入到一个类型为struct probe的节点之中,然后再把该节点加入到cdev_map所实现的哈希链表中。

对系统而言,当设备驱动程序成功调用了cdev_add之后,就意味着一个字符设备对象已经加入到了系统,在需要的时候,系统就可以找到它。对用户态的程序而言,cdev_add调用之后,就已经可以通过文件系统的接口调用到我们的驱动程序。

四、卸载cdev

当一个字符设备驱动不再需要的时候(比如模块卸载),就可以用 cdev_del() 函数来释放 cdev 占用的内存。

1
2
3
4
5
void cdev_del(struct cdev *p)
{
cdev_unmap(p->dev, p->count);
kobject_put(&p->kobj);
}

其中 cdev_unmap() 调用 kobj_unmap() 来释放 cdev_map 散列表中的对象。kobject_put() 释放 cdev 结构本身。