Chinaunix首页 | 论坛 | 博客
  • 博客访问: 81156
  • 博文数量: 22
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 140
  • 用 户 组: 普通用户
  • 注册时间: 2015-07-29 10:17
文章分类
文章存档

2016年(1)

2015年(21)

我的朋友

分类: LINUX

2015-11-15 09:59:30

原文地址:内核设备模型分析 作者:alloysystem

内核设备模型分析

Sysfs文件系统 

内核设备模型主要的模块和用户之间能看到的相关部分就是sysfs文件系统了。内核在启动的时候会注册sysfs文件系统,并且在启动系统的初期。通过mount命令挂载sysfs文件系统到/sys挂载点。

Mount -t sysfs sysfs /sys

 

那么sysfs文件系统的作用是什么呢。概括的说有三点:

1、建立系统中总线、驱动、设备三者之间的桥梁

2、像用户空间展示内核中各种设备的拓扑图

3、提供给用户空间对设备获取信息和操作的接口,部分取代ioctl功能。

 

Kobject

Sysfs文件系统中最基本的结构就是kobjectkobject可以代表一个设备,一条总线等。在sys目录下直观的以一个目录表示出来。

struct kobject 

    const char *name;                   /* 对应sysfs的目录名 */ 
    struct list_head entry;             /* kobjetct双向链表 */ 
    struct kobject *parent;             /* 指向kset中的kobject,相当于指向父目录 */ 
    struct kset *kset;                     /*指向所属的kset */ 
    struct kobj_type *ktype;             /*负责对kobject结构跟踪*/ 
    struct sysfs_dirent *sd;             /*在 /sys 目录下对应的目录实例*/ 
    struct kref kref;                     /*kobject引用计数*/ 
    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结构中还有一个 kobj_type数据结构成员,该成员保护了kobject的属性和属性相关的操作函数。一个属性对应于kobject目录下的一个文件。在用户态可以通过读写来操作这个属性。

struct kobj_type 

    void (*release)(struct kobject *kobj); 
    struct sysfs_ops *sysfs_ops; 
    struct attribute **default_attrs; 
}; 

 

Kset

Kset可以认为是一组kobject的集合,kset首先自己是一个kobjectKset处理将同类型的kobject组合到一起外,还有一个重要的作用就是当kset中的某些kobject对象发生了状态的改变需要通知到用户空间时,就会调用其中uevent相关的函数向用户空间发送消息来完成。

struct kset 

    struct list_head list; 
    spinlock_t list_lock; 
    struct kobject kobj; 
    struct kset_uevent_ops *uevent_ops; 
}; 

 

下图描述了kset下挂接多个kobject时相应的数据结构连接。

 

Uevent机制

上面的分析其实只是对linux设备模型做了一些基础性的了解。也就是一个穿针引线的作用,如果要细致了解,需要仔细阅读代码。有了上面对于sysfs的基础。接下来我们来比较详细的了解一下uevent机制。

什么是uevent机制。这个得从热插拔设备开始说起。最简单的一个例子就是U盘了。当我们在计算机上插上一个U盘的时候,系统的USB hub会检测到U盘设备接入,并且完成设备枚举过程(从设备上读出相应的设备信息),并在内核中创建相应的设备结构体。但是,usb设备千奇百态,内核不可能预先将所有usb设备驱动都增加到内存中来。也就是当插入U盘设备的时候,内核中不一定存在对应这个设备的usb驱动。这个时候USB驱动也许以模块的形式保存在硬盘上。载入驱动必然只能从用户态来进行,那这时候应该怎么办呢?

看到这里的时候,有人一定会想,人工敲入命令载入驱动,呵呵。这必然是一种方法,但是是一种很古老的方法。Linux对类似的情况设计了一种uevent的机制。当有新的设备加入的时候,将设备的信息发送消息到用户态。而用户态有一个udev的进程监听这个信息。当收到信息后做一定的解析,根据解析到的结果和用户程序的配置做一些处理,也包括加载驱动程序。

 

上图就是usb设备插入到主机后,usb hub检测到设备并添加设备,会调用到device_add函数,该函数会调用到kobject_uevent函数,想用户态发送KOBJ_ADD设备接入的消息。

int kobject_uevent(struct kobject *kobj, enum kobject_action action) 

    return kobject_uevent_env(kobj, action, NULL); 

EXPORT_SYMBOL_GPL(kobject_uevent); 

具体调用到kobject_uevent_env函数完成了消息的拼装和具体的发送。下面直接将这段代码贴出来并且附上关键部分的分析,函数长但是逻辑比较简单。

