<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
        <title>Think In Python</title>
        <link>http://www.thinkinpython.com</link>
        <description>Think in Python, Programming better.</description>
        <atom:link href="http://www.thinkinpython.com/rss.html" rel="self" />
        <language>zh-cn</language>
        <lastBuildDate>Sat, 25 Apr 2026 12:27:48 GMT</lastBuildDate>
        <item>
            <title>大型代码库的 Claude Code 最佳实践：构建层级化知识库</title>
            <link>http://www.thinkinpython.com/post/claude-code-large-codebase-best-practices-cn.html</link>
            <description><![CDATA[
            <div class="toc"><ul>
<li><a href="#toc-dd2">大型代码库的 Claude Code 最佳实践：构建层级化知识库</a><ul>
<li><a href="#toc-e45">引言</a></li>
<li><a href="#toc-903">核心原则：渐进式披露</a></li>
<li><a href="#toc-9dc">三级层级结构</a><ul>
<li><a href="#toc-887">第一层：项目根目录（全局上下文）</a></li>
<li><a href="#toc-b86">第二层：模块/领域（战略上下文）</a></li>
<li><a href="#toc-4a7">第三层：叶子节点/组件（战术上下文）</a></li>
</ul>
</li>
<li><a href="#toc-1d3">AI 友好内容的编写规范</a><ul>
<li><a href="#toc-81d">1. 明确而非习惯用语</a></li>
<li><a href="#toc-157">2. 使用&quot;要/不要&quot;列表</a></li>
<li><a href="#toc-9b6">3. 引用具体文件路径</a></li>
<li><a href="#toc-dd2">4. 保持精简</a></li>
</ul>
</li>
<li><a href="#toc-794">层级化 vs 扁平化：对比</a></li>
<li><a href="#toc-0b2">实施步骤</a><ul>
<li><a href="#toc-981">第一阶段：根目录</a></li>
<li><a href="#toc-862">第二阶段：核心模块</a></li>
<li><a href="#toc-a6d">第三阶段：复杂组件</a></li>
<li><a href="#toc-6ad">第四阶段：让 Claude 协助</a></li>
</ul>
</li>
<li><a href="#toc-25f">总结</a></li>
</ul>
</li>
</ul>
</div><h1><a id="toc-dd2" class="anchor" href="#toc-dd2"></a>大型代码库的 Claude Code 最佳实践：构建层级化知识库</h1>
<blockquote>
<p><strong>摘要</strong>：在大型代码库中高效使用 Claude Code 的核心在于&quot;渐进式披露&quot;原则。通过构建三级层级化知识库，让 AI 在需要时获取恰到好处的上下文，避免 token 浪费和上下文混淆。</p>
</blockquote>
<hr>
<p><a href="/post/claude-code-large-codebase-best-practices.html">to English</a></p>
<h2><a id="toc-e45" class="anchor" href="#toc-e45"></a>引言</h2>
<p>AI 辅助编程工具日益普及，开发者面临新挑战：如何在大型代码库中高效使用 Claude Code。在根目录放一个庞大的 README 并非良策——token 浪费、上下文混淆、维护困难。</p>
<p>本文介绍一种<strong>层级化知识库</strong>最佳实践，通过三级文档结构，让 Claude Code 在正确时机获取正确信息。</p>
<hr>
<h2><a id="toc-903" class="anchor" href="#toc-903"></a>核心原则：渐进式披露</h2>
<p><strong>渐进式披露（Progressive Disclosure）</strong>：在根目录提供高层规则，随着 AI 深入子目录，逐步提供更具体的&quot;为什么&quot;和&quot;怎么做&quot;。</p>
<p>Claude Code 原生识别 <code>CLAUDE.md</code> 文件。我们将此模式扩展为层级化方法，让文档结构与目录树对齐。</p>
<hr>
<h2><a id="toc-9dc" class="anchor" href="#toc-9dc"></a>三级层级结构</h2>
<h3><a id="toc-887" class="anchor" href="#toc-887"></a>第一层：项目根目录（全局上下文）</h3>
<p><strong>文件位置</strong>：<code>/CLAUDE.md</code></p>
<p><strong>定位</strong>：项目&quot;宪法&quot;，定义技术栈、核心命令、不可违背的标准。</p>
<p><strong>示例内容</strong>：</p>
<pre><code class="hljs lang-markdown"><span class="hljs-section">## 构建命令</span>
<span class="hljs-bullet">- </span>生产构建：pnpm build
<span class="hljs-bullet">- </span>开发模式：pnpm dev

<span class="hljs-section">## 测试命令</span>
<span class="hljs-bullet">- </span>单元测试：pnpm test:unit <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">文件</span>&gt;</span></span>
<span class="hljs-bullet">- </span>E2E 测试：pnpm test:e2e

<span class="hljs-section">## 代码规范</span>
<span class="hljs-bullet">- </span>仅使用函数式组件
<span class="hljs-bullet">- </span>禁止 default exports
<span class="hljs-bullet">- </span>样式使用 Tailwind CSS

<span class="hljs-section">## 架构概述</span>
<span class="hljs-bullet">- </span>Next.js 单体仓库
<span class="hljs-bullet">- </span>共享逻辑位于 /packages/shared
</code></pre>
<hr>
<h3><a id="toc-b86" class="anchor" href="#toc-b86"></a>第二层：模块/领域（战略上下文）</h3>
<p><strong>文件位置</strong>：<code>/src/features/billing/CONTEXT.md</code></p>
<p><strong>定位</strong>：解释代码无法揭示的业务逻辑和隐性数据流。</p>
<p><strong>示例内容</strong>：</p>
<pre><code class="hljs lang-markdown"><span class="hljs-section">## 领域逻辑</span>
本模块处理 Stripe 支付集成。

<span class="hljs-section">## 数据流</span>
所有支付必须先触发 webhook-handler，再更新数据库。

<span class="hljs-section">## 安全要求</span>
<span class="hljs-bullet">- </span>禁止在前端暴露 Secret_Key
<span class="hljs-bullet">- </span>使用 /api/stripe 中的代理进行后端调用

<span class="hljs-section">## 依赖关系</span>
<span class="hljs-bullet">- </span>依赖 UserStore 进行税费计算
<span class="hljs-bullet">- </span>与 OrderService 双向同步
</code></pre>
<hr>
<h3><a id="toc-4a7" class="anchor" href="#toc-4a7"></a>第三层：叶子节点/组件（战术上下文）</h3>
<p><strong>文件位置</strong>：<code>/src/components/DataGrid/NOTES.md</code></p>
<p><strong>定位</strong>：解释&quot;陷阱&quot;和技术债务。</p>
<p><strong>示例内容</strong>：</p>
<pre><code class="hljs lang-markdown"><span class="hljs-section">## 性能注意事项</span>
<span class="hljs-bullet">- </span>使用 react-virtualized 进行虚拟滚动
<span class="hljs-bullet">- </span>禁止移除 rowHeight 属性，否则在 Safari 上会崩溃

<span class="hljs-section">## 已知问题</span>
<span class="hljs-bullet">- </span>排序切换与 API 存在竞态条件
<span class="hljs-bullet">- </span>解决方案：使用 isLoading ref 进行防抖

<span class="hljs-section">## 待重构</span>
<span class="hljs-bullet">- </span>[ ] 将排序逻辑提取为独立 Hook
<span class="hljs-bullet">- </span>[ ] 替换已废弃的 componentWillReceiveProps
</code></pre>
<hr>
<h2><a id="toc-1d3" class="anchor" href="#toc-1d3"></a>AI 友好内容的编写规范</h2>
<h3><a id="toc-81d" class="anchor" href="#toc-81d"></a>1. 明确而非习惯用语</h3>
<p>❌ 错误：<code>Just run the tests</code><br>✅ 正确：<code>Use npm run test</code></p>
<p>AI 对明确指令响应更好，避免开发者&quot;行话&quot;。</p>
<h3><a id="toc-157" class="anchor" href="#toc-157"></a>2. 使用&quot;要/不要&quot;列表</h3>
<p>AI 对否定约束响应极佳：</p>
<pre><code class="hljs lang-markdown"><span class="hljs-section">## 类型规范</span>
<span class="hljs-bullet">- </span>❌ 不要使用 'any' 类型
<span class="hljs-bullet">- </span>✅ 类型 truly 不明确时使用 'unknown'
<span class="hljs-bullet">- </span>✅ 优先使用 TypeScript 严格模式
</code></pre>
<h3><a id="toc-9b6" class="anchor" href="#toc-9b6"></a>3. 引用具体文件路径</h3>
<p>让 AI 知道&quot;真相&quot;在哪里：</p>
<pre><code class="hljs lang-markdown"><span class="hljs-section">## 数据模型</span>
<span class="hljs-bullet">- </span>主 Schema 定义在 /src/db/schema.ts
<span class="hljs-bullet">- </span>类型导出在 /src/types/index.ts
</code></pre>
<h3><a id="toc-dd2" class="anchor" href="#toc-dd2"></a>4. 保持精简</h3>
<p>单个上下文文件<strong>不超过 50 行</strong>高密度信息。超过 10KB，AI 可能浪费过多 token。</p>
<hr>
<h2><a id="toc-794" class="anchor" href="#toc-794"></a>层级化 vs 扁平化：对比</h2>
<table>
<thead>
<tr>
<th>特性</th>
<th>扁平化（单一 README）</th>
<th>层级化（多级结构）</th>
</tr>
</thead>
<tbody>
<tr>
<td>Token 使用</td>
<td>高（AI 每次读取全部内容）</td>
<td>低（仅读取相关目录笔记）</td>
</tr>
<tr>
<td>精确度</td>
<td>低（可能混淆 Webhook 规则与 UI 规则）</td>
<td>高（特定文件夹的特定规则）</td>
</tr>
<tr>
<td>可维护性</td>
<td>难（单一文件变成&quot;杂物抽屉&quot;）</td>
<td>易（小文件贴近所描述的代码）</td>
</tr>
<tr>
<td>扩展性</td>
<td>差（随项目增长迅速膨胀）</td>
<td>好（新增模块只需添加对应文件）</td>
</tr>
</tbody>
</table>
<hr>
<h2><a id="toc-0b2" class="anchor" href="#toc-0b2"></a>实施步骤</h2>
<p>无需一次性完成。采用渐进式方法：</p>
<h3><a id="toc-981" class="anchor" href="#toc-981"></a>第一阶段：根目录</h3>
<ol>
<li>创建 <code>/CLAUDE.md</code></li>
<li>定义技术栈、构建命令、代码规范</li>
</ol>
<h3><a id="toc-862" class="anchor" href="#toc-862"></a>第二阶段：核心模块</h3>
<ol>
<li>识别 3-5 个核心业务模块</li>
<li>为每个模块创建 <code>CONTEXT.md</code></li>
<li>描述数据流、安全要求、依赖关系</li>
</ol>
<h3><a id="toc-a6d" class="anchor" href="#toc-a6d"></a>第三阶段：复杂组件</h3>
<ol>
<li>识别存在技术债务或&quot;陷阱&quot;的组件</li>
<li>创建 <code>NOTES.md</code> 记录已知问题和解决方案</li>
</ol>
<h3><a id="toc-6ad" class="anchor" href="#toc-6ad"></a>第四阶段：让 Claude 协助</h3>
<p>使用 Claude Code 本身帮助生成文档：</p>
<pre><code class="hljs lang-bash"><span class="hljs-comment"># 让 Claude 分析模块并生成 CONTEXT.md</span>
claude <span class="hljs-string">"Analyze the billing module and create a CONTEXT.md 
        that explains the data flow and security requirements"</span>
</code></pre>
<hr>
<h2><a id="toc-25f" class="anchor" href="#toc-25f"></a>总结</h2>
<p>层级化知识库的核心价值：</p>
<ol>
<li><strong>节省 token</strong>：AI 仅读取相关上下文</li>
<li><strong>提高精确度</strong>：特定规则作用于特定范围</li>
<li><strong>易于维护</strong>：小文件贴近代码，更新成本低</li>
<li><strong>可扩展</strong>：随项目增长自然扩展</li>
</ol>
<p>实施时从根目录 <code>/CLAUDE.md</code> 入手，逐步向下扩展。记住：<strong>文档的价值在于被使用，而非被写完</strong>。</p>
<hr>
<p><strong>参考资料</strong></p>
<ul>
<li><a href="https://docs.anthropic.com/claude-code">Claude Code 官方文档</a></li>
<li><a href="https://www.nngroup.com/articles/progressive-disclosure/">Progressive Disclosure in Software Design</a></li>
</ul>

            ]]></description>
            <pubDate>Tue, 31 Mar 2026 04:36:26 GMT</pubDate>
            <guid>http://www.thinkinpython.com/post/claude-code-large-codebase-best-practices-cn.html</guid>
        </item>
        <item>
            <title>Best Practices for Large Codebases with Claude Code: Building a Level-Structured Knowledge Base</title>
            <link>http://www.thinkinpython.com/post/claude-code-large-codebase-best-practices.html</link>
            <description><![CDATA[
            <div class="toc"><ul>
<li><a href="#toc-699">Best Practices for Large Codebases with Claude Code: Building a Level-Structured Knowledge Base</a><ul>
<li><a href="#introduction">Introduction</a></li>
<li><a href="#toc-562">Core Principle: Progressive Disclosure</a></li>
<li><a href="#the3-tierhierarchy">The 3-Tier Hierarchy</a><ul>
<li><a href="#toc-05b">Tier 1: Project Root (Global Context)</a></li>
<li><a href="#toc-aaa">Tier 2: Module/Domain (Strategic Context)</a></li>
<li><a href="#toc-822">Tier 3: Leaf/Component (Tactical Context)</a></li>
</ul>
</li>
<li><a href="#toc-6ea">Best Practices for &quot;AI-Friendly&quot; Content</a><ul>
<li><a href="#toc-267">1. Be Explicit, Not Idiomatic</a></li>
<li><a href="#toc-c43">2. Use &quot;Do/Don&#39;t&quot; Lists</a></li>
<li><a href="#toc-5f4">3. Reference Specific Files</a></li>
<li><a href="#toc-eb3">4. Keep It Small</a></li>
</ul>
</li>
<li><a href="#toc-dca">Hierarchical vs. Flat: Comparison</a></li>
<li><a href="#implementationsteps">Implementation Steps</a><ul>
<li><a href="#toc-c7a">Phase 1: Root Directory</a></li>
<li><a href="#toc-959">Phase 2: Core Modules</a></li>
<li><a href="#toc-13e">Phase 3: Complex Components</a></li>
<li><a href="#toc-404">Phase 4: Let Claude Help</a></li>
</ul>
</li>
<li><a href="#summary">Summary</a></li>
</ul>
</li>
</ul>
</div><h1><a id="toc-699" class="anchor" href="#toc-699"></a>Best Practices for Large Codebases with Claude Code: Building a Level-Structured Knowledge Base</h1>
<blockquote>
<p><strong>Abstract</strong>: The key to efficiently using Claude Code in large codebases lies in the principle of &quot;Progressive Disclosure.&quot; By building a three-tier hierarchical knowledge base, AI can access just the right amount of context when needed, avoiding token waste and context confusion.</p>
</blockquote>
<hr>
<p><a href="/post/claude-code-large-codebase-best-practices-cn.html">中文版</a></p>
<h2><a id="introduction" class="anchor" href="#introduction"></a>Introduction</h2>
<p>As AI-assisted programming tools become ubiquitous, developers face a new challenge: how to use Claude Code effectively in large codebases. Placing a massive README file at the root is far from optimal—it leads to token waste, context confusion, and maintenance difficulties.</p>
<p>This article presents a <strong>hierarchical knowledge base</strong> best practice, using a three-tier documentation structure to ensure Claude Code receives the right information at the right time.</p>
<hr>
<h2><a id="toc-562" class="anchor" href="#toc-562"></a>Core Principle: Progressive Disclosure</h2>
<p><strong>Progressive Disclosure</strong> means providing high-level rules at the root, then offering increasingly specific &quot;why&quot; and &quot;how&quot; details as AI drills down into subdirectories.</p>
<p>Claude Code natively recognizes <code>CLAUDE.md</code> files. We can extend this pattern into a hierarchical approach, aligning documentation structure with the directory tree.</p>
<hr>
<h2><a id="the3-tierhierarchy" class="anchor" href="#the3-tierhierarchy"></a>The 3-Tier Hierarchy</h2>
<h3><a id="toc-05b" class="anchor" href="#toc-05b"></a>Tier 1: Project Root (Global Context)</h3>
<p><strong>File Location</strong>: <code>/CLAUDE.md</code></p>
<p><strong>Purpose</strong>: The &quot;Constitution&quot;—defining the tech stack, core commands, and non-negotiable standards.</p>
<p><strong>Example Content</strong>:</p>
<pre><code class="hljs lang-markdown"><span class="hljs-section">## Build Commands</span>
<span class="hljs-bullet">- </span>Production build: <span class="hljs-code">`pnpm build`</span>
<span class="hljs-bullet">- </span>Development mode: <span class="hljs-code">`pnpm dev`</span>

<span class="hljs-section">## Test Commands</span>
<span class="hljs-bullet">- </span>Unit tests: <span class="hljs-code">`pnpm test:unit &lt;file&gt;`</span>
<span class="hljs-bullet">- </span>E2E tests: <span class="hljs-code">`pnpm test:e2e`</span>

<span class="hljs-section">## Code Style</span>
<span class="hljs-bullet">- </span>Functional components only
<span class="hljs-bullet">- </span>No default exports
<span class="hljs-bullet">- </span>Use Tailwind CSS for styling

<span class="hljs-section">## Architecture</span>
<span class="hljs-bullet">- </span>Next.js monorepo
<span class="hljs-bullet">- </span>Shared logic in <span class="hljs-code">`/packages/shared`</span>
</code></pre>
<hr>
<h3><a id="toc-aaa" class="anchor" href="#toc-aaa"></a>Tier 2: Module/Domain (Strategic Context)</h3>
<p><strong>File Location</strong>: <code>/src/features/billing/CONTEXT.md</code></p>
<p><strong>Purpose</strong>: Explaining business logic and invisible data flows that code alone won&#39;t reveal.</p>
<p><strong>Example Content</strong>:</p>
<pre><code class="hljs lang-markdown"><span class="hljs-section">## Domain Logic</span>
This module handles Stripe integration.

<span class="hljs-section">## Data Flow</span>
All payments must trigger the <span class="hljs-code">`webhook-handler`</span> before updating the DB.

<span class="hljs-section">## Security</span>
<span class="hljs-bullet">- </span>Never expose Secret_Key to the frontend
<span class="hljs-bullet">- </span>Use the proxy in <span class="hljs-code">`/api/stripe`</span> for backend calls

<span class="hljs-section">## Dependencies</span>
<span class="hljs-bullet">- </span>Depends on UserStore for tax calculations
<span class="hljs-bullet">- </span>Bidirectional sync with OrderService
</code></pre>
<hr>
<h3><a id="toc-822" class="anchor" href="#toc-822"></a>Tier 3: Leaf/Component (Tactical Context)</h3>
<p><strong>File Location</strong>: <code>/src/components/DataGrid/NOTES.md</code></p>
<p><strong>Purpose</strong>: Explaining &quot;gotchas&quot; and specific technical debt.</p>
<p><strong>Example Content</strong>:</p>
<pre><code class="hljs lang-markdown"><span class="hljs-section">## Performance</span>
<span class="hljs-bullet">- </span>Uses react-virtualized for virtual scrolling
<span class="hljs-bullet">- </span>Do not remove the <span class="hljs-code">`rowHeight`</span> prop or it will crash on Safari

<span class="hljs-section">## Known Issues</span>
<span class="hljs-bullet">- </span>Sorting toggle has a race condition with the API
<span class="hljs-bullet">- </span>Workaround: use <span class="hljs-code">`isLoading`</span> ref to debounce

<span class="hljs-section">## TODO</span>
<span class="hljs-bullet">- </span>[ ] Extract sorting logic into a standalone Hook
<span class="hljs-bullet">- </span>[ ] Replace deprecated componentWillReceiveProps
</code></pre>
<hr>
<h2><a id="toc-6ea" class="anchor" href="#toc-6ea"></a>Best Practices for &quot;AI-Friendly&quot; Content</h2>
<h3><a id="toc-267" class="anchor" href="#toc-267"></a>1. Be Explicit, Not Idiomatic</h3>
<p>❌ Wrong: <code>Just run the tests</code><br>✅ Right: <code>Use npm run test</code></p>
<p>AI models respond better to explicit instructions. Avoid developer &quot;jargon.&quot;</p>
<h3><a id="toc-c43" class="anchor" href="#toc-c43"></a>2. Use &quot;Do/Don&#39;t&quot; Lists</h3>
<p>AI models respond exceptionally well to negative constraints:</p>
<pre><code class="hljs lang-markdown"><span class="hljs-section">## Type Safety</span>
<span class="hljs-bullet">- </span>❌ Don't use 'any' types
<span class="hljs-bullet">- </span>✅ Use 'unknown' if the type is truly ambiguous
<span class="hljs-bullet">- </span>✅ Prefer TypeScript strict mode
</code></pre>
<h3><a id="toc-5f4" class="anchor" href="#toc-5f4"></a>3. Reference Specific Files</h3>
<p>Let AI know where &quot;The Truth&quot; lives:</p>
<pre><code class="hljs lang-markdown"><span class="hljs-section">## Data Models</span>
<span class="hljs-bullet">- </span>Master schema defined in <span class="hljs-code">`/src/db/schema.ts`</span>
<span class="hljs-bullet">- </span>Type exports in <span class="hljs-code">`/src/types/index.ts`</span>
</code></pre>
<h3><a id="toc-eb3" class="anchor" href="#toc-eb3"></a>4. Keep It Small</h3>
<p>Individual context files should be <strong>under 50 lines</strong> of high-density information. If a file exceeds 10KB, AI may waste too many tokens reading it.</p>
<hr>
<h2><a id="toc-dca" class="anchor" href="#toc-dca"></a>Hierarchical vs. Flat: Comparison</h2>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Flat (One Big README)</th>
<th>Hierarchical (Level-Structured)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Token Usage</td>
<td>High (Claude reads everything every time)</td>
<td>Low (Claude only reads relevant folder&#39;s notes)</td>
</tr>
<tr>
<td>Precision</td>
<td>Low (May confuse Webhook rules with UI rules)</td>
<td>High (Specific rules for specific folders)</td>
</tr>
<tr>
<td>Maintainability</td>
<td>Hard (One file becomes a &quot;junk drawer&quot;)</td>
<td>Easy (Small files stay close to the code they describe)</td>
</tr>
<tr>
<td>Scalability</td>
<td>Poor (Rapidly bloats as project grows)</td>
<td>Good (New modules just add corresponding files)</td>
</tr>
</tbody>
</table>
<hr>
<h2><a id="implementationsteps" class="anchor" href="#implementationsteps"></a>Implementation Steps</h2>
<p>You don&#39;t have to write all of this at once. Adopt a progressive approach:</p>
<h3><a id="toc-c7a" class="anchor" href="#toc-c7a"></a>Phase 1: Root Directory</h3>
<ol>
<li>Create <code>/CLAUDE.md</code></li>
<li>Define tech stack, build commands, code conventions</li>
</ol>
<h3><a id="toc-959" class="anchor" href="#toc-959"></a>Phase 2: Core Modules</h3>
<ol>
<li>Identify 3-5 core business modules</li>
<li>Create <code>CONTEXT.md</code> for each module</li>
<li>Document data flows, security requirements, dependencies</li>
</ol>
<h3><a id="toc-13e" class="anchor" href="#toc-13e"></a>Phase 3: Complex Components</h3>
<ol>
<li>Identify components with technical debt or &quot;gotchas&quot;</li>
<li>Create <code>NOTES.md</code> documenting known issues and workarounds</li>
</ol>
<h3><a id="toc-404" class="anchor" href="#toc-404"></a>Phase 4: Let Claude Help</h3>
<p>Use Claude Code itself to help generate documentation:</p>
<pre><code class="hljs lang-bash"><span class="hljs-comment"># Ask Claude to analyze a module and generate CONTEXT.md</span>
claude <span class="hljs-string">"Analyze the billing module and create a CONTEXT.md 
        that explains the data flow and security requirements"</span>
</code></pre>
<hr>
<h2><a id="summary" class="anchor" href="#summary"></a>Summary</h2>
<p>The core value of a hierarchical knowledge base:</p>
<ol>
<li><strong>Saves tokens</strong>: AI reads only relevant context</li>
<li><strong>Improves precision</strong>: Specific rules apply to specific scopes</li>
<li><strong>Easy maintenance</strong>: Small files stay close to code, low update cost</li>
<li><strong>Scalable</strong>: Grows naturally with the project</li>
</ol>
<p>When starting, begin with <code>/CLAUDE.md</code> at the root, then expand downward. Remember: <strong>The value of documentation lies in being used, not in being finished.</strong></p>
<hr>
<p><strong>References</strong></p>
<ul>
<li><a href="https://docs.anthropic.com/claude-code">Claude Code Official Documentation</a></li>
<li><a href="https://www.nngroup.com/articles/progressive-disclosure/">Progressive Disclosure in Software Design</a></li>
</ul>

            ]]></description>
            <pubDate>Tue, 31 Mar 2026 02:43:30 GMT</pubDate>
            <guid>http://www.thinkinpython.com/post/claude-code-large-codebase-best-practices.html</guid>
        </item>
        <item>
            <title>古都之秋</title>
            <link>http://www.thinkinpython.com/post/fall_of_the_ancient_city.html</link>
            <description><![CDATA[
            <div class="toc"></div><p><img src="https://thinkinpython.com/static/upload/20251102/upload_e6cf72beb7b880c3ec9cead25984501f.png" alt="2.png"></p>
<p><img src="https://thinkinpython.com/static/upload/20251102/upload_d63ab713da10fc48f31030de8a09ddcc.png" alt="4.png"></p>
<p><img src="https://thinkinpython.com/static/upload/20251102/upload_e82a73d7a44f953360441e910aa80fed.png" alt="5.png"></p>
<p><img src="https://thinkinpython.com/static/upload/20251102/upload_a62227a287b0de0c9a6303f6a186779b.png" alt="9.png"></p>

            ]]></description>
            <pubDate>Sun, 02 Nov 2025 14:20:37 GMT</pubDate>
            <guid>http://www.thinkinpython.com/post/fall_of_the_ancient_city.html</guid>
        </item>
        <item>
            <title>Amber---下一个 shell 脚本何必是 shell script</title>
            <link>http://www.thinkinpython.com/post/amber_script.html</link>
            <description><![CDATA[
            <div class="toc"><ul>
<li><a href="#toc-9ed">先睹为快</a></li>
<li><a href="#toc-24c">执行命令</a></li>
<li><a href="#toc-25f">总结</a></li>
</ul>
</div><p>Linux shell script 是我最喜欢的脚本之一，历史悠久，底蕴深厚，shell script 几乎是内核的一部分，托 POSIX 标准的福，你可以在任何 Linux 系统中使用它。但是编写 shell script 体验并不太好，各种奇怪的语法、没有类型检查、孱弱的数组支持，有时候排查很久的问题，仅仅是错误的使用引号、或者多了一个空格。</p>
<p>我很喜欢造轮子，曾打算写一个高级语言，可以编译成 shell script 执行，即使是一些简单的语法糖，也能很大程度提升 shell script 用户的幸福感。前几天发现一个项目 <a href="https://amber-lang.com">Amber</a> 已经做了这些工作，试用了下还不错，语法类似 Ecmas 或者 js，支持 Text、Num、Bool、Null 和 [] 数组，可以直接编译成 shell script，直接拿到任何 shell 中执行，没有任何移植性困扰。</p>
<h1><a id="toc-9ed" class="anchor" href="#toc-9ed"></a>先睹为快</h1>
<p>参考官网的安装指导，只需要一行命令：</p>
<pre><code class="hljs lang-bash">curl -s <span class="hljs-string">"https://raw.githubusercontent.com/Ph0enixKM/AmberNative/master/setup/install.sh"</span> | bash
</code></pre>
<p>请确保你的系统已安装了 curl、bc， 安装以后就可以使用 <code>amber</code> 命令。 下面是一个简单的例子：</p>
<pre><code class="hljs lang-undefined">let fruits = ["apple", "banana", "grape"]

fun show_opt(fruits) {
    loop index, f in fruits {
        echo "{index}: {f}"
    }
}

show_opt(fruits)
</code></pre>
<p>将上面的代码保存为 <code>test1.ab</code>, 执行它</p>
<pre><code class="hljs lang-bash">~/<span class="hljs-built_in">test</span> $ amber test1.ab
0: apple
1: banana
2: grape
</code></pre>
<p>只要你有任何一种变成经验，很容易明白 amber 的基本语法。</p>
<p>上面的 amber 脚本等效的 shell 如何呢？我们可以将 amber 脚本编译为 shell 脚本。</p>
<pre><code class="hljs lang-undefined">amber test1.ab test1.sh
</code></pre>
<p>相比直接执行 amber 脚本，只需要增加一个参数指定编译输出的脚本文件名即可。<code>test1.sh</code> 看起来这样：</p>
<pre><code class="hljs lang-bash">__AMBER_ARRAY_0=(<span class="hljs-string">"apple"</span> <span class="hljs-string">"banana"</span> <span class="hljs-string">"grape"</span>);
__0_fruits=(<span class="hljs-string">"<span class="hljs-variable">${__AMBER_ARRAY_0[@]}</span>"</span>);
<span class="hljs-keyword">function</span> show_opt__0_v0 {
    <span class="hljs-built_in">local</span> fruits=(<span class="hljs-string">"<span class="hljs-variable">${!1}</span>"</span>)
    index=0;
<span class="hljs-keyword">for</span> f <span class="hljs-keyword">in</span> <span class="hljs-string">"<span class="hljs-variable">${fruits[@]}</span>"</span>
<span class="hljs-keyword">do</span>
        <span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">${index}</span>: <span class="hljs-variable">${f}</span>"</span>
        <span class="hljs-built_in">let</span> index=<span class="hljs-variable">${index}</span>+1
<span class="hljs-keyword">done</span>
};
show_opt__0_v0 __0_fruits[@];
__AMBER_FUN_show_opt0_v0__9=<span class="hljs-variable">${__AMBER_FUN_show_opt0_v0}</span>;
<span class="hljs-built_in">echo</span> <span class="hljs-variable">${__AMBER_FUN_show_opt0_v0__9}</span> &gt; /dev/null 2&gt;&amp;1
</code></pre>
<p>相比之下，amber 脚本真的是相当人性化。</p>
<h1><a id="toc-24c" class="anchor" href="#toc-24c"></a>执行命令</h1>
<p>shell 最强大的能力在于可以方便的调用已安装的命令。amber 也具备这样的能力，而且提供了良好的异常处理能力。</p>
<p>基本语法是 </p>
<pre><code class="hljs lang-undefined">$your cmd$ failed { exception handler }
</code></pre>
<p>两个 $ 之间可以是任何有效的 shell 命令，failed 是 amber 的关键字，表示指令的异常处理。</p>
<p>为了正常使用异常机制，异常所在代码要么位于 main block，要么在函数中。amber 可以指定 main 作为脚本入口。</p>
<pre><code class="hljs lang-bash">main {
    <span class="hljs-built_in">let</span> file_name = <span class="hljs-string">"1.txt"</span>
    <span class="hljs-built_in">let</span> cmd = <span class="hljs-string">"cat"</span>
    <span class="hljs-built_in">let</span> file_content = <span class="hljs-variable">${cmd}</span> {file_name}$ failed {
        <span class="hljs-built_in">echo</span> <span class="hljs-string">"{file_name} not exist"</span>
        fail
    }

    <span class="hljs-built_in">echo</span> file_content
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"done"</span>
}
</code></pre><p>这段代码的尝试获取 <code>1.txt</code> 的内容，如果这个文件不存在就报错退出，否则打印文件内容。</p>
<p>指令可以是任何字符串，比如例子中的命令由 <code>cmd</code> 和 <code>file_name</code> 变量拼接生成。<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="4.263ex" height="2.176ex" style="vertical-align: -0.338ex;" viewBox="0 -791.3 1835.5 936.9" role="img" focusable="false" xmlns="http://www.w3.org/2000/svg" aria-labelledby="MathJax-SVG-1-Title">
<title id="MathJax-SVG-1-Title">cmd</title>
<defs aria-hidden="true">
<path stroke-width="1" id="E1-MJMATHI-63" d="M34 159Q34 268 120 355T306 442Q362 442 394 418T427 355Q427 326 408 306T360 285Q341 285 330 295T319 325T330 359T352 380T366 386H367Q367 388 361 392T340 400T306 404Q276 404 249 390Q228 381 206 359Q162 315 142 235T121 119Q121 73 147 50Q169 26 205 26H209Q321 26 394 111Q403 121 406 121Q410 121 419 112T429 98T420 83T391 55T346 25T282 0T202 -11Q127 -11 81 37T34 159Z"></path>
<path stroke-width="1" id="E1-MJMATHI-6D" d="M21 287Q22 293 24 303T36 341T56 388T88 425T132 442T175 435T205 417T221 395T229 376L231 369Q231 367 232 367L243 378Q303 442 384 442Q401 442 415 440T441 433T460 423T475 411T485 398T493 385T497 373T500 364T502 357L510 367Q573 442 659 442Q713 442 746 415T780 336Q780 285 742 178T704 50Q705 36 709 31T724 26Q752 26 776 56T815 138Q818 149 821 151T837 153Q857 153 857 145Q857 144 853 130Q845 101 831 73T785 17T716 -10Q669 -10 648 17T627 73Q627 92 663 193T700 345Q700 404 656 404H651Q565 404 506 303L499 291L466 157Q433 26 428 16Q415 -11 385 -11Q372 -11 364 -4T353 8T350 18Q350 29 384 161L420 307Q423 322 423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 181Q151 335 151 342Q154 357 154 369Q154 405 129 405Q107 405 92 377T69 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path>
<path stroke-width="1" id="E1-MJMATHI-64" d="M366 683Q367 683 438 688T511 694Q523 694 523 686Q523 679 450 384T375 83T374 68Q374 26 402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487H491Q506 153 506 145Q506 140 503 129Q490 79 473 48T445 8T417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157Q33 205 53 255T101 341Q148 398 195 420T280 442Q336 442 364 400Q369 394 369 396Q370 400 396 505T424 616Q424 629 417 632T378 637H357Q351 643 351 645T353 664Q358 683 366 683ZM352 326Q329 405 277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q233 26 290 98L298 109L352 326Z"></path>
</defs>
<g stroke="currentColor" fill="currentColor" stroke-width="0" transform="matrix(1 0 0 -1 0 0)" aria-hidden="true">
 <use xlink:href="#E1-MJMATHI-63" x="0" y="0"></use>
 <use xlink:href="#E1-MJMATHI-6D" x="433" y="0"></use>
 <use xlink:href="#E1-MJMATHI-64" x="1312" y="0"></use>
</g>
</svg> 的标准输出可以直接赋值给变量，获取指令输出非常方便。当使用 <svg xmlns:xlink="http://www.w3.org/1999/xlink" width="4.263ex" height="2.176ex" style="vertical-align: -0.338ex;" viewBox="0 -791.3 1835.5 936.9" role="img" focusable="false" xmlns="http://www.w3.org/2000/svg" aria-labelledby="MathJax-SVG-1-Title">
<title id="MathJax-SVG-1-Title">cmd</title>
<defs aria-hidden="true">
<path stroke-width="1" id="E1-MJMATHI-63" d="M34 159Q34 268 120 355T306 442Q362 442 394 418T427 355Q427 326 408 306T360 285Q341 285 330 295T319 325T330 359T352 380T366 386H367Q367 388 361 392T340 400T306 404Q276 404 249 390Q228 381 206 359Q162 315 142 235T121 119Q121 73 147 50Q169 26 205 26H209Q321 26 394 111Q403 121 406 121Q410 121 419 112T429 98T420 83T391 55T346 25T282 0T202 -11Q127 -11 81 37T34 159Z"></path>
<path stroke-width="1" id="E1-MJMATHI-6D" d="M21 287Q22 293 24 303T36 341T56 388T88 425T132 442T175 435T205 417T221 395T229 376L231 369Q231 367 232 367L243 378Q303 442 384 442Q401 442 415 440T441 433T460 423T475 411T485 398T493 385T497 373T500 364T502 357L510 367Q573 442 659 442Q713 442 746 415T780 336Q780 285 742 178T704 50Q705 36 709 31T724 26Q752 26 776 56T815 138Q818 149 821 151T837 153Q857 153 857 145Q857 144 853 130Q845 101 831 73T785 17T716 -10Q669 -10 648 17T627 73Q627 92 663 193T700 345Q700 404 656 404H651Q565 404 506 303L499 291L466 157Q433 26 428 16Q415 -11 385 -11Q372 -11 364 -4T353 8T350 18Q350 29 384 161L420 307Q423 322 423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 181Q151 335 151 342Q154 357 154 369Q154 405 129 405Q107 405 92 377T69 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path>
<path stroke-width="1" id="E1-MJMATHI-64" d="M366 683Q367 683 438 688T511 694Q523 694 523 686Q523 679 450 384T375 83T374 68Q374 26 402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487H491Q506 153 506 145Q506 140 503 129Q490 79 473 48T445 8T417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157Q33 205 53 255T101 341Q148 398 195 420T280 442Q336 442 364 400Q369 394 369 396Q370 400 396 505T424 616Q424 629 417 632T378 637H357Q351 643 351 645T353 664Q358 683 366 683ZM352 326Q329 405 277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q233 26 290 98L298 109L352 326Z"></path>
</defs>
<g stroke="currentColor" fill="currentColor" stroke-width="0" transform="matrix(1 0 0 -1 0 0)" aria-hidden="true">
 <use xlink:href="#E1-MJMATHI-63" x="0" y="0"></use>
 <use xlink:href="#E1-MJMATHI-6D" x="433" y="0"></use>
 <use xlink:href="#E1-MJMATHI-64" x="1312" y="0"></use>
</g>
</svg> 调用指令时，必须指定异常处理，可以通过 <code>failed</code> 关键字指明之后的代码块用作异常处理。<code>fail</code> 关键字用于抛出异常。</p>
<p>还有一种简化的一场处理方式，上面代码可以稍作调整:</p>
<pre><code class="hljs lang-xquery">    <span class="hljs-keyword">let</span> file_content = ${cmd} {file_name}$?
</code></pre><p>相当于</p>
<pre><code class="hljs lang-xquery">    <span class="hljs-keyword">let</span> file_content = ${cmd} {file_name}$ failed {
        fail status
    }
</code></pre><p>我很喜欢 <code>?</code> 的设计，表示指令是不可靠的，出错时就停止。二者的区别是简写方式无法指定报错信息。</p>
<h1><a id="toc-25f" class="anchor" href="#toc-25f"></a>总结</h1>
<p>目前我已用 amber 作为 Linux 下编写脚本的首选工具，感觉好用。好工具，好生活，拯救脱发。</p>

            ]]></description>
            <pubDate>Wed, 29 May 2024 17:25:14 GMT</pubDate>
            <guid>http://www.thinkinpython.com/post/amber_script.html</guid>
        </item>
        <item>
            <title>如何实现可动态扩展的共享内存池</title>
            <link>http://www.thinkinpython.com/post/shmPoolImplement.html</link>
            <description><![CDATA[
            <div class="toc"><ul>
<li><a href="#toc-e56">共享内存的原理</a></li>
<li><a href="#toc-bf7">抽象共享内存指针</a></li>
<li><a href="#toc-544">动态扩展</a></li>
<li><a href="#toc-25f">总结</a></li>
</ul>
</div><p>开发者通常为了提高内存使用效率，或者避免内存泄漏，需要将内存池化管理。稍复杂一点的系统，一般都会有自己的内存管理机制，我在研读源码的时候，比较习惯先看内存管理模块的实现，这块很见开发者的基本工和工程思想，颇有一叶知秋之感，一个内存管理一团乱麻的系统，注定不会是艺术品。
内存池的实现方法很多，但万变不离其宗，通常就是一次申请大块内存，进程再将其化整为零的重复使用，对于操作系统来说，只发生了少数几次内存分配调用，避免长时间运行后内存碎片化。当内存池耗尽时，可以再申请一大块内存入池，实现内存池扩容。比较常见的是进程私有内存池，共享内存如何实现池化呢？</p>
<h1><a id="toc-e56" class="anchor" href="#toc-e56"></a>共享内存的原理</h1>
<p>我们通常认为的内存地址，实际上并不是物理内存上的位置。操作系统出于安全性和效率考虑，每个进程都有独立的虚拟地址空间，A 进程的 0X123 和 B 进程的 0X123 处的数据通常没有任何关系。</p>
<p>本文均以 X64/X86 架构为例。</p>
<p><img src="https://thinkinpython.com/static/upload/20240528/upload_63f94905c3e25388d89b4c161e7951f3.png" alt="image-1.png"></p>
<p>Linux 系统中，所有进程看到的地址空间大体如图，低地址处通常为用户程序，高地址处为内核空间。这个地址空间在不同 CPU 下也有一些差异：</p>
<ul>
<li>32bit 系统中，虚拟地址空间总大小 4G，其中 1G 为内核区域。</li>
<li>64bit 系统中，只使用了 48 位地址，因此虚拟地址空间总大小为 256T，通常 8T 为内核区域。</li>
</ul>
<p>64bit 系统的的进程虚拟地址空间非常大，普通计算机只能用到其中一小部分，操作系统通过 MMU 将虚拟地址映射到真实的物理内存上，这样只需要很小内存，也能同时运行大量程序。</p>
<p><img src="https://thinkinpython.com/static/upload/20240528/upload_e0f99182b4f976585a9932f321de17ad.png" alt="image-2.png"></p>
<p>在虚拟内存中地址连续的页面，在物理内存上的地址是不确定的，甚至在不同时刻，相同的虚拟内存地址也会被映射到不同的物理地址。</p>
<p>如果把同一块物理内存映射到两个进程的虚拟地址会发生什么呢？</p>
<p><img src="https://thinkinpython.com/static/upload/20240528/upload_0ad0449cfd90547501d3e74157706309.png" alt="image-3.png"></p>
<p>任何一方修改这块区域，另一方都能立刻看到，因为它们实际上是相同的物理内存。但是请注意，这块物理内存区域在两个进程的虚拟内存中的地址却不一定相同。除非两个进程有亲缘关系，一个进程从另一个进程完整的继承了共享内存的映射关系，否则一般来说，同一块共享内存在不同进程中的地址是不同的。</p>
<h1><a id="toc-bf7" class="anchor" href="#toc-bf7"></a>抽象共享内存指针</h1>
<p>对于没有亲缘关系的进程们，同一块共享内存通常会映射到各自虚拟内存的不同地址。显然进程之间无法共享虚拟内存地址，我们需要一种通用方法，描述共享内存中的位置。其实内存地址本质上是指针到 0x00 的偏移，我们可以稍作修改，创造一个全新的概念 “共享指针”，它也是一个偏移，但其基准地址是这块共享内存的虚拟内存地址。</p>
<p><img src="https://thinkinpython.com/static/upload/20240528/upload_14cc69a68bb8d4a8d9d74f4b1bda6cf1.png" alt="image-4.png"></p>
<p>无论在哪个进程，我们都可以通过共享指针，即相对共享内存起始位置的 offset 定位到相同位置，就算各进程共享内存的映射地址（起始地址）不同也没所谓。</p>
<p>很容易得到这样的公式：</p>
<pre><code class="hljs lang-undefined">共享指针的在进程中的真实地址 = 共享内存基址 + 共享指针（也就是 offset）；
共享指针 = 共享指针的在进程中的真实地址 -  共享内存基址；
</code></pre>
<p>只要将共享内存中所有指针都改为 “虚拟指针”，那么所有进程都可以将 “虚拟指针” 转换为自己虚拟内存中的地址正确访问。</p>
<p><img src="https://thinkinpython.com/static/upload/20240528/upload_a46f02b55ba3cf4d9ba994e79d71c7f6.png" alt="image-5.png"></p>
<p>共享内存就可以被切一组 block，通过一些 “共享指针”，把空闲 block 管理起来。</p>
<h1><a id="toc-544" class="anchor" href="#toc-544"></a>动态扩展</h1>
<p>Linux 系统常见的 3 中动态内存接口：</p>
<ul>
<li><p>POSIX </p>
<ul>
<li>shm_open 函数创建。</li>
<li>ftruncate 设置大小。</li>
<li>通过mmap 将其映射到进程的地址空间，返回一个指针。</li>
<li>munmap 解除映射。</li>
<li>shm_unlink 删除共享内存对象。</li>
</ul>
</li>
<li><p>System V</p>
<ul>
<li>shmget 函数。</li>
<li>shmat 将共享内存段附加到进程的地址空间，返回一个指针。</li>
<li>shmdt 从进程的地址空间分离该共享内存。</li>
<li>shmctl 支持其他操作，如删除共享内存段。</li>
</ul>
</li>
<li><p>mmap 匿名内存映射</p>
</li>
</ul>
<p>无论哪种方式，都无法自动调整共享内存大小，有没有可能创建可以动态调整大小的共享内存池呢？前一步我们已经可以将一块共享内存按 block 管理起来，我们只需要再多申请一块共享内存，把它切分后就是一些 block 了。当所有进程都可以看到第一块共享内存的时候，如何找到新增的共享内存呢？</p>
<p><img src="https://thinkinpython.com/static/upload/20240528/upload_970a6f7747ebd62d75b7bfda5afc8240.png" alt="image-6.png"></p>
<p>我们只需要在每块共享内存的特定位置，比如开始处，保存下一个共享内存的挂载参数，SystemV 接口就保存 shm key 和 size，POSIX 就保存 name 和 size，用全 0 值表示结束。 逻辑与单向链表类似，只不过通过共享内存参数而非指针串联起来。</p>
<p>前面我们引入一层抽象：“共享指针”，以统一的形式对所有进程描述共享内存中的位置，实际上就是共享内存中的 offset。现在情况变得复杂一点，单纯依靠 offset 已经不足以定位共享内存中的位置，还需要区分是在哪个共享内存上。是时候让 “共享指针” 变得复杂一些。</p>
<p>如果你对网络地址比较熟悉，应该听说过 “子网掩码”，我们也可以将 “共享指针” 的地址空间划分为共享内存编号 + offset。</p>
<p><img src="https://thinkinpython.com/static/upload/20240528/upload_8c4e6edf6dd8d01ba98427212e23e91a.png" alt="image-7.png"></p>
<p>以 64 位地址为例，offset 只需占据 40 位就可以支持 1T 大小的单块共享内存，以目前的硬件内存价格来说，已经是相当充足了，剩下 24 位作为共享内存编号使用，支持管理 16M 个共享内存块，也相当富余。理论上说，“共享指针” 的这两部分构成比例，决定了管理较少但更大的共享内存块，或者更小但更多共享内存块。</p>
<p><img src="https://thinkinpython.com/static/upload/20240528/upload_48475947e9eacd816c4d60c26cbdb4d5.png" alt="image-8.png"></p>
<p>比如一个 “共享指针” 如图，它指向位于 1 号共享内存起始位置之后 32Bytes 位置的一块区域。共享内存中记录这些 “共享指针”，像一般指针一样构建复杂的结构。所有进程只需将 “共享指针”转换为自己的虚拟内存地址，就可以正确访问。</p>
<h1><a id="toc-25f" class="anchor" href="#toc-25f"></a>总结</h1>
<p>只要 shm 中全部使用 “共享指针”，就可以扫清多进程共享内存地址映射的不一致性障碍，以很小的代价移植常见的内存管理方案。</p>

            ]]></description>
            <pubDate>Tue, 28 May 2024 15:20:18 GMT</pubDate>
            <guid>http://www.thinkinpython.com/post/shmPoolImplement.html</guid>
        </item>
        <item>
            <title>超纲词汇检查工具使用说明</title>
            <link>http://www.thinkinpython.com/post/englishWordCheck.html</link>
            <description><![CDATA[
            <div class="toc"><ul>
<li><a href="#toc-b4d">文件清单</a></li>
<li><a href="#toc-aaa">使用要求</a></li>
<li><a href="#toc-ec0">使用方法</a></li>
</ul>
</div><h1><a id="toc-b4d" class="anchor" href="#toc-b4d"></a>文件清单</h1>
<p>压缩包中包含：</p>
<ul>
<li>可执行程序 EnglishWordCheck.ext</li>
<li>示例单词清单：english_words1.txt</li>
<li>示例 word 文档：sample.doc</li>
</ul>
<h1><a id="toc-aaa" class="anchor" href="#toc-aaa"></a>使用要求</h1>
<ol>
<li>单词清单文件</li>
</ol>
<p>文件名自定，文件类型为 txt。可以为不同的大纲分别准备单词表。</p>
<p>内容为大纲单词，各单词以空格、tab、换行分割，空白数量不限。</p>
<p>连接符连载一起的单词 <code>A-B</code> 识别为一个单词。</p>
<p>不区分大小写。</p>
<p>例如以下几种都是可以的：</p>
<pre><code class="hljs lang-undefined">apple
banana
peach
</code></pre>
<pre><code class="hljs lang-undefined">apple   banana                   peach
</code></pre>
<pre><code class="hljs lang-undefined">apple   banana
                   peach
</code></pre>
<ol start="2">
<li>检测文件</li>
</ol>
<p>仅支持  <code>docx</code>。</p>
<p>工具会检查 doc 中所有英文单词是否属于 1 中定义的大纲词汇。</p>
<p>单复数、过去时、ing 等都会自动转换为基本形式比较。也就是说单词清单中词汇的所有事态、型格都会被认为属于大纲内词汇。</p>
<h1><a id="toc-ec0" class="anchor" href="#toc-ec0"></a>使用方法</h1>
<p><img src="https://thinkinpython.com/static/upload/20240422/upload_cd9e3dd49136e25788fc41a0d44d1b08.png" alt="image.png"></p>
<p>按步骤选择词汇表、word 文档，点击 check 即可。</p>
<p>超纲词汇报表会输出在 word 文档同级目录，与 word 文档同名的 xlsx 文件中。</p>
<p><img src="https://thinkinpython.com/static/upload/20240422/upload_333da0add9a9ce2d83e0890639d1ee36.png" alt="image.png"></p>

            ]]></description>
            <pubDate>Mon, 22 Apr 2024 04:46:37 GMT</pubDate>
            <guid>http://www.thinkinpython.com/post/englishWordCheck.html</guid>
        </item>
        <item>
            <title>快速离线部署 LLama2</title>
            <link>http://www.thinkinpython.com/post/gpt4all_install.html</link>
            <description><![CDATA[
            <div class="toc"><ul>
<li><a href="#toc-fee">安装 GPT4All</a></li>
<li><a href="#toc-d14">安装模型</a></li>
<li><a href="#toc-fa1">进阶</a></li>
<li><a href="#toc-25f">总结</a></li>
</ul>
</div><p>今年 AI 发展迅速，尤其是 LLM 涌现出了爆款应用 “ChatGPT”，不会整两句 GPT 你都不好意思扫共享单车。但是由于 OpenAI 的服务限制，国内访问比较困难。再加上自己的问题多少会有一些隐私问题，如果让云厂商知道我账户里的好几千巨款，心里不踏实。信息放在云端远不如运行在本地放心。</p>
<p>感谢伟大的开源社区，Meta 开源了 Llama2 模型，据社区测评，在多数维度可以接近 ChatGPT 的效果。后来 Github 有热心大神开发了 llama.cpp，让 LLM 运行在本地 CPU 上成为现实。llama.cpp 项目的编译安装都非常简单，如果你是个程序员，会很熟悉 <code>./configure &amp;&amp; make &amp;&amp; make install</code> 三部曲。从 llama.cpp 开始折腾还是需要点耐心，想快速尝鲜的话，推荐 GPT4All 这款应用。大概看了下，它应该就是 llama.cpp 的 UI 版。</p>
<h1><a id="toc-fee" class="anchor" href="#toc-fee"></a>安装 GPT4All</h1>
<p>GPT4All 支持 Linux、Mac、Windows 平台，前往官网 <a href="https://gpt4all.io/index.html">https://gpt4all.io/index.html</a> 下载安装适合自己平台的版本。</p>
<p><img src="https://thinkinpython.com/static/upload/20231221/upload_452362d577f73513f8d80010e9799e4e.png" alt="image.png"></p>
<p>如果需要设置代理和本地缓存目录，请点击 Setting。点击下一步。</p>
<p><img src="https://thinkinpython.com/static/upload/20231221/upload_d48fd43f0317f627c818ecde318813f5.png" alt="image.png"></p>
<p>请浏览选择安装路径，因为我打算把模型文件下载到 GPT4All 子目录，一般一个中小型模型大约在 10G 左右，所以最好安装磁盘剩余空间充足。</p>
<p><img src="https://thinkinpython.com/static/upload/20231221/upload_9bfb69f6a3670ff4fee148ebf2c1bd6e.png" alt="image.png"></p>
<p>点击下一步。</p>
<p><img src="https://thinkinpython.com/static/upload/20231221/upload_36a716e1e0057700d37cbf9333ae5bdf.png" alt="image.png"></p>
<p>每个软件都有的许可协议，勾选同意，继续下一步。</p>
<p><img src="https://thinkinpython.com/static/upload/20231221/upload_ef6e37c9841a9c4f509f4a8d666fdba8.png" alt="image.png"></p>
<p>点击 Install，正式开始安装。</p>
<p><img src="https://thinkinpython.com/static/upload/20231221/upload_8048a1a84578da8b3d37de3e7177d025.png" alt="image.png"></p>
<p>安装过程需要十分钟左右，取决于你的网速。</p>
<p><img src="https://thinkinpython.com/static/upload/20231221/upload_af65de68bb604fd90caee51e743cf3a8.png" alt="image.png"></p>
<p>安装完毕。</p>
<h1><a id="toc-d14" class="anchor" href="#toc-d14"></a>安装模型</h1>
<p>GPT4All 内置了一些训练好的模型，可以通过 GPT4All 下载使用。</p>
<p>这里先说一下如何打开 GPT4All，Mac 下安装好后会看到这两个图标：</p>
<p><img src="https://thinkinpython.com/static/upload/20231221/upload_243e34b46f3b00329b5b5d5f653df647.png" alt="image.png"></p>
<p>请点击右侧图表 gpt4all 启动。左侧为管理工具。</p>
<p>Windows 下开始菜单里似乎只有左边的 maintenance tool，GPT4All 的本体需要去安装目录/bing/ 下找 chat.ext。总之非常诡异，可能这就是所谓的 “工程师文化”，不管你用户的死活。 </p>
<p>如果你顺利的找到 GPT4All 的启动图标，第一次启动会看到这个页面，提示你下载模型文件。这是因为 GPT4All 只是模型框架，需要模型文件作为初始化参数。</p>
<p><img src="https://thinkinpython.com/static/upload/20231221/upload_c6c4ead4e6158549a79f87063dec40c0.png" alt="image.png"></p>
<p>亲测 OpenOrca 还可以，其他部分模型准确度很差。</p>
<p>下载前，点击最下面的 Browse，重新选择模型保存目录，为了方便，请在 GPT4All 安装目录下新建 models 目录，并将其选定为模型下载目录。</p>
<p>点击 OpenOrca 的 download，耐心等待下载完毕。</p>
<p><img src="https://thinkinpython.com/static/upload/20231221/upload_6c8586eda809bc0af539b9488cda399b.png" alt="image.png"></p>
<p>下载完毕就可以进入对话，与 AI 畅聊。由于这些模型的训练输入主要是英语，所以对中文支持不怎么样。虽然是基于 CPU 实现推理，但也能达到 5 Tokens 每秒，还在可以用的程度。</p>
<h1><a id="toc-fa1" class="anchor" href="#toc-fa1"></a>进阶</h1>
<p>前面提到 GPT4All 应该是 llama.cpp 的 UI 版，所以它也支持的模型类型和 llama.cpp 是一样。除了从 GPT4All 自带的模型库选择不多，我们也可以自己从 huggingface 找到大量训练好的模型，放到前面设置的模型下载目录 models，GPT4All 也可以加载运行。</p>
<p>注意，模型需为 gguf 格式。</p>
<p>huggingface 访问速度很慢，模型又动则七八 G，推荐一个国内镜像 <a href="https://hf-mirror.com/">https://hf-mirror.com/</a> 下载速度可达 MB/s。</p>
<h1><a id="toc-25f" class="anchor" href="#toc-25f"></a>总结</h1>
<p>试用了三四个模型，包括一个国内某大学训练的中文模型，与 ChatGPT 还有很大的差距，应该是我的电脑能力有限，最多只能跑 7B 的 llama，再加上社区训练的模型数据量和调优没法和 OpenAI 匹敌，果然 AI 时代，数据为王。如果想在本地获得比较好的使用效果，还需要针对自己的需求继续调教模型。现阶段，个人使用还是推荐云厂商的 GPT 产品，数据更新及时，有超算支撑，使用体验更好。</p>

            ]]></description>
            <pubDate>Wed, 20 Dec 2023 16:11:28 GMT</pubDate>
            <guid>http://www.thinkinpython.com/post/gpt4all_install.html</guid>
        </item>
        <item>
            <title>bpftrace 实战(2)-向进程注入钩子</title>
            <link>http://www.thinkinpython.com/post/bpftrace_example_hook_app.html</link>
            <description><![CDATA[
            <div class="toc"><ul>
<li><a href="#toc-fdf">system 函数</a></li>
<li><a href="#toc-25f">总结</a></li>
</ul>
</div><p>在<a href="/post/bpftrace_example_mem_leak_detect.html">上一实战案例</a>中，展示了 <code>bpftrace</code> 最常规的用法：观察。利用 <code>bpftrace</code> 可以我们可以从内核层面，观察整个服务的任何函数调用状态。借助收集到的信息，辅助定位 bug。</p>
<p>最近几个月我都投入在一个分布式系统的开发，在做在线故障恢复时，需要根据一些触发条件模拟故障场景调试。这些场景都不太可能通过人工直接模拟，一般采用这些方法：</p>
<p>直接修改代码，在复合条件时 <code>abort</code>；</p>
<ul>
<li>动态库注入；</li>
<li><a href="https://zhuanlan.zhihu.com/p/370245380">利用 <code>LD_PRELOAD</code> 挟持函数</a>，改变其默认行为；</li>
<li>利用一些测试框架。
但我们有 <code>bpftrace</code>，就有了新选择，无需编译，随编随用，不要太方便。</li>
</ul>
<h1><a id="toc-fdf" class="anchor" href="#toc-fdf"></a>system 函数</h1>
<p><code>bpftrace</code> 提供了类似 C 中的 <code>system</code> 函数，用于执行 shell 命令。</p>
<p>前面我们提到，bpftrace 脚本会被编译为 ByteCode，交由内核中的 bpf VM 执行。内核是整个系统的中枢，容不得一点闪失，放任所有资源被用户脚本控制实在太危险了。如果希望通过 <code>bpftrace</code> 直接修改进程行为，显然是不为系统安全策略允许的（至少目前如此）。因此 <code>bpftrace</code> 需要在便利和安全之间权衡，结果就是绝大多数敏感资源都被 bpf VM 隔离，用户只能获得经过审查的，限制的能力。</p>
<p>虽然我们无法直接通过 <code>bpftrace</code> 影响进程的运行状态，但是 <code>system</code> 函数提供了通往 shell 的道路，我们可以借助 shell 实现目的。当然 <code>bpftrace</code> 出于安全考虑，<code>system</code> 函数的调用需要 root 权限，使用时还需为 <code>bpftrace</code> 传入 <code>--unsafe</code> 参数，表明我们要做一些 “危险” 的事。</p>
<p>回到我要处理的问题：在满足条件时，模拟进程故障，也就是杀死某些进程。下面是虚构的例子：</p>
<pre><code class="hljs lang-bash">// killme.bpf
uprobe:/my/lib/path/mylib.so:killme {
    system(<span class="hljs-string">"kill -9 %d"</span>, pid)
}
</code></pre>
<p>当 mylib.so 中的 <code>killme</code> 函数被调用时，我们就向这个进程发送 <code>kill</code> 信号杀掉它。</p>
<p>然后让我们启动 <code>bpftrace</code> ，静静等待事件被触发：</p>
<pre><code class="hljs lang-undefined">bpftrace --unsafe killme.bpf
</code></pre>
<p>很简单对吧，试想一下你甚至无需拥有这个库的源码，不需要繁杂的编译过程就可以轻松实现目的，真方便！</p>
<h1><a id="toc-25f" class="anchor" href="#toc-25f"></a>总结</h1>
<p><code>system</code> 函数为我们开了一个窗口，借助 shell 我们几乎可以轻松的实现任何目的。在我的实际应用中，甚至实现了多台机器之间通过 bpftrace 和网络彼此触发，构成复杂的消息网络（虽然有点过度使用，但很有意思）。</p>

            ]]></description>
            <pubDate>Wed, 09 Aug 2023 17:23:17 GMT</pubDate>
            <guid>http://www.thinkinpython.com/post/bpftrace_example_hook_app.html</guid>
        </item>
        <item>
            <title>bpftrace 实战 - 排查内存泄漏</title>
            <link>http://www.thinkinpython.com/post/bpftrace_example_mem_leak_detect.html</link>
            <description><![CDATA[
            <div class="toc"><ul>
<li><a href="#toc-f82">leak.c</a></li>
<li><a href="#toc-c88">探测 leak 中的 malloc 调用</a></li>
<li><a href="#toc-3b9">探测 malloc 的 size</a></li>
<li><a href="#toc-fbd">探测 malloc 的返回值</a></li>
<li><a href="#toc-fe5">探测 free 的地址</a></li>
<li><a href="#toc-25e">探测泄漏地址</a></li>
<li><a href="#toc-d16">准确定位内存泄漏</a></li>
</ul>
</div><p>内存泄漏是 C 开发非常经典的问题，目前已经存在很多优秀的内存检测工具，比如强大的 Valgrind。</p>
<p>借助 ebpftrace 我们可以非常简单、直观的找到泄漏的位置，而且非常灵活，自己决定其中的细节。</p>
<p>文中我构建了一个 <code>leak.c</code>，其中同时存在正确释放的内存和泄漏的内存，模拟一些比较复杂的内存泄漏情况。通过 ebpftrace 对运行中的 <code>leak</code> 程序统计内存的申请和释放过程定位内存泄漏，也就是 probe 两个关键的接口调用：</p>
<ul>
<li>malloc</li>
<li>free</li>
</ul>
<p>这两个接口的实现在 <code>libc</code> 中，后面我们会找到 <code>leak</code> 连接的准确 <code>libc.so</code> 文件。</p>
<h1><a id="toc-f82" class="anchor" href="#toc-f82"></a>leak.c</h1>
<pre><code class="hljs lang-c"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdio.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdlib.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;curses.h&gt;</span></span>

<span class="hljs-function"><span class="hljs-keyword">char</span> * <span class="hljs-title">wrapper1</span><span class="hljs-params">()</span></span>{
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">8</span>);
}

<span class="hljs-function"><span class="hljs-keyword">char</span> * <span class="hljs-title">wrapper2</span><span class="hljs-params">()</span></span>{
    <span class="hljs-comment">// leak here</span>
    <span class="hljs-keyword">char</span> * p = wrapper1();
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">16</span>);
}

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span>{
    <span class="hljs-keyword">char</span> * p = <span class="hljs-literal">NULL</span>;
    <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">5</span>; i ++){
        p = wrapper2();
        <span class="hljs-built_in">free</span>(p);
    }

      <span class="hljs-comment">/* wait */</span>
    getchar();
    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
<p>代码非常简单，<code>wrapper2</code> 内调用 <code>wrapper1</code> 申请到的内存没有被返回，当然永远也不会被释放，发生了泄漏；而 <code>wrapper2</code> 自己 <code>malloc</code> 一块内存返回，并在 main 中被正确释放。</p>
<ul>
<li><code>wrapper1</code> 中申请的内存泄漏了；</li>
<li><code>wrapper2</code> 中申请的内存没有泄漏；</li>
</ul>
<p>只需要简单的编译即可使用：</p>
<pre><code class="hljs lang-undefined">gcc leak.c -o leak -g
</code></pre>
<p>我的工作路径是: /home/test</p>
<h1><a id="toc-c88" class="anchor" href="#toc-c88"></a>探测 leak 中的 malloc 调用</h1>
<p>bpftrace 主要的 probe 类型：</p>
<ul>
<li>kprobe/kretprobe，内核探针</li>
<li>uprobe/uretprobe，用户态探针</li>
</ul>
<p>本文的目标是 <code>leak</code> 程序，一个用户态进程，显然应当使用 用户态探针：uprobe/uretprobe。</p>
<p>首先通过 <code>uprobe</code> 探测 <code>malloc</code> 调用。</p>
<pre><code class="hljs lang-bash">bpftrace -e <span class="hljs-string">'uprobe:/home/test/leak:malloc {printf("malloc call\n")}'</span>
</code></pre>
<p>参数格式是 <code>uprobe</code>:<code>可执行文件</code>:<code>函数名</code>，看起来一切都没有问题，但是运行上面的指令会得到这样的输出：</p>
<pre><code class="hljs lang-undefined">No probes to attach
</code></pre>
<p>原因是在 <code>/home/test/leak</code> 中找不到符号 <code>malloc</code>，我们可以自己确认这一事实：</p>
<pre><code class="hljs lang-bash"><span class="hljs-comment"># nm leak | grep malloc</span>
  U malloc@@GLIBC_2.2.5
</code></pre>
<p>可以看到 <code>malloc</code> 是一个链接自 <code>GLIBC_2.2.5</code> 的符号，并不是 <code>leak</code> 自身的符号，因此参数中可执行文件名应当改为 <code>GLIBC_2.2.5</code> 对应的 so 文件 (Linux 系统的动态链接库)，具体方法稍后介绍。</p>
<p>其实，前面的指令只要修改需要探测的函数名为 leak.c 中定义的函数，也可以正常工作，比如：</p>
<pre><code class="hljs lang-bash"><span class="hljs-comment"># bpftrace -e 'uprobe:/home/test/leak:wrapper1 {printf("wrapper call\n")}'</span>
Attaching 1 probe...
wrapper call
.... 省略一些输出
wrapper call
</code></pre>
<p>只不过探测的是 <code>leak</code> 自身的函数：<code>wrapper1</code>，并不是我们关注的目标。</p>
<p>所以，uprobe 需要指定准确的可执行文件，和该文件中的一个符号（函数）。</p>
<p>需要注意 <code>printf</code> 中的换行符 <code>\n</code>，如果没有换行符，数据只会在缓存中，只能在 bpftrace 退出时打印出结果，如果希望实时看到输出，就不要忘记它。</p>
<p>让我们继续完成 <code>malloc</code> 的探测，系统中可能存在多个 GLIBC 库，我们必须找到 <code>leak</code> 程序连接的准确文件：</p>
<pre><code class="hljs lang-bash"><span class="hljs-comment"># ldd leak</span>
linux-vdso.so.1 (0x00007ffc0afaa000)
libc.so.6 =&gt; /lib64/libc.so.6 (0x00007f55ea078000)
/lib64/ld-linux-x86-64.so.2 (0x00007f55ea43d000)
</code></pre>
<p>第二行即 <code>leak</code> 链接的 libc 动态库文件，将前面 bpftrace 参数中 “可执行文件” 替换为 <code>/lib64/libc.so.6</code>，再次尝试：</p>
<pre><code class="hljs lang-bash">bpftrace -e <span class="hljs-string">'uprobe:/lib64/libc.so.6:malloc {printf("malloc call\n")}'</span>
</code></pre>
<p>屏幕立刻输出了大量内容，但 <code>leak</code> 还没有开始运行啊？！</p>
<p>其实这些输出是系统中其他进程调用 <code>malloc</code> 引起的，动态库在系统中被所有进程共享，显然我们只关心 <code>leak</code> 程序的 <code>malloc</code> 调用，其他的应当被忽略。</p>
<p>我们需要在此参数基础上，增加 <code>filter</code>，过滤掉无关进程引起的函数调用事件。</p>
<p>bpftrace 提供了系统变量 <code>comm</code> 表示可执行文件名 (进程名)，只需要在上述指令中增加 filter，只处理 <code>comm ==&quot;leak&quot;</code> 的 <code>malloc</code> 调用事件：</p>
<pre><code class="hljs lang-bash"><span class="hljs-comment"># bpftrace -e 'uprobe:/lib64/libc.so.6:malloc /comm == "leak"/{printf("malloc call\n")}'</span>
Attaching 1 probe...
</code></pre>
<p>bpftrace 提示 <code>Attaching 1 probe...</code>，成功了，只有 <code>leak</code> 运行时才会触发输出。</p>
<p>不要关闭 bpftrace 所在终端，在另外一个终端运行 <code>leak</code>，在 bpftrace 所在终端会看到这样的输出：</p>
<pre><code class="hljs lang-bash"><span class="hljs-comment"># bpftrace -e 'uprobe:/lib64/libc.so.6:malloc /comm == "leak"/{printf("malloc call\n")}'</span>
Attaching 1 probe...
malloc call
.... 省略一些输出
malloc call
malloc call
</code></pre>
<p>成功探测到 <code>leak</code> 的 <code>malloc</code> 调用！</p>
<p>目前参数已经变得很长了，最好将它们放在一个脚本文件 <code>trace.bt</code> 中，便于编辑。</p>
<pre><code class="hljs lang-c">uprobe:/lib64/libc.so<span class="hljs-number">.6</span>:<span class="hljs-built_in">malloc</span> /comm == <span class="hljs-string">"leak"</span>/{
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"malloc call\n"</span>);
}
</code></pre>
<p>再次运行它：</p>
<pre><code class="hljs lang-bash"><span class="hljs-comment"># bpftrace trace.bt</span>
</code></pre>
<p>然后运行 <code>leak</code> 程序，会得到相同的结果。</p>
<h1><a id="toc-3b9" class="anchor" href="#toc-3b9"></a>探测 malloc 的 size</h1>
<p>bpftrace 的 uprobe 和 kprobe 可以通过内置变量 <code>arg0</code>、<code>arg1</code> ··· ··· 访问函数参数，对 <code>trace.bt</code> 稍作修改就可以打印 malloc 的参数：</p>
<pre><code class="hljs lang-C">uprobe:/lib64/libc.so<span class="hljs-number">.6</span>:<span class="hljs-built_in">malloc</span> /comm == <span class="hljs-string">"leak"</span>/{
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"malloc(%d)\n"</span>, arg0);
}
</code></pre>
<p>malloc 的原型：</p>
<pre><code class="hljs lang-c"><span class="hljs-function"><span class="hljs-keyword">void</span> *<span class="hljs-title">malloc</span><span class="hljs-params">(<span class="hljs-keyword">size_t</span> size)</span></span>;
</code></pre>
<p>第一个参数是整数，直接按 C 语言风格输出就可以。</p>
<p>运行新的 <code>trace.bt</code>:</p>
<pre><code class="hljs lang-bash"><span class="hljs-comment"># bpftrace trace.bt </span>
Attaching 1 probe...
malloc(8)
malloc(8)
malloc(16)
.... 省略一些输出
malloc(8)
malloc(16)
malloc(1024)
</code></pre>
<p>可以看到 <code>malloc</code> 调用和对应的参数，最后一个 <code>malloc(1024)</code> 是 <code>leak</code> 自动创建输出缓冲区内存申请，其他的 <code>malloc</code> 都是 <code>wrapper1</code> 和 <code>wrapper2</code> 中的内存申请，参数与 <code>leak.c</code> 源码一致。</p>
<h1><a id="toc-fbd" class="anchor" href="#toc-fbd"></a>探测 malloc 的返回值</h1>
<p>我们更关心的是 <code>malloc</code> 返回的内存地址，需借助 uretprobe 进行探测，函数返回值可通过内置变量 <code>retval</code> 访问。uretprobe 的 filter 与 <code>malloc</code> 参数探测时类似:</p>
<pre><code class="hljs lang-c">uprobe:/lib64/libc.so<span class="hljs-number">.6</span>:<span class="hljs-built_in">malloc</span> /comm == <span class="hljs-string">"leak"</span>/{
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"malloc(%d)\n"</span>, arg0);
}

