RK3568驱动指南|第七篇-设备树-第68章 ranges属性实验

news2024/11/16 23:53:53

瑞芯微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主板


第68章 ranges属性实验

68.1 platform_get_resource 获取设备树资源

在上个章节中讲解了使用of操作函数来获取设备树的属性,由于设备树在系统启动的时候都会转化为platform设备,那我们能不能直接在驱动中使用在53.1小节中讲解的platform_get_resource函数直接获取platform_device资源呢?

68.1.1 驱动程序编写

带着疑惑我们这里仍旧以65章的驱动程序为原型,在probe函数中加入使用platform_get_resource函数获取reg资源的函数,添加完成的驱动程序内容如下所示:

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
// 平台设备的初始化函数
struct resource *myresources;
static int my_platform_probe(struct platform_device *pdev)
{
    printk(KERN_INFO "my_platform_probe: Probing platform device\n");

    // 获取平台设备的资源
    myresources = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (myresources == NULL) {
        // 如果获取资源失败,打印value_compatible的值
        printk("platform_get_resource is error\n");
}
printk("reg valus is %llx\n" , myresources->start); 

    return 0;
}

// 平台设备的移除函数
static int my_platform_remove(struct platform_device *pdev)
{
    printk(KERN_INFO "my_platform_remove: Removing platform device\n");

    // 清理设备特定的操作
    // ...

    return 0;
}


const struct of_device_id of_match_table_id[]  = {
	{.compatible="my devicetree"},
};

// 定义平台驱动结构体
static struct platform_driver my_platform_driver = {
    .probe = my_platform_probe,
    .remove = my_platform_remove,
    .driver = {
        .name = "my_platform_device",
        .owner = THIS_MODULE,
		.of_match_table =  of_match_table_id,
    },
};

// 模块初始化函数
static int __init my_platform_driver_init(void)
{
    int ret;

    // 注册平台驱动
    ret = platform_driver_register(&my_platform_driver);
    if (ret) {
        printk(KERN_ERR "Failed to register platform driver\n");
        return ret;
    }

    printk(KERN_INFO "my_platform_driver: Platform driver initialized\n");

    return 0;
}

// 模块退出函数
static void __exit my_platform_driver_exit(void)
{
    // 注销平台驱动
    platform_driver_unregister(&my_platform_driver);

    printk(KERN_INFO "my_platform_driver: Platform driver exited\n");
}

module_init(my_platform_driver_init);
module_exit(my_platform_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("topeet");

编译成模块之后,放到开发板上进行加载,打印信息如下(图 68-1)所示:

图 218-1

可以看到使用platform_get_resource函数获取reg资源的函数失败了,在下一个小节中将分析获取资源失败的原因。

68.1.2 获取资源失败源码分析

platform_get_resource定义在内核源码目录下的"/drivers/base/platform.c"目录下,具体内容如下所示:

struct resource *platform_get_resource(struct platform_device *dev,
				       unsigned int type, unsigned int num)
{
	u32 i;

	for (i = 0; i < dev->num_resources; i++) {
		struct resource *r = &dev->resource[i];

		if (type == resource_type(r) && num-- == 0)
			return r;
	}
	return NULL;
}

该函数返回NULL符合第一小节中的情况,返回NULL的情况有两种可能性,一种是没进入上面的for循环直接返回了NULL,另外一种是进入了for循环,但是类型匹配不正确,跳出for循环之后再返回NULL。这里的类型一定是匹配的,所以我们就来寻找为什么没有进入for循环,这里只有一种可能,也就是dev->num_resources为0。

所以现在的目标来到了寻找dev->num_resources是在哪里进行的赋值,前面已经讲解过了由设备树转换为platform的过程,而且在系统启动后,在对应目录下也有了相应的节点:

图 68-2

证明转换是没问题的,所以继续寻找中间转换过程中有关资源数量的相关函数,定位到了of_platform_device_create_pdata函数,该函数定义在内核源码目录下的“drivers/of/platform.c”文件中,具体内容如下所示:

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;
}

