Linux 设备树的加载与匹配

2023-11-13

之前学习了platform设备与总线是如何匹配的,但是在读某一驱动程序中,该设备由dts文件描述,设备的匹配与platform设有所不同,因此记录下来。

1. 什么是设备树

在内核源码中存在大量对板级细节信息描述的代码,但是对于内核而言,这些代码对于内核毫无意义。 ARM内核版本3.x引入了Flattened Device Tree(FDT),这是一种描述硬件资源的数据结构,通过BootLoader将硬件资源传给内核,使得内核和硬件资源描述相对独立。采用Device Tree后,许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余码。
本质上,Device Tree改变了原来用hardcode方式将HW 配置信息嵌入到内核代码的方法,改用bootloader传递一个DB的形式。
设备树是一种描述硬件的数据结构,采用设备树后,硬件细节可以直接通过设备树传递给Linux,不需要在内核中进行大量的冗余编码。
Device Tree由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点。所谓属性,其实就是成对出现的name和value。在Device Tree中,可描述的信息包括(原先这些信息大多被hard code到kernel中):

  • CPU的数量和类别
  • 内存基地址和大小
  • 总线和桥
  • 外设连接
  • 中断控制器和中断使用情况
  • GPIO控制器和GPIO使用情况
  • Clock控制器和Clock使用情况

它基本上就是画一棵电路板上CPU、总线、设备组成的树,Bootloader会将这棵树传递给内核,然后内核可以识别这棵树,并根据它展开出Linux内核中的platform_device、i2c_client、spi_device等设备,而这些设备用到的内存、IRQ等资源,也被传递给了内核,内核会将这些资源绑定给展开的相应的设备。

DTS(device tree source)

.dts文件是一种对Device Tree的ASCII文本描述,一个dts文件对应一个ARM架构的machine。但是一个SOC板可能对应多个产品。这些产品的dts文件会存在大量冗余。为了简化,Device Tree将这些冗余提炼为.dtsi文件,dtsi文件相当于C语言的头文件,dts文件需要include引入dtsi文件。

DTC(编译工具)

DTC为编译工具,它可以将.dts文件编译成.dtb文件。DTC的源码位于内核的scripts/dtc目录下,内核选中CONFIG_OF,编译内核的时候,主机可执行程序DTC就会被编译出来。

DTB(二进制文件)

DTC编译.dts生成的二进制文件(.dtb),bootloader在引到内核时,会预先读取.dtb到内存,进而由内核解析。

BootLoader(bootloader支持)

Bootloader需要将设备树在内存中的地址传给内核。在ARM中通过bootm或bootz命令来进行传递。

Device Tree组成和结构

device tree的基本单元是node。这些node被组织成树状结构,除了root node,每个node都只有一个parent。一个device tree文件中只能有一个root node。每个node中包含了若干的property/value来描述该node的一些特性。每个node用节点名字(node name)标识,节点名字的格式是node-name@unit-address。如果该node没有reg属性),那么该节点名字中必须不能包括@和unit-address。root node的node name是确定的,必须是“/”。

2. 设备树信息加载

设备树的目的是用于抽象硬件资源,减少代码的重复定义。内核会识别出相应设备树中的节点,并展开出platform_device、i2c_client等各种设备,对应到platform上,设备树上的node会展开为platform_device,设备用到的资源也会由内核绑定给相应设备。

2.1 dtb展开为device_node

Linux最底层的初始化是用汇编实现的,而在C中的start_kernel()中,具体是在setup_arch()中完成初始化的。
setup_arch()中有三个重要的函数,分别是setup_machine_fdt()、arm_memblock_init()以及unflatten_device_tree()。
setup_machine_fdt()函数根据传入的设备树dtb的首地址完成一些初始化操作;arm_memblock_init()函数主要是内存相关,为设备树保留相应的内存空间,保证设备树dtb本身存在于内存中而不被覆盖。用户可以在设备树中设置保留内存,这一部分同时作了保留指定内存的工作;unflatten_device_tree()从命名可以看出,这个函数就是对设备树具体的解析,事实上在这个函数中所做的工作就是将设备树各节点转换成相应的struct device_node结构体。
各函数详细作用可看附录,以后在详细了解后应该会进行补充。

