bus中设备驱动的probe触发逻辑和device、driver的添加逻辑

news2024/12/23 17:46:07

注:以下的代码皆摘自于linux 4.9.88版本的内核源码,不同版本可能有所出入。

往期内容:

  1. 驱动中的device和device_driver结构体
  2. bus总线的相关结构体和注册逻辑

1. driver的probe触发方式

在 Linux 设备模型中,probe() 函数是驱动与设备绑定的核心机制。其触发时机与设备(device)和设备驱动(device_driver)的注册紧密相关。当内核发现设备与驱动匹配时,调用驱动的 probe() 函数来初始化设备,通常包括硬件初始化、资源分配、设备文件的注册等操作。以下是设备驱动 probe() 触发的几种时机,分为自动触发和手动触发:

自动触发probe()

  1. 设备(device)注册到内核时触发
    • 当通过 device_register() 或者其他相关接口(例如 device_add()device_create())将 struct device 类型的设备对象注册到内核时,内核会查找与该设备名称匹配的驱动程序。如果找到了匹配的 device_driver,并且设备未绑定其他驱动,内核会自动调用该驱动的 probe() 函数来进行初始化。
    • 例如:
struct device my_device;
device_register(&my_device);

在这个过程中,内核会尝试在总线(bus)下查找与 my_device 名称匹配的驱动程序。

  1. 驱动(device_driver)注册到内核时触发
    • 同样地,当通过 driver_register() 接口将一个新的 device_driver 驱动对象注册到内核时,内核会遍历当前总线下已注册的所有设备,检查是否有与该驱动匹配的设备。如果存在匹配设备,并且设备未绑定其他驱动,内核将自动调用该驱动的 probe() 函数。
    • 例如:
struct device_driver my_driver;
driver_register(&my_driver);

在这个过程中,内核会为总线下所有与 my_driver 匹配的设备执行 probe() 操作。

手动触发probe()

  1. 手动调用 device_attach()
    • device_attach() 是一个手动触发设备驱动配对的函数。它会在总线下遍历所有已注册的驱动程序,寻找与指定设备匹配的驱动。如果找到匹配的驱动,且该设备尚未绑定任何驱动,则调用该驱动的 probe()
    • 这种方式的使用场景通常是在设备已经存在但驱动程序尚未加载的情况下,通过手动调用 device_attach(),使设备与驱动程序绑定并执行 probe()
device_attach(&my_device);
  1. 手动调用 driver_attach()
    • driver_attach() 是另一个手动触发配对的接口,它的功能是遍历总线下的所有设备,查找与指定驱动匹配的设备。如果找到匹配的设备,且设备尚未绑定驱动,则执行该驱动的 probe() 操作。
    • 使用场景通常是在驱动已经加载但设备尚未注册的情况下,通过手动调用 driver_attach() 来触发设备绑定:
driver_attach(&my_driver);
- **<font style="color:#DF2A3F;">说白了,driver_register自动触发驱动的probe函数,其实研究一下它的内部就可以发现它调用到了driver_attach</font>**
driver_register -> bus_add_driver(下面会讲) -> 
int bus_add_driver(struct device_driver *drv)
{
    //......
    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);  //这不就体现到了,调用了driver_attach,内部会去调用设备驱动probe
			if (error)
				goto out_unregister;
		}
	}
    //......	
}
  1. 手动调用 device_bind_driver()
    • 通过 device_bind_driver(),可以手动将驱动绑定到指定设备上,并调用该驱动的 probe() 函数进行初始化。这种方式比上述手动触发的方法更为直接,因为它不进行遍历,而是显式地将驱动和设备关联。
    • 在该接口中,用户可以自己指定设备和驱动的配对,而无需总线来查找匹配项:
device_bind_driver(&my_device);

2. bus中device和device_driver的添加

在 Linux 内核中,设备和驱动程序的管理由 bus 模块处理,提供了一种通用的机制来注册和管理设备(device)和设备驱动(device_driver)。具体来说,bus_add_devicebus_add_driver 是处理设备和驱动程序注册的核心逻辑。