第15行:函数调用 of_device_alloc 分配一个平台设备结构体,并将设备节点指针、设备标识符和父设备指针传递给它,正是该函数决定的resource.num,然后找到该函数的定义,如下所示:

struct platform_device *of_device_alloc(struct device_node *np,
				  const char *bus_id,
				  struct device *parent)
{
	struct platform_device *dev;
	int rc, i, num_reg = 0, num_irq;
	struct resource *res, temp_res;

	dev = platform_device_alloc("", PLATFORM_DEVID_NONE);
	if (!dev)
		return NULL;

	/* count the io and irq resources */
	while (of_address_to_resource(np, num_reg, &temp_res) == 0)
		num_reg++;
	num_irq = of_irq_count(np);

	/* Populate the resource table */
	if (num_irq || num_reg) {
		res = kcalloc(num_irq + num_reg, sizeof(*res), GFP_KERNEL);
		if (!res) {
			platform_device_put(dev);
			return NULL;
		}

		dev->num_resources = num_reg + num_irq;
		dev->resource = res;
		for (i = 0; i < num_reg; i++, res++) {
			rc = of_address_to_resource(np, i, res);
			WARN_ON(rc);
		}
		if (of_irq_to_resource_table(np, res, num_irq) != num_irq)
			pr_debug("not all legacy IRQ resources mapped for %pOFn\n",
				 np);
	}

	dev->dev.of_node = of_node_get(np);
	dev->dev.fwnode = &np->fwnode;
	dev->dev.parent = parent ? : &platform_bus;

	if (bus_id)
		dev_set_name(&dev->dev, "%s", bus_id);
	else
		of_device_make_bus_id(&dev->dev);

	return dev;
}

在第26行出现了for循环的dev->num_resources = num_reg + num_irq;reg的number和irq的number,由于在设备树中并没有添加中断相关的属性num_irq为0,那这里的num_reg是哪里确定的呢。

我们向上找到14、15行,具体内容如下所示:

	/* count the io and irq resources */
	while (of_address_to_resource(np, num_reg, &temp_res) == 0)
		num_reg++;

然后跳转到while循环中的of_address_to_resource函数,该函数定义在内核源码目录的drivers/of/address.c文件中,具体内容如下所示:

int of_address_to_resource(struct device_node *dev, int index,
			   struct resource *r)
{
	const __be32	*addrp;
	u64		size;
	unsigned int	flags;
	const char	*name = NULL;

	addrp = of_get_address(dev, index, &size, &flags);
	if (addrp == NULL)
		return -EINVAL;

	/* Get optional "reg-names" property to add a name to a resource */
	of_property_read_string_index(dev, "reg-names",	index, &name);

	return __of_address_to_resource(dev, addrp, size, flags, name, r);
}

第9行,获取reg属性的地址、大小和类型,在设备树中reg属性已经存在了,所以这里会正确返回。

第14行,读取reg-names属性,由于设备树中没有定义这个属性,所以该函数不会有影响。

最后具有决定性作用的函数就是返回的__of_address_to_resource函数了,跳转到该函数的定义如下所示:

static int __of_address_to_resource(struct device_node *dev,
		const __be32 *addrp, u64 size, unsigned int flags,
		const char *name, struct resource *r)
{
	u64 taddr;

	if (flags & IORESOURCE_MEM)
		taddr = of_translate_address(dev, addrp);
	else if (flags & IORESOURCE_IO)
		taddr = of_translate_ioport(dev, addrp, size);
	else
		return -EINVAL;

	if (taddr == OF_BAD_ADDR)
		return -EINVAL;
	memset(r, 0, sizeof(struct resource));

	r->start = taddr;
	r->end = taddr + size - 1;
	r->flags = flags;
	r->name = name ? name : dev->full_name;

	return 0;
}

