其实我也是过来人,深知没有接触过内核开发的同志在无人指导的情况下独自学习Linux设备驱动的感受。都是买书,看BLOG,自已调个小程序,很难有同志能够坚持下去,市面上的书籍也不是说写的不好,只是大家的基础都不一样,另外我感觉有一点就是那些书籍在看了之后始终感觉有一层捅不破的纸,看书的时候感觉啥都会,书一关,好像又啥都不懂了。其实我进书店看过市面上的书,很少有讲内核的框架的,不论是国内的还是国外的,要么框架讲的很抽象,离实际的代码距离太远,看书的人难以把两者统一起来,要么就纯粹是源码分析,忽略框架。
本人接触Linux其实时间也不久,从接触Linux内核至今也不过三年左右,并且大部分时间还是在做应用。但是凭着个人的
兴趣爱好,一直都坚持抽时间学习内核,属于入门级的学习者,本人开发或修改过部分的设备驱动及网络协议栈,也算是略有心得吧。我非常愿意把我觉得最重要最难懂的部分与大家一起分享,但是时隔三年,入门时的一些难题对我来说已经不是难题了,在某种程度上可能会略去一些我现在认为不重要的但对入门来说又很重要的东西,这个就只能靠网友一起帮我来查漏补缺了。
首先我说一下要开始设备驱动开发所具备的基本能力:
1. 会C语言,并且基础还不错,能跟踪调试分析C代码。
2. 有一定的Linux使用基础。
3. 能写一个hellword的驱动模块。
4. 偶尔需要一点汇编(就算不会,对我们分析Linux内核也不会有太大压力)
如果以上四点中的前三点你不具备,那请你先把这三点学会再看本系列的文章,否则会很难受的。
再说下工具的使用:
笔者的机器为WINDOWSXP的系统,在这个基础上装虚拟机,然后再在虚拟机上装Linux。两个系统间采用samba共享文件(当然你也可以采用hgfs或nfs等)。源码阅读就采用sourceinsight+find+grep+vi这种组合的方式。这个是写给还在门外的同志看的,如果你已经有了顺手的工具,可以凭自已的爱好,不必强求。我们的目的是分析内核源码,而不是比拼工具的优劣。
最后再说一下环境:
建议最好找一段比较连续的时间,尤其在入门的时候。我当时经常一看就三四个小时过去了,并且觉得时间太少了。现在虽然好点,但是如果时间经常被打断,效率会很低的。
如果你具备以上这些条件,OK。我们就可以开始我们的Linux内核之旅了。
可能把驱动模型放在第一章讲会会有点难度,但是只要能跨过这道坎,后面就会轻松很多,驱动模型是整个linux设备驱动的基石。大部分人把驱动模型叫做设备模型,但是我查了linux的帮助文档,就是在下载源码路径下的Documentation目录中找到driver-model这个目录,里面包含的文件就是我在本章中所要讲述的东西,也就是我所说的驱动模型。因此本文都会用驱动模型这个术语(如果各位觉得这种叫法是错误的,请在评论中指出,并给出理由,本人非常诚恳的接受各位善意的批评与指正)。驱动模型的核心结构就是我们通常所说的bus、device、device_driver。即总线、设备、设备驱动。首先分析linux内核要有层次的概念,linux从设计上来说是一层套一层的,那么在这一层之下,还有一层由kobject、kobj_type、kset这三者组成,也可以认为其属于驱动模型的范围内,我们可以看到内核对它的描述是:generic kernel object infrastructure。就是通用内核对象基础的意思。我们暂且叫它内核对象层吧。在驱动模型的上层我们可以封装各种子模块子系统,这个以后再做讲解。
我们首先来看看内核对象层是什么东西,都有些什么功能。在这个分析的过程中请多一点耐心,在这中间需要的仅仅是耐心而已。
首先给出内核对象层各成员的原型:
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *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;
};
struct kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
struct kset_uevent_ops *uevent_ops;
};
struct kobj_type {
void (*release)(struct kobject *kobj);
struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
};
首先从各结构体的成员我们发现这没有三角恋的关系,kobject中包含有kobj_type和kset及自身,kset中包含有kobject,而kobj_type则不包含上面两者,只要是在道上混的兄弟,一眼就可以看出kobject在这场恋爱关系中是占据绝对地位的。
针对这三者,linux内核提供了一些操作这些成员的方法。(位于kobject.c)。
我们挑几个名角讲一讲:
void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
char *err_str;
if (!kobj) {
err_str = “invalid kobject pointer!”;
goto error;
}
if (!ktype) {
err_str = “must have a ktype to be initialized properly!\n”;
goto error;
}
if (kobj->state_initialized) {
/* do not error out as sometimes we can recover */
printk(KERN_ERR “kobject (%p): tried to init an initialized “
“object, something is seriously wrong.\n”, kobj);
dump_stack();
}
kobject_init_internal(kobj);
kobj->ktype = ktype;
return;
error:
printk(KERN_ERR “kobject (%p): %s\n”, kobj, err_str);
dump_stack();
}
我们避重就轻的看一下(前面初始化和合法条件判断在实际的运行当中是很重要的,但是对我们分析来说只要能抓主线,分析我们感兴趣的内容就可以了),可以把这个函数简化:
void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
char *err_str;
kobject_init_internal(kobj);
kobj->ktype = ktype;
return;
}
OK,我们看见传入了两个参数,一个是kobject的指针,一个是kobj_type的指针,在调用完kobject_init_internal(kobj)之后,就将传入的ktype赋值给了kobject的ktype成员。我们先来看看ktype到底是何方神胜。在分析ktype之前,我们先要往上跑一层,这一层我们选择int device_register(struct device *dev)这个函数,先给出函数原型:
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
void device_initialize(struct device *dev)
{
dev->kobj.kset = devices_kset;
kobject_init(&dev->kobj, &device_ktype);
INIT_LIST_HEAD(&dev->dma_pools);
init_MUTEX(&dev->sem);
spin_lock_init(&dev->devres_lock);
INIT_LIST_HEAD(&dev->devres_head);
device_init_wakeup(dev, 0);
device_pm_init(dev);
set_dev_node(dev, -1);
}
我们找到我们感兴趣的kobject_init(&dev->kobj, &device_ktype);
我们查看device_ktype的定义:
static struct kobj_type device_ktype = {
.release = device_release,
.sysfs_ops = &dev_sysfs_ops,
};
很明显release的作用就是release,至于怎么release我们先不看,下面一个就是sysfs_ops。这个sysfs与用户空间通信的一个接口,我们点击进去查看一下:
static struct sysfs_ops dev_sysfs_ops = {
.show = dev_attr_show,
.store = dev_attr_store,
};分别对应了我们读和写sysfs下面节点的两个动作。至于里面干嘛的我们先不管。从上面我们知道,ktype包含了一个sysfs的读写接口,另外包含了一个具有release功能的函数。
回到我们之前的内容:简化版的kobject_init函数:
void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
char *err_str;
kobject_init_internal(kobj);
kobj->ktype = ktype;
return;
}
剩下的就是kobject_init_internal(kobj)了。
static void kobject_init_internal(struct kobject *kobj)
{
if (!kobj)
return;
kref_init(&kobj->kref);
INIT_LIST_HEAD(&kobj->entry);
kobj->state_in_sysfs = 0;
kobj->state_add_uevent_sent = 0;
kobj->state_remove_uevent_sent = 0;
kobj->state_initialized = 1;
}
这个函数的功能纯粹就是初始化。
从这个初始经我们了解一些更新的东西:
kref_init(&kobj->kref);
这个叫引用计数,kref_init的作用就是将kobjct->kref设为1。
接下来就是初始化kobject->entry这条链表(linux内核的链表是非常重要且比较精妙的,网上相关的好文章也很多,请同志们自行查阅学习)。
接下来就是一堆的位域。
kobj->state_in_sysfs这个成员正如其名:指明是否使用了sysfs。初始化为0,显然是说:哥现在还没用。
kobj->state_add_uevent_sent、kobj->state_remove_uevent_sent 这两个成员的名命也是非常直观的:指明是否有加载或删除事件。这个是和热插拔相关的,当我们增加一个设备或者删除一个设备的时候,会在合适的时候将此位域置为1。
kobj->state_initialized指明kobject是否有被初始化,这们是唯一个置1的。显然自身被初始化了。
在分析之前有必要说明一下,为了让我们的分析更加简练,我们只会在合适的时候分析结构体的相关成员,不会在没有用到的情况下将成员的作用全都描述出来。
int device_add(struct device *dev)
{
struct device *parent = NULL;
struct class_interface *class_intf;
int error = -EINVAL;
dev = get_device(dev);
if (!dev)
goto done;
if (!dev->p) {
error = device_private_init(dev);
if (error)
goto done;
}
/*
* for statically allocated devices, which should all be converted
* some day, we need to initialize the name. We prevent reading back
* the name, and force the use of dev_name()
*/
if (dev->init_name) {
dev_set_name(dev, “%s”, dev->init_name);
dev->init_name = NULL;
}
if (!dev_name(dev))
goto name_error;
pr_debug(“device: ‘%s’: %s\n”, dev_name(dev), __func__);
parent = get_device(dev->parent);
setup_parent(dev, parent);
/* use parent numa_node */
if (parent)
set_dev_node(dev, dev_to_node(parent));
/* first, register with generic layer. */
/* we require the name to be set before, and pass NULL */
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
if (error)
goto Error;
/* notify platform of device entry */
if (platform_notify)
platform_notify(dev);
error = device_create_file(dev, &uevent_attr);
if (error)
goto attrError;
if (MAJOR(dev->devt)) {
error = device_create_file(dev, &devt_attr);
if (error)
goto ueventattrError;
error = device_create_sys_dev_entry(dev);
if (error)
goto devtattrError;
devtmpfs_create_node(dev);
}
error = device_add_class_symlinks(dev);
if (error)
goto SymlinkError;
error = device_add_attrs(dev);
if (error)
goto AttrsError;
error = bus_add_device(dev);
if (error)
goto BusError;
error = dpm_sysfs_add(dev);
if (error)
goto DPMError;
device_pm_add(dev);
/* Notify clients of device addition. This call must come
* after dpm_sysf_add() and before kobject_uevent().
*/
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_ADD_DEVICE, dev);
kobject_uevent(&dev->kobj, KOBJ_ADD);
bus_probe_device(dev);
if (parent)
klist_add_tail(&dev->p->knode_parent,
&parent->p->klist_children);
if (dev->class) {
mutex_lock(&dev->class->p->class_mutex);
/* tie the class to the device */
klist_add_tail(&dev->knode_class,
&dev->class->p->class_devices);
/* notify any interfaces that the device is here */
list_for_each_entry(class_intf,
&dev->class->p->class_interfaces, node)
if (class_intf->add_dev)
class_intf->add_dev(dev, class_intf);
mutex_unlock(&dev->class->p->class_mutex);
}
done:
put_device(dev);
return error;
DPMError:
bus_remove_device(dev);
BusError:
device_remove_attrs(dev);
AttrsError:
device_remove_class_symlinks(dev);
SymlinkError:
if (MAJOR(dev->devt))
device_remove_sys_dev_entry(dev);
devtattrError:
if (MAJOR(dev->devt))
device_remove_file(dev, &devt_attr);
ueventattrError:
device_remove_file(dev, &uevent_attr);
attrError:
kobject_uevent(&dev->kobj, KOBJ_REMOVE);
kobject_del(&dev->kobj);
Error:
cleanup_device_parent(dev);
if (parent)
put_device(parent);
name_error:
kfree(dev->p);
dev->p = NULL;
goto done;
}
当你看到这一大段的时候,是不是感觉很郁闷,我也很郁闷,但是哥很高兴的说:依我们目前的功能,我们只分析kobject_add(&dev->kobj, dev->kobj.parent, NULL)就够了。从人生的低谷瞬间又找回自信其实很简单,就在现在。先给出函数定义:
int kobject_add(struct kobject *kobj, struct kobject *parent,
const char *fmt, …)
{
va_list args;
int retval;
if (!kobj)
return -EINVAL;
if (!kobj->state_initialized) {
printk(KERN_ERR “kobject ‘%s’ (%p): tried to add an “
“uninitialized object, something is seriously wrong.\n”,
kobject_name(kobj), kobj);
dump_stack();
return -EINVAL;
}
va_start(args, fmt);
retval = kobject_add_varg(kobj, parent, fmt, args);
va_end(args);
return retval;
}
这下代码少多了。
我们可以看到核心函数是kobject_add_varg(kobj, parent, fmt, args),其定义如下:
static int kobject_add_varg(struct kobject *kobj, struct kobject *parent,
const char *fmt, va_list vargs)
{
int retval;
retval = kobject_set_name_vargs(kobj, fmt, vargs);
if (retval) {
printk(KERN_ERR “kobject: can not set name properly!\n”);
return retval;
}
kobj->parent = parent;
return kobject_add_internal(kobj);
}
其中的kobject_set_name_vargs就是用于设置kobject的名字。
int kobject_set_name_vargs(struct kobject *kobj, const char *fmt,
va_list vargs)
{
const char *old_name = kobj->name;
char *s;
if (kobj->name && !fmt)
return 0;
kobj->name = kvasprintf(GFP_KERNEL, fmt, vargs);
if (!kobj->name)
return -ENOMEM;
/* ewww… some of these buggers have ‘/’ in the name … */
while ((s = strchr(kobj->name, ‘/’)))
s[0] = ‘!’;
kfree(old_name);
return 0;
}
下面就是kobject_add_internal这个函数了,其定义如下:
static int kobject_add_internal(struct kobject *kobj)
{
int error = 0;
struct kobject *parent;
if (!kobj)
return -ENOENT;
if (!kobj->name || !kobj->name[0]) {
WARN(1, “kobject: (%p): attempted to be registered with empty “
“name!\n”, kobj);
return -EINVAL;
}
parent = kobject_get(kobj->parent);
/* join kset if set, use it as parent if we do not already have one */
if (kobj->kset) {
if (!parent)
parent = kobject_get(&kobj->kset->kobj);
kobj_kset_join(kobj);
kobj->parent = parent;
}
pr_debug(“kobject: ‘%s’ (%p): %s: parent: ‘%s’, set: ‘%s’\n”,
kobject_name(kobj), kobj, __func__,
parent ? kobject_name(parent) : “<NULL>”,
kobj->kset ? kobject_name(&kobj->kset->kobj) : “<NULL>”);
error = create_dir(kobj);
if (error) {
kobj_kset_leave(kobj);
kobject_put(parent);
kobj->parent = NULL;
/* be noisy on error issues */
if (error == -EEXIST)
printk(KERN_ERR “%s failed for %s with “
“-EEXIST, don’t try to register things with “
“the same name in the same directory.\n”,
__func__, kobject_name(kobj));
else
printk(KERN_ERR “%s failed for %s (%d)\n”,
__func__, kobject_name(kobj), error);
dump_stack();
} else
kobj->state_in_sysfs = 1;
return error;
}
凭着一个程序员的直觉,我们可以看到最重要的是create_dir(kobj);没错,哥猜的很对,就是它了,它和sysfs相关,创建了一个目录,具体这个函数因为牵涉的非常广,我们暂且不做分析。君子报仇,十年不晚,我们看谁笑到最后。在create_dir(kobj)之后我们将kobj->state_in_sysfs =置为1,很亲切吧。撞到老相识的感觉很爽吧,我们在后续分析内核的过程中会撞到越来越多的老相识,并且结识更多的新朋友。连著名歌唱家殷秀梅都知道学习内核的方法:结识新朋友,不忘老朋友……(80后朋友应该都认识,90后的有可能就不认识了)。
接下来我们来分析一下和kset有关的一个函数,那就是先给出函数原型:
struct kset *kset_create_and_add(const char *name,
struct kset_uevent_ops *uevent_ops,
struct kobject *parent_kobj)
{
struct kset *kset;
int error;
kset = kset_create(name, uevent_ops, parent_kobj);
if (!kset)
return NULL;
error = kset_register(kset);
if (error) {
kfree(kset);
return NULL;
}
return kset;
}
和上一节分析kobject一样,为了更好的讲解这个函数我们先要跳到上一层,我们先有必要看一下都有哪些朋友调用了它:int bus_register(struct bus_type *bus)。大名鼎鼎的总线注册。
我们看到bus_register函数中有这样几行代码:
priv->devices_kset = kset_create_and_add(“devices”, NULL,
&priv->subsys.kobj);
if (!priv->devices_kset) {
retval = -ENOMEM;
goto bus_devices_fail;
}
priv->drivers_kset = kset_create_and_add(“drivers”, NULL,
&priv->subsys.kobj);
可见kset和总线是关系的。OK。我们以第一段为基础讲解,分别传入了一个常字符串”devices”,一个空指针,一个kobject指针。
函数内部首先调用
static struct kset *kset_create(const char *name,
struct kset_uevent_ops *uevent_ops,
struct kobject *parent_kobj)
{
struct kset *kset;
int retval;
kset = kzalloc(sizeof(*kset), GFP_KERNEL); //分配一个kset结构体并初始化
if (!kset)
return NULL;
retval = kobject_set_name(&kset->kobj, name); //将传入的常字符串赋值给//kset->kobj->name
if (retval) {
kfree(kset);
return NULL;
}
kset->uevent_ops = uevent_ops; //将uevent_ops
kset->kobj.parent = parent_kobj; //将父类kobject指针赋值给kset->kobj.parent
/*
* The kobject of this kset will have a type of kset_ktype and belong to
* no kset itself. That way we can properly free it when it is
* finished being used.
*/
kset->kobj.ktype = &kset_ktype; //将kset_ktyp赋值给kset->kobj.parent
kset->kobj.kset = NULL; //将NULL赋值给kset->kobj.kset
return kset;
}
从上面标红的注释我们发现kset内嵌的kobject的重要性了。这是kset和kobject的重要关系。有一句话来形容叫我中有你,你中有我。接下来我们将做好的kset的指针的形式传给kset_register.
int kset_register(struct kset *k)
{
int err;
if (!k)
return -EINVAL;
kset_init(k);
err = kobject_add_internal(&k->kobj);
if (err)
return err;
kobject_uevent(&k->kobj, KOBJ_ADD);
return 0;
}
void kset_init(struct kset *k)
{
kobject_init_internal(&k->kobj);
INIT_LIST_HEAD(&k->list);
spin_lock_init(&k->list_lock);
}
接下来就是kobject_add_internal(&k->kobj),又撞到老相识了,让我们再次高歌:结识新朋友,不忘老朋友…
好了,kobject,kobj_type,kset的关系我们大概清楚了,下面是我画的一个图用于表示这三者的关系:
好了,下节我们继续分析。
上节我们分析到int kset_register(struct kset *k)函数中的kobject_add_internal(&k->kobj),我们接着分析,先唤起来一下大家的记忆,给出kset_register的函数定义:
int kset_register(struct kset *k)
{
int err;
if (!k)
return -EINVAL;
kset_init(k);
err = kobject_add_internal(&k->kobj);
if (err)
return err;
kobject_uevent(&k->kobj, KOBJ_ADD);
return 0;
}
我们接着需要分析kobject_uevent(&k->kobj, KOBJ_ADD),这个东西是用来告诉用户空间有新朋友来啦,他叫什么什么名字。我们来看看它具体是怎么干活的。
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
return kobject_uevent_env(kobj, action, NULL);
}
里面就一个函数:
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 */
top_kobj = kobj;
while (!top_kobj->kset && top_kobj->parent)
top_kobj = top_kobj->parent;
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 uevent_suppress is set*/
if (kobj->uevent_suppress) {
pr_debug(“kobject: ‘%s’ (%p): %s: uevent_suppress “
“caused the event to drop!\n”,
kobject_name(kobj), kobj, __func__);
return 0;
}
/* 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 */
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 */
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);
/* ENOBUFS should be handled in userspace */
if (retval == -ENOBUFS)
retval = 0;
} 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;
}
我们经过前面那些初始化等之后,最后调用了call_usermodehelper函数。
static inline int
call_usermodehelper(char *path, char **argv, char **envp, enum umh_wait wait)
{
struct subprocess_info *info;
gfp_t gfp_mask = (wait == UMH_NO_WAIT) ? GFP_ATOMIC : GFP_KERNEL;
info = call_usermodehelper_setup(path, argv, envp, gfp_mask);
if (info == NULL)
return -ENOMEM;
return call_usermodehelper_exec(info, wait);
}
凭着在linux上的编程经验我们都知道,这个东西按照传入的参数执行了一个程序。
没错,这个就是用于与用户空间交互的重要角色。后们将会在后续合适的章节中详细讲述。
实在是让大家久等了,因为最近离职,所以繁锁的事情很多,今天终于把离职手续给办完了,不过还有一堆事情没有做,如果更新的慢的话,请大家多多包涵。言归正传。
俗话说的好:光说不练假把式。很明显本帅哥不是假把式。为了让大家一起对驱动模型有直观的认识,小弟花了一点点时间写了几个小例子(目前只有一个)。就是为了让大家领会最神秘的驱动模型,有了这些小例子,结合我前面两篇源码分析的文章,哥可以告诉你,你已经具备内核分析的基本能力了。
首先说一下我的环境:我linux是装在虚拟机上的,装的linux发行版是CentOS5.4。后面将内核升级到了2.6.32.3。为什么要选这个版本呢?因为网上有对应的内核升级教程,这可以省掉我很多在我看来不是很重要的文字(哥很懒滴)。可能有的朋友会说直接装一个发行版的linux不就得了,为什么要升级自已的内核呢?这个我觉得有必要说明一下。你如果直接从网上下个发行版装上以后,只有一堆内核头文件(我喜欢用的centos5和rhel5是这样的),如果你要在不是自已写的内核源码中加入调试信息就非常不方便了,如果你自已下载源码编译内核,你就可以直接在内核源码中加调试信息,再编译就OK了。再说了,如果没玩过的同志花个一天时间把这个东西给搞好了,也会增加自已的信心。OK。游戏开始了:
先给出源码:
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/init.h>
#define wwhs_dbg(dbgbuf) printk(KERN_ERR”wwhs:%s\n”,dbgbuf);
static struct kobject *wwhs_kobj;
static int __init wwhs_drvmode_init()
{
wwhs_dbg(“drvmode_init”);
wwhs_kobj = kobject_create_and_add(“wwhs_drvmode”,NULL);
if (!wwhs_kobj)
wwhs_dbg(“out of memory”);
return 0;
}
static void __exit wwhs_drvmode_exit()
{
wwhs_dbg(“drvmode_exit”);
kobject_del(wwhs_kobj);
}
module_init(wwhs_drvmode_init);
module_exit(wwhs_drvmode_exit);
MODULE_AUTHOR(“wwhs”);
MODULE_DESCRIPTION(“wwhs_drvmode”);
MODULE_LICENSE(“GPL”);
下面给出makefile,makefile请根据自已的开发环境进行适当调整。
obj-m+=wwhs_drvmode.o
KERNELDIR=/opt/linux-2.6.32.3
PWD:=$(shell pwd)
all:
make -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o* *.ko* *.mod.c *.cmd *.symvers .tmp_versions .*.cmd
将这两个文件放在同一目录下,然后进入root 模式。
make一下。
如果不出意外的话应该可以生成wwhs_drvmode.ko这样一个内核模块。
执行:insmod wwhs_drvmode.ko
执行:dmesg
应该可以看到打印信息了吧。
好,我们进去/sys目录下看看。
见证奇迹的时候到了,我们可以激动的看到在/sys目录下面多了一个名为wwhs_drvmode的目录。
执行:rmmod wwhs_drvmode
执行:dmesg
可以看到有exit的打印。
再进/sys目录下,wwhs_drvmode这个目录不见了。
神奇啊!
这与我们之前的源码分析是一一对应的,真是太神奇了。
如果对之前的源码分析没兴趣的同志现在应该有兴趣了吧,如果没看懂马上回过头去看第一二小节,再仔细看一遍。后面游戏会越来越好玩。先抽根烟。下回见。
今天又升级了最新版内核2.6.38.5,编了一下,我之前写的几个模块都可以用,看来这一块没有太大差异。顺便把内核升级写了一个相关的文档。大家如果没升级过内核的可以参考一下:http://www.linuxidc.com/Linux/2011-05/35838.htm。好了。上节我们用最直观的方式知道了kobject_create_and_add(“wwhs_drvmode”,NULL)这个函数做了些什么事。那kset呢?经过我们在最前面两小节的分析可以知道kset_register其实就比kobject多了一个函数kobject_uevent。先给出kset_register的定义吧:
int kset_register(struct kset *k)
{
int err;
if (!k)
return -EINVAL;
kset_init(k);
err = kobject_add_internal(&k->kobj);
if (err)
return err;
kobject_uevent(&k->kobj, KOBJ_ADD);
return 0;
}
记起来了吧。这个讲还是为时过早,反正大家只要知道这个东西是用来通知用户空间的就行了。
接下来我们来个大家感兴趣一点的:bus_register。下面这个是经过我改装过的小例子。总共三个文件:
1.wwhs_public.h
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/kobject.h>
#define wwhs_dbg(dbgbuf) printk(KERN_ERR”wwhs:%s\n”,dbgbuf);
2.wwhs_bus.c
#include “../wwhs_public.h”
static int wwhs_bus_match(struct device *dev, struct device_driver *drv)
{
return 1;
}
struct bus_type wwhs_bus_type = {
.name = “wwhs_bus”,
.match = wwhs_bus_match,
};
static int __init wwhs_bus_init()
{
wwhs_dbg(“bus register”);
return bus_register(&wwhs_bus_type);
}
static void __exit wwhs_bus_exit()
{
wwhs_dbg(“bus unregister”);
bus_unregister(&wwhs_bus_type);
}
module_init(wwhs_bus_init);
module_exit(wwhs_bus_exit);
MODULE_AUTHOR(“wwhs”);
MODULE_DESCRIPTION(“wwhs_bus test”);
MODULE_LICENSE(“GPL”);
3.Makefile
obj-m+=wwhs_bus.o
KERNELDIR=/opt/kernel/linux-2.6.38/linux-2.6.38.5
PWD:=$(shell pwd)
all:
make -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o* *.ko* *.mod.c *.cmd *.symvers .tmp_versions .*.cmd
编译并安装模块,我们发现在/sys/bus下面多了一个目录:wwhs_bus。进去看看:
Ls
devices drivers drivers_autoprobe drivers_probe uevent
多了五个东西。
下面我们来结合源码对bug_register进行详细的分析。
int bus_register(struct bus_type *bus)
{
int retval;
struct subsys_private *priv;
priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->bus = bus;
bus->p = priv;
BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
retval = kobject_set_name(&priv->subsys.kobj, “%s”, bus->name);
if (retval)
goto out;
priv->subsys.kobj.kset = bus_kset;
priv->subsys.kobj.ktype = &bus_ktype;
priv->drivers_autoprobe = 1;
retval = kset_register(&priv->subsys);
if (retval)
goto out;
retval = bus_create_file(bus, &bus_attr_uevent);
if (retval)
goto bus_uevent_fail;
priv->devices_kset = kset_create_and_add(“devices”, NULL,
&priv->subsys.kobj);
if (!priv->devices_kset) {
retval = -ENOMEM;
goto bus_devices_fail;
}
priv->drivers_kset = kset_create_and_add(“drivers”, NULL,
&priv->subsys.kobj);
if (!priv->drivers_kset) {
retval = -ENOMEM;
goto bus_drivers_fail;
}
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&priv->klist_drivers, NULL, NULL);
retval = add_probe_files(bus);
if (retval)
goto bus_probe_files_fail;
retval = bus_add_attrs(bus);
if (retval)
goto bus_attrs_fail;
pr_debug(“bus: ‘%s’: registered\n”, bus->name);
return 0;
bus_attrs_fail:
remove_probe_files(bus);
bus_probe_files_fail:
kset_unregister(bus->p->drivers_kset);
bus_drivers_fail:
kset_unregister(bus->p->devices_kset);
bus_devices_fail:
bus_remove_file(bus, &bus_attr_uevent);
bus_uevent_fail:
kset_unregister(&bus->p->subsys);
out:
kfree(bus->p);
bus->p = NULL;
return retval;
}
在函数中首先们感兴趣的是:
priv->subsys.kobj.kset = bus_kset;
priv->subsys.kobj.ktype = &bus_ktype;
priv->drivers_autoprobe = 1;
我们通过对代码进行查找发现bus_kset其定义中下:
static struct kset *bus_kset;
很明显会有别的地方对它已经进行初始化了。我们在bus.c文件中发现有这样的一个函数:
int __init buses_init(void)
{
bus_kset = kset_create_and_add(“bus”, &bus_uevent_ops, NULL);
if (!bus_kset)
return -ENOMEM;
return 0;
}
而bus_uevent_ip的定义如下:
static const struct kset_uevent_ops bus_uevent_ops = {
.filter = bus_uevent_filter,
};
其中��数bus_uevent_filter,定义如下:
static int bus_uevent_filter(struct kset *kset, struct kobject *kobj)
{
struct kobj_type *ktype = get_ktype(kobj);
if (ktype == &bus_ktype)
return 1;
return 0;
}
OK,下一个, bus_ktype:
static struct kobj_type bus_ktype = {
.sysfs_ops = &bus_sysfs_ops,
};
static const struct sysfs_ops bus_sysfs_ops = {
.show = bus_attr_show,
.store = bus_attr_store,
};
static ssize_t bus_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct bus_attribute *bus_attr = to_bus_attr(attr);
struct subsys_private *subsys_priv = to_subsys_private(kobj);
ssize_t ret = 0;
if (bus_attr->show)
ret = bus_attr->show(subsys_priv->bus, buf);
return ret;
}
static ssize_t bus_attr_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct bus_attribute *bus_attr = to_bus_attr(attr);
struct subsys_private *subsys_priv = to_subsys_private(kobj);
ssize_t ret = 0;
if (bus_attr->store)
ret = bus_attr->store(subsys_priv->bus, buf, count);
return ret;
}
接下来是:
kset_register(&priv->subsys);
又见到老朋友了,这个老朋友将会帮我们在/sys/bus下创建一个名为wwhs_bus的目录。
接下来是bus_create_file(bus, &bus_attr_uevent);
bus_attr_uevent 是什么呢?用sourceinsight看是黑的,直接点击的话是跳转不了的,另外有很多高手推荐的vim、emacs,或者再装上高级的插件cscope、ctags也肯定是直接找不到的。
除非你自已根据linux内核的语法规则自已再写个插件还差不多,但前提条件是你要先手工找到熟悉规则才能写得出来,否则,你永远也别想直接找到。
不过没事,哥已经帮你找到了,在这:
static BUS_ATTR(uevent, S_IWUSR, NULL, bus_uevent_store);
再看gh BUS_ATTR这个宏:
#define BUS_ATTR(_name, _mode, _show, _store) \
struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)
明白了吧。
static ssize_t bus_uevent_store(struct bus_type *bus,
const char *buf, size_t count)
{
enum kobject_action action;
if (kobject_action_type(buf, count, &action) == 0)
kobject_uevent(&bus->p->subsys.kobj, action);
return count;
}
很明显我们上面分析的这一堆代码会在/sys/bus/wwhs_bus目录之下创建一个叫uevent的文件。并且我们如果往这个文件中写入字符的时候会触发bus_uevent_store函数的执行。这个函数实在简单的连我这种笨鸟都不想分析,各位大侠就自个看一看吧。
接下来要登场的是一女一男,女的不但身材绝美,面容娇好,身怀绝技,且用情专一,一生只为一个人服务。男的虽然才高八斗,学富五车,但是确是个花心大萝卜,只要能达到他的条件,都会为女方服务,比如我们马上要讲的这位男同志,就只要身材好,漂亮,他就愿意为人家服务。扯了半天,我们的美女就是device,帅哥就是device_driver。总线作为红娘,肯定同一时间不止为一对男女提供机会,<<非诚勿扰>>和<<我们约会吧>>这种市井节目都知道一次邀请多位男女同志一起配对,难道我们英明神武的内核开发者不会?不可能嘛。所以我们内核作者和何炅老师一样的聪明,他分别利用下面这段代码创建了一个美女阵营和一个帅哥阵营。
priv->devices_kset = kset_create_and_add(“devices”, NULL,
&priv->subsys.kobj); //美女阵营
if (!priv->devices_kset) {
retval = -ENOMEM;
goto bus_devices_fail;
}
priv->drivers_kset = kset_create_and_add(“drivers”, NULL,
&priv->subsys.kobj); //帅哥阵营
if (!priv->drivers_kset) {
retval = -ENOMEM;
goto bus_drivers_fail;
}
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put); //美女阵营
klist_init(&priv->klist_drivers, NULL, NULL); //帅哥阵营
接下来就是:
static int add_probe_files(struct bus_type *bus)
{
int retval;
retval = bus_create_file(bus, &bus_attr_drivers_probe);
if (retval)
goto out;
retval = bus_create_file(bus, &bus_attr_drivers_autoprobe);
if (retval)
bus_remove_file(bus, &bus_attr_drivers_probe);
out:
return retval;
}
凭着刚才对bus_attr_uevent 的分析,我们可以利用人本身的惰性来查找上面的几个在sourceinsight下的灰色成员。
是的这两个成员就是:
static BUS_ATTR(drivers_probe, S_IWUSR, NULL, store_drivers_probe);
static BUS_ATTR(drivers_autoprobe, S_IWUSR | S_IRUGO,
show_drivers_autoprobe, store_drivers_autoprobe);
在这里可以告诉大家一个技巧:linux内核在用宏定义这类变量时,都会在需要调用这个变量的最近的地方定义,不会离的很远。
显然,我们会在/sys/bus/wwhs_bus/目录下再创建两个文件:drivers_autoprobe 、drivers_probe。都是很简单的几个函数。列一下吧:
static ssize_t store_drivers_probe(struct bus_type *bus,
const char *buf, size_t count)
{
struct device *dev;
dev = bus_find_device_by_name(bus, NULL, buf);
if (!dev)
return -ENODEV;
if (bus_rescan_devices_helper(dev, NULL) != 0)
return -EINVAL;
return count;
}
static ssize_t show_drivers_autoprobe(struct bus_type *bus, char *buf)
{
return sprintf(buf, “%d\n”, bus->p->drivers_autoprobe);
}
static ssize_t store_drivers_autoprobe(struct bus_type *bus,
const char *buf, size_t count)
{
if (buf[0] == ‘0’)
bus->p->drivers_autoprobe = 0;
else
bus->p->drivers_autoprobe = 1;
return count;
}
至此bug_register分析完了,六个文件(目录也是文件)也创建完成了。