uretprobe:/lib64/libc.so<span class="hljs-number">.6</span>:<span class="hljs-built_in">malloc</span> /comm == <span class="hljs-string">"leak"</span>/{
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"addr = %p\n"</span>, retval);
}
</code></pre>
<p>再次运行 bpftrace：</p>
<pre><code class="hljs lang-bash"><span class="hljs-comment"># bpftrace trace.bt </span>
Attaching 2 probes...
malloc(8)
malloc(8)
addr = 0xb892a0
addr = 0xb892a0
malloc(16)
.... 省略一些输出
malloc(16)
addr = 0xb89340
malloc(1024)
addr = 0xb89360
</code></pre>
<p>此时 <code>trace.bt</code> 中定义了 2 个 probe，分别用于探测 <code>malloc</code> 申请的内存大小和返回的地址。</p>
<h1><a id="toc-fe5" class="anchor" href="#toc-fe5"></a>探测 free 的地址</h1>
<p>目前已经可以准确捕获  <code>leak</code> 申请内存的大小和返回地址，下一步探测 <code>free</code> 了哪些地址，只需要对比 <code>malloc</code> 和 <code>free</code> 内存地址集合的差异，就能找到内存泄漏。</p>
<p>参考探测 <code>malloc</code> 的方法，探测 <code>free</code> 调用非常简单，<code>free</code> 的内存地址正好是其第一个参数，我们使用 <code>uprobe</code> 即可，下面继续扩展 <code>trace.bt</code>:</p>
<pre><code class="hljs lang-c">uprobe:/lib64/libc.so<span class="hljs-number">.6</span>:<span class="hljs-built_in">malloc</span> /comm == <span class="hljs-string">"leak"</span>/{
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"malloc(%d)\n"</span>, arg0);
}