2.2 device_node展开为platform_device

将device_node展开为platform_device是of_platform_default_populate_init()函数实现的。

arch_initcall_sync(of_platform_default_populate_init);

其具体调用为

static int __init of_platform_default_populate_init(void)
	int of_platform_default_populate(struct device_node *root, const struct of_dev_auxdata *lookup,struct device *parent)
		int of_platform_populate(struct device_node *root, const struct of_device_id *matches, const struct of_dev_auxdata *lookup, struct device *parent)
			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)
				static struct platform_device *of_platform_device_create_pdata(struct device_node *np, const char *bus_id, void *platform_data, struct device *parent)

of_platform_default_populate_init()在内核中没有直接对它的调用,而是使用放在".initcall3s.init"代码段中,在内核启动时进行 do_initcall_level()时调用这个函数。

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;

	/*
	 * Handle certain compatibles explicitly, since we don't want to create
	 * platform_devices for every node in /reserved-memory with a
	 * "compatible",
	 */
	for_each_matching_node(node, reserved_mem_matches) //处理一些保留的节点
		of_platform_device_create(node, NULL, NULL);

	node = of_find_node_by_path("/firmware"); //处理firmware目录下的节点
	if (node) {
		of_platform_populate(node, NULL, NULL, NULL);
		of_node_put(node);	//减少node引用计数
	}

	/* Populate everything else. */
	of_platform_default_populate(NULL, NULL, NULL); //处理其余类型的节点

	return 0;
}

其作用是遍历device_tree,生成platform_device,最主要的处理函数就是of_platform_default_populate(),对应在该函数中,root、lookup与parent均为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);
}
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 */
};

of_platform_populate()函数会根据node的compatible属性调用of_platform_bus_create()创建device,其中root的值为of_find_node_by_path("/"),并对其子节点分别创建device。matches为上面的of_default_bus_match_table。

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 ? 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();
	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();

	of_node_set_flag(root, OF_POPULATED_BUS);

	of_node_put(root);
	return rc;
}

该函数的作用是根据device_node创建platform_device,并递归的对子节点创建platform_device。首先判断节点是否有compatible属性,没有的话不做处理;接着判断of_match_node()函数,根据of_skipped_node_table表查看节点属性,判断节点是否需要创建设备;如果节点中的compatible属性的值为operating-points-v2,则跳过;接着根据bus总线的值判断该总线节点是否已被展开,如果是,跳过;lookup根据of_platform_default_populate()函数发现值为空,直接返回NULL;接着,amba设备做单独处理;然后调用of_platform_device_create_pdata()函数将节点转化为设备;最后递归地将子节点转化为设备。在总线上的节点转化完毕后,设置标志OF_POPULATED_BUS。
在对子节点进行操作时,根据matchs传入的数组进行判断,如果包括"simple-bus"、“simple-mfd”、“isa”、"arm,amba-bus"等属性时,会创建对子节点创建platform_device。

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;

	/* Make sure it has a compatible property */
	if (strict && (!of_get_property(bus, "compatible", NULL))) {
		pr_debug("%s() - skipping %pOF, no compatible prop\n",
			 __func__, bus);
		return 0;
	}

	/* Skip nodes for which we don't want to create devices */
	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")) {
		/*
		 * Don't return an error here to keep compatibility with older
		 * device tree files.
		 */
		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;
}

很明显,在该函数中,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);

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

