U-Boot 之七 详解 Driver Model 架构、配置、命令、初始化流程

news2024/11/15 17:49:18

  U-Boot 在 2014 年 4 月参考 Linux Kernel 的驱动模型设计并引入了自己的 Driver Model(官方简称 DM) 驱动架构。这个驱动模型(DM)为驱动的定义和访问接口提供了统一的方法,提高了驱动之间的兼容性以及访问的标准性。
在这里插入图片描述
  文中涉及的代码均放到了我个人的 Github 上:https://github.com/ZCShou/BOARD-STM32F769I-EVAL,大家可以直接拿来边学习边验证,避免眼高手低。 本文中涉及的源码主要是使用 U-Boot-v2022.10,不同版本源码差异可能较大!!!

配置

  DM 架构需要通过配置项 CONFIG_DM=y 来启用,对应的实际外设的驱动则需要通过使能 CONFIG_DM_xxx 来使能。其中,xxx 表示某个具体的外设,例如,启用 CONFIG_DM_SERIAL 则会自动启用 Makefile 中添加对应的源码文件:
在这里插入图片描述
  目前,绝大多数的设备的驱动均已经完全迁移到了 DM 架构,所以,在实际源码中,我们经常可以看到 CONFIG_DM_xxx 对应的驱动接口被实现了,旧版的则没有实现或者直接没有旧的驱动接口了(部分驱动仍然是旧驱动模式)。
在这里插入图片描述

链接选项

  DM 驱动在编译后会被统一存放在最终的镜像文件中,每个设备的 DM 架构的驱动都会在编译时单独放到一个节区当中。 当我们编译 U-Boot 时所有这些驱动程序都会使用 __u_boot_list_2_"#_list"_2_"#_name 的节区作为名字,此外,这些节区还会被 __u_boot_list_2_"#_list"_1__u_boot_list_2_"#_list"_3 包裹起来,这样就可以计算出所有 __u_boot_list_2_* 的大小。这些信息可以直接在 u-boot.map 看到:
在这里插入图片描述

实际不只有驱动,其他部分,例如,cmd,也是这样处理的

  代码中的实现方式的关键就在于 UCLASS_DRIVER(__name)U_BOOT_DRIVER(__name) 等这几个宏值,这些宏值最终都会引用 ./include/linker_lists.h 中的相关宏 ll_entry_declare,这个就是实现的关键。
在这里插入图片描述
  在初始化过程中,U-Boot 就会遍历上面这些节区,然后进行内容匹配,依次创建各种设备和对应的 UCLASS。如下是根据驱动的名字查找指定驱动的方法:
在这里插入图片描述

Device Tree / Platform Data

  驱动必须知道硬件的基本信息,U-Boot 支持 Platform Data(平台数据,代码中常简称 platplatdata)和 Flattened Device Tree(设备树,代码常简称 fdt)这两种硬件基本配置信息提供方式。其中,平台数据是旧方式,设备树则是标准方式。

Platform Data

  Platform Data 是通过一个 C 结构体来将平台特定的配置信息(寄存器的地址,总线速度等)传递给驱动程序,设备信息最终被存放到 udevice ->plat_ 指向的内存中, 驱动可以随时通过 dev->plat_ 访问他们的数据。官方指出,除非有必要的理由,否则不要使用平台数据这种方式,而应该使用设备树方式。

static const struct dm_demo_pdata red_square = {
        .colour = "red",
        .sides = 4.
};

/* 直接定义(不推荐) */
static const struct driver_info info[] = {
        {
                .name = "demo_shape_drv",
                .plat = &red_square,
        },
};
demo1 = driver_bind(root, &info[0]);

/* 使用 U_BOOT_DRVINFO 宏(推荐) */
U_BOOT_DRVINFO(demo0) = {
	.name = "demo_shape_drv",
	.plat = &red_square,
};

  Platform Data 只有在有当内存限制不允许使用设备树时才会使用。 此外,U-Boot 提供了一种方法,自动将设备树转换为 Platform Data,即 of-platdata 特性。但是,of-platdata 仅在 SPL/TPL 阶段可用。

Device Tree

  设备树提供了一种更灵活的提供设备数据的方法,官方推荐要使用设备树方式。U-Boot 内部将自动解析设备树获取相关设备信息,设备信息最终也是被存放到 udevice ->plat_ 指向的内存中(具体方式就是通过 driver 中的 plat_autoof_to_plat)。
在这里插入图片描述

框架

  U-Boot 的 DM 使用 uclassudevice 这两个抽象的类来管理所有的设备驱动,这两个抽象类分别各自对应 uclass_driverdriverudevice 是根据 driver 动态创建的;uclass 是根据 uclass_driver 创建的。但是只有在创建 udevice 才会查找对应的 uclass ,因此, 最终是只有 driver 存在时,才会创建 uclass
在这里插入图片描述
  真正有用的是一个个独立的 uclass_driverdriver,他们分别通过 uclassudevice 管理起来。在代码实现上,uclassudevice 其实都是双向链表的节点,通过双向链表将所有驱动串起来进行管理。并最终由 global_data 中的相关变量指示链表位置。一个简易的框图如下所示:
在这里插入图片描述
  官方提供了一个驱动 DEMO (drivers/demo),通过开启 CONFIG_DM=yCONFIG_CMD_DEMO=yCONFIG_DM_DEMO=yCONFIG_DM_DEMO_IMX6ULL=y 就可以把这个 DEMO 添加我们的构建中,然后进行学习测试。

global_data

  ./include/asm-generic/global_data.h 文件中的 struct global_data 结构管理着整个 U-Boot 的全局变量,当我们定义 CONFIG_DM=y 后,global_data 中就会多出一些 DM 相关的字段,保存 DM 相关信息。具体见下面的代码注释:

struct global_data {
/* ... 略 ... */
#ifdef CONFIG_DM
	struct udevice *dm_root; /* 指向 DM 的根设备 */
	struct udevice *dm_root_f; /* 指向重定向前的 DM 的根设备 */
	struct list_head uclass_root_s; /* 非只读性内存中的 UCLASS 链表表头 */
	struct list_head *uclass_root;	/* UCLASS 链表表头指针,非只读内存中他就指向上面的 uclass_root_s */
	/* ... 略 ... */
#endif
/* ... 略 ... */

uclass 和 uclass_driver