int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, 
                       char *envp_ext[]) 

    struct kobj_uevent_env *env; 
    const char *action_string = kobject_actions[action]; 
    const char *devpath = NULL; 
    const char *subsystem; 
    struct kobject *top_kobj; 
    struct kset *kset; 
    struct kset_uevent_ops *uevent_ops; 
    u64 seq; 
    int i = 0
    int retval = 0
 
    pr_debug("kobject: '%s' (%p): %s\n"
             kobject_name(kobj), kobj, __func__); 
 
    /* search the kset we belong to */ 
    /*首先如果传入的kobj并没有直接指定kset,找齐上层kobj指定的kset*/ 
    top_kobj = kobj; 
    while (!top_kobj->kset && top_kobj->parent) 
        top_kobj = top_kobj->parent; 
 
    /*如果这个kobj是孤立的,无kset指定,不发送uevent消息*/ 
    if (!top_kobj->kset) 
    { 
        pr_debug("kobject: '%s' (%p): %s: attempted to send uevent " 
                 "without kset!\n", kobject_name(kobj), kobj, 
                 __func__); 
        return -EINVAL; 
    } 
 
    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
        } 
 
    /* originating subsystem */ 
    if (uevent_ops && uevent_ops->name) 
        subsystem = uevent_ops->name(kset, kobj); 
    else 
        subsystem = kobject_name(&kset->kobj); 
    if (!subsystem) 
    { 
        pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the " 
                 "event to drop!\n", kobject_name(kobj), kobj, 
                 __func__); 
        return 0
    } 
 
    /* environment buffer */ 
    /*申请了一个kobj_uevent_env的结构体,之后将需要发送的信息 
      填充到这个结构体中*/ 
    env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL); 
    if (!env) 
        return -ENOMEM; 
 
    /* complete object path */ 
    devpath = kobject_get_path(kobj, GFP_KERNEL); 
    if (!devpath) 
    { 
        retval = -ENOENT; 
        goto exit; 
    } 
 
    /* default keys */ 
    /*填充消息结构体,准备发送消息*/ 
    retval = add_uevent_var(env, "ACTION=%s", action_string); 
    if (retval) 
        goto exit; 
    retval = add_uevent_var(env, "DEVPATH=%s", devpath); 
    if (retval) 
        goto exit; 
    retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem); 
    if (retval) 
        goto exit; 
 
    /* keys passed in from the caller */ 
    if (envp_ext) 
    { 
        for (i = 0; envp_ext[i]; i++) 
        { 
            retval = add_uevent_var(env, "%s", envp_ext[i]); 
            if (retval) 
                goto exit; 
        } 
    } 
 
    /* let the kset specific function add its stuff */ 
    if (uevent_ops && uevent_ops->uevent) 
    { 
        retval = uevent_ops->uevent(kset, kobj, env); 
        if (retval) 
        { 
            pr_debug("kobject: '%s' (%p): %s: uevent() returned " 
                     "%d\n", kobject_name(kobj), kobj, 
                     __func__, retval); 
            goto exit; 
        } 
    } 
 
    /* 
     * Mark "add" and "remove" events in the object to ensure proper 
     * events to userspace during automatic cleanup. If the object did 
     * send an "add" event, "remove" will automatically generated by 
     * the core, if not already done by the caller. 
     */ 
    if (action == KOBJ_ADD) 
        kobj->state_add_uevent_sent = 1
    else if (action == KOBJ_REMOVE) 
        kobj->state_remove_uevent_sent = 1
 
    /* we will send an event, so request a new sequence number */ 
    spin_lock(&sequence_lock); 
    seq = ++uevent_seqnum; 
    spin_unlock(&sequence_lock); 
    retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)seq); 
    if (retval) 
        goto exit; 
 
#if defined(CONFIG_NET) 
    /* send netlink message */ 
    /*使用netlink机制发送uevent消息到用户态*/ 
    if (uevent_sock) 
    { 
        struct sk_buff *skb; 
        size_t len; 
 
        /* allocate message with the maximum possible size */ 
        len = strlen(action_string) + strlen(devpath) + 2
        skb = alloc_skb(len + env->buflen, GFP_KERNEL); 
        if (skb) 
        { 
            char *scratch; 
 
            /* add header */ 
            scratch = skb_put(skb, len); 
            sprintf(scratch, "%s@%s", action_string, devpath); 
 
            /* copy keys to our continuous event payload buffer */ 
            for (i = 0; i < env->envp_idx; i++) 
            { 
                len = strlen(env->envp[i]) + 1
                scratch = skb_put(skb, len); 
                strcpy(scratch, env->envp[i]); 
            } 
 
            NETLINK_CB(skb).dst_group = 1
            retval = netlink_broadcast(uevent_sock, skb, 01
                                       GFP_KERNEL); 
        } 
        else 
            retval = -ENOMEM; 
    } 
#endif 
 
    /* call uevent_helper, usually only enabled during early boot */ 
    if (uevent_helper[0]) 
    { 
        char *argv [3]; 
 
        argv [0] = uevent_helper; 
        argv [1] = (char *)subsystem; 
        argv [2] = NULL; 
        retval = add_uevent_var(env, "HOME=/"); 
        if (retval) 
            goto exit; 
        retval = add_uevent_var(env, 
                                "PATH=/sbin:/bin:/usr/sbin:/usr/bin"); 
        if (retval) 
            goto exit; 
 
        retval = call_usermodehelper(argv[0], argv, 
                                     env->envp, UMH_WAIT_EXEC); 
    } 
 
exit: 
    kfree(devpath); 
    kfree(env); 
    return retval; 

EXPORT_SYMBOL_GPL(kobject_uevent_env); 

 

Uevent的用户态部分是一个udev的后台进程,它随系统启动后监听内核发送到消息。主要执行的工作有两个:

1、自动加载驱动模块

2、根据uevent消息在/dev目录下自动添加或者删除设备节点

Udev进程相关的文档比较多,这里不再做详细的叙述。Udev进程在etc目录下面有一个uevent规则文件/etc/udev/rules.d/50-udev.rules类似的以.rules为后缀的文件。Udev程序收到uevent消息后,在这些规则文件中寻找匹配的规则,如果找到了就执行匹配的shell命令。

例如:

ACTION=="add", SUBSYSTEM=="?*", ENV{MODALIAS}=="?*", RUN+="/sbin/modprobe $env{MODALIAS}"

udev收到add事件后,shell能自动去加载MODALIAS定义的模块。

关于如何写udev的匹配规则,这里也不做原理类的内容来叙述。或许有时间的时候专门写和试验的小程序来玩一玩udev机制。

 

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