Linux内核的I2C驱动框架详解------这应该是我目前600多篇博客中耗时最长的一篇博客
目录
1 I2C驱动整体框架图
2 I2C控制器
2.1 I2C控制器设备--I2C控制器在内核中也被看做一个设备
2.2 i2c控制器驱动程序
2.3 platform_driver结构体中的probe函数做了什么
2.3.1 疑问: i2cdev_notifier_call函数哪里来的
2.3.2 疑问:为什么有两个probe
2.3.3 疑问:of_i2c_register_devices(adap);和bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter)函数的功能是不是重叠了
2.3.4 疑问:platform_bus_type和I2c_bus_type的问题
2.3.5 疑问:为什么i2c_imx_probe函数里面最终还是调用了match和probe函数
3 i2c-core
4 i2c设备
4.1 i2c-client
4.2 i2c-driver
4.2.1 疑问:i2c_register_driver函数中调用driver_register(&driver->driver);函数增加驱动就行了,为什么还调用了i2c_for_each_dev(driver, __process_new_driver);
4.3 probe函数做了什么
4.3.1 疑问:at24_probe里面怎么又有match和probe
5 i2c-tools
5.1 为什么说i2c-tools是一套好用的工具
5.2 为什么说是i2c-tools也是一套示例代码
6 i2c_dev.c通用驱动
7 GPIO模拟I2C
参考文献:
1 I2C驱动整体框架图
上图是I2C系统的整体框架,介绍如下。
- 最上层是应用层,在应用层用户可以直接用open read write对设备进行操作,
- 往下是设备驱动层,这个就是外围的比如一些用I2C总线连接到SOC的传感器或者EEPROM的驱动程序,这个一般由普通驱动工程师负责,
- 再往下的I2C-Core是核心层,这个是Linux内核源码里面本来就有的,这里面主要是一些驱动和设备的注册函数以及i2c_transfer函数,
- 再往下就是I2C控制器驱动,这个一般是由芯片原厂的程序员负责编写,
- 再往下就是具体的硬件了。
上图是I2C驱动的软件框架,介绍如下。
- 首先最右边的是I2C设备驱动,它分为i2c-client和i2c-driver,i2c设备驱动是挂载在i2c_bus_type的,其中i2c-client来自设备树文件,通过of_i2c_register_devices(adap);函数转成i2c-client,然后添加到总线的设备链表中,然后i2c_driver结构体通过注册函数添加到总线的驱动链表中,当新增驱动或者设备时,会调用总线的mach函数进行匹配,然后调用驱动里面的probe函数,在probe函数里面添加一个结构体,然后这个结构体里面就包含设备的读写函数。
- 最左边的是I2C控制器驱动,其中设备树的i2c节点被转换成platform_device,然后添加到platform_bus_type的设备链表中,然后还有一个platform_driver驱动结构体,这个结构体注册到platform_bus_type的驱动链表中,然后当添加设备和驱动的时候,会调用platform_match函数,当匹配之后会调用platform_driver驱动里面的i2x_imx_probe函数。
- 中间是i2x_imx_probe函数里面做的工作,这个函数里面先是调用了device_register把adapter添加到i2c_bus_type的device结构体中,注意是i2c_bus_type,不是platform_bus_type,adapter里面包含一个algorithm成员,这个algorithm里面有master_xfer函数,i2c-core里面的i2c_transfer函数就是调用的algorithm里面的master_xfer函数,然后i2x_imx_probe函数里面还调用了of_i2c_register_device用于添加i2c-client。
以上是i2c驱动的整体介绍,下面分别介绍i2c控制器,i2c-core和i2c设备驱动的相关内容。
2 I2C控制器
2.1 I2C控制器设备--I2C控制器在内核中也被看做一个设备
首先看一下i2c控制器设备,在./Linux-4.9.88/arch/arm/boot/dts/imx6ull.dtsi设备树文件中可以看到i2c节点,
i2c1: i2c@021a0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
reg = <0x021a0000 0x4000>;
interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_I2C1>;
status = "disabled"; #实际使用的时候这个地方要改成"okay"。
};
of_platform_default_populate(NULL, NULL, parent);函数里面把I2C节点转换成platform_device,并添加设备,具体的函数调用关系如下:
of_platform_default_populate(NULL, NULL, parent);
of_platform_populate(root, of_default_bus_match_table, lookup,parent);
of_find_node_by_path("/")//查找设备树的根节点
of_platform_bus_create//这个函数会被循环调用
of_platform_device_create_pdata
of_device_alloc(np, bus_id, parent);
of_device_add(dev)
device_add(&ofdev->dev);
bus_add_device(dev);
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);添加到链表
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,调用bus_notifier
i2cdev_notifier_call
i2cdev_attach_adapter
if (dev->type != &i2c_adapter_type)//直接返回device_create不调用
return 0;
device_create//增加i2c-%d节点bus_probe_device(dev);
device_initial_probe(dev);
__device_attach(dev, true);
bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver);
__device_attach_driver
driver_match_device(drv, dev);
drv->bus->match ? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
really_probe(dev, drv);
dev->bus->probe(dev);或drv->probe(dev)
最后调用platform_driver结构体里面的i2c_imx_probe函数,i2c_imx_probe函数后面会再分析,platform_device设备先看到这里。
static struct platform_driver i2c_imx_driver = {.probe = i2c_imx_probe,.remove = i2c_imx_remove,.driver = {.name = DRIVER_NAME,.pm = I2C_IMX_PM_OPS,.of_match_table = i2c_imx_dt_ids,},.id_table = imx_i2c_devtype,
};
2.2 i2c控制器驱动程序
通过前面 i2c1 节点的 compatible 属性值 可以在 Linux 源码里面找到对应的驱动文件。这里 i2c1节点的compatible 属性值有两个:“fsl,imx6ul-i2c”和“fsl,imx21-i2c”,在 Linux 源码中搜索这两个字符串即可找到对应的驱动文件。I.MX6U 的 I2C 适配器驱动驱动文件为 drivers/i2c/busses/i2c-imx.c,上面i2c控制器设备最终被转成了platform_device,那么i2c控制器驱动采用的也是platform_driver,挂载在platform_bus_type.
看一个驱动先从入口函数开始看,我们找到drivers/i2c/busses/i2c-imx.c文件中的i2c_adap_imx_init函数,首先调用platform_driver_register(&i2c_imx_driver)注册i2c_imx_driver结构体,具体的函数调用关系如下,然后当match函数发现驱动和设备匹配,就会调用驱动里面的额probe函数,也就是i2c_imx_probe函数。
platform_driver_register(&i2c_imx_driver);
__platform_driver_register(drv, THIS_MODULE)
drv->driver.owner = owner;
drv->driver.bus = &platform_bus_type;
drv->driver.probe = platform_drv_probe;
drv->driver.remove = platform_drv_remove;
drv->driver.shutdown = platform_drv_shutdown;
driver_register(&drv->driver);
bus_add_driver(drv);
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);把驱动放到klist_driver
driver_attach(drv);
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
__driver_attach
driver_match_device(drv, dev);
drv->bus->match ? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
ret = really_probe(dev, drv);
dev->bus->probe(dev);或drv->probe(dev)
2.3 platform_driver结构体中的probe函数做了什么
当新增设备或者驱动的时候,都会调用总线的match函数,然后match函数根据compatible属性值或者name去匹配设备和驱动,
* Platform device IDs are assumed to be encoded like this:* "<name><instance>", where <name> is a short description of the type of* device, like "pci" or "floppy", and <instance> is the enumerated* instance of the device, like '0' or '42'. Driver IDs are simply* "<name>". So, extract the <name> from the platform_device structure,* and compare it against the name of the driver. Return whether they match* or not.*/
static int platform_match(struct device *dev, struct device_driver *drv)
{struct platform_device *pdev = to_platform_device(dev);struct platform_driver *pdrv = to_platform_driver(drv);/* When driver_override is set, only bind to the matching driver */if (pdev->driver_override)return !strcmp(pdev->driver_override, drv->name);/* Attempt an OF style match first */if (of_driver_match_device(dev, drv))return 1;/* Then try ACPI style match */if (acpi_driver_match_device(dev, drv))return 1;/* Then try to match against the id table */if (pdrv->id_table)return platform_match_id(pdrv->id_table, pdev) != NULL;/* fall-back to driver name match */return (strcmp(pdev->name, drv->name) == 0);
}
匹配上之后,就会调用驱动结构体里面的probe函数,接下来看一下struct platform_driver i2c_imx_driver结构体中的i2c_imx_probe函数做了什么。
static struct platform_driver i2c_imx_driver = {.probe = i2c_imx_probe,.remove = i2c_imx_remove,.driver = {.name = DRIVER_NAME,.pm = I2C_IMX_PM_OPS,.of_match_table = i2c_imx_dt_ids,},.id_table = imx_i2c_devtype,
};
具体的函数调用关系如下:
i2c_imx_probe
i2c_add_numbered_adapter
__i2c_add_numbered_adapter
i2c_register_adapter
device_register(&adap->dev);
device_add(dev);
bus_add_device(dev);
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);添加到链表
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,调用bus_notifier
i2cdev_notifier_call
i2cdev_attach_adapter
device_create//增加i2c-%d节点
bus_probe_device(dev);
device_initial_probe(dev);
__device_attach(dev, true);
bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver);
__device_attach_driver
driver_match_device(drv, dev);
drv->bus->match ? drv->bus->match(dev, drv) : 1;//匹配不成功,直接返回,
driver_probe_device(drv, dev);上面匹配不成功,这里直接不调用
really_probe(dev, drv);
dev->bus->probe(dev);或drv->probe(dev),of_i2c_register_devices(adap);
of_i2c_register_device(adap, node);
i2c_new_device(adap, &info);用来增加client的
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type;//注意这里是i2c-bus,不是platform_bus
client->dev.type = &i2c_client_type;
client->dev.of_node = info->of_node;
client->dev.fwnode = info->fwnode;
status = device_register(&client->dev);注册新的 i2c_client 设备
device_add(dev);
bus_add_device(dev);
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
bus_probe_device
device_initial_probe(dev);
__device_attach(dev, true);
bus_for_each_drv
__device_attach_driver
driver_match_device
drv->bus->match ? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
really_probe(dev, drv);
dev->bus->probe(dev);或drv->probe(dev)
bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter)
__process_new_adapter(struct device_driver *d, void *data)
i2c_do_add_adapter(struct i2c_driver *driver, struct i2c_adapter *adap)
i2c_detect(adap, driver);通过i2c_detect检测是否有适合的设备连接在总线上
if (!driver->detect || !address_list) return 0;如果没定义detect或address_list就直接返回了
i2c_detect_address(temp_client, driver);
err = driver->detect(temp_client, &info);根据对应client发送一个测试数据如果没有问题则证明这个client是这个驱动所需要的设备,最后将设备添加到链表,最后调用bus_probe_device,尝试绑定驱动。
client = i2c_new_device(adapter, &info);用来增加client的
device_register(&client->dev);
device_add(dev);
bus_add_device(dev);
klist_add_tail
bus_probe_device(dev);
bus_probe_device(dev);
device_initial_probe(dev);
__device_attach(dev, true);
bus_for_each_drv
__device_attach_driver
driver_match_device(drv, dev);
drv->bus->match ? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
really_probe(dev, drv);
dev->bus->probe(dev);或drv->probe(dev)
自己在看内核代码,得到了上面的函数调用流程,但同时有以下几个问题或疑问;
2.3.1 疑问: i2cdev_notifier_call函数哪里来的
上面流程中为什么 blocking_notifier_call_chain(&dev->bus->p->bus_notifier,调用bus_notifier会调用i2cdev_notifier_call,原因在这里。
static int i2cdev_notifier_call(struct notifier_block *nb, unsigned long action,void *data)
{struct device *dev = data;switch (action) {case BUS_NOTIFY_ADD_DEVICE:return i2cdev_attach_adapter(dev, NULL);case BUS_NOTIFY_DEL_DEVICE:return i2cdev_detach_adapter(dev, NULL);}return 0;
}static struct notifier_block i2cdev_notifier = {.notifier_call = i2cdev_notifier_call,
};static int __init i2c_dev_init(void)
{...res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);...
}
2.3.2 疑问:为什么有两个probe
platform_driver 这个结构体里面有个probe函数了,
static struct platform_driver i2c_imx_driver = {.probe = i2c_imx_probe,.remove = i2c_imx_remove,.driver = {.name = DRIVER_NAME,.pm = I2C_IMX_PM_OPS,.of_match_table = i2c_imx_dt_ids,},.id_table = imx_i2c_devtype,
};
可是在注册这个驱动的时候,怎么里面还有个platform_drv_probe函数,
*/
int __platform_driver_register(struct platform_driver *drv,struct module *owner)
{drv->driver.owner = owner;drv->driver.bus = &platform_bus_type;drv->driver.probe = platform_drv_probe;drv->driver.remove = platform_drv_remove;drv->driver.shutdown = platform_drv_shutdown;return driver_register(&drv->driver);
}
看了下代码发现,这是因为外层的platform_drv_probe里面其实最终就是调用了platform_driver里面的probe。
static int platform_drv_probe(struct device *_dev)
{struct platform_driver *drv = to_platform_driver(_dev->driver);struct platform_device *dev = to_platform_device(_dev);int ret;ret = of_clk_set_defaults(_dev->of_node, false);if (ret < 0)return ret;ret = dev_pm_domain_attach(_dev, true);if (ret != -EPROBE_DEFER) {if (drv->probe) {ret = drv->probe(dev); //在这个地方调用了platform_driver的probe函数if (ret)dev_pm_domain_detach(_dev, true);} else {/* don't fail if just dev_pm_domain_attach failed */ret = 0;}}if (drv->prevent_deferred_probe && ret == -EPROBE_DEFER) {dev_warn(_dev, "probe deferral not supported\n");ret = -ENXIO;}return ret;
}
2.3.3 疑问:of_i2c_register_devices(adap);和bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter)函数的功能是不是重叠了
在看上面的函数调用流程的时候,发现of_i2c_register_devices(adap);和 bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter)函数里面都调用了i2c_new_device来添加i2c-client的,那功能岂不是重复了吗,仔细看了下代码发现应该是这样的,of_i2c_register_devices(adap);是从设备树节点中获取设备信息,然后注册i2c-client,而bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter)最终其实是调用的i2c_detect,然后根据驱动里面定义的detect函数和address_list去检测总线上的i2c-client,然后这相当于是添加client的不同的方法,具体解释可以看内核的这个文档:Linux内核中实例化i2c设备的几种方法----./Linux-4.9.88/Documentation/i2c/instantiating-devices文件翻译_陈 洪 伟的博客-CSDN博客
2.3.4 疑问:platform_bus_type和I2c_bus_type的问题
注意在函数i2c_adap_imx_init
static int __init i2c_adap_imx_init(void)
{return platform_driver_register(&i2c_imx_driver);
}
然后进一步调用__platform_driver_register,这时候的总线是platform_bus_type
int __platform_driver_register(struct platform_driver *drv,struct module *owner)
{drv->driver.owner = owner;drv->driver.bus = &platform_bus_type;drv->driver.probe = platform_drv_probe;drv->driver.remove = platform_drv_remove;drv->driver.shutdown = platform_drv_shutdown;return driver_register(&drv->driver);
}
但是在驱动中的probe中注册adapter(控制器)时调用i2c_add_numbered_adapter接口,这时候的总线是i2c_bus_type。
static int i2c_register_adapter(struct i2c_adapter *adap)
{...dev_set_name(&adap->dev, "i2c-%d", adap->nr);//BUS指向I2Cadap->dev.bus = &i2c_bus_type;adap->dev.type = &i2c_adapter_type;res = device_register(&adap->dev);...
又仔细看了下代码理解了一下,其实是这样的,设备树节点中的I2C节点确实是转成platform_device然后挂载到platform_bus总线上的,然后当platform_bus_type的match函数发现设备和驱动匹配后,调用driver结构体中的probe函数,然后再probe函数中构建adapter并且添加,然后adapter是添加到i2c_bus_type的。
2.3.5 疑问:为什么i2c_imx_probe函数里面最终还是调用了match和probe函数
这个 i2c_imx_probe函数是当plarform_bus_type的match函数发现控制器驱动和控制器设备匹配之后调用i2c_imx_probe函数,然后在这里面增加adapter,可是在i2c_imx_probe函数内部一层层的最终怎么又有了drv->bus->match ? drv->bus->match(dev, drv) : 1;和 dev->bus->probe(dev);或drv->probe(dev)函数, probe里面怎么又调用了probe,那内部的probe是用来做什么的。
i2c_imx_probe
i2c_add_numbered_adapter
__i2c_add_numbered_adapter
i2c_register_adapter
device_register(&adap->dev);
device_add(dev);
bus_add_device(dev);
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);添加到链表
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,调用bus_notifier
i2cdev_notifier_call
i2cdev_attach_adapter
device_create//增加i2c-%d节点
bus_probe_device(dev);
device_initial_probe(dev);
__device_attach(dev, true);
bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver);
__device_attach_driver
driver_match_device(drv, dev);
drv->bus->match ? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
really_probe(dev, drv);
dev->bus->probe(dev);或drv->probe(dev)
为什么里面又有probe,看不明白很难受,我又去看内核代码把i2c_imx_probe函数的调用流程捋了,我发现,应该是这样的,不过不确定我理解的是不是对的。前面的那些增加什么adapter都是没问题的,在注册adapter的时候,bus是i2c_bus_tyupe,
static int i2c_register_adapter(struct i2c_adapter *adap)
{...adap->dev.bus = &i2c_bus_type;adap->dev.type = &i2c_adapter_type;res = device_register(&adap->dev);...}
那么,到了 drv->bus->match,这里的时候,这个bus是i2c_bus_type,那么调用的也就是
struct bus_type i2c_bus_type = {.name = "i2c",.match = i2c_device_match,.probe = i2c_device_probe,.remove = i2c_device_remove,.shutdown = i2c_device_shutdown,
};
那么也就是
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{struct i2c_client *client = i2c_verify_client(dev);struct i2c_driver *driver;if (!client)return 0;/* Attempt an OF style match */if (of_driver_match_device(dev, drv))return 1;/* Then ACPI style match */if (acpi_driver_match_device(dev, drv))return 1;driver = to_i2c_driver(drv);/* match on an id table if there is one */if (driver->id_table)return i2c_match_id(driver->id_table, client) != NULL;return 0;
}
那么由于这里添加的是adapter设备,那么if (!client)根本就不成立,所以,这里match是0,那么
static int __device_attach_driver(struct device_driver *drv, void *_data)
{struct device_attach_data *data = _data;struct device *dev = data->dev;bool async_allowed;int ret;/** Check if device has already been claimed. This may* happen with driver loading, device discovery/registration,* and deferred probe processing happens all at once with* multiple threads.*/if (dev->driver)return -EBUSY;ret = driver_match_device(drv, dev);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 */async_allowed = driver_allows_async_probing(drv);if (async_allowed)data->have_async = true;if (data->check_async && async_allowed != data->want_async)return 0;return driver_probe_device(drv, dev);
}
由于driver_match_device(drv, dev);函数直接返回的0,那么__device_attach_driver函数也就直接返回了,也就不会调用driver_probe_device(drv, dev);函数了。
3 i2c-core
I2C 设备和驱动的匹配过程是由 I2C 核心来完成的,drivers/i2c/i2c-core.c 就是 I2C 的核心
部分,I2C 核心提供了一些与具体硬件无关的 API 函数,比如前面讲过的:
1、i2c_adapter 注册/注销函数
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
void i2c_del_adapter(struct i2c_adapter * adap)
2、i2c_driver 注册/注销函数
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
int i2c_add_driver (struct i2c_driver *driver)
void i2c_del_driver(struct i2c_driver *driver)
设备和驱动的匹配过程也是由 I2C 总线完成的,I2C 总线的数据结构为 i2c_bus_type,定义
在 drivers/i2c/i2c-core.c 文件。
另外i2c-core里面还有i2c_transfer函数,然后设备驱动里面直接用i2c_transfer 函数发送数据,而这个i2c_transfer 函数最终调用的是adapter里面的algorithm里面的master_xfer 函数,从这里也能看出来,i2c-core起到了一个承上启下的作用,连接设备驱动和控制器驱动。
4 i2c设备
4.1 i2c-client
i2c-client来自设备树文件,一般放在i2c节点里面的子节点,比如下面的ap3216设备。
&i2c1 {
ap3216c@1e {
compatible = "lite-on,ap3216c";
reg = <0x1e>;
};/*i2c里面的子节点,就是用来表示i2c设备的*/
};&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
};/*这个是用来表示i2c控制器的,不是i2c设备的*/
i2c总线节点下的子节点不会被转成platform_device,他们是由I2C总线驱动程序来处理, 把I2C下的设备节点转成client其实是i2c控制器驱动程序里面的probe函数来做的,前面已经分析过probe函数内部的流程,其中中间部分的of_i2c_register_devices函数就是用来增加i2c-client的。
i2c_imx_probe
i2c_add_numbered_adapter
__i2c_add_numbered_adapter
i2c_register_adapter
device_register(&adap->dev);
device_add(dev);
bus_add_device(dev);
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);添加到链表
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,调用bus_notifier
i2cdev_notifier_call
i2cdev_attach_adapter
device_create//增加i2c-%d节点
bus_probe_device(dev);
device_initial_probe(dev);
__device_attach(dev, true);
bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver);
__device_attach_driver
driver_match_device(drv, dev);
drv->bus->match ? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
really_probe(dev, drv);
dev->bus->probe(dev);或drv->probe(dev)
of_i2c_register_devices(adap);
of_i2c_register_device(adap, node);
i2c_new_device(adap, &info);用来增加client的
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type;//注意这里是i2c-bus,不是platform_bus
client->dev.type = &i2c_client_type;
client->dev.of_node = info->of_node;
client->dev.fwnode = info->fwnode;
status = device_register(&client->dev);注册新的 i2c_client 设备
device_add(dev);
bus_add_device(dev);
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
bus_probe_device
device_initial_probe(dev);
__device_attach(dev, true);
bus_for_each_drv
__device_attach_driver
driver_match_device
drv->bus->match ? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
really_probe(dev, drv);
dev->bus->probe(dev);或drv->probe(dev)
bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter)
__process_new_adapter(struct device_driver *d, void *data)
i2c_do_add_adapter(struct i2c_driver *driver, struct i2c_adapter *adap)
i2c_detect(adap, driver);通过i2c_detect检测是否有适合的设备连接在总线上
if (!driver->detect || !address_list) return 0;如果没定义detect或address_list就直接返回了
i2c_detect_address(temp_client, driver);
err = driver->detect(temp_client, &info);根据对应client发送一个测试数据如果没有问题则证明这个client是这个驱动所需要的设备,最后将设备添加到链表,最后调用bus_probe_device,尝试绑定驱动。
client = i2c_new_device(adapter, &info);用来增加client的
device_register(&client->dev);
device_add(dev);
bus_add_device(dev);
klist_add_tail
bus_probe_device(dev);
bus_probe_device(dev);
device_initial_probe(dev);
__device_attach(dev, true);
bus_for_each_drv
__device_attach_driver
driver_match_device(drv, dev);
drv->bus->match ? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
really_probe(dev, drv);
dev->bus->probe(dev);或drv->probe(dev)
4.2 i2c-driver
i2c_driver采用的是这个总线结构体
struct bus_type i2c_bus_type = {.name = "i2c",.match = i2c_device_match,.probe = i2c_device_probe,.remove = i2c_device_remove,.shutdown = i2c_device_shutdown,
};
先从入口函数module_init(at24_init);开始看,这里面调用了i2c_add_driver(&at24_driver);,然后里面调用了i2c_register_driver(THIS_MODULE, driver),然后里面调用了driver_register(&driver->driver);然后再往里调用了bus_add_driver(drv);,然后继续往里调用了driver_attach(drv);然后继续bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);这个函数就是就是针对每个device都调用__driver_attach函数,那进去__driver_attach函数发现里面有两个重要的函数
- driver_match_device(drv, dev);
- driver_probe_device(drv, dev);
driver_match_device(drv, dev);里面进一步调用了drv->bus->match(dev, drv),这便是i2c_bus_type里面的match函数了。
driver_probe_device(drv, dev);里面进一步调用了really_probe(dev, drv);,然后再往里进一步调用了dev->bus->probe(dev);,这便是i2c_bus_type里面的probe函数了。
i2c_add_driver(&at24_driver)
i2c_register_driver(THIS_MODULE, driver)
driver_register(&driver->driver)
bus_add_driver(drv)
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers)把驱动放到klist_driver
driver_attach(drv)
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
__driver_attach(struct device *dev, void *data)
driver_match_device(drv, dev);
drv->bus->match ? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
really_probe(dev, drv);
dev->bus->probe(dev);或drv->probe(dev)
/* Walk the adapters that are already present */
i2c_for_each_dev(driver, __process_new_driver);//
__process_new_driver //下面的代码不会被调用,从这里就直接返回了。
i2c_do_add_adapter(struct i2c_driver *driver,struct i2c_adapter *adap)
i2c_detect(adap, driver);
if (!driver->detect || !address_list) return 0;如果没定义detect或address_list就直接返回了
i2c_detect_address(temp_client, driver);
err = driver->detect(temp_client, &info);
client = i2c_new_device(adapter, &info);
device_register(&client->dev);
device_add(dev);
bus_add_device(dev);
klist_add_tail
bus_probe_device(dev);
bus_probe_device(dev);
device_initial_probe(dev);
__device_attach(dev, true);
bus_for_each_drv
__device_attach_driver
driver_match_device(drv, dev);
drv->bus->match ? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
really_probe(dev, drv);
dev->bus->probe(dev);或drv->probe(dev)
4.2.1 疑问:i2c_register_driver函数中调用driver_register(&driver->driver);函数增加驱动就行了,为什么还调用了i2c_for_each_dev(driver, __process_new_driver);
我在看 i2c_add_driver(&at24_driver)函数的时候,发现里面调用driver_register函数其实就已经完成了驱动注册工作,下面还调用了一个i2c_for_each_dev(driver, __process_new_driver);做什么用,而且这个函数内部竟然是i2c_do_add_adapter的,又看了下代码,其实__process_new_driver函数没被调用,
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{int res;/* Can't register until after driver model init */if (WARN_ON(!is_registered))return -EAGAIN;/* add the driver to the list of i2c drivers in the driver core */driver->driver.owner = owner;driver->driver.bus = &i2c_bus_type;INIT_LIST_HEAD(&driver->clients);/* When registration returns, the driver core* will have called probe() for all matching-but-unbound devices.*/res = driver_register(&driver->driver);if (res)return res;pr_debug("driver [%s] registered\n", driver->driver.name);/* Walk the adapters that are already present */i2c_for_each_dev(driver, __process_new_driver);return 0;
}
原因在这里,
static int __process_new_driver(struct device *dev, void *data)
{if (dev->type != &i2c_adapter_type)return 0;return i2c_do_add_adapter(data, to_i2c_adapter(dev));
}
这里有个判断if (dev->type != &i2c_adapter_type),所以后面的函数根本没被调用,疑问解决。
4.3 probe函数做了什么
当新增设备或驱动后,会调用i2c_bus_type中的match函数,match匹配之后就会调用驱动程序里面的probe函数,来看一下驱动程序里面的probe函数做了什么。
at24_probe
....
at24->nvmem_config.name = dev_name(&client->dev);
at24->nvmem_config.dev = &client->dev;
at24->nvmem_config.read_only = !writable;
at24->nvmem_config.root_only = true;
at24->nvmem_config.owner = THIS_MODULE;
at24->nvmem_config.compat = true;
at24->nvmem_config.base_dev = &client->dev;
at24->nvmem_config.reg_read = at24_read;//读函数
at24->nvmem_config.reg_write = at24_write;//写函数
at24->nvmem_config.priv = at24;
at24->nvmem_config.stride = 1;
at24->nvmem_config.word_size = 1;
at24->nvmem_config.size = chip.byte_len;
at24->nvmem = nvmem_register(&at24->nvmem_config);
....
nvmem->id = rval;
nvmem->owner = config->owner;
nvmem->stride = config->stride;
nvmem->word_size = config->word_size;
nvmem->size = config->size;
nvmem->dev.type = &nvmem_provider_type;
nvmem->dev.bus = &nvmem_bus_type;//注意这个地方。
nvmem->dev.parent = config->dev;
nvmem->priv = config->priv;
nvmem->reg_read = config->reg_read;
nvmem->reg_write = config->reg_write;
np = config->dev->of_node;
nvmem->dev.of_node = np;
rval = device_add(&nvmem->dev);//这个device_add函数在前面看多很多遍了,无非就是那一套。
bus_add_device(dev);
bus_probe_device(dev);
device_initial_probe(dev);
__device_attach(dev, true);
bus_for_each_drv(dev->bus, NULL, &data,__device_attach_driver);
__device_attach_driver
driver_match_device(drv, dev);
return drv->bus->match ? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
driver_probe_device(drv, dev);
really_probe(dev, drv);
dev->bus->probe(dev);或drv->probe(dev)
4.3.1 疑问:at24_probe里面怎么又有match和probe
我的理解at24_probe 函数里面应该是类似实现一个file_operation结构体,然后里面有具体的读写函数这不就行了吗,可是从上面的流程看怎么at24_probe 函数里面又调用了match和probe函数,好吧继续看内核代码解决我的困惑。。。。。。。。
首先看一下
static struct bus_type nvmem_bus_type = {.name = "nvmem",
};
然后发现这里面没定义match函数,那么match函数就是空的,那么return drv->bus->match ? drv->bus->match(dev, drv) : 1;直接返回1,然后会调用driver_probe_device(drv, dev);函数,
static int really_probe(struct device *dev, struct device_driver *drv)
{...if (dev->bus->probe) {ret = dev->bus->probe(dev);if (ret)goto probe_failed;} else if (drv->probe) {ret = drv->probe(dev);if (ret)goto probe_failed;}...
}
这里调用dev->bus->probe空的,那么就去调用driver结构体的probe函数,那我在内核代码中找nvmem driver结构体,没找到,那么求助Bing AI
那么
struct nvmem_device {const char *name;struct module *owner;struct device dev;int stride;int word_size;int ncells;int id;int users;size_t size;bool read_only;int flags;struct bin_attribute eeprom;struct device *base_dev;nvmem_reg_read_t reg_read;nvmem_reg_write_t reg_write;void *priv;
};
这里面就没有probe函数,所以else if (drv->probe)也不成立。
到这里,I2C驱动框架其实就算是看完了,下面再简单介绍一下I2C驱动相关的其他东西。
5 i2c-tools
i2c-tools 是一套好用的工具,也是一套示例代码。
5.1 为什么说i2c-tools是一套好用的工具
为什么说i2c-tools是一套好用的工具,因为他里面实现了 i2cdetect检测函数, i2cget读函数, i2cset写函数,i2ctransfer传输函数,我们可以字节用这些命令去操作或调试I2C设备,比如
5.2 为什么说是i2c-tools也是一套示例代码
为什么说i2c-tools也是一套示例代码,比如如果用I2C总线进行传输,在./tools/i2ctransfer.c里面,我们可以看到他的代码实现,
那我们就可以模仿他的流程操作我们自己的I2C设备,上面的比如 set_slave_addr函数具体实现就是在./tools/i2cbusses.c里面,我们写代码需要包含./tools/i2cbusses.c文件。
如果用SMBus总线进行传输,i2cget.c、i2cset.c里面的示例代码是这样的
然后如果我们想用SMBus总线操作我们的i2c设备,我们就可以模仿他的代码,上面的比如i2c_smbus_access函数具体实现是在./lib/smbus.c文件里面,那我们写代码的时候需要包含./lib/smbus.c文件.
比如编写一个读写eeprom的测试程序
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <i2c/smbus.h>
#include "i2cbusses.h"
#include <time.h>/* ./at24c02 <i2c_bus_number> w "100ask.taobao.com"* ./at24c02 <i2c_bus_number> r*/int main(int argc, char **argv)
{unsigned char dev_addr = 0x50;unsigned char mem_addr = 0;unsigned char buf[32];int file;char filename[20];unsigned char *str;int ret;struct timespec req;if (argc != 3 && argc != 4){printf("Usage:\n");printf("write eeprom: %s <i2c_bus_number> w string\n", argv[0]);printf("read eeprom: %s <i2c_bus_number> r\n", argv[0]);return -1;}file = open_i2c_dev(argv[1][0]-'0', filename, sizeof(filename), 0);if (file < 0){printf("can't open %s\n", filename);return -1;}if (set_slave_addr(file, dev_addr, 1)){printf("can't set_slave_addr\n");return -1;}if (argv[2][0] == 'w'){// write str: argv[3]str = argv[3];req.tv_sec = 0;req.tv_nsec = 20000000; /* 20ms */while (*str){// mem_addr, *str// mem_addr++, str++ret = i2c_smbus_write_byte_data(file, mem_addr, *str);if (ret){printf("i2c_smbus_write_byte_data err\n");return -1;}// wait tWR(10ms)nanosleep(&req, NULL);mem_addr++;str++;}ret = i2c_smbus_write_byte_data(file, mem_addr, 0); // string end charif (ret){printf("i2c_smbus_write_byte_data err\n");return -1;}}else{// readret = i2c_smbus_read_i2c_block_data(file, mem_addr, sizeof(buf), buf);if (ret < 0){printf("i2c_smbus_read_i2c_block_data err\n");return -1;}buf[31] = '\0';printf("get data: %s\n", buf);}return 0;}
6 i2c_dev.c通用驱动
i2c_dev.c其实就是通用驱动或者说万能驱动,它里面实现了一个
static const struct file_operations i2cdev_fops = {.owner = THIS_MODULE,.llseek = no_llseek,.read = i2cdev_read,.write = i2cdev_write,.unlocked_ioctl = i2cdev_ioctl,.open = i2cdev_open,.release = i2cdev_release,
};
如果我们使用i2c_dev.c这个万能驱动,那么我们不需要增加i2c_client以及i2c_driver,然后我们在应用层可以直接操作i2c控制器,然后去和挂载在I2C总线的从设备进行通信,就相当于把操作具体硬件的时序放到应用去实现了,要求应用开发人员既要了解具体的硬件操作时序,也要了解I2C总线协议。也就是红线画的走向
7 GPIO模拟I2C
简单看一下./Linux-4.9.88_just_for_read/drivers/i2c/busses/i2c-gpio.c文件,还是从入口函数开始看
static struct platform_driver i2c_gpio_driver = {.driver = {.name = "i2c-gpio",.of_match_table = of_match_ptr(i2c_gpio_dt_ids),},.probe = i2c_gpio_probe,.remove = i2c_gpio_remove,
};
static int __init i2c_gpio_init(void)
{int ret;ret = platform_driver_register(&i2c_gpio_driver);if (ret)printk(KERN_ERR "i2c-gpio: probe failed: %d\n", ret);return ret;
}
函数调用关系无非又是那一套
i2c_gpio_init
platform_driver_register
__platform_driver_register
drv->driver.owner = owner;
drv->driver.bus = &platform_bus_type;
drv->driver.probe = platform_drv_probe;
drv->driver.remove = platform_drv_remove;
drv->driver.shutdown = platform_drv_shutdown;
driver_register(&drv->driver);
bus_add_driver
driver_attach(drv);
__driver_attach
driver_match_device(drv, dev);
drv->bus->match ? drv->bus->match(dev, drv) : 1;
driver_probe_device(drv, dev);
really_probe(dev, drv);
dev->bus->probe或drv->probe(dev)
match之后就调用驱动结构体里面的i2c_gpio_probe函数,然后首先调用of_i2c_gpio_get_props函数从设备树里面获取gpio的信息和一些属性,就是频率,开漏的设置,然后获取sda引脚,scl引脚, 然后根据从设备树中获取的值设置adapter,然后利用i2c_bit_add_numbered_bus注册adapter,然后i2c_bit_add_numbered_bus里面是调用了__i2c_bit_add_bus,在这里面设置了algo算法,然后add_adapter。
i2c_gpio_probe
of_i2c_gpio_get_pins
devm_gpio_request(&pdev->dev, sda_pin, "sda");
devm_gpio_request(&pdev->dev, scl_pin, "scl");
i2c_bit_add_numbered_bus(adap);
__i2c_bit_add_bus(adap, i2c_add_numbered_adapter);
adap->algo = &i2c_bit_algo;
adap->retries = 3;
if (bit_adap->getscl == NULL)
adap->quirks = &i2c_bit_quirk_no_clk_stretch;
ret = add_adapter(adap);//add_adapter就是i2c_add_numbered_adapter
再往后的调用不看了,前面类似的分析了很多遍了
以上是Linux内核的驱动框架介绍,如有错误和问题恳请指出。
参考文献:
正点原子驱动开发手册
韦东山老师驱动开发大全学习视频
Linux4.9.88内核源码
7. 平台设备驱动 — [野火]嵌入式Linux驱动开发实战指南——基于i.MX6ULL系列 文档
I2C驱动实现的两种思路(i2c-dev.c和i2c-core.c)_正在起飞的蜗牛的博客-CSDN博客
https://www.cnblogs.com/happybirthdaytoyou/p/13594060.html
【I2C】通用驱动i2c-dev分析_i2c_dev_init_ZHONGCAI0901的博客-CSDN博客
linux内核I2C子系统详解——看这一篇就够了_正在起飞的蜗牛的博客-CSDN博客
https://www.cnblogs.com/burnk/p/17454052.html
十分钟带你搞懂 Linux I2C 软件架构_哔哩哔哩_bilibili
I2C——i2c_driver的注册及probe探测函数调用过程_i2c probe_lxllinux的博客-CSDN博客
内核对设备树的处理__device_node转换为platform_device_initcall_from_entry_陈 洪 伟的博客-CSDN博客 https://www.cnblogs.com/schips/p/linux_driver_device_node_to_platform_device.html
Linux设备模型之device_add_庐州拎壶冲的博客-CSDN博客
https://www.cnblogs.com/yangjiguang/p/6220600.html
i2c设备添加、驱动的加载和设备匹配_安卓 i2c 心率设备添加_bruk_spp的博客-CSDN博客
【I2C】Linux I2C子系统分析_ZHONGCAI0901的博客-CSDN博客
相关文章:

Linux内核的I2C驱动框架详解------这应该是我目前600多篇博客中耗时最长的一篇博客
目录 1 I2C驱动整体框架图 2 I2C控制器 2.1 I2C控制器设备--I2C控制器在内核中也被看做一个设备 2.2 i2c控制器驱动程序 2.3 platform_driver结构体中的probe函数做了什么 2.3.1 疑问: i2cdev_notifier_call函数哪里来的 2.3.2 疑问:为什么有两…...

【点云处理教程】05-Python 中的点云分割
一、说明 这是我的“点云处理”教程的第 5 篇文章。“点云处理”教程对初学者友好,我们将在其中简单地介绍从数据准备到数据分割和分类的点云处理管道。 在上一教程中,我们看到了如何过滤点云以减少噪声或其密度。在本教程中,我们将应用一些聚…...

代码随想录算法训练营之JAVA|第十七天| 654. 最大二叉树
今天是第17天刷leetcode,立个flag,打卡60天。 算法挑战链接 654. 最大二叉树https://leetcode.cn/problems/maximum-binary-tree/description/ 第一想法 错误的想法,就不说了。 看完代码随想录之后的想法 用递归模拟真实的过程 如果我…...

C++重写函数、隐藏函数、重载函数的区别对比
目录 1.函数重载 1.1定义 1.2函数重载的规则: 1.3函数重载的作用: 2.函数重写: 2.1定义 2.2例子: 3.函数隐藏 3.1定义 3.2举个例子: 1.函数重载 1.1定义 我们在学类和对象的封装特性时学过一个词叫重载,…...

15.python设计模式【函数工厂模式】
1.知识讲解 内容:定义一个字典,在python中一切皆对象,将所有的函数进行封装,然后定一个分发函数进行分发,将原来if…else全部干掉。角色: 函数(function)函数工厂(funct…...

Redis主从复制、哨兵、cluster集群原理+实验
目录 一、Redis 主从复制 1、主从复制的作用 2、主从复制流程 3、搭建Redis 主从复制 安装Redis(所有主机) 修改Master节点Redis配置文件 修改Slave节点Redis配置文件 验证主从效果 一、Redis 主从复制 主从复制,是指将一台Redis服务器的数据&am…...

微信小程序如何实现页面传参?
前言 只要你的小程序超过一个页面那么可能会需要涉及到页面参数的传递,下面我总结了 4 种页面方法。 路径传递 通过在url后面拼接参数,参数与路径之间使用 ? 分隔,参数键与参数值用 相连,不同参数用 & 分隔;如…...

OPC DA 客户端与服务器的那点事
C#开发OPC客户端,使用OPCDAAuto.dll。在开发过程中偶遇小坎坷,主要记录一下问题解决办法。 1、建立客户端,参考链接。建立WinFrom工程,将博客中代码全部复制即可运行: https://www.cnblogs.com/kjgagaga/p/17011730.…...

Java 错误异常介绍(Exceptions)
1、异常介绍 异常是程序执行期间发生的意外事件。它影响程序指令流,从而导致程序异常终止。 发生异常的原因有很多。其中包括: 无效的用户输入 设备故障 网络连接丢失 物理限制(磁盘内存不足) 代码错误 打开一个不可用的文…...

每日一题——旋转数组的最小数字
题目 有一个长度为 n 的非降序数组,比如[1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问,给定这…...

SpringBoot Jackson 日期格式化统一配置
目录 1.在全局配置文件配置 2.通过JavaBean方式配置 1.在全局配置文件配置 spring:jackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT8 该配置方式仅支持 Date 类型的日期格式化,不支持LocalDate 及 LocalDateTime 的格式化。 2.通过JavaBean方式配置 …...

剑指 Offer 38. 字符串的排列 / LeetCode 47. 全排列 II(回溯法)
题目: 链接:剑指 Offer 38. 字符串的排列 难度:中等 输入一个字符串,打印出该字符串中字符的所有排列。 你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。 示例: 输入:s “abc” 输出&…...

【前端知识】React 基础巩固(四十三)——Effect Hook
React 基础巩固(四十三)——Effect Hook 一、Effect Hook的基本使用 Effect Hook 用来完成一些类似class中生命周期的功能。 在使用类组件时,不管是渲染、网路请求还是操作DOM,其逻辑和代码是杂糅在一起的。例如我们希望把计数器结果显示在标签上&…...

一百三十八、ClickHouse——使用clickhouse-backup备份ClickHouse库表
一、目标 使用clickhouse-backup在本地全库备份ClickHouse的数据库 二、前提 已经安装好clickhouse-backup 注意:由于之前同事已经按照好clickhouse-backup,所以我就没有安装 如有需要请参考其他人的博客安装一下,下面是我认为比较好的一…...

【无标题】使用Debate Dynamics在知识图谱上进行推理(2020)7.31
使用Debate Dynamics在知识图谱上进行推理 摘要介绍背景与相关工作我们的方法 摘要 我们提出了一种新的基于 Debate Dynamics 的知识图谱自动推理方法。 其主要思想是将三重分类任务定义为两个强化学习主体之间的辩论游戏,这两个主体提取论点(知识图中…...

windows下若依vue项目部署
下载若依项目,前端后端项目本地启动前端打包,后端打包配置nginx.conf 需要注意的是:路径别用中文,要不然报错 #前台访问地址及端口80,在vue.config.js中可查看server {listen 80;server_name localhost; #后台…...

【目标检测】基于yolov5的水下垃圾检测(附代码和数据集,7684张图片)
写在前面: 首先感谢兄弟们的订阅,让我有创作的动力,在创作过程我会尽最大能力,保证作品的质量,如果有问题,可以私信我,让我们携手共进,共创辉煌。 路虽远,行则将至;事虽难,做则必成。只要有愚公移山的志气、滴水穿石的毅力,脚踏实地,埋头苦干,积跬步以至千里,就…...

P1734 最大约数和
题目描述 选取和不超过 S 的若干个不同的正整数,使得所有数的约数(不含它本身)之和最大。 输入格式 输入一个正整数 S。 输出格式 输出最大的约数之和。 输入输出样例 输入 11 输出 9 说明/提示 【样例说明】 取数字 4 和 6&a…...

Excel将单元格中的json本文格式化
打开Excel文件并按下ALT F11打开Visual Basic for Applications(VBA)编辑器。 输入下面的代码 Sub FormatJSONCells()Dim cell As RangeDim jsonString As StringDim json As ObjectDim formattedJSON As String 循环遍历选定的单元格范围For Each ce…...

Baumer工业相机堡盟工业相机如何通过BGAPI SDK获取相机当前实时帧率(C#)
Baumer工业相机堡盟工业相机如何通过BGAPISDK里函数来计算相机的实时帧率(C#) Baumer工业相机Baumer工业相机的帧率的技术背景Baumer工业相机的帧率获取方式CameraExplorer如何查看相机帧率信息在BGAPI SDK里通过函数获取相机帧率 Baumer工业相机通过BGA…...
XGBoost的基础思想与实现
目录 1. XGBoost VS 梯度提升树 1.1 XGBoost实现精确性与复杂度之间的平衡 1.2 XGBoost极大程度地降低模型复杂度、提升模型运行效率 1.3 保留了部分与梯度提升树类似的属性 2. XGBoost回归的sklearnAPI实现 2.1 sklearn API 实现回归 2.2 sklearn API 实现分类 3. XGBo…...

【Docker】Docker的服务更新与发现
consul 一、服务注册与发现1. 服务注册与发现的概念2. 服务发现的机制二、consul 的概念1. 什么是 consul2. consul 的特性三、consul 的部署1. consul 服务器架构2. consul 的部署过程2.1 环境配置2.2 consul 服务器建立 Consul 服务查看集群信息通过 http api 获取集群信息2.…...

【Docker 学习笔记】Docker架构及三要素
文章目录 一、Docker 简介二、Docker 架构1. Docker 客户端和服务器2. Docker 架构图3. Docker 运行流程图 三、Docker 三要素1. 镜像(Image)2. 容器(Container)3. 仓库(Repository) 一、Docker 简介 Dock…...

matlab编程实践14、15
目录 数独 "四独"游戏 解的存在和唯一性 算法 常微分方程 数独 采用蛮力试凑法来解决数独问题。(采用单选数,以及计算机科学技术中的递推回溯法) 以上的数独是图14-2的两个矩阵的和,左侧的矩阵可以由kron和magic函…...

C++ ——STL容器【list】模拟实现
代码仓库: list模拟实现 list源码 数据结构——双向链表 文章目录 🍇1. 节点结构体🍈2. list成员🍉3. 迭代器模板🍊4. 迭代器🍋5. 插入删除操作🍌5.1 insert & erase🍌5.2 push_…...

ubuntu 16.04 安装mujoco mujoco_py gym stable_baselines版本问题
ubuntu 16.04系统 Python 3.7.16 mujoco200 (py37mujoco) abc123:~/github/spinningup$ pip list Package Version Editable project location ----------------------------- --------- --------------------------- absl-py …...

自然语言处理(NLP)技术
自然语言处理技术是一种人工智能技术,它的目标是使计算机能够理解、分析、处理和生成自然语言(人类使用的语言)。NLP技术包括文本分类、情感分析、机器翻译、语音识别、语音合成、信息检索、信息抽取、问答系统等。NLP技术的应用非常广泛&…...

如何将ubuntu LTS升级为Pro
LTS支持周期是5年; Pro支持周期是10年。 Ubuntu Pro专业版笔记 步骤: 打开“软件和更新” 可以看到最右侧的标签是Ubuntu Pro。 在没有升级之前,如果使用下面两步: sudo apt updatesudo apt upgrade 出现如下提示ÿ…...

如何学习ARM嵌入式开发?
ARM和单片机还是有许多区别的,可以说比单片机的应用更为复杂吧,往往在单片机里只需要对一个寄存器赋值就可以的初始化,在ARM下就要调用库函数了。甚至每个引脚其功能都多了许多,相应的配置也会更为麻烦,但如果做多了AR…...

二、使用运行自己的docker python容器环境
第一篇参考: https://blog.csdn.net/weixin_42357472/article/details/131953866 运行容器同时执行命令或脚本 1)这是打开一个对外的jupyter notebook容器环境 docker run -d --name my_container -p 8090:8888 mynewpythonimage jupyter notebook --…...