bus_add_device 处理逻辑

/**
 * bus_add_device - add device to bus
 * @dev: device being added
 *
 * - Add device's bus attributes.
 * - Create links to device's bus.
 * - Add the device to its bus's list of devices.
 */
int bus_add_device(struct device *dev)
{
	struct bus_type *bus = bus_get(dev->bus);
	int error = 0;

	if (bus) {
		pr_debug("bus: '%s': add device %s\n", bus->name, dev_name(dev));
		error = device_add_attrs(bus, dev);
		if (error)
			goto out_put;
		error = device_add_groups(dev, bus->dev_groups);
		if (error)
			goto out_id;
		error = sysfs_create_link(&bus->p->devices_kset->kobj,
						&dev->kobj, dev_name(dev));
		if (error)
			goto out_groups;
		error = sysfs_create_link(&dev->kobj,
				&dev->bus->p->subsys.kobj, "subsystem");
		if (error)
			goto out_subsys;
		klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
	}
	return 0;

out_subsys:
	sysfs_remove_link(&bus->p->devices_kset->kobj, dev_name(dev));
out_groups:
	device_remove_groups(dev, bus->dev_groups);
out_id:
	device_remove_attrs(bus, dev);
out_put:
	bus_put(dev->bus);
	return error;
}

bus_add_device 的主要目的是将设备添加到总线,并进行相关的初始化和管理操作。其处理逻辑如下:

  1. 添加默认属性到内核
    • 调用 device_add_attrs 函数,该函数根据 bus->dev_attrs 指针定义的默认属性,将这些属性添加到内核中。这些属性会显示在设备的 sysfs 目录下,例如 /sys/devices/xxx/xxx_device/
  2. 创建符号链接到总线的设备目录
    • 使用 sysfs_create_link 创建一个符号链接,将设备的 sysfs 目录链接到该设备所在总线的 devices 目录下。这一操作提供了方便的路径,以便用户空间程序能够轻松访问设备的信息。例如:
ls -l /sys/bus/spi/devices/spi1.0
lrwxrwxrwx root root 2014-04-11 10:46 spi1.0 -> ../../../devices/platform/s3c64xx-spi.1/spi_master/spi1/spi1.0
  1. 创建指向总线目录的链接
    • 再次调用 sysfs_create_link,在设备的 sysfs 目录(如 /sys/devices/platform/alarm/)中,创建一个名为 subsystem 的符号链接,指向该设备所在总线的目录。这种设计便于在设备目录中快速找到其所在的总线信息。
ls -l /sys/devices/platform/alarm/subsystem
lrwxrwxrwx root root 2014-04-11 10:28 subsystem -> ../../../bus/platform
  1. 将设备指针保存到总线的设备列表
    • 最后,将设备指针保存在 bus->priv->klist_devices 链表中,以便后续操作中可以方便地查找和管理该设备。

其实也就是调用了device_register内部会调用bus_add_device:

  1. device_register** 函数**:
    • device_register 是用于注册一个新的设备的主要接口。当你调用这个函数时,它会处理设备的创建和初始化。
  2. **内部调用 **bus_add_device:
    • device_register 中,它会执行一系列初始化操作,然后调用 bus_add_device 函数。这个调用的主要目的是将设备添加到相应的总线(bus)中,并执行必要的注册操作。
  3. bus_add_device** 的作用**:
    • bus_add_device 负责将设备的信息添加到总线的数据结构中,设置设备的默认属性,创建符号链接等,使得设备能够在 sysfs 中正确显示,并允许后续的设备管理和操作。

以下是一个简化的示例流程:

int device_register(struct device *dev) {
    // 一些设备初始化逻辑
    ...

    // 调用 bus_add_device 将设备添加到总线
    int ret = bus_add_device(dev);
    if (ret) {
        // 错误处理
        ...
    }

    // 其他初始化逻辑
    ...
    return 0;
}
  • device_register 的具体实现可以在 drivers/base/core.c 中找到。
  • bus_add_device 的实现如前所述,在 drivers/base/bus.c 中。

