版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xi_xix_i/article/details/134388432
在使用linux的RTC框架的时候,使用rtc_device_register()
函数时出现内核错误,报错如下:kobject: ‘null’ (0000000007394d5b): is not initialized, yet kobject_get() is being called. 以及 bl5372:failed to add char device 253:0
先贴上调用该函数前后部分的代码:
static int bl5372_read_time(struct device * device_, struct rtc_time * time_struct)
{
...
}
static int bl5372_set_time(struct device * device_, struct rtc_time * time_struct)
{
...
}
...
...
char* device_name = "BL5372";
const struct rtc_class_ops rtc_ioctl_ops =
{
.read_time = bl5372_read_time,
.set_time = bl5372_set_time,
};
struct device parent_device = {
.init_name = "parent_device",
};
...
...
int bl5372_module_init(void) /* modprobe .ko的时候执行*/
{
...
bl5372_data.rtc_device_ = rtc_device_register(device_name, parent_device, &rtc_ioctl_ops, THIS_MODULE);
}
先说结论,出现这个错误的原因是因为在使用rtc_device_register()
时传入的第二个参数dev没有注册,也就是这个RTC设备的父设备没有注册导致的
在分析了rtc_device_register()
函数所做的事情后(参考我的另外一篇博客),我想当然的认为该函数传入的第二个参数就是指定要创建的rtc
设备的父设备,而且这个父设备在使用Linux的RTC框架的用处并不多,唯一有用的就是可以在自己定义的RTC底层操作集函数中来获取这个父设备的私有数据,并以此来传递一些信息(如何传递一些数据呢,我看了看内核中自带的一些rtc驱动,通过设置设备结构体的私有数据来实现,在这篇博客中记录了一下)。所以我以为随便定义一个struct device
类型的变量作为该参数传入就可以了,但是事实并非如此。
首先看一下内核报错的信息,根据call trace
来看,程序是在执行到rtc_device_register()
下的cdev_device_add()
时出错了。
先看一下cdev_device_add()
函数的源码,该函数位于fs/char_dev.c下:
int cdev_device_add(struct cdev *cdev, struct device *dev) /* 传进来的是rtc->dev */
{
int rc = 0;
if (dev->devt) {
cdev_set_parent(cdev, &dev->kobj); /* 这个是给cdev设备设置父节点的,就是把cdev的kobj.parent = dev->kobj了 */
rc = cdev_add(cdev, dev->devt, 1); /* 这里面会调用kobject_get(cdev->kobj.parent),但是没问题,这个kobj已经被initialize了 */
if (rc)
return rc;
}
rc = device_add(dev); /* 根据calltrace的提示,是这里出问题了 */
if (rc)
cdev_del(cdev);
return rc;
}
其中涉及到一个比较重要的变量,就是dev->kobj
,此为struct device
下的一个变量,其类型为struct kobject
,定义于include/linux/kobject.h
中
include/linux/device.h:
struct device {
...
struct kobject kobj;
...
};
include/linux/kobject.h:
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct kernfs_node *sd; /* sysfs directory entry */
struct kref kref;
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
struct delayed_work release;
#endif
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;
};
其中的变量state_initialized
用来指示该kobject是否已经初始化,也是后面理解和解决BUG的关键。而至于struct kobject
是什么东西?是最终boss,是linux内核的基石之一,是linux下设备模型的大一统。这里linux使用了面向对象的设计思想来设计设备模型,也就是说,struct kobject
是设备模型的基类,其余像是struct device
什么的都是公有“继承”(以struct device
为例,就是结构体中包含了一个struct kobject
类型的变量)了这个基类。那么每个继承了struct kobject
的子类都能访问struct kobject
的成员变量,也会有自己的一些成员变量。如果想要继续了解的话,可以看下这几篇文章和专栏,这是我看到的写的挺好的:
言归正传,继续来解决BUG。虽然后面知道问题出在devcie_add()里了(call_trace里也提到了),但是解决问题的时候还是把这几个函数看了一遍,出现BUG的cdev_device_add()
函数里面只调用了三个函数:
先来看看cdev_set_parent()
函数吗,该函数位于fs/char_dev.c中:
void cdev_set_parent(struct cdev *p, struct kobject *kobj)
{
WARN_ON(!kobj->state_initialized);
p->kobj.parent = kobj;
}
很简单,就是判断是否传入的kobj(rtc->dev->kobj)
是否已经初始化,如果没有的话就报出警告。随后令p->kobj
的父kobj
指向传入的kobj
,最终的效果就是:rtc->cdev->kobj.paret = rtc->dev->kobj
。
那么报错是不是从这里的WARN_ON发起的呢?好像并没有对传入的rtc->dev->kobj
的state_initialized
进行过初始化啊?实则不然,在rtc_device_register()
函数中调用过一个函数叫rtc_allocate_device()
函数,之前写笔记的时候只是写了一下这个函数对定时器的初始化,但是其实设备的初始化也是在这个设备中进行的,再回头看一下这个函数:
drivers/rtc/class.c:
struct rtc_device *rtc_device_register(const char *name, struct device *dev,
const struct rtc_class_ops *ops,
struct module *owner)
{
...
rtc = rtc_allocate_device(); /* 这里面调用了device_initialize(&rtc->dev)-->kobject_init(&(rtc->dev)->kobj, &device_ktype);*/
...
}
drivers/rtc/class.c:
static struct rtc_device *rtc_allocate_device(void)
{
struct rtc_device *rtc;
rtc = kzalloc(sizeof(*rtc), GFP_KERNEL);
if (!rtc)
return NULL;
...
device_initialize(&rtc->dev);
...
return rtc;
}
从来没见过device_initialize()这个函数,接下来看一下这个device_initialize()
函数做了什么事情,该函数位于drivers/base/core.c中:
void device_initialize(struct device *dev)
{
dev->kobj.kset = devices_kset;
kobject_init(&dev->kobj, &device_ktype);
INIT_LIST_HEAD(&dev->dma_pools);
mutex_init(&dev->mutex);
lockdep_set_novalidate_class(&dev->mutex);
spin_lock_init(&dev->devres_lock);
INIT_LIST_HEAD(&dev->devres_head);
device_pm_init(dev);
set_dev_node(dev, -1);
#ifdef CONFIG_GENERIC_MSI_IRQ
INIT_LIST_HEAD(&dev->msi_list);
#endif
INIT_LIST_HEAD(&dev->links.consumers);
INIT_LIST_HEAD(&dev->links.suppliers);
dev->links.status = DL_DEV_NO_DRIVER;
}
其他函数暂时不考虑,重点看一下kobject_init()
函数,该函数位于lib/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 */
pr_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:
pr_err("kobject (%p): %s\n", kobj, err_str);
dump_stack();
}
...
...
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; /* 初始化 */
}
可以看到,最终在kobject_init_internal()
函数中完成了对rtc->dev->kobj->state_initialized
做了初始化。所以在cdev_set_parent()
函数中的WARN_ON是不起作用的,所以错误并不是在这个函数中。接着来看一下cdev_add()
函数,该函数同样位于fs/char_dev.c中:
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
int error;
p->dev = dev;
p->count = count;
error = kobj_map(cdev_map, dev, count, NULL,
exact_match, exact_lock, p);
if (error)
return error;
kobject_get(p->kobj.parent);
return 0;
}
让我们看一下其中调用的kobject_get()函数,该函数位于lib/kobject.c:
struct kobject *kobject_get(struct kobject *kobj)
{
if (kobj) {
if (!kobj->state_initialized)
WARN(1, KERN_WARNING
"kobject: '%s' (%p): is not initialized, yet kobject_get() is being called.\n",
kobject_name(kobj), kobj);
kref_get(&kobj->kref);
}
return kobj;
}
等等,感觉什么地方有点眼熟,看一眼内核崩溃时报的错:kobject: ‘null’ (0000000007394d5b): is not initialized, yet kobject_get() is being called. 与WARN中的内容是不是一模一样?算是找到了问题所在,就是在某处调用kobject_get()函
数时,传入的kobject->state_initialized
没有被初始化导致的,也就是没有对该kobjcet调用kobject_init()
函数。
但是问题是不是出现在这里?是不是出现在cdev_add()
函数中?其实并不是,在cdev_add()
函数中调用kobject_get()
时传入的是p->kobj.parent
,也就是rtc->dev->kobject
。根据前面梳理的内容,已经调用了kobject_init()
函数对rtc->dev->kobject
进行了初始化,所以问题不是出在这里。
那么问题只能是出在最后一个函数,也就是device_add()
函数,该函数位于drivers/base/core.c中,由于该函数内容较多,所以只放上部分源码:
int device_add(struct device *dev)
{
...
dev = get_device(dev);
if (!dev)
goto done;
if (!dev->p) {
error = device_private_init(dev);
if (error)
goto done;
}
...
parent = get_device(dev->parent); /* 应该是这里出问题了,因为本来dev就是没注册的 */
kobj = get_device_parent(dev, parent);
...
done:
...
}
EXPORT_SYMBOL_GPL(device_add);
问题就出在get_device(dev->parent)中,该函数定义如下:
struct device *get_device(struct device *dev)
{
return dev ? kobj_to_dev(kobject_get(&dev->kobj)) : NULL;
}
可以看到,该函数最终调用了刚才讲的kobject_get()函数,而在这次调用kobject_get()函数时,传入的是rtc->dev->parent.kobjcet
。在分析rtc_device_register()
函数时就提到,刚函数中会将rtc->dev->parent
设为调用rtc_device_register()
时传入的第二个参数,也就是一个struct device
类型的变量。现在就很明了了,在开头我放出的我的驱动程序的代码中,我只是随便传入了一个我自己定义的struct device
类型的变量,肯定是失败的。
其实正点原子教程里面在讲字符设备驱动开发时使用到的device_create()
函数,最终也调用了device_initialize()
函数,这里我把device_create()
函数的层层调用过程贴上来,这些函数全部位于drivers/base/core.c中,但是我没有细究其他细节:
drivers/base/core.c:
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
{
va_list vargs;
struct device *dev;
va_start(vargs, fmt);
dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
va_end(vargs);
return dev;
}
...
...
struct device *device_create_vargs(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt,
va_list args)
{
return device_create_groups_vargs(class, parent, devt, drvdata, NULL,
fmt, args);
}
...
...
device_create_groups_vargs(struct class *class, struct device *parent,
dev_t devt, void *drvdata,
const struct attribute_group **groups,
const char *fmt, va_list args)
{
struct device *dev = NULL;
int retval = -ENODEV;
if (class == NULL || IS_ERR(class))
goto error;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) {
retval = -ENOMEM;
goto error;
}
device_initialize(dev); /* 在这里有调用 */
dev->devt = devt;
dev->class = class;
dev->parent = parent;
dev->groups = groups;
dev->release = device_create_release;
dev_set_drvdata(dev, drvdata);
retval = kobject_set_name_vargs(&dev->kobj, fmt, args);
if (retval)
goto error;
retval = device_add(dev);
if (retval)
goto error;
return dev;
error:
put_device(dev);
return ERR_PTR(retval);
}
所以我只要传入一个使用device_create()注册的设备,应该就能解决该问题。于是我修改了一下我的驱动程序:
static int bl5372_prepare_register_rtc(bl5372_driver_data* bl5372)
{
bl5372->parent_devid = -1;
bl5372->parent_class = NULL;
bl5372->parent_device = NULL;
alloc_chrdev_region(&bl5372->parent_devid, 0, 1, RTC_PARENT_NAME); /* 由系统来申请设备号 */
if(bl5372->parent_devid < 0)
{
printk("alloc parent-devid error!\r\n");
return 0;
}
bl5372->parent_class = class_create(THIS_MODULE, RTC_PARENT_NAME); /* 创建类,最终会加载为/sys/class/xxx(class-name)*/
if(!bl5372->parent_class)
{
printk("create parent-class error!\r\n");
return 0;
}
bl5372->parent_device = device_create(bl5372->parent_class, NULL, bl5372->parent_devid, NULL, RTC_PARENT_NAME); /* 创建设备,最终会加载在:1./dev/xxx(device-name) 2./dev/class/xxx(class-name)/xxx(device-name)*/
if(!bl5372->parent_device)
{
printk("create parent-device error!\r\n");
return 0;
}
dev_set_drvdata(bl5372->parent_device, (void *)&bl5372_data);
}
int bl5372_module_init(void)
{
...
ret = bl5372_prepare_register_rtc(&bl5372_data); /* 这个函数给注册rtc设备做初始化,因为必须要注册设备 */
if (!ret)
{
printk("Init bl5372 parent device fail\n");
goto prepare_rtc_error;
}
...
bl5372_data.rtc_device_ = rtc_device_register(device_name, bl5372_data.parent_device, &rtc_ioctl_ops, THIS_MODULE);
...
}
最终解决该BUG,可以正常注册rtc设备。
但其实有简单的办法,让我们再回头看一下get_device()
函数:
struct device *get_device(struct device *dev)
{
return dev ? kobj_to_dev(kobject_get(&dev->kobj)) : NULL;
}
如果传入的dev为NULL
的话,就不会调用kobject_get()
函数,而是直接返回NULL
,所以最简单的方法就是调用rtc_device_register()时传入的第二个参数为NULL就可以了。为什么没有想到这个呢?因为在看Linux下自带的其他rtc驱动程序在调用rtc_device_register()
时都传入了一个struct device
类型的变量,所以我想当然的认为这个是必须的。
插句题外话。我想结合我这一个月的经验说一下我的观点。首先说一下我的情况:我是看完正点原子的linux驱动开发这个教程之后就开始上手实际的项目中的驱动开发的,有些问题通过正点教程中的公式化方法可以解决,但是有些问题难以解决。所以我就尝试着去深入理解项目中需要用到的linux内核知识,并开始尝试阅读源码来解决bug。但是我发现这样效率很低,因为linux下这么多框架与子系统并不是完全独立的,往往是一环嵌套一环。
所以我认为应该先学习linux内核的机制,了解linux下的框架与子系统到底是如何设计又是如何构成这个庞大的工程的,才能在后面驱动开发的时候得心应手,看起源码来也有一个上层的理论指导。而我就是跳过了这一步,以为学了正点的linux驱动开发就可以上手驱动开发了,现在发现真的不行。所以我认为应该至少看完Robert Love所著的《Linux内核设计与实现》之后再进行开发更好一些。
而且像我这么分析问题,没有一个对linux内核的一个整体的认知,解决问题时的逻辑也是比较混乱的,最终复盘梳理起来逻辑还算清晰,但实际上在解决问题的过程中效率非常低。