-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.json
1 lines (1 loc) · 254 KB
/
search.json
1
[{"title":"Verilog 部分特性整理","url":"/HDL/verilog/","content":"<p>找老师问有什么东西可以给我先看起来,老师发来了Verilog……</p>\n<p>孽缘未了啊,这回重头认真学一学。</p>\n<p>记录一些之前没有考虑过的内容。</p>\n<p>参考这个网站https://hdlbits.01xz.net/</p>\n<span id=\"more\"></span>\n<h1 id=\"隐式声明\">隐式声明</h1>\n<p>通常变量需要使用 <code>wire</code> 或 <code>reg</code> 声明。</p>\n<p>但是也可以直接使用 <code>wire t = a & b;</code>\n这样直接创建一个中间变量 <code>t</code> ,但是 <code>t</code>\n的宽度<strong>一定是1bit</strong>。</p>\n<p>这个在之前写CPU的时候错过无数遍了。</p>\n<h1 id=\"wire-和-reg\">wire 和 reg</h1>\n<blockquote>\n<p>A note on wire vs. reg: The left-hand-side of an assign statement\nmust be a <em>net</em> type (e.g., <code>wire</code>), while the\nleft-hand-side of a procedural assignment (in an always block) must be a\n<em>variable</em> type (e.g., <code>reg</code>). These types (wire vs.\nreg) have nothing to do with what hardware is synthesized, and is just\nsyntax left over from Verilog's use as a hardware <em>simulation</em>\nlanguage.</p>\n</blockquote>\n<p>其实没有本质区别。</p>\n<p>reg只能写在always里,wire只能写在外面通过assign或module连接。</p>\n<p>reg只是设计Verilog时的历史遗留问题。</p>\n<h1 id=\"避免锁存器\">避免锁存器</h1>\n<p>https://hdlbits.01xz.net/wiki/Always_if2</p>\n<blockquote>\n<p>一个常见的错误来源:如何避免生成锁存器(Latches)</p>\n<p>在设计电路时,你必须首先从电路的角度进行思考:</p>\n<ul>\n<li>我需要这个逻辑门</li>\n<li>我需要一个组合逻辑块,它有这些输入并产生这些输出</li>\n<li>我需要一个组合逻辑块,后面跟着一组触发器(flip-flops)</li>\n</ul>\n<p>你不能做的是:先写代码,然后希望它能生成一个合适的电路。</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">If (cpu_overheated) then shut_off_computer = 1;</span><br><span class=\"line\">If (~arrived) then keep_driving = ~gas_tank_empty;</span><br></pre></td></tr></table></figure>\n<p>语法正确的代码不一定会生成合理的电路(组合逻辑 +\n触发器)。通常的问题是:\"在你未指定的情况下会发生什么?\"。Verilog\n的回答是:保持输出不变。</p>\n<p>这种“保持输出不变”的行为意味着当前状态需要被记住,因此会产生一个锁存器(latch)。组合逻辑(例如逻辑门)无法记住任何状态。要留意\n“Warning (10240): … inferring latch(es)”\n警告消息。除非锁存器是故意设计的,否则它几乎总是表示一个错误。组合电路必须在所有情况下对所有输出赋值。这通常意味着你需要使用\n<code>else</code> 子句或对输出赋予默认值。</p>\n</blockquote>\n<p>一个最重要的思路,写HDL和其他高级语言是非常不一样的两个体系。</p>\n<p>其他编程语言描述过程,HDL如其名描述<strong>硬件结构</strong>。</p>\n<p>不管是写什么代码,都是在告诉编译器生成一个什么样的硬件结构。</p>\n<p>这个问题就是如果不明确指明所有情况,则会产生锁存器,因为Verilog在没有指明的情况下会保持wire的输出不变。</p>\n<p>总之:<strong>当使用分支语句时必须覆盖所有情况。</strong></p>\n<hr>\n<p>后续遇到继续补充</p>\n","categories":["HDL"],"tags":["HDL","FPGA","学习笔记"]},{"title":"Spring Cloud - 注册中心、中央配置、网关","url":"/cloud-native/spring-cloud/","content":"<blockquote>\n<p>本文属于学习笔记,内容可能有误、可能不全面,仅代表个人在学习这一特性时的理解和总结</p>\n</blockquote>\n<p>实习第一天,主要目的是学习云原生架构。今天先从 Spring Cloud\n开始。</p>\n<figure>\n<img src=\"/cloud-native/spring-cloud/202408050942244.jpg\" alt=\"31722821596_.pic\">\n<figcaption aria-hidden=\"true\">31722821596_.pic</figcaption>\n</figure>\n<p>总的来说,Spring Boot 提供构建应用的基础,Spring Cloud\n在此基础上提供了分布式系统和微服务架构所需的工具和支持。</p>\n<span id=\"more\"></span>\n<h1 id=\"注册中心eureka\">1 注册中心(Eureka)</h1>\n<h2 id=\"eureka-名字由来\">1.1 Eureka 名字由来</h2>\n<p>“Eureka”是希腊语,意思是“我发现了!”</p>\n<p>源自经典的 <strong>阿基米德洗澡时发现浮力原理</strong> 小故事。</p>\n<blockquote>\n<p>阿基米德在洗澡时发现了证明王冠是否纯金的方法(黄金密度),他激动地一边大喊“Eureka!”一边跳出澡盆奔去王宫,连衣服都忘了穿。后来人们用Eureka这个词来形容洞察浮现的瞬间。</p>\n</blockquote>\n<h2 id=\"网络架构\">1.2 网络架构</h2>\n<p>一个最简单的示例如下:</p>\n<figure>\n<img src=\"https://gitee.com/Cishoon/pic-bed/raw/master/202408051045349.png\" alt=\"image-20240805104516240\">\n<figcaption aria-hidden=\"true\">image-20240805104516240</figcaption>\n</figure>\n<p>要记住这个架构是一个微服务架构,将各个功能的实现分布式地部署在多个服务器中。每一个微服务都是独立的Springboot\nApp,分为以下三类:</p>\n<ol type=\"1\">\n<li><strong>Eureka Server(注册中心)</strong></li>\n<li><strong>Eureka Client(服务提供者)</strong></li>\n<li><strong>Eureka Client(服务消费者)</strong></li>\n</ol>\n<h3 id=\"注册中心\">1.2.1 注册中心</h3>\n<p><strong>注册中心</strong>是一个中央服务器,其他微服务都知道注册中心的Url。</p>\n<p>只需要增加配置,并在入口处增加 <code>@EnableEurekaServer</code>\n注解。</p>\n<figure class=\"highlight yaml\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"attr\">server:</span></span><br><span class=\"line\"> <span class=\"attr\">port:</span> <span class=\"number\">8761</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"attr\">eureka:</span></span><br><span class=\"line\"> <span class=\"attr\">client:</span></span><br><span class=\"line\"> <span class=\"attr\">register-with-eureka:</span> <span class=\"literal\">false</span></span><br><span class=\"line\"> <span class=\"attr\">fetch-registry:</span> <span class=\"literal\">false</span></span><br><span class=\"line\"> <span class=\"attr\">server:</span></span><br><span class=\"line\"> <span class=\"attr\">enable-self-preservation:</span> <span class=\"literal\">false</span></span><br></pre></td></tr></table></figure>\n<figure class=\"highlight java\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">@SpringBootApplication</span></span><br><span class=\"line\"><span class=\"meta\">@EnableEurekaServer</span></span><br><span class=\"line\"><span class=\"keyword\">public</span> <span class=\"keyword\">class</span> <span class=\"title class_\">EurekaServerApplication</span> {</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">public</span> <span class=\"keyword\">static</span> <span class=\"keyword\">void</span> <span class=\"title function_\">main</span><span class=\"params\">(String[] args)</span> {</span><br><span class=\"line\"> SpringApplication.run(EurekaServerApplication.class, args);</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<h3 id=\"服务提供者\">1.2.2 服务提供者</h3>\n<p><strong>服务提供者</strong>实现了一部分的 REST 接口,即实现\n<code>Controller</code>。在启动时会连接上注册中心,将他提供的接口注册到Eureka\nServer。可以有多个提供者提供相同的接口。</p>\n<p>服务提供者在配置文件中设置实例名称,以及注册中心的Url</p>\n<figure class=\"highlight yaml\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"attr\">server:</span></span><br><span class=\"line\"> <span class=\"attr\">port:</span> <span class=\"number\">8081</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"attr\">spring:</span></span><br><span class=\"line\"> <span class=\"attr\">application:</span></span><br><span class=\"line\"> <span class=\"attr\">name:</span> <span class=\"string\">provider-service</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"attr\">eureka:</span></span><br><span class=\"line\"> <span class=\"attr\">client:</span></span><br><span class=\"line\"> <span class=\"attr\">service-url:</span></span><br><span class=\"line\"> <span class=\"attr\">defaultZone:</span> <span class=\"string\">http://localhost:8761/eureka/</span></span><br></pre></td></tr></table></figure>\n<h3 id=\"服务消费者\">1.2.3 服务消费者</h3>\n<p><strong>服务消费者</strong>的工作流程:</p>\n<ul>\n<li>消费者发送请求 <code>http://provider-service/hello</code>。(这里的\n<code>provider-service</code>\n就是服务提供者设置的实例名称,<code>hello</code>\n是提供者已经实现的一个接口。)</li>\n<li><code>RestTemplate</code> 查询 Eureka 注册中心,获取\n<code>provider-service</code> 的所有实例地址。</li>\n<li>负载均衡器选择一个实例,将请求路由到该实例。</li>\n</ul>\n<p>代码实现是:</p>\n<figure class=\"highlight java\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">@RestController</span></span><br><span class=\"line\"><span class=\"keyword\">public</span> <span class=\"keyword\">class</span> <span class=\"title class_\">ConsumerController</span> {</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"meta\">@Autowired</span></span><br><span class=\"line\"> <span class=\"keyword\">private</span> RestTemplate restTemplate;</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"meta\">@GetMapping(\"/invoke\")</span></span><br><span class=\"line\"> <span class=\"keyword\">public</span> String <span class=\"title function_\">invokeProviderService</span><span class=\"params\">()</span> {</span><br><span class=\"line\"> <span class=\"keyword\">return</span> restTemplate.getForObject(<span class=\"string\">\"http://provider-service/hello\"</span>, String.class);</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>在配置中设置 <code>RestTemplate</code> ,增加\n<code>@loadBalanced</code> 负载均衡注解:</p>\n<figure class=\"highlight java\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">@Configuration</span></span><br><span class=\"line\"><span class=\"keyword\">public</span> <span class=\"keyword\">class</span> <span class=\"title class_\">AppConfig</span> {</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"meta\">@Bean</span></span><br><span class=\"line\"> <span class=\"meta\">@LoadBalanced</span></span><br><span class=\"line\"> <span class=\"keyword\">public</span> RestTemplate <span class=\"title function_\">restTemplate</span><span class=\"params\">()</span> {</span><br><span class=\"line\"> <span class=\"keyword\">return</span> <span class=\"keyword\">new</span> <span class=\"title class_\">RestTemplate</span>();</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<h4 id=\"resttemplate的简化\">RestTemplate的简化</h4>\n<blockquote>\n<p><code>RestTemplate</code> 是 Spring 提供的一个同步 HTTP\n客户端,用于简化与 RESTful\n服务的通信。它提供了多种便捷的方法,可以轻松地执行各种 HTTP 请求(如\nGET、POST、PUT、DELETE 等)并处理响应。<code>RestTemplate</code>\n可以自动处理请求和响应的序列化和反序列化,使得与 REST API\n的交互变得更加简洁和直观。</p>\n</blockquote>\n<p><code>@LoadBalanced</code> 注解是 Spring Cloud\n实现的,会自动完成以下步骤:</p>\n<ol type=\"1\">\n<li><strong>服务发现</strong>:从 Eureka\n注册中心获取指定服务的所有可用实例。</li>\n<li><strong>负载均衡</strong>:根据负载均衡策略(默认是轮询)选择一个实例。</li>\n<li><strong>请求转发</strong>:将请求转发到选择的实例。</li>\n</ol>\n<p>如果不使用 <code>@LoadBalance</code> 注解,完整的 <code>invoke</code>\n接口实现如下:</p>\n<figure class=\"highlight java\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">import</span> cn.hutool.http.HttpUtil;</span><br><span class=\"line\"><span class=\"keyword\">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class=\"line\"><span class=\"keyword\">import</span> org.springframework.cloud.client.ServiceInstance;</span><br><span class=\"line\"><span class=\"keyword\">import</span> org.springframework.cloud.client.discovery.DiscoveryClient;</span><br><span class=\"line\"><span class=\"keyword\">import</span> org.springframework.web.bind.annotation.GetMapping;</span><br><span class=\"line\"><span class=\"keyword\">import</span> org.springframework.web.bind.annotation.RestController;</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">import</span> java.util.List;</span><br><span class=\"line\"><span class=\"keyword\">import</span> java.util.Random;</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"meta\">@RestController</span></span><br><span class=\"line\"><span class=\"keyword\">public</span> <span class=\"keyword\">class</span> <span class=\"title class_\">ConsumerController</span> {</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"meta\">@Autowired</span></span><br><span class=\"line\"> <span class=\"keyword\">private</span> DiscoveryClient discoveryClient;</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">private</span> <span class=\"keyword\">final</span> <span class=\"type\">Random</span> <span class=\"variable\">random</span> <span class=\"operator\">=</span> <span class=\"keyword\">new</span> <span class=\"title class_\">Random</span>();</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"meta\">@GetMapping(\"/invoke\")</span></span><br><span class=\"line\"> <span class=\"keyword\">public</span> String <span class=\"title function_\">invokeProviderService</span><span class=\"params\">()</span> {</span><br><span class=\"line\"> <span class=\"comment\">// 获取 provider-service 的所有实例</span></span><br><span class=\"line\"> List<ServiceInstance> instances = discoveryClient.getInstances(<span class=\"string\">\"provider-service\"</span>);</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">if</span> (instances == <span class=\"literal\">null</span> || instances.isEmpty()) {</span><br><span class=\"line\"> <span class=\"keyword\">throw</span> <span class=\"keyword\">new</span> <span class=\"title class_\">IllegalStateException</span>(<span class=\"string\">\"No instances available for provider-service\"</span>);</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"comment\">// 随机选择一个实例(简单的负载均衡策略)</span></span><br><span class=\"line\"> <span class=\"type\">ServiceInstance</span> <span class=\"variable\">selectedInstance</span> <span class=\"operator\">=</span> instances.get(random.nextInt(instances.size()));</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"comment\">// 构造服务URL</span></span><br><span class=\"line\"> <span class=\"type\">String</span> <span class=\"variable\">url</span> <span class=\"operator\">=</span> selectedInstance.getUri().toString() + <span class=\"string\">\"/hello\"</span>;</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"comment\">// 使用 Hutool 发送 HTTP 请求</span></span><br><span class=\"line\"> <span class=\"keyword\">return</span> HttpUtil.get(url);</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<h1 id=\"中央配置spring-cloud-config\">2 中央配置(Spring Cloud\nConfig)</h1>\n<p>原理差不多。中央配置与服务注册的逻辑是独立的,他们互不相关。</p>\n<p>也分为 <strong>config-client</strong> 和\n<strong>config-server</strong>.</p>\n<p>配置文件参考网上其他教程。</p>\n<hr>\n<p>我测试过程中遇到一个问题,client一直没有向 server\n发送获取配置的请求。</p>\n<p>原因:</p>\n<p><code>pom.xml</code> 中必须有以下配置(开始运行时获取配置)</p>\n<figure class=\"highlight xml\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"tag\"><<span class=\"name\">dependency</span>></span></span><br><span class=\"line\"> <span class=\"tag\"><<span class=\"name\">groupId</span>></span>org.springframework.cloud<span class=\"tag\"></<span class=\"name\">groupId</span>></span></span><br><span class=\"line\"> <span class=\"tag\"><<span class=\"name\">artifactId</span>></span>spring-cloud-starter-config<span class=\"tag\"></<span class=\"name\">artifactId</span>></span></span><br><span class=\"line\"><span class=\"tag\"></<span class=\"name\">dependency</span>></span></span><br></pre></td></tr></table></figure>\n<p>并且不能有 server 的依赖:</p>\n<figure class=\"highlight xml\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"tag\"><<span class=\"name\">dependency</span>></span></span><br><span class=\"line\"> <span class=\"tag\"><<span class=\"name\">groupId</span>></span>org.springframework.cloud<span class=\"tag\"></<span class=\"name\">groupId</span>></span></span><br><span class=\"line\"> <span class=\"tag\"><<span class=\"name\">artifactId</span>></span>spring-cloud-config-server<span class=\"tag\"></<span class=\"name\">artifactId</span>></span></span><br><span class=\"line\"><span class=\"tag\"></<span class=\"name\">dependency</span>></span></span><br></pre></td></tr></table></figure>\n<p>我就是在client的 <code>pom.xml</code> 里不小心赋值进去了\n<code>config-server</code> 的依赖,导致一直没有获取配置。</p>\n<hr>\n<p>另外,有两个设计思路:</p>\n<ol type=\"1\">\n<li>config-client 连接 config-server,自动获取 config-server\n的注册中心</li>\n<li>设置注册中心,获取注册中心里的 config-server</li>\n</ol>\n<p>总之目的就是有一个统一的配置文件管理仓库,所有微服务启动时都能获取到最新的配置。</p>\n<h1 id=\"网关spring-cloud-gateway\">3 网关(Spring Cloud Gateway)</h1>\n<p>也是一个单独的应用。通过集成 Spring Cloud\nGateway,可以在微服务架构中实现集中路由和过滤功能。</p>\n<p>贴一下GPT的回答。</p>\n<h3 id=\"spring-cloud-gateway-简介\">Spring Cloud Gateway 简介</h3>\n<p>Spring Cloud Gateway 是一种基于 Spring Framework 5、Spring Boot 2 和\nProject Reactor 的 API\n网关,旨在为微服务架构提供路由、过滤、监控和安全功能。与 Zuul\n不同,Spring Cloud Gateway 使用非阻塞的 WebFlux\n框架,这使得它在处理高并发请求时更加高效。</p>\n<h3 id=\"集成-spring-cloud-gateway\">集成 Spring Cloud Gateway</h3>\n<h4 id=\"创建-spring-cloud-gateway-项目\">1. 创建 Spring Cloud Gateway\n项目</h4>\n<ol type=\"1\">\n<li><strong>创建一个新的 Spring Boot 项目</strong>。</li>\n<li><strong>添加依赖</strong>:</li>\n</ol>\n<p>在 <code>pom.xml</code> 中添加 Spring Cloud Gateway 和 Eureka Client\n的依赖:</p>\n<figure class=\"highlight xml\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"tag\"><<span class=\"name\">dependencies</span>></span></span><br><span class=\"line\"> <span class=\"tag\"><<span class=\"name\">dependency</span>></span></span><br><span class=\"line\"> <span class=\"tag\"><<span class=\"name\">groupId</span>></span>org.springframework.cloud<span class=\"tag\"></<span class=\"name\">groupId</span>></span></span><br><span class=\"line\"> <span class=\"tag\"><<span class=\"name\">artifactId</span>></span>spring-cloud-starter-gateway<span class=\"tag\"></<span class=\"name\">artifactId</span>></span></span><br><span class=\"line\"> <span class=\"tag\"></<span class=\"name\">dependency</span>></span></span><br><span class=\"line\"> <span class=\"tag\"><<span class=\"name\">dependency</span>></span></span><br><span class=\"line\"> <span class=\"tag\"><<span class=\"name\">groupId</span>></span>org.springframework.cloud<span class=\"tag\"></<span class=\"name\">groupId</span>></span></span><br><span class=\"line\"> <span class=\"tag\"><<span class=\"name\">artifactId</span>></span>spring-cloud-starter-netflix-eureka-client<span class=\"tag\"></<span class=\"name\">artifactId</span>></span></span><br><span class=\"line\"> <span class=\"tag\"></<span class=\"name\">dependency</span>></span></span><br><span class=\"line\"> <span class=\"tag\"><<span class=\"name\">dependency</span>></span></span><br><span class=\"line\"> <span class=\"tag\"><<span class=\"name\">groupId</span>></span>org.springframework.boot<span class=\"tag\"></<span class=\"name\">groupId</span>></span></span><br><span class=\"line\"> <span class=\"tag\"><<span class=\"name\">artifactId</span>></span>spring-boot-starter-actuator<span class=\"tag\"></<span class=\"name\">artifactId</span>></span></span><br><span class=\"line\"> <span class=\"tag\"></<span class=\"name\">dependency</span>></span></span><br><span class=\"line\"><span class=\"tag\"></<span class=\"name\">dependencies</span>></span></span><br></pre></td></tr></table></figure>\n<h4 id=\"配置-gateway\">2. 配置 Gateway</h4>\n<p>在 <code>src/main/resources/application.yml</code> 中配置\nGateway:</p>\n<figure class=\"highlight yaml\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"attr\">server:</span></span><br><span class=\"line\"> <span class=\"attr\">port:</span> <span class=\"number\">8080</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"attr\">spring:</span></span><br><span class=\"line\"> <span class=\"attr\">application:</span></span><br><span class=\"line\"> <span class=\"attr\">name:</span> <span class=\"string\">gateway</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"attr\">eureka:</span></span><br><span class=\"line\"> <span class=\"attr\">client:</span></span><br><span class=\"line\"> <span class=\"attr\">service-url:</span></span><br><span class=\"line\"> <span class=\"attr\">defaultZone:</span> <span class=\"string\">http://localhost:8761/eureka/</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"attr\">spring:</span></span><br><span class=\"line\"> <span class=\"attr\">cloud:</span></span><br><span class=\"line\"> <span class=\"attr\">gateway:</span></span><br><span class=\"line\"> <span class=\"attr\">routes:</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"attr\">id:</span> <span class=\"string\">provider-service</span></span><br><span class=\"line\"> <span class=\"attr\">uri:</span> <span class=\"string\">lb://provider-service</span></span><br><span class=\"line\"> <span class=\"attr\">predicates:</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"string\">Path=/provider/**</span></span><br><span class=\"line\"> <span class=\"attr\">filters:</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"string\">StripPrefix=1</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"attr\">id:</span> <span class=\"string\">consumer-service</span></span><br><span class=\"line\"> <span class=\"attr\">uri:</span> <span class=\"string\">lb://consumer-service</span></span><br><span class=\"line\"> <span class=\"attr\">predicates:</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"string\">Path=/consumer/**</span></span><br><span class=\"line\"> <span class=\"attr\">filters:</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"string\">StripPrefix=1</span></span><br></pre></td></tr></table></figure>\n<p>以上配置定义了两个路由: - <code>/provider/**</code>\n路径的请求将被转发到 <code>provider-service</code>。 -\n<code>/consumer/**</code> 路径的请求将被转发到\n<code>consumer-service</code>。</p>\n<p><code>StripPrefix=1</code> 表示在转发请求之前,将 URL 路径前缀\n<code>/provider</code> 或 <code>/consumer</code> 去掉。</p>\n<h4 id=\"启动类\">3. 启动类</h4>\n<p>在主类中添加 <code>@SpringBootApplication</code> 和\n<code>@EnableEurekaClient</code> 注解:</p>\n<figure class=\"highlight java\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"meta\">@SpringBootApplication</span></span><br><span class=\"line\"><span class=\"meta\">@EnableEurekaClient</span></span><br><span class=\"line\"><span class=\"keyword\">public</span> <span class=\"keyword\">class</span> <span class=\"title class_\">GatewayApplication</span> {</span><br><span class=\"line\"> <span class=\"keyword\">public</span> <span class=\"keyword\">static</span> <span class=\"keyword\">void</span> <span class=\"title function_\">main</span><span class=\"params\">(String[] args)</span> {</span><br><span class=\"line\"> SpringApplication.run(GatewayApplication.class, args);</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<h3 id=\"启动和测试\">启动和测试</h3>\n<ol type=\"1\">\n<li><strong>启动 Eureka Server</strong>。</li>\n<li><strong>启动 Config Server</strong>。</li>\n<li><strong>启动多个服务提供者实例</strong>。</li>\n<li><strong>启动服务消费者</strong>。</li>\n<li><strong>启动 Gateway</strong>。</li>\n</ol>\n<h3 id=\"测试-gateway-路由\">测试 Gateway 路由</h3>\n<p>访问 Gateway 路由以测试:</p>\n<ol type=\"1\">\n<li><p>测试服务提供者: <figure class=\"highlight sh\"><table><tr><td class=\"code\"><pre><span class=\"line\">curl http://localhost:8080/provider/hello</span><br></pre></td></tr></table></figure> 或在浏览器中访问:\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">http://localhost:8080/provider/hello</span><br></pre></td></tr></table></figure></p></li>\n<li><p>测试服务消费者: <figure class=\"highlight sh\"><table><tr><td class=\"code\"><pre><span class=\"line\">curl http://localhost:8080/consumer/invoke</span><br></pre></td></tr></table></figure> 或在浏览器中访问:\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">http://localhost:8080/consumer/invoke</span><br></pre></td></tr></table></figure></p></li>\n</ol>\n<h3 id=\"总结\">总结</h3>\n<p>通过集成 Spring Cloud\nGateway,您可以在微服务架构中实现集中路由和过滤功能。它提供了强大的功能和灵活性,可以根据需要进行自定义和扩展。在上述配置中,我们实现了基本的服务路由,实际项目中可以根据需求增加更多的路由规则和过滤器。</p>\n<h1 id=\"总结-1\">总结</h1>\n<p>终于接触了微服务架构。初步了解了Spring\nCloud的框架。总之就是进一步的分布,进一步的解耦。</p>\n","categories":["笔记"],"tags":["实习,SpringCloud,微服务"]},{"title":"Rust 学习笔记 - 变量与可变性","url":"/rust/1-%E5%8F%98%E9%87%8F%E4%B8%8E%E5%8F%AF%E5%8F%98%E6%80%A7/","content":"<blockquote>\n<p>本文属于学习笔记,内容可能有误、可能不全面,仅代表个人在学习这一特性时的理解和总结</p>\n</blockquote>\n<p>开始学习Rust。这是第一篇学习笔记,记录一些基础的概念。</p>\n<span id=\"more\"></span>\n<h1 id=\"rust-变量与可变性\">1 Rust 变量与可变性</h1>\n<h2 id=\"常量\">1.1 常量</h2>\n<p>Rust中的常量与其他语言类似,使用 <code>const</code>\n声明,命名规范为<strong>所有字母大写,用下划线分割</strong> 。</p>\n<p>常量声明时,<strong>必须指定类型</strong>,<strong>必须赋初值</strong>。并且初值只能是<strong>常量表达式</strong>,不能是函数的调用结果、或是运行过程中计算得到的值。</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">const</span> THREE_HOURS_IN_SECONDS: <span class=\"type\">u32</span> = <span class=\"number\">60</span> * <span class=\"number\">60</span> * <span class=\"number\">3</span>;</span><br></pre></td></tr></table></figure>\n<h2 id=\"变量\">1.2 变量</h2>\n<p>Rust中的变量使用 <code>let</code> 声明,可以自动推导类别,也可以使用\n<code>:</code> 指定类别。</p>\n<p>变量又分为:<strong>可变变量</strong>和<strong>不可变变量</strong>,<code>let</code>\n声明的默认是不可变变量,在变量前加上 <code>mut</code> 才是可变变量。</p>\n<h3 id=\"可变变量\">1.2.1 可变变量</h3>\n<p>可变变量的使用方法是符合在其他编程语言里使用习惯的写法,直接使用\n<code>=</code> 进行赋值,变量的类型无法更改。</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"keyword\">mut </span><span class=\"variable\">x</span> = <span class=\"number\">5</span>;</span><br><span class=\"line\"> x = <span class=\"number\">6</span>; <span class=\"comment\">// 正确</span></span><br><span class=\"line\"> x = <span class=\"string\">\"abc\"</span>; <span class=\"comment\">// 改变了类型,错误</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<h3 id=\"不可变变量-shadow\">1.2.2 不可变变量 & Shadow</h3>\n<p>顾名思义,不可变变量无法直接复制修改:</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">x</span> = <span class=\"number\">5</span>;</span><br><span class=\"line\"> x = <span class=\"number\">6</span>; <span class=\"comment\">// 错误 </span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<figure class=\"highlight sh\"><table><tr><td class=\"code\"><pre><span class=\"line\">error[E0384]: cannot assign twice to immutable variable `x`</span><br><span class=\"line\"> --> test.rs:3:5</span><br><span class=\"line\"> |</span><br><span class=\"line\">2 | <span class=\"built_in\">let</span> x = 5;</span><br><span class=\"line\"> | -</span><br><span class=\"line\"> | |</span><br><span class=\"line\"> | first assignment to `x`</span><br><span class=\"line\"> | <span class=\"built_in\">help</span>: consider making this binding mutable: `mut x`</span><br><span class=\"line\">3 | x = 6; // 错误 </span><br><span class=\"line\"> | ^^^^^ cannot assign twice to immutable variable</span><br><span class=\"line\"></span><br><span class=\"line\">error: aborting due to 1 previous error; 2 warnings emitted</span><br></pre></td></tr></table></figure>\n<p>传统的方法在Rust里变得复杂了,说明Rust一定提出了一个更好的特性——<strong>Shadow</strong></p>\n<p>Rust允许使用 <code>let</code> 创建同名变量,例如:</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">x</span> = <span class=\"number\">5</span>;</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"The value of x is: {}\"</span>, x);</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">x</span> = <span class=\"number\">6</span>;</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"The value of x is: {}\"</span>, x);</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">x</span> = x * <span class=\"number\">2</span>;</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"The value of x is: {}\"</span>, x);</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">x</span> = <span class=\"string\">\"hello\"</span>;</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"The value of x is: {}\"</span>, x);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>结果是:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">The value of x is: 5</span><br><span class=\"line\">The value of x is: 6</span><br><span class=\"line\">The value of x is: 12</span><br><span class=\"line\">The value of x is: hello</span><br></pre></td></tr></table></figure>\n<p>并不像其他编程语言里,定义同名变量通常会报错。Rust直接使用后定义的变量覆盖之前定义的变量。因为这是重新定义变量,所以甚至可以改变变量的类型。</p>\n<p>这个过程被取了一个好听的名字\nShadow,新变量像把旧变量罩住了、盖住了一样。</p>\n<p>这个特性最好用的地方就在于<strong>可以改变变量的类型</strong>。</p>\n<p>在其他语言中经常有以下情况,在类型转换前后需要定义两个不同名称的变量,实际上他们表达的含义是完全相同的。</p>\n<figure class=\"highlight c++\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"type\">int</span> <span class=\"title\">main</span><span class=\"params\">()</span> </span>{</span><br><span class=\"line\">\t<span class=\"type\">char</span> score_str[] = <span class=\"string\">\"123\"</span>;</span><br><span class=\"line\"> <span class=\"type\">int</span> score = <span class=\"built_in\">atoi</span>(score_str);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>使用 Rust 可以解决这个问题:</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">score</span> = <span class=\"string\">\"123\"</span>;</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">score</span>: <span class=\"type\">i32</span> = score.<span class=\"title function_ invoke__\">parse</span>().<span class=\"title function_ invoke__\">unwrap</span>();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>即保证了变量的强类型安全性,又保留了使用的便捷性。很优雅。</p>\n<p>还有一个情景 shadow\n很好用。假如我在写一个很复杂的数学公式,公式的好几个部分都用到了同样的符号\n<code>a</code></p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\">\t<span class=\"keyword\">let</span> <span class=\"variable\">a</span> = <span class=\"number\">3</span>;</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">b</span> = a + <span class=\"number\">1</span> ... </span><br><span class=\"line\"> <span class=\"comment\">// 此处省略一大堆内容</span></span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"comment\">// 在里上面的a隔了好多行的地方,我又想定义一个变量a</span></span><br><span class=\"line\"> <span class=\"comment\">// 我可以当作之前从来没有使用过那个变量a一样,直接定义一个新的a</span></span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">a</span> = <span class=\"number\">4</span>;</span><br><span class=\"line\"> <span class=\"comment\">// 在后续的代码中,我也会很自然的认为a就是我最近定义的a = 4</span></span><br><span class=\"line\"> <span class=\"comment\">// 而不会是之前的 a = 3</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>另外,shadow 可变变量会报 warning:</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"keyword\">mut </span><span class=\"variable\">score</span> = <span class=\"string\">\"123\"</span>;</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">score</span>: <span class=\"type\">i32</span> = score.<span class=\"title function_ invoke__\">parse</span>().<span class=\"title function_ invoke__\">unwrap</span>();</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"score: {}\"</span>, score);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<figure class=\"highlight sh\"><table><tr><td class=\"code\"><pre><span class=\"line\">warning: variable does not need to be mutable</span><br><span class=\"line\"> --> test.rs:2:9</span><br><span class=\"line\"> |</span><br><span class=\"line\">2 | <span class=\"built_in\">let</span> mut score = <span class=\"string\">\"123\"</span>;</span><br><span class=\"line\"> | ----^^^^^</span><br><span class=\"line\"> | |</span><br><span class=\"line\"> | <span class=\"built_in\">help</span>: remove this `mut`</span><br><span class=\"line\"> |</span><br><span class=\"line\"> = note: `#[warn(unused_mut)]` on by default</span><br><span class=\"line\"></span><br><span class=\"line\">warning: 1 warning emitted</span><br><span class=\"line\"></span><br><span class=\"line\">score: 123</span><br></pre></td></tr></table></figure>\n<p>因为这样写语法上没问题,但是这样就改变了使用 <code>mut</code>\n的本意。</p>\n<p>我的理解是:</p>\n<ul>\n<li>如果需要使用的变量是传统意义的变量,也就是其他编程语言中最常用的变量,使用\n<code>let mut</code> 声明可变变量。</li>\n<li>不可变变量的 shadow\n就是用来解决无用中间变量过多的问题。生命周期短的变量,都可以使用不可变变量,因为他们通常不会被修改,并且影响的范围很小。解决了一个变量名焦虑的问题。</li>\n</ul>\n","categories":["笔记"],"tags":["学习笔记","Rust"]},{"title":"Rust 学习笔记 - 所有权","url":"/rust/3-%E6%89%80%E6%9C%89%E6%9D%83/","content":"<blockquote>\n<p>本文属于学习笔记,内容可能有误、可能不全面,仅代表个人在学习这一特性时的理解和总结</p>\n</blockquote>\n<p>本文记录了 Rust 所有权 (<em>ownership</em>) 相关的内容。</p>\n<span id=\"more\"></span>\n<p>其他语言的内存管理机制基本分为两类:</p>\n<ul>\n<li>以 C 为代表的,显示分配和释放内存。\n<ul>\n<li>优点:内存管理完全由程序员负责,效率高</li>\n<li>缺点:显而易见,程序员会出错,容易发生内存泄漏、产生重复释放等Bug</li>\n</ul></li>\n<li>以 Java 为代表的,垃圾回收机制自动管理内存。\n<ul>\n<li>优点:方便,程序员不需要在意内存何时释放,不会发生内存泄露</li>\n<li>缺点:需要频繁扫描追踪分配的对象,慢</li>\n</ul></li>\n</ul>\n<p>而 Rust\n为了同时实现安全和高效两个目标,提出了<strong>所有权</strong>的机制。</p>\n<ul>\n<li>为了安全,就不能完全放任程序员负责内存分配,编写代码的过程中需要有所限制。</li>\n<li>为了高效,在运行阶段不能频繁进行内存检查,所以保证内存安全的算法必须在\n<strong>编译</strong> 过程实现。</li>\n</ul>\n<h1 id=\"拷贝与移动\">1 拷贝与移动</h1>\n<p>首先,简单变量类型(整型、浮点数等,以及仅包含简单类型的元组和数组),他们的长度固定,直接压入栈中就行。只在栈上的数据赋值时进行的是<strong>拷贝(<em>copy</em>)</strong>操作。</p>\n<p>即每次赋值时都会在栈内压入一个新的值,就是最符合直觉的实现。</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">x</span> = <span class=\"number\">5</span>;</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">y</span> = x;</span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"x = {}, y = {}\"</span>, x, y);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>此时栈中有两个 <code>5</code> ,分别所属于 <code>x</code> 和\n<code>y</code>。</p>\n<blockquote>\n<p>这里说简单变量类型不够严谨,准确说是实现了 Copy trait 的类型。</p>\n</blockquote>\n<p>对于复杂类型的变量,例如 <code>String</code>\n,需要在堆中分配空间,在栈中压入一个<strong>指针</strong>,指向堆中的空间。</p>\n<figure>\n<img src=\"/rust/3-%E6%89%80%E6%9C%89%E6%9D%83/202407292226831.svg\" alt=\"String in memory\">\n<figcaption aria-hidden=\"true\">String in memory</figcaption>\n</figure>\n<p>此类变量的类型在 Rust 中显示为 <code>{unknown}</code> ,</p>\n<figure>\n<img src=\"/rust/3-%E6%89%80%E6%9C%89%E6%9D%83/202407292223784.png\" alt=\"image-20240729222344611\">\n<figcaption aria-hidden=\"true\">image-20240729222344611</figcaption>\n</figure>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">x</span> = <span class=\"type\">String</span>::<span class=\"title function_ invoke__\">from</span>(<span class=\"string\">\"hello\"</span>);</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">y</span> = x;</span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"x = {}, y = {}\"</span>, x, y); <span class=\"comment\">// 报错,x无法使用</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>在执行 <code>let y = x;</code>\n时,并不是像其他语言那样浅拷贝,创建了第二个指针 <code>y</code>\n指向相同的内存空间。</p>\n<p>而是类似于执行:<code>y = std::move(x)</code> ,之后 <code>x</code>\n就无法再使用了。</p>\n<p>这个特性使得可以确保字符串 <code>hello</code>\n的内存空间<strong>仅所属于一个变量</strong>。</p>\n<p>这样做的好处是,堆空间的生命周期与变量的作用域强绑定。当\n<code>y</code> 离开作用域时,就释放 <code>y</code> 所指的内存空间。</p>\n<p>同样的,默认的函数传参过程也是执行 <code>move</code>:</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">x</span> = <span class=\"type\">String</span>::<span class=\"title function_ invoke__\">from</span>(<span class=\"string\">\"hello\"</span>);</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">y</span> = x;</span><br><span class=\"line\"> <span class=\"title function_ invoke__\">test</span>(y);</span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"x = {}, y = {}\"</span>, x, y); <span class=\"comment\">// 报错,x、y均无法使用</span></span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">test</span>(s: <span class=\"type\">String</span>) {</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"{}\"</span>, s);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>Rust\n里没有浅拷贝的概念,并且默认情况下绝对不会隐式进行深拷贝,所以这种赋值操作都可以被认为是非常高效的。</p>\n<p>如果要深拷贝,使用 <code>clone</code> 方法。</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">x</span> = <span class=\"type\">String</span>::<span class=\"title function_ invoke__\">from</span>(<span class=\"string\">\"hello\"</span>);</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">y</span> = x.<span class=\"title function_ invoke__\">clone</span>();</span><br><span class=\"line\"> <span class=\"title function_ invoke__\">test</span>(y.<span class=\"title function_ invoke__\">clone</span>());</span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"x = {}, y = {}\"</span>, x, y); </span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">test</span>(s: <span class=\"type\">String</span>) {</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"{}\"</span>, s);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<figure>\n<img src=\"/rust/3-%E6%89%80%E6%9C%89%E6%9D%83/202407292253395-2850267.svg\" alt=\"s1 and s2 to two places\">\n<figcaption aria-hidden=\"true\">s1 and s2 to two places</figcaption>\n</figure>\n<p>总结一下,就是这个非常暴力、强硬的规则,保证了 Rust 的内存安全:</p>\n<ul>\n<li>一个内存空间,有且仅有一个变量具有其所有权</li>\n<li>当这个变量离开作用域时,自动销毁其内存空间</li>\n</ul>\n<p>在 Rust\n里就不会有多个指针共同拥有一段空间的所有权,也不会有指向非法空间的指针,也不会有没有被指针指向的内存空间。</p>\n<p>这使得 Rust 不需要手动释放内存,也不需要运行时垃圾回收。Rust\n为了内存安全做出的牺牲就是这套强硬的规则。</p>\n<h1 id=\"引用和借用\">2 引用和借用</h1>\n<p>但是这个规则导致了某些情况非常麻烦,例如函数传参。</p>\n<p>这是非常可笑的,把一个变量的所有权传给了一个函数,函数结束后这个变量和对应的内存空间也就被销毁了。返回原逻辑后这个参数就无法使用了。</p>\n<p>所以 Rust 还提供了 <strong>引用</strong> 的概念。</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">s1</span> = <span class=\"type\">String</span>::<span class=\"title function_ invoke__\">from</span>(<span class=\"string\">\"hello\"</span>);</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">len</span> = <span class=\"title function_ invoke__\">calculate_length</span>(&s1);</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"The length of '{}' is {}.\"</span>, s1, len);</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">calculate_length</span>(s: &<span class=\"type\">String</span>) <span class=\"punctuation\">-></span> <span class=\"type\">usize</span> {</span><br><span class=\"line\"> s.<span class=\"title function_ invoke__\">len</span>()</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<figure>\n<img src=\"/rust/3-%E6%89%80%E6%9C%89%E6%9D%83/202407292254851.svg\" alt=\"&String s pointing at String s1\">\n<figcaption aria-hidden=\"true\">&String s pointing at String\ns1</figcaption>\n</figure>\n<p>这些 & 符号就是\n<strong>引用</strong>,它们允许你使用值但不获取其所有权。</p>\n<p>应用的变量被离开作用域时,不会释放内存空间。这很正常,因为只有具有所有权的变量离开作用域时才会释放内存空间,而引用不具有所有权。</p>\n<p>同样的,默认情况下引用是 <strong>只读</strong>\n的。必须显示的规定引用的 <strong>可写</strong> 性:</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"keyword\">mut </span><span class=\"variable\">s</span> = <span class=\"type\">String</span>::<span class=\"title function_ invoke__\">from</span>(<span class=\"string\">\"hello\"</span>);</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"title function_ invoke__\">change</span>(&<span class=\"keyword\">mut</span> s);</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">change</span>(some_string: &<span class=\"keyword\">mut</span> <span class=\"type\">String</span>) {</span><br><span class=\"line\"> some_string.<span class=\"title function_ invoke__\">push_str</span>(<span class=\"string\">\", world\"</span>);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>首先,我们必须将 <code>s</code> 改为 <code>mut</code>。然后必须在调用\n<code>change</code> 函数的地方创建一个可变引用\n<code>&mut s</code>,并更新函数签名以接受一个可变引用\n<code>some_string: &mut String</code>。这就非常清楚地表明,<code>change</code>\n函数将改变它所借用的值。</p>\n<p>这样的好处是,程序员可以非常清楚的知道我所调用的函数有没有可能会修改我提供的变量,能够使得函数调用更加的解耦。</p>\n<hr>\n<p>为了防止数据冲突,Rust 对引用和借用也有限制。简单来说,就是:</p>\n<ul>\n<li>写写冲突</li>\n<li>读写冲突</li>\n</ul>\n<p>看下面两段代码:</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"keyword\">mut </span><span class=\"variable\">x</span> = <span class=\"type\">String</span>::<span class=\"title function_ invoke__\">from</span>(<span class=\"string\">\"hello\"</span>);</span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">y</span> = &<span class=\"keyword\">mut</span> x; \t\t<span class=\"comment\">// y 的生命周期开始</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">z</span> = &x;\t\t\t\t<span class=\"comment\">// 报错,读写冲突</span></span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">z_mut</span> = &<span class=\"keyword\">mut</span> x;\t\t<span class=\"comment\">// 报错,写写冲突</span></span><br><span class=\"line\"></span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"{}\"</span>, y);\t\t<span class=\"comment\">// y 的生命周期结束</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>在可变引用 <code>y</code> 的生命周期内,不能有任何其他引用。</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"keyword\">mut </span><span class=\"variable\">x</span> = <span class=\"type\">String</span>::<span class=\"title function_ invoke__\">from</span>(<span class=\"string\">\"hello\"</span>);</span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">y</span> = &x; \t\t\t<span class=\"comment\">// y 的生命周期开始</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">z</span> = &x;\t\t\t\t<span class=\"comment\">// 不报错,可以同时读取不会造成冲突</span></span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">z_mut</span> = &<span class=\"keyword\">mut</span> x;\t\t<span class=\"comment\">// 报错,读写冲突</span></span><br><span class=\"line\"></span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"{}\"</span>, y);\t\t<span class=\"comment\">// y 的生命周期结束</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>在不可变引用 <code>y</code> 的生命周期内,不能有可变引用。</p>\n<p>总结一下,Rust\n同样有一个严苛的规则,强行保证了变量不会发生访问冲突。</p>\n<h1 id=\"总结\">总结</h1>\n<p>现在 Rust\n给我一种暴力美学的感觉,用非常严格的规则在编译阶段限制死了可能存在的安全问题。</p>\n<p>另外还有一个<strong>切片(<em>slice</em>)</strong>的概念,我感觉就是对数组的局部引用,很直观,是某种特殊的引用(不一定引用整体,可以引用局部),就没有什么值得单独写的。</p>\n","categories":["笔记"],"tags":["学习笔记","Rust"]},{"title":"浙软夏令营卓越营员面试复盘","url":"/interview/%E6%B5%99%E8%BD%AF%E5%A4%8F%E4%BB%A4%E8%90%A5%E5%8D%93%E8%B6%8A%E8%90%A5%E5%91%98%E9%9D%A2%E8%AF%95/","content":"<p>参加了浙软的面试,抽签抽到倒数第二个……在门口等了五个小时,非常热……</p>\n<p>记录一下面试的内容,整理了一下经验。</p>\n<p>整体来说老师的问题很有含金量很有启发性,没有一个水问题。</p>\n<p>也暴露了我专业课准备不充分的问题。</p>\n<p>之前完全没想到,会问这么多软件工程的专业问题,老师最感兴趣的尽然是济星云……</p>\n<p>与我准备的内容大相径庭2333我只能尽量的说,感觉大错是没有的,个别题目回答不流利,让人感觉基础不扎实</p>\n<span id=\"more\"></span>\n<h1 id=\"面试内容复盘\">面试内容复盘</h1>\n<p>刚开始是一个很和蔼的老师,笑呵呵的,坐在正中间:</p>\n<ol type=\"1\">\n<li><p><strong>济星云和你名字一样,是为什么</strong></p>\n<p>热场小问题,开玩笑,讲讲就过去了</p></li>\n<li><p><strong>ChatGLM和llama的区别</strong></p>\n<p>因为我讲了一下人工智能算法精英赛,项目做的是大语言模型</p>\n<p>但是真的不会,我直接说不是做大语言模型方向的,确实不了解</p></li>\n</ol>\n<p>然后换了一个最边上的老师,应该是负责软件工程的。</p>\n<ol type=\"1\">\n<li><p><strong>你的第一个项目,这个需求是真实的吗</strong></p>\n<p>这个项目就是夏令营做的《基于分布式数字身份的电影院售票系统》</p>\n<p>电影院的背景是强行加上去的,本身就不是实际的需求</p>\n<p>他讲了一些需求要从实际出发之类的问题,我频频点头2333</p></li>\n<li><p><strong>济星云中的需求是怎么来的</strong></p>\n<p>一开始是组内大家自己根据日常需要提出的,之后有反馈和建议渠道,收集+评审,确定新需求</p>\n<p>追问,反馈渠道获得的需求都做吗?有什么其他的判断标准吗?(表述不太准确)</p>\n<p>我总结一下就是说这样不会导致这个项目很臃肿吗?(其实我觉得济星云确实是个有点臃肿的项目……)</p></li>\n<li><p><strong>如何审查反馈渠道提供的需求</strong></p>\n<p>我回答,会再由产品组的同学审核一遍,判断是不是真的有需求,有没有必要加到我们的小程序里,有些也会做问卷征集意见。</p>\n<p>老师追问,能举一个例子,有哪些需求被舍弃了</p></li>\n<li><p><strong>有哪些需求被舍弃了</strong></p>\n<p>我举了乌龙茶(实际上没有被舍弃,脑子一空)</p>\n<p>我说是在上面进行课程评价、老师评价的一个需求</p>\n<p>但是我们担心可能有同学会在上面发表一些过激言论,而且已经有了一些相同功能的产品</p>\n<p>老师补充比如学校的什么官网,我说对</p></li>\n<li><p><strong>你觉得什么是软件工程</strong></p>\n<p>……扯了一点是一个保证项目稳定开发的方法(类似这样的)</p></li>\n<li><p><strong>济星云的团队有多少人</strong></p>\n<p>我说产品组和研发组都有30~40人,(可能其实还更多)</p>\n<p>他就问这真的需要这么多人吗</p>\n<p>我说这是一个给同学们练手的平台,没有什么成本……</p>\n<p>他打断,交流也是成本,然后开始问下一个问题</p></li>\n<li><p><strong>如何管理团队,保证产品的开发效率和质量</strong></p>\n<p>我说一个大型团队会根据项目分成多个小组,每组七八个人,每个小组有一个负责人</p>\n<p>他总结,一个两层的架构。追问,是如何分组的?</p>\n<p>我说根据项目需求分组的,每一个需求会拉一个群</p>\n<p>然后隔两三天定期进行沟通,跟踪进度。</p>\n<p>质量的话最后会有一个星期的测试环节,由团队里的同学们进行测试。</p>\n<p>他又追问:</p></li>\n<li><p><strong>上过软件测试的课吗?有什么软件测试方法</strong></p>\n<p>我说白盒黑盒……然后这里愣了很久,突然忘了里面具体的测试方法了(黑盒有等价类,边界值,判定表……白盒有各种路径覆盖)</p>\n<p>这里很尴尬,我呃呃啊啊了一会儿,坐立不安2333</p>\n<p>他说你说的白盒黑盒是一种分类,还有其它分类</p>\n<p>我说噢,集成测试……他打断,集成测试是很后面的了,前面还有</p>\n<p>我就知道问什么了,说先是单元测试、功能测试、集成测试,最后还有性能测试</p>\n<p>只能说没记到烂熟于心的程度,脑子里有,但是一紧张讲不出来……</p></li>\n<li><p><strong>济星云有使用什么协同工具吗</strong></p>\n<p>我说用阿里云云效,有代码仓库,有CICD,有类似飞书的共享文档,有任务看板</p></li>\n<li><p><strong>熟悉什么设计模式?</strong></p>\n<p>我说,就从我用到过的几个讲,把我一时间想到的都说了一遍</p></li>\n<li><p><strong>讲一下单例模式和工厂模式在什么时候使用</strong></p>\n<p>就举了几个例子说明</p></li>\n</ol>\n<p>然后他开始问OJ了,就到了我觉得答的最不好的一个题目</p>\n<ol type=\"1\">\n<li><p><strong>讲一下从提交代码到判题完成的全流程</strong></p>\n<p>我一开始没get到,我以为讲一下OJ的工作过程就可以了。结果他想问的是“从浏览器输入地址到打开网页的全流程”这样的问题,要你讲出所有小细节。</p>\n<p>我都讲到开始评测的时候,他打断我,说直接就能评测吗?编译完直接就能评测吗?</p>\n<p>我蒙了,(不是吗?</p>\n<p>后来听明白了,他就是想我讲操作系统的细节。这就有点慌,没能慢慢思考,讲的有点磕巴</p>\n<p>相关的我好像只提到了进程切换的时候保存上下文,switch(我还口误说成io多路复用的select了),中断,其他我啥都没说出来。</p>\n<p>确实也没复习,有好多知识点分散在我脑子里,我串不起来,我觉得这个题扣大分,接下来就复习操作系统</p>\n<p>现在我复盘有好多可以讲的,从发送请求开始就有套接字,io多路复用这些。</p></li>\n</ol>\n<p>然后时间就已经到了,最后一个老师问我小胶质细胞的项目:</p>\n<ol type=\"1\">\n<li><p><strong>为什么要先分割再检测</strong></p>\n<p>我说因为认为可能分割会提供一些更有用的特征,作为检测的输入可以提供一些更好的依据(类似这个意思)</p>\n<p>他追问,有一个XXXX模型(名字忘了),可以同时分割和检测,有试过吗?</p>\n<p>之前我答辩的时候,问我的是Yolo本身就可以检测和分割,那个问题我准备好了,说实验过,yolo效果不如这样……就行了</p>\n<p>但是这个模型我名字都没听过,不敢多说,就直接说没用过了</p></li>\n<li><p><strong>你提到了使用了Focal Loss,讲解一下Focal\nLoss的形式</strong></p>\n<p>我说损失函数外面套一个 <span class=\"math inline\"><mjx-container class=\"MathJax\" jax=\"SVG\"><svg style=\"vertical-align: -1.126ex;\" xmlns=\"http://www.w3.org/2000/svg\" width=\"1.864ex\" height=\"3.083ex\" role=\"img\" focusable=\"false\" viewbox=\"0 -864.9 824 1362.7\"><g stroke=\"currentColor\" fill=\"currentColor\" stroke-width=\"0\" transform=\"scale(1,-1)\"><g data-mml-node=\"math\"><g data-mml-node=\"mfrac\"><g data-mml-node=\"mn\" transform=\"translate(235.2,394) scale(0.707)\"><path data-c=\"31\" d=\"M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z\"/></g><g data-mml-node=\"mi\" transform=\"translate(220,-345) scale(0.707)\"><path data-c=\"1D6FE\" d=\"M31 249Q11 249 11 258Q11 275 26 304T66 365T129 418T206 441Q233 441 239 440Q287 429 318 386T371 255Q385 195 385 170Q385 166 386 166L398 193Q418 244 443 300T486 391T508 430Q510 431 524 431H537Q543 425 543 422Q543 418 522 378T463 251T391 71Q385 55 378 6T357 -100Q341 -165 330 -190T303 -216Q286 -216 286 -188Q286 -138 340 32L346 51L347 69Q348 79 348 100Q348 257 291 317Q251 355 196 355Q148 355 108 329T51 260Q49 251 47 251Q45 249 31 249Z\"/></g><rect width=\"584\" height=\"60\" x=\"120\" y=\"220\"/></g></g></g></svg></mjx-container></span> 次方</p></li>\n</ol>\n<p>另外问了我:</p>\n<ol type=\"1\">\n<li><p><strong>你自我介绍里提到,想要实现区块链和AI的结合,你觉得有哪些地方可以用上AI</strong></p>\n<p>我说比如现在区块链的排序算法,来控制这个请求应该在哪个节点上执行的调度算法,现在是用的传统的RIFT,也许可以使用AI的一些方式来优化这里的排序</p></li>\n</ol>\n<h1 id=\"总结\">总结</h1>\n<p>面试的时候嘴巴基本没停(有2个地方有磕巴:软件测试方法想不出来、OJ+操作系统底层),但是看第二个软工老师的反应并不是很好,看不出来他的态度。</p>\n<p>问题密度非常大,我忘记看我自我介绍完还剩多少时间,应该大概还剩15分钟,上面一个老师就提了快15个问题,我语速也非常快,拼命讲拼命讲。</p>\n<p>没想到会问这么多软件工程相关的内容……我以为他会侧重项目一些细节,我详细准备的地方他都没有问。自我介绍里有一些一笔带过的技术细节,其实就是希望老师提问的时候再问一点的,也可能是因为老师也没了解这一块内容不能判断我讲的对不对、或者他知道这种问题我肯定会准备的很充分,反而挑着我没准备的内容问。</p>\n<p>以及面试前还会担心老师问一些水问题,比如为什么选择来浙大、为什么想做系统而不是AI等等,完全没问,15分钟提问时间非常扎实,全是干货,好评。</p>\n<p>问题提的确实招招致命,济星云确实就不是一个很优秀的大型项目,更多的是同学们练手的平台。但是既然这个项目老师这么喜欢问,就留下来吧。</p>\n<hr>\n<p>需要再好好准备一下软件工程相关的内容:</p>\n<ul>\n<li>设计模式,直接全背熟</li>\n<li>软件测试</li>\n<li>需求提取</li>\n<li>什么是软件工程</li>\n<li>如何提升团队合作效率</li>\n</ul>\n<p>操作系统:</p>\n<ul>\n<li>进程管理,中断等等,好好复习</li>\n<li>文件管理</li>\n<li>网络编程,套接字这些</li>\n</ul>\n<p>其实面试前重点准备了文件管理(因为写在了简历里)和网络编程(特地复习了IO多路复用),都没用上……</p>\n<p>其实也有机会,我可以在问OJ流程那个题里把网络编程加进去,但是没意识到老师问的是啥</p>\n<p>自我介绍优化一下,不要提算法精英赛了,项目内的优化方式我能讲,项目外的其他AI常见知识真的不了解了。</p>\n<p>然后开始复习,复习完把上面的问题重新回答一遍。预推免的ppt和自我介绍应该都不用重新准备了,基本上可以复用。</p>\n<hr>\n<p>最后总结个准备面试的经验:能准备的内容只有专业知识。</p>\n<p>专业知识是问到了一定得答对,打错了很扣分的。而且专业知识被问到了,如果准备的好就有一连串保证不会错的内容可以输出,很加分且增加自信。</p>\n<p>其他非专业知识的内容,面试前可能准备了好多,但是很小概率能被问到,而且被问到的时候受限于一个上下文的提问环境,可能没办法把准备的答案直接用上,还是得随机应变。所以干脆不准备了。</p>\n<p>项目相关的细节本来其实就是很熟悉的,简单过一遍就可以,相信自己在面试的情况下可以讲得出来,编也可以编出来,具体的细节老师也不知道,只要自圆其说讲的合理就行。</p>\n<h1 id=\"补充\">补充</h1>\n<p>结果通过啦!感谢老师感谢学校</p>\n","categories":["面试"],"tags":["面试","复盘","浙大","保研"]},{"title":"Rust 学习笔记 - 数据类型、函数、控制流","url":"/rust/2-%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E3%80%81%E5%87%BD%E6%95%B0%E3%80%81%E6%8E%A7%E5%88%B6%E6%B5%81/","content":"<blockquote>\n<p>本文属于学习笔记,内容可能有误、可能不全面,仅代表个人在学习这一特性时的理解和总结</p>\n</blockquote>\n<p>本文记录了对于 Rust\n数据类型、函数、控制流相关的内容。并不详细记录所有细节,只记录和其他高级语言有区别的部分。</p>\n<span id=\"more\"></span>\n<h1 id=\"数据类型\">1 数据类型</h1>\n<p>再次强调 Rust\n是一个<strong>静态类型</strong>语言,必须能在编译阶段知道所有变量的类型。不需要显式规定类型的必要前提是可以推导。</p>\n<p>例如在进行类型转换时:</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">let</span> <span class=\"variable\">guess</span>: <span class=\"type\">u32</span> = <span class=\"string\">\"42\"</span>.<span class=\"title function_ invoke__\">parse</span>().<span class=\"title function_ invoke__\">expect</span>(<span class=\"string\">\"Not a number!\"</span>);</span><br></pre></td></tr></table></figure>\n<p>这里不能确定要将 <code>42</code> 解析成什么类型,可能是\n<code>i32</code> <code>u32</code> <code>f64</code>……,所以必须指定\n<code>guess: u32</code></p>\n<p>Rust 的数据类型分为两类,标量类型(scarlar)和组合类型(compound)。</p>\n<h2 id=\"标量类型\">1.1 标量类型</h2>\n<h3 id=\"整数类型\">1.1.1 整数类型</h3>\n<p>变量名从表意的 <code>int</code> <code>long</code>\n变成了可以清晰表示数字位数的 <code>i32</code>\n<code>i64</code>,jiangly写算法就喜欢\n<code>using i64 = long long;</code>。</p>\n<p>并且最大提供了 <code>i128</code> <code>u128</code>\n,<code>u128</code> 最大能表示\n<code>340282366920938463463374607431768211455</code>\n,一般情况绝对够用了。</p>\n<p>类似于 <code>size_t</code> Rust 有 <code>isize</code>\n<code>usize</code>。位数和系统的位数一致。</p>\n<p>数字的字面量有以下特性:</p>\n<ul>\n<li>默认类型为 <code>i32</code></li>\n<li>可以在后缀增加指定类型,例如 <code>123u8</code></li>\n<li>与其他语言相同,前缀指定进制,十六进制:<code>0x</code>\n;八进制:<code>0o</code> ;二进制 <code>0b</code></li>\n<li>可以用下划线作为分隔符,例如<code>1000_0000</code></li>\n<li><strong>字节</strong>:<code>b'A'</code> 表示一个 <code>u8</code>\n类型的整数,即 <code>60</code> ,等价于C/C++中的\n<code>unsigned char</code></li>\n</ul>\n<h3 id=\"字符类型\">1.1.2 字符类型</h3>\n<p>Rust 中的字符类型(char) 和 C/C++\n中的不同,占用空间<strong>4字节</strong>,表示的是 Unicode 编码而非\nASCII 编码。</p>\n<p>总而言之,Rust 中的 char\n类型可以表示任何键盘可以打出来的<strong>一个</strong>字符。这里的<strong>一个</strong>是直观感觉的一个字符,站在用户角度的一个字符,而非程序员习惯的一个字符。</p>\n<h3 id=\"浮点类型\">1.1.3 浮点类型</h3>\n<p>基本和传统语言一样,但是字面量的<strong>默认类型</strong>为\n<code>f64</code> ,因为 Rust\n认为现代计算机中双浮点数和单浮点数的计算效率已经差距不大。</p>\n<h3 id=\"布尔类型\">1.1.4 布尔类型</h3>\n<p>基本和传统语言一样,占用空间<strong>1字节</strong>。</p>\n<h2 id=\"复合类型\">1.2 复合类型</h2>\n<p>分为元组(tuple)和数组(array)。</p>\n<h3 id=\"元组\">1.2.1 元组</h3>\n<p>元组中的元素类型可以不同。</p>\n<p>下面一个例子包含了元组的所有基本用法:</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">tup</span> = (<span class=\"number\">500</span>, <span class=\"number\">6.4</span>, <span class=\"number\">1</span>);</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">let</span> (_, y, _) = tup;</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"The value of x is: {}\"</span>, tup.<span class=\"number\">0</span>);</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"The value of y is: {}\"</span>, y);</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"The value of z is: {}\"</span>, tup.<span class=\"number\">2</span>);</span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">unit</span>: () = ();</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"The value of unit is: {:?}\"</span>, unit);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<ul>\n<li><p>使用 <code>(type1, type2, ..., type_n)</code>\n来定义元组类型</p></li>\n<li><p>用模式匹配的方式<strong>解构</strong>元组</p></li>\n<li><p>支持<strong>匿名变量</strong> <code>_</code></p></li>\n<li><p>用句点 <code>.</code> 索引访问变量</p></li>\n<li><p>特殊的,空元组 <code>()</code> 叫做<strong>单元类型</strong>(unit\ntype),该类型只有一种值,即<strong>单元值</strong>。单元值和单元类型都写作\n<code>()</code></p>\n<p>如果一个<strong>表达式</strong>不返回任何值,就隐式返回单元值。</p>\n<blockquote>\n<p>单元类型就类似于 C 中的 <code>void</code></p>\n<p>这里的<strong>表达式</strong>用编译原理语法分析过程中的状态来理解。见2.1节</p>\n</blockquote></li>\n</ul>\n<h3 id=\"数组\">1.2.2 数组</h3>\n<p>数组中的元素类型必须相同。</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">a</span>: [<span class=\"type\">i32</span>; <span class=\"number\">5</span>] = [<span class=\"number\">1</span>,<span class=\"number\">2</span>,<span class=\"number\">3</span>,<span class=\"number\">4</span>,<span class=\"number\">5</span>];</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"a has {} elements\"</span>, a.<span class=\"title function_ invoke__\">len</span>());</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"{:?}\"</span>, a);</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"a[0] = {}\"</span>, a[<span class=\"number\">0</span>]);</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"a[1] = {}\"</span>, a[<span class=\"number\">1</span>]); </span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">b</span>: [<span class=\"type\">i32</span>; <span class=\"number\">5</span>] = [<span class=\"number\">1</span>; <span class=\"number\">5</span>];</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"{:?}\"</span>, b);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<ul>\n<li>类型声明:[type; len]</li>\n<li>数组的值有两种表示:\n<ul>\n<li><code>[num1, num2, ..., num_n]</code></li>\n<li><code>[num; repeat]</code></li>\n</ul></li>\n<li>使用方括号 <code>[]</code> 索引</li>\n</ul>\n<p>和大多数其他语言相同,Rust 的数组使用栈空间。同样也有\n<code>Vector</code> 类型占用堆空间,这在后面再讨论。</p>\n<p>最重要的一点是,Rust 的索引必须在 <code>[0, len-1]</code>\n的范围里,即不可以访问未被分配的无效内存。在运行过程中,任何对无效内存的访问均会报错;在编译阶段,一些很明显的访问无效内存操作也会被检测到。</p>\n<h2 id=\"类型转换\">1.3 类型转换</h2>\n<p>暂时只讨论最简单的类型转换。还有很多使用了标准库中的一些 Trait\n进行类型转换的方法。</p>\n<blockquote>\n<p>Trait 是 Rust 中的一个重要概念,可以被简单的理解为接口。</p>\n</blockquote>\n<p>不像 C/C++ 有很多隐式类型转换的情况(如整型提升等),Rust\n中<strong>几乎所有类型转换都需要显式进行</strong>。</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">x</span>: <span class=\"type\">i32</span> = <span class=\"number\">5</span>;</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">y</span>: <span class=\"type\">i64</span> = <span class=\"number\">5</span>;</span><br><span class=\"line\"> </span><br><span class=\"line\"> x + y; <span class=\"comment\">// 报错,类型不匹配</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">x</span> = <span class=\"number\">5</span>; <span class=\"comment\">// 自动推导为i64</span></span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">y</span>: <span class=\"type\">i64</span> = <span class=\"number\">5</span>;</span><br><span class=\"line\"> </span><br><span class=\"line\"> x + y; <span class=\"comment\">// 不报错,类型为 i64</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>如果不指定 <code>x</code> 的类型为 <code>i32</code>\n,则会在类型推导过程中把 <code>x</code> 的类型推导为\n<code>i64</code>,看似是隐式类型转换了,本质上还是定义过程的类型推导。</p>\n<p>显示类型转换的方式类似于 Typescript:</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">x</span>: <span class=\"type\">i32</span> = <span class=\"number\">5</span>;</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">y</span>: <span class=\"type\">i64</span> = <span class=\"number\">5</span>;</span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">z</span> = x <span class=\"keyword\">as</span> <span class=\"type\">i64</span> + y;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>使用 <code>as type</code> 的方式转换类型。</p>\n<h1 id=\"函数语句和表达式\">2 函数、语句和表达式</h1>\n<p>这一部分站在编译原理的角度理解。</p>\n<h2 id=\"表达式\">2.1 表达式</h2>\n<p>表达式用于计算并返回值。表达式可以是常量、变量、算术运算、函数调用等。</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"number\">3</span> + <span class=\"number\">4</span> * <span class=\"number\">2</span></span><br><span class=\"line\"><span class=\"title function_ invoke__\">add</span>(y, <span class=\"number\">5</span>)</span><br></pre></td></tr></table></figure>\n<p>用来创建新作用域的大括号(代码块) <code>{}</code>\n也是一个表达式,返回值大括号里的最后一个表达式:</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\">{</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">x</span> = <span class=\"number\">3</span>;</span><br><span class=\"line\"> x + <span class=\"number\">1</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>这里的返回值就是 4。需要注意的是,<code>x + 1</code> 的末尾没有\n<code>;</code> ,如果加上分号,则变成了一个语句,而语句没有返回值。</p>\n<p>总结:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">大括号表达式 ::= { <语句列表> [表达式] }</span><br></pre></td></tr></table></figure>\n<p>如果有表达式,大括号表达式的返回值为表达式的值;如果没有表达式,返回值为单元值<code>()</code>。</p>\n<h2 id=\"语句\">2.2 语句</h2>\n<p>语句用于执行某些操作,<strong>通常不返回值</strong>。常见的语句包括变量声明、赋值、表达式语句、控制流语句(如if、for、while)等。</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">let</span> <span class=\"variable\">x</span> = <span class=\"number\">5</span>;</span><br><span class=\"line\">x = x + <span class=\"number\">1</span>;</span><br><span class=\"line\"><span class=\"keyword\">if</span> x > <span class=\"number\">5</span> {</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"x is greater than 5\"</span>);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>通常不返回值,也就是说有例外。见3.1节 if else 语句。</p>\n<h2 id=\"函数\">2.3 函数</h2>\n<p>函数是代码的基本组织单位,用于封装特定的功能。函数的定义包括函数名、参数列表、返回类型和函数体。</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">test</span>(a: <span class=\"type\">i32</span>, b: <span class=\"type\">i32</span>) <span class=\"punctuation\">-></span> <span class=\"type\">i32</span> {</span><br><span class=\"line\"> <span class=\"keyword\">if</span> a > b {<span class=\"keyword\">return</span> a - b} </span><br><span class=\"line\"> a + b</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>函数体有点像是一个大括号表达式,所以末尾的表达式可以不使用\n<code>return</code> 进行返回。用 <code>return</code>\n可以让函数提前返回。(但是大括号表达式里不能用 <code>return</code>\n进行返回)</p>\n<p>如果函数有返回值,必须指定返回值类型(否则返回值类型为单元类型\n<code>()</code> )。</p>\n<p>函数在使用前,并不需要先声明。例如</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"The value of a is: {}\"</span>, <span class=\"title function_ invoke__\">test</span>(<span class=\"number\">1</span>, <span class=\"number\">2</span>));</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">test</span>(a: <span class=\"type\">i32</span>, b: <span class=\"type\">i32</span>) <span class=\"punctuation\">-></span> <span class=\"type\">i32</span> {</span><br><span class=\"line\"> a + b</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<h1 id=\"控制流\">3 控制流</h1>\n<p>外观上最显著的不同是,条件不需要加括号。</p>\n<h2 id=\"if-语句\">3.1 if 语句</h2>\n<p>基本用法与其他语言类似,不赘述。</p>\n<p>但是 Rust 中,if-else 语句是可以有返回值的。</p>\n<blockquote>\n<p>需要明确一下,这里的有返回值指的是返回值不是单元值\n<code>()</code></p>\n</blockquote>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">x</span> = <span class=\"number\">5</span>;</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">ret</span> = <span class=\"keyword\">if</span> x > <span class=\"number\">5</span> {</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"x is greater than 5\"</span>);</span><br><span class=\"line\"> <span class=\"number\">0</span></span><br><span class=\"line\"> } <span class=\"keyword\">else</span> {</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"x is less than or equal to 5\"</span>);</span><br><span class=\"line\"> <span class=\"number\">1</span></span><br><span class=\"line\"> };</span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"ret is {}\"</span>, ret);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<ul>\n<li>必须有一个 <code>else</code>\n才可以有返回值。(否则可能没有返回值)</li>\n<li>所有大括号表达式的返回值类型必须相同。(否则类型不可推断)</li>\n</ul>\n<p>再次强调,上面说的 <strong>没有返回值</strong> 指的是\n<strong>返回值=()</strong>。如果显示指定变量的返回值就是\n<code>()</code>,不需要 <code>else</code> 也可以。</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">x</span> = <span class=\"number\">5</span>;</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">ret</span>: () = <span class=\"keyword\">if</span> x == <span class=\"number\">5</span> {</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"x is five!\"</span>);</span><br><span class=\"line\"> }; </span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>当然,这种写法并没有什么意义,只是在反向理解 Rust\n编译过程的实现。</p>\n<p>通常的用法如下,目的是压行,代码更模块化、可读性更强,类似与 python\n里的 <code>x = 0 if condition else 1</code></p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">x</span> = <span class=\"number\">5</span>;</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">ret</span> = <span class=\"keyword\">if</span> x == <span class=\"number\">5</span> {<span class=\"literal\">true</span>} <span class=\"keyword\">else</span> {<span class=\"literal\">false</span>};</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"ret is {}\"</span>, ret);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<h2 id=\"loop\">3.2 loop</h2>\n<p>Rust 提供了 3\n中循环,<code>loop</code>,<code>while</code>,<code>for</code>。<code>loop</code>\n是其他语言没有的。</p>\n<p><code>loop</code> 就是一个 <code>while true</code>\n的死循环。但是提供了一些语法糖:</p>\n<h3 id=\"嵌套循环跳出\">3.2.1 嵌套循环跳出</h3>\n<p><code>break</code>\n语句用于跳出循环,同其他语言只能跳出最内层循环。</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"keyword\">loop</span> {</span><br><span class=\"line\"> <span class=\"built_in\">print!</span>(<span class=\"string\">\"1\"</span>);</span><br><span class=\"line\"> <span class=\"keyword\">loop</span> {</span><br><span class=\"line\"> <span class=\"built_in\">print!</span>(<span class=\"string\">\"2\"</span>);</span><br><span class=\"line\"> <span class=\"keyword\">break</span>;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br><span class=\"line\"><span class=\"comment\">// 结果是 1212121212...</span></span><br></pre></td></tr></table></figure>\n<p>但是可以给外层的 <code>loop</code> 添加一个标记:<code>'label</code>\n,就可以直接跳出外层循环。</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"symbol\">'out</span>: <span class=\"keyword\">loop</span> {</span><br><span class=\"line\"> <span class=\"built_in\">print!</span>(<span class=\"string\">\"1\"</span>);</span><br><span class=\"line\"> <span class=\"keyword\">loop</span> {</span><br><span class=\"line\"> <span class=\"built_in\">print!</span>(<span class=\"string\">\"2\"</span>);</span><br><span class=\"line\"> <span class=\"keyword\">break</span> <span class=\"symbol\">'out</span>;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br><span class=\"line\"><span class=\"comment\">// 结果是 12</span></span><br></pre></td></tr></table></figure>\n<p>相比于其他语言,想要直接跳出多层循环只有两种方法:</p>\n<ul>\n<li>使用一个中间变量记录结果(<code>while (loop) {}</code> )</li>\n<li>使用goto</li>\n</ul>\n<h3 id=\"返回值\">3.2.2 返回值</h3>\n<p><code>loop</code> 可以通过 <code>break</code>\n传递返回值。太方便了。</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">fn</span> <span class=\"title function_\">main</span>() {</span><br><span class=\"line\"> <span class=\"keyword\">let</span> <span class=\"variable\">ret</span> = <span class=\"symbol\">'out</span>: <span class=\"keyword\">loop</span> {</span><br><span class=\"line\"> <span class=\"built_in\">print!</span>(<span class=\"string\">\"1\"</span>);</span><br><span class=\"line\"> <span class=\"keyword\">loop</span> {</span><br><span class=\"line\"> <span class=\"built_in\">print!</span>(<span class=\"string\">\"2\"</span>);</span><br><span class=\"line\"> <span class=\"keyword\">break</span> <span class=\"symbol\">'out</span> <span class=\"number\">123</span>;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> };</span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"ret: {}\"</span>, ret);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<h2 id=\"while循环\">3.3 while循环</h2>\n<p>没什么区别,不讲了</p>\n<h2 id=\"for循环\">3.4 for循环</h2>\n<p>没什么细节,让GPT写了一些常见的用法。</p>\n<p>在 Rust 中,<code>for</code>\n循环有多种用法,通常用于遍历集合或范围。以下是所有常见的\n<code>for</code> 循环用法:</p>\n<ol type=\"1\">\n<li><strong>遍历范围:</strong></li>\n</ol>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">for</span> <span class=\"variable\">i</span> <span class=\"keyword\">in</span> <span class=\"number\">0</span>..<span class=\"number\">5</span> {</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"{}\"</span>, i);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>上面的代码将输出 0 到 4。</p>\n<ol start=\"2\" type=\"1\">\n<li><strong>遍历集合:</strong></li>\n</ol>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">let</span> <span class=\"variable\">arr</span> = [<span class=\"number\">10</span>, <span class=\"number\">20</span>, <span class=\"number\">30</span>, <span class=\"number\">40</span>, <span class=\"number\">50</span>];</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">for</span> <span class=\"variable\">element</span> <span class=\"keyword\">in</span> arr.<span class=\"title function_ invoke__\">iter</span>() {</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"{}\"</span>, element);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>使用 <code>iter</code> 方法遍历数组。</p>\n<ol start=\"3\" type=\"1\">\n<li><strong>遍历可变集合:</strong></li>\n</ol>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">let</span> <span class=\"keyword\">mut </span><span class=\"variable\">vec</span> = <span class=\"built_in\">vec!</span>[<span class=\"number\">1</span>, <span class=\"number\">2</span>, <span class=\"number\">3</span>, <span class=\"number\">4</span>, <span class=\"number\">5</span>];</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">for</span> <span class=\"variable\">element</span> <span class=\"keyword\">in</span> vec.<span class=\"title function_ invoke__\">iter_mut</span>() {</span><br><span class=\"line\"> *element *= <span class=\"number\">2</span>;</span><br><span class=\"line\">}</span><br><span class=\"line\"><span class=\"built_in\">println!</span>(<span class=\"string\">\"{:?}\"</span>, vec);</span><br></pre></td></tr></table></figure>\n<p>使用 <code>iter_mut</code> 方法遍历和修改向量中的元素。</p>\n<ol start=\"4\" type=\"1\">\n<li><strong>遍历字符串字符:</strong></li>\n</ol>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">let</span> <span class=\"variable\">s</span> = <span class=\"string\">\"hello\"</span>;</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">for</span> <span class=\"variable\">c</span> <span class=\"keyword\">in</span> s.<span class=\"title function_ invoke__\">chars</span>() {</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"{}\"</span>, c);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>使用 <code>chars</code> 方法遍历字符串中的字符。</p>\n<ol start=\"5\" type=\"1\">\n<li><strong>遍历字节:</strong></li>\n</ol>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">let</span> <span class=\"variable\">s</span> = <span class=\"string\">\"hello\"</span>;</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">for</span> <span class=\"variable\">b</span> <span class=\"keyword\">in</span> s.<span class=\"title function_ invoke__\">bytes</span>() {</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"{}\"</span>, b);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>使用 <code>bytes</code> 方法遍历字符串中的字节。</p>\n<ol start=\"6\" type=\"1\">\n<li><strong>遍历 <code>Option</code> 类型:</strong></li>\n</ol>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">let</span> <span class=\"variable\">maybe_value</span> = <span class=\"title function_ invoke__\">Some</span>(<span class=\"number\">42</span>);</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">for</span> <span class=\"variable\">value</span> <span class=\"keyword\">in</span> maybe_value {</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"{}\"</span>, value);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>如果 <code>Option</code> 是 <code>Some</code>,则 <code>for</code>\n循环会遍历其中的值。</p>\n<ol start=\"7\" type=\"1\">\n<li><strong>遍历 <code>Result</code> 类型:</strong></li>\n</ol>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">let</span> <span class=\"variable\">result</span>: <span class=\"type\">Result</span><<span class=\"type\">i32</span>, &<span class=\"type\">str</span>> = <span class=\"title function_ invoke__\">Ok</span>(<span class=\"number\">42</span>);</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">for</span> <span class=\"variable\">value</span> <span class=\"keyword\">in</span> result {</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"{}\"</span>, value);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>如果 <code>Result</code> 是 <code>Ok</code>,则 <code>for</code>\n循环会遍历其中的值。</p>\n<ol start=\"8\" type=\"1\">\n<li><strong>结合 <code>enumerate</code> 方法:</strong></li>\n</ol>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">let</span> <span class=\"variable\">arr</span> = [<span class=\"number\">10</span>, <span class=\"number\">20</span>, <span class=\"number\">30</span>, <span class=\"number\">40</span>, <span class=\"number\">50</span>];</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">for</span> (index, value) <span class=\"keyword\">in</span> arr.<span class=\"title function_ invoke__\">iter</span>().<span class=\"title function_ invoke__\">enumerate</span>() {</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"Index: {}, Value: {}\"</span>, index, value);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>使用 <code>enumerate</code> 方法获取索引和值对。</p>\n<ol start=\"9\" type=\"1\">\n<li><strong>使用 <code>into_iter</code> 方法:</strong></li>\n</ol>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">let</span> <span class=\"variable\">vec</span> = <span class=\"built_in\">vec!</span>[<span class=\"number\">1</span>, <span class=\"number\">2</span>, <span class=\"number\">3</span>, <span class=\"number\">4</span>, <span class=\"number\">5</span>];</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">for</span> <span class=\"variable\">element</span> <span class=\"keyword\">in</span> vec.<span class=\"title function_ invoke__\">into_iter</span>() {</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"{}\"</span>, element);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>使用 <code>into_iter</code> 方法将向量所有权移动到迭代器中。</p>\n<ol start=\"10\" type=\"1\">\n<li><strong>多重循环:</strong></li>\n</ol>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">for</span> <span class=\"variable\">i</span> <span class=\"keyword\">in</span> <span class=\"number\">1</span>..<span class=\"number\">3</span> {</span><br><span class=\"line\"> <span class=\"keyword\">for</span> <span class=\"variable\">j</span> <span class=\"keyword\">in</span> <span class=\"number\">1</span>..<span class=\"number\">3</span> {</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"i: {}, j: {}\"</span>, i, j);</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>嵌套循环。</p>\n<p>在 Rust 中,<code>for</code> 循环的范围 (<code>range</code>)\n默认步长为 1,无法直接通过语法来控制步长。不过,你可以通过使用迭代器的\n<code>step_by</code> 方法来控制步长。以下是一些示例:</p>\n<ol type=\"1\">\n<li><strong>使用 <code>step_by</code> 控制步长:</strong></li>\n</ol>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">for</span> <span class=\"variable\">i</span> <span class=\"keyword\">in</span> (<span class=\"number\">0</span>..<span class=\"number\">10</span>).<span class=\"title function_ invoke__\">step_by</span>(<span class=\"number\">2</span>) {</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"{}\"</span>, i);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>上面的代码将以步长 2 遍历范围,输出 0、2、4、6、8。</p>\n<ol start=\"2\" type=\"1\">\n<li><strong>步长为负数(降序循环):</strong></li>\n</ol>\n<p>Rust 的 <code>Range</code>\n类型不支持直接创建降序范围。你可以通过自定义迭代器来实现:</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">for</span> <span class=\"variable\">i</span> <span class=\"keyword\">in</span> (<span class=\"number\">0</span>..<span class=\"number\">10</span>).<span class=\"title function_ invoke__\">rev</span>().<span class=\"title function_ invoke__\">step_by</span>(<span class=\"number\">2</span>) {</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"{}\"</span>, i);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>上面的代码将以步长 2 逆序遍历范围,输出 9、7、5、3、1。</p>\n<ol start=\"3\" type=\"1\">\n<li><strong>自定义范围和步长:</strong></li>\n</ol>\n<p>你也可以使用 <code>while</code> 循环来更灵活地控制范围和步长:</p>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">let</span> <span class=\"keyword\">mut </span><span class=\"variable\">i</span> = <span class=\"number\">0</span>;</span><br><span class=\"line\"><span class=\"keyword\">while</span> i < <span class=\"number\">10</span> {</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"{}\"</span>, i);</span><br><span class=\"line\"> i += <span class=\"number\">2</span>;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>上面的代码同样以步长 2 遍历范围,输出 0、2、4、6、8。</p>\n<ol start=\"4\" type=\"1\">\n<li><strong>遍历数组或向量时使用步长:</strong></li>\n</ol>\n<figure class=\"highlight rust\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">let</span> <span class=\"variable\">arr</span> = [<span class=\"number\">10</span>, <span class=\"number\">20</span>, <span class=\"number\">30</span>, <span class=\"number\">40</span>, <span class=\"number\">50</span>, <span class=\"number\">60</span>];</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">for</span> (index, value) <span class=\"keyword\">in</span> arr.<span class=\"title function_ invoke__\">iter</span>().<span class=\"title function_ invoke__\">enumerate</span>().<span class=\"title function_ invoke__\">step_by</span>(<span class=\"number\">2</span>) {</span><br><span class=\"line\"> <span class=\"built_in\">println!</span>(<span class=\"string\">\"Index: {}, Value: {}\"</span>, index, value);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>上面的代码以步长 2 遍历数组中的元素,输出 Index: 0, Value: 10 和\nIndex: 2, Value: 30 等。</p>\n<p>这些方法可以让你在 Rust 中通过 <code>for</code> 循环控制步长。</p>\n<h1 id=\"其他\">其他</h1>\n<p>vscode 里的 Rust 插件真的是非常的智能。</p>\n<p>cargo 的报错提示也非常的清晰,很优雅。</p>\n","categories":["笔记"],"tags":["学习笔记","Rust"]},{"title":"EVM 学习笔记","url":"/blockchain/EVM/","content":"<blockquote>\n<p>本文属于学习笔记,内容可能有误、可能不全面,仅代表个人在学习这一特性时的理解和总结</p>\n</blockquote>\n<p>老师安排的任务是EVM智能合约的加速计算,给我的论文提出了一个新的架构……</p>\n<p>但是我还不知道原始的EVM是怎么实现的,所以先学习一下</p>\n<span id=\"more\"></span>\n<h1 id=\"solidity\">1 Solidity</h1>\n<p>Solidity 是一种为实现智能合约而创建的高级编程语言。</p>\n<p>最终跑在以太坊虚拟机(EVM)上的代码,就是由Solidity编译得到的。所以得先学一下Solidity是怎么写的。</p>\n<p>基本的运算规则、语法等等查阅文档:https://learnblockchain.cn/docs/solidity</p>\n<h2 id=\"数据位置\">1.1 数据位置</h2>\n<h3 id=\"分类\">1.1.1 分类</h3>\n<ul>\n<li><code>storage</code>:用于存储状态变量,即需要长期保存的数据,生命周期与合约相同。</li>\n<li><code>memory</code>:用于存储临时变量,可以读写,生命周期在函数执行期间。</li>\n<li><code>calldata</code>:用于存储函数输入参数,是只读的,生命周期在函数执行期间。</li>\n</ul>\n<p><code>storage</code> 存储在区块链上,<code>memory</code>\n在合约执行过程中的内存中,<code>calldata</code>\n中的数据来自执行的交易本身(所以不占据额外空间而且是只读的)</p>\n<p>通过 <code>msg.data</code> 可以获取完整的 <code>calldata</code></p>\n<h3 id=\"默认位置\">1.1.2 默认位置</h3>\n<ul>\n<li><strong>状态变量</strong>:默认在<code>storage</code>中。</li>\n<li><strong>函数内部的局部变量</strong>:值类型默认在<code>memory</code>中。</li>\n<li><strong>函数参数</strong>:\n<ul>\n<li><code>external</code>函数的引用类型参数默认在<code>calldata</code>中。</li>\n<li>其他函数的引用类型参数需要显式指定<code>memory</code>或<code>storage</code>。</li>\n</ul></li>\n<li><strong>返回值</strong>:引用类型默认在<code>memory</code>中。</li>\n</ul>\n<h2 id=\"函数调用\">1.2 函数调用</h2>\n<p>(GPT)</p>\n<p>内部调用和外部调用在Solidity的合约编程中都有不同的行为,在以太坊虚拟机(EVM)的实现中也有显著的区别。以下是这两种调用方式在EVM层面的差异:</p>\n<h3 id=\"内部调用internal-call\">1. <strong>内部调用(Internal\nCall)</strong></h3>\n<p><strong>特点:</strong></p>\n<ul>\n<li><strong>直接调用</strong>:内部调用是通过直接跳转到目标函数的代码位置来实现的。这是因为内部调用发生在同一合约内部,EVM不需要额外的处理步骤,调用者和被调用者共享相同的上下文和存储。</li>\n<li><strong>无消息传递</strong>:内部调用不涉及消息传递,也没有额外的Gas消耗。因为调用在相同的合约上下文中进行,所有的状态变量和内存都是直接共享的。</li>\n<li><strong>编译优化</strong>:在内部调用时,Solidity编译器可以进行优化,如内联函数(inline\nfunctions),使得调用过程更加高效。</li>\n</ul>\n<p><strong>EVM行为:</strong></p>\n<ul>\n<li>EVM中的内部调用相当于一次代码跳转(jump),指令指针直接跳转到目标函数的起始位置。</li>\n<li>内部调用没有创建新的调用栈帧,保持相同的内存、堆栈和存储上下文。</li>\n<li>因为没有新的消息传递(即没有新的合约上下文被创建),内部调用的成本(Gas)相对较低。</li>\n</ul>\n<h3 id=\"外部调用external-call\">2. <strong>外部调用(External\nCall)</strong></h3>\n<p><strong>特点:</strong></p>\n<ul>\n<li><strong>消息传递</strong>:外部调用涉及到向另一个合约(即使是调用自身的<code>external</code>函数)发送消息,这会创建一个新的合约执行上下文,包括新的堆栈、内存和存储访问。</li>\n<li><strong>新Gas限制</strong>:每次外部调用会分配一个新的Gas限制,这意味着被调用合约的执行有自己独立的Gas预算。如果这个调用消耗了超出分配的Gas,调用将失败并回滚。</li>\n<li><strong>EVM上下文切换</strong>:外部调用会在EVM中引发上下文切换,即从调用者的上下文切换到被调用合约的上下文。这种切换会涉及到堆栈的保存和恢复,以及传递调用数据。</li>\n</ul>\n<p><strong>EVM行为:</strong></p>\n<ul>\n<li>EVM在执行外部调用时,会创建一个新的消息调用(Message\nCall)。这包括构建一个新的堆栈帧,传递调用数据(包括函数选择器和参数),以及初始化一个新的存储和内存上下文。</li>\n<li>外部调用会触发EVM的CALL指令。该指令在EVM中相对昂贵,因为它涉及到一系列的操作,如设置新的Gas限制、管理新的堆栈帧、以及潜在的跨合约调用的风险管理(例如重入攻击的防范)。</li>\n<li>外部调用的结果(如返回值或状态变更)在调用结束后返回给调用者,这通常需要通过<code>abi.decode</code>等方法进行解码。</li>\n<li>在外部调用中,如果调用的合约抛出异常或耗尽Gas,调用者合约将会得到失败的结果,并且可以选择回滚操作。</li>\n</ul>\n<h3 id=\"调用成本\">3. <strong>调用成本</strong></h3>\n<ul>\n<li><strong>内部调用</strong>:由于没有新的消息传递和上下文切换,内部调用的成本(Gas消耗)非常低。它是通过简单的跳转和共享上下文来实现的,因此更加高效。</li>\n<li><strong>外部调用</strong>:外部调用由于需要创建新的上下文、消息传递、处理潜在的返回数据等,成本较高。此外,外部调用可能带来安全风险,如重入攻击,这需要额外的防护措施。</li>\n</ul>\n<h3 id=\"安全性考虑\">4. <strong>安全性考虑</strong></h3>\n<ul>\n<li><strong>内部调用</strong>:通常被认为是安全的,因为它们在相同的合约上下文中运行,没有额外的消息传递和上下文切换。</li>\n<li><strong>外部调用</strong>:外部调用则需要更多的安全性考虑,尤其是在涉及到第三方合约时。常见的攻击手段如重入攻击就是通过外部调用进行的。因此,开发者通常会使用<code>checks-effects-interactions</code>模式来防范这种风险。</li>\n</ul>\n<h3 id=\"总结\">总结</h3>\n<ul>\n<li><strong>内部调用</strong>:是一次简单的代码跳转,没有消息传递和上下文切换,效率高且Gas成本低。</li>\n<li><strong>外部调用</strong>:涉及到新的消息传递和上下文切换,消耗更多Gas,并且需要特别注意安全性,尤其是在跨合约调用时。</li>\n</ul>\n<p>理解这些差异对编写高效和安全的Solidity合约非常重要。开发者应根据实际需求选择合适的调用方式,以优化合约的性能和安全性。</p>\n<hr>\n<p>一个合约最终会编译成一段字节码,函数是字节码中的一个部分。内部调用就是直接把执行的代码跳转到另一个函数;外部调用是发起了一个新的消息,创建了新的上下文。</p>\n<h1 id=\"evm\">2 EVM</h1>\n<p>看了这三篇文章:https://learnblockchain.cn/article/3779</p>\n<p>总而言之,了解了EVM中内存存储方式,以及栈式指令的执行过程。</p>\n","categories":["笔记"],"tags":["区块链","以太坊"]},{"title":"毕设01 - 安装QEMU和OP-TEE环境","url":"/graduation-project/01/","content":"<p>前置任务,安装ubuntu22.04虚拟机,以及必要的环境。</p>\n<p>官方文档提供了教程:https://optee.readthedocs.io/en/latest/building/devices/qemu.html#qemu-v8</p>\n<span id=\"more\"></span>\n<p><strong>注意不要有中文路径!!</strong></p>\n<figure class=\"highlight bash\"><table><tr><td class=\"code\"><pre><span class=\"line\">$ <span class=\"built_in\">mkdir</span> optee</span><br><span class=\"line\">$ <span class=\"built_in\">cd</span> optee</span><br><span class=\"line\">$ repo init -u https://github.com/OP-TEE/manifest.git -m qemu_v8.xml</span><br><span class=\"line\">$ repo <span class=\"built_in\">sync</span></span><br><span class=\"line\">$ <span class=\"built_in\">cd</span> build</span><br><span class=\"line\">$ make toolchains</span><br><span class=\"line\">$ make run </span><br></pre></td></tr></table></figure>\n<p>但是 <code>repo</code> 会连接不上服务器,改成清华源。</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"code\"><pre><span class=\"line\">repo init -u https://github.com/OP-TEE/manifest.git -m qemu_v8.xml --repo-url=https://mirrors.tuna.tsinghua.edu.cn/git/git-repo</span><br></pre></td></tr></table></figure>\n<p>下载速度太慢,在 <code>~/.bashrc</code> 配置一下代理</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"built_in\">export</span> http_proxy=192.168.3.56:10809</span><br><span class=\"line\"><span class=\"built_in\">export</span> https_proxy=192.168.3.56:10809</span><br><span class=\"line\"><span class=\"built_in\">export</span> ftp_proxy=192.168.3.56:10808</span><br><span class=\"line\"><span class=\"built_in\">export</span> socks_proxy=192.168.3.56:10808</span><br></pre></td></tr></table></figure>\n<p>发现 ping 不通谷歌,是因为 ping 走的是 ICMP 协议,不走代理。用 curl\n来检查。</p>\n<p>如果发现 <code>apt</code> 用不了了,输入:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"built_in\">export</span> -n ftp_proxy </span><br><span class=\"line\"><span class=\"built_in\">export</span> -n http_proxy</span><br><span class=\"line\"><span class=\"built_in\">export</span> -n https_proxy </span><br><span class=\"line\"><span class=\"built_in\">export</span> -n socks_proxy</span><br></pre></td></tr></table></figure>\n<p>中间遇到报错,看缺什么包就安什么包。</p>\n<p>记录几个不好解决的:</p>\n<p>提示需要设置参数 <code>FORCE_UNSAFE_CONFIGURE=1</code></p>\n<p>修改指令为</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">make run FORCE_UNSAFE_CONFIGURE=1</span><br></pre></td></tr></table></figure>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">configure: error: winscard.h is required for pcsc</span><br></pre></td></tr></table></figure>\n<p>需要先安装必要的包,然后手动设置:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"built_in\">export</span> C_INCLUDE_PATH=/usr/include/PCSC:<span class=\"variable\">$C_INCLUDE_PATH</span></span><br></pre></td></tr></table></figure>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\"> File \"/home/cishoon/桌面/optee/out-br/build/host-python3-3.11.8/./setup.py\", line 1450, in detect_ctypes</span><br><span class=\"line\"> print('Header file {} does not exist'.format(ffi_h))</span><br><span class=\"line\">UnicodeEncodeError: 'utf-8' codec can't encode character '\\udce6' in position 27: surrogates not allowed</span><br></pre></td></tr></table></figure>\n<p>是因为ubuntu系统是中文的,我的路径放在桌面上,桌面是中文文件夹。不能在有中文文件夹路径里安装。</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">*** WARNING: renaming \"_ctypes\" since importing it failed: /home/cishoon/桌面/optee/out-br/build/host-python3-3.11.8/build/lib.linux-x86_64-3.11/_ctypes.cpython-311-x86_64-linux-gnu.so: undefined symbol: ffilibffi_type_void</span><br><span class=\"line\"></span><br><span class=\"line\">The necessary bits to build these optional modules were not found:</span><br><span class=\"line\">_bz2 _curses _curses_panel </span><br><span class=\"line\">_dbm _gdbm _lzma </span><br><span class=\"line\">_tkinter nis readline </span><br><span class=\"line\">Compiling '/home/cishoon/桌面/optee/out-br/per-package/host-python3/host/lib/python3.11/warnings.py'...</span><br><span class=\"line\">Compiling '/home/cishoon/桌面/optee/out-br/per-package/host-python3/host/lib/python3.11/wave.py'...</span><br><span class=\"line\">To find the necessary bits, look in setup.py in detect_modules() for the module's name.</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\">The following modules found by detect_modules() in setup.py have not</span><br><span class=\"line\">been built, they are *disabled* by configure:</span><br><span class=\"line\">_ctypes_test _sqlite3 _testbuffer </span><br><span class=\"line\">_testcapi _testclinic _testimportmultiple</span><br><span class=\"line\">_testinternalcapi _testmultiphase _xxtestfuzz </span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\">Following modules built successfully but were removed because they could not be imported:</span><br><span class=\"line\">_ctypes </span><br><span class=\"line\"></span><br><span class=\"line\">......</span><br><span class=\"line\"></span><br><span class=\"line\">File \"/home/cishoon/桌面/optee/out-br/per-package/host-python-setuptools/host/lib/python3.11/site-packages/wheel/bdist_wheel.py\", line 28, in <module></span><br><span class=\"line\"> from .macosx_libfile import calculate_macosx_platform_tag</span><br><span class=\"line\"> File \"/home/cishoon/桌面/optee/out-br/per-package/host-python-setuptools/host/lib/python3.11/site-packages/wheel/macosx_libfile.py\", line 43, in <module></span><br><span class=\"line\"> import ctypes</span><br><span class=\"line\"> File \"/home/cishoon/桌面/optee/out-br/per-package/host-python-setuptools/host/lib/python3.11/ctypes/__init__.py\", line 8, in <module></span><br><span class=\"line\"> from _ctypes import Union, Structure, Array</span><br><span class=\"line\">ModuleNotFoundError: No module named '_ctypes'</span><br></pre></td></tr></table></figure>\n<p>没有 <code>_ctypes</code> 包,原因是编译 <code>python</code>\n时没有找到 <code>libffi</code>,但是已经安装了 <code>libffi</code></p>\n<p>是因为路径没有指定正确。</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"code\"><pre><span class=\"line\">[root@cishoon-virtual-machine:out-br]# find /usr -name ffi.h</span><br><span class=\"line\">/usr/include/x86_64-linux-gnu/ffi.h</span><br><span class=\"line\">[root@cishoon-virtual-machine:out-br]# find /usr -name libffi.so</span><br><span class=\"line\">/usr/lib/x86_64-linux-gnu/libffi.so</span><br></pre></td></tr></table></figure>\n<p>安装结束后报错:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">* QEMU is now waiting to start the execution</span><br><span class=\"line\">* Start execution with either a 'c' followed by <enter> in the QEMU console or</span><br><span class=\"line\">* attach a debugger and continue from there.</span><br><span class=\"line\">*</span><br><span class=\"line\">* To run OP-TEE tests, use the xtest command in the 'Normal World' terminal</span><br><span class=\"line\">* Enter 'xtest -h' for help.</span><br><span class=\"line\"></span><br><span class=\"line\"># 选项“-x”已弃用并可能在 gnome-terminal 的后续版本中移除。</span><br><span class=\"line\"># 选项“-x”已弃用并可能在 gnome-terminal 的后续版本中移除。</span><br><span class=\"line\"># 使用“-- ”以结束选项并将要执行的命令行追加至其后。</span><br><span class=\"line\"># 使用“-- ”以结束选项并将要执行的命令行追加至其后。</span><br><span class=\"line\"># 无法处理参数:无法打开显示:</span><br><span class=\"line\"># 无法处理参数:无法打开显示:</span><br></pre></td></tr></table></figure>\n<p>https://blog.csdn.net/Frinklin_wang/article/details/135404352</p>\n<p>直接在虚拟机里执行</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"code\"><pre><span class=\"line\">dbus-launch gnome-terminal</span><br></pre></td></tr></table></figure>\n<p>会创建一个新的终端窗口。</p>\n<p>然后再在新窗口里执行 make run,完成后就会进入 QEMU 并自动打开secure\nworld和normal world</p>\n<figure>\n<img src=\"/graduation-project/01/image-20240928011246367.png\" alt=\"image-20240928011246367\">\n<figcaption aria-hidden=\"true\">image-20240928011246367</figcaption>\n</figure>\n<p>明天先了解一下 dbus-launch gnome-terminal\n这些指令是啥意思,然后看看能不能ssh远程连接使用。</p>\n<p>具体启动optee的流程看下一篇博客。</p>\n","categories":["笔记"],"tags":["毕设","op-tee","TrustZone","QEMU"]},{"title":"Hyperledger Fabric 源码精读(1)","url":"/fabric/fabric%E6%BA%90%E7%A0%81%E7%B2%BE%E8%AF%BB%20(1)/","content":"<ul>\n<li><p>开坑,学习 Fabric 的源码。</p></li>\n<li><p>思路是根据 <code>fabric-sample</code> 的\n<code>test-network</code>\n中的脚本,一行行分析。遇到里面使用的指令,看源码如何实现。</p></li>\n<li><p>下面内容非常混乱,写的毫无逻辑,之后有空重新整理一遍。</p></li>\n<li><p>一口气写完太长了,typora里会卡了,分章节发。</p></li>\n<li><p>学习笔记,不保证内容正确性。</p></li>\n</ul>\n<span id=\"more\"></span>\n<h1 id=\"文件结构概览\">0 文件结构概览</h1>\n<ol type=\"1\">\n<li><p>ccaas_builder:\n包含构建链代码即服务(CCaaS)相关命令的代码。</p></li>\n<li><p>ci:\n包含持续集成(CI)相关的脚本,用于自动化测试和构建过程。</p></li>\n<li><p><strong>cmd</strong>:\n包含Fabric项目中各种命令行工具的实现代码,如<code>configtxgen</code>、<code>cryptogen</code>、<code>peer</code>等。</p></li>\n<li><p><strong>common</strong>:\n包含Fabric项目中各模块通用的功能模块,如加密、配置、错误处理等。</p></li>\n<li><p><strong>core</strong>:\n实现了Fabric的核心功能,如ACL管理、链代码生命周期、提交人、账本管理、策略管理等。</p></li>\n<li><p><strong>discovery</strong>:\n处理Fabric中的服务发现功能,包括客户端、命令行工具、背书策略等。</p></li>\n<li><p>docs: 文档生成工具及相关资源文件。</p></li>\n<li><p><strong>gossip</strong>:\n包含实现Fabric中gossip协议的相关代码,用于网络节点间的数据传播与共识。</p></li>\n<li><p>images:\n包含Fabric各组件的Docker镜像构建文件,如<code>peer</code>、<code>orderer</code>等。</p></li>\n<li><p>integration:\n包含集成测试相关的代码,用于验证Fabric各组件间的相互作用。</p></li>\n<li><p><strong>internal</strong>:\n包含Fabric内部使用的一些模块和工具,如配置生成器、加密工具等。</p></li>\n<li><p><strong>msp</strong>:\n包含成员服务提供者(MSP)相关的代码,用于管理组织的身份和证书。</p></li>\n<li><p><strong>orderer</strong>:\n包含排序服务节点(Orderer)相关的功能模块,如共识机制实现、样例客户端等。</p></li>\n<li><p>pkg:\n包含一些通用的包和工具,如状态数据和交易处理相关的代码。</p></li>\n<li><p>protoutil: 包含与Protobuf相关的工具和测试文件。</p></li>\n<li><p>release_notes: 包含项目的发行说明,记录版本更新和变化。</p></li>\n<li><p>sampleconfig:\n包含一些样例配置文件,如MSP配置,用于演示和测试。</p></li>\n<li><p>scripts: 包含各种脚本文件,用于辅助项目的构建和部署。</p></li>\n<li><p>swagger:\n用于生成Swagger文档的文件,Swagger用于API文档的生成。</p></li>\n<li><p>tools: 包含一些额外的工具和实用程序。</p></li>\n<li><p>vagrant: 包含用于创建虚拟开发环境的Vagrant配置文件。</p></li>\n<li><p>vendor: 包含Fabric项目依赖的第三方库和包。</p></li>\n</ol>\n<h1 id=\"networkup\">1 networkUp</h1>\n<h2 id=\"证书生成与msp\">1.1 证书生成与MSP</h2>\n<p>启动网络首先会执行证书生成。</p>\n<p>fabric 提供三种证书生成的工具:<code>cryptogen</code>\n<code>cfssl</code> <code>Fabric CA</code>。这里先以\n<code>cryptogen</code> 为例,后续补充 <code>Fabric CA</code>\n的实现。</p>\n<h3 id=\"cryptogen\">1.1.1 cryptogen</h3>\n<p><code>cryptogen</code> 是 fabric 提供的一个命令行工具。</p>\n<p>命令帮助:https://hyperledger-fabric.readthedocs.io/zh-cn/latest/commands/cryptogen.html</p>\n<p>它为测试提供了一种预配置网络的工具。\n通常它<strong>不应使用在生产环境中</strong>。</p>\n<p>networkUp中执行如下指令:</p>\n<figure class=\"highlight shell\"><table><tr><td class=\"code\"><pre><span class=\"line\">cryptogen generate --config=./organizations/cryptogen/crypto-config-org1.yaml --output=\"organizations\"</span><br><span class=\"line\">cryptogen generate --config=./organizations/cryptogen/crypto-config-org2.yaml --output=\"organizations\"</span><br><span class=\"line\">cryptogen generate --config=./organizations/cryptogen/crypto-config-orderer.yaml --output=\"organizations\"</span><br></pre></td></tr></table></figure>\n<p><code>cryptogen generate</code>\n用于生成秘钥材料。指定两个参数,分别是配置文件和输出目录。</p>\n<p>配置文件结构如下:</p>\n<figure class=\"highlight yaml\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"><span class=\"comment\"># \"OrdererOrgs\" - 管理orderer节点的组织定义</span></span><br><span class=\"line\"><span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"><span class=\"attr\">OrdererOrgs:</span></span><br><span class=\"line\"> <span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"> <span class=\"comment\"># Orderer</span></span><br><span class=\"line\"> <span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"attr\">Name:</span> <span class=\"string\">Orderer</span></span><br><span class=\"line\"> <span class=\"attr\">Domain:</span> <span class=\"string\">example.com</span></span><br><span class=\"line\"> <span class=\"attr\">EnableNodeOUs:</span> <span class=\"literal\">false</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"> <span class=\"comment\"># \"Specs\" - 完整描述请参见下面的PeerOrgs</span></span><br><span class=\"line\"> <span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"> <span class=\"attr\">Specs:</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"attr\">Hostname:</span> <span class=\"string\">orderer</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"><span class=\"comment\"># \"PeerOrgs\" - 管理peer节点的组织定义</span></span><br><span class=\"line\"><span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"><span class=\"attr\">PeerOrgs:</span></span><br><span class=\"line\"> <span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"> <span class=\"comment\"># Org1</span></span><br><span class=\"line\"> <span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"attr\">Name:</span> <span class=\"string\">Org1</span></span><br><span class=\"line\"> <span class=\"attr\">Domain:</span> <span class=\"string\">org1.example.com</span></span><br><span class=\"line\"> <span class=\"attr\">EnableNodeOUs:</span> <span class=\"literal\">false</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"> <span class=\"comment\"># \"CA\"</span></span><br><span class=\"line\"> <span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"> <span class=\"comment\"># 取消注释这个部分以启用这个组织的CA的显式定义。</span></span><br><span class=\"line\"> <span class=\"comment\"># 这个条目是一个Spec。详细信息请参见下面的\"Specs\"部分。</span></span><br><span class=\"line\"> <span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"> <span class=\"comment\"># CA:</span></span><br><span class=\"line\"> <span class=\"comment\"># Hostname: ca # 默认为ca.org1.example.com</span></span><br><span class=\"line\"> <span class=\"comment\"># Country: US</span></span><br><span class=\"line\"> <span class=\"comment\"># Province: California</span></span><br><span class=\"line\"> <span class=\"comment\"># Locality: San Francisco</span></span><br><span class=\"line\"> <span class=\"comment\"># OrganizationalUnit: Hyperledger Fabric</span></span><br><span class=\"line\"> <span class=\"comment\"># StreetAddress: org的地址 # 默认为nil</span></span><br><span class=\"line\"> <span class=\"comment\"># PostalCode: org的邮政编码 # 默认为nil</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"> <span class=\"comment\"># \"Specs\"</span></span><br><span class=\"line\"> <span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"> <span class=\"comment\"># 取消注释这个部分以在配置中启用主机的显式定义。</span></span><br><span class=\"line\"> <span class=\"comment\"># 大多数用户会使用下面的Template。</span></span><br><span class=\"line\"> <span class=\"comment\">#</span></span><br><span class=\"line\"> <span class=\"comment\"># Specs是Spec条目的数组。每个Spec条目由两个字段组成:</span></span><br><span class=\"line\"> <span class=\"comment\"># - Hostname: (必需) 期望的主机名,不包括域名部分。</span></span><br><span class=\"line\"> <span class=\"comment\"># - CommonName: (可选) 指定CN的模板或显式覆盖。</span></span><br><span class=\"line\"> <span class=\"comment\"># 默认情况下,这是一个模板:</span></span><br><span class=\"line\"> <span class=\"comment\">#</span></span><br><span class=\"line\"> <span class=\"comment\"># \"{{.Hostname}}.{{.Domain}}\"</span></span><br><span class=\"line\"> <span class=\"comment\">#</span></span><br><span class=\"line\"> <span class=\"comment\"># 它的值分别从Spec.Hostname和Org.Domain中获取。</span></span><br><span class=\"line\"> <span class=\"comment\"># - SANS: (可选) 指定将在生成的x509中设置的一个或多个主题备用名称。</span></span><br><span class=\"line\"> <span class=\"comment\"># 接受模板变量{{.Hostname}}, {{.Domain}}, {{.CommonName}}。</span></span><br><span class=\"line\"> <span class=\"comment\"># 这里提供的IP地址将被正确识别。其他值将被视为DNS名称。</span></span><br><span class=\"line\"> <span class=\"comment\"># 注意:会为你创建两个隐式条目:</span></span><br><span class=\"line\"> <span class=\"comment\"># - {{ .CommonName }}</span></span><br><span class=\"line\"> <span class=\"comment\"># - {{ .Hostname }}</span></span><br><span class=\"line\"> <span class=\"comment\"># 即:SANS里定义的域名也会被指向这个节点,证书会认为这些域名是这个节点的合法别名。</span></span><br><span class=\"line\"> <span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"> <span class=\"comment\"># Specs:</span></span><br><span class=\"line\"> <span class=\"comment\"># - Hostname: foo # 默认为\"foo.org1.example.com\"</span></span><br><span class=\"line\"> <span class=\"comment\"># CommonName: foo27.org5.example.com # 覆盖基于主机名的上面设置的FQDN</span></span><br><span class=\"line\"> <span class=\"comment\"># SANS:</span></span><br><span class=\"line\"> <span class=\"comment\"># - \"bar.{{.Domain}}\"</span></span><br><span class=\"line\"> <span class=\"comment\"># - \"altfoo.{{.Domain}}\"</span></span><br><span class=\"line\"> <span class=\"comment\"># - \"{{.Hostname}}.org6.net\"</span></span><br><span class=\"line\"> <span class=\"comment\"># - 172.16.10.31</span></span><br><span class=\"line\"> <span class=\"comment\"># - Hostname: bar</span></span><br><span class=\"line\"> <span class=\"comment\"># - Hostname: baz</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"> <span class=\"comment\"># \"Template\"</span></span><br><span class=\"line\"> <span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"> <span class=\"comment\"># 允许从模板中顺序生成一个或多个主机。</span></span><br><span class=\"line\"> <span class=\"comment\"># 默认情况下,这看起来像是从0到Count-1的\"peer%d\"。</span></span><br><span class=\"line\"> <span class=\"comment\"># 你可以覆盖节点数(Count)、起始索引(Start)或用于构建名称的模板(Hostname)。</span></span><br><span class=\"line\"> <span class=\"comment\">#</span></span><br><span class=\"line\"> <span class=\"comment\"># 注意:Template和Specs不是互斥的。你可以定义这两个部分,系统会为你创建聚合的节点。</span></span><br><span class=\"line\"> <span class=\"comment\"># 注意名称冲突</span></span><br><span class=\"line\"> <span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"> <span class=\"attr\">Template:</span></span><br><span class=\"line\"> <span class=\"attr\">Count:</span> <span class=\"number\">1</span></span><br><span class=\"line\"> <span class=\"comment\"># Start: 5</span></span><br><span class=\"line\"> <span class=\"comment\"># Hostname: {{.Prefix}}{{.Index}} # 默认</span></span><br><span class=\"line\"> <span class=\"comment\"># SANS:</span></span><br><span class=\"line\"> <span class=\"comment\"># - \"{{.Hostname}}.alt.{{.Domain}}\"</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"> <span class=\"comment\"># \"Users\"</span></span><br><span class=\"line\"> <span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"> <span class=\"comment\"># Count: 除Admin外的用户账户数量</span></span><br><span class=\"line\"> <span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"> <span class=\"attr\">Users:</span></span><br><span class=\"line\"> <span class=\"attr\">Count:</span> <span class=\"number\">1</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"> <span class=\"comment\"># Org2: 完整规范请参见\"Org1\"</span></span><br><span class=\"line\"> <span class=\"comment\"># ---------------------------------------------------------------------------</span></span><br><span class=\"line\"> <span class=\"bullet\">-</span> <span class=\"attr\">Name:</span> <span class=\"string\">Org2</span></span><br><span class=\"line\"> <span class=\"attr\">Domain:</span> <span class=\"string\">org2.example.com</span></span><br><span class=\"line\"> <span class=\"attr\">EnableNodeOUs:</span> <span class=\"literal\">false</span></span><br><span class=\"line\"> <span class=\"attr\">Template:</span></span><br><span class=\"line\"> <span class=\"attr\">Count:</span> <span class=\"number\">1</span></span><br><span class=\"line\"> <span class=\"attr\">Users:</span></span><br><span class=\"line\"> <span class=\"attr\">Count:</span> <span class=\"number\">1</span></span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<p>执行 <code>cryptogen generate</code> 会遍历创建所有配置文件中定义的\n<code>Ordered</code> 和 <code>Peer</code>\n节点的<strong>组织</strong>。注意这里是组织,一个组织里面可以包含很多个节点。</p>\n<figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"keyword\">func</span> <span class=\"title\">generate</span><span class=\"params\">()</span></span> {</span><br><span class=\"line\">\tconfig, err := getConfig() <span class=\"comment\">// 读取配置文件,反序列化成config</span></span><br><span class=\"line\">\t<span class=\"keyword\">if</span> err != <span class=\"literal\">nil</span> {</span><br><span class=\"line\">\t\tfmt.Printf(<span class=\"string\">\"Error reading config: %s\"</span>, err)</span><br><span class=\"line\">\t\tos.Exit(<span class=\"number\">-1</span>)</span><br><span class=\"line\">\t}</span><br><span class=\"line\"></span><br><span class=\"line\">\t<span class=\"keyword\">for</span> _, orgSpec := <span class=\"keyword\">range</span> config.PeerOrgs {</span><br><span class=\"line\">\t\terr = renderOrgSpec(&orgSpec, <span class=\"string\">\"peer\"</span>)</span><br><span class=\"line\">\t\t<span class=\"keyword\">if</span> err != <span class=\"literal\">nil</span> {</span><br><span class=\"line\">\t\t\tfmt.Printf(<span class=\"string\">\"Error processing peer configuration: %s\"</span>, err)</span><br><span class=\"line\">\t\t\tos.Exit(<span class=\"number\">-1</span>)</span><br><span class=\"line\">\t\t}</span><br><span class=\"line\">\t\tgeneratePeerOrg(*outputDir, orgSpec)</span><br><span class=\"line\">\t}</span><br><span class=\"line\"></span><br><span class=\"line\">\t<span class=\"keyword\">for</span> _, orgSpec := <span class=\"keyword\">range</span> config.OrdererOrgs {</span><br><span class=\"line\">\t\terr = renderOrgSpec(&orgSpec, <span class=\"string\">\"orderer\"</span>)</span><br><span class=\"line\">\t\t<span class=\"keyword\">if</span> err != <span class=\"literal\">nil</span> {</span><br><span class=\"line\">\t\t\tfmt.Printf(<span class=\"string\">\"Error processing orderer configuration: %s\"</span>, err)</span><br><span class=\"line\">\t\t\tos.Exit(<span class=\"number\">-1</span>)</span><br><span class=\"line\">\t\t}</span><br><span class=\"line\">\t\tgenerateOrdererOrg(*outputDir, orgSpec)</span><br><span class=\"line\">\t}</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p><code>renderOrgSpec</code>:处理配置文件中的<code>OrgSpec</code>,完成配置文件中模板的处理。即完整定义一个组织中的所有节点。</p>\n<figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"keyword\">func</span> <span class=\"title\">renderOrgSpec</span><span class=\"params\">(orgSpec *OrgSpec, prefix <span class=\"type\">string</span>)</span></span> <span class=\"type\">error</span> {</span><br><span class=\"line\">\t<span class=\"comment\">// 获取配置文件中定义的公钥算法,默认为ECDSA</span></span><br><span class=\"line\">\tpublickKeyAlg := getPublicKeyAlg(orgSpec.Template.PublicKeyAlgorithm)</span><br><span class=\"line\"></span><br><span class=\"line\">\t<span class=\"comment\">// 首先处理Template的配置,自动根据模板生成Specs</span></span><br><span class=\"line\">\t<span class=\"keyword\">for</span> i := <span class=\"number\">0</span>; i < orgSpec.Template.Count; i++ {</span><br><span class=\"line\">\t\tdata := HostnameData{</span><br><span class=\"line\">\t\t\tPrefix: prefix,</span><br><span class=\"line\">\t\t\tIndex: i + orgSpec.Template.Start,</span><br><span class=\"line\">\t\t\tDomain: orgSpec.Domain,</span><br><span class=\"line\">\t\t}</span><br><span class=\"line\"></span><br><span class=\"line\">\t\t<span class=\"comment\">// 生成完整的域名</span></span><br><span class=\"line\">\t\thostname, err := parseTemplateWithDefault(orgSpec.Template.Hostname, defaultHostnameTemplate, data)</span><br><span class=\"line\">\t\t<span class=\"keyword\">if</span> err != <span class=\"literal\">nil</span> {</span><br><span class=\"line\">\t\t\t<span class=\"keyword\">return</span> err</span><br><span class=\"line\">\t\t}</span><br><span class=\"line\"></span><br><span class=\"line\">\t\tspec := NodeSpec{</span><br><span class=\"line\">\t\t\tHostname: hostname,</span><br><span class=\"line\">\t\t\tSANS: orgSpec.Template.SANS,</span><br><span class=\"line\">\t\t\tPublicKeyAlgorithm: publickKeyAlg,</span><br><span class=\"line\">\t\t}</span><br><span class=\"line\">\t\torgSpec.Specs = <span class=\"built_in\">append</span>(orgSpec.Specs, spec)</span><br><span class=\"line\">\t}</span><br><span class=\"line\"></span><br><span class=\"line\">\t<span class=\"comment\">// 再处理Specs中显式定义的Specs</span></span><br><span class=\"line\">\t<span class=\"keyword\">for</span> idx, spec := <span class=\"keyword\">range</span> orgSpec.Specs {</span><br><span class=\"line\">\t\terr := renderNodeSpec(orgSpec.Domain, &spec)</span><br><span class=\"line\">\t\t<span class=\"keyword\">if</span> err != <span class=\"literal\">nil</span> {</span><br><span class=\"line\">\t\t\t<span class=\"keyword\">return</span> err</span><br><span class=\"line\">\t\t}</span><br><span class=\"line\"></span><br><span class=\"line\">\t\torgSpec.Specs[idx] = spec</span><br><span class=\"line\">\t}</span><br><span class=\"line\"></span><br><span class=\"line\">\t<span class=\"comment\">// 同样处理CA节点</span></span><br><span class=\"line\">\t<span class=\"keyword\">if</span> <span class=\"built_in\">len</span>(orgSpec.CA.Hostname) == <span class=\"number\">0</span> {</span><br><span class=\"line\">\t\torgSpec.CA.Hostname = <span class=\"string\">\"ca\"</span></span><br><span class=\"line\">\t}</span><br><span class=\"line\">\terr := renderNodeSpec(orgSpec.Domain, &orgSpec.CA)</span><br><span class=\"line\">\t<span class=\"keyword\">if</span> err != <span class=\"literal\">nil</span> {</span><br><span class=\"line\">\t\t<span class=\"keyword\">return</span> err</span><br><span class=\"line\">\t}</span><br><span class=\"line\"></span><br><span class=\"line\">\t<span class=\"keyword\">return</span> <span class=\"literal\">nil</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p><code>generatePeerOrg</code> /\n<code>generateOrderedOrg</code>:Ordered与Peer类似,区别只在于生成的nodeType是PEER还是Ordered</p>\n<ul>\n<li><p><strong>生成Signing证书</strong></p></li>\n<li><p><strong>生成CA证书</strong></p>\n<p>生成证书的通过调用ca.NewCA方法,给定地址、街道等基本信息和加密算法,自动生成私钥和公钥</p>\n<p>证书的版本是X.509</p></li>\n<li><p><strong>生成验证 MSP</strong></p>\n<p><code>GenerateVerifyingMSP</code> 函数负责生成 Hyperledger Fabric\n中验证 MSP(Membership Service\nProvider)所需的工件。以下是其功能的简要说明:</p>\n<ol type=\"1\">\n<li><p><strong>创建文件夹结构</strong>: 该函数首先通过\n<code>createFolderStructure</code> 创建所需的文件夹结构:\n<figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\">err := createFolderStructure(baseDir, <span class=\"literal\">false</span>)</span><br></pre></td></tr></table></figure></p></li>\n<li><p><strong>导出 CA 证书</strong>: 它将签名 CA 和 TLS CA\n证书导出到相应的目录: <figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\">err = x509Export(filepath.Join(baseDir, <span class=\"string\">\"cacerts\"</span>, x509Filename(signCA.Name)), signCA.SignCert)</span><br><span class=\"line\">err = x509Export(filepath.Join(baseDir, <span class=\"string\">\"tlscacerts\"</span>, x509Filename(tlsCA.Name)), tlsCA.SignCert)</span><br></pre></td></tr></table></figure></p></li>\n<li><p><strong>生成 <code>config.yaml</code></strong>: 如果启用了\n<code>nodeOUs</code>,则生成 <code>config.yaml</code> 文件:\n<figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">if</span> nodeOUs {</span><br><span class=\"line\"> exportConfig(baseDir, <span class=\"string\">\"cacerts/\"</span>+x509Filename(signCA.Name), <span class=\"literal\">true</span>)</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></p></li>\n<li><p><strong>创建管理员证书</strong>: 如果未启用\n<code>nodeOUs</code>,则为单元测试创建一个临时的管理员证书:\n<figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">if</span> !nodeOUs {</span><br><span class=\"line\"> ksDir := filepath.Join(baseDir, <span class=\"string\">\"keystore\"</span>)</span><br><span class=\"line\"> err = os.Mkdir(ksDir, <span class=\"number\">0o755</span>)</span><br><span class=\"line\"> <span class=\"keyword\">defer</span> os.RemoveAll(ksDir)</span><br><span class=\"line\"> priv, err := csp.GeneratePrivateKey(ksDir, keyAlg)</span><br><span class=\"line\"> _, err = signCA.SignCertificate(</span><br><span class=\"line\"> filepath.Join(baseDir, <span class=\"string\">\"admincerts\"</span>),</span><br><span class=\"line\"> signCA.Name,</span><br><span class=\"line\"> <span class=\"literal\">nil</span>,</span><br><span class=\"line\"> <span class=\"literal\">nil</span>,</span><br><span class=\"line\"> getPublicKey(priv),</span><br><span class=\"line\"> x509.KeyUsageDigitalSignature,</span><br><span class=\"line\"> []x509.ExtKeyUsage{},</span><br><span class=\"line\"> )</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></p></li>\n</ol>\n<p>该函数确保所有必要的 MSP\n工件都已生成并放置在正确的目录中,从而便于设置 Hyperledger Fabric\n中的验证 MSP。</p>\n<blockquote>\n<p>MSP 的功能是什么?</p>\n<p>nodeOUs是什么?</p>\n</blockquote></li>\n<li><p><strong>生成所有 PEER 节点</strong></p>\n<p>遍历 <code>orgSpec.Specs</code> ,给每一个节点调用\n<code>msp.GenerateLocalMSP</code> 生成 MSP</p>\n<p><code>GenerateLocalMSP</code> 函数用于生成本地 MSP(Membership\nService Provider)身份和 TLS(Transport Layer\nSecurity)证书。以下是对该函数的简要解释:</p>\n<ol type=\"1\">\n<li><p><strong>创建文件夹结构</strong>: <figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\">mspDir := filepath.Join(baseDir, <span class=\"string\">\"msp\"</span>)</span><br><span class=\"line\">tlsDir := filepath.Join(baseDir, <span class=\"string\">\"tls\"</span>)</span><br><span class=\"line\">err := createFolderStructure(mspDir, <span class=\"literal\">true</span>)</span><br><span class=\"line\">err = os.MkdirAll(tlsDir, <span class=\"number\">0o755</span>)</span><br></pre></td></tr></table></figure> 该部分代码创建\nMSP 和 TLS 所需的文件夹结构。</p></li>\n<li><p><strong>生成私钥</strong>: <figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\">keystore := filepath.Join(mspDir, <span class=\"string\">\"keystore\"</span>)</span><br><span class=\"line\">priv, err := csp.GeneratePrivateKey(keystore, keyAlg)</span><br></pre></td></tr></table></figure> 生成 MSP 的私钥并存储在\n<code>keystore</code> 文件夹中。</p></li>\n<li><p><strong>生成 X509 证书</strong>: <figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\">cert, err := signCA.SignCertificate(</span><br><span class=\"line\"> filepath.Join(mspDir, <span class=\"string\">\"signcerts\"</span>),</span><br><span class=\"line\"> name,</span><br><span class=\"line\"> ous,</span><br><span class=\"line\"> <span class=\"literal\">nil</span>,</span><br><span class=\"line\"> getPublicKey(priv),</span><br><span class=\"line\"> x509.KeyUsageDigitalSignature,</span><br><span class=\"line\"> []x509.ExtKeyUsage{},</span><br><span class=\"line\">)</span><br></pre></td></tr></table></figure> 使用签名 CA 生成\nX509 证书,并将其存储在 <code>signcerts</code> 文件夹中。</p></li>\n<li><p><strong>导出 CA 证书</strong>: <figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\">err = x509Export(</span><br><span class=\"line\"> filepath.Join(mspDir, <span class=\"string\">\"cacerts\"</span>, x509Filename(signCA.Name)),</span><br><span class=\"line\"> signCA.SignCert,</span><br><span class=\"line\">)</span><br><span class=\"line\">err = x509Export(</span><br><span class=\"line\"> filepath.Join(mspDir, <span class=\"string\">\"tlscacerts\"</span>, x509Filename(tlsCA.Name)),</span><br><span class=\"line\"> tlsCA.SignCert,</span><br><span class=\"line\">)</span><br></pre></td></tr></table></figure> 将签名 CA 和 TLS\nCA 的证书分别导出到 <code>cacerts</code> 和 <code>tlscacerts</code>\n文件夹中。</p></li>\n<li><p><strong>生成 config.yaml</strong>: <figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">if</span> nodeOUs {</span><br><span class=\"line\"> exportConfig(mspDir, filepath.Join(<span class=\"string\">\"cacerts\"</span>, x509Filename(signCA.Name)), <span class=\"literal\">true</span>)</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure> 如果启用了节点\nOU(Organizational Units),则生成 <code>config.yaml</code>\n配置文件。</p></li>\n<li><p><strong>生成 TLS 证书</strong>: <figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\">tlsPrivKey, err := csp.GeneratePrivateKey(tlsDir, keyAlg)</span><br><span class=\"line\">_, err = tlsCA.SignCertificate(</span><br><span class=\"line\"> filepath.Join(tlsDir),</span><br><span class=\"line\"> name,</span><br><span class=\"line\"> <span class=\"literal\">nil</span>,</span><br><span class=\"line\"> sans,</span><br><span class=\"line\"> getPublicKey(tlsPrivKey),</span><br><span class=\"line\"> x509.KeyUsageDigitalSignature|x509.KeyUsageKeyEncipherment,</span><br><span class=\"line\"> []x509.ExtKeyUsage{</span><br><span class=\"line\"> x509.ExtKeyUsageServerAuth,</span><br><span class=\"line\"> x509.ExtKeyUsageClientAuth,</span><br><span class=\"line\"> },</span><br><span class=\"line\">)</span><br></pre></td></tr></table></figure> 生成 TLS\n私钥和证书,并将其存储在 TLS 文件夹中。</p></li>\n<li><p><strong>重命名 TLS 证书</strong>:</p>\n<figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\">tlsFilePrefix := <span class=\"string\">\"server\"</span></span><br><span class=\"line\"><span class=\"keyword\">if</span> nodeType == CLIENT || nodeType == ADMIN {</span><br><span class=\"line\"> tlsFilePrefix = <span class=\"string\">\"client\"</span></span><br><span class=\"line\">}</span><br><span class=\"line\">err = os.Rename(filepath.Join(tlsDir, x509Filename(name)),</span><br><span class=\"line\"> filepath.Join(tlsDir, tlsFilePrefix+<span class=\"string\">\".crt\"</span>))</span><br></pre></td></tr></table></figure>\n<p>根据节点类型重命名生成的 TLS 证书。</p></li>\n<li><p><strong>导出私钥</strong>: <figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\">err = keyExport(tlsDir, filepath.Join(tlsDir, tlsFilePrefix+<span class=\"string\">\".key\"</span>))</span><br></pre></td></tr></table></figure>\n将私钥导出到指定文件中。</p></li>\n</ol>\n<p>该函数的主要目的是生成和配置 MSP 和 TLS\n所需的各种证书和密钥文件。</p>\n<blockquote>\n<p><code>GenerateLocalMSP</code> 和 <code>GenerateVerifyingMSP</code>\n函数的主要区别在于它们的用途和生成的内容:</p>\n<ol type=\"1\">\n<li><strong><code>GenerateLocalMSP</code></strong>:\n<ul>\n<li>主要用于生成本地 MSP(Membership Service Provider)身份工件。</li>\n<li>创建文件夹结构并生成私钥。</li>\n<li>使用签名 CA 生成 X509 证书。</li>\n<li>将签名 CA 证书和 TLS CA 证书分别写入 <code>cacerts</code> 和\n<code>tlscacerts</code> 文件夹。</li>\n<li>如果启用了节点 OU(Organizational Units),则生成\n<code>config.yaml</code> 配置文件。</li>\n<li>生成 TLS 工件,包括私钥和 X509 证书。</li>\n<li>适用于需要完整 MSP 身份的节点,如客户端、排序节点或对等节点。</li>\n</ul></li>\n<li><strong><code>GenerateVerifyingMSP</code></strong>:\n<ul>\n<li>主要用于生成验证 MSP 工件。</li>\n<li>创建文件夹结构并写入签名 CA 和 TLS CA 证书。</li>\n<li>如果启用了节点 OU,则生成 <code>config.yaml</code> 配置文件。</li>\n<li>生成一个临时的管理员证书用于单元测试。</li>\n<li>适用于只需要验证 MSP 身份的场景,不需要生成完整的本地 MSP\n身份。</li>\n</ul></li>\n</ol>\n总结:\n<ul>\n<li><code>GenerateLocalMSP</code> 生成完整的本地 MSP\n身份,包括私钥和证书。</li>\n<li><code>GenerateVerifyingMSP</code> 生成用于验证的 MSP 工件,主要包含\nCA 证书和配置文件。</li>\n</ul>\n</blockquote></li>\n<li><p><strong>生成所有用户节点(CLIENT和ADMIN)</strong></p>\n<p>同上,给每一个节点调用 <code>msp.GenerateLocalMSP</code></p></li>\n<li><p><strong>复制管理员证书到相应的文件夹</strong></p></li>\n</ul>\n<h3 id=\"msp\">1.1.2 MSP</h3>\n<p>可见上面的 <code>cryptogen generate</code>\n只干了两件事,生成各种CA证书和生成MSP。</p>\n<p>那MSP是什么?这几种类型有什么区别?<code>LocalMSP</code> 和\n<code>VerifyingMSP</code> 有什么区别?<code>nodeOUs</code>\n的作用是什么?</p>\n<p>身份认证机构(Certificate Authorities,\nCA)通过生成公钥和私钥对来发行身份,这对密钥可以用来证明身份。这个身份需要被网络识别,这时就需要用到会员服务提供者(Membership\nService Provider,\nMSP)。例如,一个节点(peer)使用其私钥对交易进行数字签名或背书。MSP用来检查该节点是否有权背书交易。然后使用节点证书中的公钥来验证附加在交易上的签名是否有效。因此,MSP是允许该身份被网络其他成员信任和识别的机制。</p>\n<p>可以将CA比作信用卡提供商,它发行多种可验证的身份。而MSP则决定哪些信用卡提供商在商店被接受。</p>\n<p>CA 提供身份,MSP 验证你的身份能不能在我这里用。</p>\n<p>在 <code>fabric-samples</code> 的\n<code>asset-transfer-basic/application-gateway</code> 中,上面由\n<code>cryptogen</code> 生成的 MSP 证书和\nCA签名被用作创建身份和签名,与网关建立连接。</p>\n<p>可以推测,在初始构建区块链网络时,验证MSP会被作为配置加载到网络里。</p>\n<p>在 Hyperledger Fabric 中,<code>NodeOUs</code> 是 Membership Service\nProvider (MSP)\n配置的一部分,用于定义和区分网络中不同节点的角色(身份)类型。<code>OU</code>\n代表 \"Organizational Unit\"(组织单位),通过配置\n<code>NodeOUs</code>,你可以明确指定每个节点在网络中的角色,例如是 Peer\n节点、Orderer 节点,还是客户端等。</p>\n<p><code>NodeOUs</code> 的作用:</p>\n<ol type=\"1\">\n<li><strong>角色区分</strong>:通过\n<code>NodeOUs</code>,可以在网络中区分不同的节点类型。例如,MSP\n可以配置哪些证书对应 Peer 节点,哪些证书对应 Orderer 节点。</li>\n<li><strong>访问控制</strong>:在 Hyperledger Fabric\n中,<code>NodeOUs</code>\n配置有助于实施更精细的访问控制策略。不同角色的节点可以被赋予不同的权限和职责,从而确保网络的安全性和有效性。</li>\n<li><strong>身份验证</strong>:当一个节点加入网络或执行操作时,MSP\n会使用 <code>NodeOUs</code>\n配置来验证该节点的身份,并确保它符合其配置的角色。例如,只有被正确标识为\nOrderer 的节点,才能参与共识过程。</li>\n</ol>\n<p>MSP 这部分感觉还没有完全理解。因为到这里,才刚刚完成了 MSP\n配置文件的创建,具体的验证逻辑等等还在后面。先留个印象,MSP\n的作用是验证用户身份的。</p>\n<h2 id=\"docker-compose\">1.2 docker-compose</h2>\n<p>至此,test-network\n完成了组织的创建。得到的文件(证书和秘钥)都保存在本地的\n<code>organization</code> 文件夹中。</p>\n<p>接下来,就是使用 <code>docker-compose</code> 进行网络的创建。</p>\n<figure class=\"highlight shell\"><table><tr><td class=\"code\"><pre><span class=\"line\">docker-compose -f compose/compose-test-net.yaml -f compose/docker/docker-compose-test-net.yaml</span><br></pre></td></tr></table></figure>\n<blockquote>\n<p>在 <code>docker-compose</code> 中,你可以使用多个 <code>-f</code>\n选项来指定多个 YAML\n文件。这样做的目的是将这些文件的内容合并起来,以便更灵活地配置和管理\nDocker 服务。</p>\n</blockquote>\n<h3 id=\"具体执行内容\">1.2.1 具体执行内容</h3>\n<p>具体来说,这个 <code>docker-compose</code> 文件启动了一个 Hyperledger\nFabric 测试网络,它包括以下组件:</p>\n<ol type=\"1\">\n<li><strong>网络和卷配置</strong></li>\n</ol>\n<ul>\n<li><strong>Volumes</strong>: 定义了三个卷,用于持久化\n<code>orderer.example.com</code>、<code>peer0.org1.example.com</code> 和\n<code>peer0.org2.example.com</code>\n节点的数据。这些卷确保了即使容器停止或重启,数据仍然可以保留。</li>\n<li><strong>Networks</strong>: 定义了一个名为 <code>fabric_test</code>\n的 Docker 网络,所有服务将会连接到这个网络中以便相互通信。</li>\n</ul>\n<ol start=\"2\" type=\"1\">\n<li><strong>服务(Services)</strong></li>\n</ol>\n<ul>\n<li><p><strong>Orderer 节点:orderer.example.com</strong></p>\n<ul>\n<li><p><strong>Container Name</strong>:\n<code>orderer.example.com</code></p></li>\n<li><p><strong>Image</strong>: 使用了最新版本的\n<code>hyperledger/fabric-orderer</code> 镜像。</p></li>\n<li><p><strong>Environment</strong>:</p>\n<ul>\n<li>配置了 Fabric 的日志级别为 <code>INFO</code>。</li>\n<li>配置了 Orderer 的监听地址和端口。</li>\n<li>使用 <code>OrdererMSP</code> 作为本地 MSP(Membership Service\nProvider)。</li>\n<li>启用了 TLS,并提供了 TLS 的证书和密钥路径。</li>\n<li>配置了 Orderer 的管理端口(7053)和操作端口(9443),以及相关的 TLS\n设置。</li>\n</ul></li>\n<li><p><strong>Volumes</strong>: 挂载了组织相关的 MSP 和 TLS\n证书目录,及 Orderer 的数据存储目录。</p></li>\n<li><p><strong>Ports</strong>: 暴露了 Orderer 的主要端口 7050、管理端口\n7053、以及操作端口 9443。</p></li>\n<li><p><strong>Networks</strong>: 连接到 <code>fabric_test</code>\n网络。</p></li>\n<li><p><strong>Command</strong>:<code>orderer</code></p></li>\n</ul></li>\n<li><p><strong>Peer 节点:peer0.org1.example.com</strong></p>\n<ul>\n<li><p><strong>Container Name</strong>:\n<code>peer0.org1.example.com</code></p></li>\n<li><p><strong>Image</strong>: 使用了最新版本的\n<code>hyperledger/fabric-peer</code> 镜像。</p></li>\n<li><p><strong>Environment</strong>:</p>\n<ul>\n<li>配置了 Fabric 的日志级别为 <code>INFO</code>。</li>\n<li>启用了 TLS,并提供了 TLS 的证书和密钥路径。</li>\n<li>设置了该节点的 ID 为\n<code>peer0.org1.example.com</code>,并配置了监听地址。</li>\n<li>配置了节点的 Gossip 协议启动节点和外部端点地址。</li>\n<li>使用 <code>Org1MSP</code> 作为本地 MSP。</li>\n<li>配置了节点的操作端口(9444)和链码执行超时(300秒)。</li>\n</ul></li>\n<li><p><strong>Volumes</strong>: 挂载了组织相关的 MSP 和 TLS\n证书目录,及节点的数据存储目录。</p></li>\n<li><p><strong>Ports</strong>: 暴露了 Peer 节点的主要端口 7051、操作端口\n9444。</p></li>\n<li><p><strong>Networks</strong>: 连接到 <code>fabric_test</code>\n网络。</p></li>\n<li><p><strong>Command</strong>:<code>peer node start</code></p></li>\n</ul></li>\n<li><p><strong>Peer 节点:peer0.org2.example.com</strong></p>\n<ul>\n<li>与上面org1的类似</li>\n</ul></li>\n</ul>\n<ol start=\"3\" type=\"1\">\n<li><strong>Additional Configuration in the Second Compose\nFile</strong></li>\n</ol>\n<p>在第二个 <code>docker-compose</code> 文件中,针对\n<code>peer0.org1.example.com</code> 和\n<code>peer0.org2.example.com</code> 这两个节点,增加了一些额外的配置: -\n<strong>CORE_VM_ENDPOINT</strong>: 配置了 Docker 守护进程的 Unix\n套接字,用于管理容器内的虚拟机。 -\n<strong>CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE</strong>: 设置了 Docker\n网络模式为 <code>fabric_test</code>。</p>\n<h3 id=\"fabric-orderer-fabric-peer-镜像\">1.2.2\n<code>fabric-orderer</code> / <code>fabric-peer</code> 镜像</h3>\n<p>源码仓库中 <code>fabric/image</code> 路径下可以找到他们的\nDockerfile。</p>\n<p>简而言之,这个 Dockerfile 的设计分为两个阶段:</p>\n<ol type=\"1\">\n<li>首先在构建阶段生成所需的二进制文件(包括\n<code>peer</code>/<code>orderer</code> 和 CCaaS 相关组件)</li>\n<li>然后在运行时阶段,将这些构建产物打包到一个精简的 Ubuntu\n镜像中,并配置好环境变量和默认启动命令</li>\n</ol>\n<p>那接下来主要关注的就是 <code>orderer</code> 和 <code>peer</code>\n的具体实现了。根据镜像启动的顺序,先看 <code>orderer</code>。</p>\n<h3 id=\"peer\">1.2.3 peer</h3>\n<h3 id=\"orderer\">1.2.4 orderer</h3>\n<p>暂时理解不了 <code>peer</code> 和 <code>orderer</code>\n是怎么进行网络通信的,先放一放,看看后面干了什么,确定一下这两个指令干的事情的范围</p>\n","categories":["笔记"],"tags":["区块链","go","fabric","超级账本"]},{"title":"Hyperledger Fabric 源码精读(2)","url":"/fabric/fabric%E6%BA%90%E7%A0%81%E7%B2%BE%E8%AF%BB%20(2)/","content":"<ul>\n<li><p>开坑,学习 Fabric 的源码。</p></li>\n<li><p>思路是根据 <code>fabric-sample</code> 的\n<code>test-network</code>\n中的脚本,一行行分析。遇到里面使用的指令,看源码如何实现。</p></li>\n<li><p>下面内容非常混乱,写的毫无逻辑,之后有空重新整理一遍。</p></li>\n<li><p>一口气写完太长了,typora里会卡,分章节发。</p></li>\n<li><p>学习笔记,不保证内容正确性。</p></li>\n</ul>\n<span id=\"more\"></span>\n<h1 id=\"createchannel\">2 createChannel</h1>\n<p><code>network.sh</code> 的注释里明确说明,这个函数干了两件事:</p>\n<ol type=\"1\">\n<li>join the peers of org1 and org2:加入两个组织的对等节点</li>\n<li>update the <strong>anchor peers</strong> for each\norganization:更新每个组织的锚定节点</li>\n</ol>\n<p>我完善一下:</p>\n<ol type=\"1\">\n<li>创建创世区块</li>\n<li>利用创世区块创建通道</li>\n<li>加入对等节点</li>\n<li>更新锚定节点</li>\n</ol>\n<h2 id=\"创建创世区块\">2.1 创建创世区块</h2>\n<p>test-network 的脚本固定由第一个组织进行创世区块的创建。</p>\n<p>创建创世区块的核心语句是:</p>\n<figure class=\"highlight shell\"><table><tr><td class=\"code\"><pre><span class=\"line\">configtxgen -profile ChannelUsingRaft -outputBlock ./channel-artifacts/${CHANNEL_NAME}.block -channelID $CHANNEL_NAME</span><br></pre></td></tr></table></figure>\n<p>接下来看看configtxgen</p>\n<h3 id=\"configtxgen\">2.1.1 configtxgen</h3>\n<table>\n<thead>\n<tr>\n<th>选项</th>\n<th>描述</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>-channelID string</code></td>\n<td>指定在配置交易中使用的通道ID。</td>\n</tr>\n<tr>\n<td><code>-configPath string</code></td>\n<td>指定包含要使用配置的路径。</td>\n</tr>\n<tr>\n<td><code>-inspectBlock string</code></td>\n<td>打印指定路径区块中包含的配置。</td>\n</tr>\n<tr>\n<td><code>-outputBlock string</code></td>\n<td>指定写入创世区块的路径。</td>\n</tr>\n<tr>\n<td><code>-profile string</code></td>\n<td>指定 <code>configtx.yaml</code> 中用于生成的配置文件。</td>\n</tr>\n</tbody>\n</table>\n<p>这个工具的输出主要受 <code>configtx.yaml</code> 文件内容的控制。</p>\n<p>默认情况下,<code>configtxgen</code> 工具会依次尝试从\n<code>$FABRIC_CFG_PATH</code> 环境变量指定的路径,当前路径和\n<code>/etc/hyperledger/fabric</code> 路径下查找\n<code>configtx.yam</code> 配置文件并读入,作为默认的配置。或者使用参数的\n<code>-configPath</code> 定义。环境变量中以<code>CONFIGTX_</code>\n前缀开头的变量也会被作为配置项。</p>\n<p>很多功能都被启用了,好像唯一的功能就是创建创世区块?</p>\n<p>在源码中,创建创世区块的核心代码都是由 <code>protoutil</code>\n这个包实现的。</p>\n<h3 id=\"protoutil\">2.1.2 protoutil</h3>\n<p><code>protoutil</code> 是 Hyperledger Fabric\n中的一个实用工具,用于处理和操作协议缓冲区(protobuf)格式的数据。Hyperledger\nFabric 使用协议缓冲区(Protobuf)作为其内部数据结构的主要序列化格式,而\n<code>protoutil</code>\n提供了一组工具和函数来简化这些数据的创建、解析、和转换过程。</p>\n<p>具体来说,<code>protoutil</code> 可能包括以下功能:</p>\n<ul>\n<li>序列化和反序列化 Protobuf 消息。</li>\n<li>生成交易提案和区块。</li>\n<li>解析和检验区块数据结构。</li>\n<li>操作链码提案、响应和其他相关的 Protobuf 消息。</li>\n</ul>\n<p>这些功能对于开发和维护 Fabric 网络至关重要,因为它们简化了与 Fabric\n内部数据结构的交互。</p>\n<p>什么是协作缓冲区 Protobuf ?https://protobuf.com.cn/overview/</p>\n<p>简言之就是类似 Json 但比 Json\n更紧凑轻量的数据格式。一个区块,无非就是一个键值对,里面存的数据不同而已。</p>\n<table>\n<colgroup>\n<col style=\"width: 25%\">\n<col style=\"width: 23%\">\n<col style=\"width: 50%\">\n</colgroup>\n<thead>\n<tr>\n<th>Field</th>\n<th>Type</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>Header</code></td>\n<td><code>*cb.BlockHeader</code></td>\n<td>Contains metadata about the block itself.</td>\n</tr>\n<tr>\n<td><code>Header.Number</code></td>\n<td><code>uint64</code></td>\n<td>The sequence number of the block.</td>\n</tr>\n<tr>\n<td><code>Header.PreviousHash</code></td>\n<td><code>[]byte</code></td>\n<td>The hash of the previous block.</td>\n</tr>\n<tr>\n<td><code>Header.DataHash</code></td>\n<td><code>[]byte</code></td>\n<td>The hash of the block's data.</td>\n</tr>\n<tr>\n<td><code>Data</code></td>\n<td><code>*cb.BlockData</code></td>\n<td>Contains the actual data of the block.</td>\n</tr>\n<tr>\n<td><code>Metadata</code></td>\n<td><code>*cb.BlockMetadata</code></td>\n<td>Contains metadata for the block.</td>\n</tr>\n<tr>\n<td><code>Metadata.Metadata</code></td>\n<td><code>[][]byte</code></td>\n<td>Array of metadata entries.</td>\n</tr>\n</tbody>\n</table>\n<p>创世区块具体的值如下:</p>\n<ul>\n<li><code>Header.Number</code>:<code>0</code>,因为这是创世区块。</li>\n<li><code>Header.PreviousHash</code>:<code>nil</code>,因为这是创世区块,没有前一个区块。</li>\n<li><code>Header.DataHash</code>:由<code>protoutil.ComputeBlockDataHash(block.Data)</code>计算得出。</li>\n<li><code>Data</code>:包含一个<code>cb.Envelope</code>,其<code>Payload</code>为<code>cb.Payload</code>,<code>Data</code>为<code>cb.ConfigEnvelope</code>。</li>\n<li><code>Metadata.Metadata</code>:包含两个条目:\n<ul>\n<li><code>cb.BlockMetadataIndex_LAST_CONFIG</code>:包含<code>cb.LastConfig{Index: 0}</code>。</li>\n<li><code>cb.BlockMetadataIndex_SIGNATURES</code>:包含<code>cb.OrdererBlockMetadata{LastConfig: &cb.LastConfig{Index: 0}}</code>。</li>\n</ul></li>\n</ul>\n<p>然后把这个东西写入一个文件中\n<code>./channel-artifacts/${CHANNEL_NAME}.block</code>\n就完成了创世区块的创建。</p>\n<h2 id=\"创建通道\">2.2 创建通道</h2>\n<p>循环执行以下脚本:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"code\"><pre><span class=\"line\">. scripts/orderer.sh <span class=\"variable\">${CHANNEL_NAME}</span>> /dev/null 2>&1</span><br></pre></td></tr></table></figure>\n<blockquote>\n<p><code>.</code> 命令是 <code>source</code> 命令的简写形式,用于在当前\nshell 环境中执行一个脚本文件。这意味着文件中的所有命令都会在当前 shell\n中运行,而不会创建新的子 shell。这与直接执行脚本文件(如\n<code>./script.sh</code>)不同,后者会创建一个新的子 shell\n来运行脚本中的命令。</p>\n</blockquote>\n<p>orderer.sh:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"code\"><pre><span class=\"line\">osnadmin channel <span class=\"built_in\">join</span> </span><br><span class=\"line\">\t--channelID <span class=\"variable\">${channel_name}</span> </span><br><span class=\"line\">\t--config-block ./channel-artifacts/<span class=\"variable\">${channel_name}</span>.block </span><br><span class=\"line\">\t-o localhost:7053 </span><br><span class=\"line\">\t--ca-file <span class=\"string\">\"<span class=\"variable\">$ORDERER_CA</span>\"</span> </span><br><span class=\"line\">\t--client-cert <span class=\"string\">\"<span class=\"variable\">$ORDERER_ADMIN_TLS_SIGN_CERT</span>\"</span> </span><br><span class=\"line\">\t--client-key <span class=\"string\">\"<span class=\"variable\">$ORDERER_ADMIN_TLS_PRIVATE_KEY</span>\"</span> </span><br><span class=\"line\">>> log.txt 2>&1</span><br></pre></td></tr></table></figure>\n<p>突然出现了一个新工具:<code>osnadmin</code></p>\n<h3 id=\"osnadmin-channel\">2.2.1 osnadmin channel</h3>\n<p>OSN 是 Ordering Service Node 的缩写。osnadmin channel\n命令允许管理员在排序节点上执行与通道相关的操作,例如<strong>加入通道</strong>、<strong>列出排序节点已加入的通道</strong>以及<strong>移除通道</strong>。必须启用通道参与API,并且在每个排序节点的\norderer.yaml 中配置Admin端点。</p>\n<p><code>osnadmin channel join</code> 干的事,就是向\n<code>https://localhost:7053/participation/v1/channels</code>\n发送了一个HTTP POST请求,请求体是上一步生成的创世区块。</p>\n<p>顺便的,<code>osnadmin channel list</code> 就是向\n<code>https://localhost:7053/participation/v1/channels/${channel-id}</code>\n发送 HTTP GET 请求,如果不携带特定的 <code>channel-id</code>\n就是返回所有通道。</p>\n<p><code>osnadmin channel remove</code> 干的事,就是向\n<code>https://localhost:7053/participation/v1/channels</code>\n发送了一个HTTP DELETE请求。</p>\n<p>这里也表明了,<code>orderer</code>\n启动的服务里应该有很多接口可调用。</p>\n<h2 id=\"对等节点加入通道\">2.3 对等节点加入通道</h2>\n<p>在排序节点上创建好了通道,接下来就是让对等节点加入通道。</p>\n<p>同样是循环执行脚本:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"code\"><pre><span class=\"line\">peer channel <span class=\"built_in\">join</span> -b <span class=\"variable\">$BLOCKFILE</span> >&log.txt</span><br></pre></td></tr></table></figure>\n<p>其中\n<code>BLOCKFILE=\"./channel-artifacts/${CHANNEL_NAME}.block\"</code>\n,就是刚刚产生的创世区块。</p>\n<p>这里的 <code>peer channel</code> 好像就是\n<code>osnadmin channel</code> 的 <code>peer</code> 版。</p>\n<h3 id=\"peer-channel-join\">2.3.1 peer channel join</h3>\n<ul>\n<li><p><strong>ChannelCmdFactory</strong></p>\n<p>其中用到了一个工厂模式,<code>InitCmdFactory</code>\n,<code>peer channel</code>\n会根据不同的指令需求(是否需要背书、是否需要广播给对等节点、是否需要广播给排序节点)打包客户端。</p>\n<p><figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"comment\">// ChannelCmdFactory holds the clients used by ChannelCmdFactory</span></span><br><span class=\"line\"><span class=\"keyword\">type</span> ChannelCmdFactory <span class=\"keyword\">struct</span> {</span><br><span class=\"line\">\tEndorserClient pb.EndorserClient</span><br><span class=\"line\">\tSigner msp.SigningIdentity</span><br><span class=\"line\">\tBroadcastClient common.BroadcastClient</span><br><span class=\"line\">\tDeliverClient deliverClientIntf</span><br><span class=\"line\">\tBroadcastFactory BroadcastClientFactory</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></p>\n<p>执行 join 只需要背书,不需要广播给对等节点和排序节点。</p></li>\n<li><p><strong>getJoinCCSpec</strong></p>\n<p>听函数名字和链码有关,获取了一个链码的Spec,但是到目前为止我还没有创建过链码?先来看看源码:</p>\n<p><figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"keyword\">func</span> <span class=\"title\">getJoinCCSpec</span><span class=\"params\">()</span></span> (*pb.ChaincodeSpec, <span class=\"type\">error</span>) {</span><br><span class=\"line\">\t<span class=\"keyword\">if</span> genesisBlockPath == common.UndefinedParamValue {</span><br><span class=\"line\">\t\t<span class=\"keyword\">return</span> <span class=\"literal\">nil</span>, errors.New(<span class=\"string\">\"Must supply genesis block file\"</span>)</span><br><span class=\"line\">\t}</span><br><span class=\"line\"></span><br><span class=\"line\">\tgb, err := os.ReadFile(genesisBlockPath)</span><br><span class=\"line\">\t<span class=\"keyword\">if</span> err != <span class=\"literal\">nil</span> {</span><br><span class=\"line\">\t\t<span class=\"keyword\">return</span> <span class=\"literal\">nil</span>, GBFileNotFoundErr(err.Error())</span><br><span class=\"line\">\t}</span><br><span class=\"line\">\t<span class=\"comment\">// Build the spec</span></span><br><span class=\"line\">\tinput := &pb.ChaincodeInput{Args: [][]<span class=\"type\">byte</span>{[]<span class=\"type\">byte</span>(cscc.JoinChain), gb}}</span><br><span class=\"line\"></span><br><span class=\"line\">\tspec := &pb.ChaincodeSpec{</span><br><span class=\"line\">\t\tType: pb.ChaincodeSpec_Type(pb.ChaincodeSpec_Type_value[<span class=\"string\">\"GOLANG\"</span>]),</span><br><span class=\"line\">\t\tChaincodeId: &pb.ChaincodeID{Name: <span class=\"string\">\"cscc\"</span>},</span><br><span class=\"line\">\t\tInput: input,</span><br><span class=\"line\">\t}</span><br><span class=\"line\"></span><br><span class=\"line\">\t<span class=\"keyword\">return</span> spec, <span class=\"literal\">nil</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></p>\n<p>返回一个 ChaincodeSpec 结构体指针,类型指定为 GOLAN 语言,链码ID为\n<strong>cscc</strong>\n,并且还设置了这个链码的输入参数:<code>JoinChain: 创世区块字节码</code></p>\n<p><strong>cscc</strong>,其实是一个内置的系统链码 (System\nChaincode),全称为 <strong>Configuration System Chaincode</strong>。</p>\n<p>系统链码是由 Hyperledger Fabric\n平台内置的特殊链码,负责处理一些核心功能。开发者不需要自己编写或部署这些系统链码,它们在\nFabric 网络启动时自动部署,并在整个网络中使用。</p>\n<p>JoinChain 这个函数用于将一个新的节点加入到现有的通道中。当调用\n<code>cscc.JoinChain</code>\n时,节点会使用给定的创世区块来加入指定的通道。</p></li>\n<li><p><strong>executeJoin(cf <em>ChannelCmdFactory, spec\n</em>pb.ChaincodeSpec)</strong></p>\n<p>最后,将上面两个函数得到的结果(工厂和spec),放到一起执行。</p>\n<p><figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"keyword\">func</span> <span class=\"title\">executeJoin</span><span class=\"params\">(cf *ChannelCmdFactory, spec *pb.ChaincodeSpec)</span></span> (err <span class=\"type\">error</span>) {</span><br><span class=\"line\">\t<span class=\"comment\">// Build the ChaincodeInvocationSpec message</span></span><br><span class=\"line\">\tinvocation := &pb.ChaincodeInvocationSpec{ChaincodeSpec: spec}</span><br><span class=\"line\">\tcreator, err := cf.Signer.Serialize()</span><br><span class=\"line\"></span><br><span class=\"line\">\t<span class=\"comment\">// 根据序列化的身份(signer)和链码调用规范(invocation)创建提案</span></span><br><span class=\"line\">\t<span class=\"keyword\">var</span> prop *pb.Proposal</span><br><span class=\"line\">\tprop, _, err = protoutil.CreateProposalFromCIS(pcommon.HeaderType_CONFIG, <span class=\"string\">\"\"</span>, invocation, creator)</span><br><span class=\"line\"></span><br><span class=\"line\">\t<span class=\"comment\">// 根据提案和签名者创建签名的提案</span></span><br><span class=\"line\">\t<span class=\"keyword\">var</span> signedProp *pb.SignedProposal</span><br><span class=\"line\">\tsignedProp, err = protoutil.GetSignedProposal(prop, cf.Signer)</span><br><span class=\"line\"></span><br><span class=\"line\">\t<span class=\"comment\">// EndorserClient 是背书服务的客户端 API。</span></span><br><span class=\"line\">\t<span class=\"comment\">// ProcessProposal 用于处理提案。</span></span><br><span class=\"line\"> </span><br><span class=\"line\">\t<span class=\"keyword\">var</span> proposalResp *pb.ProposalResponse</span><br><span class=\"line\">\tproposalResp, err = cf.EndorserClient.ProcessProposal(context.Background(), signedProp)</span><br><span class=\"line\"></span><br><span class=\"line\">\tlogger.Info(<span class=\"string\">\"Successfully submitted proposal to join channel\"</span>)</span><br><span class=\"line\">\t<span class=\"keyword\">return</span> <span class=\"literal\">nil</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></p>\n<p>还是很清晰的,前两步准备好了请求客户端和要发送的数据,这一步签了名之后,直接调用<code>EndorserClient.ProcessProposal(context.Background(), signedProp)</code>\n来执行提议。</p>\n<p>封装的很深,执行的逻辑还藏在这个函数里。这个函数奇妙,有一个\n<code>context</code> 参数,上下文在这里是怎么用的?</p></li>\n<li><p><code>EndorserClient.ProcessProposal</code></p>\n<blockquote>\n<p>// For semantics around ctx use and closing/ending streaming RPCs,\nplease refer to\nhttps://godoc.org/google.golang.org/grpc#ClientConn.NewStream.</p>\n</blockquote>\n<p>这个是一个接口,这里使用的实现是 <code>perr.pb.go</code>\n中的实现,具体代码如下:</p>\n<p><figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">type</span> endorserClient <span class=\"keyword\">struct</span> {</span><br><span class=\"line\">\tcc *grpc.ClientConn</span><br><span class=\"line\">}</span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">func</span> <span class=\"params\">(c *endorserClient)</span></span> ProcessProposal(</span><br><span class=\"line\"> ctx context.Context, in *SignedProposal, </span><br><span class=\"line\"> opts ...grpc.CallOption</span><br><span class=\"line\"> ) (*ProposalResponse, <span class=\"type\">error</span>) </span><br><span class=\"line\">{</span><br><span class=\"line\">\tout := <span class=\"built_in\">new</span>(ProposalResponse)</span><br><span class=\"line\">\terr := c.cc.Invoke(ctx, <span class=\"string\">\"/protos.Endorser/ProcessProposal\"</span>, in, out, opts...)</span><br><span class=\"line\">\t<span class=\"keyword\">if</span> err != <span class=\"literal\">nil</span> {</span><br><span class=\"line\">\t\t<span class=\"keyword\">return</span> <span class=\"literal\">nil</span>, err</span><br><span class=\"line\">\t}</span><br><span class=\"line\">\t<span class=\"keyword\">return</span> out, <span class=\"literal\">nil</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></p>\n<p><code>c.cc</code> 就是结构体中定义的 <code>cc *grpc.ClientConn</code>\n一个普通的客户端连接。</p>\n<p>主要方法和属性</p>\n<ul>\n<li><code>Invoke</code>:用于调用 RPC 方法。</li>\n<li><code>NewStream</code>:用于创建流式 RPC 调用。</li>\n<li><code>Target</code>:返回连接的目标地址。</li>\n<li><code>Close</code>:关闭连接并清理相关资源。</li>\n<li><code>State</code>:返回连接的当前状态(例如,<code>Ready</code>、<code>Connecting</code>、<code>Idle</code>\n等)。</li>\n<li><code>WaitForStateChange</code>:等待连接状态发生变化。</li>\n</ul>\n<p>这里传了一个 <code>context.Context</code>\n参数,目的是允许给用户提供控制权,用户可以通过 ctx 对这个grpc goroutine\n进行取消、超时等操作。但是这段代码里没有在上层进行其他控制,所以直接传了一个\n<code>context.Background()</code> 进去。</p>\n<p>关于 gRPC 和 context 的详细内容见文末的附录。</p></li>\n</ul>\n<h3 id=\"viper\">2.3.2 viper</h3>\n<p>但是我还有个疑问,<code>peer channel join</code>\n的参数或者环境变量是在哪里设置的?之前的 <code>osnadmin</code>\n直接在参数里设置全了,而 <code>peer</code> 却没有任何参数设置。</p>\n<p>其实是 <code>network.sh</code> 设置了这个参数:</p>\n<figure class=\"highlight shell\"><table><tr><td class=\"code\"><pre><span class=\"line\">FABRIC_CFG_PATH=$PWD/../config/</span><br></pre></td></tr></table></figure>\n<p>在该文件夹里有一个 <code>core.yaml</code>\n里面定义了所有配置,里面写死了:</p>\n<figure class=\"highlight yaml\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"attr\">peer:</span></span><br><span class=\"line\">\t<span class=\"attr\">address:</span></span><br><span class=\"line\">\t\t<span class=\"string\">localhost:7051</span></span><br></pre></td></tr></table></figure>\n<p>另外,<code>setGlobals</code>\n函数设置了全局变量,用于切换两个组织的配置:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">if</span> [ <span class=\"variable\">$USING_ORG</span> -eq 1 ]; <span class=\"keyword\">then</span></span><br><span class=\"line\"> <span class=\"built_in\">export</span> CORE_PEER_LOCALMSPID=Org1MSP</span><br><span class=\"line\"> <span class=\"built_in\">export</span> CORE_PEER_TLS_ROOTCERT_FILE=<span class=\"variable\">$PEER0_ORG1_CA</span></span><br><span class=\"line\"> <span class=\"built_in\">export</span> CORE_PEER_MSPCONFIGPATH=...org1...</span><br><span class=\"line\"> <span class=\"built_in\">export</span> CORE_PEER_ADDRESS=localhost:7051</span><br><span class=\"line\"><span class=\"keyword\">elif</span> [ <span class=\"variable\">$USING_ORG</span> -eq 2 ]; <span class=\"keyword\">then</span></span><br><span class=\"line\"> <span class=\"built_in\">export</span> CORE_PEER_LOCALMSPID=Org2MSP</span><br><span class=\"line\"> <span class=\"built_in\">export</span> CORE_PEER_TLS_ROOTCERT_FILE=<span class=\"variable\">$PEER0_ORG2_CA</span></span><br><span class=\"line\"> <span class=\"built_in\">export</span> =...org2...</span><br><span class=\"line\"> <span class=\"built_in\">export</span> CORE_PEER_ADDRESS=localhost:9051</span><br><span class=\"line\"><span class=\"keyword\">fi</span></span><br></pre></td></tr></table></figure>\n<p>前三个变量来区别组织1和组织2的证书、MSP,最后一个变量区分节点的监听地址。</p>\n<p>(test-network\n中一个组织只有一个节点。实际上,同一个组织下会有多个节点,他们的前三个变量相同,最后一个参数不同)</p>\n<p>这里我很奇怪,怎么又是环境变量,又是配置文件,他们的命名方式都不一样,甚至环境变量还多了一个前缀\n<code>CORE</code> 这怎么关联上的?</p>\n<p>fabric 用了一个 go 的开源库 <code>viper</code>\n,其功能就是很方便的从配置文件中读取值,也能使用环境变量去覆盖配置文件里的值。他会自动解析环境变量,并把变量名小写、将下划线替换。并且源码中还有:</p>\n<figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\">viper.SetEnvPrefix(<span class=\"string\">\"CORE\"</span>)</span><br></pre></td></tr></table></figure>\n<p>显而易见,是设置了环境变量的前缀为 CORE 。这就是 fabric\n进行配置读取的方式。</p>\n<h3 id=\"总结复盘\">2.3.3 总结复盘</h3>\n<p>至此,<code>peer channel join -b 创世区块.block</code>\n执行完毕。再次复盘提出问题:</p>\n<p>具体来说,这个过程调用了一个系统内置的链码函数\n<code>cscc.JoinChain</code>\n,节点会使用给定的创世区块来加入指定的通道。</p>\n<blockquote>\n<p><strong>问题1:</strong>新的通道是通过创世区块来标识的吗?如果这个通道已经有好多个区块了,新的节点想加入进来,也是通过创世区块来加入吗?如果是,我这个例子都在本地,创世区块就存在本地,其他新节点想加入的时候,怎么获得创世区块?</p>\n<p><strong>通道与创世区块的关系</strong>:</p>\n<ul>\n<li><strong>创世区块</strong>\n是通道的第一个区块,它包含了通道的初始配置和一些重要的元数据。每个通道都有一个唯一的创世区块,通道的标识在区块链中是通过区块链上的区块来维护的,其中创世区块是至关重要的第一块。</li>\n<li>当一个新节点要加入到一个已有的通道时,即使这个通道已经包含了很多区块,新节点仍然是通过创世区块来加入的。这是因为创世区块包含了通道的初始配置和结构信息,节点需要这些信息来了解通道的基本设置。</li>\n</ul>\n<p><strong>获取创世区块</strong>:</p>\n<ul>\n<li>在生产环境中,新节点通常无法直接从本地获得创世区块,而是通过其他方式获取:\n<ol type=\"1\">\n<li><strong>通过已经加入通道的节点</strong>:新节点可以从已经是通道成员的节点那里请求创世区块。这通常通过\n<code>peer channel fetch</code>\n命令来实现,该命令可以从通道的区块链上获取创世区块或其他指定的区块。</li>\n<li><strong>从区块存储库中提取</strong>:在某些情况下,创世区块可能会被存储在一个共享的存储库或文件系统中,节点可以从中获取。</li>\n<li><strong>通过网络传输</strong>:创世区块也可以通过安全的网络传输从其他节点或管理系统中获取。</li>\n</ol></li>\n</ul>\n<p><strong>加入现有通道</strong>:</p>\n<ul>\n<li><p>当新节点使用创世区块加入现有的通道时,它首先会同步到当前通道的最新状态(即下载并验证通道中的所有区块,直到最新的区块)。这样,新节点便可以与通道中的其他节点保持一致。</p>\n<p>这里提到新节点会同步通道的最新状态,这部分代码在 peer\n内部执行。也就是 gRPC 发送请求后,docker 容器里的 peer 执行。</p></li>\n</ul>\n</blockquote>\n<p>调用链码的方式是给背书节点发送 gRPC 请求。</p>\n<p><strong>问题2:</strong>通过阅读源码,我发现这里的背书节点,就是要加入的节点。这是为什么?背书节点的作用是什么?有没有其他使用背书节点的例子?</p>\n<blockquote>\n<p><strong>背书节点的角色</strong>:</p>\n<ul>\n<li>在 Hyperledger Fabric 中,<strong>背书节点(Endorser Peer)</strong>\n是负责模拟和验证交易的节点。每个参与者节点都可以配置为背书节点,背书节点根据链码的逻辑模拟交易,并生成背书(endorsement),背书包含节点对交易的认可。</li>\n<li><strong>为什么背书节点就是要加入的节点</strong>:\n<ul>\n<li>当一个节点想要加入一个通道时,它需要运行一个系统链码(如\n<code>cscc</code>)来执行这个操作。在这种情况下,节点实际上是在请求自己作为背书节点来执行这个操作。这是因为加入通道的过程需要在本节点上进行一些检查和操作(例如验证创世区块、更新节点的通道状态等),这些操作都需要由本节点背书和认可。</li>\n</ul></li>\n<li><strong>背书节点的其他使用例子</strong>:\n<ul>\n<li><strong>交易提案的背书</strong>:在通常的交易流程中,客户端将交易提案发送给多个背书节点。这些节点分别模拟交易并生成背书响应。客户端收集足够的背书后,将交易提交给排序服务(Orderer)。</li>\n<li><strong>链码的安装与实例化</strong>:当一个链码被安装或实例化时,背书节点也会参与其中,模拟链码的执行并生成相应的状态更新或响应。</li>\n</ul></li>\n</ul>\n<p><strong>总结</strong>:</p>\n<ul>\n<li>背书节点在 Fabric\n中不仅用于加入通道的操作,还用于各种交易和链码操作中,是确保区块链数据一致性和安全性的关键组件。</li>\n<li>在你提到的场景中,背书节点执行的 <code>cscc.JoinChain</code>\n操作是为了确保节点正确加入通道,确保它能够获取到通道的配置并同步到最新状态。</li>\n</ul>\n</blockquote>\n<p>同时 <code>peer</code>\n会自动获取配置文件和环境变量。具体让哪个组织、哪个节点加入通道,都是在配置文件和环境变量中设置的。</p>\n<h2 id=\"设置锚定节点\">2.4 设置锚定节点</h2>\n<p>什么是锚定节点?锚定节点的作用是什么?</p>\n<h3 id=\"获取通道配置\">2.4.1 获取通道配置</h3>\n<h4 id=\"peer-channel-fetch-config\">2.4.1.1 peer channel fetch\nconfig</h4>\n<figure class=\"highlight bash\"><table><tr><td class=\"code\"><pre><span class=\"line\">peer channel fetch config </span><br><span class=\"line\">\t<span class=\"variable\">${TEST_NETWORK_HOME}</span>/channel-artifacts/config_block.pb </span><br><span class=\"line\">\t-o localhost:7050 </span><br><span class=\"line\">\t--ordererTLSHostnameOverride orderer.example.com </span><br><span class=\"line\">\t-c <span class=\"variable\">$CHANNEL</span> --tls --cafile <span class=\"string\">\"<span class=\"variable\">$ORDERER_CA</span>\"</span></span><br></pre></td></tr></table></figure>\n<p><code>peer channel fetch</code>\n用于获取一个特定的区块,并把它写入文件。后面可跟参数</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"code\"><pre><span class=\"line\"><newest|oldest|config|(number)</span><br></pre></td></tr></table></figure>\n<p>除了 <code>config</code> 都好理解。看看源码</p>\n<figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">switch</span> args[<span class=\"number\">0</span>] {</span><br><span class=\"line\">\t<span class=\"keyword\">case</span> <span class=\"string\">\"oldest\"</span>:</span><br><span class=\"line\">\t\tblock, err = cf.DeliverClient.GetOldestBlock()</span><br><span class=\"line\">\t<span class=\"keyword\">case</span> <span class=\"string\">\"newest\"</span>:</span><br><span class=\"line\">\t\tblock, err = cf.DeliverClient.GetNewestBlock()</span><br><span class=\"line\">\t<span class=\"keyword\">case</span> <span class=\"string\">\"config\"</span>:</span><br><span class=\"line\">\t\tiBlock, err2 := cf.DeliverClient.GetNewestBlock()</span><br><span class=\"line\">\t\t<span class=\"keyword\">if</span> err2 != <span class=\"literal\">nil</span> { <span class=\"keyword\">return</span> err2 }</span><br><span class=\"line\"> </span><br><span class=\"line\">\t\tlc, err2 := protoutil.GetLastConfigIndexFromBlock(iBlock)</span><br><span class=\"line\">\t\t<span class=\"keyword\">if</span> err2 != <span class=\"literal\">nil</span> { <span class=\"keyword\">return</span> err2 }</span><br><span class=\"line\"> </span><br><span class=\"line\">\t\tlogger.Infof(<span class=\"string\">\"Retrieving last config block: %d\"</span>, lc)</span><br><span class=\"line\">\t\tblock, err = cf.DeliverClient.GetSpecifiedBlock(lc)</span><br><span class=\"line\">\t<span class=\"keyword\">default</span>:</span><br><span class=\"line\">\t\tnum, err2 := strconv.Atoi(args[<span class=\"number\">0</span>])</span><br><span class=\"line\">\t\t<span class=\"keyword\">if</span> err2 != <span class=\"literal\">nil</span> {</span><br><span class=\"line\">\t\t\t<span class=\"keyword\">return</span> fmt.Errorf(<span class=\"string\">\"fetch target illegal: %s\"</span>, args[<span class=\"number\">0</span>])</span><br><span class=\"line\">\t\t}</span><br><span class=\"line\">\t\tblock, err = cf.DeliverClient.GetSpecifiedBlock(<span class=\"type\">uint64</span>(num))</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>即最新的块里存放了一项数据,记录最近的 config\n存放在第几个区块里。</p>\n<p>再具体一点,<code>InitCmdFactory</code> 根据有没有设置\n<code>-o</code> 决定需要 <code>peerDeliver</code> 还是\n<code>ordererDeliver</code>。</p>\n<p><code>peerDeliver</code> 还是 <code>ordererDeliver</code>\n唯一的区别在于,<code>peer</code> 创建的 <code>CommonClient</code> 的\n<code>keepalive</code> 选项为 <code>true</code>。</p>\n<blockquote>\n<p><code>keepalive</code>\n是一种网络层的机制,用于在没有数据流动时通过发送定期的 \"心跳\"\n消息来保持连接的活跃状态。</p>\n<p><strong>Peer 节点和 Orderer 节点的通信差异</strong></p>\n<ol type=\"1\">\n<li><strong>Peer 节点的通信需求</strong>:\n<ul>\n<li><strong>实时性和持久连接</strong>:Peer 节点之间或客户端与 Peer\n节点之间的通信往往涉及长时间的实时交互,如链码执行、状态查询、区块广播等。这些操作可能需要持久的连接,尤其是在监听区块事件或等待交易结果时,连接可能会长时间处于空闲状态。</li>\n<li><strong>保持连接稳定</strong>:为了避免在这些操作期间连接断开,<code>keepalive</code>\n选项被启用,以确保连接的稳定性,即使在长时间的空闲期内也能保持连接活跃。</li>\n</ul></li>\n<li><strong>Orderer 节点的通信需求</strong>:\n<ul>\n<li><strong>批量性和间歇通信</strong>:Orderer\n节点的主要职责是排序交易并将它们打包成区块。客户端与 Orderer\n节点的通信通常是间歇性的,如提交交易或请求区块。由于这种通信通常不是长时间持续的,连接建立和断开的频率较高,因此不太需要持续的\n<code>keepalive</code>。</li>\n<li><strong>较短的通信生命周期</strong>:与 Peer\n节点的长时间通信不同,Orderer\n节点的通信一般是短暂且快速的,通常在完成一次性请求后即关闭连接,因此\n<code>keepalive</code> 的需求不大。</li>\n</ul></li>\n</ol>\n</blockquote>\n<p>与 <code>EndorserClient</code> 不同的是,<code>peer</code> 和\n<code>orderer</code> 使用的是 <code>AtomicBroadcast_DeliverClient</code>\n原子的广播。</p>\n<figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"keyword\">func</span> <span class=\"params\">(x *atomicBroadcastBroadcastClient)</span></span> Send(m *common.Envelope) <span class=\"type\">error</span> {</span><br><span class=\"line\">\t<span class=\"keyword\">return</span> x.ClientStream.SendMsg(m)</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">func</span> <span class=\"params\">(x *atomicBroadcastBroadcastClient)</span></span> Recv() (*BroadcastResponse, <span class=\"type\">error</span>) {</span><br><span class=\"line\">\tm := <span class=\"built_in\">new</span>(BroadcastResponse)</span><br><span class=\"line\">\t<span class=\"keyword\">if</span> err := x.ClientStream.RecvMsg(m); err != <span class=\"literal\">nil</span> {</span><br><span class=\"line\">\t\t<span class=\"keyword\">return</span> <span class=\"literal\">nil</span>, err</span><br><span class=\"line\">\t}</span><br><span class=\"line\">\t<span class=\"keyword\">return</span> m, <span class=\"literal\">nil</span></span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>其中 <code>ClientStream</code> 是 <code>gRPC</code> 的内置对象。</p>\n<figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"keyword\">func</span> <span class=\"params\">(d *DeliverClient)</span></span> GetOldestBlock() (*cb.Block, <span class=\"type\">error</span>) {</span><br><span class=\"line\">\terr := d.seekOldest()</span><br><span class=\"line\">\t<span class=\"keyword\">if</span> err != <span class=\"literal\">nil</span> {</span><br><span class=\"line\">\t\t<span class=\"keyword\">return</span> <span class=\"literal\">nil</span>, errors.WithMessage(err, <span class=\"string\">\"error getting oldest block\"</span>)</span><br><span class=\"line\">\t}</span><br><span class=\"line\"></span><br><span class=\"line\">\t<span class=\"keyword\">return</span> d.readBlock()</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>先发送请求,再读取返回值。go 中使用 gRPC 的请求和返回不用像 Js\n那样特意写异步函数。</p>\n<p>至此,读取了该通道的配置区块并写入 <code>config_block.pb</code>\n本地保存。</p>\n<h4 id=\"configtxlator\">2.4.1.2 configtxlator</h4>\n<figure class=\"highlight bash\"><table><tr><td class=\"code\"><pre><span class=\"line\">configtxlator proto_decode </span><br><span class=\"line\">\t--input <span class=\"variable\">${TEST_NETWORK_HOME}</span>/channel-artifacts/config_block.pb </span><br><span class=\"line\">\t--<span class=\"built_in\">type</span> common.Block </span><br><span class=\"line\">\t--output <span class=\"variable\">${TEST_NETWORK_HOME}</span>/channel-artifacts/config_block.json</span><br></pre></td></tr></table></figure>\n<p>这个函数功能很明显,将区块中的数据转为 JSON\n格式。不分析这个工具的源码了,大体上就是用 proto 读取,保存成 json。</p>\n<h4 id=\"jq\">2.4.1.3 jq</h4>\n<figure class=\"highlight bash\"><table><tr><td class=\"code\"><pre><span class=\"line\">jq .data.data[0].payload.data.config</span><br><span class=\"line\"> <span class=\"variable\">${TEST_NETWORK_HOME}</span>/channel-artifacts/config_block.json ><span class=\"string\">\"<span class=\"variable\">${OUTPUT}</span>\"</span></span><br></pre></td></tr></table></figure>\n<p><code>jq</code> 是一个用于处理 JSON 数据的命令行工具,它可以方便地从\nJSON 文件中提取数据、进行过滤和格式化。</p>\n<p>这段 Bash 代码执行了以下操作:</p>\n<ol type=\"1\">\n<li>使用 <code>jq</code> 从 <code>config_block.json</code>\n文件中提取通道配置块(<code>.data.data[0].payload.data.config</code>\n字段)。</li>\n<li>提取到的数据被写入到由环境变量 <code>OUTPUT</code>\n指定的文件中。</li>\n</ol>\n<h3 id=\"修改通道配置\">2.4.2 修改通道配置</h3>\n<figure class=\"highlight json\"><table><tr><td class=\"code\"><pre><span class=\"line\">jq '.channel_group.groups.Application.groups.'$<span class=\"punctuation\">{</span>CORE_PEER_LOCALMSPID<span class=\"punctuation\">}</span>'.values += <span class=\"punctuation\">{</span><span class=\"attr\">\"AnchorPeers\"</span><span class=\"punctuation\">:</span><span class=\"punctuation\">{</span><span class=\"attr\">\"mod_policy\"</span><span class=\"punctuation\">:</span> <span class=\"string\">\"Admins\"</span><span class=\"punctuation\">,</span><span class=\"attr\">\"value\"</span><span class=\"punctuation\">:</span><span class=\"punctuation\">{</span><span class=\"attr\">\"anchor_peers\"</span><span class=\"punctuation\">:</span> <span class=\"punctuation\">[</span><span class=\"punctuation\">{</span><span class=\"attr\">\"host\"</span><span class=\"punctuation\">:</span> <span class=\"string\">\"'$HOST'\"</span><span class=\"punctuation\">,</span><span class=\"attr\">\"port\"</span><span class=\"punctuation\">:</span> '$PORT'<span class=\"punctuation\">}</span><span class=\"punctuation\">]</span><span class=\"punctuation\">}</span><span class=\"punctuation\">,</span><span class=\"attr\">\"version\"</span><span class=\"punctuation\">:</span> <span class=\"string\">\"0\"</span><span class=\"punctuation\">}</span><span class=\"punctuation\">}</span>' $<span class=\"punctuation\">{</span>TEST_NETWORK_HOME<span class=\"punctuation\">}</span>/channel-artifacts/$<span class=\"punctuation\">{</span>CORE_PEER_LOCALMSPID<span class=\"punctuation\">}</span>config.json > $<span class=\"punctuation\">{</span>TEST_NETWORK_HOME<span class=\"punctuation\">}</span>/channel-artifacts/$<span class=\"punctuation\">{</span>CORE_PEER_LOCALMSPID<span class=\"punctuation\">}</span>modified_config.json</span><br></pre></td></tr></table></figure>\n<p>这段代码使用 <code>jq</code> 修改了 Hyperledger Fabric\n网络中与某个组织(由 <code>CORE_PEER_LOCALMSPID</code>\n环境变量指定)相关的通道配置。具体操作是:</p>\n<ol type=\"1\">\n<li><strong>定位组织的配置部分</strong>:\n<ul>\n<li>通过 <code>jq</code> 表达式\n<code>'.channel_group.groups.Application.groups.'${CORE_PEER_LOCALMSPID}'.values'</code>\n定位到通道配置 JSON 文件中,特定组织的配置部分。</li>\n</ul></li>\n<li><strong>添加或更新锚节点配置</strong>:\n<ul>\n<li>在定位到的组织配置部分中,使用 <code>+=</code> 操作符向\n<code>values</code> 字段中添加或更新一个名为 <code>AnchorPeers</code>\n的配置项。这个配置项包含锚节点的信息,包括\n<code>mod_policy</code>、<code>value</code> 和\n<code>version</code>。</li>\n<li><code>value</code> 字段中嵌套了 <code>anchor_peers</code> 信息,其中\n<code>host</code> 和 <code>port</code> 的值分别从环境变量\n<code>HOST</code> 和 <code>PORT</code>\n中获取,代表了锚节点的主机和端口。</li>\n</ul></li>\n<li><strong>保存修改后的配置</strong>:\n<ul>\n<li>将修改后的 JSON 数据保存到一个新的文件中,该文件路径由\n<code>TEST_NETWORK_HOME</code> 和 <code>CORE_PEER_LOCALMSPID</code>\n环境变量指定,表示该组织的修改后的通道配置。</li>\n</ul></li>\n</ol>\n<h3 id=\"创建配置更新\">2.4.3 创建配置更新</h3>\n<figure class=\"highlight bash\"><table><tr><td class=\"code\"><pre><span class=\"line\">createConfigUpdate <span class=\"variable\">${CHANNEL_NAME}</span> <span class=\"variable\">${TEST_NETWORK_HOME}</span>/channel-artifacts/<span class=\"variable\">${CORE_PEER_LOCALMSPID}</span>config.json <span class=\"variable\">${TEST_NETWORK_HOME}</span>/channel-artifacts/<span class=\"variable\">${CORE_PEER_LOCALMSPID}</span>modified_config.json <span class=\"variable\">${TEST_NETWORK_HOME}</span>/channel-artifacts/<span class=\"variable\">${CORE_PEER_LOCALMSPID}</span>anchors.tx</span><br></pre></td></tr></table></figure>\n<p><code>createConfigUpdate</code>\n函数的总体作用是生成一个配置更新交易(configuration update\ntransaction)。这个过程涉及将原始和修改后的通道配置转换为二进制格式,计算出两者之间的差异,并将这个差异打包为一个可以提交的交易。</p>\n<p>其具体实现如下:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"code\"><pre><span class=\"line\">configtxlator proto_encode --input <span class=\"string\">\"<span class=\"variable\">${ORIGINAL}</span>\"</span> --<span class=\"built_in\">type</span> common.Config --output <span class=\"variable\">${TEST_NETWORK_HOME}</span>/channel-artifacts/original_config.pb</span><br><span class=\"line\"></span><br><span class=\"line\">configtxlator proto_encode --input <span class=\"string\">\"<span class=\"variable\">${MODIFIED}</span>\"</span> --<span class=\"built_in\">type</span> common.Config --output <span class=\"variable\">${TEST_NETWORK_HOME}</span>/channel-artifacts/modified_config.pb</span><br><span class=\"line\"></span><br><span class=\"line\">configtxlator compute_update --channel_id <span class=\"string\">\"<span class=\"variable\">${CHANNEL}</span>\"</span> --original <span class=\"variable\">${TEST_NETWORK_HOME}</span>/channel-artifacts/original_config.pb --updated <span class=\"variable\">${TEST_NETWORK_HOME}</span>/channel-artifacts/modified_config.pb --output <span class=\"variable\">${TEST_NETWORK_HOME}</span>/channel-artifacts/config_update.pb</span><br><span class=\"line\"></span><br><span class=\"line\">configtxlator proto_decode --input <span class=\"variable\">${TEST_NETWORK_HOME}</span>/channel-artifacts/config_update.pb --<span class=\"built_in\">type</span> common.ConfigUpdate --output <span class=\"variable\">${TEST_NETWORK_HOME}</span>/channel-artifacts/config_update.json</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"built_in\">echo</span> <span class=\"string\">'{\"payload\":{\"header\":{\"channel_header\":{\"channel_id\":\"'</span><span class=\"variable\">$CHANNEL</span><span class=\"string\">'\", \"type\":2}},\"data\":{\"config_update\":'</span>$(<span class=\"built_in\">cat</span> <span class=\"variable\">${TEST_NETWORK_HOME}</span>/channel-artifacts/config_update.json)<span class=\"string\">'}}}'</span> | jq . > <span class=\"variable\">${TEST_NETWORK_HOME}</span>/channel-artifacts/config_update_in_envelope.json</span><br><span class=\"line\"></span><br><span class=\"line\">configtxlator proto_encode --input <span class=\"variable\">${TEST_NETWORK_HOME}</span>/channel-artifacts/config_update_in_envelope.json --<span class=\"built_in\">type</span> common.Envelope --output <span class=\"string\">\"<span class=\"variable\">${OUTPUT}</span>\"</span> </span><br></pre></td></tr></table></figure>\n<ol type=\"1\">\n<li><strong>编码原始配置文件为二进制格式</strong>:\n<ul>\n<li><strong>目的</strong>:将原始的 JSON 格式配置文件\n(<code>${ORIGINAL}</code>) 编码为 <code>protobuf</code> 格式的二进制文件\n<code>original_config.pb</code>。<code>protobuf</code> 是 Hyperledger\nFabric 用于内部数据表示的格式。</li>\n</ul></li>\n<li><strong>编码修改后的配置文件为二进制格式</strong>:\n<ul>\n<li><strong>目的</strong>:将修改后的 JSON 格式配置文件\n(<code>${MODIFIED}</code>) 编码为 <code>protobuf</code> 格式的二进制文件\n<code>modified_config.pb</code>。</li>\n</ul></li>\n<li><strong>计算配置更新的差异</strong>:\n<ul>\n<li><strong>目的</strong>:比较原始配置和修改后的配置,计算出两者之间的差异,并生成一个表示这些差异的配置更新文件\n<code>config_update.pb</code>。这个文件以 <code>protobuf</code>\n格式保存,包含了需要应用的配置更改。</li>\n</ul></li>\n<li><strong>解码配置更新为 JSON 格式</strong>:\n<ul>\n<li><strong>目的</strong>:将 <code>protobuf</code> 格式的配置更新文件\n<code>config_update.pb</code> 解码回 JSON 格式的文件\n<code>config_update.json</code>,方便后续处理或查看。</li>\n</ul></li>\n<li><strong>创建包含配置更新的信封(Envelope)</strong>:\n<ul>\n<li><strong>目的</strong>:将配置更新嵌入到一个 <code>Envelope</code>\n中,添加必要的元数据(如 <code>channel_id</code>\n和类型)。这是为了将配置更新打包成一个完整的交易,可以提交到区块链网络中。</li>\n</ul></li>\n<li><strong>编码带信封的配置更新为二进制格式</strong>:\n<ul>\n<li><strong>目的</strong>:将包含信封的配置更新(<code>config_update_in_envelope.json</code>)再次编码为\n<code>protobuf</code> 格式的二进制文件,生成最终可以提交的配置更新交易\n<code>anchors.tx</code>。</li>\n</ul></li>\n</ol>\n<h3 id=\"peer-channel-update\">2.4.4 peer channel update</h3>\n<figure class=\"highlight bash\"><table><tr><td class=\"code\"><pre><span class=\"line\">peer channel update </span><br><span class=\"line\">\t-o localhost:7050 </span><br><span class=\"line\">\t--ordererTLSHostnameOverride orderer.example.com </span><br><span class=\"line\">\t-c <span class=\"variable\">$CHANNEL_NAME</span> </span><br><span class=\"line\">\t-f <span class=\"variable\">${TEST_NETWORK_HOME}</span>/channel-artifacts/<span class=\"variable\">${CORE_PEER_LOCALMSPID}</span>anchors.tx </span><br><span class=\"line\">\t--tls --cafile <span class=\"string\">\"<span class=\"variable\">$ORDERER_CA</span>\"</span> >&log.txt</span><br></pre></td></tr></table></figure>\n<p>已经很明白这段代码的目的了。看看具体实现有没有特别的地方。</p>\n<p>工厂什么都不需要:</p>\n<figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\">InitCmdFactory(EndorserNotRequired, PeerDeliverNotRequired, OrdererNotRequired)</span><br></pre></td></tr></table></figure>\n<p>官方文档说,Use the orderer at ip address\n<code>orderer.example.com:7050</code> to send the configuration\ntransaction to all peers in the channel to update their copy of the\nchannel configuration.</p>\n<p>即这段代码是指定一个排序节点向通道内的所有对等节点广播,修改他们的配置。</p>\n<p>要记住什么操作室部署好的节点服务做的,什么是调用的指令做的。广播的操作是\norderer 服务内部做的,<code>peer channel update</code>\n只负责向排序节点发送一个 gRPC。</p>\n<h2 id=\"问题\">问题</h2>\n<h3 id=\"锚节点的作用\">锚节点的作用</h3>\n<ol type=\"1\">\n<li><strong>跨组织的区块传播</strong>:\n<ul>\n<li>锚节点是用于在不同组织之间进行区块传播的关键节点。当一个新的区块被\nOrderer\n节点生成并分发时,它首先会发送给每个组织的锚节点。然后,锚节点负责将这些区块传递给本组织内的其他\nPeer 节点。</li>\n<li>这种机制确保了区块能够在不同组织的节点之间有效传播,保持所有节点的数据同步。</li>\n</ul></li>\n<li><strong>跨组织的服务发现</strong>:\n<ul>\n<li>锚节点用于跨组织的服务发现。在 Hyperledger Fabric\n中,当客户端(或其他 Peer\n节点)需要与其他组织的节点通信时,它们可以通过查询锚节点来获取目标组织内的其他\nPeer 节点的信息。</li>\n<li>例如,在执行一个跨组织的链码调用时,客户端可能需要发送交易提案到多个组织的\nPeer 节点。锚节点提供了一个入口,使得客户端能够发现并连接到这些 Peer\n节点。</li>\n</ul></li>\n<li><strong>优化网络流量</strong>:\n<ul>\n<li>通过将区块传播的责任集中到少数锚节点,可以减少网络中全网广播带来的流量开销。这种集中化传播可以提高网络的效率和性能,避免不必要的数据冗余和延迟。</li>\n</ul></li>\n</ol>\n<p>有点类似于网关。</p>\n<h3 id=\"各个client的作用\">各个Client的作用</h3>\n<p>在 Hyperledger Fabric 的源码中,<code>ChannelCmdFactory</code>\n结构体中的几个客户端(<code>EndorserClient</code>、<code>BroadcastClient</code>、<code>DeliverClient</code>)各自承担着不同的角色和职责,它们分别用于与不同类型的\nFabric 节点通信。以下是对这些客户端的具体实现及其用途的详细解释:</p>\n<h4 id=\"endorserclient-pb.endorserclient\">1. EndorserClient\n(<code>pb.EndorserClient</code>)</h4>\n<ul>\n<li><strong>作用</strong>:<code>EndorserClient</code>\n是用于与背书节点(Endorser\nPeer)进行通信的客户端。它的主要职责是发送交易提案给背书节点,并接收背书节点返回的背书响应。</li>\n<li><strong>具体实现</strong>:\n<ul>\n<li><code>EndorserClient</code> 通常实现了 gRPC 接口,负责与 Peer 节点的\n<code>ProcessProposal</code>\n方法交互。背书节点会对交易提案进行模拟执行,并返回模拟的结果(包括读取集和写入集),这个过程称为“背书”。</li>\n<li><code>EndorserClient</code> 的具体实现类可能是通过 gRPC\n框架生成的客户端代码,例如 <code>endorserClient</code>,它封装了与\n<code>ProcessProposal</code> 的 gRPC 调用。</li>\n</ul></li>\n<li><strong>使用场景</strong>:\n<ul>\n<li>在客户端提交交易之前,会使用 <code>EndorserClient</code> 向多个 Peer\n节点请求交易提案的背书。</li>\n</ul></li>\n</ul>\n<h4 id=\"broadcastclient-common.broadcastclient\">2. BroadcastClient\n(<code>common.BroadcastClient</code>)</h4>\n<ul>\n<li><strong>作用</strong>:<code>BroadcastClient</code> 是用于与 Orderer\n节点通信的客户端。它的主要职责是将经过背书的交易提交给 Orderer\n节点,以便将交易排序后打包进区块。</li>\n<li><strong>具体实现</strong>:\n<ul>\n<li><code>BroadcastClient</code> 也通常通过 gRPC 与 Orderer\n节点通信。它实现了与 Orderer 节点的 <code>Broadcast</code>\n方法的交互,负责将交易数据发送给 Orderer,Orderer\n节点接收到交易后会对其进行排序,并打包到区块中。</li>\n<li>一个常见的实现类可能是 <code>broadcastClientImpl</code>,它封装了与\n<code>Broadcast</code> 方法的 gRPC 调用。</li>\n</ul></li>\n<li><strong>使用场景</strong>:\n<ul>\n<li>在交易得到足够的背书之后,客户端会使用 <code>BroadcastClient</code>\n将交易提交给 Orderer 节点进行排序和区块打包。</li>\n</ul></li>\n</ul>\n<h4 id=\"deliverclient-deliverclientintf\">3. DeliverClient\n(<code>deliverClientIntf</code>)</h4>\n<ul>\n<li><strong>作用</strong>:<code>DeliverClient</code> 是用于从 Orderer\n或 Peer\n节点接收区块和事件的客户端。它的主要职责是监听区块的传递或接收事件通知。</li>\n<li><strong>具体实现</strong>:\n<ul>\n<li><code>DeliverClient</code> 通过 gRPC 接口与 Orderer 或 Peer\n节点进行通信,通常会实现 <code>Deliver</code>\n方法的调用。<code>Deliver</code> 方法允许客户端从 Peer 节点或 Orderer\n节点获取区块的传递或接收区块的通知。</li>\n<li>实现类可能是 <code>deliverClientImpl</code> 或者其他实现了\n<code>deliverClientIntf</code> 接口的类,它封装了与 <code>Deliver</code>\n方法的 gRPC 交互。</li>\n</ul></li>\n<li><strong>使用场景</strong>:\n<ul>\n<li>当客户端需要获取通道中的最新区块或监听特定事件(如区块提交事件)时,会使用\n<code>DeliverClient</code> 来实现这一功能。</li>\n</ul></li>\n</ul>\n<h3 id=\"总结\">总结</h3>\n<ul>\n<li><code>EndorserClient</code>:用于与 Peer\n节点通信,发送交易提案并接收背书响应。</li>\n<li><code>BroadcastClient</code>:用于与 Orderer 节点通信,将交易提交给\nOrderer 进行排序和区块打包。</li>\n<li><code>DeliverClient</code>:用于接收区块或事件通知,可以从 Orderer\n或 Peer 节点获取区块信息。</li>\n</ul>\n<h1 id=\"总结-1\">总结</h1>\n<p>至此,已经完成了 test-network 中网络的启动和通道的创建。</p>\n<p>主要分析了如何使用 fabric 提供的工具(如 <code>peer</code>\n<code>osnadmin</code> <code>cryptogen</code> 等)进行构建网络。</p>\n<p>但是网络的启动中留了一个问题:<code>peer node start</code> 和\n<code>orderer start</code>\n干了什么。源码中哪里进行了端口的开放,这些节点收到 gRPC\n请求之后,后续进行了什么操作。</p>\n<p>下一节完成了这部分问题。</p>\n<h1 id=\"附录\">附录</h1>\n<h2 id=\"grpc-与传统-http-调用的区别\">gRPC 与传统 HTTP 调用的区别</h2>\n<p>gRPC 和传统的 HTTP\n调用虽然都用于客户端与服务器之间的通信,但它们在底层实现、性能、数据格式、传输协议等方面有显著的区别。以下是一些关键的差异:</p>\n<h4 id=\"通信协议\">1. <strong>通信协议</strong></h4>\n<ul>\n<li><strong>gRPC</strong>:基于 HTTP/2 协议,这使得 gRPC\n具备了流式通信、多路复用、头部压缩、双向流等特性。HTTP/2\n的多路复用允许多个请求和响应通过单个 TCP 连接同时传输,减少了延迟。</li>\n<li><strong>传统 HTTP</strong>:基于 HTTP/1.1(或\nHTTP/2,但应用较少),主要通过请求-响应的方式进行通信,每次请求通常会创建一个新的连接(除非使用了持久连接)。</li>\n</ul>\n<h4 id=\"数据格式\">2. <strong>数据格式</strong></h4>\n<ul>\n<li><strong>gRPC</strong>:使用 Protocol\nBuffers(protobuf)作为其序列化协议。这是一种高效的二进制格式,体积小、解析速度快,非常适合跨语言通信。</li>\n<li><strong>传统 HTTP</strong>:通常使用 JSON、XML 等文本格式。虽然 JSON\n可读性好,但在性能和数据大小方面不如 protobuf 高效。</li>\n</ul>\n<h4 id=\"性能\">3. <strong>性能</strong></h4>\n<ul>\n<li><strong>gRPC</strong>:由于使用了 HTTP/2 和 protobuf,gRPC\n在性能和资源利用率上要优于传统的 HTTP 调用。gRPC\n提供更低的延迟和更高的吞吐量。</li>\n<li><strong>传统\nHTTP</strong>:相对较慢,尤其是在处理大规模通信或需要高并发的场景下,性能不如\ngRPC。</li>\n</ul>\n<h4 id=\"双向流式通信\">4. <strong>双向流式通信</strong></h4>\n<ul>\n<li><strong>gRPC</strong>:支持双向流式通信,这意味着客户端和服务器可以在单个\ngRPC 调用中同时发送和接收消息。这对实时通信和流式数据处理特别有用。</li>\n<li><strong>传统\nHTTP</strong>:基于请求-响应模型,通常是一对一的交互方式。虽然可以通过\nWebSocket 实现双向通信,但这不是 HTTP 协议的原生功能。</li>\n</ul>\n<h4 id=\"服务定义\">5. <strong>服务定义</strong></h4>\n<ul>\n<li><strong>gRPC</strong>:服务接口使用 protobuf\n文件定义,强类型化,接口可以跨语言调用,且编译器自动生成客户端和服务器代码。</li>\n<li><strong>传统 HTTP</strong>:没有标准化的接口定义方式,通常使用\nOpenAPI(Swagger)来定义 RESTful\nAPI,但客户端和服务器代码需要手动编写。</li>\n</ul>\n<h4 id=\"适用场景\">6. <strong>适用场景</strong></h4>\n<ul>\n<li><strong>gRPC</strong>:非常适合微服务架构、大规模分布式系统、实时通信、跨语言服务调用等场景。</li>\n<li><strong>传统\nHTTP</strong>:适合需要高可读性、与浏览器交互、简单或公开的 API\n服务,通常用于 Web 服务和 RESTful API。</li>\n</ul>\n<h4 id=\"能否使用传统-http-进行相同的调用\">7. <strong>能否使用传统 HTTP\n进行相同的调用?</strong></h4>\n<p>在技术上,你可以使用传统的 HTTP 来实现类似的 RPC\n调用,但这会带来一些挑战:</p>\n<ul>\n<li>你需要自己定义数据格式(例如 JSON),处理序列化和反序列化。</li>\n<li>缺乏 gRPC\n提供的许多高级功能,如双向流、自动代码生成、负载均衡、强类型接口等。</li>\n<li>性能和资源利用率可能不如 gRPC 高效。</li>\n</ul>\n<h2 id=\"go-标准库中的-context-详细讲解\">Go 标准库中的\n<code>context</code> 详细讲解</h2>\n<p><code>context</code> 是 Go 标准库中的一个包,用于在不同的 goroutine\n之间传递请求范围内的元数据、取消信号和超时信息。<code>context</code>\n在处理并发操作时特别有用,尤其是在 gRPC、HTTP\n服务器、数据库操作等场景下。</p>\n<h4 id=\"基本概念\">1. <strong>基本概念</strong></h4>\n<ul>\n<li><strong><code>context.Context</code> 接口</strong>:\n<ul>\n<li><code>context.Context</code> 是一个接口,它定义了在不同的 goroutine\n之间传递请求范围内的信息的标准方法。</li>\n<li>它是不可变的,一旦创建,就不能修改,而是通过派生(创建子\ncontext)的方式来添加新的信息。</li>\n</ul></li>\n<li><strong>背景上下文</strong>:\n<ul>\n<li><strong><code>context.Background()</code></strong>:通常作为根\ncontext\n使用,没有携带任何信息,一般在主函数、初始化或者测试时使用。</li>\n<li><strong><code>context.TODO()</code></strong>:占位用的\ncontext,当你不确定应该使用什么 context 时,可以使用\n<code>TODO()</code>。</li>\n</ul></li>\n</ul>\n<h4 id=\"常用的函数\">2. <strong>常用的函数</strong></h4>\n<ul>\n<li><strong><code>context.WithCancel(parent Context) (ctx Context, cancel CancelFunc)</code></strong>:\n<ul>\n<li>创建一个子 context,并返回一个取消函数 <code>cancel</code>。</li>\n<li>调用 <code>cancel()</code> 时,会向所有使用该 context 的 goroutine\n发送取消信号。</li>\n</ul></li>\n<li><strong><code>context.WithDeadline(parent Context, d time.Time) (Context, CancelFunc)</code></strong>:\n<ul>\n<li>创建一个子 context,该 context 会在指定的时间点自动取消。</li>\n<li>同样返回一个 <code>CancelFunc</code>,可以主动取消。</li>\n</ul></li>\n<li><strong><code>context.WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)</code></strong>:\n<ul>\n<li>与 <code>WithDeadline</code>\n类似,但这里是指定一个相对的超时时间。</li>\n</ul></li>\n<li><strong><code>context.WithValue(parent Context, key, val interface{}) Context</code></strong>:\n<ul>\n<li>返回一个子\ncontext,携带一个键值对,可以用于传递请求范围内的特定数据(例如用户身份、请求\nID 等)。</li>\n<li>注意:<code>WithValue</code> 应该谨慎使用,避免滥用造成混乱。</li>\n</ul></li>\n</ul>\n<h4 id=\"如何在代码中使用-context\">3. <strong>如何在代码中使用\n<code>context</code></strong></h4>\n<p>使用 <code>context</code>\n的典型场景包括取消正在进行的操作、设置超时、传递元数据等。以下是一个简单的使用示例:</p>\n<figure class=\"highlight go\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"keyword\">func</span> <span class=\"title\">main</span><span class=\"params\">()</span></span> {</span><br><span class=\"line\"> <span class=\"comment\">// 创建一个带有超时的 context</span></span><br><span class=\"line\"> ctx, cancel := context.WithTimeout(context.Background(), <span class=\"number\">5</span>*time.Second)</span><br><span class=\"line\"> <span class=\"keyword\">defer</span> cancel()</span><br><span class=\"line\"></span><br><span class=\"line\"> ch := <span class=\"built_in\">make</span>(<span class=\"keyword\">chan</span> <span class=\"type\">int</span>)</span><br><span class=\"line\"> <span class=\"keyword\">go</span> doSomething(ctx, ch)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">select</span> {</span><br><span class=\"line\"> <span class=\"keyword\">case</span> result := <-ch:</span><br><span class=\"line\"> fmt.Println(<span class=\"string\">\"Received result:\"</span>, result)</span><br><span class=\"line\"> <span class=\"keyword\">case</span> <-ctx.Done():</span><br><span class=\"line\"> fmt.Println(<span class=\"string\">\"Operation timed out:\"</span>, ctx.Err())</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">func</span> <span class=\"title\">doSomething</span><span class=\"params\">(ctx context.Context, ch <span class=\"keyword\">chan</span> <span class=\"type\">int</span>)</span></span> {</span><br><span class=\"line\"> <span class=\"keyword\">select</span> {</span><br><span class=\"line\"> <span class=\"keyword\">case</span> <-time.After(<span class=\"number\">10</span> * time.Second): <span class=\"comment\">// 模拟耗时操作</span></span><br><span class=\"line\"> ch <- <span class=\"number\">42</span></span><br><span class=\"line\"> <span class=\"keyword\">case</span> <-ctx.Done():</span><br><span class=\"line\"> fmt.Println(<span class=\"string\">\"Operation cancelled:\"</span>, ctx.Err())</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>在这个例子中,如果 <code>doSomething</code> 操作超过 5\n秒没有完成,<code>ctx.Done()</code> 会被触发,导致操作取消。</p>\n<h4 id=\"context-的使用建议\">4. <strong><code>context</code>\n的使用建议</strong></h4>\n<ul>\n<li><strong>传递上下文</strong>:函数之间传递 context 时,通常将\n<code>context.Context</code> 作为第一个参数。</li>\n<li><strong>及时取消</strong>:使用\n<code>WithCancel</code>、<code>WithTimeout</code>、<code>WithDeadline</code>\n创建的 context 一定要在不需要时调用返回的\n<code>CancelFunc</code>,否则可能导致资源泄露。</li>\n<li><strong>避免滥用\n<code>WithValue</code></strong>:<code>WithValue</code>\n适合传递请求范围内少量的信息,但不应该用它来传递大量数据或者频繁使用。</li>\n</ul>\n<h3 id=\"总结-2\">总结</h3>\n<ul>\n<li><strong>gRPC</strong> 是一个高性能的 RPC\n框架,适合微服务和高并发场景,与传统 HTTP\n调用在协议、数据格式、性能和功能上有显著差异。</li>\n<li><strong>Go 的 <code>context</code></strong>\n用于在并发操作中传递元数据、取消信号和超时控制,是处理并发任务时的重要工具。它有助于管理资源,避免资源泄露或长时间未完成的任务。</li>\n</ul>\n","categories":["笔记"],"tags":["区块链","go","fabric","超级账本"]},{"title":"毕设02 - OP-TEE,Hello World","url":"/graduation-project/02/","content":"<p>昨天配了一天环境,今天先解决昨天遗留的问题:</p>\n<ul>\n<li>dbus-launch gnome-terminal 是什么指令?</li>\n<li>optee 怎么启动?</li>\n</ul>\n<span id=\"more\"></span>\n<h1 id=\"dbus-launch-gnome-terminal\">1 dbus-launch gnome-terminal</h1>\n<ol type=\"1\">\n<li><p><code>dbus-launch</code>: <code>dbus-launch</code> 是一个用于启动\nD-Bus 会话总线的命令。D-Bus 是 Linux\n系统中进程间通信(IPC)的系统,它允许不同的应用程序彼此之间进行通信。使用\n<code>dbus-launch</code> 可以确保应用程序运行在一个有 D-Bus\n支持的环境中,特别是在图形用户界面中。如果你的桌面会话中 D-Bus\n没有正确启动,某些需要 D-Bus 通信的程序可能无法正常工作。</p></li>\n<li><p><code>gnome-terminal</code>: 这是启动 GNOME 终端的命令,GNOME\n终端是 Linux 上 GNOME 桌面环境中的默认终端模拟器。</p></li>\n</ol>\n<p>结合起来,<code>dbus-launch gnome-terminal</code> 的作用是: -\n启动一个带有 D-Bus 会话总线支持的 GNOME 终端。换句话说,它确保 GNOME\n终端启动时可以正常使用 D-Bus 的功能。</p>\n<p>通常情况下,D-Bus 会在桌面环境启动时自动启动,因此用户不需要手动运行\n<code>dbus-launch</code>。但在某些特殊情况下,例如在没有图形会话或远程会话中,<code>dbus-launch</code>\n可以用来手动启动一个 D-Bus 实例以支持某些应用程序的运行。</p>\n<h1 id=\"启动optee\">2 启动optee</h1>\n<p>注意,optee同时只能启动一次,可能是因为会监听相同的端口。</p>\n<h2 id=\"直接在虚拟机中操作\">2.1 直接在虚拟机中操作</h2>\n<ol type=\"1\">\n<li>打开控制台,输入\n<code>dbus-launch gnome-terminal</code>,会打开一个新的控制台</li>\n<li>在新的控制台里,进入 <code>/root/optee/build</code> (你的\n<code>optee</code> 安装路径),执行 <code>make run-only</code>\n<code>make run</code> 会执行所有编译和检查,比较慢;安装成功后直接执行\n<code>make run-only</code> 更快。</li>\n</ol>\n<h2 id=\"通过ssh连接远程操作\">2.2 通过ssh连接远程操作</h2>\n<p>我使用的是mac。</p>\n<h3 id=\"方法一xquartz\">方法一:XQuartz</h3>\n<ol type=\"1\">\n<li><p>安装 XQuartz,在官网下载最新版直接安装。</p></li>\n<li><p>启动 XQuartz,在偏好里设置”允许从网络客户端连接“</p>\n<p><img src=\"/graduation-project/02/image-20240928105303669.png\" alt=\"image-20240928105303669\" style=\"zoom:50%;\"></p></li>\n<li><p>在终端输入 <code>dbus-launch gnome-terminal</code>,会在 XQuartz\n里打开一个控制台:</p>\n<p><img src=\"/graduation-project/02/image-20240928110041269.png\" alt=\"image-20240928110041269\" style=\"zoom: 33%;\"></p>\n<blockquote>\n<p>使用 <code>dbus-launch</code>\n还可以启动很多东西,甚至是浏览器,<del>虽然会很卡</del>。</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"code\"><pre><span class=\"line\"><span class=\"built_in\">export</span> XAUTHORITY=<span class=\"variable\">$HOME</span>/.Xauthority <span class=\"comment\"># 如果报错设置这个环境变量</span></span><br><span class=\"line\">dbus-launch firefox</span><br></pre></td></tr></table></figure>\n<figure>\n<img src=\"/graduation-project/02/image-20240928110909695.png\" alt=\"image-20240928110909695\">\n<figcaption aria-hidden=\"true\">image-20240928110909695</figcaption>\n</figure>\n</blockquote></li>\n<li><p>在这个控制台里,进入 <code>/root/optee/build</code> (你的\n<code>optee</code> 安装路径),执行 <code>make run-only</code></p>\n<p><img src=\"/graduation-project/02/image-20240928113548477.png\" alt=\"image-20240928113548477\" style=\"zoom: 33%;\"></p></li>\n<li><p>(第三步好像可以)</p></li>\n</ol>\n<h3 id=\"方法二tmux推荐\">方法二:tmux(推荐)</h3>\n<ol type=\"1\">\n<li><p>安装 <code>tmux</code></p>\n<p><figure class=\"highlight bash\"><table><tr><td class=\"code\"><pre><span class=\"line\">apt install tmux</span><br></pre></td></tr></table></figure></p></li>\n<li><p>启动 <code>tmux</code></p></li>\n<li><p>进入 <code>/root/optee/build</code> (你的 <code>optee</code>\n安装路径),执行 <code>make run-only</code></p>\n<p><img src=\"/graduation-project/02/image-20240928114747520.png\" alt=\"image-20240928114747520\" style=\"zoom: 33%;\"></p></li>\n<li><p>使用 <code>tmux</code>\n的命令切换窗口,查看安全世界和普通世界。例如\n<code>ctrl + b, w</code></p>\n<p><img src=\"/graduation-project/02/image-20240928114851423.png\" alt=\"image-20240928114851423\" style=\"zoom:33%;\"></p></li>\n</ol>\n<blockquote>\n<p>Tmux 使用教程:https://www.ruanyifeng.com/blog/2019/10/tmux.html</p>\n</blockquote>\n<p>方法二不需要传输图形界面,会流畅很多,而且最通用。</p>\n<h1 id=\"运行测试\">3 运行测试</h1>\n<p>在 QEMU Monitor 界面输入 <code>'c'</code>\n并按下回车来继续执行虚拟机。</p>\n<p>此时进入 Normal World 终端,会发现虚拟机已经开始执行:</p>\n<figure>\n<img src=\"/graduation-project/02/image-20240928151826867.png\" alt=\"image-20240928151826867\">\n<figcaption aria-hidden=\"true\">image-20240928151826867</figcaption>\n</figure>\n<h2 id=\"buildroot\">3.1 Buildroot</h2>\n<p>系统输出这段话:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">Welcome to Buildroot,type root or test to login</span><br><span class=\"line\">buildroot login:</span><br></pre></td></tr></table></figure>\n<ul>\n<li><p><strong>什么是 Buildroot?</strong></p>\n<p>*Buildroot** 是一个构建嵌入式 Linux\n系统的工具,用于生成小型且高度定制化的 Linux\n操作系统。它通常被用于嵌入式设备,如路由器、IoT 设备等。</p>\n<p>在 OP-TEE 的虚拟机环境中,Buildroot 被用来创建 Normal World 的 Linux\n环境。这个环境足够轻量级,用于执行测试、运行基本应用,并和 OP-TEE 的\nSecure World 交互。</p></li>\n<li><p><strong><code>root</code> 和 <code>test</code>\n的区别</strong></p>\n<p>在登录提示下,系统提供了两个账户选项:</p>\n<ul>\n<li><p><strong>root</strong>:这是系统的超级用户(管理员)。使用\n<code>root</code>\n登录后,你拥有对系统的完全控制权限,可以执行所有操作,包括安装软件、修改系统配置、启动或关闭服务等。一般来说,默认的登录密码可能为空,或者会在相关的文档中提到。</p></li>\n<li><p><strong>test</strong>:这是一个普通用户账户,用于测试目的。使用\n<code>test</code>\n登录后,你可能只能执行一些受限的操作,比如运行特定的应用程序或脚本,但没有修改系统配置或执行管理员任务的权限。</p></li>\n</ul></li>\n</ul>\n<h2 id=\"xtest\">3.2 xtest</h2>\n<p><code>xtest</code> 是 OP-TEE 提供的一个测试工具,用于验证 OP-TEE\n系统和其功能是否正确工作。它是 OP-TEE 项目的一部分,能够在 OP-TEE Secure\nWorld 和 Normal World 之间执行一系列的集成测试。这些测试涵盖了许多\nOP-TEE\n的核心功能,包括加密操作、密钥管理、身份认证、文件系统操作、以及与普通世界的交互。</p>\n<p>以 root 身份登录,运行 <code>xtest</code>。</p>\n<p>没有任何报错。</p>\n<h2 id=\"运行hello-world\">3.3 运行hello world</h2>\n<p>直接运行 <code>optee_example_hello_world</code> 。这个程序在\n<code>make run</code> 的时候就写入操作系统了。</p>\n<h1 id=\"ta文件结构\">4 TA文件结构</h1>\n<blockquote>\n<p>参考博客:https://kickstartembedded.com/2022/11/13/op-tee-part-4-writing-your-first-trusted-application</p>\n</blockquote>\n<p><code>optee_example</code> 中提供了一个 <code>hello_world</code>\n文件夹 ,结构如下:</p>\n<p><img src=\"/graduation-project/02/image-20240930212609062.png\" alt=\"image-20240930212609062\" style=\"zoom:50%;\"></p>\n<p>可以被清晰的分为三个部分:</p>\n<ol type=\"1\">\n<li>主机部分:<code>host/</code> 文件夹</li>\n<li>可信部分:<code>ta/</code> 文件夹</li>\n<li>高层构建 Helper</li>\n</ol>\n<h2 id=\"主机部分\">3.1 主机部分</h2>\n<p>主机部分是运行在 <strong>Normal World</strong>\n中的代码。构建过程的一个输出是 <code>libteec.so</code>\n文件,它必须与主机部分链接。主机部分的整体流程大致如下所示。</p>\n<p><img src=\"/graduation-project/02/image-20240930213254910.png\" alt=\"image-20240930213254910\" style=\"zoom:50%;\"></p>\n<h2 id=\"可信部分\">3.2 可信部分</h2>\n<p>可信部分是在 <strong>Secure World</strong>\n中运行的代码。可信部分的总体结构如下所示。注意,各种函数实际上是主机部分启动的事件的回调函数,例如上下文初始化和结束,会话打开和关闭等。</p>\n<p><img src=\"/graduation-project/02/image-20240930213634738.png\" alt=\"image-20240930213634738\" style=\"zoom:50%;\"></p>\n<p>注意到主机部分的函数前缀是 <code>TEEC_</code>,可信部分前缀是\n<code>TA_</code>。</p>\n<h2 id=\"高层构建助手\">3.3 高层构建助手</h2>\n<p>构建助手是 <code>Makefiles</code> 和与 <code>cmake</code>\n相关的文件,它们将帮助我们正确构建 TA。</p>\n<p>Linaro提供的示例最好的特性之一是,用户可以简单地将自己的示例应用程序添加到这个repo中,如果文件夹结构保持正确,您的应用程序将被构建为默认示例!</p>\n<p>一定要探索构建助手文件,亲自查看实现!</p>\n<h1 id=\"修改hello-world\">5 修改Hello World</h1>\n<p>我们将基于 Linaro 提供的 <code>hello_world</code>\n应用程序编写一个修改版本。更重要的是,我们将使其完全与提供的示例分离,而不是直接修改源代码。这将帮助我们更好地理解哪些组件是重要的。</p>\n<h2 id=\"主机部分修改\">5.1 主机部分修改</h2>\n<ol type=\"1\">\n<li><strong>修改 <code>host/Makefile</code> 文件</strong>:\n<ul>\n<li>修改 <code>Makefile</code> 文件中的 <code>BINARY</code>\n变量的值。将其命名为 <code>optee_example_ke_hello_world</code> 。</li>\n</ul></li>\n<li><strong>修改 <code>host/main.c</code> 和\n<code>ta/include/ke_hello_world_ta.h</code> 文件</strong>:\n<ul>\n<li>将名为 <code>hello_world_ta.h</code> 的包含文件名更改为\n<code>ke_hello_world_ta.h</code> 。这意味着你需要在代码中找到所有引用\n<code>hello_world_ta.h</code> 的地方,并将其更改为\n<code>ke_hello_world_ta.h</code> 。</li>\n<li>将 <code>UUID</code> 变量的值更改为\n<code>TA_KE_HELLO_WORLD_UUID</code> 。这意味着你需要在\n<code>ta/include/ke_hello_world_ta.h</code> 中定义一个新的宏\n<code>TA_KE_HELLO_WORLD_UUID</code>\n,这个宏将代表新应用的唯一标识符。</li>\n<li>在 <code>TEEC_InvokeCommand(...)</code> 函数调用中,将命令值更改为\n<code>TA_KE_HELLO_WORLD_INC_VALUE</code> 。</li>\n</ul></li>\n</ol>\n<h2 id=\"可信部分修改\">5.2 可信部分修改</h2>\n<ol type=\"1\">\n<li><strong>ta/Android.mk</strong> - 将 <code>local_module</code>\n变量的值替换为从可信网站生成的新版本4 UUID。</li>\n<li><strong>ta/user_ta_header_defines.h</strong> - 将包含的文件更改为\n<code>ke_hello_world_ta.h</code>。</li>\n<li><strong>ta/user_ta_header_defines.h</strong> - 将名为\n<code>TA_UUID</code> 的宏的值更改为\n<code>TA_KE_HELLO_WORLD_UUID</code>。</li>\n<li><strong>ta/user_ta_header_defines.h</strong> - 在\n<code>TA_CURRENT_TA_EXT_PROPERTIES</code> 宏中,将\n<code>hello_world</code> 的所有出现更改为\n<code>ke_hello_world</code>。</li>\n<li><strong>ta/sub.mk</strong> - 将 <code>srcs-y</code>\n变量中的文件替换为 <code>ke_hello_world_ta.c</code>。</li>\n<li><strong>ta/Makefile</strong> - 将我们在 <code>ta/Android.mk</code>\n中粘贴的版本4 UUID粘贴到 <code>BINARY</code> 变量的值中。</li>\n<li><strong>ta/ke_hello_world_ta.c</strong> - 将\n<code>hello_world_ta.c</code> 重命名为\n<code>ke_hello_world_ta.c</code>,并在其中将包含的文件名称更改为\n<code>ke_hello_world_ta.h</code>。</li>\n<li><strong>ta/ke_hello_world_ta.c</strong> - 在名为\n<code>TA_InvokeCommandEntryPoint(...)</code>\n的函数中,更改开关案例以反映带有 <code>KE_HELLO_WORLD_*</code> 而不是\n<code>HELLO_WORLD_*</code> 的宏。</li>\n<li><strong>ta/include/ke_hello_world_ta.h</strong> - 将\n<code>hello_world_ta.h</code> 重命名为\n<code>ke_hello_world_ta.h</code>,并在其中将定义的宏更改为\n<code>TA_KE_HELLO_WORLD_UUID</code>。同时,将此宏的值更改为我们上面使用的版本4\nUUID。</li>\n<li><strong>ta/include/ke_hello_world_ta.h</strong> -\n将函数ID宏的值更改为使用 <code>KE_HELLO_WORLD_*</code> 而不是\n<code>HELLO_WORLD_*</code>。</li>\n</ol>\n<h2 id=\"高层构建助手修改\">5.3 高层构建助手修改</h2>\n<ul>\n<li><strong>CMakeLists.txt</strong> -\n需要将CMake项目的名称更改为<code>optee_example_ke_hello_world</code>。</li>\n<li><strong>Android.mk</strong> -\n需要将<code>LOCAL_MODULE</code>变量的值更改为<code>optee_example_ke_hello_world</code>。</li>\n</ul>\n<h2 id=\"修改总结\">5.4 修改总结</h2>\n<ul>\n<li>生成UUID(直接使用 Linux 命令 <code>uuidgen</code>\n)替换所有原来的UUID</li>\n<li>把所有 <code>hello_world</code> 改成\n<code>ke_hello_world</code></li>\n</ul>\n<h2 id=\"编译\">5.5 编译</h2>\n<p>直接回到 <code>build</code> 目录,运行 <code>make run</code>。</p>\n<p>就会自动把刚刚的 Hello World 编译到 OP-TEE 里。</p>\n<p>如何单独编译一个TA,之后再看……明天开始阅读 TSC-VEE</p>\n<p>看到 TSC-VEE 的文档里,</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">For QEMU v8:</span><br><span class=\"line\"></span><br><span class=\"line\">Place the trust applications(TAs) in the optee-examples/ folder</span><br><span class=\"line\">Execute make run in the build folder</span><br><span class=\"line\">Run the TA.</span><br></pre></td></tr></table></figure>\n<p>说明就是这么运行的2333</p>\n<h1 id=\"附录\">附录</h1>\n<h2 id=\"交叉编译\">交叉编译</h2>\n<p>与交叉编译相对的改变是本地编译。</p>\n<p>本地编译就是编译出的代码只能在我本地运行,交叉编译就是在一个平台上生成可以在另一个平台运行的代码。</p>\n<p>本文提到的所有交叉编译,就是在Linux中编译可以在嵌入式环境中运行的可执行文件。</p>\n","categories":["笔记"],"tags":["毕设","op-tee","TrustZone","QEMU"]},{"title":"Hyperledger Fabric 源码精读(3)","url":"/fabric/fabric%E6%BA%90%E7%A0%81%E7%B2%BE%E8%AF%BB%20(3)/","content":"<ul>\n<li><p>开坑,学习 Fabric 的源码。</p></li>\n<li><p>思路是根据 <code>fabric-sample</code> 的\n<code>test-network</code>\n中的脚本,一行行分析。遇到里面使用的指令,看源码如何实现。</p></li>\n<li><p>下面内容非常混乱,写的毫无逻辑,之后有空重新整理一遍。</p></li>\n<li><p>一口气写完太长了,typora里会卡,分章节发。</p></li>\n<li><p>学习笔记,不保证内容正确性。</p></li>\n<li><p>因为台风改签明天了……再看一点</p></li>\n</ul>\n<span id=\"more\"></span>\n<h1 id=\"peer-node-start\">3 peer node start</h1>\n<p>有一个参数:</p>\n<figure class=\"highlight bash\"><table><tr><td class=\"code\"><pre><span class=\"line\">--peer-chaincodedev start peer <span class=\"keyword\">in</span> chaincode development mode</span><br></pre></td></tr></table></figure>\n<p>最终执行的是 <code>internal/peer/node/start.go</code> 中的\n<code>serve()</code> 函数。非常长,挑重点的详细记录:</p>\n<ol type=\"1\">\n<li><p><code>viper</code> 获取所有参数,输出所有环境变量</p></li>\n<li><p>检查 MSP 类型,仅支持 <code>FABRIC</code> 模式</p></li>\n<li><p><code>grpc.EnableTracing = true</code>\n(效果类似于,每次发送请求都会在控制台输出请求信息)</p></li>\n<li><p>获取核心配置</p></li>\n<li><p>创建平台注册凭证</p></li>\n<li><p>启动操作系统</p></li>\n<li><p>观察者模式,监听 <code>opsSystem.Provider</code></p></li>\n<li><p>配置链码安装路径、存储器和解码器</p></li>\n<li><p>解析 <code>peerHost</code> 和 <code>listenAddress</code>\n(这两个地址有区别吗??)</p>\n<blockquote>\n<p>发现一个之前没搞清楚的概念,主机地址和监听地址其实是不同的,只不过大多数开发场景中,<code>peerHost</code>\n和 <code>listenAddr</code>\n可能会指向相同的主机地址,但它们的上下文和作用略有不同:一个是用于解析配置和标识网络位置,另一个是用于节点启动时的监听配置。</p>\n<p>主机地址一般是 IP 地址或域名,例如,<code>192.168.1.10</code> 或\n<code>example.com</code> 都是主机地址。</p>\n<p>监听地址可以是 IP 地址或通配符,比如 <code>0.0.0.0</code> 和\n<code>192.168.1.10</code> 都是监听地址。</p>\n<p>存在很多主机地址和监听地址不同的情况:</p>\n<p>单网卡多IP,一台电脑有多个IP地址(网卡有多个网络接口),但是只监听一个IP;使用通配符或者localhost监听。</p>\n</blockquote></li>\n<li><p>配置一堆服务端配置</p></li>\n<li><p>创建 <code>peer</code> 实例</p></li>\n<li><p>获取本地 MSP ,从 MSP 中获取身份</p></li>\n<li><p>根据身份,创建 <code>MembershipInfoProvider</code>\n,用来判断一个节点是否连接到一个组织。</p></li>\n<li><p>创建政策检查器和管理器</p></li>\n<li><p>启动 <code>aclProvider</code> (ACL 是什么?)</p>\n<blockquote>\n<p>ACL 是 Access Control List(访问控制列表),和计网中的概念相同。</p>\n</blockquote></li>\n<li><p>创建 <code>lifecycleValidatorCommitter</code>\n,用于链码生命周期管理。</p></li>\n<li><p>配置 <code>ccprovider</code></p></li>\n<li></li>\n</ol>\n","categories":["笔记"],"tags":["区块链","go","fabric","超级账本"]},{"title":"AXI 系统总线","url":"/blockchain/AXI/","content":"<blockquote>\n<p>本文属于学习笔记,内容可能有误、可能不全面,仅代表个人在学习这一知识时的理解和总结</p>\n</blockquote>\n<p>第一次接触AMBA AXI系统总线,学习 <span class=\"exturl\" data-url=\"aHR0cHM6Ly9kZXZlbG9wZXIuYXJtLmNvbS9kb2N1bWVudGF0aW9uLzEwMjIwMi8wMzAwP2xhbmc9ZW4=\">Learn\nthe architecture - An introduction to AMBA AXI<i class=\"fa fa-external-link-alt\"></i></span> ,做到笔记。</p>\n<span id=\"more\"></span>\n<h1 id=\"amba\">AMBA</h1>\n<h2 id=\"qa\">Q&A</h2>\n<blockquote>\n<p>什么是AMBA?</p>\n</blockquote>\n<p>AMBA(Advanced Microcontroller Bus\nArchitecture)是由ARM公司开发的一种标准化的芯片内部总线协议,简单来说,它就是一种让芯片内部不同部分能够快速、可靠地相互通信的“高速公路”。在一个芯片里,有很多不同的“模块”,比如处理器、内存、输入输出设备等等,这些模块需要彼此传递数据,而AMBA就是负责管理这些数据怎么传输的。</p>\n<blockquote>\n<p>在AMBA提出之前,芯片内部的各个模块应该也能传递数据,那时候是怎么实现数据传递的?</p>\n</blockquote>\n<p>在AMBA提出之前,芯片内部各个模块之间的确已经能够实现数据传递,但方式较为复杂和不统一。以下是一些在AMBA之前常见的数据传输方式:</p>\n<ol type=\"1\">\n<li><p><strong>定制总线</strong></p>\n<p>在AMBA出现之前,芯片制造商通常会为每个项目定制设计自己的总线系统。这种定制总线完全由设计团队根据具体需求设计,缺乏统一的标准。这意味着每个新项目可能需要重新设计数据传输的方式,耗时且复杂。</p>\n<p>缺点:</p>\n<pre><code>- 缺乏标准化,导致设计难度大。\n\n- 不同模块之间的接口可能不兼容,集成和调试复杂。\n\n- 难以重复使用设计,增加开发成本和时间。</code></pre></li>\n<li><p><strong>共享总线架构</strong></p>\n<p>在一些设计中,多个模块会共享同一条总线。这种架构较为简单,但由于所有模块都使用同一条总线,可能会产生“总线争用”的问题,即多个模块同时请求访问总线,导致数据传输的延迟或效率下降。</p>\n<p>缺点:</p>\n<ul>\n<li>总线带宽有限,当多个模块同时访问时,容易产生瓶颈。</li>\n<li>缺乏灵活性,难以适应复杂的多模块通信需求。</li>\n</ul></li>\n<li><p><strong>专用接口</strong></p>\n<p>一些芯片设计会为特定模块之间的通信设计专用的接口。这种方式虽然在特定场景下效率较高,但其灵活性很差。每对模块之间需要专用的连接,这使得芯片设计复杂度大幅增加。</p>\n<p>缺点:</p>\n<pre><code>- 专用接口需要针对每对模块单独设计,增加了设计工作量。\n\n- 难以扩展,增加新模块时需要重新设计接口。</code></pre></li>\n</ol>\n<p>AMBA的提出为这些问题提供了一个解决方案。它标准化了芯片内部的通信,允许设计者使用一套通用的协议和总线结构来连接不同的模块。这样一来,不仅减少了设计复杂度,也提高了模块之间的兼容性和重用性,使得芯片设计变得更高效、更灵活。</p>\n<p>通过AMBA,总线系统可以自动处理诸如优先级、仲裁等复杂问题,开发者只需要专注于模块本身的设计,而不必再花费大量时间去解决数据传输的问题。</p>\n<blockquote>\n<p>AMBA是什么时候提出的?</p>\n</blockquote>\n<p>AMBA(Advanced Microcontroller Bus\nArchitecture)最早是由ARM公司在<strong>1996年</strong>提出的。最初的版本被称为AMBA\n2.0,后来逐步发展出更高级的版本,如AMBA 3和AMBA\n4,分别引入了更先进的功能和特性,以适应不断发展的嵌入式系统需求。</p>\n<p>AMBA 2.0版本主要引入了两种总线:AHB(Advanced High-performance\nBus)和 APB(Advanced Peripheral\nBus)。在后续的版本中,<strong>AXI(Advanced eXtensible\nInterface)</strong>等新总线协议被引入,以支持更高性能和更复杂的系统设计。</p>\n<p>AMBA的推出标志着嵌入式系统设计进入了一个新的标准化阶段,使得芯片设计和模块集成变得更加高效和灵活。</p>\n<h2 id=\"总结\">总结</h2>\n<p>AMBA是一个<strong>标准化协议</strong>,用于芯片内部各个模块之间的数据传输。</p>\n<p>使用AMBA,可以一定程度降低嵌入式系统设计的复杂度。</p>\n<p>AXI是AMBA 3.0\n提出的总线协议,下面详细学习AXI的设计,理解他的工作原理和使用方法,感受为什么AXI总线能够降低设计的复杂度。</p>\n<p>以及,有很多其他现成的IP核,使用的是AXI总线。如果我想设计一个模块与其他IP核连接,必须在我的模块里实现AXI总线。</p>\n<h1 id=\"axi-总线协议\">AXI 总线协议</h1>\n<h2 id=\"详细架构\">详细架构</h2>\n<h2 id=\"关键特性\">关键特性</h2>\n<p>AXI\n协议具有多个关键特性,这些特性旨在提高数据传输和事务处理的带宽和延迟,如下所示:</p>\n<ul>\n<li><strong>独立的读写通道</strong>:AXI\n支持两组不同的通道,一组用于写操作,另一组用于读操作。拥有两组独立的通道有助于提高接口的带宽性能,因为读写操作可以同时进行。</li>\n<li><strong>多重未完成地址</strong>:AXI\n允许存在多个未完成的地址。这意味着管理器可以在不等待先前事务完成的情况下发起新事务。这可以提高系统性能,因为它使得事务的并行处理成为可能。</li>\n<li><strong>地址和数据操作之间没有严格的时间关系</strong>:在 AXI\n中,地址和数据操作之间没有严格的时间关系。这意味着,例如,管理器可以在写地址通道上发出写地址,但没有时间要求规定管理器何时必须在写数据通道上提供相应的数据。</li>\n<li><strong>支持非对齐数据传输</strong>:对于由宽度超过一个字节的数据传输组成的突发传输,访问的首字节可以与自然地址边界不对齐。例如,起始字节地址为\n0x1002 的 32 位数据包并未对齐到自然的 32 位地址边界。</li>\n<li><strong>乱序事务完成</strong>:AXI 支持乱序事务完成。AXI\n协议包含事务标识符,不同 ID\n值的事务完成顺序没有限制。这意味着单个物理端口可以支持乱序事务,通过充当多个逻辑端口,每个逻辑端口按顺序处理其事务。</li>\n<li><strong>基于起始地址的突发事务</strong>:AXI\n管理器仅发出第一个传输的起始地址。对于后续的任何传输,受控端将根据突发类型计算下一个传输地址。</li>\n</ul>\n<h2 id=\"传输和事务\">传输和事务</h2>\n<p>https://developer.arm.com/documentation/102202/0300/Channel-transfers-and-transactions?lang=en</p>\n<p>简而言之,分为读写两个部分,分别有以下接口:</p>\n<ul>\n<li><strong>写</strong>:\n<ul>\n<li>Write Address</li>\n<li>Write Data</li>\n<li>Write Response</li>\n</ul></li>\n<li><strong>读</strong>:\n<ul>\n<li>Read Address</li>\n<li>Read Data (包括Response的内容)</li>\n</ul></li>\n</ul>\n<p>传输就是读或写1个数据的过程。一个事务可以由一个或多个传输组成,也就是可以一次性传输多次。</p>\n<p>传输前需要先握手配对,即主机的VALID和从机的READY均为1时,开始传输。</p>\n<p>写事务是所有数据写入<strong>之后</strong>,返回一个OK。读事务是每读取一个数据的<strong>同时</strong>,返回一个OK。</p>\n<h2 id=\"信号\">信号</h2>\n<p>https://developer.arm.com/documentation/102202/0300/Channel-signals?lang=en</p>\n<p>定义了各个接口的具体信号实现。</p>\n<p>其中提到了安全支持,这在TrustZone中很有用。</p>\n","categories":["笔记"],"tags":["FPGA","ARM","AMBA"]},{"title":"404","url":"//404.html","content":"<figure class=\"highlight plaintext\"><table><tr><td class=\"code\"><pre><span class=\"line\">██╗ ██╗ ██████╗ ██╗ ██╗ ███╗ ██╗ ██████╗ ████████╗</span><br><span class=\"line\">██║ ██║██╔═████╗██║ ██║ ████╗ ██║██╔═══██╗╚══██╔══╝</span><br><span class=\"line\">███████║██║██╔██║███████║ ██╔██╗ ██║██║ ██║ ██║</span><br><span class=\"line\">╚════██║████╔╝██║╚════██║ ██║╚██╗██║██║ ██║ ██║</span><br><span class=\"line\"> ██║╚██████╔╝ ██║ ██║ ╚████║╚██████╔╝ ██║</span><br><span class=\"line\"> ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═══╝ ╚═════╝ ╚═╝</span><br><span class=\"line\"></span><br><span class=\"line\"> ███████╗ ██████╗ ██╗ ██╗███╗ ██╗██████╗</span><br><span class=\"line\"> ██╔════╝██╔═══██╗██║ ██║████╗ ██║██╔══██╗</span><br><span class=\"line\"> █████╗ ██║ ██║██║ ██║██╔██╗ ██║██║ ██║</span><br><span class=\"line\"> ██╔══╝ ██║ ██║██║ ██║██║╚██╗██║██║ ██║</span><br><span class=\"line\"> ██║ ╚██████╔╝╚██████╔╝██║ ╚████║██████╔╝</span><br><span class=\"line\"> ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚═════╝</span><br></pre></td></tr></table></figure>\n"},{"title":"文章标签","url":"/tags/index.html","content":"\n"}]