由of_device_alloc()分配一个platform_device型结构体,在对bus、platform_data(此时为NULL)赋值后,由of_device_add()将创建的设备添加到系统中。
对于of_device_alloc(),首先调用platform_device_alloc()创建一个platform_device,接着函数统计设备树中reg属性和中断irq属性的个数,然后分别为它们申请内存空间,链入到platform_device中的struct resources成员中。除了设备树中"reg"和"interrupt"属性之外,还有可选的"reg-names"和"interrupt-names"这些io中断资源相关的设备树节点属性也在这里被转换。

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;
	//统计中断irq属性的数量
	/* 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);	//统计中断irq属性的数量
	//根据num_irq和num_reg的数量申请相应的struct resource内存空间。
	/* 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;
		//将device_node中的reg属性转换成platform_device中的struct resource成员
		for (i = 0; i < num_reg; i++, res++) {
			rc = of_address_to_resource(np, i, res);
			WARN_ON(rc);
		}
		//将device_node中的irq属性转换成platform_device中的struct resource成员
		if (of_irq_to_resource_table(np, res, num_irq) != num_irq)
			pr_debug("not all legacy IRQ resources mapped for %pOFn\n",
				 np);
	}
	//将platform_device的dev.of_node成员指针指向device_node。
	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;
}

最后调用of_device_add()将相应的设备树节点生成的device_node节点链入到platform_device的dev.of_node中。
设备树的目的是将硬件资源抽象出来,由系统统一解析,避免驱动中对硬件资源大量的重复定义。在老版本的内核中,platform_device部分是静态定义的,其实最主要的部分就是resources部分,这一部分描述了当前驱动需要的硬件资源,一般是IO,中断等资源。在设备树中,这一类资源通常通过reg属性来描述,中断则通过interrupts来描述,所以,设备树中的reg和interrupts资源将会被转换成platform_device内的struct resources资源。在platform_device中有一个成员struct device dev,这个dev中又有一个指针成员struct device_node* of_node,linux的做法就是将这个of_node指针直接指向由设备树转换而来的device_node结构。例如,有这么一个struct platform_device* of_test.我们可以直接通过of_test->dev.of_node来访问设备树中的信息.

3. 设备的匹配

在之前分析的platform_match()中,调用了of_driver_match_device()函数,这个函数就是将驱动与设备树中的设备进行匹配,其具体调用为:

static inline int of_driver_match_device(struct device *dev,const struct device_driver *drv)
	const struct of_device_id *of_match_device(const struct of_device_id *matches,const struct device *dev)
		static const struct of_device_id *__of_match_node(const struct of_device_id *matches, const struct device_node *node)
			static int __of_device_is_compatible(const struct device_node *device, const char *compat, const char *type, const char *name)

首先调用of_driver_match_device()函数进行设备与驱动的匹配,而该函数调用了of_match_device()函数,主要是通过驱动的of_match_table结构体。与设备进行匹配。

static inline int of_driver_match_device(struct device *dev,
					 const struct device_driver *drv)
{
	return of_match_device(drv->of_match_table, dev) != NULL;
}

of_match_table的具体类型如下所示,包含了驱动支持的多个设备类型

struct of_device_id                                      
{
	char    name[32];  //设备名
	char    type[32];  //设备类型
	char    compatible[128];  //与设备信息compatible相匹配
	const void *data;  //驱动私有数据
};

设备树中的设备信息在加载时被组织为device_node结构体,转变为platform_device时,其of_node指针指向的就是该device_node结构。

const struct of_device_id *of_match_device(const struct of_device_id *matches,
					   const struct device *dev)
{
	if ((!matches) || (!dev->of_node))
		return NULL;
	return of_match_node(matches, dev->of_node);
}

其匹配的方式主要是通过compatible、type以及name等方式进行匹配,其优先级在__of_device_is_compatible()函数注释中进行了说明。

const struct of_device_id *__of_match_node(const struct of_device_id *matches,
					   const struct device_node *node)
{
	const struct of_device_id *best_match = NULL;
	int score, best_score = 0;

	if (!matches)
		return NULL;

	for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
		score = __of_device_is_compatible(node, matches->compatible,
						  matches->type, matches->name);
		if (score > best_score) {
			best_match = matches;
			best_score = score;
		}
	}

	return best_match;
}

参数 device 指向一个 device_node; 参数指向 compatible 字符串。函数调用 __of_find_property() 函数获得该设备的属性“compatible”。如果属性compatible找到,那么返回属性compatible的值;否则返回 NULL。调用 of_compat_cmp() 函数对比 compatible 属性的值是否和参数一致。

static int __of_device_is_compatible(const struct device_node *device,
				     const char *compat, const char *type, const char *name)
{
	struct property *prop;
	const char *cp;
	int index = 0, score = 0;

	/* Compatible match has highest priority */
	if (compat && compat[0]) {
		prop = __of_find_property(device, "compatible", NULL);
		for (cp = of_prop_next_string(prop, NULL); cp;
		     cp = of_prop_next_string(prop, cp), index++) {
			if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
				score = INT_MAX/2 - (index << 2);
				break;
			}
		}
		if (!score)
			return 0;
	}

	/* Matching type is better than matching name */
	if (type && type[0]) {
		if (!__of_node_is_type(device, type))
			return 0;
		score += 2;
	}

	/* Matching name is a bit better than not */
	if (name && name[0]) {
		if (!of_node_name_eq(device, name))
			return 0;
		score++;
	}

	return score;
}

