[{"content":"Linux三板斧 U-Boot 编译uboot **u-boot.bin**只包含 U-Boot proper（第二阶段引导程序)\n**u-boot-with-spl.bin **= SPL（第一阶段） + U-Boot proper（第二阶段） + 可能的对齐/头部/校验/填充。\nBootROM 存放在Soc内部的ROM，这个ROM的size很小。\n作用：\n**确定启动源 **,根据Boot 引脚，决定从SPI NOR 还是SPI NAND ,eMMC,SDK启动。 **初始化最最基础的硬件，**加载 SPL 所必需的最少部分 极少量时钟 SPI / NAND / eMMC 的 只读能力 内部 SRAM（供 SPL 使用） 不初始化 DRAM 把 SPL 读到 SRAM （可选）做安全校验（Secure Boot） 校验签名 校验 hash 校验证书链 跳转执行 SPL SPL (Secondary Program Loader) 在CPU刚开始上电的时候，没有 DRAM，没有 C 运行环境，栈都没有。你如果想跑u-boot程序，前提就是DRAM初始化完成。\n作用：\n初始化最基本的硬件尤其是 DRAM 提供“最小运行环境 设置栈（stack） 设置 BSS 关闭/简单配置 cache 让 C 代码能跑起来 从启动介质加载 U-Boot proper 如果启动介质：SPI NOR 初始化 SPI 从固定 offset 读取 u-boot.bin 把它搬到 DRAM 查看uboot的启动参数 在 U-Boot 交互命令行中查看\nprintenv bootcmd 查看 /proc/cmdline\ncat /proc/cmdline Kernel 编译内核 君正A1N\nmake ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- distclean make ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- isvp_a1_calf_defconfig make ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- menuconfig make ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- uImage -j8 富瀚MC6830\nmake ARCH=arm CROSS_COMPILE=arm-mol-linux-uclibcgnueabihf- menuconfig make ARCH=arm CROSS_COMPILE=arm-mol-linux-uclibcgnueabihf- -j8 #编译出现问题 cp .config my_config_backup make ARCH=arm CROSS_COMPILE=arm-mol-linux-uclibcgnueabihf- mrproper #会把.config删除 make ARCH=arm CROSS_COMPILE=arm-mol-linux-uclibcgnueabihf- olddefconfig 当环境乱掉了 # 1️⃣ 先备份配置 cp .config my_config_backup # 2️⃣ 彻底清理 make ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- mrproper # 或 distclean（更彻底，但通常 mrproper 就够） # 3️⃣ 恢复配置 cp my_config_backup .config # 4️⃣ 让内核“消化”你的配置 #读取当前的.config,对比Kconfig定义 #找出.config中缺失的新选项 #缺失的选项全部填入默认值（不询问，静默处理 #更新 .config 后退出 make ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- olddefconfig # 5️⃣ 再编 make ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- uImage -j$(nproc) 添加新选项 #添加新选项 make menuconfig #编译内核 make ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- -j8 uImage 内核的产物 一般放在arch/架构/boot里\nuImage uImage的本质是zImage + U-Boot image header 通过 mkimage 对普通的压缩内核映像文件（zImage）加工而得 一般由bootm启动 +-----------------------+ | U-Boot Image Header | 64 bytes +-----------------------+ | zImage / Image data | +-----------------------+ header 里包含：\n字段 作用 Magic 识别是不是 uImage Load Addr 加载地址 Entry Addr 跳转地址 Size 内核大小 CRC 校验 Type kernel / ramdisk zImage 解压器 + 压缩后的内核\n可自解压的压缩内核镜像\n一般由bootz启动\nzImage通常包含：\n内容 说明 解压启动代码 一小段架构相关的汇编/C代码，负责准备环境、解压内核 压缩后的内核数据 由 vmlinux 转换、压缩后得到 入口地址信息 供 bootloader 或自身解压逻辑跳转使用 可选 initramfs 如果内核配置了 CONFIG_INITRAMFS_SOURCE，可能会被打进内核 通常不包含 U-Boot header 这个是 uImage 才有的 bootz：\nbootz \u0026lt;kernel_addr\u0026gt; \u0026lt;initrd_addr\u0026gt; \u0026lt;fdt_addr\u0026gt; 第1个参数：Linux 内核 zImage 在 RAM 中的地址 第2个参数：initrd/initramfs 地址，没有则用 - 表示没有 第3个参数：Device Tree Blob(DTB) 地址 Device Tree 设备树专题见：dts.md。\n制作根文件系统 ​\t成功安装 SDK 后，在 resource/rootfs 目录中存放已制作好的根文件系统，可以跳过文件系统的制作，直接使用 SDK 中提供的文件系统。如果用户如果有特殊需求，可在 busybox 的基础上制作根文件系统。制作根文件系统的具体操作步骤如下\n**第一步：**建立根文件系统文件 rootfs\n#mkdir rootfs #cd rootfs #cp –R opensource/busybox/_intsall/* ./ #mkdir etc dev lib tmp var mnt system proc **第二步：**配置 etc、lib、dev 等目录的必需文件(可以参考 SDK 中的文件系统)。\n​\tetc 目录可参考系统 /etc 下的文件。其中最主要的文件包括 inittab、fstab、init.d/rcS 文件等，这些文件最好从 busybox 的 examples 目录下拷贝过来，根据需求自行修改。\n​\tdev 目录下的设备文件，可以直接从系统中拷贝过来或者使用 mknod 命令生成需要的设备文件。拷贝文件时请使用 cp –R filename。\n​\tlib 目录是存放应用程序所需要的库文件，根据应用程序需要拷贝相应的库文件。\n文件系统的选择 对于 NorFlash Based 系统，存储空间一般较小，因此会选用压缩文件系统。推荐以下两种文件系统：\nA. squashfs：只读文件系统，压缩率高\rB. jffs2：可读写文件系统，可选择压缩方式 ​\t推荐的系统搭建的方案是系统 rootfs 以及不需要经常修改的系统分区采用squashfs 文件系统，而配置分区 system 等需要经常读写的分区采用 jffs2 文件系统。\nSquashfs 文件系统 ​\tsquashfs 文件系统是一套基于 Linux 内核使用的压缩只读文件系统，压缩率高。squashfs 具有如下特点：\n​\t**优点：**压缩率高，数据(data)，节点(inode)和目录(directories)都被压缩，可以节省flash 空间，最大可以支持 4G 文件系统。\n​\t**缺点：**无法写操作\n​\tsquashfs 制作方法为：\nmksquashfs \u0026lt;源目录\u0026gt; \u0026lt;输出镜像\u0026gt; [选项] -b 64K\n指定squashfs block size -comp (压缩算法)\nxz -noappend(不追加)\n强制覆盖现有的输出文件，而不是在旧文件后面追加数据。 -no-xattrs（不包含扩展属性)）\n禁止在镜像中保存文件的“扩展属性”（Extended Attributes）。 什么是扩展属性：它类似于 Windows 下的文件元数据，通常用于存储 SELinux 安全标签、文件 ACL（访问控制列表）或某些特定功能的校验值。 mksquashfs rootfs_jz_nvr_36 rootfs_36_v1 -b 64K -comp xz -noappend -no-xattrs 其中，mksquashfs 为制作 squashfs 文件系统工具可网上下载；rootfs 是之前已经制作好的根文件系统； rootfs.squashfs.img 是生成输出的 squashfs 文件系统映像文件。 -b 64K 指定 squashfs 文件系统的块大小为 64K（决定于实际 spi flash 块大小）。-comp 指定文件系统压缩方式为 xz。请根据实际情况修改参数。\nJffs2 文件系统 ​\tjffs2 是 RedHat 的 David Woodhouse 在 jffs2 基础上改进的文件系统，是用于微型嵌入式设备的原始闪存芯片的实际文件系统。jffs2 文件系统是日志结构化的**可读写**的文件系统。jffs2 的优缺点如下：\n​\t优点: 使用了压缩的文件格式。最重要的特性是可读写操作。\n​\t缺点: jffs2 文件系统挂载时需要扫描整个 jffs2 文件系统，因此当 jffs2 文件系统分区增大时，挂载时间也会相应的变长。使用 jffs2 格式可能带来少量的 Flash 空间的浪费。这主要是由于日志文件的过度开销和用于回收系统的无用存储单元，浪费的空间大小大致是若干个数据段。jffs2 的另一缺点是当文件系统已满或接近满时，jffs2运行速度会迅速降低。这是因为垃圾收集的问题。\njffs2 的制作方法为：\n# mkfs.jffs2 -o rootfs.jffs2 -r ../rootfs -e 0x10000 -s 0x1000 -n -l -X zlib --pad=0x20000\n​\t其中，mkfs.jffs2 工具可以从互联网中下载。rootfs 为之前已经制作好的根文件系统；rootfs.jffs2 为输出的镜像文件。\n参数说明：\n-d 指定根文件系统;\n-l little-endian 小端模式;\n-e flash 的块大小;\n-o 输出镜像文件;\n-s 页大小;\n\u0026ndash;pad 输出文件大小\n注意 jffs2 制作某个分区为 jffs2 文件，分区大小必须为 nor erase_size 的整数倍，这个是官方驱动的要求。\n这是因为JFFS2\n/app是squashfs文件系统， 它的大小以实际 squashfs 镜像的大小为准，而不是mtd4分区16MB\nJFFS2 是可读写的,它会用满整个 mtd5 分区, 大小略小于mtd5分区，是因为JFFS2 自身的元数据开销\n[root@Ingenic-uc1_1:smart]# df -h\rFilesystem Size Used Available Use% Mounted on\r/dev/root 1.8M 1.8M 0 100% /\rdevtmpfs 122.3M 40.0K 122.2M 0% /dev\rtmpfs 122.3M 40.0K 122.2M 0% /dev\rtmpfs 122.3M 4.0K 122.3M 0% /tmp\rtmpfs 122.3M 0 122.3M 0% /run\r/dev/mtdblock4 13.0M 13.0M 0 100% /app\r/dev/mtdblock5 11.1M 388.0K 10.7M 3% /var\rtmpfs 5.0M 0 5.0M 0% /var/run mdev ​\t在 Linux 启动过程中，内核会先检测硬件（如 Flash、网卡、串口、I2C 设备等）。虽然内核知道了这些硬件的存在，但在用户空间（也就是你的程序里），需要通过 /dev/ttyS0 或 /dev/sda 这样的文件才能访问硬件。\nmdev 的静态扫描（-s）：在系统刚启动时，/dev 目录下通常是空的（如果是挂载的 tmpfs）。执行 mdev -s 会强制内核重新遍历整个 /sys/class 和 /sys/block 目录，把已经存在的硬件信息找出来，批量在 /dev 下生成节点。 如果不执行：你会发现 /dev 目录下空空如也，你的 NVR 程序可能会报错“找不到设备”。 它是如何工作的？（原理） mdev 并不是凭空想象设备名字的，它依赖于 sysfs 文件系统：\n内核启动后，会将所有设备信息导出到 /sys 目录下。 执行 mdev -s 时，它会递归扫描 /sys/class 和 /sys/block。 它会寻找每个设备目录下的 dev 文件（这个文件记录了设备的主次设备号）。 mdev 读取这些编号，结合 /etc/mdev.conf 中的规则，在 /dev 下调用 mknod 创建对应的设备节点。 启动流程 上电\r↓\rBoot ROM\r↓\rBootloader，例如 U-Boot\r↓\r加载并启动 Linux kernel\r↓\r内核初始化\r↓\r挂载根文件系统 rootfs\r↓\r启动 PID 1：/sbin/init\r↓\rinit 读取 /etc/inittab\r↓\r执行 /etc/init.d/rcS 等初始化脚本\r↓\r启动 getty、业务程序和后台服务\r↓\r进入可登录、可工作的系统 挂载根文件系统 flash上的根文件系统 root=/dev/mtdblock3 rootfstype=squashfs 内核启动后会直接尝试挂载这个设备作为 /，然后执行：/sbin/init\n这种流程是：U-Boot \u0026ndash; \u0026gt; kernel \u0026ndash;\u0026gt; 直接挂载 flash 上的 rootfs \u0026ndash;\u0026gt; 执行 /sbin/init\n适合：\nflash 分区固定\rrootfs 类型固定\r驱动全部内建进 kernel\r不需要升级判断\r不需要解密\r不需要校验\r不需要选择 A/B 分区 内存里的initramfs initramfs 的作用是“先有一个能跑脚本的最小系统”，initramfs.cpio.gz 会被 kernel 解压到 RAM 里，形成一个临时 /\n里面通常有\n/init\r/bin/busybox\r/bin/sh\r/dev\r/proc\r/sys\r/etc/init.d/rcS 它可以做很多 kernel 直接 mount rootfs 前做不了，或者做起来不方便的事。\nflash/rootfs 设备还没准备好 NAND flash、SPI-NOR、eMMC、SATA 硬盘、SD 卡有些设备不是 kernel 一启动就能立刻稳定可用。如果 kernel 直接 mount，设备还没出来，就会报：VFS: Cannot open root device\ninitramfs 可以先循环等待：\nwhile [ ! -e /dev/mmcblk0p2 ]; do sleep 1 done 然后再挂载真正 rootfs。\n需要先加载驱动或创建设备节点 直接挂载 rootfs 要求相关驱动已经编进 kernel：\nMTD 驱动\ninit进程 init 是内核启动的第一个用户态进程。它负责把系统从 \u0026ldquo;内核已经启动\u0026rdquo; 推进到\u0026quot;业务可以运行\u0026quot;。既是用户空间初始化管理者，也是PID 1进程。\n在 BusyBox 系统里，主要职责通常是：\n读取 /etc/inittab；\n执行系统初始化脚本；\n启动登录终端；\n启动需要常驻的进程；\n回收孤儿进程；\n在系统重启、关机时运行相应命令；\n对 respawn 类型的进程进行重启。\n/etc/inittab文件 /etc/inittab 是 init的配置文件。\n它告诉 init：\n系统初始化时执行什么； 哪些程序只运行一次； 哪些程序退出后要重新启动； 哪个终端需要启动登录程序； 按下特定组合键时执行什么； 重启和关机时执行什么。 inittab的内容：\n# Startup the system ::sysinit:/sbin/swapoff -a ::sysinit:/bin/mount -t tmpfs tmpfs /dev ::sysinit:/bin/mkdir -p /dev/pts ::sysinit:/bin/mkdir -p /dev/shm ::sysinit:/bin/mount -a ::sysinit:/bin/hostname -F /etc/hostname # now run any rc scripts ::sysinit:/etc/init.d/rcS # Put a getty on the serial port console::respawn:/sbin/getty -L console 115200 vt100 # GENERIC_SERIAL 常见的action action 作用 sysinit 系统初始化时优先执行 wait 执行并等待结束 once 执行一次，不等待永久常驻 respawn 退出后重新启动 askfirst 按 Enter 后启动，退出后再次等待 shutdown 关机或重启时执行 ctrlaltdel 收到 Ctrl+Alt+Delete 时执行 restart init自身重启时执行 /etc/inittab里的格式：\n\u0026lt;id\u0026gt;:\u0026lt;runlevels\u0026gt;:\u0026lt;action\u0026gt;:\u0026lt;process\u0026gt; 例子：\nconsole : 空 : respawn : /sbin/getty -L console 115200 vt100\r│ │ │ │\r│ │ │ └─ 要启动的程序\r│ │ └─ 退出后重新启动\r│ └─ runlevels，BusyBox 中通常忽略\r└─ 标识符，同时常用于指定终端 getty：打开 TTY，提示输入登录名，然后调用 /bin/login。\n/bin/login 主要负责：\n查找用户信息，例如 /etc/passwd 检查密码，例如 /etc/shadow 检查用户是否允许登录 设置 UID、GID 设置 HOME、USER、SHELL、PATH 等环境变量 切换到用户主目录 启动用户 shell 那么认证成功后，login 会启动：/bin/sh ，此时你才真正看到 shell 提示符：\n[root@Ingenic-uc1_1:~]# /etc/passwd 格式 #用户名:密码字段:UID:GID:描述:家目录:登录Shell root:x:0:0:root:/root:/bin/sh 字段名称 示例值 详细含义 用户名 (Username) root 用户登录系统时输入的名称。 密码占位符 (Password) x 早期这里存放加密后的密码。出于安全考虑，现代 Linux 将密码加密串移到了 /etc/shadow 文件中，这里仅用 x 占位。 用户 ID (UID) 0 系统的唯一数字标识。0 永远代表超级用户 root。通常系统用户的 UID 为 1~999，普通用户从 1000 开始。 组 ID (GID) 0 用户所属**主组（Primary Group）**的数字标识，对应 /etc/group 中的记录。 用户信息说明 (GECOS) root 备注字段，通常写用户的真实姓名、办公地址或电话。一般可以为空。 家目录 (Home Dir) /root 用户登录成功后，默认进入的初始工作目录。普通用户通常是 /home/用户名。 登录 Shell (Shell) /bin/bash 用户登录后由哪个程序来解析命令。如果是 /sbin/nologin 或 /usr/sbin/nologin，则代表该用户不允许登录系统（常用于系统服务账号）。 ","permalink":"https://plhwiki.cn/basic/%E5%9F%BA%E6%9C%AC%E5%8A%9F/","summary":"\u003ch1 id=\"linux三板斧\"\u003eLinux三板斧\u003c/h1\u003e\n\u003ch2 id=\"u-boot\"\u003eU-Boot\u003c/h2\u003e\n\u003ch3 id=\"编译uboot\"\u003e编译uboot\u003c/h3\u003e\n\u003cp\u003e\u003cimg src=\"/basic/assets/image-20260105185419377.png\" alt=\"image-20260105185419377\" loading=\"lazy\"\u003e\n\u003c/p\u003e\n\u003cp\u003e**\u003ccode\u003eu-boot.bin\u003c/code\u003e**只包含 U-Boot proper（第二阶段引导程序)\u003c/p\u003e\n\u003cp\u003e**\u003ccode\u003eu-boot-with-spl.bin\u003c/code\u003e **= SPL（第一阶段） + U-Boot proper（第二阶段） + 可能的对齐/头部/校验/填充。\u003c/p\u003e","title":"基本功"},{"content":"目标文件 目标文件的格式 ELF文件类型 说明 实例 可重定向文件(Relocatable File) 这类文件包含代码与数据，可以被链接为可执行文件或者共享目标文件，静态链接库也可以归这一类 Linux的.o文件 可执行文件 已完成链接；内核可直接加载执行 /bin/bash文件 共享目标文件(Shared Object File) 不能单独执行；可链接或运行时动态加载 Linux的.so 核心转储文件（Core Dump File） 进程内存快照；含栈/寄存器/状态信息 core 文件 目标文件的内容 ![ChatGPT Image 2026年7月2日 11_28_06](assets/ChatGPT Image 2026年7月2日 11_28_06.png)\nSection Section 是\u0026quot;编译器视角的分类\u0026quot;\n","permalink":"https://plhwiki.cn/basic/%E7%A8%8B%E5%BA%8F%E5%91%98%E8%87%AA%E6%88%91%E4%BF%AE%E5%85%BB/","summary":"\u003ch1 id=\"目标文件\"\u003e目标文件\u003c/h1\u003e\n\u003ch2 id=\"目标文件的格式\"\u003e目标文件的格式\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003eELF文件类型\u003c/th\u003e\n          \u003cth\u003e说明\u003c/th\u003e\n          \u003cth\u003e实例\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e可重定向文件(Relocatable File)\u003c/td\u003e\n          \u003ctd\u003e这类文件包含代码与数据，可以被链接为可执行文件或者共享目标文件，静态链接库也可以归这一类\u003c/td\u003e\n          \u003ctd\u003eLinux的.o文件\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e可执行文件\u003c/td\u003e\n          \u003ctd\u003e已完成链接；内核可直接加载执行\u003c/td\u003e\n          \u003ctd\u003e/bin/bash文件\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e共享目标文件(Shared Object File)\u003c/td\u003e\n          \u003ctd\u003e\u003cstrong\u003e不能单独执行\u003c/strong\u003e；可链接或运行时动态加载\u003c/td\u003e\n          \u003ctd\u003eLinux的.so\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e核心转储文件（Core Dump File）\u003c/td\u003e\n          \u003ctd\u003e进程内存快照；含栈/寄存器/状态信息\u003c/td\u003e\n          \u003ctd\u003e\u003ccode\u003ecore\u003c/code\u003e 文件\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"目标文件的内容\"\u003e目标文件的内容\u003c/h2\u003e\n\u003cp\u003e![ChatGPT Image 2026年7月2日 11_28_06](assets/ChatGPT Image 2026年7月2日 11_28_06.png)\u003c/p\u003e","title":"程序员自我修养"},{"content":"现代编译器通常采用“三段式”架构，分为：前端（Frontend）、中端（Optimizer，优化器）和后端（Backend，代码生成器）。前端的任务是把程序员编写的、具备特定语法的高级语言源代码（如 C++、Java、Python），翻译成一种与具体硬件平台无关的中间表示（IR，Intermediate Representation），同时检查代码中的语法和语义错误。\nclangd clangd 是一个基于 Clang 的 C/C++ 语言服务器。它负责给 VS Code、Vim、CLion 等编辑器提供：代码跳转、自动补全、查找引用、实时语法检查、重命名、查看类型、函数签名、参数提示\ncompile_commands.json [ { \u0026#34;directory\u0026#34;: \u0026#34;/home/plh/work/nvr/build\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;/opt/toolchain/bin/arm-linux-g++ -std=c++11 -DPLATFORM_A1 -I../modules/base/include -I../modules/media/include -c ../modules/decode/DecoderManager.cpp\u0026#34;, \u0026#34;file\u0026#34;: \u0026#34;../modules/decode/DecoderManager.cpp\u0026#34; } ] file： 这是哪个源文件的编译命令\ndirectory： 编译命令在哪个工作目录执行。相对路径需要以这个目录为基准解析。\n**command：**表示完整编译命令\n编译器 -I 头文件路径 -D 宏定义 -std=c++11 平台参数 目标架构参数 如何实现代码跳转 假设有以下代码：\nDecoderManager::instance().addSlot(channel, streamType); 当你点击 addSlot 跳转时，clangd 大致做了以下工作\n找到这个 .cpp 的编译命令：clangd 从 compile_commands.json 中找到对应记录：\n{ \u0026#34;file\u0026#34;: \u0026#34;DecoderManager.cpp\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;g++ -I... -D... -std=c++11 ...\u0026#34; } 按照真实参数解析源码\n使用Clang前端处理:\n#include \u0026#34;DecoderManager.h\u0026#34; 建立 AST clangd 会将代码构建成 AST（Abstract Syntax Tree，抽象语法树）。\nCallExpr\r├── MemberExpr: DecoderManager::addSlot\r├── DeclRefExpr: channel\r└── DeclRefExpr: type 因此 clangd 知道这里调用的是：\nDecoderManager::addSlot 而不是另一个类中同名的 addSlot。\n建立符号索引 clangd 会给项目中的符号建立索引，例如：\nDecoderManager\rDecoderManager::addSlot\rDecoderManager::removeSlot\rChannelSource::subscribe\rStreamHub::start Clang 编译器 Clang 前端 负责理解 C、C++、Objective-C 代码，包括：\n处理 #include 处理 #define 识别关键字和标识符 检查语法 检查类型 解析模板 解析函数重载 建立 AST 例如：\nFoo* p = new Bar; 前端会检查：\nFoo 是否是合法类型 Bar 是否已定义 Bar* 能否转换为 Foo* 是否存在继承关系 构造函数能否调用 如果不合法，前端直接报错。\n","permalink":"https://plhwiki.cn/basic/%E7%BC%96%E8%AF%91%E5%99%A8/","summary":"\u003cp\u003e现代编译器通常采用“三段式”架构，分为：\u003cstrong\u003e前端（Frontend）、中端（Optimizer，优化器）和后端（Backend，代码生成器）\u003c/strong\u003e。前端的任务是把程序员编写的、具备特定语法的高级语言源代码（如 C++、Java、Python），翻译成一种与具体硬件平台无关的\u003cstrong\u003e中间表示（IR，Intermediate Representation）\u003c/strong\u003e，同时检查代码中的语法和语义错误。\u003c/p\u003e","title":"编译器"},{"content":"","permalink":"https://plhwiki.cn/hardware/%E6%A8%A1%E7%94%B5/","summary":"","title":"模电"},{"content":"模块机制 可加载模块的概念 ​\n微内核：只保留核心模块，易维护、低性能\n内核许可声明 协议分类 Page Cache ​\tPage Cache 就是内核为了加速文件读写而在内存中保留的文件页。你每次 read/write 文件时，其实不是直接访问硬盘，而是在和 Page Cache 互动。\n产生 调用\nwrite(fd, buffer, len); 这个时候你只是拿着一个缓冲区（Userspace Page），把数据交给内核。\n内核检查：\n文件在该 offset 对应的 page cache 是否存在？ 如果不存在 → 会触发缺页（page fault）并分配 page cache 页。 meian@ubuntu200406:~/work/MyProject/study$ sar -B 1 Linux 5.4.0-153-generic (ubuntu200406) 11/13/2025 _x86_64_\t(8 CPU) 07:23:57 PM pgpgin/s pgpgout/s fault/s majflt/s pgfree/s pgscank/s pgscand/s pgsteal/s %vmeff 07:23:58 PM 0.00 0.00 9.00 0.00 15.00 0.00 0.00 0.00 0.00 07:23:59 PM 0.00 0.00 8.00 0.00 16.00 0.00 0.00 0.00 0.00 07:24:00 PM 0.00 0.00 15.00 0.00 15.00 0.00 0.00 0.00 0.00 07:24:01 PM 0.00 40.00 33.00 0.00 28.00 0.00 0.00 0.00 0.00 07:24:02 PM 0.00 0.00 22.00 0.00 15.00 0.00 0.00 0.00 0.00 07:24:03 PM 0.00 404.00 11.00 0.00 15.00 0.00 0.00 0.00 0.00 07:24:04 PM 0.00 0.00 12.00 0.00 15.00 0.00 0.00 0.00 0.00 07:24:05 PM 0.00 0.00 16.00 0.00 15.00 0.00 0.00 0.00 0.00 07:24:06 PM 0.00 44.00 150.00 0.00 252.00 0.00 0.00 0.00 0.00 07:24:07 PM 0.00 0.00 25.00 0.00 15.00 0.00 0.00 0.00 0.00 pgpgin/s Page cache 读入量（KB/s） 定义：从磁盘读入 page cache 的速率（KB/s）。\n你的程序读磁盘 → 系统把数据读到 Page Cache → pgpgin/s 就会上升。\npgpgout/s — Page cache 写出量（KB/s） 定义：把 page cache 写回磁盘的速率（KB/s）。\n你的程序 write 文件 → 内核把 dirty page 刷到磁盘 → pgpgout/s 上升。\nfault/s — minor page faults（轻微缺页） 定义：轻微缺页次数（不需要读磁盘的 page fault）。\n访问了一块不在进程页表里的内存，但数据已经在 RAM 中（比如 page cache），只是缺少映射。\nmajflt/s — major page faults（严重缺页） 定义：必须从磁盘读数据才能满足的缺页。\n​\t内存里没有对应内容 → 必须读硬盘 → 最慢的那种 page fault。\npgscank/s — kswapd 扫描的页数（后台扫描） 定义：kswapd（后台内存回收线程）扫描的页数量（per-second）。\n系统检测到内存压力 → 内核后台回收线程试图找可以回收的内存。\npgscand/s — direct reclaim 扫描的页数（应用线程主动扫描） 定义：应用进程在内存不足时，被迫参与内存回收所扫描的页数。\n进程自己在扫描内存 → 内存已经很紧张 → 接近 OOM。\npgsteal/s — 每秒成功回收的 page 数 定义：通过扫描找到并回收掉的 page 数量。\n系统正在回收 page cache 或匿名页。\n","permalink":"https://plhwiki.cn/linux/common/linux%E5%86%85%E6%A0%B8/","summary":"\u003ch1 id=\"模块机制\"\u003e模块机制\u003c/h1\u003e\n\u003ch2 id=\"可加载模块的概念\"\u003e可加载模块的概念\u003c/h2\u003e\n\u003cp\u003e​\u003c/p\u003e\n\u003cp\u003e微内核：只保留核心模块，易维护、低性能\u003c/p\u003e\n\u003ch2 id=\"内核许可声明\"\u003e内核许可声明\u003c/h2\u003e\n\u003ch3 id=\"协议分类\"\u003e协议分类\u003c/h3\u003e\n\u003cp\u003e\u003cimg src=\"/linux/common/assets/image-20250628134458427.png\" alt=\"image-20250628134458427\" loading=\"lazy\"\u003e\n\u003c/p\u003e\n\u003ch1 id=\"page--cache\"\u003ePage  Cache\u003c/h1\u003e\n\u003cp\u003e​\tPage Cache 就是内核为了加速文件读写而在内存中保留的文件页。你每次 read/write 文件时，其实不是直接访问硬盘，而是在和 Page Cache 互动。\u003c/p\u003e","title":"linux内核"},{"content":"Linux 干货速查 面向 Ubuntu 开发主机 + BusyBox 嵌入式开发板的个人速查。\n约定\n⚠️ 标记危险命令（误用会丢数据 / 断网 / 毁分区），执行前务必确认设备名和路径。 命令行为在不同实现下可能不同，差异处标注：（GNU）Ubuntu 自带 / （BusyBox）板子 / （systemd）。 拿不准的地方标注「待确认」，不猜。 目录 Shell 与环境变量 文件与目录操作 文本搜索与处理 磁盘、分区与文件系统 内存、CPU 与 I/O 排查 网络配置与抓包 文件传输与共享 systemd 与系统服务 设备管理：udev 与 uevent 交叉编译工具链 常见故障与排查案例 Shell 与环境变量 重定向 从文件读入 stdin：\ngrep \u0026#34;search_term\u0026#34; \u0026lt; file.txt # 等价 grep \u0026#34;search_term\u0026#34; file.txt 注：原写法 cat \u0026lt; file.txt | grep ... 里的 cat 是多余的，直接 grep ... file.txt 即可（避免 useless use of cat）。\n符号 作用 \u0026gt; / \u0026gt;\u0026gt; 覆盖 / 追加 stdout 2\u0026gt; 重定向 stderr 2\u0026gt;\u0026amp;1 stderr 合并到 stdout \u0026amp;\u0026gt; file stdout+stderr 一起（bash 专有；BusyBox ash 不支持，用 \u0026gt; file 2\u0026gt;\u0026amp;1） \u0026lt; file 从文件读 stdin 持久化环境变量 写进 ~/.bashrc（当前用户）或 /etc/profile（全局），常用于把工具链、代理等加进 export PATH / export XXX。工具链一键配置见 交叉编译工具链。\n文件与目录操作 分类：复制/移动/删除 · 链接 · 时间戳 · 归档压缩 · 查找\ncp 复制文件/目录。\ncp -a src/ dst/ # 归档式复制：保留权限/属主/软链接/时间戳（= -dpR） -a：等于 -d -p -R 的集合 -d：源为软链接时，目标也建为软链接（对硬链接无效） -r / -R：递归复制目录 -l：把目标建为源文件的硬链接，而非复制 -s：把目标建为源文件的软链接，而非复制 ln 创建链接。\nln -s mke2fs mkfs.ext2 # 软链接：ln -s [目标] [链接名] -s：创建软链接（不加 -s 是硬链接） 软链接特点：可跨分区/文件系统、可指向目录；源被删则变成\u0026quot;死链接\u0026quot;（ls 里常显红色） touch 修改时间戳 / 创建空文件。\ntouch file # 文件不存在则创建空文件；存在则更新 atime/mtime touch -d \u0026#34;2026-01-01\u0026#34; file 权限：root 可保留复制的属主/属组，普通用户不行。\nrm / 回收站 ⚠️ rm -rf 不可恢复，删前确认路径；尤其变量为空时 rm -rf \u0026quot;$DIR\u0026quot;/ 可能变成 rm -rf /。\n日常建议用 trash-cli（开发主机）代替 rm：\ntrash-put /path/to/file # 删除到回收站 trash-list # 查看回收站 trash-restore # 恢复 trash-empty # 清空 trash-empty \u0026lt;days\u0026gt; # 清空 N 天前的 归档与压缩 tar tar -zcvf out.tar.gz dir/ # 打包 + gzip tar -zxvf out.tar.gz # 解压 gzip tar -Jxvf out.tar.xz # 解压 xz tar -ztvf out.tar.gz # 只看内容不解压 -c 建立归档 / -x 解压 / -t 看内容 / -r 追加 / -u 更新 -f 指定档案名，必须是最后一个参数，后面紧跟文件名 -z gzip / -j bz2 / -J xz / -Z compress(.Z) -v 显示过程 / -O 解到标准输出 （BusyBox）tar 是子集，部分构建不带 -J/-j/-Z，需外部 xz/gzip 配合。\nunzip unzip file.zip # 解压到当前目录 unzip file.zip -d /target/ # 解压到指定目录 -d 目录 指定输出目录 / -l 只列内容 / -o 覆盖不询问 / -n 不覆盖 / -q 安静 / -j 不建目录 7za 7za x archive.7z -r -o./ # 按原目录树解压到当前目录 x 按原始目录树解压（e 会把所有文件解到同一层） -r 递归子目录 / -o 指定输出目录，-o 后无空格直接跟路径 find find [路径] [条件] [动作]\n# 删除当前目录下除 system 外的所有第一层项（无交互）⚠️ find . -mindepth 1 -maxdepth 1 ! -name system -exec rm -rf {} + -mindepth 1：排除 . 自身 -maxdepth 1：只匹配第一层，不递归 ! -name system：排除名为 system 的项 -exec rm -rf {} +：批量删除匹配项 环境差异：{} +（批量传参）是（GNU）find；（BusyBox）find 的 -exec 多数构建只支持以 \\; 结尾，即 -exec rm -rf {} \\;（逐个执行，较慢）。\nls 列目录。\n-a 含隐藏文件 / -l 详细 / -h 人类可读大小（配合 -l） -t 按修改时间 / -S 按大小 / -r 反序 -f 不排序直接输出（大目录更快，隐含 -a） 文本搜索与处理 grep 文本搜索。\ngrep -n \u0026#34;pattern\u0026#34; file # 带行号 grep -rn \u0026#34;pattern\u0026#34; . # 递归搜索目录 grep -o \u0026#34;pattern\u0026#34; file # 只输出匹配的部分 grep \u0026#34;pattern\u0026#34; f1 f2 f3 # 多文件 # 仅（GNU）grep 支持 include/exclude： grep -rn \u0026#34;main()\u0026#34; . --include=*.{c,h,cpp} grep -rn \u0026#34;main()\u0026#34; . --exclude=\u0026#34;README\u0026#34; --exclude-from=filelist -n 行号 / -r、-R 递归 / -o 只出匹配部分 / -i 忽略大小写 / -v 反选 / -E 扩展正则 环境差异：（BusyBox）grep 支持 -n/-r/-o/-i/-v/-E；不支持 --include/--exclude/--exclude-from。\nrg（ripgrep） 比 grep 快，默认递归、默认遵守 .gitignore。开发主机用，板子一般没有。\nrg \u0026#34;pattern\u0026#34; # 当前目录递归 rg -n \u0026#34;pattern\u0026#34; -g \u0026#34;*.c\u0026#34; # 限定文件类型 rg -i \u0026#34;pattern\u0026#34; # 忽略大小写 sort 对文本按行排序。\nsort file.txt # 升序 sort -u file.txt # 排序并去重 sort -rn file.txt # 按数值倒序 sort -t: -k2 file.txt # 以 \u0026#34;:\u0026#34; 分隔，按第 2 列排 -u 去重 / -r 倒序 / -n 按数值 / -k N 按第 N 列 / -t C 指定分隔符 示例：\n$ cat sort.txt aaa:10:1.1 ccc:30:3.3 bbb:20:2.2 eee:50:5.5 eee:50:5.5 $ sort -u sort.txt # 排序 + 去重 aaa:10:1.1 bbb:20:2.2 ccc:30:3.3 eee:50:5.5 scp hexdump 十六进制查看二进制。\nhexdump -C file.bin # 十六进制 + ASCII hexdump -C -s 0x100 -n 64 file # 从偏移 0x100 起看 64 字节 -C 规范的十六进制+ASCII / -n length 处理长度 / -s offset 跳过开头字节 cloc 统计代码行数（开发主机）。\ncloc --include-lang=\u0026#34;C,C++\u0026#34; . --exclude-dir=\u0026#34;Reference_project,3rdpart,SDK\u0026#34; --include-lang 只统计指定语言 / --exclude-dir 排除目录 ------------------------------------------------------------------------------- Language files blank comment code ------------------------------------------------------------------------------- C++ 62 2557 1224 11747 C 1 134 61 633 ------------------------------------------------------------------------------- SUM: 63 2691 1285 12380 ------------------------------------------------------------------------------- code：真正代码行（不含空行/注释） / comment：注释行 / blank：空行 / files：文件数 磁盘、分区与文件系统 df 看文件系统占用（排查\u0026quot;磁盘满\u0026quot;第一步）。\ndf -h # 各文件系统占用，人类可读 df -h / # 只看根 df -i # 看 inode 占用（文件数满同样导致写失败） du 看目录/文件占用。\ndu -h -d1 # 当前目录下一层各项大小 du -sh dir/ # 目录总大小 -h 人类可读 / -s 只出总计 / -d N 限制深度 环境差异：--max-depth=1 是（GNU）写法；-d1 在 GNU 和 BusyBox 均可用，更通用。\nmount 挂载文件系统。\nmount -o remount,rw / # 只读 rootfs 重挂为可写（板子常用） mount -t tmpfs -o size=64M tmpfs /mnt/ram # 挂内存文件系统 mount -t nfs 192.168.2.100:/home/meian/nfs_share /mnt # 挂 NFS -t 指定文件系统类型 / -o 选项 tmpfs：基于内存的临时文件系统，占用随写入增长，上限由 size 限定 mkfs / mke2fs ⚠️ 格式化会清空整个分区，认准设备名，写错分区会毁掉系统盘。\nsudo mkfs.ext4 /dev/sdX1 # 格式化为 ext4 mke2fs -t ext4 /dev/sdX1 # 等价；mkfs.extN 本质是指向 mke2fs 的软链接 dd ⚠️ 按扇区裸写，of= 写错设备直接覆盖目标盘、不可恢复。 先 lsblk 确认设备名，注意 /dev/sdX 是整盘、/dev/sdX1 是分区。\nsudo dd if=image.img of=/dev/sdX bs=4M status=progress conv=fsync # 烧写镜像 sudo dd if=/dev/mmcblk0p1 of=part1.img bs=4M # 备份分区为镜像 环境差异：status=progress 是（GNU）dd；（BusyBox）dd 无此选项。\n内存、CPU 与 I/O 排查 free 看内存占用。输出格式因版本差异较大：\n新版（GNU / procps-ng，Ubuntu）：关注 available（真正可立即分配的量，已扣除可回收缓存），比 free 列更有参考意义。\ntotal used free shared buff/cache available Mem: 16384000 ... ... ... ... ... Swap: ... buff/cache = Buffers + Cached + SReclaimable\n旧版（BusyBox / 老 procps，多见于板子）：带 -/+ buffers 行。\ntotal used free shared buffers Mem: 250444 95988 154456 8 1968 -/+ buffers: 94020 156424 Swap: 0 0 0 Mem 行：total 总量 / used 已分配（含内核缓存）/ free 完全空闲 / shared 共享内存（tmpfs、shmget）/ buffers 块 I/O 缓冲 -/+ buffers 行：把 buffers 剔除后的视角——used = Mem.used - buffers（应用真正占用）、free = Mem.free + buffers（真正可立即分配） Swap 行：全 0 表示未配置 swap iostat 看 CPU 与块设备 I/O（来自 sysstat 包，BusyBox 默认无）。\n~ # iostat Linux 4.9.138 (Smart Home) 07/28/25 _armv7l_ (2 CPU) avg-cpu: %user %nice %system %iowait %steal %idle 5.45 0.00 11.98 0.00 0.00 82.57 Device: tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn mtdblock3 0.00 0.07 0.00 9058 0 sda 0.69 0.01 4.12 1530 540352 avg-cpu：%user 用户态 / %system 内核态 / %iowait 等 I/O 空闲 / %idle 空闲 / %nice 低优先级用户态 / %steal 虚拟化被抢占 Device：tps 每秒 I/O 次数 / Blk_read/s、Blk_wrtn/s 每秒读写块数（默认块 512B）/ Blk_read、Blk_wrtn 开机以来累计 排查经验：%iowait 高 → 硬盘 I/O 瓶颈；%idle 高但响应慢 → 可能在等内存，考虑加内存；%idle 持续 \u0026lt;10 → CPU 处理能力不足。\n网络配置与抓包 ⚠️ 改网络配置可能断掉当前 SSH/telnet 连接，远程操作前想好恢复手段。\nifconfig / ip（配置 IP、网关、MAC） # 设置 IP / 掩码 ifconfig eth0 192.168.2.206 netmask 255.255.255.0 # net-tools / BusyBox ip addr add 192.168.2.206/24 dev eth0 \u0026amp;\u0026amp; ip link set eth0 up # iproute2（Ubuntu 推荐） # 默认网关 route add default gw 192.168.2.1 # net-tools / BusyBox ip route add default via 192.168.2.1 # iproute2 # 改 MAC（先 down 再改再 up） ifconfig eth0 down ifconfig eth0 hw ether 00:11:22:33:44:55 ifconfig eth0 up # iproute2 等价： ip link set eth0 down ip link set eth0 address 00:11:22:33:44:55 ip link set eth0 up 环境差异：（BusyBox）板子一般只有 ifconfig/route；（GNU）Ubuntu 上 ifconfig 需装 net-tools，官方推荐 ip。\nudhcpc （BusyBox）DHCP 客户端，向 DHCP 服务器申请 IP/掩码/网关/DNS。\nudhcpc -i eth0 -b -p /tmp/udhcpc.eth0.pid -i \u0026lt;iface\u0026gt;：指定网卡 -b：后台运行（未立刻拿到 IP 时转后台重试，避免同步调用卡死） -p \u0026lt;file\u0026gt;：写 PID 到文件，便于按网卡精确 kill，避免误杀 -f：前台运行（默认调试用） -q：拿到租约后立即退出（单次获取） tcpdump 抓包（BusyBox 默认多无，需交叉编译；开发主机 sudo apt install tcpdump）。\ntcpdump -i eth0 -nn # 抓 eth0，不解析主机名/端口名 tcpdump -i eth0 -nn host 192.168.2.100 # 只抓与某主机 tcpdump -i eth0 -nn port 69 # 抓 TFTP(69) tcpdump -i any -nn icmp # 抓所有接口的 ping tcpdump -i eth0 -w cap.pcap # 存文件，拿到 PC 用 Wireshark 看 虚拟机设置静态 IP（netplan，Ubuntu 开发主机） ⚠️ 改错会导致虚拟机断网。\nsudo vi /etc/netplan/01-network-manager-all.yaml network: version: 2 ethernets: ens33: # 替换为你的实际网卡名（ip a 查看） dhcp4: no addresses: - 192.168.2.222/24 # 静态 IP / 掩码 routes: - to: default via: 192.168.2.2 # 默认网关 nameservers: addresses: [8.8.8.8, 8.8.4.4] sudo netplan apply 待确认 / 注意：旧写法 gateway4: 192.168.2.2 在 Ubuntu 22.04+（netplan 0.103+）已弃用，改用上面的 routes:。若文件名含 network-manager 且系统用 NetworkManager 渲染，networkd 语法未必生效，以实际 renderer 为准。\n文件传输与共享 tftp（板子 ↔ PC） （BusyBox）板子端上传/下载文件：\ntftp -p -l /etc/smart_home.conf -r smart_home.conf 192.168.2.100 -p PUT（上传）/ -g GET（下载） -l 板子上的本地文件 / -r PC 端 TFTP 目录里的文件名 / 末尾是 PC 的 IP 环境差异：以上是（BusyBox）tftp 语法；Ubuntu 的 tftp-hpa 客户端语法不同（交互式或 -m）。\nPC 端（Ubuntu）tftpd-hpa 服务配置：\nsudo vi /etc/default/tftpd-hpa TFTP_DIRECTORY=\u0026#34;/srv/tftp\u0026#34; TFTP_OPTIONS=\u0026#34;--secure --create\u0026#34; --create：允许新建文件（板子 PUT 上传时必须） sudo systemctl restart tftpd-hpa NFS（Ubuntu 做 server，板子挂载） # 1. 安装 sudo apt update \u0026amp;\u0026amp; sudo apt install nfs-kernel-server # 2. 创建并放开共享目录 mkdir -p ~/nfs_share \u0026amp;\u0026amp; chmod 777 ~/nfs_share # 3. 配置导出 sudo vi /etc/exports /home/meian/nfs_share 192.168.2.0/24(rw,sync,no_root_squash,no_subtree_check) ⚠️ 选项之间用英文逗号分隔，不能用中文逗号 ，，否则导出解析失败。\nrw 可读写 / sync 同步写 / no_root_squash 保留客户端 root 权限 / no_subtree_check 不检查子目录树 # 4. 刷新并查看 sudo exportfs -ra showmount -e # 服务管理 sudo systemctl status nfs-kernel-server sudo systemctl restart nfs-kernel-server 板子端挂载见 mount。\nSamba（Ubuntu 共享给 Windows/局域网） sudo vi /etc/samba/smb.conf [work] path = /home/plh/work browseable = yes writable = yes available = yes comment = smb share sudo systemctl restart smbd sudo smbpasswd -a plh # 创建 Samba 用户 [work]：共享名（客户端\u0026quot;网络邻居\u0026quot;里看到的名字） path：服务器上真实共享目录 browseable：yes 出现在网络列表；no 需手动 \\\\server\\work 访问 writable = yes（等价 read only = no）：允许写/改/删 systemd 与系统服务 systemd 是系统启动后第一个进程（PID 1），负责启动和管理服务（daemon）。以下以 mihomo 代理为例。\n1. 准备可执行文件（示例） gzip -d mihomo-linux-amd64-compatible-v1.19.21.gz mv mihomo-linux-amd64-compatible-v1.19.21 mihomo chmod +x mihomo sudo mv mihomo /usr/local/bin/ 2. 编写 service 文件 sudo vi /etc/systemd/system/mihomo.service [Unit] Description=Mihomo Daemon # 在网络就绪后再启动（代理依赖网络） After=network.target [Service] Type=simple ExecStart=/usr/local/bin/mihomo -d /home/meian/.config/mihomo # 进程退出后自动重启 Restart=always # 重启前等待秒数 RestartSec=3 # 不输出日志到终端 StandardOutput=null StandardError=null [Install] WantedBy=multi-user.target ⚠️ systemd 不支持行内注释。注释必须独占一行、以 # 开头。像 Type=simple#服务类型、After=network.target # ... 这类写法会把注释解析进值里导致出错。\n字段含义：\n[Unit]：描述、依赖、启动顺序（After= 只定义顺序，不代表强依赖） [Service]：怎么启动/重启 ExecStart：启动命令，必须绝对路径 Restart=always：进程挂掉自动重启 RestartSec：重启前等待秒数（不是最大重启次数；限制次数用 StartLimitBurst / StartLimitIntervalSec） [Install] → WantedBy=multi-user.target：enable 时把本服务挂到\u0026quot;多用户模式\u0026quot;这个 target 下，实现开机自启（不是\u0026quot;挂载点\u0026quot;） 3. 启动与管理 sudo systemctl daemon-reload # 改动 unit 文件后必须执行 sudo systemctl start mihomo # 启动 sudo systemctl enable mihomo # 开机自启 sudo systemctl stop mihomo # 停止 sudo systemctl restart mihomo # 重启 sudo systemctl status mihomo # 查看状态 daemon-reexec（重启 systemd 自身）一般不需要，改 unit 用 daemon-reload 即可。\n设备管理：udev 与 uevent 内核通过 Netlink 协议族的 NETLINK_KOBJECT_UEVENT 类型，向用户态广播内核对象（kobject）相关的设备事件（uevent），用于热插拔监听。\nuevent 消息格式 每条 uevent 是一组键值对，用 \\0 分隔，核心字段：\nACTION：add / remove / change 等 DEVPATH：sysfs 路径，如 /devices/pci0000:00/.../usb1/1-1 SUBSYSTEM：所属子系统，如 usb / block / input 设备属性：如 DRIVER=usb-storage、PRODUCT=1234/5678（厂商/产品 ID） 其他元数据：如 ID_SERIAL、DEVNAME=sdb ACTION=add\\0 DEVPATH=/devices/pci0000:00/0000:00:14.0/usb1/1-1\\0 SUBSYSTEM=usb\\0 DRIVER=usb-storage\\0 PRODUCT=1234/5678\\0 ...\\0 监听代码（C++） 要点：socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT) 建套接字，nl_groups=1 订阅内核 uevent 组，SO_RCVTIMEO 设接收超时避免完全阻塞。\n_hotplug_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT); if (_hotplug_fd \u0026lt; 0) { DEBUG_ERROR(\u0026#34;Failed to create netlink socket: %s\u0026#34;, strerror(errno)); _hotplug_running.store(false); return; } nladdr.nl_family = AF_NETLINK; nladdr.nl_pid = getpid(); nladdr.nl_groups = 1; // NETLINK_KOBJECT_UEVENT 组 if (bind(_hotplug_fd, (struct sockaddr*)\u0026amp;nladdr, sizeof(nladdr)) \u0026lt; 0) { DEBUG_ERROR(\u0026#34;Failed to bind netlink socket: %s\u0026#34;, strerror(errno)); ::close(_hotplug_fd); _hotplug_fd = -1; _hotplug_running.store(false); return; } // 设置接收超时，避免完全阻塞 struct timeval tv; tv.tv_sec = 1; tv.tv_usec = 0; setsockopt(_hotplug_fd, SOL_SOCKET, SO_RCVTIMEO, \u0026amp;tv, sizeof(tv)); char buffer[4096]; while (_hotplug_running.load()) { memset(buffer, 0, sizeof(buffer)); int len = recv(_hotplug_fd, buffer, sizeof(buffer) - 1, 0); if (len \u0026lt; 0) { int err = errno; if (err == EAGAIN || err == EWOULDBLOCK) continue; // 超时，继续 if (err == EINTR) continue; // 被信号中断，继续 if (!_hotplug_running.load()) break; // 正常退出 DEBUG_ERROR(\u0026#34;recv error: %s (errno=%d), will retry\u0026#34;, strerror(err), err); std::this_thread::sleep_for(std::chrono::milliseconds(100)); continue; } if (len == 0) { // socket 被关闭 if (!_hotplug_running.load()) break; std::this_thread::sleep_for(std::chrono::milliseconds(100)); continue; } parseUevent(buffer); } if (_hotplug_fd \u0026gt;= 0) { ::close(_hotplug_fd); _hotplug_fd = -1; } 交叉编译工具链 快速配置工具链 写进 ~/.bashrc，用时执行 rk3588 一键设置 ARCH / CROSS_COMPILE / PATH：\nrk3588() { # 按你的实际路径改 local TC_ROOT=\u0026#34;$HOME/work/toolchain/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu\u0026#34; local TC_BIN=\u0026#34;$TC_ROOT/bin\u0026#34; export ARCH=arm64 export CROSS_COMPILE=aarch64-none-linux-gnu- export PATH=\u0026#34;$TC_BIN:$PATH\u0026#34; echo \u0026#34;ARCH=$ARCH\u0026#34; echo \u0026#34;CROSS_COMPILE=$CROSS_COMPILE\u0026#34; echo \u0026#34;TOOLCHAIN_BIN=$TC_BIN\u0026#34; which \u0026#34;${CROSS_COMPILE}gcc\u0026#34; || return 1 \u0026#34;${CROSS_COMPILE}gcc\u0026#34; -v 2\u0026gt;\u0026amp;1 | head -n 3 } 查看工具链支持的 C++ 版本 # 看默认 C++ 标准（__cplusplus 宏） g++ -dM -E -x c++ /dev/null | grep __cplusplus # 199711=C++98 201103=C++11 201402=C++14 201703=C++17 202002=C++20 # 测试是否支持某标准（能编过即支持） g++ -std=c++17 -x c++ -fsyntax-only /dev/null \u0026amp;\u0026amp; echo OK 原写法 g++ -std=c++11 -E - \u0026lt; /dev/null 只能验证\u0026quot;是否接受 -std=c++11\u0026quot;，不能直接看出默认/最高标准，用上面的 __cplusplus 更直观。交叉工具链把 g++ 换成 ${CROSS_COMPILE}g++。\nnm 列出二进制文件（可执行/库/目标文件）中的符号表。分析目标平台产物时用交叉版 ${CROSS_COMPILE}nm。\naarch64-none-linux-gnu-nm -D -C libfoo.so -D：只显示动态符号。库被 strip 后，不加 -D 可能看不到任何输出；加 -D 才能看到对外暴露的接口 -C：demangle，把 C++ 修饰后的符号名还原成可读形式 常见故障与排查案例 排查速查对照 现象 先看什么 命令 磁盘满 / 写失败 磁盘、inode 占用 df -h、df -i、du -h -d1 内存不足 / OOM 真正可用内存 free -h（看 available） 系统卡、I/O 慢 I/O 瓶颈 iostat（看 %iowait） 加了磁盘但系统没变大 分区/文件系统未扩展 见下方「虚拟机磁盘扩容」 设备插拔无反应 热插拔事件 监听 uevent，见 设备管理 板子 TFTP 上传失败 服务端未允许新建文件 tftpd-hpa 加 --create systemd 服务不生效 改 unit 未重载 / 行内注释 daemon-reload；注释必须独占一行 虚拟机磁盘扩容 现象：在 VMware 给虚拟机硬盘\u0026quot;加了 20 GB\u0026quot;，只是把底层 .vmdk 拉大了，Ubuntu 里的系统并不知道，需要手动扩分区 + 扩文件系统。\n⚠️ 对正被使用的根分区做 parted/resizepart 有风险，务必先确认分区号，最好有快照/备份。\n1. 确认磁盘已变大、分区还是旧大小\nsudo fdisk -l /dev/sda # Disk /dev/sda：200 GiB ... # /dev/sda1 * 2048 377487359 377485312 180G 83 Linux ← 分区仍是旧大小 2. 用 parted 把分区扩到磁盘末尾 ⚠️\nsudo parted /dev/sda (parted) print free # 查看末尾的可用空间 (parted) resizepart 1 100% # 把 1 号分区扩到 100% # 警告: 分区正被使用。是否继续? → yes (parted) quit 3. 扩展文件系统到分区大小\n# ext2/3/4： sudo resize2fs /dev/sda1 # xfs（若根是 xfs）： # sudo xfs_growfs / df -h / # 确认已生效 说明：分区表变化后可能需要检查 /etc/fstab。resize2fs 可在线扩展已挂载的 ext4；缩小则必须先卸载。\n","permalink":"https://plhwiki.cn/linux/common/linux%E5%B9%B2%E8%B4%A7/","summary":"\u003ch1 id=\"linux-干货速查\"\u003eLinux 干货速查\u003c/h1\u003e\n\u003cp\u003e面向 \u003cstrong\u003eUbuntu 开发主机 + BusyBox 嵌入式开发板\u003c/strong\u003e的个人速查。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003e约定\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e⚠️ 标记危险命令（误用会丢数据 / 断网 / 毁分区），执行前务必确认设备名和路径。\u003c/li\u003e\n\u003cli\u003e命令行为在不同实现下可能不同，差异处标注：（GNU）Ubuntu 自带 / （BusyBox）板子 / （systemd）。\u003c/li\u003e\n\u003cli\u003e拿不准的地方标注「待确认」，不猜。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"目录\"\u003e目录\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\u003ca href=\"/linux/common/linux%E5%B9%B2%E8%B4%A7/#shell-%e4%b8%8e%e7%8e%af%e5%a2%83%e5%8f%98%e9%87%8f\"\u003eShell 与环境变量\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/linux/common/linux%E5%B9%B2%E8%B4%A7/#%e6%96%87%e4%bb%b6%e4%b8%8e%e7%9b%ae%e5%bd%95%e6%93%8d%e4%bd%9c\"\u003e文件与目录操作\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/linux/common/linux%E5%B9%B2%E8%B4%A7/#%e6%96%87%e6%9c%ac%e6%90%9c%e7%b4%a2%e4%b8%8e%e5%a4%84%e7%90%86\"\u003e文本搜索与处理\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/linux/common/linux%E5%B9%B2%E8%B4%A7/#%e7%a3%81%e7%9b%98%e5%88%86%e5%8c%ba%e4%b8%8e%e6%96%87%e4%bb%b6%e7%b3%bb%e7%bb%9f\"\u003e磁盘、分区与文件系统\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/linux/common/linux%E5%B9%B2%E8%B4%A7/#%e5%86%85%e5%ad%98cpu-%e4%b8%8e-io-%e6%8e%92%e6%9f%a5\"\u003e内存、CPU 与 I/O 排查\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/linux/common/linux%E5%B9%B2%E8%B4%A7/#%e7%bd%91%e7%bb%9c%e9%85%8d%e7%bd%ae%e4%b8%8e%e6%8a%93%e5%8c%85\"\u003e网络配置与抓包\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/linux/common/linux%E5%B9%B2%E8%B4%A7/#%e6%96%87%e4%bb%b6%e4%bc%a0%e8%be%93%e4%b8%8e%e5%85%b1%e4%ba%ab\"\u003e文件传输与共享\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/linux/common/linux%E5%B9%B2%E8%B4%A7/#systemd-%e4%b8%8e%e7%b3%bb%e7%bb%9f%e6%9c%8d%e5%8a%a1\"\u003esystemd 与系统服务\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/linux/common/linux%E5%B9%B2%E8%B4%A7/#%e8%ae%be%e5%a4%87%e7%ae%a1%e7%90%86udev-%e4%b8%8e-uevent\"\u003e设备管理：udev 与 uevent\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/linux/common/linux%E5%B9%B2%E8%B4%A7/#%e4%ba%a4%e5%8f%89%e7%bc%96%e8%af%91%e5%b7%a5%e5%85%b7%e9%93%be\"\u003e交叉编译工具链\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/linux/common/linux%E5%B9%B2%E8%B4%A7/#%e5%b8%b8%e8%a7%81%e6%95%85%e9%9a%9c%e4%b8%8e%e6%8e%92%e6%9f%a5%e6%a1%88%e4%be%8b\"\u003e常见故障与排查案例\u003c/a\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003chr\u003e\n\u003ch2 id=\"shell-与环境变量\"\u003eShell 与环境变量\u003c/h2\u003e\n\u003ch3 id=\"重定向\"\u003e重定向\u003c/h3\u003e\n\u003cp\u003e从文件读入 stdin：\u003c/p\u003e","title":"Linux干货"},{"content":"文件系统 文件索引节点：inode\n两部分 纯数据区\n文件真正的数据存储区、基本存储单位为block 元数据区\n文件属性：磁盘中的存储位置、文件长度等信息 时间戳：创建时间、修改时间 读写权限：使用read/write系统调用时，要首先要进行权限检 所属组、所有者 链接数 挂载 ​\t挂载是一个非常重要的功能，使用非常频繁。它指将一个设备(通常是存储设备，可以挂载光盘、硬盘、磁带、光盘镜像文件等)挂接到一个已存在的目录上，那么，你访问这个目录就是访问这个设备（这个目录可以不为空，但挂载后这个目录下以前的内容将不可用） ，一个目录一旦成为\u0026quot;挂载点\u0026quot;，该目录下看到的内容，来自\u0026quot;被挂载的文件系统\u0026quot;， 与父目录的存储介质完全无关。\n挂载需要有 挂载源（设备） 和 挂载点（目录）\n/var → /dev/mtdblock5 (JFFS2 flash) /var/run → tmpfs (RAM) /var/run/hdd0 → /dev/sda2 (HDD / ext2) 进程间通信 通过文件\n通过内核\n共享内存\n匿名管道 匿名管道是一种只用于亲缘关系进程间、基于内核缓冲、半双工、顺序可靠的字节流通信方式，读写都会阻塞，必须注意正确关闭读写端才能避免死锁。\n匿名管道的基本用法 创建匿名管道 int pipe_fd[2]; pipe(pipe_fd); // pipe_fd[0] = 读端, pipe_fd[1] = 写端 fork 之后由父子进程各关闭不需要的端口 写数据的进程：关闭读端 读数据的进程：关闭写端 否则 read/write 行为会受到影响（例如 EOF 不会出现）\n父子通过 read / write 进行通信 write(pipefd[1], buf, n) read(pipefd[0], buf, n) 匿名管道的阻塞行为（重点） Write 可能阻塞的情况：\n管道缓冲区已满时，write 阻塞，只有当读进程读走数据后，write 才能继续 Read 可能阻塞的情况：\n管道缓冲区为空 → read 阻塞，直到写进程写入更多数据 如果写端全部关闭 → read 返回 0（代表 EOF） 匿名管道的注意事项 必须正确关闭端口，否则会出现以下情况： 读端不关闭写端 ⇢ 读不到 EOF，一直阻塞 写端不关闭读端 ⇢ 异常行为，甚至 SIGPIPE 管道大小有限 ,一般为64KB 匿名管道不能用于非亲缘进程 多个写端写入时是原子化的？ 小于 PIPE_BUF（通常 4096 字节）的写入是原子的 大写可能导致数据交错 管道是流式无格式通信，没有消息边界，需要自己定义协议格式（分包、长度） 问：如果读端进程已经关闭（close(pipefd[0])），写进程继续 write 会发生什么？\n答 write 失败，收到 SIGPIPE\n因为匿名管道有一个特殊规则：\n规则：当所有读端被关闭时： write 不再阻塞 write 立即返回错误：EPIPE 并向进程发送 SIGPIPE 信号 → 如果进程没有处理该信号，会被终止！ 这就是“Broken Pipe”错误的来源。 写端写管道时，如果所有读端都关闭，write 不会阻塞，而是立即失败，并收到 SIGPIPE。\n例如：\n每个进程各关闭一端，我们强制建立了一个明确的单向通信流：子进程 -\u0026gt; 父进程。\n对于读者（父进程）： 只有当所有指向管道写端的文件描述符都被关闭时，读者在读取管道时才会接收到 EOF（文件结束符）。如果父进程不关闭自己的 fds[1]，那么即使子进程退出了（关闭了它的 fds[1]），管道的写端依然被父进程自己持有着。这样，父进程在读取 fds[0] 时永远不会拿到 EOF，可能会导致程序死锁，一直在等待永远不会到来的数据。 对于写者（子进程）： 如果读者（父进程）关闭了读端，子进程再尝试写入数据时，操作系统会向子进程发送 SIGPIPE 信号，表明管道已断开。 命名管道 FIFO 与匿名管道不同，有文件名，FIFO最大的特性就是每个FIFO都有一个路径名与之相关联，从而允许无亲缘关系的任意两个进程间通过FIFO进行通信。\nint mkfifo(const char * pathname,mode_t mode); 特点 使用方式像文件，但不能 seek，你可以通过： open() read() write() close() 但不能lseek,因为 FIFO 是流式文件，没有偏移量。 进入 FIFO 的数据顺序，就是读出的顺序。 管道一样，FIFO仅提供半双工的数据通信 open 行为会阻塞 打开 FIFO 的读端，如果没有写端打开 → 阻塞 int fd = open(\u0026#34;fifo\u0026#34;, O_RDONLY); // 没有写端 会阻塞，直到有进程打开写端为止。\n打开 FIFO 的写端，如果没有读端打开 → 阻塞 int fd = open(\u0026#34;fifo\u0026#34;, O_WRONLY); // 没有人读 也会阻塞，直到读端出现。\n⚠️ 这是命名管道区别于匿名管道的重要特性！！！\n匿名管道是在父子进程中自然同时存在读写端，而 FIFO 是“双方需要主动 open”，因此更容易阻塞。\nPIPE 与 FIFO 的共同点 内核实现层面：本质是同一套机制 内核维护的一块 环形缓冲区 基于 page cache 通过 等待队列 + 锁 实现同步与阻塞 都是“流式文件”（Stream） 不支持 lseek 只能顺序读 / 顺序写 都是 FIFO（先进先出） 先写入的数据，先被读出 没有消息边界（除非你自己定义协议） 共享内存 D-BUS D-BUS**（Desktop Bus）是一种“消息总线式”的进程间通信（IPC）机制。**用于让不同进程互相通信\n特点：\n多个进程共享一个**“软件总线”** 广播消息 点对点 ：A 进程直接发给 B 进程 System Bus（系统总线） +\n","permalink":"https://plhwiki.cn/linux/common/linux%E7%B3%BB%E7%BB%9F%E7%BC%96%E7%A8%8B/","summary":"\u003ch1 id=\"文件系统\"\u003e文件系统\u003c/h1\u003e\n\u003cp\u003e文件索引节点：inode\u003c/p\u003e\n\u003cp\u003e两部分\n纯数据区\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e文件真正的数据存储区、基本存储单位为block\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e元数据区\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e文件属性：磁盘中的存储位置、文件长度等信息\u003c/li\u003e\n\u003cli\u003e时间戳：创建时间、修改时间\u003c/li\u003e\n\u003cli\u003e读写权限：使用read/write系统调用时，要首先要进行权限检\u003c/li\u003e\n\u003cli\u003e所属组、所有者\u003c/li\u003e\n\u003cli\u003e链接数\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"挂载\"\u003e挂载\u003c/h2\u003e\n\u003cp\u003e​\t\u003cstrong\u003e挂载\u003c/strong\u003e是一个非常重要的功能，使用非常频繁。它指将一个设备(通常是存储设备，可以挂载光盘、硬盘、磁带、光盘镜像文件等)\u003cstrong\u003e挂接到一个已存在的目录上，那么，你访问这个目录就是访问这个设备\u003c/strong\u003e（这个目录可以不为空，但挂载后这个目录下以前的内容将不可用）\n，\u003cfont color = red\u003e一个目录一旦成为\u0026quot;挂载点\u0026quot;，该目录下看到的内容，来自\u0026quot;被挂载的文件系统\u0026quot;， 与父目录的存储介质完全无关。\u003c/font\u003e\u003c/p\u003e","title":"Linux系统编程"},{"content":"TCP建立连接的过程 listen listen的工作主要是申请和初始化接收队列，包括全连接队列和半连接队列。全/半连接队列是三次握手中重要的数据结构，有它服务端才能响应客户端的三次握手\n全连接队列 全连接是链表，最大长度为listen传入的backlog和net.core.somaxconn之间较小的值。\n半连接队列 半链接的数据结构为哈希表，长度为min(backlog,somaxconn,tcp_max_syn_backlog)+1再向上取到2的N次幂。\nconnect ","permalink":"https://plhwiki.cn/linux/net/linux%E7%BD%91%E7%BB%9C/","summary":"\u003ch1 id=\"tcp建立连接的过程\"\u003eTCP建立连接的过程\u003c/h1\u003e\n\u003ch2 id=\"listen\"\u003elisten\u003c/h2\u003e\n\u003cp\u003elisten的工作主要是申请和初始化接收队列，包括全连接队列和半连接队列。全/半连接队列是三次握手中重要的数据结构，有它服务端才能响应客户端的三次握手\u003c/p\u003e","title":"linux网络"},{"content":"基本 函数定义\n返回值类型 函数名(参数列表) //函数头 { 函数体 } 函数声明\n返回值类型 函数名（参数列表）; ODR规则 ODR = One Definition Rule（单一定义规则）,C++ 程序有一个核心规则：\n一个变量、函数、类型，在整个程序中必须满足定义规则\n禁止：\n// a.cpp int g_value = 10; // b.cpp int g_value = 20; 在链接的时候会出现multiple definition of g_value，违反了ODR\nODR-use 编译器必须真的需要这个对象的地址或者存储空间时，就叫 ODR-use。\n例如：\nint x = 10; int main() { int y = x;//只是读取值。 int* p = \u0026amp;x;//需要x 的地址，x 必须存在内存中，x 被 ODR-use } 例如\n//c++11 .h struct State { static constexpr uint32_t kDiagFrames = 20; }; //.cpp constexpr int State::value; static 表示这个变量属于类，不属于对象 constexpr 表示编译期常量 auto p = \u0026amp;State::kDiagFrames;//必须存在一个真正的对象，也就是State::kDiagFrames需要内存地址 注意：\nstatic constexpr 初始化，只满足“编译期使用”，并不一定提供一个真正的内存对象。\n产生编译期常量，可以直接替换20 ，但是内存对象不存在 文件 写文件 std::ofstream （Output File Stream，输出文件流）专门用于向文件中写入数据。它包含在 \u0026lt;fstream\u0026gt; 头文件中。\n常用的标志位：\nstd::ios::out：为输出打开文件（ofstream 默认自带）。 std::ios::app：追加模式（所有写入都追加到文件末尾）。 std::ios::trunc：截断模式（如果文件存在，先清空内容，ofstream 默认自带）。 std::ios::binary：二进制形式打开。 安全的写文件 在嵌入式系统中， jffs2/ubifs/ext2，掉电是常态，配置文件损坏就意味着设备变砖。\n这个流程是安全替换整个文件的套路：要求断电或进程崩溃后，文件要么是旧版本，要么是完整的新版本\npath.tmp -\u0026gt; fsync(tmp) -\u0026gt; rename(tmp, path) -\u0026gt; fsync(dir)\r旧文件 path 仍然保留\r↓\r把新内容写入 path.tmp\r↓\r确认 path.tmp 内容落盘\r↓\rrename(path.tmp, path)\r↓\r旧 path 被原子替换成新 path rename(\u0026ldquo;path.tmp\u0026rdquo;, \u0026ldquo;path\u0026rdquo;)具有原子可见性。其他进程看到的通常只有两种状态：\nrename 前：完整旧文件 rename 后：完整新文件 //config.json -\u0026gt; 旧 inode //config.json.tmp -\u0026gt; 新 inode rename(\u0026#34;config.json.tmp\u0026#34;, \u0026#34;config.json\u0026#34;); //config.json -\u0026gt; 新 inode /* 如果某个进程之前已经打开旧文件： int fd = open(\u0026#34;config.json\u0026#34;, O_RDONLY); 即使随后发生 rename()，这个 fd 仍然可以继续读取旧 inode。 而之后重新 open(\u0026#34;config.json\u0026#34;) 的进程会打开新 inode。 */ 访问语义 c++里所有“访问语义”本质：指针类型 + 是否允许写解引用目标\n在编译器层面：\n类型 = [是否可写] + [间接层级(指针/引用)] + [对象类型]\n例如：\n写法 编译器真实语义 T a 直接对象 T* p 指向 T 的指针 T\u0026amp; r “不可改指针”的 T*（语法糖） const T* p 指针可改，但指向内容不可改 T* const p 指针不可改，但内容可改 const T\u0026amp; r 指向 const T 的“引用”（本质也是指针） const 成员函数 this 变成 const T* const this（核心） 螺旋法则 从变量名开始，按顺时针方向“绕一圈”，遇到谁就读谁\nconst int* const p; 找到变量名\np，从这里开始\n先向右看（顺时针）\n如果右边是：\n[] → 数组 () → 函数 没东西 → 往左走 再向左看\n* → pointer const → 只读 类型名 → 基类型 1.左边：const → p 不可改 2.再左：* → pointer 3.再左：const int → 指向内容不可改 指针不可改 + 指向内容不可改 const 成员函数 void f() const; 变成：\nvoid f(const A* const this); 注意：\nthis 指向 const A 所以不能改成员 this-\u0026gt;x = 1; // ❌ 虚函数 virtual 声明的成员函数，允许在派生类中重写（override），通过基类指针/引用实现运行时多态。\n多态 通过基类的指针或引用调用虚函数，触发动态绑定；通过对象直接调用，或显式限定 Base::show()，则为静态绑定。\nDerived d; Base b = d; // 对象切片 b.show(); // 静态绑定：Base::show Base* p = \u0026amp;d; p-\u0026gt;show(); // 动态绑定：Derived::show Base::show(); // 显式限定，静态绑定 对象切片 派生类对象按值赋给/复制成基类对象时，发生对象切片（Object Slicing），派生类部分被丢掉。\nclass Base { public: virtual void show() const { std::cout \u0026lt;\u0026lt; \u0026#34;Base::show\\n\u0026#34;; } int baseValue = 10; }; class Derived : public Base { public: void show() const override { std::cout \u0026lt;\u0026lt; \u0026#34;Derived::show\\n\u0026#34;; } int derivedValue = 20; }; int main() { Derived d; Base b = d; // 对象切片 b.show(); // Base::show } 调用 Base 的复制构造函数，得到真正的 Base 对象；其 vptr 指向 Base 的 vtable，故 b.show() 必走 Base::show。\noverride 与 final（C++11） override：标记派生类函数意图重写基类虚函数；签名不匹配时编译失败，避免「以为重写了其实没重写」。 final：禁止进一步重写；可修饰虚函数，也可修饰类（该类不可被继承）。 派生类重写时不必再写 virtual；写了 override 即表示重写。static 成员函数不能是 virtual。\nclass Base { public: virtual void foo() {} virtual void bar() {} }; class Derived final : public Base { public: void foo() override {} // 重写 void bar() final override {} // 重写，且禁止子类再重写 bar }; 纯虚函数与抽象类 纯虚函数：virtual void f() = 0;，无默认实现（也可在类外提供实现，但类仍保持抽象）。\n含纯虚函数的类是抽象类，不能实例化，可作接口。 派生类须实现全部纯虚函数后才能实例化。 class IDevice { public: virtual ~IDevice() = default; virtual void start() = 0; virtual void stop() = 0; }; class Camera : public IDevice { public: void start() override { /* ... */ } void stop() override { /* ... */ } }; 虚析构 多态基类几乎总要 virtual ~Base()。通过基类指针 delete 派生对象时，无虚析构则只析构基类部分，派生资源泄漏。\nclass Base { public: virtual ~Base() { std::cout \u0026lt;\u0026lt; \u0026#34;~Base\\n\u0026#34;; } }; class Derived : public Base { public: ~Derived() override { std::cout \u0026lt;\u0026lt; \u0026#34;~Derived\\n\u0026#34;; } }; int main() { Base* p = new Derived; delete p; // 有虚析构：~Derived → ~Base } 规则：类可能被 delete 基类指针、或放入多态容器时，基类析构必须是 virtual。\n重写与隐藏（name hiding） 派生类声明与基类同名但签名不同的函数时，隐藏基类同名函数，不会自动形成重写关系。\nclass Base { public: virtual void f(int) { std::cout \u0026lt;\u0026lt; \u0026#34;Base::f(int)\\n\u0026#34;; } }; class Derived : public Base { public: void f(double) { std::cout \u0026lt;\u0026lt; \u0026#34;Derived::f(double)\\n\u0026#34;; } // 隐藏 Base::f，非重写 }; Derived d; d.f(1); // Derived::f(double) d.f(1.0); // Derived::f(double) Base* p = \u0026amp;d; p-\u0026gt;f(1); // Base::f(int)，动态绑定仍走基类版本 若需同时暴露基类版本：using Base::f;，或对每个要重写的签名显式 override。\n构造与析构期间的虚函数 构造/析构过程中，this 的类型随阶段变化，虚函数调用通常不按完整派生类型绑定：\n构造基类时：对象视为基类，调虚函数走基类实现。 析构派生类已完成后：对象视为基类，调虚函数走基类实现。 因此不要在基类构造/析构里依赖「派生类尚未构造/已销毁」的虚函数行为。\n默认实参与虚函数 默认实参在编译期按静态类型绑定，与动态绑定的函数体无关。\nclass Base { public: virtual void f(int x = 1) { std::cout \u0026lt;\u0026lt; \u0026#34;Base \u0026#34; \u0026lt;\u0026lt; x \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; } }; class Derived : public Base { public: void f(int x = 2) override { std::cout \u0026lt;\u0026lt; \u0026#34;Derived \u0026#34; \u0026lt;\u0026lt; x \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; } }; Derived d; Base* p = \u0026amp;d; p-\u0026gt;f(); // 输出 Derived 1（默认实参来自 Base，函数体来自 Derived） 避免在派生类重写时修改默认实参；若两侧默认值不同，易出歧义。\n协变返回类型 重写时返回类型可以是基类返回类型的派生类指针/引用（协变）。\nclass Base { public: virtual Base* clone() { return new Base(*this); } }; class Derived : public Base { public: Derived* clone() override { return new Derived(*this); } }; 仅适用于指针/引用返回；void、值类型返回不能协变。\n虚函数表 每个含虚函数的类有一份虚函数表（vtable），存放该类虚函数入口地址。\nvtable 通常在只读段（如 .rodata），全进程一份；不同类各有一份。 每个对象含编译器生成的 vptr（虚表指针），指向所属类的 vtable；同类实例共享同一 vtable。 vptr 在构造时写入；对象切片后 vptr 变为基类 vtable。 调用虚函数：经 vptr 查表 + 间接跳转，有额外开销；极热路径慎用多态。 vptr 的类型与布局是实现定义的，不要手写 void** 去碰。\n多继承（简述） 多继承且多个基类含虚函数时，对象可能有多张 vtable / 多个 vptr；菱形继承需虚继承避免重复基类子对象。细节见 OOP/继承专题。\n异步编程 ","permalink":"https://plhwiki.cn/cpp/language/c++11/","summary":"\u003ch1 id=\"基本\"\u003e基本\u003c/h1\u003e\n\u003cp\u003e函数定义\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e返回值类型\u003c/span\u003e \u003cspan class=\"err\"\u003e函数名\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"err\"\u003e参数列表\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e      \u003cspan class=\"c1\"\u003e//函数头\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"err\"\u003e函数体\u003c/span\u003e                      \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e函数声明\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e返回值类型\u003c/span\u003e \u003cspan class=\"err\"\u003e函数名（参数列表）\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"odr规则\"\u003eODR规则\u003c/h2\u003e\n\u003cp\u003eODR = \u003cstrong\u003eOne Definition Rule（单一定义规则）\u003c/strong\u003e,C++ 程序有一个核心规则：\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e一个变量、函数、类型，在整个程序中必须满足定义规则\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e禁止：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-c++\" data-lang=\"c++\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// a.cpp\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003eg_value\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e10\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// b.cpp\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003eg_value\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e20\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e在链接的时候会出现\u003ccode\u003emultiple definition of g_value\u003c/code\u003e，违反了ODR\u003c/p\u003e","title":"c++11"},{"content":"学习资料 win11搭建网络映射器：https://m.comcw.cn/jc/4614.html\n音视频学习：https://github.com/0voice/audio_video_streaming\n阿秀的学习笔记：https://interviewguide.cn/\n音视频开发的学习资源：https://github.com/xhunmon/VABlog.git\n​\t解压密码：a123654\n​\t验证码：公众号获取\n飞宇压缩包的解压密码：\nhfy1234或wj1234或twj123\nCoderUtil：https://www.coderutil.com/\nC++：https://stibel.icu/md/guide/guide-overview.html\n​\t：http://purecpp.cn/ modern c++开源社区\nhttps://cpp-reference.com/\nhttps://strikefreedom.top/\nhttps://planetscale.com/blog/category/engineering io设备可视化\nCSDN：https://www.cnblogs.com/liuhanxu/p/13455554.html 密码：1024\n电子书：https://juejin.cn/user/2858385965070952/posts\n夏槽俊qt 零声教育\n课百网：https://www.kebaiwan.com/\n罗剑锋的C++实战笔记学习\n文档 h265白皮书：https://www.itu.int/rec/T-REC-H.265-202407-I/en\nc++网络库 https://github.com/PHZ76/xop.git\nGit https://learngitbranching.js.org/?locale=zh_CN\nGit的三个区域 Git 的文件有三种状态区域：\n区域 命令 说明 工作区 (Working Directory) 你编辑的实际文件 改动还没 add 暂存区 (Staging Area/index) 用 git add 后进入这里 等待被 commit 本地仓库 (Repository) 用 git commit 后进入这里 已经记录在历史中 对应命令区别：\n目标 用法 说明 ❌ 撤销刚才的 git add（把文件从暂存区移回工作区） git restore --staged \u0026lt;file\u0026gt; ✅ 把文件移出暂存区，但保留修改内容 ❌ 丢弃修改，回到上一次提交状态 git restore \u0026lt;file\u0026gt; ⚠️ 彻底丢失工作区改动 ❌ 既撤回暂存区，又丢弃修改 git restore --staged \u0026lt;file\u0026gt; + git restore \u0026lt;file\u0026gt; 等价于完全回到上一次提交的版本 流程 初始化 在本地初始化仓库并提交 git init 将所有当前文件添加到暂存区 git add . 提交到本地仓库 git commit -m \u0026#34;Initial commit\u0026#34; 重命名本地默认分支为 main（可选，但现在比较常见） git branch -M main 关联本地仓库与远程仓库\n添加远程仓库地址\ngit remote add origin \u0026lt;远程仓库URL\u0026gt; 或\ngit remote add origin git@192.168.10.28:pilinghui/test_project.git 推送本地分支到远程\ngit push -u origin main -u 选项会把本地的 main 与远程 origin/main 关联起来，之后推送可以直接用 git push。 在推送之前先拉取 git pull origin main 拉取远程仓库并允许合并无关历史\ngit pull origin main \u0026ndash;allow-unrelated-histories 代码的合并 访问不了github 修改hosts文件：\nsudo vi /etc/hosts 在hosts文件末尾的ip地址如下： #github 140.82.114.4 github.com 151.101.1.6 github.global.ssl.fastly.net 151.101.65.6 github.global.ssl.fastly.net 151.101.129.6 github.global.ssl.fastly.net 151.101.193.6 github.global.ssl.fastly.net 关掉hosts文件，重启网络服务\n【ubuntu20】sudo systemctl restart systemd-networkd.service 【ubuntu18】 /etc/init.d/networking restart\ngit add add 命令的作用就是将工作区的文件添加到暂存区\n-A 参数会监控工作区的状态树，它会把工作区的所有变化提交到暂存区，包括修改(modified)、新文件(Untracked files)、删除的文件(deleted)。git add . 等价于git add -A -u --update 只更新已经被Git跟踪的文件，即只添加那些已经存在于版本库中且被修改或删除的文件，但不包括新创建的文件（未被Git跟踪的文件） git branch -M 参数是 --move --force 的简写，它会强制将当前分支重命名为 main，即使目标分支 main 已经存在。通常用于以下情况：\n你想将当前分支重命名为 main，而不是创建一个新的分支。\n你正在遵循社区中越来越普遍的做法，将默认分支从 master 更改为 main。\ngit branch -M main 如果你当前在 master 分支上，这将把 master 分支重命名为 main。\n如果你在其他分支上，这将把该分支重命名为 main。\n-r\n作用： 显示你本地仓库知道的所有 远程分支 的列表\n输出格式：\nmeian@ubuntu200406:~/work/MyProject/test_nvr/nvr_36chn$ git branch -r origin/HEAD -\u0026gt; origin/main origin/develop origin/main origin/HEAD -\u0026gt; origin/main: 这表示远程仓库 origin 的 HEAD 指针指向 origin/main 分支。HEAD 指针通常表示远程仓库的默认分支（通常是 main 或 master）。\n-a\n作用会列出所有的分支，包括本地分支和远程分支。 \u0026ndash;contains 过滤器 查询到包含特定commitID的所有本地分支，加上\u0026ndash;all 还会显示远程分支。\n删除分支\n-d git branch -d 新分支名 -d 是安全删除，如果新分支上有未合并到 main 的提交，Git 会报错阻止删除，防止你丢掉工作。 假设你在当前分支上运行以下命令：\n一句话黄金法则 修复 bug 的分支永远基于出现 bug 的来源分支来决定。\n也就是说：\n如果 auth-server 还没合入 develop bug 修复必须在 feature/plh/auth-server 上进行 如果 auth-server 已经部分合入 develop bug 修复通常在 fix/plh/auth-server-xxx（新建一个 fix 分支） 如果 bug 出在主分支 main（线上环境） 必须从 main 开 hotfix 分支 ⭐ 场景 A：auth-server 正在开发，还未完成 → bug 修复应该继续在 feature 分支做 你现在是这种情况（重构，分阶段开发）。\n例如：\nfeature/plh/auth-server ← 你正在这里开发 突然发现一个 bug 那就继续在这个分支上修即可：\ngit checkout feature/plh/auth-server\r# 修复代码\rgit commit -m \u0026#34;fix(auth): 修复认证流程死锁\u0026#34;\rgit push 不需要新建分支。这是最干净、最合理的方式。\n⭐ 场景 B：auth-server 已经合入 develop，但你还要继续修复 → 创建 fix 分支（推荐） 此时你已经执行过：\ngit checkout develop\rgit merge feature/plh/auth-server\rgit push 并且 auth-server 已经部分进入 develop。 此时如果发现 bug：\n🔧 必须基于 develop 创建 fix 分支：\ngit checkout develop\rgit pull\rgit checkout -b fix/plh/auth-server-deadlock 修复：\ngit add .\rgit commit -m \u0026#34;fix(auth): 修复死锁问题\u0026#34;\rgit push 修复完成后：\ngit checkout develop\rgit merge fix/plh/auth-server-deadlock\rgit push 最后可删除 fix 分支：\ngit branch -d fix/plh/auth-server-deadlock\rgit push origin --delete fix/plh/auth-server-deadlock ⭐ 场景 C：auth-server 已经合入 main（正式发布版本）→ bug 必须 hotfix 假设你已经发版：\nv1.0.0（包含 auth-server） 用户反馈 bug 了。\n🚑 必须从 main 开热修复分支：\ngit checkout main\rgit pull\rgit checkout -b hotfix/plh/auth-server-token-bug 修复完后必须合两个地方：\n1）合回 main（用于发 v1.0.1）\ngit checkout main\rgit merge hotfix/plh/auth-server-token-bug\rgit push 2）合回 develop（保持一致）\ngit checkout develop\rgit merge hotfix/plh/auth-server-token-bug\rgit push 最后删除 hotfix 分支。\ngit checkout 切换到现有的分支\ngit checkout \u0026lt;branch-name\u0026gt; 创建并切换到新分支\ngit checkout -b \u0026lt;new-branch-name\u0026gt; 从指定的 commit 中取出某个文件或目录，覆盖到当前分支的工作区和暂存区\ngit checkout \u0026lt;commit\u0026gt; -- \u0026lt;文件路径\u0026gt; 只想恢复某个/某几个文件到某版本 → git checkout \u0026ndash; 想切换整个工作区到某分支 → git checkout 只是想临时看看某个历史 commit 整体长什么样、不打算改动 → 才用 git checkout ,并且事后记得 git checkout main 切回来 git cherry-pick 对于多分支的代码库，将代码从一个分支转移到另一个分支是常见需求。 这时分两种情况。一种情况是，你需要另一个分支的所有代码变动，那么就采用合并（git merge）。另一种情况是，你只需要部分代码变动（某几个提交），这时可以采用（cherry pick）。\ngit commit 提交空消息 git commit -m \u0026quot;\u0026quot; --allow-empty-message\ngit config 本地仓库配置 \u0026ndash;local：仅对当前git仓库生效 全局用户配置 \u0026ndash;global：当前操作系统下用户的所有git仓库都生效 当通过git status 看到一串令人困惑的八进制转义字符，例如：\u0026quot;\\345\\237\\272\\346\\234\\254\\345\\212\\237\u0026quot;，只要字节值超过0x80的字符都被视为unusual字符\n关闭路径转义\ngit config --global core.quotepath false 设置 HTTP/HTTPS 代理 git config --global http.proxy http://127.0.0.1:7897 git config --global https.proxy http://127.0.0.1:7897 git clone 浅拷贝 git clone --depth=1 \u0026lt;rep_url\u0026gt; --shallow-submodules = 子模块也用浅克隆 --recurse-submodules\n读取 .gitmodules 自动 clone 每个 submodule checkout 到指定 commit git diff 在git pull 完成后，Git 会在 reflog 里留下一个记录点\nHEAD@{1} = 执行 pull 之前的 HEAD HEAD = 执行 pull 之后的 HEAD\n查看单个文件的改动\ngit diff HEAD@{1} HEAD \u0026ndash; path/to/your/file.cpp 前提：你刚刚执行的就是一次普通 git pull（没有用 --rebase），且还没再提交或切换分支，否则 HEAD@{1} 可能不是刚才的 pull 点。\ngit fetch git fetch [\u0026lt;remote\u0026gt;] [\u0026lt;branch\u0026gt;] \u0026lt;remote\u0026gt;：远程仓库的名称，默认为 origin。 \u0026lt;branch\u0026gt;：指定要获取的远程分支。如果不指定分支，则会获取所有分支。 常见场景与实际例子： 获取远程仓库的所有更新： git fetch origin 这条命令会从远程仓库origin获取所有的分支信息和标签的更新信息，并更新本地的远程追踪分支（如 origin/master）。注意，这并不会修改当前的工作分支或合并任何更改。\n获取特定分支的更新： git fetch origin feature-branch 这条命令只会从远程仓库 origin 获取 feature-branch 分支的更新信息，不会获取其他分支的内容。\n参数：\n\u0026ndash;all\n获取所有远程仓库的更新信息，而不仅仅是默认的远程仓库（origin）。 \u0026ndash;depth=\n通过指定深度来获取一个浅拷贝。此选项会限制获取的历史记录的深度，适用于只关心最近提交的情况。 -p/--prune\n删除本地不存在的远程分支。当远程仓库删除了某个分支，而你本地的远程追踪分支仍然存在时，使用此选项可以同步删除本地不再存在的远程分支。\n--tags\n获取远程仓库的所有标签信息。 git log git log用于查询版本的历史，命令形式如下：\ngit log [\u0026lt;options\u0026gt;] [\u0026lt;since\u0026gt;..\u0026lt;until\u0026gt;] [[--] \u0026lt;path\u0026gt;...] git merge 将两个分支的更改合并到一个分支中\ngit merge \u0026lt;branch-name\u0026gt; \u0026lt; branch-name \u0026gt; :指定要合并的目标分支。当前分支会与该目标分支合并。 命令的常用选项及参数：\n\u0026ndash;no-ff 选项：强制执行合并提交\n用途：即使目标分支可以快进合并，使用 --no-ff 选项仍然会生成一个新的合并提交。这个选项有助于保留合并的历史记录。\n即使目标分支没有修改，如果你使用 --no-ff 选项，Git 也会创建一个新的合并提交。\ngit push 提交到远程仓库，本地的代码已经add ，commit了\n关联远程仓库\ngit remote add origin https://github.com/你的用户名/muduo.git git push [\u0026lt;remote\u0026gt;] \u0026lt;branch\u0026gt; \u0026lt;remote\u0026gt;：远程仓库的名称，默认通常为 origin，表示你与远程仓库的连接。\n\u0026lt;branch\u0026gt;：本地分支的名称，表示将当前分支推送到远程的哪个分支。\n-u 等同 \u0026ndash;set-upstream\n把当前本地分支与刚推上的远端分支建立跟踪关系（upstream）。这样以后在该分支上可以直接用 git pull / git push 不带任何参数，它会默认对应到那个远端分支；git status 也会显示 ahead/behind。\n如果执行git push -u origin main失败：\nerror: 源引用规格 main 没有匹配 error: 无法推送一些引用到 \u0026lsquo;origin\u0026rsquo;\ngit branch -m 命令用于重命名 Git 分支\n重命名当前所在的分支\n如果你在某个分支上，并且希望将其名称更改为新的名字，只需执行：git branch -m 新的分支名称 重命名指定的分支\ngit branch -m 旧的分支名称 新的分支名称 error:\nfatal: 拒绝合并无关的历史\n通常会出现这种情况的原因有\n本地仓库与远程仓库独立初始化： 你可能在本地创建了一个仓库并做了一些提交，而远程仓库中也有独立的提交记录，两者没有共同的起始点。\n仓库历史被重写或重新初始化： 如果远程仓库经历了历史重写（例如使用了 git rebase 或 git init 重新初始化），就可能导致两边的历史不再匹配。\ngit pull 拉取远程仓库中的最新提交，并自动将其合并到当前工作分支\ngit pull \u0026lt;remote\u0026gt; \u0026lt;branch\u0026gt; \u0026lt;remote\u0026gt;：远程仓库的名称，通常默认为 origin。 \u0026lt;branch\u0026gt;：远程分支的名称，指定要拉取并合并的远程分支。如果不指定分支，git pull 会默认拉取当前跟踪的远程分支。 常见场景与实际例子： 拉取远程仓库的更新并合并到当前分支： git pull origin master 这条命令会从远程仓库 origin 的 master 分支拉取更新，并将其自动合并到当前分支。\n拉取远程分支并合并到当前分支： 如果你当前在 develop 分支上，且远程仓库中有 develop 分支的更新，执行以下命令会拉取并合并这些更新：\ngit pull origin develop 这条命令会从远程仓库 origin 的 develop 分支拉取更新，并将其合并到本地的 develop 分支。\n只拉取更新而不合并 如果你只是想获取远程仓库的更新但不进行合并，可以使用 --no-commit 选项：\ngit pull --no-commit origin master 这条命令会拉取远程仓库的更新并将其合并到当前分支，但不会立即提交更改，允许你在合并之前查看差异并做进一步调整。\n强制合并 如果你不介意丢失本地更改，可以使用 git reset 来放弃本地更改并强制拉取远程仓库的内容\ngit reset --hard \u0026lt;commit\u0026gt;\n\u0026lt;commit\u0026gt; 是你要重置到的目标提交，可以是： 提交的哈希值（如 a1b2c3d4） 分支名（如 origin/main 或 master） HEAD~1（上一提交） HEAD（当前提交） git reset --hard HEAD git pull 遇到build目录里的文件没有跟踪，但这是是临时文件并且不需要保留，所以直接删除这些未跟踪的文件。\nerror: The following untracked working tree files would be overwritten by merge: build/CMakeFiles/CMakeDirectoryInformation.cmake build/CMakeFiles/Makefile.cmake build/compile_commands.json\ngit clean -fd删除未跟踪的文件\n-f 是强制删除未跟踪的文件。 -d 是删除未跟踪的目录。 在项目的根目录下创建.gitignore，并添加内容：build/\ngit remote git remote add\ngit stash ​\t用于临时保存当前工作目录和暂存区的改动，使得工作区能够恢复到上一次提交的干净状态。这对于在开发过程中需要切换分支但不想提交当前修改的情况非常有用。你可以将工作进度“存放”起来，稍后再回来继续工作。\n用途： 切换分支时保存当前工作：当你在当前分支上有未提交的更改，并且需要临时切换到其他分支处理问题时，可以使用 git stash 来保存这些更改。 避免提交不完整的工作：当你正在开发一个功能，途中遇到需要解决的紧急问题时，可以暂时将当前工作保存在 stash 中，待解决完紧急问题后再回来继续。 多任务处理：在一个任务的中间，切换到另一个任务时，你可以使用 git stash 临时存储工作，以便稍后恢复。 git stash [\u0026lt;options\u0026gt;] 使用场景与实际例子 查看当前所有的 stash 条目 git stash list 这会列出所有的 stash 条目，每个条目都用一个标识符（例如 stash@{0}）进行区分。\n应用最近保存的 stash git stash apply 应用特定的 stash git stash apply stash@{1} 当你有多个 stash 条目时，可以指定特定的 stash 来应用。stash@{1} 指的是第二个保存的 stash。\n常用选项：\n-u 或 \u0026ndash;include-untracked 除了包含已跟踪文件的改动外，还会保存未跟踪的文件。 -a 或 \u0026ndash;all 除了未跟踪的文件外，还会将忽略文件（.gitignore 文件中列出的文件）保存到 stash 中。 \u0026ndash;keep-index 保存工作目录的改动，但不保存暂存区的内容。此选项会将暂存区的更改保持不变，仅保存工作目录的改动。 git tag git tag [选项] [标签名称] [提交哈希] -a . 附注标签（Annotated Tag)\n-m 指定附注标签的注释信息。\n常用的.gitignore # Prerequisites\r*.d\r# Compiled Object files\r*.slo\r*.lo\r*.o\r*.obj\r# Precompiled Headers\r*.gch\r*.pch\r# Compiled Dynamic libraries\r*.so\r*.dylib\r*.dll\r# Fortran module files\r*.mod\r*.smod\r# Compiled Static libraries\r*.lai\r*.la\r*.a\r*.lib\r# Executables\r*.exe\r*.out\r*.app\rbuild/\rbin/\robjects/\r.vscode/ 如果不小心把 build 目录的内容提交到了 Git 仓库中，你可以按照下面的步骤将其移除，并让 Git 后续忽略该目录：\n添加忽略规则\n在项目根目录下的 .gitignore 文件中添加一行，指定忽略 build 目录（如果没有该文件则新建一个）\n从 Git 暂存区移除 build 目录\n使用下面的命令将 build 目录及其内容从 Git 的版本控制中移除，但本地文件不会被删除\ngit rm -r --cached build 提交更改 提交此次移除操作\ngit commit -m \u0026#34;Remove build directory from version control\u0026#34; 推送到远程仓库 如果你有远程仓库，还需要推送更新\ngit push gdb调试 问题：\narm-mol-linux-uclibcgnueabihf-gdb: loadlocale.c:130: _nl_intern_locale_data: Assertion `cnt \u0026lt; (sizeof (_nl_value_type_LC_TIME) / sizeof (_nl_value_type_LC_TIME[0]))\u0026#39; failed.\rAborted (core dumped) 在运行GDB前临时修改locale环境变量，可以有效避免此问题： export LC_ALL=C export LANG=C 在启动 gdb 时传递参数\n使用 --args 选项启动 gdb\ngdb --args ./main 10001 /home/meian/work/MyProject/study/reator/reatorHttp/readme/ 在 gdb 内部设置参数\n启动 gdb 后，使用 set args 命令设置参数，然后运行\n(gdb) set args 10001 /home/meian/work/MyProject/study/reator/reatorHttp/readme/\r(gdb) run gdbserver 使用方法 PC 运行 gdb mips-linux-gnu-gdb [PC 端应用程序路径] 设置动态库搜索路径 ​\t应用程序往往动态链接 libc、ld 等库，当运行于动态库中 break 时（比如 memcpy）往往无法跟踪到当前 pc 所在位置。这是因为动态库的加载地址是不固定的。因此需要在 PC 端 gdb 上指定动态库加载路径。方法是\nset solib-search-path \\\r/home/meian/work/toolChain/mips-gcc720-glibc229-r519/mips-linux-gnu/libc/lib:\\\r/home/meian/work/toolChain/mips-gcc720-glibc229-r519/mips-linux-gnu/lib:\\\r/home/meian/work/toolChain/mips-gcc720-uclibc229/mips-linux-uclibc/libc/lib:\\\r/home/meian/work/toolChain/mips-gcc720-uclibc229/mips-linux-uclibc/lib\r--------------------------------------------\rsdk的rootfs里的库\rset sysroot /home/meian/work/SDK/JZ_SDK/ISVP-A1-1.6.1-20230911/software/sdk/Ingenic-SDK-A1-1.6.1-20230911/resource/rootfs/root-uclibc-toolchain720_229\rset solib-search-path /home/meian/work/SDK/JZ_SDK/ISVP-A1-1.6.1-20230911/software/sdk/Ingenic-SDK-A1-1.6.1-20230911/resource/rootfs/root-uclibc-toolchain720_229/lib\r---------------------------\rset sysroot /home/meian/work/SDK/JZ_SDK/ISVP-A1-1.7.0-20250407/software/zh/board/Ingenic-SDK-A1-1.7.0-20250407-zh/resource/rootfs/root-uclibc-toolchain720_229\rset solib-search-path \\\r/home/meian/work/SDK/JZ_SDK/ISVP-A1-1.7.0-20250407/software/zh/board/Ingenic-SDK-A1-1.7.0-20250407-zh/resource/rootfs/root-uclibc-toolchain720_229/lib ​\t多个路径之间通过“：”来隔开。注：上述例子中仅指定了 toolchain 中 glibc 的libc 系列库和 libstdc++等主要外部库。\nglibc 的动态库在 toolchain 文件夹下的相对目录为：mips-linux-gnu/libc/lib 与mips-linux-gnu/lib\nuclibc 的动态库在 toolchain 文件夹下的相对目录为：mips-linux-uclibc/libc/lib 与 mips-linux-uclibc/lib\n开发板启动 gdbserver gdbserver [PC 机 IP:端口，与步骤 2 中端口需一致] [应用程序路径] ./gdbserver_a1n 192.168.10.27:1234 nvr_test 链接开发板 target remote [开发板 IP:端口] GDB 找不到带调试信息的库 你要确保运行时使用的 /tmp/nfs/lib//libmafs.so 与带有 debug 信息的版本是同一个文件，或者二者在不同路径导致 GDB 只看到运行时库，却找不到符号对应的调试信息库。\n可以在 GDB 中设置正确的搜索路径：\nset solib-search-path ~/work/MyProject/nvr_nfs/test/ set auto-solib-add on 查看当前栈帧的变量 使用 info locals 查看当前函数的局部变量\n(gdb) info locals pts = 0 tmp = \u0026#34;\\000\\000\\000\\000\\000\\000\\000\u0026#34; u32SetLen = 4 frmLen = 0 frame = 0xe43cf8 __FUNCTION__ = \u0026#34;PutOneNetAFrameToBuffer\u0026#34; systime = {year = 24 \u0026#39;\\030\u0026#39;, month = 11 \u0026#39;\\v\u0026#39;, mday = 28 \u0026#39;\\034\u0026#39;, hour = 16 \u0026#39;\\020\u0026#39;, minute = 5 \u0026#39;\\005\u0026#39;, second = 57 \u0026#39;9\u0026#39;, timestamp = 1732809957, week = 4 \u0026#39;\\004\u0026#39;, reserve = 245 \u0026#39;\\365\u0026#39;} 使用info frame查看当前栈帧的所有信息\n(gdb) info frame Stack level 2, frame at 0xa8685c38: pc = 0x7783c in FrameData::InitData (./encode/include/BufferManage.h:148); saved pc = 0x76b80 called by frame at 0xa8685cc0, caller of frame at 0xa8685c08 source language c++. Arglist at 0xa8685c34, args: this=0xee9da0, datalen=7492 Locals at 0xa8685c34, Previous frame\u0026#39;s sp is 0xa8685c38 Saved registers: r11 at 0xa8685c30, lr at 0xa8685c34 p 打印变量\n以16进制打印内容 p/x \u0026lt;variable\u0026gt; x (examine memory)\n​\t功能：原始字节以十六进制打出来\n起始地址（spsData 指针） 要看的字节数（这里是 21） (gdb) x/21xb spsData 0x7ffff79d7234:\t0x67\t0x64\t0x00\t0x28\t0xac\t0xd2\t0x01\t0xe0 0x7ffff79d723c:\t0x08\t0x9f\t0x96\t0x10\t0x00\t0x00\t0x3e\t0x80 0x7ffff79d7244:\t0x00\t0x0c\t0x35\t0x00\t0x40 x examine memory\n/21 取 21 个单元\nx 以十六进制显示\nb 每个单元大小是 1 byte\nn （next）命令：\n​\t功能：执行下一行代码。如果当前行是一个函数调用，它会把整个函数当作一条语句执行完，不会进入函数内部逐行调试，而是直接执行完函数调用后停在下一行代码处。\ns （step）命令：\n​\t功能：同样也是执行下一行代码，但如果当前行是一个函数调用，它会进入函数内部，开始逐行调试函数里的代码，方便查看函数内部具体的执行细节。\n跳出循环until, /wach、\ngdb 应用程序与源码不在一起： **在 gdb 中添加源码搜索路径: **\n(gdb) directory /tmp/source 验证源码路径是否正确： 你可以使用 info sources 查看 gdb 当前知道的源码文件列表，确保 /tmp/source 被包含其中\n(gdb) info sources 查看socket对应的ip地址\n2263 root 1:28 {IdleProcess} ./FocusAppMc6830v1 2278 root 0:00 [jpegenc] 2392 root 0:00 [kworker/0:2] 2393 root 0:00 -sh 2397 root 0:00 ps ~ # ls -l /proc/2263/fd total 0 lrwx------ 1 root root 64 Jan 14 15:25 0 -\u0026gt; /dev/console lrwx------ 1 root root 64 Jan 14 15:25 1 -\u0026gt; /dev/console l-wx------ 1 root root 64 Jan 14 15:25 10 -\u0026gt; pipe:[3630] lrwx------ 1 root root 64 Jan 14 15:25 11 -\u0026gt; /dev/sda1 lr-x------ 1 root root 64 Jan 14 15:25 12 -\u0026gt; /var/hi_system_config.xml lr-x------ 1 root root 64 Jan 14 15:25 13 -\u0026gt; pipe:[923] l-wx------ 1 root root 64 Jan 14 15:25 14 -\u0026gt; pipe:[923] lr-x------ 1 root root 64 Jan 14 15:25 15 -\u0026gt; pipe:[924] l-wx------ 1 root root 64 Jan 14 15:25 16 -\u0026gt; pipe:[924] ~ # cat /proc/net/tcp sl local_address rem_address st tx_queue rx_queue tr tm-\u0026gt;when retrnsmt uid timeout inode 0: 00000000:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 3637 1 c92c2280 100 0 0 10 0 1: 00000000:5BA0 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 2945 1 c92c0000 100 0 0 10 0 2: 00000000:1F48 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 929 1 c8a06780 100 0 0 10 0 3: 00000000:0050 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 2946 1 c92c05c0 100 0 0 10 0 4: 00000000:0017 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 393 1 c8a00000 100 0 0 10 0 5: DE01A8C0:9CCC C801A8C0:1F40 01 00000000:00000000 00:00000000 00000000 0 0 3633 2 c92c6d40 21 4 0 10 -1 6: 0100007F:5BA0 0100007F:A2D2 08 00000000:00000000 00:00000000 00000000 0 0 798 1 c92c0b80 21 4 28 10 -1 7: 0100007F:5BA0 0100007F:A31E 08 00000000:00000000 00:00000000 00000000 0 0 886 1 c92c5640 21 4 28 10 -1 8: 0100007F:5BA0 0100007F:A2BC 08 00000000:00000000 00:00000000 00000000 0 0 779 1 c8a04500 21 4 28 10 -1 9: 0100007F:5BA0 0100007F:A340 01 00000000:00000000 00:00000000 00000000 0 0 3632 1 c8a005c0 21 4 31 10 -1 gdb不处理信号 (gdb) handle SIGINT pass nostop noprint handle SIGABRT stop print nopass handle SIGSEGV stop print nopass handle SIGILL stop print nopass handle SIGBUS stop print nopass pass —— 将 SIGINT 转发给被调试程序 nostop —— 不在 GDB 层面停下来 noprint—— 不在 GDB 层面打印任何信息 推荐启动模板 在~/.gdbinit 里配置通用的gdbinit\n# ========== 基本体验 ========== set pagination off set confirm off set print thread-events off set disassemble-next-line on # 关键：共享库符号未加载时也允许设置断点（变为 pending） set breakpoint pending on # ========== 信号策略 ========== # Ctrl-C：你要让程序自己处理就用 pass nostop；想随时停住就改成 stop print nopass handle SIGINT pass nostop noprint # 致命信号：停住并打印，不交给程序（避免直接死掉） handle SIGSEGV stop print nopass handle SIGABRT stop print nopass handle SIGBUS stop print nopass handle SIGILL stop print nopass handle SIGFPE stop print nopass # ========== C++ 异常/终止路径 ========== # 所有 throw（可能很吵，必要时再开） # break __cxa_throw # break __cxa_rethrow # terminate/abort（强烈建议） break __cxa_terminate break std::terminate break abort # 这条在你环境里很可能有效（你曾命中过） break std::terminate()@plt # 可选：如果你的 libstdc++ 有 verbose handler break __gnu_cxx::__verbose_terminate_handler add-auto-load-safe-path /home/meian/work/MyProject/test_nvr/nvr_36chn set pagination off (关闭分页显示) 作用：当输出内容超过一屏时，GDB 默认会暂停并提示 --Type \u0026lt;RET\u0026gt; for more, q to quit, c to continue without paging--。 set print thread-events off (关闭线程事件打印) 作用：默认情况下，每当程序创建一个新线程或销毁一个线程时，GDB 都会在控制台打印一条消息（例如 [New Thread 0x... (LWP ...)] 或 [Thread ... exited]）。 set breakpoint pending on (开启待定断点) 作用：允许你对**尚未加载的共享库（.so）**中的函数设置断点。 为什么在嵌入式开发中是必须的： 动态加载库：你的项目引用了 ZLToolkit 和 P2P SDK。这些库通常是以动态库形式存在的。 先设置后运行：在程序刚启动但还没运行到加载这些 .so 文件的代码时，如果你尝试设置断点，GDB 可能会报错说“找不到函数”。开启此选项后，GDB 会先记录下这个断点，等程序运行并加载了对应的库后，再自动激活断点。 set disassemble-next-line on 让 GDB 在每次程序暂停（例如停在断点或单步执行后）时，自动显示即将执行的下一行源代码对应的汇编指令 在项目的.gdbinit\necho [loaded nvr_36 project .gdbinit]\\n # ========== sysroot ========== set sysroot /home/meian/work/SDK/JZ_SDK/ISVP-A1-1.7.0-20250407/software/zh/board/Ingenic-SDK-A1-1.7.0-20250407-zh/resource/rootfs/root-uclibc-toolchain720_229 # ========== 搜索共享库 ========== set solib-search-path \\ /home/meian/work/SDK/JZ_SDK/ISVP-A1-1.7.0-20250407/software/zh/board/Ingenic-SDK-A1-1.7.0-20250407-zh/resource/rootfs/root-uclibc-toolchain720_229/lib:\\ /home/meian/work/SDK/JZ_SDK/ISVP-A1-1.7.0-20250407/software/zh/board/Ingenic-SDK-A1-1.7.0-20250407-zh/resource/rootfs/root-uclibc-toolchain720_229/usr/lib 开启coredump #确保内核中有开启CONFIG_COREDUMP,开启了这个目录就存在 ls /proc/sys/kernel/core_pattern # 1. 打开 core ulimit -c unlimited # 2. 设置路径 echo \u0026#34;/tmp/core.%e.%p.%t\u0026#34; \u0026gt; /proc/sys/kernel/core_pattern # 3. 运行程序 ./nvr_app 内存泄露 asan g++ -fsanitize=address -o test_asan memory.cpp -g plh@plh-virtual-machine:~/work/MyProject/c++/plh/DataStructrues/1.linear_List$ ./test_asan =================================================================\r==12293==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000018 at pc 0x5578e1a712ff bp 0x7ffe0f40ead0 sp 0x7ffe0f40eac0\rWRITE of size 4 at 0x602000000018 thread T0\r#0 0x5578e1a712fe in func() /home/plh/work/MyProject/c++/plh/DataStructrues/1.linear_List/memory.cpp:6\r#1 0x5578e1a71314 in main /home/plh/work/MyProject/c++/plh/DataStructrues/1.linear_List/memory.cpp:11\r#2 0x7fdbb00f2082 in __libc_start_main ../csu/libc-start.c:308\r#3 0x5578e1a711ad in _start (/home/plh/work/MyProject/c++/plh/DataStructrues/1.linear_List/test_asan+0x11ad)\r0x602000000018 is located 4 bytes to the right of 4-byte region [0x602000000010,0x602000000014)\rallocated by thread T0 here:\r#0 0x7fdbb071b587 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cc:104\r#1 0x5578e1a7127e in func() /home/plh/work/MyProject/c++/plh/DataStructrues/1.linear_List/memory.cpp:5\r#2 0x5578e1a71314 in main /home/plh/work/MyProject/c++/plh/DataStructrues/1.linear_List/memory.cpp:11\r#3 0x7fdbb00f2082 in __libc_start_main ../csu/libc-start.c:308\rSUMMARY: AddressSanitizer: heap-buffer-overflow /home/plh/work/MyProject/c++/plh/DataStructrues/1.linear_List/memory.cpp:6 in func()\rShadow bytes around the buggy address:\r0x0c047fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\r0x0c047fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\r0x0c047fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\r0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\r0x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\r=\u0026gt;0x0c047fff8000: fa fa 04[fa]fa fa fa fa fa fa fa fa fa fa fa fa\r0x0c047fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\r0x0c047fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\r0x0c047fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\r0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\r0x0c047fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\rShadow byte legend (one shadow byte represents 8 application bytes):\rAddressable: 00\rPartially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa\rFreed heap region: fd\rStack left redzone: f1\rStack mid redzone: f2\rStack right redzone: f3\rStack after return: f5\rStack use after scope: f8\rGlobal redzone: f9\rGlobal init order: f6\rPoisoned by user: f7\rContainer overflow: fc\rArray cookie: ac\rIntra object redzone: bb\rASan internal: fe\rLeft alloca redzone: ca\rRight alloca redzone: cb\rShadow gap: cc\r==12293==ABORTING ","permalink":"https://plhwiki.cn/linux/common/git/","summary":"\u003ch1 id=\"学习资料\"\u003e学习资料\u003c/h1\u003e\n\u003cp\u003ewin11搭建网络映射器：https://m.comcw.cn/jc/4614.html\u003c/p\u003e\n\u003cp\u003e音视频学习：https://github.com/0voice/audio_video_streaming\u003c/p\u003e","title":"Git"},{"content":"V4L2 框架核心概念 Video for Linux 2 (V4L2) 是 Linux 内核中处理视频设备的标准框架，用于统一管理摄像头、视频采集卡等设备。设计 V4L2 框架的核心目的（思想）：\n用户空间和内核空间分离：应用通过标准系统调用与内核交互 设备抽象：所有视频设备通过 /dev/videoX 节点暴露 缓冲区队列管理：通过 vb2_queue 实现高效的零拷贝数据传输 struct v4l2_device { struct device *dev; struct media_device *mdev; struct list_head subdevs; spinlock_t lock; char name[36]; void (*notify)(struct v4l2_subdev *sd, unsigned int notification, void *arg); struct v4l2_ctrl_handler *ctrl_handler; struct v4l2_prio_state prio; struct kref ref; void (*release)(struct v4l2_device *v4l2_dev); }; 获取当前的格式\nstruct v4l2_format current_format; memset(\u0026amp;current_format, 0, sizeof(current_format)); current_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ioctl(fd, VIDIOC_G_FMT, \u0026amp;current_format) 流程步骤深度解析 标准采集流程顺序： 打开设备 → 查询能力 → 设置格式 → 申请缓冲区 (REQBUFS) → 查询并映射缓冲区 (QUERYBUF + mmap) → 缓冲区入队 (QBUF) → 启动视频流 (STREAMON) → 取帧循环 (DQBUF → 处理 → QBUF) → 停止与释放。\n关键点：必须先把缓冲区 QBUF 入队，再 STREAMON 开流。因为开流后硬件立即开始往队列里的缓冲区填数据，若队列为空则硬件无处可写。\n打开设备文件 /dev/videoX 系统调用：\nint fd = open(\u0026#34;/dev/video0\u0026#34;, O_RDWR); 关键机制：\n内核通过 VFS（Virtual File System） 将设备文件映射到 v4l2_fops 操作集 设备号（主设备号 81，次设备号 X）由内核在设备注册时分配（主设备号 81 专用于 V4L2） 错误处理：\nENODEV：设备不存在 EBUSY：设备被其它进程占用 查询设备能力集 struct v4l2_capability cap; ioctl(fd, VIDIOC_QUERYCAP, \u0026amp;cap) struct v4l2_capability { __u8\tdriver[16]; __u8\tcard[32]; __u8\tbus_info[32]; __u32 version; __u32\tcapabilities; __u32\tdevice_caps; __u32\treserved[3]; }; driver 驱动程序名称。 cap.driver 内核驱动模块名（如 uvcvideo、rkisp、msm-camera 等） cap.card 设备名称（例如:\u0026ldquo;USB Camera\u0026rdquo;、\u0026ldquo;isp-video-output\u0026rdquo;） cap.bus_info 总线信息（设备挂载在哪条总线上） cap.version v4l2驱动版本 cap.capabilities 设备能力标志（bitmask） cap.device_caps 具体 node 的能力（更细粒度) reserved 保留字段 常见能力位 V4L2_CAP_VIDEO_CAPTURE 支持视频采集（摄像头） V4L2_CAP_VIDEO_OUTPUT 支持视频输出（显示设备） V4L2_CAP_STREAMING 支持 mmap / userptr / dmabuf V4L2_CAP_READWRITE 支持 read() / write() 方式 V4L2_CAP_META_CAPTURE 支持元数据采集（ISP metadata） if (cap.capabilities \u0026amp; V4L2_CAP_STREAMING) { // 支持零拷贝流 } 设置视频格式参数 struct v4l2_format fmt = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, .fmt.pix = { .width = 1920, .height = 1080, .pixelformat = V4L2_PIX_FMT_YUYV, .field = V4L2_FIELD_NONE } }; ioctl(fd, VIDIOC_S_FMT, \u0026amp;fmt); 参数协商机制 应用提出请求格式 → 驱动调整为最接近的硬件支持格式 需要用 VIDIOC_G_FMT 验证最终生效的参数（或回读 S_FMT 调用后的 fmt） 常见的格式 V4L2_PIX_FMT_YUYV：YUV 4:2:2 打包格式（裸数据，体积大） V4L2_PIX_FMT_MJPEG：压缩格式（节省带宽） V4L2_PIX_FMT_NV12：Android 常用的 YUV 4:2:0 格式 分配缓冲区（VIDIOC_REQBUFS） struct v4l2_requestbuffers req = { .count = 4, // 请求 4 个缓冲区 .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, .memory = V4L2_MEMORY_MMAP // 内存类型 }; ioctl(fd, VIDIOC_REQBUFS, \u0026amp;req); 类型 说明 使用场景 V4L2_MEMORY_MMAP 内核分配 + mmap 映射 高性能场景（主流选择） V4L2_MEMORY_USERPTR 用户空间分配地址 特殊内存管理需求 V4L2_MEMORY_DMABUF DMA 共享缓冲区 多进程 / 跨设备共享数据型 流程 应用请求缓冲区数量 内核在 vb2_queue 中预分配资源 驱动返回实际分配到的缓冲区数量（req.count 可能被调整，应回读确认） 查询并映射缓冲区（VIDIOC_QUERYBUF + mmap） REQBUFS 只是预分配req.count 块缓冲区，应用还需逐个查询每块缓冲区的长度和偏移量，再用 mmap 映射到用户空间才能访问数据。\nfor (int i = 0; i \u0026lt; req.count; i++) { struct v4l2_buffer buf = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, .memory = V4L2_MEMORY_MMAP, .index = i }; ioctl(fd, VIDIOC_QUERYBUF, \u0026amp;buf); // 拿到 buf.length 和 buf.m.offset buffers[i].length = buf.length; buffers[i].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, // 注意是 PROT_WRITE MAP_SHARED, fd, buf.m.offset); } // mmap 最后一个参数 buf.m.offset 必须来自 VIDIOC_QUERYBUF 的返回，不能凭空构造。\n缓冲区入队（VIDIOC_QBUF） 在开流之前，先把所有缓冲区交给内核，让硬件有地方写数据。\nfor (int i = 0; i \u0026lt; req.count; i++) { struct v4l2_buffer buf = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, .memory = V4L2_MEMORY_MMAP, .index = i }; ioctl(fd, VIDIOC_QBUF, \u0026amp;buf); } 入队（QBUF）是把缓冲区加入队列的动作，由应用完成——这一点不要和 STREAMON 混淆。\n启动视频流（VIDIOC_STREAMON） enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ioctl(fd, VIDIOC_STREAMON, \u0026amp;type); 内核行为 激活设备硬件（如启动摄像头传感器） 初始化 DMA 传输通道 开始让已入队的缓冲区被硬件填充（缓冲区是上一步 QBUF 放进队列的，不是 STREAMON 放进去的） 缓冲区循环处理（VIDIOC_DQBUF → 处理 → VIDIOC_QBUF） 开流后进入核心循环：出队拿到一帧满缓冲区 → 处理 → 再入队循环利用。\nstruct v4l2_buffer buf = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, .memory = V4L2_MEMORY_MMAP }; ioctl(fd, VIDIOC_DQBUF, \u0026amp;buf); // 出队：拿到一帧 // buffers[buf.index].start 即该帧数据，buf.bytesused 为有效长度 process_frame(buffers[buf.index].start, buf.bytesused); ioctl(fd, VIDIOC_QBUF, \u0026amp;buf); // 处理完再入队 sequenceDiagram\rparticipant App as 应用（用户空间）\rparticipant Kernel as 内核空间（V4L2 驱动 / vb2_queue）\rparticipant HW as 硬件（传感器 / DMA）\rApp-\u0026gt;\u0026gt;Kernel: VIDIOC_QBUF（入队空缓冲区）\rKernel-\u0026gt;\u0026gt;HW: 触发 DMA 传输\rHW--\u0026gt;\u0026gt;Kernel: 填充数据完成\rApp-\u0026gt;\u0026gt;Kernel: VIDIOC_DQBUF（请求满缓冲区）\rKernel--\u0026gt;\u0026gt;App: 返回满缓冲区\rNote over App: 处理视频数据\rApp-\u0026gt;\u0026gt;Kernel: 再次 VIDIOC_QBUF（回收缓冲区） 出队（DQBUF）要点 阻塞行为：默认会等待直到有数据就绪（设备以 O_NONBLOCK 打开时变为非阻塞，配合 select/poll 使用）\nbytesused：实际填充的数据长度 sequence：帧序号（用于检测丢帧） flags \u0026amp; V4L2_BUF_FLAG_ERROR：指示传输错误 循环设计优势 零拷贝：数据直接从 DMA 缓冲区映射到用户空间，无需额外拷贝 双缓冲机制：至少需要 2 个缓冲区保证连续采集（一个被硬件写，一个被应用读） 动态调整：应用可动态调整处理速度（如跳过某些帧） 停止与释放 enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ioctl(fd, VIDIOC_STREAMOFF, \u0026amp;type); // 停止采集 for (int i = 0; i \u0026lt; req.count; i++) munmap(buffers[i].start, buffers[i].length); // 解除映射 close(fd); // 关闭设备 核心结构体 核心组件作用\n组件 功能 video_device 设备节点注册（创建 /dev/videoX） v4l2_device 设备逻辑管理（电源 / 子设备协调） vb2_queue 缓冲区队列管理（核心创新点） v4l2_subdev 子设备抽象（传感器 / ISP 等） struct v4l2_buffer 用来描述\u0026quot;一块视频缓冲区\u0026quot;的所有元信息。它的常用字段大致有：\nstruct v4l2_buffer { __u32\tindex;//这块缓冲区在缓冲区队列里的编号（0、1、2...），对应 REQBUFS 申请到的第几块 __u32\ttype;//缓冲区类型，V4L2_BUF_TYPE_VIDEO_CAPTURE（采集） __u32\tbytesused;//实际有效数据长度（采集完成后填充） __u32\tflags;//状态标志位，比如 V4L2_BUF_FLAG_DONE（已完成采集）等 __u32\tfield; struct timeval\ttimestamp;//采集这一帧的时间戳 struct v4l2_timecode\ttimecode; __u32\tsequence; /* memory location */ /*内存管理方式：V4L2_MEMORY_MMAP（内核分配，mmap映射）、V4L2_MEMORY_USERPTR（用户自己分配）、V4L2_MEMORY_DMABUF 等*/ __u32\tmemory; union { __u32 offset;//当 memory = MMAP 时有效，表示这块缓冲区在设备内存映射空间里的偏移量，供 mmap() 使用 unsigned long userptr;//当 memory = USERPTR 时有效，指向用户自己分配的内存地址 struct v4l2_plane *planes; __s32\tfd; } m; __u32\tlength;//缓冲区长度（字节数） __u32\treserved2; union { __s32\trequest_fd; __u32\treserved; }; }; ","permalink":"https://plhwiki.cn/multimedia/v4l2/","summary":"\u003ch1 id=\"v4l2-框架核心概念\"\u003eV4L2 框架核心概念\u003c/h1\u003e\n\u003cp\u003e\u003cstrong\u003eVideo for Linux 2 (V4L2)\u003c/strong\u003e 是 Linux 内核中处理视频设备的标准框架，用于统一管理摄像头、视频采集卡等设备。设计 V4L2 框架的核心目的（思想）：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e用户空间和内核空间分离\u003c/strong\u003e：应用通过标准系统调用与内核交互\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e设备抽象\u003c/strong\u003e：所有视频设备通过 \u003ccode\u003e/dev/videoX\u003c/code\u003e 节点暴露\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e缓冲区队列管理\u003c/strong\u003e：通过 \u003ccode\u003evb2_queue\u003c/code\u003e 实现高效的零拷贝数据传输\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003estruct\u003c/span\u003e \u003cspan class=\"n\"\u003ev4l2_device\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003estruct\u003c/span\u003e \u003cspan class=\"n\"\u003edevice\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"n\"\u003edev\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003estruct\u003c/span\u003e \u003cspan class=\"n\"\u003emedia_device\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"n\"\u003emdev\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003estruct\u003c/span\u003e \u003cspan class=\"n\"\u003elist_head\u003c/span\u003e \u003cspan class=\"n\"\u003esubdevs\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kt\"\u003espinlock_t\u003c/span\u003e \u003cspan class=\"n\"\u003elock\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kt\"\u003echar\u003c/span\u003e \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e36\u003c/span\u003e\u003cspan class=\"p\"\u003e];\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"n\"\u003enotify\u003c/span\u003e\u003cspan class=\"p\"\u003e)(\u003c/span\u003e\u003cspan class=\"k\"\u003estruct\u003c/span\u003e \u003cspan class=\"n\"\u003ev4l2_subdev\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"n\"\u003esd\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kt\"\u003eunsigned\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003enotification\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"n\"\u003earg\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003estruct\u003c/span\u003e \u003cspan class=\"n\"\u003ev4l2_ctrl_handler\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"n\"\u003ectrl_handler\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003estruct\u003c/span\u003e \u003cspan class=\"n\"\u003ev4l2_prio_state\u003c/span\u003e \u003cspan class=\"n\"\u003eprio\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003estruct\u003c/span\u003e \u003cspan class=\"n\"\u003ekref\u003c/span\u003e \u003cspan class=\"n\"\u003eref\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"n\"\u003erelease\u003c/span\u003e\u003cspan class=\"p\"\u003e)(\u003c/span\u003e\u003cspan class=\"k\"\u003estruct\u003c/span\u003e \u003cspan class=\"n\"\u003ev4l2_device\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"n\"\u003ev4l2_dev\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e};\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e获取当前的格式\u003c/p\u003e","title":"v4l2"},{"content":"I2C ​\tI2C（Inter-Integrated Circuit）协议是由Philips公司于1980年代提出的一种 同步、半双工、多主从结构 的串行通信协议，广泛应用于嵌入式系统中的芯片间通信。其核心特点在于仅使用 两条信号线 ： SDA（数据线） 与 SCL（时钟线） ，即可实现多个设备之间的可靠数据传输。\n传输数据 总线时序 i2c 通信以起始信号（START）开始，以终止信号（STOP）结束，起始信号和终止信号均为主机发出\n起始信号：\n当 SCL 为高电平时，SDA 从高电平变为低电平时为起始信号 终止信号：\n当 SCL 为高电平时，SDA 从低电平变成高电平时为终止信号 写操作 主设备要发出一个start信号 然后发出一个设备地址（用来确定往哪个从设备写数据），方向（读/写, 0表示写，1表示读） 从设备回应（用来确定这个设备是否存在），然后就可以传输数据 主设备发送一个字节数据给从设备，并等待回应 每传输一个字节数据，接收方要有一个回应信号（确定数据是否接收完成），然后再传输下一个数据 数据发送完之后，主设备发送一个停止信号 下图：白色背景表示：主→从，灰色背景表示：从→主 驱动 ","permalink":"https://plhwiki.cn/hardware/i2c/","summary":"\u003ch1 id=\"i2c\"\u003eI2C\u003c/h1\u003e\n\u003cp\u003e​\tI2C（Inter-Integrated Circuit）协议是由Philips公司于1980年代提出的一种 \u003cstrong\u003e同步、半双工、多主从结构\u003c/strong\u003e 的串行通信协议，广泛应用于嵌入式系统中的芯片间通信。其核心特点在于仅使用 两条信号线 ： \u003cstrong\u003eSDA（数据线） 与 SCL（时钟线）\u003c/strong\u003e ，即可实现多个设备之间的可靠数据传输。\u003c/p\u003e","title":"i2c"},{"content":"TTY终端 终端 终端是一个电子（或电气）设备，用于向系统输入数据（input），或者将系统接收到的数据显示出来（output），即我们常说的“人机交互设备”。 TTY(Teletypewriter)代表终端设备的概念，它允许用户与系统进行交互，在Linux中，TTY可分为如下几类:\n物理终端（serial port terminal）: 指使用计算机串行端口连接的终端设备, /dev/ttyS* 伪终端（pseudo terminal）: 通常是通过ssh/telnet 登陆的终端, /dev/pts/* 控制终端（controlling terminal）: 代表当前tty设备 /dev/tty 控制台终端（console）: 指计算机的输出设备, 通常是printk信息输出的设备, /dev/ttyn、/dev/console TTY 设备文件 TTY 设备在 Linux 文件系统 中的表现为设备文件，通常位于 /dev 目录下。常见的 TTY 设备文件包括：\n/dev/tty：当前终端的设备文件。 /dev/ttyN：虚拟控制台设备文件，其中 N 是控制台编号（例如，/dev/tty1 对应第一个虚拟控制台）。 /dev/pts/N：伪终端设备文件，其中 N 是伪终端编号。 /dev/ttyS0：串口设备文件，/dev/ttyS1 对应第二个串口设备，以此类推。 使用TTY 进程调度 进程 进程是程序在一个数据集合上的一次动态执行过程。它不仅包含代码，还包含当前的活动状态、内存、系统资源等。进程可以被分为 I/O 密集型（I/O-bound）和 CPU 密集型（CPU-bound）\nI/O 密集型进程 大部分时间都在等待\n数据库 摄像头 网络 网络 策略 策略决定调度程序什么时候让进程运行。\n","permalink":"https://plhwiki.cn/linux/common/kernel/","summary":"\u003ch1 id=\"tty终端\"\u003eTTY终端\u003c/h1\u003e\n\u003ch2 id=\"终端\"\u003e终端\u003c/h2\u003e\n\u003cp\u003e终端是一个电子（或电气）设备，用于向系统输入数据（input），或者将系统接收到的数据显示出来（output），即我们常说的“人机交互设备”。\nTTY(Teletypewriter)代表终端设备的概念，它允许用户与系统进行交互，在Linux中，TTY可分为如下几类:\u003c/p\u003e","title":"kernel"},{"content":"Make 与 CMake 构建笔记 面向 Linux / C/C++ / 交叉编译（ARM、MIPS uClibc 工具链）的构建笔记。\nMake 基础概念 Make 通过比较**目标（target）与其依赖（prerequisite）的修改时间（mtime）**决定是否重建：只要任一依赖比目标新，就执行该目标的命令，并沿依赖图递归向上。\nMakefile 中第一个 target 是默认目标（下文示例里是 all），make 不带参数时构建它。 常用调试开关： make -n：dry-run，只打印将执行的命令，不真正执行。 make -p：打印 make 内部的规则与变量数据库。 make -d：打印为什么某个目标被判定需要重建。 NFS/时钟偏差可能导致 mtime 误判，必要时 make clean 重来。 交叉编译静态库示例 一个完整的交叉编译静态库 Makefile（已修正原示例中的 ar 修饰符与缺失的 .PHONY）：\n# 交叉编译器前缀 CROSS_COMPILER := arm-mol-linux-uclibcgnueabihf- CXX := $(CROSS_COMPILER)g++ AR := $(CROSS_COMPILER)ar PROJECT_ROOT := /home/meian/work/SVN/nvr/plh/eavs_FH_list/eavs_mc6830/project # 编译选项：只放编译期标志。 # -L ./lib -lfdk-aac 属于“链接期”选项，不应放进 CXXFLAGS（编译 .o 阶段用不到） CXXFLAGS = -std=c++11 -Wall -O0 FDK_AAC_INC = -I./include -I./include/fdk-aac -I../mc6830/include # 静态库名称 LIBNAME = libG711toAAC.a # 源文件列表 CPPSRC := AACEncoder.cpp G711toAAC.cpp ../mc6830/G711.cpp # 目标文件列表（把 .cpp 后缀替换成 .o） CPPOBJ = $(CPPSRC:.cpp=.o) # 默认目标 all: lib # 模式规则：编译每个 .cpp -\u0026gt; .o %.o: %.cpp $(CXX) $(CXXFLAGS) $(FDK_AAC_INC) -c $\u0026lt; -o $@ # 归档静态库 # 说明：原示例用的是 rcu，其中 u（仅更新更新的成员）在现代 binutils 默认的 # “确定性归档”模式(D)下会被忽略并告警，故改为 rcs：s 显式表明写入符号索引 lib: $(CPPOBJ) $(AR) rcs $(LIBNAME) $(CPPOBJ) # 仅当使用该 ARM 交叉编译器时，把生成的静态库拷到工程 lib 目录 # 该 ifeq 为解析期条件，紧跟 lib 规则，其下的 cp 会追加到 lib 的 recipe ifeq ($(CROSS_COMPILER),arm-mol-linux-uclibcgnueabihf-) cp $(LIBNAME) $(PROJECT_ROOT)/lib_mc6830/ endif # 清理生成文件 clean: rm -f $(LIBNAME) $(CPPOBJ) # 声明伪目标：避免与同名文件冲突（见“伪目标”一节） .PHONY: all lib clean 关于 ar 修饰符：GNU ar 的 r 操作会自动写入符号索引（armap），rc / rcs 均可直接链接，无需再跑 ranlib；只有大写 S 才会抑制索引。rcs 里的 s 只是把“写索引”的意图显式化，并对非 GNU 的 ar 更具可移植性。\nMakefile 变量与赋值 四种赋值运算符的差异会直接影响标志拼接与工具链切换，是最容易踩的坑：\n运算符 名称 展开时机 典型用途 = 递归 / 延迟展开 每次引用时重新求值（可引用后面才定义的变量） 一般变量；注意 X = $(X)... 会无限递归 := 立即 / 简单展开 定义时求值一次 路径、CROSS_COMPILE 前缀、$(shell ...) 结果，避免反复求值 ?= 条件赋值 仅当变量未定义时赋值 允许命令行/环境覆盖，如 CROSS_COMPILE ?= arm-mol-...- += 追加 继承左侧原有的展开语义 CXXFLAGS += -g 其它要点：\n$(VAR) 与 ${VAR} 等价。 命令行传入的变量优先级最高：make CROSS_COMPILE=mips-linux- 会覆盖 Makefile 里的同名赋值（除非用 override）。 用 := 冻结 $(shell ...) 结果，用 = 则每次引用都会重新执行 shell： GITVER := $(shell git rev-parse --short HEAD) # 只执行一次 GITVER2 = $(shell git rev-parse --short HEAD) # 每次引用都执行一次 recipe 里访问 shell 变量要写两个 $：$$HOME（单个 $ 会被 make 当成自己的变量）。 Target、Prerequisite、Recipe 规则的三要素：\ntarget: prerequisites recipe # 必须以 TAB 开头 recipe 行必须以真实 TAB 开头，用空格会报 *** missing separator. Stop.——编辑器把 TAB 自动转空格是最高频踩坑。 每条 recipe 行在独立的 shell 中执行；跨行要么行尾加 \\ 续行，要么用 ; 连接（如 cd build \u0026amp;\u0026amp; make）。 order-only 依赖：target: normal-prereqs | order-only-prereqs。竖线右侧的依赖只保证“先存在”，其 mtime 变化不会触发重建，常用于先建输出目录： OBJDIR := build $(OBJDIR)/%.o: %.cpp | $(OBJDIR) # $(OBJDIR) 是 order-only 依赖 $(CXX) $(CXXFLAGS) -c $\u0026lt; -o $@ $(OBJDIR): mkdir -p $@ 模式规则与自动变量 自动变量表 变量 含义 $@ 目标名 $\u0026lt; 第一个依赖（编译规则里就是源文件） $^ 全部依赖，去重（写链接行的对象列表用它） $+ 全部依赖，不去重、保持顺序（链接顺序敏感时有用） $? 比目标新的依赖 $* 模式规则中 % 匹配到的茎（stem） $(@D) / $(@F) 目标的目录部分 / 文件名部分 链接规则示例：\napp: $(CPPOBJ) $(CXX) $^ -o $@ $(LDFLAGS) # $^ = 所有 .o 静态模式规则与 VPATH 静态模式规则：只对指定目标列表套用模式，比通配 %.o: %.cpp 更可控： $(CPPOBJ): %.o: %.cpp $(CXX) $(CXXFLAGS) -c $\u0026lt; -o $@ 源文件散落在多目录（如本例的 ../mc6830/）时，用 vpath 让 make 跨目录搜索： vpath %.cpp ../mc6830 # 只对 .cpp 生效 # VPATH = ../mc6830:../common # 对所有类型生效 自动头文件依赖生成 %.o: %.cpp 只把 .cpp 列为依赖，头文件对 make 不可见——改了 .h 不会触发重编，会链接进过期的 .o，是典型的隐蔽 bug。让编译器生成依赖文件即可解决：\nCXXFLAGS += -MMD -MP -include $(CPPOBJ:.o=.d) # 放在 Makefile 末尾 -MMD：生成每个 .o 对应的 .d 依赖文件，只记录用户头文件（-MD 连系统头也记）。 -MP：为每个头文件生成一个空的伪目标，避免头文件被删后 make 报“无规则可用”。 编译和链接选项 编译分阶段 -E：只做预处理，输出到 stdout。 -S：编译到汇编（.s）。 -c：编译+汇编到目标文件 .o，不链接（编译多文件工程的常态）。 不带上述选项：一路编译并链接成可执行文件。 gcc -c hello.c -o hello.o # 只编译，产出 .o gcc hello.o -o hello # 链接 编译期 vs 链接期选项 阶段 常用选项 编译期 -I\u0026lt;dir\u0026gt; -D\u0026lt;宏\u0026gt; -std= -O0/-O2/-Os -g -Wall -Wextra -Werror -f*（如 -fPIC） 链接期 -L\u0026lt;dir\u0026gt; -l\u0026lt;name\u0026gt; -Wl,\u0026lt;原样传给 ld 的选项\u0026gt; -shared -static -pthread -I（大写 i）：头文件搜索目录——把 \u0026lt;dir\u0026gt; 加入预处理时 #include 的搜索路径（参数是目录，不是某个头文件）。 -L（大写 L）：库文件搜索目录。 -l（小写 L）：链接指定库（-lworld 找 libworld.so 或 libworld.a）。 gcc -o hello hello.c -I /home/hello/include -L /home/hello/lib -lworld 嵌入式常用体积/优化选项：-Os（为体积优化，flash 受限时常用）、-Og（可调试的优化）；配合 -ffunction-sections -fdata-sections + 链接期 -Wl,--gc-sections 剔除未用段；strip（或链接加 -s）去符号进一步减小体积。 -fpic、-fPIC、-fpie、-fPIE 的区别 （原为空标题，此处补齐。）\n-fPIC：生成位置无关代码（Position-Independent Code），共享库（.so）必需——凡是要进 .so 的 .o 都得用它编译。 -fpic（小写）：同样是位置无关代码，但用更小/更快的 GOT 寻址序列，受平台 GOT 表大小限制；MIPS/ARM 上大型库容易溢出，拿不准就用 -fPIC。 -fPIE / -fpie：位置无关可执行文件（Position-Independent Executable），配合 ASLR；-fPIE 是编译期标志，链接期还要加 -pie。 PIC vs PIE：PIC 允许符号插入（interposition，用于库）；PIE 面向主程序、可做更激进优化。 交叉/嵌入式注意：很多 uClibc 工具链默认构建非 PIE；把用 -fPIC 编的 .o 混进非 PIC 的静态库再链进 .so，会报 recompile with -fPIC 的重定位错误。 静态库与动态库 创建 静态库（.a）：把多个 .o 打包，链接时只抽取被引用到的成员，代码进最终可执行文件（体积大、无运行期依赖）。 $(CROSS)gcc -c a.c b.c $(CROSS)ar rcs libfoo.a a.o b.o # s = 写符号索引 动态库（.so）：编译加 -fPIC，链接加 -shared，运行期由动态加载器解析（体积小、多进程共享）。 $(CROSS)gcc -fPIC -c a.c b.c $(CROSS)gcc -shared -Wl,-soname,libfoo.so.1 -o libfoo.so.1.0.0 a.o b.o soname 与三级命名 用 -Wl,-soname,libfoo.so.1 记录 soname。 磁盘惯例：真实文件 libfoo.so.1.0.0 ← 运行期符号链接 libfoo.so.1（= soname）← 开发期符号链接 libfoo.so（-lfoo 链接时找它）。 链接顺序敏感（高频 undefined reference 根源） 链接器从左到右单遍处理，库必须出现在引用它符号的对象/库之后，否则报 undefined reference：\n# G711toAAC 用到 fdk-aac，所以 fdk-aac 放在它右边 $(CXX) main.o libG711toAAC.a -L./lib -lfdk-aac -o app 两个静态库相互依赖时：重复列出，或用 -Wl,--start-group libA.a libB.a -Wl,--end-group。 -Wl,-Bstatic / -Wl,-Bdynamic 可强制某段按静态/动态链接。 运行期库查找与排查 加载器查找顺序：DT_RPATH → LD_LIBRARY_PATH → DT_RUNPATH → /etc/ld.so.cache（ldconfig）→ 默认目录。 嵌入搜索路径：-Wl,-rpath,/usr/local/lib，或用 $ORIGIN 相对可执行文件定位 -Wl,-rpath,'$ORIGIN/../lib'。 交叉编译时为传递性 .so 依赖指定仅链接期搜索路径：-Wl,-rpath-link,\u0026lt;dir\u0026gt;。 全静态可执行文件（最小 rootfs 常用）：-static；只静态化工具链运行时：-static-libgcc -static-libstdc++。 排查：arm-...-readelf -d a.out 看 NEEDED/RPATH；readelf -h a.out 看架构与 ABI。 Linux 下的符号可见性（对应 Windows 的 dllexport/dllimport） 下面这段 xlog_API 宏是 Windows 的库导出/导入写法（__declspec(dllexport/dllimport)）；在 Linux 分支上它展开为空，也就没法控制 .so 到底导出哪些符号。\n#ifndef XLOG_H #define XLOG_H // __declspec(dllexport) 导出 xLog 类的函数到 lib 文件中 // __declspec(dllimport) 导入 xLog 类的函数到 exe 文件中 #ifndef _WIN32 // linux # define xlog_API #else # ifdef xlog_STATIC // 静态库 # define xlog_API # else # ifdef xlog_EXPORTS # define xlog_API __declspec(dllexport) // 库项目 # else # define xlog_API __declspec(dllimport) // 调用方 # endif # endif #endif class xlog_API XLog { public: XLog(); ~XLog() = default; }; #endif // XLOG_H Linux/GCC 的对应机制是 visibility 属性：\n用 __attribute__((visibility(\u0026quot;default\u0026quot;))) 标记要导出的符号，编译 .so 时加 -fvisibility=hidden 把其余符号默认隐藏。 即把上面 Linux 分支从 #define xlog_API（空）改为构建共享库时定义为 __attribute__((visibility(\u0026quot;default\u0026quot;)))。 好处：减小 .so 符号表、加快目标机加载，是 dllexport/dllimport 的跨平台对应物。 伪目标 避免和同名文件冲突 如果一个规则并不产生与目标同名的文件，则其命令在每次 make 该目标时都应执行。例如：\nclean: rm *.o temp 因为 rm 命令并不产生名为 clean 的文件，正常情况下每次 make clean 都会执行。但如果目录里恰好出现了名为 clean 的文件，规则就失效了：它没有依赖，clean 文件永远被视为“最新”，命令永远不执行。用 .PHONY 声明可避免此问题：\n.PHONY: clean clean: rm *.o temp 这样 make clean 会无视是否存在 clean 文件，始终执行。\n常备伪目标清单 工程里常声明为伪目标的：all clean distclean install uninstall test。可集中声明：\n.PHONY: all clean install .PHONY 除避免与同名文件冲突外，还能让 make 跳过对该名字的文件系统 stat 检查，略微提速。\n递归 Make 内核模块编译 -C \u0026lt;dir\u0026gt;：进入 \u0026lt;dir\u0026gt; 目录后再执行其 Makefile。 M=：传给内核 Makefile 的变量，指定外部模块源码目录。 make -C $(KDIR) M=$(PWD) modules 跨目录递归要点 子目录调用用 $(MAKE) 而非裸 make——这样 -jN、MAKEFLAGS、jobserver 才能正确下传，并行才生效： sub: $(MAKE) -C subdir export VAR 把变量下传给子 make；命令行传入的变量（如 make CROSS_COMPILE=...）默认自动下传。 make -jN 并行构建；个别有竞态的目标用 .NOTPARALLEL 强制串行，或用 order-only 依赖保证顺序。 递归 make 的固有缺点是跨目录依赖不完整（Recursive Make Considered Harmful）；大工程可考虑非递归单 Makefile，或改用 CMake。 常见问题 典型编译/链接/交叉错误排查 报错 原因与处理 *** missing separator. Stop. recipe 用了空格而非 TAB undefined reference to X 缺库，或库顺序错（库要放在引用它的对象右侧），循环依赖用 --start-group cannot find -lfoo -L 路径或库名不对 wrong ELF class / file 显示架构不符 误用了主机 gcc 或错的工具链 version 'GLIBC_2.xx' not found / ABI 报错 目标机 libc 与工具链不匹配（uClibc 与 glibc 混用尤甚） fatal error: xxx.h: No such file or directory 缺 -I，或交叉编译未设 --sysroot 导致系统头缺失 找不到头文件（CMake） main.cpp:10:10: fatal error: gpio.h: No such file or directory #include \u0026#34;gpio.h\u0026#34; ^~~~~~~~ 排查思路：确认头文件实际所在目录，然后用 target_include_directories（见“CMake Target 化用法”）把该目录加入目标的头文件搜索路径；交叉编译还要确认系统头是否在 sysroot 内。\n待确认：原笔记此处「目录结构」示意图缺失，具体工程的目录布局需本人补充。\n设置 gdb 调试模式（CMake） set(CMAKE_BUILD_TYPE Debug) set(CMAKE_CXX_FLAGS_DEBUG \u0026#34;-O0 -g -Wall\u0026#34;) 如果不把 CMAKE_BUILD_TYPE 设成 Debug，CMake 根本不会去读 CMAKE_CXX_FLAGS_DEBUG，只会用无后缀的 CMAKE_CXX_FLAGS。\nCMake 基础 out-of-source 构建规范 cmake -S . -B build # 配置：源码在 .，构建产物放 build/ cmake --build build -jN # 构建（-jN 并行） -S \u0026lt;dir\u0026gt;：源码目录（含顶层 CMakeLists.txt 的目录，不是 CMakeLists.txt 文件本身）。 -B \u0026lt;dir\u0026gt;：构建目录，不存在会自动创建，所有中间文件/生成的 makefile 都放这里。 选项大小写敏感：是大写 -S/-B，-s/-b 无效；注意 -S . -B b 中间的空格不能少（-S .-B b 会把 .-B 当成 -S 的值）。 好处：产物与源码隔离，清理只需删 build/。 Windows（需先打开 Developer Command Prompt）：\ncmake -S . -B build -G \u0026#34;NMake Makefiles\u0026#34; cmake --build build 生成 compile_commands.json 供 clangd / VS Code 等做代码索引：\nset(CMAKE_EXPORT_COMPILE_COMMANDS ON) VS Code CMake Tools 里也可配置：\n// .vscode/settings.json { \u0026#34;cmake.configureArgs\u0026#34;: [ \u0026#34;-DCMAKE_EXPORT_COMPILE_COMMANDS=ON\u0026#34; ] } CMake 工作原理 安装指定版本 CMake（源码编译） Linux：\nwget https://github.com/Kitware/CMake/releases/download/v3.23.1/cmake-3.23.1.tar.gz tar -xvf cmake-3.23.1.tar.gz cd cmake-3.23.1 ./configure make -j16 sudo make install 默认前缀 /usr/local：可执行文件（cmake/ctest/cpack/ccmake）装到 /usr/local/bin，模块/模板数据装到 /usr/local/share/cmake-3.23。 /usr/local/bin 通常已在 PATH 中，一般无需再 export；若不在，用 export PATH=/usr/local/bin:$PATH（不要把 share/cmake-3.23 加进 PATH，那里没有可执行文件）。 Windows：官网 https://cmake.org/files/v3.23/ 下载，把 ...\\cmake-3.23.1-windows-x86_64\\bin 加入 PATH。\n第一个 CMakeLists.txt # 指定 cmake 最低版本 cmake_minimum_required(VERSION 3.20) # 项目名称 project(first_cmake) # 生成可执行程序 add_executable(first_cmake 101first_cmake.cpp) 配置 + 构建：\ncmake -S . -B build # 配置 cmake --build build # 构建 路径相关变量 变量 含义 CMAKE_SOURCE_DIR 整个构建的顶层源码目录（第一次进入的 CMakeLists.txt 所在目录），不随 add_subdirectory() 或子目录的 project() 改变 PROJECT_SOURCE_DIR 当前 project() 的源码目录。顶层时等于 CMAKE_SOURCE_DIR；子目录再次调用 project() 后，在其作用域内变为子项目根 CMAKE_CURRENT_SOURCE_DIR 当前 CMakeLists.txt 所在目录（写相对本目录路径最稳） CMAKE_CURRENT_LIST_DIR 当前被处理文件（含被 include 的 .cmake 模块）所在目录 用法建议：\n# 本模块内的路径用 CMAKE_CURRENT_LIST_DIR（推荐） ${CMAKE_CURRENT_LIST_DIR}/include # 引用其他模块用 PROJECT_SOURCE_DIR / CMAKE_SOURCE_DIR ${PROJECT_SOURCE_DIR}/modules/parameter/include ${PROJECT_SOURCE_DIR}/3rdpart/tinyxml2 编译类型与 C++ 标准 CMAKE_BUILD_TYPE：Debug / Release / RelWithDebInfo / MinSizeRel。单配置生成器（Unix Makefiles、Ninja）需显式设置才生效；嵌入式常用 MinSizeRel（→ -Os）。 C++ 标准优先用标准变量或 target 级命令，而非手写 -std=： set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 或 target 级：target_compile_features(app PUBLIC cxx_std_11) 追加编译选项 # 注意：必须加引号，否则会存成分号列表 \u0026#34;-O2;-g\u0026#34;，传给编译器是畸形单参数、原值非空时会编译失败 set(CMAKE_CXX_FLAGS \u0026#34;${CMAKE_CXX_FLAGS} -g\u0026#34;) # 更稳妥的等价写法： string(APPEND CMAKE_CXX_FLAGS \u0026#34; -g\u0026#34;) add_compile_options(-g) # 正确处理列表语义 其它示例中出现的变量：\ncmake_minimum_required(VERSION 3.0) project(chat) # 最终可执行文件输出路径 set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) # 头文件搜索路径（全局，不递归子目录） include_directories(${PROJECT_SOURCE_DIR}/include) 收集源文件 aux_source_directory(\u0026lt;dir\u0026gt; \u0026lt;var\u0026gt;)：收集目录下所有源文件。 aux_source_directory(\u0026#34;./src\u0026#34; LIB_SRCS) file(GLOB ...)：按通配收集。语法 file(\u0026lt;operation\u0026gt; \u0026lt;output_variable\u0026gt; \u0026lt;args...\u0026gt;)，常用 operation：GLOB（收集）、GLOB_RECURSE（递归收集）、COPY、REMOVE、WRITE。 # 修正：原写法 file(GLOB SRC2 FILE ...) 中的 FILE 不是合法关键字， # 会被当成匹配不到的 glob 模式静默吞掉。删掉即可： file(GLOB SRC2 ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) FILE(GLOB H_FILE \u0026#34;${INCLUDE_PATH}/xcpp/*.h\u0026#34;) file(GLOB) 合法选项只有 LIST_DIRECTORIES / RELATIVE / CONFIGURE_DEPENDS。且 GLOB 结果在配置阶段固定，增删文件不会自动重配（除非用 CONFIGURE_DEPENDS）；单个已知源文件建议直接 set(SRC ...)。\nmessage 用法 日志级别 级别 说明 输出流 FATAL_ERROR 终止 CMake 过程 stderr SEND_ERROR 进程继续但最终不生成（不会执行 add_executable/add_library），打印行号 stderr WARNING 打印代码路径和行号 stderr 无关键字（NOTICE） 等同于不加级别 message(\u0026quot;...\u0026quot;) stderr STATUS 加前缀 --，用户可能感兴趣 stdout VERBOSE 默认不显示的详细信息 stdout DEBUG 加前缀 -- stdout TRACE 加前缀 -- stdout message(FATAL_ERROR \u0026#34;TEST FATAL ERROR\u0026#34;) # 终止；其后不执行 message(SEND_ERROR \u0026#34;TEST SEND ERROR\u0026#34;) # 继续但不生成目标 message(WARNING \u0026#34;TEST WARNING\u0026#34;) message(\u0026#34;TEST NOTICE\u0026#34;) # 等同无级别 message(STATUS \u0026#34;TEST STATUS\u0026#34;) message(VERBOSE \u0026#34;TEST VERBOSE\u0026#34;) # 需 --log-level=VERBOSE 才显示 设置显示级别与重定向：\ncmake -S . -B build --log-level=VERBOSE cmake -S . -B build --log-level=VERBOSE \u0026gt; log.txt # 仅 stdout 重定向到文件 cmake -S . -B build --log-level=VERBOSE \u0026gt; log.txt 2\u0026gt;\u0026amp;1 # stdout 和 stderr 都写入 log.txt 修正：原笔记写作 \u0026gt; log.txt \u0026gt; 2\u0026gt;\u0026amp;1，多出的第二个 \u0026gt; 是 bash 语法错误，命令根本无法执行；正确写法是 \u0026gt; log.txt 2\u0026gt;\u0026amp;1。\n查找库日志（CHECK_START / CHECK_PASS / CHECK_FAIL） message(CHECK_START \u0026#34;查找 xcpp\u0026#34;) set(CMAKE_MESSAGE_INDENT \u0026#34;--\u0026#34;) # 嵌套查找的缩进 message(CHECK_START \u0026#34;查找 xlog\u0026#34;) message(CHECK_PASS \u0026#34;成功\u0026#34;) message(CHECK_START \u0026#34;查找 xtheadpool\u0026#34;) message(CHECK_FAIL \u0026#34;失败\u0026#34;) set(CMAKE_MESSAGE_INDENT \u0026#34;\u0026#34;) # 取消缩进 message(CHECK_FAIL \u0026#34;失败\u0026#34;) 让 message 输出颜色 # \\033[显示方式;前景色;背景色m ... \\033[0m # 显示方式：0-默认 1-高亮 2-低亮 3-下划线 4-闪烁 5-慢闪 6-快闪 7-反白 8-隐藏 9-划掉 string(ASCII 27 Esc) set(Red \u0026#34;${Esc}[0;31m\u0026#34;) # 红色 set(Blue \u0026#34;${Esc}[1;34m\u0026#34;) # 蓝色高亮 set(RedB \u0026#34;${Esc}[1;31;40m\u0026#34;) # 红字黑底 set(End \u0026#34;${Esc}[0m\u0026#34;) message(\u0026#34;${Red} 红色内容${End}\u0026#34;) message(\u0026#34;${Blue} 蓝色内容${End}\u0026#34;) message(\u0026#34;${RedB} 红色+背景内容${End}\u0026#34;) 前景色 / 背景色 / 颜色（修正：黄色背景码为 43，原表误写作 34）\n前景色 背景色 颜色 30 40 黑色 31 41 红色 32 42 绿色 33 43 黄色 34 44 蓝色 35 45 紫红色 36 46 青蓝色 37 47 白色 变量使用 变量引用是值替换，未设置的变量返回空字符串。 变量引用可嵌套，从内向外求值。 变量名大小写敏感。 message() 拼接多个字符串参数时无分隔符。 set(VAR1 \u0026#34;测试变量VAR1的值\u0026#34;) message(\u0026#34;VAR1= \u0026#34; ${VAR1}) message(\u0026#34;VAR1 in string ${VAR1}\u0026#34;) message(\u0026#34;\\${VAR1} = ${VAR1}\u0026#34;) # 想打印字面 ${} 用 \\ 转义 set(VAR2 \u0026#34;VAR1\u0026#34;) message(\u0026#34;VAR2= \u0026#34; ${${VAR2}}) # 嵌套访问，取到 VAR1 的值 unset(VAR1) # 取消变量 message(\u0026#34;\\${VAR1} = ${VAR1}\u0026#34;) # 注意：多参数无分隔符，下面输出为“参数1参数2参数3”，不是带空格的 message(\u0026#34;参数1\u0026#34; \u0026#34;参数2\u0026#34; \u0026#34;参数3\u0026#34;) if 流程控制 if(\u0026lt;conditional\u0026gt;) elseif(\u0026lt;conditional\u0026gt;) else() endif() if(\u0026lt;constant\u0026gt;) 常量：\nON,YES,TRUE,Y 或非零数（含浮点数）→ 真。 OFF,NO,FALSE,N,IGNORE,NOTFOUND、空字符串、或以 -NOTFOUND 结尾的字符串 → 假。 if(\u0026lt;variable\u0026gt;) 变量： 非假值常量为真；未定义变量为假；环境变量总为假。\nif(1) message(\u0026#34;1 is true\u0026#34;) endif() if(0) message(\u0026#34;0 is true\u0026#34;) else() message(\u0026#34;0 is false\u0026#34;) endif() if(VAR_NOT_DEF) # 未定义变量默认为假 message(\u0026#34;VAR_NOT_DEF is true\u0026#34;) else() message(\u0026#34;VAR_NOT_DEF is false\u0026#34;) endif() if(\u0026#34;ON\u0026#34;) message(\u0026#34;String ON is true\u0026#34;) endif() 一元 / 二元 / 正则匹配 一元判断：DEFINED（变量是否定义）、COMMAND（是否为已定义的命令/宏/函数）、EXISTS（路径是否存在）。\n二元判断：EQUAL（数字相等）、STREQUAL（字符串相等）。\nset(VAR1 123) if(VAR1 EQUAL 123) message(\u0026#34;VAR1 EQUAL 123\u0026#34;) endif() if(VAR1 STREQUAL \u0026#34;123\u0026#34;) message(\u0026#34;VAR1 STREQUAL 123\u0026#34;) endif() # 推荐用 STREQUAL MATCHES 正则：if(\u0026lt;variable|string\u0026gt; MATCHES regex)\nset(STR1 \u0026#34;abcd1234\u0026#34;) if(STR1 MATCHES \u0026#34;[a-z]+\u0026#34;) message(\u0026#34;MATCHES [a-z]+ true\u0026#34;) endif() if(STR1 MATCHES \u0026#34;^[a-z]+$\u0026#34;) message(\u0026#34;full match\u0026#34;) else() message(\u0026#34;no full match\u0026#34;) endif() cache 缓存变量 语法：set(\u0026lt;var\u0026gt; \u0026lt;value\u0026gt;... CACHE \u0026lt;type\u0026gt; \u0026lt;docstring\u0026gt; [FORCE])\ntype：BOOL（ON/OFF 勾选）、FILEPATH（选文件）、PATH（选目录）、STRING、INTERNAL（内部，不显示在 GUI）。 docstring：说明文档。 set(VAR1 \u0026#34;VALUE1-1\u0026#34; CACHE STRING \u0026#34;cache doc\u0026#34;) set(VAR1 \u0026#34;VALUE1-2\u0026#34; CACHE STRING \u0026#34;cache doc\u0026#34;) # 缓存变量二次赋值不生效 message(\u0026#34;VAR1 = ${VAR1}\u0026#34;) set(VAR1 \u0026#34;VALUE1-3\u0026#34; CACHE STRING \u0026#34;cache doc\u0026#34; FORCE) # FORCE 强制修改 set(VAR_BOOL1 \u0026#34;ON\u0026#34; CACHE BOOL \u0026#34;bool 001\u0026#34;) set(VAR_FILE \u0026#34;filepath\u0026#34; CACHE FILEPATH \u0026#34;var file path\u0026#34;) set(VAR_PATH \u0026#34;path\u0026#34; CACHE PATH \u0026#34;var path\u0026#34;) set(VAR_INTERNAL \u0026#34;internal var\u0026#34; CACHE INTERNAL \u0026#34;internal var\u0026#34;) option(OPT1 \u0026#34;opt1 doc\u0026#34; OFF) option(OPT2 \u0026#34;opt2 doc\u0026#34; ON) Windows：cmake-gui。 Linux：先 cmake -S . -B build 生成 cache，再 ccmake build 交互修改。 普通变量与缓存变量重名（CACHE 覆盖） set(normalVar1 \u0026#34;normal var1\u0026#34;) set(normalVar1 \u0026#34;Cache var1\u0026#34; CACHE STRING \u0026#34;cache doc\u0026#34;) message(\u0026#34;normalVar1 = ${normalVar1}\u0026#34;) message(\u0026#34;CACHE{normalVar1} = $CACHE{normalVar1}\u0026#34;) # CMP0126 策略：OLD 删除同名普通变量；NEW 保留普通变量（读 cache 需用 $CACHE{VAR}） cmake_policy(SET CMP0126 OLD) 用 -D 传递缓存变量 cmake -S . -B b5 -D PARA1=para00001 第二次不带 -D 运行仍会保留 PARA1（已写入 cache），除非再次 -D PARA1=new 覆盖。\ninclude 用法 include(\u0026#34;cmake/test_cmake.cmake\u0026#34;) include(\u0026#34;cmake/test_cmake1.cmake\u0026#34; OPTIONAL) # 找不到不报错 include(\u0026#34;cmake/test_cmake1.cmake\u0026#34; OPTIONAL RESULT_VARIABLE ret) message(\u0026#34;ret = ${ret}\u0026#34;) # 未找到为 NOTFOUND include(\u0026#34;cmake/test_cmake.cmake\u0026#34; OPTIONAL RESULT_VARIABLE ret) message(\u0026#34;ret = ${ret}\u0026#34;) # 找到则为绝对路径 CMake 自带变量 提供信息：PROJECT_NAME（对应 project() 的名字）。 改变行为：BUILD_SHARED_LIBS（ON 动态库 / OFF 静态库，默认 OFF）。 描述系统：MSVC、WIN32（=1 为 Windows）、UNIX（=1 为 Linux）、CMAKE_SYSTEM_NAME。 控制构建：CMAKE_COLOR_MAKEFILE、输出路径等。 set(BUILD_SHARED_LIBS ON) # 后续 add_library 默认建动态库 add_definitions(-Dxlog_STATIC) # 向 C++ 传宏（现代写法见下节 target_compile_definitions） message(\u0026#34; MSVC = \u0026#34; ${MSVC}) message(\u0026#34; WIN32 = \u0026#34; ${WIN32}) message(\u0026#34; UNIX = \u0026#34; ${UNIX}) message(\u0026#34;CMAKE_SYSTEM_NAME = \u0026#34; ${CMAKE_SYSTEM_NAME}) # 把头文件也列进来：作用是让 IDE/工程生成器在项目树里显示（头文件本身不编译） # 注意：并不是“列了头文件才会在头文件改动时重编”——重编靠编译器/CMake 的依赖扫描自动完成 add_library(${PROJECT_NAME} xlog.cpp xlog.h) 分步编译与清理指定 target cmake --build . --target help # 查看所有目标 cmake --build . --target 101first_cmake.i # 预处理 cmake --build . --target 101first_cmake.s # 编译到汇编 cmake --build . --target 101first_cmake.o # 汇编到目标文件 cmake --build . --target clean # 清理 调试：打印生成的具体指令 set(CMAKE_VERBOSE_MAKEFILE ON) # 默认 OFF 或 cmake --build . -v（第一次生成就要加 -v，否则日志不完整，可清理后重生成）。 输出路径设置 set(CMAKE_LIBRARY_OUTPUT_DIRECTORY \u0026#34;${CMAKE_CURRENT_LIST_DIR}/lib\u0026#34;) # .so set(CMAKE_RUNTIME_OUTPUT_DIRECTORY \u0026#34;${CMAKE_CURRENT_LIST_DIR}/bin\u0026#34;) # 可执行/dll/pdb set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY \u0026#34;${CMAKE_CURRENT_LIST_DIR}/lib\u0026#34;) # .a 静态库 括号注释 3.0 之后支持括号注释 #[[ ... ]]，可跨行、可行内：\nmessage(\u0026#34;参数1\u0026#34; #[[中间注释]] \u0026#34;参数2\u0026#34;) # 输出：参数1参数2 属性 属性是作用域为特定对象（全局/目录/目标/源文件/测试/缓存）的变量。\nset_property / get_property：设置 / 读取属性。 define_property：定义自定义属性。 set_property(GLOBAL PROPERTY TEST_GLOBAL \u0026#34;test global 001\u0026#34;) set_property(SOURCE myfile.cpp PROPERTY COMPILE_FLAGS \u0026#34;-Wall\u0026#34;) get_property(result TARGET my_target PROPERTY CXX_STANDARD) 作用域类型：GLOBAL（全局）、DIRECTORY [\u0026lt;dir\u0026gt;]（目录）、TARGET \u0026lt;target\u0026gt;（目标）、SOURCE \u0026lt;src\u0026gt;（源文件）、INSTALL \u0026lt;file\u0026gt;、TEST \u0026lt;test\u0026gt;、CACHE \u0026lt;entry\u0026gt;、VARIABLE。\nCMake Target 化用法 现代 CMake 提倡以「目标（target）」为中心组织依赖，而非全局的 include_directories / add_definitions / link_libraries。\ntarget_include_directories 为特定目标设置头文件搜索目录，支持三种作用域：\ntarget_include_directories(\u0026lt;target\u0026gt; [PRIVATE|PUBLIC|INTERFACE] \u0026lt;dirs\u0026gt;) PRIVATE：仅当前目标可见，下游不继承。 PUBLIC：当前目标 + 依赖它的下游都可见。 INTERFACE：当前目标不用，只有下游用。 target_link_libraries（依赖传播） 组织多库交叉编译工程的核心，PUBLIC/PRIVATE/INTERFACE 在这里真正发挥作用——它会自动传播被链接目标的 usage requirements（头文件目录、宏、编译/链接选项等）：\ntarget_link_libraries(app PRIVATE foo) # foo 仅 app 自己用 target_link_libraries(app PUBLIC foo) # app 及其下游都继承 foo 的 usage requirements target_link_libraries(app INTERFACE foo) # app 自己不用，只有下游用 同理，用 target_compile_options / target_compile_definitions / target_compile_features 取代全局的 add_definitions / CMAKE_CXX_FLAGS。\n链接预编译的第三方库（.a / .so） 原「cmake 链接静态库」一节只有 add_executable + include_directories，缺真正的链接命令，补齐如下：\ncmake_minimum_required(VERSION 3.20) project(test_xlog) add_executable(test_xlog test_log.cpp) include_directories(\u0026#34;../xlog\u0026#34;) # 头文件路径 # 方式一：find_library 搜索后链接 find_library(FDK fdk-aac PATHS ${CMAKE_CURRENT_LIST_DIR}/lib) target_link_libraries(test_xlog PRIVATE ${FDK}) # 方式二：导入目标（IMPORTED_LOCATION 告诉 cmake 库在磁盘的位置） add_library(fdkaac STATIC IMPORTED) set_target_properties(fdkaac PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_LIST_DIR}/lib/libfdk-aac.a) target_link_libraries(test_xlog PRIVATE fdkaac) 优先用 target_link_libraries 而非全局的 link_directories() / link_libraries()。\nset_target_properties set_target_properties(\u0026lt;目标1\u0026gt; \u0026lt;目标2\u0026gt; ... PROPERTIES \u0026lt;属性1\u0026gt; \u0026lt;值1\u0026gt; \u0026lt;属性2\u0026gt; \u0026lt;值2\u0026gt;) 必须在目标创建之后（add_executable/add_library 之后）才能设置。 常用属性：IMPORTED_LOCATION（导入目标在磁盘的位置）、VERSION / SOVERSION（.so 版本，对应上文 soname）、OUTPUT_NAME 等。 find_package 与导入目标 find_package(Threads REQUIRED) target_link_libraries(app PRIVATE Threads::Threads) # 导入目标自带 usage requirements 生成器表达式基础 在生成阶段（而非配置阶段）求值，用于按配置/构建期或安装期切换：\ntarget_compile_options(app PRIVATE $\u0026lt;$\u0026lt;CONFIG:Debug\u0026gt;:-O0 -g\u0026gt;) target_include_directories(lib PUBLIC $\u0026lt;BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include\u0026gt; $\u0026lt;INSTALL_INTERFACE:include\u0026gt;) install install(TARGETS app RUNTIME DESTINATION bin # 可执行文件 LIBRARY DESTINATION lib # .so ARCHIVE DESTINATION lib) # .a 配合 CMAKE_INSTALL_PREFIX（交叉编译常指向 sysroot / rootfs 打包目录）。\n交叉编译与 Toolchain CMake toolchain 文件 用 -DCMAKE_TOOLCHAIN_FILE 指定，是让 CMake「真正做交叉编译」的关键：\ncmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=arm.cmake # arm.cmake set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR arm) set(CMAKE_C_COMPILER arm-mol-linux-uclibcgnueabihf-gcc) set(CMAKE_CXX_COMPILER arm-mol-linux-uclibcgnueabihf-g++) set(CMAKE_SYSROOT /path/to/arm-mol-linux-uclibcgnueabihf/sysroot) set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) # 让 find_* 只在 sysroot 里找库/头，程序仍用主机的 set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) Makefile 交叉编译要点 用 CROSS_COMPILE ?= arm-mol-linux-uclibcgnueabihf- + CC=$(CROSS_COMPILE)gcc，?= 便于命令行覆盖切换工具链。 带 gnueabihf 后缀的工具链默认硬浮点 ABI，通常无需再显式加 -mfloat-abi=hard（与工具链默认不符反而会报错）。soft/hard-float ABI 不匹配会在链接或运行期报错。 前缀式交叉 gcc 通常已内置 sysroot，无需显式 --sysroot；确需覆盖时再加 --sysroot=\u0026lt;path\u0026gt;。 pkg-config 交叉编译 设 PKG_CONFIG_SYSROOT_DIR + PKG_CONFIG_LIBDIR/PKG_CONFIG_PATH 指向 sysroot，否则 pkg-config 会吐出主机的 -I/-L。\n产物校验 file a.out # 确认目标架构 arm-...-readelf -h a.out # 架构、ABI arm-...-readelf -d a.out # NEEDED 动态库依赖、RPATH 工具链目录结构 MIPS 工具链（mips-gcc720-uclibc229） mips-linux-uclibc/ ├── include/c++/ # C++ 标准库头文件 ├── libc/ # 默认 sysroot 根目录 ⭐ │ ├── usr/include/ # 系统头文件 │ ├── usr/lib/ # 系统库文件 │ ├── glibc/ # glibc 变体 │ ├── soft-float/ # 软浮点变体 │ └── mfp32/ # mfp32 浮点变体 默认 sysroot：mips-linux-uclibc/libc/\nARM 工具链（molchipv500-armgcc-uclibc） arm-mol-linux-uclibcgnueabihf/ ├── bin/ ├── include/ # 工具链头文件 ├── lib/ # 工具链库文件 ├── debug-root/ └── sysroot/ # 默认 sysroot 根目录 ⭐ ├── lib/ # 系统库文件 ├── sbin/ └── usr/ ├── include/ # 系统头文件 └── lib/ # 系统库文件 默认 sysroot：arm-mol-linux-uclibcgnueabihf/sysroot/\nMIPS 工具链使用 multilib 架构，支持多种 C 库变体，sysroot 在 libc/ 子目录。 ARM 工具链使用标准 sysroot 架构，所有系统文件在 sysroot/ 子目录。 待确认：MIPS multilib 各浮点/ C 库变体（soft-float/、mfp32/、glibc/）对应的具体选择标志（如 -mfloat-abi / -march / -mfp32 组合）与该工具链的实际 multilib 映射有关，需按本人工具链 gcc -print-multi-lib 输出确认后再补。\n","permalink":"https://plhwiki.cn/linux/common/makefile/","summary":"\u003ch1 id=\"make-与-cmake-构建笔记\"\u003eMake 与 CMake 构建笔记\u003c/h1\u003e\n\u003cblockquote\u003e\n\u003cp\u003e面向 Linux / C/C++ / 交叉编译（ARM、MIPS uClibc 工具链）的构建笔记。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"make-基础概念\"\u003eMake 基础概念\u003c/h2\u003e\n\u003cp\u003eMake 通过比较**目标（target）与其依赖（prerequisite）的修改时间（mtime）**决定是否重建：只要任一依赖比目标新，就执行该目标的命令，并沿依赖图递归向上。\u003c/p\u003e","title":"Makefile"},{"content":"claude CLAUDE.md CLAUDE.md 的多层级设计借鉴了 Git 的配置分层（system → global → local）：\nManaged (/etc/claude-code/) ← 企业管理员 ↓ User (~/.claude/) ← 个人全局 ↓ Project (CLAUDE.md) ← 团队共享 ↓ Local (CLAUDE.local.md) ← 个人私有 MCP 让 AI 工具能连接外部数据源和服务。把它想象成 Claude Code 的 USB 接口：插上不同的 MCP 服务器，Claude 就获得了对应的能力。\n添加 MCP 服务器 # 注册新 MCP 服务器，支持 HTTP 和 stdio 传输 claude mcp add \u0026lt;name\u0026gt; \u0026lt;uri\u0026gt; claude mcp add \u0026lt;服务器名称\u0026gt; -- \u0026lt;启动命令\u0026gt; -s（\u0026ndash;scope） -s local：默认值。仅对当前机器上的当前项目有效，配置保存在本地（不对外分享）。 -s project：项目级共享。配置会写入项目根目录的 .mcp.json 文件中，可以随 Git 提交，让团队中所有参与该项目的人共同使用。 -s user：全局用户级。保存在你个人的全局配置中，你电脑上的所有项目都可以使用该服务器。 例子\nclaude mcp add --transport http context7 https://mcp.context7.com/mcp claude mcp add context7 -s user -- npx @context7/mcp 通信方式 Stdio 方式 当客户端（例如 Claude Code）启动时，它会在后台直接以子进程（Subprocess）的形式运行 MCP 服务器。两者之间通过系统的标准输入（stdin）和标准输出（stdout）进行 JSON-RPC 数据的传递。\n客户端发送请求： 向子进程的 stdin 写入数据。 服务器返回响应： 向自身的 stdout 写入数据，被客户端捕获。 特点\n纯本地进程间通信，外界无法通过网络访问该服务器。 服务器的生命周期完全由客户端控制。Claude 启动时它启动，Claude 关闭时它自动退出，不会残留后台进程。 因为 stdout 被用来传输协议数据，服务器代码里绝对不能包含任何普通的 printf 或 console.log，否则会破坏协议格式导致报错（日志必须输出到 stderr）。 HTTP / SSE 方式 MCP 服务器作为一个独立的 Web 服务器运行在某个端口上（本地或远程云端）。\n客户端发送请求： 使用标准的 HTTP POST 请求向服务器发送指令。 服务器推送数据： 服务器通过一个长连接的 SSE (Server-Sent Events) 通道，向客户端实时推送事件和响应。 特点\n支持远程调用： 服务器可以部署在局域网内的另一台机器，甚至云端（如 Context7 的官方 MCP 托管服务），实现\u0026quot;一套工具，多端共享\u0026quot;。 解耦运行： MCP 服务器是一个独立的常驻进程，可以单独启动、停止、扩容，不依赖客户端编辑器的生命周期。 调试方便： 可以使用 Postman 等工具直接测试接口，普通的日志打印不会干扰通信。 如何选择 服务的核心功能在云端（GitHub、Stripe、context7 查公开文档）→ 优先 HTTP，省事 服务需要访问你本地的东西（filesystem、git、本地数据库）→ 必须 stdio 离线环境 → stdio 想锁版本避免上游变更 → stdio codex 安装（不要用 sudo） codex 不要用 sudo 安装，应把 npm 全局目录改到用户目录后以当前用户安装：\n先查当前 npm 全局前缀 npm config get prefix # 大概率会得到 /usr 把 npm 全局目录改到用户目录 npm config set prefix ~/.local 确保 ~/.local/bin 在 PATH 里 echo $PATH # 如果里面没有 ~/.local/bin，就在 ~/.bashrc 里加一行： export PATH=\u0026#34;$HOME/.local/bin:$PATH\u0026#34; source ~/.bashrc 卸载旧的系统级 codex sudo npm uninstall -g @openai/codex # 再次检查 which codex 重新用当前用户安装 npm install -g @openai/codex 安装中断：清理残留 先确认残留 ls -al ~/.npm-global/lib/node_modules/@openai/ drwxrwxr-x 4 meian meian 4096 Jul 7 16:09 . drwxrwxr-x 4 meian meian 4096 Jun 10 09:34 .. drwxrwxr-x 4 meian meian 4096 Jul 7 16:09 codex drwxrwxr-x 4 meian meian 4096 Jul 6 09:14 .codex-VyfsgKFN 删除残留 rm -rf ~/.npm-global/lib/node_modules/@openai/codex rm -rf ~/.npm-global/lib/node_modules/@openai/.codex-* npm npm（Node Package Manager）是 Node.js 的包管理器，类似于：\nLinux 的 apt / yum Python 的 pip Rust 的 cargo 什么是 Node.js？Node.js 是让 JavaScript 能在服务器/终端运行的环境。\n安装包 # 本地安装 —— 只在当前项目可用，装到 ./node_modules/ npm install lodash # 全局安装 —— 系统任意位置可用，装到全局 bin 目录 npm install -g @anthropic-ai/claude-code -g 为 \u0026ndash;global 的缩写，表示安装到全局目录里 常用命令 命令 作用 示例 npm init 创建 package.json（项目说明书） npm init -y（快速创建） npm install 安装依赖包 npm install lodash npm run 运行自定义脚本 npm run dev npm update 更新依赖包 npm update react npm publish 发布自己的包 npm publish 设置镜像 npm config set registry https://registry.npmmirror.com npx npx 是 npm 自带（5.2+）的包执行工具：不用先全局安装，直接下载并运行某个包的可执行文件，用完即走，不污染全局环境。\n# 临时下载并执行，不需要提前 npm install -g npx create-react-app my-app # stdio 方式的 MCP 服务器常用 npx 启动 npx @context7/mcp ","permalink":"https://plhwiki.cn/ai/ai/","summary":"\u003ch1 id=\"claude\"\u003eclaude\u003c/h1\u003e\n\u003ch2 id=\"claudemd\"\u003eCLAUDE.md\u003c/h2\u003e\n\u003cp\u003eCLAUDE.md 的多层级设计借鉴了 Git 的配置分层（system → global → local）：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-scss\" data-lang=\"scss\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003eManaged\u003c/span\u003e \u003cspan class=\"o\"\u003e(/\u003c/span\u003e\u003cspan class=\"nt\"\u003eetc\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"nt\"\u003eclaude-code\u003c/span\u003e\u003cspan class=\"o\"\u003e/)\u003c/span\u003e    \u003cspan class=\"err\"\u003e←\u003c/span\u003e \u003cspan class=\"nt\"\u003e企业管理员\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"err\"\u003e↓\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003eUser\u003c/span\u003e \u003cspan class=\"o\"\u003e(~/\u003c/span\u003e\u003cspan class=\"nc\"\u003e.claude\u003c/span\u003e\u003cspan class=\"o\"\u003e/)\u003c/span\u003e              \u003cspan class=\"err\"\u003e←\u003c/span\u003e \u003cspan class=\"nt\"\u003e个人全局\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"err\"\u003e↓\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003eProject\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"nt\"\u003eCLAUDE\u003c/span\u003e\u003cspan class=\"nc\"\u003e.md\u003c/span\u003e\u003cspan class=\"o\"\u003e)\u003c/span\u003e            \u003cspan class=\"err\"\u003e←\u003c/span\u003e \u003cspan class=\"nt\"\u003e团队共享\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"err\"\u003e↓\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003eLocal\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"nt\"\u003eCLAUDE\u003c/span\u003e\u003cspan class=\"nc\"\u003e.local.md\u003c/span\u003e\u003cspan class=\"o\"\u003e)\u003c/span\u003e        \u003cspan class=\"err\"\u003e←\u003c/span\u003e \u003cspan class=\"nt\"\u003e个人私有\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"mcp\"\u003eMCP\u003c/h2\u003e\n\u003cp\u003e让 AI 工具能连接外部数据源和服务。把它想象成 Claude Code 的 USB 接口：插上不同的 MCP 服务器，Claude 就获得了对应的能力。\u003c/p\u003e","title":"AI"},{"content":"技术术语 最小可行性产品 (Minimum Viable Product) 定义：指用最快的时间、最低的成本开发出一个只包含核心功能的产品原型，直接推向市场让真实用户使用，以此来验证这个产品的核心逻辑是否能跑通、用户是否愿意买单。 软件即服务 (Software as a Service) 在 SaaS/互联网行业中：\nOnboard意为“入职”或“新手引导” DDL( Data Definition Language) ​\t简单来说，SQL 语句被分为几大类，而 DDL 是专门用来定义、修改或删除数据库结构（如表、视图、索引等）的命令。它决定了数据的“容器”长什么样，就像是在盖房子、规划房间，而不是搬家具。\n分类简称 全称 核心职责 常见关键字 DDL Data Definition Language (数据定义语言) 定义/修改/删除 数据库结构（建表、改表、删表） CREATE, ALTER, DROP, TRUNCATE DML Data Manipulation Language (数据操纵语言) 增、删、改 表里的数据 INSERT, UPDATE, DELETE DQL Data Query Language (数据查询语言) 查询、筛选 数据 SELECT DCL Data Control Language (数据控制语言) 管理权限与安全 GRANT, REVOKE 进行中的工作（Work in Progress） 这是最常见的 IT 和敏捷开发（Agile/Kanban）术语。\n含义：指已经开始制作但尚未完成的任务或需求。 WIP 限制（WIP Limit）：在看板（Kanban）流程中，团队通常会限制同时处于“进行中”状态的任务数量。例如，规定 WIP 限制为 3，意味着团队成员同时处理的任务不能超过 3 个。 目的：防止团队成员因多任务切换而降低效率，暴露流程中的瓶颈，促使大家“聚焦完成，而非盲目开始”。 网络技术 DDNS DDNS（Dynamic Domain Name System，动态域名解析），作用是：当设备的公网 IP 会变化时，自动把新的公网 IP 更新到一个固定域名上。\nPPPoE PPPoE（Point-to-Point Protocol over Ethernet，以太网上的点对点协议），最常见的用途就是家庭宽带里的“拨号上网”。\nMermaid markdown里时序图语法\ngraph 定义流程图，表示图表的类型。可以是graph、pie、classDiagram、stateDiagram等。 序列图（Sequence Diagram） 用于表示对象间交互的时间顺序。\n箭头语法 语法 含义 A-\u0026gt;\u0026gt;B 实线箭头 A--\u0026gt;\u0026gt;B 虚线箭头，常用于响应 A-\u0026gt;B 实线无实心箭头 A--\u0026gt;B 虚线无实心箭头 A-xB 末尾为叉号 A-)B 异步消息箭头 消息语法 ","permalink":"https://plhwiki.cn/basic/industry_term/","summary":"\u003ch1 id=\"技术术语\"\u003e技术术语\u003c/h1\u003e\n\u003ch2 id=\"最小可行性产品-minimum-viable-product\"\u003e最小可行性产品 (Minimum Viable Product)\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e定义\u003c/strong\u003e：指用\u003cstrong\u003e最快的时间、最低的成本\u003c/strong\u003e开发出一个只包含\u003cstrong\u003e核心功能\u003c/strong\u003e的产品原型，直接推向市场让真实用户使用，以此来验证这个产品的核心逻辑是否能跑通、用户是否愿意买单。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"软件即服务-software-as-a-service\"\u003e软件即服务 (Software as a Service)\u003c/h2\u003e\n\u003cp\u003e在 SaaS/互联网行业中：\u003c/p\u003e","title":"industry_term"},{"content":"短信相关 ​\t发送短信的流程就是配置短信模式-\u0026gt;配置字符集-\u0026gt;下发发送命令-\u0026gt;等待，提示符-\u0026gt;发送正文/PDU -\u0026gt;发送ctrl+z \u0026ndash;\u0026gt;等待结果\nTEXT 格式 AT+CMGF=1 //设置短信为文本模式 PDU格式 ​\tPDU模式不仅能发送中文短信，也能发送英文短信。PDU收发短信有三种编码可用：7-bit、8-bit和UCS2编码。7-bit编码用于发送普通的ASCII字符，即英文短信，最多可发送160字符。8-bit编码通常用于发送数据消息。UCS2编码用于发送Unicode字符，可发送中文字符，最多发送70字符。\nAT+CMGS=0//设置短信为PDU 0:PDU,1:文本模式 发送PUD（SUBMIT-PDU） 数据格式：\nSCA PDU-Type MR DA PID DCS VP UDL UD 1-12 1 1 2-12 1 1 0,1,7 1 0-140 SCA 短消息服务中心号码\nPDU-Type 协议数据单元类型(1字节)\n发送时\nBIT 7 6 5 4 3 2 1 0 参数 RP UDHI SRR VPF VPF RD MTI MTI MTI 2bit：消息类型，00表示收，01表示发\nRD 1bit：重复信元丢弃。0通知服务中心碰到同源、同目的地、同样的MR（短消息序号）的短消息接受；\nVPF 2bit：有效期限格式。00 VP不存在； 10 VP区存在用一个字节表示，是相对值；01保留； 11存在，半个字节表示，绝对值。\nSRR 2bit：状态报告要求。\nMR 所有成功的短信发送参考数目（0..255）\nDA 接收方地址（手机号码 半字节倒序编码）\nPID 协议标识\nDCS 编码方法和消息类别 （UCS2编码0等级为08H，7bit编码是00H)\nVP 有效期\n0 到 143 $(V + 1) \\times 5$ 分钟 5 分钟 到 12 小时 144 到 167 $12$ 小时 $+ (V - 143) \\times 30$ 分钟 12.5 小时 到 24 小时 168 到 196 $(V - 166) \\times 1$ 天 2 天 到 30 天 197 到 255 $(V - 192) \\times 1$ 周 5 周 到 63 周 例如：\n//向15050850677发送一条短信，内容“Test” //0011000D91685150800576F70000C404D4F29C0E //向15050850677发送一条短信，内容“你好” //0011000B815150800576F70008C4044F60597D SCA PDU-Type MR DA PID DCS VP UDL UD 1-12 1 1 2-12 1 1 0,1,7 1 0-140 00 11 00 0D91685150800576F7 00 00 C4 04 D4F29C0E 00 11 00 0B815150800576F7 00 08 C4 04 4F60597D 关键字段 DA/OA：接收方与发送方地址\nDA与OA编码方式是一样的 2-12个8位位组\n例如：0D91685150800576F7\nDA LEN DA TYPE 短信服务中心号码的类型 DA ADDR 存储目的地址号码 0D （一个字节，表示DA ADDR长度） 91（国际型） 685150800576F7 占用DA LEN/2字节 短信配置流程 //1.设置短信编码格式为PDU AT+CMGF=0 /** *\t2. 设置短信上报方式 \u0026lt;mode\u0026gt;,\u0026lt;mt\u0026gt;,\u0026lt;bm\u0026gt;,\u0026lt;ds\u0026gt;,\u0026lt;bfr\u0026gt; \u0026lt;mode\u0026gt; 2 缓存通知 当串口忙（如处于数据模式）时，模块会暂时缓存通知，待回到指令模式后再发送 \u0026lt;mt\u0026gt; 1 存储索引通知 收到短信后，模块将其存入 SIM 卡或 Flash，然后通过串口主动发送+CMTI: \u0026#34;ME\u0026#34;, index 与 2（直接把短信内容吐到串口）相比, 1具有以下的优势， 数据安全：短信先存盘，避免由于串口波特率太高或单片机/嵌入式系统处理不及时导致的数据丢失。 解析简单：你的程序只需要监听 +CMTI 这个特征字符串，然后根据索引去读取，逻辑更加解耦。 防止干扰：如果短信很长或者是 PDU 格式，直接吐出大量数据可能会干扰到正在进行的 AT 指令交互。 \u0026lt;bm\u0026gt; 0 不广播 不向TE终端主动发送小区广播消息 \u0026lt;ds\u0026gt; 0 无状态报告 不主动发送短信状态报告（即确认对方是否收到的回执） \u0026lt;bfr\u0026gt; 0 清除缓存 模块在设置此指令时，如果缓冲区有待发的通知，直接清除。 */ AT+CNMI=2,1,0,0,0 打电话 上网 //查询命令 响应+CREG: \u0026lt;n\u0026gt;,\u0026lt;stat\u0026gt;[,\u0026lt;lac\u0026gt;,\u0026lt;ci\u0026gt;[,\u0026lt;AcT\u0026gt;]] AT+CREG? ","permalink":"https://plhwiki.cn/hardware/4g/","summary":"\u003ch1 id=\"短信相关\"\u003e短信相关\u003c/h1\u003e\n\u003cp\u003e​\t发送短信的流程就是配置短信模式-\u0026gt;配置字符集-\u0026gt;下发发送命令-\u0026gt;等待，提示符-\u0026gt;发送正文/PDU -\u0026gt;发送ctrl+z \u0026ndash;\u0026gt;等待结果\u003c/p\u003e","title":"4G"},{"content":"驱动移植 ​\t4G模块加载 USB 转串口驱动option后会在/dev/下创建ttyUSB0、ttyUSB1 和 ttyUSB2 等设备文件。如果没有出现，需要在drivers/usb/serial/option.c里添加VID（Vendor ID）与PID（Product ID）\nUSB 转串口驱动修改 通过lsusb可得到\nBus 001 Device 002: ID 1a86:8091 Bus 001 Device 001: ID 1d6b:0002 Bus 002 Device 001: ID 1d6b:0002 Bus 003 Device 001: ID 1d6b:0002 Bus 001 Device 004: ID 2c7c:0904 在drivers/usb/serial/option.c里添加\nstatic const struct usb_device_id option_ids[] = { { USB_DEVICE(0x2C7C, 0x6002) }, /* Quectel EC800M */ { USB_DEVICE(0x2C7C, 0x0904) }, /* Quectel EC800G */ { USB_DEVICE(0x05C6, 0x9215) }, /* Quectel EC20 */ .. } static int option_probe(struct usb_serial *serial, const struct usb_device_id *id) { struct usb_interface_descriptor *iface_desc = \u0026amp;serial-\u0026gt;interface-\u0026gt;cur_altsetting-\u0026gt;desc; struct usb_device_descriptor *dev_desc = \u0026amp;serial-\u0026gt;dev-\u0026gt;descriptor; const struct option_blacklist_info *blacklist; #if 1 //Added byQuectel if (serial-\u0026gt;dev-\u0026gt;descriptor.idVendor == cpu_to_le16(0x2C7C)) { __u16 idProduct = le16_to_cpu(serial-\u0026gt;dev-\u0026gt;descriptor.idProduct); struct usb_interface_descriptor *intf = \u0026amp;serial-\u0026gt;interface-\u0026gt;cur_altsetting-\u0026gt;desc; if (intf-\u0026gt;bInterfaceClass != 0xFF || intf-\u0026gt;bInterfaceSubClass == 0x42) { //ECM, RNDIS, NCM, MBIM, ACM, UAC, ADB return -ENODEV; } if ((idProduct\u0026amp;0xF000) == 0x0000) { //MDM interface 4 is QMI if (intf-\u0026gt;bInterfaceNumber == 4 \u0026amp;\u0026amp; intf-\u0026gt;bNumEndpoints == 3 \u0026amp;\u0026amp; intf-\u0026gt;bInterfaceSubClass == 0xFF \u0026amp;\u0026amp; intf-\u0026gt;bInterfaceProtocol == 0xFF) return -ENODEV; } } #endif ....... } 添加零包机制 根据 USB 协议的要求，通过添加如下语句在 bulk-out 传输过程中添加处理零包的机制： 高于 2.6.34 的 Linux 内核版本，需在[KERNEL]/drivers/usb/serial/usb_wwan.c 文件中添加以下语句。低于 2.6.35 的 Linux 内核版本，需在[KERNEL]/drivers/usb/serial/option.c 文件中添加。\nstatic struct urb *usb_wwan_setup_urb(struct usb_serial_port *port, int endpoint, int dir, void *ctx, char *buf, int len, void (*callback) (struct urb *)) { struct usb_serial *serial = port-\u0026gt;serial; struct urb *urb; urb = usb_alloc_urb(0, GFP_KERNEL);\t/* No ISO */ if (!urb) return NULL; usb_fill_bulk_urb(urb, serial-\u0026gt;dev, usb_sndbulkpipe(serial-\u0026gt;dev, endpoint) | dir, buf, len, callback, ctx); #if 1 //Added by Quectel for Zero Packet if (dir == USB_DIR_OUT) { struct usb_device_descriptor *desc = \u0026amp;serial-\u0026gt;dev-\u0026gt;descriptor; if (desc-\u0026gt;idVendor == cpu_to_le16(0x2C7C)) urb-\u0026gt;transfer_flags |= URB_ZERO_PACKET; } #endif return urb; } 添加 Reset-resume 机制 static struct usb_serial_driver option_1port_device = { …… #ifdef CONFIG_PM .suspend = usb_wwan_suspend, .resume = usb_wwan_resume, #if 1 //Added by Quectel .reset_resume = usb_wwan_resume, #endif #endif }; 确保内核开启CONFIG_USB_SERIAL = y\n","permalink":"https://plhwiki.cn/linux/drive/4g/","summary":"\u003ch1 id=\"驱动移植\"\u003e驱动移植\u003c/h1\u003e\n\u003cp\u003e​\t4G模块加载 \u003cstrong\u003eUSB 转串口驱动option\u003c/strong\u003e后会在/dev/下创建ttyUSB0、ttyUSB1 和 ttyUSB2 等设备文件。如果没有出现，需要在drivers/usb/serial/option.c里添加VID（Vendor ID）与PID（Product ID）\u003c/p\u003e","title":"4G"},{"content":"平台设备驱动程序 片上设备 物理存在于 SoC 内部的硬件模块，其特点是焊死在芯片内部，不可插拔，不可枚举(无discovery机制)\nUART 控制器 I2C 控制器 SPI 控制器 GPIO Timer 平台设备 设备树 设备树解决什么问题 Device Tree 用来描述板级硬件资源，尤其适合描述 SoC 内部这类无法自动枚举的设备，例如 UART、I2C、SPI、GPIO、Timer 等。Bootloader 启动内核前把 .dtb 放到内存中，内核启动后解析 .dtb，再根据节点中的 compatible、reg、interrupts、pinctrl 等信息完成设备创建和驱动匹配。\n可以把设备树理解成“硬件描述”和“驱动代码”的分离：驱动不应该把某块板子的寄存器地址、中断号、 引脚复用写死在 C 代码里，而是从设备树读取。\n文件关系和编译链路 文件/工具 作用 .dts 板级设备树源码，通常一个开发板对应一个 .dts。 .dtsi 公共设备树片段，常用于 SoC、芯片系列、公共外设配置复用。 DTC Device Tree Compiler，把 .dts 编译成 .dtb。 .dtb 设备树二进制文件，由 Bootloader 加载并传给内核。 .dtsi 的用法类似 C 语言头文件，可以被 .dts 通过 #include 引入；.dtsi 本身也可以继续 include 其它 .dtsi。\n/dts-v1/; #include \u0026lt;dt-bindings/input/input.h\u0026gt; #include \u0026#34;a1.dtsi\u0026#34; #include \u0026lt;dt-bindings/interrupt-controller/irq.h\u0026gt; DTC 源码位于内核的 scripts/dtc 目录。内核选中 CONFIG_OF 后，编译内核时会生成主机侧 可执行程序 DTC。\n基本结构 根节点 根节点 / 描述整块板子的顶层信息，常见属性是 model 和 compatible。\n/dts-v1/; #include \u0026#34;topeet-rk3588-linux.dtsi\u0026#34; #include \u0026#34;rk3588-linux.dtsi\u0026#34; #include \u0026#34;topeet-screen-lcds.dts\u0026#34; #include \u0026#34;topeet-camera-config.dtsi\u0026#34; / { model = \u0026#34;TOPEET RK3588 LP4 LP5 Board\u0026#34;; compatible = \u0026#34;rockchip,rk3588-evb3-lp5-v10\u0026#34;, \u0026#34;rockchip,rk3588\u0026#34;; }; model：当前开发板名称，主要用于标识。 compatible：匹配 machine 或驱动，通常按“从具体到通用”的顺序写。 子节点 设备节点的一般格式如下：\n[label:] node-name[@unit-address] { [property definitions]; [child nodes]; }; **label：**节点标签，用于 \u0026amp;label 引用，必须全局唯一。（可选字段）\n提供简短的引用名称，避免写冗长的全路径，提高可读性 **node-name：**节点名称，表示设备类型，可以重复。(必须字段)\n**unit-address：**节点地址，通常应与 reg 中的首地址对应。（可选字段）\n格式规范：节点名@地址，紧跟着节点名称后，地址值支持10/16进制，字符串，需与reg属性匹配 **属性定义:**节点的核心，由一系列键值对组成，用来描述设备的具体属性（如compatible属性）\n示例：\nuart0: serial@0x10030000 { //是 label，方便其他 DTS 节点用 \u0026amp;uart0 引用 compatible = \u0026#34;ingenic,8250-uart\u0026#34;; reg = \u0026lt;0x10030000 0x1000\u0026gt;; reg-shift = \u0026lt;2\u0026gt;; interrupt-parent = \u0026lt;\u0026amp;core_intc\u0026gt;; interrupts = \u0026lt;IRQ_UART0\u0026gt;; pinctrl-names = \u0026#34;default\u0026#34;; pinctrl-0 = \u0026lt;\u0026amp;uart0_pc1\u0026gt;; status = \u0026#34;disabled\u0026#34;; }; 特殊节点 chosen 不描述硬件，用于bootloader向内核传递运行是的参数\nchosen { bootargs = \u0026#34;earlycon=uart8250,mmio32,0xfeb50000 console=ttyFIQ0 irqchip.gicv3_pseudo_nmi=0 rw rootwait rcupdate.rcu_expedited=1 rcu_nocbs=all\u0026#34;; }; bootargs: 传递内核启动参数 **stdout-path:**指定内核标准输出设备 aliases 节点顾名思义就是“别名”。设备树中的硬件节点路径通常非常长且复杂，为了方便人类阅读和内核驱动快速找到设备，就给长路径起一个短别名。\naliases { i2s0 = \u0026#34;/i2s@19700000\u0026#34;; i2s1 = \u0026#34;/i2s@19800000\u0026#34;; i2c0 = \u0026#34;/i2c@18d00000\u0026#34;; i2c1 = \u0026#34;/i2c@18e00000\u0026#34;; gpio0 = \u0026#34;/gpio@1B000000\u0026#34;; gpio1 = \u0026#34;/gpio@1B100000\u0026#34;; gpio2 = \u0026#34;/gpio@1B200000\u0026#34;; efuse0 = \u0026#34;/efuse@1BA00000\u0026#34;; }; cpus 描述板子上的中央处理器（CPU）信息，device_type = \u0026quot;cpu\u0026quot;：这是每个 CPU 子节点必须包含的属性，用来明确告诉内核“我是一个处理器核心”。\ncpus { #address-cells = \u0026lt;1\u0026gt;; #size-cells = \u0026lt;0\u0026gt;; cpu@0 { device_type = \u0026#34;cpu\u0026#34;; compatible = \u0026#34;arm,cortex-a7\u0026#34;; reg = \u0026lt;0xf00\u0026gt;; }; cpu@1 { device_type = \u0026#34;cpu\u0026#34;; compatible = \u0026#34;arm,cortex-a7\u0026#34;; reg = \u0026lt;0xf01\u0026gt;; }; }; memory 描述系统的物理内存（RAM）布局\nmemory@80000000 { device_type = \u0026#34;memory\u0026#34;; reg = \u0026lt;0x80000000 0x10000000\u0026gt;;/* 物理内存的起始地址是 0x80000000（2GB 处），大小是 0x20000000（512MB） */ }; 标准属性 属性 作用 compatible 最重要的匹配属性，驱动通常通过 of_match_table 与它匹配。 reg 定义该设备的寄存器基地址和长度 status 控制节点是否启用，常见值是 okay、disabled。 interrupt-parent 指定中断所属的中断控制器。 interrupts 描述中断号及触发方式，具体格式由中断控制器决定。 pinctrl-names 定义 pinctrl 状态名称，例如 default、sleep。 pinctrl-0 引用具体引脚复用配置，例如把 GPIO 切换成 UART 功能。 compatible 内核通过该属性的值来为设备匹配对应的驱动程序，是设备树中最关键的属性之一。\n**格式：**采用\u0026quot;厂商，设备型号\u0026quot;格式(如\u0026quot;sony,imx415\u0026quot;).值可以是单个字符串或字符串列表,也可以是多值。 compatible = \u0026#34;rockchip,rk3588-evb3-lp5-v10\u0026#34;, \u0026#34;rockchip,rk3588\u0026#34; reg **核心作用:**定义设备在父总线地址空间中的寄存器地址范围或内存映射区域，告知驱动程序硬件资源的具体位置。 **格式：**由一个或多个对组成 寻址属性 #address-cells 定义子节点reg属性中**，地址字段占用的32位单元** #size-cells 定义子节点reg属性中，大小字段占用的32位单元 例：\n/ { compatible = \u0026#34;rockchip,rk3399\u0026#34;; #address-cells = \u0026lt;2\u0026gt;; #size-cells = \u0026lt;2\u0026gt;; }; root节点的子节点 reg格式： address占2个cell，address占2个cell reg = \u0026lt;addr_hi addr_low size_hi size_low\u0026gt;\n\u0026amp;csi2_dphy3 { status = \u0026#34;okay\u0026#34;; ports { #address-cells = \u0026lt;1\u0026gt;; #size-cells = \u0026lt;0\u0026gt;; port@0 { reg = \u0026lt;0\u0026gt;; #address-cells = \u0026lt;1\u0026gt;; #size-cells = \u0026lt;0\u0026gt;; mipidphy3_in_ucam0: endpoint@1 { reg = \u0026lt;1\u0026gt;; remote-endpoint = \u0026lt;\u0026amp;ov5695_out_4\u0026gt;; data-lanes = \u0026lt;1 2\u0026gt;; }; mipidphy3_in_ucam0_ov13850: endpoint@2 { reg = \u0026lt;2\u0026gt;; remote-endpoint = \u0026lt;\u0026amp;ov13850_out_4\u0026gt;; data-lanes = \u0026lt;1 2 3 4\u0026gt;; }; mipidphy3_in_ucam0_imx415: endpoint@3 { reg = \u0026lt;3\u0026gt;; remote-endpoint = \u0026lt;\u0026amp;imx415_out_4\u0026gt;; data-lanes = \u0026lt;1 2 3 4\u0026gt;; }; }; port@1 { reg = \u0026lt;1\u0026gt;; #address-cells = \u0026lt;1\u0026gt;; #size-cells = \u0026lt;0\u0026gt;; csidphy3_out: endpoint@0 { reg = \u0026lt;0\u0026gt;; remote-endpoint = \u0026lt;\u0026amp;mipi4_csi2_input\u0026gt;; }; }; }; }; 特有属性 属性 作用 reg-shift 描述 UART 寄存器地址步进 reg-shift = \u0026lt;2\u0026gt; 后，驱动访问第 n 个寄存器时地址变成：base + (n \u0026lt;\u0026lt; 2)。 也就是： UART_RX/TX：base + 0x00 UART_IER：base + 0x04 UART_IIR/FCR：base + 0x08 UART_LCR：base + 0x0c 引用与板级覆盖 SoC 级 .dtsi 通常先定义硬件节点，并默认 status = \u0026quot;disabled\u0026quot;。板级 .dts 再通过 \u0026amp;label 引用该节点，按实际硬件连接启用或补充配置。\nrtc: rtc@0x10003000 { compatible = \u0026#34;ingenic,rtc\u0026#34;; reg = \u0026lt;0x10003000 0x1000\u0026gt;; interrupt-parent = \u0026lt;\u0026amp;core_intc\u0026gt;; interrupts = \u0026lt;IRQ_RTC\u0026gt;; status = \u0026#34;disabled\u0026#34;; }; \u0026amp;rtc { status = \u0026#34;okay\u0026#34;; /* 修改状态 */ }; \u0026amp;label 的含义不是重新创建节点，而是找到已经存在的节点，对它追加、修改或覆盖属性。\n地址描述 设备树用 #address-cells、#size-cells 和 reg 描述子节点地址。它们通常出现在总线节点中， 例如 soc、i2c、spi、pci、simple-bus 等。\ncell cell 是设备树中地址和长度的基本单位，1 cell = 32 bit = 4 字节。在 .dts 文件里，尖括号 \u0026lt; \u0026gt; 中的每个数字通常就是一个 cell。\n#address-cells：子节点地址占几个 cell。 #size-cells：子节点长度占几个 cell。 reg：按父节点的 cell 规则解释地址和长度。 reg reg 的基本形式是：\nreg = \u0026lt;address size\u0026gt;; 但 address 和 size 实际上可能由多个 cell 组成：\n/* 每个元组代表设备使用的一段地址范围 */ reg = \u0026lt;address... size...\u0026gt;; 32 位地址常见写法：\n#address-cells = \u0026lt;1\u0026gt;; #size-cells = \u0026lt;1\u0026gt;; uart@fe660000 { reg = \u0026lt;0xfe660000 0x100\u0026gt;; }; 64 位地址常见写法：\n#address-cells = \u0026lt;2\u0026gt;; #size-cells = \u0026lt;2\u0026gt;; 如果子节点只需要编号，不需要长度，例如某些 ports、I2C 设备、SPI 设备，#size-cells 可以为 0。\n","permalink":"https://plhwiki.cn/linux/drive/dts/","summary":"\u003ch1 id=\"平台设备驱动程序\"\u003e平台设备驱动程序\u003c/h1\u003e\n\u003ch2 id=\"片上设备\"\u003e片上设备\u003c/h2\u003e\n\u003cp\u003e物理存在于 SoC 内部的硬件模块，其特点是焊死在芯片内部，不可插拔，不可枚举(无discovery机制)\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eUART 控制器\u003c/li\u003e\n\u003cli\u003eI2C 控制器\u003c/li\u003e\n\u003cli\u003eSPI 控制器\u003c/li\u003e\n\u003cli\u003eGPIO\u003c/li\u003e\n\u003cli\u003eTimer\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"平台设备\"\u003e平台设备\u003c/h2\u003e\n\u003ch1 id=\"设备树\"\u003e设备树\u003c/h1\u003e\n\u003ch2 id=\"设备树解决什么问题\"\u003e设备树解决什么问题\u003c/h2\u003e\n\u003cp\u003eDevice Tree 用来描述板级硬件资源，尤其适合描述 SoC 内部这类无法自动枚举的设备，例如\nUART、I2C、SPI、GPIO、Timer 等。Bootloader 启动内核前把 \u003ccode\u003e.dtb\u003c/code\u003e 放到内存中，内核启动后解析\n\u003ccode\u003e.dtb\u003c/code\u003e，再根据节点中的 \u003ccode\u003ecompatible\u003c/code\u003e、\u003ccode\u003ereg\u003c/code\u003e、\u003ccode\u003einterrupts\u003c/code\u003e、\u003ccode\u003epinctrl\u003c/code\u003e 等信息完成设备创建和驱动匹配。\u003c/p\u003e","title":"dts"},{"content":"GB28181协议 GB28181协议会话通道实际上使用的是SIP协议，并且在SIP协议的基础之上做了些私有化处理。SIP是一个由IETF MMUSIC工作组开发的协议，作为标准被提议用于创建，修改和终止包括视频，语音，即时通信，在线游戏和虚拟现实等多种多媒体元素在内的交互式用户会话。SIP中一个比较重要的概念是用户代理（User Agent），指的是一个SIP逻辑网络端点，用于创建、发送、接收SIP消息并管理一个SIP会话。SIP用户代理又可分为用户代理客户端UAC（User Agent Client）和用户代理服务端UAS（User Agent Server）。UAC创建并发送SIP请求，UAS接收处理SIP请求，发送SIP响应。SIP协议会与许多其它的协议协同工作，如SIP报文内容发送会话描述协议（Session Description Protocol，SDP）4，SDP协议描述了会话所使用流媒体细节，如：使用哪个IP端口，采用哪种编解码器等等。SIP的一个典型用途是：SIP会话传输一些简单的经过报文的实时传输协议流，RTP本身才是语音或视频的载体。在GB28181协议中，联网系统在进行视音频传输及控制时应建立两个传输通道: 会话通道和媒体流通道。会话通道用于在设备之间建立会话并传输系统控制命令; 媒体流通道用于传输视音频数据， 经过压缩编码的视音频流采用流媒体协议RTP/RTCP传输。GB28181协议中具体通信协议结构图如下图所示：\nGB28181 中 SIP 用到的核心能力 SIP能力 GB28181用途 REGISTER 设备注册 MESSAGE 心跳 / 目录 / 状态 / 控制 INVITE 实时预览 / 回放 ACK 确认媒体会话建立 BYE 结束预览 CANCEL 取消 INVITE OPTIONS 能力探测（部分平台） 401/407 Digest认证 NOTIFY 订阅通知（扩展/部分平台） REGISTER 注册流程\nSIP代理向SIP服务器发送Register（未鉴权）请求; SIP服务器向SIP代理发送响应401，并在响应的消息头WWW_Authenticate字段中给出适合SIP代理的认证体制和参数; SIP代理重新向SIP服务器发送REGISTER请求， 在请求的Authorization字段给出信任书，包含认证信息; SIP服务器对请求进行验证，如果检查出SIP代理身份合法，向SIP代理发送成功响应200OK，如果身份不合法则发送拒绝服务应答。 sequenceDiagram\rparticipant Proxy as SIP代理\rparticipant Server as SIP服务器\rProxy-\u0026gt;\u0026gt;Server: 1. REGISTER\rServer--\u0026gt;\u0026gt;Proxy: 2. 401 Unauthorized\rProxy-\u0026gt;\u0026gt;Server: 3. REGISTER（携带认证信息）\rServer--\u0026gt;\u0026gt;Proxy: 4. 200 OK MESSAGE ​\t当UA（User Agent）发现工作异常时， 应立即向本SIP监控域的SIP服务器发送状态信息; 无异常时，应定时向本SIP监控域的SIP服务器发送状态信息。状态信息报送采用IETF RFC3427中定义的方法MESSAGE实现。通过周期性的状态信息报送，实现注册服务器与源设备之间的状态检测即心跳机制。心跳发送方、接收方需统一配置“心跳间隔”参数，按照“心跳间隔”定时发送心跳消息，默认心跳间隔60s。心跳发送方、接收方需统一配置“心跳超时次数”参数，心跳消息连续超时达到“心跳超时次数”则认为对方下线，默认心跳超时次数3次。心跳接收方在心跳发送方上线状态下检测到心跳消息连续超时达到商定次数则认为心跳发送方离线; 心跳发送方在心跳接收方上线状态下检测到心跳消息响应消息连续超时达到商定次数则认为心跳接收方离线。\nsequenceDiagram\rparticipant Proxy as SIP代理\rparticipant Server as SIP服务器\rProxy-\u0026gt;\u0026gt;Server: 1. MESSAGE (状态信息报送)\rServer-\u0026gt;\u0026gt;Proxy: 2. 200 OK SIP文本分析 REGISTER文本 SIP代理向SIP服务器发送:\n1：REGISTER sip:41010500002000000001@192.168.10.27:8116 SIP/2.0\r2：Via: SIP/2.0/UDP 192.168.2.179:5060;rport;branch=z9hG4bK1527517184\r3：From: \u0026lt;sip:41010500001110000001@192.168.2.179:5060\u0026gt;;tag=1219350858\r4：To: \u0026lt;sip:41010500001110000001@192.168.2.179:5060\u0026gt;\r5：Call-ID: f3g4h51806249538@192.168.2.179\r6：CSeq: 1 REGISTER\r7：Contact: \u0026lt;sip:41010500001110000001@192.168.2.179:5060\u0026gt;\r8：Max-Forwards: 70\r9：User-Agent: VCP MWARE\r10:Expires: 3600\rRegMode: DEVICE;Describe=IPC2A5I-IR6-AUF40-B-DT;Register;DevVer=default\rReplaceDescribe: IPC2A5I-IR6-AUF40-B-DT;ProductId=defaultProductId;RegionCode=DT;Vendor=UNIVIEW;Resolve=HIC3MP;Ptzfg=0;;SnmpVer=v3\rX-GB-Ver: 3.0\rAbility: UCSStore=0\rContent-Length: 0 **第一行：**REGISTER sip:41010500002000000001@192.168.10.27:8116 SIP/2.0\n国标ID指的是由中心编码(8位) 、行业编码(2位) 、类型编码(3位)和序号(7位)四个码段共20位十进制数字字符构成\n41010500 00 200 0000001是SIP服务器的国标ID ，SIP/2.0指的是SIP协议版本。\n**第二行：**Via: SIP/2.0/UDP 192.168.2.179:5060;rport;branch=z9hG4bK327447864\nVia头中包含了发送请求方的相关信息，后续需要使用这些信息进行回复。SIP/2.0/UDP表示使用的是2.0版本的SIP协议，使用的传输协议是UDP，也可以使用TCP协议。192.168.2.179:5060为请求发送方的IP地址和端口号，Via头中必须包含branch参数，具体值是一个在整个SIP通信过程中不重复的数值。branch是一个事务ID（Transaction ID），用于区分同一个UA所发起的不同Transaction，它不会对未来的request或者是response造成影响，对于遵循IETF RFC3261规范的实现，这个branch参数的值必须用”z9hG4bK”打头. 其它部分是对To， From, Call-ID头域和Request-URI按一定的算法加密后得到。rport字段表示使用rport机制路由响应，即发送的响应时，按照rport中的端口发送SIP响应，也就是说IP和端口均完全遵照从哪里来的，发回哪里去的原则，如果没有rport字段时，服务端的策略是IP使用UDP包中的地址，即从哪里来回哪里去，但是端口使用的是via中的端口，详情见IETF RFC35818。\n**第三行：**From:\u0026lt;sip:41010500001110000001@192.168.2.179:5060\u0026gt;;tag=1219350858\nFrom头中包含了请求发送方的逻辑标识，在GB28181协议中是发送请求的设备国标ID和域国标ID信息。tag参数是为了身份认证的，值为随机数字字符。\n**第四行：**To: \u0026lt;sip:41010500001110000001@192.168.2.179:5060\u0026gt;\nTo头在SIP协议中是为了标明请求接收方的逻辑标识的，在GB28181协议中填写的是发送请求的设备国标ID和域国标ID信息。\n**第五行：**Call-ID: f3g4h51806249538@192.168.2.179\nCall-ID头是全局唯一的，在同一个session中保持一致，在不同session中不同\n**第六行：**CSeq: 2 REGISTER\nCSeq头又叫Command Seqence（命令队列），用于标识命令顺序，值为序号+Method，序号部分为无符号整数，最大值为2^31。序号起始值是随机的，后续在同一个session中依次递增，比如发1 REGISTER没返回\u0026mdash;\u0026gt;再发2 REGISTER\u0026mdash;\u0026gt;没返回\u0026mdash;\u0026gt;再发3 REGISTER\u0026mdash;\u0026gt;这时返回了2 REGISTER就知道是第2个请求得到了响应。对于ACK和CANCLE中的CSeq与INVITE中的Cseq保持一致。\n**第七行：**Contact: \u0026lt;sip:41010500001110000001@192.168.2.179:5060\u0026gt;\nContact头包含源的URI信息，用来给响应消息直接和源建立连接用。在GB28181协议中为SIP设备编码@源IP地址端口。\n**第八行：**Forwards: 70\nMax-Forwards头用于设置包最大中转次数，默认是70。（宇视的是Forwards）\n**第九行：**User-Agent: VCP MWARE\nUser-Agent头用于设置关于UA的信息，用户可以自定义\n**第十行：**Expires: 3600\nExpires头表示超时时间\n**第11行：**Content-Length: 0\nContent-Length头，Content-Length头表示SDP消息的长度，因为REGISTER消息不需要SDP，因此为0。\nMESSAGE文本 ​\tMESSAGE消息头 Content-type 头为 Content-type: Application/MANSCDP+xml。状态信息报送命令采用MANSCDP（监控报警联网系统控制描述协议，Monitoringand Alarming Network System Control Description Protocol）协议格式定义， 详细描述见GB/T 28181—2016中A.2.5状态信息报送。状态信息报送命令应包括命令类型(CmdType)、设备/系统编码(DeviceID)、是否正常工作(Status)等， 采用MESSAGE方法的消息体携带。Message消息的成功和错误应答均无消息体，Message回复消息内容范例如下：\nMESSAGE sip:41010500001110000001@192.168.2.179:5060 SIP/2.0\rCall-ID: b6e51d3973e35d9a21a3a324789e3ebd@192.168.10.27\rCSeq: 2 MESSAGE\rFrom: \u0026lt;sip:41010500002000000001@4101050000\u0026gt;;tag=ac22a981778c40849a827b1e0246715f\rTo: \u0026lt;sip:41010500001110000001@192.168.2.179:5060\u0026gt;\rVia: SIP/2.0/UDP 192.168.10.27:8116;branch=z9hG4bK3242685359;rport\rMax-Forwards: 70\rUser-Agent: WVP-Pro v2.7.4.2026-06-02T16:15:21+08:00\rContent-Type: Application/MANSCDP+xml\rContent-Length: 151\r\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;GB2312\u0026#34;?\u0026gt;\r\u0026lt;Query\u0026gt;\r\u0026lt;CmdType\u0026gt;DeviceInfo\u0026lt;/CmdType\u0026gt;\r\u0026lt;SN\u0026gt;353602\u0026lt;/SN\u0026gt;\r\u0026lt;DeviceID\u0026gt;41010500001110000001\u0026lt;/DeviceID\u0026gt;\r\u0026lt;/Query\u0026gt; NVR 作为视频代理网关\n对内（局域网）：它管理着一大堆摄像头 对外（国标网）：它把自己伪装成一个“大号的摄像头客户端”注册到 WVP 平台。 sequenceDiagram\rautonumber\rparticipant Receiver as Web前端\rparticipant SIP_Server as SIP服务器\rparticipant Media_Server as 媒体服务器\rparticipant Sender as 媒体流发送者\rReceiver-\u0026gt;\u0026gt;SIP_Server: Invite (携带SDP消息体)\rSIP_Server-\u0026gt;\u0026gt;Media_Server: Invite\rMedia_Server--\u0026gt;\u0026gt;SIP_Server: 200 OK (携带SDP消息体)\rSIP_Server-\u0026gt;\u0026gt;Sender: Invite (携带SDP消息体)\rSender--\u0026gt;\u0026gt;SIP_Server: 200 OK (携带SDP消息体)\rSIP_Server-\u0026gt;\u0026gt;Media_Server: ACK (携带SDP消息体)\rSIP_Server-\u0026gt;\u0026gt;Sender: ACK\rnote over Sender, Media_Server: 【第一阶段】实时媒体流 (IPC/NVR -\u0026gt; ZLM)\rSender--\u0026gt;\u0026gt;Media_Server: 实时媒体流\rSIP_Server-\u0026gt;\u0026gt;Media_Server: Invite (携带SDP消息体)\rMedia_Server--\u0026gt;\u0026gt;SIP_Server: 200 OK (携带SDP消息体)\rSIP_Server--\u0026gt;\u0026gt;Receiver: 200 OK (携带SDP消息体)\rReceiver-\u0026gt;\u0026gt;SIP_Server: ACK\rSIP_Server-\u0026gt;\u0026gt;Media_Server: ACK\rnote over Media_Server, Receiver: 【第二阶段】实时媒体流 (ZLM -\u0026gt; 前端播放器)\rMedia_Server--\u0026gt;\u0026gt;Receiver: 实时媒体流\rnote over Receiver, Sender: 【停止播放阶段】释放呼叫流程\rReceiver-\u0026gt;\u0026gt;SIP_Server: BYE\rSIP_Server--\u0026gt;\u0026gt;Receiver: 200 OK\rSIP_Server-\u0026gt;\u0026gt;Media_Server: BYE\rMedia_Server--\u0026gt;\u0026gt;SIP_Server: 200 OK\rSIP_Server-\u0026gt;\u0026gt;Media_Server: BYE\rMedia_Server--\u0026gt;\u0026gt;SIP_Server: 200 OK\rSIP_Server-\u0026gt;\u0026gt;Sender: BYE\rSender--\u0026gt;\u0026gt;SIP_Server: 200 OK ","permalink":"https://plhwiki.cn/multimedia/protocol/gb28181/","summary":"\u003ch1 id=\"gb28181协议\"\u003eGB28181协议\u003c/h1\u003e\n\u003cp\u003eGB28181协议会话通道实际上使用的是SIP协议，并且在SIP协议的基础之上做了些私有化处理。SIP是一个由IETF MMUSIC工作组开发的协议，作为标准被提议用于创建，修改和终止包括视频，语音，即时通信，在线游戏和虚拟现实等多种多媒体元素在内的交互式用户会话。SIP中一个比较重要的概念是\u003cstrong\u003e用户代理（User Agent）\u003c/strong\u003e，指的是一个SIP逻辑网络端点，用于创建、发送、接收SIP消息并管理一个SIP会话。SIP用户代理又可分为用户代理客户端UAC（User Agent Client）和用户代理服务端UAS（User Agent Server）。UAC创建并发送SIP请求，UAS接收处理SIP请求，发送SIP响应。SIP协议会与许多其它的协议协同工作，如SIP报文内容发送会话描述协议（Session Description Protocol，SDP）4，SDP协议描述了会话所使用流媒体细节，如：使用哪个IP端口，采用哪种编解码器等等。SIP的一个典型用途是：SIP会话传输一些简单的经过报文的实时传输协议流，RTP本身才是语音或视频的载体。在GB28181协议中，联网系统在进行视音频传输及控制时应建立两个传输通道: 会话通道和媒体流通道。会话通道用于在设备之间建立会话并传输系统控制命令; 媒体流通道用于传输视音频数据， 经过压缩编码的视音频流采用流媒体协议RTP/RTCP传输。GB28181协议中具体通信协议结构图如下图所示：\u003c/p\u003e","title":"gb28181"},{"content":"SIP协议介绍 ​\tSIP 是一个应用层的控制协议，可以用来建立、修改、和终止多媒体会话（或者会议）例如 Internet 电话/视频会议系统\n","permalink":"https://plhwiki.cn/multimedia/sip/","summary":"\u003ch1 id=\"sip协议介绍\"\u003eSIP协议介绍\u003c/h1\u003e\n\u003cp\u003e​\tSIP 是一个应用层的控制协议，可以用来建立、修改、和终止多媒体会话（或者会议）例如 Internet 电话/视频会议系统\u003c/p\u003e","title":"sip"},{"content":"软件资产 易于理解：\n最小表面积 规范简洁的呈现 清晰且完整的参考注释 有关的用况 易于使用：\n有效的使用模型 直观的接口 合适的安全等级 最小的物理依赖 高性能：\n执行(真实和CPU)运行时间 进程（即内核内存）大小 编译时间（或编译时耦合的程度） 链接时间（或链接时的依赖程度） 可移植的：\n在所有支持的平台均可构建 在所有支持的平台均可运行 在所有支持的平台均可产生同样的结果 在所有支持的平台上均可达到\u0026quot;合理的\u0026quot;性能 可靠的：\n没有核心转存储 没有内存泄露 没有错误的结果 没有bug ","permalink":"https://plhwiki.cn/basic/system_architecture/","summary":"\u003ch1 id=\"软件资产\"\u003e软件资产\u003c/h1\u003e\n\u003cp\u003e\u003cstrong\u003e易于理解：\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e最小表面积\u003c/li\u003e\n\u003cli\u003e规范简洁的呈现\u003c/li\u003e\n\u003cli\u003e清晰且完整的参考注释\u003c/li\u003e\n\u003cli\u003e有关的用况\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003e易于使用：\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e有效的使用模型\u003c/li\u003e\n\u003cli\u003e直观的接口\u003c/li\u003e\n\u003cli\u003e合适的安全等级\u003c/li\u003e\n\u003cli\u003e最小的物理依赖\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003e高性能：\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e执行(真实和CPU)运行时间\u003c/li\u003e\n\u003cli\u003e进程（即内核内存）大小\u003c/li\u003e\n\u003cli\u003e编译时间（或编译时耦合的程度）\u003c/li\u003e\n\u003cli\u003e链接时间（或链接时的依赖程度）\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003e可移植的：\u003c/strong\u003e\u003c/p\u003e","title":"system_architecture"},{"content":"可变参数模板 声明可变参数模板的时候需要在typename或class 后面带上省略号...\n声明一个参数包，这个参数包中可以包含0到任意个板参数。 在模板定义的右边，可以将参数包展开成一个一个独立的参数。 template\u0026lt;typename... T\u0026gt; void f(T... args) { coout \u0026lt;\u0026lt; sizeof...(args) \u0026lt;\u0026lt;endl; } f(); f(1,2); f(1,2,\u0026#34;\u0026#34;); ZLMediaKit的模板\ntemplate\u0026lt;typename Creator, typename HttpSessionType = mediakit::HttpSession, mediakit::WebSocketHeader::Type DataType = mediakit::WebSocketHeader::TEXT\u0026gt; class WebSocketSessionBase : public HttpSessionType { void onError(const toolkit::SockException \u0026amp;err) override{ HttpSessionType::onError(err);//明确告诉编译器，这个 onError 是从当前模板参数基类 HttpSessionType 里找 if(_session){ _session-\u0026gt;onError(err); } } }; 类型模板参数：Createor 类型模板参数：HttpSessionType ，且有默认值，如果外面不传参数，就默认用mediakit::HttpSession 非类型模板参数：mediakit::WebSocketHeader::Type DataType class WebSocketSessionBase : public HttpSessionType 的语法含义是“继承一个由模板参数决定的基类”。 这叫 依赖基类，因为基类类型依赖于模板参数，只有将来实例化时才知道到底继承谁。\n这个onError()函数，编译器不会在第一阶段普通名字查找里直接去基类里找，除非你写成this-\u0026gt;onError(err);或者HttpSessionType::onError(err);\n这里的HttpSessionType::onError是明确告诉编译器，这个 onError 是从当前模板参数基类 HttpSessionType 里找\n模板元编程 模板变量的定义 std::enable_if std::enable_if\u0026lt;条件，类型\u0026gt;::type; 当条件为 true 时：\nstd::enable_if\u0026lt;true，类型\u0026gt;::type 其中的type存在，并且就是第二个模板参数指定的类型。\n例如：\nstd::enable_if\u0026lt;true, bool\u0026gt;::type //条件为true,等价于 bool std::enable_if\u0026lt;false, int\u0026gt;::type//条件为false,其中不存在type std::is_same 两个模板参数类型参数是否一致，一致的话返回true。\n例如：\nstd::is_same\u0026lt;bool, bool\u0026gt;::value //结果为true; std::is_same\u0026lt;int, bool\u0026gt;::value //结果为false; ","permalink":"https://plhwiki.cn/cpp/language/template/","summary":"\u003ch1 id=\"可变参数模板\"\u003e可变参数模板\u003c/h1\u003e\n\u003cp\u003e声明可变参数模板的时候\u003cstrong\u003e需要在typename或class 后面带上省略号\u003ccode\u003e...\u003c/code\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e声明一个参数包，这个参数包中可以包含0到任意个板参数。\u003c/li\u003e\n\u003cli\u003e在模板定义的右边，可以将参数包展开成一个一个独立的参数。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-c++\" data-lang=\"c++\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003etemplate\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"k\"\u003etypename\u003c/span\u003e\u003cspan class=\"p\"\u003e...\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e \u003cspan class=\"n\"\u003ef\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e...\u003c/span\u003e \u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ecoout\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e \u003cspan class=\"k\"\u003esizeof\u003c/span\u003e\u003cspan class=\"p\"\u003e...(\u003c/span\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eendl\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ef\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ef\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ef\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eZLMediaKit的模板\u003c/p\u003e","title":"template"},{"content":"流媒体协议 RTSP 在上层发号施令（管控制），RTP 在底层埋头搬砖（运数据），RTCP 在旁边盯着质量（做监督）\n三者关系：\nRTSP (应用层)： 流媒体的“遥控器”。 它不传数据，只负责播放、暂停、快进和挂断等控制信令，并在正式传输前商量好底层数据通道的端口。 RTP (传输层/应用层)： 流媒体的“快递员”。 它负责将庞大的音视频切片、打包、加上时间戳和序列号，然后通过 UDP 疯狂地发往目的地。 RTCP (应用层)： 流媒体的“质检员”。 它不传数据，只在 RTP 旁边拉一条线，定期汇报丢包率和网络延迟。上层流媒体服务器（如 ZLMediaKit）根据它的反馈来决定是否降低码率或进行丢包重传（NACK）。 在实际传输中，RTP 和 RTCP 就像连体婴儿，它们的网络端口是成对绑定的：\nRTP 占用偶数端口 $N$（如 9000）传输音视频裸数据； RTCP 占用相邻的奇数端口 $N+1$（如 9001）传输控制反馈报告。 RTP（实时传输协议） RTP/RTCP 是实际传输数据的协议 头部 0 1 2 3 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |V=2|P|X| CC |M| PT | sequence number | // 4Byte 固定 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | timestamp | // 4Byte 固定 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | synchronization source (SSRC) identifier | // 4Byte 固定 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | contributing source (CSRC) identifiers | \u0026lt;- 每个4 字节, 共 CC 个 | ... | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | extension header (opt) | ext_length | \u0026lt;- 16 bit + 16 bit +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | extension data … | \u0026lt;- ext_length × 4 字节 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | payload … | 固定头部字段 头部固定12Byte:\nV (2 bits)：版本号（通常为2） P (1 bit)：填充位（末尾有填充字节时置1） X (1 bit)：扩展头标志 CC (4 bits)：CSRC计数器（混合流数量） M (1 bit)：标记位（视频帧结束/音频静音等） PT (7 bits)：负载类型（映射SDP中的a=rtpmap）G.711音频的负载类型为0，H.264视频的负载类型为96。负载类型的明确标识使得接收端能够正确解码数据 Sequence Number：包序号（检测丢包和乱序） Timestamp：时间戳（基于采样频率的时钟值） SSRC：数据源标识（唯一ID） CSRC：贡献源列表（混流时使用） 扩展头部 ​\t扩展头部是RTP协议的一个重要扩展机制，它是RTP头部的扩展标志（X）位决定的，它允许在RTP数据包中添加额外的信息，以满足特定应用的需求。扩展头部的格式如下：\n扩展头部标识符：占16位，用于标识扩展头部的类型和格式。不同的标识符对应不同的扩展头部定义，使得接收端能够正确解析扩展头部的内容。 扩展头部长度：占16位，指示扩展头部的长度（以32位字为单位）。扩展头部的长度可以根据实际需求进行调整，从而灵活地携带各种额外信息。 扩展头部内容：扩展头部的内容根据具体的扩展头部标识符而定，可以包含各种类型的信息，如时间戳扩展、序列号扩展、加密信息等。扩展头部的灵活性使得RTP协议能够适应不断发展的应用需求。扩展头部的使用提高了RTP协议的适应性和扩展性，使得RTP协议能够在多种复杂的网络环境中稳定运行，并满足不同应用的特定需求。 RTP会话过程 会话建立 RTP会话的建立是实时数据传输的起点，涉及多个关键步骤，确保数据能够正确传输到接收端。\n确定传输地址：当应用程序启动一个RTP会话时，首先需要确定一对目的传输地址。这包括一个网络地址和一对端口，一个用于RTP数据包，另一个用于RTCP控制包**。RTP数据通常发送到偶数UDP端口，而RTCP数据发送到相邻的奇数UDP端口**。这种端口对的设置使得RTP和RTCP数据能够正确地发送和接收。 初始化RTP和RTCP：在会话建立过程中，RTP和RTCP协议需要进行初始化。RTP协议从上层接收流媒体信息码流（如H.263），将其封装成RTP数据包；RTCP协议则从上层接收控制信息，封装成RTCP控制包。根据国际数据公司（IDC）的统计，RTP协议在初始化过程中，平均每个会话需要处理约1000个数据包，确保数据的完整性和实时性。 同步源标识符（SSRC）的生成：每个RTP会话都有一个唯一的同步源标识符（SSRC），用于标识数据的来源。SSRC是随机生成的，确保在同一RTP会话中不会出现相同的SSRC值。这使得接收端能够区分不同的数据源，从而正确处理多源数据。根据国际电信联盟（ITU）的统计，SSRC的生成过程在99%的情况下能够在1毫秒内完成，确保会话建立的高效性。 载荷 H.264 封装 RTP 是传输协议，H.264 是编码格式。RTP 对 H.264 码流进行封装传输。\n┌─────────────┬──────────────────────────────────────────┐\r│ RTP Header │ RTP Payload │\r│ (12字节) │ (H.264 NALU数据，不含起始码) │\r└─────────────┴──────────────────────────────────────────┘ H.264 NALU 头格式 在RTP头之后就是数据负载部分，在H264的负载封装中，第一个字节包含了数据传输的类型\n+---------------+ |F| NRI | Type | +---------------+ 1 2 5 bits h264的NALU头长度为：1字节\n字段 长度 说明 F (forbidden_zero_bit) 1 bit 必须为 0 NRI (nal_ref_idc) 2 bit NALU 重要性，值越大越重要（0-3） Type 5 bit NALU 类型 Type 取值：\n1-23：单个 NAL 单元包 24-27：组合包（STAP-A、STAP-B、MTAP16、MTAP24） 28-29：分片包（FU-A、FU-B） 例如：\n0x67（二进制：0110 0111）\n拆解：F=0，NRI=11（最高重要性），Type=7（SPS）。 0x68（二进制：0110 1000）\n拆解：F=0，NRI=11（最高重要性），Type=8（PPS）。 0x65（二进制：0110 0101）\n拆解：F=0，NRI=11（最高重要性），Type=5（I 帧）。 0x41（二进制：0100 0001）\n拆解：F=0，NRI=10（中重要性），Type=1（P 帧）。 单 NALU 模式 ​\t单一NAL单元模式是最简单的RTP封装方式，每个RTP包只包含一个H.264的NAL单元。这种模式适用于NAL单元大小较小，且网络条件较好，不需要对NAL单元进行分片或聚合的场景。在这种模式下，RTP负载的第一个字节是NAL单元的头信息（1字节），紧接着是NAL单元的RBSP数据。封装和解封装过程简单，易于实现。由于每个RTP包只包含一个NAL单元，因此在解码端可以快速识别和处理每个NAL单元，减少了处理复杂度。适用于NAL单元较小（如SPS、PPS等参数集或较小的I帧切片）的场景，或者在网络带宽充足且对延迟要求不高的情况下。\nNAL 类型（NAL Unit Type）： 1–23 结构： RTP 载荷直接就是一个完整的 NAL 单元（不含起始码 0x00000001），大小不得超过 MTU。 处理： 收到包后，直接将 payload[0..] 作为一个完整的 NALU，加入当前 RTP 时间戳对应的帧缓存，并在标记位（M=1）出现时输出整帧。 分片封包（FU-A） RTP Header + FU indicator （1字节） + FU header（1字节） + Fragment data\nFU indicator :\n+---------------+ |0|1|2|3|4|5|6|7| +-+-+-+-+-+-+-+-+ |F|NRI| Type | +---------------+ Type = 28 表示这是FU-A\nFU header :\n+---------------+ |0|1|2|3|4|5|6|7| +-+-+-+-+-+-+-+-+ |S|E|R| Type | +---------------+ S（开始位）: 1 bit， 当设置成1，指示分片NAL单元的开始。当跟随的FU荷载不是分片NAL单元荷载的开始，开始位设为0。 E（结束位）: 1 bit， 当设置成1，指示分片NAL单元的结束，即,荷载的最后字节也是分片NAL单元的最后一个字节。当跟随的 FU荷载不是分片NAL单元的最后分片，结束位设置为0。 R（保留位）: 1 bit， 保留位必须设置为0，接收者必须忽略该位。 Type（类型）：5 bit，原始NAL类型 H.265 封装 H.265 NALU头格式 +-------------------------------+ | F | Type | LayerId |TID| +-------------------------------+ Bits: F(1), Type(6), LayerId(6), TID(3) h265的NALU头长度为：2字节\n字段 长度 说明 F (forbidden_zero_bit) 1 bit 禁止位。最高位（第 15 位），必须为 0。若为 1 则标识该帧为无效帧、损坏帧。 Type (nal_unit_type) 6 bit 帧类型（第 9 ~ 14 位）。0 - 31：VCL NAL 单元（携带编码视频图像数据的流，如 19/20 为 IDR 关键帧，1 为普通 P/B 帧）。32 - 63：非 VCL NAL 单元（控制数据流，如 32 为 VPS，33 为 SPS，34 为 PPS）。 LayerId (nuh_layer_id) 6 bit 层标识（第 3 ~ 8 位）目前普通单视角的 IPC 编码中一般固定为 0 TID (nuh_temporal_id_plus1) 3 bit 时间层 ID 加上 1（第 0 ~ 2 位）。用于时域可分级（Temporal Scalability）。值等于 $tid + 1$（实际时间层 $tid = TID - 1$）。由于 $tid$ 值不能为负，且为了确保报头中至少有一个比特等于 1（防止与防惊群启动代码冲突），它的值一般默认写为 1（即对应 $tid = 0$ 基础时间层）。 ","permalink":"https://plhwiki.cn/multimedia/protocol/media_protocol/","summary":"\u003ch1 id=\"流媒体协议\"\u003e流媒体协议\u003c/h1\u003e\n\u003cp\u003e\u003cimg src=\"/multimedia/protocol/assets/image-20260520092716153.png\" alt=\"image-20260520092716153\" loading=\"lazy\"\u003e\n\u003c/p\u003e\n\u003cp\u003eRTSP 在上层\u003cstrong\u003e发号施令\u003c/strong\u003e（管控制），RTP 在底层\u003cstrong\u003e埋头搬砖\u003c/strong\u003e（运数据），RTCP 在旁边\u003cstrong\u003e盯着质量\u003c/strong\u003e（做监督）\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e三者关系：\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eRTSP (应用层)：\u003c/strong\u003e \u003cstrong\u003e流媒体的“遥控器”。\u003c/strong\u003e 它不传数据，只负责播放、暂停、快进和挂断等控制信令，并在正式传输前商量好底层数据通道的端口。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eRTP (传输层/应用层)：\u003c/strong\u003e \u003cstrong\u003e流媒体的“快递员”。\u003c/strong\u003e 它负责将庞大的音视频切片、打包、加上时间戳和序列号，然后通过 UDP 疯狂地发往目的地。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eRTCP (应用层)：\u003c/strong\u003e \u003cstrong\u003e流媒体的“质检员”。\u003c/strong\u003e 它不传数据，只在 RTP 旁边拉一条线，定期汇报丢包率和网络延迟。上层流媒体服务器（如 ZLMediaKit）根据它的反馈来决定是否降低码率或进行丢包重传（NACK）。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e在实际传输中，RTP 和 RTCP 就像连体婴儿，它们的网络端口是\u003cstrong\u003e成对绑定\u003c/strong\u003e的：\u003c/p\u003e","title":"media_protocol"},{"content":"线程管理的基础 启动线程 std::thread 可传函数、函数对象、lambda\n正确写法 void background_task() { /* ... */ } std::thread t(background_task); // 传函数指针，不要加 () // 或 std::thread t(\u0026amp;background_task); // 显式取地址，等价 函数名在表达式中会 衰变为函数指针，正好符合 std::thread 对 callable 的要求。\n歧义写法 background_task是类 class background_task { /* ... */ }; std::thread t(background_task()); // 解析为：声明函数 t，参数类型是「指向无参、返回 background_task 的函数的指针」 // t 不是 std::thread 对象！ background_task是函数 void background_task() { /* ... */ } std::thread t(background_task()); // 解析为：std::thread t( background_task() ); // ^^^^^^^^^^^^^^^^^ // 表达式：调用 background_task()，得到 void 记忆规则：\n写法 background_task 是类 background_task 是函数 t(background_task()) 声明函数 t（静默坑） 先调用函数，再构造（编译失败） t(background_task) 传临时对象（若可默认构造则可行） 正确：传函数指针 t((background_task())) 构造临时对象再建线程 不适用 t{background_task()} 构造临时对象再建线程 不适用（函数不能 {} 初始化）写法 线程完成 必须遵守的规则 析构前二选一：每个 std::thread 在销毁前必须 join() 或 detach()，否则析构函数调用 std::terminate() 直接终止程序 只能 join 一次：join() 后线程与 std::thread 对象解绑，joinable() 变为 false，再次 join() 是错误 join 会阻塞：当前线程一直等到目标线程执行完；第2章指出它不如 condition_variable / future 灵活（超时、轮询等见第4章） 使用场景与对比 方式 含义 典型场景 join() 等待线程结束 需要结果、需要保证子线程完成后再继续 detach() 不等待，后台运行 「发后即忘」；但主线程不能依赖子线程里的局部数据 同步 std::atomic 操作\n存储（store）操作，可选用的内存次序有 std::memory_order_relaxed、std::memory_order_release 或 std::memory_order_seq_cst\n载入（load）操作，可选用的内存次序有 std::memory_order_relaxed、std::memory_ order_consume、std::memory_order_acquire 或std::memory_order_seq_cst。\n\u0026ldquo;读-改-写\u0026rdquo;（read-modify-write）操作，可选用的内存次序有 std::memory_order_\nrelaxed、std::memory_order_consume、std::memory_order_acquire、std::memory_order_release、std::memory_order_acq_rel 或 std::memory_order_seq_cst。\ncompare_exchange_strong bool compare_exchange_strong(T\u0026amp; expected, T desire, ...); 成功：原子值原等于 expected → 改为 desired，返回 true 失败：原子值已变 → expected 被更新为 当前值，返回 false（值未改） failure 的内存序不能是 release/acq_rel（失败时没有「发布」语义）。\nstrong vs weak\ncompare_exchange_strong compare_exchange_weak 失败原因 仅当 当前值 ≠ expected 同上，还可能 伪失败（spurious failure） 典型用法 单次判断、逻辑必须准确 放在 while 循环 里重试 性能 某些 CPU 上可能多等一轮 可能少一次硬 CAS 指令 用法 std::atomic\u0026lt;bool\u0026gt; flag{false}; bool expected = false; if (flag.compare_exchange_strong(expected, true)) { // 抢到：原先确实是 false } else { // 别人已置 true，expected 已被更新为 true } ///////// 等价 /////////// bool expected = false; while (!b.compare_exchange_weak(expected, true) \u0026amp;\u0026amp; !expected) { // 伪失败或竞争：expected 会被更新，可重试或退出 } 一次性事件 std::future 一次性事件：结果就绪后 不可重置（与条件变量可反复等待不同）\nfuture 是一个模板类，用来等待异步操作的结果。它本身不能直接存值，只能从 promise 或 async 那里获取结果。\n核心方法 方法 作用 get() 阻塞等待，直到结果就绪，然后返回值（只能调用 1 次） wait() 阻塞等待结果就绪，但不返回值 wait_for(timeout) 等待指定时间，超时就返回，不卡死 wait_until(time) 等待到指定时间点 valid() 判断 future 是否还有效 关键特性 get() 只能调用一次，调用后 future 失效 没结果时调用 get() 会阻塞当前线程，直到结果到来 可以安全接收异步线程抛出的异常（传统线程做不到） std::promise ","permalink":"https://plhwiki.cn/cpp/language/thread/","summary":"\u003ch1 id=\"线程管理的基础\"\u003e线程管理的基础\u003c/h1\u003e\n\u003ch2 id=\"启动线程\"\u003e启动线程\u003c/h2\u003e\n\u003cp\u003estd::thread 可传函数、函数对象、lambda\u003c/p\u003e\n\u003ch3 id=\"正确写法\"\u003e正确写法\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-c++\" data-lang=\"c++\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e \u003cspan class=\"nf\"\u003ebackground_task\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"cm\"\u003e/* ... */\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003estd\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"kr\"\u003ethread\u003c/span\u003e \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebackground_task\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e   \u003cspan class=\"c1\"\u003e// 传函数指针，不要加 ()\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 或\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003estd\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"kr\"\u003ethread\u003c/span\u003e \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e\u003cspan class=\"n\"\u003ebackground_task\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e  \u003cspan class=\"c1\"\u003e// 显式取地址，等价\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e函数名在表达式中会 衰变为函数指针，正好符合 std::thread 对 callable 的要求。\u003c/p\u003e","title":"thread"},{"content":"命令行 命令行都是由\u0026rsquo;.\u0026lsquo;开始，\u0026rsquo;;\u0026lsquo;结束。\nSQLite 中「新建数据库」和「打开数据库」是同一个命令（文件不存在则创建，存在则打开）：\n# 方式1：创建/打开名为 test.db 的数据库（文件会生成在当前目录）\rsqlite3 test.db\r# 方式2：打开后查看当前数据库文件路径（验证）\rsqlite\u0026gt; .database # 输入点命令，显示当前连接的数据库文件 核心点命令 命令 作用 .database 查看当前连接的数据库文件 .tables 查看当前数据库中有哪些表 .schema 查看表的创建语句 .header on 查询结果显示列名(默认 off) .mode column 结果按列对齐显示(还有 box / csv / json / list) .dump 把整个库导出成 SQL 文本(备份/迁移用) .read init.sql 执行一个 .sql 脚本文件里的所有语句 .quit / .exit 退出 推荐每次进去先设置好显示格式:\nsqlite\u0026gt; .headers on sqlite\u0026gt; .mode column SQL语句 创建表 CREATE TABLE [IF NOT EXISTS] 表名 ( 列名1 数据类型 [约束], 列名2 数据类型 [约束], ... ); 数据类型\nINTEGER：整数（int）； TEXT：字符串（varchar）； REAL：浮点数（double）； DATETIME：日期时间； BLOB：二进制数据（如图片）。 常用约束\nPRIMARY KEY：主键（唯一标识行）； AUTOINCREMENT：自增（仅 INTEGER 主键可用）； NOT NULL：非空； UNIQUE：唯一； DEFAULT：默认值 系统表 每一个 SQLite 数据库都有一个对应的 SQLITE_MASTER 表， 它定义了数据库的模式。你不能对它使用UPDATE、INSERT 或 DELETE。\nCREATE TABLE sqlite_master(\rtype text,\rname text,\rtbl_name text,\rrootpage integer,\rsql text\r); sqlite\u0026gt; create table admin(username text,age integer); sqlite\u0026gt; insert into admin values(\u0026#34;ppp\u0026#34;,20); sqlite\u0026gt; .schema CREATE TABLE admin(username text,age integer); sqlite\u0026gt; .table admin sqlite\u0026gt; SELECT * FROM sqlite_master; table|admin|admin|2|CREATE TABLE admin(username text,age integer) 字段 值 含义 type table 对象类型 name admin 对象名称 tbl_name admin 所属表名称 rootpage 2 B树根页号 sql CREATE TABLE admin(\u0026hellip;) 创建语句 ","permalink":"https://plhwiki.cn/database/sqlite3/","summary":"\u003ch1 id=\"命令行\"\u003e命令行\u003c/h1\u003e\n\u003cp\u003e命令行都是由\u0026rsquo;.\u0026lsquo;开始，\u0026rsquo;;\u0026lsquo;结束。\u003c/p\u003e\n\u003cp\u003eSQLite 中「新建数据库」和「打开数据库」是同一个命令（文件不存在则创建，存在则打开）：\u003c/p\u003e","title":"sqlite3"},{"content":"概述与定义 SPI Flash 接口协议（芯片与主机的通信方式）\nNOR Flash NOR Flash是一种非易失性存储器，采用\u0026quot;或非门\u0026quot;(NOR)架构。它的特点是支持随机访问，可以直接在芯片上执行代码(XIP)，常用于存储启动代码、固件等对可靠性要求较高的应用。\nNAND Flash NAND Flash同样是非易失性存储器，采用\u0026quot;与非门\u0026quot;(NAND)架构。它以页为单位进行读写操作，以块为单位进行擦除，具有高密度、低成本的特点，主要用于大容量数据存储。\n架构与原理对比 NOR Flash\n硬盘 硬盘的层次关系是：\n硬盘(/dev/sda) ↓ 分区表(MBR/GPT) ↓ 分区(/dev/sda1 /dev/sda2 \u0026hellip;) ↓ 文件系统(ext4/FAT32/DVRFS\u0026hellip;) ↓ 文件(SQLite数据库、录像文件\u0026hellip;)\n​\t磁盘分区表常见有两种格式：MBR（Master Boot Record） 和 GPT（GUID Partition Table）。它们主要在 容量支持、分区数量、启动方式、可靠性 等方面有明显区别。\n分区表\n","permalink":"https://plhwiki.cn/hardware/flash/","summary":"\u003ch1 id=\"概述与定义\"\u003e概述与定义\u003c/h1\u003e\n\u003ch2 id=\"spi-flash\"\u003eSPI Flash\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e接口协议\u003c/strong\u003e（芯片与主机的通信方式）\u003c/p\u003e\n\u003ch2 id=\"nor-flash\"\u003eNOR Flash\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003eNOR Flash\u003c/strong\u003e是一种非易失性存储器，采用\u0026quot;或非门\u0026quot;(NOR)架构。它的特点是支持随机访问，可以直接在芯片上执行代码(XIP)，常用于存储启动代码、固件等对可靠性要求较高的应用。\u003c/p\u003e","title":"flash"},{"content":"","permalink":"https://plhwiki.cn/hardware/mipi/","summary":"","title":"mipi"},{"content":"","permalink":"https://plhwiki.cn/linux/drive/char_driver/","summary":"","title":"char_driver"},{"content":"","permalink":"https://plhwiki.cn/basic/data_structure/","summary":"","title":"data_structure"},{"content":"单例模式 Service Locator 这实际上是属于一种全局获取依赖对象的设计模式，可以理解为“我不通过构造函数传进来，而是到一个全局注册中心里去拿对象。”\nMeyer\u0026rsquo;s Singleton 经典代码：\nclass StreamManager { public: static StreamManager\u0026amp; instance() { static StreamManager obj; return obj; } private: StreamManager() {} }; //使用 CmsStreamManager::instance().start(); 缺点 隐式依赖 RecordController(controller) //它内部还依赖： CmsStreamManager::instance() 生命周期不可控 谁创建？ 谁销毁？ 什么时候初始化？全隐藏。\n多线程 shutdown 麻烦 ","permalink":"https://plhwiki.cn/cpp/language/design/","summary":"\u003ch1 id=\"单例模式\"\u003e单例模式\u003c/h1\u003e\n\u003ch2 id=\"service-locator\"\u003eService Locator\u003c/h2\u003e\n\u003cp\u003e这实际上是属于一种\u003cstrong\u003e全局获取依赖对象\u003c/strong\u003e的设计模式，可以理解为“我不通过构造函数传进来，而是到一个全局注册中心里去拿对象。”\u003c/p\u003e\n\u003ch2 id=\"meyers-singleton\"\u003eMeyer\u0026rsquo;s Singleton\u003c/h2\u003e\n\u003cp\u003e经典代码：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-c++\" data-lang=\"c++\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eStreamManager\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003epublic\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003estatic\u003c/span\u003e \u003cspan class=\"n\"\u003eStreamManager\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e \u003cspan class=\"n\"\u003einstance\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003estatic\u003c/span\u003e \u003cspan class=\"n\"\u003eStreamManager\u003c/span\u003e \u003cspan class=\"n\"\u003eobj\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eobj\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eprivate\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eStreamManager\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e};\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//使用\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eCmsStreamManager\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"n\"\u003einstance\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"n\"\u003estart\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"缺点\"\u003e缺点\u003c/h3\u003e\n\u003ch4 id=\"隐式依赖\"\u003e隐式依赖\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-c++\" data-lang=\"c++\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eRecordController\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003econtroller\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//它内部还依赖：\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eCmsStreamManager\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"n\"\u003einstance\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"生命周期不可控\"\u003e生命周期不可控\u003c/h4\u003e\n\u003cp\u003e谁创建？ 谁销毁？ 什么时候初始化？全隐藏。\u003c/p\u003e","title":"design"},{"content":"花屏情况 起因 修改了uboot 的bootargs里的rmem导致花屏，mem=254M@0x0 rmem=258M@0xFE00000就会绿屏\nddr大小不匹配，512MB与256MB芯片的编译选项选择错误。remap_array的值不对\nuboot的时候，开机画面是正常的，跑完内核后出现花屏现象\n解决 改为mem=254M@0x0 rmem=258M@0x2FE00000\nmake isvp_a1_x_sfc0nor ，确保生成的ddr_reg_values.h一样\n原因 地址映射的问题： console=ttyS1,115200n8 mem=254M@0x0 rmem=258M@0x2FE00000 A1 mips上物理地址的0x10000000~0x20000000（256M）通常给I/O控制器使用 A1 mips架构上地址空间前256M是DDR，0x10000000-0x20000000是寄存器地址空间，需要跳过。0x20000000之后相当于跟0x10000000接上了。\n0x0-0x10000000第一段mem，0x10000000-0x20000000 io空间，0x20000000-0x30000000也是映射的第一段mem，0x30000000-0x40000000第二段mem\nMIPS 地址空间布局 MIPS 的硬件设计 在芯片出厂时就硬性规定了地址空间的用途。正如图所示：\n用户空间 (kuseg): 只有 2GB (0x00000000 到 0x7FFFFFFF)。 硬件强制要求最高位（Bit 31）为 0 时才是用户模式，导致用户空间被物理地限制在了低 2GB 空间内。 内核空间: 占据了剩下的 2GB (0x80000000 到 0xFFFFFFFF)。 kseg0/kseg1 的物理地址换算：\n物理地址 = 虚拟地址 \u0026amp; 0x1FFFFFFF（去掉最高 3 位）\r例：\rU-Boot 地址 0x80600000（kseg0）→ 物理 0x00600000 ✅ Flash logo 地址\rU-Boot 地址 0xBFC00000（kseg1）→ 物理 0x1FC00000 ✅ vobuf 落在物理 RAM 内 0x00000000 ~ 0x10000000 第一段 mem（256M DDR） 0x10000000 ~ 0x20000000 IO空间（寄存器地址，需跳过）-\u0026gt; 0x90000000 0xF0000000 0x20000000 ~ 0x30000000 映射回第一段 mem 0x30000000 ~ 0x40000000 第二段 mem vobuf 必须同时满足：\n物理地址落在真实 DDR（非 IO 空间） 落在 rmem 区（Linux 不会动这块内存） 距离 rmem 末尾有足够空间存放 NV12 数据 vobuf = 0x80000000 + rmem起始物理地址 + rmem内的偏移\nrmem 起始 = 0x2FE00000\nrmem 大小 = 258MB = 0x10200000\nrmem 末尾 = 0x2FE00000 + 0x10200000 = 0x40000000\nvobuf = 0x80000000 + rmem起始 + (rmem大小 - 预留空间)\r= 0x80000000 + 0x2FE00000 + (0x10200000 - 0x400000)\r= 0x80000000 + 0x2FE00000 + 0xFE00000\r= 0x80000000 + 0x3FE00000\r= 0xBFC00000 ","permalink":"https://plhwiki.cn/interview/nvr/","summary":"\u003ch1 id=\"花屏情况\"\u003e花屏情况\u003c/h1\u003e\n\u003ch2 id=\"起因\"\u003e起因\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e修改了uboot 的bootargs里的rmem导致花屏，\u003ccode\u003emem=254M@0x0 rmem=258M@0xFE00000\u003c/code\u003e就会绿屏\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eddr大小不匹配，512MB与256MB芯片的编译选项选择错误。remap_array的值不对\u003c/p\u003e","title":"nvr"},{"content":" mcu是否可以跑linux系统？ 普通 MCU 通常不能直接运行完整 Linux。\nLinux 一般需要：\nMMU：内存管理单元，用于虚拟地址、进程隔离。 较大 RAM：通常至少几十 MB，实际产品常见 64MB、128MB 以上。 存储空间：内核、rootfs、动态库、应用都需要空间。 ","permalink":"https://plhwiki.cn/interview/basic/","summary":"\u003col\u003e\n\u003cli\u003emcu是否可以跑linux系统？\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e\u003cstrong\u003e普通 MCU 通常不能直接运行完整 Linux。\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eLinux 一般需要：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eMMU\u003c/strong\u003e：内存管理单元，用于虚拟地址、进程隔离。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e较大 RAM\u003c/strong\u003e：通常至少几十 MB，实际产品常见 64MB、128MB 以上。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e存储空间\u003c/strong\u003e：内核、rootfs、动态库、应用都需要空间。\u003c/li\u003e\n\u003c/ul\u003e","title":"basic"},{"content":"编码方式 Base64 ​\t把任意二进制数据安全地表示成只含可打印 ASCII 字符的文本，便于在只支持文本的协议里传输，比如邮件 SMTP、HTTP、JSON 嵌入二进制、证书/密钥文本化等。它不是加密，也不是压缩；任何人都可以轻易还原\n核心原理：3 字节变 4 字符 ​\t3 个字节 = 24 bit。把这 24 bit 切成 4 组，每组 6 bit。每个 6 bit 的值范围是 0~63，再映射到 64 个字符表中： ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\n0~25 -\u0026gt; A~Z 26~51 -\u0026gt; a~z 52~61 -\u0026gt; 0~9 62 -\u0026gt; + 63 -\u0026gt; / 编码过程 ​\tbase64编码，每3个8位明文数据为一组，取这3个字数据的ASCII码，然后以6位为一组组成4个新的数据。对于不足3字节的处理：\n将每3个字节作为一组，3个字节一共24个二进制位 将这24个二进制位分为4组，每个组有6个二进制位 在每组的6个二进制位前面补两个00，扩展成32个二进制位，即四个字节 每个字节对应的将是一个小于64的数字，即为字符编号,再根据字符索引关系表，每个字符编号对应一个字符，就得到了Base64编码字符 UTF-8 协议 SMTP协议 ​\t用于电子邮件传输的协议\n","permalink":"https://plhwiki.cn/basic/com_protocl/","summary":"\u003ch1 id=\"编码方式\"\u003e编码方式\u003c/h1\u003e\n\u003ch2 id=\"base64\"\u003eBase64\u003c/h2\u003e\n\u003cp\u003e​\t把任意二进制数据安全地表示成只含可打印 ASCII 字符的文本，便于在只支持文本的协议里传输，比如邮件 SMTP、HTTP、JSON 嵌入二进制、证书/密钥文本化等。它不是加密，也不是压缩；任何人都可以轻易还原\u003c/p\u003e","title":"com_protocl"},{"content":"","permalink":"https://plhwiki.cn/cpp/3rdpart/zlmediakit/","summary":"","title":"ZLMediakit"},{"content":"ZLToolKit源码框架 主要分为Thread、Poller、Network、Util四大部分。\nThread文件夹 TaskExecutor.h 有cpu负载计算，Task函数指针模板，任务执行器管理，管理任务执行线程池\nThreadLoadCounter ​\tcpu负载计算器，基类，统计线程每一次的睡眠时长和工作时长，并记录样本，调用load计算cpu负载=工作时长/总时长。通过滑动窗口busy-rate计算，不是去读OS的真实cpu统计。\n原理：\n通过在线程进入阻塞前记一段 run_time，被唤醒后记一段 sleep_time，load() 再按最近窗口做比例计算。\n//startSleep() 记录的是“刚刚结束的一段运行时间”。 void ThreadLoadCounter::startSleep() { lock_guard\u0026lt;mutex\u0026gt; lck(_mtx); _sleeping = true; auto current_time = getCurrentMicrosecond(); auto run_time = current_time - _last_wake_time;//线程准备休眠时，说明上一段活跃执行区间结束了 _last_sleep_time = current_time; //为下一段 sleep 计时做准备 _time_list.emplace_back(run_time, false);//表示这段时间不是睡眠，而是运行 if (_time_list.size() \u0026gt; _max_size) { _time_list.pop_front(); } } //sleepWakeUp() 记录的是“刚刚结束的一段休眠时间”。 void ThreadLoadCounter::sleepWakeUp() { lock_guard\u0026lt;mutex\u0026gt; lck(_mtx); _sleeping = false; auto current_time = getCurrentMicrosecond(); auto sleep_time = current_time - _last_sleep_time;//计算睡了多久，相当于空闲时间 _last_wake_time = current_time//更新为当前时刻。开始新的运行区间计时 _time_list.emplace_back(sleep_time, true); if (_time_list.size() \u0026gt; _max_size) { _time_list.pop_front(); } } Util文件夹 RingBuffer RingBuffer是由多个类组成，分为两大功能：存储和数据分发。\n存储功能由类RingStorage实现，是数据存储类，它是一个循环队列，有最大容量定义，从尾部插入最新数据，当队列满了，从头部删除老数据。\n在RingBuffer类里中的数据结构，以EventPoller的指针作为Key，\ntemplate \u0026lt;typename T\u0026gt; class RingBuffer public std::enable_shared_from_this\u0026lt;RingBuffer\u0026lt;T\u0026gt;\u0026gt; { public: void write(T in, bool is_key = true) { if (_delegate) { _delegate-\u0026gt;onWrite(std::move(in), is_key); return; } LOCK_GUARD(_mtx_map); for (auto \u0026amp;pr : _dispatcher_map) { auto \u0026amp;second = pr.second; //切换线程后触发onRead事件 [AUTO-TRANSLATED:4ca6647d] //Switch thread and trigger onRead event pr.first-\u0026gt;async([second, in, is_key]() mutable { second-\u0026gt;write(std::move(in), is_key); }, false); } _storage-\u0026gt;write(std::move(in), is_key); } private: std::unordered_map\u0026lt;EventPoller::Ptr, typename RingReaderDispatcher::Ptr, HashOfPtr\u0026gt; _dispatcher_map; }; **向读者分发数据：**当RingReaderDispatcher收到write调用时，它已处于对应的EventPoller线程。RingReaderDispatcher会遍历其管理的所有读者句柄（_RingReader），并逐一调用reader-\u0026gt;onRead(in, is_key)。实时分发给该 poller 上的 reader，_storage-\u0026gt;write(std::move(in), is_key); 更新该 poller 自己的 GOP cache // RingReaderDispatcher::write() 的核心代码段 void write(T in, bool is_key = true) { // 遍历本分发器管理的所有读者，直接回调 for (auto it = _reader_map.begin(); it != _reader_map.end();) { auto reader = it-\u0026gt;second.lock(); if (reader) { reader-\u0026gt;onRead(in, is_key); // 最终会调用业务回调 _read_cb ++it; } } // 通知存储层 _storage-\u0026gt;write(std::move(in), is_key); } 写入存储层：_RingStorage决定帧的缓存命运 所以整体模型是：\nRingBuffer::_storage 作用：主 GOP cache，新 poller attach 时用来 clone RingReaderDispatcher::_storage 作用：该 poller 线程内的 GOP cache，供该 poller 的 reader flushGop 使用 RingReader 作用：实际观看者/消费者，只能在自己的 poller 线程里操作 //////////////// RingBuffer::write(frame, is_key=true) │ ├── 1. 锁定 _mtx_map，遍历 _dispatcher_map │ ├── 对线程A的分发器：通过 pollerA-\u0026gt;async 将任务抛到线程A │ └── 对线程B的分发器：通过 pollerB-\u0026gt;async 将任务抛到线程B │ ├── 2. 写入主存储：_storage-\u0026gt;write(frame, true) （主存储也更新） │ └── 3. 解锁 ▼ （异步执行，各自独立） 线程A：DispatcherA-\u0026gt;write(frame, true) │ ├── 遍历线程A的 reader_map，逐个回调 onRead (直接访问副本A里的历史GOP) └── 写入副本A：_storage_of_A-\u0026gt;write(frame, true) （副本A开启新GOP，淘汰旧数据） 线程B：DispatcherB-\u0026gt;write(frame, true) │ ├── 遍历线程B的 reader_map，逐个回调 onRead └── 写入副本B：_storage_of_B-\u0026gt;write(frame, true) （副本B开启新GOP，淘汰旧数据） c\n","permalink":"https://plhwiki.cn/cpp/3rdpart/zltoolkit/","summary":"\u003ch1 id=\"zltoolkit源码框架\"\u003eZLToolKit源码框架\u003c/h1\u003e\n\u003cp\u003e主要分为Thread、Poller、Network、Util四大部分。\u003c/p\u003e\n\u003ch1 id=\"thread文件夹\"\u003eThread文件夹\u003c/h1\u003e\n\u003ch3 id=\"taskexecutorh\"\u003eTaskExecutor.h\u003c/h3\u003e\n\u003cp\u003e有\u003cstrong\u003ecpu负载计算，Task函数指针模板，任务执行器管理，管理任务执行线程池\u003c/strong\u003e\u003c/p\u003e","title":"ZLToolKit"},{"content":"","permalink":"https://plhwiki.cn/cpp/3rdpart/openssl/","summary":"","title":"openssl"}]