内核设备模型分析
来源:互联网 发布:多益网络绑定战盟 编辑:程序博客网 时间:2024/04/30 06:24
Sysfs文件系统
内核设备模型主要的模块和用户之间能看到的相关部分就是sysfs文件系统了。内核在启动的时候会注册sysfs文件系统,并且在启动系统的初期。通过mount命令挂载sysfs文件系统到/sys挂载点。
Mount -t sysfs sysfs /sys
那么sysfs文件系统的作用是什么呢。概括的说有三点:
1、建立系统中总线、驱动、设备三者之间的桥梁
2、像用户空间展示内核中各种设备的拓扑图
3、提供给用户空间对设备获取信息和操作的接口,部分取代ioctl功能。
Kobject
Sysfs文件系统中最基本的结构就是kobject,kobject可以代表一个设备,一条总线等。在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首先自己是一个kobject。Kset处理将同类型的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, 0, 1,
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机制。
- 内核设备模型分析
- 内核设备模型分析
- Linux内核部件分析--设备驱动模型的基石kobject
- Linux内核部件分析--设备驱动模型之device
- Linux内核部件分析--设备驱动模型之driver
- Linux内核部件分析--设备驱动模型之bus
- Linux内核部件分析--设备驱动模型之device-driver
- Linux内核部件分析<6> 设备驱动模型之device
- Linux内核部件分析<7> 设备驱动模型之driver
- Linux内核部件分析<8> 设备驱动模型之bus
- linux内核部件分析之----设备驱动模型之device
- linux内核组件分析之---设备驱动模型之driver
- linux内核组件分析之--设备驱动模型之bus
- Linux内核部件分析--设备驱动模型之driver
- Linux内核部件分析--设备驱动模型之device
- Linux内核部件分析--设备驱动模型之bus
- Linux内核部件分析--设备驱动模型之device-driver
- linux内核组件分析之--设备驱动模型之bus
- Android LayoutInflater详解
- linux设备驱动程序总述
- 将字符串中的(汉字Unicode编码)解析成汉字
- 【BestCoder】 HDOJ 5162 Jump and Jump...
- 【BestCoder】 HDOJ 5163 Taking Bus
- 内核设备模型分析
- 发送带附件的邮件 java
- 【AC自动机】 HDOJ 5164 Matching on Arrayy
- 不止是产品经理(一)----作为刚入行产品经理,如何开展工作?
- 黑马程序员--线程
- USACO 2.1 The Castle
- 设计模式一:单例模式
- Android ADB命令大全(通过ADB命令查看wifi密码、MAC地址、设备信息、操作文件、查看文件、日志信息、卸载、启动和安装APK等)
- 解决 ubuntu14.04 chromium flash 问题