  uclassuclass_driver 定义在 ./include/dm/uclass.h 文件中,其中,uclass 将同类型的设备划为一组进行归类管理;uclass_driver 为一组相关的驱动程序提供了一致的接口。uclassuclass_driver 是一一对应的。
在这里插入图片描述
  在表述中,我们通常使用 uclass_id 来表示一个 uclass,例如,UCLASS_ROOT 表示 ROOT UCLASS 本身,其对应的驱动则称为 uclass_driver_root。 uclass 的 ID 可用值定义在 ./include/dm/uclass-id.h 文件的 enum uclass_id 中,当时需要注意,该 ID 实际是在 struct uclass_driver 中被使用。
在这里插入图片描述

struct uclass

  uclass 将同类型的设备划为一组进行归类管理。注意,uclass 是 U-Boot 在初始化过程中自动生成的,并且不是所有 uclass 都会生成,有对应 uclass_driver 并且有被 udevice 匹配到的 uclass 才会生成(下面的初始化章节详细说明)。

struct uclass {
	void *priv_;					/* uclass 本身使用的私有数据指针。不对外使用。*/
	struct uclass_driver *uc_drv; 	/* 一个 UCLASS 对一个 uclass_driver,这个指针指向对应的 uclass_driver */
	struct list_head dev_head;		/* 本 UCLASS 下对应的 udevice 链表 */
	struct list_head sibling_node;  /* 本 UCLASS 节点本身的前后节点(用于串联 uclass 链表) */
};

  在代码实现上,struct uclass 这个结构体其实就是一个链表节点,当 DM 初始化之后,所有的 uclass 会形成一个由 gd->uclass_root 为链表头的双向链表。这个链表是通过其中的 sibling_node 这个这个成员串起来的。
在这里插入图片描述
  在初始化时,会遍历所有 uclass_driver,每发现一个 uclass_driver 就会查找其中的 ID 对应的 uclass 是否存在(判断条件是已存在的 uclass->uc_drv->id 是否等于当前 uclass_driver->id),不存在就会以 uclass_driver 中的 ID新建一个 uclass,然后关联到 uclass_driver 上。也就是说,uclass 是根据 uclass_driver 动态创建的。

struct uclass_driver

  uclass_driver 为一组相关的驱动程序提供了一致的接口,每一个 uclass 都会对应一个 uclass_driver 。在代码实现上,struct uclass 中的 uc_drv 就指向了当前 uclass 对应的 uclass_driver

struct uclass_driver {
	const char *name;						/* uclass driver 的名称,在定义 uclass_driver 时,填写的一个字符串 */
	enum uclass_id id;						/* uclass 的 ID 号,取值见 ./include/dm/uclass-id.h 文件中的 enum uclass_id 定义 */
	int (*post_bind)(struct udevice *dev);  /* 在一个新设备绑定到这个 uclass 后被调用 */
	int (*pre_unbind)(struct udevice *dev); /* 在一个设备从该 uclass 解绑定之前调用 */
	int (*pre_probe)(struct udevice *dev);  /* 在 probe 一个新设备之前调用 */
	int (*post_probe)(struct udevice *dev); /* 在 probe 一个新设备之后调用 */
	int (*pre_remove)(struct udevice *dev); /* 在移除设备之前调用 */
	int (*child_post_bind)(struct udevice *dev);  	/* 在这个 uclass 的 child 绑定到一个设备之后被调用 */
	int (*child_pre_probe)(struct udevice *dev);	/* 在这个 uclass 的 child 被 probed 之前被调用 */
	int (*child_post_probe)(struct udevice *dev);	/* 在这个 uclass 的 child 被 probed 之后被调用 */
	int (*init)(struct uclass *class);				/* 在创建一个新的 uclass 时被调用 */
	int (*destroy)(struct uclass *class);			/* 在 uclass 被销毁时被调用 */
	int priv_auto;				/* 如果非零,它就是 uclass->priv_ 指针中分配的私有数据的大小。如果为 0,则 uclass driver 负责分配所需私有数据的空间 */
	int per_device_auto;		/* 每个 device 都可以将 uclass 拥有的私有数据保存在自己的 dev->uclass_priv_ 中。如果该值是非零,将在 device 初始化时自动分配该值大小的空间 */
	int per_device_plat_auto;	/* 每个 device 都可以将 uclass 拥有的平台数据保存在自己的 dev->uclass_plat_ 中。如果该值是非零,将在 device 初始化时自动分配该值大小的空间 */
	int per_child_auto;			/* 每个子设备可以保存它的 parent 私有数据到 dev->parent_priv_ 中。如果该值是非零,将在 device 初始化时自动分配该值大小的空间。在 udevice 对应的 driver 中,也存在该变量,只有 udevice 对应的 driver 中该值为 0 时,才会使用该值 */
	int per_child_plat_auto;	/* 每个子设备可以保存它的 parent 平台数据到 dev->parent_plat_ 中。如果该值是非零,将在 device 初始化时自动分配该值大小的空间。在 udevice 对应的 driver 中,也存在该变量,只有 udevice 对应的 driver 中该值为 0 时,才会使用该值 */
	uint32_t flags;				/*  这个 uclass 的标志(DM_UC_…) */
};

  在代码实现中,uclass_driver 必须使用宏 UCLASS_DRIVER(__name) 来进行定义。该宏值除了使用 struct uclass_driver 定义 __name 变量外,还会定义一个同名节区,并将 __name 放到这个同名的节区当中。以 serial 为例,如下所示:
在这里插入图片描述
  uclass_driver 是通过 struct uclass 链表管理起来的,每个 uclass_driver 都必须关联到一个指定的 struct uclass 上(链表节点上),当不存在 uclass_driver 对应的 struct uclass 时,就会自动创建一个 struct uclass 然后关联起来。
在这里插入图片描述

udevice 和 driver

  udevicedriver 定义在 ./include/dm/device.h 文件中,其中,udevice 是一个抽象类表示一个设备(驱动程序的实例);driver 则是一个设备实际对应的驱动程序。udevicedriver 可以是多对一的关系(即多个设备可能共用同一个 driver)。
在这里插入图片描述
  不同于 UCLASS,udevicedriver 中均有 name 成员来标识自己,而且这两个名字可以是不相同的,也可以相同。例如,ROOT 设备的名字和 ROOT 驱动的名字均为 root_driver。设备树中定义的设备名字通常是节点名,而对应的驱动名字则是代码中一个有确切含义的字符串。

struct udevice