uretprobe:/lib64/libc.so<span class="hljs-number">.6</span>:<span class="hljs-built_in">malloc</span> /comm == <span class="hljs-string">"leak"</span>/{
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"alloc addr = %p\n"</span>, retval);
}

<span class="hljs-comment">/* 探测 free 的地址 */</span>
uprobe:/lib64/libc.so<span class="hljs-number">.6</span>:<span class="hljs-built_in">free</span> /comm == <span class="hljs-string">"leak"</span>/{
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"free addr = %p\n"</span>, arg0);
}
</code></pre>
<p>运行 <code>trace.bt</code>:</p>
<pre><code class="hljs lang-bash"><span class="hljs-comment"># bpftrace trace.bt </span>
Attaching 3 probes...
malloc(8)
malloc(8)
alloc addr = 0x19e72a0
alloc addr = 0x19e72a0
malloc(16)
.... 省略一些输出
alloc addr = 0x19e7340
free addr = 0x19e7340
malloc(1024)
alloc addr = 0x19e7360
</code></pre>
<h1><a id="toc-25e" class="anchor" href="#toc-25e"></a>探测泄漏地址</h1>
<p>现在已经获得了：</p>
<ul>
<li><code>malloc</code> 的大小、地址;</li>
<li><code>free</code>  的地址；</li>
</ul>
<p>只需计算二者地址集合的差，就可以得到泄漏的地址有哪些了。</p>
<p>bpftrace 底层使用的是 eBPF 的 map 作为存储结构，可以简单的看作 K-V 存储，比如：</p>
<pre><code class="hljs lang-undefined"># bpftrace -e 'BEGIN{@kv["hello"] = 1; @kv["world"] = 2 } END{delete(@kv["world"]); return;}'
Attaching 2 probes...
^C

