Linux字符设备编程(五)之struct class
在前面我们已经介绍了两种方法来实现在编写设备驱动程序时由系统自动给我们创建设备文件的方法。现在我们接着讲解第三种方法.
一.前言
内核中定义了struct class结构体,一个struct class 结构体类型变量对应一个类,内核同时提供了class_create()函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建了这个类,再调用device_create()函数在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应device_create()函数,去/sysfs下寻找对应的类而创建设备节点。
说明:
在2.6较早的内核版本中,device_cerate()函数名称是calss_device_create()。
二.具体实现
1>在2.6.26.6内核版本中,struct class定义在头文件
include/linux/device.h中:
struct class结构体
191struct class {
192 const char *name;
193 struct module *owner;
194
195 struct class_attribute *class_attrs;
196 struct device_attribute *dev_attrs;
197 struct bin_attribute *dev_bin_attrs;
198 struct kobject *dev_kobj;
199
200 int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
201 char *(*devnode)(struct device *dev, mode_t *mode);
202
203 void (*class_release)(struct class *class);
204 void (*dev_release)(struct device *dev);
205
206 int (*suspend)(struct device *dev, pm_message_t state);
207 int (*resume)(struct device *dev);
208
209 const struct kobj_ns_type_operations *ns_type;
210 const void *(*namespace)(struct device *dev);
211
212 const struct dev_pm_ops *pm;
213
214 struct subsys_private *p;
215};
2>class_create()在/drivers/base/class.c中实现
307/* This is a #define to keep the compiler from merging different
308 * instances of the __key variable */
309#define class_create(owner, name) /
310({ /
311 static struct lock_class_key __key; /
312 __class_create(owner, name, &__key); /
313})
注:
(1).第一个参数指定类的所有者是那个模块,一般为THIS_MODULE
(2)第二个参数是类目录名,在/sys/class下创建类目录
(3)调用class_create()函数后,要用IS_ERR()函数判断创建的类是否正确。
3>class_destroy()
/** * class_destroy - destroys a struct class structure * @cls: pointer to the struct class that is to be destroyed * * Note, the pointer to be destroyed must have been created with a call * to class_create(). */ void class_destroy(struct class *cls) { if ((cls == NULL) || (IS_ERR(cls))) return; class_unregister(cls); }
(1)参数struct class*cls为creat_class创建的类
(2)函数功能删除class类。
4>device_create:
(1)一旦创建好了类,再调用device_create()函数来在/dev/目录下创建相应的设备节点,这样,加载模块的时候,用户空间中的udev会自动响应device_create()函数去寻找对应的类从而创建设备节点。
(2)device_create()函数实现
1484 * device_create - creates a device and registers it with sysfs
1485 * @class: pointer to the struct class that this device should be registered to
1486 * @parent: pointer to the parent struct device of this new device, if any
1487 * @devt: the dev_t for the char device to be added
1488 * @drvdata: the data to be added to the device for callbacks
1489 * @fmt: string for the device's name
1490 *
1491 * This function can be used by char device classes. A struct device
1492 * will be created in sysfs, registered to the specified class.
1493 *
1494 * A "dev" file will be created, showing the dev_t for the device, if
1495 * the dev_t is not 0,0.
1496 * If a pointer to a parent struct device is passed in, the newly created
1497 * struct device will be a child of that device in sysfs.
1498 * The pointer to the struct device will be returned from the call.
1499 * Any further sysfs files that might be required can be created using this
1500 * pointer.
1501 *
1502 * Returns &struct device pointer on success, or ERR_PTR() on error.
1503 *
1504 * Note: the struct class passed to this function must have previously
1505 * been created with a call to class_create().
1506 */
1507struct device *device_create(struct class *class, struct device *parent,
1508 dev_t devt, void *drvdata, const char *fmt, ...)
1509{
1510 va_list vargs;
1511 struct device *dev;
1512
1513 va_start(vargs, fmt);
1514 dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
1515 va_end(vargs);
1516 return dev;
1517}
a.先看参数
class: pointer to the struct class that this device should be registered to
the struct class 指针,必须在本函数调用之前先被创建
parent: pointer to the parent struct device of this new device
该设备的parent指针。
devt: the dev_t for the char device to be added
字符设备的设备号,如果dev_t不是0,0的话,1个”dev”文件将被创建。
drvdata: the data to be added to the device for callbacks
被添加到该设备回调的数据。
fmt: string for the device's name
设备名字。
b.例子:
device_create(global_class,NULL,devno,NULL,DEVICE_NAME);
如果调用成功,会在/dev/目录下产生/dev/DEVICE_NAME设备。
从上面我们可以看到利用class_create()和device_create()可以使系统自动生成设备节点文件
三.具体实现.
还是以globalmen驱动程序为例,采用calss_create,和device_create()方法自动生成设备节点文件
1 /**
2 **Filename : globalmen.c
3 ** Author : Tiger-john
4 **Time :2011-05-24
5 **
6 **/
7 #include<linux/kernel.h>
8 #include<linux/init.h>
9 #include<linux/module.h>
10 #include<linux/cdev.h>
11 #include<linux/types.h>
12 #include<linux/fs.h>
13 #include<asm/uaccess.h>
14 #include<linux/errno.h>
15 #include<linux/platform_device.h>
16 #define GLOBALMEM_SIZE 0x1000//全局内存大小
17 #define GLOBAL_MAJOR 248
18 #define MEM_CLEAR 0x1//清空全局内存
19 #define DEVICE_NAME "Tiger-global"
20
21 #define GLOBAL_BUG
22
23 static dev_t globalmem_major = GLOBAL_MAJOR;
24 struct globalmem_dev
25 {
26 struct cdev cdev;//cdev结构体
27 unsigned char mem[GLOBALMEM_SIZE];
28 };
29 struct globalmem_dev *global_dev;
30 static struct class *global_class;
31 //文件打开函数
32 static int globalmem_open(struct inode *inode,struct file *filp)
33 {
34 //将设备结构体指针赋值给文件私有数据指针
35 filp->private_data = global_dev;
36 return 0;
37 }
38 //文件读函数
39 static ssize_t globalmem_read(struct file *filp,char __user *buf,size_t size,loff_t *ppos)
40 {
41 unsigned long p = *ppos;
42 unsigned int count = size;
43 int retval = 0;
44 struct globalmem_dev *dev = filp->private_data;
45 //判断
46 if(p >= GLOBALMEM_SIZE)
47 return count ? -ENXIO : 0;
48 if(count > GLOBALMEM_SIZE -p)
49 count = GLOBALMEM_SIZE -p;
50 //从内核空间向用户空间写数据
51 if(copy_to_user(buf,(void *)(dev->mem +p),count)){
52 retval = -EFAULT;
53 } else {
54 *ppos +=count;
55 retval =count;
56 #ifdef GLOBAL_BUG
57 printk(KERN_INFO "read %d bytes from %d/n",count,p);
58 #endif
59 }
60 return retval;
61
62 }
63 static ssize_t globalmem_write(struct file *filp,const char __user *buf,size_t size,loff_t *ppos)
64 {
65 unsigned long p = *ppos;
66 unsigned int count = size;
67 int retval;
68 struct globalmem_dev *dev = filp->private_data;//获得设备结构体指针
69 //判断
70 if ( p >= GLOBALMEM_SIZE)
71 return count ? -ENXIO :0;
72 if (count > GLOBALMEM_SIZE -p)
73 count = GLOBALMEM_SIZE-p;
74 //从用户空间想内核空间写数据
75 if(copy_from_user((void*)dev->mem+p,buf,count))
76 retval = -EFAULT;
77 else {
78 *ppos += count;
79 retval = count;
80 #ifdef GLOBAL_BUG
81 printk(KERN_INFO "writen %d bytes from %d/n",count,p);
82 #endif
83 }
84 return retval;
85 }
86 //文件定位函数
87 static loff_t globalmem_llseek(struct file *filp,loff_t offset,int orig)
88 {
89 loff_t retval = 0;
90 switch (orig)
91 {
92 case 0 ://相对文件开始位置偏移
93 if (offset < 0){
94 retval = - EINVAL;
95 break;
96 }
97 if ((unsigned int)offset > GLOBALMEM_SIZE){
98
99 retval = -EINVAL;
100 break;
101 }
102 filp->f_pos = (unsigned int)offset;
103 retval = filp->f_pos;
104 case 1: //相对文件当前位置偏移
105 if ((filp->f_pos + offset) > GLOBALMEM_SIZE){
106
107 retval = -EINVAL;
108 break;
109 }
110 if ((filp->f_pos + offset) < 0){
111
112 retval = -EINVAL;
113 break;
114 }
115 //seek_cur
116 filp->f_pos += offset;
117 retval = filp->f_pos;
118 break;
119 default :
120 retval = -EINVAL;
121 break;
122 }
123 return retval;
124 }
125 static int globalmem_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
126 {
127 struct globalmem_dev *dev = filp->private_data;//获得设备结构体指针
128 switch (cmd)
129 {
130 case MEM_CLEAR :
131 memset(dev->mem,0,GLOBALMEM_SIZE);
132 printk(KERN_INFO "globalmem is set to zero/n");
133 break;
134 default :
135 return -EINVAL;
136 }
137 return 0;
138 }
139 int globalmem_release(struct inode *inode,struct file *filp)
140 {
141 return 0;
142 }
143 static const struct file_operations globalmem_fops =
144 {
145 .owner = THIS_MODULE,
146 .open = globalmem_open,
147 .llseek = globalmem_llseek,
148 .read = globalmem_read,
149 .write = globalmem_write,
150 .ioctl = globalmem_ioctl,
151 .release = globalmem_release,
152 };
153 static void globalmem_setup_cdev(struct globalmem_dev *dev,int index)
154 {
155 int retval;
156 dev_t devno = MKDEV(globalmem_major,index);
157
158 cdev_init(&dev->cdev,&globalmem_fops);
159 dev->cdev.owner = THIS_MODULE;
160 dev->cdev.ops = &globalmem_fops;
161 retval = cdev_add(&(dev->cdev),devno,1);
162 if(retval){
163 printk(KERN_NOTICE "Error %d adding globalmem %d",retval,index);
164 }
165 }
166 static int __init global_init(void)
167 {
168 int result;
169 dev_t devno = MKDEV(globalmem_major,0);
170 #ifdef GLOBAL_BUG
171 printk("Bgein:/n");
172 #endif
173 if(globalmem_major)
174 result = register_chrdev_region(devno,1,DEVICE_NAME);
175 else{
176 result = alloc_chrdev_region(&devno,0,1,DEVICE_NAME);
177 globalmem_major = MAJOR(devno);
178 }
179 if(result < 0)
180 return result;
181 #ifdef GLOBAL_BUG
182 printk("devno sucess/n");
183 #endif
184 global_dev = kmalloc(sizeof(struct globalmem_dev),GFP_KERNEL);
185 if(!global_dev){
186 result = -ENOMEM;
187 goto fail_malloc;
188 }
189 memset(global_dev,0,sizeof(struct globalmem_dev));
190 global_class = class_create(THIS_MODULE,DEVICE_NAME);
191 if(IS_ERR(global_class))
192 {
193 printk("can not create a beep calss!/n");
194 return -1;
195 }
196 device_create(global_class,NULL,devno,NULL,DEVICE_NAME);
197 globalmem_setup_cdev(global_dev,0);
198 return 0;
199 fail_malloc:
200 unregister_chrdev_region(devno,1);
201 return result;
202 }
203 static void __exit global_exit(void)
204 {
205 cdev_del(&global_dev->cdev);
206 kfree(global_dev);
207 device_destroy(global_class,MKDEV(globalmem_major,0));
208 class_destroy(global_class);
209 unregister_chrdev_region(MKDEV(globalmem_major,0),1);
210 printk("Goodbye global module!/n");
211 }
212 MODULE_LICENSE("GPL");
213 module_param(globalmem_major,int, S_IRUGO);
214 module_init(global_init);
215 module_exit(global_exit);
216 MODULE_AUTHOR("Tiger John");
217 MODULE_DESCRIPTION("a globalmem module");
我们发现用class_create()和device_create()方法写的驱动程序和我们以前写的驱动程序之是在
(1)static void __exit global_exit(void) 和static int __init global_init(void) 中不同
(2)定义了一个全局变量 static struct class *global_class;
其他的都和我们以前编写的globalmen驱动程序一模一样,而且用该方法比以前的方法系统会自动创建设备节点。我们要做的只是
(1)make
(2)sudo insmod globalmen.ko加载模块
(3)sudo chmod 666 /dev/Tiger-globalmen修改设备节点权限。后面的的应用层程序编写都和前面的方法相同,如果你不明白的话可以参看《Linux字符设备驱动(三)》
(4)系统自动创建的设备节点名为驱动程序中的DEVICE_NAME