由于每个 device_node 包含一个名为 properties 的成员,properties 是一个单链表,包含了该节点的所有属性,函数通过使用 for 循环遍历这个链表,以此遍历节点所包含的所有属性。每遍历一个属性就会调用 of_prop_cmp() 函数, of_prop_cmp() 函数用于对比两个字符串是否相等,如果相等返回 0。因此,当遍历到的属性的名字与参数 name 一致,那么定义为找到了指定的属性。

static struct property *__of_find_property(const struct device_node *np,
					   const char *name, int *lenp)
{
	struct property *pp;

	if (!np)
		return NULL;

	for (pp = np->properties; pp; pp = pp->next) {
		if (of_prop_cmp(pp->name, name) == 0) {
			if (lenp)
				*lenp = pp->length;
			break;
		}
	}

	return pp;
}

在设备匹配方式改变的同时,其driver的初始化也发生了改变,这是基于设备树的设备匹配方式的驱动:

static const struct of_device_id arc_pmu_match[] = {
	{ .compatible = "snps,arc700-pct" },
	{ .compatible = "snps,archs-pct" },
	{},
};
static struct platform_driver arc_pmu_driver = {
	.driver	= {
		.name		= "arc-pct",
		.of_match_table = of_match_ptr(arc_pmu_match),
	},
	.probe		= arc_pmu_device_probe,
};

这是一般platform_device设备匹配的驱动的初始化:

static struct platform_driver db1300_wm97xx_driver = {
	.driver.name	= "wm97xx-touch",
	.driver.owner	= THIS_MODULE,
	.probe		= db1300_wm97xx_probe,
};

