RK3568驱动指南|第七篇-设备树-第64章 device_node转换成platform_device实验

瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。


【公众号】迅为电子

【粉丝群】824412014(加群获取驱动文档+例程)

【视频观看】嵌入式学习之Linux驱动(第七期_设备树_全新升级)_基于RK3568

【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板


第64章 device_node转换成platform_device实验

在上一章中,我们学习了dtb二进制文件展开成device_node的具体流程,而device_node这时候还并不能跟内核中的platform_driver进行对接,而为了让操作系统能够识别和管理设备,需要将设备节点转换为平台设备。

64.1 转换规格

在之前学习的平台总线模型中,device部分是用platform_device结构体来描述硬件资源的,所以内核最终会将内核认识的device_node树转换platform_ device,但是并不是所有的device_node都会被转换成platform_ device,只有满足要求的才会转换成platform_ device,转换成platform_device的节点可以在/sys/bus/platform/devices下查看,那device_node节点要满足什么要求才会被转换成platform_device呢?

根据规则1,首先遍历根节点下包含 compatible 属性的子节点,对于每个子节点,创建一个对应的 platform_device。

根据规则2,遍历包含 compatible 属性为 "simple-bus"、"simple-mfd" 或 "isa" 的节点以及它们的子节点。如果子节点包含 compatible 属性值则会创建一个对应的platform_device。

根据规则3,检查节点的 compatible 属性是否包含 "arm" 或 "primecell"。如果是,则不将该节点转换为 platform_device,而是将其识别为 AMBA 设备。

接下来将通过几个设备树示例对上述规则进行实践。

举例1:

/dts-v1/;

/ {
    model = "This is my devicetree!";
    #address-cells = <1>;
    #size-cells = <1>;

    chosen {
        bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0, 115200";
    };

    cpu1: cpu@1 {
        device_type = "cpu";
        compatible = "arm,cortex-a35", "arm,armv8";
        reg = <0x0 0x1>;
    };

    aliases {
        led1 = "/gpio@22020101";
    };

    node1 {
        #address-cells = <1>;
        #size-cells = <1>;

        gpio@22020102 {
            reg = <0x20220102 0x40>;
        };
    };

    node2 {
        node1-child {
            pinnum = <01234>;
        };
    };

    gpio@22020101 {
        compatible = "led";
        reg = <0x20220101 0x40>;
        status = "okay";
    };
};

在上面的设备树中,总共有chosen、cpu1: cpu@1、aliases、node1、node2、gpio@22020101

这六个节点,其中前五个节点都没有compatible属性,所以并不会被转换为platform_device,而最后一个gpio@22020101节点符合规则一,在根节点下,且有compatible属性,所以最后会转换为platform_device。

举例2:

/dts-v1/;

/ {
    model = "This is my devicetree!";
    #address-cells = <1>;
    #size-cells = <1>;

    chosen {
        bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0, 115200";
    };

    cpu1: cpu@1 {
        device_type = "cpu";
        compatible = "arm,cortex-a35", "arm,armv8";
        reg = <0x0 0x1>;
    };

    aliases {
        led1 = "/gpio@22020101";
    };

    node1 {
        #address-cells = <1>;
        #size-cells = <1>;
		    compatible = "simple-bus";
        gpio@22020102 {
            reg = <0x20220102 0x40>;
        };
    };

    node2 {
        node1-child {
            pinnum = <01234>;
        };
    };

    gpio@22020101 {
        compatible = "led";
        reg = <0x20220101 0x40>;
        status = "okay";
    };
};

相较于示例1的设备树,这里在node1节点中添加了 compatible 属性,但是这个compatible属性值为simple-bus,我们需要继续看他的子节点,子节点 gpio@22020102 并没有compatible属性值,所以这里的node1节点不会被转换。

举例3:

/dts-v1/;

/ {
    model = "This is my devicetree!";
    #address-cells = <1>;
    #size-cells = <1>;

    chosen {
        bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0, 115200";
    };

    cpu1: cpu@1 {
        device_type = "cpu";
        compatible = "arm,cortex-a35", "arm,armv8";
        reg = <0x0 0x1>;
    };

    aliases {
        led1 = "/gpio@22020101";
    };

    node1 {
        #address-cells = <1>;
        #size-cells = <1>;
		    compatible = "simple-bus";
        gpio@22020102 {
			compatible = "gpio";
            reg = <0x20220102 0x40>;
        };
    };

    node2 {
        node1-child {
            pinnum = <01234>;
        };
    };

    gpio@22020101 {
        compatible = "led";
        reg = <0x20220101 0x40>;
        status = "okay";
    };
};

