linux驱动开发-设备树
设备树的历史背景
背景:
在早期的嵌入式系统中,硬件配置信息通常硬编码在内核源码中,这导致了内核代码的冗长和难以维护。
为了解决这个问题,设备树(Device Tree)被引入,使得硬件描述与内核代码分离,从而提高了内核的可移植性和可维护性。
引入时间:
设备树最初在PowerPC架构中引入,后来被ARM架构广泛采用,并逐渐成为Linux内核的标准配置方式。
设备树的基本概念
节点(Node):
设备树中的每个节点代表一个硬件设备或一个硬件组件。
节点可以包含子节点,形成树状结构。属性(Property):
节点中的键值对,用于描述硬件设备的特性或配置。
属性可以是字符串、整数、布尔值、数组等。路径(Path):
设备树中的节点路径表示节点在树中的位置。
例如,/cpus/cpu@0 表示根节点下的 cpus 节点下的 cpu@0 节点。
设备树的常用节点和属性
常用节点:
/:根节点,表示整个系统。/cpus:包含所有CPU节点的父节点。/memory:描述系统内存配置。/chosen:用于传递启动参数,如 bootargs。/soc:描述片上系统(SoC)的硬件配置。/aliases:定义设备树中的别名,方便引用。
常用属性:
compatible:描述设备的兼容性,通常用于匹配驱动程序。reg:描述设备的寄存器地址和大小。interrupts:描述设备的中断信息。clock-frequency:描述时钟频率。status:描述设备的状态,如 "okay" 或 "disabled"。
设备树的编译与使用
编译设备树:使用 dtc(Device Tree Compiler)工具将DTS文件编译为DTB文件。
dtc -I dts -O dtb -o my_device_tree.dtb my_device_tree.dts加载设备树:在启动Linux内核时,可以通过引导加载程序(如U-Boot)将DTB文件传递给内核。
bootm <kernel_image> - <dtb_file>内核解析设备树:
内核在启动时会解析DTB文件,并根据设备树中的描述配置硬件设备。
内核驱动程序可以通过设备树节点和属性来识别和配置硬件设备。
设备树与驱动程序
驱动程序与设备树的交互:
驱动程序可以通过设备树节点和属性来获取硬件信息,并进行相应的初始化和配置。
例如,驱动程序可以通过 of_get_property() 函数获取设备树中的属性值。设备树与设备模型的关系:
Linux内核使用设备模型(Device Model)来管理硬件设备。
设备树节点会被转换为设备模型中的设备节点(Device Node),驱动程序通过这些设备节点与硬件交互。
如何书写设备树
设备树的基本语法:
节点定义:使用大括号 {} 定义节点。
属性定义:使用键值对的形式定义属性,键和值之间用等号 = 连接。
注释:使用 // 或 /* */ 进行注释。
/dts-v1/; // 声明设备树文件的版本为v1/ {// 根节点,表示整个系统model = "My Custom Board"; // 描述系统的型号compatible = "my,custom-board"; // 描述系统的兼容性,用于匹配驱动程序cpus {// 定义CPU节点#address-cells = <1>; // 定义地址单元的数量,用于描述寄存器地址#size-cells = <0>; // 定义大小单元的数量,用于描述寄存器大小cpu@0 {// 定义第一个CPU节点compatible = "arm,cortex-a9"; // 描述CPU的兼容性reg = <0>; // 描述CPU的寄存器地址};cpu@1 {// 定义第二个CPU节点compatible = "arm,cortex-a9"; // 描述CPU的兼容性reg = <1>; // 描述CPU的寄存器地址};};memory@80000000 {// 定义内存节点device_type = "memory"; // 描述设备的类型为内存reg = <0x80000000 0x10000000>; // 描述内存的起始地址和大小(256MB)};chosen {// 定义chosen节点,用于传递启动参数bootargs = "console=ttyS0,115200"; // 设置启动参数,配置串口终端};soc {// 定义SoC节点,表示片上系统compatible = "simple-bus"; // 描述SoC的兼容性#address-cells = <1>; // 定义地址单元的数量#size-cells = <1>; // 定义大小单元的数量serial@101f1000 {// 定义串口节点compatible = "ns16550a"; // 描述串口的兼容性reg = <0x101f1000 0x100>; // 描述串口的寄存器地址和大小interrupts = <1 1>; // 描述串口的中断信息clock-frequency = <1843200>; // 描述串口的时钟频率};ethernet@10108000 {// 定义以太网节点compatible = "smc91x"; // 描述以太网的兼容性reg = <0x10108000 0x1000>; // 描述以太网的寄存器地址和大小interrupts = <2 1>; // 描述以太网的中断信息};};aliases {// 定义别名节点,方便引用serial0 = "/soc/serial@101f1000"; // 定义串口0的别名ethernet0 = "/soc/ethernet@10108000"; // 定义以太网0的别名};
};
示例
my_device_tree.dts
/dts-v1/; // 声明设备树文件的版本为v1/ {// 根节点,表示整个系统model = "My Custom Board"; // 描述系统的型号compatible = "my,custom-board"; // 描述系统的兼容性,用于匹配驱动程序cpus {// 定义CPU节点#address-cells = <1>; // 定义地址单元的数量,用于描述寄存器地址#size-cells = <0>; // 定义大小单元的数量,用于描述寄存器大小cpu@0 {// 定义第一个CPU节点compatible = "arm,cortex-a9"; // 描述CPU的兼容性reg = <0>; // 描述CPU的寄存器地址};};memory@80000000 {// 定义内存节点device_type = "memory"; // 描述设备的类型为内存reg = <0x80000000 0x10000000>; // 描述内存的起始地址和大小(256MB)};chosen {// 定义chosen节点,用于传递启动参数bootargs = "console=ttyS0,115200"; // 设置启动参数,配置串口终端};my_custom_device {// 定义自定义设备节点compatible = "my,custom-device"; // 描述自定义设备的兼容性status = "okay"; // 描述设备的状态,表示设备可用my_property = "Hello, Device Tree!"; // 自定义字符串属性my_int_property = <42>; // 自定义整数属性my_array_property = <0x11 0x22 0x33>; // 自定义数组属性};
};
my_custom_device_module.c
#include <linux/module.h> // 包含内核模块相关的头文件
#include <linux/of.h> // 包含设备树相关的头文件
#include <linux/of_device.h> // 包含设备树设备相关的头文件MODULE_LICENSE("GPL"); // 声明模块的许可证为GPL
MODULE_AUTHOR("Your Name"); // 声明模块的作者
MODULE_DESCRIPTION("A simple module to read custom device tree properties"); // 描述模块的功能// 设备探测函数,当设备匹配时调用
static int my_custom_device_probe(struct platform_device *pdev)
{struct device_node *node = pdev->dev.of_node; // 获取设备树节点const char *my_property; // 定义字符串属性变量u32 my_int_property; // 定义整数属性变量u32 my_array_property[3]; // 定义数组属性变量int ret; // 定义返回值变量// 获取字符串属性ret = of_property_read_string(node, "my_property", &my_property);if (ret) {pr_err("Failed to read my_property\n"); // 打印错误信息return ret; // 返回错误码}pr_info("my_property: %s\n", my_property); // 打印字符串属性值// 获取整数属性ret = of_property_read_u32(node, "my_int_property", &my_int_property);if (ret) {pr_err("Failed to read my_int_property\n"); // 打印错误信息return ret; // 返回错误码}pr_info("my_int_property: %u\n", my_int_property); // 打印整数属性值// 获取数组属性ret = of_property_read_u32_array(node, "my_array_property", my_array_property, 3);if (ret) {pr_err("Failed to read my_array_property\n"); // 打印错误信息return ret; // 返回错误码}pr_info("my_array_property: 0x%x 0x%x 0x%x\n", my_array_property[0], my_array_property[1], my_array_property[2]); // 打印数组属性值return 0; // 返回成功
}// 设备移除函数,当设备移除时调用
static int my_custom_device_remove(struct platform_device *pdev)
{return 0; // 返回成功
}// 设备树匹配表,用于匹配设备树节点
static const struct of_device_id my_custom_device_of_match[] = {{ .compatible = "my,custom-device", }, // 匹配自定义设备节点{ /* sentinel */ } // 结束标记
};
MODULE_DEVICE_TABLE(of, my_custom_device_of_match); // 声明设备树匹配表// 平台驱动结构体
static struct platform_driver my_custom_device_driver = {.probe = my_custom_device_probe, // 设置探测函数.remove = my_custom_device_remove, // 设置移除函数.driver = {.name = "my_custom_device", // 设置驱动名称.of_match_table = my_custom_device_of_match, // 设置设备树匹配表},
};// 注册平台驱动
module_platform_driver(my_custom_device_driver);
device_node 结构体
device_node 结构体是设备树节点的核心数据结构,表示设备树中的一个节点。
struct device_node {const char *name; // 节点名称const char *type; // 节点类型phandle phandle; // 节点的句柄const char *full_name; // 节点的完整路径struct fwnode_handle fwnode;// 固件节点句柄struct property *properties; // 节点的属性链表struct property *deadprops; // 已删除的属性链表struct device_node *parent; // 父节点struct device_node *child; // 子节点struct device_node *sibling; // 兄弟节点struct kobject kobj; // 内核对象void *data; // 私有数据
};
设备树操作API
2.1 of_find_node_by_path()
根据路径查找设备树节点。struct device_node *of_find_node_by_path(const char *path);
path:设备树节点的路径。返回值:找到的设备树节点指针,如果未找到则返回 NULL。示例:struct device_node *node = of_find_node_by_path("/my_custom_device");
if (node) {pr_info("Found node: %s\n", node->full_name);
} else {pr_info("Node not found\n");
}2.2 of_find_node_by_name()
根据节点名称查找设备树节点。struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
from:从哪个节点开始查找(可选,传 NULL 表示从根节点开始)。name:节点名称。返回值:找到的设备树节点指针,如果未找到则返回 NULL。示例:struct device_node *node = of_find_node_by_name(NULL, "my_custom_device");
if (node) {pr_info("Found node: %s\n", node->full_name);
} else {pr_info("Node not found\n");
}2.3 of_find_node_by_type()
根据节点类型查找设备树节点。struct device_node *of_find_node_by_type(struct device_node *from, const char *type);
from:从哪个节点开始查找(可选,传 NULL 表示从根节点开始)。type:节点类型。返回值:找到的设备树节点指针,如果未找到则返回 NULL。示例:struct device_node *node = of_find_node_by_type(NULL, "memory");
if (node) {pr_info("Found node: %s\n", node->full_name);
} else {pr_info("Node not found\n");
}2.4 of_find_compatible_node()
根据 compatible 属性查找设备树节点。struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible);
from:从哪个节点开始查找(可选,传 NULL 表示从根节点开始)。type:节点类型(可选)。compatible:compatible 属性值。返回值:找到的设备树节点指针,如果未找到则返回 NULL。示例:struct device_node *node = of_find_compatible_node(NULL, NULL, "my,custom-device");
if (node) {pr_info("Found node: %s\n", node->full_name);
} else {pr_info("Node not found\n");
}2.5 of_get_property()
获取设备树节点的属性值。const void *of_get_property(const struct device_node *np, const char *name, int *lenp);
np:设备树节点指针。name:属性名称。lenp:属性值的长度(可选)。返回值:属性值的指针,如果未找到则返回 NULL。示例:const char *prop_value;
int prop_len;prop_value = of_get_property(node, "my_property", &prop_len);
if (prop_value) {pr_info("my_property: %s\n", prop_value);
} else {pr_info("my_property not found\n");
}2.6 of_property_read_string()
读取字符串类型的属性值。int of_property_read_string(const struct device_node *np, const char *propname, const char **out_string);
np:设备树节点指针。propname:属性名称。out_string:输出字符串指针。返回值:0表示成功,负数表示失败。示例:const char *my_property;if (of_property_read_string(node, "my_property", &my_property) == 0) {pr_info("my_property: %s\n", my_property);
} else {pr_info("my_property not found\n");
}2.7 of_property_read_u32()
读取32位整数类型的属性值。int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value);
np:设备树节点指针。propname:属性名称。out_value:输出整数值。返回值:0表示成功,负数表示失败。示例:u32 my_int_property;if (of_property_read_u32(node, "my_int_property", &my_int_property) == 0) {pr_info("my_int_property: %u\n", my_int_property);
} else {pr_info("my_int_property not found\n");
}2.8 of_property_read_u32_array()
读取32位整数数组类型的属性值。int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz);
np:设备树节点指针。propname:属性名称。out_values:输出整数数组。sz:数组大小。返回值:0表示成功,负数表示失败。示例:u32 my_array_property[3];if (of_property_read_u32_array(node, "my_array_property", my_array_property, 3) == 0) {pr_info("my_array_property: 0x%x 0x%x 0x%x\n", my_array_property[0], my_array_property[1], my_array_property[2]);
} else {pr_info("my_array_property not found\n");
}2.9 of_property_count_elems_of_size()
获取属性值的元素数量。int of_property_count_elems_of_size(const struct device_node *np, const char *propname, int elem_size);
np:设备树节点指针。propname:属性名称。elem_size:元素大小(字节)。返回值:元素数量,负数表示失败。示例:int count = of_property_count_elems_of_size(node, "my_array_property", sizeof(u32));
if (count > 0) {pr_info("my_array_property has %d elements\n", count);
} else {pr_info("my_array_property not found\n");
}2.10 of_property_read_variable_u32_array()
读取可变长度的32位整数数组类型的属性值。int of_property_read_variable_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz_min, size_t sz_max);
np:设备树节点指针。propname:属性名称。out_values:输出整数数组。sz_min:最小数组大小。sz_max:最大数组大小。返回值:实际读取的元素数量,负数表示失败。示例:u32 my_array_property[3];
int count = of_property_read_variable_u32_array(node, "my_array_property", my_array_property, 1, 3);
if (count > 0) {pr_info("my_array_property: 0x%x 0x%x 0x%x\n", my_array_property[0], my_array_property[1], my_array_property[2]);
} else {pr_info("my_array_property not found\n");
}
设备树绑定API
设备树绑定API用于将设备树节点与驱动程序绑定。以下是一些常用的API函数:3.1 of_device_is_compatible()
检查设备树节点是否与指定的 compatible 属性匹配。int of_device_is_compatible(const struct device_node *device, const char *compat);
device:设备树节点指针。compat:compatible 属性值。返回值:非零表示匹配,零表示不匹配。示例:if (of_device_is_compatible(node, "my,custom-device")) {
pr_info("Node is compatible with 'my,custom-device'\n");
} else {
pr_info("Node is not compatible with 'my,custom-device'\n");
}3.2 of_device_is_available()
检查设备树节点是否可用(即 status 属性是否为 "okay")。int of_device_is_available(const struct device_node *device);
device:设备树节点指针。返回值:非零表示可用,零表示不可用。示例:if (of_device_is_available(node)) {
pr_info("Node is available\n");
} else {
pr_info("Node is not available\n");
}3.3 of_platform_populate()
递归地为设备树节点创建平台设备。int of_platform_populate(struct device_node *root, const struct of_device_id *matches, const struct of_dev_auxdata *lookup, struct device *parent);
root:设备树根节点指针。matches:设备树匹配表。lookup:设备树辅助数据(可选)。parent:父设备指针(可选)。返回值:0表示成功,负数表示失败。示例:static const struct of_device_id my_custom_device_of_match[] = {
{ .compatible = "my,custom-device", },
{ /* sentinel */ }
};int ret = of_platform_populate(NULL, my_custom_device_of_match, NULL, NULL);
if (ret) {
pr_err("Failed to populate platform devices\n");
}
在设备树中,别名(Aliases)和引用(References)
1. 别名(Aliases)别名是一种方便的方式,用于给设备树中的节点或路径指定一个易于使用的名称。在设备树中,别名通常用于简化设备的访问,使得访问特定设备的代码更直观。定义方式:别名在设备树的顶部通过aliases节点进行声明。例如:aliases {
spi0 = "spi@1";
i2c0 = "i2c@0";
serial0 = "uart@0";
};在这个例子中,您可以使用spi0来引用名为spi@1的设备节点。使用场景:别名通常用于驱动程序中,以便通过别名快速访问设备。例如,在驱动程序中,您可以使用of_alias_get_id函数依据别名获取设备的ID。2. 引用(References)引用用于在设备树中引用其他节点或属性,主要通过节点路径来实现。
引用可以帮助减少冗余,特别是在多个节点需要共享同一设备或配置时。定义方式:设备树中的节点可以通过路径引用。例如:my_device: my_device_node {
compatible = "my,device";
reg = <0x1000 0x100>;
};another_device: another_device_node {
compatible = "another,device";
depends_on = <&my_device>; // 远程引用
};在这个例子中,another_device_node中的depends_on属性用来引用了my_device_node。使用场景:引用可以用来指定设备之间的依赖关系,或者在一个设备中引用其他设备的资源。使用引用可以避免重复定义相同的设备配置,有助于提高设备树结构的可读性和可维护性。
compatible 字段
设备树中一个重要的属性,它用于指定设备的兼容性字符串,帮助操作系统识别硬件和对应的驱动程序。
什么是 compatible 属性?
compatible 属性的作用是告诉操作系统这个设备支持的功能和
它的驱动程序的匹配信息。它包含一个或多个字符串,
描述了不同的兼容设备类或设备名称。
该属性通常是一个字符串数组,包含一个或多个值,
描述设备的不同兼容性,开发者可以在设备树源文件中指定。
示例
一些设备树中的节点可能如下所示:
my_device: my_device@0 {
compatible = "vendor,my_device", "vendor,generic_device";
reg = <0x0 0x10000 0x0 0x1000>; // 寄存器地址
interrupts = <1>; // 中断号
};解释
my_device@0:节点的名称,通常包含设备的名称和地址。
compatible:这个节点指定了它的兼容性为vendor,my_device和vendor,generic_device。这表明这个设备可以被这两个驱动程序支持。
reg 和 interrupts 是与硬件设备相关的其他重要信息。
在驱动程序中的作用
在涉及 Linux 内核驱动的开发时,设备树的 compatible 属性用于驱动程序的识别和绑定。通常在驱动代码中,也对 compatible 属性进行匹配,以确定是否能够处理特定的设备。驱动代码示例
在驱动程序中,可能会看到如下代码片段:static const struct of_device_id my_device_ids[] = {{ .compatible = "vendor,my_device" },{ .compatible = "vendor,generic_device" },{},
};MODULE_DEVICE_TABLE(of, my_device_ids);of_device_id:用于定义与设备树中的 compatible 字符串匹配的结构体数组。
MODULE_DEVICE_TABLE:用于将模块与设备树的兼容性代码进行连结,这样,当设备在启动时,内核可以根据设备树中的信息找到相应的驱动程序进行加载。
ompatible 属性相关的API
1. of_device_get_match_data功能: 根据设备树节点中的 compatible 属性,返回与之匹配的驱动数据。const void *of_device_get_match_data(struct device *dev);参数:dev:指向设备结构的指针,通常在驱动程序中获取。返回值: 返回指向匹配数据的指针,如果没有匹配,返回 NULL。2. of_matchDevice功能: 检查设备树节点与给定的设备 ID 列表的匹配。const struct of_device_id *of_match_device(const struct of_device_id *matches, struct device *dev);参数:matches:设备ID匹配表。dev:指向要匹配的设备结构的指针。返回值: 返回匹配的 of_device_id 结构指针,如果没有匹配,返回 NULL。3. of_device_id功能: 定义设备与驱动匹配的结构。通常用于在驱动程序中声明支持哪些设备。struct of_device_id {const char *compatible; // 设备的兼容性字符串const void *data; // 可选的私有数据,用于驱动程序};使用示例: 将其用于驱动程序中,定义支持的设备。static const struct of_device_id my_device_ids[] = {{ .compatible = "vendor,my_device" },{ .compatible = "vendor,generic_device" },{},};4. of_register_driver功能: 注册一个与设备树相连接的驱动程序。int of_register_driver(struct of_driver *drv);参数:drv:指向 of_driver 结构体的指针,包含驱动的描述信息。5. of_driver功能: 描述一个设备树驱动的结构。struct of_driver {struct module *owner; // 驱动模块的所有者struct of_device_id *ids; // 支持的设备ID列表int (*probe)(struct device *dev); // 驱动的探测函数void (*remove)(struct device *dev); // 驱动的移除函数};6. of_get_child_by_name功能: 根据名称获取设备树节点的子节点。struct device_node *of_get_child_by_name(const struct device_node *np, const char *name);参数:np:父节点。name:要匹配的子节点的名称。返回值: 返回匹配的子节点指针,如果未找到,则返回 NULL。7. of_get_parent功能: 获取设备树节点的父节点。struct device_node *of_get_parent(const struct device_node *np);参数:np:设备树节点。返回值: 返回父节点指针,如果没有父节点则返回 NULL。
相关文章:

linux驱动开发-设备树
设备树的历史背景 背景: 在早期的嵌入式系统中,硬件配置信息通常硬编码在内核源码中,这导致了内核代码的冗长和难以维护。 为了解决这个问题,设备树(Device Tree)被引入,使得硬件描述与内核代…...

数据结构——二叉树堆的专题
1.堆的概念及结构 如果有一个关键码的集合K {K0 ,K1 ,K2 ,K3…,K(N-1) },把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足:Ki < K2*i1且 Ki<K2*i2 ) i 0&#…...

