知识屋:更实用的电脑技术知识网站
所在位置:首页 > 操作系统 > linux

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

发布时间:2014-09-05 17:42:23作者:知识屋

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

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

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

一.理论

这就要谈到两个重要的结构体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"时就会发现自己在在应用层设置的程序或脚本被调用了(记得修改程序或脚本的可执行权限)。

(免责声明:文章内容如涉及作品内容、版权和其它问题,请及时与我们联系,我们将在第一时间删除内容,文章内容仅供参考)
收藏
  • 人气文章
  • 最新文章
  • 下载排行榜
  • 热门排行榜