bus_add_driver 处理逻辑

Linux-4.9.88\drivers\base\bus.c:
/**
 * bus_add_driver - Add a driver to the bus.
 * @drv: 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;
	error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
				     "%s", drv->name);
	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;
		}
	}
	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;

out_unregister:
	kobject_put(&priv->kobj);
	/* drv->p is freed in driver_release()  */
	drv->p = NULL;
out_put_bus:
	bus_put(bus);
	return error;
}

bus_add_driver 的目的是将设备驱动注册到相应的总线。其处理逻辑如下:

  1. 分配并初始化struct driver_private
    • 为该驱动的 struct driver_private 指针(priv)分配内存,并初始化其内部字段,包括 priv->klist_devicespriv->driverpriv->kobj.kset 等。同时,将该指针保存在 device_driver 结构体的 p 字段中。
  2. 设置驱动的 kset
    • 将驱动的 kset(priv->kobj.kset)设置为总线的驱动 kset(bus->p->drivers_kset),这意味着所有驱动的 kobject 都位于该总线的驱动目录下(例如 /sys/bus/xxx/drivers)。
  3. 注册驱动的 kobject
    • 以驱动的名字为参数,调用 kobject_init_and_add 函数,在 sysfs 中注册驱动的 kobject,并体现在 /sys/bus/xxx/drivers/ 目录下,例如 /sys/bus/spi/drivers/spidev
  4. 将驱动保存在总线的驱动列表中
    • 将该驱动保存在 bus->priv->klist_drivers 链表中,以便后续管理。
  5. **根据 drivers_autoprobe 值决定是否调用 **driver_attach
    • 根据 drivers_autoprobe 的值来选择是否调用 driver_attach 函数。如果该值为 1,则会自动尝试将匹配的设备与驱动进行配对并调用 probe() 函数。
  6. 创建驱动的 uevent 属性
    • 调用 driver_create_file,在 sysfs 的该驱动目录下创建 uevent 属性,允许用户空间程序获取和响应设备事件。
  7. 添加驱动的默认属性
    • 调用 driver_add_attrs 函数,在该驱动的 sysfs 目录下创建由 bus->drv_attrs 指针定义的默认属性。
  8. 创建 bindunbind 属性
    • 根据 suppress_bind_attrs 标志的值,决定是否在 sysfs 的该驱动目录下创建 bindunbind 属性,这些属性允许用户空间手动绑定和解绑驱动与设备。

通过 bus_add_devicebus_add_driver 接口,Linux 内核提供了一个统一的机制来注册和管理设备和驱动。前者处理设备的注册、属性的创建和符号链接的设置,后者则负责驱动的注册、kobject 的初始化和自动 probe 的控制。这些操作不仅简化了设备与驱动之间的管理流程,也为用户空间提供了便利的接口来监控和管理设备驱动。

3. Bus中设备的probe触发逻辑

这里是通过内部代码对上面第1点的进一步讲解

  • 在 Linux 设备模型中,设备device和驱动device_driver的匹配和 probe() 调用实际是由总线(bus)模块负责的。每个设备和驱动都是通过总线连接的,因此总线模块知道如何为设备和驱动进行配对。
  • 当设备或驱动注册到总线时,总线会负责调用 bus_probe_device()driver_attach()(内部会调用bus_probe_device),以检查当前设备和驱动的匹配情况,并决定是否的调用device_driver中 probe()

来看一下内核代码,以设备调用了driver_register后,自动触发调用设备驱动的probe为例:

\Linux-4.9.88\Linux-4.9.88\drivers\base\driver.c:
int driver_register(struct device_driver *drv)
{
    //.......
	ret = bus_add_driver(drv); //继续进入看
	if (ret)
		return ret;
	//.......
}
//---------------分割--------------------
int bus_add_driver(struct device_driver *drv)
{
    //.......
    if (drv->bus->p->drivers_autoprobe) { //drivers_autoprobe的影响,这个看下面小点介绍
        //-----------------------------------------------------------------(1)
		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); //这里,内部必定会去匹配然后调用设备驱动的probe
            //继续进入内部查看
			if (error)
				goto out_unregister;
		}
	}
	module_add_driver(drv->owner, drv);
    //.......
}

//---------------分割--------------------
int driver_attach(struct device_driver *drv)
{
	return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); //继续进入
    //留意一下参4:__driver_attach函数,调用设备驱动的probe函数就在里面
}
//---------------分割--------------------
int bus_for_each_dev(struct bus_type *bus, struct device *start,
		     void *data, int (*fn)(struct device *, void *))
{
	struct klist_iter i;
	struct device *dev;
	int error = 0;

	if (!bus || !bus->p)
		return -EINVAL;

	klist_iter_init_node(&bus->p->klist_devices, &i,
			     (start ? &start->p->knode_bus : NULL));
	while ((dev = next_device(&i)) && !error)
		error = fn(dev, data); //这里,调用了传进来的参四,也就是__driver_attach函数,来查看一下这个函数
	klist_iter_exit(&i);
	return error;
}
//---------------分割--------------------
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); //这里是去查看是否和device匹配,一共有四种
	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);//--------------(2)篇幅有点长了,在下面(2)处在贴内核源码吧
	device_unlock(dev);
	if (dev->parent)
		device_unlock(dev->parent);

	return 0;
}

其中__driver_attach的ret = driver_match_device(drv, dev); 就是去查看driver和device是否匹配,一共有四种匹配方式,具体的留到platform bus的讲解时再说

(1) drivers_autoprobe 的影响

  • 每个总线对象中有一个 drivers_autoprobe 变量,控制是否在设备或驱动程序注册时自动触发 probe() 操作。
    • drivers_autoprobe 设为 1(默认值),设备或驱动程序注册时会自动进行配对并调用 probe()
    • drivers_autoprobe 设为 0,则需要手动调用上面所说的手动触发的接口来进行配对和 probe()

drivers_autoprobe 是通过 sysfs 暴露给用户空间的,路径为 /sys/bus/<bus_name>/drivers_autoprobe,因此可以通过用户空间的操作修改 drivers_autoprobe 的值,控制自动 probe 行为。

(2)总线的 probe 操作: bus_probe_device() driver_attach()

  • bus_probe_device()
    • 当设备被添加到总线时(比如调用了device_register()),会调用 bus_probe_device()。该函数遍历该总线下的所有驱动,检查是否有与该设备匹配的驱动程序。如果找到匹配的驱动,并且设备没有绑定驱动,则调用该驱动的 probe() 函数。可以继续看内部代码:
\Linux-4.9.88\drivers\base\dd.c:
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
	int ret = 0;

	if (!device_is_registered(dev))
		return -ENODEV;

	pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
		 drv->bus->name, __func__, dev_name(dev), drv->name);

	if (dev->parent)
		pm_runtime_get_sync(dev->parent);

	pm_runtime_barrier(dev);
	ret = really_probe(dev, drv);  //这里,内部就会去调用driver的probe函数
	pm_request_idle(dev);

	if (dev->parent)
		pm_runtime_put(dev->parent);

	return ret;
}
//---------------分割--------------------
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);  //这,不就调用到了driver的probe函数
		if (ret)
			goto probe_failed;
	}
    //,,,,,,,,
}
  • driver_attach()
    • driver_attach() 是在驱动注册时被调用的,它会遍历总线下的所有设备,寻找与该驱动匹配的设备。如果找到匹配的设备,且设备没有绑定其他驱动,则调用该驱动的 probe()。它内部还是会调用到bus_probe_device,这个在上面代码分析中已经指出来了