@kv[hello]: 1
</code></pre>
<p><code>BEGIN</code> 中 在 <code>@kv</code> 新增了 “hello”-&gt;1, &#39;world&#39; -&gt;2 两组值，<code>END</code> 通过 <code>delete</code> 方法移除了  <code>@kv</code> 中的 ‘world’-&gt;2。bpftrace 结束时自动输出 <code>@kv</code>, 其中只剩下 “hello”-&gt;1 一组值。</p>
<p>这里注意 3 点：</p>
<ol>
<li><code>@kv</code> 在多个 probe 之间共享；</li>
<li>对 <code>@kv[KEY]</code>赋值 <code>VALUE</code> 即创建对应对 KEY-VALUE；</li>
<li>调用 <code>delete(@kv[KEY])</code> 函数删除 KEY-VALUE；</li>
</ol>
<p>那么可以用一个 map  <code>@mem</code> 保存 <code>malloc</code> 返回的内存地址，当发生 <code>free</code> 调用时，从 <code>@mem</code>  中删除释放的地址，最后 <code>@mem</code> 中剩余的就是泄漏的内存地址。</p>
<pre><code class="hljs lang-c">uprobe:/lib64/libc.so<span class="hljs-number">.6</span>:<span class="hljs-built_in">malloc</span> /comm == <span class="hljs-string">"leak"</span>/{
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"malloc(%d)\n"</span>, arg0);
  @size = arg0;
}

