在linux驱动模型中,为了便于管理各种设备,我们把不同设备分别挂在他们对应的总线上,设备对应的驱动程序也在总线上找,这样就提出了deivce-bus-driver的模型,硬件上有许多设备总线,那么我们就在设备模型上抽象出bus概念,相应的device就代表设备,driver表示驱动,在代码中它们对应的结构体下面介绍,对于实际的设备及总线,这些结构体就可以嵌入到实际总线上。

1. bus

了解bus,就要先介绍下bus的结构体,一条总线定义完后要注册到系统中,第二节介绍注册函数,最后再介绍下其他一些相关API

1.1 struct bus_type

struct bus_type {const char      *name;--------------------------------------------总线名字const char      *dev_name;struct device       *dev_root;struct device_attribute *dev_attrs; /* use dev_groups instead */const struct attribute_group **bus_groups;const struct attribute_group **dev_groups;const struct attribute_group **drv_groups;int (*match)(struct device *dev, struct device_driver *drv);-------匹配函数(用于匹配device&driver)int (*uevent)(struct device *dev, struct kobj_uevent_env *env);int (*probe)(struct device *dev);----------------------------------用于初始化驱动int (*remove)(struct device *dev);void (*shutdown)(struct device *dev);int (*online)(struct device *dev);int (*offline)(struct device *dev);int (*suspend)(struct device *dev, pm_message_t state);-----------PM相关int (*resume)(struct device *dev);--------------------------------PM相关const struct dev_pm_ops *pm;--------------------------------------PM相关const struct iommu_ops *iommu_ops;struct subsys_private *p;struct lock_class_key lock_key;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

1.2 注册总线

int bus_register(struct bus_type *bus)
{int retval;struct subsys_private *priv;struct lock_class_key *key = &bus->lock_key;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);--------------------在/sys/bus目录下创建当前总线目录if (retval)goto out;retval = bus_create_file(bus, &bus_attr_uevent);----------在当前总线目录下创建文件ueventif (retval)goto bus_uevent_fail;priv->devices_kset = kset_create_and_add("devices", NULL,&priv->subsys.kobj);-----------------在当前总线目录下创建devices目录if (!priv->devices_kset) {retval = -ENOMEM;goto bus_devices_fail;}priv->drivers_kset = kset_create_and_add("drivers", NULL,&priv->subsys.kobj);----------------在当前总线目录下创建drivers目录if (!priv->drivers_kset) {retval = -ENOMEM;goto bus_drivers_fail;}INIT_LIST_HEAD(&priv->interfaces);__mutex_init(&priv->mutex, "subsys mutex", key);klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);klist_init(&priv->klist_drivers, NULL, NULL);retval = add_probe_files(bus);-------------------------在当前总线目录下创建probe相关文件if (retval)goto bus_probe_files_fail;retval = bus_add_groups(bus, bus->bus_groups);if (retval)goto bus_groups_fail;pr_debug("bus: '%s': registered\n", bus->name);return 0;。。。。。。
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

1.3 其他API

总线卸载函数

extern void bus_unregister(struct bus_type *bus);
  • 1

2. device

先介绍devices结构体,在介绍注册函数

2.1 struct device