相较于示例2的设备树,这里在node1节点的子节点 gpio@22020102 中添加了 compatible 属性,node1节点的compatible属性值为simple-bus,然后需要继续看他的子节点,子节点 gpio@22020102 的compatible属性值为gpio,所以这里的gpio@22020102节点会被转换成platform_device。

举例4:

/dts-v1/;

/ {
    model = "This is my devicetree!";
    #address-cells = <1>;
    #size-cells = <1>;

    chosen {
        bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";
    };

    cpul: cpu@1 {
        device_type = "cpu";
        compatible = "arm,cortex-a35", "arm,armv8";
        reg = <0x0 0x1>;

        amba {
            compatible = "simple-bus";
            #address-cells = <2>;
            #size-cells = <2>;
            ranges;

            dmac_peri: dma-controller@ff250000 {
                compatible = "arm,p1330", "arm,primecell";
                reg = <0x0 0xff250000 0x0 0x4000>;
                interrupts = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>,
                             <GIC_SPI 3 IRQ_TYPE_LEVEL_HIGH>;
                #dma-cells = <1>;
                arm,pl330-broken-no-flushp;
                arm,p1330-periph-burst;
                clocks = <&cru ACLK DMAC_PERI>;
                clock-names = "apb_pclk";
            };

            dmac_bus: dma-controller@ff600000 {
                compatible = "arm,p1330", "arm,primecell";
                reg = <0x0 0xff600000 0x0 0x4000>;
                interrupts = <GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>,
                             <GIC_SPI 1 IRQ_TYPE_LEVEL_HIGH>;
                #dma-cells = <1>;
                arm,pl330-broken-no-flushp;
                arm,pl330-periph-burst;
                clocks = <&cru ACLK_DMAC_BUS>;
                clock-names = "apb_pclk";
            };
        };
    };
};

amba 节点的compatible值为simple-bus,不会被转换为 platform_device,而是作为父节点用于组织其他设备,所以需要来查看他的子节点。

dmac_peri: dma-controller@ff250000 节点: 该节点的 compatible 属性包含 "arm,p1330" 和 "arm,primecell",根据规则3,该节点不会被转换为 platform_device,而是被识别为 AMBA 设备。

dmac_bus: dma-controller@ff600000 节点: 该节点的 compatible 属性包含 "arm,p1330" 和 "arm,primecell",根据规则3,该节点不会被转换为 platform_device,而是被识别为 AMBA 设备。

64.2 转换流程源码分析

首先进入到内核源码目录下的“drivers/of/platform.c”文件中,找到第555行,具体内容如下所示:

arch_initcall_sync(of_platform_default_populate_init);

arch_initcall_sync 是 Linux 内核中的一个函数,用于在内核初始化过程中执行架构相关的初始化函数。它属于内核的初始化调用机制,用于确保在系统启动过程中适时地调用特定架构的初始化函数。

在Linux内核的初始化过程中,各个子系统和架构会注册自己的初始化函数。这些初始化函数负责完成特定子系统或架构相关的初始化工作,例如初始化硬件设备、注册中断处理程序、设置内存映射等。而 arch_initcall_sync 函数则用于调用与当前架构相关的初始化函数。

当内核启动时,调用 rest_init() 函数来启动初始化过程。在初始化过程中,arch_initcall_sync 函数会被调用,以确保所有与当前架构相关的初始化函数按照正确的顺序执行。这样可以保证在启动过程中,特定架构相关的初始化工作得到正确地完成。

而of_platform_default_populate_init函数的作用是在内核初始化过程中自动解析设备树,并根据设备树中的设备节点创建对应的 platform_device 结构。它会遍历设备树中的设备节点,并为每个设备节点创建一个对应的 platform_device 结构,然后将其注册到内核中,使得设备驱动程序能够识别和操作这些设备。该函数的具体内容如下所示:

static int __init of_platform_default_populate_init(void)
{
    struct device_node *node;

    // 暂停设备链接供应商同步状态
    device_links_supplier_sync_state_pause();

    // 如果设备树尚未填充,则返回错误码
    if (!of_have_populated_dt())
        return -ENODEV;

    /*
     * 显式处理某些兼容性,因为我们不想为/reserved-memory中的每个具有“compatible”的节点创建platform_device。
     */
    for_each_matching_node(node, reserved_mem_matches)
        of_platform_device_create(node, NULL, NULL);

    // 查找节点 "/firmware"
    node = of_find_node_by_path("/firmware");
    if (node) {
        // 使用该节点进行设备树平台设备的填充
        of_platform_populate(node, NULL, NULL, NULL);
        of_node_put(node);
    }

    // 填充其他设备
    fw_devlink_pause();
    of_platform_default_populate(NULL, NULL, NULL);
    fw_devlink_resume();

    return 0;
}

第6行:暂停设备链接供应商的同步状态,确保设备链接的状态不会在此过程中被改变。

第9行:检查设备树是否已经被填充。如果设备树尚未填充,则返回错误码 -ENODEV。

第16行:遍历设备树中与 reserved_mem_matches 匹配的节点。这些节点是 /reserved-memory 中具有 "compatible" 属性的节点。

第17行:为 /reserved-memory 中匹配的节点创建 platform_device 结构。这些节点不会为每个节点都创建 platform_device,而是根据需要进行显式处理。

第20行:在设备树中查找路径为 "/firmware" 的节点。

第23行:使用找到的节点填充设备树中的平台设备。这些节点可能包含与固件相关的设备。

第28行:暂停固件设备链接,确保在填充其他设备时链接状态不会改变。

第29行:填充设备树中的其他设备。

第30行:恢复固件设备链接。

上诉内容中我们要着重关注的是第29行的of_platform_default_populate(NULL, NULL, NULL)函数,找到该函数的定义之后如下所示:

int of_platform_default_populate(struct device_node *root,
				 const struct of_dev_auxdata *lookup,
				 struct device *parent)
{
	return of_platform_populate(root, of_default_bus_match_table, lookup,
				    parent);
}

该函数的作用是调用 of_platform_populate 函数来填充设备树中的平台设备,并使用默认的设备匹配表 of_default_bus_match_table,设备匹配表内容如下所示:

const struct of_device_id of_default_bus_match_table[] = {
	{ .compatible = "simple-bus", },
	{ .compatible = "simple-mfd", },
	{ .compatible = "isa", },
#ifdef CONFIG_ARM_AMBA
	{ .compatible = "arm,amba-bus", },
#endif /* CONFIG_ARM_AMBA */
	{} /* Empty terminated list */
};

上述的设备匹配表就是我们在第一小节中第2条规则,,函数将自动根据设备树节点的属性匹配相应的设备驱动程序,并填充内核的平台设备列表。接下来找到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)
{
	struct device_node *child;
	int rc = 0;

	// 如果 root 不为空,则增加 root 节点的引用计数;否则,在设备树中根据路径查找 root 节点
	root = root ? of_node_get(root) : of_find_node_by_path("/");
	if (!root)
		return -EINVAL;

	pr_debug("%s()\n", __func__);
	pr_debug(" starting at: %pOF\n", root);

	// 暂停设备链接供应商同步状态
	device_links_supplier_sync_state_pause();

	// 遍历 root 节点的所有子节点
	for_each_child_of_node(root, child) {
		// 创建平台设备并添加到设备树总线
		rc = of_platform_bus_create(child, matches, lookup, parent, true);
		if (rc) {
			of_node_put(child);
			break;
		}
	}

	// 恢复设备链接供应商同步状态
	device_links_supplier_sync_state_resume();

	// 设置 root 节点的 OF_POPULATED_BUS 标志
	of_node_set_flag(root, OF_POPULATED_BUS);

	// 释放 root 节点的引用计数
	of_node_put(root);

	return rc;
}

该函数的具体执行步骤如下:

第10行:检查给定的设备树节点 node 是否为有效节点。如果节点为空,函数将立即返回。

第21行:遍历设备树节点的子节点,查找与平台设备相关的节点。这些节点通常具有 compatible 属性,用于匹配设备驱动程序。

第23行:对于每个找到的平台设备节点,创建一个 platform_device 结构,并根据设备树节点的属性设置该结构的各个字段。

第25行:将创建的 platform_device 添加到内核的平台设备列表中,以便设备驱动程序能够识别和操作这些设备。

接下来对该函数的第23行核心代码of_platform_bus_create(child, matches, lookup, parent, true)函数进行讲解,该函数的具体定义如下所示:

static int of_platform_bus_create(struct device_node *bus,
				  const struct of_device_id *matches,
				  const struct of_dev_auxdata *lookup,
				  struct device *parent, bool strict)
{
	const struct of_dev_auxdata *auxdata;
	struct device_node *child;
	struct platform_device *dev;
	const char *bus_id = NULL;
	void *platform_data = NULL;
	int rc = 0;

	/* 确保设备节点具有 compatible 属性 */
	if (strict && (!of_get_property(bus, "compatible", NULL))) {
		pr_debug("%s() - skipping %pOF, no compatible prop\n",
			 __func__, bus);
		return 0;
	}

	/* 跳过不想创建设备的节点 */
	if (unlikely(of_match_node(of_skipped_node_table, bus))) {
		pr_debug("%s() - skipping %pOF node\n", __func__, bus);
		return 0;
	}


	if (of_node_check_flag(bus, OF_POPULATED_BUS)) {
		pr_debug("%s() - skipping %pOF, already populated\n",
			__func__, bus);
		return 0;
	}

	auxdata = of_dev_lookup(lookup, bus);
	if (auxdata) {
		bus_id = auxdata->name;
		platform_data = auxdata->platform_data;
	}

	if (of_device_is_compatible(bus, "arm,primecell")) {
		/*
		 * 在此处不返回错误以保持与旧设备树文件的兼容性。
		 */
		of_amba_device_create(bus, bus_id, platform_data, parent);
		return 0;
	}

	dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
	if (!dev || !of_match_node(matches, bus))
		return 0;

	for_each_child_of_node(bus, child) {
		pr_debug("   create child: %pOF\n", child);
		rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
		if (rc) {
			of_node_put(child);
			break;
		}
	}
	of_node_set_flag(bus, OF_POPULATED_BUS);
	return rc;
}

第14行:如果 strict 为真且设备节点 bus 没有兼容性属性,则输出调试信息并返回 0。这个条件判断确保设备节点具有 compatible 属性,因为 compatible 属性用于匹配设备驱动程序,对应我们在上一小节的第1条规则。

第21行:如果设备节点 bus 在被跳过的节点表中,则输出调试信息并返回 0。这个条件判断用于跳过不想创建设备的节点。

第27行:如果设备节点 bus 的 OF_POPULATED_BUS 标志已经设置,则输出调试信息并返回 0。这个条件判断用于避免重复创建已经填充的设备节点。

第34行:使用 lookup 辅助数据结构查找设备节点 bus 的特定配置信息,并将其赋值给变量 bus_id 和 platform_data。这个步骤用于获取设备节点的特定配置信息,以便在创建平台设备时使用,由于这里传入的参数为NULL,所以下面的条件判断并不会被执行。

第39行:如果设备节点 bus 兼容于 "arm,primecell",则调用 of_amba_device_create 函数创建 AMBA 设备,并返回 0,对应我们在上一小节学习的第3条规则。

第47行:调用 of_platform_device_create_pdata函数创建平台设备,并将其赋值给变量 dev。然后,检查设备节点 bus是否与给定的匹配表 `matches` 匹配。如果平台设备创建失败或者设备节点不匹配,那么返回 0。

第51行-第58行:遍历设备节点 bus 的每个子节点 child,并递归调用 of_platform_bus_create 函数来创建子节点的平台设备。

接下来对该函数的第47行 of_platform_device_create_pdata函数进行讲解,该函数的具体定义如下所示:

static struct platform_device *of_platform_device_create_pdata(
					struct device_node *np,
					const char *bus_id,
					void *platform_data,
					struct device *parent)
{
	struct platform_device *dev;

	/* 检查设备节点是否可用或已填充 */
	if (!of_device_is_available(np) ||
	    of_node_test_and_set_flag(np, OF_POPULATED))
		return NULL;

	/* 分配平台设备结构体 */
	dev = of_device_alloc(np, bus_id, parent);
	if (!dev)
		goto err_clear_flag;

	/* 设置平台设备的一些属性 */
	dev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
	if (!dev->dev.dma_mask)
		dev->dev.dma_mask = &dev->dev.coherent_dma_mask;
	dev->dev.bus = &platform_bus_type;
	dev->dev.platform_data = platform_data;
	of_msi_configure(&dev->dev, dev->dev.of_node);
	of_reserved_mem_device_init_by_idx(&dev->dev, dev->dev.of_node, 0);

	/* 将平台设备添加到设备模型中 */
	if (of_device_add(dev) != 0) {
		platform_device_put(dev);
		goto err_clear_flag;
	}