uretprobe:/lib64/libc.so<span class="hljs-number">.6</span>:<span class="hljs-built_in">malloc</span> /comm == <span class="hljs-string">"leak"</span>/{
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"alloc addr = %p\n"</span>, retval);
  <span class="hljs-comment">/* 
  @size 在第一个 probe 被赋值, 内存大小。
  @mem 中记录的是 内存地址 -&gt; 内存大小
  */</span>
  @mem[retval] = @size;
}

uprobe:/lib64/libc.so<span class="hljs-number">.6</span>:<span class="hljs-built_in">free</span> /comm == <span class="hljs-string">"leak"</span>/{
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"free addr = %p\n"</span>, arg0);
  <span class="hljs-comment">/* arg0 是 free 的内存地址，删除被 free 的地址记录 */</span>
  <span class="hljs-keyword">delete</span>(@mem[arg0])
}
</code></pre>
<p>可以看到这样的结果：</p>
<pre><code class="hljs lang-bash"><span class="hljs-comment"># bpftrace trace.bt </span>
Attaching 3 probes...
malloc(8)
.... 省略一些输出
alloc addr = 0x225b360
^C

@mem[36025120]: 8
@mem[36025056]: 8
@mem[36024992]: 8
@mem[36025024]: 8
@mem[36025088]: 8
@mem[36025184]: 1024