【C语言零基础入门篇 - 7】:拆解函数的奥秘:定义、声明、变量,传递须知,嵌套玩转,递归惊艳
文章目录 函数函数的定义与声明局部变量和全局变量、静态变量静态变量和动态变量函数的值传递函数参数的地址传值 函数的嵌套使用函数的递归调用 函数 函数的定义与声明 函数的概念:函数是C语言项目的基本组成单位。实现一个功能可以封装一个函数来实现。定义函数的…...

ClickHouse在AI领域的结合应用
文章目录 引言1.1 人工智能与大数据的融合1.2 ClickHouse在大数据平台中的地位2.1 BI与AI的融合从传统BI到智能BIAI赋能BI融合的优势实际应用案例 2.2 异构数据处理的重要性数据多样性的挑战异构数据处理的需求技术实现实际应用案例 2.3 向量检索与AIOps技术向量检索的背景AIOp…...

git push出错Push cannot contain secrets
报错原因: 因为你的代码里面包含了github token明文信息,github担心你的token会泄漏,所以就不允许你推送这些内容。 解决办法: 需要先把代码里面的github token信息删除掉,并且删掉之前的历史提交,只要包…...

OpenAI 的最强模型 o1 的“护城河”失守?谷歌 DeepMind 早已揭示相同原理
发布不到一周,OpenAI 的最新模型 o1 的“护城河”似乎已经失守。 近日,有人发现谷歌 DeepMind 早在今年 8 月发表的一篇论文,揭示了与 o1 模型极其相似的工作原理。 这项研究指出,在模型推理过程中增加测试时的计算量,…...