  udevice 包含有关设备的信息,其本质上是一个驱动程序实例,必须绑定到特定 port 或 peripheral 的驱动上的(udevice 必须通过其成员 const struct driver *driver 与一个指定的 driver 关联)。udevice 本身并无法关联 UCLASS,必须根据其关联 struct driver 中的 id 属性来关联其所在的 UCLASS 的。

struct udevice {
	const struct driver *driver;/* 此设备使用的驱动程序 */
	const char *name;			/* 设备名称,通常为 FDT 节点名称 */
	void *plat_;				/* 此设备的配置数据(DM 之外不能访问),这通常由驱动程序制定大小,并且由驱动程序负责填充内容 */
	void *parent_plat_;			/* 该设备的父总线配置数据(DM 之外不能访问) */
	void *uclass_plat_;			/* 此设备对应的 uclass 的配置数据(DM 之外不能访问) */
	ulong driver_data;			/* 驱动程序数据字,用于将此设备与其驱动程序相匹配的条目 */
	struct udevice *parent;		/* 该设备的父设备,顶级设备(例如,ROOT DEVICE)的 parent 为 NULL */
	void *priv_;				/* 此设备的私有数据(DM 之外不能访问) */
	struct uclass *uclass;		/* 指向该设备对应的 uclass 的指针 */
	void *uclass_priv_;			/* 此设备对应的 uclass 的私有数据(DM 之外不能访问) */
	void *parent_priv_;			/* 此设备的父设备的私有数据 */
	struct list_head uclass_node;	/* 由此设备对应的 uclass 用于连接它的设备 */
	struct list_head child_head;	/* 此设备的子设备列表 */
	struct list_head sibling_node;	/* 所有设备列表中的下一个设备 */
#if !CONFIG_IS_ENABLED(OF_PLATDATA_RT)
	u32 flags_;					/* 此设备的标志 DM_FLAG_xx */
#endif
	int seq_;					/* 为该设备分配的序列号(-1 表示没有序列号)。这是在设备绑定时设置的,在设备对应的 uclass 中是唯一的。如果设备在设备树中有别名,则别名用于设置序列号。否则,使用下一个可用号码。序列号用于某些需要对设备进行编号的命令(例如 mmc dev)(DM 之外不能访问)*/
#if CONFIG_IS_ENABLED(OF_REAL)
	ofnode node_;				/* 此设备的设备树节点的引用 */
#endif
#if CONFIG_IS_ENABLED(DEVRES)
	struct list_head devres_head;	/* 与此设备关联的内存分配列表。当 CONFIG_DEVRES 被启用时,devm_kmalloc()和 friends 会添加到这个列表中。这样分配的内存将在移除或解绑设备时自动释放 */
#endif
#if CONFIG_IS_ENABLED(DM_DMA)
	ulong dma_offset;			/* (CPU 的)物理地址空间和设备总线地址空间之间的偏移量 */
#endif
};

  在代码实现上,struct udevice 这个结构体其实就是一个链表节点,当 DM 初始化之后,所有的 udevice 会形成一个由 gd->dm_root 为链表头的双向链表,这个链表是通过其中的 child_headsibling_node 这个这个成员串起来的。与 uclass 不同, udevice 还支持通过 uclass_node 串联到 UCLASS 中。即一个 struct udevice 会同时位于多个链表中。
在这里插入图片描述
  在初始化时,会遍历所有 driver ,每发现一个 driver 就会查找对应的 udevice 是否存在,不存在就会以 driver 的名字新建一个设备。也就是说,udevice 是根据 driver 动态创建的。

  一个设备将通过一个 ‘bind’ 调用来产生,要么是由于 U_BOOT_DRVINFO() 宏(在这种情况下,plat 是非 null),要么是由于设备树中的一个节点(在这种情况下,of_offset为 >= 0)。在后一种情况下,我们将在驱动程序的 of_to_plat 方法中将设备树信息转换为 plat(如果设备有设备树节点,则在 probe 方法之前调用)。

关于 Device Sequence Numbers

  在多数情况下 U-Boot 从 0 开始为设备进行编号。这个编号唯一地标识了其 UCLASS 中的一个设备,因此在一个特定 UCLASS 中没有两个设备可以具有相同的序列号。

  序列号从 0 开始,但允许有间隙。 例如,一个开发板可能有 I2C1、I2C4、I2C5,但没有 I2C0、I2C2、I2C3。设备如何编号的选择取决于特定的开发板,在某些情况下可能由 SoC 设置。
在这里插入图片描述
设备序列号在绑定设备时进行解析,存储在 udevice->seq_ 成员变量中,且在设备的整个生命周期内都不会改变。

struct driver

  driver 含有创建新设备和删除设备的方法,设备由 platdata 或者 device tree 节点(通过查找与 of_match 匹配的 compatible 字符串进行配对)提供的信息来设置自己。

struct driver {
	char *name;			/* 设备名字。在定义 driver 时指定的一个字符串 */
	enum uclass_id id;	/* 标记此驱动属于哪个 uclass 的 id,取值是 ./include/dm/uclass-id.h 中定义的 enum uclass_id  */
	const struct udevice_id *of_match;	/* 要匹配的 compatible 字符串列表 */
	int (*bind)(struct udevice *dev);	/* 绑定 device 到它的 driver 时被调用 */
	int (*probe)(struct udevice *dev);	/* 被调用来探测一个设备,即激活设备 */
	int (*remove)(struct udevice *dev);	/* 被调用来移除一个设备 */
	int (*unbind)(struct udevice *dev);	/* 调用来解除设备与其驱动程序的绑定 */
	int (*of_to_plat)(struct udevice *dev);			/* 在 probe 之前,解析对应 udevice 的 dts 节点,转化成 udevice 的平台数据(存放于 udevice->plat_ 中) */
	int (*child_post_bind)(struct udevice *dev);	/* 在一个新的 child 设备被绑定之后调用 */
	int (*child_pre_probe)(struct udevice *dev);	/* 在探测子设备之前调用。设备已分配内存,但尚未被探测。. */
	int (*child_post_remove)(struct udevice *dev);	/* 在移除子设备后调用。设备已经分配了内存,但是它的 device_remove() 方法已经被调用 */
	int priv_auto;		/* 如果非零,这是在 udevice->priv_ 指针中分配的私有数据的大小。如果为零,则驱动程序负责分配所需的任何数据。 */
	int plat_auto;		/* 如果非零,这是要分配到 udevice->plat_ 指针中的平台数据的大小。这通常只对支持设备树的驱动程序(使用 of_match 的驱动程序)有用,因为使用 platform data 的驱动程序将拥有 U_BOOT_DRVINFO() 实例化中提供的数据 */
	int per_child_auto;	/* 每个设备都可以保存其父设备拥有的私有数据。如果需要,如果该值非零,将自动分配到 udevice->parent_priv_ 指针中。 */
	int per_child_plat_auto;	/* 总线喜欢存储关于其子节点的信息。如果非零,这是该数据的大小,将分配到子对象的 udevice->parent_plat_ 指针中 */
	const void *ops;	/* driver的具体操作,这通常是一个由driver定义的函数指针列表,用于实现 uclass 所需的驱动程序函数。 */
	uint32_t flags;		/* 驱动程序标志-参见' DM_FLAGS_…' */
#if CONFIG_IS_ENABLED(ACPIGEN)
	struct acpi_ops *acpi_ops;	/* 高级配置和电源接口(ACPI)操作,允许设备向传递给Linux的ACPI表中添加东西 */
#endif
};