@size: 1024
</code></pre>
<p>可以看到 <code>@mem</code> 中记录了一些内存地址和它们的大小，这些大小都是 8，对比 <code>leak.c</code> 代码：</p>
<pre><code class="hljs lang-c"><span class="hljs-function"><span class="hljs-keyword">char</span> * <span class="hljs-title">wrapper1</span><span class="hljs-params">()</span></span>{
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">8</span>);
}

<span class="hljs-function"><span class="hljs-keyword">char</span> * <span class="hljs-title">wrapper2</span><span class="hljs-params">()</span></span>{
    <span class="hljs-keyword">char</span> * p = wrapper1();
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">malloc</span>(<span class="hljs-number">16</span>);
}
</code></pre>
<p>可知 <code>wrapper1</code> 中的 <code>malloc</code> 发生了泄漏，与预期一致。</p>
<h1><a id="toc-d16" class="anchor" href="#toc-d16"></a>准确定位内存泄漏</h1>
<p>上一部分已经可以得到有价值结论：发生了内存，并且根据内存大小确定了发生泄漏的位置。</p>
<p>但更普遍的情况下，通过内存地址或内存大小，无法确认泄漏的位置，更可靠的方法是获得内存泄漏时的调用栈。</p>
<p>bpftrace 的内置函数 <code>ustack</code> 可以获得用户态程序当前调用栈，并且可以接受参数指定获取的栈深度，默认为获得最大深度栈。</p>
<p>前文 <code>@mem</code> 中记录的是内存地址-&gt;内存大小，我们将其修改为记录内存地址-&gt;栈:</p>
<pre><code class="hljs lang-c">uprobe:/lib64/libc.so<span class="hljs-number">.6</span>:<span class="hljs-built_in">malloc</span> /comm == <span class="hljs-string">"leak"</span>/{
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"malloc(%d)\n"</span>, arg0);
  @size = arg0;
}

