1、 程序要实现的功能
使用一个虚拟的设备来模拟一个字符设备,该虚拟设备为一块内存。在内存中开辟一块4K的空间,将4K的内存看作一个字符设备,编写一个字符设备驱动程序来操作这块内存。
2、实验环境
内核版本:linux-2.6.35-32
3、字符设备驱动程序设计步骤
Step1:设备注册
在linux 2.6内核中,字符设备使用struct cdev来描述。字符设备的注册可分为如下3个步骤:
1.分配cdev
2.初始化cdev
3.添加cdev
step2:实现设备所支持的操作
1.int (*open)(struct inode *, struct file *)
在设备文件上的第一个操作,并不要求驱动程序一定要实现这个方法。如果该项为NULL,设备的打开操作永远成功。
2.void (*release)(struct inode *, struct file *)当设备文件被关闭时调用这个操作。与open相仿,release也可以没有。
3.ssize_t (*read) (struct file *, char __user *, size_t, loff_t *)从设备中读取数据。
4. ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *)向设备发送数据。
5.off_t (*llseek) (struct file *, loff_t, int)修改文件的当前读写位置,并将新位置作为返回值。
step3:设备注消
字符设备的注销使用cdev_del函数来完成。
int cdev_del(struct cdev *p)
参数:
p:要注销的字符设备结构
4、程序源码如下所示
4.1 memdev.h 源代码
1 #ifndef _MEMDEV_H_
2 #define _MEMDEV_H_
3
4 #ifndef MEMDEV_MAJOR
5 #define MEMDEV_MAJOR 260 /*预设的mem的主设备号*/
6 #endif
7
8 #ifndef MEMDEV_NR_DEVS
9 #define MEMDEV_NR_DEVS 2 /*设备数*/
10 #endif
11
12 #ifndef MEMDEV_SIZE
13 #define MEMDEV_SIZE 4096
14 #endif
15
16 /*mem设备描述结构体*/
17 struct mem_dev
18 {
19 char *data; //该指针模拟设备的地址
20 unsigned long size; //模拟设备的大小
21 };
22
23 #endif /*_MEMDEV_H_*/
4.2 memdev.c 源代码
1 #include<linux/module.h>
2 #include<linux/types.h>
3 #include<linux/fs.h>
4 #include<linux/errno.h>
5 #include<linux/mm.h>
6 #include<linux/sched.h>
7 #include<linux/init.h>
8 #include<linux/cdev.h>
9 #include<linux/slab.h>
10 #include<asm/io.h>
11 #include<asm/system.h>
12 #include<asm/uaccess.h>
13 #include "memdev.h"
14
15 static mem_major = MEMDEV_MAJOR;
16
17 module_param(mem_major, int, S_IRUGO);
18
19 struct mem_dev *mem_devp;/*设备描述结构体指针,清查看mem_dev.h中的定义*/
20
21 /*静态分配字符设备描述符cdev结构*/
22 struct cdev cdev;
23
24 /*文件打开函数*/
25 int mem_open(struct inode *inode, struct file *filp)
26 {
27 struct mem_dev *dev;
28
29 /*获取次设备号*/
30 int num = MINOR(inode->i_rdev);
31 if(num >= MEMDEV_NR_DEVS)
32 return -ENODEV;
33 dev = &mem_devp[num];
34
35 /*将设备描述结构指针复制给文件私有数据指针*/
36 filp->private_data = dev;
37 return 0;
38 }
39
40 /*文件释放函数*/
41 int mem_release(struct inode *inode, struct file *filp)
42 {
43 return 0;
44 }
45
46 /*读函数*/
47 static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
48 {
49 unsigned long p = *ppos;
50 unsigned int count = size;
51 int ret = 0;
52 /*获得设备结构体指针*/
53 struct mem_dev *dev = filp->private_data;
54
55 /*判断读位置是否有效*/
56 if(p >= MEMDEV_SIZE)
57 return 0;
58 if(count > MEMDEV_SIZE - p)
59 count = MEMDEV_SIZE - p;
60
61 /*读数据到用户空间*/
62 if(copy_to_user(buf, (void*)(dev->data + p), count))
63 {
64 ret = -EFAULT;
65 }
66 else
67 {
68 *ppos += count;
69 ret = count;
70
71 printk(KERN_INFO "read %d btyes(s) from %d\n", count, p);
72 }
73 return ret;
74 }
75
76 /*写函数*/
77 static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *p pos)
78 {
79 unsigned long p = *ppos;
80 unsigned int count = size;
81 int ret = 0;
82 /*获得设备结构体*/
83 struct mem_dev *dev = filp->private_data;
84
85 /*分析和获取有效的写长度*/
86 if(p >= MEMDEV_SIZE)
87 return 0;
88 if(count > MEMDEV_SIZE - p)
89 count = MEMDEV_SIZE - p;
90
91 /*从用户空间写入数据*/
92 if(copy_from_user(dev->data + p, buf, count))
93 ret = -EFAULT;
94 else
95 {
96 *ppos += count;
97 ret = count;
98
99 printk(KERN_INFO "write %d btyes(s) from %d\n", count, p);
100 }
101 return ret;
102
103 }
104
105 /*seek 文件定位函数*/
106 static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
107 {
108 loff_t newpos;
109 switch(whence)
110 {
111 case 0: /*SEEK_SET*/
112 newpos = offset;
113 break;
114
115 case 1: /*SEEK_CUR*/
116 newpos = filp->f_pos + offset;
117 break;
118
119 case 2: /*SEEK_END*/
120 newpos = MEMDEV_SIZE -1 + offset;
121 break;
122
123 default: /* can't happen */
124 return -EINVAL;
125 }
126 if((newpos < 0) || (newpos > MEMDEV_SIZE))
127 return -EINVAL;
128
129 filp->f_pos = newpos;
130 return newpos;
131 }
132
133 /*文件操作结构体*/
134 static const struct file_operations mem_fops =
135 {
136 .owner = THIS_MODULE,
137 .llseek = mem_llseek,
138 .read = mem_read,
139 .write = mem_write,
140 .open = mem_open,
141 .release = mem_release,
142 };
143
144 /*设备驱动模块加载函数*/
145 static int memdev_init(void)
146 {
147 int result;
148 int i;
149
150 dev_t devno = MKDEV(mem_major, 0);
151
152 /*静态申请设备号*/
153 if (mem_major)
154 result = register_chrdev_region(devno, 2, "memdev");
155 else /*动态分配设备号*/
156 {
157 result = alloc_chrdev_region(&devno, 0, 2, "memdev");
158 mem_major = MAJOR(devno);
159 }
160
161 if(result < 0)
162 return result;
163
164 /*程序开始处已静态分配了cdev结构*/
165 /*初始化cdev结构*/
166 cdev_init(&cdev, &mem_fops);
167 cdev.owner = THIS_MODULE;
168 cdev.ops = &mem_fops;
169
170 /*注册字符设备*/
171 cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);
172
173 /*为设备描述结构分配内存*/
174 mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
175 if(!mem_devp) /*申请失败*/
176 {
177 result = -ENOMEM;
178 goto fail_malloc;
179 }
180 memset(mem_devp, 0, sizeof(struct mem_dev));
181
182 /*为设备分配内存*/
183 for(i>0; i < MEMDEV_NR_DEVS; i++)
184 {
185 mem_devp[i].size = MEMDEV_SIZE;
186 mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
187 memset(mem_devp[i].data, 0, MEMDEV_SIZE);
188 }
189
190 return 0;
191
192 fail_malloc:
193 unregister_chrdev_region(devno, 1); /*注销设备号*/
194 }
195
196 /*模块卸载函数*/
197 static void memdev_exit(void)
198 {
199 cdev_del(&cdev); /*注销设备*/
200 kfree(mem_devp); /*释放设备结构体内存*/
201 unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/
202 }
203
204 MODULE_AUTHOR("yinjiabin");
205 MODULE_LICENSE("GPL");
206
207 module_init(memdev_init);
208 module_exit(memdev_exit);
4.3 对应的Makefile文件
ifneq ($(KERNELRELEASE),)
obj-m := memdev.o
else
KDIR := /lib/modules/2.6.35-32-generic/build(指定自己的内核所在路径)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o *.mod *.mod.c *.symvers
endif
4.4 应用程序app-mem.c源码
#include<stdio.h>
#include<string.h>
int main()
{
FILE *fp0 = NULL;
char Buf[4096];
/*初始化Buf*/
strcpy(Buf,"Mem is char dev!");
printf("BUF: %s\n",Buf);
/*打开设备文件*/
fp0 = fopen("/dev/memdev0","r+");
if(fp0 == NULL)
{
printf("Open Memdev0 Error!\n");
return -1;
}
/*写入设备*/
fwrite(Buf, sizeof(Buf), 1, fp0);
/*重新定位文件位置*/
fseek(fp0, 0, SEEK_SET);
/*清除Buf*/
strcpy(Buf,"Buf is NULL!");
printf("BUF: %s\n",Buf);
/*读出设备*/
fread(Buf, sizeof(Buf), 1, fp0);
/*检测结果*/
printf("BUF: %s\n",Buf);
return 0;
}
~
5、代码分析:
该程序采用的是内核模块编写方式,对应上面提到的字符设备驱动设计步骤来分析该代码。
5.1 首先分析memdev_init()设备模块加载函数,因为它是第一个运行的函数。该函数主要功能是完成设计字符设备驱动的Step1:设备注册
1. 分配cdev
2. 初始化cdev
3. 添加cdev
因为我用的是虚拟设备,所以还要为设备描述结构和设备分配内存。
static int memdev_init(void)
146 {
147 int result;
148 int i;
149 /*定义一个设备号,并利用主、次设备号构造该设备号*/
150 dev_t devno = MKDEV(mem_major, 0); // MKDEV函数实现构造设备号。 mem_major对应宏MEMDEV_MAJOR 254,在memdev.h中定义
151
152 /*静态申请设备号,mem_major>0*/
153 if (mem_major)
154 result = register_chrdev_region(devno, 2, "memdev");
155 else /*动态分配设备号mem_major==0*/
156 {
157 result = alloc_chrdev_region(&devno, 0, 2, "memdev"); //将申请的主设备号存放在devno中
158 mem_major = MAJOR(devno); //从devno中提取主设备号
159 }
160
161 if(result < 0)
162 return result;
163
164 /*(分配cdev)程序开始处已静态分配了cdev结构,查看mem_dev.c的第22行*/
165 /*(初始化cdev)初始化字符设备描述符cdev结构*/
166 cdev_init(&cdev, &mem_fops);
167 cdev.owner = THIS_MODULE;
168 cdev.ops = &mem_fops;
169
170 /*(添加cdev)注册字符设备*/
171 cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS); //cdev_add()执行后,驱动程序就完成了在内核中的注册
172
173 /*为设备描述结构分配内存*/
174 mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
175 if(!mem_devp) /*申请失败*/
176 {
177 result = -ENOMEM;
178 goto fail_malloc;
179 }
180 memset(mem_devp, 0, sizeof(struct mem_dev));
181
182 /*为设备分配内存*/
183 for(i>0; i < MEMDEV_NR_DEVS; i++)
184 {
185 mem_devp[i].size = MEMDEV_SIZE;
186 mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
187 memset(mem_devp[i].data, 0, MEMDEV_SIZE);
188 }
189
190 return 0;
191
192 fail_malloc:
193 unregister_chrdev_region(devno, 1); /*注销设备号*/
194 }
5.2 分析file_operation,该函数的主要功能是实现设计字符设备驱动的step2:实现设备所支持的操作
文件操作结构体
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,
};
文件操作结构体,他其实就是一张对应关系表,把应用程序中对设备文件的操作转化成驱动对应的操作函数。
5.2.1 首先分析open()函数
25 int mem_open(struct inode *inode, struct file *filp)
26 {
27 struct mem_dev *dev;
28
29 /*获取次设备号*/
30 int num = MINOR(inode->i_rdev);
31 if(num >= MEMDEV_NR_DEVS)
32 return -ENODEV;
33 dev = &mem_devp[num];//根据次设备号获取设备描述结构指针
34
35 /*将设备描述结构指针复制给文件私有数据指针*/
36 filp->private_data = dev;
37 return 0;
38 }
39
注意考虑: 为什么不能在read()函数中直接获取设备结构体指针(&mem_dev[num])?
因为read()函数没有inode参数,所以只能通过open()函数传递设备结构体指针。
5.2.2 分析read()函数
46 /*读函数*/
47 static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
48 {
49 unsigned long p = *ppos;
50 unsigned int count = size;
51 int ret = 0;
52 /*获得设备结构体指针*/
53 struct mem_dev *dev = filp->private_data;
54
55 /*判断读位置是否有效*/
56 if(p >= MEMDEV_SIZE)
57 return 0;
58 if(count > MEMDEV_SIZE - p)
59 count = MEMDEV_SIZE - p;
60
61 /*读数据到用户空间*/
62 if(copy_to_user(buf, (void*)(dev->data + p), count))
63 {
64 ret = -EFAULT;
65 }
66 else
67 {
68 *ppos += count;
69 ret = count;
70
71 printk(KERN_INFO "read %d btyes(s) from %d\n", count, p);
72 }
73 return ret;
74 }
75
5.2.3 分析写函数
76 /*写函数*/
77 static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *p pos)
78 {
79 unsigned long p = *ppos;
80 unsigned int count = size;
81 int ret = 0;
82 /*获得设备结构体*/
83 struct mem_dev *dev = filp->private_data;
84
85 /*分析和获取有效的写长度*/
86 if(p >= MEMDEV_SIZE)
87 return 0;
88 if(count > MEMDEV_SIZE - p)
89 count = MEMDEV_SIZE - p;
90
91 /*从用户空间写入数据*/
92 if(copy_from_user(dev->data + p, buf, count))
93 ret = -EFAULT;
94 else
95 {
96 *ppos += count;
97 ret = count;
98
99 printk(KERN_INFO "write %d btyes(s) from %d\n", count, p);
100 }
101 return ret;
102
103 }
5.2.4 分析文件定位函数
105 /*seek 文件定位函数*/
106 static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
107 {
108 loff_t newpos;
109 switch(whence)
110 {
111 case 0: /*SEEK_SET*/
112 newpos = offset;
113 break;
114
115 case 1: /*SEEK_CUR*/
116 newpos = filp->f_pos + offset;
117 break;
118
119 case 2: /*SEEK_END*/
120 newpos = MEMDEV_SIZE -1 + offset;
121 break;
122
123 default: /* can't happen */
124 return -EINVAL;
125 }
126 if((newpos < 0) || (newpos > MEMDEV_SIZE))
127 return -EINVAL;
128
129 filp->f_pos = newpos;
130 return newpos;
131 }
5.3 分析memdev_exit()模块卸载函数,对应设计字符设备驱动的step3:设备注消
196 /*模块卸载函数*/
197 static void memdev_exit(void)
198 {
199 cdev_del(&cdev); /*注销设备*/
200 kfree(mem_devp); /*释放设备结构体内存*/
201 unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/
202 }