  struct driver 都属于 UCLASS,代表同一类型的一类设备。驱动程序的共同元素可以在 UCLASS 中实现,或者 UCLASS 可以为其中的驱动程序提供一致的接口。udevice 是根据 struct driver 中的 id 属性来关联其所在的 UCLASS。

  在代码实现中,driver 必须使用宏 U_BOOT_DRIVER(__name) 来进行定义。该宏值除了使用 struct uclass_driver 定义 __name 变量外,还会定义一个同名节区,并将 __name 放到这个同名的节区当中。以 serial 为例,如下所示:
在这里插入图片描述
  driver 是通过 struct udevice 链表管理起来的,每个 driver 都必须关联到一个指定的 struct udevice 上(链表节点上),当不存在 driver 对应的 struct udevice 时,就会自动创建一个 struct udevice 然后关联起来。
在这里插入图片描述

DM 命令

  U-Boot 在 ./cmd/dm.c 文件中提供了 DM 相关的命令,可以在 U-Boot 命令界面查看 DM 相关信息。进入 U-Boot 的命令行模式以后输入 help 或者 ?,然后按下回车即可查看当前 U-Boot 默认支持的所有命令。还可以输入help 命令名 或者 ? 命令名 来查看命令的详细用法,例如,help dm 就会打印出 dm 这个命令的详细介绍。
在这里插入图片描述

dm compat

  dm compat 用于显示与每个驱动程序相关联的兼容字符串(可以在每个开发板的设备树文件中查找这些字符串),如果有多个字符串,则每行显示一个。
在这里插入图片描述
各列含义如下:

列名含义
Driver驱动的名字,即 driver->name 的值
Compatible驱动兼容字符串,即 driver->of_match 的值。如果设备树中 Compatible 与这里的匹配,则表示设备树节点设备使用该驱动

dm devres

  dm devres用于显示一个设备的 devres(设备资源)记录列表。一些驱动程序使用 devres API 来分配内存,这样当设备被移除时,就可以自动释放内存(在驱动程序的 remove() 方法中不需要任何代码)。

该特性需要定义 CONFIG DEVRES 来启用。

dm drivers

  dm drivers 用于显示所有可用的驱动程序,驱动程序对应的 UCLASS 和使用该驱动程序的设备列表(多个设备时每行一个设备),每行一个驱动。如果驱动程序没有对应的设备,则设备显示为 none
在这里插入图片描述
各列含义如下:

列名含义
Driver驱动的名字,即 driver->name 的值
uidUID 即 enum uclass_id 中对应的值
uclassUCLASS 名字,即 uclass_driver->name 的值
Devices设备名字,即 udevice->name 的值

dm static

  dm static 用于显示由平台数据绑定的设备,即不是来自设备树的设备。这些通常都没有,但一些开发板可能会出于空间原因使用静态设备。

列名含义
Driverdriver->name 中定义的驱动的名字
Address驱动的内存地址

dm tree

  dm tree 用于显示设备的完整树。
在这里插入图片描述
各列含义如下:

列名含义
Class设备的 UCLASS 名,即 uclass_driver->name 的值
Index在 UCLASS 中设备的索引号。注意不是 Sequence Number。
Probed如果设备处于活动状态,则显示 +
Driver此设备使用的驱动程序的名称,即 driver->name 的值
Name以树型结构(含子设备容易查看)显示设备名称(即 udevice->name 的值)

dm uclass

  dm uclass 用于显示每个类以及该类中的设备列表。
在这里插入图片描述

container_of

  DM 中是通过链表来管理设备的,链表的管理用到了 scripts/kconfig/list.h 中定义的 container_of 这个宏。U-Boot 中的 container_of 就是从 Linux 拿过来,这个宏的设计还是比较有意思,必须要重点解析一下。乍一看这个宏并不复杂,就一个代码块({})两个独立的语句(;)。
在这里插入图片描述

const typeof( ((type *)0)->member ) *__mptr = (ptr);

  typeof 是关键字,获取成员类型。所以,前半句 const typeof( ((type *)0)->member ) 实际就是获取 member 的类型,整句就是以 member 的类型定义指针变量 _mptr 并赋值为 ptrptr 实际是指向 member 的指针。

(type *)( (char *)__mptr - ((size_t) &((type *)0)->member) );

  1. (char *)__mptr 将成员类型强制转化为 char *,这要地址进行加减时以字节为单位
  2. offsetof 用于获取结构体成员偏移量。这是个巧妙用法,我们知道,结构体成员得地址减去结构体基地址就是偏移量。而如果这个基地址为 0 ,则直接取成员地址就是偏移量。
  3. (char *)__mptr - ((size_t) &((type *)0)->member) 就是得到了 type 结构体变量的首地址,只不过类型是 char*,最后使用 (type *) 在转换为 type 类型指针。

结论

  container_of 最终的目的返回的就是 member 所在的结构体的基地址。简单来说,container_of 的作用就是根据结构体的成员获取结构体基地址。而 const typeof( ((type *)0)->member ) *__mptr = (ptr); 仅仅是个中间状态,如果没有这一句,就无法实现 container_of 的通用性(代替方案是使用类型强转,但是也就限定了只能用在特定类型中)。
在这里插入图片描述

初始化流程

  DM 初始化的接口在 dm_init_and_scan 中,初始化流程主要有两次,入口函数分别是在重定位之前调用的 ./common/board_f.c 文件中的 static int initf_dm(void) 和在重定位之后调用的 ./common/board_r.c 文件中的 static int initr_dm(void)。重定位后的初始化与重定位前并没有太多区别。
在这里插入图片描述
  U-Boot 提供了 bootstage 记录每个阶段的执行时间等信息,可以将此记录信息报告给用户,并将其传递给操作系统进行日志记录/进一步分析。默认 bootstage 并没有启用,所以这里直接忽略。真正与 DM 初始化相关的是 dm_init_and_scan,接下来重点关注这个函数。

至于这里为啥还需要根据 CONFIG_TIMER_EARLY 来初始化一个定时器暂时还不知道原因。

dm_init_and_scan