【胡乱念叨】大模型的“我”
下面的内容很有可能事实错误,胡说八道,前后不连贯,举例随意且未经考证 甚至 有意欺骗!嘻嘻。所以是【胡乱念叨】 文章目录 【胡乱念叨】大模型的“我”参数量和“我”什么是“我”从输入输出的观点看“我”大模型的“我”乱讨论 …...

Flag_AGtivity_clear_top网页编程指南如何退出多activity程序
activity的启动模式:FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_REORDER_TO_FRONT。 1. 如果已经启动了四个Activity:A,B,C和D。在D Activity里,我们要跳到B Activity,同时希望C finish掉,可以在start…...

克隆centos网卡uuid相同如何修改
在克隆CentOS系统后,网卡的UUID相同会导致网络配置冲突,使得网络无法正常工作。要解决这个问题,你需要为每个克隆的系统生成新的UUID。 以下是解决步骤: 进入原始CentOS系统。 找到网络配置文件的位置,通常在 /etc/s…...

C语言习题~day11
1、C程序常见的错误分类不包含:( ) A.编译错误 B.链接错误 C.栈溢出 D.运行时错误 栈溢出是运行时错误的一种,因此C程序不会将栈溢出错误单独列出来,栈溢出包含在运行时错误中。 因此:选择C 2、关于VS调…...

