Chinaunix首页 | 论坛 | 博客
  • 博客访问: 713936
  • 博文数量: 192
  • 博客积分: 2653
  • 博客等级: 少校
  • 技术积分: 1623
  • 用 户 组: 普通用户
  • 注册时间: 2012-05-31 18:25
个人简介

How tough life is, how strong you should be!

文章分类

全部博文(192)

文章存档

2015年(1)

2014年(2)

2013年(37)

2012年(152)

分类: LINUX

2012-08-01 10:53:26

利用udev、sys动态创建linux设备结点

作者:刘洪涛,讲师。

在Linux2.6内核中,devfs被认为是过时的方法,并最终被抛弃,udev取代了它。Devfs的一个很重要的特点就是可以动态创建设备结点。那我们现在如何通过udev和sys文件系统动态创建设备结点呢?

下面通过一个实例,说明udev、sys动态创建设备结点的方法。注意代码中红色的部分是为了实现动态创建设备结点添加的。 
         #include  
         #include
         #include  
         #include  
         #include  
         #include  
         #include
         MODULE_LICENSE ("GPL");
         int hello_major = 252;
         int hello_minor = 0;
         int number_of_devices = 1;
         char data[50]="foobar not equal to barfoo";
         struct cdev cdev;
         dev_t dev = 0;
         static int hello_open (struct inode *inode, struct file *file)
         {
         printk (KERN_INFO "Hey! device opened\n");
         return 0;
         }
         static int hello_release (struct inode *inode, struct file *file)
         {
         printk (KERN_INFO "Hmmm... device closed\n");
         return 0;
         }
         ssize_t hello_read (struct file *filp, char *buff, size_t count, loff_t *offp)
         {
         ssize_t result = 0;
         if (copy_to_user (buff, data, sizeof(data)-1)) 
         result = -EFAULT;
         else 
         printk (KERN_INFO "wrote %d bytes\n", count);
         return result;
         }
         ssize_t hello_write (struct file *filp, const char *buf, size_t count, loff_t *f_pos)
         {
         ssize_t ret = 0;
         printk (KERN_INFO "Writing %d bytes\n", count);
         if (count>127) return -ENOMEM;
         if (count<0) return -EINVAL;
         if (copy_from_user (data, buf, count)) {
         ret = -EFAULT;
         }
         else {
         data[127]='\0';
         printk (KERN_INFO"Received: %s\n", data);
         ret = count;
         }
         return ret;
         }
         struct file_operations hello_fops = {
.         owner = THIS_MODULE,
.         open = hello_open,
.         release = hello_release,
.         read = hello_read,
.         write = hello_write
         };
         struct class *my_class;
         static void char_reg_setup_cdev (void)
         {
         int error, devno = MKDEV (hello_major, hello_minor);
         cdev_init (&cdev, &hello_fops);
         cdev.owner = THIS_MODULE;
         cdev.ops = &hello_fops;
         error = cdev_add (&cdev, devno , 1);
         if (error)
         printk (KERN_NOTICE "Error %d adding char_reg_setup_cdev", error);
         /* creating your own class */
         my_class =class_create(THIS_MODULE, "farsight_class");//add by lht
         if(IS_ERR(my_class)) {
         printk("Err: failed in creating class.\n");
         return ;
         }
         /* register your own device in sysfs, and this will cause udevd to create corresponding device node */
         class_device_create(my_class,NULL, devno, NULL,"farsight_dev");
         // device_create(my_class,NULL, devno,"farsight_dev");
         }
         static int __init hello_2_init (void)
         {
         int result;
         dev = MKDEV (hello_major, hello_minor);
         result = register_chrdev_region (dev, number_of_devices, "test");
         if (result<0) {
         printk (KERN_WARNING "hello: can't get major number %d\n", hello_major);
         return result;
         }
         char_reg_setup_cdev ();
         printk (KERN_INFO "char device registered\n");
         return 0;
         }
         static void __exit hello_2_exit (void)
         {
         dev_t devno = MKDEV (hello_major, hello_minor);
         cdev_del (&cdev);
         unregister_chrdev_region (devno, number_of_devices);
         class_device_destroy(my_class, devno);
         class_destroy(my_class);
         }
         module_init (hello_2_init);
         module_exit (hello_2_exit);v