设备驱动 probe() 的调用机制贯穿整个 Linux 设备模型,通过设备和驱动的注册,手动或自动进行设备和驱动的匹配。在大多数情况下,系统会自动触发 probe(),但在某些特殊情况下(如设备和驱动的异步加载),用户也可以手动触发 probe() 来绑定设备和驱动。这一切的背后,是由总线模块负责管理设备与驱动的匹配和绑定逻辑。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2194592.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

自动驾驶系列—智能驾驶中的“换挡革命”:线控换挡技术详解

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

Java垃圾回收简述

什么是Java的垃圾回收&#xff1f; 自动管理内存的机制&#xff0c;负责自动释放不再被程序引用的对象所占用的内存。 怎么触发垃圾回收&#xff1f; 内存不足时&#xff1a;JVM检测到堆内存不足时&#xff0c;无法为新的对象分配内存时&#xff0c;会自动触发垃圾回收。手动…

UDP协议【网络】

文章目录 UDP协议格式 UDP协议格式 16位源端口号&#xff1a;表示数据从哪里来。16位目的端口号&#xff1a;表示数据要到哪里去。16位UDP长度&#xff1a;表示整个数据报&#xff08;UDP首部UDP数据&#xff09;的长度。16位UDP检验和&#xff1a;如果UDP报文的检验和出错&…

【电路】1.2 电流和电压的参考方向

1.2 电流和电压的参考方向 参考方向就是一个假设的方向&#xff08;类似中学物理的正方形&#xff09;&#xff0c;选参考方向的原因是电路结构略显复杂&#xff0c; 无法直接看出支路电压、电流的实际方向&#xff0c;参考方向可以任意选择&#xff0c;任意支路电压、电流只有…

【韩顺平Java笔记】第8章:面向对象编程(中级部分)【272-284】

272. 包基本介绍 272.1 看一个应用场景 272.2 包的三大作用 272.3 包的基本语法 273. 包原理 274. 包快速入门 在不同的包下面创建不同的Dog类 275. 包命名 276. 常用的包 一个包下,包含很多的类,java 中常用的包有: java.lang.* //lang 包是基本包&#xff0c;默认引入&…

【英语】5. 考研英语语法体系

文章目录 前言句字的成分一、常规句型简单句&#xff08;5 种&#xff09;1. 定义&#xff1a;句子中只包含 *一套主谓结构* 的句子。&#xff08;一个句子只能有一个谓语动词&#xff09;2. 分类 并列句&#xff08;由关联词组成&#xff09;&#xff08;3 种&#xff09;基本…

Unity Shader Graph基础包200+节点及术语解释

目录 Master Stack: Vertex block&#xff1a; Fragment block​编辑 Alpha Clip Threshold Dither transparency Graph Inspector Texture 2d Array/Texture 3d Virtual Texture Sampler State Keywords Boolean keyword 右键显示所有节点 简化测试系列节点&#x…

[SpringBoot] 苍穹外卖--面试题总结--上

前言 1--苍穹外卖-SpringBoot项目介绍及环境搭建 详解-CSDN博客 2--苍穹外卖-SpringBoot项目中员工管理 详解&#xff08;一&#xff09;-CSDN博客 3--苍穹外卖-SpringBoot项目中员工管理 详解&#xff08;二&#xff09;-CSDN博客 4--苍穹外码-SpringBoot项目中分类管理 详…

ThreeJS入门(091):THREE.PositionalAudio 知识详解,示例代码

作者&#xff1a; 还是大剑师兰特 &#xff0c;曾为美国某知名大学计算机专业研究生&#xff0c;现为国内GIS领域高级前端工程师&#xff0c;CSDN知名博主&#xff0c;深耕openlayers、leaflet、mapbox、cesium&#xff0c;webgl&#xff0c;ThreeJS&#xff0c;canvas&#xf…

二叉树—相关结构