Ansible——Playbook基本功能???
文章目录 一、Ansible Playbook介绍1、Playbook的简单组成1)“play”2)“task”3)“playbook” 2、Playbook与ad-hoc简单对比区别联系 3、YAML文件语法:---以及多个---??使用 include 指令 1. 基本结构2. 数…...

多线程学习篇一:启动多线程的三种方式
1. 继承 Thread 类 Slf4j public class MyThread extends Thread {Overridepublic void run() {log.info("MyThread run ...");}public static void main(String[] args) {MyThread myThread new MyThread();myThread.start();} } 2. 实现 Runnable 接口 Slf4j pu…...

【专题】2024跨境出海供应链洞察-更先进供应链报告合集PDF分享(附原数据表)
原文链接:https://tecdat.cn/?p37665 当前,全球化商业浪潮促使跨境电商行业飞速发展,产业带与跨境电商接轨、平台半托管模式涌现、社交电商带来红利机会以及海外仓不断扩张,这使得产业带外贸工厂、内贸工厂、传统进出口企业和品…...

git submodule
git submodule 是 Git 提供的一种功能,用于在一个 Git 仓库中嵌套另一个 Git 仓库。它可以帮助管理和跟踪外部项目或依赖项,特别是在以下场景中非常有用: 1. 管理外部依赖 当你的项目依赖于其他外部项目或库时,可以使用 git sub…...

