热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

linux驱动编程设备模型1

????最近学习设备模型的运行机制,进过书上和网上资料的训练,貌似已经修改出了自己的网络权值,所以写了下来并整理一下自己的思路。之前的驱动程序由于硬件信息和逻辑操作是写在一起的

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

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

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

一.理论

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

 

 

 

 

 

 


 

 

 

 

 

 

linux驱动编程--设备模型1,布布扣,bubuko.com


推荐阅读
author-avatar
书友73428983
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有