1.相关的结构问题&#xff08;分治递归&#xff09; 1.1节点个数 1.2叶子结点个数 叶子结点&#xff1a;没有孩子的节点 1.3树的高度&#xff08;深度&#xff09; 1.4二叉树第k层的节点个数 1.5二叉树查找值为x的节点 2.二叉树的创建和销毁 2.1二叉树的构建 二叉树遍历_牛客…

mysql语句执行过程

具体流程如下: 1】当客户端的SOL发送到MySQL时&#xff0c;首先是到达服务器层的连接器&#xff0c;连接器会对你此次发起的连接进行权限校验&#xff0c;以此来获取你这个账号拥有的权限。当你的账号或密码不正确时&#xff0c;会报用户错误。连接成功如果后续没有任何操作&am…

xlwings直接读取使用区域的数据

这里的读取方法&#xff0c;不用最大行号最大列号&#xff0c;直接使用used_range对应的方法 代码如下&#xff1a; import xlwings as xwfile_path r...\AAA.xlsx# 实例化app&#xff0c;打开文件 app_e xw.App(VisibleFalse,add_bookFalse) work_book appe.books.open(f…

New Phytologist | 中国科学院地理所:菌根共生途径塑造树木根经济空间

本文首发于“生态学者”微信公众号&#xff01; 根经济空间是环境驱动下一系列根属性复杂互作的结果&#xff0c;本质上反映了植物地下碳&#xff08;“货币”&#xff09;投资与养分&#xff08;“商品”&#xff09;收益的多维策略权衡关系&#xff08;图1&#xff09;。然而…

【java】数据类型与变量以及操作符

各位看官&#xff1a;如果您觉得这篇文章对您有帮助的话 欢迎您分享给更多人哦 感谢大家的点赞收藏评论&#xff0c;感谢您的支持&#xff01;&#xff01;&#xff01; 目录 一.字面变量&#xff1a; 二&#xff1a;数据类型 1.1&#xff1a;int类型&#xff1a;&#xff0…

高速机器人的点动与直线运动

工业机器人中的点动和直线运动非常之重要&#xff0c;接下来说一下他们的实现过程。 点动&#xff1a; 点动包括两个部分&#xff0c;第一个点动是每一个关节电机的点动&#xff0c;第二个是机器末端向xyz的三个方向进行点动处理。 第一个点动是非常简单的&#xff0c;即把对…

IO系列-3 NIO基本概念:Buffer和Channel和Selector

1.缓冲区 1.1 Buffer类介绍 java在BIO中通常使用字节数组byte[]和字符数组char[]实现数据传输&#xff0c;在NIO中&#xff0c;引入了缓冲区Buffer进行数据传输&#xff0c;相对数组提供了更多的属性和API.Buffer在java.nio包中引入&#xff0c;Buffer对于常见的类型有对应的…

【C++指南】类和对象(二):类的默认成员函数——全面剖析 :构造函数

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《C指南》 期待您的关注 ​ 阅读本篇文章之前&#xff0c;你需要具备的前置知识&#xff1a;类和对象的基础 点击下方链接 【C指南…

Stable Diffusion的核心插件—ControlNet!万字长文解读!

前言 一、介绍 论文地址&#xff1a;[https://arxiv.org/abs/2302.05543](https://arxiv.org/abs/2302.05543 "https://arxiv.org/abs/2302.05543")代码地址&#xff1a;[GitHub - lllyasviel/ControlNet: Let us control diffusion models!](https://github.com/ll…

Python和C++胶体粒子三维残差算法模型和细化亚像素算法

&#x1f3af;要点 使用信噪比、对比度噪声比和点扩展函数量化实验数据&#xff0c;增强共聚焦显微镜成像。参考粒子跟踪算法&#xff1a;使用二维和三维径向模型细化亚像素。胶体粒子三维图形分割学习模型模拟检测球形胶体。使用网格搜索优化模型和归一化处理以避免光漂白。 …

idear 控制台乱码、查询结果、参数、控制台乱码

一、 -Dfile.encodingUTF-8 二、 三、 四、 五、