【Power Compiler手册】13.UPF多电压设计实现(3)
创建供电端口 要创建电源和地端口,请使用`create_supply_port`命令。 供电端口的名称应该是一个简单的(非层次化的)名称,并且在其定义的层次级别上是唯一的。除非指定了`-domain`选项,否则端口是在当前作用域或层次级别创建的,当前作用域中的所有电源域都可以使用创建的…...

RTX 4090 系列即将停产,RTX 5090 系列蓄势待发
据最新消息,英伟达将于今年10月正式终结其GeForce RTX 4090及RTX 4090D两款旗舰级显卡的生产线。根据行业媒体报道,英伟达及其合作厂商将从下个月开始全面停止这两款显卡的制造。 自2022年10月问世以来,GeForce RTX 4090凭借其无与伦比的GPU…...

【MySQL】使用C语言连接数据库
看到标题,可能会疑惑,我们学习的不是C吗,为什么使用C语言去连接数据库呢??实际上,这两种语言都可以连接数据库,但是C语言提供的API没有进行封装,更有利于我们学习数据库连接。面向API编程,哈哈…...

Vue学习记录之四(watch侦听器和watchEffect高级侦听器)
watch watch 用于侦听特定的响应式数据源(如数据、计算属性等),比如ref或者是reactive时,并在其变化时执行回调函数。它适合用于处理副作用,如 API 请求或异步操作。使用 watch 适合特定数据变化的侦听,提…...