struct device {struct device       *parent;-------------------------父设备struct device_private   *p;struct kobject kobj;--------------------------------嵌入的kobjectconst char      *init_name; /* initial name of the device */const struct device_type *type;---------------------所属的device类型struct mutex        mutex;  /* mutex to synchronize calls to* its driver.*/struct bus_type *bus;       /* type of bus device is on */---所属的busstruct device_driver *driver;   /* which driver has allocated thisdevice */---------------------------------对应的驱动drivervoid        *platform_data; /* Platform specific data, devicecore doesn't touch it */------------------私有platform数据void        *driver_data;   /* Driver data, set and get withdev_set/get_drvdata */--------------------私有driver数据struct dev_pm_info  power;-----------------------------------PM相关struct dev_pm_domain    *pm_domain;--------------------------PM相关#ifdef CONFIG_PINCTRLstruct dev_pin_info *pins;
#endif#ifdef CONFIG_NUMAint     numa_node;  /* NUMA node this device is close to */
#endifu64     *dma_mask;  /* dma mask (if dma'able device) */u64     coherent_dma_mask;/* Like dma_mask, but foralloc_coherent mappings asnot all hardware supports64 bit addresses for consistentallocations such descriptors. */unsigned long   dma_pfn_offset;struct device_dma_parameters *dma_parms;struct list_head    dma_pools;  /* dma pools (if dma'ble) */struct dma_coherent_mem *dma_mem; /* internal for coherent memoverride */
#ifdef CONFIG_DMA_CMAstruct cma *cma_area;       /* contiguous memory area for dmaallocations */
#endif/* arch specific additions */struct dev_archdata archdata;struct device_node  *of_node; /* associated device tree node */------设备树相关struct fwnode_handle    *fwnode; /* firmware device node */dev_t           devt;   /* dev_t, creates the sysfs "dev" */u32         id; /* device instance */spinlock_t      devres_lock;struct list_head    devres_head;-----------------------------------devres相关struct klist_node   knode_class;struct class        *class;----------------------------------------所属的classconst struct attribute_group **groups;  /* optional groups */void    (*release)(struct device *dev);struct iommu_group  *iommu_group;bool            offline_disabled:1;bool            offline:1;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

2.2 设备注册函数

device的注册函数device_register分两步,先初始化device_initialize,主要是初始化所属的kset为/sys/devices目录,及其他(如PM相关),然后再注册,函数为device_add

int device_add(struct device *dev)
{struct device *parent = NULL;struct kobject *kobj;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);------------------有初始name就设置dev->init_name = NULL;}/* subsystems can specify simple device enumeration */if (!dev_name(dev) && dev->bus && dev->bus->dev_name)dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);---没有初始name就设置默认的if (!dev_name(dev)) {error = -EINVAL;goto name_error;}pr_debug("device: '%s': %s\n", dev_name(dev), __func__);parent = get_device(dev->parent);kobj = get_device_parent(dev, parent);if (kobj)dev->kobj.parent = kobj;/* use parent numa_node */if (parent && (dev_to_node(dev) == NUMA_NO_NODE))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);---在/sys/devices目录下创建当前设备目录if (error)goto Error;/* notify platform of device entry */if (platform_notify)platform_notify(dev);error = device_create_file(dev, &dev_attr_uevent);--------在当前设备目录下创建文件ueventif (error)goto attrError;error = device_add_class_symlinks(dev);-------------------创建链接文件if (error)goto SymlinkError;error = device_add_attrs(dev);----------------------------创建其他文件,如在class目录下if (error)goto AttrsError;error = bus_add_device(dev);------------------------------device加入到bus-device的链表中if (error)goto BusError;error = dpm_sysfs_add(dev);if (error)goto DPMError;device_pm_add(dev);if (MAJOR(dev->devt)) {error = device_create_file(dev, &dev_attr_dev);if (error)goto DevAttrError;error = device_create_sys_dev_entry(dev);if (error)goto SysEntryError;devtmpfs_create_node(dev);}/* Notify clients of device addition.  This call must come* after dpm_sysfs_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);--------__device_attach-------调用此函数来和driver进行匹配if (parent)-----------------------------------------加入到父设备链表中klist_add_tail(&dev->p->knode_parent,&parent->p->klist_children);if (dev->class) {-----------------------------------和class相关的操作mutex_lock(&dev->class->p->mutex);/* tie the class to the device */klist_add_tail(&dev->knode_class,&dev->class->p->klist_devices);/* notify any interfaces that the device is here */list_for_each_entry(class_intf,&dev->class->p->interfaces, node)if (class_intf->add_dev)class_intf->add_dev(dev, class_intf);mutex_unlock(&dev->class->p->mutex);}
。。。。。。
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115

具体device如何匹配到对应的driver在函数__device_attach中进行,加入开始device没有driver,那么会在bus中找到对应的driver,看下函数__device_attach

