14-使用文件私有数据
6.3.6 使用文件私有数据
6.3.1~6.3.5节给出的代码完整地实现了预期的globalmem雏形,在其代码中,为globalmem设备结构体globalmem_dev定义了全局实例dev(见代码清单6.7第25行),而globalmem的驱动中read()、write()、ioctl()、llseek()函数都针对dev进行操作。
实际上,大多数Linux驱动工程师遵循一个“潜规则”,那就是将文件的私有数据private_data指向设备结构体,在read()、write()、ioctl()、llseek()等函数通过private_data访问设备结构体。
这个时候,我们要将各函数进行少量的修改,为了让读者朋友建立字符设备驱动的全貌视图,代码清单6.17列出了完整的使用文件私有数据的globalmem的设备驱动,本程序位于虚拟机/home/lihacker/develop/svn/ldd6410-read-only/training/kernel/drivers/globalmem/ch6目录。
代码清单6.17 使用文件私有数据的globalmem的设备驱动
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 <asm/io.h>
10 #include <asm/system.h>
11 #include <asm/uaccess.h>
12
13 #define GLOBALMEM_SIZE 0x1000 /全局内存最大4KB/
14 #define MEM_CLEAR 0x1 /清零全局内存/
15 #define GLOBALMEM_MAJOR 250 /预设的globalmem的主设备号/
16
17 static int globalmem_major = GLOBALMEM_MAJOR;
18 /globalmem设备结构体/
19 struct globalmem_dev {
20 struct cdev cdev; /cdev结构体/
21 unsigned char mem[GLOBALMEM_SIZE]; /全局内存/
22 };
23
24 struct globalmem_dev globalmem_devp; /设备结构体指针*/
25 /文件打开函数/
26 int globalmem_open(struct inode inode, struct file filp)
27 {
28 / 将设备结构体指针赋值给文件私有数据指针 /
29 filp->private_data = globalmem_devp;
30 return 0;
31 }
32 /文件释放函数/
33 int globalmem_release(struct inode inode, struct file filp)
34 {
35 return 0;
36 }
37
38 / ioctl设备控制函数 /
39 static int globalmem_ioctl(struct inode inodep, struct file filp, unsigned
40 int cmd, unsigned long arg)
41 {
42 struct globalmem_dev dev = filp->private_data;/ 获得设备结构体指针 */
43
44 switch (cmd) {
45 case MEM_CLEAR:
46 memset(dev->mem, 0, GLOBALMEM_SIZE);
47 printk(KERN_INFO "globalmem is set to zero\n");
48 break;
49
50 default:
51 return - EINVAL;
52 }
53
54 return 0;
55 }
56
57 /读函数/
58 static ssize_t globalmem_read(struct file filp, char __user buf, size_t size,
59 loff_t *ppos)
60 {
61 unsigned long p = *ppos;
62 unsigned int count = size;
63 int ret = 0;
64 struct globalmem_dev dev = filp->private_data;/ 获得设备结构体指针 */
65
66 /分析和获取有效的写长度/
67 if (p >= GLOBALMEM_SIZE)
68 return 0;
69 if (count > GLOBALMEM_SIZE - p)
70 count = GLOBALMEM_SIZE - p;
71
72 /内核空间→用户空间/
73 if (copy_to_user(buf, (void *)(dev->mem + p), count)) {
74 ret = - EFAULT;
75 } else {
76 *ppos += count;
77 ret = count;
78
79 printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p);
80 }
81
82 return ret;
83 }
84
85 /写函数/
86 static ssize_t globalmemwrite(struct file *filp, const char _user *buf,
87 size_t size, loff_t *ppos)
88 {
89 unsigned long p = *ppos;
90 unsigned int count = size;
91 int ret = 0;
92 struct globalmem_dev dev = filp->private_data;/ 获得设备结构体指针 */
93
94 /分析和获取有效的写长度/
95 if (p >= GLOBALMEM_SIZE)
96 return 0;
97 if (count > GLOBALMEM_SIZE - p)
98 count = GLOBALMEM_SIZE - p;
99
100 /用户空间→内核空间/
101 if (copy_from_user(dev->mem + p, buf, count))
102 ret = - EFAULT;
103 else {
104 *ppos += count;
105 ret = count;
106
107 printk(KERN_INFO "written %u bytes(s) from %lu\n", count, p);
108 }
109
110 return ret;
111 }
112
113 / seek文件定位函数 /
114 static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
115 {
116 loff_t ret = 0;
117 switch (orig) {
118 case 0: /相对文件开始位置偏移/
119 if (offset < 0) {
120 ret = - EINVAL;
121 break;
122 }
123 if ((unsigned int)offset > GLOBALMEM_SIZE) {
124 ret = - EINVAL;
125 break;
126 }
127 filp->f_pos = (unsigned int)offset;
128 ret = filp->f_pos;
129 break;
130 case 1: /相对文件当前位置偏移/
131 if ((filp->f_pos + offset) > GLOBALMEM_SIZE) {
132 ret = - EINVAL;
133 break;
134 }
135 if ((filp->f_pos + offset) < 0) {
136 ret = - EINVAL;
137 break;
138 }
139 filp->f_pos += offset;
140 ret = filp->f_pos;
141 break;
142 default:
143 ret = - EINVAL;
144 break;
145 }
146 return ret;
147 }
148
149 /文件操作结构体/
150 static const struct file_operations globalmem_fops = {
151 .owner = THIS_MODULE,
152 .llseek = globalmem_llseek,
153 .read = globalmem_read,
154 .write = globalmem_write,
155 .ioctl = globalmem_ioctl,
156 .open = globalmem_open,
157 .release = globalmem_release,
158 };
159
160 /初始化并注册cdev/
161 static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
162 {
163 int err, devno = MKDEV(globalmem_major, index);
164
165 cdev_init(&dev->cdev, &globalmem_fops);
166 dev->cdev.owner = THIS_MODULE;
167 err = cdev_add(&dev->cdev, devno, 1);
168 if (err)
169 printk(KERN_NOTICE "Error %d adding globalmem %d", err, index);
170 }
171
172 /设备驱动模块加载函数/
173 int globalmem_init(void)
174 {
175 int result;
176 dev_t devno = MKDEV(globalmem_major, 0);
177
178 / 申请设备号/
179 if (globalmem_major)
180 result = register_chrdev_region(devno, 1, "globalmem");
181 else { / 动态申请设备号 /
182 result = alloc_chrdev_region(&devno, 0, 1, "globalmem");
183 globalmem_major = MAJOR(devno);
184 }
185 if (result < 0)
186 return result;
187
188 / 动态申请设备结构体的内存/
189 globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
190 if (!globalmem_devp) { /申请失败/
191 result = - ENOMEM;
192 goto fail_malloc;
193 }
194
195 memset(globalmem_devp, 0, sizeof(struct globalmem_dev));
196
197 globalmem_setup_cdev(globalmem_devp, 0);
198 return 0;
199
200 fail_malloc:
201 unregister_chrdev_region(devno, 1);
202 return result;
203 }
204
205 /模块卸载函数/
206 void globalmem_exit(void)
207 {
208 cdev_del(&globalmem_devp->cdev); /注销cdev/
209 kfree(globalmem_devp); /释放设备结构体内存/
210 unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);/释放设备号/
211 }
212
213 MODULE_AUTHOR("Barry Song [email protected]");
214 MODULE_LICENSE("Dual BSD/GPL");
215
216 module_param(globalmem_major, int, S_IRUGO);
217
218 module_init(globalmem_init);
219 module_exit(globalmem_exit);
除了在globalmem_open()函数中通过filp->private_data = globalmem_devp语句(见第29行)将设备结构体指针赋值给文件私有数据指针并在globalmem_read()、globalmem_write()、globalmem_llseek()和globalmem_ioctl()函数中通过struct globalmem_dev *dev = filp->private_data语句获得设备结构体指针并使用该指针操作设备结构体外,代码清单6.17与代码清单6.7~6.15的程序基本相同。
读者朋友们,这个时候,请您翻回到本书的第1章,再次阅读代码清单1.4,即Linux下LED的设备驱动,是否豁然开朗?
代码清单6.17仅仅作为使用private_data的范例,实际上,在这个程序中使用private_data没有任何意义,直接访问全局变量globalmem_devp会更加结构清晰。如果globalmem不只包括一个设备,而是同时包括两个或两个以上的设备,采用private_data的优势就会集中显现出来。
在不对代码清单6.17中的globalmem_read()、globalmem_write()、globalmem_ioctl()等重要函数及globalmem_fops结构体等数据结构进行任何修改的前提下,只是简单地修改globalmem_init()、globalmem_exit()和globalmem_open(),就可以轻松地让globalmem驱动中包含两个同样的设备(次设备号分别为0和1),如代码清单6.18所示。
代码清单6.18 支持2个globalmem设备的globalmem驱动
1 /文件打开函数/
2 int globalmem_open(struct inode inode, struct file filp)
3 {
4 /将设备结构体指针赋值给文件私有数据指针/
5 struct globalmem_dev *dev;
6
7 dev = container_of(inode->i_cdev,struct globalmem_dev,cdev);
8 filp->private_data = dev;
9 return 0;
10 }
11
12 /设备驱动模块加载函数/
13 int globalmem_init(void)
14 {
15 int result;
16 dev_t devno = MKDEV(globalmem_major, 0);
17
18 / 申请设备号/
19 if (globalmem_major)
20 result = register_chrdev_region(devno, 2, "globalmem");
21 else { / 动态申请设备号 /
23 result = alloc_chrdev_region(&devno, 0, 2, "globalmem");
24 globalmem_major = MAJOR(devno);
25 }
26 if (result < 0)
27 return result;
28
29 / 动态申请两个设备结构体的内存/
30 globalmem_devp = kmalloc(2*sizeof(struct globalmem_dev), GFP_KERNEL);
31 if (!globalmem_devp) { /申请失败/
33 result = - ENOMEM;
34 goto fail_malloc;
35 }
36 memset(globalmem_devp, 0, 2*sizeof(struct globalmem_dev));
37
38 globalmem_setup_cdev(&globalmem_devp[0], 0);
39 globalmem_setup_cdev(&globalmem_devp[1], 1);
40 return 0;
41
42 fail_malloc: unregister_chrdev_region(devno, 1);
43 return result;
44 }
45
46 /模块卸载函数/
47 void globalmem_exit(void)
48 {
49 cdev_del(&(globalmem_devp[0].cdev));
50 cdev_del(&(globalmem_devp[1].cdev)); / 注销cdev /
51 kfree(globalmem_devp); /释放设备结构体内存/
52 unregister_chrdev_region(MKDEV(globalmem_major, 0), 2); /释放设备号/
53 }
/ 其他代码同清单6.16 /
代码清单6.18第7行调用的container_of()的作用是通过结构体成员的指针找到对应结构体的指针,这个技巧在Linux内核编程中十分常用。在container_of(inode->i_cdev,struct globalmem_dev, cdev)语句中,传给container_of()的第1个参数是结构体成员的指针,第2个参数为整个结构体的类型,第3个参数为传入的第1个参数即结构体成员的类型,container_of()返回值为整个结构体的指针。