reg属性的flags为IORESOURCE_MEM,所以又会执行第9行的of_translate_address函数,跳转到该函数,该函数的定义如下所示:

u64 of_translate_address(struct device_node *dev, const __be32 *in_addr)
{
	struct device_node *host;
	u64 ret;

	ret = __of_translate_address(dev, in_addr, "ranges", &host);
	if (host) {
		of_node_put(host);
		return OF_BAD_ADDR;
	}

	return ret;
}

该函数的重点在第6行,上述函数实际上是__of_translate_address函数的封装,其中传入的第三个参数“ranges”是我们要关注的重点,继续跳转到该函数的定义,具体内容如下所示:

static u64 __of_translate_address(struct device_node *dev,
				  const __be32 *in_addr, const char *rprop,
				  struct device_node **host)
{
	struct device_node *parent = NULL;
	struct of_bus *bus, *pbus;
	__be32 addr[OF_MAX_ADDR_CELLS];
	int na, ns, pna, pns;
	u64 result = OF_BAD_ADDR;

	pr_debug("** translation for device %pOF **\n", dev);

	/* Increase refcount at current level */
	of_node_get(dev);

	*host = NULL;
	/* Get parent & match bus type */
	parent = of_get_parent(dev);
	if (parent == NULL)
		goto bail;
	bus = of_match_bus(parent);

	/* Count address cells & copy address locally */
	bus->count_cells(dev, &na, &ns);
	if (!OF_CHECK_COUNTS(na, ns)) {
		pr_debug("Bad cell count for %pOF\n", dev);
		goto bail;
	}
	memcpy(addr, in_addr, na * 4);

	pr_debug("bus is %s (na=%d, ns=%d) on %pOF\n",
	    bus->name, na, ns, parent);
	of_dump_addr("translating address:", addr, na);

	/* Translate */
	for (;;) {
		struct logic_pio_hwaddr *iorange;

		/* Switch to parent bus */
		of_node_put(dev);
		dev = parent;
		parent = of_get_parent(dev);

		/* If root, we have finished */
		if (parent == NULL) {
			pr_debug("reached root node\n");
			result = of_read_number(addr, na);
			break;
		}

		/*
		 * For indirectIO device which has no ranges property, get
		 * the address from reg directly.
		 */
		iorange = find_io_range_by_fwnode(&dev->fwnode);
		if (iorange && (iorange->flags != LOGIC_PIO_CPU_MMIO)) {
			result = of_read_number(addr + 1, na - 1);
			pr_debug("indirectIO matched(%pOF) 0x%llx\n",
				 dev, result);
			*host = of_node_get(dev);
			break;
		}

		/* Get new parent bus and counts */
		pbus = of_match_bus(parent);
		pbus->count_cells(dev, &pna, &pns);
		if (!OF_CHECK_COUNTS(pna, pns)) {
			pr_err("Bad cell count for %pOF\n", dev);
			break;
		}

		pr_debug("parent bus is %s (na=%d, ns=%d) on %pOF\n",
		    pbus->name, pna, pns, parent);

		/* Apply bus translation */
		if (of_translate_one(dev, bus, pbus, addr, na, ns, pna, rprop))
			break;

		/* Complete the move up one level */
		na = pna;
		ns = pns;
		bus = pbus;

		of_dump_addr("one level translation:", addr, na);
	}
 bail:
	of_node_put(parent);
	of_node_put(dev);

	return result;
}

第18行,获取父节点和匹配的总线类型

第24行,获取address-cell和size-cells

然后是一个for循环,在76行使用of_translate_one函数进行转换,其中rprop参数表示要转换的资源属性,该参数的值为传入的“ranges”,然后我们继续跳转到该函数,该函数的具体内容如下所示:

static int of_translate_one(struct device_node *parent, struct of_bus *bus,
			    struct of_bus *pbus, __be32 *addr,
			    int na, int ns, int pna, const char *rprop)
{
	const __be32 *ranges;
	unsigned int rlen;
	int rone;
	u64 offset = OF_BAD_ADDR;

	/*
	 * Normally, an absence of a "ranges" property means we are
	 * crossing a non-translatable boundary, and thus the addresses
	 * below the current cannot be converted to CPU physical ones.
	 * Unfortunately, while this is very clear in the spec, it's not
	 * what Apple understood, and they do have things like /uni-n or
	 * /ht nodes with no "ranges" property and a lot of perfectly
	 * useable mapped devices below them. Thus we treat the absence of
	 * "ranges" as equivalent to an empty "ranges" property which means
	 * a 1:1 translation at that level. It's up to the caller not to try
	 * to translate addresses that aren't supposed to be translated in
	 * the first place. --BenH.
	 *
	 * As far as we know, this damage only exists on Apple machines, so
	 * This code is only enabled on powerpc. --gcl
	 */
	ranges = of_get_property(parent, rprop, &rlen);
	if (ranges == NULL && !of_empty_ranges_quirk(parent)) {
		pr_debug("no ranges; cannot translate\n");
		return 1;
	}
	if (ranges == NULL || rlen == 0) {
		offset = of_read_number(addr, na);
		memset(addr, 0, pna * 4);
		pr_debug("empty ranges; 1:1 translation\n");
		goto finish;
	}

	pr_debug("walking ranges...\n");

	/* Now walk through the ranges */
	rlen /= 4;
	rone = na + pna + ns;
	for (; rlen >= rone; rlen -= rone, ranges += rone) {
		offset = bus->map(addr, ranges, na, ns, pna);
		if (offset != OF_BAD_ADDR)
			break;
	}
	if (offset == OF_BAD_ADDR) {
		pr_debug("not found !\n");
		return 1;
	}
	memcpy(addr, ranges + na, 4 * pna);

 finish:
	of_dump_addr("parent translation for:", addr, pna);
	pr_debug("with offset: %llx\n", (unsigned long long)offset);

	/* Translate it into parent bus space */
	return pbus->translate(addr, offset, pna);
}

在该函数的第26行使用of_get_property函数获取“ranges”属性,但由于在我们添加的设备树节点中并没有该属性,所以这里的ranges值就为NULL,第27行的条件判断成立,也就会返回1。

接下来再根据这个返回值继续分析上级函数:

of_translate_one函数返回1之后,上一级的_of_translate_address的返回值就为OF BAD ADDR,再上一级的of_translate_address返回值也是OF BAD _ADDR,继续向上查找_of_address_to_resource函数会返回EINVAL,of address_ to resource 返回EINVAL,所以num_reg 为0;到这里关于为什么platform_get_resource函数获取资源失败的问题就找到了,只是因为在设备树中并没有这个名为ranges这个属性,所以只需要对设备树进行ranges属性的添加即可,要修改的设备树为arch/arm64/boot/dts/rockchip/rk3568-evb1-ddr4-v10-linux.dts,修改完成如下(图 68-3)所示:

图 228-3

然后重新编译内核,将编译生成的boot.img烧写进开发板之后重新加载上面编写的驱动程序,可以看到之前获取失败的打印就消失了,而且成功打印出了reg属性的第一个值,如下图(图 68-4)所示:

图 238-4

虽然这里的问题解决了,但引起的思考并没有结束,那我们在这里添加的ranges属性的作用是啥呢,带着疑问,开始下一小节的学习吧。

68.2 ranges属性

68.2.1 ranges属性介绍

ranges 属性是一种用于描述设备之间地址映射关系的属性。它在设备树(Device Tree)中使用,用于描述子设备地址空间如何映射到父设备地址空间。设备树是一种硬件描述语言,用于描述嵌入式系统中的硬件组件和它们之间的连接关系。

设备树中的每个设备节点都可以具有 ranges 属性,其中包含了地址映射的信息。下面是一个常见的格式:

ranges = <child-bus-address parent-bus-address length>;

 或者

ranges;

然后对上述格式中每个部分进行解释:

child-bus-address:子设备地址空间的起始地址。它指定了子设备在父设备地址空间中的位置。具体的字长由 ranges 所在节点的 #address-cells 属性决定。

parent-bus-address:父设备地址空间的起始地址。它指定了父设备中用于映射子设备的地址范围。具体的字长由 ranges 的父节点的 #address-cells 属性决定。

length:映射的大小。它指定了子设备地址空间在父设备地址空间中的长度。具体的字长由 ranges 的父节点的 #size-cells 属性决定。

当 ranges 属性的值为空时,表示子设备地址空间和父设备地址空间具有完全相同的映射,即1:1映射。这通常用于描述内存区域,其中子设备和父设备具有相同的地址范围。

当 ranges 属性的值不为空时,按照指定的映射规则将子设备地址空间映射到父设备地址空间。具体的映射规则取决于设备树的结构和设备的特定要求。

然后以下面的设备树为例进行ranges属性的讲解,设备树内容如下所示:

/dts-v1/;

/ {
  compatible = "acme,coyotes-revenge";
  #address-cells = <1>;
  #size-cells = <1>;
  ....
  external-bus {
    #address-cells = <2>;
    #size-cells = <1>;
    ranges = <0 0 0x10100000 0x10000
              1 0 0x10160000 0x10000
              2 0 0x30000000 0x30000000>;
    // Chipselect 1, Ethernet
    // Chipselect 2, i2c controller
    // Chipselect 3, NOR Flash
.......

这里以ranges的第一个属性值为例进行具体解释如下:

在 external-bus 节点中#address-cells 属性值为2表示child-bus-address由两个值表示,也就是0和0,父节点的 #address-cells 属性值和#size-cells 属性值为1,表示parent-bus-address和length都由一个表示,也就是0x10100000和0x10000,该ranges值表示将子地址空间(0x0-0xFFFF)映射到父地址空间0x10100000 - 0x1010FFFF,这里的例子为带参数ranges属性映射,不带参数的ranges属性为1:1映射,较为简单,这里不再进行举例。

在嵌入式系统中,不同的设备可能连接到相同的总线或总线控制器上,它们需要在物理地址空间中进行正确的映射,以便进行数据交换和通信。例如,一个设备可能通过总线连接到主处理器或其他设备,而这些设备的物理地址范围可能不同。ranges 属性就是用来描述这种地址映射关系的。

68.2.2设备分类

根据上面讲解的映射关系可以将设备分为两类:内存映射型设备和非内存映射型设备。

1内存映射型设备:
内存映射型设备是指可以通过内存地址进行直接访问的设备。这类设备在物理地址空间中的一部分被映射到系统的内存地址空间中,使得CPU可以通过读写内存地址的方式与设备进行通信和控制。

特点:

(1)直接访问:内存映射型设备可以被CPU直接访问,类似于访问内存中的数据。这种直接访问方式提供了高速的数据传输和低延迟的设备操作。

(2)内存映射:设备的寄存器、缓冲区等资源被映射到系统的内存地址空间中,使用读写内存的方式与设备进行通信。

(3)读写操作:CPU可以通过读取和写入映射的内存地址来与设备进行数据交换和控制操作。

在设备树中,内存映射型设备的设备树举例如下所示:

/dts-v1/;
/ {
    #address-cells = <1>;
    #size-cells = <1>;
    ranges;

    serial@101f0000 {
        compatible = "arm,pl011";
        reg = <0x101f0000 0x1000>;
    };

    gpio@101f3000 {
        compatible = "arm,pl061";
        reg = <0x101f3000 0x1000
                0x101f4000 0x10>;
    };

    spi@10115000 {
        compatible = "arm,pl022";
        reg = <0x10115000 0x1000>;
    };
};

第5行的ranges属性表示该设备树中会进行1:1的地址范围映射。

2非内存映射型设备:
非内存映射型设备是指不能通过内存地址直接访问的设备。这类设备可能采用其他方式与CPU进行通信,例如通过I/O端口、专用总线或特定的通信协议。

特点:

(1)非内存访问:非内存映射型设备不能像内存映射型设备那样直接通过内存地址进行访问。它们可能使用独立的I/O端口或专用总线进行通信。

(2)特定接口:设备通常使用特定的接口和协议与CPU进行通信和控制,例如SPI、I2C、UART等。

(3)驱动程序:非内存映射型设备通常需要特定的设备驱动程序来实现与CPU的通信和控制。

在设备树中,非内存映射型设备的设备树举例如下所示:

/dts-v1/;

/ {
  compatible = "acme,coyotes-revenge";
  #address-cells = <1>;
  #size-cells = <1>;
  ....
  external-bus {
    #address-cells = <2>;
    #size-cells = <1>;
    ranges = <0 0 0x10100000 0x10000
              1 0 0x10160000 0x10000
              2 0 0x30000000 0x30000000>;
    // Chipselect 1, Ethernet
    // Chipselect 2, i2c controller
    // Chipselect 3, NOR Flash

    ethernet@0,0 {
      compatible = "smc,smc91c111";
      reg = <0 0 0x1000>;
    };

    i2c@1,0 {
      compatible = "acme,a1234-i2c-bus";
      #address-cells = <1>;
      #size-cells = <0>;
      reg = <1 0 0x1000>;

      rtc@58 {
        compatible = "maxim,ds1338";
        reg = <0x58>;
      };
    };
  } ;
}; 

68.2.3 映射地址计算

接下来以上面列举的非内存映射型设备的设备树中的ethernet@0节点为例,计算该网卡设备的映射地址。

首先,找到ethernet@0所在的节点,并查看其reg属性。在给定的设备树片段中,ethernet@0的reg属性为<0 0 0x1000>。在根节点中,#address-cells的值为1,表示地址由一个单元格组成。

接下来,根据ranges属性进行地址映射计算。在external-bus节点的ranges属性中,有三个映射条目:

第一个映射条目为“0 0 0x10100000 0x10000”,表示外部总线的地址范围为0x10100000到0x1010FFFF。该映射条目的第一个值为0,表示与external-bus节点的第一个子节点(ethernet@0,0)相关联。

第二个映射条目:“1 0 0x10160000 0x10000”,表示外部总线的地址范围为0x10160000到0x1016FFFF。该映射条目的第一个值为1,表示与external-bus节点的第二个子节点(i2c@1,0)相关联。

第三个映射条目:“2 0 0x30000000 0x30000000”,表示外部总线的地址范围为0x30000000到0x5FFFFFFF。该映射条目的第一个值为2,表示与external-bus节点的第三个子节点相关联。

由于ethernet@0与external-bus的第一个子节点相关联,并且它的reg属性为<0 0 0x1000>,我们可以进行以下计算:

ethernet@0的物理地址 = 外部总线地址起始值 + ethernet@0的reg属性的第二个值
= 0x10100000 + 0x1000
= 0x10101000

因此,ethernet@0的物理起始地址为0x10101000,又根据0x1000的地址范围可以确定ethernet@0的结束起始地址为0x10101FFF,至此,关于映射地址的计算就讲解完成了,大家可以根据同样的方法计算i2c@1的物理地址。


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

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

相关文章

IDC最新报告,增速减缓+AI增势,阿里云视频云中国市场第一

国际权威数据公司IDC发布 《中国视频云市场跟踪&#xff08;2023 H1&#xff09;》报告 自2018年至今&#xff0c;阿里云持续保持 中国视频云整体市场第一 整体市场占比达24.4% 01 第一之外&#xff0c;低谷之上 近期&#xff0c;国际权威数据公司IDC最新发布了《中国视频…

万宾科技可燃气体监测仪的功能有哪些?

随着城市人口的持续增长和智慧城市不断发展&#xff0c;燃气作为一种重要的能源供应方式&#xff0c;已经广泛地应用于居民生活和工业生产的各个领域。然而燃气泄漏和安全事故的风险也随之增加&#xff0c;对城市的安全和社会的稳定构成了潜在的威胁。我国燃气管道安全事故的频…

[C/C++]数据结构 循环队列

前言: 队列是一种具有先进先出特性的结构,但是当数据出队列以后,前面的空间就无法再次利用了,循环队列就可以解决这个问题 一:概念及结构: 1.循环队列概念 循环队列是一种线性数据结构&#xff0c;其操作表现基于 FIFO&#xff08;先进先出&#xff09;原则并且队尾被连接在队…

2023软件测试6大实战练手项目,你不打算看看嘛

前言 写了9年多年的代码&#xff0c;之前做过网易的架构师&#xff0c;在技术这条路上跌跌撞撞了很多&#xff0c;我今天在这里向大家介绍6个适合新人练手的项目&#xff0c;这些项目来自不同领域和行业&#xff0c;涵盖了金融、医药、电商等多个领域。如果您正在寻找一个可以…

【开源】基于Vue和SpringBoot的企业项目合同信息系统

项目编号&#xff1a; S 046 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S046&#xff0c;文末获取源码。} 项目编号&#xff1a;S046&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 合同审批模块2.3 合…

Matlab通信仿真系列——离散信号和系统

微信公众号上线&#xff0c;搜索公众号小灰灰的FPGA,关注可获取相关源码&#xff0c;定期更新有关FPGA的项目以及开源项目源码&#xff0c;包括但不限于各类检测芯片驱动、低速接口驱动、高速接口驱动、数据信号处理、图像处理以及AXI总线等 本节目录 一、离散信号 1、离散信…

Git控制指令

git status查看当前本地分支的修改状态 git diff 文件路径 查看具体文件的修改内容 git log打印用户信息 git remote -v查看远程地址 git checkout -- *还原被删除的文件 git rm -r --force .删除本地所有文件 git commit -m "Remove all files from repositor…

【Kotlin精简】第9章 Kotlin Flow

1 前言 上一章节我们学习了Kotlin的协程【Kotlin精简】第8章 协程&#xff0c;我们知道 协程实质是对线程切换的封装&#xff0c;能更加安全实现异步代码同步化&#xff0c;本质上协程、线程都是服务于并发场景下&#xff0c;其中协程是协作式任务&#xff0c;线程是抢占式任务…

XML创建IOC容器的过程

Spring框架的核心之一是IOC&#xff0c;那么我们是怎么创建出来的Bean呢&#xff1f; 作者进行了简单的总结&#xff0c;希望能对你有所帮助。 IOC的创建并不是通过new而是利用了java的反射机制&#xff0c;利用了newInstance方法进行的创建对象。 首先&#xff0c;我们先定义…

数智赋能 | 迅镭激光中标起重装备龙头株洲天桥起重!

工程机械是装备工业的重要组成部分&#xff0c;而作为工程机械最重要的产品体系之一的起重装备&#xff0c;在日新月异的城市化发展进程中&#xff0c;以效率高、性能强、智能化等特点迅速占领市场的一席之地。 在国家双碳目标下&#xff0c;国内起重装备产业正在加快向大型化、…

做亚马逊多久可以赚钱?做亚马逊需要多少资金?——站斧浏览器

做亚马逊需要时间、资金和全面的市场策略。创业者需要有耐心和决心&#xff0c;同时也要灵活应对市场变化。那么做亚马逊多久可以赚钱,做亚马逊需要多少资金。 做亚马逊多久可以赚钱 首先&#xff0c;就像任何其他生意一样&#xff0c;做亚马逊需要时间和努力来建立起稳定的客…

VUE限制文件上传大小和上传格式

<el-form-item label"图片&#xff1a;" prop"tempImagePath"><el-uploadclass"upload"accept"image/jpeg":show-file-list"false"list-type"picture-card":headers"{ token: token}":action&…

【分享】前后端分离框架的相关内容有哪些?

在当前&#xff0c;低代码技术平台的灵活性、可视性、高效性等优势特点在职场办公中非常亮眼&#xff0c;成为众多企业提升办公效率和有效利用数据资源的重要工具。为了迎合市场需求&#xff0c;前后端分离也成为了发展趋势&#xff0c;如果大家想了解前后端分离框架的具体内容…

保姆级连接FusionInsight MRS kerberos Hive

数新网络&#xff0c;让每个人享受数据的价值https://xie.infoq.cn/link?targethttps%3A%2F%2Fwww.datacyber.com%2F 概述 本文将介绍在华为云 FusionInsight MRS&#xff08;Managed Relational Service&#xff09;的Kerberos环境中&#xff0c;如何使用Java和DBeaver实现远…

JOSEF约瑟 瓦斯继电器(又称气体继电器) QJ4-25 250V 0.3A

QJ4系列气体继电器 QJ4-25气体继电器 QJ4-25瓦斯继电器 QJ4-25TH气体继电器 QJ4-25TH瓦斯继电器 QJ4G-25气体继电器 QJ4G-25瓦斯继电器 QJ4G-25TH气体继电器 QJ4G-25TH瓦斯继电器 用途 QJ4-25气体继电器是油浸式有载分接开关所用的一种保护装置。(体继电器安装在变压器箱盖 与…

KubeSphere 社区双周报 | Fluent Operator 2.6.0 发布 | 2023.11.10-11.23

KubeSphere 社区双周报主要整理展示新增的贡献者名单和证书、新增的讲师证书以及两周内提交过 commit 的贡献者&#xff0c;并对近期重要的 PR 进行解析&#xff0c;同时还包含了线上/线下活动和布道推广等一系列社区动态。 本次双周报涵盖时间为&#xff1a;2023.11.10-2023.…

python之pyqt专栏2-项目文件解析

项目结构 在上一篇文章python之pyqt专栏1-环境搭建&#xff0c;创建新的pyqt项目&#xff0c;下面我们来看一下这个项目下的文件。 从下面的文件结构图可以看到&#xff0c;该项目下有3个文件&#xff0c;untitled.ui,untitled.py 以及main.py。 QtDesigner可以UI界面的方式&am…

MySQL 基于成本的优化

其实在MySQL中⼀条查询语句的执⾏成本是由下边这两个⽅⾯组成的&#xff1a; I/O成本 我们的表经常使⽤的MyISAM、InnoDB存储引擎都是将数据和索引都存储到磁盘上的&#xff0c;当我们想查询表中的记录时&#xff0c;需要先把数据或者索引加载到内存中 然后再操作。这个从磁盘…

livox 半固体激光雷达 gazebo 仿真 | 更换仿真中mid360雷达外形

livox 半固体激光雷达 gazebo 仿真 | 更换仿真中mid360雷达外形 livox 半固体激光雷达 gazebo 仿真 | 更换仿真中mid360雷达外形livox 介绍更换仿真中mid360雷达外形 livox 半固体激光雷达 gazebo 仿真 | 更换仿真中mid360雷达外形 livox 介绍 览沃科技有限公司&#xff08;L…

基于51单片机电子钟闹钟LCD1602显示proteus仿真设计

基于51单片机的LCD1602电子钟闹钟proteus仿真设计 基于51单片机的LCD1602电子钟闹钟proteus仿真设计功能介绍&#xff1a;仿真图&#xff1a;原理图&#xff1a;设计报告&#xff1a;程序&#xff1a;器件清单&#xff1a;资料清单&&下载链接&#xff1a; 基于51单片机…