针对设备树节点展开为platform_device的过程,主要通过这篇文章进行学习的:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux 设备树的加载与匹配 的相关文章

  • 抑制 makefile 中命令调用的回显?

    我为一个作业编写了一个程序 该程序应该将其输出打印到标准输出 分配规范需要创建一个 Makefile 当调用它时make run gt outputFile应该运行该程序并将输出写入一个文件 该文件的 SHA1 指纹与规范中给出的指纹相同
  • 如何检测并找出程序是否陷入死锁?

    这是一道面试题 如何检测并确定程序是否陷入死锁 是否有一些工具可用于在 Linux Unix 系统上执行此操作 我的想法 如果程序没有任何进展并且其状态为运行 则为死锁 但是 其他原因也可能导致此问题 开源工具有valgrind halgr
  • 从 csv 文件中删除特定列,保持输出上的相同结构[重复]

    这个问题在这里已经有答案了 我想删除第 3 列并在输出文件中保留相同的结构 输入文件 12 10 10 10 10 1 12 23 1 45 6 7 11 2 33 45 1 2 1 2 34 5 6 I tried awk F 3 fil
  • 应用程序无缘无故地被杀死。怀疑 BSS 高。如何调试呢?

    我已经在CentOs6 6中成功运行我的应用程序 最近 硬件 主板和内存 更新了 我的应用程序现在毫无理由地被杀死 root localhost PktBlaster PktBlaster Killed 文件和 ldd 输出 root lo
  • Linux TUN/TAP:无法从 TAP 设备读回数据

    问题是关于如何正确配置想要使用 Tun Tap 模块的 Linux 主机 My Goal 利用现有的路由软件 以下为APP1和APP2 但拦截并修改其发送和接收的所有消息 由Mediator完成 我的场景 Ubuntu 10 04 Mach
  • 为什么 Linux perf 使用事件 l1d.replacement 来处理 x86 上的“L1 dcache misses”?

    在英特尔 x86 上 Linux用途 https stackoverflow com a 52172985 149138事件l1d replacements来实施其L1 dcache load misses event 该事件定义如下 计数
  • Linux中的定时器类

    我需要一个计时器来以相对较低的分辨率执行回调 在 Linux 中实现此类 C 计时器类的最佳方法是什么 有我可以使用的库吗 如果您在框架 Glib Qt Wx 内编写 那么您已经拥有一个具有定时回调功能的事件循环 我认为情况并非如此 如果您
  • Linux 中的动态环境变量?

    Linux 中是否可以通过某种方式拥有动态环境变量 我有一个网络服务器 网站遵循以下布局 site qa production 我想要一个环境变量 例如 APPLICATION ENV 当我在 qa 目录中时设置为 qa 当我在生产目录中时
  • arm64和armhf有什么区别?

    Raspberry Pi Type 3 具有 64 位 CPU 但其架构不是arm64 but armhf 有什么区别arm64 and armhf armhf代表 arm hard float 是给定的名称Debian 端口 https
  • 尝试安装 LESS 时出现“请尝试以 root/管理员身份再次运行此命令”错误

    我正在尝试在我的计算机上安装 LESS 并且已经安装了节点 但是 当我输入 node install g less 时 出现以下错误 并且不知道该怎么办 FPaulMAC bin paul npm install g less npm ER
  • Discord.net 无法在 Linux 上运行

    我正在尝试让在 Linux VPS 上运行的 Discord net 中编码的不和谐机器人 我通过单声道运行 但我不断收到此错误 Unhandled Exception System Exception Connection lost at
  • 无法使用 wget 在 CentOS 机器上安装 oracle jdk

    我想在CentOS上安装oracle java jdk 8 我无法安装 java jdk 因为当我尝试使用命令安装 java jdk 时 root ADARSH PROD1 wget no cookies no check certific
  • 如何将目录及其子目录中的所有 PDF 文件复制到一个位置?

    如何全部复制PDF文件从目录及其子目录到单个目录 实际上还有更多的文件 并且深度有些任意 假设四个目录的最大深度是公平的 我想这些文件需要重命名 如果a pdf例如 位于多个目录中 因为我会adding https ebooks stack
  • Linux 内核标识符中前导和尾随下划线的含义是什么?

    我不断遇到一些小约定 比如 KERNEL Are the 在这种情况下 是内核开发人员使用的命名约定 还是以这种方式命名宏的语法特定原因 整个代码中有很多这样的例子 例如 某些函数和变量以 甚至 这有什么具体原因吗 它似乎被广泛使用 我只需
  • 使用 sh 运行 bash 脚本

    我有 bash 脚本 它需要 bash 另一个人尝试运行它 sh script name sh 它失败了 因为 sh 是他的发行版中 dash 的符号链接 ls la bin sh lrwxrwxrwx 1 root root 4 Aug
  • 如何通过保持目录结构完整来同步路径中匹配模式的文件?

    我想将所有文件从服务器 A 复制到服务器 B 这些文件在不同级别的文件系统层次结构中具有相同的父目录名称 例如 var lib data sub1 sub2 commonname filetobecopied foo var lib dat
  • 为arm构建WebRTC

    我想为我的带有arm926ej s处理器的小机器构建webrtc 安装 depot tools 后 我执行了以下步骤 gclient config http webrtc googlecode com svn trunk gclient s
  • 多处理:仅使用物理核心?

    我有一个函数foo它消耗大量内存 我想并行运行多个实例 假设我有一个有 4 个物理核心的 CPU 每个核心有两个逻辑核心 我的系统有足够的内存来容纳 4 个实例foo并行但不是 8 个 此外 由于这 8 个核心中的 4 个是逻辑核心 我也不
  • 我的线程图像生成应用程序如何将其数据传输到 GUI?

    Mandelbrot 生成器的缓慢多精度实现 线程化 使用 POSIX 线程 Gtk 图形用户界面 我有点失落了 这是我第一次尝试编写线程程序 我实际上并没有尝试转换它的单线程版本 只是尝试实现基本框架 到目前为止它是如何工作的简要描述 M
  • 如何更改 Apache 服务器的根目录? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 如何更改 Apache 服务器的文档根目录 我基本上想要localhost从 来 users spencer projects目录而不是