	return dev;

err_clear_flag:
	/* 清除设备节点的已填充标志 */
	of_node_clear_flag(np, OF_POPULATED);
	return NULL;

第10行:函数会检查设备节点的可用性,即检查设备树对应节点的status属性。如果设备节点不可用或已经被填充,则直接返回 NULL。

第15行:函数调用 of_device_alloc 分配一个平台设备结构体,并将设备节点指针、设备标识符和父设备指针传递给它。如果分配失败,则跳转到 err_clear_flag 标签处进行错误处理。

第19行,函数设置平台设备的一些属性。它将 coherent_dma_mask 属性设置为 32 位的 DMA 位掩码,并检查 dma_mask 属性是否为 NULL。如果 dma_mask 为 NULL,则将其指向 coherent_dma_mask。然后,函数设置平台设备的总线类型为 platform_bus_type,并将平台数据指针存储在 platform_data 属性中。接着,函数调用 of_msi_configure 和 of_reserved_mem_device_init_by_idx 来配置设备的 MSI 和保留内存信息。

第29行:函数调用 of_device_add 将平台设备添加到设备模型中。如果添加失败,则释放已分配的平台设备,并跳转到 err_clear_flag 标签处进行错误处理。

至此,关于device_node转换成platform_device的具体流程就分析完成了,函数调用流程图如下(图 64-1)所示:


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Excel 常用技巧

1: 拼接 公式: C1&B1&A1 如 D CBA 将公式输入目标列之后回车即可得到结果 , 如果有多行需要处理 , 光标选中目标单元格右下角变为 按着左键下拉即可 最后选择转换功能转换为文本即可 2: 时间戳转时间格式 公式: TEXT((B2/10008*3600)/8640070*36519,"yyyy/mm…

VC6.0 高亮扩展

输入关键字 "asist vc6.0" 点击网页&#xff1a; https://wws.lanzouj.com/isNmZe9ap2f 几秒后下载成功 在VS2021 安装以下这个扩展 打开vc6.0 代码有高亮了

RustRover里使用AI通义灵码来写代码

AI通义灵码我选择RustRover里的 plugin进行下载使用 然后我们就提问好了&#xff1a;让他用c语言写一个冒泡排序程序 #include <stdio.h>void bubble_sort(int arr[], int size) {int i, j, temp;for (i 0; i < size - 1; i) {for (j 0; j < size - i - 1; j) {i…

Edge浏览器新建标签页如何更改为指定网址

Edge浏览器新建标签页如何更改为指定网址&#xff1f; 启动时新建标签页 不是说启动时&#xff0c;而是加号新建标签页时候 启动时 新建标签页 New Tab Changer 可以了 如果没有需要应用商店下载 参考文章

Clickhouse 学习笔记(6)—— ClickHouse 分片集群

前置知识&#xff1a; Clickhouse学习笔记&#xff08;5&#xff09;—— ClickHouse 副本-CSDN博客 与副本对比&#xff1a; 副本虽然能够提高数据的可用性&#xff0c;降低丢失风险&#xff0c;但是每台服务器实际上必须容纳全量数据&#xff0c;对数据的横向扩容没有解决 …

Windows下Python及Anaconda的安装与设置之保姆指南

学习Python编程需要安装基本的开发环境。 &#xff08;1&#xff09;python ——编译器&#xff1b;这个是任何语言都需要的&#xff1b;必需&#xff01; &#xff08;2&#xff09;Anaconda ——主要的辅助工具&#xff0c;号称是 Python‘OS&#xff1b;必需&#xff01; …

Postman的环境变量和全局变量

近期在复习Postman的基础知识&#xff0c;在小破站上跟着百里老师系统复习了一遍&#xff0c;也做了一些笔记&#xff0c;希望可以给大家一点点启发。 多种环境&#xff1a;开发环境、测试环境、预发布环境、生产环境&#xff0c;可以用环境变量来解决。 今天的分享就到这里&a…

nodejs nvm 环境安装踩坑记录--google镜像chatgpt

nvm-win10 nvm : Node Version Manager : 解决版本匹配问题 nvm-windows 安装nvm-windows 安装完nvm-setup.exe后&#xff0c;以管理员权限重新开一个powershell窗口执行以下命令&#xff1a;&#xff08;否则会报错命令找不到&#xff0c;因为刚刚的nvm-setup.exe更新了系统PA…

为什么继电器上会有多组电压/电流标识

问题 玩过继电器的朋友一定会注意到这么一个细节&#xff0c;大部分的继电器上&#xff0c;都会标有多组电压电流参数&#xff0c;就比如下面的继电器&#xff0c;一共有三组电气参数&#xff1a; 10A 250V AC &#xff08;250V交流情况下&#xff0c;最大电流10A&#xff09;…

内存管理

目录 C/C内存分布 引入 分析 说明 C语言内存管理方式&#xff1a;malloc calloc realloc free malloc realloc calloc 面试题 C内存管理方式 new/delete操作符 用法 new和delete操作自定义类型 operator new和operator delete函数 operator new ​编辑 operator…

HarmonyOS应用开发者高级认证(88分答案)

看好选择题&#xff0c;每个2分多答对2个刚好88分&#xff0c;祝你顺利。 其它帮扶选择题。 一、判断 只要使用端云一体化的云端资源就需要支付费用&#xff08;错&#xff09;所有使用Component修饰的自定义组件都支持onPageShow&#xff0c;onBackPress和onPageHide生命周期…

Postman for Mac(HTTP请求发送调试工具)v10.18.10官方版

Postman for mac是一个提供在MAC设备上功能强大的开发&#xff0c;监控和测试API的绝佳工具。非常适合开发人员去使用。此版本通过Interceptor添加了对请求捕获的支持&#xff0c;修正了使用上下文菜单操作未复制响应正文的问题和预请求脚本的垂直滚动条与自动完成下拉列表重叠…

【算法与数据结构】40、LeetCode组合总和 II

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;【算法与数据结构】39、LeetCode组合总和的基础之上&#xff0c;这道题变成了candidates中有重复元素&…

mysql索引下推

文章目录 什么是索引下推索引下推优化的原理索引下推的具体实践没有使用ICP使用ICP 总结索引下推使用条件相关系统参数 什么是索引下推 索引下推(Index Condition Pushdown&#xff0c;简称ICP)&#xff0c;是MySQL5.6版本的新特性&#xff0c;它能减少回表查询次数&#xff0…

U-Mail邮箱系统,政务邮箱国产化改造优质之选

近年来&#xff0c;我国电子政务进入了全面铺开快速发展的阶段&#xff0c;政府机构的信息化管理能力也大幅提升。但是&#xff0c;随着国际形势的新变化&#xff0c;国家主管部门陆续出台相关政策&#xff0c;全面指导并要求政府机构落实国产化信息技术建设。因此&#xff0c;…

C++入门篇3(类和对象【重点】)

文章目录 C入门篇3&#xff08;类和对象【重点】&#xff09;1、面向过程和面向对象2、类的引入3、类的定义4、类的访问限定符及封装4.1、访问限定符4.2、封装 5、类的作用域6、类的实例化&#xff08;对象&#xff09;7、类对象模型7.1、类对象的存储方式7.2、结构体&#xff…

模电学习路径--google镜像chatgpt

交流通路实质 列出电路方程1&#xff0c;方程1对时刻t做微分 所得方程1‘ 即为 交流通路 方程1对时刻t做微分&#xff1a;两个不同时刻的方程1相减&#xff0c;并 令两时刻差为 无穷小 微分 改成 差 模电学习路径&#xff1a; 理论 《电路原理》清华大学 于歆杰 朱桂萍 陆文…

PowerPoint to HTML5 SDK Crack

Convert PowerPoint to HTML5 Retaining Animations, Transitions, Hyperlinks, Smartart, Triggers and other multimedia effects World’s first and industry best technology for building web/mobile based interactive presentations directly from PowerPoint – that …

electron 内部api capturePage实现webview截图

官方文档 .capturePage([rect]) rect Rectangle (可选) - 要捕获的页面区域。 返回 Promise - 完成后返回一个NativeImage 在 rect内捕获页面的快照。 省略 rect 将捕获整个可见页面。 async function cap(){ let image await webviewRef.value.capturePage() console.log(im…

回调地狱 与 Promise(JavaScript)

目录捏 前言一、异步编程二、回调函数三、回调地狱四、Promise1. Promise 简介2. Promise 语法3. Promise 链式 五、总结 前言 想要学习Promise&#xff0c;我们首先要了解异步编程、回调函数、回调地狱三方面知识&#xff1a; 一、异步编程 异步编程技术使你的程序可以在执行一…