  dm_init_and_scan 定义于 drivers/core/root.c 中,入参 pre_reloc_only 为 true 时表示只解析重定位之前的节点(只会对设备树中带有 u-boot,dm-pre-reloc 属性的节点或者带有 DM_FLAG_PRE_RELOC 标志的设备进行解析);pre_reloc_only 为 false 的时则会对所有节点都进行解析。
在这里插入图片描述

  1. 因为 of-platdata 仅在 SPL/TPL 阶段可用,所以后续忽略所有 of-platdata 相关代码。
  2. DM_EVENT 我这里默认也没有启用,直接忽略。

dm_init

  dm_init 定义于 drivers/core/root.c 中,主要用于初始化 ./drivers/core/root.c 中定义的根设备(U_BOOT_DRIVER(root_driver))。 根设备不是通过设备树定义的,而是直接在代码中定义的,因此,它的初始化比较特殊。

所有设备都是根设备的子节点

  dm_init 的入参 of_live 表示是否启用了 Live Device Tree,由源码可知该入参并没有被使用。Live Device Tree 是一个与 Flattened Device Tree 相对应的概念。主要用于加快启动的扫描时间,但是只能在重定位之后才能使用。

  首先,将 gd->uclass_root 指向 gd->uclass_root_s,然后初始化 gd->uclass_root 中的成员:gd->uclass_root.next = gd->uclass_rootgd->uclass_root.prev = gd->uclass_root
在这里插入图片描述

device_bind_by_name

  device_bind_by_name 定义于 drivers/core/device.c 中,主要用于绑定那些不使用设备树定义的设备。这个接口用于创建一个设备并将其绑定到驱动程序。对于 DM 初始化来说,这里就会创建 ROOT 设备,并将设备与 ./drivers/core/root.c 中定义的 U_BOOT_DRIVER(root_driver) 绑定。

device_bind_by_name 是个通用接口,其他设备初始化也会调用,见后文

lists_driver_lookup_name

  lists_driver_lookup_name 定义于 drivers/core/device.c 中,其会遍历所有 struct driver 对应的节区,从中匹配指定的驱动名字。这里的根设备的初始化,入参 name 就是与 ./drivers/core/root.c 中定义的 U_BOOT_DRIVER(root_driver) 中的 name 取值 root_driver,最终会返回基地址 0x804b438
在这里插入图片描述

uclass_get

  设备是需要归属 UCLASS 的,uclass_get 定义于 drivers/core/uclass.c 中,实现根据 udevice 中的 uclass id 遍历 gd->uclass_root 指向的 uclass 链表,返回找到的 uclass 地址,如果没有找到则会新建一个 uclass,并返回新建的 uclass 地址。

  1. 调用 uclass_find 遍历 gd->uclass_root 指向的 uclass 链表,查找指定 id 的 uclass。对于 DM 初始化来说,由于 gd->dm_root 是 NULL,因此不会实际执行 lis_for_each_entry;其他情况下,展开如下所示:
    在这里插入图片描述

uclass_get 是个通用接口,其他设备初始化也会调用,见后文

  1. 当找不到指定 id 的 uclass 时,调用 uclass_add 新建一个 uclass
    1. 调用 lists_uclass_lookup 查找 uclass_driver,返回找到的 uclass_driver 地址,否则返回错误
      在这里插入图片描述
    2. 新建一个 uclass,然后进行一系列初始化,最终返回新建的 uclass。
      在这里插入图片描述
      1. uc = calloc(1, sizeof(*uc)); 申请一个 UCLASS 节点内存
      2. 判断 uclass_driver->priv_auto 申请 uclaas->priv_ 内存空间(接口 uclass_set_priv 就是一个简单的赋值语句 uc->priv_ = priv;)。
      3. INIT_LIST_HEAD() 用于将 dev_headsibling_node 中的指针指向自身
      4. list_add 负责将申请的 UCLASS 节点内存串联到 gd->uclass_root 链表之上
      5. 判断并调用当前 uclass 对应的 uclass_driverinit 接口:uc_drv->init

device_bind_common

  device_bind_common 定义于 drivers/core/device.c/ 中,作用是将设备驱动、设备、UCLASS 三者(DM 初始化的 root_driver 设备 与 U_BOOT_DRIVER(root_driver)UCLASS_ROOT )进行绑定,根据上面的初始化流程,只有存在一个设备驱动时,才会创建对应的设备。

  1. 调用 uclass_get 根据设备 ID 查找对应的 UCLASS,详细过程参见上面的介绍。
  2. dev = calloc(1, sizeof(struct udevice)); 申请一个 udevice 节点内存,即建立一个设备(DM 初始化中,这里就会建立 ROOT DEVICE),然后初始化其中的链表节点。
    在这里插入图片描述
    1. INIT_LIST_HEAD 用于将各链表节点指向自身
    2. dev_set_plat 就是 dev->plat_ = plat;
    3. 有些设备(如 SPI 总线、I2C 总线和串口)使用别名进行编号。因此需要从设备树节点中提取出来放到 dev->seq_ 中。
      在这里插入图片描述
  3. 初始化当前设备的使用的一些内存,例如 设备的的 plat_
    	/* Check if we need to allocate plat */
    	if (drv->plat_auto) { /* 当驱动中 将 plat_auto 设置为实际的 platform data 的大小 */
    		bool alloc = !plat; /* plat 是入参,如果入参指定了,则就不会在分配驱动中 drv->plat_auto 的内存空间 */
    
    		/*
    		 * For of-platdata, we try use the existing data, but if
    		 * plat_auto is larger, we must allocate a new space
    		 */
    		if (CONFIG_IS_ENABLED(OF_PLATDATA)) {
    			if (of_plat_size)
    				dev_or_flags(dev, DM_FLAG_OF_PLATDATA);
    			if (of_plat_size < drv->plat_auto)
    				alloc = true;
    		}
    		if (alloc) {
    			dev_or_flags(dev, DM_FLAG_ALLOC_PDATA);
    			ptr = calloc(1, drv->plat_auto);	/* 分配内存 */
    			if (!ptr) {
    				ret = -ENOMEM;
    				goto fail_alloc1;
    			}
    
    			/*
    			 * For of-platdata, copy the old plat into the new
    			 * space
    			 */
    			if (CONFIG_IS_ENABLED(OF_PLATDATA) && plat)
    				memcpy(ptr, plat, of_plat_size);
    			dev_set_plat(dev, ptr);	/* dev->plat_ = plat; */
    		}
    	}
    