uretprobe:/lib64/libc.so<span class="hljs-number">.6</span>:<span class="hljs-built_in">malloc</span> /comm == <span class="hljs-string">"leak"</span>/{
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"alloc addr = %p\n"</span>, retval);
  <span class="hljs-comment">/* 修改了这里 */</span>
  <span class="hljs-comment">// @mem[retval] = @size;</span>
  @mem[retval] = ustack();
}

uprobe:/lib64/libc.so<span class="hljs-number">.6</span>:<span class="hljs-built_in">free</span> /comm == <span class="hljs-string">"leak"</span>/{
  <span class="hljs-built_in">printf</span>(<span class="hljs-string">"free addr = %p\n"</span>, arg0);
  <span class="hljs-keyword">delete</span>(@mem[arg0])
}
</code></pre>
<p>运行结果：</p>
<pre><code class="hljs lang-bash"><span class="hljs-comment"># bpftrace trace.bt </span>
Attaching 3 probes...
malloc(8)
.... 省略一些输出
alloc addr = 0x206a360
^C

@mem[33989280]: 
        wrapper1+14
        wrapper2+18
        main+35
        __libc_start_main+243
        0x5541f689495641d7
.... 省略一些输出
@mem[33989408]: 
        wrapper1+14
        wrapper2+18
        main+35
        __libc_start_main+243
        0x5541f689495641d7