static int __device_attach(struct device *dev, bool allow_async)
{int ret = 0;device_lock(dev);if (dev->driver) {if (device_is_bound(dev)) {ret = 1;goto out_unlock;}ret = device_bind_driver(dev);---------如果已经有driver,那么就绑定到device并进行probeif (ret == 0)ret = 1;else {dev->driver = NULL;ret = 0;}} else {struct device_attach_data data = {.dev = dev,.check_async = allow_async,.want_async = false,};if (dev->parent)pm_runtime_get_sync(dev->parent);ret = bus_for_each_drv(dev->bus, NULL, &data,__device_attach_driver);----如果没有driver,就遍历总线上的driver,直到找到并进行probeif (!ret && allow_async && data.have_async) {/** If we could not find appropriate driver* synchronously and we are allowed to do* async probes and there are drivers that* want to probe asynchronously, we'll* try them.*/dev_dbg(dev, "scheduling asynchronous probe\n");get_device(dev);async_schedule(__device_attach_async_helper, dev);} else {pm_request_idle(dev);}if (dev->parent)pm_runtime_put(dev->parent);}
out_unlock:device_unlock(dev);return ret;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

2.3 其他API

device卸载函数

extern void device_unregister(struct device *dev);
  • 1

3. driver

先介绍driver的结构体,再介绍注册函数

3.1 struct device_driver

struct device_driver {const char      *name;-----------------------------driver名字struct bus_type     *bus;--------------------------所属总线struct module       *owner;const char      *mod_name;  /* used for built-in modules */bool suppress_bind_attrs;   /* disables bind/unbind via sysfs */enum probe_type probe_type;const struct of_device_id   *of_match_table;const struct acpi_device_id *acpi_match_table;int (*probe) (struct device *dev);----------------探测初始化函数int (*remove) (struct device *dev);---------------删除函数void (*shutdown) (struct device *dev);int (*suspend) (struct device *dev, pm_message_t state);---PM相关int (*resume) (struct device *dev);------------------------PM相关const struct attribute_group **groups;const struct dev_pm_ops *pm;-------------------------------PM相关struct driver_private *p;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

3.2 注册函数

int driver_register(struct device_driver *drv)
{int ret;struct device_driver *other;BUG_ON(!drv->bus->p);if ((drv->bus->probe && drv->probe) ||(drv->bus->remove && drv->remove) ||(drv->bus->shutdown && drv->shutdown))printk(KERN_WARNING "Driver '%s' needs updating - please use ""bus_type methods\n", drv->name);other = driver_find(drv->name, drv->bus);if (other) {printk(KERN_ERR "Error: Driver '%s' is already registered, ""aborting...\n", drv->name);return -EBUSY;}ret = bus_add_driver(drv);--------------------主要在这里进行注册if (ret)return ret;ret = driver_add_groups(drv, drv->groups);if (ret) {bus_remove_driver(drv);return ret;}kobject_uevent(&drv->p->kobj, KOBJ_ADD);return ret;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

看下函数bus_add_driver

int bus_add_driver(struct device_driver *drv)
{struct bus_type *bus;struct driver_private *priv;int error = 0;bus = bus_get(drv->bus);-----------------------得到所属的总线if (!bus)return -EINVAL;pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);priv = kzalloc(sizeof(*priv), GFP_KERNEL);if (!priv) {error = -ENOMEM;goto out_put_bus;}klist_init(&priv->klist_devices, NULL, NULL);priv->driver = drv;drv->p = priv;priv->kobj.kset = bus->p->drivers_kset;-------设置所属总线的driverserror = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,"%s", drv->name);------------在所属总线的drivers目录下创建本驱动目录if (error)goto out_unregister;klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);---把本驱动加入到总线驱动链表中if (drv->bus->p->drivers_autoprobe) {if (driver_allows_async_probing(drv)) {pr_debug("bus: '%s': probing driver %s asynchronously\n",drv->bus->name, drv->name);async_schedule(driver_attach_async, drv);} else {error = driver_attach(drv);if (error)goto out_unregister;}}------------------__driver_attach--------------调用此函数来进行探测初始化module_add_driver(drv->owner, drv);error = driver_create_file(drv, &driver_attr_uevent);if (error) {printk(KERN_ERR "%s: uevent attr (%s) failed\n",__func__, drv->name);}error = driver_add_groups(drv, bus->drv_groups);if (error) {/* How the hell do we get out of this pickle? Give up */printk(KERN_ERR "%s: driver_create_groups(%s) failed\n",__func__, drv->name);}if (!drv->suppress_bind_attrs) {error = add_bind_files(drv);if (error) {/* Ditto */printk(KERN_ERR "%s: add_bind_files(%s) failed\n",__func__, drv->name);}}return 0;
。。。。。。。。
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64

那么驱动是如何匹配到对应的device的,继续探索函数__driver_attach

(drivers/base/dd.c)

static int __driver_attach(struct device *dev, void *data)
{struct device_driver *drv = data;int ret;/** Lock device and try to bind to it. We drop the error* here and always return 0, because we need to keep trying* to bind to devices and some drivers will return an error* simply if it didn't support the device.** driver_probe_device() will spit a warning if there* is an error.*/ret = driver_match_device(drv, dev);--------调用总线的match函数来进行匹配if (ret == 0) {/* no match */
        return 0;} else if (ret == -EPROBE_DEFER) {dev_dbg(dev, "Device match requests probe deferral\n");driver_deferred_probe_add(dev);} else if (ret < 0) {dev_dbg(dev, "Bus failed to match device: %d", ret);
        return ret;} /* ret > 0 means positive match */if (dev->parent)    /* Needed for USB */device_lock(dev->parent);device_lock(dev);if (!dev->driver)driver_probe_device(drv, dev);------没有driver就设置此driver为device的driver,然后调用驱动probe初始化device_unlock(dev);if (dev->parent)device_unlock(dev->parent);
return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

3.3 其他API

卸载函数

extern void driver_unregister(struct device_driver *drv);

linux设备驱动模型 - device/bus/driver相关推荐

  1. LINUX设备驱动模型分析之三 驱动(DRIVER)接口分析

    上一章我们分析了bus-driver-device模型中bus接口部分,本章我们将分析driver接口,在bus-driver-device模型中,driver接口是依附于bus上,而不像device ...

  2. Linux设备驱动模型1——简介和底层架构

    以下内容源于朱有鹏<物联网大讲堂>课程的学习整理,如有侵权,请告知删除. 一.linux设备驱动模型简介 1.什么是设备驱动模型? (1)类class.总线bus.设备device.驱动d ...

  3. linux设备驱动模型及其他,Linux设备驱动模型

    Linux设备驱动模型,主要函数分析 整个驱动模型中,最核心的三个函数分别是 __bus_register.driver_register.device_register int __bus_regi ...

  4. linux平台设备驱动模型是什么意思,Linux设备驱动模型之我理解

    点击(此处)折叠或打开 /* my_bus.c   */ #include #include #include #include #include #include "my_bus.h&qu ...

  5. Linux设备驱动模型之platform(平台)总线详解

    /********************************************************/ 内核版本:2.6.35.7 运行平台:三星s5pv210 /*********** ...

  6. Linux设备驱动模型三 kset

    Linux设备驱动模型三 kset 1 kset数据结构 kset的定义在前文已有描述,我们再回顾一下: [cpp] view plain copy struct kset { /*与子kobject ...

  7. 五.linux设备驱动模型

    站在设备驱动这个角度分析,设备驱动模型是如何构建出来,起到什么作用,认识它并在写驱动的时候去利用设备驱动模型 目录 一.linux 设备驱动模型简介 1.1. 什么是设备驱动模型 1.2. 为什么需要 ...

  8. 整理--linux设备驱动模型

    知识整理–linux设备驱动模型 以kobject为底层,组织类class.总线bus.设备device.驱动driver等高级数据结构,同时实现对象引用计数.维护对象链表.对象上锁.对用户空间的表示 ...

  9. linux 内核驱动模型,linux设备驱动模型架构分析 一

    linux设备驱动模型架构分析 一 发布时间:2018-07-04 15:14, 浏览次数:584 , 标签: linux 概述 LDD3中说:"Linux内核需要一个对系统结构的一般性描述 ...

最新文章

  1. 立体显示与BCN双稳态手性向列相
  2. Centos中文输入法安装以及切换
  3. 纯CSS实现蓝色圆角下拉菜单
  4. java关闭ie提示_java 关闭IE
  5. PO BO VO DTO POJO DAO概念及其作用(附转换图)
  6. 统计学习方法第十一章作业:随机条件场—概率计算问题、IIS/GD学习算法、维特比预测算法 代码实现
  7. md5 java 工具类_Java实现一个MD5工具类
  8. 点喷丸打标机行业调研报告 - 市场现状分析与发展前景预测(2021-2027年)
  9. 电脑显示器尺寸对照表_三分钟带你了解五花八门的显示器参数,买显示器不在跳坑...
  10. HarmonyOS 十分钟快速入门教程|和车神哥一起学
  11. 蓝桥杯c语言基础试题答案,蓝桥杯试题C语言答案.doc
  12. 三段式状态机的写法总结
  13. 几种常见开源软件授权协议
  14. 游戏服务端(MMORPG)的基础算法一、AOI
  15. 非线性方程模型及求解实例
  16. Tables[0].Rows.count是什么意思
  17. 六级阅读真题词组(2016)
  18. virtual 关键字
  19. 实战1 - 空气质量数据的校准
  20. 易风互联网邮件监控系统 v2.21 怎么用

热门文章

  1. 史上最全 Java 多线程面试题及答案 1
  2. Nginx 容器教程
  3. 数据库:MySQL索引总结
  4. 6.Hibernate综述
  5. Android --- GridLayoutManager 设置了 item 均匀分布,但是无效
  6. java实现最长连续子序列_Java实现O(n)最大连续子序列和 | 学步园
  7. ML之分类预测:分类预测评估指标之AUC计算的的两种函数具体代码案例实现
  8. GPU:nvidia-smi的简介、安装、使用方法之详细攻略
  9. NLP:NLP领域没有最强,只有更强的模型——GPT-3的简介、安装、使用方法之详细攻略
  10. Py之featuretools:featuretools库的简介、安装、使用方法之详细攻略