搜索
查看: 1451|: 0

linux驱动编程--设备模型1

[复制链接]

165

主题

5

回帖

1123

积分

金牌会员

积分
1123
发表于 2014-9-16 16:09:59 | 显示全部楼层 |阅读模式

最近学习设备模型的运行机制,进过书上和网上资料的训练,貌似已经修改出了自己的网络权值,所以写了下来并整理一下自己的思路。

之前的驱动程序由于硬件信息和逻辑操作是写在一起的,所以一个驱动只能适应一种平台。为了提高驱动程序的可移植性,就引出了设备模型。

那么现在面临的问题是:设备模型是怎么工作的?

一.理论

这就要谈到两个重要的结构体kobject和kset。

1.1 kobject

kobject是设备对象的基础结构体。很多的kobject对象连接在一起构成了一个分级的拓扑结构,相当于这栋建筑的钢架,负责各个对象间的连接工作。

         struct kobject;         struct kobject {                  const char        *name;             //                  struct list_head  entry;            //内核链表的入口,通过container_of()                  struct kobject    *parent; //父对象,用于构建kobject对象的层级关系                    struct kset          *kset;                 //kobject对象所属的kset集合,同类型的对象会被加入同一个集合中                    struct kobj_type         *ktype;                 //属性文件及其操作函数句柄                  struct sysfs_dirent *sd;                 //??目录结构                    struct kref       kref;             //对象引用计数,用于计算生命周期                    unsigned int state_initialized:1;   //是否初始化                    unsigned int state_in_sysfs:1;       //是否出现在文件树中                    unsigned int state_add_uevent_sent:1;          //                  unsigned int state_remove_uevent_sent:1;                  unsigned int uevent_suppress:1;                //是否发送通知事件         };

对应的操作函数如下

        kobject_set_name(struct kobject * kobj,const char * fmt,...);        kobject_init(struct kobject * kobj,struct kobj_type * ktype);        kobject_add(struct kobject * kobj,struct kobject * parent,const char * fmt,...);//1).保证kobject的层次关系。2).在sysfs中建立对应目录        kobject_del(struct kobject * kobj);

其中的kobject_add()函数会将新的kobject对象添加到这栋建筑的对应层级中,且还会在sysfs中建立对应的目录。关于属性文件,它存在的意义是为用户提供了一种与驱动模型交互的方式。在之前的驱动模型中,应用层与驱动层的交互是依靠设备文件来完成,典型交互过程就是:

打开设备文件 --> 向设备文件读写操作 --> 关闭设备文件

对设备进行读写的时候,会将数据传输到内核层的驱动处理函数处。有了属性文件后就可以有另外一种交互方式:

cat /sys/(设备模型的对应目录)/(对应的属性文件)

echo 'xx' > /sys/(设备模型的对应目录)/(对应的属性文件)

对属性文件的读写最终会调用到"对应的"kobject对象的ktype成员下的操作函数(show或store)。这样就通过对属性文件的操作实现了与内核中kobject对象的交流。(在下面的例子中会有演示)

1.2 kset

kset是一个容器放有所有同类型的kobject对象,相当于这栋建筑的一个楼层。

        struct kset {                struct list_head list;        //同类型的kobject链表                spinlock_t list_lock;        //                struct kobject kobj;        //本身所属的kobject对象                struct kset_uevent_ops *uevent_ops;        //通知事件的操作函数        };        struct kset_uevent_ops {        //在kobject_uevent()中先后调用函数1和函数3                int (*filter)(struct kset *kset, struct kobject *kobj);                //函数1                const char *(*name)(struct kset *kset, struct kobject *kobj);                int (*uevent)(struct kset *kset, struct kobject *kobj,                //函数3                                        struct kobj_uevent_env *env);        };

在有些时候我们需要将一个kobject对象的变化通知到应用层(比如热插拔事件,然后在应用层会查找并加载相应驱动程序),这时就需要调用kobject_uevent()函数。
该函数会找到该kobject对象所属的kset集合。然后分别调用uevent_ops成员下的filter函数(),和uevent函数()。最终会调用call_usermodehelper()。在call_usermodehelper会根据指定的路径将一个用户空间的程序带进内核空间执行,从而完成事件的通知。函数主干如下(注意英语注释)

        kobject_uevent(struct kobject * kobj,enum kobject_action action);        {                ......                /* search the kset we belong to */                top_kobj = kobj;                while (!top_kobj->kset && top_kobj->parent)                        top_kobj = top_kobj->parent;                  ......                kset = top_kobj->kset;                uevent_ops = kset->uevent_ops;                /* skip the event, if the filter returns zero. */                if (uevent_ops && uevent_ops->filter)                        if (!uevent_ops->filter(kset, kobj)) {                                pr_debug("kobject: '%s' (%p): %s: filter function "                                         "caused the event to drop!\n",                                         kobject_name(kobj), kobj, __func__);                                return 0;                        }                ......                /* let the kset specific function add its stuff */                if (uevent_ops && uevent_ops->uevent) {                        retval = uevent_ops->uevent(kset, kobj, env);//完成kset对象的私人事件                        if (retval) {                                pr_debug("kobject: '%s' (%p): %s: uevent() returned "                                         "%d\n", kobject_name(kobj), kobj,                                         __func__, retval);                                goto exit;                        }                }                ......                ......                /* 调用用户空间的程序*/                  argv [0] = uevent_helper;         //这里指定了应用层程序的路径                retval = call_usermodehelper(argv[0], argv,                                                 env->envp, UMH_WAIT_EXEC);                ......        }

kobject_uevent与上层的交流实际就是在内核为应用层的程序建立一个线程。而这个应用程序由 uevent_helper 变量指定。那么怎么修改这个内核变量呢,这就涉及到另外一个点。

在内核运行的过程中会有一些全局变量,这些全局变量的确定着内核的运行方式。在linux内核编写时为了给用户层留出他们的接口,就将这些变量以及内核信息虚拟成了一个文件放在"/proc/sys/kernel/" 目录下。当然也有可能在 "/sys/kernel/" 目录下,他们在这个功能方面有一些重复。关于proc目录的具体信息可以 "man proc" 来查看。

现在继续回来讨论 uevent_helper变量的修改,经过查找在 "/sys/kernel" 下发现了uevent_helper,又在 "/proc/sys/kernel/" 下发现了hotplug。对这两个的修改都能修改到内核中的uevent_helper变量。

二. 例子

现在制作一个具体的测试程序来检验一下。

#include <linux/module.h>#include <linux/kernel.h>#include <linux/kobject.h>#include <linux/sysfs.h>#include <linux/slab.h>#define NAME_PARENT                "dem_parent"        //parent 对象#define NAME_CHILD                "dem_child"        //child 对象#define NAME_SET                "dem_set"        //child 对象所属的kset集合#define NAME_CHATTR                "child_attr"        //child 对象的属性文件static struct kobject *parent;static struct kobject *child;static struct kset        *c_kset;static int flag = 0;static ssize_t attr_show(struct kobject *kobj, struct attribute *attr,char *buf){        ssize_t size = 0;        size = sprintf( buf, "%d\n", flag);        return size;}static ssize_t attr_store(struct kobject *kobj,struct attribute *attr,const char *buf, size_t len){        //printk( "kobject: %x,  kchild: %x\n", kobj, child);        int        old_flag = flag;        flag = buf[0]-'0';        //将从属性文件传下来的信息通知会应用层,进而验证热插拔        switch( flag)        {                case 0:                        kobject_uevent( kobj,  KOBJ_ADD);                        break;                case 1:                        kobject_uevent( kobj,  KOBJ_REMOVE);                        break;                case 2:                        kobject_uevent( kobj,  KOBJ_CHANGE);                        break;                case 3:                        kobject_uevent( kobj,  KOBJ_MOVE);                        break;                case 4:                        kobject_uevent( kobj,  KOBJ_ONLINE);                        break;                case 5:                        kobject_uevent( kobj,  KOBJ_OFFLINE);                        break;                default :                        break;        }        return old_flag;}//child对象的属性文件static struct attribute kchild_attr[] = {        {                .name = NAME_CHATTR,                .mode = S_IRUGO|S_IWUGO,        }};//child对象属性文件的操作函数static struct sysfs_ops kchild_ops = {        .show = attr_show,        .store = attr_store,};static struct kobj_type kchild_type = {        .sysfs_ops = &kchild_ops,};static int __init demo_init( void){        printk("load vision: %s\n", __TIME__);        //创建一个kobject对象作为child对象的父对象        parent = kobject_create_and_add( NAME_PARENT, NULL);        if( NULL==parent )        {                printk("error: %s, %d\n", __FILE__, __LINE__);                goto ERR_KPARENT;        }        //如果child对象要进行事件通知,就必须属于一个kset集合        c_kset = kset_create_and_add( NAME_SET, NULL, parent);        if( NULL==c_kset)        {                printk("error: %s, %d\n", __FILE__, __LINE__);                goto ERR_KSET;        }        child = kzalloc(sizeof(*child), GFP_KERNEL);        if (!child)        {                printk("error: %s, %d\n", __FILE__, __LINE__);                goto ERR_KCHILD;        }        int        retval;        child->kset = c_kset;        retval = kobject_init_and_add( child, &kchild_type, parent, NAME_CHILD);        if (retval) {                printk("error: %s, %d\n", __FILE__, __LINE__);                goto ERR_CHILDADD;        }        //创建child对象对应的属性文件        retval = sysfs_create_file( child, &kchild_attr);        OUT:                return retval;        ERR_CHILDADD:                kobject_put(child);                child = NULL;        ERR_KCHILD:                kset_unregister( c_kset);                c_kset = NULL;        ERR_KSET:                kobject_del( parent);                parent = NULL;        ERR_KPARENT:                return -1;        }static void __exit demo_exit( void){        printk("unload vision: %s\n", __TIME__);        kobject_del( child);        kset_unregister( c_kset);        kobject_del( parent);}MODULE_LICENSE("GPL");module_init(demo_init);module_exit(demo_exit);程序思路按照一般的驱动加载思路执行。最终这个程序会在 /sys/目录下建立 dem_parent文件夹,再在其下建立 dem_child 和dem_set两个文件夹。并建立了属性文件/sys/dem_parent/dem_child/child_attr。当我们执行"echo '1' > /sys/dem_parent/dem_child/child_attr" 时,消息会通知到内核的child对象处,并调用store处理函数。这就完成了应用层到内核层的沟通。

而内核层到用户层的沟通,通过kobject_uevent()来实现。在store函数中,我们已经调用了该函数。其会去指定路径下找到相应的程序或脚本文件,并在内核空间中构建进程。这样就完成了内核到用户空间的交流。经过查找发现了应用层程序的路径由uevent_helper变量指定,去/sys/kernel目录下果然找到了一个文件uevent_helper,这应该就是留给应用层的接口。现在输入:"echo '/sbin/XXX' > /sys/kernel/uevent_helper",当我们再次执行"echo '1' > /sys/dem_parent/dem_child/child_attr"时就会发现自己在在应用层设置的程序或脚本被调用了(记得修改程序或脚本的可执行权限)。


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

大数据中国微信

QQ   

版权所有: Discuz! © 2001-2013 大数据.

GMT+8, 2024-12-23 22:54 , Processed in 0.120789 second(s), 24 queries .

快速回复 返回顶部 返回列表