RedisTemplate操作ZSet的API
文章目录 ⛄概述⛄常见命令有⛄RedisTemplate API❄️❄️ 向集合中插入元素,并设置分数❄️❄️向集合中插入多个元素,并设置分数❄️❄️按照排名先后(从小到大)打印指定区间内的元素, -1为打印全部❄️❄️获得指定元素的分数❄️❄️返回集合内的成员个数❄️❄…...

Android 15 正式发布至 AOSP
Google官方宣布,将于近期发布了 Android 15,而在早些时候,Google已经将其源代码推送至 Android 开源项目 (AOSP)。未来几周内,Android 15 将在受支持的 Pixel 设备上正式推出,并将于今年晚些时候在三星、Honor、iQOO、…...

IEEE Electronic Library(IEL)数据库文献检索下载介绍及个人获取IEEE文献途径
一、数据库介绍 IEEE(The Institute of Electrical and Electronics Engineers,电气电子工程师学会)是目前全球最大的非营利性专业技术学会,在全球160多个国家拥有超过45万名会员。IEEE在电气电子、计算机、半导体、通讯、电力能…...

动手学习RAG:大模型重排模型 bge-reranker-v2-gemma微调
动手学习RAG: 向量模型动手学习RAG: moka-ai/m3e 模型微调deepspeed与对比学习动手学习RAG:rerank模型微调实践 bge-reranker-v2-m3动手学习RAG:迟交互模型colbert微调实践 bge-m3动手学习RAG: 大模型向量模型微调 intfloat/e5-mistral-7b-instruct动手学…...