随机推荐

  • 上周AI热点回顾:AI“模拟”出暗物质、AI挖掘毕加索秘密、CPU在大型神经网络超越V100 GPU...

    01 全球首个AI宇宙模拟器跑出了暗物质 Space Engine是一款宇宙模拟游戏 它包含数千个真实的天体 包括来自HIP目录的恒星 来自NGC和IC目录的星系 几个知名的星云 以及所有已知的系外行星和它们的恒星 它采用星表与程序化生成创
  • Mac 环境现有项目集成 RN环境

    开发环境 mac rn版本 0 62 2 xcode版本 11 6 一 集成cocopods 参考文档 https www jianshu com p 6d51362b7e64 1 查看当前Ruby版本 ruby v 2 升级Ruby环境
  • 48-输入和显示-进度条控件QProgressBar

    进度条控件QProgressBar 进度条控件QProgressBar 通常用来显示一项任务完成的进度例如复制文件导出数据的进度 进度条QProgressBar是从QWidget 继承而来的 用QProgressBar类创建实例对象的方法如
  • [python] 安装numpy+scipy+matlotlib+scikit-learn及问题解决

    这篇文章主要讲述Python如何安装Numpy Scipy Matlotlib Scikit learn等库的过程及遇到的问题解决方法 最近安装这个真是一把泪啊 各种不兼容问题和报错 希望文章对你有所帮助吧 你可能遇到的问题包括 Impor
  • android父元素,Android之布局

    LinearLayout 线性布局 线性布局 最常用的布局之一 所有包含在线性布局里的控件在线性方向上依次排列 注意 线性布局不会换行 当组件一个挨着一个地排列到头之后 剩下的组件将不会被显示出来 1 方向 在线性布局里面的控件 是按照线性
  • vue axios解决文件流下载乱码

    前端请求头 responseType blob 一定要加 是单独一个对象 不能放在请求参数里面 new Blob res type application vnd ms excel charset utf 8 一定要设置类型 和后端resp
  • JDK、JRE、JVM三者间的关系

    JDK Java Development Kit 是针对Java开发员的产品 是整个Java的核心 包括了Java运行环境JRE Java工具和Java基础类库 Java Runtime Environment JRE 是运行JAVA程序所
  • Go语言简介

    一 Go编程语言概述 Go语言也叫Golang 是由谷歌 Google 公司在2007年推出的一款静态编译型语言 主要将其用于服务端开发 并发编程和网络编 程等 1 1 Go语言特性及应用场景 1 容易上手 2 编程速度快 3 原生支持并发
  • iPhone手机屏幕尺寸分辨率一览

    机型 物理像素 逻辑像素 规格 对角线 iPhone 12 Pro Max 1284 2778px 428 926pt 3x 6 7英寸 iPhone 12 Pro 1170 2532px 390 844pt 3x 6 1英寸 iPhone
  • 吃货联盟订餐系统(用对象和数组来写的)

    目录 一 自我介绍 2 吃货联盟订餐系统 1 首先创建一个订单类 2 创建一个餐品类 3 创建一个操作类 作用是添加订单 删除订单等操作 三 未来的发展规划 四 图书管理系统 用数组写的 一 自我介绍 我目前还是正在上学的学生 现在正在学习
  • Cpolar内网穿透+HadSky:搭建私密高效的轻量化论坛网站

    文章目录 前言 1 网站搭建 1 1 网页下载和安装 1 2 网页测试 1 3 cpolar的安装和注册 2 本地网页发布 2 1 Cpolar临时数据隧道 2 2 Cpolar稳定隧道 云端设置 2 3 Cpolar稳定隧道 本地设置 2
  • arduino舵机达180不到_【舵机初动】基于Mind+ Ardunio入门教程10

    点击上方 蘑菇云创造 可以关注我们哦 本项目要接触到舵机 舵机是一种电机 它使用一个反馈系统来控制电机的位置 可以很好掌握电机角度 大多数舵机是可以最大旋转180 的 也有一些能转更大角度 甚至360 舵机比较多的用于对角度有要求的场合 比
  • 【Basis】变分推断以及VIEM

    在包含隐变量 latent variables 的推断问题中 针对连续性随机变量的情况 隐变量的高维以及被积函数 intergrand 的复杂度使积分 intergration 无法进行 而针对离散型随机变量 隐变量呈指数 exponent
  • Git 本地代码上传到远程仓库

    Git本地代码上传到远程仓库 1 进入项目地址 通过命令git init将项目初始化成git本地仓库 git init 2 将项目内所有文件都添加到暂存区 git add 3 该命令会将git add 存入暂存区修改内容提交至本地仓库中 若
  • 寒假:HTML

    gt 框架的主要作用是使页面中的部分内容实现框架实现 一般用于在页面中引用站外的页面内容 1 在被打开的框架上加name属性 代码如下 2 在超链接上设置target目标窗口属性为希望显示的框架窗口名 lt a href target ma
  • dbeaver无法修改表数据_解决MDL锁导致无法操作数据库表的问题

    背景信息 MYSQL的MDL锁 用于解决或者保证DDL操作与DML操作之间的一致性 但是在部分场景下会出现阻塞 例如执行DML操作时执行ALTER操作 存在长时间查询时执行ALTER操作等等 表象如下 出现 Waiting for tabl
  • STM32 电机教程 20 - 基于ST MC Workbench 无感FOC

    前言 磁场定向控制又称矢量控制 FOC 本质上为控制定子电流的幅度和相位 使之产生的磁场和转子的磁场正交 以产生最大的扭矩 PMSM的磁场定向控制框图如下图所示 第19讲成功实现了基于NUCLEO F103RB和X NUCLEO IHM07
  • 计算几何学

    问题描述 对于线段s1 s2 如果相交则输出 1 否则输出 0 设s1的端点为p0 p1 s2的端点为p2 p3 输入 第1行输入问题数q 接下来q行给出q个问题 各问题线段s1 s2的坐标按照以下格式给出 x p 0 x p0
  • final关键字的继承问题

    final关键字的继承问题 前言 接口中的final关键字 基本接口 内部接口 接口中使用final有什么影响 抽象类中的final关键字 普通类中的final关键字 更多一点思考 前言 虽然现在已经有很多博客验证了final关键字的继承问
  • Linux 设备树的加载与匹配

    之前学习了platform设备与总线是如何匹配的 但是在读某一驱动程序中 该设备由dts文件描述 设备的匹配与platform设有所不同 因此记录下来 1 什么是设备树 在内核源码中存在大量对板级细节信息描述的代码 但是对于内核而言 这些代