  4. 当前设备可以选择将对应的 UCLASS 中的一些平台数据保存到自己的 uclass_plat_ 中。如果对应的 uclass->per_device_plat_auto 不是 0,则申请内存,并调用 dev_set_uclass_plat(dev, ptr); 赋值 dev->uclass_plat_ = uclass_plat; 指向申请的内存。
    	size = uc->uc_drv->per_device_plat_auto;
    	if (size) {
    		dev_or_flags(dev, DM_FLAG_ALLOC_UCLASS_PDATA);
    		ptr = calloc(1, size);
    		if (!ptr) {
    			ret = -ENOMEM;
    			goto fail_alloc2;
    		}
    		dev_set_uclass_plat(dev, ptr);
    	}
    
  5. 如果当前设备存在父节点设备的话,则初始化父节点设备的 per_child_plat_auto,然后调用 list_add_tail 将新的设备添加到其父节点设备。对于我们的 ROOT 设备,其 parent 是 NULL,因此不需要添加。parent 不是 NULL 时,就会通过 child_headsibling_node 这两个成员把设备串到 Parent 上,后文有详细图示。
  6. 调用 uclass_bind_device 将上面创建的设备添加到 UCLASS,同时,如果设备存在父设备的话,需要调用父设备的 child_post_bind 方法(对于这里的 DM 初始化,根设备没有父设备)。直接上图:
    在这里插入图片描述
  7. 调用当前设备对应驱动的 bind 方法完成设备与对应驱动的绑定,然后调用当前设备对应的父设备的 child_post_bind(这里与 uclass_bind_device 中其实存在重复),最后调用当前设备对应的 UCLASS(对应的 uclass_driver) 的 post_bind 方法
    在这里插入图片描述

device_bind_common 是个通用接口,其他设备初始化也会调用,见后文

dev_set_ofnode

  OF_CONTROL 表示是否启用了设备树,这个默认是启用的,因此会继续调用定义于 drivers/core/device.c 中的 dev_set_ofnode,将根设备中 node_ 指向根节点
在这里插入图片描述

device_probe

  device_probe 定义于 drivers/core/device.c 中,用于激活一个设备以便它可以随时使用,为了节省资源,U-Boot 中的设备会被延迟探测。如果设备已经激活了,则直接返回。对于 DM 初始化来说,这里就是探测并激活根设备。

  1. 调用 device_of_to_plat 将 dts 中的信息转化为设备的平台数据,以便提供探测设备等操作所需的信息。这可能会导致一些其他设备被探测,如果这个设备依赖于它们,例如一条 GPIO 线将导致一个 GPIO 设备被探测。
    1. 如果当前设备有父设备,则递归执行父设备的 device_of_to_plat
      在这里插入图片描述
    2. 调用 device_alloc_priv 分配私有数据使用的内存
      在这里插入图片描述
    3. 调用设备对应的驱动自己的 of_to_plat,将设备树中描述的设备信息,转化为一个平台数据(存储于 udevice ->plat_ 指向的内存中),后续驱动在使用使用硬件时就从平台数据中获取相关资源。
      在这里插入图片描述
  2. 如果该设备存在 parent,那么先 probe parent 设备,确保所有的父设备都被 probed。直接递归 device_probe 实现
    在这里插入图片描述
  3. 调用 device_get_dma_constraints 填充设备的 DMA 约束。从固件中获取设备的 DMA 约束。驱动程序后来使用此信息将物理地址转换为设备的总线地址空间。目前,仅支持设备树。
  4. 调用 uclass_pre_probe_device 执行探测设备前当前设备对应的 uclass(对应的 uclass_driver) 中需要执行的接口。
    1. 当前设备对应的 uclass(对应的 uclass_driver) 中的 pre_probe() 方法
      在这里插入图片描述
    2. 当前设备的父设备对应的 uclass(对应的 uclass_driver) 中的 child_pre_probe() 方法
      在这里插入图片描述
  5. 调用当前设备的父设备的 child_pre_probe
  6. 调用 dev_has_ofnode 只处理具有有效 ofnode 的设备
  7. 执行该设备的 driver 的 probe 函数,真正激活该设备。
    在这里插入图片描述
  8. 调用 uclass_post_probe_device 执行探测设备后 uclass(对应的 uclass_driver)中需要执行的接口。这包括 uclass_driver 的 post_probe() 方法和父 uclass(对应的 uclass_driver) 的child_post_probe() 方法。
    1. 当前设备的父设备对应的 uclass(对应的 uclass_driver) 中的 child_post_probe() 方法
      在这里插入图片描述
    2. 当前设备对应的 uclass(对应的 uclass_driver) 中的 post_probe() 方法
      在这里插入图片描述

device_probe 是个通用接口,其他设备初始化也会调用,见后文

dm_scan

  dm_scan 定义于 drivers/core/root.c 中,负责初始化根设备之外的所有设备。 前面说过,U-Boot 支持 Platform Data(代码中常简称 plat)和 Flattened Device Tree(代码常简称 fdt)这两种驱动配置的基本方式。因此,这里必须处理这两种驱动定义的设备。

dm_scan_plat

  dm_scan_plat 定义于 drivers/core/root.c 中,查找并绑定使用 U_BOOT_DRVINFO(__name) 直接定义的设备。U_BOOT_DRVINFO(__name) 定义于 ./include/dm/platdata.h 中,与 U_BOOT_DRIVER(__name) 类似,U_BOOT_DRVINFO(__name) 除了使用 struct driver_info 定义变量 __name,也会同时定义一个同名节区,并将 __name 放到此节区中。
在这里插入图片描述
  dm_scan_plat 实际就是遍历 driver_info 表(__u_boot_list_2_driver_info_1__u_boot_list_2_driver_info_3 之间),然后以 driver_info 中的 name 成员去查找使用 U_BOOT_DRIVER(__name) 定义的驱动程序。
在这里插入图片描述
  udevice 的建立、driver 的绑定、UCLASS 的建立、uclass_driver 的绑定与上面的 root 设备一样,其内部最终也是调用 device_bind_by_name 完成这一些列的动作(调用 device_bind_by_name 时传参不同而已)。
在这里插入图片描述
注意,所有节点都是以 gd->dm_root 为父节点!!!

dm_extended_scan