在编译了驱动后,可以查看/dev/farsight_dev设备结点,和 /sys/class/farsight_class/farsight_dev/ 本代码的测试环境是Ubantu7.04,内核版本是2.6.20-15-generi。在不同版本的内核中,有些系统函数的参数可能不太一样。 


在上篇《利用udev、sys动态创建设备结点》的记录中,设备驱动中主要依靠下面两个功能完成的:

1、在/sys/class下创建farsight_class类

my_class =class_create(THIS_MODULE, "farsight_class");

2、在farsight_class中创建新的class设备

class_device_create(my_class,NULL, devno, NULL,"farsight_dev");

然后会在/sys中出现如图的文件结构:

 

其中”dev”和uevent都是“属性”,可以读取dev获取设备的主次设备号;也可以对uevent操作;让内核发出“add”事件用于热插拔。如:

 

1

注:这里写入任何值都会导致“add”事件的产生,udevmonitor检测时现象如下:

UEVENT[1220019773.507374] add????? /class/farsight_class/farsight_dev (farsight_class)

那么上述功能实现的原理是什么呢?现在就要过度到本文的主题ktype的使用了。先认识下这个结构

kype的结构定义为:

struct kobj_type {
        void (*release)(struct kobject *);
        struct sysfs_ops *sysfs_ops;/*提供实现以下属性的方法*/
         struct attribute **default_attrs; /*用于保存类型属性列表(指针的指针) */
         };

其中 attribute定义为:

struct attribute {
        char *name;/*属性的名字(在kobject的sysfs 目录中显示,如上文的dev、uvent)*/
        struct module *owner;/*指向模块的指针(如果有), 此模块负责实现这个属性*/
        mode_t mode; /*属性的保护位,modes 的宏定义在 :例如S_IRUGO 为只读属性等等*/
        }; /*default_attrs 列表中的最后一个元素必须用 0 填充*/

sysfs 系统中的属性读写是由 kobj_type->sysfs_ops 成员中的函数完成的:

struct sysfs_ops {
        ssize_t (*show)(struct kobject *kobj, struct attribute *attr, char *buffer);
        ssize_t (*store)(struct kobject *kobj, struct attribute *attr, const char *buffer, size_t size);
        };

当用户空间读取一个属性时(如:#cat dev),内核会使用指向 kobject 的指针(kobj)和正确的属性结构(*attr)来调用show 方法,该方法将给定属性值编码进缓冲(buffer)(注意不要越界( PAGE_SIZE 字节)), 并返回实际数据长度。

也可对所有 kobject (通常指在一个kset关联的范围内)关联的属性使用同一个 show 方法,用传递到函数的 attr 指针来判断所请求的属性。有的 show 方法包含对属性名字的检查。有的show 方法会将属性结构嵌入另一个结构(本文举的例子就是用的这种方法), 这个结构包含需要返回属性值的信息,这时可用container_of 获得上层结构的指针以返回属性值的信息。

当用户空间写入一个属性时(如echo “hello” > event)内核会使用指向 kobject 的指针(kobj)和正确的属性结构(*attr)来调用store 方法。

store 方法将存在缓冲(buffer)的数据( size为数据的长度,不能超过 PAGE_SIZE )解码并保存新值到属性(*attr), 返回实际解码的字节数。store 方法只在拥有属性的写权限时才能被调用。此时注意:接收来自用户空间的数据一定要验证其合法性。如果到数据不匹配, 返回一个负的错误值。

每一个 kobject 需要有一个关联的 kobj_type 结构,指向这个结构的指针能在 2 个不同的地方找到:

(1)kobject 结构自身包含一个成员(ktype)指向kobj_type ;

struct kobject {
        …… 
        struct kobj_type    * ktype;/*负责对该kobject类型进行跟踪的struct kobj_type的指针*/
        …… 
        }

(2)如果这个 kobject 是一个 kset 的成员, kset 会提供kobj_type 指针。

struct kset {
        struct kobj_type    * ktype; /*指向该kset对象类型的指针*/
        …… 
        }

访问属性的时候到底是用的哪个kobj_type呢?

下面这个函数用以查找指定kobject的kobj_type 指针:

static inline struct kobj_type * get_ktype(struct kobject * k)
        {
            if (k->kset && k->kset->ktype)
                return k->kset->ktype;
            else 
                return k->ktype;
        }

上面可以看出,kset中的ktype这个类型优先于 kobject 自身中的 ktype 。因此在典型的应用中, 在 struct kobject 中的 ktype 成员被设为 NULL, 而 kset 中的ktype是实际被使用的。

下面通过跟踪class_device_create(my_class,NULL, devno, NULL,"farsight_dev");来确定ktype的使用。

1、 

struct class_device *class_device_create(……) 
        {
        …… 
        retval = class_device_register(class_dev);
         }

2、

int class_device_register(struct class_device *class_dev)
        {
         class_device_initialize(class_dev);
         return class_device_add(class_dev);
         }

3、

void class_device_initialize(struct class_device *class_dev)
        {
        kobj_set_kset_s(class_dev, class_obj_subsys);
         kobject_init(&class_dev->kobj);
        INIT_LIST_HEAD(&class_dev->node);
         }

4、

#define kobj_set_kset_s(obj,subsys) /
         (obj)->kobj.kset = &(subsys).kset
        从中可以看出名为“farsight_dev”的kobject对应的kset是class_obj_subsys.kset

5、

看看class_obj_subsys的定义 
        static decl_subsys(class_obj, &ktype_class_device, &class_uevent_ops);

#define decl_subsys(_name,_type,_uevent_ops) /
        struct subsystem _name##_subsys = { /
        .kset = { /
         .kobj = { .name = __stringify(_name) }, /
         .ktype = _type, /
         .uevent_ops =_uevent_ops, /
                      } /
        }

