摘要

本报告旨在为嵌入式Linux开发者详细梳理设备树(Device Tree, DT)在系统启动中的完整解析流程。报告将从引导加载程序(Bootloader)如何准备和传递设备树二进制文件(DTB)开始,逐步深入到内核如何将其转化为可操作的内存数据结构,如何与设备驱动程序进行匹配与绑定,最终阐述驱动程序如何利用这些信息在sysfs文件系统中创建可供用户空间访问的设备节点。这份报告旨在解答从静态的硬件描述到用户可见的动态设备文件这一过程中的所有核心技术问题。

引言:设备树的核心作用与演变

1.1 设备树的起源与必要性

设备树最初源于SPARC和PowerPC平台的Open Firmware项目,其设计初衷是为了在不修改操作系统内核的情况下,支持多种不同的硬件配置 。在ARM架构中,设备树的引入尤其关键,因为它彻底改变了传统的“一个板子一个内核”的困境 。

在设备树出现之前,Linux内核的硬件描述是硬编码在所谓的board-file(如arch/arm/mach-xxx/board-yyy.c)中的 。这意味着,每当硬件发生微小改动,例如更改了一个I2C外设的地址,就需要重新编译整个内核镜像 。这种紧耦合的设计使得内核开发难以扩展,也极大地增加了新板卡移植的复杂性。

设备树的出现标志着一种从硬编码到数据驱动的嵌入式软件开发范式转变。在旧的ARM启动机制中,引导加载程序使用一种名为ATAGS(一个链表结构)的机制,只能传递内存大小、内核命令行等少量基本信息 。而其他所有非自发现(non-discoverable)的硬件信息,如I2C控制器地址、GPIO引脚配置等,都必须预先硬编码在内核源码的

board-file里 。

与此形成鲜明对比的是,设备树二进制文件(DTB)包含了完整的硬件拓扑结构。引导加载程序只需将DTB的物理地址传递给内核(在ARM上,这一地址通常被加载到R2寄存器中),内核就能在运行时自主解析整个硬件配置 。这种转变将硬件描述从内核源码中移除,放入独立的文件(

.dts)中,使得同一个内核镜像能够通过加载不同的DTB文件来支持多个不同的板卡 。这种解耦机制是现代Linux内核在嵌入式领域取得成功,特别是实现通用主线内核支持的关键。

1.2 设备树的基本构成

