[{"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 个。 目的：防止团队成员因多任务切换而降低效率，暴露流程中的瓶颈，促使大家“聚焦完成，而非盲目开始”。 Doxygen 文档注释 标签 作用 @brief 简要描述函数、结构体或类 @param 描述函数参数 @return 描述函数返回值 @retval 描述具体返回值及含义 @note 补充说明 @warning 警告或注意事项 @details 详细描述 /** * @brief 写入磁盘数据 * @param disk 磁盘对象 * @param data 待写入的数据 * @param size 数据长度（字节） * @return 成功返回 0，失败返回负数 */ int disk_write_data(Disk* disk, const void* data, size_t size); 技术提供商 DNSPod DNSPod 是腾讯云旗下的 DNS（Domain Name System，域名系统）解析服务，主要作用是把你的域名转换成服务器 IP 地址，让用户能够通过域名访问服务器。\n具体对应 DNSPod 配置：\n主机记录 完整域名 解析 IP 用途 note note.plhwiki.cn 111.229.216.239 笔记网站 analytics analytics.plhwiki.cn 111.229.216.239 访客统计 @ plhwiki.cn 111.229.216.239 301 跳转到 note.plhwiki.cn www www.plhwiki.cn 111.229.216.239 301 跳转到 note.plhwiki.cn 为什么同一个 IP 可以对应多个网站？ 因为 DNS 负责把域名解析成 IP，而 Web 服务器负责区分具体访问哪个网站。\nflowchart TD\rA[\u0026#34;🌐 note.plhwiki.cn\u0026#34;]\rB[\u0026#34;📊 analytics.plhwiki.cn\u0026#34;]\rA --\u0026gt; C\rB --\u0026gt; C\rsubgraph Server[\u0026#34;腾讯云服务器 · 111.229.216.239\u0026#34;]\rC[\u0026#34;Nginx / Web Server\u0026lt;br/\u0026gt;根据域名区分请求\u0026#34;]\rD[\u0026#34;Hugo 博客\u0026lt;br/\u0026gt;静态文章网站\u0026#34;]\rE[\u0026#34;Analytics 服务\u0026lt;br/\u0026gt;网站访问统计\u0026#34;]\rC --\u0026gt; D\rC --\u0026gt; E\rend\rstyle Server fill:#f8fafc,stroke:#94a3b8,stroke-width:1px\rstyle C fill:#e0f2fe,stroke:#0284c7\rstyle D fill:#dcfce7,stroke:#16a34a\rstyle E fill:#fef3c7,stroke:#d97706 浏览器访问时大致经历三个步骤：\nDNS 解析： analytics.plhwiki.cn → 111.229.216.239。 建立连接： 浏览器连接服务器的 443 端口（HTTPS）。 服务分发： Web 服务器根据访问的域名（TLS SNI、HTTP Host 等信息），将请求交给对应服务。 网络技术 DDNS DDNS（Dynamic Domain Name System，动态域名解析），作用是：当设备的公网 IP 会变化时，自动把新的公网 IP 更新到一个固定域名上。\nPPPoE PPPoE（Point-to-Point Protocol over Ethernet，以太网上的点对点协议），最常见的用途就是家庭宽带里的“拨号上网”。\nMermaid markdown里时序图语法\n图表声明决定类型：graph／flowchart 用于流程图，sequenceDiagram 用于时序图；pie、classDiagram、stateDiagram 是各自独立的图表声明，不是 graph 的参数。 序列图（Sequence Diagram） 用于表示对象间交互的时间顺序。\n箭头语法 语法 含义 A-\u0026gt;\u0026gt;B 实线箭头 A--\u0026gt;\u0026gt;B 虚线箭头，常用于响应 A-\u0026gt;B 实线，无箭头头部 A--\u0026gt;B 虚线，无箭头头部 A-xB 末尾为叉号 A-)B 异步消息箭头 消息语法 ","permalink":"https://note.plhwiki.cn/reference/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":"HTTPS 配置 配置HTTPS 安装 Certbot： sudo apt update sudo apt install -y snapd sudo snap install core sudo snap refresh core sudo snap install --classic certbot sudo ln -s /snap/bin/certbot /usr/local/bin/certbot 申请证书并自动配置 Nginx： sudo certbot --nginx -d note.plhwiki.cn 旧域名 plhwiki.cn、www.plhwiki.cn 的证书仍然保留，用于把 HTTPS 访问 301 跳转到 note.plhwiki.cn。\n","permalink":"https://note.plhwiki.cn/tools/deploy/https/","summary":"\u003ch1 id=\"https-配置\"\u003eHTTPS 配置\u003c/h1\u003e\n\u003ch2 id=\"配置https\"\u003e配置HTTPS\u003c/h2\u003e\n\u003ch3 id=\"安装-certbot\"\u003e安装 Certbot：\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo apt update\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo apt install -y snapd\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\"\u003esudo snap install core\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo snap refresh core\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo snap install --classic certbot\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\"\u003esudo ln -s /snap/bin/certbot /usr/local/bin/certbot\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"申请证书并自动配置-nginx\"\u003e申请证书并自动配置 Nginx：\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo certbot --nginx -d note.plhwiki.cn\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e旧域名 \u003ccode\u003eplhwiki.cn\u003c/code\u003e、\u003ccode\u003ewww.plhwiki.cn\u003c/code\u003e 的证书仍然保留，用于把 HTTPS 访问 301 跳转到 \u003ccode\u003enote.plhwiki.cn\u003c/code\u003e。\u003c/p\u003e","title":"HTTPS"},{"content":"claude CLAUDE.md CLAUDE.md 可按管理范围分层组织；这些文件作为上下文提供给模型，不能简单理解为 Git 配置那样的键值覆盖：\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 --transport http \u0026lt;name\u0026gt; \u0026lt;url\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 --scope user context7 -- npx -y @upstash/context7-mcp 通信方式 Stdio 方式 当客户端（例如 Claude Code）启动时，它会在后台直接以子进程（Subprocess）的形式运行 MCP 服务器。两者之间通过系统的标准输入（stdin）和标准输出（stdout）进行 JSON-RPC 数据的传递。\n客户端发送请求： 向子进程的 stdin 写入数据。 服务器返回响应： 向自身的 stdout 写入数据，被客户端捕获。 特点\n纯本地进程间通信，外界无法通过网络访问该服务器。 客户端通常负责启动和停止 stdio 子进程；具体启动时机及异常退出后的清理取决于客户端和服务器实现，不能保证永不残留子进程。 因为 stdout 被用来传输协议数据，服务器代码里绝对不能包含任何普通的 printf 或 console.log，否则会破坏协议格式导致报错（日志必须输出到 stderr）。 HTTP / SSE 方式 MCP 服务器作为一个独立的 Web 服务器运行在某个端口上（本地或远程云端）。\n客户端发送请求： 使用标准的 HTTP POST 请求向服务器发送指令。 服务器响应与推送： Streamable HTTP 可以直接返回 JSON，也可以使用 SSE 流，并非每个响应都必须走长连接。旧版 HTTP+SSE transport 与 Streamable HTTP 的端点和会话机制不同。 特点\n支持远程调用： 服务器可以部署在局域网内的另一台机器，甚至云端（如 Context7 的官方 MCP 托管服务），实现\u0026quot;一套工具，多端共享\u0026quot;。 解耦运行： MCP 服务器是一个独立的常驻进程，可以单独启动、停止、扩容，不依赖客户端编辑器的生命周期。 调试方便： 可以使用 Postman 等工具直接测试接口，普通的日志打印不会干扰通信。 如何选择 服务的核心功能在云端（GitHub、Stripe、context7 查公开文档）→ 优先 HTTP，省事 服务需要访问本地文件或数据库 → stdio 通常更方便，也可以使用有适当访问控制的本地 HTTP 服务；“本地访问”不决定传输协议 离线环境 → 选择不依赖外部服务的本地实现；stdio 和本地 HTTP 都可能适用 想锁版本 → 固定包版本、镜像或服务端部署版本；与选择 stdio／HTTP 没有必然关系 codex npm 安装 先确认当前使用哪套 Node/npm，再选择目录方案；nvm 与自定义 npm prefix 不应混用。\ncommand -v node command -v npm npm config get prefix npm root -g **已经使用 nvm：**在当前用户下选择 Node 版本，直接运行 npm install -g @openai/codex。不要另外设置 prefix=~/.local；若之前手动设置过，应先移除该项，避免与 nvm 冲突。\n**使用系统 Node、且全局目录需要 root 权限：**可把 npm prefix 改为用户目录（此方案与 nvm 二选一）：\nnpm config set prefix \u0026#34;$HOME/.local\u0026#34; export PATH=\u0026#34;$HOME/.local/bin:$PATH\u0026#34; npm install -g @openai/codex codex --version 需要长期生效时，把 PATH 配置写入对应 shell 的启动文件。已有系统级 Codex 是否需要卸载，应先用 type -a codex 确认其来源；修改 prefix 后再执行卸载，并不保证会命中旧安装目录。\n安装中断：检查残留 全局模块路径以当前 npm root -g 为准，不固定是 ~/.npm-global 或 ~/.local：\nnpm root -g ls -al \u0026#34;$(npm root -g)/@openai/\u0026#34; 结合 npm 的错误日志识别具体残留目录。确认没有 npm 安装进程正在使用它后，再处理对应目录并重新安装；不要照抄其他 Node 版本的路径删除包。\nnpm npm（Node Package Manager）是 Node.js 的包管理器，类似于：\nLinux 的 apt / yum Python 的 pip Rust 的 cargo 什么是 Node.js？Node.js 是让 JavaScript 能在服务器/终端运行的环境。\n安装包 # 本地安装 —— 只在当前项目可用，装到 ./node_modules/ npm install lodash # 全局安装 —— Unix 下包放在 prefix/lib/node_modules，可执行入口通常链接到 prefix/bin（需在 PATH 中） 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 删除配置 #查看旧环境 npm config get prefix /home/plh/.npm-global cat ~/.npmrc prefix=/home/plh/.npm-global registry=https://registry.npmmirror.com/ 执行 npm config delete prefix\nnpx npx 是 npm 自带（5.2+）的包执行工具：不用先全局安装，直接下载并运行某个包的可执行文件，通常会复用或保留 npm 缓存，不等于执行完就删除下载内容；包运行时也可能创建文件。\n# 临时下载并执行，不需要提前 npm install -g npx create-react-app my-app # stdio 方式的 MCP 服务器常用 npx 启动 npx -y @upstash/context7-mcp node -v：查看Node.js的版本 nvm ​\tnvm（Node Version Manager，Node.js 版本管理器）是一个用来在同一台电脑上安装、切换多个 Node.js 版本的命令行工具。 它能让你在不同的 Node.js 版本之间快速切换，避免版本冲突，特别适合同时维护多个对 Node.js 版本要求不同的项目。\n从层级上看：\nnvm │ └── 管理多个 Node.js 版本 │ ├── node │ └── 执行 JavaScript │ └── npm ├── 安装 JS 包 ├── 管理项目依赖 └── 安装 CLI 工具 安装 通过官方的脚本安装\ncurl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.7/install.sh | bash #安装成功之后刷新shell source ~/.bashrc 常用命令 安装node24：nvm install 24 查看可以安装的 Node:nvm ls-remote 切换到 Node 22:nvm use 22 之后安装 Codex：npm install -g @openai/codex。入口通常位于当前所选 nvm Node 版本的 bin 目录；用 command -v codex 查看，不要固定写成某个用户或 Node 24 的路径。\n参考：官方 Codex 安装示例；nvm；MCP transports；Claude MCP；Context7。\n","permalink":"https://note.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 配置那样的键值覆盖：\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":"编码方式 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; / 编码过程 Base64 直接处理输入字节，不要求输入原本是 ASCII 文本。每 3 个字节分为 4 组 6 bit，再映射到字符表。\n将每3个字节作为一组，3个字节一共24个二进制位 将这24个二进制位分为4组，每个组有6个二进制位 在每组的6个二进制位前面补两个00，扩展成32个二进制位，即四个字节 每个字节对应的将是一个小于64的数字，即为字符编号,再根据字符索引关系表，每个字符编号对应一个字符，就得到了Base64编码字符 不足 3 字节时，最后一组右侧补零后编码：剩 1 字节得到 2 个字符并补 ==，剩 2 字节得到 3 个字符并补 =。这是标准带填充的 Base64；某些协议会另行规定省略填充。\nUTF-8 协议 SMTP协议 ​\t用于电子邮件传输的协议\nHTTP协议 参考：RFC 4648。\n","permalink":"https://note.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":"非对称加密 公钥密码使用一对相关的公钥和私钥。对于公钥加密方案，使用接收方公钥加密、接收方私钥解密；对于数字签名方案，使用签名方私钥签名、公钥验签。两类操作的目的和算法要求不同，不能把签名简单等同于“私钥加密”。\nECC 椭圆曲线密码学（英语：Elliptic curve cryptography，缩写为 ECC），基于椭圆曲线数学的一类公钥密码技术，而不是某一个加密算法。常见用途包括 ECDH 密钥协商、ECDSA 数字签名。 对称加密 AES\n","permalink":"https://note.plhwiki.cn/basic/security/","summary":"\u003ch1 id=\"非对称加密\"\u003e非对称加密\u003c/h1\u003e\n\u003cp\u003e公钥密码使用一对相关的公钥和私钥。对于公钥加密方案，使用接收方公钥加密、接收方私钥解密；对于数字签名方案，使用签名方私钥签名、公钥验签。两类操作的目的和算法要求不同，不能把签名简单等同于“私钥加密”。\u003c/p\u003e","title":"security"},{"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实现，是数据存储类，它按关键帧/GOP 管理缓存，并受容量参数约束；不能简单当作满了就逐帧 pop_front 的普通环形队列，具体淘汰和清空规则应对照所用版本源码。\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; } else { it = _reader_map.erase(it); --_reader_size; onSizeChanged(false); } } // 通知存储层 _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，传递本次 write 的数据；历史 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参考：ZLToolKit RingBuffer 源码。\n","permalink":"https://note.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":"基本 函数定义\n返回值类型 函数名(参数列表) //函数头 { 函数体 } 函数声明\n返回值类型 函数名（参数列表）; 函数类型\nstd::string encode(const XmlFieldValue\u0026amp; field) { ... } 返回类型 参数列表 ↓ ↓ std::string (const XmlFieldValue\u0026amp;) using EncodeFunc = std::string(const XmlFieldValue\u0026amp;); 相当于给这个函数类型起了个名字 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;//读取普通非 const 变量的值，也会 odr-use x。 int* p = \u0026amp;x;//需要x 的地址，x 必须存在内存中，x 被 ODR-use } 例如\n//c++11 .h struct State { static constexpr std::uint32_t kDiagFrames = 20; // 需包含 \u0026lt;cstdint\u0026gt; }; //.cpp constexpr std::uint32_t State::kDiagFrames; // C++11/14：被 odr-use 时需提供类外定义 static 表示这个变量属于类，不属于对象 constexpr 表示编译期常量 auto p = \u0026amp;State::kDiagFrames;//必须存在一个真正的对象，也就是State::kDiagFrames需要内存地址 注意：\nC++11/14 中，类内 static constexpr 成员可用于常量表达式；若被 odr-use（如取地址），仍需在一个源文件中提供上面的类外定义，且不再写初始化器。C++17 起 constexpr 静态数据成员隐含 inline，不再需要额外定义。\n三五准则 禁止拷贝构造 EventLogManager(const EventLogManager\u0026amp;) = delete; 禁止拷贝赋值 EventLogManager\u0026amp; operator=(const EventLogManager\u0026amp;) = delete; 关键字 explicit explicit 的作用是禁止隐式类型转换；而“有没有默认构造函数”取决于类里是否存在一个可以无参调用的构造函数，以及编译器是否还会自动生成默认构造函数。\nclass A { public: explicit A(int x) {} }; A a; // ❌ 编译失败 A a(10); // ✅ 左值 左值是标识对象或函数的一类表达式，例如变量名 a、解引用表达式 *p、返回 T\u0026amp; 的函数调用。左值不一定有名字，也不一定可修改（如 const 对象）；位域左值还不能取地址。\nint a = 10;（a是左值） 右值 右值包括纯右值（prvalue）和将亡值（xvalue），不等于“不可修改”。\n纯右值：例如整数常量 42、a + b 和按值返回的函数调用；字符串字面量是左值。\n将亡值：例如 std::move(a) 或返回 T\u0026amp;\u0026amp; 的函数调用。std::move 只做类型转换，不会自行移动或释放资源；有名字的右值引用变量在表达式中仍是左值。\nstd::move(a) 42, a + b, func() 右值引用 智能指针 unique_ptr 介绍 unique_ptr 表示独占所有权，不能复制，但可以移动。可以通过值传递把所有权移入函数，例如 consume(std::move(ptr))；也可用于支持移动语义的容器和算法。\n创建 std::unique_ptr\u0026lt;int\u0026gt; intprt {}; std::unique_ptr\u0026lt;int\u0026gt; intPtrB{ new int{ 15 } }; //C++ 14 及之后的初始化方式：std::make_unique \u0026lt;类型\u0026gt;(); std::unique_ptr\u0026lt;int\u0026gt; ptrA{ std::make_unique \u0026lt;int\u0026gt;(5)}; //把指针指向的那块内存初始化为 5; std::unique_ptr\u0026lt;int[]\u0026gt; ptrB{ std::make_unique\u0026lt;int[]\u0026gt;(5) }; // 数组初始化为 有 5个元素; 用法 reset() 调用删除器释放原管理对象，并保存新指针（默认变为 nullptr）；unique_ptr 对象本身仍存在。 get() 返回所管理的原始指针，不转移所有权；不要据此重复释放对象。 自定义删除器\nstd::unique_ptr\u0026lt;T, Deleter\u0026gt; enable_shared_from_this 类型转换 dynamic_cast 将一个基类对象指针（或引用cast到继承类指针，dynamic_cast会根据基类指针是否真正指向继承类指针来做相应处理。\nclass Animal { public: virtual ~Animal() = default; virtual void speak() { std::cout \u0026lt;\u0026lt; \u0026#34;Animal\u0026#34; \u0026lt;\u0026lt; std::endl; } }; class Dog : public Animal { public: void speak() override { std::cout \u0026lt;\u0026lt; \u0026#34;Dog\u0026#34; \u0026lt;\u0026lt; std::endl; } void run() { std::cout \u0026lt;\u0026lt; \u0026#34;Dog running\u0026#34; \u0026lt;\u0026lt; std::endl; } }; Dog dog; Animal* ptr = \u0026amp;dog; 虽然 ptr 的静态类型是 Animal*，但它实际指向的是一个 Dog 对象：\n编译时类型：Animal* 运行时真实类型：Dog ptr-\u0026gt;speak()：调用 Dog::speak()，因为它是虚函数。 ptr-\u0026gt;run()：编译失败，因为 Animal 没有声明 run()。 如果我们需要调用Dog::run()，应该怎么办？\n基本用法 基类指针转换为派生类指针 Dog dog; Animal* ptr = \u0026amp;dog; Dog* dogPtr = dynamic_cast\u0026lt;Dog*\u0026gt;(ptr); if (dogPtr != nullptr) { dogPtr-\u0026gt;run(); } //输出 \u0026#34;Dog running\u0026#34; 对于这种需要运行时检查的向下转换，源类型必须是多态类型（至少有一个虚函数）。失败时返回nullptr\ndynamic_pointer_cast std::dynamic_pointer_cast 是 C++11 提供的智能指针类型转换函数，专门用于 std::shared_ptr 在继承体系中的安全类型转换。std::dynamic_pointer_cast会正确处理 shared_ptr的引用计数和所有权。\nclass Cat : public Animal {}; std::shared_ptr\u0026lt;Animal\u0026gt; animal = std::make_shared\u0026lt;Cat\u0026gt;(); std::shared_ptr\u0026lt;Dog\u0026gt; dog = std::dynamic_pointer_cast\u0026lt;Dog\u0026gt;(animal); // 为实际对象是 Cat，并不是 Dog，所以返回一个空的 shared_ptr\u0026lt;Dog\u0026gt; if (!dog) { std::cout \u0026lt;\u0026lt; \u0026#34;转换失败\u0026#34; \u0026lt;\u0026lt; std::endl; } 引用计数怎么变化 std::shared_ptr\u0026lt;Animal\u0026gt; animal = std::make_shared\u0026lt;Dog\u0026gt;(); std::cout \u0026lt;\u0026lt; animal.use_count() \u0026lt;\u0026lt; std::endl; // 1 std::shared_ptr\u0026lt;Dog\u0026gt; dog = std::dynamic_pointer_cast\u0026lt;Dog\u0026gt;(animal); std::cout \u0026lt;\u0026lt; animal.use_count() \u0026lt;\u0026lt; std::endl; // 2 std::cout \u0026lt;\u0026lt; dog.use_count() \u0026lt;\u0026lt; std::endl; // 2 static_cast 静态类型转换，用于编译期可确定的、相对安全的类型转换，包括：\n基本类型之间的转换（如 int ↔ double）。 指针/引用在 有继承关系的类之间 向上或向下转换（但不安全！）。 void*与其他指针类型之间的转换。 显式调用构造函数或转换函数 （如 const char* →std::string，可直接将 char * 、const char * 类型的对象赋值给 string 对象 enum 与整型之间。 void*与其他指针类型之间（但不能用于函数指针）。 注意\nstatic_cast 不进行运行时类型检查，向下转型（基类 → 派生类）可能不安全！ 写文件 std::ofstream （Output File Stream，输出文件流）专门用于向文件中写入数据。它包含在 \u0026lt;fstream\u0026gt; 头文件中。\n常用的标志位：\nstd::ios::out：为输出打开文件（ofstream 默认自带）。 std::ios::app：追加模式（所有写入都追加到文件末尾）。 std::ios::trunc：截断已有文件。ofstream 默认以 out 模式打开时会截断；显式使用 app 或 in | out 等组合时，行为不同。 std::ios::binary：二进制形式打开。 安全的写文件 在嵌入式系统中， jffs2/ubifs/ext2，掉电是常态，配置文件损坏就意味着设备变砖。\n这个流程用于提高整文件替换的崩溃一致性：临时文件应位于目标文件的同一目录，完整写入并检查每次返回值，再 fsync 文件、rename、fsync 目录。rename 的原子可见性不等于掉电持久性；最终保证还取决于文件系统、驱动及存储设备是否正确实现同步语义。\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。 */ 读文件 std::ifstream\nexplicit 访问语义 指针、引用、const 限定和值类别共同影响表达式的使用方式。引用是对象或函数的别名，不能把“常见实现使用地址”当作语言规定的“引用就是指针”。\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*，它是纯右值 螺旋法则 从变量名开始，按顺时针方向“绕一圈”，遇到谁就读谁\nconst int* const p; 找到变量名\np，从这里开始\n先向右看（顺时针）\n如果右边是：\n[] → 数组 () → 函数 没东西 → 往左走 再向左看\n* → pointer const → 只读 类型名 → 基类型 1.左边：const → p 不可改 2.再左：* → pointer 3.再左：const int → 指向内容不可改 指针不可改 + 指向内容不可改 const 成员函数 void f() const; 在 A::f() const 内，this 是类型为 const A* 的纯右值表达式。编译器可能通过隐含参数实现成员调用，但下面只是理解用的近似示意，不是合法的成员函数改写：\n// 概念示意：void f_impl(const A* self); 注意：\nthis 指向 const A 不能通过 this 修改普通非 mutable 成员；mutable 成员仍可修改，指针成员所指对象也不会自动变成 const this-\u0026gt;x = 1; // ❌ 虚函数 virtual 声明的成员函数，允许在派生类中重写（override），通过基类指针/引用实现运行时多态。\n多态 未显式限定类名的虚函数调用，语义上由对象的动态类型选择最终重写函数；直接对对象调用也遵守此规则。编译器能确定类型时可以去虚拟化优化。显式限定 Base::show() 会抑制虚派发。\nDerived d; Base b = d; // 对象切片 b.show(); // b 已切片，动态类型就是 Base，因此调用 Base::show Base* p = \u0026amp;d; p-\u0026gt;show(); // 动态绑定：Derived::show p-\u0026gt;Base::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，因此 b.show() 调用 Base::show。vptr／vtable 是常见实现方式，不是 C++ 标准强制规定的对象布局。\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()。在这里讨论的 C++11 常规 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 派生对象，应提供 public virtual 析构；若禁止这种删除，可采用 protected non-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虚函数表 常见 C++ ABI 使用虚函数表（vtable）和虚表指针（vptr）实现虚派发，但标准不规定必须采用这种布局；一个类也可能涉及多张虚表。\n虚表通常放在运行时只读的数据区域；例如 ELF 动态重定位可能使用 .data.rel.ro。数量和布局由编译器、ABI 及继承结构决定。 采用这种 ABI 时，多态对象通常含一个或多个 vptr；相同动态类型、相同子对象位置通常共享相应的虚表。 构造／析构时编译器会按当前阶段设置相应 vptr；按值复制产生的 Base 对象有自己的状态，不会改变原 Derived 对象的 vptr。 调用虚函数：经 vptr 查表 + 间接跳转，有额外开销；极热路径慎用多态。 vptr 的类型与布局是实现定义的，不要手写 void** 去碰。\n多继承（简述） 多继承且多个基类含虚函数时，对象可能有多张 vtable / 多个 vptr；菱形继承需虚继承避免重复基类子对象。细节见 OOP/继承专题。\n异步编程 参考：odr-use；值类别；this；delete。\n","permalink":"https://note.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\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=\"n\"\u003estd\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"n\"\u003estring\u003c/span\u003e \u003cspan class=\"n\"\u003eencode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003econst\u003c/span\u003e \u003cspan class=\"n\"\u003eXmlFieldValue\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e \u003cspan class=\"n\"\u003efield\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=\"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=\"err\"\u003e返回类型\u003c/span\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=\"err\"\u003e↓\u003c/span\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=\"n\"\u003estd\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"n\"\u003estring\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003econst\u003c/span\u003e \u003cspan class=\"n\"\u003eXmlFieldValue\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026amp;\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\"\u003eusing\u003c/span\u003e \u003cspan class=\"n\"\u003eEncodeFunc\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003estd\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"n\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003econst\u003c/span\u003e \u003cspan class=\"n\"\u003eXmlFieldValue\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e \u003cspan class=\"err\"\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","title":"c++11"},{"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())) 构造临时对象再建线程 不适用 t{background_task()} 构造可调用临时对象再建线程 本例函数返回 void，不能作为 thread 的可调用参数 线程完成 必须遵守的规则 析构时若 joinable() 为 true，会调用 std::terminate()；默认构造、已移动走、已 join 或 detach 的对象不需要再处理。线程函数执行完毕也不会自动让对象变为不可 join。 只能 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_relaxed、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 desired, ...); 成功：原子值原等于 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 } ///////// 等价 /////////// expected = false; // 下面是另一种写法，实际使用时与上面的 strong 示例二选一 while (!flag.compare_exchange_weak(expected, true) \u0026amp;\u0026amp; !expected) { // 伪失败或竞争：expected 会被更新，可重试或退出 } 一次性事件 std::future 一次性事件：结果就绪后 不可重置（与条件变量可反复等待不同）\nfuture 是一个模板类，用来等待异步操作的结果。它本身不能直接存值，可从 promise、packaged_task 或 async 关联的共享状态中获取结果。\n核心方法 方法 作用 get() 阻塞等待，直到结果就绪，然后返回值（只能调用 1 次） wait() 阻塞等待结果就绪，但不返回值 wait_for(timeout) 返回 ready、timeout 或 deferred；调度延迟可能使实际等待超过指定时长 wait_until(time) 等待到指定时间点 valid() 判断 future 是否还有效 关键特性 get() 只能调用一次，调用后 future 失效 没结果时调用 get() 会阻塞当前线程，直到结果到来 std::async、std::packaged_task 或 promise::set_exception() 保存到共享状态中的异常会由 get() 重新抛出；普通 std::thread 中未捕获的异常仍会调用 std::terminate()。 std::promise 参考：thread 构造；thread 析构；future。\n","permalink":"https://note.plhwiki.cn/cpp/language/concurrency/","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":"concurrency"},{"content":"可变参数模板 声明可变参数模板的时候需要在typename或class 后面带上省略号...\n声明一个参数包，这个参数包中可以包含0到任意个板参数。 在模板定义的右边，可以将参数包展开成一个一个独立的参数。 template\u0026lt;typename... T\u0026gt; void f(T... args) { std::cout \u0026lt;\u0026lt; sizeof...(args) \u0026lt;\u0026lt; std::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); } } }; 类型模板参数：Creator 类型模板参数：HttpSessionType ，且有默认值，如果外面不传参数，就默认用mediakit::HttpSession 非类型模板参数：mediakit::WebSocketHeader::Type DataType class WebSocketSessionBase : public HttpSessionType 的语法含义是“继承一个由模板参数决定的基类”。 这叫 依赖基类，因为基类类型依赖于模板参数，只有将来实例化时才知道到底继承谁。\n依赖基类成员通常需要通过 this-\u0026gt;成员 或基类限定名访问。但本例已重写同名 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://note.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\"\u003estd\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"n\"\u003ecout\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\"\u003estd\u003c/span\u003e\u003cspan class=\"o\"\u003e::\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":"命令行 SQLite CLI 的点命令以 . 开始，单独占一行，不需要分号；SQL 语句通常以 ; 结束。点命令也不能追加 SQL 风格的行尾注释。\nSQLite 中「新建数据库」和「打开数据库」是同一个命令（文件不存在则创建，存在则打开）：\n# 方式1：创建/打开名为 test.db 的数据库（文件会生成在当前目录）\rsqlite3 test.db\r# 方式2：打开后查看当前数据库文件路径（验证）\rsqlite\u0026gt; .databases 核心点命令 命令 作用 .databases 查看当前连接的数据库文件 .tables 查看当前数据库中有哪些表 .schema 查看表的创建语句 .headers 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 数据类型 [约束], ... ); -- 创建用户表（IF NOT EXISTS 避免重复创建报错） CREATE TABLE IF NOT EXISTS user ( id INTEGER PRIMARY KEY AUTOINCREMENT, -- 主键，自增 name TEXT NOT NULL, -- 姓名，非空 age INTEGER DEFAULT 0, -- 年龄，默认0 phone TEXT UNIQUE, -- 手机号，唯一 create_time DATETIME DEFAULT CURRENT_TIMESTAMP -- 创建时间，默认当前时间 ); 数据类型\nINTEGER：整数（int）； TEXT：字符串（varchar）； REAL：浮点数（double）； SQLite 没有独立的 DATETIME 存储类别；日期时间可用 TEXT、REAL 或 INTEGER 保存。示例中的 DATETIME 是列声明类型，CURRENT_TIMESTAMP 默认产生 UTC 日期时间文本； BLOB：二进制数据（如图片）。 常用约束\nPRIMARY KEY：主键（唯一标识行）； AUTOINCREMENT：仅用于 INTEGER PRIMARY KEY，自动生成编号时避免复用历史上已提交过的 ROWID；一般自动生成编号只需 INTEGER PRIMARY KEY，编号不保证连续； NOT NULL：非空； UNIQUE：唯一； DEFAULT：默认值 修改表 -- 给user表添加 email 列 ALTER TABLE user ADD COLUMN email TEXT; -- 重命名表 ALTER TABLE user RENAME TO user_info; 删除表 -- 删除user表（谨慎！数据会全部丢失） DROP TABLE IF EXISTS user; 数据操作 以下示例以“创建表”中的初始 user 表为前提，各节示例独立演示；如果已经重命名、删除或添加列，应相应调整表名和列清单。\n插入数据 -- 方式1：指定列插入 INSERT INTO user (name, age, phone) VALUES (\u0026#39;张三\u0026#39;, 25, \u0026#39;13800138000\u0026#39;); -- 方式2：插入所有列（按创建表时的五列顺序；用 NULL 占据 id 的位置以自动生成编号） INSERT INTO user VALUES (NULL, \u0026#39;李四\u0026#39;, 30, \u0026#39;13900139000\u0026#39;, CURRENT_TIMESTAMP); -- 批量插入 INSERT INTO user (name, age, phone) VALUES (\u0026#39;王五\u0026#39;, 28, \u0026#39;13700137000\u0026#39;), (\u0026#39;赵六\u0026#39;, 32, \u0026#39;13600136000\u0026#39;); 查询数据 SELECT 列名1, 列名2 FROM 表名 [WHERE 条件] [ORDER BY 列名] [LIMIT 数量]; 系统表 每一个 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#39;ppp\u0026#39;,20); sqlite\u0026gt; .schema CREATE TABLE admin(username text,age integer); sqlite\u0026gt; .tables 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;) 创建语句 参考：SQLite CLI；SQLite 数据类型；AUTOINCREMENT。\n","permalink":"https://note.plhwiki.cn/database/sqlite3/","summary":"\u003ch1 id=\"命令行\"\u003e命令行\u003c/h1\u003e\n\u003cp\u003eSQLite CLI 的点命令以 \u003ccode\u003e.\u003c/code\u003e 开始，单独占一行，不需要分号；SQL 语句通常以 \u003ccode\u003e;\u003c/code\u003e 结束。点命令也不能追加 SQL 风格的行尾注释。\u003c/p\u003e\n\u003cp\u003eSQLite 中「新建数据库」和「打开数据库」是同一个命令（文件不存在则创建，存在则打开）：\u003c/p\u003e","title":"sqlite3"},{"content":"内核添加模块 在 drivers/usb/serial/option.c\n短信相关 ​\t发送短信的流程就是配置短信模式-\u0026gt;配置字符集-\u0026gt;下发发送命令-\u0026gt;等待，提示符-\u0026gt;发送正文/PDU -\u0026gt;发送ctrl+z \u0026ndash;\u0026gt;等待结果\nTEXT 格式 AT+CMGF=1 //设置短信为文本模式 PDU格式 ​\tPDU模式不仅能发送中文短信，也能发送英文短信。PDU收发短信有三种编码可用：7-bit、8-bit和UCS2编码。GSM 7-bit 默认字母表不是 ASCII；不带用户数据头时最多 160 个 septet，扩展表字符会占两个 septet。8-bit 编码通常用于二进制数据；UCS-2 可表示中文等 BMP 字符，不带用户数据头时最多 70 个码元。长短信的 UDH 会减少每段可用长度。\nAT+CMGF=0 // 设置 PDU 模式；发送命令另用 AT+CMGS=\u0026lt;TPDU 字节数\u0026gt; 发送 PDU（SMS-SUBMIT） 数据格式：\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 为 1 字节相对有效期；01 为 7 字节增强格式；11 为 7 字节绝对有效期（半字节编码）。\nSRR 1bit：是否请求状态报告。\nMR 是发送方设置的 8 位消息参考号（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 占用 ceil(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 输出缓存 进入相应通知模式后向 TE 输出缓存的通知；1 才表示清除缓存而不输出。 */ AT+CNMI=2,1,0,0,0 打电话 测试 通过microcom 与/dev/ttyUSB 进行通信\n-t 超过多少ms超时 -s 设置波特率 常用命令 AT+CGMM 请求 MT 型号 ID\n上网 查询信号强度： //+CSQ: \u0026lt;rssi\u0026gt;,\u0026lt;ber\u0026gt; AT+CSQ +CSQ: 24,99 //rssi越接近31信号越强 OK rssi：\n//查询命令 响应+CREG: \u0026lt;n\u0026gt;,\u0026lt;stat\u0026gt;[,\u0026lt;lac\u0026gt;,\u0026lt;ci\u0026gt;[,\u0026lt;AcT\u0026gt;]] AT+CREG? EG800G EG800G/ EC800G\n下表仅作为特定固件的端口观察记录。ttyUSB 编号会受枚举顺序、接口组合及其他 USB 串口设备影响，不应跨型号、固件版本写死；需对照对应型号手册和 USB 接口信息确认。\n设备节点 用途 能否发 AT /dev/ttyUSB0 AT 命令口 ✅ 最推荐 /dev/ttyUSB1 DIAG/AP log ❌ /dev/ttyUSB2 调试串口，目前无实际作用 ❌ /dev/ttyUSB3 CP log ❌ /dev/ttyUSB4 Modem / AT 命令 ✅ 也可以 /dev/ttyUSB5 GNSS / AT 命令，部分 GA 型号 ✅ 条件支持 参考：3GPP TS 27.005；3GPP TS 23.040。\n","permalink":"https://note.plhwiki.cn/hardware/4g/","summary":"\u003ch1 id=\"内核添加模块\"\u003e内核添加模块\u003c/h1\u003e\n\u003cp\u003e在 \u003ccode\u003edrivers/usb/serial/option.c\u003c/code\u003e\u003c/p\u003e\n\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":"概述与定义 SPI Flash 接口协议（芯片与主机的通信方式）\nNOR Flash NOR Flash是一种非易失性存储器，采用\u0026quot;或非门\u0026quot;(NOR)架构。它的特点是支持随机访问，可以直接在芯片上执行代码(XIP)，常用于存储启动代码、固件等对可靠性要求较高的应用。\nNAND Flash NAND Flash同样是非易失性存储器，采用\u0026quot;与非门\u0026quot;(NAND)架构。它以页为单位进行读写操作，以块为单位进行擦除，具有高密度、低成本的特点，主要用于大容量数据存储。\n架构与原理对比 NOR Flash\nNOR 和 NAND 最核心的区别 对比项 NOR Flash NAND Flash 典型产品 W25Q256JV、GD25Q256 eMMC 内部 NAND、SPI NAND、Raw NAND 读取方式 类似内存，可随机读取 通常按 Page 读取 写入方式 Page Program Page Program 擦除方式 Sector / Block Block 随机读性能 好 相对差 顺序读写 一般 好 XIP 直接运行代码 支持，常见 通常不直接支持 坏块 出厂通常不允许坏块/使用上简单 允许存在坏块 ECC 通常控制器负担较小 通常必须有 ECC 软件管理 简单 复杂 容量 小到中等 很大 单位容量价格 贵 便宜 常见用途 Bootloader、Kernel、固件、配置 大容量数据、录像、文件系统 硬盘 硬盘的层次关系是：\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分区表 MBR MBR 是传统的分区表格式，如果存在MBR的话，那么它占用第一个扇区的512个字节，前446个字节为MBR启动代码(这里不关心),接下来16*4个字节为磁盘分区表，每个分区的信息要占16个字节，所以，MBR 主分区表只有 4 个条目；其中一个可作为扩展分区，再通过 EBR 链描述多个逻辑分区，最后两个字节是结束标识0x55AA\n特点 使用 512 字节逻辑扇区时，32 位 LBA 带来的常见容量边界约为 2 TiB；不能脱离逻辑扇区大小把它当作固定上限\n只能保存4个主分区\n文件系统 FAT32 错误 硬盘坏道 ata1.00: exception Emask 0x0 SAct 0x800 SErr 0x0 action 0x0 ata1.00: failed command: READ FPDMA QUEUED ata1.00: cmd 60/01:58:fb:02:71/00:00:2f:00:00/40 tag 11 ncq dma 512 in res 41/40:01:fb:02:71/00:00:2f:00:00/00 Emask 0x409 (media error) ata1.00: status: { DRDY ERR } ata1.00: error: { UNC } sd 0:0:0:0: [sda] tag#11 Sense Key : 0x3 [current] sd 0:0:0:0: [sda] tag#11 ASC=0x11 ASCQ=0x4 ← UNRECOVERED READ ERROR, AUTO REALLOCATE FAILED blk_update_request: I/O error, dev sda, sector 795935483 ata1: EH complete ← 本段日志未显示 hard reset，不能据此证明链路正常 当前错误主要指向不可纠正的介质读取错误；不能据此排除线缆、供电或背板问题。 UNC 和上述 sense 信息支持“本次读取无法纠正”的判断；SErr 0x0 只表示该次记录未报告相应 SATA 链路错误。需结合完整日志、SMART 与交叉替换测试定位，不能凭这一段日志证明链路绝对健康。\nU盘 可以通过 cat /sys/block/sdb/removable 初步判断，removable表示内核认为这个该块设备的存储介质是否可移除。\n1 ：存储介质被标记为可移除 0：存储介质被标记为不可移除 设备类型 removable 是否 USB 普通 U 盘 通常为 1，也可能为 0 是 USB 移动硬盘 通常为 0 是 USB 读卡器 通常为 1 是 SATA 硬盘 通常为 0 否 SD 卡设备 通常为 1 不一定 参考：libATA 错误处理。\n","permalink":"https://note.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":"I2C ​\tI2C（Inter-Integrated Circuit）协议是由Philips公司于1980年代提出的一种 同步、半双工、多主从结构 的串行通信协议，广泛应用于嵌入式系统中的芯片间通信。其核心特点在于仅使用 两条信号线 ： SDA（数据线） 与 SCL（时钟线） ，即可实现多个设备之间的可靠数据传输。\n传输数据 总线时序 i2c 通信以起始信号（START）开始，以终止信号（STOP）结束，起始信号和终止信号均为主机发出\n起始信号：\n当 SCL 为高电平时，SDA 从高电平变为低电平时为起始信号 终止信号：\n当 SCL 为高电平时，SDA 从低电平变成高电平时为终止信号 写操作 ​\t起始信号后的首字节前 7 位为从设备地址，最后 1 位是读写方向位， 0 表示写操作，1 表示读操作。7 位字段有 128 个编码，其中 0x00～0x07 和 0x78～0x7F 为保留地址，通常可用的普通设备地址为 0x08～0x77，共 112 个；实际设备数量还受电气负载等限制。主机发送首字节（7 位地址+1 位读写位）后，若总线上存在地址匹配的从机，该从机将在第 9 个时钟周期拉低 SDA 线，即返回应答信号（ACK）表示成功匹配地址；若地址无匹配，第 9 个时钟周期 SDA 线保持高电平，即产生非应答信号（NACK）\n主机向从机(0x55)写地址，主机首字节需发送 0xAA（0x55 左移 1 位+最低位 0 表示写操作）\n驱动 启用 i2c-dev 后，用户程序可通过适配器设备节点 /dev/i2c-X 访问 I2C 总线；还需选择目标从设备地址。\n参考：NXP I2C 规范。\n","permalink":"https://note.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":"USB USB总线系统中的设备 USB 是以主机为中心的分层星形拓扑（tiered star），从上到下依次是：主机（含根集线器）→ 集线器 → 外围设备。\nUSB主机 每条 USB 总线（一棵拓扑树）有且只有一个主机。一个 SoC 可以有多个主机控制器。A1 的三个 DWC2 控制器各注册一条总线；但不能推广为所有控制器都一对一，例如 xHCI 通常分别注册 USB 2 和 USB 3 两条逻辑总线。 USB 2.0 的常规事务由主机调度，设备不能自行发起普通总线事务；远程唤醒等信号是另一回事。USB 3.x 还支持 ERDY 等设备通知，不能把“所有版本始终只能等待轮询”作为通用结论。 主机按固定周期划分时间：每 1 ms 发一个 SOF（Start of Frame）包标识一帧；High Speed 下每帧再细分为 8 个 125 µs 的微帧（microframe）。 负责枚举设备（分配地址、读取描述符、选择配置）、加载驱动，以及对总线上的错误进行管理和恢复。 USB Root Hub（根集线器） 集成在主机控制器内部，不是独立芯片，由 HCD（Host Controller Driver）在注册总线时虚拟出来。 Linux 中设备名为 usbN（N 为总线号），接口为 N-0:1.0，绑定 hub 驱动。 VID:PID 固定为 Linux Foundation 的虚拟 ID：1d6b:0001（USB 1.1）、1d6b:0002（USB 2.0）、1d6b:0003（USB 3.x）。 端口数等于控制器的物理端口数，见 sysfs 的 maxchild。君正A1 每个控制器只有 1 个端口。 product、manufacturer 由内核填写，A1 上分别是 DWC OTG Controller、Linux 4.4.94-20220517 dwc2_hsotg。 对应的启动日志：\ndwc2 13680000.otg2: new USB bus registered, assigned bus number 3 hub 3-0:1.0: USB hub found hub 3-0:1.0: 1 port detected USB集线器（USB HUB） 作用类似于网络集线器，用于扩展端口。 检测下游端口的插拔，通过自身的中断端点上报主机；负责端口的复位、使能、供电和挂起。设备的\u0026quot;添加\u0026quot;（枚举、分配地址）由主机完成，Hub 只负责发现和上报。 USB 2.0 Hub 下行方向是广播转发，确实像网络集线器；USB 3.x SuperSpeed 按路由定向转发，更像交换机。 High Speed Hub 内有 TT（Transaction Translator），把 HS 事务转换成 FS/LS 事务，低速设备（键鼠）才能挂在 HS Hub 下。Hub 设备描述符的 bDeviceProtocol：0 = FS Hub，1 = HS 单 TT，2 = HS 多 TT。 USB总线设备 即外围设备（Function），如U盘、键鼠、4G模块、摄像头。 一个物理设备可以包含多个功能（Interface），称为复合设备（Composite Device）。例如 4G 模块同时提供 AT 串口、诊断口和网卡，一个功能可能由一个或多个 Interface 组成，例如 CDC 功能可同时使用控制接口与数据接口；不能把 Interface 与功能严格一一对应。 速度等级与主机控制器 速度 速率 引入版本 sysfs speed 典型设备 Low Speed (LS) 1.5 Mbps USB 1.x 1.5 键盘、鼠标 Full Speed (FS) 12 Mbps USB 1.x 12 早期U盘、USB 音频 High Speed (HS) 480 Mbps USB 2.0 480 U盘、4G模块、摄像头 SuperSpeed (SS) 5 Gbps USB 3.0 5000 USB3 移动硬盘 主机控制器 支持的速度 Linux 驱动 OHCI / UHCI USB 1.1（LS/FS） ohci-hcd / uhci-hcd EHCI USB 2.0 HS；LS/FS 设备通常交给配套的 OHCI/UHCI（companion）处理 ehci-hcd xHCI USB 3.x，一个控制器支持所有速度 xhci-hcd DWC2 Synopsys DesignWare HS OTG，自身支持 LS/FS/HS，嵌入式 SoC 常见 dwc2 君正A1 有 3 个 DWC2 控制器（13600000.otg0、13640000.otg1、13680000.otg2），所以有 3 条总线，都是 USB 2.0。\nUSB描述符 描述符层次 设备通过描述符告诉主机\u0026quot;我是谁、有什么功能、怎么通信\u0026quot;：\nDevice（设备描述符） 一个设备一个：USB 版本、VID/PID、EP0 最大包长 └── Configuration（配置描述符） 通常只有 1 个：供电方式、最大电流 ├── Interface 0（接口描述符） 接口可独立或与其他接口组成一个功能，驱动可关联多个接口 │ ├── Endpoint 0x81（IN） │ └── Endpoint 0x02（OUT） └── Interface 1 ... 复合设备有多个接口 sysfs 命名 \u0026lt;bus\u0026gt;-\u0026lt;port_path\u0026gt;:\u0026lt;config\u0026gt;.\u0026lt;interface\u0026gt; 中的 config、interface 就是这里的 bConfigurationValue 和 bInterfaceNumber。 端点 0（EP0）每个设备都有，是双向的控制端点，用于枚举和标准请求，不出现在配置描述符里。 端点地址 bit7 表示方向：1 = IN（设备→主机），0 = OUT（主机→设备）；低 4 位是端点号。0x81 = EP1 IN，0x02 = EP2 OUT。 Device 绑定通用驱动 usb，Interface 绑定具体的类驱动（如 usb-storage、hub）。 实例：U盘的描述符 在君正A1 上读取 Kingston U盘的原始描述符（sysfs 的 descriptors 文件 = 设备描述符 + 所有配置描述符）：\nhexdump -C /sys/bus/usb/devices/1-1.1/descriptors 00000000 12 01 00 02 00 00 00 40 51 09 65 16 00 00 01 02 |.......@Q.e.....| 00000010 03 01 09 02 20 00 01 01 00 80 32 09 04 00 00 02 |.... .....2.....| 00000020 08 06 50 00 07 05 81 02 00 02 00 07 05 02 02 00 |..P.............| 00000030 02 00 |..| 按描述符拆开：\n12 01 00 02 00 00 00 40 51 09 65 16 00 00 01 02 03 01 # 设备描述符（18 字节） 09 02 20 00 01 01 00 80 32 # 配置描述符（9 字节） 09 04 00 00 02 08 06 50 00 # 接口描述符（9 字节） 07 05 81 02 00 02 00 # 端点描述符（7 字节） 07 05 02 02 00 02 00 # 端点描述符（7 字节） 描述符 字段 值 含义 设备 bcdUSB 0x0200 USB 2.0 设备 bDeviceClass 0x00 类别由接口描述符定义 设备 bMaxPacketSize0 0x40 EP0 最大包长 64 字节 设备 idVendor / idProduct 0x0951 / 0x1665 Kingston / DataTraveler 2.0（小端存储） 设备 bNumConfigurations 1 1 个配置 配置 wTotalLength 0x0020 配置 + 接口 + 端点共 32 字节 配置 bNumInterfaces 1 1 个接口 配置 bConfigurationValue 1 配置编号 1，即 1-1.1:1.0 中的 1 配置 bmAttributes 0x80 总线供电，不支持远程唤醒 配置 bMaxPower 0x32 50 × 2 mA = 100 mA 接口 bInterfaceNumber 0 接口 0，即 1-1.1:1.0 中的 0 接口 bNumEndpoints 2 2 个端点（不含 EP0） 接口 bInterfaceClass / SubClass / Protocol 0x08 / 0x06 / 0x50 Mass Storage / SCSI 透明命令集 / Bulk-Only Transport 端点 bEndpointAddress / bmAttributes / wMaxPacketSize 0x81 / 0x02 / 0x0200 EP1 IN，Bulk，512 字节 端点 bEndpointAddress / bmAttributes / wMaxPacketSize 0x02 / 0x02 / 0x0200 EP2 OUT，Bulk，512 字节 常见设备类别 Class 含义 例子 Linux 驱动 0x00 由接口描述符定义 大多数设备 — 0x02 / 0x0a CDC 通信 / CDC 数据 4G 模块网卡口、USB 串口 cdc_ether、cdc_ncm、cdc_acm 0x03 HID 键盘、鼠标 usbhid 0x08 Mass Storage U盘、读卡器、移动硬盘 usb-storage、uas 0x09 Hub Hub、Root Hub hub 0x0e Video UVC 摄像头 uvcvideo 0xe0 无线控制器 RNDIS 网卡 rndis_host 0xff 厂商自定义 4G 模块的 AT 口/诊断口 option 等 usbserial 驱动 枚举流程 设备插入后，主机通过枚举识别设备并加载驱动：\n检测插入：Hub 检测到端口连接状态变化，通过中断端点通知主机（Linux 中由 hub 驱动的 hub_event() 处理）。 复位端口：主机复位该端口，复位过程中确定设备速度（HS 设备通过 chirp 握手升到 480 Mbps）。 读设备描述符：设备此时使用默认地址 0。主机先读设备描述符，主要为了拿到 bMaxPacketSize0。Linux 这一步按 64 字节请求，日志里的 read/64 就是它。 分配地址：SET_ADDRESS 分配唯一地址（1~127），即 sysfs 的 devnum。每次插拔通常会递增，与端口无关。 读全部描述符：用新地址读完整的设备描述符（18 字节）、配置描述符（含接口、端点）和字符串描述符。 选择配置：SET_CONFIGURATION 选中配置（通常是 1），接口开始生效。 匹配驱动：内核为每个 Interface 生成 uevent，按驱动的 id_table（VID/PID 或 Class/SubClass/Protocol）匹配并调用 probe；mdev/udev 创建 /dev 节点。 驱动匹配用到的 MODALIAS 可以在 uevent 里看到：\ncat /sys/bus/usb/devices/1-1.1:1.0/uevent DEVTYPE=usb_interface DRIVER=usb-storage PRODUCT=951/1665/0 TYPE=0/0/0 INTERFACE=8/6/80 MODALIAS=usb:v0951p1665d0000dc00dsc00dp00ic08isc06ip50in00 君正A1 上插入U盘的实际日志：\n[19845.669991] usb 1-1.1: new high-speed USB device number 7 using dwc2 # 复位完成，HS 设备，将分配地址 7 [19845.793363] usb-storage 1-1.1:1.0: USB Mass Storage device detected # 接口匹配到 usb-storage [19845.810874] scsi host5: usb-storage 1-1.1:1.0 # usb-storage 注册 SCSI host [19848.937381] scsi 5:0:0:0: Direct-Access 1.00 PQ: 0 ANSI: 4 # SCSI 扫描到 LUN [19849.000416] sda: sda1 # 解析分区表 [19849.010995] sd 5:0:0:0: [sda] Attached SCSI removable disk # /dev/sda 可用 拔出时：\n[19843.623371] usb 1-1.2: USB disconnect, device number 6 传输类型 类型 特点 带宽 / 时延 出错重传 典型用途 君正A1 实例 Control 每个设备必有（EP0），Setup→可选 Data→Status 保留一部分带宽 是 枚举、标准请求、设备配置 所有设备的 ep_00 Bulk 大块数据，只用空闲带宽 不保证时延 是 U盘、打印机、4G 网卡数据 U盘 ep_81(IN) / ep_02(OUT)，512 字节 Interrupt 少量数据，主机按 bInterval 周期轮询 保证最大时延 是 键鼠、Hub 端口状态变化 Hub ep_81，interval 256ms Isochronous 实时数据流 预留带宽 否 UVC 摄像头、音频 — Interrupt 传输不是真正的中断，仍然是主机周期性轮询。 周期性传输（Interrupt + Isochronous）最多占用 FS 帧的 90%、HS 微帧的 80%，剩余的留给 Control；Bulk 只能用剩下的空闲带宽，总线忙时会变慢。 LS 设备只支持 Control 和 Interrupt。 HS 下最大包长：Control 64、Bulk 512、Interrupt / Isochronous 最大 1024 字节。 查看端点：\ncd /sys/bus/usb/devices for e in 1-1.1/ep_00 1-1.1:1.0/ep_* 1-1:1.0/ep_*; do echo \u0026#34;$e addr=$(cat $e/bEndpointAddress) type=$(cat $e/type) dir=$(cat $e/direction) maxp=$(cat $e/wMaxPacketSize) interval=$(cat $e/interval)\u0026#34; done # 输出 1-1.1/ep_00 addr=00 type=Control dir=both maxp=0040 interval=0ms 1-1.1:1.0/ep_02 addr=02 type=Bulk dir=out maxp=0200 interval=0ms 1-1.1:1.0/ep_81 addr=81 type=Bulk dir=in maxp=0200 interval=0ms 1-1:1.0/ep_81 addr=81 type=Interrupt dir=in maxp=0001 interval=256ms Linux USB 驱动栈 应用层 mount /dev/sda1 /dev/ttyUSB* ethX / usbX /dev/input/eventX │ │ │ │ 子系统 文件系统 → sd → SCSI tty 网络协议栈 input │ │ │ │ USB 类驱动 usb-storage option/usbserial cdc_ether/rndis_host/asix usbhid └───────────────────┴─────────┬──────────┴────────────────────┘ │ URB（USB Request Block） USB Core usbcore：设备模型、驱动匹配、hub 驱动（端口管理与枚举） │ HCD dwc2（otg0 / otg1 / otg2） │ 硬件 DWC2 控制器 → Root Hub → Hub → 设备 类驱动通过 URB 向 usbcore 提交传输请求，usbcore 交给 HCD 调度到总线上。 U盘的完整路径：dwc2 → usbcore → usb-storage → SCSI 中间层 → sd → /dev/sda → 文件系统。 君正A1 上已注册的 USB 驱动（ls /sys/bus/usb/drivers）： 驱动 用途 usb、hub 通用设备驱动、Hub 驱动 usbfs 用户态访问 /dev/bus/usb/*（libusb） usb-storage U盘、移动硬盘 usbhid 键盘、鼠标 usbserial、option 4G 模块的 AT 口/诊断口 → /dev/ttyUSB* cdc_ether、cdc_ncm、cdc_subset、rndis_host、zaurus、net1080 4G 模块网卡口、USB 网络设备 asix、ax88179_178a USB 有线网卡芯片 当时板上没有 /lib/modules，但仅凭目录缺失不能证明所有驱动都内置，也可能从其他位置加载模块。当时驱动列表中没有 uvcvideo、qmi_wwan、uas，只能说明这些驱动尚未注册；是否可用还需检查内核配置和可加载模块。没有 uas 也不代表 UAS 移动硬盘完全不能用：设备若支持 Bulk-Only Transport，可能通过 usb-storage 工作。\nLinux sysfs 中的 USB 设备 命名格式 \u0026lt;bus\u0026gt;-\u0026lt;port_path\u0026gt;:\u0026lt;config\u0026gt;.\u0026lt;interface\u0026gt;\n# 例如 1-1:1.0 │ │ │ │ │ │ │ └── Interface 0 │ │ └──── Configuration 1 │ └────── 根 Hub 的 Port 1 └──────── USB Bus 1 1-0:1.0 │ │ │ │ │ │ │ └── Interface 0 │ │ └──── Configuration 1 │ └────── Root Hub 的特殊标识 0 └──────── USB Bus 1 # 这里的 0 并不是物理 USB Port 0，而是 Linux 用于表示 Root Hub 的特殊命名。 1-1.1 │ │ │ │ │ └── 下一级 Hub 的 Port 1 │ └──── 根 Hub 的 Port 1 └────── USB Bus 1 # 其中的 . 表示又经过了一级 Hub。 1-1 表示整个USB设备，1-1:1.0 表示USB设备的一个功能接口。 端口路径对应物理端口，同一个口插什么设备名字都一样；换个口名字就变。程序里不要写死 1-1.2 这类路径。 devnum（设备地址）每次插拔通常递增，与端口无关。 常用属性与工具 文件 位置 含义 君正A1 U盘（1-1.1） idVendor / idProduct 设备 VID / PID 0951 / 1665 manufacturer / product / serial 设备 字符串描述符 Kingston / DataTraveler 2.0 speed 设备 实际协商速率（Mbps） 480 version 设备 bcdUSB 2.00 busnum / devnum 设备 总线号 / 设备地址 1 / 7 devpath 设备 端口路径 1.1 maxchild 设备 下游端口数，非 Hub 为 0 0 bMaxPower 设备 当前配置的最大电流 100mA descriptors 设备 原始描述符（二进制） 见\u0026quot;USB描述符\u0026quot; bInterfaceClass / SubClass / Protocol 接口 接口类别 08 / 06 / 50 bNumEndpoints 接口 端点数（不含 EP0） 02 ep_XX/ 设备/接口 端点属性：type、direction、wMaxPacketSize、interval — driver 设备/接口 绑定的驱动（符号链接） usb / usb-storage uevent 设备/接口 热插拔环境变量，含 MODALIAS — 列出所有设备和接口：\ncd /sys/bus/usb/devices # 设备 for d in usb* [0-9]-[0-9]*; do case $d in *:*) continue ;; esac # 跳过接口 echo \u0026#34;$d $(cat $d/idVendor):$(cat $d/idProduct) speed=$(cat $d/speed) $(cat $d/product 2\u0026gt;/dev/null)\u0026#34; done # 接口 for i in *:*; do echo \u0026#34;$i class=$(cat $i/bInterfaceClass) driver=$(basename $(readlink $i/driver))\u0026#34; done 其他工具：\nlsusb：板上是 busybox 版本，只能列出 Bus/Device/ID，不支持 -t 树形显示和厂商名称，详细信息要靠 sysfs。 dmesg | grep -iE \u0026quot;usb|hub\u0026quot;：查看枚举和插拔日志。 /sys/kernel/debug/usb/devices：需要内核开启 debugfs 并挂载，A1 默认未挂载。 判断U盘 readlink -f /sys/block/sda/device/ # 得到 /sys/devices/platform/ahb2/13600000.otg0/usb1/1-1/1-1.1/1-1.1:1.0/host5/target5:0:0/5:0:0:0 路径逐段含义：\n/sys/devices/platform/ahb2/13600000.otg0 ← DWC2 OTG 控制器（工作在 host 模式） /usb1 ← 总线 1 的 Root Hub /1-1 ← Root Hub 端口 1 上的 Hub /1-1.1 ← 该 Hub 端口 1 上的设备 = U盘 /1-1.1:1.0 ← U盘的接口 0（Mass Storage，绑定 usb-storage） /host5/target5:0:0/5:0:0:0 ← usb-storage 注册的 SCSI host / target / LUN 判断依据：路径里有 /usbN/ 说明 sda 挂在 USB 上；SATA 硬盘的路径里是 ataN。 case \u0026#34;$(readlink -f /sys/block/sda/device)\u0026#34; in */usb[0-9]*/*) echo \u0026#34;sda 是 USB 存储设备\u0026#34; ;; */ata[0-9]*/*) echo \u0026#34;sda 是 SATA 设备\u0026#34; ;; esac 局限：只能判断出\u0026quot;USB 存储设备\u0026quot;。U盘、USB 移动硬盘、读卡器都是 Mass Storage，路径区分不出来。/sys/block/sda/removable（A1 上U盘为 1）也不可靠，很多移动硬盘报 0，部分U盘也报 0。需要区分时只能结合容量、VID/PID 判断。 SCSI host 编号和 USB devnum 都属于运行时分配标识，可能被复用；不要依赖它们永久递增或用作设备的稳定身份。 君正A1的USB拓扑 2026-10-13 实测：\n[root@Ingenic-uc1_1:devices]# ls /sys/bus/usb/devices 1-0:1.0 1-1.1 1-1:1.0 3-0:1.0 usb2 1-1 1-1.1:1.0 2-0:1.0 usb1 usb3 13600000.otg0 → usb1（Root Hub，1 口） └── 1-1 USB HUB 1a86:8091（4 口，HS 多 TT） ├── 1-1.1 Kingston U盘 0951:1665（HS） ├── 1-1.2 空 ├── 1-1.3 空 └── 1-1.4 空 13640000.otg1 → usb2（Root Hub，1 口，未接设备） 13680000.otg2 → usb3（Root Hub，1 口，未接设备） 名称 类型 含义 usb1 Root Hub 控制器 otg0（13600000）的根 Hub，1 个端口 usb2 Root Hub 控制器 otg1（13640000）的根 Hub，1 个端口 usb3 Root Hub 控制器 otg2（13680000）的根 Hub，1 个端口 1-0:1.0 / 2-0:1.0 / 3-0:1.0 Interface 各根 Hub 的接口，绑定 hub 驱动 1-1 Device（Hub） usb1 端口 1 上的 4 口 Hub，1a86:8091（南京沁恒 WCH），开机即存在 1-1:1.0 Interface 该 Hub 的接口，绑定 hub 驱动 1-1.1 Device Hub 端口 1 上的 Kingston U盘 1-1.1:1.0 Interface U盘的 Mass Storage 接口，绑定 usb-storage 驱动 注意，Root Hub 比较特殊：它的 USB Device 目录叫 usb1，而不是 1-0，但其 Interface 仍使用 1-0:1.0。\n开机时 1-1.1 上插的是一个低速 USB 鼠标，1-1.2 上是U盘。后来拔掉鼠标、把U盘换到端口 1，U盘的名字就从 1-1.2 变成了 1-1.1，devnum 也从 4 涨到了 7。低速鼠标能挂在 HS Hub 下，是靠 Hub 内部的 TT：\n[ 3.020085] usb 1-1.1: new low-speed USB device number 3 using dwc2 [ 3.120089] usb 1-1.1: device descriptor read/64, error -32 [ 3.357370] hid-generic 0003:13EE:0001.0001: input,hidraw0: USB HID v1.00 Mouse [MOON AND ] on usb-13600000.otg0-1.1/input0 热插拔检测 相关设备对象增删时，内核可产生 uevent，通知路径包括：\n通过 NETLINK_KOBJECT_UEVENT 广播给用户态； 启用相应 helper 配置且路径非空时，调用 /proc/sys/kernel/hotplug 指定的程序；现代系统不一定启用此机制。A1 上是 /sbin/mdev，且没有 /etc/mdev.conf，只按默认规则在 /dev 下创建设备节点。 插一次U盘会依次产生 usb、scsi、block 等多个子系统的 uevent。应用一般只关心 SUBSYSTEM=block 的 add/remove，收到后再 mount/umount。\n应用层监听 uevent 示例：\n#include \u0026lt;stdio.h\u0026gt; #include \u0026lt;string.h\u0026gt; #include \u0026lt;unistd.h\u0026gt; #include \u0026lt;sys/socket.h\u0026gt; #include \u0026lt;linux/netlink.h\u0026gt; int main(void) { struct sockaddr_nl addr; memset(\u0026amp;addr, 0, sizeof(addr)); addr.nl_family = AF_NETLINK; addr.nl_pid = 0; // 0 表示由内核分配 addr.nl_groups = 1; // 订阅内核 uevent 组播组 int fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); if (fd \u0026lt; 0 || bind(fd, (struct sockaddr *)\u0026amp;addr, sizeof(addr)) \u0026lt; 0) { perror(\u0026#34;netlink\u0026#34;); return 1; } char buf[4096]; for (;;) { ssize_t len = recv(fd, buf, sizeof(buf) - 1, 0); if (len \u0026lt;= 0) continue; buf[len] = \u0026#39;\\0\u0026#39;; // 消息由多个 \u0026#39;\\0\u0026#39; 分隔的字符串组成，例如： // \u0026#34;add@/devices/.../block/sda/sda1\u0026#34; \u0026#34;ACTION=add\u0026#34; \u0026#34;SUBSYSTEM=block\u0026#34; \u0026#34;DEVNAME=sda1\u0026#34; \u0026#34;DEVTYPE=partition\u0026#34; ... for (char *p = buf; p \u0026lt; buf + len; p += strlen(p) + 1) printf(\u0026#34;%s\\n\u0026#34;, p); printf(\u0026#34;\\n\u0026#34;); } } 拔U盘前要先 umount。A1 上出现过拔出后再插入时的告警：\nFAT-fs (sda1): Volume was not properly unmounted. Some data may be corrupt. Please run fsck. 常见故障与日志 错误码 USB 日志里的错误码是负的 errno。MIPS 的 errno 数值和 x86 不同，查日志时要按平台对照：\n错误码 MIPS（A1） x86 含义 EPIPE -32 -32 端点 STALL，设备拒绝了该请求 ETIME -62 -62 总线上等不到设备应答（部分 HCD） EPROTO -71 -71 协议错误：位填充错误、应答超时等，多为信号质量或供电问题 EOVERFLOW -79 -75 babble，设备发送的数据超过包长 EILSEQ -88 -84 CRC 错误（部分 HCD） ETIMEDOUT -145 -110 同步传输（如控制传输）超时 ENODEV -19 -19 设备已拔出 ESHUTDOWN -143 -108 设备或主机控制器已关闭 常见日志 日志 含义 常见原因 / 处理 device descriptor read/64, error -71（或 -32、-145） 地址 0 阶段读描述符失败 偶发一次后能枚举成功可忽略（A1 开机时的低速鼠标就是 -32 后成功）；反复出现查供电、线材、Hub device not accepting address N, error -71 SET_ADDRESS 失败 多为信号质量、供电问题 unable to enumerate USB device 多次重试后放弃枚举 设备损坏、供电不足、兼容性问题 USB disconnect 后紧跟 new ... USB device，反复出现 设备掉线重连 供电不足（移动硬盘最常见）、接触不良、静电 reset high-speed USB device number N using dwc2 设备无响应，被主机复位 usb-storage 命令超时，常见于劣质U盘、供电不足 over-current condition 端口过流 短路、负载电流超过限流 FAT-fs (sda1): Volume was not properly unmounted 文件系统脏标志未清除 拔U盘前未 umount、写入过程中掉电 排查步骤 dmesg | grep -iE \u0026quot;usb|hub\u0026quot;：看有没有枚举日志、报什么错。 sysfs 里有没有生成设备；speed 是否符合预期（HS 设备只跑到 12，说明降速了，查线材和 Hub）。 接口目录下有没有 driver 链接。没有说明没匹配到驱动：检查 bInterfaceClass 和 VID/PID 是否在驱动的 id_table 里，或内核根本没编译该驱动（见\u0026quot;Linux USB 驱动栈\u0026quot;中的驱动列表）。 换 Hub 口、去掉 Hub 直连、外接供电，排除供电和 Hub 问题。 参考：Linux USB API。\n","permalink":"https://note.plhwiki.cn/hardware/usb/","summary":"\u003ch1 id=\"usb\"\u003eUSB\u003c/h1\u003e\n\u003ch2 id=\"usb总线系统中的设备\"\u003eUSB总线系统中的设备\u003c/h2\u003e\n\u003cp\u003eUSB 是以主机为中心的分层星形拓扑（tiered star），从上到下依次是：主机（含根集线器）→ 集线器 → 外围设备。\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"/hardware/assets/0280a0c9854063cd20bdbc4098b001c9.png\" alt=\"USB总线拓扑\" loading=\"lazy\"\u003e\n\u003c/p\u003e\n\u003ch3 id=\"usb主机\"\u003eUSB主机\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e每条 USB 总线（一棵拓扑树）有且只有一个主机。一个 SoC 可以有多个主机控制器。A1 的三个 DWC2 控制器各注册一条总线；但不能推广为所有控制器都一对一，例如 xHCI 通常分别注册 USB 2 和 USB 3 两条逻辑总线。\u003c/li\u003e\n\u003cli\u003eUSB 2.0 的常规事务由主机调度，设备不能自行发起普通总线事务；远程唤醒等信号是另一回事。USB 3.x 还支持 ERDY 等设备通知，不能把“所有版本始终只能等待轮询”作为通用结论。\u003c/li\u003e\n\u003cli\u003e主机按固定周期划分时间：每 1 ms 发一个 SOF（Start of Frame）包标识一帧；High Speed 下每帧再细分为 8 个 125 µs 的微帧（microframe）。\u003c/li\u003e\n\u003cli\u003e负责枚举设备（分配地址、读取描述符、选择配置）、加载驱动，以及对总线上的错误进行管理和恢复。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"usb-root-hub根集线器\"\u003eUSB Root Hub（根集线器）\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e集成在主机控制器内部，不是独立芯片，由 HCD（Host Controller Driver）在注册总线时虚拟出来。\u003c/li\u003e\n\u003cli\u003eLinux 中设备名为 \u003ccode\u003eusbN\u003c/code\u003e（N 为总线号），接口为 \u003ccode\u003eN-0:1.0\u003c/code\u003e，绑定 \u003ccode\u003ehub\u003c/code\u003e 驱动。\u003c/li\u003e\n\u003cli\u003eVID:PID 固定为 Linux Foundation 的虚拟 ID：\u003ccode\u003e1d6b:0001\u003c/code\u003e（USB 1.1）、\u003ccode\u003e1d6b:0002\u003c/code\u003e（USB 2.0）、\u003ccode\u003e1d6b:0003\u003c/code\u003e（USB 3.x）。\u003c/li\u003e\n\u003cli\u003e端口数等于控制器的物理端口数，见 sysfs 的 \u003ccode\u003emaxchild\u003c/code\u003e。君正A1 每个控制器只有 1 个端口。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eproduct\u003c/code\u003e、\u003ccode\u003emanufacturer\u003c/code\u003e 由内核填写，A1 上分别是 \u003ccode\u003eDWC OTG Controller\u003c/code\u003e、\u003ccode\u003eLinux 4.4.94-20220517 dwc2_hsotg\u003c/code\u003e。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e对应的启动日志：\u003c/p\u003e","title":"usb"},{"content":"Uboot tftp 烧写\n以下地址和大小仅适用于当时的分区表。预填充 0xff 要使用 mw.b，count 才按字节计；默认 mw 通常按 32 位单元写入，相同 count 会覆盖四倍的内存范围。烧写前还需确认 TFTP 成功且镜像大小未超过分区。\n烧写内核(2560KB) mw.b 80008000 ff 280000;tftpboot 80008000 uImage_918 sf probe 0;sf erase 70000 280000; sf write 80008000 70000 280000 烧写根文件系统(3072KB) mw.b 80008000 ff 300000;tftpboot 80008000 rootfs_36_2026_09_18 sf probe 0;sf erase 2F0000 300000 ;sf write 80008000 2F0000 300000 烧写app(16384KB) mw.b 80008000 ff 1000000;tftpboot 80008000 appfs_2026_09_18 sf probe 0;sf erase 5F0000 1000000 ;sf write 80008000 5F0000 1000000 #给算法分配内存 setenv bootargs \u0026#39;console=ttyS1,115200n8 mem=128M@0x0 rmem=96M@0x8000000 nmem=32M@0xE000000 root=/dev/mtdblock3 rootfstype=squashfs ro init=/linuxrc ignore_loglevel mtdparts=jz_sfc:384k(boot),64k(logo),2560k(kernel),3072k(rootfs),16384k(appfs),-(para) lpj=11968512\u0026#39; make\n媒体 音频 一帧的时长 = 一帧采样点的个数 / 采样率\n采样率：8000Hz，一帧采样点个数：320。可以计算出一帧的时长是40ms\n音频帧 音频帧其实只是把很多采样点打包起来，ADC实际上就是不断的产生数据： sample0\rsample1\rsample2\rsample3\rsample4\r... 但是软件如果每产生 1 个 sample 就通知应用程序一次，效率会非常低，所以驱动通常把一定数量的采样点聚合成一个 Audio Frame：\n连续 PCM：\r0 1 2 3 4 5 ............ 319\r└───────────────────────────────┘\rFrame 0\r320 samples\r40 ms\r320 321 322 .................... 639\r└───────────────────────────────┘\rFrame 1\r320 samples\r40 ms A1的音频帧参数\n采样率：8000Hz 位深：16 Bit 单声道 1帧为320个采样点 一帧的大小 ：320 *2 Byte =640Bytes\n","permalink":"https://note.plhwiki.cn/linux/chip/a1/","summary":"\u003ch1 id=\"uboot\"\u003eUboot\u003c/h1\u003e\n\u003cp\u003etftp 烧写\u003c/p\u003e\n\u003cp\u003e以下地址和大小仅适用于当时的分区表。预填充 0xff 要使用 mw.b，count 才按字节计；默认 mw 通常按 32 位单元写入，相同 count 会覆盖四倍的内存范围。烧写前还需确认 TFTP 成功且镜像大小未超过分区。\u003c/p\u003e","title":"A1"},{"content":"平台设备驱动程序 片上设备 物理存在于 SoC 内部的硬件模块，其特点是焊死在芯片内部，不可插拔，不可枚举(无discovery机制)\nUART 控制器 I2C 控制器 SPI 控制器 GPIO Timer 平台设备 设备树 **DTS（Device Tree Source）：**DTS 是设备树的源文件，采用一种类似于文本的语法来描述硬件设备的结构、属性和连接关系\n**DTSI（Device Tree Source Include）：**DTSI 文件是设备树源文件的包含文件。它扩展了 DTS文件的功能，用于定义可重用的设备树片段。DTSI 文件以.dtsi 为扩展名，可以在多个 DTS 文件中包含和共享\n**DTB（Device Tree Blob）：**DTB 是设备树的二进制表示形式。DTB 文件是通过将 DTS 或 DTSI文件编译而成的二进制文件，以.dtb 为扩展名。\n**DTC（Device Tree Compiler）：**DTC 是设备树的编译器。它是一个命令行工具，用于将 DTS和 DTSI 文件编译成 DTB 文件。\n设备树解决什么问题 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格式规范：节点名@地址，紧跟着节点名称后，地址格式由父总线绑定规定；常见 MMIO 地址使用不带 0x 前缀和多余前导零的十六进制，不能随意混用十进制或任意字符串 **属性定义:**节点的核心，由一系列键值对组成，用来描述设备的具体属性（如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 功能。 pinctrl-N，这里的表示第N个state，名字由 pinctrl-names 按顺序对应，下面的例子 pinctrl-names = \u0026quot;default\u0026quot;，\u0026quot;reset\u0026quot;，所以 pinctrl-0 = default ，pinctrl-1=reset\n\u0026amp;gmac0 { pinctrl-names = \u0026#34;default\u0026#34;, \u0026#34;reset\u0026#34;; pinctrl-0 = \u0026lt;\u0026amp;gmac0_rmii_p0_normal\u0026gt;, \u0026lt;\u0026amp;gmac0_rmii_p1_normal\u0026gt;; pinctrl-1 = \u0026lt;\u0026amp;gmac0_rmii_p0_rst\u0026gt;, \u0026lt;\u0026amp;gmac0_rmii_p1_normal\u0026gt;; status = \u0026#34;okay\u0026#34;; ingenic,mode-reg = \u0026lt;0xb00000c8\u0026gt;;\t/* config phy control */ phy-handle = \u0026lt;\u0026amp;ethphy0\u0026gt;; phy-mode = \u0026#34;rmii\u0026#34;; ingenic,reset-gpios = \u0026lt;\u0026amp;gpc 18 GPIO_ACTIVE_LOW INGENIC_GPIO_NOBIAS\u0026gt;; ingenic,rst-ms = \u0026lt;10\u0026gt;; max-speed = \u0026lt;100\u0026gt;; macphy-rate = \u0026lt;25000000\u0026gt;; mdio { compatible = \u0026#34;snps,dwmac-mdio\u0026#34;; #address-cells = \u0026lt;1\u0026gt;; #size-cells = \u0026lt;0\u0026gt;; ethphy0: ethernet-phy@0 { reg = \u0026lt;0\u0026gt;;\t/* phy addr */ max-speed = \u0026lt;100\u0026gt;; reset-gpios = \u0026lt;\u0026amp;gpc 18 GPIO_ACTIVE_LOW INGENIC_GPIO_NOBIAS\u0026gt;; reset-delay-us = \u0026lt;10000\u0026gt;; }; }; }; compatible 属性 ​\t用于设备树（Device Tree）中的设备节点与 Linux 内核中的驱动（Driver）进行匹配，通常按 \u0026ldquo;最具体 → 最通用\u0026quot;排列，内核用它们与驱动的 of_match_table 匹配\n**格式：**compatible = \u0026ldquo;厂商,设备型号\u0026rdquo;, \u0026ldquo;通用标准\u0026rdquo;。 第一字符串：精确匹配（如\u0026quot;rockchip,rk3588-evb3-lp5-v10\u0026rdquo;） 后续字符串：通用兼容（如\u0026quot;rockchip,rk3588\u0026quot;） compatible = \u0026#34;rockchip,rk3588-evb3-lp5-v10\u0026#34;, \u0026#34;rockchip,rk3588\u0026#34;; // 文件: sun8i-t113.dtsi \u0026amp;i2c2 { status = \u0026#34;okay\u0026#34;; aht20: sensor@38 { compatible = \u0026#34;aosong,aht20\u0026#34;; // 匹配驱动 reg = \u0026lt;0x38\u0026gt;;// I2C地址 vdd-supply = \u0026lt;\u0026amp;reg_dcdc1\u0026gt;;// 电源 }; }; 驱动：\nstatic const struct of_device_id aht20_of_match[] = { { .compatible = \u0026#34;aosong,aht20\u0026#34; }, { } }; MODULE_DEVICE_TABLE(of, aht20_of_match); //////// static struct i2c_driver aht20_driver = { .driver = { .name = \u0026#34;aht20\u0026#34;, .of_match_table = aht20_of_match, }, }; reg 属性 **核心作用:**定义设备在父总线地址空间中的寄存器地址范围或内存映射区域，告知驱动程序硬件资源的具体位置。\n**格式：**由一个或多个对组成\n#address-cells 地址字段占用的32位单元 #size-cells 大小字段占用的32位单元 到底分别占几个 cell，由父节点决定。\rmemory@80000000 { device_type = \u0026#34;memory\u0026#34;; reg = \u0026lt;0x80000000 0x20000000\u0026gt;; // 512MB内存 }; #address-cells = \u0026lt;2\u0026gt; 表示“地址要用 2 个 32-bit cell 表示”； #size-cells = \u0026lt;2\u0026gt; 表示“长度也要用 2 个 32-bit cell 表示”。 reg = \u0026lt;0x00000000 0x80000000 0x00000000 0x20000000\u0026gt;; reg在不同 bus 上含义不同：\nBus reg 常见含义 SoC/MMIO 寄存器物理地址 + 长度 I2C Slave Address SPI Chip Select Memory 内存起始地址 + 大小 PCI PCI 地址空间 寻址属性 #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;; }; status 属性 status 属性用于描述设备或节点的状态，属性值是字符串。属性值有以下几种：okay 表示设备或节点是可用状态；disabled 表示设备或节点是不可用状态；reserved 表示设备或节点已被保留，暂时不可用；fail 表示设备或节点是不可用状态并且检测到了错误\n特有属性 属性 作用 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@10003000 { 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://note.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\u003cp\u003e**DTS（Device Tree Source）：**DTS 是设备树的源文件，采用一种类似于文本的语法来描述硬件设备的结构、属性和连接关系\u003c/p\u003e","title":"dts"},{"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 串口子系统 串口子系统框架是 Linux 内核中专门用于处理串口设备的模块化框架\ngraph TD\rA[应用层]\rB[字符设备]\rC[tty_core层]\rD[uart_core层]\rE[硬件层]\rA--\u0026gt;B\rB--\u0026gt;C\rC--\u0026gt;D\rD--\u0026gt;E 应用层：\n​\t位于最顶层，应用层包括用户空间的串口应用程序，如串口通信工具 minicom等\n字符设备层：\n​\t位于应用层的下方，负责将用户空间的串口读写请求传递给内核空间的tty_core 层。字符设备层将串口设备看作一个特殊的字符设备，通过字符设备接口来进行操作。\ntty_core层：\n​\t位于字符设备层的下方，是串口设备的通用处理层。在 Linux 内核中用于管理串口设备，如数据传输，控制，缓冲管理等\nuart_core 层：\n位于 tty_core 层的下方，提供了串口设备的底层驱动接口，如负责控制串口数据的发送和接收、中断处理、时钟管理等底层操作。\n串口设备节点 串口设备节点命名以tty为前缀。可以通过ls /dev/tty*查看已创建的串口设备节点。\n虚拟控制终端 /dev/tty1、/dev/tty2 等是虚拟控制台的编号接口。是否支持虚拟控制台、创建哪些设备节点以及实际分配多少控制台，取决于内核配置与用户空间设备管理，不能假定所有系统启动时都有 63 个已分配控制台。\n/dev/tty0：表示前台程序的虚拟终端 UART串口 /dev/ttyS1、/dev/ttyS2 是串口设备节点，只有通过内核 console 参数等配置选中后才承担串口控制台角色；登录终端通常还需配置 getty。\nA1 在\n/* 在a1_claf_v12.dts*/ \u0026amp;uart1 { status = \u0026#34;okay\u0026#34;; pinctrl-names = \u0026#34;default\u0026#34;; pinctrl-0 = \u0026lt;\u0026amp;uart1_pc\u0026gt;; }; /*a1.dtsi*/ uart1: serial@0x10031000 { compatible = \u0026#34;ingenic,8250-uart\u0026#34;; reg = \u0026lt;0x10031000 0x1000\u0026gt;; reg-shift = \u0026lt;2\u0026gt;; interrupt-parent = \u0026lt;\u0026amp;core_intc\u0026gt;; interrupts = \u0026lt;IRQ_UART1\u0026gt;; pinctrl-names = \u0026#34;default\u0026#34;; pinctrl-0 = \u0026lt;\u0026amp;uart1_pc\u0026gt;; /*指向a1-pinctrl.dtsi*/ status = \u0026#34;disabled\u0026#34;; }; /*a1-pinctrl.dtsi*/ uart1_pin: uart1-pin { uart1_pc: uart1-pc { /*真正的引脚配置节点*/ ingenic,pinmux = \u0026lt;\u0026amp;gpc 6 7\u0026gt;; ingenic,pinmux-funcsel = \u0026lt;PINCTL_FUNCTION0\u0026gt;; }; }; ","permalink":"https://note.plhwiki.cn/linux/drive/tty/","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":"tty"},{"content":"未压缩的原始图像 YUV420 YUV420是描述色度采样的方式\nNV12 NV12 属于 YUV420SP 格式，包含两个 Plane（平面）：\nY Plane：亮度数据，每个像素一个 Y 分量。 UV Plane：色度数据，U 和 V 交错存储，每个 2×2 像素块共用一组 UV。 描述Y/U/V 数据在内存里怎么排：\nY 平面：\rYYYYYYYYYYYY\rYYYYYYYYYYYY\r...\r共 height 行\rUV 平面：\rUVUVUVUVUVUV\rUVUVUVUVUVUV\r...\r共 height / 2 行 YUV420P YUV420P:\rYYYYYYYYYYYYYYYY\rYYYYYYYYYYYYYYYY\rUUUUUUUU\rVVVVVVVV 注意事项：\n如果图片是nv12的格式,但是用yuv420p去解析图片，会出现大量彩色竖条纹 （UV顺序不正确）\n有损压缩的图像格式 JPG ","permalink":"https://note.plhwiki.cn/multimedia/basic/basic/","summary":"\u003ch1 id=\"未压缩的原始图像\"\u003e未压缩的原始图像\u003c/h1\u003e\n\u003ch2 id=\"yuv420\"\u003eYUV420\u003c/h2\u003e\n\u003cp\u003eYUV420是描述色度采样的方式\u003c/p\u003e\n\u003ch3 id=\"nv12\"\u003eNV12\u003c/h3\u003e\n\u003cp\u003eNV12 属于 YUV420SP 格式，包含两个 Plane（平面）：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eY Plane：亮度数据，每个像素一个 Y 分量。\u003c/li\u003e\n\u003cli\u003eUV Plane：色度数据，U 和 V 交错存储，每个 2×2 像素块共用一组 UV。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e描述Y/U/V 数据在内存里怎么排：\u003c/p\u003e","title":"basic"},{"content":"分辨率 显示分辨率 也就是vo输出分辨率，最终HDMI真正输出给显示器的分辨率\n图形层分辨率 也就是framebuffer分辨率，应用程序实际绘图时使用的画布分辨率\n图形层 framebuffer：1920 × 1080\r↓\rVO 硬件缩放\r↓\rHDMI 最终输出：3840 × 2160 视频层与图形层通常作为并行输入，由 VO 合成后输出，并非视频层先经过 framebuffer 图形层：\n摄像头码流 → VDEC 解码 → 视频层 ──┐\r├→ VO 显示合成 → HDMI\r透明背景和红框 → framebuffer图形层┘ FrameBuffer 数据类型 位域信息，设置像素格式\nstruct fb_bitfield { __u32 offset;\t/* beginning of bitfield\t*/ __u32 length;\t/* length of bitfield\t*/ __u32 msb_right;\t/* != 0 : Most significant bit is */ /* right */ }; ","permalink":"https://note.plhwiki.cn/multimedia/basic/framebuffer/","summary":"\u003ch1 id=\"分辨率\"\u003e分辨率\u003c/h1\u003e\n\u003ch2 id=\"显示分辨率\"\u003e显示分辨率\u003c/h2\u003e\n\u003cp\u003e也就是vo输出分辨率，最终HDMI真正输出给显示器的分辨率\u003c/p\u003e\n\u003ch2 id=\"图形层分辨率\"\u003e图形层分辨率\u003c/h2\u003e\n\u003cp\u003e也就是framebuffer分辨率，应用程序实际绘图时使用的画布分辨率\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e图形层 framebuffer：1920 × 1080\r\n          ↓\r\n      VO 硬件缩放\r\n          ↓\r\nHDMI 最终输出：3840 × 2160\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e视频层与图形层通常作为并行输入，由 VO 合成后输出，并非视频层先经过 framebuffer 图形层：\u003c/p\u003e","title":"framebuffer"},{"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 RFC 3428 中定义的方法 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 用于响应路由和事务匹配。branch 通常以 z9hG4bK 开头；同一请求重传保留原 branch，CANCEL 和非 2xx 的 ACK 还有复用规则，并非每条报文都生成新值。branch 也不要求通过“加密 To/From”等字段生成。UDP 下，rport 配合 received 可让响应返回请求实际来源的 IP 和端口，见 RFC 3581。\n**第三行：**From:\u0026lt;sip:41010500001110000001@192.168.2.179:5060\u0026gt;;tag=1219350858\nFrom 表示请求发起者的逻辑身份。tag 与 Call-ID、对端 tag 一起参与对话标识，不是身份鉴权凭据；鉴权使用 Authorization 等机制。\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 用于关联相关消息；对话由 Call-ID 和本地、远端 tag 共同标识。REGISTER 不建立 dialog，其刷新请求也可复用 Call-ID。\n**第六行：**CSeq: 2 REGISTER\nCSeq 由序号和方法组成，序号小于 2^31。同一请求的重传保持原 CSeq；发起新的注册请求（包括鉴权重试）应按规则递增。ACK 和 CANCEL 的序号与对应 INVITE 相同，但方法字段分别为 ACK、CANCEL。\n**第七行：**Contact: \u0026lt;sip:41010500001110000001@192.168.2.179:5060\u0026gt;\nContact 表示可到达该 UA 的联系 URI；REGISTER 用它建立注册绑定，对话中可用作后续请求的目标。响应通常沿 Via 路由，不是按 Contact 返回。\n**第八行：**Max-Forwards: 70\nMax-Forwards 限制请求可经过的代理跳数；示例值为 70，标准字段名是 Max-Forwards。\n**第九行：**User-Agent: VCP MWARE\nUser-Agent头用于设置关于UA的信息，用户可以自定义\n**第十行：**Expires: 3600\nREGISTER 中的 Expires 表示希望注册绑定保持有效的秒数；最终接受的有效期以服务器响应为准，不是请求的网络超时时间。\n**第11行：**Content-Length: 0\nContent-Length 表示消息体的字节数，消息体不一定是 SDP，也可以是 XML 等。此 REGISTER 没有消息体，所以为 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消息的成功和错误应答均无消息体，下面是 DeviceInfo 查询请求示例，不是心跳，也不是 200 OK 响应；Content-Length 应按实际编码后的消息体字节数计算：\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 平台。 以下为一种常见的“平台控制 + 独立媒体接收端”流程。HTTP/API 调用属于实现细节；GB28181 的 SIP 会话建立在平台与设备之间，普通 Web 播放器不必作为 SIP UA。\nsequenceDiagram\rparticipant Web as Web 播放器\rparticipant Platform as 平台 SIP UA\rparticipant Media as 媒体接收端\rparticipant Device as IPC/NVR SIP UA\rWeb-\u0026gt;\u0026gt;Platform: 请求点播（平台 API）\rPlatform-\u0026gt;\u0026gt;Media: 准备媒体接收地址与端口\rPlatform-\u0026gt;\u0026gt;Device: INVITE（SDP offer）\rDevice--\u0026gt;\u0026gt;Platform: 200 OK（SDP answer）\rPlatform-\u0026gt;\u0026gt;Device: ACK\rDevice--\u0026gt;\u0026gt;Media: 按协商结果发送媒体流\rMedia--\u0026gt;\u0026gt;Web: 按播放器协议输出媒体\rWeb-\u0026gt;\u0026gt;Platform: 请求停止（平台 API）\rPlatform-\u0026gt;\u0026gt;Device: BYE\rDevice--\u0026gt;\u0026gt;Platform: 200 OK\rPlatform-\u0026gt;\u0026gt;Media: 释放接收资源 参考：SIP；MESSAGE；rport。\n","permalink":"https://note.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":"流媒体协议 RTSP 在上层发号施令（管控制），RTP 在底层埋头搬砖（运数据），RTCP 在旁边盯着质量（做监督）\n三者关系：\nRTSP：控制播放、暂停和会话参数；RTP/RTCP 还可通过 RTSP 的 TCP 连接交错传输，不能理解为控制连接一定不承载媒体字节。 RTP：封装媒体载荷、时间戳和序列号，常运行于 UDP 之上，也可使用 TCP 等承载；RTP 本身不保证可靠、有序或按时送达。 RTCP：发送传输质量、同步和参与者信息；NACK 等反馈属于扩展机制，是否重传或调整码率由协议协商和实现决定。 传统 RTP/UDP 会话通常使用偶数 RTP 端口及相邻的奇数 RTCP 端口，但这是常见约定。SDP 可以明确协商 RTCP 端口，rtcp-mux 还允许两者共用同一端口：\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 … | 固定头部字段 固定头部最少 12 字节；CSRC 列表和扩展头会增加总头部长度：\nV (2 bits)：版本号（通常为2） P (1 bit)：填充位（末尾有填充字节时置1） X (1 bit)：扩展头标志 CC (4 bits)：CSRC计数器（混合流数量） M (1 bit)：含义由载荷规范定义；H.264 通常标记访问单元末尾，某些音频格式用于标记 talkspurt 开始，不能通用解释为“静音” PT (7 bits)：载荷类型。RTP/AVP 中 PCMU（G.711 μ-law）静态 PT 为 0，PCMA（A-law）为 8；H.264 通常使用 SDP 协商的动态 PT，96 只是常见取值。 Sequence Number：包序号（检测丢包和乱序） Timestamp：时间戳（基于采样频率的时钟值） SSRC：会话内同步源的 32 位标识，一个会话可以有多个 SSRC；发送方需检测并处理冲突 CSRC：贡献源列表（混流时使用） 扩展头部 ​\t扩展头部是RTP协议的一个重要扩展机制，它是RTP头部的扩展标志（X）位决定的，它允许在RTP数据包中添加额外的信息，以满足特定应用的需求。扩展头部的格式如下：\n扩展头部标识符：占16位，用于标识扩展头部的类型和格式。不同的标识符对应不同的扩展头部定义，使得接收端能够正确解析扩展头部的内容。 扩展头部长度：16 位，按 32 位字计数，不包含前面的 4 字节扩展头自身。 扩展头部内容：扩展头部的内容根据具体的扩展头部标识符而定，可以包含各种类型的信息，如时间戳扩展、序列号扩展、加密信息等。扩展头部的灵活性使得RTP协议能够适应不断发展的应用需求。扩展头部的使用提高了RTP协议的适应性和扩展性，使得RTP协议能够在多种复杂的网络环境中稳定运行，并满足不同应用的特定需求。 RTP会话过程 会话建立 RTP会话的建立是实时数据传输的起点，涉及多个关键步骤，确保数据能够正确传输到接收端。\n确定传输地址：按会话协商结果确定 RTP/RTCP 的传输地址、端口及是否复用。 初始化 RTP 和 RTCP：设置源标识、序列号、时间戳及 RTCP 报告状态。 SSRC 的生成：通常随机选取，会话内可能发生碰撞，必须按协议处理；不能保证随机生成就绝不重复。 载荷 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（IDR 图像的编码切片）。 0x41（二进制：0100 0001）\n拆解：F=0，NRI=10（中重要性），Type=1（非 IDR 图像的编码切片；I/P/B 类型需继续解析 slice header）。 单 NALU 模式 ​\t单一NAL单元模式是最简单的RTP封装方式，每个RTP包只包含一个H.264的NAL单元。这种模式适用于NAL单元大小较小，且网络条件较好，不需要对NAL单元进行分片或聚合的场景。在这种模式下，RTP负载的第一个字节是NAL单元的头信息（1字节），紧接着是 NAL 单元的字节序列载荷（EBSP，可能含 emulation prevention 字节），不是直接去除防竞争字节后的 RBSP。封装和解封装过程简单，易于实现。由于每个RTP包只包含一个NAL单元，因此在解码端可以快速识别和处理每个NAL单元，减少了处理复杂度。适用于NAL单元较小（如SPS、PPS等参数集或较小的I帧切片）的场景，或者在网络带宽充足且对延迟要求不高的情况下。\nNAL 类型（NAL Unit Type）： 1–23 结构： RTP 载荷直接就是一个完整的 NAL 单元（不含起始码 0x00000001），若要避免 IP 分片，需要从路径 MTU 中扣除 IP、UDP、RTP 及扩展等头部开销，再判断整个 NALU 是否能装入单包。 处理： 按 SSRC、序列号和 RTP 时间戳重排、检测丢包并组装访问单元；M 位可作为边界提示，但收到 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，合法值为 1～7，不能为 0；实际时间层 ID 为 TID − 1。单时间层码流常为 1，不能把所有码流都固定写成 1。 参考：RTP；RTP/AVP；rtcp-mux；H.264 RTP；H.265 RTP。\n","permalink":"https://note.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：控制播放、暂停和会话参数；RTP/RTCP 还可通过 RTSP 的 TCP 连接交错传输，不能理解为控制连接一定不承载媒体字节。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eRTP\u003c/strong\u003e：封装媒体载荷、时间戳和序列号，常运行于 UDP 之上，也可使用 TCP 等承载；RTP 本身不保证可靠、有序或按时送达。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eRTCP\u003c/strong\u003e：发送传输质量、同步和参与者信息；NACK 等反馈属于扩展机制，是否重传或调整码率由协议协商和实现决定。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e传统 RTP/UDP 会话通常使用偶数 RTP 端口及相邻的奇数 RTCP 端口，但这是常见约定。SDP 可以明确协商 RTCP 端口，rtcp-mux 还允许两者共用同一端口：\u003c/p\u003e","title":"media_protocol"},{"content":"V4L2 框架核心概念 Video for Linux 2 (V4L2) 是 Linux 内核中处理视频设备的标准框架，用于统一管理摄像头、视频采集卡等设备。设计 V4L2 框架的核心目的（思想）：\n用户空间和内核空间分离：应用通过标准系统调用与内核交互 设备抽象：视频采集／输出通常使用 /dev/videoX；子设备等还可使用 /dev/v4l-subdevX 等节点 缓冲区队列管理：通过 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); 流程步骤深度解析 以下示例用于展示调用顺序，省略了部分返回值检查。实际程序必须检查 open、ioctl、mmap 的返回值，处理 EINTR、非阻塞 DQBUF 的 EAGAIN，并在失败时释放已获取的资源。\n标准采集流程顺序： 打开设备 → 查询能力 → 设置格式 → 申请缓冲区 (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） 错误处理：\nENOENT：设备节点路径不存在 ENODEV：节点对应的设备不可用 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_DEVICE_CAPS(特殊)\n告诉你，真正针对当前 /dev/videoX 节点的能力，请看 cap.device_caps 假设一个驱动创建了：/dev/video0,/dev/video1,/dev/video2。整个物理设备可能同时拥有：Video Capture、Video Output、Streaming、MPlane。因此cap.capabilities可能包含整个设备/驱动的综合能力。而cap.device_caps就专门描述当前这个 device node（/dev/video*） 实际拥有的能力。\nV4L2_CAP_VIDEO_CAPTURE\n支持视频采集（摄像头） V4L2_CAP_VIDEO_CAPTURE_MPLANE\n设备支持 multi-planar Video Capture API。 V4L2_CAP_VIDEO_OUTPUT\n支持视频输出（显示设备） V4L2_CAP_STREAMING\n支持 Streaming I/O；具体支持 MMAP、USERPTR、DMABUF 中哪些方式，还需结合 REQBUFS 等接口确认 V4L2_CAP_READWRITE\n支持 read() / write() 方式 V4L2_CAP_META_CAPTURE\n支持元数据采集（ISP metadata） uint32_t node_caps = (cap.capabilities \u0026amp; V4L2_CAP_DEVICE_CAPS) ? cap.device_caps : cap.capabilities; if (node_caps \u0026amp; V4L2_CAP_STREAMING) { // 当前节点支持 Streaming I/O，不等于保证所有内存模式或端到端零拷贝 } 设置视频格式参数 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); v4l2_format fmt; memset(\u0026amp;fmt, 0, sizeof(fmt)); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; fmt.fmt.pix_mp.width = kCamWidth; fmt.fmt.pix_mp.height = kCamHeight; fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12;//两个区域连续存放在同一块内存里 fmt.fmt.pix_mp.field = V4L2_FIELD_NONE; fmt.fmt.pix_mp.num_planes = 1; 怎么知道type应该填写 V4L2_BUF_TYPE_VIDEO_CAPTURE 还是V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE\n通过VIDIOC_QUERYCAP获取capability，\n//查询能力集 v4l2_capability cap; memset(\u0026amp;cap, 0, sizeof(cap)); if (Xioctl(cam-\u0026gt;fd, VIDIOC_QUERYCAP, \u0026amp;cap) \u0026lt; 0) { perror(\u0026#34;VIDIOC_QUERYCAP\u0026#34;); return false; } uint32_t caps = (cap.capabilities \u0026amp; V4L2_CAP_DEVICE_CAPS) ? cap.device_caps: cap.capabilities; if (caps \u0026amp; V4L2_CAP_VIDEO_CAPTURE_MPLANE) { printf(\u0026#34;支持 multi-planar capture\\n\u0026#34;); } if (caps \u0026amp; V4L2_CAP_VIDEO_CAPTURE) { printf(\u0026#34;支持 single-planar capture\\n\u0026#34;); } if (caps \u0026amp; V4L2_CAP_STREAMING) { printf(\u0026#34;支持 Streaming I/O\\n\u0026#34;); } /// //支持 multi-planar capture --\u0026gt;V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE //支持 Streaming I/O 对比项 VIDEO_CAPTURE VIDEO_CAPTURE_MPLANE API 类型 Single-planar Multi-planar 格式结构 fmt.fmt.pix fmt.fmt.pix_mp 格式类型 struct v4l2_pix_format struct v4l2_pix_format_mplane stride fmt.fmt.pix.bytesperline fmt.fmt.pix_mp.plane_fmt[i].bytesperline 图像缓冲大小 fmt.fmt.pix.sizeimage fmt.fmt.pix_mp.plane_fmt[i].sizeimage Plane 数量 固定为一个内存缓冲区 fmt.fmt.pix_mp.num_planes QUERYBUF/QBUF/DQBUF 直接使用 v4l2_buffer 还要提供 v4l2_plane[] MMAP 偏移 buf.m.offset planes[i].m.mem_offset 实际数据长度 buf.bytesused planes[i].bytesused 参数协商机制 应用提出请求格式 → 驱动调整为最接近的硬件支持格式 需要用 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（入队空缓冲区）\rApp-\u0026gt;\u0026gt;Kernel: VIDIOC_STREAMON（初次开流）\rKernel-\u0026gt;\u0026gt;HW: 启动采集，使用已入队缓冲区\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 缓冲区映射到用户空间，无需额外拷贝 多缓冲机制：通常使用多个缓冲区以重叠采集和处理；最少数量取决于驱动，两个缓冲区也不能保证在处理过慢时不丢帧 动态调整：应用可动态调整处理速度（如跳过某些帧） 停止与释放 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); // 关闭设备 参考：V4L2 buffer；QBUF / DQBUF。\n","permalink":"https://note.plhwiki.cn/multimedia/v4l2/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；子设备等还可使用 \u003ccode\u003e/dev/v4l-subdevX\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":"CMake 学习笔记 目录\n快速入门 CMake 工作原理 安装指定版本 CMake（源码编译） out-of-source 构建规范 第一个 CMakeLists.txt 变量与流程控制 变量使用 路径相关变量 CMake 自带变量 if 流程控制 cache 缓存变量 include 用法 括号注释 属性 构建配置与日志 编译类型与 C++ 标准 追加编译选项 收集源文件 输出路径设置 生成 compile_commands.json 分步编译与清理指定 target 调试：打印生成的具体指令 message 用法 Target 与依赖管理 add_library（创建库目标） 导入外部库：IMPORTED target_include_directories target_link_libraries（依赖传播） 链接预编译的第三方库（.a / .so） set_target_properties find_package 与导入目标 生成器表达式基础 install 交叉编译与 Toolchain CMake toolchain 文件 Makefile 交叉编译要点 pkg-config 交叉编译 产物校验 工具链目录结构 快速入门 CMake 工作原理 CMake、构建工具、编译器与最终产物之间的关系。\n安装指定版本 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。\nout-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 第一个 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 # 构建 变量与流程控制 变量使用 变量引用是值替换，未设置的变量返回空字符串。 变量引用可嵌套，从内向外求值。 变量名大小写敏感。 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;) 变量引用与取消变量后的输出示例。\n路径相关变量 变量 含义 CMAKE_SOURCE_DIR 整个构建的顶层源码目录 PROJECT_SOURCE_DIR 当前 project() 的源码目录 CMAKE_CURRENT_SOURCE_DIR 当前 CMakeLists.txt 所在目录 CMAKE_CURRENT_LIST_DIR 当前被处理文件所在目录，包含被 include 的 .cmake 模块 作用域区别：\nCMAKE_SOURCE_DIR 是第一次进入的 CMakeLists.txt 所在目录，不随 add_subdirectory() 或子目录的 project() 改变。 顶层的 PROJECT_SOURCE_DIR 等于 CMAKE_SOURCE_DIR；子目录再次调用 project() 后，在其作用域内变为子项目根。 写相对当前 CMakeLists.txt 的路径时，使用 CMAKE_CURRENT_SOURCE_DIR 最稳妥。 用法建议：\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 CMake 自带变量 提供信息：PROJECT_NAME（对应 project() 的名字）。 改变行为：BUILD_SHARED_LIBS（ON 动态库 / OFF 静态库，默认 OFF）。 描述系统：MSVC、WIN32（=1 为 Windows）、UNIX（Unix 或类 Unix，包含 macOS 等，不等于仅 Linux）、CMAKE_SYSTEM_NAME。 控制构建：CMAKE_COLOR_MAKEFILE、输出路径等。 set(BUILD_SHARED_LIBS ON) # 后续 add_library 默认建动态库 add_definitions(-Dxlog_STATIC) # 向 C++ 传宏；现代写法见“Target 与依赖管理” 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) 动态库构建输出：libxlog.so。\n静态库构建输出：libxlog.a。\nif 流程控制 if(\u0026lt;conditional\u0026gt;) elseif(\u0026lt;conditional\u0026gt;) else() endif() if(\u0026lt;constant\u0026gt;) 常量：\n真：ON、YES、TRUE、Y 或非零数（含浮点数）。 假：0、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() 常量、字符串与未定义变量的条件判断示例。\n一元 / 二元 / 正则匹配 一元判断：DEFINED（变量是否定义）、COMMAND（是否为已定义的命令/宏/函数）、EXISTS（路径是否存在）。\n二元判断：EQUAL（数字相等）、STREQUAL（字符串相等）。\nset(VAR1 123) if(VAR1 EQUAL 123) message(\u0026#34;VAR1 EQUAL 123\u0026#34;) endif() # 推荐用 STREQUAL if(VAR1 STREQUAL \u0026#34;123\u0026#34;) message(\u0026#34;VAR1 STREQUAL 123\u0026#34;) endif() 数字相等与字符串相等的判断结果对比。\nMATCHES 正则：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() 正则匹配对比：包含匹配与使用 ^、$ 限定的完整匹配。\ncache 缓存变量 语法：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 强制修改 缓存变量取值与强制修改后的输出示例。\n不同类型的缓存变量与 option() 示例：\nset(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 交互修改。 Windows：在 cmake-gui 中查看和修改缓存变量。\nLinux：在 ccmake 中查看和修改缓存变量。\n普通变量与缓存变量重名（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;) 同名普通变量与缓存变量保留不同取值的示例。\n# CMP0126：NEW 保留同名普通变量；OLD 仅在新建缓存项、原缓存项无类型、FORCE/INTERNAL 等条件下移除普通变量 cmake_policy(SET CMP0126 OLD) 设置 CMP0126 OLD 后的输出示例。\n用 -D 传递缓存变量 cmake -S . -B b5 -D PARA1=para00001 第二次不带 -D 运行仍会保留 PARA1（已写入 cache），除非再次 -D PARA1=new 覆盖。\n通过 -D PARA1=para00001 设置缓存变量。\n通过 -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;) # 找到则为绝对路径 RESULT_VARIABLE 返回未找到标记或模块绝对路径的示例。\n括号注释 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) 属性作用域类型及 SOURCE 附加选项说明。\n作用域类型：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。\n构建配置与日志 编译类型与 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) 追加编译选项 注意：给 CMAKE_CXX_FLAGS 追加内容时必须加引号，否则会存成分号列表（如 \u0026quot;-O2;-g\u0026quot;），传给编译器时成为畸形单参数，原值非空时会编译失败。\nset(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) # 头文件搜索路径（目录作用域，可被后续 add_subdirectory 的子目录继承；不会递归搜索磁盘上的子目录） include_directories(${PROJECT_SOURCE_DIR}/include) 收集源文件 aux_source_directory：收集目录下所有源文件，语法为 aux_source_directory(\u0026lt;dir\u0026gt; \u0026lt;var\u0026gt;)。\naux_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。\nfile(GLOB SRC2 ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) file(GLOB H_FILE \u0026#34;${INCLUDE_PATH}/xcpp/*.h\u0026#34;) 常见错误：file(GLOB SRC2 FILE ...) 中的 FILE 不是合法关键字，会被当成匹配不到的 glob 模式静默吞掉，应删去。\nfile(GLOB) 合法选项只有 LIST_DIRECTORIES / RELATIVE / CONFIGURE_DEPENDS。\n注意：GLOB 结果在配置阶段固定，增删文件不会自动重配（除非用 CONFIGURE_DEPENDS）；单个已知源文件建议直接 set(SRC ...)。\n输出路径设置 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 静态库 生成 compile_commands.json 供 clangd / VS Code 等做代码索引：\nset(CMAKE_EXPORT_COMPILE_COMMANDS ON) VS Code CMake Tools 也可通过 .vscode/settings.json 配置：\n{ \u0026#34;cmake.configureArgs\u0026#34;: [ \u0026#34;-DCMAKE_EXPORT_COMPILE_COMMANDS=ON\u0026#34; ] } 分步编译与清理指定 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 # 清理 --target help 列出清理、预处理、汇编等可用目标。\n调试：打印生成的具体指令 set(CMAKE_VERBOSE_MAKEFILE ON) # 默认 OFF 或 cmake --build . -v（第一次生成就要加 -v，否则日志不完整，可清理后重生成）。 message 用法 日志级别 级别 说明 输出流 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 才显示 WARNING 输出示例：包含文件位置与警告内容。\nNOTICE 与 STATUS 输出对比：STATUS 带有 -- 前缀。\n设置显示级别与重定向：\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 2\u0026gt;\u0026amp;1，不要在 2\u0026gt;\u0026amp;1 前再加一个 \u0026gt;。\n使用 --log-level=VERBOSE 后显示详细日志。\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;) 嵌套查找日志：展示缩进及成功、失败状态。\n让 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;) 前景色与背景色对照：\n颜色 前景色 背景色 黑色 30 40 红色 31 41 绿色 32 42 黄色 33 43 蓝色 34 44 紫红色 35 45 青蓝色 36 46 白色 37 47 常见错误：黄色的背景色代码为 43，不要误写成 34。\n终端中的文字颜色与背景色效果。\nTarget 与依赖管理 现代 CMake 提倡以「目标（target）」为中心组织依赖，而非全局的 include_directories / add_definitions / link_libraries。\nadd_library（创建库目标） add_library() 可以从源码创建库目标，也可以用 IMPORTED 引用已编译的外部库。下面先介绍从源码创建库的语法，实际编译在 cmake --build 时执行。\nadd_library(\u0026lt;name\u0026gt; [STATIC|SHARED|MODULE] \u0026lt;sources\u0026gt;...) \u0026lt;name\u0026gt; 是项目内唯一的目标名，例如 xlog；链接该目标时也使用这个名字。 \u0026lt;sources\u0026gt; 是源文件列表，例如 src/xlog.cpp。Linux 默认会为库文件添加 lib 前缀及相应扩展名，目标名无需写成 libxlog.a。 静态库、动态库与插件库 类型 用途 Linux 默认产物（目标名为 xlog） STATIC 静态库，在链接时把需要的目标代码纳入使用它的程序或库 libxlog.a SHARED 动态库，可被其他目标链接，并在运行时加载 libxlog.so MODULE 插件库，通常通过 dlopen() 等方式加载，不能作为其他目标的链接依赖 libxlog.so 分别创建静态库和动态库时，使用不同的目标名：\nadd_library(xlog_static STATIC src/xlog.cpp) # libxlog_static.a add_library(xlog_shared SHARED src/xlog.cpp) # libxlog_shared.so 库类型及命名规则参见 add_library 官方文档。\n省略类型与 BUILD_SHARED_LIBS 省略类型时，BUILD_SHARED_LIBS 为真则创建 SHARED，否则创建 STATIC。CMake 本身不预设这个变量；未定义时按静态库处理。\n通常在顶层、创建库目标之前提供一个开关：\noption(BUILD_SHARED_LIBS \u0026#34;Build shared libraries\u0026#34; OFF) add_library(xlog src/xlog.cpp) 配置时切换为动态库：\ncmake -S . -B build-shared -DBUILD_SHARED_LIBS=ON cmake --build build-shared 注意：这个开关只影响省略类型的 add_library() 调用；显式写了 STATIC 或 SHARED 的目标不受影响。参见 BUILD_SHARED_LIBS 官方文档。\n示例：创建库并链接到程序 假设已有以下源文件，main.cpp 通过 xlog.h 调用 xlog.cpp 中的函数：\nlibrary_demo/ ├── CMakeLists.txt ├── include/ │ └── xlog.h ├── src/ │ └── xlog.cpp └── main.cpp CMakeLists.txt：\ncmake_minimum_required(VERSION 3.20) project(library_demo LANGUAGES CXX) add_library(xlog STATIC src/xlog.cpp ) target_include_directories(xlog PUBLIC \u0026#34;${CMAKE_CURRENT_SOURCE_DIR}/include\u0026#34; ) add_executable(app main.cpp) target_link_libraries(app PRIVATE xlog) PUBLIC 使 xlog 和使用它的 app 都能找到 include/ 下的头文件；链接目标 xlog 也建立了构建依赖，无需手写 .a 文件路径。参见 CMake 目标与依赖说明及 target_include_directories 官方文档。\n在项目根目录执行：\ncmake -S . -B build cmake --build build ./build/app 在 Linux 的 Unix Makefiles 生成器下，默认得到 build/libxlog.a 和 build/app。把示例中的 STATIC 改为 SHARED，即可构建动态库 build/libxlog.so。\n如果在 Linux 中将静态库继续链接进动态库，通常需要在创建静态库后为它开启 PIC：\nset_target_properties(xlog PROPERTIES POSITION_INDEPENDENT_CODE ON) SHARED、MODULE 目标默认开启 PIC。参见 POSITION_INDEPENDENT_CODE 官方文档。\n导入外部库：IMPORTED 已有第三方提供的 .a 或 .so 时，可以用 IMPORTED 将它注册为 CMake 目标，再通过目标名链接。CMake 不会为该库生成编译规则；库文件和头文件需要事先准备好。\nadd_library(\u0026lt;name\u0026gt; \u0026lt;type\u0026gt; IMPORTED [GLOBAL]) 参数 含义 \u0026lt;name\u0026gt; 导入目标名，例如 xlog::xlog，不必与库文件名相同 \u0026lt;type\u0026gt; 导入 .a 使用 STATIC，导入 Linux .so 使用 SHARED GLOBAL 可选；默认仅在创建目标的目录及其子目录可见，加上后整个项目可见 导入静态库\n假设 main.cpp 通过 xlog.h 调用外部库中的函数，目录如下：\nimported_library_demo/ ├── CMakeLists.txt ├── main.cpp └── third_party/ └── xlog/ ├── include/ │ └── xlog.h └── lib/ └── libxlog.a CMakeLists.txt：\ncmake_minimum_required(VERSION 3.20) project(imported_library_demo LANGUAGES CXX) set(XLOG_ROOT \u0026#34;${CMAKE_CURRENT_LIST_DIR}/third_party/xlog\u0026#34;) add_library(xlog::xlog STATIC IMPORTED) set_target_properties(xlog::xlog PROPERTIES IMPORTED_LOCATION \u0026#34;${XLOG_ROOT}/lib/libxlog.a\u0026#34; INTERFACE_INCLUDE_DIRECTORIES \u0026#34;${XLOG_ROOT}/include\u0026#34; ) add_executable(app main.cpp) target_link_libraries(app PRIVATE xlog::xlog) 属性 作用 IMPORTED_LOCATION 库文件的完整路径，应指向 libxlog.a 文件，而非 lib/ 目录 INTERFACE_INCLUDE_DIRECTORIES 使用该库的目标所需的头文件目录；app 链接 xlog::xlog 后会自动获得它 头文件目录也可以通过 target_include_directories(xlog::xlog INTERFACE \u0026quot;${XLOG_ROOT}/include\u0026quot;) 设置，用来替代上面的 INTERFACE_INCLUDE_DIRECTORIES 属性。参见 target_include_directories 官方文档。\n构建并运行：\ncmake -S . -B build cmake --build build ./build/app 导入动态库\n准备好 third_party/xlog/lib/libxlog.so 后，用下面的声明和属性设置替换静态库示例中的对应部分；创建 app 和链接目标的写法保持一致：\nadd_library(xlog::xlog SHARED IMPORTED) set_target_properties(xlog::xlog PROPERTIES IMPORTED_LOCATION \u0026#34;${XLOG_ROOT}/lib/libxlog.so\u0026#34; INTERFACE_INCLUDE_DIRECTORIES \u0026#34;${XLOG_ROOT}/include\u0026#34; ) 动态库在运行时需要由加载器找到。部署程序时，还要部署所需的 .so 及其依赖，并配置相应的运行时搜索路径。参见 CMake 动态库说明。\n声明外部库的额外依赖\n如果外部库还依赖其他库，应把这些依赖记录到导入目标的接口中。例如，xlog 需要线程库时，在创建 xlog::xlog 后添加：\nfind_package(Threads REQUIRED) target_link_libraries(xlog::xlog INTERFACE Threads::Threads) 这样 app 仍只需链接 xlog::xlog，就会同时获得它声明的线程依赖。参见 INTERFACE_LINK_LIBRARIES 官方文档。\n导入目标与 GLOBAL 的完整规则参见 CMake 导入库指南；库文件路径要求参见 IMPORTED_LOCATION 官方文档。\n其他常用形式 形式 声明示例 用途 OBJECT add_library(xlog_obj OBJECT src/xlog.cpp) 只编译出目标文件，不打包成 .a 或链接成 .so，供其他目标复用 INTERFACE add_library(xlog_headers INTERFACE) 不编译、不生成库文件，用于传递头文件目录、宏等使用要求，常用于纯头文件库 IMPORTED add_library(vendor STATIC IMPORTED) 声明已有的预编译库，不重新编译；还需设置库文件位置等属性 ALIAS add_library(demo::xlog ALIAS xlog) 为已有的 xlog 目标创建别名；配置属性时使用原目标 这些形式使用各自的参数格式，参见 add_library 官方文档。IMPORTED 的完整示例见本节导入外部库：IMPORTED，库文件的搜索方式见链接预编译的第三方库。\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） 先创建可执行目标并设置头文件路径，再从下面两种方式中选择一种链接库。\n公共配置：\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 指定库在磁盘上的位置；包含头文件配置和动态库导入的完整示例见导入外部库：IMPORTED。\nadd_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) 头文件路径未配置正确时的错误示例：找不到 xlog.h。\n优先用 target_link_libraries 而非全局的 link_directories() / link_libraries()。\nset_target_properties 设置目标（target）属性的命令，目标可以是可执行文件、库、自定义目标，也可以是 IMPORTED 目标。它的作用是修改一个已创建目标的各项属性，从而控制编译、链接、输出路径、使用要求等行为\nset_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 之后）才能设置。 常用属性：\nIMPORTED_LOCATION（导入目标在磁盘的位置）、VERSION / SOVERSION（.so 版本与 soname）、OUTPUT_NAME 等。\nfind_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参考：UNIX 变量；CMP0126。\n","permalink":"https://note.plhwiki.cn/tools/build/cmake/","summary":"\u003ch1 id=\"cmake-学习笔记\"\u003eCMake 学习笔记\u003c/h1\u003e\n\u003cp\u003e\u003cstrong\u003e目录\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#%e5%bf%ab%e9%80%9f%e5%85%a5%e9%97%a8\"\u003e快速入门\u003c/a\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#cmake-%e5%b7%a5%e4%bd%9c%e5%8e%9f%e7%90%86\"\u003eCMake 工作原理\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#%e5%ae%89%e8%a3%85%e6%8c%87%e5%ae%9a%e7%89%88%e6%9c%ac-cmake%e6%ba%90%e7%a0%81%e7%bc%96%e8%af%91\"\u003e安装指定版本 CMake（源码编译）\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#out-of-source-%e6%9e%84%e5%bb%ba%e8%a7%84%e8%8c%83\"\u003eout-of-source 构建规范\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#%e7%ac%ac%e4%b8%80%e4%b8%aa-cmakeliststxt\"\u003e第一个 CMakeLists.txt\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#%e5%8f%98%e9%87%8f%e4%b8%8e%e6%b5%81%e7%a8%8b%e6%8e%a7%e5%88%b6\"\u003e变量与流程控制\u003c/a\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#%e5%8f%98%e9%87%8f%e4%bd%bf%e7%94%a8\"\u003e变量使用\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#%e8%b7%af%e5%be%84%e7%9b%b8%e5%85%b3%e5%8f%98%e9%87%8f\"\u003e路径相关变量\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#cmake-%e8%87%aa%e5%b8%a6%e5%8f%98%e9%87%8f\"\u003eCMake 自带变量\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#if-%e6%b5%81%e7%a8%8b%e6%8e%a7%e5%88%b6\"\u003eif 流程控制\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#cache-%e7%bc%93%e5%ad%98%e5%8f%98%e9%87%8f\"\u003ecache 缓存变量\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#include-%e7%94%a8%e6%b3%95\"\u003einclude 用法\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#%e6%8b%ac%e5%8f%b7%e6%b3%a8%e9%87%8a\"\u003e括号注释\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#%e5%b1%9e%e6%80%a7\"\u003e属性\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#%e6%9e%84%e5%bb%ba%e9%85%8d%e7%bd%ae%e4%b8%8e%e6%97%a5%e5%bf%97\"\u003e构建配置与日志\u003c/a\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#%e7%bc%96%e8%af%91%e7%b1%bb%e5%9e%8b%e4%b8%8e-c-%e6%a0%87%e5%87%86\"\u003e编译类型与 C++ 标准\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#%e8%bf%bd%e5%8a%a0%e7%bc%96%e8%af%91%e9%80%89%e9%a1%b9\"\u003e追加编译选项\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#%e6%94%b6%e9%9b%86%e6%ba%90%e6%96%87%e4%bb%b6\"\u003e收集源文件\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#%e8%be%93%e5%87%ba%e8%b7%af%e5%be%84%e8%ae%be%e7%bd%ae\"\u003e输出路径设置\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#%e7%94%9f%e6%88%90-compile_commandsjson\"\u003e生成 compile_commands.json\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#%e5%88%86%e6%ad%a5%e7%bc%96%e8%af%91%e4%b8%8e%e6%b8%85%e7%90%86%e6%8c%87%e5%ae%9a-target\"\u003e分步编译与清理指定 target\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#%e8%b0%83%e8%af%95%e6%89%93%e5%8d%b0%e7%94%9f%e6%88%90%e7%9a%84%e5%85%b7%e4%bd%93%e6%8c%87%e4%bb%a4\"\u003e调试：打印生成的具体指令\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#message-%e7%94%a8%e6%b3%95\"\u003emessage 用法\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#target-%e4%b8%8e%e4%be%9d%e8%b5%96%e7%ae%a1%e7%90%86\"\u003eTarget 与依赖管理\u003c/a\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#add_library%e5%88%9b%e5%bb%ba%e5%ba%93%e7%9b%ae%e6%a0%87\"\u003eadd_library（创建库目标）\u003c/a\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#%e5%af%bc%e5%85%a5%e5%a4%96%e9%83%a8%e5%ba%93imported\"\u003e导入外部库：IMPORTED\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#target_include_directories\"\u003etarget_include_directories\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#target_link_libraries%e4%be%9d%e8%b5%96%e4%bc%a0%e6%92%ad\"\u003etarget_link_libraries（依赖传播）\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#%e9%93%be%e6%8e%a5%e9%a2%84%e7%bc%96%e8%af%91%e7%9a%84%e7%ac%ac%e4%b8%89%e6%96%b9%e5%ba%93a--so\"\u003e链接预编译的第三方库（.a / .so）\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#set_target_properties\"\u003eset_target_properties\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#find_package-%e4%b8%8e%e5%af%bc%e5%85%a5%e7%9b%ae%e6%a0%87\"\u003efind_package 与导入目标\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#%e7%94%9f%e6%88%90%e5%99%a8%e8%a1%a8%e8%be%be%e5%bc%8f%e5%9f%ba%e7%a1%80\"\u003e生成器表达式基础\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#install\"\u003einstall\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#%e4%ba%a4%e5%8f%89%e7%bc%96%e8%af%91%e4%b8%8e-toolchain\"\u003e交叉编译与 Toolchain\u003c/a\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#cmake-toolchain-%e6%96%87%e4%bb%b6\"\u003eCMake toolchain 文件\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#makefile-%e4%ba%a4%e5%8f%89%e7%bc%96%e8%af%91%e8%a6%81%e7%82%b9\"\u003eMakefile 交叉编译要点\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#pkg-config-%e4%ba%a4%e5%8f%89%e7%bc%96%e8%af%91\"\u003epkg-config 交叉编译\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#%e4%ba%a7%e7%89%a9%e6%a0%a1%e9%aa%8c\"\u003e产物校验\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"/tools/build/cmake/#%e5%b7%a5%e5%85%b7%e9%93%be%e7%9b%ae%e5%bd%95%e7%bb%93%e6%9e%84\"\u003e工具链目录结构\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"快速入门\"\u003e快速入门\u003c/h2\u003e\n\u003ch3 id=\"cmake-工作原理\"\u003eCMake 工作原理\u003c/h3\u003e\n\u003cp\u003e\u003cimg src=\"/tools/build/assets/image-20241217185914285.png\" alt=\"CMake 生成构建文件并调用构建工具的流程图\" loading=\"lazy\"\u003e\n\u003c/p\u003e","title":"CMake"},{"content":"Make 面向 Linux / C/C++ / 交叉编译（ARM、MIPS uClibc 工具链）的构建笔记。\nMake 基础概念 Make 通过比较**目标（target）与其依赖（prerequisite）的修改时间（mtime）**决定是否重建：只要任一依赖比目标新，就执行该目标的命令，并沿依赖图递归向上。\n常用选项 -C ：Change directory（切换目录） Makefile 中第一个 target 是默认目标（下文示例里是 all），make 不带参数时构建它。\n常用调试开关： make -n：通常只打印 recipe；含 $(MAKE) 的递归调用行、带 + 前缀的行以及某些重建 makefile 的操作仍可能执行，不能当作绝对无副作用的模式。 make -p：打印内部规则与变量数据库，同时仍可能正常构建；只查询时可用 make -qp，注意其退出码也表示目标是否需要重建。 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} 等价。 用 := 冻结 $(shell ...) 结果，用 = 则每次引用都会重新执行 shell： GITVER := $(shell git rev-parse --short HEAD) # 只执行一次 GITVER2 = $(shell git rev-parse --short HEAD) # 每次引用都执行一次 recipe 里访问 shell 变量要写两个 $：$$HOME（单个 $ 会被 make 当成自己的变量）。 优先级 同一个变量名同时从多个地方定义时，有优先级考虑\noverride \u0026gt; make 命令行变量 \u0026gt; Makefile 普通赋值 \u0026gt; Shell 环境变量 \u0026gt; Make 内置默认变量 override最高优先级 MCU_VERSION = FROM_MAKEFILE override MCU_VERSION = FROM_OVERRIDE all: @echo $(MCU_VERSION) @echo origin=$(origin MCU_VERSION) ####### 执行 make MCU_VERSION=FROM_COMMAND_LINE 输出结果 FROM_OVERRIDE origin=override 即使命令行指定make MCU_VERSION=FROM_COMMAND_LINE，输出结果依然不变 make 命令行变量 MCU_VERSION = FROM_MAKEFILE all: @echo $(MCU_VERSION) @echo origin=$(origin MCU_VERSION) ####### 执行 make MCU_VERSION=MCU_CIU32 (这就是command line) 输出结果 MCU_CIU32 origin=command line 这是command line \u0026gt; Makefile 普通赋值 普通赋值 MCU_VERSION = FROM_MAKEFILE all: @echo $(MCU_VERSION) @echo origin=$(origin MCU_VERSION) shell环境变量 这个需要与make XXX=xxx区分\nall: @echo $(MCU_VERSION) @echo origin=$(origin MCU_VERSION) 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 链接期选项 阶段 常用选项 编译期 -pthread -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；若最终被抽取并链接进 .so 的静态库成员含不适用于共享对象的非 PIC 重定位，可能报 recompile with -fPIC；应以 PIC 方式重新编译相关成员，不能仅给最终链接命令加 -fPIC。 静态库与动态库 创建 静态库（.a）：把多个 .o 打包，链接时只抽取被引用到的成员，所需代码进入最终产物，不再运行时加载这个 .a；但程序仍可能依赖 libc 等其他动态库。 $(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 可强制某段按静态/动态链接。 运行期库查找与排查 glibc 加载器在依赖名不含斜杠时，大致依次考虑：仅在无 DT_RUNPATH 时使用 DT_RPATH → LD_LIBRARY_PATH（安全执行模式会忽略）→ DT_RUNPATH → /etc/ld.so.cache → 默认目录。RUNPATH 只用于直接依赖，其他 libc 的规则可能不同。 嵌入搜索路径：-Wl,-rpath,/usr/local/lib，或用 $ORIGIN 相对可执行文件定位 -Wl,-rpath,'$ORIGIN/../lib'。若写在 Makefile recipe 中，要把 $ 写成 $$，例如 -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 跳过该目标的隐式规则搜索。\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 选择对应的 CMAKE_CXX_FLAGS_，并与 CMAKE_CXX_FLAGS 一起使用；例如 Release 使用 CMAKE_CXX_FLAGS_RELEASE。多配置生成器通过 \u0026ndash;config Debug 等选择构建配置，不依赖 CMAKE_BUILD_TYPE。\n参考：GNU Make；ld.so。\n","permalink":"https://note.plhwiki.cn/tools/build/makefile/","summary":"\u003ch1 id=\"make\"\u003eMake\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":"内存错误检查 AddressSanitizer（ASan） 下面日志中的 heap-buffer-overflow 是堆缓冲区越界写，不是内存泄漏。ASan 主要检测越界、释放后使用等错误；泄漏检测由 LeakSanitizer 提供，在部分平台可与 ASan 集成。\ng++ -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://note.plhwiki.cn/tools/debug/asan/","summary":"\u003ch1 id=\"内存错误检查\"\u003e内存错误检查\u003c/h1\u003e\n\u003ch2 id=\"addresssanitizerasan\"\u003eAddressSanitizer（ASan）\u003c/h2\u003e\n\u003cp\u003e下面日志中的 \u003ccode\u003eheap-buffer-overflow\u003c/code\u003e 是堆缓冲区越界写，不是内存泄漏。ASan 主要检测越界、释放后使用等错误；泄漏检测由 LeakSanitizer 提供，在部分平台可与 ASan 集成。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eg++ -fsanitize\u003cspan class=\"o\"\u003e=\u003c/span\u003eaddress -o test_asan memory.cpp  -g\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eplh@plh-virtual-machine:~/work/MyProject/c++/plh/DataStructrues/1.linear_List$ ./test_asan \r\n=================================================================\r\n==12293==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000018 at pc 0x5578e1a712ff bp 0x7ffe0f40ead0 sp 0x7ffe0f40eac0\r\nWRITE of size 4 at 0x602000000018 thread T0\r\n    #0 0x5578e1a712fe in func() /home/plh/work/MyProject/c++/plh/DataStructrues/1.linear_List/memory.cpp:6\r\n    #1 0x5578e1a71314 in main /home/plh/work/MyProject/c++/plh/DataStructrues/1.linear_List/memory.cpp:11\r\n    #2 0x7fdbb00f2082 in __libc_start_main ../csu/libc-start.c:308\r\n    #3 0x5578e1a711ad in _start (/home/plh/work/MyProject/c++/plh/DataStructrues/1.linear_List/test_asan+0x11ad)\r\n\r\n0x602000000018 is located 4 bytes to the right of 4-byte region [0x602000000010,0x602000000014)\r\nallocated by thread T0 here:\r\n    #0 0x7fdbb071b587 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cc:104\r\n    #1 0x5578e1a7127e in func() /home/plh/work/MyProject/c++/plh/DataStructrues/1.linear_List/memory.cpp:5\r\n    #2 0x5578e1a71314 in main /home/plh/work/MyProject/c++/plh/DataStructrues/1.linear_List/memory.cpp:11\r\n    #3 0x7fdbb00f2082 in __libc_start_main ../csu/libc-start.c:308\r\n\r\nSUMMARY: AddressSanitizer: heap-buffer-overflow /home/plh/work/MyProject/c++/plh/DataStructrues/1.linear_List/memory.cpp:6 in func()\r\nShadow bytes around the buggy address:\r\n  0x0c047fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\r\n  0x0c047fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\r\n  0x0c047fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\r\n  0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\r\n  0x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\r\n=\u0026gt;0x0c047fff8000: fa fa 04[fa]fa fa fa fa fa fa fa fa fa fa fa fa\r\n  0x0c047fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\r\n  0x0c047fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\r\n  0x0c047fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\r\n  0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\r\n  0x0c047fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\r\nShadow byte legend (one shadow byte represents 8 application bytes):\r\n  Addressable:           00\r\n  Partially addressable: 01 02 03 04 05 06 07 \r\n  Heap left redzone:       fa\r\n  Freed heap region:       fd\r\n  Stack left redzone:      f1\r\n  Stack mid redzone:       f2\r\n  Stack right redzone:     f3\r\n  Stack after return:      f5\r\n  Stack use after scope:   f8\r\n  Global redzone:          f9\r\n  Global init order:       f6\r\n  Poisoned by user:        f7\r\n  Container overflow:      fc\r\n  Array cookie:            ac\r\n  Intra object redzone:    bb\r\n  ASan internal:           fe\r\n  Left alloca redzone:     ca\r\n  Right alloca redzone:    cb\r\n  Shadow gap:              cc\r\n==12293==ABORTING\n\u003c/code\u003e\u003c/pre\u003e","title":"ASan"},{"content":"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 所在位置。动态库地址可由 GDB 结合目标装载信息重定位；无法解析 PC 的常见原因是缺少与目标二进制匹配的库或调试符号。因此应先提供与目标 rootfs/ABI 对应的 sysroot，再按需设置主机侧库搜索路径。方法是\n# 将路径替换为与目标系统匹配的 rootfs，选择 glibc 或 uClibc 对应的一套\rset sysroot /path/to/target-rootfs\rset solib-search-path /path/to/target-rootfs/lib:/path/to/target-rootfs/usr/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 ​\tUnix 主机上多个搜索路径用英文冒号 : 分隔。下面的 glibc/uClibc 目录仅展示不同工具链路径，实际调试必须选择与目标一致的一套，不能把不同 libc 的库混放在同一个搜索路径中。\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 验证源码路径是否正确： show directories 查看源码搜索目录，info sources 查看调试信息记录的源文件；再用 list 文件名:行号 验证是否实际找到了源码。构建时绝对路径与当前路径不同时，可用 set substitute-path 映射。\n(gdb) show directories (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 ","permalink":"https://note.plhwiki.cn/tools/debug/gdb/","summary":"\u003ch1 id=\"gdb调试\"\u003egdb调试\u003c/h1\u003e\n\u003cp\u003e问题：\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003earm-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.\r\nAborted (core dumped)\n\u003c/code\u003e\u003c/pre\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e在运行GDB前临时修改locale环境变量，可以有效避免此问题：\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eexport\u003c/span\u003e \u003cspan class=\"nv\"\u003eLC_ALL\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003eC\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eexport\u003c/span\u003e \u003cspan class=\"nv\"\u003eLANG\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003eC\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e在启动 gdb 时传递参数\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e使用 \u003ccode\u003e--args\u003c/code\u003e 选项启动 gdb\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003egdb --args ./main 10001 /home/meian/work/MyProject/study/reator/reatorHttp/readme/\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e\u003cstrong\u003e在 gdb 内部设置参数\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e启动 gdb 后，使用 \u003ccode\u003eset args\u003c/code\u003e 命令设置参数，然后运行\u003c/p\u003e","title":"GDB"},{"content":"Docker 配置 国内镜像 在Docker daemon 的全局配置文件 /etc/docker/daemon.json写入：\n{ \u0026#34;registry-mirrors\u0026#34;: [ \u0026#34;https://mirror.ccs.tencentyun.com\u0026#34; ] } 配置完重启docker sudo systemctl restart docker\nDocker的client 你输入命令 docker pull nginx │ ↓ Docker CLI /usr/bin/docker │ │ /var/run/docker.sock ↓ Docker daemon dockerd │ │ 读取 ↓ /etc/docker/daemon.json │ ↓ Registry Mirror │ ↓ 腾讯云镜像站 常用命令 镜像类 查看镜像 docker images 拉取镜像 docker pull docker pull mysql # 未指定标签时使用 latest；latest 是普通标签，不保证代表最新发布版本 运行镜像 docker run docker run tomcat 容器类 查看正在运行的容器 docker ps docker ps -a # 查看所有容器 #加格式化方式访问，格式会更加清爽 docker ps --format \u0026#34;table {{.ID}}\\t{{.Image}}\\t{{.Ports}}\\t{{.Status}}\\t{{.Names}}\u0026#34; 停止容器 docker stop 容器名 docker stop nginx 启动容器 docker start 容器名 docker start nginx docker restart 容器名 docker restart nginx 删除容器 #删除一个 docker rm 容器ID docker rm nginx docker rm -f 容器ID #强制删除 docker rm -f nginx #删除多个 docker rm -f $(docker ps -a -q) 或 docker ps -a -q | xargs docker rm 参考：docker image pull。\n","permalink":"https://note.plhwiki.cn/tools/docker/","summary":"\u003ch1 id=\"docker\"\u003eDocker\u003c/h1\u003e\n\u003ch2 id=\"配置\"\u003e配置\u003c/h2\u003e\n\u003ch3 id=\"国内镜像\"\u003e国内镜像\u003c/h3\u003e\n\u003cp\u003e在Docker daemon 的全局配置文件\u003ccode\u003e /etc/docker/daemon.json\u003c/code\u003e写入：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\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=\"nt\"\u003e\u0026#34;registry-mirrors\u0026#34;\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=\"s2\"\u003e\u0026#34;https://mirror.ccs.tencentyun.com\u0026#34;\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=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e配置完重启docker \u003ccode\u003esudo systemctl restart docker\u003c/code\u003e\u003c/p\u003e\n\u003ch2 id=\"docker的client\"\u003eDocker的client\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e你输入命令\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker pull nginx\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      ↓\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eDocker CLI\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e/usr/bin/docker\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      │ /var/run/docker.sock\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\"\u003eDocker daemon\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edockerd\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      │ 读取\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/etc/docker/daemon.json\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      ↓\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eRegistry Mirror\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      ↓\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e腾讯云镜像站\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"常用命令\"\u003e常用命令\u003c/h1\u003e\n\u003ch2 id=\"镜像类\"\u003e镜像类\u003c/h2\u003e\n\u003ch3 id=\"查看镜像\"\u003e查看镜像\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker images\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"拉取镜像\"\u003e拉取镜像\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker pull\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker pull mysql \u003cspan class=\"c1\"\u003e# 未指定标签时使用 latest；latest 是普通标签，不保证代表最新发布版本\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\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker run\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker run tomcat\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"容器类\"\u003e容器类\u003c/h2\u003e\n\u003ch3 id=\"查看正在运行的容器\"\u003e查看正在运行的容器\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker ps\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker ps -a \u003cspan class=\"c1\"\u003e# 查看所有容器\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#加格式化方式访问，格式会更加清爽\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker ps --format \u003cspan class=\"s2\"\u003e\u0026#34;table {{.ID}}\\t{{.Image}}\\t{{.Ports}}\\t{{.Status}}\\t{{.Names}}\u0026#34;\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\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker stop 容器名\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker stop nginx\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"启动容器\"\u003e启动容器\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker start 容器名\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker start nginx\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker restart 容器名\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker restart nginx\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"删除容器\"\u003e删除容器\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#删除一个\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker rm 容器ID  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker rm nginx\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker rm -f 容器ID  \u003cspan class=\"c1\"\u003e#强制删除\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker rm -f nginx\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#删除多个\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker rm -f \u003cspan class=\"k\"\u003e$(\u003c/span\u003edocker ps -a -q\u003cspan class=\"k\"\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\"\u003edocker ps -a -q \u003cspan class=\"p\"\u003e|\u003c/span\u003e xargs docker rm\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e参考：\u003ca href=\"https://docs.docker.com/reference/cli/docker/image/pull/\"\u003edocker image pull\u003c/a\u003e。\u003c/p\u003e","title":"docker"},{"content":"相关笔记：GDB 调试、ASan 内存检查、学习资源。\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 代码的合并 下面是一次完整的真实流程（2026-08-12，eavs_mc6830 修 websocket recv 超时误判）： 从 main 开 fix 分支 → 改代码 → 分批提交 → 切回 main 合并 → 推送 → 删分支。 分支该从哪个基点开，见下文 一句话黄金法则 的场景 A/B/C。\n0. 动手前先看清楚现状 git status --porcelain # 工作区/暂存区都脏了什么 git branch -vv # 当前在哪个分支, 跟踪的是哪个远端分支 git log --oneline -5 # 最近提交, 顺便学仓库的 commit message 风格 关键：先把脏文件分成两类 —— 「这次要改的」和「别人/以前留下的 WIP」。 后面提交时只提交前者，绝不能 git add -A / git commit -a 一把梭。\n1. 从 main 开分支 git checkout main git pull # 确保基点是最新的 git checkout -b fix/ws-recv-timeout-misjudge 命名习惯：fix/xxx（缺陷修复）、feature/xxx（新功能）、hotfix/xxx（线上紧急）。 在默认分支（main/master）上直接改代码是坏习惯：出问题不好回退，也挡不住别人的推送。 2. 在分支上改代码、分批提交 改完后按路径提交，不要提交无关文件：\ngit diff --stat # 先看改了哪些文件、增删多少行 git diff \u0026lt;file\u0026gt; # 逐个文件确认 git commit -m \u0026#34;fix(xxx): 一句话说明\u0026#34; -- \u0026lt;file1\u0026gt; \u0026lt;file2\u0026gt; git commit -- \u0026lt;路径\u0026gt; 的两个坑：\n坑 说明 路径是相对「当前目录」的，不是仓库根 若仓库根是 ~/proj，你人在 ~/proj/sub，git status 显示 sub/a.cpp，但提交要写 git commit -- a.cpp。写成 sub/a.cpp 会报 error: pathspec 'sub/a.cpp' did not match any file(s) known to git 它会绕过暂存区 只提交你点名的路径的工作区内容；暂存区里别的已 git add 的东西不会被带进这次提交。这正是我们要的效果 —— 工作区里那 14 个 git rm --cached 的暂存删除原封不动留着 对比一下三种提交方式：\n写法 提交什么 什么时候用 git commit -m \u0026quot;...\u0026quot; 暂存区里的全部内容 你确定暂存区干净、只有本次改动 git commit -a -m \u0026quot;...\u0026quot; 所有已跟踪文件的改动（不含新文件） ⚠️ 工作区有别的 WIP 时会误伤 git commit -m \u0026quot;...\u0026quot; -- \u0026lt;路径\u0026gt; 只有点名的路径 ✅ 工作区混杂时最安全 提交完立刻复核：\ngit show --stat HEAD # 这次提交到底含了哪些文件 git status --porcelain | wc -l # 剩下的脏文件数量应该和预期一致 3. 合并回 main —— 先判断能不能 fast-forward 切回去之前，先搞清楚两个分支的拓扑关系：\ngit rev-parse main fix/ws-recv-timeout-misjudge # 两个分支各自指向哪个 commit git merge-base main fix/ws-recv-timeout-misjudge # 共同祖先 git rev-list --left-right --count origin/main...main # 落后/领先各几个提交 判据：如果 merge-base == main 当前的 commit，说明 main 是分支的严格祖先、没有分叉，可以 fast-forward，零冲突风险。\ngit checkout main git merge --ff-only fix/ws-recv-timeout-misjudge --ff-only 是保险丝：能快进就快进，不能快进就直接报错退出， 绝不会偷偷给你造一个 merge commit、也不会把你拖进冲突解决。\n\u0026ndash;ff-only / \u0026ndash;no-ff / 默认 三者怎么选：\n方式 结果 适用 git merge --ff-only \u0026lt;br\u0026gt; 只快进，历史保持线性；不能快进就报错 ✅ 提交少、分支没分叉、想要干净线性历史 git merge --no-ff \u0026lt;br\u0026gt; 强制产生一个 merge commit 想在历史上保留「这几个提交是一个整体」的痕迹；长期 feature 分支 git merge \u0026lt;br\u0026gt;（默认） 能快进就快进，否则造 merge commit 不确定时容易得到意料之外的历史 选哪个看仓库习惯：git log --oneline --graph -20 看看历史是线性的还是一堆菱形。\n4. 合并后核对：别人的 WIP 有没有被动过 合并会改工作区文件，所以合并前存一份快照，合并后 diff 一下：\ngit status --porcelain \u0026gt; /tmp/pre_merge.txt git merge --ff-only fix/ws-recv-timeout-misjudge git status --porcelain \u0026gt; /tmp/post_merge.txt diff /tmp/pre_merge.txt /tmp/post_merge.txt \u0026amp;\u0026amp; echo \u0026#34;工作区未受影响\u0026#34; 再确认改动确实落地了：\ngit log --oneline --decorate -3 # main 和分支应该指向同一个 commit grep -c \u0026#34;你新加的关键字\u0026#34; \u0026lt;改过的文件\u0026gt; 5. 推送 git push origin main 输出形如 63cda5f..3a5c41b main -\u0026gt; main，左边是推之前远端的位置、右边是现在的。\n推之前如果不确定远端有没有被别人推过：git fetch origin main 再 git rev-list --left-right --count origin/main...main。 第一次推某个分支要建立跟踪关系用 -u：git push -u origin \u0026lt;branch\u0026gt;。 6. 删除已合并的分支 git branch -d fix/ws-recv-timeout-misjudge # 本地 git push origin --delete fix/ws-recv-timeout-misjudge # 远端(如果推过) -d 是安全删除：分支上若有未合并的提交，Git 会报错拦住你。 -D 是强制删除（--delete --force），会丢提交，确认不要了才用。 一页速查 # 1) 开分支 git checkout main \u0026amp;\u0026amp; git pull git checkout -b fix/xxx # 2) 改完, 按路径提交(工作区有别的 WIP 时必须这样) git diff --stat git commit -m \u0026#34;fix(mod): 说明\u0026#34; -- path/to/file.cpp git show --stat HEAD # 复核只含该文件 # 3) 判断能否快进 git merge-base main fix/xxx # == main 的 commit 即可快进 # 4) 合并 git checkout main git merge --ff-only fix/xxx # 5) 推送 git push origin main # 6) 删分支 git branch -d fix/xxx 容易踩的几点 现象 原因 处理 error: pathspec 'a/b.cpp' did not match any file(s) known to git pathspec 相对当前目录解析，而 git status 输出的是仓库根相对路径 cd 到仓库根，或把路径改成相对当前目录的写法 git merge 意外产生了 merge commit 用了默认行为，而分支已经分叉 想要线性历史就固定用 --ff-only，报错了再决定是 rebase 还是接受 merge fatal: Not possible to fast-forward, aborting. main 上有分支里没有的提交（分叉了） 要么 git rebase main 把分支挪到 main 顶上，要么老实用 --no-ff 合并 合并后发现把别人的改动一起提交了 用了 git add -A / git commit -a 提交前 git status --porcelain 分类，提交后 git show --stat HEAD 复核 git branch -d 报错删不掉 分支上有未合并的提交 先确认这些提交要不要 —— 确实不要了才用 -D 访问不了github 下面是曾经使用过的 hosts 配置记录，IP 可能已经变化，不应作为固定解决方案照抄。应先区分 DNS、代理和网络连通性问题：\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 修改 /etc/hosts 通常不需要重启整个网络服务；用 getent hosts github.com 检查系统解析结果。应用或 DNS 缓存仍使用旧结果时，再针对具体缓存处理。 git 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 要求分支已合并到其 upstream；没有 upstream 时检查是否已合并到当前 HEAD，并不是固定检查 main。 假设你在当前分支上运行以下命令：\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 # http.proxy 同时用于 Git 的 HTTP 与 HTTPS 传输；https.proxy 不是 Git 对应的标准配置项 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;：指定要获取的远程分支；省略时按该 remote 的 fetch refspec 获取，常见配置覆盖所有分支，但 single-branch 等配置可能只覆盖部分分支。 常见场景与实际例子： 获取远程仓库的所有更新： git fetch origin 这条命令按 origin 的 fetch refspec 更新相应远程跟踪引用；默认只自动跟随与所获取历史相关的标签，需要所有标签时另用 \u0026ndash;tags。注意，这并不会修改当前的工作分支或合并任何更改。\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;...] \u0026ndash;oneline：每行显示缩写后的提交 ID 和提交主题；缩写长度会为消除歧义而增加，不保证固定 7 位。\n\u0026ndash;graph：在左侧以文本分支图的形式展示分支合并历史\n\u0026ndash;stat：是在git log 的基础上输出文件增删改的统计数据。\ngit merge 将两个分支的更改合并到一个分支中\ngit merge \u0026lt;branch-name\u0026gt; \u0026lt; branch-name \u0026gt; :指定要合并的目标分支。当前分支会与该目标分支合并。 命令的常用选项及参数：\n\u0026ndash;no-ff 选项：强制执行合并提交\n用途：即使目标分支可以快进合并，使用 --no-ff 选项仍然会生成一个新的合并提交。这个选项有助于保留合并的历史记录。\n如果目标提交已经是当前 HEAD 的祖先（Already up to date），即使指定 \u0026ndash;no-ff，也不会凭空创建合并提交。\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 后重新建库；对已有仓库单独执行 git init 不会清空提交历史），就可能导致两边的历史不再匹配。\ngit pull 先 fetch，再根据命令参数和配置，以 merge、rebase 或仅快进方式整合到当前分支；发生分叉且未指定策略时也可能拒绝继续。\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只拉取更新而不合并 只下载远程提交、更新远程跟踪信息而不整合当前分支，使用 fetch：\ngit fetch origin master fetch 不修改当前分支的工作区内容。git pull --no-commit 仍会尝试整合；\u0026ndash;no-commit 只阻止产生合并提交前的自动提交，不能阻止快进更新。\n丢弃本地状态并对齐远程分支 以下操作不是合并：确认本地已跟踪文件的修改和当前分支独有提交都可以放弃后，先 fetch，再把当前分支重置到目标远程跟踪分支。\ngit reset --hard \u0026lt;commit\u0026gt;\n\u0026lt;commit\u0026gt; 是你要重置到的目标提交，可以是： 提交的哈希值（如 a1b2c3d4） 分支名（如 origin/main 或 master） HEAD~1（上一提交） HEAD（当前提交） git fetch origin git reset --hard origin/main # main 应替换为实际要对齐的远程分支 遇到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\n仅处理示例中的 build/ 时，先用 git clean -nd -- build/ 预览，再确认是否执行 git clean -fd -- build/；不带路径限制会影响整个工作区的未跟踪文件。\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 参考：git restore；git pull。\n","permalink":"https://note.plhwiki.cn/tools/git/","summary":"\u003cp\u003e相关笔记：\u003ca href=\"/tools/debug/gdb/\"\u003eGDB 调试\u003c/a\u003e、\u003ca href=\"/tools/debug/asan/\"\u003eASan 内存检查\u003c/a\u003e、\u003ca href=\"/reference/%E5%AD%A6%E4%B9%A0%E8%B5%84%E6%BA%90/\"\u003e学习资源\u003c/a\u003e。\u003c/p\u003e\n\u003ch1 id=\"git\"\u003eGit\u003c/h1\u003e\n\u003cp\u003e\u003ca href=\"https://learngitbranching.js.org/?locale=zh_CN\"\u003ehttps://learngitbranching.js.org/?locale=zh_CN\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"/tools/assets/image-20240111143435610.png\" alt=\"image-20240111143435610\" loading=\"lazy\"\u003e\n\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"git的三个区域\"\u003eGit的三个区域\u003c/h2\u003e\n\u003cp\u003eGit 的文件有三种状态区域：\u003c/p\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003e区域\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e命令\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e说明\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003e工作区 (Working Directory)\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e你编辑的实际文件\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e改动还没 \u003ccode\u003eadd\u003c/code\u003e\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003e暂存区 (Staging Area/index)\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e用 \u003ccode\u003egit add\u003c/code\u003e 后进入这里\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e等待被 \u003ccode\u003ecommit\u003c/code\u003e\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003e本地仓库 (Repository)\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e用 \u003ccode\u003egit commit\u003c/code\u003e 后进入这里\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e已经记录在历史中\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003e对应命令区别：\u003c/p\u003e","title":"Git"},{"content":"单例模式 Service Locator 这实际上是属于一种全局获取依赖对象的设计模式，可以理解为“我不通过构造函数传进来，而是到一个全局注册中心里去拿对象。”\nMeyer\u0026rsquo;s Singleton 经典代码（C++11 起，函数局部 static 的首次初始化具有线程安全保证，但不代表对象后续的所有操作都线程安全）：\nclass StreamManager { public: StreamManager(const StreamManager\u0026amp;) = delete; StreamManager\u0026amp; operator=(const StreamManager\u0026amp;) = delete; static StreamManager\u0026amp; instance() { static StreamManager obj; return obj; } private: StreamManager() {} }; //使用 StreamManager\u0026amp; manager = StreamManager::instance(); 缺点 隐式依赖 RecordController(controller) //它内部还依赖： CmsStreamManager::instance() 生命周期不可控 谁创建？ 谁销毁？ 什么时候初始化？全隐藏。\n多线程 shutdown 麻烦 ","permalink":"https://note.plhwiki.cn/cpp/design/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/","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经典代码（C++11 起，函数局部 static 的首次初始化具有线程安全保证，但不代表对象后续的所有操作都线程安全）：\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://note.plhwiki.cn/tools/build/%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":"Don\u0026rsquo;t hurt yourself 减少函数参数 函数参数过多，是复杂度失控的信号。\n当一个函数不断需要增加参数时，不要急着继续加参数，而应先检查：\n函数是否承担了过多职责，需要拆分； 多个参数是否属于同一个概念，可以封装成结构体/参数对象； 不同使用场景是否应该用不同函数、重载或模板表达； 只有以上方式都不合适时，才考虑增加新参数。 最终目标是：\n减少参数数量，降低调用者的理解成本，让函数职责和抽象边界更加清晰。\n例如：\nstartRecord(channel, stream, type, alarm, overwrite, audio, duration, priority, callback); //应该把相同概念的参数提取为结构体 struct RecordOptions { RecordType type; bool audio; int duration; int priority; }; ","permalink":"https://note.plhwiki.cn/learning/c++%E4%B9%8B%E7%BE%8E/","summary":"\u003ch1 id=\"dont-hurt-yourself\"\u003eDon\u0026rsquo;t hurt yourself\u003c/h1\u003e\n\u003ch2 id=\"减少函数参数\"\u003e减少函数参数\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e函数参数过多，是复杂度失控的信号。\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e当一个函数不断需要增加参数时，不要急着继续加参数，而应先检查：\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，可以封装成结构体/参数对象；\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e不同使用场景是否应该用不同函数、重载或模板表达\u003c/strong\u003e；\u003c/li\u003e\n\u003cli\u003e只有以上方式都不合适时，才考虑增加新参数。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e最终目标是：\u003c/p\u003e","title":"c++之美"},{"content":"目标文件 目标文件的格式 ELF 文件类型 说明 实例 可重定位文件（ET_REL） 包含待链接的代码、数据和重定位信息 Linux 的 .o 可执行文件（ET_EXEC） 可装载执行的文件类型 非 PIE 的 ELF 可执行程序 共享目标文件（ET_DYN） 用于共享库，也用于 PIE 可执行程序；能否直接运行取决于文件用途和装载要求 .so、PIE 可执行程序 核心转储文件（ET_CORE） 记录进程崩溃时的内存与执行状态 core 文件 静态库 .a 通常是多个目标文件组成的归档，不是单个 ET_REL 文件。Windows 的 .obj、.exe、.dll 仅可作用途类比，通常采用 COFF／PE 格式，不属于 ELF。\n目标文件的内容 ![ChatGPT Image 2026年7月2日 11_28_06](assets/ChatGPT Image 2026年7月2日 11_28_06.png)\n同一进程的线程共享地址空间，包括代码段、数据段、堆和 mmap 映射；每个线程有各自的执行栈、寄存器状态和 TLS 实例。线程栈仍位于共享地址空间中，并非其他线程不可访问的隔离内存。\n节 Section 主要描述文件中的代码、数据、符号等如何组织，供编译器和链接器使用。\n","permalink":"https://note.plhwiki.cn/learning/%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\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003eELF 文件类型\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e说明\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e实例\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e可重定位文件（ET_REL）\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e包含待链接的代码、数据和重定位信息\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eLinux 的 \u003ccode\u003e.o\u003c/code\u003e\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e可执行文件（ET_EXEC）\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e可装载执行的文件类型\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e非 PIE 的 ELF 可执行程序\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e共享目标文件（ET_DYN）\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e用于共享库，也用于 PIE 可执行程序；能否直接运行取决于文件用途和装载要求\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ccode\u003e.so\u003c/code\u003e、PIE 可执行程序\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e核心转储文件（ET_CORE）\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e记录进程崩溃时的内存与执行状态\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ccode\u003ecore\u003c/code\u003e 文件\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003e静态库 \u003ccode\u003e.a\u003c/code\u003e 通常是多个目标文件组成的归档，不是单个 ET_REL 文件。Windows 的 \u003ccode\u003e.obj\u003c/code\u003e、\u003ccode\u003e.exe\u003c/code\u003e、\u003ccode\u003e.dll\u003c/code\u003e 仅可作用途类比，通常采用 COFF／PE 格式，不属于 ELF。\u003c/p\u003e","title":"程序员自我修养"},{"content":"复杂性的本质 复杂性的定义 复杂性是指与软件系统结构有关的，使人难以理解和修改系统的所有因素。\n难以理解代码怎么工作 （理解） 难以在修bug时避免引入其他bug （修改） 从成本的角度上看，复杂系统小的修改需要耗费大量人力物力，而简单的系统，只需要较少的精力就能实现较大的改进\n计算公式：\n$$ C = \\sum_{p} c_{p} t_{p} $$ 系统的总体复杂度 C 由每个部分 p 的复杂度 cp 乘以开发者在该部分花的时间比例tp上。\n复杂性的表现形式 一般有3种表现形式\n变更放大 ​\t一个简单的需求，需要修改多个地方的不同代码。例如：修改网页的背景色，糟糕的设计是一个网页上指定背景色，如果多个网页需要修改背景色就要改动多个网页的代码，良好的设计：集中在一个地方指定网页的背景色，所有的网页引用这个共享值。\n认知负担 ​\t开发者需要了解多少知识才能完成任务。认知负担越多，意味着开发者需要花费更多的时间学习所需的信息，但这样可能会遗漏一些重要的信息，出现错误的风险也就越大。代码行数不等同于复杂性，有时候更多的代码行实现的方法，理解更加简单。\n未知的未知 ​\t不清楚必须修改哪些代码才能完成任务，或者开发者必须掌握哪些信息才能完成任务。例如：网站决定使用集中变量来设置背景色，但是有几个网页需要强调色进行内容的强调，如果背景色改变，而强调色也不行改变。开发者不得不搜索网站中搜索有强调色的网页\n未知的未知是最糟糕的，我们不清楚该怎么做，也不清楚解决方案是否行得通。\n复杂性的原因 依赖关系 ​\t当某一段代码不能孤立地理解和修改时，就存在依赖关系。依赖关系是软件的基本组成部分，无法完全消除事实上，我们在软件设计过程中有意引人了依赖关系 每次编写一个新类，都会在该类的应用程序接口周围创建依赖关系。\n模糊性 ​\t重要的信息不明显时，就会出现模糊性。例如：变量名称太笼统，没有额外的有用信息（如time），或者没有文档说明该变量的单位。在许多情况下，模糊性是文档不足造成的；但如果一个系统需要大量的文档，往往是设计不完善的表现。\n复杂性是增量的 复杂性并不是由单一的灾难性错误造成的，而**是由许多小问题累积而成的。**复杂性一旦起来就像很难消除，因此为了减缓复杂性，我们必须零容忍\n","permalink":"https://note.plhwiki.cn/learning/%E8%BD%AF%E4%BB%B6%E8%AE%BE%E8%AE%A1%E5%93%B2%E5%AD%A6/","summary":"\u003ch1 id=\"复杂性的本质\"\u003e复杂性的本质\u003c/h1\u003e\n\u003ch2 id=\"复杂性的定义\"\u003e复杂性的定义\u003c/h2\u003e\n\u003cp\u003e复杂性\u003cstrong\u003e是指与软件系统结构有关的，使人难以理解和修改系统的所有因素。\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e难以理解代码怎么工作 （理解）\u003c/li\u003e\n\u003cli\u003e难以在修bug时避免引入其他bug （修改）\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e从成本的角度上看，复杂系统小的修改需要耗费大量人力物力，而简单的系统，只需要较少的精力就能实现较大的改进\u003c/p\u003e","title":"软件设计哲学"},{"content":"与内存有关的api mmap ​\tmmap将文件或设备内存映射到进程地址空间的系统调用，常用于高效 I/O（如视频采集、文件读写）或进程间共享内存。在 V4L2（Video4Linux2）驱动中，它用来将内核的摄像头缓冲区映射到用户空间，以便直接访问图像数据，避免数据拷贝。\n函数原型： #include \u0026lt;sys/mman.h\u0026gt; void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); 参数说明\naddr：建议的映射起始地址（通常传 NULL，由内核选择）。 length：映射的字节数。 prot：内存保护模式（PROT_READ 可读，PROT_WRITE 可写，PROT_EXEC 可执行，PROT_NONE 不可访问）。 flags：映射类型和其他行为。 MAP_SHARED：映射内容对其它进程可见，且写回底层文件（或设备）。 MAP_PRIVATE：写时复制，修改不写回。 还可组合 MAP_FIXED、MAP_LOCKED 等。 fd：要映射的文件描述符（设备或文件）。 offset：文件偏移量（必须按系统页大小对齐，通常为 0 或设备指定的偏移）。 返回值\n成功：返回映射区的起始地址（void*）。 失败：返回 MAP_FAILED（即 (void*)-1），并设置 errno。 例子 以下为 C++ 片段，假设协商后 fmt.fmt.pix_mp.num_planes == 1，且 kCamBufCount 已采用 REQBUFS 返回的实际数量；多内存平面格式需逐平面映射，并在失败时清理此前映射。\nfor (uint32_t i = 0; i \u0026lt; kCamBufCount; i++) { // 1. 准备 v4l2_buffer 结构，查询缓冲区的内存信息 v4l2_plane planes[1]; memset(planes, 0, sizeof(planes)); v4l2_buffer buf; memset(\u0026amp;buf, 0, sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; buf.m.planes = planes; buf.length = 1; if (Xioctl(cam-\u0026gt;fd, VIDIOC_QUERYBUF, \u0026amp;buf) \u0026lt; 0) { perror(\u0026#34;VIDIOC_QUERYBUF\u0026#34;); return false; } // 2. 核心 mmap 调用 void *ptr = mmap(nullptr, // 由内核选择映射地址 planes[0].length, // 缓冲区大小（字节） PROT_READ | PROT_WRITE, // 用户映射的访问权限；V4L2 MMAP 通常要求同时指定 READ 和 WRITE MAP_SHARED, // 共享映射，驱动和用户空间共享同一块物理内存 cam-\u0026gt;fd, // 摄像头设备文件描述符 planes[0].m.mem_offset); // 缓冲区在设备内存中的偏移量 if (ptr == MAP_FAILED) { perror(\u0026#34;mmap camera buffer\u0026#34;); return false; } cam-\u0026gt;bufs[i].start = ptr; cam-\u0026gt;bufs[i].length = planes[0].length; // 3. 入队缓冲区，等待驱动填充数据 if (Xioctl(cam-\u0026gt;fd, VIDIOC_QBUF, \u0026amp;buf) \u0026lt; 0) { perror(\u0026#34;VIDIOC_QBUF (initial)\u0026#34;); return false; } } 映射成功后，ptr 是映射地址。QBUF 后缓冲区归驱动使用，应用应在 STREAMON 后等待成功 DQBUF，再读取该帧；处理完重新 QBUF，之后不可继续访问它直到再次 DQBUF。多平面 API 的有效数据范围由各平面的 bytesused 和 data_offset 描述。\n参考：QBUF / DQBUF。\n","permalink":"https://note.plhwiki.cn/linux/api/%E5%86%85%E5%AD%98/","summary":"\u003ch1 id=\"与内存有关的api\"\u003e与内存有关的api\u003c/h1\u003e\n\u003ch2 id=\"mmap\"\u003emmap\u003c/h2\u003e\n\u003cp\u003e​\tmmap将文件或设备内存映射到进程地址空间的系统调用，常用于高效 I/O（如视频采集、文件读写）或进程间共享内存。在 V4L2（Video4Linux2）驱动中，它用来将内核的摄像头缓冲区映射到用户空间，以便直接访问图像数据，避免数据拷贝。\u003c/p\u003e","title":"内存"},{"content":"获取当地时间 将时间戳转换为进程当前时区的当地时间；时区通常由 TZ 或系统默认时区决定。“TZ 时间”不是独立的时间标准。\nlocaltime time_t now = time(NULL); struct tm *tm_now = localtime(\u0026amp;now); printf(\u0026#34;%04d-%02d-%02d %02d:%02d:%02d\\n\u0026#34;, tm_now-\u0026gt;tm_year + 1900, tm_now-\u0026gt;tm_mon + 1, tm_now-\u0026gt;tm_mday, tm_now-\u0026gt;tm_hour, tm_now-\u0026gt;tm_min, tm_now-\u0026gt;tm_sec); localtime_r 适用于多线程，函数原型：\n/* 功能：将 time_t 类型的时间戳（自 1970-1-1 的秒数）转换为本地时间的 struct tm 结构体。 timep：指向 time_t 变量的指针。 result（仅 localtime_r）：用户提供的 struct tm 存储地址。 */ struct tm *localtime_r(const time_t *timep, struct tm *result); 例程：\ntime_t now = time(NULL); struct tm tm_now; localtime_r(\u0026amp;now, \u0026amp;tm_now); 获取UTC时间 gmtime struct tm *gmtime(const time_t *timer); // 非线程安全 gmtime_r /* 功能：将 time_t 类型的时间戳（自 Epoch 的秒数）转换为UTC 时间（协调世界时）。 timer：指向 time_t 变量的指针。 result（仅 gmtime_r）：用户提供的 struct tm 存储地址。 */ struct tm *gmtime_r(const time_t *timer, struct tm *result); time_t now = time(NULL); // 获取当前时间戳 struct tm tm_utc; // 用户分配的存储空间 if (gmtime_r(\u0026amp;now, \u0026amp;tm_utc) == NULL) { perror(\u0026#34;gmtime_r failed\u0026#34;); return -1; } printf(\u0026#34;UTC 时间：%04d-%02d-%02d %02d:%02d:%02d\\n\u0026#34;, tm_utc.tm_year + 1900, tm_utc.tm_mon + 1, tm_utc.tm_mday, tm_utc.tm_hour, tm_utc.tm_min, tm_utc.tm_sec); 本地时间转换为时间戳：mktime 把 struct tm 中的本地年月日时分秒按当前时区转换为 Unix 时间戳。下面示例显式设置 UTC+8，输出才对应北京时间；POSIX TZ 字符串中的符号方向与 UTC 偏移相反。\n/* POSIX 示例：需要 \u0026lt;stdlib.h\u0026gt;、\u0026lt;time.h\u0026gt; 和 \u0026lt;stdio.h\u0026gt;。 TZ 是进程级设置，应在启动阶段配置，避免多线程运行时反复修改。 */ if (setenv(\u0026#34;TZ\u0026#34;, \u0026#34;CST-8\u0026#34;, 1) != 0) { perror(\u0026#34;setenv\u0026#34;); return -1; } tzset(); struct tm local = {0}; local.tm_year = 2026 - 1900; local.tm_mon = 10 - 1; local.tm_mday = 10; local.tm_hour = 12; local.tm_min = 0; local.tm_sec = 0; local.tm_isdst = -1; // 把本地时间转换为 Unix 时间戳 time_t timestamp = mktime(\u0026amp;local); printf(\u0026#34;timestamp = %lld\\n\u0026#34;, (long long)timestamp); struct tm utc; struct tm bj; gmtime_r(\u0026amp;timestamp, \u0026amp;utc); localtime_r(\u0026amp;timestamp, \u0026amp;bj); printf(\u0026#34;UTC: %02d:%02d\\n\u0026#34;, utc.tm_hour, utc.tm_min); printf(\u0026#34;BJ : %02d:%02d\\n\u0026#34;, bj.tm_hour, bj.tm_min); /* timestamp = 1791604800 UTC: 04:00 BJ : 12:00 */ ","permalink":"https://note.plhwiki.cn/linux/api/%E6%97%B6%E9%97%B4/","summary":"\u003ch1 id=\"获取当地时间\"\u003e获取当地时间\u003c/h1\u003e\n\u003cp\u003e将时间戳转换为进程当前时区的当地时间；时区通常由 TZ 或系统默认时区决定。“TZ 时间”不是独立的时间标准。\u003c/p\u003e\n\u003ch2 id=\"localtime\"\u003elocaltime\u003c/h2\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\"\u003etime_t\u003c/span\u003e \u003cspan class=\"n\"\u003enow\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eNULL\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=\"k\"\u003estruct\u003c/span\u003e \u003cspan class=\"n\"\u003etm\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"n\"\u003etm_now\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003elocaltime\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e\u003cspan class=\"n\"\u003enow\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=\"nf\"\u003eprintf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;%04d-%02d-%02d %02d:%02d:%02d\u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\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\"\u003etm_now\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003etm_year\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"mi\"\u003e1900\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\"\u003etm_now\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003etm_mon\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\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\"\u003etm_now\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003etm_mday\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\"\u003etm_now\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003etm_hour\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\"\u003etm_now\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003etm_min\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\"\u003etm_now\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"n\"\u003etm_sec\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=\"localtime_r\"\u003elocaltime_r\u003c/h2\u003e\n\u003cp\u003e适用于多线程，函数原型：\u003c/p\u003e","title":"时间"},{"content":"Linux三板斧 U-Boot 编译uboot MC6830 export PATH=/home/meian/work/toolChain/molchipv500-armgcc-uclibc/bin:$PATH make boot myboard=mc6830/demoboard 产物\nA1 产物\n**u-boot.bin**只包含 U-Boot proper（第二阶段引导程序)\n**u-boot-with-spl.bin **= SPL（第一阶段） + U-Boot proper（第二阶段） + 可能的对齐/头部/校验/填充。\nBootROM 下面是常见的 BootROM → SPL → U-Boot proper 流程，具体初始化职责和加载格式由 SoC／SDK 决定，不能套用于所有芯片。\n存放在Soc内部的ROM，这个ROM的size很小。\n作用：\n**确定启动源 **,根据Boot 引脚，决定从SPI NOR 还是SPI NAND ,eMMC、SD 卡启动。 **初始化最最基础的硬件，**加载 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的命令 查看启动参数 bootcmd： 告诉 \u0026ldquo;U-Boot“怎么启动 Linux\u0026rdquo;，uboot上电倒计时结束运行的就是这个（run bootcmd）。\n在 U-Boot 交互命令行中查看\nprintenv bootcmd bootargs：告诉 Linux 内核 \u0026ldquo;启动之后怎么运行\u0026rsquo;\u0026rsquo; ，里面的内容是传递给Linux内核\n查看 /proc/cmdline cat /proc/cmdline mem=160M earlycon console=ttyS0,115200 root=/dev/mtdblock3 rootfstype=jffs2 rw mtdparts=spi-nor:512K(uboot),128K(logo),2816K(kernel),8192K(rootfs),21120K(app) sf read 用来读取flash的内存数据\nsf read \u0026lt;RAM地址\u0026gt; \u0026lt;SPI Flash偏移\u0026gt; \u0026lt;长度\u0026gt; sf read 0x80008000 0xA0000 2C0000 编译内核 君正A1N\nmake ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- distclean make ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- clean #设置 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删除 cp my_config_backup .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 clean 它只删除编译生成的中间文件（.o 文件）和最终的内核镜像。 它不会删除 .config 文件，也不会删除编译过程中自动生成的一些依赖文件和配置缓存。 适用场景：你只是修改了某个驱动代码，想快速重新编译一下。 make mrproper（彻底清理）： 它会删除所有编译生成的文件，包括 .o、内核镜像、.config 配置文件，以及各种自动生成的依赖（如 .depend、.version 等）。它会将源码树恢复到刚解压时的“最干净”状态。 适用场景：“环境乱掉了”，比如你切换了交叉编译器、修改了架构（ARCH），或者遇到了一些莫名其妙的链接错误、头文件找不到的错误。 make distclean（最彻底）： 比 mrproper 还要狠，它不仅清理编译产物，还会删除编辑器产生的备份文件（如 *~）和补丁文件（*.rej）。一般用于准备打包发布源码。 添加新选项 #添加新选项 make ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- menuconfig #编译内核 make ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- -j8 uImage 内核的产物 一般放在arch/架构/boot里\nuImage legacy uImage 是 64 字节 U-Boot image header + 镜像载荷；载荷由架构和构建目标决定，不一定是 zImage 可用 mkimage 为内核载荷添加头部，其中记录实际采用的压缩方式、加载地址、入口地址等信息 一般由bootm启动 uImage\r┌────────────────────────────┐\r│ U-Boot legacy header 64B │\r├────────────────────────────┤\r│ zImage / compressed kernel │\r└────────────────────────────┘ 这个U-Boot 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 编译得到的dtb可以有两种方式添加到内核\n内置方式 在.config里\nCONFIG_BUILTIN_DTB=y\rCONFIG_MIPS_NO_APPENDED_DTB=y 设备树专题见：dts.md。\n制作根文件系统 ​\t成功安装 SDK 后，在 resource/rootfs 目录中存放已制作好的根文件系统，可以跳过文件系统的制作，直接使用 SDK 中提供的文件系统。如果用户如果有特殊需求，可在 busybox 的基础上制作根文件系统。制作根文件系统的具体操作步骤如下\n**第一步：**建立根文件系统文件 rootfs\n#mkdir rootfs #cd rootfs #cp -a opensource/busybox/_install/. ./ #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 -a）；不能把开发主机的 /dev 节点不加区分地搬到目标板。\n​\tlib 目录是存放应用程序所需要的库文件，根据应用程序需要拷贝相应的库文件。\n文件系统的选择 对于 NorFlash Based 系统，存储空间一般较小，因此会选用压缩文件系统。推荐以下两种文件系统：\nA. squashfs：只读文件系统，压缩率高\rB. jffs2：可读写文件系统，可选择压缩方式 ​\t推荐的系统搭建的方案是系统 rootfs 以及不需要经常修改的系统分区采用squashfs 文件系统，而配置分区 system 等需要经常读写的分区采用 jffs2 文件系统。\nSquashfs 文件系统 ​\tsquashfs 文件系统是一套基于 Linux 内核使用的压缩只读文件系统，压缩率高。squashfs 具有如下特点：\n​\t**优点：**压缩率高，数据(data)，节点(inode)和目录(directories)都被压缩，可以节省flash 空间，Squashfs 4.0 支持 64 位文件及文件系统大小，不限于 4 GiB；实际还受工具版本、内核和设备限制。\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 在 JFFS 基础上改进的文件系统，是用于微型嵌入式设备的原始闪存芯片的实际文件系统。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 会由用户态 mdev 扫描 sysfs 中已存在的设备信息，把已经存在的硬件信息找出来，批量在 /dev 下生成节点。 如果不执行：仅在 /dev 没有静态节点、devtmpfs 或其他设备管理器负责填充时，才可能缺少节点；使用 devtmpfs 的系统并非必须靠 mdev -s 创建设备节点。 它是如何工作的？（原理） 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;业务可以运行\u0026rdquo;。既是用户空间初始化管理者，也是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，则代表该用户不允许登录系统（常用于系统服务账号）。 常用 自定义函数 在uboot阶段，自定义函数并调用\n在include/command.h\n#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)\t\\ U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL) U_BOOT_CMD( sata_power, 2, 2, do_sata_power, \u0026#34;sata_power 0/1.{######### add by lifan@2022.06.22 ########}\\n\u0026#34;, \u0026#34;\\t- sata_power 0/1 ;(0 - ON , 1 - OFF) \\n\u0026#34; ); 位置 值 含义 _name sata_power 命令名 _maxargs 2 最大参数个数，包含命令名本身 _rep 2 是否可重复；非 0 即可。惯例写 0/1，写 2 效果等同 1 _cmd do_sata_power 处理函数 _usage 短帮助 敲 help（无参数）列出全部命令时 _help 长帮助 敲 help sata_power，或命令返回 CMD_RET_USAGE（-1）时 操作寄存器 md = memory display，读内存 mw = memory write，写内存 .l = long word，每次按 32 bit / 4 字节 操作 md.l \u0026lt;address\u0026gt; \u0026lt;count\u0026gt; 配置gpio uboot PIN Mux 把物理的PAD 切换到GPIO。PAD（焊盘）可以理解为芯片的的\u0026quot;物理引脚（管脚）控制器\u0026quot;\n引脚对应Linux gpio编号 例如 PE16对应gpio144\nPA = 0 × 32\rPB = 1 × 32\rPC = 2 × 32\rPD = 3 × 32\rPE = 4 × 32\rPE16 = 4 × 32 + 16 = 144 参考：Squashfs 4.0；U-Boot mkimage。\n","permalink":"https://note.plhwiki.cn/linux/boot/%E7%B3%BB%E7%BB%9F%E6%9E%84%E5%BB%BA%E4%B8%8E%E5%90%AF%E5%8A%A8/","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\u003ch4 id=\"mc6830\"\u003e\u003cstrong\u003eMC6830\u003c/strong\u003e\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eexport\u003c/span\u003e \u003cspan class=\"nv\"\u003ePATH\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e/home/meian/work/toolChain/molchipv500-armgcc-uclibc/bin:\u003cspan class=\"nv\"\u003e$PATH\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emake boot \u003cspan class=\"nv\"\u003emyboard\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003emc6830/demoboard\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e产物\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"/linux/boot/assets/image-20260819103939845.png\" alt=\"image-20260819103939845\" loading=\"lazy\"\u003e\n\u003c/p\u003e\n\u003ch4 id=\"a1\"\u003e\u003cstrong\u003eA1\u003c/strong\u003e\u003c/h4\u003e\n\u003cp\u003e\u003cimg src=\"/linux/boot/assets/image-20260105185419377.png\" alt=\"image-20260105185419377\" loading=\"lazy\"\u003e\n\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e产物\u003c/strong\u003e\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":"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 Bash 扩展写法；BusyBox ash 的支持取决于版本和配置，兼容 POSIX sh 时用 \u0026gt; file 2\u0026gt;\u0026amp;1 \u0026lt; file 从文件读 stdin 持久化环境变量 写进 ~/.bashrc（当前用户）或 /etc/profile（全局），常用于把工具链、代理等加进 export PATH / export XXX。工具链一键配置见 交叉编译工具链。\n文件与目录操作 分类：复制/移动/删除 · 链接 · 时间戳 · 归档压缩 · 查找\ncp 复制文件/目录。\ncp -a src/ dst/ # 归档式复制：保留权限/属主/软链接/时间戳（= -dpR） （GNU）-a：相当于 -dR \u0026ndash;preserve=all，递归并尽量保留链接关系、权限、时间戳、扩展属性等；BusyBox 支持的属性范围可能不同 （GNU）-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 指定归档文件，其参数是文件名；不要求 -f 是整条命令的最后一个选项。例如 tar -f archive.tar -t 合法 -z gzip / -j bz2 / -J xz / -Z compress(.Z) -v 显示过程 / -O 解到标准输出 -t 列出归档包里的文件列表 （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 {} +：批量删除匹配项 环境差异：-exec ... {} + 是 POSIX 规定的批量调用形式；BusyBox 是否支持取决于版本和 FEATURE_FIND_EXEC_PLUS 配置，不支持时可使用 -exec ... {} \\;。\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 的选项取决于版本与编译配置；部分版本支持 \u0026ndash;include/\u0026ndash;exclude，不能笼统判定均不支持。板上以 grep --help 为准。\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 传输单个文件\nscp /path/to/local/file username@192.168.2.223:/path/to/remote/destination nc 一个负责监听端口并把收到的数据写入文件，另一个负责连接该端口并把文件内容发送过去。\n#在开发板上运行 nc 接收文件 nc -l -p 8888 \u0026gt; gui_client_test -l 监听 -p 指定端口 \u0026gt; gui_client_test 把收到的网络数据重定向并保存为这个文件名 注意：执行这条命令后，开发板的终端会卡住不动，这是正常的，因为它在等待接收数据。\n#在Ubuntu 服务器上运行 nc 发送文件 nc 192.168.2.223 8888 \u0026lt; gui_client_test 192.168.2.223：你的开发板 IP。 8888：刚才在开发板上指定的端口号。 \u0026lt; gui_client_test：把本地的文件内容作为输入发送出去。 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,test\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：文件数 file ​\tfile 是 Linux 下用来判断文件真实类型的命令。它最大的特点是：主要不是看扩展名，而是读取文件内容、文件头和 magic 特征来判断类型。\n-d debug 模式：它会在判断文件类型时，把 file 内部匹配 magic database 的调试信息打印出来，主要用于排查 file 为什么把某个文件识别成某种类型。 findmnt findmnt 用来查看 Linux 当前的挂载关系，比直接看 /proc/mounts 或 mount 的输出更清晰。\nTARGET 挂载点 SOURCE 挂载来源，例如 /dev/sdb1 FSTYPE 文件系统类型，例如 ext4、nfs、tmpfs OPTIONS 挂载参数 findmnt -T /home/meian/work TARGET SOURCE FSTYPE OPTIONS /home /home esn_cfs rw,relatime 磁盘、分区与文件系统 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 dd是按字节/块复制数据工具\nif (input file) of (out file) ⚠️ 按扇区裸写，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（旧工具按此公式调整后的 used，不等于应用实际占用）、free = Mem.free + buffers（旧工具给出的粗略估计，不等于现代 MemAvailable） 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 是需要结合磁盘 await、队列及负载继续排查的信号，不足以单独认定硬盘瓶颈；高 idle 下的响应慢也可能由锁、网络等等待造成，不能直接得出应加内存的结论。\n网络配置与抓包 ⚠️ 改网络配置可能断掉当前 SSH/telnet 连接，远程操作前想好恢复手段。\nroute [root@Ingenic-uc1_1:~]# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.2.1 0.0.0.0 UG 0 0 0 eth0 192.168.2.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0 192.168.11.0 0.0.0.0 255.255.255.0 U 0 0 0 eth1 Destinatio：目标网络 Gateway：下一跳IP 0.0.0.0表示直连 Genmask：掩码 Flags：路由属性 U （Up） 路由有效 G （Gateway）要经过网关 H （主机路由） Iface：出口网卡 ip route # ip route default via 10.49.93.1 dev usb0 10.49.93.0/24 dev usb0 scope link src 10.49.93.93 192.168.2.0/24 dev eth0 scope link src 192.168.2.166 Linux 路由匹配遵循 最长前缀匹配（Longest Prefix Match）。 能匹配 /24 的，就不会走 /0 的 default。\n默认路由 default via 10.49.93.1 dev usb0 等价于 0.0.0.0/0 via 10.49.93.1 dev usb0\n如果目标 IP 没有匹配到更具体的路由，就把数据包从 usb0 发出去，并交给下一跳网关 10.49.93.1。\n例如访问8.8.8.8 ，都不属于10.49.93.0/24 -\u0026gt;走默认-\u0026gt;ubs0\n4G 的直连网段 10.49.93.0/24 dev usb0 scope link src 10.49.93.93\n10.49.93.0/24 表示 10.49.93.0 ~10.49.93.255\nscope link\n删除路由 route del default gw 192.168.2.1 dev eth0 ifconfig / 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 为 init 的系统上，systemd 作为 PID 1 管理服务；BusyBox init 等系统并不使用它。以下以 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：首个参数可为绝对路径，也可为不含斜杠的简单文件名（按 systemd 的搜索规则查找）；写绝对路径更明确，不会自动经过 shell 解析 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） 内核事件可包含 DEVNAME=sdb；ID_SERIAL 等通常由 udev 补充，直接订阅内核 uevent 时不保证存在 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; } struct sockaddr_nl nladdr = {}; nladdr.nl_family = AF_NETLINK; nladdr.nl_pid = 0; // 由内核分配 port ID，避免同进程多个 netlink socket 冲突 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) { // 数据报长度为 0；不能按 TCP 的 EOF 解释 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：显示动态符号表；动态链接的可执行文件也可能有 .dynsym，不只有 .so。常规符号表被 strip 后，仍可用 nm -D 查看保留的动态符号（其中也包括未定义的导入符号） -C：demangle，把 C++ 修饰后的符号名还原成可读形式 -g ：只显示外部可见的符号（全局符号或弱符号） -A：显示符号来自哪一个.o文件 --defined-only：只显示已经定义的符号，过滤掉未定义符号（U） tolerance.o: U close U _disk_path_redirect U do_cmd_get_capacity U _find_disk_by_id U free U g_mafs 000001a8 R head_blocks U mafs_buffer_read_disk U mafs_buffer_write_disk U mafs_hdd_fd 000002e8 T mafs_search_next_block 00000810 T mafs_search_prev_block 00000d78 T mafs_tolerance_clear 常见符号的含义：\n类型 含义 常见示例 T 全局代码符号（.text） 普通全局函数 t 局部代码符号（.text） static 函数 R 全局只读数据符号 全局 const 数组 r 局部只读数据符号 static const 数据 D 已初始化的全局数据符号 int g_count = 10; d 已初始化的局部数据符号 static int count = 10; B 未初始化的全局数据符号 int g_count; b 未初始化的局部数据符号 static int count; U 未定义符号 引用其他文件中的函数或变量 W 弱符号（能被同名强符号覆盖） __attribute__((weak)) 定义的符号 大写字母通常表示全局符号（Global）。 小写字母通常表示局部符号（Local）。 常见故障与排查案例 排查速查对照 现象 先看什么 命令 磁盘满 / 写失败 磁盘、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；注释必须独占一行 检测静态库的构成 readelf 虚拟机磁盘扩容 现象：在 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参考：GNU cp；free(1)；systemd.service。\n","permalink":"https://note.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文件属性：磁盘中的存储位置、文件长度等信息 时间戳：atime（访问）、mtime（内容修改）、ctime（inode 状态变更）；ctime 不是创建时间，创建时间 btime 是否可用取决于文件系统和接口 读写权限：通常在 open 等获取访问权限时检查文件权限；后续 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匿名管道 Linux 匿名管道提供单向、基于内核缓冲的字节流通信，常通过 fork 继承描述符使用。读写只在特定条件下阻塞，也可设置 O_NONBLOCK；应关闭不需要的描述符以正确触发 EOF 或 SIGPIPE。\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/EPIPE，写满后可能一直阻塞 管道容量有限；很多 Linux 配置默认 16 页，但可能受内核版本和资源限制影响，可用 F_GETPIPE_SZ 查询，不能硬编码为 64 KiB 匿名管道没有用于重新 open 的路径；但可通过 Unix 域 socket 的 SCM_RIGHTS 把描述符传给非亲缘进程，因此不是绝对不能用于非亲缘进程 多个写端写入时是原子化的？ 不超过 PIPE_BUF（Linux 为 4096 字节）的单次写入保证不与其他写者交错；非阻塞且空间不足时会返回 EAGAIN 大写可能导致数据交错 管道是流式无格式通信，没有消息边界，需要自己定义协议格式（分包、长度） 问：如果读端进程已经关闭（close(pipefd[0])），写进程继续 write 会发生什么？\n答 write 失败，收到 SIGPIPE\n因为匿名管道有一个特殊规则：\n规则：当所有读端被关闭时： write 不再阻塞 向调用进程产生 SIGPIPE 信号 若该信号被忽略、阻塞或处理后返回，write 才有机会返回 -1 并设置 errno 为 EPIPE → 如果进程没有处理该信号，会被终止！ 这就是“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 行为会阻塞 以下讨论阻塞模式。加 O_NONBLOCK 时，只读 open 可立即成功；只写 open 在没有读端时失败并返回 ENXIO。\n打开 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，数据不会写入 FIFO 路径对应的存储文件 通过 等待队列 + 锁 实现同步与阻塞 都是“流式文件”（Stream） 不支持 lseek 只能顺序读 / 顺序写 都是 FIFO（先进先出） 先写入的数据，先被读出 没有消息边界（除非你自己定义协议） 共享内存 D-BUS D-BUS**（Desktop Bus）是一种“消息总线式”的进程间通信（IPC）机制。**用于让不同进程互相通信\n特点：\n多个进程共享一个**“软件总线”** 广播消息 点对点 ：A 进程直接发给 B 进程 System Bus（系统总线） +\nUDS UNIX domain socket：允许位于同一主机系统上的进程之间相互通信的\nstruct sockaddr_un { sa_family_t sun_family; /* Address family (AF_UNIX) */ char sun_path[108]; /* Path name */ }; #define SOCKET_PATH \u0026#34;/tmp/uds_socket\u0026#34; #define BUFFER_SIZE 1024 int server_fd; struct sockaddr_un server_addr; char buffer[BUFFER_SIZE]; // 创建 Unix 域套接字 server_fd = socket(AF_UNIX, SOCK_STREAM, 0); if (server_fd == -1) { perror(\u0026#34;socket\u0026#34;); exit(EXIT_FAILURE); } // 初始化服务器地址结构 memset(\u0026amp;server_addr, 0, sizeof(server_addr)); server_addr.sun_family = AF_UNIX; strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1); // 绑定套接字到地址 if (bind(server_fd, (struct sockaddr *)\u0026amp;server_addr, sizeof(server_addr)) == -1) { perror(\u0026#34;bind\u0026#34;); exit(EXIT_FAILURE); } 时间 设置时区 下面是该开发板所用 libc／根文件系统约定的 /etc/TZ -\u0026gt; /tmp/TZ 配置，不是所有 Linux 通用。glibc 系统通常读取 TZ 环境变量或 /etc/localtime，不能只创建 /etc/TZ 就假定生效。\n[root@Ingenic-uc1_1:~]# ls -l /etc/TZ lrwxrwxrwx 1 root root 9 Aug 15 11:20 /etc/TZ -\u0026gt; ../tmp/TZ [root@Ingenic-uc1_1:~]# cat /tmp/TZ GMT-08:00 需要注意的是，POSIX TZ 的正负号和我们日常说的 UTC 偏移方向是反的。\n查看时间 date -u 查看的是UTC 时间 [root@Ingenic-uc1_1:~]# date Fri Sep 11 10:07:01 GMT 2026 [root@Ingenic-uc1_1:~]# date -u Fri Sep 11 02:07:25 UTC 2026 获取时间 如果通过 TZ 配置了时区，比如东八区，那么按标准 Linux/POSIX 的用法：\n获取 UTC 时间：用 gmtime() / gmtime_r() 获取 东八区本地时间：用 localtime() / localtime_r() time_t now = time(NULL); struct tm utc_tm; struct tm local_tm; gmtime_r(\u0026amp;now, \u0026amp;utc_tm); // UTC+0 localtime_r(\u0026amp;now, \u0026amp;local_tm); // 根据 TZ 转成本地时间 参考：pipe(7)；unix(7)；inode(7)；tzset(3)。\n","permalink":"https://note.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时间戳：atime（访问）、mtime（内容修改）、ctime（inode 状态变更）；ctime 不是创建时间，创建时间 btime 是否可用取决于文件系统和接口\u003c/li\u003e\n\u003cli\u003e读写权限：通常在 open 等获取访问权限时检查文件权限；后续 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":"Page Cache ​\tPage Cache 就是内核为了加速文件读写而在内存中保留的文件页。普通文件的缓冲 I/O 通常经过 Page Cache；缓存未命中时仍可能发起存储 I/O。O_DIRECT 等路径可绕过 Page Cache，因此不能把所有 read/write 都视为缓存访问。\n产生 调用\nwrite(fd, buffer, len); 这个时候你只是拿着一个缓冲区（Userspace Page），把数据交给内核。\n内核检查：\n文件在该 offset 对应的 page cache 是否存在？ 如果不存在，缓冲写路径会按需分配缓存页；部分页写入还可能需要先读入原数据。这不等同于 CPU 访问未映射虚拟地址触发的 page fault。 写入通常先把缓存页标记为 dirty，随后回写；write 返回成功本身不代表已经持久化到存储介质。 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 — 系统从磁盘换入／读入的数据速率（KiB/s） 定义：sar 报告的系统分页读入速率，单位 KiB/s，不是应用 read 调用读取的字节数。\n缓存命中的读取不需要磁盘 I/O，不能根据应用读取量直接推算此值。\npgpgout/s — 系统向磁盘换出／写出的数据速率（KiB/s） 定义：sar 报告的系统分页写出速率，单位 KiB/s，不等于应用 write 调用的即时写入速率。\n你的程序 write 文件 → 内核把 dirty page 刷到磁盘 → pgpgout/s 上升。\nfault/s — 全部 page faults（major + minor） 定义：每秒缺页次数，包含 major 和 minor；minor 的速率可由 fault/s − majflt/s 得到。\n缺页不一定需要 I/O，例如首次建立映射、写时复制等都可能产生 minor fault。\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参考：sar 手册；Page Cache。\n","permalink":"https://note.plhwiki.cn/linux/kernel/%E5%86%85%E5%AD%98/","summary":"\u003ch1 id=\"page--cache\"\u003ePage  Cache\u003c/h1\u003e\n\u003cp\u003e​\tPage Cache 就是内核为了加速文件读写而在内存中保留的文件页。普通文件的缓冲 I/O 通常经过 Page Cache；缓存未命中时仍可能发起存储 I/O。O_DIRECT 等路径可绕过 Page Cache，因此不能把所有 read/write 都视为缓存访问。\u003c/p\u003e","title":"内存"},{"content":"模块机制 可加载模块的概念 ​\nLinux 是支持可加载模块的宏内核。模块可以在运行时加载或卸载，加载后仍在内核空间执行。\n微内核把部分服务移到用户空间，与“宏内核支持模块”是两回事。跨进程通信可能引入开销，但不能据此笼统断言微内核一定低性能。\n内核许可声明 协议分类 ","permalink":"https://note.plhwiki.cn/linux/kernel/%E6%A8%A1%E5%9D%97%E6%9C%BA%E5%88%B6/","summary":"\u003ch1 id=\"模块机制\"\u003e模块机制\u003c/h1\u003e\n\u003ch2 id=\"可加载模块的概念\"\u003e可加载模块的概念\u003c/h2\u003e\n\u003cp\u003e​\u003c/p\u003e\n\u003cp\u003eLinux 是支持可加载模块的宏内核。模块可以在运行时加载或卸载，加载后仍在内核空间执行。\u003c/p\u003e\n\u003cp\u003e微内核把部分服务移到用户空间，与“宏内核支持模块”是两回事。跨进程通信可能引入开销，但不能据此笼统断言微内核一定低性能。\u003c/p\u003e","title":"模块机制"},{"content":"进程调度 进程 进程是程序在一个数据集合上的一次动态执行过程。它不仅包含代码，还包含当前的活动状态、内存、系统资源等。进程可以被分为 I/O 密集型（I/O-bound）和 CPU 密集型（CPU-bound）\nI/O 密集型进程 大部分时间都在等待\n数据库 摄像头 网络 网络 策略 策略决定调度程序什么时候让进程运行。\n","permalink":"https://note.plhwiki.cn/linux/kernel/%E8%BF%9B%E7%A8%8B%E8%B0%83%E5%BA%A6/","summary":"\u003ch1 id=\"进程调度\"\u003e进程调度\u003c/h1\u003e\n\u003ch2 id=\"进程\"\u003e进程\u003c/h2\u003e\n\u003cp\u003e进程是程序在一个数据集合上的\u003cstrong\u003e一次动态执行过程\u003c/strong\u003e。它不仅包含代码，还包含当前的活动状态、内存、系统资源等。进程可以被分为 I/O 密集型（I/O-bound）和 CPU 密集型（CPU-bound）\u003c/p\u003e","title":"进程调度"},{"content":"TCP建立连接的过程 listen listen 将 socket 标记为被动监听状态。理解正常握手时，可以区分未完成握手的请求和已完成握手、等待 accept 的连接；开启 SYN cookies 等机制时，不一定为每个 SYN 保存半连接记录。\n全连接队列 Linux 中，backlog 控制已完成握手、等待 accept 的连接队列上限；超过 net.core.somaxconn 的请求值会被截到该上限。具体边界行为和内部结构以内核版本为准。\n半连接队列 未完成握手的请求受 tcp_max_syn_backlog、内核实现和 SYN cookies 等因素影响。旧版本源码中的哈希表大小计算不能作为所有 Linux 版本通用的队列容量公式。\nconnect ​\t客户端在执行connect函数的时候，会把本地socket状态设置为TCP_SYN_SENT,选择一个可用的端口，接着发出SYN握手请求并启动重传定时器。（一个客户端最大能建立的连接数不是65535，只要服务器足够多，单机可能建立百万级连接，实际还受文件描述符、内存、端口分配方式等限制）\n连接建立过程总结 服务端 服务端的核心就是创建socket绑定端口、listen监听、最后accept客户端的请求\nint main(){ int fd =socket(AF_INET,SOCK_STREAM,0); bind(fd,..); listen(fd,128); accept(fd,..); } 客户端 主要是创建socket，调用connect连接服务器\nint main(){ int fd =socket(AF_INET,SOCK_STREAM,0); connect(fd,..); ... } sequenceDiagram autonumber participant C as 客户端 participant S as 服务端 TCP/IP 协议栈 participant Q1 as 半连接队列 participant Q2 as 全连接队列 participant A as 服务端应用程序 Note over S,A: listen()\u0026lt;br/\u0026gt;申请并初始化接收队列 C-\u0026gt;\u0026gt;C: 选择可用的本地端口 C-\u0026gt;\u0026gt;S: SYN Note left of C: connect()\u0026lt;br/\u0026gt;1. 发送 SYN 握手请求\u0026lt;br/\u0026gt;2. 启动重传定时器 S-\u0026gt;\u0026gt;Q1: 创建半连接记录 S--\u0026gt;\u0026gt;C: SYN + ACK Note right of S: 1. 发送 SYN+ACK\u0026lt;br/\u0026gt;2. 进入半连接队列\u0026lt;br/\u0026gt;3. 启动重传定时器 C-\u0026gt;\u0026gt;C: 清除 SYN 重传定时器 C-\u0026gt;\u0026gt;C: 设置连接状态为已连接 C-\u0026gt;\u0026gt;S: ACK S-\u0026gt;\u0026gt;S: 创建新的已连接 socket S-\u0026gt;\u0026gt;Q1: 从半连接队列移除 S-\u0026gt;\u0026gt;Q2: 加入全连接队列 A-\u0026gt;\u0026gt;S: accept() S-\u0026gt;\u0026gt;Q2: 从全连接队列取出 socket S--\u0026gt;\u0026gt;A: 返回已连接 socket 客户端：调用connect，把本地的socket状态设置为TCP_SYN_SENT，选择一个可用端口发出SYN握手请求并启动重传定时器。 服务端：正常情况下记录请求并返回 SYN+ACK，等待第三次 ACK；队列溢出时的丢弃、SYN cookies 等处理取决于内核和配置。 客户端：收到合法 SYN+ACK 后进入 ESTABLISHED 并发送 ACK。TCP keepalive 只有设置 SO_KEEPALIVE 才启用，并非建立连接就自动启用。 服务器：响应第三次ACK，把当前的半连接对象删除，创建新的socket加入到全连接队列，最后设置连接状态为ESTABLISHED 假设双向单程延迟近似相等，忽略处理时间与重传时，客户端通常在约 1 RTT 后收到 SYN+ACK，阻塞式 connect 可返回；服务端约在 1.5 RTT 后收到第三次 ACK。这是不同观察点，不能统一说建连耗时为 1.5 RTT。\nRTT（Round-Trip Time 往返时延）：指的是一个数据包从 A 发到 B，再从 B 返回 A，整个来回所花的时间。\n参考：listen(2)；tcp(7)。\n","permalink":"https://note.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 将 socket 标记为被动监听状态。理解正常握手时，可以区分未完成握手的请求和已完成握手、等待 accept 的连接；开启 SYN cookies 等机制时，不一定为每个 SYN 保存半连接记录。\u003c/p\u003e","title":"linux网络"},{"content":"核心结构体 核心组件作用\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;//single-planar 的有效数据字节数；multi-planar 使用 planes[i].bytesused __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;//single-planar 为缓冲区字节数；multi-planar 为 m.planes 数组的元素数 __u32\treserved2; union { __s32\trequest_fd; __u32\treserved; }; }; 参考：V4L2 buffer。\n","permalink":"https://note.plhwiki.cn/multimedia/v4l2/v4l2%E6%A0%B8%E5%BF%83%E7%BB%93%E6%9E%84%E4%BD%93/","summary":"\u003ch1 id=\"核心结构体\"\u003e核心结构体\u003c/h1\u003e\n\u003cp\u003e\u003cstrong\u003e核心组件作用\u003c/strong\u003e\u003c/p\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003e组件\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e功能\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ccode\u003evideo_device\u003c/code\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e设备节点注册（创建 \u003ccode\u003e/dev/videoX\u003c/code\u003e）\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ccode\u003ev4l2_device\u003c/code\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e设备逻辑管理（电源 / 子设备协调）\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ccode\u003evb2_queue\u003c/code\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e缓冲区队列管理（核心创新点）\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ccode\u003ev4l2_subdev\u003c/code\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e子设备抽象（传感器 / ISP 等）\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"struct-v4l2_buffer\"\u003estruct v4l2_buffer\u003c/h2\u003e\n\u003cp\u003e用来描述\u0026quot;一块视频缓冲区\u0026quot;的所有元信息。它的常用字段大致有：\u003c/p\u003e","title":"v4l2核心结构体"},{"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\n","permalink":"https://note.plhwiki.cn/reference/%E5%AD%A6%E4%B9%A0%E8%B5%84%E6%BA%90/","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":"学习资源"},{"content":"命令测试 播放视频 gst-play-1.0 --flags=3 \\ --videosink=\u0026#34;fpsdisplaysink video-sink=xvimagesink signal-fps-measurements=true text-overlay=false sync=false\u0026#34; \\ --audiosink=\u0026#34;alsasink device=hw:1,0\u0026#34; \\ /root/nfs/test2.mp4 参数 flag 这个参数控制 playbin 开启哪些功能，本质上是一个 bit mask（位掩码）\nbit 0 = 1 VIDEO\rbit 1 = 2 AUDIO\rbit 2 = 4 TEXT / subtitle flag=3 意味着开启视频和音频，但不启用字幕 TEXT。\n","permalink":"https://note.plhwiki.cn/multimedia/gstream/gstreamer/","summary":"\u003ch1 id=\"命令测试\"\u003e命令测试\u003c/h1\u003e\n\u003ch2 id=\"播放视频\"\u003e播放视频\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egst-play-1.0 --flags\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"m\"\u003e3\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  --videosink\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;fpsdisplaysink video-sink=xvimagesink signal-fps-measurements=true text-overlay=false sync=false\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  --audiosink\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;alsasink device=hw:1,0\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  /root/nfs/test2.mp4\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"参数\"\u003e\u003cstrong\u003e参数\u003c/strong\u003e\u003c/h2\u003e\n\u003ch3 id=\"flag\"\u003eflag\u003c/h3\u003e\n\u003cp\u003e这个参数控制 \u003ccode\u003eplaybin\u003c/code\u003e 开启哪些功能，本质上是一个 \u003cstrong\u003ebit mask（位掩码）\u003c/strong\u003e\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003ebit 0 = 1    VIDEO\r\nbit 1 = 2    AUDIO\r\nbit 2 = 4    TEXT / subtitle\n\u003c/code\u003e\u003c/pre\u003e\u003cblockquote\u003e\n\u003cp\u003eflag=3 意味着开启视频和音频，但不启用字幕 TEXT。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"gstreamer"},{"content":"","permalink":"https://note.plhwiki.cn/hardware/ddr/","summary":"","title":"ddr"},{"content":"MIPI接口 MIPI有多个\n","permalink":"https://note.plhwiki.cn/hardware/mipi/","summary":"\u003ch1 id=\"mipi接口\"\u003eMIPI接口\u003c/h1\u003e\n\u003cp\u003eMIPI有多个\u003c/p\u003e","title":"mipi"},{"content":"Shell语法 case case \u0026#34;$变量\u0026#34; in 模式1) 命令 ;; 模式2) 命令 ;; esac )：表示这个匹配模式写完了，后面开始执行这个分支的命令。 ;;：表示这个分支结束，类似 C/C++ switch 里的 break。 |：表示“或者”，例如 bound|renew) 表示匹配 bound 或 renew。 in：开始列出匹配规则。 esac：结束 case，就是 case 倒过来写。 ","permalink":"https://note.plhwiki.cn/linux/common/shell/","summary":"\u003ch1 id=\"shell语法\"\u003eShell语法\u003c/h1\u003e\n\u003ch2 id=\"case\"\u003ecase\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003ecase\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e$\u003cspan class=\"s2\"\u003e变量\u0026#34;\u003c/span\u003e in\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    模式1\u003cspan class=\"o\"\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=\"p\"\u003e;;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    模式2\u003cspan class=\"o\"\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=\"p\"\u003e;;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eesac\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cul\u003e\n\u003cli\u003e\u003ccode\u003e)\u003c/code\u003e：表示这个匹配模式写完了，后面开始执行这个分支的命令。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003e;;\u003c/code\u003e：表示这个分支结束，类似 C/C++ \u003ccode\u003eswitch\u003c/code\u003e 里的 \u003ccode\u003ebreak\u003c/code\u003e。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003e|\u003c/code\u003e：表示“或者”，例如 \u003ccode\u003ebound|renew)\u003c/code\u003e 表示匹配 \u003ccode\u003ebound\u003c/code\u003e 或 \u003ccode\u003erenew\u003c/code\u003e。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ein\u003c/code\u003e：开始列出匹配规则。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eesac\u003c/code\u003e：结束 \u003ccode\u003ecase\u003c/code\u003e，就是 \u003ccode\u003ecase\u003c/code\u003e 倒过来写。\u003c/li\u003e\n\u003c/ul\u003e","title":"Shell"},{"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常用命令 udhcpc ​\tudhcpc是Busybox的非常小的DHCP的客户端，所谓的DHCP（Dynamic Host Configuration Protocol）是一个局域网的网络协议，使用UDP协议进行工作。1：用于给内部网或网络服务供应商自动分配 IP 地址；2：用户用于内部网管理员作为对所有计算机作中央管理的手段\n-i 指定网卡 -s 指定发生dhcp事件时运行的脚本 租约 ​\tDHCP Server 临时把一个 IP 地址以及相关网络参数 \u0026ldquo;租\u0026rdquo; 给客户端使用一段时间。ubs0 使用dhcpc申请地址的时候，4G模块内部的DHCP Server 可能会返回如下内容：\nIP: 10.49.93.93 Netmask: 255.255.255.0 Gateway: 10.49.93.1 DNS: 8.8.8.8 Lease: 3600 秒 这个Lease就租约时间，相当于\u0026quot;10.49.93.93 这个 IP 给你用 3600 秒。\u0026quot; 租约到期之前，udhcpc会负责续约，调用脚本\n#!/bin/sh # generated by nvr_test (WanFailover): usb0 lease recorder case \u0026#34;$1\u0026#34; in deconfig) ifconfig usb0 0.0.0.0 rm -f /tmp/usb0.lease ;; bound|renew) NETMASK=\u0026#34;\u0026#34; [ -n \u0026#34;$subnet\u0026#34; ] \u0026amp;\u0026amp; NETMASK=\u0026#34;netmask $subnet\u0026#34; ifconfig usb0 $ip $NETMASK { echo \u0026#34;router $router\u0026#34; echo \u0026#34;dns $dns\u0026#34; } \u0026gt; /tmp/usb0.lease.tmp mv /tmp/usb0.lease.tmp /tmp/usb0.lease ;; esac exit 0 bound：第一次成功拿到租约 renew：已有租约续租成功 deconfig：租约失效，需要把地址撤掉 ","permalink":"https://note.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":"","permalink":"https://note.plhwiki.cn/linux/drive/scsi/","summary":"","title":"scsi"},{"content":"ffmpeg-6.0编译 把x264/x265 全部去掉，同时保留 MP4 写文件和音频转 AAC 所需的组件\n./configure --prefix=/home/meian/work/MyProject/ffmpeg/ffmpeg-6.0/out_Jz --arch=mips --target-os=linux --enable-cross-compile --cross-prefix=mips-v720s229-linux-uclibc-gnu- --disable-asm --enable-pic --disable-debug --disable-programs --disable-ffmpeg --disable-ffprobe --disable-ffplay --disable-doc --disable-htmlpages --disable-manpages --disable-podpages --disable-txtpages --disable-symver --disable-iconv --disable-avfilter --disable-avdevice --disable-postproc --disable-indevs --disable-outdevs --disable-v4l2-m2m --disable-encoders --disable-decoders --disable-demuxers --disable-muxers --disable-parsers --disable-protocols --enable-decoder=pcm_alaw --enable-decoder=pcm_mulaw --enable-encoder=aac --enable-parser=h264 --enable-parser=hevc --enable-muxer=mp4 --enable-protocol=file --extra-libs=\u0026#34;-lpthread -lm\u0026#34; 正常的编译\n./configure \\\r--prefix=/home/meian/work/MyProject/ffmpeg/ffmpeg-6.0/out_linux \\\r--target-os=linux \\\r--enable-pic \\\r--disable-debug \\\r--disable-programs \\\r--disable-ffmpeg \\\r--disable-ffprobe \\\r--disable-ffplay \\\r--disable-doc \\\r--disable-htmlpages \\\r--disable-manpages \\\r--disable-podpages \\\r--disable-txtpages \\\r--disable-symver \\\r--disable-iconv \\\r--disable-avfilter \\\r--disable-avdevice \\\r--disable-postproc \\\r--disable-indevs \\\r--disable-outdevs \\\r--disable-v4l2-m2m \\\r--disable-encoders \\\r--disable-decoders \\\r--disable-demuxers \\\r--disable-muxers \\\r--disable-parsers \\\r--disable-protocols \\\r--enable-decoder=pcm_alaw \\\r--enable-decoder=pcm_mulaw \\\r--enable-encoder=aac \\\r--enable-parser=h264 \\\r--enable-parser=hevc \\\r--enable-muxer=mp4 \\\r--enable-protocol=file \\\r--extra-libs=\u0026#34;-lpthread -lm\u0026#34; ","permalink":"https://note.plhwiki.cn/multimedia/ffmpeg/ffmpeg/","summary":"\u003ch1 id=\"ffmpeg-60编译\"\u003effmpeg-6.0编译\u003c/h1\u003e\n\u003cp\u003e把x264/x265 全部去掉，同时保留 MP4 写文件和音频转 AAC 所需的组件\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e ./configure --prefix\u003cspan class=\"o\"\u003e=\u003c/span\u003e/home/meian/work/MyProject/ffmpeg/ffmpeg-6.0/out_Jz --arch\u003cspan class=\"o\"\u003e=\u003c/span\u003emips --target-os\u003cspan class=\"o\"\u003e=\u003c/span\u003elinux     --enable-cross-compile --cross-prefix\u003cspan class=\"o\"\u003e=\u003c/span\u003emips-v720s229-linux-uclibc-gnu- --disable-asm --enable-pic  --disable-debug --disable-programs --disable-ffmpeg --disable-ffprobe --disable-ffplay --disable-doc --disable-htmlpages --disable-manpages --disable-podpages --disable-txtpages --disable-symver --disable-iconv --disable-avfilter --disable-avdevice --disable-postproc --disable-indevs --disable-outdevs --disable-v4l2-m2m     --disable-encoders --disable-decoders --disable-demuxers --disable-muxers --disable-parsers --disable-protocols --enable-decoder\u003cspan class=\"o\"\u003e=\u003c/span\u003epcm_alaw --enable-decoder\u003cspan class=\"o\"\u003e=\u003c/span\u003epcm_mulaw --enable-encoder\u003cspan class=\"o\"\u003e=\u003c/span\u003eaac  --enable-parser\u003cspan class=\"o\"\u003e=\u003c/span\u003eh264     --enable-parser\u003cspan class=\"o\"\u003e=\u003c/span\u003ehevc --enable-muxer\u003cspan class=\"o\"\u003e=\u003c/span\u003emp4 --enable-protocol\u003cspan class=\"o\"\u003e=\u003c/span\u003efile  --extra-libs\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;-lpthread -lm\u0026#34;\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":"ffmpeg"},{"content":"","permalink":"https://note.plhwiki.cn/database/database/","summary":"","title":"Database"},{"content":"","permalink":"https://note.plhwiki.cn/hardware/%E6%A8%A1%E7%94%B5/","summary":"","title":"模电"},{"content":"","permalink":"https://note.plhwiki.cn/linux/drive/char_driver/","summary":"","title":"char_driver"},{"content":"","permalink":"https://note.plhwiki.cn/cpp/3rdpart/zlmediakit/","summary":"","title":"ZLMediakit"},{"content":"","permalink":"https://note.plhwiki.cn/cpp/3rdpart/openssl/","summary":"","title":"openssl"}]