  dm_extended_scan 定义于 drivers/core/root.c 中,处理设备树中定义的设备。根据源码,dm_extended_scan 主要包含两部分,首先,通过 dm_scan_fdt 处理设备树中的设备节点;其次,由于有些节点(/chosen/clocks/firmware)本身不是设备,但可能包含一些设备。此时通过 dm_scan_fdt_ofnode_path 来挨个处理这些节点中的设备。
在这里插入图片描述
  ofnode_root 就是根节点(of_offset=0),而 ofnode_path 这是根据完整的路径来查找指定的设备树节点的,这两部分最终都是提供过 dm_scan_fdt_node 来处理节点设备的,因此我们只需要重点关注 dm_scan_fdt_node 即可。

dm_scan_fdt_node

  dm_scan_fdt_node 定义于 drivers/core/root.c 中,实现扫描设备树,为节点绑定驱动。它会给绑定的设备树节点创建一个新 udevice,并使用入参 parent 作为其父设备(入参固定为 gd->dm_root,也就是根设备)。

   dm_scan_fdt_node 会从根节点开始,依次遍历所有子节点。由于根设备已经在前面单独初始化了,所以这里找的设备就是根节点下的第一个设备,然后使用 lists_bind_fdt 挨个对节点进行绑定(这里的绑定即将节点与对应的 udevice、driver、uclass、uclass_driver 关联起来)。

	for (node = ofnode_first_subnode(parent_node);	/* 以根节点开始,获取第一个子节点 */
	     ofnode_valid(node);
	     node = ofnode_next_subnode(node)) {		/* 当前节点的子节点 */
		const char *node_name = ofnode_get_name(node); /* 节点名字 */

		if (!ofnode_is_enabled(node)) {
			pr_debug("   - ignoring disabled device\n");
			continue;
		}
		err = lists_bind_fdt(parent, node, NULL, NULL, pre_reloc_only); /* 绑定 udevice、driver、uclass */
		if (err && !ret) {
			ret = err;
			debug("%s: ret=%d\n", node_name, ret);
		}
	}
lists_bind_fdt

  lists_bind_fdt 内部会创建一个 udevice,然后将 udevice 与当前节点(入参 node)进行绑定。当然,创建 udevice 的同时,其对应的 driver,uclass、uclass_driver 都会进行绑定。