设备树本质上是一种描述硬件的数据结构,采用树形或非循环图的格式 。其基本结构由节点(nodes)和属性(properties)组成 。每个节点都代表一个设备或总线,并可以包含任意数量的命名属性和子节点 。

  • 根节点(/:设备树的顶层节点,没有名称和地址 。

  • 节点命名:每个节点都遵循node-name@unit-address的命名惯例 。

    node-name描述设备的类型,unit-address则指定设备在其父总线地址空间中的基地址 。

  • 核心属性

    • #address-cells#size-cells:这两个属性定义了子节点reg属性中地址和大小字段的单元格(cell,即32位值)数量 。这是一种可扩展的地址表示机制。

    • compatible(兼容性字符串):这是连接硬件描述与设备驱动程序的关键契约 。它是一个字符串列表,通常由

      vendor,device组成,其中最具体的字符串排在最前面,最通用的排在最后 。驱动程序会使用这个字符串来声明自己支持哪些硬件 。

    • phandlealiases:设备树允许节点之间通过phandle进行引用 。这种引用通常通过

      &label的形式来实现,其中label是节点的标签 。此外,

      /aliases节点为常用的节点路径提供了简短的别名,便于访问 。

第一章:启动加载阶段:DTB的准备与传递

2.1 DTB的生成与存储

设备树的生命周期始于其源文件。硬件开发者通常会编写设备树源文件(.dts),并引用包含SoC级别通用定义的设备树包含文件(.dtsi) 。这种

.dtsi文件通过#include指令实现模块化和重用,使得不同板卡可以共享SoC的硬件描述 。

这些.dts.dtsi文件由设备树编译器(Device Tree Compiler, dtc)处理,dtc将人类可读的文本格式转换为紧凑的、面向机器的二进制格式,即设备树二进制文件(DTB,文件后缀为.dtb)。生成的DTB文件通常与Linux内核镜像(如

zImageuImage)一同存储在系统的非易失性存储介质(如SD卡、eMMC或NAND Flash)的启动分区中 。

2.2 U-Boot的职责与DTB的修改

引导加载程序(例如U-Boot)是DTB解析过程的第一阶段参与者 。它的主要职责是:

  1. 将内核镜像和DTB文件从存储介质加载到RAM中 。

  2. 在内存中对DTB进行必要的运行时修改,例如更新chosen节点中的bootargs(内核命令行参数)、设置RAM的基地址和大小,或者配置网络接口的MAC地址等 。

  3. 加载完成后,U-Boot将最终的DTB准备好,并将其物理内存地址传递给内核 。

这些修改至关重要,它确保了内核接收到的硬件描述是最新、最准确的,能够反映引导加载程序在启动时根据用户配置或探测结果所做的任何调整。

2.3 DTB的关键传递机制

在ARM架构中,U-Boot将DTB的物理地址传递给内核的机制是一个设计精巧的关键环节 。在跳转到内核入口点之前,U-Boot会执行以下操作:

  1. 将CPU的r0寄存器设置为0。

  2. r1寄存器设置为机器类型ID(旧机制)。

  3. r2寄存器设置为DTB在系统RAM中的物理地址 。

然后,U-Boot跳转到内核的入口点,内核从此接管控制权 。下表对比了设备树机制与旧的ATAGS机制的差异,突出了前者在灵活性和可扩展性方面的优势。

表1:ATAGS与设备树传递机制对比

特性ATAGS (旧机制)设备树 (现代机制)
数据结构一个链表,由一系列标签(tag)组成一个扁平化、树状的二进制数据块(DTB)
传递方式引导加载程序通过r2寄存器传递一个指向ATAGS链表头部的指针

引导加载程序通过r2寄存器传递一个指向DTB二进制数据块的指针

描述内容

仅限于少量基本信息,如内存大小、命令行参数等

包含完整的硬件拓扑、外设地址、中断、GPIO等所有非自发现的硬件信息

优点简单、开销小

硬件描述与内核代码完全解耦,支持通用内核镜像

缺点

缺乏灵活性,板级硬件信息需要硬编码在内核源码中

需要额外的dtc工具编译,DTB文件需要占用一定内存空间
适用范围

已被弃用,不建议用于新平台

现代嵌入式Linux的首选,ARM等多种架构强制使用

第二章:内核早期启动:DTB的初步解析

3.1 内核入口点与DTB的接收

在U-Boot将控制权交给Linux内核后,内核的启动代码会开始执行。在start_kernel()函数被调用之前,内核的架构特定启动代码(如ARM上的head.S文件)会从r2寄存器中读取DTB的物理地址 。这个地址随后会被保存到一个全局变量中,供内核后续使用 。

这个阶段非常关键,因为此时内存管理单元(MMU)尚未启用,内核只能访问物理地址 。这意味着内核无法进行复杂的动态内存分配和构建复杂的C数据结构。然而,它需要一些关键信息才能完成MMU的初始化,例如RAM的地址和大小,以及

bootargs中的内核命令行参数 。

3.2 of_scan_flat_dt()libfdt

为了在MMU启用之前的受限环境中获取必要信息,内核使用一个轻量级的库来直接解析DTB的扁平化二进制数据 。这个库就是

libfdt(Flattened Device Tree library)。

libfdt被集成在内核源码中,提供了一系列低级API来检查、读取和修改DTB二进制文件 。

内核的启动代码会调用of_scan_flat_dt()函数来遍历DTB,并使用回调函数来提取关键信息 。例如:

  • early_init_dt_scan_chosen():用于解析/chosen节点下的内核命令行参数 bootargs

  • early_init_dt_scan_memory():用于确定系统RAM的地址和大小 。

这种设计表明内核对DTB的解析并非一次性完成,而是分为两个阶段:早期扫描(pre-MMU)和后期解压(post-MMU)。这个设计是启动过程中内存和寻址限制所决定的。在MMU启用前,内核无法进行复杂的动态内存分配,因此需要libfdt这种能够在物理内存中直接操作DTB二进制数据的工具。

3.3 DTB的“解压”:从扁平化到树形结构

当内核完成了MMU的初始化和虚拟内存的配置后,它就可以进行更复杂的内存操作。此时,一个关键的函数unflatten_device_tree()会被调用 。这个函数的核心任务是将静态的DTB二进制数据转换成一个动态的、链表连接的内存数据结构:

struct device_node树 。

unflatten_device_tree()的执行标志着DTB的静态描述阶段结束,进入了DT在内存中作为动态数据结构的活跃阶段。这种两阶段解析过程是内核为了在有限制的早期启动环境中获取关键信息,同时在成熟的运行时环境中构建高效、易于访问的数据结构之间做出的权衡。

第三章:DT的内存表示与驱动程序的接口

4.1 struct device_node:DT在内存中的实体

DTB经过unflatten_device_tree()的转换后,在内核中被表示为一系列相互连接的struct device_node实例 。

struct device_node是DT中每个节点的内存表示,它包含了指向其父节点、子节点、兄弟节点和属性列表的指针,从而构成了完整的树形结构 。

驱动程序不会直接操作DTB二进制数据,而是通过一套标准的of_*家族API来与这个struct device_node树进行交互 。这些API包括:

  • of_get_next_child():用于遍历一个节点的所有子节点 。

  • of_find_compatible_node():用于根据兼容性字符串在DT树中查找匹配的节点 。

  • of_get_property():用于读取一个节点中特定属性的值 。

这些高级API将复杂的树形遍历和数据解析细节封装起来,为驱动程序提供了一个简洁、高效的接口。

4.2 DT与内核设备模型的整合

设备树的解析为Linux设备模型提供了设备实例化的数据来源。Linux设备模型的核心思想是围绕着总线(bus)、设备(device)和驱动(driver)这三元组来组织的 。

DT中的节点本身并不是Linux设备模型中的“设备”,而只是对硬件的静态描述 。内核在解析DT后,会根据这些静态数据来动态创建

struct device实例。对于SoC上那些无法被自动枚举的内存映射设备,内核为此创建了一个虚拟总线——platform_bus

of_platform_populate()函数是连接DT解析和Linux设备模型的关键桥梁 。它会遍历DT树,找到那些具有

compatible属性的节点,并为它们创建对应的platform_device实例,然后将这些设备注册到platform_bus上 。对于其他总线类型(如I2C、SPI),其总线驱动的

probe函数会负责为其子节点创建相应的设备实例(如i2c_client),并注册到各自的总线上 。

这个过程将设备描述从静态数据转换为了内核设备模型中的动态实例,从而实现了从设计蓝图到可操作实体的转变。

第四章:驱动程序绑定与设备实例

5.1 设备与驱动的匹配模型

在Linux设备模型中,一个设备只有在找到与其匹配的驱动程序时,才会被“激活”并进行初始化 。当一个新的设备实例(例如一个

platform_device)被注册到总线上时,内核的驱动核心会自动遍历所有已注册的驱动程序,寻找与该设备匹配的项 。

platform_bus上的匹配过程由platform_match()函数实现 。该函数会尝试多种匹配方式,其中最重要的一种就是基于设备树的兼容性匹配 。

5.2 DT与驱动的“兼容”桥梁

设备树驱动绑定机制的核心是compatible字符串 。这是一种在设备和驱动之间建立“契约”的机制:

  • DT端:设备树节点通过compatible属性来声明其所代表的硬件类型 。该属性是一个字符串列表,由最具体的兼容性字符串到最通用的兼容性字符串排列 。

  • 驱动端:驱动程序通过定义一个名为of_match_tableconst struct of_device_id数组来声明自己支持的compatible字符串 。这个数组通常通过

    MODULE_DEVICE_TABLE(of,...)宏导出,供内核驱动核心识别 。

匹配过程由of_match_device()函数执行 。当一个

platform_device被注册时,内核会调用此函数,将设备的compatible字符串列表与所有已注册驱动的of_match_table进行比较,如果找到了匹配项,则认为匹配成功 。

表2:DT节点与驱动匹配示例

模块关键元素示例内容作用
设备树DT节点路径&i2c1 {... my_device@4a {... } }定义一个在I2C总线1上,地址为0x4a的设备
compatible属性compatible = "acme-inc,my-device";

声明该设备的硬件兼容性字符串

驱动程序of_match_tablestatic const struct of_device_id my_device_of_match = { {.compatible = "acme-inc,my-device", }, { } }; MODULE_DEVICE_TABLE(of, my_device_of_match);

声明该驱动程序支持“acme-inc,my-device”这个兼容性字符串

probe()函数static int my_device_probe(struct platform_device *pdev) {... }

匹配成功后,内核调用的初始化函数

上表展示了compatible字符串如何成为连接设备树和驱动程序的“魔术字符串”。一个在设备树中声明的设备节点,只有当其compatible字符串与某个驱动程序的of_match_table中的一项完全匹配时,才会被内核成功绑定。

5.3 probe()函数的调用与驱动的参与

当内核找到匹配的设备与驱动后,它会自动调用驱动程序提供的probe()函数 。例如,对于

platform_driver,其platform_driver结构体中的probe()函数会被调用,并将对应的platform_device实例作为参数传入 。

probe()函数是驱动程序参与设备初始化的起点 。在该函数中,驱动程序会:

  1. 获取DT节点:从传入的platform_device实例中,通过dev->of_node指针获取到对应的struct device_node

  2. 读取属性:使用of_*家族API(例如of_get_property()of_get_next_child())从DT节点中读取硬件配置信息 。这些信息可能包括内存映射地址(

    reg)、中断号(interrupts)、GPIO引脚配置(gpios)等 。

  3. 初始化硬件:利用读取到的信息,驱动程序对硬件进行具体的初始化和配置 。例如,I2C控制器驱动会根据DT中的

    clock-frequency属性设置总线速度 。

  4. 注册设备:完成初始化后,驱动程序会向内核注册更高层级的设备接口,例如字符设备、块设备或网络设备 。

第五章:从DT节点到/sys目录的最终映射

6.1 kobject基础设施与设备模型的基石

/sys文件系统是一个虚拟文件系统,它的主要功能是将内核中的数据结构和关系以目录和文件的形式,层次化地暴露给用户空间 。

sysfs的核心是kobject

kobject是Linux内核中用于描述和管理对象的通用基础设施 。每个

kobject都代表内核中的一个对象,并在/sys中对应一个目录。struct device(如platform_device)是内嵌了kobject的更高级抽象 。当

struct device被注册时,其内嵌的kobject也会被注册,从而在sysfs中自动创建相应的目录结构 。

6.2 probe()函数中的/sys目录生成

当驱动程序的probe()函数成功返回后,内核设备模型会注册这个设备实例 。这个注册过程会自动触发

kobject的创建和注册,从而在/sys/devices/platform/.../sys/bus/i2c/devices/...等目录下创建对应的设备目录 。这些目录的名称通常基于设备的

nameid

6.3 驱动程序如何创建属性文件

设备目录创建后,驱动程序可以在其中添加属性文件,以暴露设备的运行时状态或提供配置接口 。这些属性文件由

struct device_attribute结构体定义,其中包含了文件的名称、访问权限以及两个关键的回调函数:show()store()

  • show():当用户空间应用程序通过cat等命令读取/sys下的属性文件时,内核会调用这个函数 。驱动程序会执行相应的逻辑,将设备的实时状态或信息写入内核提供的缓冲区,然后返回给用户空间 。

  • store():当用户写入属性文件时,内核会调用这个函数 。驱动程序会接收用户写入的数据,并据此配置或控制硬件 。

值得注意的是,用户在/sys下看到的设备节点并非DT节点的直接镜像,而是内核设备模型在运行时生成的抽象。DT节点是静态的硬件描述,而/sys文件是动态的软件接口。DTB的原始二进制数据可以在/sys/firmware/devicetree/base/proc/device-tree下以只读形式访问 。而

/sys/devices/...下的文件则是由设备驱动程序在probe()阶段动态创建的。/sys接口的背后是show()store()回调函数实现的具体设备控制逻辑,这些逻辑根据DT中提供的静态数据(如寄存器地址、中断号)来操作硬件 。因此,

/sys是内核提供给用户空间的标准化接口,而DT则是驱动程序实现这些接口所需的“设计蓝图”。

结论与展望

7.1 完整流程回顾

设备树在Linux内核启动过程中扮演着至关重要的角色,其解析流程可概括为以下步骤:

  1. DTB的准备:开发者使用dtc工具将.dts源文件编译成DTB二进制文件,并将其与内核镜像一同存储在启动介质中 。

  2. DTB的传递:引导加载程序(如U-Boot)将DTB加载到RAM中,进行必要的运行时修改,并将DTB的物理地址通过特定的CPU寄存器(如ARM上的R2)传递给内核 。

  3. 早期解析:内核在早期启动阶段(pre-MMU),利用libfdt库调用of_scan_flat_dt()函数,提取内存布局和bootargs等关键信息 。

  4. 构建数据结构:MMU启用后,unflatten_device_tree()函数将DTB的扁平化二进制数据转换成可供内核高效访问的struct device_node树形数据结构 。

  5. 设备实例化:内核设备模型遍历device_node树,根据节点的compatible属性创建对应的platform_device或其他总线设备实例 。

  6. 驱动绑定与probe:内核驱动核心将新创建的设备实例与已注册的驱动程序的of_match_table进行匹配。匹配成功后,内核调用驱动的probe()函数 。

  7. sysfs文件创建:在probe()函数中,驱动程序根据DT信息初始化硬件,并利用kobject基础设施在/sys目录下创建设备目录和属性文件,从而向用户空间暴露可读写的设备接口 。

7.2 DT的未来与意义

设备树机制的引入是嵌入式Linux发展的一个里程碑,它将硬件描述从内核源码中分离,使开发者能够使用同一个内核二进制文件来支持不同的硬件平台 。这种从硬编码到数据驱动的范式转变极大地提高了内核的可维护性和可移植性。展望未来,设备树仍然是现代嵌入式系统不可或缺的一部分。例如,设备树Overlay(DTO)机制允许在运行时动态地加载和应用硬件配置,使得系统能够支持热插拔设备或可配置的扩展板 。设备树的持续演进和广泛应用确保了Linux在快速变化的嵌入式硬件生态中保持其强大的适应性和灵活性。

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于深度学习的污水新冠RNA测序数据分析系统

基于深度学习的污水新冠RNA测序数据分析系统 摘要 本文介绍了一个完整的基于深度学习技术的污水新冠RNA测序数据分析系统,该系统能够从未经处理的污水样本中识别新冠病毒变种、监测病毒动态变化并构建传播网络。我们详细阐述了数据处理流程、深度学习模型架构、训练…

宝塔面板配置Nacos集群

一、环境准备 准备三台及以上的服务器,我这里准备了3台服务器,172.31.5.123~125;分别安装好宝塔面板,软件商店里安装nacos;二、Nacos集群配置 配置数据库连接:​ 进入每台服务器上 Nacos 解压后…

Spring Boot 3.x 全新特性解析

Spring Boot 是企业级 Java 开发中最常用的框架之一。自 Spring Boot 3.x 发布以来,其引入的一系列重大变更与优化,为开发者提供了更现代、更高效的开发体验。本文将重点解析 Spring Boot 3.x 的关键特性及其对项目架构的影响。 一、基于 Jakarta EE 10 …

2025.8.10总结

今天晚上去跑了2公里,跑完还挺爽的,然后花了1.5个小时去公司刷题,没有进行限时练,花了一周的时间才做完这题,共找了20个bug,虽然没有进行限时练,但我仿佛对测试技术掌握得更好了,知道…

qt中实现QListWidget列表

使用最基本的QListWidgetItem来创建列表项,具体使用下面setText、setIcon、addItem这三个方法#include "mainwindow.h" #include "ui_mainwindow.h" #include "QDebug"enum CustomRoles {IdRole Qt::UserRole, // 存储IDPhoneR…

nginx-主配置文件

nginx-主配置文件一、主配置文件nginx.conf内容二、修改配置的文件后的操作三、配置虚拟主机的域名1. 修改nignx.conf配置文件2. 新建域名对应的网页根目录3. 重载nginx配置4. 验证一、主配置文件nginx.conf内容 [rootweb1 conf]# cat nginx.conf#user nobody; # nginx woke…

DBSACN算法的一些应用

以下是 DBSCAN 算法在 Python 中的几个典型应用示例,涵盖了基础使用、参数调优和可视化等方面:import numpy as np import matplotlib.pyplot as plt from sklearn.cluster import DBSCAN from sklearn.datasets import make_moons, make_blobs from skl…

java9学习笔记-part1

G1 成为默认垃圾回收器在 Java 8 的时候,默认垃圾回收器是 Parallel Scavenge(新生代)Parallel Old(老年代)。到了 Java 9, CMS 垃圾回收器被废弃了,G1(Garbage-First Garbage Collector&#x…

【github.io静态网页 怎么使用 github.io 搭建一个简单的网页?】

这里是一张展示 GitHub Pages 静态网站架构与部署流程的示意图,可以帮助你更直观理解整个流程。 要使用 github.io(GitHub Pages)搭建一个简单的网页,你可以按照以下步骤操作: 快速入门:个人网站&#xff…

记录一次ubuntu20.04 解决gmock not found问题的过程

在电脑上源码编译moveit,系统是ubuntu20.04,有三个电脑,分别叫做A,B,C好了,A和C都可以很顺畅地走流程编译通过,但是B遇到了gmock not found的问题,一开始没当回事,感觉重装下库,或者…

Java基础编程核心案例:从逻辑到应用

Java编程的核心在于将逻辑思维转化为可执行的代码。本专栏通过8个实用案例,覆盖条件判断、循环结构、数组操作、用户交互等基础知识点,展示如何用Java解决实际问题,从简单游戏到数据计算,逐步构建编程思维。 案例一:剪…

Starlink卫星终端对星策略是终端自主执行的还是网管中心调度的?

以下文章首先来源于Google Gemini的Deep Research的内容,在Deep Research的报告参考了SpaceX公开信息、FCC技术报告、相关专利(如US9906292B2)以及学术研究的综合分析,并参考了RFWirelessWorld和APNIC博客等二次来源。 文章完成之后,前后发给了Grok和deepseek,让Grok和d…

【CDA案例】数据分析案例拆解:解锁数据分析全流程!

在当今数字化时代,数据如同一座座金矿,蕴含着巨大的价值。企业、组织乃至个人都渴望从海量的数据中挖掘出有用的信息,以指导决策、优化运营、提升竞争力。今天我们以一个实际的数据分析案例为蓝本,深入拆解其全过程,带…

vulnhub-drippingblues靶场攻略

1.打开靶场,我们将网络连接方式改为NAT模式2.然后使用nmap扫描一下nat的网段3.存在21,22,80端口我们先来看一下21端口的ftp协议,发现可以直接匿名登录,并且可以下载存在的东西4.但是这个压缩包被加密了,我们…

afsim2.9_使用QtCreator和VSCode编译

使用QtCreator和VSCode编译AFSIM2.9源代码指南 准备工作 在开始编译AFSIM2.9源代码前,需要确保您的开发环境满足以下条件: 安装QtCreator安装Visual Studio Code(最新稳定版)获取AFSIM2.9源代码包安装必要的编译工具链&#xf…

TC39x STM(System Timer)学习记录

STM有哪些特性?自由运行的 64 位计数器所有 64 位可同步读取可同步读取 64 位计数器的不同 32 位部分基于与 STM 部分内容的比较匹配,灵活地产生服务请求在应用复位后自动开始计数若 ARSTDIS.STMxDIS 位清零,应用复位将复位 STM 寄存器&#…

css初学者第四天

<1>snipaste工具的使用snipaste是一个简单但强大的截图工具&#xff0c;也可以让你将截图贴回屏幕上。常用的快捷方式&#xff1a;1、F1可以截图&#xff0c;同时测量大小&#xff0c;设置箭头 书写文字等2、F3在桌面置顶显示3、点击图片&#xff0c;alt可以取色&#xf…

CompletableFuture实现Excel 多个sheet页批量导出

CompletableFuture实现Excel 多个sheet页批量导出 文章目录 CompletableFuture实现Excel 多个sheet页批量导出 为什么不能直接合并文件或Sheet? 我的方案合理性 1. 操作实现步骤 1.1、导入所需要的依赖 1.2 、Excel 导入导出对象 1.3、异步生成 Excel 文件到指定路径 1.4、合并…

搭建本地 Git 服务器

以下是搭建本地 Git 服务器的通用步骤&#xff0c;支持团队协作或私有仓库管理&#xff1a;方法 1&#xff1a;基于 SSH 的简单部署&#xff08;适合小团队&#xff09; 步骤 1&#xff1a;安装 Git 在服务器上安装 Git&#xff08;以 Ubuntu 为例&#xff09;&#xff1a; sud…

【Matplotlib】中文显示问题

中文显示问题本地Mac上作图&#xff0c;可以方便地实现中文字体显示。比如在Jupter中&#xff0c;通过&#xff1a;方法一&#xff1a;不下载字体库即可实现中文显示 (MAC)plt.rcParams[font.family][Arial Unicode MS]方法二&#xff1a;下载指定字体训即可实现中文显示plt.rc…