Device Module之Kobject,Ktype,Kset(基于kernel 4.11)
来源:互联网 发布:java模糊查询 编辑:程序博客网 时间:2024/06/01 18:45
笔者:从最初的希望很快结束NET,INTERRUPT subsystem 到现在才发现,仅仅是NET都是涉及内容深广,且不说
相关协议的内容,仅仅是框架也是十分复杂的,最近又在看USB子系统,与裸板驱动USB controller的区别就是框架,
与其说是框架,不如说是Linux大牛们,当初关于USB,PCI,IIC等总线设备的想出的解决方案,我查阅了大量资料,不论
英文还是中文,但是如果想真正去理解,还必须自己去解析一次,自己去梳理下代码,别人做的永远是别人做的。本篇
将解析kobject,kset,ktype所组织的大框架,还有bus,platfrom到device drivers的框架流程图。抽象到不具体任何具体
设备。加油!之前自己开的坑也会填完,解析linux源码,学习内核需要一定的方法和时间,切勿着急出结果。本文的代
码来自最新的4.11内核。
Kobject
在这之前,必须提到笛卡尔的《方法论》,Kobject的提出背景,及Kobject的重要性,甚至于说是以C语言实现OOB
的方法。
struct kobject {const char*name;struct list_headentry;struct kobject*parent; struct kset*kset;struct kobj_type*ktype;struct kernfs_node*sd; /* sysfs directory entry */struct krefkref;#ifdef CONFIG_DEBUG_KOBJECT_RELEASEstruct delayed_workrelease;#endifunsigned 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;};
Method of kobject:
我看了很多的文章,喜欢一开始就总体详细介绍,我总觉得overview能是对作者来说一个总结,而并非读者所希望的
如果一篇文章有一定的逻辑性,循序渐进,才能体现写作者的思路。也是被大家所接受的,kobject,最为一个对象,有它的
method,刚开始关注method,也体现了学习一样东西,先会使用它的思想。
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;//这里我们可以逻辑上认为ktype是与kobject平级的层次 return;error:printk(KERN_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;}
static inline void kref_init(struct kref *kref){atomic_set(&kref->refcount, 1);}
kobj->ktype = ktype;INIT_LIST_HEAD(&kobj->entry);//其他的以后再讨论,再看第二函数
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;}
static __printf(3, 0) 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);}
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);//增加父kobj的引用计数,如何理解这个,请上升到设计的角度分析/* 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;}//如果第一次看kobject,不会理解的,但是可以提前说下,kset是kobject的合集,不指定参数的情况下默认为 //kset的内嵌kobject,之后,kobject的entry加入kset的list成员,注意是双向循环列表的尾部添加一环。error = create_dir(kobj);//创建目录具体的细节比较复杂if (error) {kobj_kset_leave(kobj);kobject_put(parent);kobj->parent = NULL;/* be noisy on error issues */if (error == -EEXIST)WARN(1, "%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));elseWARN(1, "%s failed for %s (error: %d parent: %s)\n", __func__, kobject_name(kobj), error, parent ? kobject_name(parent) : "'none'");} elsekobj->state_in_sysfs = 1;return error;}
该函数形成了框架,总之先kobject_init,接着kobject_add,此时该object会按照一定的规则加入到层次图里面,而sysfs会
有相应的体现,我忽略了create_dir的具体细节,这点做个标记,(默认还忽略了error的分析,对于理解层次框架,error不属于
考虑的范畴)
对了一个必须注意的设计细节就是spin_lock在kref上下文的使用,这点我相信值得品味,如果不想写出来到处都是Bug,多
线程一团糟的代码的话。
/** * kobject_rename - change the name of an object * @kobj: object in question. * @new_name: object's new name * * It is the responsibility of the caller to provide mutual * exclusion between two different calls of kobject_rename * on the same kobject and to ensure that new_name is valid and * won't conflict with other kobjects. */int kobject_rename(struct kobject *kobj, const char *new_name)这个函数没有展开细节,相反注释里面,如果调用这个函数,必须提供mutex机制,这个有caller自己完成。值得注意
kobject_set_name在最新的内核里面被抛弃了。
const char *kobject_name(const struct kobject * kobj);//现在使用这个函数 int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...);//上面单独两个函数的合集,值得注意的是传参数,ktype和kobj的关系,我一直认为是平等的。uevent要牵扯很多,如果详细去分析,恐怕要分析到udev,netlink等很多东西
/** * kobject_uevent_env - send an uevent with environmental data * * @kobj: struct kobject that the action is happening to * @action: action that is happening * @envp_ext: pointer to environmental data * * Returns 0 if kobject_uevent_env() is completed with success or the * corresponding error when it fails. */int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, char *envp_ext[])需要另开记录学习帖子
struct kobject *kobject_get(struct kobject *kobj); void kobject_put(struct kobject *kobj);首先看
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;}
void kobject_put(struct kobject *kobj){if (kobj) {if (!kobj->state_initialized)WARN(1, KERN_WARNING "kobject: '%s' (%p): is not " "initialized, yet kobject_put() is being " "called.\n", kobject_name(kobj), kobj);kref_put(&kobj->kref, kobject_release);}}static inline int kref_sub(struct kref *kref, unsigned int count, void (*release)(struct kref *kref)){WARN_ON(release == NULL);if (atomic_sub_and_test((int) count, &kref->refcount)) {release(kref);return 1;}return 0;}
这两个函数反而比较简单,最底层为原子操作,如果kref为0,调用release的方法
for example:sample/kobject-example.c
static int __init example_init(void){int retval;/* * Create a simple kobject with the name of "kobject_example", * located under /sys/kernel/ * * As this is a simple directory, no uevent will be sent to * userspace. That is why this function should not be used for * any type of dynamic kobjects, where the name and number are * not known ahead of time. */example_kobj = kobject_create_and_add("kobject_example", kernel_kobj);//该函数只是简单创建kobject,并向sysfs注册if (!example_kobj)return -ENOMEM; //但是并没有发送uevent,所以到这里可以简单想出来,uevent的作用和角色/* Create the files associated with this kobject */retval = sysfs_create_group(example_kobj, &attr_group);if (retval)kobject_put(example_kobj);return retval;}
理解attrbute,首先看这个文件中的定义
/* * This module shows how to create a simple subdirectory in sysfs called * /sys/kernel/kobject-example In that directory, 3 files are created: * "foo", "baz", and "bar". If an integer is written to these files, it can be * later read out of it. */static int foo;static int baz;static int bar;/* * The "foo" file where a static variable is read from and written to. */static ssize_t foo_show(struct kobject *kobj, struct kobj_attribute *attr,char *buf){return sprintf(buf, "%d\n", foo);}static ssize_t foo_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count){int ret;ret = kstrtoint(buf, 10, &foo);if (ret < 0)return ret;return count;}/* Sysfs attributes cannot be world-writable. */static struct kobj_attribute foo_attribute =__ATTR(foo, 0664, foo_show, foo_store);//一个属性的完成/* * More complex function where we determine which variable is being accessed by * looking at the attribute for the "baz" and "bar" files. */static ssize_t b_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf){int var;if (strcmp(attr->attr.name, "baz") == 0)var = baz;elsevar = bar;return sprintf(buf, "%d\n", var);}static ssize_t b_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count){int var, ret;ret = kstrtoint(buf, 10, &var);if (ret < 0)return ret;if (strcmp(attr->attr.name, "baz") == 0)baz = var;elsebar = var;return count;}static struct kobj_attribute baz_attribute =__ATTR(baz, 0664, b_show, b_store);static struct kobj_attribute bar_attribute =__ATTR(bar, 0664, b_show, b_store);//其他属性的完成,在这个文件里面提供了属性的类型,方法。且该属性是内联的(static)
/* * Create a group of attributes so that we can create and destroy them all * at once.创造了数组,仅仅是编程风格注意,此时数组成员为attribute,并非kobject_attribute */static struct attribute *attrs[] = {&foo_attribute.attr,&baz_attribute.attr,&bar_attribute.attr,NULL,/* need to NULL terminate the list of attributes */};
/* * An unnamed attribute group will put all of the attributes directly in * the kobject directory. If we specify a name, a subdirectory will be * created for the attributes with the directory being the name of the * attribute group.这里如果我们为attrgroup的name成员赋值,将会导致我们上面的属性文件出现在子目录里面,这个目录就是name */static struct attribute_group attr_group = {.attrs = attrs,};回到最上面
/* Create the files associated with this kobject */retval = sysfs_create_group(example_kobj, &attr_group);if (retval)kobject_put(example_kobj);return retval;//此时仅仅是集中创建了属性文件,
static int internal_create_group(struct kobject *kobj, int update, const struct attribute_group *grp){struct kernfs_node *kn;int error;BUG_ON(!kobj || (!update && !kobj->sd));/* Updates may happen before the object has been instantiated */if (unlikely(update && !kobj->sd))return -EINVAL;if (!grp->attrs && !grp->bin_attrs) {WARN(1, "sysfs: (bin_)attrs not set by subsystem for group: %s/%s\n",kobj->name, grp->name ?: "");return -EINVAL;}if (grp->name) {//这里判断是否给name赋值kn = kernfs_create_dir(kobj->sd, grp->name, S_IRWXU | S_IRUGO | S_IXUGO, kobj);//这里与上面对应,也就是如果没有名字会if (IS_ERR(kn)) { //创建目录,否则不创建if (PTR_ERR(kn) == -EEXIST)sysfs_warn_dup(kobj->sd, grp->name);return PTR_ERR(kn);}} elsekn = kobj->sd;kernfs_get(kn);error = create_files(kn, kobj, grp, update);//创建属性文件if (error) {if (grp->name)kernfs_remove(kn);}kernfs_put(kn);return error;}//更具体的细节,对于刨根问底的应该需要自己解析现在,我门可以做个试验,来做一些验证,将上述文件,编译成模块并且加载
[root@localhost kernel]# lsboot_params debug iommu_groups kexec_crash_loaded kexec_loaded notes rcu_expedited security tracing vmcoreinfoconfig fscaps irq kexec_crash_size mm profiling rcu_normal slab uevent_seqnum[root@localhost kernel]# insmod test.ko insmod: ERROR: could not load module test.ko: No such file or directory[root@localhost kernel]# insmod ~/dhuan/test.ko [root@localhost kernel]# lsboot_params debug iommu_groups kexec_crash_loaded kexec_loaded mm profiling rcu_normal slab uevent_seqnumconfig fscaps irq kexec_crash_size kobject_example notes rcu_expedited security tracing vmcoreinfo[root@localhost kernel]# cd kobject_example/[root@localhost kobject_example]# lsbar baz foo[root@localhost kobject_example]# cat foo0[root@localhost kobject_example]#关于属性有更多详尽的讨论,LDD3里面有具体的介绍,但是值得注意的是kobj_type的结构体
struct kobj_type { void (*release)(struct kobject *kobj); const struct sysfs_ops *sysfs_ops; struct attribute **default_attrs; const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj); const void *(*namespace)(struct kobject *kobj); };接下来也引入ktype的概念
Ktype
This structure is used to describe a particular type of kobject (or, more correctly, of containing object). Every kobject needs to have an associated kobj_type structure; a pointer to that structure must be specified when you call kobject_init() or kobject_init_and_add().The release field in struct kobj_type is, of course, a pointer to the release() method for this type of kobject. The other two fields (sysfs_ops and default_attrs) control how objects of this type are represented in sysfs; they are beyond the scope of this document.The default_attrs pointer is a list of default attributes that will be automatically created for any kobject that is registered with this ktype.摘自内核说明文档的话,ktype应该视为和kobject平级,甚至为kobject的补充,可能有些kobject具有相同的ktype
LDD3中:
Every kobject needs to have an associated kobj_type structure. Confusingly,thepointer to this structure can be found in two different places. The kobject structureitself contains a field (called ktype) that can contain this pointer. If,however,thiskobject is a member of a kset,the kobj_type pointer is provided by that kset instead.这时看kset
Kset
* struct kset - a set of kobjects of a specific type, belonging to a specific subsystem. * * A kset defines a group of kobjects. They can be individually * different "types" but overall these kobjects all want to be grouped * together and operated on in the same manner. ksets are used to * define the attribute callbacks and other common events that happen to * a kobject. * * @list: the list of all kobjects for this kset * @list_lock: a lock for iterating over the kobjects * @kobj: the embedded kobject for this kset (recursion, isn't it fun...) * @uevent_ops: the set of uevent operations for this kset. These are * called whenever a kobject has something happen to it so that the kset * can add new environment variables, or filter out the uevents if so * desired. */struct kset {struct list_head list; //我在输入子系统解析过这个结构体,是双向循环链表spinlock_t list_lock; //锁子,看描述还是不清楚,后面看应用会明白struct kobject kobj;//一切都是kobject组织的,kset本身也是kobjectconst struct kset_uevent_ops *uevent_ops;//kset这个成员一定程度上说明Kset的特点,kset所“管理”的Kobject相同的操作方式};
内核的说明文档有下面的描述:
- A kset is also a subdirectory in sysfs, where the associated kobjects with the kset can show up. Every kset contains a kobject which can be set up to be the parent of other kobjects; the top-level directories of the sysfs hierarchy are constructed in this way.- Ksets can support the "hotplugging" of kobjects and influence how uevent events are reported to user space.
在说明它们之间的关系前,必须列出来kset的method
Method of kset:
static struct kset *kset_create(const char *name,const struct kset_uevent_ops *uevent_ops,struct kobject *parent_kobj){struct kset *kset;int retval;kset = kzalloc(sizeof(*kset), GFP_KERNEL);if (!kset)return NULL;retval = kobject_set_name(&kset->kobj, "%s", name);if (retval) {kfree(kset);return NULL;}kset->uevent_ops = uevent_ops;kset->kobj.parent = parent_kobj;/* * 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->kobj.kset = NULL;return kset;}从kset的数据结构,也能推理出来,无非是填充Kset作为一个kobject应该初始化的地方,和作为kset本身所需要填充的
地方。
1.分配kset空间
2.设置kset->kobject.name (实参)这个名字代表了kset,注意
kset->uevent_ops (实参)
kset->kobj.parent (实参)
kset->kobj.ktype = &kset_ktype; 这是static 全局变量,属于kset共同的属性,有兴趣可以自己追踪
kset->kobj.kset = NULL; 注意kset自己被创建时,自己所属的kset被设置为null。
kset->kobj.kset = NULL; 注意kset自己被创建时,自己所属的kset被设置为null。
void kset_init(struct kset *k){kobject_init_internal(&k->kobj);//最上面解析过INIT_LIST_HEAD(&k->list);spin_lock_init(&k->list_lock);}//简单的继续初始化
int kset_register(struct kset *k){int err;if (!k)return -EINVAL;kset_init(k);err = kobject_add_internal(&k->kobj);//上面解析过,黑盒理解为在sysfs创建相应的目录或文件if (err)return err;kobject_uevent(&k->kobj, KOBJ_ADD);//给用户空间上报事件return 0;}在kobject_uevent中,想大概说几个地方
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;const struct kset_uevent_ops *uevent_ops;int i = 0;int retval = 0;#ifdef CONFIG_NETstruct uevent_sock *ue_sk;#endif .........../* search the kset we belong to */top_kobj = kobj;while (!top_kobj->kset && top_kobj->parent)top_kobj = top_kobj->parent;//kobject依赖parent组织成树形结构,但是kset却把某一类kobject归为一个集合,享有共同的uevent_ops和ktype, //Kset list可以找到所包含的kobject成员,而每一个kobject的kset域指向自己的kset,这样一个层次图就出来了。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 */ //这里注意下,subsystem!if (uevent_ops && uevent_ops->name)subsystem = uevent_ops->name(kset, kobj);elsesubsystem = 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;} ..........................}
/** * kset_unregister - remove a kset. * @k: kset. */void kset_unregister(struct kset *k){if (!k)return;kobject_del(&k->kobj);kobject_put(&k->kobj);}最后这个比较简单,不做解析
for example:sample/kset-example.c
static int __init example_init(void){/* * Create a kset with the name of "kset_example", * located under /sys/kernel/ */example_kset = kset_create_and_add("kset_example", NULL, kernel_kobj);//kernel_kobj是父目录if (!example_kset) //对应的是sys/kernel,return -ENOMEM;/* * Create three objects and register them with our kset */foo_obj = create_foo_obj("foo");//该函数"foo"的object->set被设置为example_ksetif (!foo_obj)goto foo_error;bar_obj = create_foo_obj("bar");if (!bar_obj)goto bar_error;baz_obj = create_foo_obj("baz");if (!baz_obj)goto baz_error;return 0;baz_error:destroy_foo_obj(bar_obj);bar_error:destroy_foo_obj(foo_obj);foo_error:kset_unregister(example_kset);return -EINVAL;}
static struct foo_obj *create_foo_obj(const char *name){struct foo_obj *foo;int retval;/* allocate the memory for the whole object */foo = kzalloc(sizeof(*foo), GFP_KERNEL);if (!foo)return NULL;/* * As we have a kset for this kobject, we need to set it before calling * the kobject core. */foo->kobj.kset = example_kset;//这才是关键,example_kset为static全局变量/* * Initialize and add the kobject to the kernel. All the default files * will be created here. As we have already specified a kset for this * kobject, we don't have to set a parent for the kobject, the kobject * will be placed beneath that kset automatically. */retval = kobject_init_and_add(&foo->kobj, &foo_ktype, NULL, "%s", name);if (retval) {kobject_put(&foo->kobj);return NULL;}//注释详细到不用再做任何说明,但是还是抽出来一些细节来品味/* * We are always responsible for sending the uevent that the kobject * was added to the system. */kobject_uevent(&foo->kobj, KOBJ_ADD);//这个具体的作用需要后面解析更多的东西,包括udevreturn foo;}
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...){va_list args;int retval;kobject_init(kobj, ktype);va_start(args, fmt);retval = kobject_add_varg(kobj, parent, fmt, args);//其实就是变参版本的kobject_addva_end(args);return retval;}
static __printf(3, 0) 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);//变参版本的kobject_set_nameif (retval) {printk(KERN_ERR "kobject: can not set name properly!\n");return retval;}kobj->parent = parent;return kobject_add_internal(kobj);}总体上和以前的函数一样,只不过做了变参的处理,注意foo的创建,kobject->parent为null,而其kset被设置为
example_kset。
其他的不用再解析了,编译该文件,进行观察:
[root@localhost kernel]# lsboot_params debug iommu_groups kexec_crash_loaded kexec_loaded mm profiling rcu_normal slab uevent_seqnumconfig fscaps irq kexec_crash_size kset_example notes rcu_expedited security tracing vmcoreinfo[root@localhost kernel]# cd kset_example/[root@localhost kset_example]# lsbar baz foo[root@localhost kset_example]# cd baz[root@localhost baz]# lsbar baz foo[root@localhost baz]# cd ..&& cd foo && lsbar baz foo[root@localhost foo]#注意一点,kobject->type三者都设置了相同的值,所以这里呈现了每个目录里面都是三个属性文件bar,baz,foo和代码分析
的也是一致的,
而kset_example所给其list所指向的kobject提供了uevent_ops的方法。
到这里已经有点清楚了。
第一篇暂时到这里,第二篇会解析一些实际代码来体现整个目录的形成过程。
阅读全文
0 0
- Device Module之Kobject,Ktype,Kset(基于kernel 4.11)
- kobject, kset, ktype
- kobject kset和ktype分析
- Linux驱动之Kobject、Kset (二)uevent mdev ktype type
- Linux驱动之Kobject、Kset (二)uevent mdev ktype type
- Linux内核修炼之kobject,ktype,kset,subsys关系
- Device Module之Bus(基于kernel 4.11)
- Device Module之platform详细分析(基于kernel 4.11)
- Device Module之device,driver和class(基于kernel 4.11)
- 关于linux设备模型kobject,kset,ktype
- 关于linux设备模型kobject,kset,ktype
- 关于linux设备模型kobject,kset,ktype
- 关于linux设备模型kobject,kset,ktype
- linux对象系统---kobject, ktype, kset, subsys
- 关于linux设备模型kobject,kset,ktype
- 关于linux设备模型kobject,kset,ktype
- Kernel 心路历程 kobject/kset 1
- 1.the linux device model--kobject kset
- Codeforces 365C Matrix 暴力
- Android 中aidl调用执行线程和同步异步问题
- Lintcode——整数排序 II
- js 前端验证表单输入
- SQL Server数据库的基本操作
- Device Module之Kobject,Ktype,Kset(基于kernel 4.11)
- 架构的理解
- android的Environment类
- Java多线程之this与Thread.currentThread()的区别——java多线程编程核心技术
- 在Android中单独编译linux kernel驱动模块
- 故障-未识别的网络的几种情况
- 机器学习-决策树算法
- Android -- Activity,Fragment切换动画。
- C++ 将时间戳转换成标准时间