@mem[33989472]: 
        _IO_file_doallocate+144

@size: 1024
</code></pre>
<p>可以看到泄漏的内存都是在 <code>wrapper1+14</code> 申请，已经成功定位内存泄漏的位置。</p>

            ]]></description>
            <pubDate>Wed, 12 Oct 2022 08:58:50 GMT</pubDate>
            <guid>http://www.thinkinpython.com/post/bpftrace_example_mem_leak_detect.html</guid>
        </item>
        <item>
            <title>bpftrace 入门(6) 控制语句</title>
            <link>http://www.thinkinpython.com/post/bpftrace_tutorial_6.html</link>
            <description><![CDATA[
            <div class="toc"><ul>
<li><a href="#toc-0aa">条件语句</a></li>
<li><a href="#toc-69b">循环</a></li>
</ul>
</div><p>bpftrace 也提供了常见的流程控制语句：</p>
<ul>
<li>条件语句</li>
<li>循环语句</li>
</ul>
<p>如果你熟悉 C 或者 Java，它们看起来会相当眼熟。</p>
<h1><a id="toc-0aa" class="anchor" href="#toc-0aa"></a>条件语句</h1>
<p>bpftrace 的条件语句与 C 语言完全一样：</p>
<pre><code class="hljs lang-c"><span class="hljs-keyword">if</span>(condition){
  statements;        <span class="hljs-comment">//A</span>
} <span class="hljs-keyword">else</span> {
  statements;        <span class="hljs-comment">//B</span>
}
</code></pre>
<p>当满足条件时执行 A 处语句，否则执行 B 处语句。当然也可能有以下更简单的形式，没有 <code>else</code> 部分：</p>
<pre><code class="hljs lang-c"><span class="hljs-keyword">if</span>(condition){
  statements;        <span class="hljs-comment">//A</span>
}

statements;        <span class="hljs-comment">//B</span>
</code></pre>
<p>条件满足时执行 A 处语句，然后执行 B 处语句，否则跳过 A 处语句。</p>
<p>多个 <code>if-else</code> 也可能连接在一起：</p>
<pre><code class="hljs lang-c"><span class="hljs-keyword">if</span>(condition){
  statements;        <span class="hljs-comment">//A</span>
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(condition){
  statements;        <span class="hljs-comment">//B</span>
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(condition){
  statements;        <span class="hljs-comment">//C</span>
} <span class="hljs-keyword">else</span> {
  statements;        <span class="hljs-comment">//D</span>
}
</code></pre>
<p>下面是一个简单的例子：</p>
<pre><code class="hljs lang-c">#!/bin/env bpftrace
BEGIN{
        $score = $<span class="hljs-number">1</span>;

        <span class="hljs-keyword">if</span>($score &gt;= <span class="hljs-number">90</span>){
                $rate = <span class="hljs-string">"A"</span>;
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>($score &gt;= <span class="hljs-number">80</span>){
                $rate = <span class="hljs-string">"B"</span>;
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>($score &gt;= <span class="hljs-number">70</span>){
                $rate = <span class="hljs-string">"C"</span>;
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>($score &gt;= <span class="hljs-number">60</span>){
                $rate = <span class="hljs-string">"D"</span>;
        } <span class="hljs-keyword">else</span> {
                $rate = <span class="hljs-string">"not passed"</span>;
        }

        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"your rate: %s\n"</span>, $rate);
        <span class="hljs-built_in">exit</span>();
}
</code></pre>
<p><code>$score</code> 、<code>$rate</code> 都是局部变量，仅在当前 action 有效。<code>$1</code> 比较特殊，表示 bfptrace 脚本的参数，上面的代码可以保存为文件 <em>if_else.bt</em>，并赋予执行权限，直接在 shell 中运行。</p>
<pre><code class="hljs lang-bash"><span class="hljs-comment"># ./if_else.bt 40</span>
Attaching 1 probe...
your rate: not passed

<span class="hljs-comment"># ./if_else.bt 60</span>
Attaching 1 probe...
your rate: D

<span class="hljs-comment"># ./if_else.bt 80</span>
Attaching 1 probe...
your rate: B
</code></pre>
<p>不要忘记最后一行<code>exit()</code>，它可以免去 <code>ctl+c</code> 的麻烦，立刻输出结果。</p>
<h1><a id="toc-69b" class="anchor" href="#toc-69b"></a>循环</h1>
<p>bpftrace 支持一种最常见的循环形式：</p>
<pre><code class="hljs lang-c"><span class="hljs-keyword">while</span>(condition){
  <span class="hljs-comment">// do something</span>
}
</code></pre>
<p>当然，也支持 <code>continue</code> 跳过当前循环剩余部分，<code>break</code> 提前结束循环，这与 C 语言完全相同。需要注意的是，如果用变量作为循环条件，应当初始化正确的值。</p>
<pre><code class="hljs lang-bash"><span class="hljs-comment"># bpftrace -e 'BEGIN{ $i = 0; while($i &lt; 10){printf("i = %d\n", $i); $i++} exit();}'</span>
Attaching 1 probe...
i = 0
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
</code></pre>
<p>另外支持 <code>unroll</code>，用于执行确定次数的循环, 比如打印 5 次 <em>hello</em>。</p>
<pre><code class="hljs lang-bash"><span class="hljs-comment"># bpftrace -e 'BEGIN{ unroll(5){printf("hello\n");} exit()}'</span>
Attaching 1 probe...
hello
hello
hello
hello
hello
</code></pre>

            ]]></description>
            <pubDate>Wed, 28 Sep 2022 16:44:48 GMT</pubDate>
            <guid>http://www.thinkinpython.com/post/bpftrace_tutorial_6.html</guid>
        </item>
    </channel>
</rss>