所以kset对应的ktype为ktype_class_device

6、

static struct kobj_type ktype_class_device = {
        .sysfs_ops????? = &class_dev_sysfs_ops,
         .release??? = class_dev_release,
        };

7、

static struct sysfs_ops class_dev_sysfs_ops = {
         .show???? = class_device_attr_show,
         .store????? = class_device_attr_store,
         };

在操作上文中的“dev”或“uevent”时都是操作这个class_dev_sysfs_ops

8、

class_device_attr_show(struct kobject * kobj, struct attribute * attr,
        char * buf)
        {
        struct class_device_attribute * class_dev_attr to_class_dev_attr(attr);
        struct class_device * cd = to_class_dev(kobj);
         ssize_t ret = 0;
        if (class_dev_attr->show)
         ret = class_dev_attr->show(cd, buf);
         return ret;
        }

class_device_attr_store(struct kobject * kobj, struct attribute * attr,
        const char * buf, size_t count)
        {
        struct class_device_attribute * class_dev_attr to_class_dev_attr(attr);
         struct class_device * cd = to_class_dev(kobj);
         ssize_t ret = 0;
        if (class_dev_attr->store)
         ret = class_dev_attr->store(cd, buf, count);
        return ret;
         }

可以看出操作函数会根据to_class_dev_attr(attr);找出对应attr的class_dev_attr并调用其对应的show或store

9、

再跟踪一下上文中的dev及uvent属性的定义及其对应的操作函数

attr->attr.name = "dev";
        attr->attr.mode = S_IRUGO;
         attr->attr.owner = parent_class->owner;
        attr->show = show_dev;
        error = class_device_create_file(class_dev, attr);

static ssize_t show_dev(struct class_device *class_dev, char *buf)
        {
        return print_dev_t(buf, class_dev->devt);//上文中将打印出“2520 
        }

class_dev->uevent_attr.attr.name = "uevent";
         class_dev->uevent_attr.attr.mode = S_IWUSR;
        class_dev->uevent_attr.attr.owner = parent_class->owner;
        class_dev->uevent_attr.store = store_uevent;
        error = class_device_create_file(class_dev, &class_dev->uevent_attr);

static ssize_t store_uevent(struct class_device *class_dev,
        const char *buf, size_t count)
         {
        kobject_uevent(&class_dev->kobj, KOBJ_ADD); 
        return count;
        }

可以看出无论写入什么值都会触发KOBJ_ADD事件,内核调用kobject_uevent函数发送netlink message给用户空间用户层,用户空间可以用udev通过取到此事件,从而处理热插拔事件。

好了,写完了,希望对大家有点参考价值。

阅读(1104) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~