  1. 获取当前节点的 compatible 内容(基地址 + 长度)
  2. 遍历 compatible ,然后挨个去 driver 表(__u_boot_list_2_driver_1__u_boot_list_2_driver_3 之间)中去对比(节点的 compatible 与 驱动中的 of_match 匹配)。代码很简单,就是个两层循环
    for (i = 0; i < compat_length; i += strlen(compat) + 1) {
    	compat = compat_list + i;
    	log_debug("   - attempt to match compatible string '%s'\n",
    		  compat);
    
    	for (entry = driver; entry != driver + n_ents; entry++) {
    		if (drv) {
    			if (drv != entry)
    				continue;
    			if (!entry->of_match)
    				break;
    		}
    		ret = driver_check_compatible(entry->of_match, &id,
    					      compat);
    		if (!ret)
    			break;
    	}
    
    只要有匹配,则调用 device_bind_with_driver_datadevice_bind_common 完成 udevice 的建立、driver 的绑定、UCLASS 的建立、uclass_driver 的绑定(调用 device_bind_common 时传参不同而已)。
    在这里插入图片描述
    注意,所有节点都是以 gd->dm_root 为父节点!!!这样所有节点都会通过 parent 串起来。此外,device_bind_common 的入参 plat 是 NULL,也就是来自设备树的设备其 plat 数据都是后续重设备树提取的
    在这里插入图片描述

dm_scan_other

  dm_scan_other 定义于 drivers/core/root.c 中,用于搜索绑定其他特殊的设备。该函数是个 __WEAK 函数,没有实质内容,如果需要,必须自行实现。 例如,在 ./lib/efi/efi_app.c./boot/bootstd-uclass.c 中就有该接口的实现,用于添加额外的设备。在这里插入图片描述

dm_probe_devices

  dm_probe_devices 定义于 drivers/core/root.c 中,实现遍历 gd->dm_root 下的所有设备然后激活设备。前面说了,udevice 是使用其中的 child_headsibling_node 这两个成员串联起来的,所以,这个接口很简单,通过递归 child_head 就可以变量所有设备了。

  1. 调用 device_probe 探测激活当前设备,这个流程和上面说的 ROOT 设备是一样的。
  2. 调用 list_for_each_entry(child, &dev->child_head, sibling_node) 递归后续子设备,如下是展开后的基本形式
    在这里插入图片描述

参考

  1. https://blog.csdn.net/ZHONGCAI0901/article/details/117781158
  2. https://zhuanlan.zhihu.com/p/460754843
  3. https://www.cnblogs.com/YYFaGe/p/16672483.html
  4. https://blog.csdn.net/weixin_41028621/article/details/90643550
  5. https://blog.csdn.net/ooonebook/article/details/53234020
  6. https://u-boot.readthedocs.io/en/latest/develop/driver-model/design.html

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

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

相关文章

和月薪3W的聊过后,才知道自己一直在打杂...

前几天和一个朋友聊面试&#xff0c;他说上个月同时拿到了腾讯和阿里的offer&#xff0c;最后选择了阿里。 我了解了下他的面试过程&#xff0c;就一点&#xff0c;不管是阿里还是腾讯的面试&#xff0c;这个级别的程序员&#xff0c;都会考察项目管理能力&#xff0c;并且权重…

[牛客网] HJ35 蛇形矩阵(写了好久才写出来)

链接https://www.nowcoder.com/practice/649b210ef44446e3b1cd1be6fa4cab5e?tpId37&tqId21258&rp1&ru/exam/oj/ta&qru/exam/oj/ta&sourceUrl%2Fexam%2Foj%2Fta%3Fdifficulty%3D2%26page%3D1%26pageSize%3D50%26search%3D%26tpId%3D37%26type%3D37&dif…

区块链行业遭供应链攻击,上万加密钱包被“抄底”损失上亿美元

当地时间8月2日晚间&#xff0c; 区块链行业遭遇了一次行业重创 。据科技媒体TechCrunch报道&#xff0c; 若干名攻击者“抄底”了上万个加密钱包&#xff0c;钱包内有价值上亿美元的代币。 据了解遭受攻击的加密钱包包括Phantom、Slope和TrustWallet等。涉及到的币种除了SOL、…

Vue2的tsx开发入门完全指南

本篇文章尽量不遗漏重要环节&#xff0c;本着真正分享的心态&#xff0c;不做标题党 下面进入正题&#xff1a; 由于现在vue的官方脚手架已经非常完善我们就不单独配置webpack了&#xff0c;节省大量的时间成本。 首先使用vue/cli创建一个vue模版项目&#xff08;记得是vue/…

Dockerfile详解

一、能干嘛&#xff1f; 我们总会遇到下面这种情况&#xff1a;使用docker pull 拉取下来的镜像发现其提供的功能并不完善&#xff0c;比如拉下来个centos的镜像&#xff0c;运行该镜像生成容器发现连vim&#xff0c;ifconfig命令都没有&#xff01;想要在该镜像的基础上扩充其…

[SQL]增删查改语法概览

数据定义 基本概念 基本表 本身独立存在的表SQL中一个关系就对应一个基本表一个(或多个)基本表对应一个存储文件一个表可以带若干索引 存储文件 物理结构对用户透明索引存放在存储文件中 视图 从一个或几个基本表导出的表数据库中至存放数据的定义而不存放视图对应的数据视…

Moonlight iPad全屏无边框串流方法

环境&#xff1a;iPad MoonLight 串流PC 问题&#xff1a;iPad无法全屏&#xff0c;有边框 解决办法&#xff1a;将电脑分辨率和MoonLight自定义分辨率调整为iPad原始分辨率 背景&#xff1a;在使用iPad进行MoonLight串流PC游戏时&#xff0c;发现客户端不论如何设定iPad都有边…

SQL函数

SQL函数 DATE_SUB()函数 1.1函数语法定义 1.2函数实际应用&#xff1a; 语法: 获取当前日期&#xff1a;select curdate()获取当前日期前一天&#xff1a;select date_sub(curdate(),interval 1 day)获取当前日期后一天&#xff1a;select date_sub(curdate(),interval -1 …

mysql(一) 使用注意事项及优化

初学mysql的时候、写了一份 "什么是CRUD&#xff1f; CRUD的操作" 的文章&#xff08;18年的&#xff09; 我开心看到有朋友经常在下面讨论一些问题、 但是以现在&#xff08;今天 23年&#xff09;回头看觉得 那些只是入门需要知道和掌握的、也刚好最近不是很忙 所…

S3C2440开发环境搭建

拿出了之前的S3C2440开发板&#xff0c;然后把移植uboot、移植内核、制作根文件系统、设备树编写驱动等几项再做一遍&#xff0c;这篇文章先记录下环境搭建过程&#xff0c;以及先把现成的uboot、内核、根文件系统下载进去&#xff0c;看看开发板还能不能用&#xff0c;先熟悉一…

【C++】踏入C++的大门(万字总结)

文章目录&#x1f3aa; 踏入C的大门&#x1f680;1.什么是C&#x1f680;2.C发展史&#x1f680;3.C关键字&#x1f680;4.命名空间⭐4.1 命名空间定义⭐4.2 命名空间使用⭐4.3 C输入和输出&#x1f680;5.缺省参数⭐5.1 缺省参数概念⭐5.2 缺省参数分类&#x1f680;6.函数重载…

Unreal Engine10:Character的实现

写在前面 这里主要是介绍一下Character的实现&#xff0c;顺带也介绍一下UE4资源的获取&#xff1b; 一、UE4资源获取 1. 地图的获取 1.1 下载资源 在Epic Games Launcher的虚幻商城中搜索内容&#xff0c;带有环境标签的就主要是地图资源&#xff1b;有一些是免费的资源和…

C#--耗时操作实现UI界面实时更新不阻塞(耗时操作解决窗体卡顿)

前言C#实现窗体加载进度条或者百分比实时显示耗时操作的进度&#xff0c;方法有很多。但是经过我的学习、查找与实际应用&#xff0c;发现Task配合MethodInvoker最为高效便捷。下面我就来结合代码讲一下要注意的问题。基础知识C#在winform上进行耗时操作往往会放置progressbar&…

JavaWeb 实战 01 - 计算机是如何工作的

计算机是如何工作的1. 计算机发展史2. 计算机的基本组成2.1 冯诺依曼体系结构2.2 CPU的内部结构2.3 指令2.3.1 指令表2.3.1.1 寄存器2.3.2 CPU的工作流程2.4 小结3. 操作系统3.1 核心功能3.2 操作系统的软硬件结构3.3 什么是进程 / 任务3.4 进程管理3.4.1 管理3.4.2 PCB : 进程…

Carl2——二叉树

一.定义struct TreeNode {int val;TreeNode *left;TreeNode *right;TreeNode(int x) : val(x), left(NULL), right(NULL) {} };二.遍历深度优先1.1 迭代法【1】前序遍历&#xff08;144&#xff09;class Solution { public:vector<int> preorderTraversal(TreeNode* roo…

小文智能结合ChatGPT的产业未来

最近几个月&#xff0c;由人工智能实验室OpenAI发布的对话式大型语言模型ChatGPT在国内外各大平台掀起了一阵AI狂潮。短短几天时间&#xff0c;其用户量就突破了百万大关&#xff0c;注册用户之多一度导致服务器爆满。 继AI画图之后&#xff0c;ChatGPT成为了新的顶流&#xf…

支付宝二面:使用 try-catch 捕获异常会影响性能吗?

一. JVM异常处理逻辑 Java 程序中显式抛出异常由athrow指令支持&#xff0c;除了通过 throw 主动抛出异常外&#xff0c;JVM规范中还规定了许多运行时异常会在检测到异常状况时自动抛出(效果等同athrow), 例如除数为0时就会自动抛出异常&#xff0c;以及大名鼎鼎的 NullPointe…

论文阅读:NeRF++: ANALYZING AND IMPROVING NEURAL RADIANCE FIELDS

中文标题&#xff1a;分析并提升神经辐射场 提出问题 把NeRF生成的视角图像投影到一个球模型上&#xff08;体密度在球面上为1&#xff0c;其余为零&#xff09;&#xff0c;这个模型可以很好解释训练集&#xff08;左2&#xff09;&#xff0c;但是一旦推广到其他视角&#x…

阶段八:服务框架高级(第五章:服务异步通信-高级篇(RabbitMQ高级))

阶段八&#xff1a;服务框架高级&#xff08;第五章&#xff1a;服务异步通信-高级篇&#xff08;RabbitMQ高级&#xff09;&#xff09;Day-第五章&#xff1a;服务异步通信-高级篇&#xff08;RabbitMQ高级&#xff09;0.学习目标1.消息可靠性1.1.生产者消息确认1.1.1.修改配…

Docker离线部署

Docker离线部署 目录 1、需求说明 2、下载docker安装包 3、上传docker安装包 4、解压docker安装包 5、解压的docker文件夹全部移动至/usr/bin目录 6、将docker注册为系统服务 7、重启生效 8、设置开机自启 9、查看docker版本信息 1、需求说明 大部份公司为了服务安全…