蓝桥杯2024省C
P10898 [蓝桥杯 2024 省 C] 拼正方形 题目描述 小蓝正在玩拼图游戏,他有 7385137888721个 22的方块和 10470245 个 11 的方块,他需要从中挑出一些来拼出一个正方形,比如用 3 个 22 和 4 个 11 的方块可以拼出一个 44 的正方形,用…...

C++:内部类,匿名对象,操作符new与delete
一.内部类 1.如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,跟定义在全局相比,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。 2.内部类默认是外部类的友元类。…...

【数据结构】排序算法---计数排序
文章目录 1. 定义2. 算法步骤3. 动图演示4. 性质5. 算法分析6. 代码实现C语言PythonJavaGo 结语 1. 定义 计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组…...

mysql时间日期函数、获取当前日期和时间、日期和时间格式化、提取日期部分、日期和时间的算术操作、其他日期函数、日期和时间的比较、日期字符串转换
获取当前日期和时间 NOW():返回当前的日期和时间。CURDATE():返回当前的日期。CURTIME():返回当前的时间。 SELECT NOW(), CURDATE(), CURTIME(); 日期和时间格式化 DATE_FORMAT(date, format):根据指定的格式字符串格式化日期…...

Android开发高频面试题之——kotlin篇
Android开发高频面试题之——kotlin篇 Android开发高频面试题之——Java基础篇 Android开发高频面试题之——Kotlin基础篇 Android开发高频面试题之——Android基础篇 1. Kotlin如何实现空安全的? Kotlin 将变量划分为可空和不可空,通过查看字节码可知,声明不可空的变量会…...

8--SpringBoot原理分析、注解-详解(面试高频提问点)
目录 SpringBootApplication 1.元注解 --->元注解 Target Retention Documented Inherited 2.SpringBootConfiguration Configuration Component Indexed 3.EnableAutoConfiguration(自动配置核心注解) 4.ComponentScan Conditional Co…...

语言的枚举
不同语言的枚举 C/C枚举本质是整型,在Java中是对象,而非基本类型,可通过instanceof Object判断是否是对象类型。C#与Java不同,枚举是值类型。C语言更纯粹,枚举绝对当成整数,可以对枚举变量用整数赋值&…...

C# Redis 框架开发技术详解
引言 Redis 是一个高性能的键值存储系统,广泛用于缓存、消息队列和实时分析等场景。在 C# 中,有几个著名的库和框架可以方便地与 Redis 进行交互。以下是几个常用的 C# Redis 库: StackExchange.Redis: 这是目前最流行、最推荐的 C# Redis 客…...