<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Tonychow's Blog</title><link href="https://blog.tonychow.me/" rel="alternate"></link><link href="https://blog.tonychow.me/feeds/all.atom.xml" rel="self"></link><id>https://blog.tonychow.me/</id><updated>2021-11-18T00:00:00+08:00</updated><entry><title>6.824 Lecture 7 Fault Tolerance Raft (2) Notes &amp; Paper Reading</title><link href="https://blog.tonychow.me/mit6.824-letcture5-notes-raft2.html" rel="alternate"></link><published>2021-11-18T00:00:00+08:00</published><updated>2021-11-18T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2021-11-18:/mit6.824-letcture5-notes-raft2.html</id><summary type="html"></summary><content type="html">&lt;h2&gt;1 概要&lt;/h2&gt;
&lt;p&gt;本节课继续 Lecture 5 的内容，对 Raft 论文剩下的内容进行了讨论，主要是包含日志复制的一些异常处理、持久化、压缩和快照，其中论文中的 Raft 集群新旧配置变化的联合一致相关内容略过没有详细讨论，但是课堂上学生比较多的问题都是围绕着这个点提出的。此外，还稍微提及了一致性模型中的线性一致性(linearizability)相关内容。&lt;/p&gt;
&lt;p&gt;本课相关的材料:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;课堂 Paper:  https://pdos.csail.mit.edu/6.824/papers/raft-extended.pdf&lt;/li&gt;
&lt;li&gt;课堂录像: https://youtu.be/h3JiQ_lnkE8&lt;/li&gt;
&lt;li&gt;课堂 Note: https://pdos.csail.mit.edu/6.824/notes/l-raft2.txt&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PS. Lecture 6 是 TA 代讲的 lab1 MapReduce 的 Q&amp;amp;A，没太多感兴趣的内容，所以就直接跳过了。&lt;/p&gt;
&lt;h2&gt;2 要点&lt;/h2&gt;
&lt;h3&gt;2.1 Raft 日志&lt;/h3&gt;
&lt;p&gt;客户端对一个有 leader 节点，正常运行的 Raft 集群的访问：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;客户端只能与 leader 节点交互；&lt;/li&gt;
&lt;li&gt;客户端看不到 follower 节点的状态或者日志；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但是，当 Raft 集群中的 leader 进行切换时，很多异常和错误都有可能发生，从应用的角度来说，我们希望能保证：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当任意服务节点执行一条日志条目中的命令，其他的服务节点不能从同一条日志目录中执行不同的命令&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这主要是为了保证状态机的安全性，避免在 leader 切换后对客户端展示不一致的状态，违背了集群对外展示为一个单独整体系统的初衷。&lt;/p&gt;
&lt;p&gt;不过服务节点之间的日志的确是有可能产生冲突：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;leader 节点在接收到客户端请求，保存到自身的日志队列，发送 AppendEntries RPC 请求给其他 follower 节点之前宕机；&lt;/li&gt;
&lt;li&gt;其他节点选举后成为新的 leader，再次接收其他客户端的请求，之前的 leader 恢复启动后可能就出现节点间日志队列不一致的情况；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在服务节点之间的日志数据产生冲突时，Raft 强制 follower 节点同步 leader 节点日志来维持一致性，也就是以有效的 leader 节点的日志队列为准。这就涉及到 leader 和 follower 节点之间日志同步的处理，这块内容是上节课涉及的 Raft 论文部分有详细的步骤。简单来说，整个同步方案还是通过 AppendEntries 来实现的，在 follower 发现自身的日志和 leader 不一致时，将会返回错误响应给 leader ，然后 leader 回退同步给该 follower 的日志，直到一致，之后就按 leader 同步过来的进行覆盖。&lt;/p&gt;
&lt;p&gt;选举限制：Raft 集群中的节点处理 RequestVote  请求时只给至少和节点日志一样新的 candidate 投票，这个新的定义是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;candidate 最后一条日志有更高的 term 值；&lt;/li&gt;
&lt;li&gt;candidate 最后一条日志有一样的 term 值，但是有一样或者更长的日志索引；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个选举限制可以保证新的 leader 能包含所有可能已经提交的日志，这块内容也是上一节课论文部分包含的。&lt;/p&gt;
&lt;h3&gt;2.2 持久化&lt;/h3&gt;
&lt;p&gt;当一个服务节点宕机时，我们希望 Raft 集群可以正常运行，但是宕机的节点也需要尽快恢复或者被替换，以避免整个集群的可用节点数量低于半数，有两个策略：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用一个新的服务节点替换：需要同步整个当前的日志和恢复状态机状态；&lt;/li&gt;
&lt;li&gt;重启宕机的服务节点，重新加入到集群中，并追上落后的日志和恢复状态一致；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;本节主要是关注第二个方案，以及需要实现的服务节点数据持久化处理。&lt;/p&gt;
&lt;p&gt;持久化和恢复时需要的数据相关，以下是相关的数据：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;日志队列数据：维持日志队列，保证已提交的日志不会丢失；&lt;/li&gt;
&lt;li&gt;当前的 term 值：保证 term 值只会增加，不能恢复为 0 ，影响后续的选举；&lt;/li&gt;
&lt;li&gt;投票给的节点 voteFor：防止一个节点投票给一个 candidate 节点后，重启又参与这轮选举；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;持久化通常是会对性能产生影响的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;普通磁盘写要 10ms，而 SSD 写只需要 0.1ms ；&lt;/li&gt;
&lt;li&gt;实现上可能需要每个日志都进行持久化操作，这样限制整个集群的处理能力为 100 ~ 10000 操作/s ；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;进行了关键数据持久化处理后，服务节点又要怎么启动恢复状态和数据呢？通常是下面两种方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;初始为空状态，将整个持久化的日志队列中已经提交的命令重新执行，这样可以恢复到宕机前状态，但是耗时会比较长，并且对于运行很久产生了很多日志的 Raft 节点这个方案也不现实；&lt;/li&gt;
&lt;li&gt;使用快照，只重新执行后面的一部分日志，这个方案就是下一部分的主题；&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.3 日志压缩及快照&lt;/h3&gt;
&lt;p&gt;当一个 Raft 集群运行足够长时间后，通常会面临一个问题：日志会越来越大，甚至有可能比整个状态机要大很多，这样无论是重启进行日志的重新执行还是发送给另外一个新启动的服务节点，都需要非常长的时间。&lt;/p&gt;
&lt;p&gt;考虑实际应用时，客户端实际上并不关心和需要整个运行中产生的日志，只是需要状态机中的状态，而在一般的应用场景中，状态机往往是比日志要小得多的(指的是有限数量的 key 不断被更新、删除的场景)，所以一个考虑方案是对状态数据进行持久化保存。&lt;/p&gt;
&lt;p&gt;对状态机数据的保存成为一个快照，而快照成功保存后，快照的最后一条相关日志及之前的都是可以被安全地删除的，这也就实现了对日志队列压缩的效果。&lt;/p&gt;
&lt;p&gt;服务节点不能删除的日志条目：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;未执行的日志条目：还没有被状态机应用；&lt;/li&gt;
&lt;li&gt;未提交的日志：有可能在将来被 leader 确认提交，保存在多数服务节点上；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;解决方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;服务定时创建一个当前状态的快照：以一个特别的日志执行条目的方式；&lt;/li&gt;
&lt;li&gt;服务将快照写到一个持久化存储中 (比如磁盘)；&lt;/li&gt;
&lt;li&gt;快照记录了包含的最后一条日志目录的索引，通知到 Raft ；&lt;/li&gt;
&lt;li&gt;Raft 撤销日志队列该索引之前的所有日志；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;宕机后重启：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;服务从磁盘读取快照；&lt;/li&gt;
&lt;li&gt;Raft 模块从磁盘读取日志；&lt;/li&gt;
&lt;li&gt;服务通知 Raft 模块设置 lastApplied 为快照最后包含的日志条目的索引，避免重新执行已快照保存的命令；&lt;/li&gt;
&lt;li&gt;接下来就是 Raft 正常的应用日志流程；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;还有个问题需要考虑：当 follower 宕机，但是 leader 节点执行了快照，导致部分还没同步到 follower 节点的日志被取消时要怎么处理？Raft 新增了一个 InstallSnapshot RPC 请求来处理这种情况，同时也应用于新 follower 节点加入集群同步状态的处理。&lt;/p&gt;
&lt;p&gt;实现时有几个点需要注意：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Raft 的快照方案适合于状态空间相对小的情况，对于大数据库，比如状态空间就包含几个 GB 数据，实行起来就不是那么合适；&lt;/li&gt;
&lt;li&gt;这种情况，状态数据可能需要一直保存在磁盘上，使用 B-Tree 或者 LSM 等数据结构；&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.4 线性一致性(linearizability)&lt;/h3&gt;
&lt;p&gt;对于一个支持 Put/Get 操作的 kv 存储服务来说，对于客户端需要能够对并发操作下操作的执行顺序有一定的预期。通常这称为一致性模型，有助于我们明白要怎么正确地处理复杂的情况，比如并发、复制、异常、RPC 请求的重传、leader 切换等等。&lt;/p&gt;
&lt;p&gt;线性一致性是对于期待能够表现为一个单服务(强一致性)的分布式最常见并且最直接的正式定义：如果我们可以为所有的操作找到一个完全的执行顺序，并且这个顺序符合时间顺序，每个读操作可以看到顺序上紧接着的前一个写操作的值，那么这个执行历史就是线性的。简单来说，在这个系统中，如果一个数据对象被更新了，那么时间上后续的读操作应该可以读到这个新的值。&lt;/p&gt;
&lt;h3&gt;2.5 重复 RPC 检测&lt;/h3&gt;
&lt;p&gt;当客户端和 Raft  leader 之间出现网络异常导致请求超时的时候，通常是要求客户端不断地进行重试请求的。这样的情况下，服务端需要考虑对重复 RPC 的检测相关的问题，以便实现操作的幂等性。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Raft 模块上层的 k/v 服务负责重复客户端请求检测&lt;/li&gt;
&lt;li&gt;客户端每次请求选择一个 id ，包含在请求中&lt;/li&gt;
&lt;li&gt;k/v 服务保持一个根据 id 索引的表，每个 rpc 请求都创建一个条目，并且保存执行后的结果&lt;/li&gt;
&lt;li&gt;如果另外一个 rpc 请求也包含了同样的 id ，从表中检测确定为重复的请求，则直接把记录的值返回给客户端；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;上面这个设计有几个问题需要考虑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;什么时候这个记录 rpc id 和值的表可以安全删除？不删除的话会占用资源&lt;/li&gt;
&lt;li&gt;如果进行了 leader 切换，那么这个表要怎么同步给新的 leader ?&lt;/li&gt;
&lt;li&gt;如果服务节点宕机，那重新期待时怎么恢复这个表？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一些想法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个客户端一个表条目，不用每个 rpc 请求一个条目，这样可以减少整个表的大小&lt;/li&gt;
&lt;li&gt;每个客户端每次只能有一个 RPC 请求&lt;/li&gt;
&lt;li&gt;每个客户端递增地标识 RPC 请求&lt;/li&gt;
&lt;li&gt;当服务接收到客户端一个新的 RPC 请求，比如 #10，那么更前的请求可以放弃重复检测了，因为客户端不会重复发送更旧的 RPC 请求&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.6 只读操作&lt;/h3&gt;
&lt;p&gt;在一个 Raft 集群中，对于读请求也需要先进行同步日志和提交才能向客户端响应，不然会出现下面一种情况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;leader 接收到客户端的 Get(k) 请求；&lt;/li&gt;
&lt;li&gt;此时，Raft 集群的 leader 进行了切换，原 leader 状态已经切换为 follower；&lt;/li&gt;
&lt;li&gt;原 leader 并没意识到新的选举，从自身的 kv 表获取 k 对应的 value 返回给客户端；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个情况可能会导致客户端读取到旧的数据，也就违背了 Raft 线性一致的保证。但是通常情况下，大部分的应用都是读多于写的，每次读都需要进行一次日志同步实现上会相对低效，性能可能不太理想。&lt;/p&gt;
&lt;p&gt;有一种解决方案是使用租约 (lease) ，需要对 Raft 算法进行调整:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;定义一个租约的时长，比如 5 秒；&lt;/li&gt;
&lt;li&gt;每次 leader 得到一次的 AppendEntries RPC 请求的多数成功响应，就表明当前这个 leader 有权力在租约时间内响应只读的请求，而不需要提交任何的日志；&lt;/li&gt;
&lt;li&gt;如果在租约有效期内，发生了新的选举，新的 leader 在原租约过期前都不能处理 Put 请求；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本课程的实验中并不要求实现租约机制。&lt;/p&gt;
&lt;h2&gt;3 Paper&lt;/h2&gt;
&lt;p&gt;lecture 5 的内容是到 Raft 论文的第五节，本节课对论文剩余的内容进行阅读讨论。其中第六节关于 Raft 集群成员变化相关的内容本来在课程安排上是略过的，但是在课程中不少学生都提及了相关的问题。在这里，我也决定不跳过这一部分内容，毕竟也属于 Raft 实际实现时需要考虑的非常重要的一个主题。&lt;/p&gt;
&lt;p&gt;剩余部分内容包含以下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;集群成员变化&lt;/li&gt;
&lt;li&gt;日志压缩&lt;/li&gt;
&lt;li&gt;客户端交互&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其他部分则是实现和性能及教学效果的评估，和最后的总结内容，不包含在这里对论文的阅读思考内容中。&lt;/p&gt;
&lt;h3&gt;3.1 集群成员变化&lt;/h3&gt;
&lt;p&gt;对于一个真实业务上运行的分布式系统来说，集群的成员并不是一成不变的，服务器的替换和更新以及因为异常或者性能缺口导致的增补，都是日常系统运维中不可避免的事情。这也就意味着，Raft 集群的运行中，可能会发生成员的数量变化，也就是集群的配置发生了变化。&lt;/p&gt;
&lt;p&gt;在业务持续运行的过程中，停机更新不是一个好的选择，因为停机意味着业务的停止，同时复杂的运维操作也可能会产生灾难性的意外后果。基于减少配置变更对正常业务影响的考虑，Raft 提出了一种把配置更新和融合进一致性算法里的自动更新配置的方案。&lt;/p&gt;
&lt;p&gt;&lt;img alt="raft-cluster-changes" src="../images/raft-cluster-changes.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;成员配置发生变化时，在集群节点新旧切换的过程中，一定要尽量避免出现 2 个 leader 的情况，这样会直接影响到数据的一致性，违背了 Raft 算法保证的各种安全性原则。而直接从旧成员切换到新成员的方案，因为难以实现原子性地同时切换所有节点，所以集群还是可能会出现脑裂的情况。&lt;/p&gt;
&lt;p&gt;基于保证安全性的考虑，通常是需要采用两步 (two-phases) 的方案来实现，不同的系统有多种实现方案，在 Raft 这里，线上将集群切换为一个被称为联合一致的过渡的配置状态，一旦联合一致相关日志被提交，整个系统就切换为新的配置。&lt;/p&gt;
&lt;p&gt;联合一致的情况同时包含了新旧的配置：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;日志条目会被复制到两个配置的所有服务节点上；&lt;/li&gt;
&lt;li&gt;两个配置中的服务节点都有可能被选为 leader；&lt;/li&gt;
&lt;li&gt;选举和日志条目的提交需要同时分别获得两个配置中的多数节点一致同意；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;联合一致支持新旧配置中的所有节点在不同的时间切换配置并且不会影响到算法的安全性。并且，在配置切换过程中，集群还能够继续对外部提供服务，响应客户端请求。&lt;/p&gt;
&lt;p&gt;&lt;img alt="raft-joint-consensus" src="../images/raft-joint-consensus.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;集群的配置是通过特别的日志条目来保存配置及通信的，如上图所示，配置切换的处理流程如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;当 C‘old 配置下的 leader 接收到更新配置的请求时，将会保存这个请求及配置为联合一致处理的一条日志，也就是图中的 C'old,new ，然后将这条日志复制到新旧服务节点上；&lt;/li&gt;
&lt;li&gt;一个服务节点一旦处理到这条日志，将会将新配置中的服务节点信息添加到其日志中，并且在将来的所有决策中都会使用这个新的配置，注意并不要求这条日志已经复制到多数节点；&lt;/li&gt;
&lt;li&gt;这过程中如果 leader 节点宕机，新的 leader 将会基于 C'old,new 的配置选出，一旦 C'old,new 被提交，则独立的 C'old 或者 C'new 都不可能缺少彼此的情况下作出决策，基于 leader 完整性的原则，只有包含了 C'old,new 的节点才能赢得选举;&lt;/li&gt;
&lt;li&gt;在 C'old,new 被提交后，现在 leader 可以基于联合一致的状态创建一条描述了新集群配置的日志并且复制到多数节点上，也就是上图中 C'new 出现的位置，同样，每个服务节点接收到新的配置后会立即生效，也就是会清除旧的配置；&lt;/li&gt;
&lt;li&gt;当 C'new 被复制到多数节点后，旧的配置项对于集群来说就再也不会有任何的影响了，其中不在新配置项的节点都可以被安全地关闭，这时候不在新配置节点中的 leader 将会退出。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;从上面的处理流程来看，在新旧配置切换过程中，并不会出现新、旧配置的节点同时成为 leader 分别进行决策的情况，也就保证了 Raft 算法在配置切换中的安全性。&lt;/p&gt;
&lt;p&gt;对于配置更新的处理，下面还有 3 个值得注意的问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;新加入集群的节点并没有任何日志数据，如果新节点一加入集群就开始上面这个流程，那么配置切换可能需要等待该新节点的日志同步追上最新进度才能开始，为了减少这其中的时间差，Raft 新增了一个新的额外阶段，新的节点加入集群后会被视为非投票节点，leader 会复制同步日志到这些节点，但是不会视其为决策中的多数，一旦新节点的日志追上集群其他服务，就可以按照上面的流程继续处理；&lt;/li&gt;
&lt;li&gt;在配置切换过程中，集群中的 leader 节点可能不包含在新的配置中，这个时候在 C'new 日志提交后，leader 将会退出转换为 follower 状态，而新配置中的节点将开始新的选举，从新配置中节点选出一个新的 leader，这是一种比较特别的情况，也就是旧配置中的 leader 进行着不包含自身的决策；&lt;/li&gt;
&lt;li&gt;被移除的服务节点如果并没有关闭，有可能在选举超时后在集群中发起新的投票，导致整个集群进行选举处理，并且每次选举超时都会触发，为了解决这个问题，Raft 的服务节点在处理 RequestVote 请求时，如果认为当前存在有效的 leader 节点，则忽略该请求，实际上指的就是在自身的选举超时前成功收到了 leader 的心跳；&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;3.2 日志压缩&lt;/h3&gt;
&lt;p&gt;在一个 Raft 集群的正常运行中，其日志队列和数据随着客户端请求不断增加，从目前看到的实现来看，如果没有任何处理，那么理论上日志数据会无限制地增长，占据所有可用的磁盘空间。更糟糕的是，存在大量日志数据时，一个节点重启时，需要重新执行大量的历史日志保存的命令才能恢复状态到最近，这显然不是实际业务系统可以接受的。&lt;/p&gt;
&lt;p&gt;对于 Raft 运行中产生的历史日志，我们有没有必要一直原样保存呢？我们知道日志里面最重要的数据是客户端的命令，而命令是用来改变状态机的状态数据的，状态机的数据才是外部客户端请求需要的。从这个方面来看，Raft 集群中重要的不是所有的历史执行的命令，而是这个状态机。&lt;/p&gt;
&lt;p&gt;所以，一种常见的解决方案是使用快照，简单来说，就是将当前状态机所有数据写入一个快照文件，保存在一个可靠存储上。然后整个日志队列的数据，一直到快照数据对应提交的最后一条日志之前的所有日志条目都可以安全地删除。因为通常的业务系统中，状态机的总数据量是有限的，所以这样可以将日志数据量压缩为状态机状态数据大小，从而减少了日志的占用的空间大小。对于状态机本身就特别大的系统，快照方案可能并不太适用，这类型系统可以考虑直接把状态数据保存在可靠存储上。&lt;/p&gt;
&lt;p&gt;&lt;img alt="raft-snapshot" src="../images/raft-snapshot.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;如上图所示，Raft 中的快照方案主要是有以下几点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Raft 集群中每个服务节点是独立地执行各自的状态机快照处理，数据主要是当前节点的状态机数据，并且只覆盖已提交的日志条目的命令执行结果；&lt;/li&gt;
&lt;li&gt;快照包含了部分的元数据：最后一条被包含的日志的索引及任期值，主要是用于 AppendEntries 的请求，另外还有集群的当前配置信息；&lt;/li&gt;
&lt;li&gt;快照文件写成功后，这个节点就可以删除掉旧的日志了，另外之前创建的快照数据也可以进行删除处理；&lt;/li&gt;
&lt;li&gt;如果某个 follower 节点大幅度落后 leader 节点或者新的节点加入集群，这个情况下，为了加速节点的状态跟上 leader，可以考虑从 leader 节点发送快照给该节点，Raft 为这个操作新增了一个 InstallSnapshot 的 RPC 请求；&lt;/li&gt;
&lt;li&gt;follower 接收到来自 leader 的 InstallSnapshot 请求时，如果发现发送过来的快照包含的最后日志要比自身日志队列的数据要新，则可以直接删除掉当前的日志数据，加载快照数据恢复状态机，如果快照数据落后于节点日志，则只删除包含的日志条目，保留其余的日志数据；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;整个日志方案是和 Raft 之前描述的强 leader 原则是分离的， follower 不需要了解 leader 信息就可以执行快照处理。主要是因为在执行快照时，每个服务节点的状态机的一致性已经由 Raft 算法的一致性保证了，所以不同节点的快照文件不会产生状态数据的冲突，只会有多少的差异。&lt;/p&gt;
&lt;p&gt;在实现快照时，有两个性能相关的问题需要注意：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;快照执行的时机：太频繁，可能有太多的磁盘 IO ，太少，则冒着存储空间被耗尽的风险，并且启动时可能要更长的时间，因为有更多的日志条目未包含在快照文件中；一个简单的方案是当日志大小达到一个预定义的大小后就执行快照处理，这样额外的磁盘带宽及 IO 开销会比较小；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;写快照文件对正常业务的影响：写快照文件可能需要比较多的时间，另外一般来说也不希望在写快照时状态空间发生变化，所以可能会考虑在写快照时停止外部请求，这样的方案实际上就对正常业务造成了影响，所以一般是采用写时拷贝 (copy-on-write) 的方案来实现快照创建处理，以 linux 系统为例，简单来说就是利用 fork 系统调用创建一个子进程共享当前状态机数据，在分离的子进程将状态机数据写入快照文件，同时主进程在执行状态机的写业务时拷贝和修改相关的内存，不影响子进程的状态机副本数据；&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;3.3 客户端交互&lt;/h3&gt;
&lt;p&gt;除了 Raft 集群节点之间的交互之外，客户端与 Raft 集群的交互在实现上也有很多需要注意的点，本小节就是对这方面的内容进行了补充。&lt;/p&gt;
&lt;p&gt;根据之前的描述，客户端应该只与 leader 进行交互，所有的数据应该通过 leader 节点同步日志到其他 follower 节点。但是在一个客户端刚开始连接到 Raft 集群时，它并不了解哪个节点是 leader，此外还存在 leader 异常宕机、leader 切换等情况。所以在实现时，如果客户端第一次请求的节点不是 leader，则这个服务节点将会拒绝客户端请求并提供 leader 的信息，然后客户端重新往 leader 节点发起请求。如果信息不正确，或者提供的 leader 节点请求超时无响应，客户端可以随机选择另外一个节点再次进行请求。&lt;/p&gt;
&lt;p&gt;在异常情况下，如果客户端的请求超时没有收到响应，则应该重新再发起请求，这样 Raft 集群可能出现多次处理同一个请求的情况。比如 leader 在提交日志后响应给客户端前宕机了，客户端可能会往新的 leader 发起同样的一个请求，这样就会导致 Raft 集群两次处理同一个请求。解决方案是，客户端每次请求都指定一个唯一的序列号标识这个请求，然后状态机追踪每个客户端最近提交的请求的序号和对应的响应，这样在遇到重复的请求时，直接把已记录的响应返回，不再执行请求的命令。&lt;/p&gt;
&lt;p&gt;对于只读请求，因为不会影响到状态机数据，理论上应该是可以不记录相关的操作日志的。但是实现时如果没有额外的操作，很有可能会导致返回过期数据给客户端，因为接收读请求的 leader 服务节点在处理时可能已经不是 leader 了，但是因为没有日志交互，它并没有意识到存在新的 leader 。&lt;/p&gt;
&lt;p&gt;为了解决只读请求的问题，Raft 需要两个额外的预防措施来保证：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;leader 节点在任期开始时提交一个空 (no-op) 的日志条目到日志队列：这样可以让  leader 与各个 follower 进行交互，并且了解最近已经提交的日志信息；&lt;/li&gt;
&lt;li&gt;leader 节点处理读请求时，在返回给客户端前都需要和集群中多数的节点进行心跳请求，这样  leader 可以确保其自身还是一个有效的 leader ，避免返回过期的数据；&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;4 总结&lt;/h2&gt;
&lt;p&gt;从这两节课的阅读来看，Raft 论文内容细节丰富，理论性的内容较少，整个算法易于理解，包含了很多实际实现上的考虑，是分布式领域一致性算法相关不可多得的一篇好论文。将一致性算法分为选举和日志复制几个子问题显然更为清晰，而强 leader 的设计对比多 leader 决策的复杂处理逻辑要更易于实现，单一节点以天然有序的日志的方式决定操作的执行顺序也很好地支持了线性一致的实现。&lt;/p&gt;
&lt;p&gt;虽然 Raft 论文易于理解，但是一个可以实际应用在生产环境的分布式系统在实现上还是会存在更多的细节和需要考虑的问题，部分性能上的考虑在论文也只是一笔带过，对于系统实现来说却是必须考虑的内容。比如只读操作的性能优化，论文中提及了基于租约 lease 的方式来实现，但是没有提供具体的方案，这只能由系统的实际开发者来进行设计实现了。&lt;/p&gt;
&lt;p&gt;当前业界也有不少开源系统基于 Raft 算法实现了容错、可靠的分布式系统，并且实际应用到了生产环境，经受了实际业务的考验，从性能和可靠性都得到了充份验证，下面是几个比较出名的项目：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/etcd-io/etcd"&gt;etcd&lt;/a&gt; : 一个分布式的 k/v 存储系统，对标参考 Paxos 实现的 Zookeeper 项目，常用于可靠存储系统关键数据，比如配置等，或者用于分布式系统中作为服务发现等功能使用，也被 kubernetes 等著名开源系统使用；&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tikv/tikv"&gt;tikv&lt;/a&gt; : 也是一个分布式的 k/v 存储系统，主要是用于为分布式数据库 TiDB 提供可靠的分布式数据存储支持，除了简单的 k/v 操作之外，它还实现了保证 ACID 的事务相关操作，此外，其数据是持久化存储在磁盘上的；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些开源的基于 Raft 算法实现数据一致的分布式系统在实现上对 Raft 算法有很多的优化和改进，也更多地考虑了实际业务的需求，非常值得参考阅读。&lt;/p&gt;</content><category term="mit6.824"></category><category term="distributed-system"></category><category term="raft"></category><category term="paper-reading"></category></entry><entry><title>6.824 Lecture 5 Fault Tolerance Raft (1) Notes &amp; Paper Reading</title><link href="https://blog.tonychow.me/mit6.824-letcture5-notes-raft1.html" rel="alternate"></link><published>2021-11-01T00:00:00+08:00</published><updated>2021-11-01T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2021-11-01:/mit6.824-letcture5-notes-raft1.html</id><summary type="html"></summary><content type="html">&lt;h2&gt;1 概要&lt;/h2&gt;
&lt;p&gt;本课主题是分布式系统中容错相关内容，主要是对一致性算法 Raft 进行分析讨论。Raft 是一个用于分布式系统中多副本之间维持数据一致性的算法，它能实现接近 Paxos 算法的性能和功能，但是更容易理解和实现。Raft 的一个创新点是在于将一致性的实现分离为更容易理解的选举及日志复制等几部分。&lt;/p&gt;
&lt;p&gt;Raft 算法目前在业界已经有不少的实现及应用，比如 etcd 就是基于 Raft 算法构建了一个容错的分布式 kv 存储系统，并且广泛被应用在线上生产环境中，经受了真实应用场景的考验。对于 Raft 算法的讨论分为两节课，本节主要涉及到 Raft 基础的结构及概念，以及选举、日志复制相关内容。&lt;/p&gt;
&lt;p&gt;本课相关的材料:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;课堂 Paper: https://pdos.csail.mit.edu/6.824/papers/raft-extended.pdf&lt;/li&gt;
&lt;li&gt;课堂录像: https://youtu.be/R2-9bsKmEbo&lt;/li&gt;
&lt;li&gt;课堂 Note: https://pdos.csail.mit.edu/6.824/notes/l-raft.txt&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2 要点&lt;/h2&gt;
&lt;h3&gt;2.1 一种模式&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;MapReduce 依赖单个 Master 来组织计算&lt;/li&gt;
&lt;li&gt;GFS 支持数据副本复制，但是依赖单个 Master 来选择数据副本的 Primary 节点&lt;/li&gt;
&lt;li&gt;VMWare FT 支持复制，但是需要依赖 test-and-set 来选择 Primary 节点&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;之前课程涉及到的分布式系统中，架构上都是依赖单个服务来做关键的决策，这有一点好处是可以避免多服务可以参与决策下因为网络分区等异常导致的脑裂现象。但是，在一个大型的分布式系统中，单点决策总是会影响到整个系统的健壮性和可用性。&lt;/p&gt;
&lt;h3&gt;2.2 脑裂&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;在一个存在多个复制节点的分布式系统中，当发生网络分区时，各个节点可能各自执行不同的用户操作，从而导致多个节点之间的状态无法维持一致，这样就出现了脑裂；&lt;/li&gt;
&lt;li&gt;客户端对于服务宕机和网络故障导致的服务不响应这两种异常状态是无法识别的&lt;/li&gt;
&lt;li&gt;这两种异常对于客户端来说，表现都是一致的：服务无网络响应&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.3 一种解决方案: 多数票决&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;奇数数量的服务节点：比如 3&lt;/li&gt;
&lt;li&gt;对于任意关键操作 (用户修改命令、配置变更)都需要多数服务达到一致决策&lt;/li&gt;
&lt;li&gt;发生网络分区时，最多只能有一个分区有多数服务节点，所以另外一个分区不会进行决策导致数据不一致，并且奇数量服务打破了偶数数量服务的对称可能性；&lt;/li&gt;
&lt;li&gt;多数服务是指整体服务节点中的多数，而不是当前存活的服务节点中多数&lt;/li&gt;
&lt;li&gt;如果需要容忍 f 个服务宕机：服务节点总数为 2f+1；&lt;/li&gt;
&lt;li&gt;这种方案通常也被称为 quorum&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;早期的一致性算法(1990年左右)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Paxos&lt;/li&gt;
&lt;li&gt;View-Stamped&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;近期算法(2014)：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Raft&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.4 Raft 总览&lt;/h3&gt;
&lt;p&gt;在论文中， Raft 主要是从状态机副本复制的角度来描述的，架构上 Raft 层在状态机的每个副本中以库的形式被包含使用，多个状态机副本之间通过 Raft 算法来实现操作命令的同步和一致性维护。&lt;/p&gt;
&lt;p&gt;架构：客户端 -&amp;gt; 3 副本 -&amp;gt; 副本  k/v 层+状态机 -&amp;gt; raft 层 + 日志&lt;/p&gt;
&lt;p&gt;客户端命令流程&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;客户端发生 put/get 命令到 leader 服务节点的 k/v 层;&lt;/li&gt;
&lt;li&gt;leader 添加命令到日志队列；&lt;/li&gt;
&lt;li&gt;leader 发送 AppendEntries 远程调用到 followers 服务节点以同步命令日志；&lt;/li&gt;
&lt;li&gt;follower 添加命令到日志队列；&lt;/li&gt;
&lt;li&gt;leader 等待多数服务响应，包含其自身；&lt;/li&gt;
&lt;li&gt;如果多数服务都成功把命令添加到其日志队列中，则该命令被视为已经提交 (committed)&lt;/li&gt;
&lt;li&gt;leader 执行命令，并响应给客户端；&lt;/li&gt;
&lt;li&gt;leader 在下一次发送给 followers 服务节点的 AppendEntry 请求中加上命令提交的信息；&lt;/li&gt;
&lt;li&gt;follower 也在 k/v 层执行命令，更新状态；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;为什么使用日志?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;日志是追加操作，可以维持命令执行的顺序，每个服务节点的 k/v 层按顺序执行命令之后，可以保证多个状态机副本之间的状态一致；&lt;/li&gt;
&lt;li&gt;多个副本可以保持一致的命令顺序；&lt;/li&gt;
&lt;li&gt;Leader 可以保证所有的 follower 都有同样的日志&lt;/li&gt;
&lt;li&gt;日志队列可以用于提交执行前暂存&lt;/li&gt;
&lt;li&gt;日志队列存储了命令可以方便 leader 重新发生旧的命令同步给 follower&lt;/li&gt;
&lt;li&gt;日志队列持久化存储命令可以实现重启后回放执行命令，恢复状态机状态&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Raft 设计的两个重点&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;选主: 选择一个服务节点作为 leader&lt;/li&gt;
&lt;li&gt;保证各个服务节点之间的日志在异常失败的情况下还是一致的&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;2.5 Raft 选主&lt;/h3&gt;
&lt;p&gt;为什么需要一个 leader ?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;需要所有的副本都按照一致的顺序执行同样的命令&lt;/li&gt;
&lt;li&gt;leader 接收客户端请求并确定执行的顺序，无 leader 多副本之间并发情况下保证一致执行顺序很困难&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Raft 中多个服务节点都有可能担任 leader 角色&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个服务节点担任 leader 的时间内为一个 term&lt;/li&gt;
&lt;li&gt;一个 term 内只能有一个  leader&lt;/li&gt;
&lt;li&gt;一个 term 内可以没有 leader&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;开始选主的时机&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;follower 节点在选定的选举超时时间内没有接收到来自 leader 的定时心跳&lt;/li&gt;
&lt;li&gt;该节点当前的 term 值加一，然后开始尝试收集投票&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如何保证一个 term 内只有一个 leader?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个节点需要接收到来自集群中多数节点的投票同意&lt;/li&gt;
&lt;li&gt;每个节点只能投一票：处于候选者 (candidate) 角色的会投给自己，非候选者角色则投票给第一个请求的&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;服务节点识别新 leader&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;新 leader 节点接收到多数的投票&lt;/li&gt;
&lt;li&gt;其他服务节点接收到一个有更高 term 值的 AppendEntry 心跳请求&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一次选举可能会没有 leader 产生&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;少于多数(过半)的服务节点无法访问&lt;/li&gt;
&lt;li&gt;多个同时请求投票的候选者分离了选票，没有一个成功拿到多数节点的投票&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一次选举失败后&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在一次超时后还是没有接收到任何 leader 的心跳消息，term 值递增并开始新的选举&lt;/li&gt;
&lt;li&gt;更高的 term 值优先级别高于旧 term 的候选者，旧的候选者退出选举&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如何避免选票分离 (split vote)？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个服务节点选择一个范围内随机的选举超时时间(election timeout)&lt;/li&gt;
&lt;li&gt;在 leader 挂掉的时候，其他 follower 节点同时发起选举请求的可能性大大降低，避免了选票的分离&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;选举超时值选取？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;至少几个心跳间隔，避免网络丢弃了一个心跳导致重新选主&lt;/li&gt;
&lt;li&gt;随机的范围应该能支持一个 candidate 在另外一个开始前成功&lt;/li&gt;
&lt;li&gt;尽量快速响应异常，避免长时间的停顿&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3 Paper&lt;/h2&gt;
&lt;p&gt;本课阅读讨论的论文是 &lt;a href="https://ramcloud.atlassian.net/wiki/download/attachments/6586375/raft.pdf"&gt;In search of an Understandable Consensus Algorithm (Extended Version) &lt;/a&gt; (2014)，这篇论文提出了一种新的分布式一致性算法 Raft，用于分布式系统中管理复制日志，维持多副本节点间的数据一致，以建立一个容错的分布式系统。Raft 算法对标传统的一致性算法 Paxos，从易于理解的角度出发进行设计及构建，能达到和 Paxos 一致的性能和功能。&lt;/p&gt;
&lt;p&gt;本课对于论文的阅读只到第 5 节，后部分内容在下一节课进行阅读讨论。&lt;/p&gt;
&lt;h3&gt;3.1 简介&lt;/h3&gt;
&lt;p&gt;一致性算法通常是用于支持一组机器像一个整体一样工作，并且在部分成员失效时也能正常工作。正因为如此，在大规模可靠系统中，一致性算法有着重要的角色。在过去十年 (2014往前)中，Paxos 是最占主导地位并且最受广泛讨论的一致性算法，业界大多数的一致性实现都是基于其或者受其影响。同时，Paxos 也是一致性算法学习时最为重要的内容。&lt;/p&gt;
&lt;p&gt;然而不幸的是，Paxos 算法难以理解，并且其架构需要复杂的调整来支持实现。所以学生和系统的构建者都饱受 Paxos 算法的折磨。本论文作者也曾经受理解 Paxos 算法的痛苦，决定尝试设计一个更好的一致性算法来支持教学及实现，并且整个算法是基于 "可理解" 的基本诉求来进行设计的。&lt;/p&gt;
&lt;p&gt;Raft 算法就是作者这个工作的成果，其基于解耦、状态空间缩减等技术来提升算法的可理解性。算法设计完毕后，在两个大学 43 个学生中进行了和 Paxos 算法对比学习的实验，最终数据，33 个学生在同时学习 Raft 及 Paxos 算法后，对于 Raft 算法的问题的回答要优于 Paxos 算法问题。&lt;/p&gt;
&lt;p&gt;Raft 算法和当前的其他几种一致性算法(Oki, Viewstamped Replication)有不少的相似点，不过其中也有几个创新的特性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;强 Leader (Strong leader)：整个系统只有 leader 负责接收客户端的请求日志，日志条目只能从 leader 流向其他服务，这个设计简化了日志复制的管理并且使得 Raft 更容易理解；&lt;/li&gt;
&lt;li&gt;选主(Leader election)：使用随机时间超时来启动选主流程，轻微的机制调整就简单地解决了选主冲突的问题；&lt;/li&gt;
&lt;li&gt;成员变化：Raft 算法使用了一个联合一致的方法来实现集群成员变化的处理机制，在不同配置转换过程中两个配置的多数成员会重叠，使得在配置变更时整个集群可以继续正常工作；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;论文作者认为 Raft 算法在教学及实际实现中要优于 Paxos 及其他已有的一致性算法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;更简单并且更容易理解&lt;/li&gt;
&lt;li&gt;算法细节描述充分完全，可以支持实际系统实现的需要&lt;/li&gt;
&lt;li&gt;有多个开源的实现并且被多个公司使用&lt;/li&gt;
&lt;li&gt;算法的安全性有形式化的指定及证明&lt;/li&gt;
&lt;li&gt;算法的性能和其他算法基本差不多&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.2 复制状态机&lt;/h3&gt;
&lt;p&gt;一致性算法通常是在复制状态机中出现，在相关的方案中，集群中服务的状态机可以产生相同的状态副本，并且在集群中某些服务宕机的时候整个系统还可以正常运行。复制状态机一般用于分布式系统中解决各种容错相关的问题。比如单集群 leader 的大规模系统 GFS、HDFS 和 RAMCloud 等，通常会使用一个分离的复制状态机来管理选主和保存关键的配置信息。&lt;/p&gt;
&lt;p&gt;&lt;img alt="replicated-state-machine" src="../images/replicated-state-machine.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;如上图所示，复制状态机通常是基于复制日志实现。每个服务都存储了一个包含一系列命令的日志，由该服务的状态机按顺序执行。不同服务的日志都包含了同样的命令，并且顺序相同，这样可以保证它们的状态和输出保持一致。&lt;/p&gt;
&lt;p&gt;而一致性算法的任务就是要保证复制日志的一致，包括内容及顺序。一个服务的一致性模块从客户端接收到命令后会将命令添加到它的日志中。然后和其他服务上的一致性模块进行通信交互，确保每个服务的日志上最终都在同样的顺序位置包含同样的请求，即使存在服务节点失效的情况。一旦命令被正确地复制到多个服务上，服务的状态机将可以按顺序执行日志的命令，变化状态，并且返回响应给客户端。最终结果，整个集群的服务对外展示为一个整体高可用的状态机。&lt;/p&gt;
&lt;p&gt;用于实际系统的一致性算法通常包含以下属性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在非拜占庭条件下确保安全性 (永远不返回一个错误结果)，包含网络延迟、分区及数据包丢失、重复和乱序重组等情况；&lt;/li&gt;
&lt;li&gt;只要集群中的多数服务是正常运行并且彼此及客户端之间都是可以正常通信的，整个集群就是可用的。比如，一个包含 5 个服务的集群可以容忍任意 2 个服务的失效。一个服务只要停止工作就被视为失效，后续可能再恢复过来并且重新加入集群；&lt;/li&gt;
&lt;li&gt;不依赖时序来保证日志的一致性，错误的时钟和极端的消息延迟在最坏的情况下才可能导致可用性问题；&lt;/li&gt;
&lt;li&gt;通常情况下，一个命令只要集群中多数服务节点都响应一轮的远程调用后就可以立即执行完成，少数的执行得慢的服务不会影响整体系统的性能；&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.3 Paxos 的问题&lt;/h3&gt;
&lt;p&gt;在过去十年中，Paxos 算法几乎就是一致性算法的代名词，最多的被在课堂上教授，也最多业界的具体实现，很多系统实现都是参考和扩展了 Paxos 算法。在本文作者看来，虽然 Paxos 在一致性算法领域曾经是绝对统治地位，但是其也存在两点非常严重的缺陷。&lt;/p&gt;
&lt;p&gt;首先，Paxos 算法特别地难以理解，其内容非常晦涩，很少人能完全地理解它，而且还需要非常大的努力。结果是，很多人都尝试对于 Paxos 算法进行更简化的解释，而这些解释主要是在其单一决策 ()(single-decree) 的子域，也非常有挑战性。&lt;/p&gt;
&lt;p&gt;Paxos 算法定义了一个能对单个决策达到一致意见的协议，这个被称为单一决策 Paxos (single decree Paxos)，然后再将多个协议的实例组合为多决策 Paxos (multi-Paxos)。单一决策 Paxos 是 Paxos 算法的基础，作者认为这正是其晦涩的地方。单一决策 Paxos 非常紧凑和精妙，它被分为两个阶段并且缺乏简单直接的解释，也没办法独立地被理解。所以一般难以对单一决策协议可行性建立一种直觉，更别说组合单一决策构成决策 Paxos 更增加了整个算法的复杂性。&lt;/p&gt;
&lt;p&gt;Paxos 算法存在的第二个问题是并没有为建立一个实际的系统实现提供很好的基础。一个原因是， Lamport 的原始论文里面大部分内容是围绕单一决策 Paxos 的，对于 multi-Paxos 只是简单提供了可能的方案，并且缺少细节。所以业界对于 multi-Paxos 的实现也没有一个被广泛认同的算法方案，有很多类似的方案，但是各自的实现细节也存在不同的地方。&lt;/p&gt;
&lt;p&gt;并且，也是由于单一决策协议，Paxos 的架构也难以建立一个实际的系统。作者这里主要是认为 Paxos 的日志的处理和对称多主决策方案在实际实现时存在比较多的问题。从当前实际实现了类 Paxos 算法的系统来看，它们很少是完全按照 Paxos 算法来实现的，每个都是从 Paxos 出发，然后设计实现了差异非常大的架构。Chubby 开发者的评论非常典型 "Paxos 算法的描述和真实世界系统的需求之间存在非常大的差距...最终实现的系统将是基于一个未证明的协议"。&lt;/p&gt;
&lt;p&gt;作者对 Paxos 算法的吐槽正是其设计 Raft 算法的初衷，他认为 Paxos 算法既不适用于教学，也不能很好达到实际系统实现的需求。&lt;/p&gt;
&lt;h3&gt;3.4 基于可理解性的设计&lt;/h3&gt;
&lt;p&gt;作者设计 Raft 算法时是基于可理解性来进行的，下面是几个需要达成的目标：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;必须为系统构建提供一个完整并且具备实际意义的基础;&lt;/li&gt;
&lt;li&gt;在所有条件下都保持安全性，并且在常规的运维操作中保持可用性；&lt;/li&gt;
&lt;li&gt;对于场景的操作效率高；&lt;/li&gt;
&lt;li&gt;最重要的目的：可理解性，必须能够让大量的阅读者舒适地理解整个算法，可以对算法建立需要的直觉，易于在实际实现中进行扩展；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;基于以上的目标，特别是易于理解和实现的考虑，Raft 算法的设计应用了下面两个常见的技术手段：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;问题解耦：将大问题解耦为小问题，再逐个解决；&lt;/li&gt;
&lt;li&gt;简化问题空间：主要是通过减少需要考虑的状态来实现，使得整个系统没有复杂的状态和尽量消除未定义的状态；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从算法设计方面来说，一般是尽量考虑消除非确定性的操作步骤，但是在 Raft 算法中，作者在选主操作中应用了随机的方案，也达到了一个很好的效果。&lt;/p&gt;
&lt;h3&gt;3.5 Raft 一致性算法&lt;/h3&gt;
&lt;p&gt;Raft 算法主要是用于管理复制日志，如上面提到的日志一样。Raft 算法的一致性实现首先是选择一个唯一  leader 节点，然后让这个 leader 完全负责管理所有日志复制的逻辑。leader 接收来自客户端的请求日志，然后把日志复制到其他的服务节点，并且通知其他节点可以安全应用日志到状态机的时机。日志条目只会从  leader 流向其他服务节点，leader 异常失效时会进行新的 leader 选举操作。&lt;/p&gt;
&lt;p&gt;Raft 单一 leader  的设计在实现上简化了整个系统的实现，并且也便于理解。整个 Raft 算法解耦为下面 3 个部分内容：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;选主操作：当前 leader 失效时，集群必须选举出一个新的 leader ；&lt;/li&gt;
&lt;li&gt;日志复制：leader 必须接收来自客户端的请求日志然后复制到集群的其他服务节点，并强制要求其他服务节点接受 leader 同步过去的日志，也就是说， leader 的日志条目可能会导致部分服务节点的日志被覆盖；&lt;/li&gt;
&lt;li&gt;安全性：Raft 算法是为了保证多个服务节点的状态机状态一致的，需要保证如果一个服务节点应用了某条日志，那么在同样的顺序，其他服务节点都不能执行不一样的日志条目，Raft 算法设计了不少的限制机制来保证算法的安全性；&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;3.5.1 基础内容&lt;/h4&gt;
&lt;p&gt;一个 Raft 集群通常包含多个服务节点，5 是一个典型的数量，允许整个集群容忍 2 个节点异常。这些服务节点通常是以下 3 种状态：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;领导者 (leader)：接收所有客户端请求；&lt;/li&gt;
&lt;li&gt;跟随者 (follower)：只响应来自 leader 和 candidate 节点的请求，如果碰到客户端请求，则转发给 leader；&lt;/li&gt;
&lt;li&gt;候选者 (candidate)：用于选举一个新的 leader；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;正常运行中，一般只有一个服务节点是 leader，其他服务节点为 follower。&lt;/p&gt;
&lt;p&gt;&lt;img alt="raft-states" src="../images/raft-states.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;如上图为 Raft 中服务节点各个状态之间的转换图：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;初始启动时所有服务节点都为 follower 状态；&lt;/li&gt;
&lt;li&gt;follower 有一个选举超时时间，超时后开始选举，转换为 candidate 状态；&lt;/li&gt;
&lt;li&gt;candidate 接收到多数服务节点的选票后转换为 leader 状态；&lt;/li&gt;
&lt;li&gt;candidate 接收到新的 leader 的请求或者新的 term 值会转换为 follower 状态；&lt;/li&gt;
&lt;li&gt;同样，一个 leader 如果也接收到新的 leader 的请求或者新的 term 值，也转换为 follower 状态；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以上只是简单的状态转换说明，这里面还有比较复杂的处理逻辑和限制，将在后续详细说明。&lt;/p&gt;
&lt;p&gt;&lt;img alt="raft-terms" src="../images/raft-terms.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;Raft 在运行中，将时间分为不同的任期 (term) ，每个任期的长度非固定，实现上 term 用递增的整型数值表示。term 有下面这些特性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个 term 起始于一次选举，并且最多只能有一个 leader，在这个 term 结束前都是作为整个集群的 leader 响应外部请求；&lt;/li&gt;
&lt;li&gt;一个 term 可以没有 leader，新的选举将会启动一个新的 term；&lt;/li&gt;
&lt;li&gt;服务节点之间的请求都包含了发送请求节点的 term 值，当一个节点发现自己的 term 值小于请求服务节点的 term ，将执行相关的状态转换处理逻辑，并且更新自己的 term 值为更大的；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Raft 服务节点相互之间用 RPC 进行通信，基础的只有两个 RPC：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RequestVote: 由 candidate 节点在选举阶段开始时发起请求；&lt;/li&gt;
&lt;li&gt;AppendEntries: 由 leader 发送日志给 follower ，也作为 leader 与 follower 服务节点之间的心跳请求；&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;3.5.2 选主 (Leader Election)&lt;/h4&gt;
&lt;p&gt;Raft 使用心跳机制来维持 leader 和触发选举:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;服务节点启动时都为 follower 状态，并且只要有来自 leader 节点有效的请求就会一直维持着；&lt;/li&gt;
&lt;li&gt;leader 会定期发送心跳请求给 follower ，实际上就是不携带任何日志条目的 AppendEntries RPC 请求；&lt;/li&gt;
&lt;li&gt;当 follower 节点在一段时间 (election timeout) 后没有接收到 leader 的有效请求，就会假设当前集群无有效的 leader，并且转换状态，开始进行选举；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当一个 follower 开始选举时，将会：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;增加其当前的 term 数值；&lt;/li&gt;
&lt;li&gt;转换状态为 candidate；&lt;/li&gt;
&lt;li&gt;然后投票给自己；&lt;/li&gt;
&lt;li&gt;再并发地发送 RequestVote RPC 请求给集群中的其他服务节点；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个 candidate 节点会一直维持状态直到：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;这个节点赢得了选举，将会转换为 leader；&lt;/li&gt;
&lt;li&gt;另外一个服务节点成功赢得选举，此节点将会转换为 follower；&lt;/li&gt;
&lt;li&gt;这个 term 没有任何的服务节点成功被选为 leader,则启动下一次选举；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;candidate 需要接收到来自多数服务节点的选票才能成功赢得选举，每个节点只能投票给一个服务节点(包含其自身)，并且是按照投票给第一个请求的原则执行的。当一个 candidate 赢得选举后，将转换为 leader 状态，并且开始给其他服务节点发送 AppendEntries 心跳请求宣告新的 leader，并且避免其他服务节点发起选举。&lt;/p&gt;
&lt;p&gt;当一个 candidate 等候其他服务节点投票时，可能会接收到其他服务节点宣称其为新的 leader 的 AppendEntries RPC 请求。如果请求方的 term 值大于或者等于 candidate 当前的 term 值，则承认该服务节点为合法的 leader，并且转换为 follower 状态，否则会拒绝该请求并保持 candidate 状态。&lt;/p&gt;
&lt;p&gt;Raft 中服务节点需要等待一个选举超时时间没有收到 leader 的心跳后才会启动选举，如果两个节点同时超时发起选举并争夺其他服务节点的选票，是有可能出现没有任何 candidate 节点得到多数选票的情况。为了避免这种情况，Raft 对于这个超时时间的值选择使用了随机化的设计。每个节点在开始一轮选举时都会重置其选举超时时间 (election timeout)，具体值通常在 150~300ms 之间。这样即使此轮存在两个节点同时发起选举分离了选票，总是会有一个节点先启动新的一轮选举。&lt;/p&gt;
&lt;h4&gt;3.5.3 日志复制&lt;/h4&gt;
&lt;p&gt;当一个节点成功选举为 leader 后，它就可以开始对外部的客户端提供服务了。每个客户端的请求实际上是包含了一个由服务节点状态机执行的命令，而 leader 接收到客户端请求后，会将这个执行命令包装为日志条目，并且追加到其日志队列中。然后 leader 并发地把日志条目发生给所有的其他 follower 节点进行复制。当日志条目成功复制后，leader 将会应用这个日志条目的命令到其状态机，然后响应结果给客户端。follower 节点异常无响应时，leader 将会无限重试发送日志条目的 RPC 请求。&lt;/p&gt;
&lt;p&gt;&lt;img alt="raft-logs" src="../images/raft-logs.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;如上图所示，每个服务节点都维持着一个日志队列，里面的每个日志条目包含了以下内容：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;执行命令：由客户端提交，将会由状态机执行；&lt;/li&gt;
&lt;li&gt;term 值：该日志被 leader 接收添加到队列时的任期，用于检测服务节点之间日志的一致性；&lt;/li&gt;
&lt;li&gt;索引序号: 用于标记这个日志条目在日志队列的位置；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;只有 commited 状态的日志条目才能安全地由状态机执行，而一个日志条目是否为 commited 则是由 leader 来决定的。一旦 leader 成功将创建的日志条目复制到多数的服务节点中，这条日志就会被视为 commited，同时，leader 在该日志之前的其他日志也应该被视为 commited。此外，leader 还会保持及持续更新当前最新 commited 的日志索引，并且在发生给 follower 节点的 AppendEntries 请求中携带上这个索引值。而 follower 一旦了解到某个日志索引及之前的日志都已经处于 commited 状态，就可以安全地在本地的状态机按顺序执行相关日志的命令，更新状态与 leader 保持一致。&lt;/p&gt;
&lt;p&gt;Raft 中的日志机制保持以下两个特性：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果不同节点日志队列的两条日志有相同的索引序号和 term 值，那么它们保存了同样的操作命令；&lt;/li&gt;
&lt;li&gt;如果不同节点日志队列的两条日志有相同的索引序号和 term 值，那么这些节点上日志队列在这条日志之前的所有日志条目都是一样的；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一个 leader 在特定的 term 和特定的日志索引位置只能创建一条日志，并且日志条目的位置永远不会发生变化，所以第一个特性得以保证。而第二个特性则是通过一个简单的一致性检查来保证的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当 leader 发送一个 AppendEntries 请求用于复制新的日志条目到 follower 节点时，会附加上这条新日志之前的日志索引及 term 值；&lt;/li&gt;
&lt;li&gt;当 follower 处理请求时，发现 AppendEntries 携带的前一个日志索引和 term 值在本地的日志队列找不到，将会拒绝该请求；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个一致性检查可以保证服务节点的日志队列从初始状态到每次扩展都保持着上面两个特性，这样，当 leader 发送给 follower 的 AppendEntries 请求成功返回时，就可以确认 follower 的日志是维持和自己一致的。&lt;/p&gt;
&lt;p&gt;在 Raft 集群正常运行中，通常各个服务节点之间的日志是保持一致的，但是实际环境是复杂的，因为各种因素影响，节点之间的日志非常有可能出现不一致的情况。在这个时候，就需要尽快恢复各个节点之间日志的一致性。Raft 中，主要是由 leader 节点来负责处理不一致的情况，采用的方案是以 leader 的日志队列数据为准，强制各个 follower 节点的日志和 leader 节点同步保持一致。这个方案意味着，当 follower 节点和 leader 节点之间的日志存在差异和冲突时，follower 节点的日志将会被 leader 覆盖。实际上这个操作需要一个额外的限制保证，下面会有一节详细论述这个处理的安全性。&lt;/p&gt;
&lt;p&gt;leader 主要是通过下面的处理逻辑来实现 follower 节点和 leader 节点的日志保持一致：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首先 leader 上会为每个 follower 节点维持一个 &lt;code&gt;nextIndex&lt;/code&gt; 的值，用于标识 leader 要发给该 follower 节点的下一条日志的索引；&lt;/li&gt;
&lt;li&gt;leader 当选后将会为每个 follower 初始化 nextIndex 值为 leader 日志队列的下一条；&lt;/li&gt;
&lt;li&gt;如果 follower 节点的日志和 leader 不一致，来自 leader 的 AppendEntries 请求将会因为日志不一致而被拒绝；&lt;/li&gt;
&lt;li&gt;当 leader 的 AppendTries 请求被拒绝后，对应 follower 的 nextIndex 将会被递减，然后再重新发起日志请求，直到 nextIndex 标识的日志和 follower 的对应上；&lt;/li&gt;
&lt;li&gt;接下来 leader 的日志将会覆盖和移除 follower 节点上的日志，最终达到 follower 保持和 leader 节点一致日志的效果；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从上面的逻辑可以看到，Raft 中 leader 及 follower 之间的日志维持一致是在正常的节点交互请求中实现的。这也就意味着，当 follower 和 leader 之间产生日志不一致的情况时，并不需要进行额外的状态切换处理，只需要按照正常的处理逻辑进行处理，follower 的日志将自动会维持和 leader 的一致。&lt;/p&gt;
&lt;p&gt;对于 follower 保持和 leader 日志一致的处理，其中可以看到，当 follower 的日志队列和 leader 日志队列相差较多的时候，因为 leader 维持的 nextIndex 每次都只是递减一，所以可能会导致比较多的 AppendEntries 请求被拒绝，从而产生比较多的来回请求响应。一种可以考虑的优化方案是，由 follower 在拒绝一个 AppendEntries 请求时，附加上产生冲突日志条目的 term 值和该 term 下保持的第一个日志，这样 leader 可以跳过该 term 所有冲突的日志，而不再需要每个日志都产生一个新的请求。这个方案理论上是有优化的效果，但是实际应用上，不一定能产生很重大的优化，毕竟实际应用时，异常并不是很常见的情况，另外产生很大日志差异的情况概率也不是很大。&lt;/p&gt;
&lt;p&gt;从上面描述的日志复制整个机制来看，在一个 Raft 集群中，只要多数的节点都是正常可用的，那么整个集群都可以对外提供正常的请求处理。内部的服务节点可以根据日志复制机制来维护数据的一致，并且也不会产生数据异常、不一致、丢失的情况。&lt;/p&gt;
&lt;h4&gt;3.5.4 安全性&lt;/h4&gt;
&lt;p&gt;论文到目前为止描述了 Raft 中选主及日志复制相关的机制，但是并未能保证不同节点的状态机以一致的顺序执行同样的命令，也就是无法保证服务节点的状态机保持一致。所以，本节将会就选主的流程添加一个限制，以保证相关的安全性。&lt;/p&gt;
&lt;p&gt;这个限制是针对能被选为 leader 节点的状态的限制，需要保证任意 term 被选为 leader 的服务节点都包含了之前所有 term 已经 commited 的日志条目。在本节同时也对日志提交的相关规则进行更详细的描述。&lt;/p&gt;
&lt;h5&gt;3.5.4.1 选举限制&lt;/h5&gt;
&lt;p&gt;这个限制是在 candidate 发起的 RequestVote 请求中的，具体很简单：请求需要包含 candidate 的日志信息，接收请求的节点如果发现其自身的日志要比 candidate 的日志更新，则拒绝给这个 candidate 投票。&lt;/p&gt;
&lt;p&gt;日志更新的定义和比较如下，主要是根据最新的一条日志的 term 及索引值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果 term 值不一样，则 term 更大的节点日志队列更新；&lt;/li&gt;
&lt;li&gt;如果 term 值一样，则日志索引值更大的更新；&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;3.5.4.2 提交前一个任期的日志&lt;/h5&gt;
&lt;p&gt;上面有描述到，一个 leader 如果将日志复制到多数的节点上，则会认为该日志已经被提交。但是还是存在一种可能性是，leader 复制日志到多数节点上，然后在本地尝试提交日志前宕机，这时，对于下一个 leader ，判断这个日志是否已经提交会存在困难。&lt;/p&gt;
&lt;p&gt;为了消除这种异常情况，Raft 算法中不允许通过计算日志复制的数量是否达到多数来提交日志，只有 leader 当前的 term 才能以这种方式来提交日志。在这个情况下，还是有可能来自上个 term 的日志被默认已经提交，比如日志已经复制到所有节点上。&lt;/p&gt;
&lt;h5&gt;3.5.4.3 安全性论证&lt;/h5&gt;
&lt;p&gt;接下来是对 Raft 算法的 leader 完整性原则(Leader Completeness Property)的论证，具体是通过反证的方式来进行的。首先假设任期 T 的 leader 在其任期提交了一条日志，但是这条日志并没有被其后任期的 leader 存储，我们定义这个最小的不保存 T 任期已提交日志的任期为 U，且 U &amp;gt; T ，下面是论证的详细步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;因为 leader 不删除或者覆盖其自身的日志队列，所以这条日志一定不会存在于 U 任期的 leader 节点中；&lt;/li&gt;
&lt;li&gt;如果 T 任期的 leader 将日志复制到了集群中多数节点，U 任期的 leader 接收到了集群中多数节点的投票，则至少有一个服务节点即复制了 T 任期 leader 的日志，又给 U 任期的 leader 投了同意的票，下面的论述将基于这个特殊的节点继续；&lt;/li&gt;
&lt;li&gt;步骤 2 提到的这个特殊节点一定是先接受来自任期 T leader 的日志，再投票给任期 U leader，否则根据日志复制的机制，节点的 term 大于任期 T leader 时会拒绝 AppendEntries 请求；&lt;/li&gt;
&lt;li&gt;这个节点在投票给任期 U 的 leader 时应该还保存着这个日志条目，因为每个相关的 leader 都包含这条日志， leader 不删除日志条目，而 follower 只有和 leader 冲突时才会删除；&lt;/li&gt;
&lt;li&gt;这个节点投票给任期 U 的 leader 节点，所以任期 U 的 leader 的日志必须至少和这个节点一样新，这就和假设矛盾；&lt;/li&gt;
&lt;li&gt;首先，如果这个节点最后的日志和任期 U 的 leader 有一样的 term 值，则 leader 必须至少和这个节点的日志一样新，所以 leader 的日志队列包含了投票节点的所有日志条目，这也与假设矛盾，因为投票节点包含了任期 T 提交的日志条目，而 leader U 假设没有；&lt;/li&gt;
&lt;li&gt;否则，任期 U leader 的最后日志任期必须要大于这个投票节点的，此外，还要求大于 T，那么任期 U 之前创建了 U leader 最后的日志条目的 leader 也必须包含了任期 T 的这个已提交日志。所以，根据日志匹配原则，U leader  的日志队列也必须包含这条 T 已提交的日志，这也与假设矛盾；&lt;/li&gt;
&lt;li&gt;这就完成了整个反证，所以，大于 T 的所有任期的 leader 必然包含了所有任期 T 已经提交的日志条目；&lt;/li&gt;
&lt;li&gt;日志匹配原则同时也保证了未来任期的 leader 也包含了间接提交的日志条目；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;基于领导者完整性原则，我们也可以证明状态机安全性原则成立，也就是如果一个服务节点应用了某个索引位置的日志到其状态机，则其他服务节点不会在同样的索引位置应用不同的日志条目。&lt;/p&gt;
&lt;h4&gt;3.5.5 Follower 和 Candidate 宕机&lt;/h4&gt;
&lt;p&gt;follower 和 candidate 节点宕机的处理相对简单，并且他们的处理逻辑是一致的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当 follower/candidate 节点宕机时，发生给它的 RPC 请求将会失败，leader 将会无限的进行请求重试，当节点启动时，将会收到并处理请求；&lt;/li&gt;
&lt;li&gt;如果节点在完成 RPC 请求后，发生响应给 leader 之前宕机，则启动后还是会接收到重复的请求，因为对于请求的处理实现是要求幂等的，所以重复的请求不会导致任何的异常；&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;3.5.6 时间及可用性&lt;/h4&gt;
&lt;p&gt;Raft 算法设计上尽量避免安全性存在对时间的依赖，但是实际实现时，系统的可用性不可避免会受到时间的影响。Raft 算法中，时间最为关键的地方是选举的逻辑，Raft 如果要成功选举并维持一个稳定的 leader 运行，有以下时间相关的需求：&lt;/p&gt;
&lt;p&gt;&lt;em&gt;broadcastTime&lt;/em&gt; ≪ &lt;em&gt;electionTimeout&lt;/em&gt; ≪ &lt;em&gt;MTBF&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;broadcastTime: 广播时间，服务节点并发地发生 RPC 请求给集群中每个节点并且成功接受到响应的平均时间；&lt;/p&gt;
&lt;p&gt;electionTimeout: 上文提到的 follower 节点选举超时时间；&lt;/p&gt;
&lt;p&gt;MTBF： mean time between failure，也就是单个服务节点异常失效的平均时间；&lt;/p&gt;
&lt;p&gt;广播时间应当比选举超时时间小几个数量级，这样 leader 可以稳定发生心跳请求避免 follower 启动选举流程。选举超时时间也应该比 MTBF 时间小几个数量级，这样整个集群可以相对稳定运行。&lt;/p&gt;
&lt;p&gt;广播时间和 MTBF 是底层系统的特性，选举超时时间的选择则需要我们进行考虑。因为 Raft 的 RPC 请求通常意味着持久化存储接收到的数据，所以广播时间可能范围是 0.5ms ~ 20ms。这样，选举时间应该在 10ms 到 500ms 之间。通常 MTBF 时间在几个月甚至更长，所以通常比较容易满足算法的时间需求。&lt;/p&gt;
&lt;h2&gt;4 总结&lt;/h2&gt;
&lt;p&gt;课程对 Raft 算法论文的阅读讨论分为了两个部分，上面这部分内容其实也是 Raft 算法最为基本，最重要的内容，包含了基础的模型、选举、日志复制和安全性的描述及论证。从目前所阅读的内容来看，Raft 算法整体上还是很容易理解的，论文包含了不少实现上的细节，易于参考实现，并且也有足够的安全性保证。&lt;/p&gt;
&lt;p&gt;在分布式系统中，各种类型的错误和异常是难以避免的，而 Raft 算法在容错和异常恢复方面也有相关的应对方案。通过相关的机制实现下面几个原则，Raft 算法可以在真实系统应用中容错，稳定地运行：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;选举安全性: 在一个 term 中，最多只能存在一个 leader；&lt;/li&gt;
&lt;li&gt;Leader 只追加写: leader 节点永远不能覆盖或者删除其日志队列的条目，只能追加写入新的条目；&lt;/li&gt;
&lt;li&gt;日志匹配原则: 如果两个节点的日志队列包含了一个同样 term 和索引的日志条目，那么这两个日志队列上，在这条日志之前的所有日志条目都是完全一致的；&lt;/li&gt;
&lt;li&gt;Leader 完整性原则: 如果一个日志条目在某个任期已经被提交，则这个日志条目一定会出现在更高任期的 leader 的日志队列中；&lt;/li&gt;
&lt;li&gt;状态机安全性: 如果一个服务节点应用了指定索引的日志条目包含的命令到状态机，那么其他服务节点的状态机在同样的日志索引位置不会应用不同的日志；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;上面这些原则之间也存在支撑的关系，比如日志匹配原则就支持了 leader 完整性原则的实现，而 leader 完整性原则又保证了状态机安全性原则。这种基于原则来进行设计的方法也很值得我们在系统设计时进行参考，只要定义好原则，然后满足原则，那么我们的系统就可以达到预期的目标。&lt;/p&gt;</content><category term="mit6.824"></category><category term="distributed-system"></category><category term="raft"></category><category term="paper-reading"></category></entry><entry><title>6.824 Lecture 4 Primary&amp;Backup Replication Notes &amp; Paper Reading</title><link href="https://blog.tonychow.me/mit6.824-letcture4-notes.html" rel="alternate"></link><published>2021-08-24T00:00:00+08:00</published><updated>2021-08-24T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2021-08-24:/mit6.824-letcture4-notes.html</id><summary type="html"></summary><content type="html">&lt;h2&gt;1 概要&lt;/h2&gt;
&lt;p&gt;本课主要是对分布式系统中复制这个主题进行了讨论，复制是为了容错而存在的，而复制本身又会带来更多的挑战。本课的论文是来自虚拟机提供商 VMWare 的 VMware vSphere Fault Tolerance 产品论文，这个产品是一个虚拟机指令级别的复制系统，是一个切实使用的企业级产品中的一个功能。虚拟机级别的复制对应用甚至操作系统都是透明的，VM 之上程序完全无感知，并且复制状态和粒度非常精确，可以带来很强大的复制功能，但是另一方面实现上也存在很多的困难。&lt;/p&gt;
&lt;p&gt;本课相关的材料:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;课堂 Paper - https://pdos.csail.mit.edu/6.824/papers/vm-ft.pdf&lt;/li&gt;
&lt;li&gt;课堂录像: https://youtu.be/gXiDmq1zDq4&lt;/li&gt;
&lt;li&gt;课堂 Note: https://pdos.csail.mit.edu/6.824/notes/l-vm-ft.txt&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2 要点&lt;/h2&gt;
&lt;h3&gt;2.1 异常与失效&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Fail-stop: 异常导致服务器宕机，完全无效 -&amp;gt; 网络、硬件、掉电等等;&lt;/li&gt;
&lt;li&gt;逻辑  bug: 分布式系统自身的代码逻辑错误导致异常；&lt;/li&gt;
&lt;li&gt;配置错误导致异常；&lt;/li&gt;
&lt;li&gt;不考虑恶意服务和黑客导致的异常；&lt;/li&gt;
&lt;li&gt;物理世界的异常：地震、台风等等导致机房出现异常或者网络中断；&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.2 挑战&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;判断主节点 (Primary) 已经失效: 网络异常导致脑裂，备份节点认为主节点失效尝试提升为主节点并对外提供服务，在网络恢复后系统处于异常状态；&lt;/li&gt;
&lt;li&gt;维持主节点/备份节点数据同步：期待对于客户端无感，主备节点需要保证修改的顺序一致，并且处理好非确定性的执行结果同步；&lt;/li&gt;
&lt;li&gt;Fail-over : 异常出现时，备份节点怎么才可以正常成功接管对外服务？应该尽量减少对外的影响，外部无感知；&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.3 复制同步方案&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;状态传输：定时创建状态的 checkpoint，然后同步到备份节点，实现上可以考虑对比上个 checkpoint，只传输差异；&lt;/li&gt;
&lt;li&gt;复制状态机 (RSM) : 主节点同步操作命令到备份节点，备份节点按顺序执行一致的操作，以保证主备状态一致；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;目的都是为保证主备节点的状态一致，状态传输的方式在状态很大的情况下会比较耗时，并且对网络带宽要求很大。&lt;/p&gt;
&lt;p&gt;而复制状态机是常见的方法，上节课的 GFS 也是应用了复制状态机的方案。复制状态机的方案还需要考虑非确定的执行，比如获取当前时间的操作、或者随机数的操作。&lt;/p&gt;
&lt;p&gt;操作复制的级别&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;应用级别的操作: 比如 GFS 的文件追加写操作，和应用强相关;&lt;/li&gt;
&lt;li&gt;机器级别的操作指令: 机器执行的指令，与应用无关，不关注运行的应用程序；&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;2.4 VMware FT&lt;/h3&gt;
&lt;p&gt;虚拟机级别的复制，直接复制虚拟机执行指令操作，对应用程序透明，对请求的客户端隐藏具体实现，尽量减少复制对性能的影响。VMware FT 是真实的应用产品，是商用的。&lt;/p&gt;
&lt;h4&gt;2.4.1 总览&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;层级：硬件 -&amp;gt; 虚拟机 (VM / Hypervisor/VM FT) -&amp;gt; 操作系统(linux) -&amp;gt; 应用程序；&lt;/li&gt;
&lt;li&gt;捕获操作系统的中断，由 VM FT 执行复制到备份 VM，通过日志通道；&lt;/li&gt;
&lt;li&gt;主备之间存在一个日志通道用于传输主节点的非确定相关操作指令；&lt;/li&gt;
&lt;li&gt;客户端只与主节点交互和通信；&lt;/li&gt;
&lt;li&gt;备份节点和主节点执行一样的操作指令，但是不会直接与客户端交互，不会对外输出；&lt;/li&gt;
&lt;li&gt;备份节点操作系统的对外写数据会被 VM FT 处理掉；&lt;/li&gt;
&lt;li&gt;主备节点同网络内会存在一个存储服务，用于主备之间的协调处理和数据存储；&lt;/li&gt;
&lt;li&gt;主备节点之间如果存在网络分区，需要依赖共享存储去协调进行主备切换，备节点状态切换为单节点的主节点状态，并开始接收和处理客户端的请求及进行响应；&lt;/li&gt;
&lt;li&gt;主备节点在共享存储上利用一个类锁机制标识(flag)来实现状态切换；&lt;/li&gt;
&lt;li&gt;主节点失效，备份节点转换为主节点对外服务，这时候就是单点状态，需要考虑启动一个新的备份节点继续执行复制，VM FT 是利用 VMMotion 复制主虚拟机来实现创建新的备份虚拟机的；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;主节点通过日志通道发送给备份节点的情况：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;有可能会导致主备执行出现分离的时候，相关执行指令及数据会发送给备份节点：外部事件，客户端的 packet 数据，中断等；&lt;/li&gt;
&lt;li&gt;非确定性执行指令的结果需要发送给备份节点；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;备份节点必须要落后主节点一个日志条目的执行，以保证备份节点的执行不会超出主节点，导致和主节点出现分离。&lt;/p&gt;
&lt;h4&gt;2.4.2 设计&lt;/h4&gt;
&lt;p&gt;目标是希望对外能表现为一个单节点服务器一样，外部客户端对主备异常下的切换无感知。&lt;/p&gt;
&lt;p&gt;指令类型：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;确定性指令：inc, dec, ... 需要保证执行的顺序一致；&lt;/li&gt;
&lt;li&gt;非确定性指令：执行多次结果产生的结果不一样，如果直接复制到备份节点执行会影响到结果，导致主备的状态不一致 -&amp;gt; 获取时间操作，计时器中断&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;多核的情况：多个线程执行是抢占式的，难以保证主备的多核执行指令顺序一致，本论文中应用的是单核虚拟机，暂不考虑多核的实现。&lt;/p&gt;
&lt;p&gt;操作系统执行非确定指令时，操作被 FT 捕获，执行结果并记录结果，然后将非确定执行转换为确定性的执行，通过日志通道传输结果给备份节点，在备份节点执行同样的指令时也会捕获指令并返回一致的结果给备份节点的操作系统。&lt;/p&gt;
&lt;p&gt;主备复制机制的存在，为了避免在异常切换主备时对外输出存在不一致，主节点对外输出需要特别考虑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主节点执行输出前，需要确保产生输出的操作指令已经同步到备份节点；&lt;/li&gt;
&lt;li&gt;只是延迟对外输出，主节点接下来指令的执行还是继续的；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;客户端可以进行重试或者重置连接，但是基于输出规则，不会存在数据不一致。这个输出机制在实际应用时，比如流量大的应用，会导致应用带宽大幅度下降(40%)，因为主节点需要等待指令操作复制到背节点后再对外响应数据接收，所以运行时带宽就会下降。&lt;/p&gt;
&lt;h2&gt;3 Paper&lt;/h2&gt;
&lt;p&gt;本课讨论的论文是来自 VMware 公司的企业级商用系统  VMware vSphere Fault Tolerance，下文简称 FT。FT 包含在其商用产品  VMware vSphere 4.0 ，是一个虚拟机指令级别复制的容错系统，实现了主备虚拟机之间完整的指令及状态复制，应用性能影响不超过 10%，并且对运行的应用无需额外实现应用层的复制。&lt;/p&gt;
&lt;p&gt;我们常见的分布式系统中，为了容错而考虑复制机制时，通常采用的是应用级别的操作日志复制实现，而本论文的思路是对应用及操作系统运行的虚拟机级别指令进行复制。这个方案可以使得主备虚拟机之间无论上层运行的应用是什么，都可以完成无感透明的复制容错。&lt;/p&gt;
&lt;p&gt;这不是一个常见的方案，在具体实现上，涉及到虚拟机底层及硬件指令级别的诸多细节，可想而知存在着很多的难点。本论文主要描述了 FT 整体的设计思路及实现，其中详细讨论了几个关键点，但是整个系统的实现肯定是存在着大量的细节难点，甚至本论文实现的还是针对单核的虚拟机的复制。虽然这个方案并没有被广泛应用，但是 FT 实现中的一些实际考虑和设计也值得我们参考。&lt;/p&gt;
&lt;h3&gt;3.1 简介&lt;/h3&gt;
&lt;p&gt;当我们想要实现一个支持容错的分布式系统时，一个常见的方案是主备机制，备份服务始终保持和主服务的状态同步，这样在主服务挂掉时，备份服务可以直接接手对外提供一直的服务。主备服务之间状态的同步通常是通过复制实现的，而无论是什么级别的复制，我们实现一个主备复制支持容错的系统时有两种常见的方案：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;状态变化数据复制：把主节点的所有状态变化数据(CPU/Memory/IO设备等等)同步到备份节点；&lt;/li&gt;
&lt;li&gt;状态机复制：将主备节点视为启动时状态一致的状态机，这样只需要把会导致主节点状态变化的指令和数据同步到备份节点，然后由备份节点按和主节点一致的顺序执行，这样可以保证主备的状态完全一致，也就实现了同步的效果；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;状态变化数据复制需要对比状态差异，并且如果内存因为指令发生大量的变化，需要同步的数据将会是非常大的，而状态机复制是关注导致变化的指令，只需要同步指令，这样实际需要同步的数据就小很多。但是因为虚拟机有很多指令是非确定性的，每次执行的结果可能都不一致，这种指令需要额外的机制来协调处理，以保证主备节点之间的状态不会出现分离。&lt;/p&gt;
&lt;p&gt;在物理机器上实现指令级别的复制是很难的，特别是多核的服务器，各种非确定性指令难以实现同步。而本论文产品是基于 hypervisor 实现的虚拟机级别的指令复制，hypervisor 拥有对虚拟机执行指令的完全控制，可以捕获所有非确定性操作的必要信息，并且同步到备份虚拟机进行重放执行，保证状态一致。&lt;/p&gt;
&lt;p&gt;基于 hypervisor 实现的虚拟机状态机复制方法不需要硬件修改，可以直接运行在商用机器机器上，并且因为这个方案对于带宽要求不大，还可以考虑部署在不同地点服务器上，以保证更高的容错和可用性。&lt;/p&gt;
&lt;p&gt;本论文实现的 FT 是基于 x86 架构的，可以对操作系统及其上运行的应用提供透明的容错支持，实现上是依赖确定性指令回放 (deterministic replay) 的方式，当前版本只支持单处理器的虚拟机复制。&lt;/p&gt;
&lt;h3&gt;3.2 基本设计&lt;/h3&gt;
&lt;p&gt;&lt;img alt="vmft" src="../images/vmft.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;如上图是 FT 的整体架构，主备 VM 分布部署在物理上分离的两个服务器上，只有主 VM  接收外部的请求和输入，并且通过 logging channel 把数据传输到备份 VM 上进行同步。除了外部输入数据之外，传输的还包含了非确定性的指令及相关信息会同步到备份 VM，备 VM 执行一致的指令和产生一致的结果，以保证主备 VM 状态安全一致。对于指令执行产生的对外输出，只有主 VM 的输出会响应到客户端，备份 VM 的输出将会被 hypervisor 丢弃。&lt;/p&gt;
&lt;h4&gt;3.2.1 确定性回放实现&lt;/h4&gt;
&lt;p&gt;FT 复制是基于确定性状态机复制来实现的，如果两个状态机初始状态一致，那么只要保证输入数据和顺序完全一致，这两个状态机就可以保证一致的状态变化，产生一样的输出。&lt;/p&gt;
&lt;p&gt;VM 的输入包含以下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;网络数据包&lt;/li&gt;
&lt;li&gt;磁盘读&lt;/li&gt;
&lt;li&gt;键盘及鼠标等外设的输入信息&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;非确定性的事件(虚拟中断)及操作(读取处理器的时钟)也会影响到 VM 的状态，对于实现 VM 的执行复制，主要是面临着下面三个挑战：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;正确捕获所有的输入和非确定性操作；&lt;/li&gt;
&lt;li&gt;正确地把输入和非确定操作应用到备份 VM；&lt;/li&gt;
&lt;li&gt;完成以上两点并且不会降低整体的性能，或者尽量要减少影响；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;实现上，一些复杂的操作系统在 x86 处理器上也有一些未定义的指令执行副作用，对于这些的捕获和在备份 VM 上重放也是一个额外的挑战。&lt;/p&gt;
&lt;p&gt;VMware 的 FT 在限定的范围内解决了上面提的这些挑战，其中的确定性回放机制记录了 VM 的输入和关联的非确定性操作指令，以日志条目的形式写到日志文件，再同步到备份 VM 完全按照顺序执行，产生一致的结果和状态。&lt;/p&gt;
&lt;p&gt;对于非确定性的指令操作，需要记录主 VM 执行时的额外信息同步到备份 VM，比如 timer 或者 IO 完成中断，这种非确定性事件发生时的指令也会被记录下来，在备份 VM 重放时，执行到该记录指令时，也会产生同样的中断事件。&lt;/p&gt;
&lt;h4&gt;3.2.2 FT 协议&lt;/h4&gt;
&lt;p&gt;FT 中使用确定性回放来产生相关的操作日志，但是日志记录还需要通过日志通道传输到备份 VM 执行，为了确保实现容错支持，FT 对日志通道和传输提出了一个协议 (个人感觉应该是成为原则) ，其中基本的需求是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输出需求：备份 VM 在主 VM 失效接管后，需要以保证和主 VM 已经发送到外部的所有输出完全一致的方式继续执行；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于一个基于复制机制实现高可用的分布式系统来说，高可用不仅要求节点异常时其他节点可以进行故障切换接管对外服务，还需要实现对于外部客户端来说，整个故障切换过程是无感知的。而系统对外的沟通就是输出，所以这里只要能保证主备切换时对外输出是一致的，那么对于外部客户端来说，整个切换就是无感知的。&lt;/p&gt;
&lt;p&gt;在这里，FT 提出了一个规则：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输出规则：主 VM 在备份 VM 接收并且确认产生输出的关联指令之前都不能发送输出数据到外部；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从这个输出规则来看，只要备份 VM 接收到了产生输出的所有关联指令，包含输出的那个指令，那在主 VM 失效时，备份 VM 是可以顺利接管并且和之前输出保持一致对外继续产生输出。如果相关的指令没有接收到就接管对外服务，因为主 VM 并未对外产生输出，那么接下来的输出就由备份 VM 产生了，即使执行产生了非确定的事件，对于外部客户端来说整个输出也是一致的。&lt;/p&gt;
&lt;p&gt;值得注意的一点是，输出规则并不会限制主 VM 继续执行其他的指令，只是延迟了主 VM 对外部输出的命令。因为输出相关基本也是 IO 操作，而操作系统可以支持非阻塞的网络 IO 和磁盘输出，并且通过异步中断来指示输出完成，所以延迟对外部输出对于操作系统执行其他的指令并不会产生太大的影响。&lt;/p&gt;
&lt;p&gt;&lt;img alt="ftprotocol.jpeg" src="../images/ftprotocol.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;如上图是一个示例，从左到右是主备节点按时间顺序执行指令，而主备之间的线是同步相关的日志，其中主 VM 往备份 VM 同步日志，备份 VM 向主 VM 响应收到日志，上图相关的执行顺序如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;异步事件从主 VM 同步到备份 VM；&lt;/li&gt;
&lt;li&gt;输入指令及数据从主 VM 同步到备份 VM；&lt;/li&gt;
&lt;li&gt;主 VM 输出操作同步到备份 VM ，但是这时主 VM 并未对发生输出，而且暂时延迟了；&lt;/li&gt;
&lt;li&gt;备份 VM 响应收到输出相关的指令给主 VM，此时主 VM 可以执行真正的对外输出；&lt;/li&gt;
&lt;li&gt;主 VM 发生异常失效时，备份 VM 进行故障切换，接管对外服务，因为输出相关指令已经接收到，备份 VM 可以和主 VM 一致地对外输出；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;此外，FT 并不保证对外输出是仅有一次的，比如主 VM 接收到备份 VM 的输出相关日志确认后，对外进行输出，然后出现异常，备份 VM 接管后也可能会再次对外进行输出。这种情况，需要依赖操作系统及传输协议来进行容错，比如 TCP 协议对于重复的数据包会自动进行识别和丢弃，这样对于应用来说也是无感知的。&lt;/p&gt;
&lt;h4&gt;3.2.3 检测和响应故障&lt;/h4&gt;
&lt;p&gt;在实际运行的过程中，主 VM 和备份 VM 都有可能发生故障：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;备份 VM 故障时：主 VM 需要从复制日志的模式切换到普通执行模式，停止往日志通道发生日志记录；&lt;/li&gt;
&lt;li&gt;主 VM 故障时：备份 VM 接管，但是需要先执行完毕所有从主 VM 同步并且已经确认的指令，然后就从回放模式转换为普通执行模式，并且被提升为主 VM，被允许对外产生输出；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;备份 VM 接管时，系统会自动广播新的主 VM  MAC 地址到网络中，这样物理网络设备可以识别主 VM 的位置。&lt;/p&gt;
&lt;p&gt;故障的检测有下面几点相关的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;FT 系统会通过 UDP 心跳来检测运行 VM 的服务器的状态；&lt;/li&gt;
&lt;li&gt;FT 还会检测日志通道的流量情况，包括主向备发送的日志和备向主发生的确认，因为操作系统总是存在定期的 timer 中断，所以主备之间的流量是不会完全消失的；&lt;/li&gt;
&lt;li&gt;网络分区总是可能存在的，如果只按上面两点，是有可能存在脑裂的情况，备份 VM 以为主 VM 发生故障而自提升为主 VM，同时存在两个主 VM；&lt;/li&gt;
&lt;li&gt;FT 中主要是利用共享的存储来处理脑裂的情况，主备 VM 尝试接管对外服务时，会在共享存储上执行一个原子的 test-and-set 操作，如果操作成功就会被允许继续，失败则停止；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最后，当 FT 出现一个 VM 故障时，在另外一个 VM 成功接管后，FT 会再启动一个冗余的备份 VM，继续进行复制和维持与主 VM 的状态一致。&lt;/p&gt;
&lt;h3&gt;3.3 FT 的实践设计&lt;/h3&gt;
&lt;h4&gt;3.3.1 VM 的启动与重启&lt;/h4&gt;
&lt;p&gt;在 FT 中的一个 VM 启动或者重启时，能够快速地将 VM 的状态和已有 VM 同步是一个很关键的问题，并且我们希望这个 VM 的启动不会影响到现有 VM 的性能和执行。在 FT，这主要是通过使用 VMware VMotion 功能修改版本来实现的。VMotion 是设计用于实现将 VM 在不同服务器上迁移的，在 FT 中，具体实现调整为复制，创建日志通道并且让主 VM 进入日志记录模式，并且尽量减少影响，整个过程不会导致主 VM 中断超过一秒。&lt;/p&gt;
&lt;p&gt;FT 中的所有 VM 都是运行在同一个 VMware vSphere 集群服务器上的，共享了同样的存储，所以在 VM 需要启动一个新的备份 VM 时，可以由集群管理服务根据资源和情况选择一个合适的服务器进行复制和启动，整个过程完全自动并且外部无感知。&lt;/p&gt;
&lt;h4&gt;3.3.2 日志通道管理&lt;/h4&gt;
&lt;p&gt;日志通道相关的实现上，FT 系统是通过 hypervisor 在主备 VM 上都维持一个很大的缓存空间，主 VM 把需要同步的操作日志写到主 VM 的缓存空间上，备份 VM 从其缓存空间消费和处理执行操作日志。日志被写到主 VM 的缓存空间后立即就会备刷入到日志通道上，并且尽快地读取到备份 VM 的日志缓存上。日志从网络到备份 VM 的日志缓存后就会立即给主 VM 发生日志确认消息，以方便主 VM 执行输出规则相关逻辑。&lt;/p&gt;
&lt;p&gt;下面几个情形会影响整体的执行：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;主 VM 的日志缓存被写满：主 VM 会停止执行，这将会影响到外部客户端；&lt;/li&gt;
&lt;li&gt;备份 VM 的日志缓存被写满：备份 VM 会进入停止状态，因为备份 VM 本来就不对外输出，所以对外部无影响；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;主 VM 的日志缓存是有可能被写满的，虽然备份 VM 理论上执行速度应该是和主 VM 基本一致的，但是在极端压力的情况下，备份 VM 运行的服务器上可能会存在资源不足，包括 CPU， 内存等资源可能都缺乏，从而导致了相关操作的执行慢于主 VM，随着运行时间增长，主备之间的差距可能会越来越大，也就有可能导致主 VM 的日志缓存被写满。&lt;/p&gt;
&lt;p&gt;从 FT 系统的实现来考虑，我们也不希望主备 VM 之间的指令间隔越来越大。因为在主 VM 失效之后，备份 VM 需要接管对外进行服务，在正式对外输出之前，备份 VM 也需要将同步到的日志按顺序执行完毕才行。如果间隔过大，那在备份 VM 在能正式对外提供服务之前会有比较大的一段时间，这样外部也会感知到了 FT 下的主备 VM 异常。&lt;/p&gt;
&lt;p&gt;基于降低 FT 中主备 VM 操作日志间隔的考虑，FT 对于这种异常情况的处理方案是，通过额外的信息记录主备 VM 之间的执行间隔和保证大小 (100 毫秒)。当执行间隔大于一定值时 (大于 1 秒)，主 VM 的执行速度将会被减缓，直到主备 VM 之间的执行间隔恢复到正常范围，如果备份 VM 的执行速度跟上来，主 VM 的 CPU 限制也会被逐渐放开，整体的执行性能也慢慢恢复到正常值。&lt;/p&gt;
&lt;h4&gt;3.3.3 FT VM 运维&lt;/h4&gt;
&lt;p&gt;对于 FT 中的 VM 正常运维操作也需要进行考虑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主 VM 执行正常关机操作，备份 VM 也应该进行关机，而不应该尝试接管对外服务；&lt;/li&gt;
&lt;li&gt;主 VM 的资源比如 CPU、内存进行了调整，备份 VM 也应该执行相对应的调整；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些运维操作，会生成特定的控制日志并发生到日志通道上传输给备份 VM 执行。对于大部分针对 VM 的运维操作，一个原则是这些操作只能在主 VM 上执行，然后再通过特定的控制日志同步给备份 VM 执行。&lt;/p&gt;
&lt;p&gt;唯一例外的运维操作是 VMotion 的执行，这个操作可以针对备份 VM 进行，用于调整备份 VM 的运行服务器或者恢复备份 VM。VMotion 操作给 FT 的实现带来很大的复杂性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主 VM 进行 VMotion 操作时，备份 VM 需要断开和原来主 VM 的日志通道连接，再重新连接到新的主 VM；&lt;/li&gt;
&lt;li&gt;备份 VM 进行 VMotion 时，也需要主 VM 进行日志通道的切换；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在进行 VMotion 操作时，FT 要求所有的磁盘操作都完全暂停，对于主 VM 来说，磁盘操作暂停很容易，直接执行就可以了，而备份 VM 则面临更复杂的情况。因为备份 VM 是执行主 VM 的指令的，这也可能包含正常的磁盘 IO 操作，对于备份 VM 来说难以保证在正确的地方停止磁盘 IO。实现上，当备份 VM 在 VMotion  操作最后切换的阶段时，会通过日志通道通知主 VM 临时停止所有的 IO 操作，这样备份 VM 消费到主 VM 同步过来的 IO 停止后会按照顺序执行，然后再执行后续的正常操作。&lt;/p&gt;
&lt;h4&gt;3.3.4 磁盘 IO 相关实现问题&lt;/h4&gt;
&lt;p&gt;在 FT 实现上，关于磁盘 IO 有几类问题需要考虑和解决。&lt;/p&gt;
&lt;p&gt;第一是磁盘 IO 操作的非确定性执行。磁盘 IO 操作是非阻塞并且可以并行执行访问同一个磁盘位置的，并且在使用 DMA 操作进行内存和磁盘之间的数据传输时，也可能会并发访问同一个内存位置，而并发带来不确定性。FT 主要是通过检测这类型的 IO 操作并转换为顺序的执行来解决这个问题的。&lt;/p&gt;
&lt;p&gt;第二个问题是磁盘操作和应用内存操作之间的冲突。磁盘操作包含了 DMA 可以直接读取内存数据，而当应用和磁盘操作同时访问内存同样位置时，也会带来不确定性。FT 主要是通过一个临时的弹性缓存 (bounce buffer) 来解决这个问题的。这个弹性缓存空间和磁盘操作要访问的内存大小一致，磁盘读操作的数据将会先缓存到这个弹性缓存空间中，直到读 IO 完成才会将数据拷贝到操作系统内存。而写磁盘操作则先写到弹性缓存空间，然后再从弹性缓存写到磁盘上。&lt;/p&gt;
&lt;p&gt;第三个问题是主 VM 的磁盘操作可能在主 VM 发生异常失效时还没有完成，对于即将要被提升为主 VM 的原备份 VM 来说这个是不可知结果的，并且也不会接收到相关的磁盘 IO 操作完成事件。新的主 VM 也不能直接对操作系统返回一个磁盘 IO 操作失败，因为这样可能会导致不确定的情况，操作系统未必能正常处理磁盘 IO 错误。FT 的方法是重新执行等待完成事件的磁盘 IO 操作，配合之前并发 IO 调整为顺序执行，这样可以保证重新执行也不会产生异常的不一致的情况。&lt;/p&gt;
&lt;h4&gt;3.3.5 网络 IO 相关实现问题&lt;/h4&gt;
&lt;p&gt;VMware vSphere 对于 VM 的网络相关内容有特别的性能优化，主要是基于 hypervisor 的异步更新虚拟机的网络设备来实现的。但是异步操作往往意味着非确定性，对于 FT 的 VM 来说，很有可能会导致主备 VM 的状态发生分离。所以，在 FT 中，异步网络优化被取消了，异步地将接受到的数据包更新到 VM  ring buffer 的代码会被 hypervisor 拦截并记录下来再应用到 VM 上。&lt;/p&gt;
&lt;p&gt;取消网络的异步优化对于性能有一定的影响，在 FT 的实现上，主要是采用以下两个方法来提升 VM 的网络性能：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;基于集群优化 VM 的相关拦截和中断：hypervisor 在流式数据传输时可以按数据包分组进行拦截处理，同样相关的中断也可以分组批量进行；&lt;/li&gt;
&lt;li&gt;减少数据包传输的延迟：如上面提及的，基于输出原则，主 VM 需要延迟对外的数据传输直至备份 VM 确认相关联的日志已经收到，hypervisor 支持在 TCP 协议栈上注册函数，在接收到数据时可以从一个延迟执行的上下文调用，这样备份 VM 在接收到日志和主 VM 接收到确认都可以快速处理，而无需进行上下文切换。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;3.4 参考设计&lt;/h3&gt;
&lt;p&gt;本节内容主要是一些实现方案的参考设计方案。&lt;/p&gt;
&lt;h4&gt;3.4.1 共享和非共享磁盘&lt;/h4&gt;
&lt;p&gt;FT 的默认设计实现中，主备 VM 是共享相同的虚拟磁盘的，这样两个 VM 之间的数据天然就是保持一致，只要维持只有主 VM 对外输出，在故障切换时，备份 VM 可以直接在同样的虚拟磁盘上进行读写操作。&lt;/p&gt;
&lt;p&gt;另外一个方案是主备 VM 使用分离的磁盘，各自维护各自的磁盘数据状态，备份 VM 也需要执行写磁盘相关操作。这种方案在主备 VM 物理上的距离太长而不能共享虚拟磁盘的情况，此外对于无法使用共享磁盘的场景也可以支持。非共享磁盘的方案有几点需要注意的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;因为主备 VM 各自维护分离的磁盘，所以对于磁盘写，主 VM 不需要根据输出规则进行延迟；&lt;/li&gt;
&lt;li&gt;首次启用主备 VM 时，需要把主 VM 磁盘数据快速同步到备份 VM 的磁盘；&lt;/li&gt;
&lt;li&gt;主备可能发生不同步的情况，在同步恢复之后还需要额外考虑磁盘数据的同步处理；&lt;/li&gt;
&lt;li&gt;对于脑裂的场景，因为不存在共享磁盘来处理，主备 VM  需要依赖额外的第三方来进行协调；&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;3.4.2 备份 VM 执行磁盘读操作&lt;/h4&gt;
&lt;p&gt;FT 的默认设计中，备份 VM 不会从其虚拟磁盘中读数据，只会从主 VM 读取后再视为输入操作同步日志到备份 VM 执行进行同步。&lt;/p&gt;
&lt;p&gt;一个替代方案是支持备份 VM 读取磁盘数据，这样主 VM 就不再需要同步磁盘读取的数据，这样可以降低整个日志通道的带宽和数据。不过这个方案也有几点问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先因为备份 VM 也需要执行磁盘读操作，而磁盘读操作有可能会延迟，备份 VM 的执行速度将有可能受到影响，从而影响到主备 VM 的同步处理，此外主 VM 磁盘操作完成后，备份 VM 有可能还没完成；&lt;/li&gt;
&lt;li&gt;备份 VM 读取磁盘的操作是有可能失败的，这样还需要进行额外的处理，比如主 VM 读取磁盘成功但是备份 VM 读取失败的情况，或者反过来，实现上都需要更复杂的处理；&lt;/li&gt;
&lt;li&gt;在共享磁盘的方案下，如果主 VM  对磁盘的同样位置进行读写，那为了让备份 VM 后续同步后能读取到同样的数据，还需要考虑延迟主 VM 的写磁盘操作。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;总而言之，支持备份 VM 也执行磁盘读操作会带来实现上很大的复杂性，而优点只是降低日志通道的带宽和流量。这个方案只是在特定的应用场景下适用，比如磁盘读操作非常多而写相对少的应用。&lt;/p&gt;
&lt;h2&gt;4 总结&lt;/h2&gt;
&lt;p&gt;VMware 的 FT 系统是非常有意思的一个实现，在看到本篇论文之前，个人对于复制相关的系统基本上都是基于应用层的一个理解和场景印象，而 FT 基于虚拟机指令级别的复制的确给我带来的一定的震撼。当然，FT 的实现还是有其前提条件的，一个是基于 hypervisor 对虚拟机的管理，使得 FT 系统可以拦截和处理虚拟机的任何执行指令，进行复制相关的逻辑，另外，本论文实现的 FT 还是只针对单处理器的虚拟机，对于并行多核的虚拟机暂未有实际的支持和实现。&lt;/p&gt;
&lt;p&gt;虽然 FT 的应用和实现比较受限，但是最终还是实现了一个完整商用的虚拟机级别的复制容错分布式系统，在 FT 上运行的任意操作系统及应用，都可以无需任何额外处理即可获得分布式容错的能力。而 FT 实现中的很多设计和考虑，在我们实现应用层级别的复制时也是可以进行参考的。&lt;/p&gt;</content><category term="mit6.824"></category><category term="distributed-system"></category></entry><entry><title>6.824 Lecture 3 GFS Notes &amp; Paper Reading</title><link href="https://blog.tonychow.me/mit6.824-letcture3-notes.html" rel="alternate"></link><published>2021-08-02T00:00:00+08:00</published><updated>2021-08-02T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2021-08-02:/mit6.824-letcture3-notes.html</id><summary type="html"></summary><content type="html">&lt;h2&gt;1 概要&lt;/h2&gt;
&lt;p&gt;本课主要是针对分布式系统中容错这个主题进行了讨论，关注的领域是分布式存储系统。先是概述了分布式存储系统的关键点和挑战，然后围绕 GFS 的实现进入了更深入的探讨。GFS 是一个曾经在谷歌中大规模应用的真实分布式系统，对之前课程中涉及到的 MapReduce 应用提供了底层的文件系统支撑。&lt;/p&gt;
&lt;p&gt;本课相关的材料:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;课堂 Paper - GFS: https://pdos.csail.mit.edu/6.824/papers/gfs.pdf&lt;/li&gt;
&lt;li&gt;课堂录像: https://youtu.be/6ETFk1-53qU&lt;/li&gt;
&lt;li&gt;课堂 Note: https://pdos.csail.mit.edu/6.824/notes/l-gfs.txt&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PS. 从课程官网发现 2021 学年新的课程录像已经同步更新到了 Youtube 上，因为疫情原因，课程是线上授课的形式。和 2020 年度现场授课的课程录像相比，线上授课可以直接打开论文进行讨论，整体的信息更丰富一点。所以从第 3 课开始，切换为 2021 年的课程录像进行学习，之前的两课就不再更新了。此外，课程安排相对旧学年有所调整。&lt;/p&gt;
&lt;h2&gt;2 要点&lt;/h2&gt;
&lt;h3&gt;2.1 存储系统&lt;/h3&gt;
&lt;p&gt;构建容错的分布式存储系统是分布式领域的一个关键领域:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;分布式存储保持全局的持久状态&lt;/li&gt;
&lt;li&gt;应用可以基于分布式存储无状态部署和运行&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2.1.1 分布式存储为什么困难?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;高性能 -&amp;gt; 数据分片(多服务器)，提升系统吞吐量&lt;/li&gt;
&lt;li&gt;大量的服务器 -&amp;gt; 错误是常态: 一台计算机一年出现一次错误，一千台服务器，每天都可能会出现错误&lt;/li&gt;
&lt;li&gt;容错设计在大型分布式系统中是必须考虑的 -&amp;gt; 复制&lt;/li&gt;
&lt;li&gt;复制 -&amp;gt; 数据同步问题(可能存在潜在的不一致)&lt;/li&gt;
&lt;li&gt;强一致性 -&amp;gt;  一致性协议 -&amp;gt; 复杂消息交互和传输 -&amp;gt; 性能降低&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在系统引入了复杂性之后，往往会带来另外一个问题，复杂系统就是需要不断地解决不同的问题，然后在实现时对不可能的点做权衡取舍。&lt;/p&gt;
&lt;h4&gt;2.1.2 分布式系统中的一致性 (high level)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;理想的一致性: 就和单服务器表现一样 (完全隐藏背后的大量服务器和复杂交互)&lt;/li&gt;
&lt;li&gt;服务器在并发时也能逐次执行客户端操作&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;读取到的数据是最近写的数据&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;并发: 单机在应对大量客户端请求时，也需要处理好并发&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为了容错引入复制机制，让分布式系统实现强一致性更困难，对于复制协议的实现需要更多的考虑。而复制协议的选择需要考虑实际系统的需求和真实的业务场景。&lt;/p&gt;
&lt;h3&gt;2.2 GFS&lt;/h3&gt;
&lt;p&gt;本课的论文是 Google File System，在谷歌当年广泛应用在各种大数据应用(MapReduce, 爬虫, 日志存储分析)中的底层文件系统。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;高性能: 复制+容错+某种程度的一致性&lt;/li&gt;
&lt;li&gt;成功的系统: 在谷歌内部广泛应用，MapReduce 的底层文件系统，成千台的服务器&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2.2.1 Non-standard design&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;单 Master: 存在单点故障问题&lt;/li&gt;
&lt;li&gt;存在不一致性: 弱一致性实现&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;GFS 的实现不是一个完美实现分布式算法或者理论的标准分布式系统，它是一个谷歌基于自身实际业务特征实现的一个可大规模部署应用的成功的分布式系统。&lt;/p&gt;
&lt;h4&gt;2.2.2 特点&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;大规模: 大量的数据集&lt;/li&gt;
&lt;li&gt;文件自动分片&lt;/li&gt;
&lt;li&gt;全局性: 上千台存储服务器对于所有应用代码来说都是同一个文件系统&lt;/li&gt;
&lt;li&gt;容错: 错误比如会出现，容错及自动恢复&lt;/li&gt;
&lt;li&gt;业界应用的真实大型分布式系统&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2.2.3 设计&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;应用: 类似 MapReduce 中的 Map 或者 Reduce 任务，作为 GFS 的客户端&lt;/li&gt;
&lt;li&gt;Master: 应用与 Master 进行通信执行创建、打开、写入文件操作 -&amp;gt; chunk locations&lt;/li&gt;
&lt;li&gt;Chunk: 64 MB，可以多副本&lt;/li&gt;
&lt;li&gt;读写: 应用直接与 ChunkServer 通信 -&amp;gt; 系统吞吐量可以很大，多个应用可以并发操作访问&lt;/li&gt;
&lt;li&gt;ChunkServer: 文件没有特殊格式，就是以 64MB 保存到 Linux 文件系统中&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2.2.4 Master 状态数据&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;filename -&amp;gt; chunk handles 数组: 一个文件可以由多个 chunk 组成&lt;/li&gt;
&lt;li&gt;Chunk handle: 版本号+chunkserver 列表(primary + secondaries)+租约(lease)&lt;/li&gt;
&lt;li&gt;Log + Checkpoint : 操作日志及检查点数据&lt;/li&gt;
&lt;li&gt;Log -&amp;gt; 持久存储: 操作先写入到 Log&lt;/li&gt;
&lt;li&gt;Checkpoint -&amp;gt; 持久存储: Master 内存数据的快照，方便重启时快速启动&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;单点的 Master 受限于单服务器资源限制，整个 GFS 系统能容纳的文件是存在一个上限的，而且根据后面谷歌工程师的访谈记录，这个限制在谷歌实际应用时，随着业务的发展和数据量的扩大，的确达到了，成为了一个瓶颈。谷歌后来也开始实现了多 Master 的类似系统来优化这个系统。&lt;/p&gt;
&lt;h4&gt;2.2.5 读文件&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;客户端向 Master 发送读请求，带上文件名和 offset&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Master -&amp;gt; 客户端: chunk handle + chunk servers + version number&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;客户端: 缓存 Master 返回的数据 -&amp;gt; 降低对 Master 的压力&lt;/li&gt;
&lt;li&gt;客户端从最近的 chunk server 读取 -&amp;gt; 减少网络传输时间及降低集群网络流量&lt;/li&gt;
&lt;li&gt;chunk server 检查 version number -&amp;gt; ok， 发生数据，避免读取到旧数据&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2.2.6 写文件&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Append 是常见的操作场景: MapReduce 场景，Map 写中间文件和 Reduce 写最终文件都是 Append 操作&lt;/li&gt;
&lt;li&gt;写操作需要考虑 chunk 是否有 primary&lt;/li&gt;
&lt;li&gt;如果没有 primary 需要提升一个 secondary 为 primary ，并且增加 verison number&lt;/li&gt;
&lt;li&gt;version number 必须保存在持久存储中：恢复时需要读取&lt;/li&gt;
&lt;li&gt;客户端可以从 Master 拿到该 chunk 的所有 Primary 及 Secondary 节点信息&lt;/li&gt;
&lt;li&gt;数据先从客户端写到该 chunk 所有节点: pipeline 的方式，流水线传输数据&lt;/li&gt;
&lt;li&gt;Master 需要检测客户端写操作的 version number + lease&lt;/li&gt;
&lt;li&gt;存在一个 secondary 写失败，会返回错误给客户端，客户端需要重新尝试写操作&lt;/li&gt;
&lt;li&gt;at least once&lt;/li&gt;
&lt;li&gt;Chunk 的多个节点之间的数据文件可能不一致&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3 Paper&lt;/h2&gt;
&lt;p&gt;本课的论文是来自谷歌的 GFS，即 Google File System，发表于 2003 年。GFS 在谷歌的服务器集群中是作为 MapReduce 框架等大数据应用的底层分布式文件系统，运行于大量的廉价商用服务器上，并且实现了容错机制，对于大数据任务提供了很不错的性能。GFS 对业界分布式系统设计和实现影响很大，Hadoop 生态中的 HDFS 就是它的开源实现版本。&lt;/p&gt;
&lt;h3&gt;3.1 简介&lt;/h3&gt;
&lt;p&gt;GFS 的设计和实现来自谷歌中对于大数据处理急剧增长的需求，是一个基于实际业务需求而设计的分布式系统。实现上，除了考虑传统分布式系统中的扩展性、鲁棒性和可用性等常见特性之外，还根据谷歌的应用实际负载模式和技术环境有额外的考虑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;组件失效是常态而不是异常：硬件是廉价的商用服务器，各种组件都很可能出错，应用 bug、操作系统 bug、磁盘、内存、连接器、网络，甚至电源都会出现问题；&lt;/li&gt;
&lt;li&gt;要处理的文件很大：GB 级别，对于当年常见的数据文件来说是非常巨大的；&lt;/li&gt;
&lt;li&gt;大部分文件写是追加写：这个是基于类似 MapReduce 这类型应用来说的，并且文件内容一旦写入了，比较少更新，大部分情况下是读取文件内容进行处理，这就说明 GFS 不是用于替换操作文件系统的；&lt;/li&gt;
&lt;li&gt;系统 API 设计上是基于应用实际需求来进行的，比如对应用提供的原子 Append 操作支持，在谷歌的应用常见下就非常有用；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;GFS 不是设计用来作为操作系统基础的文件系统的，所以并没有提供 POSIX 兼容的文件系统 API。&lt;/p&gt;
&lt;h3&gt;3.2 设计与实现&lt;/h3&gt;
&lt;h4&gt;3.2.1 假设&lt;/h4&gt;
&lt;p&gt;GFS 的设计是基于下面这些假设来进行的，在设计一个系统时，先对系统的应用场景进行假设和限制，这样我们最终实现的系统才是真正需要的系统：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;系统是在廉价的商业服务器上构建的，服务器经常会失效， 这意味着系统必须要时刻检测自身的节点状态，实现容错和快速及时地从错误恢复；&lt;/li&gt;
&lt;li&gt;系统存储的是大量的大文件，可能会有上百万个 100 MB 甚至更大的文件，几个 GB 大小的文件也是常见的，小文件可以支持，但是不会考虑特别的优化；&lt;/li&gt;
&lt;li&gt;工作负载常见是两种读方式：数据量大的流式读和小数据量的随机读，流式读通常是每个操作几百 KB 或者 1MB 的数据量，同一个客户端一般会顺序读取一个文件的内容，随机读则是在文件的任意 offset 读取几 KB 的数据，对于关注性能的应用来说，通常是将随机读批量打包为顺序往前读取的操作，而不是前后移动；&lt;/li&gt;
&lt;li&gt;写方式则常见是大量顺序的追加写文件，操作大小和流式读差不多，并且一旦写入文件内容，基本是不再进行修改，小数据量的随机写也支持，但是不会做特别的优化，效率不会很高；&lt;/li&gt;
&lt;li&gt;对于多客户端并发写入到同一个文件的场景，系统必须高效地实现相关的并发语义支持。常见的应用方式是基于文件做生产者和消费者队列的方式，数百个分布在数百个服务器的生产者并发追加写入到一个文件，并发操作的原子同步实现尽量要最少的额外开销，这是很关键的一点；文件读取可能是滞后的，也可能是和写同时进行的；&lt;/li&gt;
&lt;li&gt;对于系统来说，高吞吐量要比低延迟更重要，系统支持的应用对于数据处理的吞吐量需求要高于处理延迟，批量大数据处理才是整个系统的目标；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;总结下来，GFS 应该是一个支持容错、支持大量大文件和超大文件(GB 级别)存储、特别对顺序流式写和读优化、支持并发追加写和关注整体系统吞吐量的一个分布式存储系统。对于上面的这些假设或者说需求，GFS 是怎么实现的呢？下面是详细的一个架构和交互设计。&lt;/p&gt;
&lt;h4&gt;3.2.2 接口&lt;/h4&gt;
&lt;p&gt;GFS 虽然没有实现一个标准的 POSIX 文件系统 API，但是也提供了和常见文件系统非常相似的接口。文件也是层级组织的目录，并且根据路径名标识，支持常见的 create, delete, open, close, read 和 write 操作接口。&lt;/p&gt;
&lt;p&gt;除了常见的文件操作之外，GFS 还支持两个比较独特的操作， snapshot 和 record append 。snapshot 操作主要是用来对数据进行备份，支持对目录树或者文件进行低成本的拷贝，实现上是基于 copy on write 的。而 record append 则是特别针对 GFS 的常见应用场景和需求而实现提供的，支持多客户端原子并发写入到同一个文件。&lt;/p&gt;
&lt;h4&gt;3.2.3 整体架构&lt;/h4&gt;
&lt;p&gt;&lt;img alt="gfs.jpeg" src="../images/gfs.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;上图是 GFS 整体的一个架构，包含了主要的交互节点角色及部分的数据流转和控制交互。在 GFS 中，关键的节点和组件如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Master：单节点部署，保存整个 GFS 集群中所有文件的元数据，包括文件命名空间、访问控制信息、文件到 chunk 列表的映射关系、每个 chunk 保存的 chunkserver 位置等；还会进行整个系统基本的操作处理，比如 chunk 的租约信息管理、孤儿 chunk 的垃圾回收检索处理、chunk 在 chunkserver 之间的迁移、复制，并且还会和每个 chunkserver 定时有心跳消息，下发指令及采集状态；&lt;/li&gt;
&lt;li&gt;Chunk：文件被划分为多个 chunk，每个 chunk 是固定大小的一个文件，由 Master 分配的一个不可变且唯一的 64 位的 chunk handle 唯一标识，读写操作都是基于 chunk handle 进行的；chunk 保存在普通的 linux 文件系统上，由 chunkserver 管理，并且基于高可用考虑每个 chunk 会有复制的备份保存在多个 chunkserver 上；&lt;/li&gt;
&lt;li&gt;ChunkServer：每个数据服务器上都会部署 chunkserver 来负责对文件的 chunk 进行维护，所以一个 GFS 集群中会有大量的 chunkserver；chunkserver 会与 Master 进行心跳交互，向 Master 汇报维护的 chunk 列表及信息，并且根据 Master 的指令进行 chunk 的迁移复制等操作；chunkserver 直接和客户端交互传输和接收文件数据，大量的 chunkserver 可以实现很大的吞吐量；&lt;/li&gt;
&lt;li&gt;Client：应用代码会链接上 GFS 的客户端库，通过库提供的文件操作 API 和 GFS 进行读写操作交互，客户端和 Master 交互获取和执行元数据相关操作，然后直接和 chunkserver 进行文件数据传输，GFS 库不提供 POXISX API 支持文件操作，不会接入到 linux 的 vnode 层；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;上面所有的节点组件都是运行在廉价的商用服务器上，不会依赖特别高性能或者特殊的硬件。&lt;/p&gt;
&lt;h4&gt;3.2.4 单节点 Master&lt;/h4&gt;
&lt;p&gt;从上节的架构中，我们可以注意到，在 GFS 集群中，Master 是单节点部署的。在 GFS 中，单节点 Master 的设计极大简化了整个系统的复杂度，让 Master 可以基于全局完整的信息对文件的 chunk 位置和复制等操作进行策略决定。&lt;/p&gt;
&lt;p&gt;另外一方面，GFS 必须对读写等操作交互仔细考虑设计，尽量降低操作和 Master 的相关性，避免单点的 Master 成为整个系统的瓶颈。具体设计上，客户端只会向 Master 请求获取操作相关的 chunkserver 信息，并且将获取到的数据缓存下来作为后续操作的依据，然后直接和 chunkserver 进行文件数据读写交互。文件数据永远不会经过 Master 节点。&lt;/p&gt;
&lt;p&gt;一个简单的读流程如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先，根据 chunk 的大小值，客户端将文件名和要读取的字节位置 offset 信息转换为文件中的 chunk 索引位置；&lt;/li&gt;
&lt;li&gt;客户端发生一个包含了文件名和 chunk 索引位置信息的读请求到 Master 节点；&lt;/li&gt;
&lt;li&gt;Master 节点给客户端回复该文件的 chunk 对应的 chunk handle 值和 chunk 所在的 chunkserver 列表信息，这里应该包含 chunk 的复制数据位置；&lt;/li&gt;
&lt;li&gt;客户端根据文件名和 chunk 索引缓存 Master 返回的信息；&lt;/li&gt;
&lt;li&gt;客户端直接请求该 chunk 复制数据所在 chunkervser 节点中的一个，提供 chunk 的 handle 值和要读取的字节范围信息，接下来的读操作是由 chunkserver 和客户端进行交互，不再需要 Master 参与；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;实现上，客户端选择 chunk 的 chunkserver 节点原则上是尽量选择最近的一个，而谷歌内部集群的服务器 IP 地址是经过精心编排的，可以根据 IP 地址来计算哪个节点是最近的。此外，在应用中，常见的方式是客户端向 Master 请求多个 chunk 的位置信息，缓存到本地，后续再读取文件时，都可以直接和 chunkserver 交互，大大减少了 Master 的压力。&lt;/p&gt;
&lt;h4&gt;3.2.5 Chunk 大小&lt;/h4&gt;
&lt;p&gt;chunk 文件的大小是 GFS 中一个关键的设计点，通常是采用 64 MB ，比一般的文件系统块大小要大得多。每个 chunk 都是以普通 linux 文件的方式保存在服务器上。GFS 的每个chunk 在创建时不会一下子分配 64MB 的文件，而是采用了延迟分配的方式，只有在实际写数据的时候才按需进行分片。这样可以避免空间浪费和文件碎片。&lt;/p&gt;
&lt;p&gt;一个大的 chunk 文件大小值有几个好处：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;减少了客户端和 Master 的交互，因为读写都是可以基于客户端向 Master 初始请求拿到的 chunk 相关信息缓存数据进行；&lt;/li&gt;
&lt;li&gt;因为 chunk 比较大，一段时间内客户端和 chunkserver 的交互都是对于同一个 chunk 进行操作，这样实现上可以采用持久的 TCP 连接，降低网络相关的开销；&lt;/li&gt;
&lt;li&gt;减少了 Master 需要保存的元数据数量，实现上 Master 会将元数据保存到内存中，整体数据的大小很关键；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;同时这个设计也存在不好的地方，比如对于一个只包含几个 chunk 的小文件，如果过多的客户端同时从这个小文件读取数据，有可能会成为系统中的一个热点文件，相关应用的性能会出现问题。&lt;/p&gt;
&lt;p&gt;在 GFS 的使用中，这种情况曾经出现过，在一个批量队列的系统应用中，一个程序往 GFS 写入一个单 chunk 的文件，然后触发数百个服务器同时执行读操作，导致保存这个文件的 chunkserver 严重超负载，影响到了这些 chunkserver 上其他文件 chunk 数据的读写操作。简单的一个处理方案是对于这种类型的应用，在执行写操作时，提高文件的复制系数，把文件分散到足够多的 chunkserver 中，降低单个 chunkserver 的可能负载。&lt;/p&gt;
&lt;h4&gt;3.2.6 元数据&lt;/h4&gt;
&lt;p&gt;Master 主要是保存以下三种元数据：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;文件和 chunk 的命名空间，也就是目录结构及文件路径信息；&lt;/li&gt;
&lt;li&gt;文件到 chunk 的映射关系，一个文件可以由多个 chunk 组成；&lt;/li&gt;
&lt;li&gt;每个 chunk 的每个副本的位置信息，也就是保存在哪个 chunkserve 上；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对于命名空间和文件到 chunk 的映射信息，Master 会保存相关的修改操作本地磁盘的操作日志文件上持久化存储，并且还会复制到一个远程备用的服务器上。这种方式可以简单地保证 Master 崩溃时相关操作和数据的一致性和可靠性。至于 chunk 的位置信息，Master 不会持久化保存下来，只会在每次启动时或者一个 chunkserver 加入到集群时，直接和每个 chunkserver 节点交互获取相关的信息。&lt;/p&gt;
&lt;p&gt;下面将会对 Master 元数据的关键设计点进行说明。&lt;/p&gt;
&lt;h5&gt;3.2.6.1 内存数据结构&lt;/h5&gt;
&lt;p&gt;Master 将数据保存到内存中有几个好处:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;内存访问速度快，也就意味着 Master 节点处理请求操作的速度很快；&lt;/li&gt;
&lt;li&gt;Master 可以方便地定期扫描整个 GFS 集群的状态，根据状态执行一系列的后台处理；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Master 节点的后台处理是 GFS 集群中很重要的内容，对于 GFS 整体集群的数据一致性和高可用等等保证是关键的处理，包含以下内容：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;垃圾回收：处理异常导致的无效的 chunk；&lt;/li&gt;
&lt;li&gt;重新复制：对于一个 chunk ，如果有副本所在的 chunkserver 服务器挂掉导致复制系数达不到设定的值，需要选择新的 chunkserver 节点重新复制数据，以保证 chunk 数据的高可用；&lt;/li&gt;
&lt;li&gt;基于服务器的负载和磁盘空间信息将 chunk 数据在不同的 chunkserver 之间进行迁移，保持整个 GFS 集群的稳定和高效利用；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以上处理在实现上存在比较多的细节和考虑，论文下面的章节中都有具体地进行了讨论。&lt;/p&gt;
&lt;p&gt;对于把元数据保存到内存中这个方案，一个常见的关注点是整个 GFS 集群的文件容量是受限于 Master 节点服务器的内存大小的。论文中给出了几点解释，一个是每个 64MB chunk 的元数据不会超过 64 字节，另外真的出现容量问题，在 Master 节点服务器上添加内存的代价是比较低的。相对于可能存在的容量限制和需要硬件更新的代价，完全内存数据结构带来的简单、高效、可靠、性能和灵活性是非常值得的。&lt;/p&gt;
&lt;p&gt;PS. 在谷歌应用 GFS 的过程中，容量最终成为了一个不可忽略的问题，根据一个与 GFS 开发工程师的&lt;a href="https://queue.acm.org/detail.cfm?id=1594206"&gt;访谈记录&lt;/a&gt; ，随着谷歌内部的数据量从数百 T 到 PB，甚至到数十 PB 级别，单节点 Master 的确成为了系统的瓶颈。所以后续 GFS 也实现了类似多 Master 的模式。这个访谈记录透露了关于 GFS 的不少有趣内幕，可以看看。&lt;/p&gt;
&lt;h5&gt;3.2.6.2 Chunk 位置&lt;/h5&gt;
&lt;p&gt;对于每个 chunk 数据及其复制在哪个 chunkserver 服务的位置信息，Master 虽然会在内存中保存完整的数据，但是不会将这部分数据持久化到磁盘上。Master 节点启动的时候，直接从每个 chunkserver 拉取所有 chunk 的信息，然后后续定期通过心跳消息维持数据的时效性。这个设计有几点考虑：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;实现起来比较简单，不需要考虑太多 chunk 位置信息在 Master 内存、磁盘及 chunkserver 状态变化时的同步处理；&lt;/li&gt;
&lt;li&gt;此外，chunkserver 是保存 chunk 数据的服务，应当有最准确的 chunk 信息，没必要在 Master 还保持一份持久化信息，chunkserver 的失败是很常见的，数据持久化了再处理同步就很麻烦；&lt;/li&gt;
&lt;/ol&gt;
&lt;h5&gt;3.2.6.3 操作日志&lt;/h5&gt;
&lt;p&gt;操作日志是 GFS Master 中很重要的一类数据，关系到 GFS 关键元数据的完整性和可靠性。此外，对于 GFS 上的并发操作，操作日志也天然存在一个逻辑顺序的时间线。对于元数据操作日志， GFS 有以下处理：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用户的操作产生的元数据只有在成功持久化到磁盘后才会对客户端可见；&lt;/li&gt;
&lt;li&gt;操作日志会被复制备份到多个远程服务器上，并且要在 Master 本地和远程复制节点都把操作日志持久化到磁盘后才会返回响应给用户；&lt;/li&gt;
&lt;li&gt;Master 会将操作日志批量写到磁盘上；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Master 重新启动时，会读取操作日志进行执行来恢复元数据的内存状态。为了减少加载时间，GFS 支持定时将 Master 内存状态数据作为 checkpoint 数据保存到磁盘上。checkpoint 是元数据按内存数据结构直接保存的，在重启时可以直接加载到内存中恢复数据状态，不再需要解析和执行操作。加载完最新的 checkpoint 数据后，Master 只需要将最新 checkpoint 后的操作日志进行重新执行就可以把整体状态恢复到最新了。创建 checkpoint 之前的操作日志可以直接删除掉以减少空间。&lt;/p&gt;
&lt;p&gt;checkpoint 数据的创建也有需要注意的地方，在元数据比较多的时候，整个操作可能是比较耗时的，应该尽量避免对 GFS 正常的业务处理造成影响。所以实现上，Master 会在分离的线程上执行相关的创建操作，此外也会切换一个新的日志文件记录新的操作日志。在 checkpoint 创建完毕之后，会持久化到本地和远程磁盘上。&lt;/p&gt;
&lt;p&gt;操作日志是现在一些分布式系统中比较常见的设计方案，有些系统也会将其称为 Binary Log (binlog), Write Ahead Log (WAL) 等等。实现上也是差不多，首先系统存在状态，操作日志记录的是对系统状态的变更操作，包括插入/更新/删除。正常业务和处理多节点复制之前，都需要保证操作日志持久化成功。然后为了加速服务重启加载，通常也是采用定时 checkpoint 整个状态数据的方式，此外，还可以对操作日志进行压缩处理，比如同一个 Key 的多个变更操作，可以压缩为最后一个更新或者删除操作。&lt;/p&gt;
&lt;p&gt;现在常见的分布式系统中，操作日志也多应用于多复制节点间的数据同步处理，实现上也有差不多的考虑。&lt;/p&gt;
&lt;h4&gt;3.2.7 一致性模型&lt;/h4&gt;
&lt;p&gt;GFS 实现的一致性模型是宽松一致性模型，很好地支持了谷歌的大规模分布式应用，同时实现上也是比较简单和高效的。本节内容主要是具体讨论 GFS 提供的一致性保证，以及对于应用的实现上的影响和需要考虑的地方。&lt;/p&gt;
&lt;h5&gt;3.2.7.1 GFS 的保证&lt;/h5&gt;
&lt;p&gt;首先，文件命名空间，也就是文件目录树相关的变更操作是原子性的，实现上是通过 Master 的锁机制和操作日志来保证的。锁机制保证操作的原子性及数据的正确性，而操作日志则是明确了操作的全局执行顺序。&lt;/p&gt;
&lt;p&gt;对于文件的数据变更操作则复杂得多，操作是否成功，是否并发，都需要考虑。首先需要明确对于文件数据的几个概念:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一致(consistent) : 所有客户端从所有的 chunk 数据副本总是能读到同样的数据;&lt;/li&gt;
&lt;li&gt;确定(defined) : 如果对于文件数据的一个变更操作是一致的，并且所有客户端都读到这个写的内容，那么这个文件数据是确定；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;变更操作一致性的几种情况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;非并发成功的变更操作，所有受影响的文件都是确定的，也就是这个操作是一致的： 所有客户端都可以读到同样的数据，并且看到变更操作的内容；&lt;/li&gt;
&lt;li&gt;并发且执行成功的变更操作，所有受影响的文件是未定义状态，但是是一致的: 所有客户端可以读到同样的数据，但是数据可能不是来源同一个操作，更常见的情况是多个并发的变更操作数据混合在一起；&lt;/li&gt;
&lt;li&gt;失败的操作会导致文件不一致：不同的客户端在不同的时间点可能从不同的复制节点文件读取到不一样的数据；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;变更操作主要包含以下两种:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;写操作: 将数据写入到文件指定位置中；&lt;/li&gt;
&lt;li&gt;追加写操作: 将数据原子地写入到文件的当前位置，由 GFS 决定和解决并发写的情况；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;成功的变更操作后，GFS 会保证相关的文件所有的节点都是确定的，实现的方式首先是在每个 chunkserver 上对 chunk 的多个变更操作顺序都是保持一致的，另外，还会利用 chunk 的版本号信息来判断 chunk 的副本数据是否过期失效。包含了失效数据的复制节点不会参与变更操作或者读操作，并且会尽快被执行回收处理。&lt;/p&gt;
&lt;p&gt;之前有提到，客户端会缓存 chunk 的位置信息，所以这个缓存是的确有可能会导致客户端读取到已经过期的副本数据。这个情况无法完全避免，但是实现上会尽量缩减存在的时间窗口和影响。首先客户端的缓存数据是有超时的，在超时后会重新从 master 获取和刷新缓存。然后，对于谷歌来说，大部分的应用模式是追加操作，过期的副本节点一般是返回前面的位置信息，当读者重新从 Master 获取 chunk 信息时，会获取到当前的 chunk 位置信息。&lt;/p&gt;
&lt;p&gt;在一个长期运行的 GFS 集群中，即使操作都是成功的，还是会存在组件失败导致数据损坏或者丢失。GFS 主要是通过定期的握手识别失效的 chunkserver 节点，然后通过 checksum 校验检测数据文件是否已经损坏。一个 chunk 的数据只会在 GFS 检测和处理异常之前完全丢失所有复制节点数据的情况下才会丢失，并且也只是丢失，而不是返回错误的数据。&lt;/p&gt;
&lt;h5&gt;3.2.7.2 应用实现的考虑&lt;/h5&gt;
&lt;p&gt;对于基于 GFS 的应用来说，要实现宽松的一致性模型很简单，只需要以下几个方式:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优先使用追加操作&lt;/li&gt;
&lt;li&gt;数据检测点(checkpoint)快照&lt;/li&gt;
&lt;li&gt;写数据自校验、自标识&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;论文中对于应用的常见提了两个常见的应用例子和实现上的一些细节：&lt;/p&gt;
&lt;p&gt;writer 完整写入一个文件的内容：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;完成数据写入后会原子地重命名文件；&lt;/li&gt;
&lt;li&gt;定期 checkpoint 已写入的数据，可能会包含一些应用层的 checksum 信息；&lt;/li&gt;
&lt;li&gt;reader 只会校验和处理到最新 checkpoint 的数据；&lt;/li&gt;
&lt;li&gt;追加操作相对随机写更高效并且对于应用错误有更好的容错性；&lt;/li&gt;
&lt;li&gt;checkpoint 支持 writer 重启时增量处理数据而不需要重新开始；&lt;/li&gt;
&lt;li&gt;checkpoint 可以让 reader 避免处理到已经成功写入但是从应用层面还是不完整的数据；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;多 writer 追加写入到一个文件，作为一种生产者-消费者队列的模式:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GFS 数据追加操作的 &lt;code&gt;append-at-lease-once&lt;/code&gt; 语义实现保留每个 writer 的输出；&lt;/li&gt;
&lt;li&gt;每个  writer 写的记录数据都包含额外的信息，比如 checksum ，这样 reader 可以进行校验；&lt;/li&gt;
&lt;li&gt;reader 在读取数据时可以利用 checksum 信息忽略掉额外的 padding 数据和记录数据碎片；&lt;/li&gt;
&lt;li&gt;对于重复的记录数据，可以通过记录的唯一标识信息进行过滤处理，比如 web 文件的唯一信息；&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.3 系统交互&lt;/h3&gt;
&lt;p&gt;GFS 系统设计上考虑尽量减少系统操作交互中涉及到 Master 节点，以降低 Master 的压力。这一节内容主要是描述在 GFS 的数据变更操作、原子记录追加操作、和快照具体实现上，Master，Client 和 chunkserver 是如何交互的。&lt;/p&gt;
&lt;h4&gt;3.3.1 租约和变更顺序&lt;/h4&gt;
&lt;p&gt;在 GFS 中，变更是指会修改一个 chunk 的内容或者元数据的操作，比如写或者追加写操作。每个针对 chunk 的变更操作都会应用到该 chunk 的所有副本上。GFS 主要是利用租约机制来实现保证 chunk 变更操作在多个副本上的一致。首先 Master 会选择一个 chunk 的节点，并且给其分片一个租约，这个节点被成为该 chunk 的 primary 节点。然后对于该 chunk 的所有变更操作，primary 节点会选择分配一个顺序的操作序号，其他的 chunk 副本都会按照 primary 选择的顺序应用变更操作。&lt;/p&gt;
&lt;p&gt;所以全局的变更顺序是由两个内容决定的:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Master 选择分配的租约顺序；&lt;/li&gt;
&lt;li&gt;在一个租约内，primary 节点指定的序列；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;租约机制主要是基于减少 Master 的管理开销而设计的。一个租约初始的超时时长是 60 秒，不过，只要这个 chunk 一直被修改，primary 就可以一直请求 Master 扩展租约。租约扩展的请求和分配是附加在 chunkserver 和 Master 之间的定时心跳消息中实现的。Master 也可以在某个租约过期前主动地取消其有效期，以实现一些需要的处理操作。如果 Master 和 primary 节点之间的通讯丢失，Master 在上个租约过期后可以安全地将租约分配给一个新的副本。&lt;/p&gt;
&lt;p&gt;下面是一个详细的写操作流程，租约在变更操作中有着非常大的作用：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先客户端会请求 Master 获取指定 chunk 当前持有租约的 chunkserver 信息和其他所有副本的位置信息，如果暂无节点持有租约，则 Master 会在所有副本中选择一个进行分配；&lt;/li&gt;
&lt;li&gt;Master 响应 chunk 的 Primary 节点标识及其他副本位置信息给客户端，客户端缓存这些信息，在 Primary 节点无法访问或者响应不再持有租约时才需要再次和 Master 节点进行交互；&lt;/li&gt;
&lt;li&gt;客户端将数据推送到所有的副本节点，并且顺序可以随意选择，每个副本节点 chunkserver 会将数据保存到内部的一个 LRU 缓存中，知道数据被使用或者过期。这是将数据流和控制流解耦的方法，这样客户端可以根据副本节点的网络拓扑信息来优化数据流调度，提升整体的性能，而不用受到 Primary 节点的限制；&lt;/li&gt;
&lt;li&gt;一旦所有的副本节点都响应确认接收到数据，客户端会发生一个携带了之前推送的数据标识的写请求到 Primary 节点。Primary 会给这个写请求分配一个顺序的序号，如果存在多个客户端并发的写请求，Primary 节点会选择一个顺序进行分片，然后该写请求的数据将会按序号顺序保存到节点的本地存储中；&lt;/li&gt;
&lt;li&gt;Primary 节点完成写操作序号的分配和本地保存后，将写请求发生给其他的复制节点，并且补充序号信息，每个 Secondary 节点都按照序号顺序保存写数据；&lt;/li&gt;
&lt;li&gt;所有的 Secondary 完成本地保存的操作后响应给 Primary 节点通知已经完成操作；&lt;/li&gt;
&lt;li&gt;Primary 节点响应客户端，如果有任意的副本节点出现错误，这个错误信息也会响应给客户端。这种错误情况一般是 Primary 成功，然后 Secondary 节点的任意子集也成功的情况，这个通常也视为该客户端的请求已经失败，之前被修改的数据会保持在一个不一致的状态。客户端代码会通过重试几次 3 ~ 7 的步骤来处理这种异常，最后会从一开始再尝试整个请求。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果一个应用的写请求数据大小超过一个 chunk 的大小，GFS 客户端会将这个写请求拆分为多个写操作，然后所有的写操作都会按照上面描述的顺序和各个节点进行交互，也有可能和其他客户端的写请求顺序重叠。所以最终文件的数据是很有可能不同客户端的写请求数据分段保存在一起的，不过所有的副本的有效文件数据内容是一致的，因为都是按照 Primary 节点分配的序号进行保存的。&lt;/p&gt;
&lt;h4&gt;3.3.2 数据流&lt;/h4&gt;
&lt;p&gt;在 GFS 中，数据流和控制流是分离的，控制流是从客户端到 Primary 再到各个 Secondary 节点，而数据则是根据具体的网络拓扑信息来流转的。首先，数据是线性地在选择好的一系列 chunkserver  节点中流转的，一台 chunkserver 不会同时把数据发生给多个其他 chunkserver。chunkserver 是基于网络拓扑选择最近的另外一个 chunkserver 发送数据，并且是利用以 pipeline 的模式进行数据转发，也就是每收到一些数据，立即就转发给下一个 chunkserver。chunkserver 之间的距离是可以通过 IP 地址信息来计算的，在服务器集群中，服务器的 IP 是经过特意设计，根据物理位置来进行分配的。&lt;/p&gt;
&lt;p&gt;这种数据流转方式，一来每个 chunkserver 都只发生数据给一个其他 chunkserver ，可以最大地利用服务器之间的带宽，另外以 pipeline 的模式发送数据，可以极大地降低整体的数据推送延迟，每个 chunkserver 不需要等待接收到完整的数据再进行转发。&lt;/p&gt;
&lt;h4&gt;3.3.3 原子追加操作&lt;/h4&gt;
&lt;p&gt;GFS 对于指定位置的并发随机写并不能保证数据的顺序性，文件最终可能会包含来自多个客户端的数据分片。而对于追加写操作，客户端只是提供了具体的数据，但是最终的文件位置则是由 GFS 选择，原子写入后再返回位置信息给客户端。普通的写需要客户端使用类似分布式锁的机制来实现同步，实现上比较重而且性能开销大。
在谷歌的应用中，常见的是追加类型的写操作，一般都是将文件作为多生产者/单消费者的模式应用。追加写的流程和上面描述的类似，只是有一些额外的处理。当客户端推送数据到所有的副本节点后向 Primary 节点发起写请求时，Primary 节点会检查是否写入数据会导致 chunk 超过最大值 (64MB)，如果超过则将 chunk 填充到最大文件大小，并且指示客户端重新在下一个 chunk 发起追加写请求。&lt;/p&gt;
&lt;p&gt;任意副本节点上的写失败都会导致客户端重试写请求，这样会导致同一个 chunk 在不同副本节点上可能会包含了不同的数据，有可能是同样数据全部或部分重复保存。GFS 只保证数据在一个原子操作中至少写入一次，并且只要写操作是成功的，对于同一个 chunk 的数据，所以复制节点都是写在同样的位置上。&lt;/p&gt;
&lt;h4&gt;3.3.4 快照&lt;/h4&gt;
&lt;p&gt;快照是针对文件或者目录树的，用于备份和数据回滚，实现上基于常见的写时拷贝 (copy-on-write) 技术。当 Master 节点接收到一个创建快照的请求时，会给相关的 chunk 分片一个新的租约，这样客户端进行写操作时就必须再与 Master 节点交互获取最新的 Primary 节点信息，这样 Master 有机会对已创建快照的 chunk 数据执行拷贝处理。&lt;/p&gt;
&lt;p&gt;租约被废除或者过期后， Master 会记录这个快照操作日志到本地磁盘，然后再同步到内存的元数据状态中，复制记录一个快照涉及到的文件和 chunk 等元数据。快照创建后，当一个客户端尝试写到一个之前快照操作相关的 chunk 时，Master 会注意到这个 chunk 当前有大于 1 的引用，将会复制原来的数据创建一个新的 chunk ，之后的写操作就是基于新的 chunk 进行的了，和上面描述的流程类似，先选择一个 Primary 节点分配租约，然后进行相关的写流程。&lt;/p&gt;
&lt;h3&gt;3.4 Master 操作&lt;/h3&gt;
&lt;p&gt;Master 节点负责着 GFS 中的命名空间的相关操作，包含 chunk 副本节点的控制&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;决定 chunk 放置的服务器位置&lt;/li&gt;
&lt;li&gt;创建 chunk 和复制数据&lt;/li&gt;
&lt;li&gt;在整个系统层面协调处理，以保证每个 chunk 都能满足需求的复制程度，均衡整体负载&lt;/li&gt;
&lt;li&gt;回收未使用的存储空间&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;3.4.1 命名空间管理及锁定&lt;/h4&gt;
&lt;p&gt;Master 支持并发的操作，对于存在竞态的数据主要是使用锁机制来保证操作恰当的执行顺序。GFS 的文件逻辑上是以一个全路径的查找表形式来实现命名空间的管理的，路径管理到文件具体的元数据信息。而命名空间树以前缀压缩的形式保存在内存中，并且该树上每个节点都存在一个读写锁。&lt;/p&gt;
&lt;p&gt;举个例子，/d1/d2/d3/.../dn/leaf ，这样一个路径文件的操作，需要获取整个路径上所有节点的锁才可以进行，具体锁类型，前缀的节点需要获取到读锁： /d1, /d1/d2, /d1/d2/d3, /d1/d2/d3.../dn, 而文件上 /d1/d2/d3/.../dn/leaf 根据操作类型需要获取到读或者写锁。&lt;/p&gt;
&lt;p&gt;GFS 中的文件组织不存在目录或者类似 inode 的机制，对于子文件的写操作，只需要获取到父目录的读锁就可以避免文件创建时父目录被删除。并且当前的锁机制可以支持同个目录下多个文件的并发更新操作。&lt;/p&gt;
&lt;p&gt;GFS 的锁机制还有两点值得注意：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;读写锁对象是延迟创建的，并且在不使用的时候就立即删掉；&lt;/li&gt;
&lt;li&gt;Master 操作获取锁的时候为了避免死锁是按照一致的顺序进行：首先是按照命名空间树的层级顺序，然后同层级是按照字典顺序获取；&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;3.4.2 副本放置&lt;/h4&gt;
&lt;p&gt;在真实场景中 GFS 集群是高度分布的，具有很高的复杂性：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;数百个 chunkserver (服务器) 节点；&lt;/li&gt;
&lt;li&gt;数百个客户端请求访问 chunkserver ；&lt;/li&gt;
&lt;li&gt;多个服务器机架，机架之间可能需要跨越一个或者多个交换机；&lt;/li&gt;
&lt;li&gt;机架内部的带宽流量要大于机架之间的；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对于 chunk 副本的调度和放置，GFS 主要是基于以下两个原则进行的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;最大化数据的可靠性和可用性；&lt;/li&gt;
&lt;li&gt;最大化利用带宽；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所以实现上，副本需要分布在多个机架上，这样读写的流量也可能是需要跨机架进行的，具体实现上存在很多的取舍权衡。&lt;/p&gt;
&lt;h4&gt;3.4.3 创建、重新复制、均衡负载&lt;/h4&gt;
&lt;p&gt;GSF 的 chunk 副本创建有三种情况:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建 chunk；&lt;/li&gt;
&lt;li&gt;重新复制：机器出现异常，副本数量不满足设置的值；&lt;/li&gt;
&lt;li&gt;负载均衡：需要在服务器之间基于访问和服务器空间均衡 chunk 的副本分布；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;创建chunk 的第一个副本，会考虑以下内容：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;选择低于平均磁盘空间使用率的 chunkserver 放置新副本，这样随着系统的运行，不同的 chunkserver 服务器的磁盘使用率是趋于相等的；&lt;/li&gt;
&lt;li&gt;尽量限制每个 chunkserver 上最近创建的 chunk 副本数量：因为创建之后很可能接下来就是大量的写请求，特别是追加写的情况，这样可以均衡写的流量；&lt;/li&gt;
&lt;li&gt;基于 chunk 可用性的考虑，同一个 chunk 的副本也尽量要分布到不同的机架上；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;异常发生时可能会让某个 chunk 的副本数量低于用于设置的值，这时候 GFS 集群需要尽快让副本恢复到原来的状态。多个 chunk 的重新复制是存在一个优先级的，基于以下因素考虑：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;chunk 的副本数量距离设置值的差距，差距越大优先级越高；&lt;/li&gt;
&lt;li&gt;在用的文件的 chunk 优先级高于最近被删除的文件的 chunk；&lt;/li&gt;
&lt;li&gt;阻塞用户操作的 chunk 优先级会被提升到最高；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;chunk 重新复制和上面副本创建的考虑一致，此外为了避免副本重新复制影响到正常的其他读写业务，整个集群中的重新复制的克隆操作数量是受限的，基于 chunkserver 和 集群都存在着一个限制值。此外，chunkserver 也会通过限制复制从源 chunkserver 读取的请求数量来限制复制流量。&lt;/p&gt;
&lt;p&gt;GFS 会定期检测和计算 chunkserver 的磁盘使用状态，来进行 chunk 数据的重新负载均衡，尽量维持每个 chunkserver 的磁盘使用率接近平均值。&lt;/p&gt;
&lt;h4&gt;3.4.4 垃圾回收&lt;/h4&gt;
&lt;p&gt;GFS 对文件删除的磁盘空间回收不是立即的，而是延迟执行的，从文件和文件的 chunk 都是一样的处理机制。&lt;/p&gt;
&lt;p&gt;对于应用主动请求删除的文件，Master 会将该文件重命名为一个新的隐藏的名字，并且补充删除的时间戳。在后续定时检查进行垃圾回收时，对于删除时间大于 3 天 (内部可配置) 的文件及其 chunk 数据，才会进行真正的数据删除和磁盘空间释放，相关的元数据也会被清理掉。在文件被真正删除前，数据都是可读可恢复的。&lt;/p&gt;
&lt;p&gt;垃圾回收还存在另外一种情况，每个 chunkserver 都会和 Master 节点维持定时的心跳，心跳中包含了该 chunkserver 维护的 chunk 信息。而 Master 节点会检测并回复在 Master 已经不存在的 chunk 信息，这时候 chunkserver 需要进行对这些在 Master 已经被删除元数据的 chunk 进行删除处理。这种一般是在 Master 执行删除处理时与 chunkserver 的通信失败导致的情况，这些 chunk 也被称为孤儿 chunk。&lt;/p&gt;
&lt;p&gt;延迟删除的机制有几点优点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;实现简单，并且在一个大型的分布式系统中，因为组件和服务的异常总是会发生的，及时删除不一定是稳定可靠的，要实现稳定可靠，实现上也存在很多额外的处理和开销，延迟删除提供了一种一致的处理，并且一次失败还会在后续的定时执行的处理中再进行重试；&lt;/li&gt;
&lt;li&gt;随机的删除在定时执行检测时可以合并为批量处理，减少了 Master 节点和 chunkserver 之间的通信开销，并且删除时也可以进行批量的 IO 处理；&lt;/li&gt;
&lt;li&gt;Master 可以选择在系统空闲时进行真的的删除操作，这样一方面避免影响了正常的应用业务，另外一方面也重复利用了服务器的资源；&lt;/li&gt;
&lt;li&gt;延迟删除也可以在无意或者恶意删除时恢复数据；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;当然，延迟删除的机制也不一定是适应所有的场景的，并且也有其不足的地方。比如因为磁盘空间是延迟释放的，所以会存在一定的磁盘浪费问题，这样如果部署 GFS 集群的磁盘资源比较有限，也会存在一些问题。GFS 实现上支持对这种场景进行配置调优，可以对一些目录数下的文件 chunk 不进行复制，并且删除时是立即释放空间。&lt;/p&gt;
&lt;p&gt;所有的机制都是取舍权衡，并不存在一个合适所有业务场景的方案，我们能做的只是根据具体的业务常见选择合适的方案，并且在实现上进行取舍。当然，我们也应该认识到方案存在的缺陷和不能解决的场景，在例外业务场景中更新方案或者调整规避。&lt;/p&gt;
&lt;h4&gt;3.4.5 无效副本检测&lt;/h4&gt;
&lt;p&gt;每个 chunk 都维护着一个版本号，每次 Master 给一个 chunk 的副本分配租约时，这个版本号都会增加。所以，对于出现异常不能接收数据更新版本号的 chunk 数据，Master 节点会将其视为已经过期无效，在读写中都会屏蔽掉这些 chunk 副本，并且在定期的垃圾回收中将其删除，释放空间。当然，定期检测 chunk 副本健康度的处理也会给这些 chunk 重新复制创建新的副本。&lt;/p&gt;
&lt;p&gt;此外，Master 返回 chunk 信息给客户端时也会带上这个 chunk 版本号信息，这样客户端就可以在与 chunkserver 交互时提供最新的版本号信息，chunkserver 也可以根据这个版本号信息来决定是否可以执行相关的操作。这样，客户端总是能和有最新数据的 chunkserver 进行交互操作。&lt;/p&gt;
&lt;h3&gt;3.5 容错和诊断&lt;/h3&gt;
&lt;p&gt;论文从一开始就提及了，GFS 是设计运行在一个异常和错误普遍发生的硬件环境中，所以必须要考虑容错处理。此外，判断是否出现了异常，数据是否损坏也是很关键的内容。&lt;/p&gt;
&lt;h4&gt;3.5.1 高可用&lt;/h4&gt;
&lt;p&gt;GSF 的高可用是基于快速恢复和复制来实现的。快速恢复指的是无论 Master 还是 chunkserver 节点，在任何情况下出现异常终止，都会立即启动恢复状态，对于客户端和其他服务节点来说，必须要考虑连接失败和重试。&lt;/p&gt;
&lt;p&gt;而复制则是指的 chunk 的副本数据分布在不同的 chunkserver 服务器上及不同的机架上，用户也可以基于实际应用的可用性需求来设置副本数量。Master 节点会在 chunk 副本数量不足或者存在副本的数据被检测到已经损坏的情况下对现有的 chunk 数据进行复制，创建新的副本。&lt;/p&gt;
&lt;p&gt;至于 Master 节点，主要是基于可靠性的考虑针对其状态数据进行复制，变更操作日志和快照的创建都会复制到多个服务器上。并且对于状态数据的修改，只有在所有的 Master 副本上保存到磁盘上才被视为提交。这样在运行的 Master 节点失败挂掉时，另外一个 Master 的副本就可以立即基于复制的状态数据启动服务请求。&lt;/p&gt;
&lt;p&gt;影子 Master 节点在 Primary Master 节点宕机时也可以作为只读的 Master 节点与客户端，因为通常与 Primary Master 的落后不是特别的大。影子 Master 节点会按照 Primary 节点的顺序应用操作日志修改状态，这样就可以和主 Master 节点保持一致的状态。&lt;/p&gt;
&lt;h4&gt;3.5.2 数据完整性&lt;/h4&gt;
&lt;p&gt;GFS 是通过 checksum 来判断存储的数据是否已经损坏的。每个 chunk 分为 64KB 大小的块，而每个块都有一个 32 位的 checksum，这个数据是和 chunk 的其他元数据一起保存在内存中的。chunkserver 在客户端读取请求数据时，在返回数据之前会进行 checksum 的检查，如果和内存的不一致，则说明数据已经损坏。这时候 chunkserver 一个是响应错误信息给客户端，让客户端再尝试从其他副本所在的 chunkserver 读取数据，另外还会汇报这个信息给 Master，Master 会创建新的副本，然后再删除这个数据损坏的副本。&lt;/p&gt;
&lt;p&gt;checksum 对读操作有一定的影响，实现上客户端会对齐块大小来读取数据，方便校验 checksum。checksum 的计算对于追加写操作则有特别的优化，支持根据接收到的数据增量地进行 checksum 的计算和追加。对于指定位置的随机写则需要在写之前先校验文件覆盖区域的第一个和最后一个块的 checksum 值，这样如果写的范围是部分覆盖了这两个块数据，可以保证不会隐藏了已经损坏的内容。&lt;/p&gt;
&lt;p&gt;chunkserver  也会在空闲的时候扫描和确认每个 chunk 的每个块的 checksum 值，这样也可以清理一些不太常用的 chunk 已经损坏的数据文件。&lt;/p&gt;
&lt;h4&gt;3.5.3 诊断工具&lt;/h4&gt;
&lt;p&gt;GSF 主要是基于日志进行诊断的，日志包含管家的事件，比如 chunkserver 的上下线信息，并且还包含了所有请求及响应信息，但是不会记录具体的文件数据。基于降低对主业务影响的考虑，日志的写是异步的，并且是顺序的磁盘写操作。&lt;/p&gt;
&lt;h2&gt;4 总结&lt;/h2&gt;
&lt;p&gt;整篇 GFS 的论文包含了大量的系统实现细节，讨论了一个业界真实应用的大型分布式系统中的方方面面，其中很多设计在现在很多新的开源分布式系统中都可以找到类似的实现。GFS 是基于实际的业务场景来设计，设计上即解决了实际的业务问题和硬件资源问题，同时也兼顾考虑实现上的简易性，最终还可以保持相当的健壮性和可靠性。&lt;/p&gt;
&lt;p&gt;这是一篇值得不断重复阅读的论文。&lt;/p&gt;
&lt;h2&gt;5 后记&lt;/h2&gt;
&lt;p&gt;写这篇论文的阅读这节内容期间因为公司各种出差和事务繁忙，另外一方面也因为论文阅读这节内容一开始是逐句翻译，花费了大量的精力和时间。后面几节内容调整为记录重点内容和用自己的语言重新整理描述论文的实现和关键，整体的速度就快 了很多，而且还可以加强理解和思考。后续的课程论文阅读也计划用这个方式进行。&lt;/p&gt;</content><category term="mit6.824"></category><category term="distributed-system"></category></entry><entry><title>6.824 Lecture 2 RPC and Threads Notes</title><link href="https://blog.tonychow.me/mit6.824-letcture2-notes.html" rel="alternate"></link><published>2021-06-29T00:00:00+08:00</published><updated>2021-06-29T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2021-06-29:/mit6.824-letcture2-notes.html</id><summary type="html"></summary><content type="html">&lt;h2&gt;1 概要&lt;/h2&gt;
&lt;p&gt;本课没有涉及分布式系统方面的内容，主要是针对本课程 Lab 使用的编程语言 Go 进行了一个简单的介绍，然后讨论了一下多线程并发相关内容。最后是对一个 Go 写的多线程爬虫代码进行了解读，关注点在并发处理、竞态、锁、多线程协作这块。&lt;/p&gt;
&lt;p&gt;本课相关的材料:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;课堂 Paper: 本课无论文阅读&lt;/li&gt;
&lt;li&gt;课堂录像: https://www.bilibili.com/video/BV1R7411t71W?p=2&lt;/li&gt;
&lt;li&gt;课堂 Note: https://pdos.csail.mit.edu/6.824/notes/l-rpc.txt&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2 要点&lt;/h2&gt;
&lt;h3&gt;2.1 Why Go&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Thread (goroutine) 支持&lt;/li&gt;
&lt;li&gt;Lock: 锁机制应对并发执行和竞态&lt;/li&gt;
&lt;li&gt;类型安全&lt;/li&gt;
&lt;li&gt;方便的 RPC 库&lt;/li&gt;
&lt;li&gt;GC 内存安全: 垃圾回收&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Go 要比 C++ 更容易使用，语言更简单直接，不会有特别的语法和特性，也不会有那么多奇怪的错误。&lt;/p&gt;
&lt;h3&gt;2.2 关注并发&lt;/h3&gt;
&lt;p&gt;对于 Go 来说，并发一般情况是多个 goroutine 在同一个地址空间并发执行。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I/O concurrency: 客户端可以请求多个服务端并发等待响应，服务端处理多个客户端的连接请求，在一个请求进行 IO 操作时可以切换处理另外一个请求的计算；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Parallelism: 多线程利用多核，实际系统中应该尽量利用所有 CPU 的计算力；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;Convenience: 后台运行，方便执行处理一些分离的任务；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不用多线程，可以用异步编程 event-driven 的方式:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;单线程单 loop；&lt;/li&gt;
&lt;li&gt;保存每个状态: 比如请求客户端的状态；&lt;/li&gt;
&lt;li&gt;根据事件来执行切换执行任务；&lt;/li&gt;
&lt;li&gt;单个运行无法充分利用多核 CPU；&lt;/li&gt;
&lt;li&gt;实现相对复杂，使用起来也难以理解&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;相对大量线程的情况会更优秀，比如有上百万的连接，对应上百万个线程来说，事件驱动更好，节省资源，同时还可以减少线程切换带来的性能损耗。实现上通常可以多个线程，每个线程都有个独立的事件循环来执行任务，这样可以利用多核资源。比如 Nginx，是基于多 Worker 线程的事件驱动模型来实现高性能并发处理大量请求的支持。&lt;/p&gt;
&lt;h3&gt;2.3 多线程的挑战&lt;/h3&gt;
&lt;p&gt;共享数据、竞态数据: 多线程访问处理容易出现 bug，并发更新可能会出现问题，机器操作可能不是原子指令&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;需要使用锁来解决这个问题；&lt;/li&gt;
&lt;li&gt;或者避免共享可变数据；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;coordination 多线程协作执行&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;channels&lt;/li&gt;
&lt;li&gt;sync.Cond&lt;/li&gt;
&lt;li&gt;waitGroup&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;死锁&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;锁或者 channel 误用，出现彼此依赖释放或者消费的情况，导致了死锁；&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.4 爬虫示例&lt;/h3&gt;
&lt;p&gt;示例代码主要是实现模拟爬虫处理页面抓取的功能，需要考虑以下内容：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个页面可能还包含了其他的页面 URL&lt;/li&gt;
&lt;li&gt;多个页面可能包含同一个 URL，不应该重复抓取&lt;/li&gt;
&lt;li&gt;多个页面直接包含 URL 可能会构成一个环&lt;/li&gt;
&lt;li&gt;页面抓取应当并发进行，可以加速整个任务的执行&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;课堂上主要是介绍了两个版本的并发抓取爬虫：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;基于锁的并发爬虫&lt;/li&gt;
&lt;li&gt;每个发现的 URL 都创建一个抓取页面的线程&lt;/li&gt;
&lt;li&gt;多个线程之间共享一个 map 数据来记录已经抓取到的页面，避免重复和循环抓取&lt;/li&gt;
&lt;li&gt;多个线程对共享的 map 数据操作时需要加锁，避免出现竞态并发更新/读取，在 Go 这会导致 panic 或者内部数据异常&lt;/li&gt;
&lt;li&gt;可以通过 go 编译器自身的 &lt;code&gt;-race&lt;/code&gt; 工具来检查代码中的竞态问题&lt;/li&gt;
&lt;li&gt;基于 Channel 的并发爬虫&lt;/li&gt;
&lt;li&gt;区分为 Master 和 Worker 线程&lt;/li&gt;
&lt;li&gt;Master 线程创建 Worker 线程去抓取单个页面&lt;/li&gt;
&lt;li&gt;Master 和 Worker 线程之间共享一个 channel，Worker 把抓取到的页面里面包含的 URL 发送到这个 channel；&lt;/li&gt;
&lt;li&gt;Master 记录 Worker 执行抓取过的 URL，从 channel 获取到新的页面，先检查是否已经抓取过，如果没有则启动新的 Worker 线程抓取，有则跳过；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;基于 channel 不需要加锁，是因为记录抓取过页面的 map 数据实际上没有在多个线程中共享，也不存在多线程并发读取更新的情况。但是实际上，channel 数据结构本身在 Go 的实现应该是存在着锁的，这样多个线程每次只有一个线程可以把 URL 发送到 channel 中。&lt;/p&gt;
&lt;h2&gt;3 总结&lt;/h2&gt;
&lt;p&gt;本课内容相对简单，Go 语言对于并发的支持比较好，提供了方便的线程(goroutine) 启动方式，此外还对多线程间的协作提供了包括 channel 、sync 等工具来支持。课程原本是用 C++ 来实现 Lab 相关的编码的，近些年在 Go 语言成熟起来后就切换了。使用 Go 来学习和实现分布式系统，可以让学生更关注分布式系统本身相关的内容，而不是在 C++ 的语言特性和代码 Bug 中花费大量的时间。&lt;/p&gt;
&lt;p&gt;Go 语言本身也比较适合网络编程，在业界中有不少的成熟的分布式系统实现，比如 etcd、TiDB、Kubernetes 等。&lt;/p&gt;</content><category term="mit6.824"></category><category term="distributed-system"></category></entry><entry><title>6.824 Lecture 1 Introduction Notes &amp; Paper Reading</title><link href="https://blog.tonychow.me/mit6.824-letcture1-notes.html" rel="alternate"></link><published>2021-06-28T00:00:00+08:00</published><updated>2021-06-28T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2021-06-28:/mit6.824-letcture1-notes.html</id><summary type="html"></summary><content type="html">&lt;h2&gt;1 写在前面&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://pdos.csail.mit.edu/6.824/index.html"&gt;MIT 6.824 分布式系统&lt;/a&gt; 是一门研究生课程，主要关注的内容是分布式系统相关的抽象及实现技术，包含容错、复制、一致性等主题。方式上是通过研读分布式领域的经典论文，分析和讨论这些论文包含的系统实现来进行学习和理解分布式系统。&lt;/p&gt;
&lt;p&gt;从课程的论文来看，偏向工程实践，包含了业界经典的分布式系统工程论文，比如来自谷歌的 MapReduce、GFS、Spanner，也包含了 Zookeeper、Spark、Memcached 等流行的开源分布式中间件，此外还有 Bitcoin 等相对新的分布式系统。&lt;/p&gt;
&lt;p&gt;课程还包含了 4 个 Lab，引导学生从实现经典的 MapReduce 到实现分布式一致性算法 Raft 来进行容错的复制集群，最终会实现一个基于 Raft 的分片 kv 分布式存储系统。从 Lab 的设计来看，这门课程包含了不少的动手编码内容，有一定的挑战性。&lt;/p&gt;
&lt;p&gt;对于本课程的学习，个人计划是按照课程的 &lt;a href="https://pdos.csail.mit.edu/6.824/schedule.html"&gt;Schedule&lt;/a&gt;  进行，阅读每节课相关的论文或者材料，然后观看每节课的课堂录像，并进行课堂笔记的记录，看完后再对笔记进行整理和补充个人的思考，加上个人对每节课论文的分析和阅读思考补充为每节课的总结文章。最新的课堂录像是 2020 年的课程，发布在 &lt;a href="https://www.youtube.com/watch?v=cQP8WApzIQQ&amp;amp;list=PLrw6a1wE39_tb2fErI4-WkMbsvGQk9_UB"&gt;Youtube&lt;/a&gt; 上，国内可以在 &lt;a href="https://www.bilibili.com/video/BV1R7411t71W"&gt;B 站&lt;/a&gt; 找到。&lt;/p&gt;
&lt;h2&gt;2 概要&lt;/h2&gt;
&lt;p&gt;第一节课主要是对整个课程的介绍，对分布式系统要解决的问题和面临的挑战进行了概括，然后是对本课涉及到的论文 MapReduce 进行了解读。因为课程后面比较多的学生进行了提问，所以本课对 MapReduce 并没有进行完整的讨论，缺失的内容可以参考往年课堂完整的 Note。&lt;/p&gt;
&lt;p&gt;本课相关的材料:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;课堂 Paper - MapReduce: &lt;a href="https://pdos.csail.mit.edu/6.824/papers/mapreduce.pdf"&gt;https://pdos.csail.mit.edu/6.824/papers/mapreduce.pdf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;课堂录像: &lt;a href="https://www.bilibili.com/video/BV1R7411t71W?p=1"&gt;https://www.bilibili.com/video/BV1R7411t71W?p=1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;课堂 Note: &lt;a href="https://pdos.csail.mit.edu/6.824/notes/l01.txt"&gt;https://pdos.csail.mit.edu/6.824/notes/l01.txt&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3 要点&lt;/h2&gt;
&lt;h3&gt;3.1 为什么需要分布式系统?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;扩容: 通过并行提升系统的容量，比如多个服务器可以分散处理请求和数据存储，这里容量不同类型系统不一样，包括吞吐量和数据存储等；&lt;/li&gt;
&lt;li&gt;容错: 主要是通过复制来实现容错；&lt;/li&gt;
&lt;li&gt;物理分割: 一些系统为了靠近外部依赖或者服务的其他实体，物理上就存在分布的状态；&lt;/li&gt;
&lt;li&gt;安全隔离: 基于安全性，部分系统需要分布式实现；&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.2 分布式系统面临的挑战&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;并发: 无论是处理请求的并发还是系统内部组件之间的并发交互，对于实现一个可靠分布式系统来说都是必须要去解决的问题；&lt;/li&gt;
&lt;li&gt;局部错误: 单台服务器或者电脑，发生硬件错误可能是一两年的频率，但是对于一个有着上千台服务器的大型集群来说，硬件错误每天都是必然的事件，网络、电源、磁盘，每天都可能会发生错误，一个可靠的分布式系统必须要良好应对硬件错误；&lt;/li&gt;
&lt;li&gt;性能: 系统的性能是否可以随着服务器数量线性提升？这是一种理想状态，实际上是很难实现，而且分布式的系统往往带来了更复杂的情况；&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.3 基础设施&lt;/h3&gt;
&lt;p&gt;本课程主要关注的是服务端基础设施类的软件系统：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;存储: 从数据存储到更底层的文件系统；&lt;/li&gt;
&lt;li&gt;通讯: 分布式系统组件之间的通讯网络协议；&lt;/li&gt;
&lt;li&gt;计算: 分布式的计算模型；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个大主题: 抽象与简化分布式存储和计算基础设施的接口便于构建应用和对应用隐藏分布式系统的内部复杂性。这是个很困难的事情。&lt;/p&gt;
&lt;h3&gt;3.4 课程主题&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;实现&lt;ul&gt;
&lt;li&gt;RPC: 对调用方隐藏实现是通过不可靠网络通讯得到的结果；&lt;/li&gt;
&lt;li&gt;Threads: 多核、并发，简化实现操作；&lt;/li&gt;
&lt;li&gt;Concurrency Control: 锁，处理竞态，保证正确性；&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;性能&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;扩展性: 理想状态 → 系统性能可以随着硬件线性增长&lt;/li&gt;
&lt;li&gt;系统性能: 吞吐量、容量;&lt;/li&gt;
&lt;li&gt;一些性能无法通过增加机器数量提升: 单个用户的请求响应时间，所有用户同时更新同一个数据(涉及到数据竞争) ；&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;容错&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;单台服务器可以稳定运行很久；&lt;/li&gt;
&lt;li&gt;服务器数量多的时候，错误不是随机或者罕见的事件，而是必然事件，总是会有机器出现问题；&lt;/li&gt;
&lt;li&gt;分布式系统需要考虑容错性才能对应用隐藏系统的内部复杂性；&lt;/li&gt;
&lt;li&gt;系统的可用性 Availability: 在错误发生时总是能对外提供正常的服务 ；&lt;/li&gt;
&lt;li&gt;可恢复性 Recoverability: 无法应对的错误修复后，服务能正常恢复，尽可能保持错误发生前的状态；&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;方案&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;持久存储 、非易失存储→ 硬盘，SSD，记录数据 checkoutpoint，服务恢复后读取数据恢复状态；&lt;/li&gt;
&lt;li&gt;复制集群: 数据复制到多台服务器上；&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;其他主题&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;一致性: 数据、状态 → 多副本情况下，因为缓存或者同步的问题，需要考虑数据的一致性&lt;/li&gt;
&lt;li&gt;强一致性: 保证数据的一致性 → 从所有节点都可以看到最新的数据 → 可用性受影响 → 更多的数据通讯 → 异地，跨大洲的复制节点对强一致性有更大的性能损耗；&lt;/li&gt;
&lt;li&gt;弱一致性: 最终一致；&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;3.5 MapReduce&lt;/h3&gt;
&lt;p&gt;意义和起源&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;大量的数据: 数十 BT；&lt;/li&gt;
&lt;li&gt;数千台服务器；&lt;/li&gt;
&lt;li&gt;分布式的任务需要专家程序员写分布式的代码去分布处理任务；&lt;/li&gt;
&lt;li&gt;需要一个易用的框架，方便实现分布式任务，并对工程师隐藏分布式的复杂；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Map&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;关注输入文件&lt;/li&gt;
&lt;li&gt;将文件分散为多个文件，多个 Map 任务&lt;/li&gt;
&lt;li&gt;输出处理的中间文件 → k/v&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Reduce&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;收集 Map 任务产生的中间文件&lt;/li&gt;
&lt;li&gt;按照规则聚合中间文件的数据&lt;/li&gt;
&lt;li&gt;输出聚合结果到最终文件: k → count&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Map 和 Reduce 都是任务，N 个 Map 和 M 个 Reduce 可以分布到多台服务器上执行，可以达到 N 倍的性能提升。&lt;/p&gt;
&lt;p&gt;Word Count: 计算文件中每个单词出现的数量&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Map(k, v): k 是文件名，v 是文件的内容&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bash
Map(k, v)
    split v into words
    for each word w
      emit(w, "1")&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reduce(k, v): k  是单词，v 是单词列表&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bash
Reduce(k, v)
    emit(len(v))&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个真实的 MapReduce 工程是可能存在多个阶段的 Map/Reduce , 构成一个 pipeline 处理流，得到最终需要的结果。&lt;/p&gt;
&lt;p&gt;计算模型特性&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;纯函数无副作用、无外部依赖状态；&lt;/li&gt;
&lt;li&gt;计算模型需要可抽象为 Map/Reduce: 不支持无法抽象为 Map/Reduce 的计算任务；&lt;/li&gt;
&lt;li&gt;网络对当年的 MapReduce 存在很大的限制 → 50 M/s ；&lt;/li&gt;
&lt;li&gt;一些任务可能需要大量的数据复制: 比如排序任务，需要全量的数据进行移动；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;谷歌中的 MapReduce 底层依赖 GFS 。&lt;/p&gt;
&lt;h2&gt;4 Paper&lt;/h2&gt;
&lt;p&gt;本课的论文是来自谷歌 2004 年发表的 MapReduce，这是一个当年在谷歌基础设施中被广泛应用于大数据处理任务的编程框架，工程师只需要定义好 Map 和 Reduce 两种函数，就可以利用实现好的框架库在数千台服务器中并行执行大数据处理任务。这篇论文和 GFS、BigTable 并称为谷歌大数据三大论文， 一起催生了谷歌大数据基础设施的开源版本 Hadoop 。Hadoop 成为今后十多年大数据领域占用绝对地位的基础设施，至今，Hadoop 生态不断发展，依旧是大数据相关业务基础设施的最佳选择。&lt;/p&gt;
&lt;h3&gt;4.1 Map/Reduce 计算模型&lt;/h3&gt;
&lt;p&gt;Map/Reduce 计算模型源自函数编程语言，是用于处理列表类型数据的高阶函数，支持传入一个函数和列表，输出对列表应用传入函数的结果。以 &lt;a href="https://gigamonkeys.com/book/collections.html"&gt;Common Lisp&lt;/a&gt; 为例，Map 函数定义及示例如下:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt; &lt;span class="nv"&gt;result-type&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nv"&gt;sequence1&lt;/span&gt; &lt;span class="nv"&gt;sequence2...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;; 示例&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt; &lt;span class="ss"&gt;&amp;#39;vector&lt;/span&gt; &lt;span class="nf"&gt;#&amp;#39;&lt;/span&gt;&lt;span class="nb"&gt;*&lt;/span&gt; &lt;span class="o"&gt;#(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;#(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nv"&gt;==&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;#(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Map 函数接受一个 N 参数的函数和 N 个序列，将 N 个序列同序号元素作为函数的参数，应用后将函数输出结果连接起来作为一个新的序列返回。如以上示例，&lt;code&gt;vector&lt;/code&gt; 是返回的序列类型，&lt;code&gt;*&lt;/code&gt; 是函数，&lt;code&gt;#(1 2 3 4 5)&lt;/code&gt; 和 &lt;code&gt;#(10 9 8 7 6)&lt;/code&gt; 是输入的两个序列，执行结果是两个序列同序号元素应用函数乘 &lt;code&gt;*&lt;/code&gt; 后的结果序列 &lt;code&gt;#(10 18 24 28 30)&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;Reduce 函数定义及示例如下:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;reduce&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;sequence&lt;/span&gt; &lt;span class="k"&gt;&amp;amp;key&lt;/span&gt; &lt;span class="ss"&gt;:from-end&lt;/span&gt; &lt;span class="ss"&gt;:start&lt;/span&gt; &lt;span class="ss"&gt;:end&lt;/span&gt; &lt;span class="ss"&gt;:initial-value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;; 示例&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;reduce&lt;/span&gt; &lt;span class="nf"&gt;#&amp;#39;&lt;/span&gt;&lt;span class="nb"&gt;+&lt;/span&gt; &lt;span class="o"&gt;#(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nv"&gt;==&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;55&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Reduce 函数接受一个 2 参数的函数和一个序列，首先将该序列的前 2 个元素作为函数参数调用得到一个结果，然后将结果和后一个元素再次作为函数参数调用，依次一直到最后一次函数调用，得到最终的结果。如上示例，函数是加 &lt;code&gt;+&lt;/code&gt;，示例表达式的效果是将序列里面的所有元素相加。&lt;/p&gt;
&lt;h3&gt;4.2 MapReduce 编程模型&lt;/h3&gt;
&lt;p&gt;在本论文中，MapReduce 的编程模型与 Map/Reduce 不太一样，计算任务被抽象为接收和产生 Key/Value 对的 Map 和 Reduce 函数，并且由用户根据计算定义提供给 MapReduce 框架进行执行。Map 函数接受输入的 Key/Value 对，然后产生中间 Key/Value 对，MapReduce 库将中间数据相同 Key 的 Value 数据聚合起来，再交给 Reduce 函数计算，然后输出最终的计算结果 Key/Value 对。简化表达如下:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;map(k1,v1) → list(k2,v2)
reduce(k2,list(v2)) → list(v2)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;论文中举了一个计算文件中每个单词出现数量的计算任务:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;String&lt;/span&gt; &lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;String&lt;/span&gt; &lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="o"&gt;):&lt;/span&gt; 
  &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;document&lt;/span&gt; &lt;span class="nt"&gt;name&lt;/span&gt;
  &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;document&lt;/span&gt; &lt;span class="nt"&gt;contents&lt;/span&gt;
  &lt;span class="nt"&gt;for&lt;/span&gt; &lt;span class="nt"&gt;each&lt;/span&gt; &lt;span class="nt"&gt;word&lt;/span&gt; &lt;span class="nt"&gt;w&lt;/span&gt; &lt;span class="nt"&gt;in&lt;/span&gt; &lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="nt"&gt;EmitIntermediate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;w&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nt"&gt;reduce&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;String&lt;/span&gt; &lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;Iterator&lt;/span&gt; &lt;span class="nt"&gt;values&lt;/span&gt;&lt;span class="o"&gt;):&lt;/span&gt; 
  &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="nt"&gt;word&lt;/span&gt;
  &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nt"&gt;values&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="nt"&gt;list&lt;/span&gt; &lt;span class="nt"&gt;of&lt;/span&gt; &lt;span class="nt"&gt;counts&lt;/span&gt;
  &lt;span class="nt"&gt;int&lt;/span&gt; &lt;span class="nt"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="nt"&gt;for&lt;/span&gt; &lt;span class="nt"&gt;each&lt;/span&gt; &lt;span class="nt"&gt;v&lt;/span&gt; &lt;span class="nt"&gt;in&lt;/span&gt; &lt;span class="nt"&gt;values&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="nt"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nt"&gt;ParseInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;v&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="nt"&gt;Emit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;AsString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;result&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;map 函数的参数 Key 是文件名，Value 是文件内容，函数体就是对文件内容 value 的每个单词直接输出一个 key/value 对到中间文件，key 是该单词，value 是 "1" 表示该单词出现了一次。&lt;/li&gt;
&lt;li&gt;reduce 函数的参数 Key 是某个单词，Values 是中间文件中该 Key 的所有 value 列表，也就是一堆的 "1" 数据。函数体就是对将 Values 列表的数据转换为 Int 数值，然后加起来，最终输出一个结果。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从上面的编程模型来看，MapReduce 对于计算任务类型是有一定的要求的，需要能够将计算抽象为 Map 和 Reduce，并且应当是无副作用的。这样，才可以把大量数据的计算任务拆分为并行的处理任务分发到大量的服务器上进行计算。&lt;/p&gt;
&lt;h3&gt;4.3 架构与流程&lt;/h3&gt;
&lt;p&gt;&lt;img alt="mapreduce" src="../images/mapreduce.png"&gt;&lt;/p&gt;
&lt;p&gt;上图是 MapReduce 计算任务整体的一个架构和执行流程，MapReduce 是以一个库的形式存在，用户程序加载 MapReduce 库然后拷贝到整个集群的所有服务器上。在不同的服务器上，程序有 Master 和 Worker 两种运行模式，其中 Master 负责和其他 Worker 程序交互分发 Map 或者 Reduce 任务及记录状态等元数据。而 Worker 则分布在大量的服务器上分别执行用户程序定义的 Map 任务或者 Reduce 任务。&lt;/p&gt;
&lt;p&gt;在一个计算任务启动时，MapReduce 库会将任务数据分割为 M 个小文件，大小一般从 16 M 到 64 M ，这个主要是与底层依赖的 GFS 特性相关。&lt;/p&gt;
&lt;p&gt;Master 主要保存以下元数据:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个 Map 或者 Reduce 任务的状态: idle, in-process, completed ；&lt;/li&gt;
&lt;li&gt;每个 Worker 节点的唯一标识；&lt;/li&gt;
&lt;li&gt;每个已完成的 Map 任务，保存其产生的 R 个中间文件的位置和大小，用于分发 Reduce 任务；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Worker 根据被分发的任务类型会有不同的执行:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Map 任务:&lt;ol&gt;
&lt;li&gt;读取输入的文件片段内容，解析得到键值对数据，并且将每组键值对数据传给用户定义的 Map 函数执行，然后将产生的中间结果键值对数据缓存在内存中；&lt;/li&gt;
&lt;li&gt;缓存在内存中的数据将会被定时写到本地磁盘中，并且根据用户定义的分片函数，将数据写到本地磁盘上 R 个文件中，然后再把这些文件的位置信息传回给 Master 节点；&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Reduce 任务:&lt;ol&gt;
&lt;li&gt;启动后会根据传入的中间文件位置，通过远程调用的方式读取 Map 任务的本地文件所有内容，然后将所有键值对数据按照 Key 排序，并且同一个 Key 的数据聚合在一起；&lt;/li&gt;
&lt;li&gt;聚合好的 Key 和 Value 列表将会被传给用户定义的 Reduce 函数执行，结果将会被追加写到这个 Reduce 任务的最终输出文件；&lt;/li&gt;
&lt;li&gt;当中间数据文件内容过大内存无法容纳时，将会采用外排的方式进行处理；&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.4 容错机制&lt;/h3&gt;
&lt;p&gt;MapReduce 在谷歌中是设计来运行在成百上千的普通服务器中处理大量的数据的，错误是必然会发生的事情，MapReduce 需要有相关的容错机制来应对各种可能发生的错误。论文中提及了几种错误情况的应对，主要是从整个 MapReduce 中的各个角色来进行讨论的。&lt;/p&gt;
&lt;h4&gt;4.4.1 Worker 失效&lt;/h4&gt;
&lt;p&gt;Worker 失效是由 Master 负责处理的，Master 会定时 ping 每个 Worker 来保持状态。当一个 Worker 超时未响应时，Master 就会将该 Worker 标记为失效状态，并对该 Worker 执行的任务进行如下处理:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;该 Worker 完成的 Map 任务将会被重置为空闲状态，由 Master 再调度其他 Worker 执行；&lt;/li&gt;
&lt;li&gt;该 Worker 进行中的 Map 任务和 Reduce 任务也会被重置为空闲状态；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;已完成的 Map 任务需要重新执行是因为考虑到 Map 任务产生的中间文件是保存在 Worker 本地磁盘上的，所以如果该 Worker 失效，并不能确保数据还是有效的。而已完成的 Reduce 任务不重新执行是因为 Reduce 任务的输出结果是保存在全局的文件系统 (GFS) 中的，所以不需要再重新执行。&lt;/p&gt;
&lt;h4&gt;4.4.2 Master 失效&lt;/h4&gt;
&lt;p&gt;MapReduce 中 Master 是单点的，对于 Master 失效的容错处理方案，论文中采用的是定时将 Master 节点的数据写下来，在 Master 挂掉之后，启动一个新的 Master 节点，然后读取上个检测点数据恢复服务。&lt;/p&gt;
&lt;h4&gt;4.4.3 失效处理机制&lt;/h4&gt;
&lt;p&gt;当用户提供的 Map 和 Reduce 函数是确定性函数时，MapReduce 框架需要保证重复执行时，函数的输出都是一致的，就好像整个程序没有发生错误一样。在 MapReduce 中，主要是通过对 Map 和 Reduce 任务的输出内容进行原子提交来实现这个特性。&lt;/p&gt;
&lt;p&gt;首先每个进行中的任务都会将其输出写到一个私有的临时文件，Reduce 任务会产生一个这样的文件，而 Map 任务则会产生 R 个，R 与 Reduce 任务数量一致。不同任务完成后的处理不一样:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Map 任务完成后，会将 R 个临时文件的名字发生给 Master ，由 Master 记录下来作为任务的状态数据，已完成的 Map 任务发送的消息将会被 Master 忽略；&lt;/li&gt;
&lt;li&gt;Reduce 任务完成后，Worker 会将临时文件重命名为最终输出的文件名称，对于 Reduce 任务重复执行在多个机器的情况，MapReduce 框架主要是依赖底层的 GFS 来保证文件重命名操作的原子性；&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.5 局部性&lt;/h3&gt;
&lt;p&gt;在谷歌当时的计算集群中，网络带宽是一个相对受限的资源，所有数据在计算时都通过网络进行传输会导致网络带宽成为系统的瓶颈。 MapReduce 的优化方案比较巧妙，主要是通过尽量让数据文件和执行任务在同样的机器上，减少需要通过网络传输的数据数量来解决。具体是:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首先输入数据主要是通过 GFS 管理，大文件会被分割为 64 MB 的小文件，并且每个小文件都会有多个拷贝，通常是 3 拷贝；&lt;/li&gt;
&lt;li&gt;Master 节点会记录每个小文件的位置信息，并且作为调度 Map 任务参考依据，尽量将 Map 任务调度到存储了该文件拷贝数据的服务器上执行；&lt;/li&gt;
&lt;li&gt;如果 Map 任务无法调度到存储了该文件数据服务器上执行，则尝试会将任务调度到一个接近存储了该文件任何一份拷贝数据的服务器上执行，比如同一个网络交换机下的服务器；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;整体的考虑是，尽量不需要进行数据传输，如果无法达成，则降低数据传输的成本。&lt;/p&gt;
&lt;h3&gt;4.6 任务粒度&lt;/h3&gt;
&lt;p&gt;MapReduce 的一大设计思路是将一个大数据的处理任务分割为大量的小任务，让大量的服务器来并行处理，以达到加速计算的目的，这也是我们在算法中常见的 &lt;code&gt;divide and conquer&lt;/code&gt; 方法。在实践中，任务的粒度也是需要考虑的内容，论文中对此进行了相关的论述。&lt;/p&gt;
&lt;p&gt;以 Map 阶段的数量为 M，Reduce 阶段的数量为 R，理想状态下，M 和 R 的值应该远远大于 Worker 服务器的数量，这样才方便对 Worker 执行任务动态达到均衡的效果，并且在出现 Worker 节点失效的情况下，也可以加速恢复。&lt;/p&gt;
&lt;p&gt;在 MapReduce 中，因为 Map 和 Reduce 任务的状态等信息都存在 Master 节点的内存中，所以实际上根据 Master 节点的硬件资源是存在一个上限的。在谷歌的实践中，通常是 200000 个 Map 任务和 5000 个 Reduce 任务，运行在 2000 台 Worker 服务器上。&lt;/p&gt;
&lt;h3&gt;4.7 任务备份&lt;/h3&gt;
&lt;p&gt;一个完整的 MapReduce 计算任务需要所有切分的 Map 和 Reduce 任务全部完成才结束。在实践中，常见的一个导致 MapReduce 任务执行时间过长的情况是某个机器上执行的一些 Map 或者 Reduce 任务卡住了，导致任务一直无法完成。比如磁盘出现异常的服务器可能会导致磁盘读取性能大幅度下降，影响到了任务的执行。&lt;/p&gt;
&lt;p&gt;MapReduce 中设计了一种任务备份机制来降低这种异常的影响。主要实现是，当 MapReduce 计算操作接近完成时，对于当前还在执行中的 Map/Reduce  任务，Master 节点会对应调度一个备份的任务执行。原始的任务和备份的任务中，只要有一个执行完毕，Master 就会将该任务标记为完成。&lt;/p&gt;
&lt;p&gt;任务备份机制的关键在于开始备份任务重新执行的阈值，这个根据不同的计算任务特性，应该有不同的具体值。此外，考虑到备份执行任务会导致计算资源的使用增加，所以需要在资源增加和计算加速之间取个平衡点。&lt;/p&gt;
&lt;h3&gt;4.8 优化扩展&lt;/h3&gt;
&lt;p&gt;除了以上提及的具体实现之外，MapReduce 同时还存在着一些特殊的优化扩展点，论文中也提及了不少，值得参考。&lt;/p&gt;
&lt;h4&gt;4.8.1 分片函数&lt;/h4&gt;
&lt;p&gt;在使用 MapReduce 时，通常是由用户来指定想要的 Reduce 任务和输出文件数量，数据通过使用一个针对 Map 产生的中间文件的 Key 进行分片的函数来进行分片处理。默认的分片函数是 &lt;code&gt;hash(key) mod R&lt;/code&gt; ，这个函数可以产生相对均衡的分片结果。但是在实际应用中，不同的任务类型可能会对分片有不同的一个实际需求，比如对于 URL 的 Key，通常我们希望同一个 Host 的结果会到同一个文件中。MapReduce 库中提供了一个特殊的分片函数来支持这个特性，比如 &lt;code&gt;hash(Hostname(urlkey)) mod R&lt;/code&gt; 可以满足刚刚提到的那个需求。&lt;/p&gt;
&lt;h4&gt;4.8.2 顺序保证&lt;/h4&gt;
&lt;p&gt;MapReduce 保证在单个分片中，中间内容 Key/Value 对是以 Key 的升序排序处理的。这个顺序保证方便生成每个分片有序的输出文件，方便实现支持高效的随机查找 Key。&lt;/p&gt;
&lt;h4&gt;4.8.3 组合函数&lt;/h4&gt;
&lt;p&gt;在一些场景中，Map 任务产生的中间 Key/Value 数据可能会存在比较大的重复性，比如计算单词出现次数的任务，初始实现是每个单词输出一个 &lt;Word, 1&gt; 的数据，同一个任务对于同一个单词会产生大量这样的键值对数据。而每个键值对数据都需要通过网络传输到单个 Reduce 任务进行处理。&lt;/p&gt;
&lt;p&gt;在 MapReduce 中，对于这种情况，框架支持用户指定一个可选的组合函数来在数据被通过网络发送前对同样的 Key 进行局部的数据合并处理。组合函数是在每个执行 Map 任务的机器上执行，通常代码实现和用户的 Reduce 函数类似，区别在于 MapReduce 对执行结果处理方式。Reduce 函数的输出会写到一个最终输出文件中，而组合函数的输出则是写到一个将要发生给 Reduce 任务的中间文件。&lt;/p&gt;
&lt;h4&gt;4.8.4 输入输出类型&lt;/h4&gt;
&lt;p&gt;MapReduce 中支持不同的输入输出类型，提供了相关 reader 接口来支持从文本到用户自定义的类型。数据不一定来自文件，也可以来自数据库或者其他内存数据，只需要实现对应的 reader 就可以了。输出也类似，有不同的输出类型支持，也支持用户自定义输出类型。&lt;/p&gt;
&lt;h4&gt;4.8.5 副作用&lt;/h4&gt;
&lt;p&gt;有时候用户可能会发现在 Map/Reduce 执行中输出一些临时辅助性的文件比较方便有用，这样 Map/Reduce 操作就是包含副作用的。MapReduce 依赖应用的 Writer 来保证这些副作用的原子性和幂等。通常应用会将内容写到一个临时文件，在完全生成后原子地重命名临时文件。&lt;/p&gt;
&lt;p&gt;MapReduce 对单个任务产生多个输出文件支持二段提交来实现写文件的原子性，所以这种任务需要输出是确定性的，多次执行不会产生变化。&lt;/p&gt;
&lt;h4&gt;4.8.6 跳过坏记录&lt;/h4&gt;
&lt;p&gt;有时候用户的代码中可能会存在 bug 导致任务执行时处理特定的记录会必然崩溃，导致任务无法完成，如果这些 bug 是第三方库导致的也不好直接修复。对于一些可以允许跳过一些记录不对整体计算产生太大影响的任务来说，MapReduce 支持提供一个可选的执行模式，由 MapReduce 检测这些比如导致执行崩溃的记录，然后在下次执行时跳过这些记录继续执行。&lt;/p&gt;
&lt;p&gt;实现上，每个 Worker 进程都会注册一个信号处理捕获内存段异常和 bug 错误信息。在 Worker 执行 Map/Reduce 操作之前，MapReduce 会存储操作参数的序列号到一个全局变量中。当执行出现异常崩溃时，信号处理器会发送这个参数的序列号到 Master 节点。当 Master 发现某个特定的记录出现错误超过一次时，就会在下一次重新执行时指示该记录应该被跳过。&lt;/p&gt;
&lt;h4&gt;4.8.7 本地执行&lt;/h4&gt;
&lt;p&gt;一个跑在数千台服务器上并行执行的 MapReduce 任务是非常难以调试的。为了方便调试，谷歌实现了一个在单台机器上顺序执行任务函数的 MapReduce 库版本，用户可以自行控制特定 Map 任务的执行。用户通过一个特殊的标记启动程序，就可以使用场景的调试和测试工具对任务进行处理。&lt;/p&gt;
&lt;h4&gt;4.8.8 状态信息&lt;/h4&gt;
&lt;p&gt;Master 节点运行了一个内部的 HTTP 服务器，暴露了一系列的状态页面提供给管理员查看。包含以下内容：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;MapReduce 计算的进度: 完成和进行中状态的任务数量、输入数据大小、中间数据的大小，输出数据的大小、处理速率等等；&lt;/li&gt;
&lt;li&gt;到标准错误信息的连接及每个任务输出的标准输出文件；&lt;/li&gt;
&lt;li&gt;失败的 Worker 节点，失败的 Map/Reduce 任务信息；&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;4.8.9 计数器&lt;/h4&gt;
&lt;p&gt;在计算任务执行过程中，基于统计等需求，总是需要对一些事件或者情况发生的次数进行计数处理。MapReduce 中提供了一个计数器的机制，用户可以在用户代码中创建一个 Counter，并在 Map 等任务处理中根据具体业务增加 Counter 值。MapReduce 框架会从 Worker 节点定时把某个任务的 Counter 信息在 ping 响应时汇报给 Master 节点，当一个任务执行完毕时，Master 节点会聚合计数器信息返回给用户代码。同时，Master 节点针对重复执行的任务汇报的计数器信息也会进行过滤处理，避免同个任务多次计数。计数器信息也会展示在 MapReduce 的状态页面上。&lt;/p&gt;
&lt;h2&gt;5 总结&lt;/h2&gt;
&lt;p&gt;本课主要还是针对分布式系统做了一个概括性的介绍，包含了什么是分布式系统，为什么需要分布式系统以及在当前，分布式系统存在那些挑战，我们整个课程关注的是分布式系统中哪些内容。通过本课的学习，基本能对分布式系统的领域及问题有一个初步的了解。&lt;/p&gt;
&lt;p&gt;MapReduce 论文是一篇相对旧的论文，也是一篇非常经典的分布式系统方面的论文。从论文里面，我们可以看到，基于一个简单的模型，加上对问题域的简化，我们可以利用分布式系统来解决一个传统意义上单机非常难以解决的问题。论文中除了系统的架构和模型值得我们去关注之外，整个系统对于容错、恢复处理等的机制，也是很值得我们去参考的。在当前的业界中，这些思想仍然具备很大的价值。&lt;/p&gt;</content><category term="mit6.824"></category><category term="distributed-system"></category><category term="paper"></category></entry><entry><title>关于 Python iterator 协议的一点思考</title><link href="https://blog.tonychow.me/thoughts-about-python-iterator.html" rel="alternate"></link><published>2015-04-06T00:00:00+08:00</published><updated>2015-04-06T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2015-04-06:/thoughts-about-python-iterator.html</id><summary type="html"></summary><content type="html">&lt;p&gt;Python 中有好几种容器或者序列类型：&lt;code&gt;list&lt;/code&gt; &lt;code&gt;tuple&lt;/code&gt; &lt;code&gt;dict&lt;/code&gt; &lt;code&gt;set&lt;/code&gt; &lt;code&gt;str&lt;/code&gt;，对于这些类型中的内容，往往需要一种方式去遍历获取它们来进行操作。所以 Python 提供了迭代器的类型来对这些类型的内容进行迭代遍历，迭代类型新增于 Python 2.2。&lt;/p&gt;
&lt;p&gt;迭代器类型指的是遵循迭代器协议的类型，对于 Python2.x 版本来说就是实现了 &lt;code&gt;__iter__&lt;/code&gt; 和 &lt;code&gt;next&lt;/code&gt; 函数的对象类型。如果一个对象实现了迭代器协议，则可以用 &lt;code&gt;for&lt;/code&gt; 语句遍历这个对象的内容。其中 &lt;code&gt;__iter__&lt;/code&gt; 函数返回一个迭代器对象，而 &lt;code&gt;next&lt;/code&gt; 函数则需要返回容器的下一个内容，如果没有下一个则抛出 StopIteration 异常，这个异常在 &lt;code&gt;for ... in&lt;/code&gt; 语句中将会被捕获然后结束迭代。迭代器协议详细内容可以查看 &lt;a href="https://www.python.org/dev/peps/pep-0234/"&gt;PEP234&lt;/a&gt; 。Python3.x 将 &lt;code&gt;next&lt;/code&gt; 函数改成了 &lt;code&gt;__next__&lt;/code&gt; 函数，以和其他内置的函数保持一致的双下划线风格。&lt;/p&gt;
&lt;p&gt;以前看迭代器协议的时候，经常可以看到这样一个实现：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__iter__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;StopIteration&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;最近看到了这么一个写法：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IterObj&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__iter__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这个写法没有 &lt;code&gt;next&lt;/code&gt; 函数了，初看起来好像没有完全实现迭代器协议的样子，但是仔细考虑下的话，&lt;code&gt;__iter__&lt;/code&gt; 函数内部调用了内置函数 &lt;code&gt;iter&lt;/code&gt; ，实际上是返回了一个迭代器对象，而这个迭代器对象当然是实现了迭代器协议的。所以第二种写法也是完全可以的，并且对比第一种写法来说更加简单。&lt;/p&gt;
&lt;p&gt;在官方文档 &lt;a href="https://docs.python.org/2/library/stdtypes.html?highlight=iterator#iterator-types"&gt;迭代器类型&lt;/a&gt; 中可以看到，对于 &lt;code&gt;list&lt;/code&gt; &lt;code&gt;dict&lt;/code&gt; 等容器对象来说，它们的 &lt;code&gt;__iter__&lt;/code&gt; 函数返回的不是其自身，而是一个迭代器对象： &lt;code&gt;container.__iter__()&lt;/code&gt; 。所以当一个容器对象需要提供迭代的功能的时候，不是把这个容器对象变成一个迭代器对象，而是返回一个迭代器对象，将迭代的功能委托给这个迭代器对象。所以上面两种写法的区别在于一个是实现了迭代器对象，一个是实现了可迭代的容器对象。所以第二种写法如果稍稍微修改下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IterObj&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__iter__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这样就利用了之前定义的迭代器对象 &lt;code&gt;Reverse&lt;/code&gt; 来给对象 &lt;code&gt;IterObj&lt;/code&gt; 提供了反向迭代的功能。可以看到，这样的处理方式将迭代的逻辑和容器对象分离了，更加的灵活，容器对象本身也更加精简。&lt;/p&gt;
&lt;p&gt;Python 的迭代器协议统一了  Python 中容器对象进行迭代的方式，另一方面来说，也为用户自定义类型添加迭代的功能添加了方便的实现方式，所以无论是从语言的标准化来说还是从用户使用角度的来说都是非常有用的一个协议。&lt;/p&gt;</content><category term="python"></category><category term="iterator"></category><category term="thoughts"></category></entry><entry><title>Python 核心编程读书笔记 Day7</title><link href="https://blog.tonychow.me/corepython-reading-notes-how-day7.html" rel="alternate"></link><published>2014-07-18T00:00:00+08:00</published><updated>2014-07-18T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2014-07-18:/corepython-reading-notes-how-day7.html</id><summary type="html"></summary><content type="html">&lt;p&gt;今天把剩下的 20-23 章的内容阅读完毕了，这几章也是与 Python 相关的高级内容，包括 Web 编程，数据库接口和 Python 扩展等内容，下面稍微总结下每章的内容。&lt;/p&gt;
&lt;h3&gt;第二十章：Web 编程&lt;/h3&gt;
&lt;p&gt;这一章所谓的 Web 编程内容实际上讲的是利用 urllib 模块进行的 Web 相关的编程，同时也讲到了利用 cgi 模块进行的
原始的 cgi 编程。从内容来说的话主要介绍了 urllib 和 cgi 模块的一些使用。cgi 是比较早期的服务器处理客户端的
请求的方式，目前的 Python Web 编程已经不使用这种技术了。但是总的来说，过去和现在的 Web 编程总是接收请求，然后返回数据给客户端的模式。此外，鉴于 HTTP 协议的无状态性质，可以利用 cookie 的方式来在客户端和服务器端进行一定的状态判断。&lt;/p&gt;
&lt;h3&gt;第二十一章：数据库编程&lt;/h3&gt;
&lt;p&gt;无论是什么形式的应用程序，总会涉及到数据持久化的内容，而相比于普通的文件持久化或者 Python 提供的其他持久化的方式模块，利用数据库进行数据的持久化更适合复杂的数据和大型的系统。这章主要讲了 Python 利用数据库进行数据的持久化的内容，其中的数据库在本章主要指关系型数据库。Python 关于数据库这方面的内容，有一点让我觉得很牛逼的就是，它统一了一个数据库接口，也就是 PEP249 中规定的 Python 的 DB-API。这个规范规定了 Python 在数据库操作方面的一些通用的做法，任何依照这个规定实现的不同数据库的接口库都会表现出一致的操作方式。这样就大大地减少了程序员操作不同数据库的差异程度。虽然不是所有的接口都完全遵守，但是大体上是一致的。下面有几点：&lt;/p&gt;
&lt;p&gt;1.&lt;code&gt;connect&lt;/code&gt; 方法连接数据库；&lt;/p&gt;
&lt;p&gt;2.&lt;code&gt;close&lt;/code&gt; 方法关闭数据库连接；&lt;/p&gt;
&lt;p&gt;3.&lt;code&gt;commit&lt;/code&gt; 方法提交当前事务，对于不支持事务或者默认为立即执行的数据库来说，这个方法什么也不做；&lt;/p&gt;
&lt;p&gt;4.&lt;code&gt;rollback&lt;/code&gt; 方法取消当前事务，回滚到之前的状态；&lt;/p&gt;
&lt;p&gt;5.&lt;code&gt;cursor&lt;/code&gt; 获得一个游标对象，进行数据库的各种操作；&lt;/p&gt;
&lt;p&gt;6.游标对象具有 &lt;code&gt;execute&lt;/code&gt; 和 &lt;code&gt;executemany&lt;/code&gt; 方法执行 SQL 查询或者操作；&lt;/p&gt;
&lt;p&gt;7.游标对象具有 &lt;code&gt;fetchone&lt;/code&gt;，&lt;code&gt;fetchmany&lt;/code&gt; 和 &lt;code&gt;fetchall&lt;/code&gt; 方法获取查询的结果；&lt;/p&gt;
&lt;p&gt;8.ORM 框架是指对象关系映射框架，可以将一个对象映射为数据库中的数据内容，向使用者屏蔽了底层的数据库操作；&lt;/p&gt;
&lt;h3&gt;第二十二章：扩展 Python&lt;/h3&gt;
&lt;p&gt;这里讲到的扩展 Python 主要讲的是针对 CPython 的扩展。因为 CPython 的实现语言是 C ，所以我们也可以根据一定的方式，编写 C 语言程序，扩展 CPython 的功能。下面是要点：&lt;/p&gt;
&lt;p&gt;1.扩展 CPython 的程序需要包含 Python.h 头文件；&lt;/p&gt;
&lt;p&gt;2.扩展模块中的函数如果想要在 Python 中被调用，需要进行函数的包装，包装函数的模式为 PyObject * Module_func()；&lt;/p&gt;
&lt;p&gt;3.在 Python 中调用扩展模块的函数时，传入的是 Python 的数据类型，所以需要用 &lt;code&gt;PyArg_Parse*&lt;/code&gt; 系列函数将参数转换为 C 的数据类型；&lt;/p&gt;
&lt;p&gt;4.同样地，扩展模块的函数返回的是 C 的数据类型，也需要将这个返回结果通过　&lt;code&gt;Py_BuildValue&lt;/code&gt; 进行类型的转换然后再返回；&lt;/p&gt;
&lt;p&gt;5.扩展模块的 C 源码编写好后可以通过 distutils 模块对其进行编译和添加进 Python 的模块目录中；&lt;/p&gt;
&lt;p&gt;6.注意在扩展模块中如果想利用 Python 的对象，需要考虑引用计数的问题；&lt;/p&gt;
&lt;p&gt;7.由于扩展模块的代码最终也会在 Python 解释器中执行，所以同样也会受到 GIL 的影响；&lt;/p&gt;
&lt;h3&gt;第二十三章：其他话题&lt;/h3&gt;
&lt;p&gt;本章的内容是一些杂七杂八的内容，比如利用 Python 编写一个利用其他在线的 Web 服务的脚本程序，利用 COM 接口调用 win 平台的 office 软件还有发送邮件等内容。最后还提到了Jython 的内容，利用 Swing 进行 GUI 开发。&lt;/p&gt;
&lt;h3&gt;最终总结&lt;/h3&gt;
&lt;p&gt;花了大概一周的时间，看完了《Python 核心编程这本书》。这本书从内容来说，还是不错的，一些 Python 基本的东西都有涉及，也讲得很细，也有些经验之谈的东西也值得学习。虽然后面的章节有点凑字数的嫌疑，考虑是到面向的是 Python2.5，而现在 Python 的版本号已经跑到了 2.7 了，所以也能原谅。但是，中文版的质量真心的差强人意。不说遍布全书的各种 typo 问题(甚至连标题也出现 typo)，就一点来说，对于 Python 这种这么注重缩进的语言来说，书里的各种代码缩进乱七八糟真的好意思么。当然，考虑到这本中文版出书背后的各种八卦事情，似乎这种质量也是可以理解的。&lt;/p&gt;
&lt;p&gt;无论如何，看完这本书之后的确对 Python 有了更多的了解，或者说对 Python 的理解更加全面了，所以还是受益匪浅的。&lt;/p&gt;</content><category term="corepython"></category><category term="reading-notes"></category><category term="python"></category></entry><entry><title>Python 核心编程读书笔记 Day6</title><link href="https://blog.tonychow.me/corepython-reading-notes-how-day6.html" rel="alternate"></link><published>2014-07-17T00:00:00+08:00</published><updated>2014-07-17T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2014-07-17:/corepython-reading-notes-how-day6.html</id><summary type="html"></summary><content type="html">&lt;p&gt;今天阅读了 15-19 章的内容，前面的是 Python 中的关键重要内容，而这之后的几章内容都是 Python 的一些高级内容。所谓高级指的是这些章节描述了一些与 Python 相关的比较高的层面的内容，比如正则表达式，网络编程等等内容，下面继续总结今天的阅读笔记。&lt;/p&gt;
&lt;h3&gt;第十五章：正则表达式&lt;/h3&gt;
&lt;p&gt;在文本处理和数据处理中，正则表达式提供了一种模式匹配，搜索文本的方式。正则表达式在很多语言中都被支持，而同样 Python 也提供了对正则表达式支持的模块 re。本章的内容就是 Python 的正则表达式模块，下面是要点：&lt;/p&gt;
&lt;p&gt;1.正则表达式是一个由含有文本和特别字符组成的字符串，通过正则表达式可以描述想要匹配的内容；&lt;/p&gt;
&lt;p&gt;2.&lt;code&gt;re1|re2&lt;/code&gt; 表示匹配 re1 或者 re2；&lt;/p&gt;
&lt;p&gt;3.&lt;code&gt;.&lt;/code&gt; 表示匹配换行符 '\n' 之外的其他任何字符；&lt;/p&gt;
&lt;p&gt;4.&lt;code&gt;^&lt;/code&gt; 表示匹配字符的开始，在 &lt;code&gt;[]&lt;/code&gt; 内表示否定；&lt;/p&gt;
&lt;p&gt;5.&lt;code&gt;$&lt;/code&gt; 表示匹配字符的结尾；&lt;/p&gt;
&lt;p&gt;6.&lt;code&gt;*&lt;/code&gt; 表示匹配前面的正则表达式零次或者多次；&lt;/p&gt;
&lt;p&gt;7.&lt;code&gt;+&lt;/code&gt; 表示匹配前面的正则表达式一次或者多次；&lt;/p&gt;
&lt;p&gt;8.&lt;code&gt;?&lt;/code&gt; 表示匹配前面的正则表达式零次或者一次；&lt;/p&gt;
&lt;p&gt;9.&lt;code&gt;{N}&lt;/code&gt; 表示匹配前面的正则表达式 N 次；&lt;/p&gt;
&lt;p&gt;10.&lt;code&gt;{M, N}&lt;/code&gt; 表示匹配前面的表达式 M 次到 N 次；&lt;/p&gt;
&lt;p&gt;11.&lt;code&gt;[...]&lt;/code&gt; 表示匹配里面出现的任意字符，一个；&lt;/p&gt;
&lt;p&gt;12.&lt;code&gt;\d&lt;/code&gt; 匹配数字；&lt;/p&gt;
&lt;p&gt;13.&lt;code&gt;\w&lt;/code&gt; 匹配数字及字母；&lt;/p&gt;
&lt;p&gt;14.&lt;code&gt;\s&lt;/code&gt; 匹配任何空白符；&lt;/p&gt;
&lt;p&gt;15.&lt;code&gt;\b&lt;/code&gt; 匹配单词的边界(开始)；&lt;/p&gt;
&lt;p&gt;16.&lt;code&gt;\D&lt;/code&gt;，&lt;code&gt;\W&lt;/code&gt;，&lt;code&gt;\S&lt;/code&gt;，&lt;code&gt;\B&lt;/code&gt; 表示和小写相反，即不匹配；&lt;/p&gt;
&lt;p&gt;17.&lt;code&gt;re.search(pattern, string, flags=0)&lt;/code&gt; 表示在指定字符串中搜索指定的模式，第一次搜索到则返回匹配结果；&lt;/p&gt;
&lt;p&gt;18.&lt;code&gt;re.match(pattern, string, flags=0)&lt;/code&gt; 表示对指定字符串从字符串的开始位置尝试匹配指定模式；&lt;/p&gt;
&lt;p&gt;19.&lt;code&gt;re.findall(pattern, string[, flags])&lt;/code&gt; 表示在指定字符串中搜索所有的匹配结果；&lt;/p&gt;
&lt;p&gt;20.&lt;code&gt;re.sub(pattern, repl, string, max=0)&lt;/code&gt; 可以对匹配的结果进行替换；&lt;/p&gt;
&lt;p&gt;21.Python 的正则表达式是默认贪婪模式的，在利用通配符的时候会尝试匹配最多的字符，可以用 &lt;code&gt;?&lt;/code&gt; 来限制；&lt;/p&gt;</content><category term="corepython"></category><category term="reading-notes"></category><category term="python"></category></entry><entry><title>Python 核心编程读书笔记 Day5</title><link href="https://blog.tonychow.me/corepython-reading-notes-how-day5.html" rel="alternate"></link><published>2014-07-16T00:00:00+08:00</published><updated>2014-07-16T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2014-07-16:/corepython-reading-notes-how-day5.html</id><summary type="html"></summary><content type="html">&lt;p&gt;今天的内容是 Python 中的面向对象和 Python 的执行环境。Python 支持 OOP，虽然很多情况下 Python 直接写函数就可
可以解决大部分的问题，但是 OOP 也是 Python 中的一个重要内容。下面继续总结笔记。&lt;/p&gt;
&lt;h3&gt;第十三章：面向对象编程&lt;/h3&gt;
&lt;p&gt;本章的内容是 Python 的面向对象编程，具体来说，讲述了 Python 中关于类和 OOP 的具体内容，包括继承，类的方法等
内容，同时也涉及了 Python 中的特殊方法等类的内容。下面是要点：&lt;/p&gt;
&lt;p&gt;1.Python 中的实例方法都存在着第一个参数为 self 指示这个实例本身；&lt;/p&gt;
&lt;p&gt;2.Python 中的类方法存在着第一个参数为 cls 通常指示这个类本身；&lt;/p&gt;
&lt;p&gt;3.Python 中的 &lt;code&gt;__new__(cls,...)&lt;/code&gt; 方法才是构建实例的方法，&lt;code&gt;__init__(self,...)&lt;/code&gt; 方法是初始化实例的方法；&lt;/p&gt;
&lt;p&gt;4.Python 中的子类的构造方法会覆盖父类的构造方法，子类不存在构造方法才会调用父类的构造方法；&lt;/p&gt;
&lt;p&gt;5.Python 不支持纯虚函数或者抽象方法；&lt;/p&gt;
&lt;p&gt;6.类属性绑定到类的 &lt;code&gt;__dict__&lt;/code&gt; 中，实例属性绑定到实例的 &lt;code&gt;__dict__&lt;/code&gt; 中；&lt;/p&gt;
&lt;p&gt;7.如果实例中不存在一个和类属性同名的实例属性，则通过实例访问到的是类的属性，如果进行修改，则会在实例中保存
一个同名的实例属性存放在实例的 &lt;code&gt;__dict__&lt;/code&gt; 中，这个实例属性会屏蔽同名的类属性，注意是屏蔽不是覆盖；&lt;/p&gt;
&lt;p&gt;8.&lt;code&gt;__del__&lt;/code&gt; 是实例的析构方法，只有在真正需要对该实例进行释放内存的时候才会调用，在 Python 中也就是意味着该
实例的引用计数为 0，进行垃圾回收操作；&lt;/p&gt;
&lt;p&gt;9.类方法和实例方法也是普通的函数，和普通函数不同的是，类方法绑定了类，实例方法绑定了该实例，可以通过类调用
实例方法，但是此时实例方法没有被绑定，需要显式地传入一个实例作为第一个参数；&lt;/p&gt;
&lt;p&gt;10.静态方法是在类范围内的普通函数，不是绑定的方法，静态方法也可以通过类继承的方式由子类继承；&lt;/p&gt;
&lt;p&gt;11.类的父类保存在 &lt;code&gt;__bases__&lt;/code&gt; 类属性中；&lt;/p&gt;
&lt;p&gt;12.可以通过 &lt;code&gt;super(Cls, instance).method()&lt;/code&gt; 的方式调用父类中的方法；&lt;/p&gt;
&lt;p&gt;13.Old-style 类的 MRO 顺序是深度优先地搜索，直到找到，New-style 类的 MRO 顺序是广度优先搜索；&lt;/p&gt;
&lt;p&gt;14.可以通过 &lt;code&gt;hasattr&lt;/code&gt;，&lt;code&gt;getattr&lt;/code&gt;，&lt;code&gt;setattr&lt;/code&gt; 和 &lt;code&gt;delattr&lt;/code&gt; 等内置函数对类和实例的属性进行操作；&lt;/p&gt;
&lt;p&gt;15.Python 中的属性都是公开的，但是以下划线开始的属性会被混淆修改成为另外一个名称，显示出私有的属性；&lt;/p&gt;
&lt;p&gt;16.字典会占用大量的内存，New-style 类可以通过 &lt;code&gt;__slots__&lt;/code&gt; 属性存放实例属性，节省内存；&lt;/p&gt;
&lt;p&gt;17.New-style 的类支持 &lt;code&gt;__getattribute__&lt;/code&gt; 方法，实现了这个方法的类在属性被查找的时候都会调用这个方法；&lt;/p&gt;
&lt;p&gt;18.描述符是一种将实现了 &lt;code&gt;__get__&lt;/code&gt;，&lt;code&gt;__set__&lt;/code&gt;，&lt;code&gt;__delete__&lt;/code&gt; 特殊方法的类的实例作为另外一个类的类属性的对象
；&lt;/p&gt;</content><category term="corepython"></category><category term="reading-notes"></category><category term="python"></category></entry><entry><title>Python 核心编程读书笔记 Day4</title><link href="https://blog.tonychow.me/corepython-reading-notes-how-day4.html" rel="alternate"></link><published>2014-07-15T00:00:00+08:00</published><updated>2014-07-15T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2014-07-15:/corepython-reading-notes-how-day4.html</id><summary type="html"></summary><content type="html">&lt;p&gt;今天的主要阅读了 10-12 章的内容，这三章内容主要涉及异常，函数及模块，这几个模块
也是 Python 中比较重要的基本内容，也有相对于其他语言的独特之处，下面继续总结今
天的阅读笔记。&lt;/p&gt;
&lt;h3&gt;第十章：错误和异常&lt;/h3&gt;
&lt;p&gt;本章关注的内容是异常。异常在其他语言中也有实现，一般来说，异常处理给程序员提供
了一种在错误发生的时候对错误进行处理的方式。与其出现错误的时候，直接终止程序的
执行，不如对错误进行处理之后让程序继续执行。下面是本章要点：&lt;/p&gt;
&lt;p&gt;1.错误引发异常的时候会打断正常的程序处理流程；&lt;/p&gt;
&lt;p&gt;2.Python 异常的检测可以通过 try 语句进行，通常有 try-excetpt，try-finally模式；&lt;/p&gt;
&lt;p&gt;3.try 语句可以带多个 except ，可以处理多种异常，也可以直接多个异常放在一个元组
中；&lt;/p&gt;
&lt;p&gt;4.except Exception[, reason]；&lt;/p&gt;
&lt;p&gt;5.try-except 同样也支持 else 子句，不发生异常则执行 else 子句的语句；&lt;/p&gt;
&lt;p&gt;6.实现了 &lt;code&gt;__enter_&lt;/code&gt; 和 &lt;code&gt;__exit__&lt;/code&gt; 方法的类可以利用 with 语句；&lt;/p&gt;
&lt;p&gt;7.&lt;code&gt;__exit__&lt;/code&gt; 具有三个参数，exc_type， exc_value， exc_traceback；&lt;/p&gt;
&lt;h3&gt;第十一章：函数和函数式编程&lt;/h3&gt;
&lt;p&gt;函数在 Python 中其实也是一个对象，保存了函数的相关内容，所以在 Python 中，函
数也和普通的对象一样，可以传给一个函数，也可以作为函数的返回值返回，因此也导
至了 Python 支持一部分函数式编程的特性。以下是要点：&lt;/p&gt;
&lt;p&gt;1.Python 中的函数即使没有 return 语句，也会默认返回值为 None；&lt;/p&gt;
&lt;p&gt;2.Python 中支持默认参数，但是非默认参数需要在默认参数前；&lt;/p&gt;
&lt;p&gt;3.Python 中函数支持将参数放进元组或者字典中；&lt;/p&gt;
&lt;p&gt;4.&lt;code&gt;func(*args)&lt;/code&gt; 的形式是将参数放到元组中；&lt;/p&gt;
&lt;p&gt;5.&lt;code&gt;func(**kwargs)&lt;/code&gt; 的形式是将参数放到字典中，表示的是应对参数名及其值；&lt;/p&gt;
&lt;p&gt;6.Python 支持在函数内部定义函数，并且内部函数可以调用包含函数的局部变量；&lt;/p&gt;
&lt;p&gt;7.函数内部是一个局部空间；&lt;/p&gt;
&lt;p&gt;8.装饰器函数接受一个函数返回另外一个装饰后的函数；&lt;/p&gt;
&lt;p&gt;9.装饰器利用 &lt;code&gt;@&lt;/code&gt; 来装饰函数，相当于：&lt;code&gt;foo = deco(foo)&lt;/code&gt;；&lt;/p&gt;</content><category term="corepython"></category><category term="reading-notes"></category><category term="python"></category></entry><entry><title>Python 核心编程读书笔记 Day3</title><link href="https://blog.tonychow.me/corepython-reading-notes-how-day3.html" rel="alternate"></link><published>2014-07-13T00:00:00+08:00</published><updated>2014-07-13T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2014-07-13:/corepython-reading-notes-how-day3.html</id><summary type="html"></summary><content type="html">&lt;p&gt;今天阅读的章节是 8 和 9 章，前面的章节已经介绍了 Python 的基本的数据类型，这两章分别介绍了 Python 的条件
循环语句和文件类型。&lt;/p&gt;
&lt;h3&gt;第八章：条件和循环&lt;/h3&gt;
&lt;p&gt;这章主要就是介绍 Python 中的条件和循环语句，Python 中的条件语句有 if-else，而循环则有 while 和 for。要点：&lt;/p&gt;
&lt;p&gt;1.if 语句有 if-else 和 if-elif-elif-else 模式；&lt;/p&gt;
&lt;p&gt;2.Python 中也存在条件表达式，和其他语言的不同，是利用 if 实现的：X if C else Y；&lt;/p&gt;
&lt;p&gt;3.Python 中的 while 和其他语言的类似，而 for 循环则不一样，for 循环可以遍历可迭代对象；&lt;/p&gt;
&lt;p&gt;4.在遍历迭代器的时候，for 循环会调用迭代器的 next 方法，并且在遇到 StopIteration 异常结束遍历；&lt;/p&gt;
&lt;p&gt;5.range(start, stop, step=1) 函数可以生成一个列表；&lt;/p&gt;
&lt;p&gt;6.sorted 和 zip 函数返回一个列表，而 reversed 和 enumerate 函数则返回一个迭代器；&lt;/p&gt;
&lt;p&gt;7.else 同样可以用在 while 和 for 循环语句中，在循环结束后执行，break 则会跳出这个 else；&lt;/p&gt;
&lt;p&gt;8.迭代器对象需要实现 next 和 &lt;code&gt;__iter__&lt;/code&gt; 方法；&lt;/p&gt;
&lt;p&gt;9.列表解释：[expr for iter_var in iterable]，返回列表；&lt;/p&gt;
&lt;p&gt;10.生成器表达式：(expr for iter_var in iterable)；&lt;/p&gt;
&lt;h3&gt;第九章：文件和输入输出&lt;/h3&gt;
&lt;p&gt;本章主要关注 Python 中的文件对象及输入和输出方面，下面是要点：&lt;/p&gt;
&lt;p&gt;1.文件只是连续的字节序列；&lt;/p&gt;
&lt;p&gt;2.可以用 open 或者 file 函数打开或者创建文件，这两个函数类似；&lt;/p&gt;
&lt;p&gt;3.文件对象的 readlines 方法将会将该文件所有行都加载到内存中，打开大文件不太友好；&lt;/p&gt;
&lt;p&gt;4.xreadlines 是以迭代的方式每次读取文件的一行，不过现在可以直接对文件对象进行迭代达到一样的效果；&lt;/p&gt;
&lt;p&gt;5.readline 函数不会去除读取到的行的换行符，writelines 也不会自动添加换行符；&lt;/p&gt;</content><category term="corepython"></category><category term="reading-notes"></category><category term="python"></category></entry><entry><title>Python 核心编程读书笔记 Day2</title><link href="https://blog.tonychow.me/corepython-reading-notes-how-day2.html" rel="alternate"></link><published>2014-07-11T00:00:00+08:00</published><updated>2014-07-11T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2014-07-11:/corepython-reading-notes-how-day2.html</id><summary type="html"></summary><content type="html">&lt;p&gt;今天主要阅读了 5 - 7 章的内容，继续总结每章的内容及要点。&lt;/p&gt;
&lt;h3&gt;第五章：数字&lt;/h3&gt;
&lt;p&gt;本章介绍 Python 中的各种数字类型及其运算符和处理数字的内建函数。要点如下：&lt;/p&gt;
&lt;p&gt;1.Python 中的数字类型包括整型，长整型，布尔型，双精度浮点型，十进制浮点型和复数，都是不可变类型，对数字类型变量的变
更都会产生一个新的对象；&lt;/p&gt;
&lt;p&gt;2.现在的 Python 支持整型自动转换为长整型，不会溢出；&lt;/p&gt;
&lt;p&gt;3.Python 中只采用了双精度浮点型，不实现单精度浮点型，如果需要进行银行等系统编写可以考虑使用 Decimal 模块；&lt;/p&gt;
&lt;p&gt;4.Python 中不同类型数字运算转换规则：存在复数转换为复数，否则存在浮点数则转换为浮点数，否则存在长整数则转换为长整
数，否则都是普通整数；&lt;/p&gt;
&lt;p&gt;5.divmod 函数用于数值计算，返回一个包含商和余数的元组；&lt;/p&gt;
&lt;p&gt;6.round 函数对数值进行四舍五入取整，返回一个浮点数；&lt;/p&gt;
&lt;p&gt;7.chr 函数将 ASCII 值的数字转换为 ASCII 字符，ord 则相反；&lt;/p&gt;
&lt;h3&gt;第六章：序列：字符串，列表和元组&lt;/h3&gt;
&lt;p&gt;这一章关注的是 Python 中的序列类型，这些类型的特点是其成员有序排序，可以通过下标以类似偏移量的方式访问其成员，具体
来说这样的序列类型有三个：字符串，列表和元组。本章详细地介绍了这三个序列类型的操作符内建函数和特性等内容，以下为要
点：&lt;/p&gt;
&lt;p&gt;1.序列类型可以使用 &lt;code&gt;in&lt;/code&gt; 和 &lt;code&gt;not in&lt;/code&gt; 来判定某个元素是否属于一个序列；&lt;/p&gt;
&lt;p&gt;2.对于序列使用 &lt;code&gt;+&lt;/code&gt; 连接符会导致一个新的序列对象产生；&lt;/p&gt;
&lt;p&gt;3.序列类型支持切片操作，可以使用 seq[start:stop:step] 来进行；&lt;/p&gt;
&lt;p&gt;4.enumerate 函数接受一个可迭代对象，同样返回一个可迭代的 enumerate 对象，内容为之前对象的 index 和 item；&lt;/p&gt;
&lt;p&gt;5.字符串是不可变类型，Python 中没有字符类型，可以用长度为 1 的字符串来表达这个概念；&lt;/p&gt;
&lt;p&gt;6.Python 格式化字符：%[(name)][flags][width].[precision]typecode；&lt;/p&gt;
&lt;p&gt;7.Python 格式化字符默认右对齐，&lt;code&gt;-&lt;/code&gt; 改为左对齐，默认填充空格；&lt;/p&gt;
&lt;p&gt;8.&lt;code&gt;r&lt;/code&gt; 添加在字符串前表示为原始字符串，不需要对特殊字符进行转义；&lt;/p&gt;
&lt;p&gt;9.Unicode 字符串 encode 为 str 字符串，str 字符串 decode 为 Unicode 字符串；&lt;/p&gt;
&lt;p&gt;10.列表是可变类型，支持添加插入或者删除元素，并不会产生新的元素；&lt;/p&gt;</content><category term="corepython"></category><category term="reading-notes"></category><category term="python"></category></entry><entry><title>Python 核心编程读书笔记 Day1</title><link href="https://blog.tonychow.me/corepython-reading-notes-how-day1.html" rel="alternate"></link><published>2014-07-10T00:00:00+08:00</published><updated>2014-07-10T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2014-07-10:/corepython-reading-notes-how-day1.html</id><summary type="html"></summary><content type="html">&lt;p&gt;今天主要阅读了 1 - 4 章的内容，下面总结下每章的主要内容和一些要点。&lt;/p&gt;
&lt;h3&gt;第一章：Python 核心&lt;/h3&gt;
&lt;p&gt;书本的开始只是一些关于 Python 的常识性的内容，包括但不限于起源、各个特性、各个系统的安装
方式还有其他语言(C 语言之外)的实现方式，下面是一些要点：&lt;/p&gt;
&lt;p&gt;1.Python 是一门解释性的语言，但是却有个编译成字节码的编译过程，这一点和 Java 是类似的，
因为编译成字节码可以得到性能上的增强；&lt;/p&gt;
&lt;p&gt;2.标准的 Python 官方发行的是 C 实现的 Python 版本，被称为 CPython，此外也有其他语言实现
的版本，比如 Java 实现的 Jython，运行在 JVM 上，可以利用到 JVM 的 JIT 技术，并且可以使用
Java 的类库。此外还有 C# 语言实现的 IronPython，可以运行在 .NET 及 Mono 环境上。还有一个
基于 CPython 修改的 Stackless Python ，这个版本对 CPython 解释器进行了大量的修改，实现了
用户级别的微线程。&lt;/p&gt;
&lt;h3&gt;第二章：快速入门&lt;/h3&gt;
&lt;p&gt;第二章非常简略地过了一遍 Python 的一些特性和语言结构数据类型等，以下是要点：&lt;/p&gt;
&lt;p&gt;1.用 "Docstring" 或者 """Docstring""" 在模块，类或者函数起始添加可以实现运行时访问这个文
档字符串；&lt;/p&gt;
&lt;p&gt;2.&lt;code&gt;**&lt;/code&gt; 是乘方运算符，&lt;code&gt;//&lt;/code&gt;是取比商小的最大整数运算；&lt;/p&gt;
&lt;p&gt;3.Python 支持复数数字类型，形式类似 4 + 5j；&lt;/p&gt;
&lt;p&gt;4.元组和列表都是可以保存任意数量任意类型的 Python 对象的容器对象，并且都是从 0 开始索引
访问元素，元组可以看成只读的列表，两者都支持切片；&lt;/p&gt;
&lt;p&gt;5.Python 通过缩进来区分代码块；&lt;/p&gt;
&lt;p&gt;6.可以通过列表解释来生成一个列表；&lt;/p&gt;
&lt;h3&gt;第三章：Python 基础&lt;/h3&gt;
&lt;p&gt;这章的主要内容是基本的 Python 语法，也介绍了标识符，变量和关键字等内容。要点如下：&lt;/p&gt;
&lt;p&gt;1.可以通过 &lt;code&gt;\&lt;/code&gt; 连接多行的 Python 代码，也可以在含有小括号，中括号和花括号的时候跨行；&lt;/p&gt;
&lt;p&gt;2.Python 中，对象是通过引用传递的，将一个对象赋值给一个变量，就是将这个对象的引用赋给
这个变量；&lt;/p&gt;
&lt;p&gt;3.Python 中支持增量赋值 &lt;code&gt;+=&lt;/code&gt;，但不支持 &lt;code&gt;++&lt;/code&gt; 这种自增符；&lt;/p&gt;
&lt;p&gt;4.Python 中支持多元赋值，可以同时将多个对象赋给多个变量，这种方式的赋值等号两边其实是两个元组；&lt;/p&gt;
&lt;p&gt;5.Python 中下划线对解释器有特殊的意义， &lt;code&gt;_xx&lt;/code&gt; 表示模块私有，&lt;code&gt;__xxx__&lt;/code&gt; 表示系统定义的&lt;/p&gt;</content><category term="corepython"></category><category term="reading-notes"></category><category term="python"></category></entry><entry><title>Python 类对象与实例对象源码分析</title><link href="https://blog.tonychow.me/python-class-object-source-code.html" rel="alternate"></link><published>2013-10-02T00:00:00+08:00</published><updated>2013-10-02T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2013-10-02:/python-class-object-source-code.html</id><summary type="html"></summary><content type="html">&lt;h3&gt;一个有趣的现象&lt;/h3&gt;
&lt;p&gt;最近在翻 Python 的 Tutorial 的对象一章，随手在 Python 的交互 Shell 中敲了几段代码测试一下，发现了一个有趣的情况。代码如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestCls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;     
    &lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;say_hi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Hi!&amp;#39;&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt; 
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TestCls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;say_hi&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;Hi&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ins_new_var&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ins_new_var&lt;/span&gt;
    &lt;span class="mi"&gt;101&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TestCls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ins_new_var&lt;/span&gt;
    &lt;span class="n"&gt;Traceback&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
      &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="ne"&gt;AttributeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;TestCls&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;has&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;ins_new_var&amp;#39;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TestCls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new_var&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TestCls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new_var&lt;/span&gt;
    &lt;span class="mi"&gt;100&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new_var&lt;/span&gt;
    &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="n"&gt;这段代码中&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;定义了一个类&lt;/span&gt; &lt;span class="n"&gt;TestCls&lt;/span&gt; &lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;然后实例化了一个&lt;/span&gt; &lt;span class="n"&gt;TestCls&lt;/span&gt; &lt;span class="n"&gt;的对象&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="err"&gt;。&lt;/span&gt;&lt;span class="n"&gt;在&lt;/span&gt; &lt;span class="n"&gt;Python&lt;/span&gt; &lt;span class="n"&gt;中&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;一切皆对象&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;这是老生长谈了&lt;/span&gt;&lt;span class="err"&gt;。&lt;/span&gt;&lt;span class="n"&gt;而&lt;/span&gt; &lt;span class="n"&gt;Python&lt;/span&gt; &lt;span class="n"&gt;中的对象还有另外一个特性&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;就是可以在创建之后修改这个对象的属性和方法&lt;/span&gt;&lt;span class="err"&gt;。&lt;/span&gt;&lt;span class="n"&gt;如上所示&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;我们可以在创建了一个类对象&lt;/span&gt; &lt;span class="n"&gt;TestCls&lt;/span&gt; &lt;span class="n"&gt;和一个实例对象&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;之后&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;修改这两个对象&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;给它们分别添加了&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;new_var&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt; &lt;span class="n"&gt;和&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;ins_new_var&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt; &lt;span class="n"&gt;属性&lt;/span&gt;&lt;span class="err"&gt;。&lt;/span&gt;&lt;span class="n"&gt;从上面的运行结果可以看到&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;当我们给实例对象&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;添加属性&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;ins_new_var&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt; &lt;span class="n"&gt;之后&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;类对象&lt;/span&gt; &lt;span class="n"&gt;TestCls&lt;/span&gt; &lt;span class="n"&gt;中访问不了这个属性&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;但是对于类对象&lt;/span&gt; &lt;span class="n"&gt;TestCls&lt;/span&gt; &lt;span class="n"&gt;添加的新属性&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;new_var&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt; &lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;这个类对象的实例&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;却可以访问到&lt;/span&gt;&lt;span class="err"&gt;。&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;从 Python 代码的这个表现，我们可以推测出一些事情。那就是 Python 中，对一个对象的属性的访问会首先在这个对象的命名空间搜索，如果找不到，那就去搜索这个对象的类的命名空间，直到找到，然后取值，或者抛出没有这个属性的异常。很明显， Python 中一个对象的实例，同时还共享了这个对象的命名空间。如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;__class__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__delattr__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__dict__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__doc__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__format__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;__getattribute__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__hash__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__init__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__module__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__new__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;__reduce__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__reduce_ex__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__repr__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__setattr__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__sizeof__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;__str__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__subclasshook__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__weakref__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;ins_new_var&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;new_var&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;say_hi&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestCls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;__class__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__delattr__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__dict__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__doc__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__format__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;__getattribute__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__hash__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__init__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__module__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__new__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;__reduce__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__reduce_ex__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__repr__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__setattr__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__sizeof__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;__str__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__subclasshook__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__weakref__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;new_var&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;say_hi&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;可以看到，dir 函数搜索到的实例对象 t 和类对象 TestCls 的基本一致，但是区别在于 t 比 TestCls 多了一个 &lt;code&gt;ins_new_var&lt;/code&gt;。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__dict__&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ins_new_var&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TestCls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__dict__&lt;/span&gt;
    &lt;span class="n"&gt;dict_proxy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;__module__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;say_hi&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt; &lt;span class="n"&gt;say_hi&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt;
    &lt;span class="mh"&gt;0xb771e95c&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__dict__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__dict__&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;TestCls&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;__weakref__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__weakref__&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;TestCls&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__doc__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;new_var&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new_var&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__dict__&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ins_new_var&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;new_var&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;从这里看到，当我们试图对 &lt;code&gt;t.new_var&lt;/code&gt; 进行赋值时，t 的 &lt;code&gt;__dict__&lt;/code&gt; 增加了一个 &lt;code&gt;new_var&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;上面的推测是否正确？也许直接去查看源码会得到答案。在本文中， Python 的源码均指 CPython 源码，版本为 2.7.4。&lt;/p&gt;
&lt;p&gt;注1：一般是代码片段在上，分析在下。&lt;/p&gt;
&lt;h3&gt;数据结构&lt;/h3&gt;
&lt;p&gt;CPython 是 C 写的(很明显)，类对象和实例对象的数据结构都是 struct，定义在 CPython 源码目录的 Include/classobject.h 中：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PyObject_HEAD&lt;/span&gt;
    &lt;span class="n"&gt;PyObject&lt;/span&gt;    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cl_bases&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="cm"&gt;/* A tuple of class objects */&lt;/span&gt;
    &lt;span class="n"&gt;PyObject&lt;/span&gt;    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cl_dict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="cm"&gt;/* A dictionary */&lt;/span&gt;
    &lt;span class="n"&gt;PyObject&lt;/span&gt;    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cl_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="cm"&gt;/* A string */&lt;/span&gt;
    &lt;span class="cm"&gt;/* The following three are functions or NULL */&lt;/span&gt;
    &lt;span class="n"&gt;PyObject&lt;/span&gt;    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cl_getattr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;PyObject&lt;/span&gt;    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cl_setattr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;PyObject&lt;/span&gt;    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cl_delattr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;PyObject&lt;/span&gt;    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cl_weakreflist&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cm"&gt;/* List of weak references */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;PyClassObject&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PyObject_HEAD&lt;/span&gt;
    &lt;span class="n"&gt;PyClassObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;in_class&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="cm"&gt;/* The class object */&lt;/span&gt;
    &lt;span class="n"&gt;PyObject&lt;/span&gt;      &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;in_dict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cm"&gt;/* A dictionary */&lt;/span&gt;
    &lt;span class="n"&gt;PyObject&lt;/span&gt;      &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;in_weakreflist&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cm"&gt;/* List of weak references */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;PyInstanceObject&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这两个结构体并不复杂，除了所有 Python 对象都有的 &lt;code&gt;PyObject_HEAD&lt;/code&gt; 宏之外，类对象 PyClassObject 中还有几个属性，分别是： &lt;code&gt;cl_bases&lt;/code&gt; ，保存了这个类对象的所有父类(如果有的话)，这个属性是一个元组;&lt;code&gt;cl_dict&lt;/code&gt; ，一个字典，保存的是属于这个类对象的属性和方法;&lt;code&gt;cl_name&lt;/code&gt; ，保存的是这个类对象的名称，此外还有几个对象 &lt;code&gt;cl_getattr&lt;/code&gt;, &lt;code&gt;cl_setattr&lt;/code&gt;, &lt;code&gt;cl_delattr&lt;/code&gt; ，。而实例对象则有 &lt;code&gt;in_class&lt;/code&gt; 表示从哪个类对象实例化而来，还有 &lt;code&gt;in_dict&lt;/code&gt; 同样是一个字典对象，保存了这个实例对象的属性和方法。可以看到，一个类的实例对象保存了这个实例对象实例化自哪个类对象。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;PyObject_HEAD&lt;/code&gt; 的相关定义如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; &lt;span class="cm"&gt;/* Define pointers to support a doubly-linked list of all live heap objects. */&lt;/span&gt;
&lt;span class="cp"&gt;#define _PyObject_HEAD_EXTRA            \&lt;/span&gt;
&lt;span class="cp"&gt;struct _object *_ob_next;           \&lt;/span&gt;
&lt;span class="cp"&gt;struct _object *_ob_prev;&lt;/span&gt;

&lt;span class="cm"&gt;/* PyObject_HEAD defines the initial segment of every PyObject. */&lt;/span&gt;
&lt;span class="cp"&gt;#define PyObject_HEAD                   \&lt;/span&gt;
&lt;span class="cp"&gt;_PyObject_HEAD_EXTRA                \&lt;/span&gt;
&lt;span class="cp"&gt;Py_ssize_t ob_refcnt;               \&lt;/span&gt;
&lt;span class="cp"&gt;struct _typeobject *ob_type;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;可以看到这两个宏定义了 Python 中一个对象的常见属性，包括对象类型 &lt;code&gt;ob_type&lt;/code&gt; 和对象的引用计数 &lt;code&gt;ob_refcnt&lt;/code&gt;，这是因为 Python 的 GC 方式是引用计数。&lt;/p&gt;
&lt;h3&gt;创建函数&lt;/h3&gt;
&lt;p&gt;在 Python 中对于类对象 (PyClassObject) 和实例对象 (PyInstanceObject) 的相关函数有很多，在这里我们只是简单分析下创建类对象及实例对象的函数和关于查找属性部分的函数。&lt;/p&gt;
&lt;p&gt;注2：这里对这几个函数的代码引用不是完全的。&lt;/p&gt;
&lt;h4&gt;实例对象的创建函数&lt;/h4&gt;
&lt;p&gt;首先是创建类对象的函数：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="nf"&gt;class_new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyTypeObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;kwds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;创建类对象的函数是 &lt;code&gt;class_new&lt;/code&gt; ，参数是类型 type，还有多个参数元组对象 args 和多个关键字参数字典对象 kwds。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;bases&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;kwlist&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;bases&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;dict&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这里新建了几个 PyObject 类型的指针，分别是 name， bases 和 dict ，分别用来保存类对象的名称，继承的父类和属性方法字典。此外还有一个字符串数组 kwlist。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyArg_ParseTupleAndKeywords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;SOO&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwlist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                 &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;bases&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;PyClass_New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bases&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;然后这里是调用 &lt;code&gt;PyArg_ParseTupleAndKeywords&lt;/code&gt; 函数，这个函数的主要效果是解析参数 args 和 kwds ，得到创建新的类对象的参数 bases，dict，name，然后调用真正创建一个类对象的函数 &lt;code&gt;PyClass_New&lt;/code&gt;。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="nf"&gt;PyClass_New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;bases&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="cm"&gt;/* bases is NULL or tuple of classobjects! */&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;PyClass_New&lt;/code&gt; 函数的有三个参数，分别是父类们 bases，类的属性方法字典 dict 和 类的名称 name。&lt;/p&gt;
&lt;p&gt;接下来很长的一段代码都是对参数的解析及检查参数是否合法，比如 name 必须是一个字符串， dict 必须是一个字典等等，在这里略去。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyDict_GetItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;docstr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyDict_SetItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;docstr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Py_None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyDict_GetItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modstr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;globals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyEval_GetGlobals&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;globals&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;modname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyDict_GetItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;namestr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modname&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyDict_SetItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modstr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;检查参数 dict 中是否有 &lt;code&gt;__doc__&lt;/code&gt; 和 &lt;code&gt;__module__&lt;/code&gt; 这两个键，如果 &lt;code&gt;__doc__&lt;/code&gt; 不存在则设置并将其值设置为 &lt;code&gt;Py_None&lt;/code&gt;，如果 &lt;code&gt;__module__&lt;/code&gt; 也不存在则获取当前范围的全局变量，从中取得 &lt;code&gt;__module__&lt;/code&gt; 所对应的值，赋给这个新类对象的 &lt;code&gt;__module__&lt;/code&gt;。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bases&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;bases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyTuple_New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bases&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Py_ssize_t&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyTuple_Check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bases&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;PyErr_SetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_TypeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="s"&gt;&amp;quot;PyClass_New: bases must be a tuple&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyTuple_Size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bases&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyTuple_GET_ITEM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bases&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyClass_Check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyCallable_Check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ob_type&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;PyObject_CallFunctionObjArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ob_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bases&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;PyErr_SetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_TypeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;&amp;quot;PyClass_New: base must be a class&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;Py_INCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bases&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;检查 bases 参数是否为空，如果为空则新建一个值为 0 的元组赋给 bases。不为空，则 bases 应该是一个类对象的元组，依次对这个元组中的类对象进行检测，是否为类对象，如果不是类对象，则检测是否可调用 (callable) ，然后返回相应的错误信息或者一个可调用函数对象的执行结果(可调用)。&lt;/p&gt;
&lt;p&gt;最后如果 bases 参数合法，这个参数对象的引用计数加一。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getattrstr&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;getattrstr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyString_InternFromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;__getattr__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getattrstr&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;alloc_error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;setattrstr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyString_InternFromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;__setattr__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;setattrstr&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;alloc_error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;delattrstr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyString_InternFromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;__delattr__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delattrstr&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;alloc_error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;getattrstr&lt;/code&gt; ，&lt;code&gt;setattrstr&lt;/code&gt; 和 &lt;code&gt;delattrstr&lt;/code&gt; 是三个全局的 static PyObject 指针变量，上面这一段分别给它们赋值字符串对象。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyObject_GC_New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyClassObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;PyClass_Type&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nl"&gt;alloc_error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;Py_DECREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bases&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;给这个类对象分配内存，这个内存是在堆分配的而且受到 CPython 的 GC 管理的。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_bases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bases&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;Py_INCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;Py_XINCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_weakreflist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;将三个参数分别赋给这个新建的类对象 op。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_getattr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;class_lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;getattrstr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_setattr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;class_lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setattrstr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_delattr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;class_lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delattrstr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Py_XINCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_getattr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Py_XINCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_setattr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Py_XINCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_delattr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;_PyObject_GC_TRACK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;然后分别设置这个新类对象的 getattr , setattr 和 delattr 函数，增加这几个函数的引用计数等等，最后返回这个新建的类对象的指针。&lt;/p&gt;
&lt;h4&gt;实例对象的创建函数&lt;/h4&gt;
&lt;p&gt;实例对象 PyInstanceObject 同样也有个类似的 &lt;code&gt;instance_new&lt;/code&gt; 函数：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="nf"&gt;instance_new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyTypeObject&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;kw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;参数也和 &lt;code&gt;class_new&lt;/code&gt; 类似，三个参数分别为 type ， args 和 kw，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Py_None&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyArg_ParseTuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;O!|O:instance&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;PyClass_Type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;解析参数，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;Py_None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyDict_Check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PyErr_SetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_TypeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s"&gt;&amp;quot;instance() second arg must be dictionary or None&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;检查 dict 参数的合法性，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;PyInstance_NewRaw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;调用 &lt;code&gt;PyInstance_NewRaw&lt;/code&gt; 函数，这个才是返回新实例对象的函数：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="nf"&gt;PyInstance_NewRaw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PyInstanceObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;参数只有所实例化自的类对象和属性方法字典 dict ，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyClass_Check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PyErr_BadInternalCall&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyDict_New&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyDict_Check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;PyErr_BadInternalCall&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;Py_INCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;检查参数的合法性，如果 dict 为空 (NULL) 则调用 &lt;code&gt;PyDict_New&lt;/code&gt; 参数新建一个字典对象赋给 dict，否则检查 dict 是否是一个 CPython 的字典对象，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;inst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyObject_GC_New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyInstanceObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;PyInstance_Type&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Py_DECREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;同样是调用 &lt;code&gt;PyObject_GC_New&lt;/code&gt; 函数，给这个新建的实例对象分配内存，&lt;code&gt;PyInstance_Type&lt;/code&gt; 是一个全局的 PyTypeObject 类型的变量，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in_weakreflist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Py_INCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyClassObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in_dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;_PyObject_GC_TRACK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;最后给新建的实例对象赋值相关属性，然后返回这个新建实例对象的指针。&lt;/p&gt;
&lt;p&gt;对于 CPython 的实例对象而言，除了 &lt;code&gt;instance_new&lt;/code&gt; 之外，还有另外的一个函数也可以创建一个实例对象：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="nf"&gt;PyInstance_New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;kw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;register&lt;/span&gt; &lt;span class="n"&gt;PyInstanceObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;initstr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;PyInstance_New&lt;/code&gt; 函数也有三个参数，除了第一个是 klass 表示类对象之外，另外两个和 &lt;code&gt;instance_new&lt;/code&gt; 函数类似，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initstr&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;initstr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyString_InternFromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;__init__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initstr&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;inst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyInstanceObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;PyInstance_NewRaw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;可以看到在这里调用了 &lt;code&gt;PyInstance_NewRaw&lt;/code&gt; 函数创建一个新的实例对象，区别在于 dict 参数为 NULL ，这意味着新建的实例对象没有自己的属性和方法，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;instance_getattr2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;initstr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyErr_Occurred&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Py_DECREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyTuple_Check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
                             &lt;span class="n"&gt;PyTuple_Size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kw&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyDict_Check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
                              &lt;span class="n"&gt;PyDict_Size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;PyErr_SetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_TypeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="s"&gt;&amp;quot;this constructor takes no arguments&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;Py_DECREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;inst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;在新建的实例对象中查找初始化函数 init ，如果不存在 (init 为 NULL) 且发生错误，则返回 NULL ，没有错误则检查 arg 和 kw 这两个参数，设置错误字符串，同样将新建实例对象 inst 置为 NULL，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyEval_CallObjectWithKeywords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Py_DECREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Py_DECREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;inst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;Py_None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;PyErr_SetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_TypeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                           &lt;span class="s"&gt;&amp;quot;__init__() should return None&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;Py_DECREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;inst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;Py_DECREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;init 不为空即意味找到了初始化实例的函数，将初始化函数和参数 arg ，kw 作为参数调用，初始化这个实例对象，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;最后返回这个新建的实例对象。&lt;/p&gt;
&lt;h3&gt;查找函数与 getattr， setattr 函数&lt;/h3&gt;
&lt;p&gt;分析完创建类对象和实例对象的函数之后，我们来分析相关的查找函数，然后还有最重要的 getattr 和 setattr。类对象和实例对象都有自己特有的 getattr 和 setattr 函数，这两类函数正是 Python 中使用 dot 操作符取对象的属性值或者给对象属性赋值所调用的函数。&lt;/p&gt;
&lt;h4&gt;类对象的查找函数&lt;/h4&gt;
&lt;p&gt;首先是类对象的查找函数 &lt;code&gt;class_lookup&lt;/code&gt;，在类对象的创建函数中也曾调用这个函数：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="nf"&gt;class_lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyClassObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyClassObject&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;pclass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;class_lookup&lt;/code&gt; 函数有三个参数，分别是类对象指针 cp，查找的属性名称 name 和指向类对象指针的指针变量 pclass，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="n"&gt;Py_ssize_t&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyDict_GetItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pclass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;首先查找的是类对象 cp 的 &lt;code&gt;cl_dict&lt;/code&gt; 字典，如果找到的值 value 不为空，即已经找到了这个属性的值，则将 pclass 所指向的地址为 cp 类对象的地址，然后返回这个 value，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyTuple_Size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_bases&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;否则计算类对象 cp 的父类的个数，也就是 &lt;code&gt;cl_bases&lt;/code&gt; 元组的大小，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="cm"&gt;/* XXX What if one of the bases is not a class? */&lt;/span&gt;
        &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;class_lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyClassObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;PyTuple_GetItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_bases&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pclass&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;对 cp 的所有父类递归调用 &lt;code&gt;class_lookup&lt;/code&gt; 函数，直到找到这个 name 属性的值，返回到 v 变量，如果 v 非 NULL 则返回 v，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;否则返回 NULL ，表示查找不到这个 name 属性的值。&lt;/p&gt;
&lt;h4&gt;类对象的 getattr 函数&lt;/h4&gt;
&lt;p&gt;类对象的 getattr 函数实际上调用了 &lt;code&gt;class_lookup&lt;/code&gt;函数，如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="nf"&gt;class_getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;register&lt;/span&gt; &lt;span class="n"&gt;PyClassObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;register&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;register&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sname&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;PyClassObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;descrgetfunc&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;有两个参数，分别为类对象指针 op 和 所要获取的属性名称 name,&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyString_Check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;PyErr_SetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_TypeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;attribute name must be a string&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;首先也是检查参数的合法性，确定 name 为 PyString 对象，以防错误，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="n"&gt;sname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyString_AsString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sname&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;_&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;sname&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;_&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strcmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;__dict__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyEval_GetRestricted&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;PyErr_SetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_RuntimeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="s"&gt;&amp;quot;class.__dict__ not accessible in restricted mode&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;Py_INCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_dict&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_dict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strcmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;__bases__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Py_INCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_bases&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_bases&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strcmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;__name__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Py_None&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;
                &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;Py_INCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这一段首先是检查要获取的是否为特殊属性 &lt;code&gt;__dict__&lt;/code&gt;, &lt;code&gt;__bases__&lt;/code&gt; 和 &lt;code&gt;__name__&lt;/code&gt;，如果是则返回这个类对象的那个特殊属性。之所以作这样的检查是因为接下来就要执行 &lt;code&gt;class_lookup&lt;/code&gt; 函数查找，从上面的分析可以知道， &lt;code&gt;class_lookup&lt;/code&gt; 函数还会查找其父类，而这些特殊属性每个类对象都有的，所以先做检查可以防止返回错误的属性值，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;class_lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;PyErr_Format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_AttributeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="s"&gt;&amp;quot;class %.50s has no attribute &amp;#39;%.400s&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;PyString_AS_STRING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;sname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;通过 &lt;code&gt;class_lookup&lt;/code&gt; 函数查找这个值，如果找不到则返回 NULL，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TP_DESCR_GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ob_type&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;Py_INCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;如果找到则尝试获取这个属性值对象的描述符，如果找到(实现了 &lt;code&gt;__get__&lt;/code&gt; 方法)，则调用这个描述符方法，因为是类对象，所以第二个参数为 NULL。最后返回值 v 。&lt;/p&gt;
&lt;h4&gt;类对象的 setattr 函数&lt;/h4&gt;
&lt;p&gt;接下来的是类对象的 setattr 函数：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="nf"&gt;class_setattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyClassObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;class_setattr&lt;/code&gt; 函数有三个参数，分别是类对象指针 op，属性名称 name 和属性的值 v，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sname&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyEval_GetRestricted&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;PyErr_SetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_RuntimeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="s"&gt;&amp;quot;classes are read-only in restricted mode&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;注意到这里首先检查了此时是否处于受限制模式，如果处于受限制模式，此时类对象是只读的，函数将返回错误码 -1。受限模式下，不受信任的代码的执行将会受到限制。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyString_Check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;PyErr_SetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_TypeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;attribute name must be a string&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;sname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyString_AsString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;然后是同样检查 name 参数是否为一个 PyString 对象，是则根据这个字符串对象返回一个 C 中的字符串，方便下面的比较。&lt;/p&gt;
&lt;p&gt;接下来的一大段代码都是检查上面得到的这个 sname 字符串是否为特殊方法或者特殊的属性，比如 &lt;code&gt;__dict__&lt;/code&gt; 或者 &lt;code&gt;__getattr__&lt;/code&gt; 等，如果是则调用相关的函数 &lt;code&gt;set_dict&lt;/code&gt; 等，一般来说这些特殊属性是不可以修改的，所以会返回错误提示。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;rv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyDict_DelItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rv&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;PyErr_Format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_AttributeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="s"&gt;&amp;quot;class %.50s has no attribute &amp;#39;%.400s&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="n"&gt;PyString_AS_STRING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;sname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rv&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;参数 v 为空则将这个保存在类对象结构体 &lt;code&gt;cl_dict&lt;/code&gt; 成员中的 name 属性删除掉，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;PyDict_SetItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;否则，给这个属性 name 赋值 v，保存在类对象的 &lt;code&gt;cl_dict&lt;/code&gt; 中。&lt;code&gt;PyDict_SetItem&lt;/code&gt; 函数将会检测第一个字典参数中是否具有第二个参数 name 这个键，存在则更新其对应的值为 v，不存在则新建一个键，其值也是 v。&lt;/p&gt;
&lt;h4&gt;实例对象的 getattr 函数&lt;/h4&gt;
&lt;p&gt;实例对象只有一个简单地搜索属性字典 dict 的函数 &lt;code&gt;_PyInstance_Lookup&lt;/code&gt;，这个函数很简单，就是里面做了一点的检查，然后就调用了 &lt;code&gt;PyDict_GetItem&lt;/code&gt; 函数从实例对象的 dict 中获取这个值。&lt;/p&gt;
&lt;p&gt;而实例对象的 getattr 函数则更多地调用到了&lt;code&gt;class_lookup&lt;/code&gt; 函数。CPython 的源码中，关于实例对象的 getattr 和 setattr 函数灰常蛋疼，getattr 函数有三个，分别是 &lt;code&gt;instance_getattr&lt;/code&gt; ，&lt;code&gt;instance_getattr1&lt;/code&gt; 和 &lt;code&gt;instance_getattr2&lt;/code&gt;...而 setattr 函数也有两个，分别是 &lt;code&gt;instance_setattr1&lt;/code&gt; 和 &lt;code&gt;instance_setattr&lt;/code&gt;。如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="nf"&gt;instance_getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;register&lt;/span&gt; &lt;span class="n"&gt;PyInstanceObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;参数是实例对象指针 inst 和属性名称 name，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;register&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;instance_getattr1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;其实在这里就调用 &lt;code&gt;instance_getattr1&lt;/code&gt; 函数了，参数是一致的，如果 &lt;code&gt;instance_getattr1&lt;/code&gt; 函数的返回非 NULL，则直接会返回这个结果，下面一段不会执行，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in_class&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_getattr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyErr_ExceptionMatches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_AttributeError&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;PyErr_Clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyTuple_Pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyEval_CallObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Py_DECREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;如果 &lt;code&gt;isntance_getattr1&lt;/code&gt; 函数的返回值为 NULL 并且实例对象的类的 getattr 函数存在，则调用这个类对像的 getattr 函数，参数是将实例对象指针 inst 和属性名称 name 打包成的元组。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;最后返回结果。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;instance_getattr1&lt;/code&gt; 函数如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="nf"&gt;instance_getattr1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;register&lt;/span&gt; &lt;span class="n"&gt;PyInstanceObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;参数同样是 inst 和 name，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;register&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;register&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sname&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyString_Check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;PyErr_SetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_TypeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;attribute name must be a string&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;sname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyString_AsString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;例行检查参数的合法性，合法则将 name 参数转化为 C 的字符串，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sname&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;_&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;sname&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;_&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strcmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;__dict__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyEval_GetRestricted&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;PyErr_SetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_RuntimeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;&amp;quot;instance.__dict__ not accessible in restricted mode&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;Py_INCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in_dict&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in_dict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strcmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;__class__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Py_INCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in_class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in_class&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;同样是检查是否为特殊的属性，主要是以 &lt;code&gt;__&lt;/code&gt; 作为开头的属性，这里处理的只有 &lt;code&gt;__dict__&lt;/code&gt; 和 &lt;code&gt;__class__&lt;/code&gt;。如果是 &lt;code&gt;__dict__&lt;/code&gt; ，在受限模式下，会抛出错误表明不可以读取，非受限模式下则返回这个实例对象的属性字典 dict。如果是 &lt;code&gt;__class__&lt;/code&gt; ，也是对应地返回实例对象的类。 &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;instance_getattr2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyErr_Occurred&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;PyErr_Format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_AttributeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="s"&gt;&amp;quot;%.50s instance has no attribute &amp;#39;%.400s&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;PyString_AS_STRING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in_class&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;sname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;然后调用了 &lt;code&gt;instance_getattr2&lt;/code&gt; 函数，如果其返回值为 NULL 则表示不存在这个属性，输出提示，否则返回这个结果 v。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="nf"&gt;instance_getattr2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;register&lt;/span&gt; &lt;span class="n"&gt;PyInstanceObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;register&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;PyClassObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;descrgetfunc&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;同样的， &lt;code&gt;instance_getattr2&lt;/code&gt; 函数也是有两个参数 inst 和 name，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyDict_GetItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in_dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Py_INCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;首先在这个实例对象的 in_dict 中查找这个属性，如果找到则直接返回其值，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;class_lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in_class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;没有找到则去查找这个实例对象的类对象 &lt;code&gt;in_class&lt;/code&gt;，通过上面对 &lt;code&gt;class_lookup&lt;/code&gt; 函数的分析我们可以知道，这个查找会一直从实例对象所属的类，其类的父类，父类的父类一直搜索，直到搜索完毕。如果找到了，则返回这个属性的值对象。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Py_INCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TP_DESCR_GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ob_type&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in_class&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="n"&gt;Py_DECREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;在这里同样也试图获取这个实例对象对应类型的描述符方法，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;返回结果 v ，有值或者 NULL。&lt;/p&gt;
&lt;p&gt;从对上面三个 getattr 函数的分析可以看到，其实这三个函数各有其功能，比如 &lt;code&gt;instance_getattr1&lt;/code&gt; 处理的是特殊属性，而 &lt;code&gt;instance_getattr2&lt;/code&gt; 则是对应普通的属性，会一直搜索到其所属的类和其类的父类等等。如果这两个函数都没有结果，则会调用其类的 getattr 函数。&lt;/p&gt;
&lt;p&gt;所以这三个函数其实是有其各自的职责的，当然它们三个是可以合并起来成为一个大函数的，但是估计就是不希望看到一个大函数的出现所以才分散为三个函数，这样职责更小更分明。&lt;/p&gt;
&lt;h4&gt;实例对象的 setattr 函数&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="nf"&gt;instance_setattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyInstanceObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sname&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;instance_setattr&lt;/code&gt; 函数有三个参数，毫无疑问分别是实例对象指针 inst ，属性名称 name 和值 v，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyString_Check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;PyErr_SetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_TypeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;attribute name must be a string&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;sname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyString_AsString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;同样，惯例检查 name 参数的合法性，合法则转化为 C 的字符串类型变量，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sname&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;_&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;sname&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;_&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Py_ssize_t&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyString_Size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sname&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;_&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;sname&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="mi"&gt;-2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;_&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;判断是否为特殊属性，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strcmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;__dict__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyEval_GetRestricted&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;PyErr_SetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_RuntimeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="s"&gt;&amp;quot;__dict__ not accessible in restricted mode&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyDict_Check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;PyErr_SetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_TypeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="s"&gt;&amp;quot;__dict__ must be set to a dictionary&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="n"&gt;tmp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in_dict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="n"&gt;Py_INCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in_dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="n"&gt;Py_DECREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;是 &lt;code&gt;__dict__&lt;/code&gt; 则检查是否为受限模式，检查传入的 v 参数是否为合法的 PyDict 对象，如果是则将 v 赋值给实例对象的 &lt;code&gt;in_dict&lt;/code&gt;。可以注意到，这里用了一个 tmp 变量来保存实例对象之前的 &lt;code&gt;in_dict&lt;/code&gt; 变量，然后将其引用计数减一。    &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strcmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;__class__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyEval_GetRestricted&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;PyErr_SetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_RuntimeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;&amp;quot;__class__ not accessible in restricted mode&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyClass_Check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;PyErr_SetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_TypeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="s"&gt;&amp;quot;__class__ must be set to a class&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;tmp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in_class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Py_INCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyClassObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Py_DECREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;如果是 &lt;code&gt;__class__&lt;/code&gt; 和上面的操作类似。通过这一段代码，我们可以看到在非受限模式的情况下，一个实例对象的类是可以被动态修改的。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in_class&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_delattr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in_class&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_setattr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;如果参数 v 为 NULL，则表示要将实例对象的这个属性删除掉，试图去获取实例对象所对应的类对象的 delattr 函数。v 不为 NULL 则获取类对象的 setattr 函数，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;instance_setattr1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;如果没有获取到任何的函数，则将会调用 &lt;code&gt;instance_setattr1&lt;/code&gt; 函数。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyTuple_Pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyTuple_Pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyEval_CallObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;无论得到的是类对象的 delattr 还是 setattr 函数，这里将会调用这个函数，区别在于调用 delattr 函数参数元组只有 inst 和 name 而调用 setattr 函数参数则是多了一个参数 v。根据上面对类对象的 setattr 的分析可以知道，如果这个类有 setattr 函数，则将会调用它的 setattr 函数。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="n"&gt;Py_DECREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Py_DECREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;执行成功则返回 0。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="nf"&gt;instance_setattr1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyInstanceObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;rv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyDict_DelItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in_dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rv&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;PyErr_Format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_AttributeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="s"&gt;&amp;quot;%.50s instance has no attribute &amp;#39;%.400s&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="n"&gt;PyString_AS_STRING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in_class&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cl_name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                         &lt;span class="n"&gt;PyString_AS_STRING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rv&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;如果参数 v 为空，则表示删除这个属性，所以将会调用 &lt;code&gt;PyDict_DelItem&lt;/code&gt; 函数将这个属性从实例对象的 dict 字典中删除，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;PyDict_SetItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in_dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;否则就直接调用 &lt;code&gt;PyDict_SetItem&lt;/code&gt; 函数更新 dict 中的这个值或者添加进 dict 中。&lt;/p&gt;
&lt;p&gt;从上面对这两个 setattr 函数的分析，同样可以知道，这两个函数各自有其职责。&lt;code&gt;instance_setattr&lt;/code&gt; 主要是对特殊属性进行处理或者是调用其类对象的 setattr 或者 delattr 函数，而 &lt;code&gt;instance_setattr1&lt;/code&gt; 函数则是对这个实例对象的 dict 进行 &lt;code&gt;set_item&lt;/code&gt; 或者 &lt;code&gt;del_item&lt;/code&gt; 操作。&lt;/p&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;p&gt;其实写到后面已经有点头大了，引用了一大堆源码更像是给源码注释了。但是既然已经写了，那就当给源码注释把它给写完了。&lt;/p&gt;
&lt;p&gt;虽然是罗嗦了一堆，但是通过这个分析过程，对于文章开头的那几段代码的情况还是很清晰的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首先，给实例对象 t 添加一个属性 &lt;code&gt;ins_new_var&lt;/code&gt; 则将会保存到 t 的 &lt;code&gt;__dict__&lt;/code&gt; 中;&lt;/li&gt;
&lt;li&gt;而当试图在类对象 TestCls 中取 &lt;code&gt;ins_new_var&lt;/code&gt; 的时候，只会去搜索这个类对象的 dict 和其父类的 dict ，这肯定是找不到的，所以返回属性错误;&lt;/li&gt;
&lt;li&gt;当给类对象 TestCls 添加一个属性 &lt;code&gt;new_var&lt;/code&gt; 的时候，同样，会在 &lt;code&gt;__dict__&lt;/code&gt; 中添加一个 &lt;code&gt;new_var&lt;/code&gt; 对象;&lt;/li&gt;
&lt;li&gt;当访问 t.new_var 的时候，在 t 的命名空间中搜索不到 &lt;code&gt;new_var&lt;/code&gt; 的时候，就回去搜索其实例化自的类对象的命名空间，所以，就可以得到这个值了。&lt;/li&gt;
&lt;/ul&gt;</content><category term="python"></category><category term="source-reading"></category></entry><entry><title>斯坦福大学公开课-编程范式 笔记 1</title><link href="https://blog.tonychow.me/programming-paradigms-note-one.html" rel="alternate"></link><published>2013-07-07T00:00:00+08:00</published><updated>2013-07-07T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2013-07-07:/programming-paradigms-note-one.html</id><summary type="html"></summary><content type="html">&lt;h3&gt;前言&lt;/h3&gt;
&lt;p&gt;最近在跟一门斯坦福大学的公开课 &lt;a href="http://see.stanford.edu/see/courseInfo.aspx?coll=2d712634-2bf1-4b55-9a3a-ca9d470755ee"&gt;Programming Paradigms&lt;/a&gt; ,网易公开课也有其中文翻译版，翻译已经完成了：&lt;a href="http://v.163.com/special/opencourse/paradigms.html"&gt;斯坦福大学公开课：编程范式&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;课程内容：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Advanced memory management features of C and C++; the differences between imperative and object-oriented paradigms. The functional paradigm (using LISP) and concurrent programming (using C and C++). Brief survey of other modern languages such as Python, Objective C, and C#.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;首先涉及的是 C/C++ 的高级内存管理，内容包括 C 的各种数据类型的内存布局，malloc 和 free 的实现，等等。然后还有命令式和面向对象，函数式编程等等几种不同的编程范式及他们的差别。&lt;/p&gt;
&lt;p&gt;可以说这些内容应该是属于比较高级的内容。我之前偶尔也会接触到一些，有些书也会讲到，但是在我所在的大学的课堂上，这些内容基本上是不讲授的。在上 C 课程的时候，甚至连指针都语焉不详，更别提有一门专门的课程来讲述这些高级内容。上这个公开课刚好可以完整地补全我对这方面内容的不足，毕竟一个斯坦福大学的教授给你讲解这些内容总比自己看书效果要好得多。&lt;/p&gt;
&lt;p&gt;在这里，我要记下的是在课程中碰到的一些有趣的内容。&lt;/p&gt;
&lt;h3&gt;malloc 与 free&lt;/h3&gt;
&lt;p&gt;我们知道，malloc 函数是 C 中动态分配内存的一个函数，通过这个函数可以在堆中申请一块限定大小的内存使用。对应地，有申请内存函数就有释放内存函数，这个函数就是 free 函数。这两个函数的原型如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdlib.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;malloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;通过这两个函数的原型我们可以看到一些信息。malloc 函数的参数是一个 size_t 类型的变量 size ，而 malloc 返回的则是一个 void * 类型的指针。这不意外，因为我们知道通过 malloc 函数分配制定大小的内存成功之后，会将这块内存的首地址作为返回值返回给某个类型的指针变量。而通过 C 的自动类型转换，void * 类型的指针地址将会被转换为该指针变量类型的指针。&lt;/p&gt;
&lt;p&gt;free 函数也是只有一个参数，类型为 void * 的指针变量 ptr ，无返回值。在这里问题出现了， free 函数如果只是接受某块内存的首地址作为参数，那它是如何得知这块内存的大小？或者说，free 函数怎么知道需要被释放的内存到底有多大？这块内存的大小是必要要知道的，因为如果不知道，free 函数是无法准确地将要释放的内存释放掉，也许会将后面接着的不允许释放的内存也释放掉，也许还遗留一部分内存没有释放掉。&lt;/p&gt;
&lt;p&gt;所以，编译器，或者操作系统，肯定是提供了一种机制来告知 free 函数，这块在堆中的内存的大小。那这个机制是什么呢？&lt;/p&gt;
&lt;h3&gt;malloc 的机制&lt;/h3&gt;
&lt;p&gt;答案并不复杂，那就是在 malloc 函数返回的地址前面，有 4 字节或者 8 字节同样也是属于这块内存的内容，这几个字节中储存了该内存地址的大小。free 函数接受这个地址的时候，会回退一部分地址，根据这个结构的内容，得到该内存块的大小，然后将相关的释放掉。&lt;/p&gt;
&lt;p&gt;以上是公开课中老师的简单讲述，那实际情况是怎样？下面进行一下简单的验证。首先测试的平台是 Fedora17 ，Linux 的内核版本为 3.6.11-5.fc17.i686.PAE ，使用的 C 编译器为 GCC 4.7.2 20120921 (Red Hat 4.7.2-2) 。测试代码如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdlib.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;


&lt;span class="c1"&gt;//Test the memory alloc by malloc.&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;argc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ptr1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ptr2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cptr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;ptr1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;num1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;ptr1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;malloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;ptr2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;num2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;ptr2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;malloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;cptr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;malloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;


    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;The start of num1 memory address is %x.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ptr1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Before this address, the value is %d.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ptr1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;The start of num2 memory address is %x.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ptr2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Before this address, the value is %d.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ptr2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;The start of char memory address is %x.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cptr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Before this address, the value is %d.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;cptr&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;测试代码并不复杂，简单定义了两个 int 类型的指针变量和一个 char * 类型的指针变量，然后用 malloc 函数分配一定数量的内存，返回的内存块首地址赋值给这三个变量，然后输出这三个内存块首地址前一个位置的值。注意对于 char * 类型的地址，首先强制转换成 int * 类型的再进行 -1 操作。&lt;/p&gt;
&lt;p&gt;代码的输出结果如下所示：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;╰ ➤ ./a.out 
The start of num1 memory address is 9922008.
Before this address, the value is 2057.
The start of num2 memory address is 9922810.
Before this address, the value is 4105.
The start of char memory address is 9923818.
Before this address, the value is 1033.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;通过代码我们可以知道，ptr1 申请的内存块大小为 512  * 4 = 2048，ptr2 申请的内存块大小为 1024 * 4 = 4096，cptr 申请的内存块大小为 1024 * 1 = 1024，以上单位均为字节。根据输出，有如下计算：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mi"&gt;2057&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2048&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;
&lt;span class="mi"&gt;4105&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;
&lt;span class="mi"&gt;1033&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;可见，如果 malloc 函数返回的地址前一个字节保存了该内存块的整体大小，那可以推测到，其中 9 个字节作为额外的结构，保存了这块内存的信息，以提供给其他函数比如 free 函数利用。当然这只是一个推测，真实的情况需要深入到 glibc 的库的 malloc 和 free 的源码中。&lt;/p&gt;
&lt;p&gt;实际上，老师也说了，不同的编译器的实现是不同的，比如，可以参考下这篇文章：&lt;a href="http://www.cnblogs.com/sinaxyz/archive/2012/08/20/2647631.html"&gt;malloc/new函数及malloc()的一种简单原理性实现&lt;/a&gt; 。&lt;/p&gt;
&lt;p&gt;参考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://stackoverflow.com/questions/1518711/c-programming-how-does-free-know-how-much-to-free"&gt;C programming : How does free know how much to free?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://c-faq.com/malloc/freesize.html"&gt;comp.lang.c FAQ list · Question 7.26&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="opencourse"></category><category term="programming-paradigms"></category><category term="notes"></category></entry><entry><title>Linux中fork系统调用分析</title><link href="https://blog.tonychow.me/syscall-fork-in-linux.html" rel="alternate"></link><published>2013-06-27T00:00:00+08:00</published><updated>2013-06-27T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2013-06-27:/syscall-fork-in-linux.html</id><summary type="html"></summary><content type="html">&lt;h3&gt;1 相关概念及简单分析&lt;/h3&gt;
&lt;p&gt;在这一部分，我将会提及相关的概念比如进程，进程空间等，同时也对 fork 系统调用过程进行简单的文字描述。&lt;/p&gt;
&lt;h4&gt;1.1 进程&lt;/h4&gt;
&lt;p&gt;操作系统是在计算机硬件和应用程序或者用户程序之间的一个软件层，它通过对硬件资源的抽象，对应用程序隐藏了复杂的硬件资源，状态及操作，同时也隔离了应用程序和硬件资源，防止应用软件随意地操作硬件而带来的安全隐患。操作系统为应用程序提供了几种重要的抽象概念，进程就是操作系统中最基础的抽象概念之一。&lt;/p&gt;
&lt;p&gt;通常情况下，我们认为进程是一个程序(program)的运行实例。当一个程序存放在储存介质上的时候，它只是一个指令，数据及其组织形式的描述。操作系统可以将一个程序加载到内存中以一个进程的形式运行起来，这就是这个程序的一个运行实例。所以我们也是可以多次加载一个程序到内存中，形成该程序的多个独立的运行实例。一个进程的内容不单只是程序的执行指令，还包括了诸如打开的文件，等待的信号，内部内核数据，处理器状态，内存地址空间及内存映射等等的资源。&lt;/p&gt;
&lt;p&gt;在早期的分时系统和面向进程的操作系统(比如早期的Unix和Linux)中，进程被认为是运行的基本单位。而在面向线程的操作系统(比如Linux2.6或更高版本)中，进程是资源分配的基本单位，而线程才是运行的基本单位。进程是线程的容器，而一个进程中会有一个或多个线程。实际上，在Linux中，对线程和进程有着特别的统一实现，线程只是一种特别的进程。这在下面的分析中将会提及。&lt;/p&gt;
&lt;h4&gt;1.2 进程空间&lt;/h4&gt;
&lt;p&gt;在进程被创建的时候，操作系统同时也给这个进程创建了一个独立的虚拟内存地址空间。这个虚拟内存地址空间使得一个进程存在着它独自使用着所有的内存资源的错觉，而且这也是该进程独立的，完全不受其他进程的干扰，所以这也使得各个进程区分开来。虽然对于一个进程而言，它拥有着很大的一个虚拟内存地址空间，但是这并不意味着每个进程实际上都拥有这么大物理内存。只有在真正使用某一部分内存空间的时候，这一部分虚拟内存才会被映射到物理内存上。此外，一个进程也不是可以访问或者修改这个虚拟内存地址空间的所有地址的。一个典型的进程内存地址空间会被分为 stack，heap，text，data，bss 等多个段，如下图(来自 Unix 高级环境编程)所示，这是一个进程在Intel x86架构机器上面的进程空间的逻辑表示：&lt;/p&gt;
&lt;p&gt;&lt;img alt="进程空间" src="../images/linux-process-memory-model.webp"&gt;&lt;/p&gt;
&lt;p&gt;从上图可以看到，从低地址到高地址，有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;text 段，主要保存着程序的代码对应的机器指令，这也将会是 CPU 所将要执行的机器指令的集合。text 段是可共享的，所以对于经常执行的程序只需保留一份 text 段的拷贝在内存中就可以了。特别地，text 段是只读的，进程无法对 text 段进行修改，这样可以防止一个进程意外地修改它自己的指令。&lt;/li&gt;
&lt;li&gt;data 段，包含着程序已经被初始化的变量。&lt;/li&gt;
&lt;li&gt;bss 段，在这个段中的未初始化变量在程序开始运行之前将会被内核初始化为0或者控指针。&lt;/li&gt;
&lt;li&gt;heap 段，用户程序动态的内存分配将会在这里进行。&lt;/li&gt;
&lt;li&gt;stack 段，每次一个函数被调用，函数的返回地址和调用者函数的上下文比如一些寄存器变量将会保存在这里。同时，这个被调用的函数将会为它的临时变量在这里分配一定内存空间。&lt;/li&gt;
&lt;li&gt;在 stack 之上是命令行参数和一些环境变量。&lt;/li&gt;
&lt;li&gt;更高的空间是内核空间，一般的进程都是不被允许访问的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此外，stack 和 heap 段的增长方式是不同的，stack 段的内存是从高地址向低地址增长的，而 heap 段是从低地址向高地址增长的。一般情况下，stack 段的大小是有限制的，而 heap 段的大小是没有限制的，可以一直增长到整个系统的极限。在 stack 和 heap 之间是非常巨大的一个空间。&lt;/p&gt;
&lt;h4&gt;1.3 进程描述符&lt;/h4&gt;
&lt;p&gt;在 Linux 操作系统中，每个进程被创建的时候，内核会给这个进程分配一个进程描述符结构。进程描述符在一般的操作系统概念中也被称为 PCB ，也就是进程控制块。这个进程描述符保存了这个进程的状态，标识符，打开的文件，等待的信号，文件系统等待的资源信息。每个进程描述符都表示了独立的一个进程，而在系统中，每个进程的进程描述都加入到一个双向循环的任务队列中，由操作系统进行进程的调度，决定哪个进程可以占用 CPU ，哪个进程应该让出 CPU 。Linux 中的进程描述符是一个 task_struct 类型的结构体。在 Linux 中，一个进程的进程描述符结构如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img alt="进程描述符" src="../images/linux-task-struct.webp"&gt;)&lt;/p&gt;
&lt;p&gt;task_struct 是一个相当大的数据结构，同时里面也指向了其他类型的数据结构，比如 thread_info，指向的是这个进程的线程信息; mm_struct 指向了这个进程的内存结构; file_struct 指向了这个进程打开的进程描述符结构，等等。task_struct 是一个复杂的数据结构，我们将会在下面对其进行更详细的分析。&lt;/p&gt;
&lt;h4&gt;1.4 系统调用&lt;/h4&gt;
&lt;p&gt;操作系统内核的代码运行在内核空间中，而应用程序或者我们平时所写的程序是运行在用户空间中的。操作系统对内核空间有相关的限制和保护，以免操作系统内核的空间受到用户应用程序的修改。也就是说只有内核才具有访问内核空间的权限，而应用程序是无法直接访问内核空间的。结合虚拟内存空间和进程空间，我们可以知道，内核空间的页表是常驻在内存中，不会被替换出去的。&lt;/p&gt;
&lt;p&gt;我们上面提到，操作系统将硬件资源和应用程序隔离开来，那应用程序如果需要操作一些硬件或者获取一些资源如何实现？答案是内核提供了一系列的服务比如 IO 或者进程管理等给应用程序调用，也就是通过系统调用( system call )。如下图：&lt;/p&gt;
&lt;p&gt;&lt;img alt="系统调用" src="../images/linux-os.webp"&gt;&lt;/p&gt;
&lt;p&gt;系统调用实际上就是函数调用，也是一系列的指令的集合。和普通的应用程序不同，系统调用是运行在内核空间的。当应用程序调用系统调用的时候，将会从用户空间切换到内核空间运行内核的代码。不同的架构实现内核调用的方式不同，在 i386 架构上，运行在用户空间的应用程序如果需要调用相关的系统调用，可以首先把系统调用编号和参数存放在相关的寄存器中，然后使用0x80这个值来执行软中断 int 。软中断发生之后，内核根据寄存器中的系统调用编号去执行相关的系统调用指令。&lt;/p&gt;
&lt;p&gt;正如上面的图所展示的，应用程序可以直接通过系统调用接口调用内核提供的系统调用，也可以通过调用一些 C 库函数，而这些 C 库函数实际上是通过系统调用接口调用相关的系统调用。C 库函数有些在调用系统调用前后做一些特别的处理，但也有些函数只是单纯地对系统调用做了一层包装。&lt;/p&gt;
&lt;h4&gt;1.5 fork 系统调用&lt;/h4&gt;
&lt;p&gt;fork 系统调用是 Linux 中提供的众多系统调用中的一个，是2号系统调用。在 Linux 中，需要一种机制来创建新的进程，而 fork 就是 Linux 中提供的一个从旧的进程中创建新的进程的方法。我们在编程中，一般是调用 C 库的 fork 函数，而这个 fork 函数则是直接包装了 fork 系统调用的一个函数。fork 函数的效果是对当前进程进行复制，然后创建一个新的进程。旧进程和新进程之间是父子关系，父子进程共享了同一个 text 段，并且父子进程被创建后会从 fork 函数调用点下一个指令继续执行。fork 函数有着一次调用，两次返回的特点。在父进程中，fork 调用将会返回子进程的 PID ，而在子进程中，fork 调用返回的是0。之所以这样处理是因为进程描述符中保存着父进程的 PID ，所以子进程可以通过 getpid 来获取父进程的 PID，而进程描述符中却没有保存子进程的 PID 。&lt;/p&gt;
&lt;p&gt;fork系统调用的调用过程简单描述如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先是开始，父进程调用 fork ，因为这是一个系统调用，所以会导致 int 软中断，进入内核空间;&lt;/li&gt;
&lt;li&gt;内核根据系统调用号，调用 sys_fork 系统调用，而 sys_fork 系统调用则是通过 clone 系统调用实现的,会调用 clone 系统调用;&lt;/li&gt;
&lt;li&gt;clone 系统调用的参数有一系列的标志用来标明父子进程之间将要共享的内容，这些内容包括虚拟内存空间，文件系统，文件描述符等。而对于 fork 来说，它调用 clone 系统调用的时候只是给 clone 一个 SIGCHLD 的标志，这表示子进程结束后将会给父进程一个 SIGCHLD 信号;&lt;/li&gt;
&lt;li&gt;在 clone 函数中，将会调用 do_fork，这个函数是 fork 的主要执行部分。在 do_fork 中，首先做一些错误检查工作和准备复制父进程的初始化工作。然后 do_fork 函数调用 copy_process。&lt;/li&gt;
&lt;li&gt;copy_process 是对父进程的内核状态和相关的资源进行复制的主要函数。然后 copy_process 会调用 copy_thread 函数，复制父进程的执行状态，包括相关寄存器的值，指令指针和建立相关的栈;&lt;/li&gt;
&lt;li&gt;copy_thread 中还干了一件事，就是把0值写入到寄存器中，然后将指令指针指向一个汇编函数 ret_from_fork 。所以在子进程运行的时候，虽然代码和父进程的代码是一致的，但是还是有些区别。在 copy_thread 完毕后，没有返回到 do_fork ，而是跳到 ret_from_fork ，进行一些清理工作，然后退出到用户空间。用户空间函数可以通过寄存器中的值得到 fork 系统调用的返回值为0。&lt;/li&gt;
&lt;li&gt;copy_process 将会返回一个指向子进程的指针。然后回到 do_fork 函数，当 copy_process 函数成功返回的时候，子进程被唤醒，然后加入到进程调度队列中。此外，do_fork 将会返回子进程 的 PID;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在 Linux 中，创建一个新的进程的方式有三种，分别是 fork , vfork 和 clone。fork 是通过 clone 来实现的，而 vfork 和 clone 又是都通过 do_fork 函数来进行接下来的操作。&lt;/p&gt;
&lt;h3&gt;2 相关源码分析&lt;/h3&gt;
&lt;p&gt;本部分内容主要是对相关的具体源码进行分析，使用的 Linux 内核源码版本为3.6.11。被分析的源码并不是全部的相关源码，只是相关源码的一些重要部分。&lt;/p&gt;
&lt;h4&gt;2.1 进程描述符&lt;/h4&gt;
&lt;p&gt;在 Linux 中，进程描述符是一个 task_struct 类型的数据结构，这个数据结构的定义是在 Linux 源码的 include/linux/sched.h 中。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;task_struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;volatile&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="cm"&gt;/* -1 unrunnable, 0 runnable, &amp;gt;0 stopped */&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;atomic_t&lt;/span&gt; &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cm"&gt;/* per process flags, defined below */&lt;/span&gt;
&lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;ptrace&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;task_struct中 存放着一个进程的状态 state。进程的状态主要有五种，同时也是在 sched.h 中定义的：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#define TASK_RUNNING        0&lt;/span&gt;
&lt;span class="cp"&gt;#define TASK_INTERRUPTIBLE  1&lt;/span&gt;
&lt;span class="cp"&gt;#define TASK_UNINTERRUPTIBLE    2&lt;/span&gt;
&lt;span class="cp"&gt;#define __TASK_STOPPED      4&lt;/span&gt;
&lt;span class="cp"&gt;#define __TASK_TRACED       8&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;TASK_RUNNING：表示该进程是可以运行的，有可能是正在运行或者处于一个运行队列中等待运行。&lt;/p&gt;
&lt;p&gt;TASK_INTERRUPTIBLE：进程正在休眠，或者说是被阻塞，等待一写条件成立，然后就会被唤醒，进入 TASK_RUNNING 状态。&lt;/p&gt;
&lt;p&gt;TASK_UNINTERRUPTIBLE：和 TASK_INTERRUPTIBLE 状态一样，区别在于处于这个状态的进程不会对信号做出反应也不会转换到 TASK_RUNNING 状态。一般在进程不能受干扰或者等待的事件很快就会出现的情况下才会出现这种状态。&lt;/p&gt;
&lt;p&gt;__TASK_STOPPED：进程的执行已经停止了，进程没有在运行也不能够运行。在进程接收到 SIGSTOP，SIGTSTP，SGITTIN 或者 SIGTOU 信号的时候就会进入这个状态。&lt;/p&gt;
&lt;p&gt;__TASK_TRACED：该进程正在被其他进程跟踪运行，比如被 ptrace 跟踪中。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;prio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;static_prio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;normal_prio&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;rt_priority&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;sched_class&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sched_class&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;sched_entity&lt;/span&gt; &lt;span class="n"&gt;se&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;sched_rt_entity&lt;/span&gt; &lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这一部分是有关于进程调度信息的内容，调度程序利用这部分的信息决定哪一个进程最应该运行，并结合进程的状态信息保证系统进程调度的公平及高效。其中 prio , static_prio , normal_prio 分别表示了进程的动态优先级，静态优先级，普通优先级。rt_priority 表示进程的实时优先级，而 sched_class 则表示调度的类。se 和 rt 表示的都是调度实体，一个用于普通进程，一个用于实时进程。policy 则指出了进程的调度策略，进程的调度策略也是在 include/linux/sched.h 中定义的，如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt; * Scheduling policies&lt;/span&gt;
&lt;span class="cm"&gt; */&lt;/span&gt;
&lt;span class="cp"&gt;#define SCHED_NORMAL        0&lt;/span&gt;
&lt;span class="cp"&gt;#define SCHED_FIFO      1&lt;/span&gt;
&lt;span class="cp"&gt;#define SCHED_RR        2&lt;/span&gt;
&lt;span class="cp"&gt;#define SCHED_BATCH     3&lt;/span&gt;
&lt;span class="cm"&gt;/* SCHED_ISO: reserved but not implemented yet */&lt;/span&gt;
&lt;span class="cp"&gt;#define SCHED_IDLE      5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;也就是有这几种调度策略：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SCHED_NORMAL，用于普通进程;&lt;/li&gt;
&lt;li&gt;SCHED_FIFO，先来先服务;&lt;/li&gt;
&lt;li&gt;SCHED_RR，时间片轮转调度;&lt;/li&gt;
&lt;li&gt;SCHED_BATCH，用于非交互的处理器消耗型进程;&lt;/li&gt;
&lt;li&gt;SCHED_IDLE，主要是在系统负载低的时候使用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个进程还包括了各种的标识符，用来标识某一个特定的进程，同时也用来标识这个进程所属的进程组。如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="kt"&gt;pid_t&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;pid_t&lt;/span&gt; &lt;span class="n"&gt;tgid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;同时，在 task_struct 中也定义了一些特别指向其他进程的指针。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt; * pointers to (original) parent process, youngest child, younger sibling,&lt;/span&gt;
&lt;span class="cm"&gt; * older sibling, respectively.  (p-&amp;gt;father can be replaced with&lt;/span&gt;
&lt;span class="cm"&gt; * p-&amp;gt;real_parent-&amp;gt;pid)&lt;/span&gt;
&lt;span class="cm"&gt; */&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;task_struct&lt;/span&gt; &lt;span class="n"&gt;__rcu&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;real_parent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cm"&gt;/* real parent process */&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;task_struct&lt;/span&gt; &lt;span class="n"&gt;__rcu&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cm"&gt;/* recipient of SIGCHLD, wait4() reports */&lt;/span&gt;
&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt; * children/sibling forms the list of my natural children&lt;/span&gt;
&lt;span class="cm"&gt; */&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;list_head&lt;/span&gt; &lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="cm"&gt;/* list of my children */&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;list_head&lt;/span&gt; &lt;span class="n"&gt;sibling&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="cm"&gt;/* linkage in my parent&amp;#39;s children list */&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;task_struct&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;group_leader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="cm"&gt;/* threadgroup leader */&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;正如上面这段代码中的注释所表示的，real_parent 指向本进程真正的父进程，也就是原始的父进程，而 parent 则指向了接收 SIGCHLD 信号的进程，如果一个进程被托孤给另外一个进程，比如 init 进程，那 init 进程将会是这个进程的 parent ，但不是原始进程。childern 则是一个本进程的子进程列表，sibling 是本进程的父进程的子进程列表。而 group_leader 指针指向的是线程组的领头进程。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;cputime_t&lt;/span&gt; &lt;span class="n"&gt;utime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;utimescaled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stimescaled&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;cputime_t&lt;/span&gt; &lt;span class="n"&gt;gtime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;nvcsw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nivcsw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cm"&gt;/* context switch counts */&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;timespec&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="cm"&gt;/* monotonic time */&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;timespec&lt;/span&gt; &lt;span class="n"&gt;real_start_time&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="cm"&gt;/* boot based time */&lt;/span&gt;
&lt;span class="cm"&gt;/* mm fault and swap info: this can arguably be seen as either&lt;/span&gt;
&lt;span class="cm"&gt;mm-specific or thread-specific */&lt;/span&gt;
&lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;min_flt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maj_flt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;task_cputime&lt;/span&gt; &lt;span class="n"&gt;cputime_expires&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;list_head&lt;/span&gt; &lt;span class="n"&gt;cpu_timers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;一个进程，从创建到结束，这是它的生命周期。在进程生命周期中有许多与时间相关的内容，这些内容也包括在进程描述符中了。如上代码，我们可以看到有好几个数据类型为 cputime 的成员。utime 和 stime 分别表示进程在用户态下使用 CPU 的时间和在内核态下使用 CPU 的时间，这两个成员的单位是一个 click 。而 utimescaled 和 stimescaled 同样也是分别表示进程在这两种状态下使用 CPU 的时间，只不过单位是处理器的频率。 gtime 表示的是虚拟处理器的运行时间。start_time 和 real_start_time 表示的都是进程的创建时间，real_start_time 包括了进程睡眠的时间。cputime_expires 表示的是进程或者进程组被跟踪的 CPU 时间，对应着 cpu_timers 的三个值。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/* filesystem information */&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;fs_struct&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cm"&gt;/* open file information */&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;files_struct&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;如上，进程描述符还保存了进程的文件系统相关的信息，比如上面的两个成员，fs 表示的是进程与文件系统的关联，包括当前目录和根目录，而 files 则是指向进程打开的文件&lt;/p&gt;
&lt;p&gt;在进程描述符中，还有很多重要的信息，比如虚拟内存信息，进程间通信机制， pipe ，还有一些中断和锁的机制等等。更具体的内容可以直接翻阅 Linux 源码中 task_struct 的定义。&lt;/p&gt;
&lt;h4&gt;2.2 fork 系统调用&lt;/h4&gt;
&lt;p&gt;fork 系统调用实际上调用的是 sys_fork 这个函数，在 Linux 中，sys_fork 是一个定义在 arch/alpha/kernel/entry.S 中的汇编函数。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;align&lt;/span&gt;  &lt;span class="mi"&gt;4&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;globl&lt;/span&gt;  &lt;span class="n"&gt;sys_fork&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ent&lt;/span&gt;    &lt;span class="n"&gt;sys_fork&lt;/span&gt;
&lt;span class="nl"&gt;sys_fork&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prologue&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;mov&lt;/span&gt; &lt;span class="n"&gt;$sp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;$21&lt;/span&gt;
    &lt;span class="n"&gt;bsr&lt;/span&gt; &lt;span class="n"&gt;$1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;do_switch_stack&lt;/span&gt;
    &lt;span class="n"&gt;bis&lt;/span&gt; &lt;span class="n"&gt;$31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SIGCHLD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;$16&lt;/span&gt;
    &lt;span class="n"&gt;mov&lt;/span&gt; &lt;span class="n"&gt;$31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;$17&lt;/span&gt;
    &lt;span class="n"&gt;mov&lt;/span&gt; &lt;span class="n"&gt;$31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;$18&lt;/span&gt;
    &lt;span class="n"&gt;mov&lt;/span&gt; &lt;span class="n"&gt;$31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;$19&lt;/span&gt;
    &lt;span class="n"&gt;mov&lt;/span&gt; &lt;span class="n"&gt;$31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;$20&lt;/span&gt;
    &lt;span class="n"&gt;jsr&lt;/span&gt; &lt;span class="n"&gt;$26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alpha_clone&lt;/span&gt;
    &lt;span class="n"&gt;bsr&lt;/span&gt; &lt;span class="n"&gt;$1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;undo_switch_stack&lt;/span&gt;
    &lt;span class="n"&gt;ret&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="n"&gt;sys_fork&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;如上，可以看到在sys_fork中，将相关的标志 SIGCHLD 等参数压栈后，然后就专跳到 alpga_clone 函数中执行。&lt;/p&gt;
&lt;h4&gt;2.3 alpha_clone&lt;/h4&gt;
&lt;p&gt;alpha_clone 函数的定义在源码目录中的 arch/alpah/kernel/process.c ，具体代码如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt; * &amp;quot;alpha_clone()&amp;quot;.. By the time we get here, the&lt;/span&gt;
&lt;span class="cm"&gt; * non-volatile registers have also been saved on the&lt;/span&gt;
&lt;span class="cm"&gt; * stack. We do some ugly pointer stuff here.. (see&lt;/span&gt;
&lt;span class="cm"&gt; * also copy_thread)&lt;/span&gt;
&lt;span class="cm"&gt; *&lt;/span&gt;
&lt;span class="cm"&gt; * Notice that &amp;quot;fork()&amp;quot; is implemented in terms of clone,&lt;/span&gt;
&lt;span class="cm"&gt; * with parameters (SIGCHLD, 0).&lt;/span&gt;
&lt;span class="cm"&gt; */&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="nf"&gt;alpha_clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;clone_flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;usp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;__user&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;parent_tid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;__user&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;child_tid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;tls_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;pt_regs&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;usp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;usp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rdusp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;do_fork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;usp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parent_tid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;child_tid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;正如注释所提到的，在执行 alpah_clone 函数之前已经将寄存器的相关的值保存到栈中了，在此函数中将会根据相关的调用 do_fork 函数。&lt;/p&gt;
&lt;h4&gt;2.4 do_fork&lt;/h4&gt;
&lt;p&gt;创建一个新的进程的大部分工作是在 do_fork 中完成的，主要是根据标志参数对父进程的相关资源进行复制，得到一个新的进程。do_fork 函数定义在源码目录的 kernel/fork.c 中。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt; *  Ok, this is the main fork-routine.&lt;/span&gt;
&lt;span class="cm"&gt; *&lt;/span&gt;
&lt;span class="cm"&gt; * It copies the process, and if successful kick-starts&lt;/span&gt;
&lt;span class="cm"&gt; * it and waits for it to finish using the VM if required.&lt;/span&gt;
&lt;span class="cm"&gt; */&lt;/span&gt;
&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="nf"&gt;do_fork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;clone_flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;stack_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;pt_regs&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;stack_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;__user&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;parent_tidptr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;__user&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;child_tidptr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;task_struct&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;nr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;首先我们来了解一下 do_fork 函数的参数。clone_flags 是一个标志集合，主要是用来控制复制父进程的资源。clone_flags 的低位保存了子进程结束时发给父进程的信号号码，而高位则保存了其他的各种常数。这些常数也是定义在 include/linux/sched.h 中的，如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt; * cloning flags:&lt;/span&gt;
&lt;span class="cm"&gt; */&lt;/span&gt;
&lt;span class="cp"&gt;#define CSIGNAL     0x000000ff  &lt;/span&gt;&lt;span class="cm"&gt;/* signal mask to be sent at exit */&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;span class="cp"&gt;#define CLONE_VM    0x00000100  &lt;/span&gt;&lt;span class="cm"&gt;/* set if VM shared between processes */&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;span class="cp"&gt;#define CLONE_FS    0x00000200  &lt;/span&gt;&lt;span class="cm"&gt;/* set if fs info shared between processes */&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;span class="cp"&gt;#define CLONE_FILES 0x00000400  &lt;/span&gt;&lt;span class="cm"&gt;/* set if open files shared between processes */&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;span class="cp"&gt;#define CLONE_SIGHAND   0x00000800  &lt;/span&gt;&lt;span class="cm"&gt;/* set if signal handlers and blocked signals shared */&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;span class="cp"&gt;#define CLONE_PTRACE    0x00002000  &lt;/span&gt;&lt;span class="cm"&gt;/* set if we want to let tracing continue on the child too */&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;span class="cp"&gt;#define CLONE_VFORK 0x00004000  &lt;/span&gt;&lt;span class="cm"&gt;/* set if the parent wants the child to wake it up on mm_release */&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;span class="cp"&gt;#define CLONE_PARENT    0x00008000  &lt;/span&gt;&lt;span class="cm"&gt;/* set if we want to have the same parent as the cloner */&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;span class="cp"&gt;#define CLONE_THREAD    0x00010000  &lt;/span&gt;&lt;span class="cm"&gt;/* Same thread group? */&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;span class="cp"&gt;#define CLONE_NEWNS 0x00020000  &lt;/span&gt;&lt;span class="cm"&gt;/* New namespace group? */&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;span class="cp"&gt;#define CLONE_SYSVSEM   0x00040000  &lt;/span&gt;&lt;span class="cm"&gt;/* share system V SEM_UNDO semantics */&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;span class="cp"&gt;#define CLONE_SETTLS    0x00080000  &lt;/span&gt;&lt;span class="cm"&gt;/* create a new TLS for the child */&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;span class="cp"&gt;#define CLONE_PARENT_SETTID 0x00100000  &lt;/span&gt;&lt;span class="cm"&gt;/* set the TID in the parent */&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;span class="cp"&gt;#define CLONE_CHILD_CLEARTID    0x00200000  &lt;/span&gt;&lt;span class="cm"&gt;/* clear the TID in the child */&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;span class="cp"&gt;#define CLONE_DETACHED      0x00400000  &lt;/span&gt;&lt;span class="cm"&gt;/* Unused, ignored */&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;span class="cp"&gt;#define CLONE_UNTRACED      0x00800000  &lt;/span&gt;&lt;span class="cm"&gt;/* set if the tracing process can&amp;#39;t force CLONE_PTRACE on this clone */&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;span class="cp"&gt;#define CLONE_CHILD_SETTID  0x01000000  &lt;/span&gt;&lt;span class="cm"&gt;/* set the TID in the child */&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;CLONE_VM 表示在父子进程间共享 VM ;&lt;/li&gt;
&lt;li&gt;CLONE_FS 表示在父子进程间共享文件系统信息，包括工作目录等;&lt;/li&gt;
&lt;li&gt;CLONE_FILES 表示在父子进程间共享打开的文件;&lt;/li&gt;
&lt;li&gt;CLONE_SIGHAND 表示在父子进程间共享信号的处理函数;&lt;/li&gt;
&lt;li&gt;CLONE_PTRACE 表示如果父进程被跟踪，子进程也被跟踪;&lt;/li&gt;
&lt;li&gt;CLONE_VFORK 在 vfork 的时候使用;&lt;/li&gt;
&lt;li&gt;CLONE_PARENT 表示和复制的进程有同样的父进程;&lt;/li&gt;
&lt;li&gt;CLONE_THREAD 表示同一个线程组;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;之前提到过，在 Linux 中，线程的实现是和进程统一的，就是说，在 Linux 中，进程和线程的结构都是 task_struct 。区别在于，多个线程会共享一个进程的资源，包括虚拟地址空间，文件系统，打开的文件和信号处理函数。线程的创建和一般的进程的创建差不多，区别在于调用 clone 系统调用时，需要通过传入相关的标志参数指定要共享的特定资源。通常是这样的：clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0)。&lt;/p&gt;
&lt;p&gt;do_fork 函数的参数 stack_start 表示的是用户状态下，栈的起始地址。regs 是一个指向寄存器集合的指针，在其中保存了调用的参数。当进程从用户态切换到内核态的时候，该结构体保存通用寄存器中的值，并存放到内核态的堆栈中。stack_size 是用户态下的栈大小，一般是不必要的，设置为0。而 parent_tidptr 和 child_tidptr 则分别是指向用户态下父进程和和子进程的 TID 的指针。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt; * Do some preliminary argument and permissions checking before we&lt;/span&gt;
&lt;span class="cm"&gt; * actually start allocating stuff&lt;/span&gt;
&lt;span class="cm"&gt; */&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;CLONE_NEWUSER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;CLONE_THREAD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;EINVAL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="cm"&gt;/* hopefully this check will go away when userns support is&lt;/span&gt;
&lt;span class="cm"&gt;     * complete&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;capable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CAP_SYS_ADMIN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;capable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CAP_SETUID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
            &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;capable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CAP_SETGID&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;EPERM&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;上面这段代码主要是对参数的 clone_flags 组合的正确性进行检查，因为标志需要遵循一定的规则，如果不符合，则返回错误代码。此外还需要对权限进行检查。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt; * Determine whether and which event to report to ptracer.  When&lt;/span&gt;
&lt;span class="cm"&gt; * called from kernel_thread or CLONE_UNTRACED is explicitly&lt;/span&gt;
&lt;span class="cm"&gt; * requested, no event is reported; otherwise, report if the event&lt;/span&gt;
&lt;span class="cm"&gt; * for the type of forking is enabled.&lt;/span&gt;
&lt;span class="cm"&gt; */&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;likely&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_mode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;CLONE_UNTRACED&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;CLONE_VFORK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;trace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PTRACE_EVENT_VFORK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;CSIGNAL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;SIGCHLD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;trace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PTRACE_EVENT_CLONE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;trace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PTRACE_EVENT_FORK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;likely&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ptrace_event_enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="n"&gt;trace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;决定报告给 ptracer 的事件，如果是从 kernel_thread 中调用后者参数中指明了 CLONE_UNTRACED ，将不会有任何的事件被报告。否则，根据创建进程的类型 clone ，fork 或者 vfork 报告支持的事件。&lt;/p&gt;
&lt;p&gt;然后 do_fork 将会调用 copy_process，如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;copy_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stack_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stack_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;child_tidptr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;2.5 copy_process&lt;/h4&gt;
&lt;p&gt;copy_process 函数也是定义在源码目录的 kernel/fork.c 中，这个函数将会复制父进程，作为新创建的一个进程，也就是子进程。copy_process 会复制寄存器，然后也根据每个 clone 的标志，复制父进程环境的相关内容或者也可能共享父进程的内容。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;task_struct&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;copy_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;clone_flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;stack_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;pt_regs&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;stack_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;__user&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;child_tidptr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;retval&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;task_struct&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;cgroup_callbacks_done&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;从 copy_process 函数的参数来看，do_fork 函数的所有参数也都被传入到这个函数中了，此外，后面还有一个参数 trace 标识是否对子进程进行跟踪和参数 pid 。在函数的开始，定义了一个未初始化的 task_struct 类型的指针 p。&lt;/p&gt;
&lt;p&gt;在 copy_process 这里也对 clone 标志的有效性进行了检查，如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CLONE_NEWNS&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;CLONE_FS&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CLONE_NEWNS&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;CLONE_FS&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ERR_PTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;EINVAL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;CLONE_THREAD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;CLONE_SIGHAND&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ERR_PTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;EINVAL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;CLONE_SIGHAND&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;CLONE_VM&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ERR_PTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;EINVAL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;CLONE_PARENT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;SIGNAL_UNKILLABLE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ERR_PTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;EINVAL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;在 copy_process 函数中同样也进行了一系列的函数调用。比如 dup_task_struct 函数：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dup_task_struct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;fork_out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;dup_task_struct 函数将会为心的进程创建一个新的内核栈，thread_info 结构和 task_struct 结构。thread_info 结构是一个比较简单的数据结构，主要保存了进程的 task_struct 还有其他一些比较底层的内容。新值和当前进程的值是一致，所以可以说此时父子进程的进程描述符是一致的。current 实际上是一个获取当前进程描述符的宏定义函数，返回当前调用系统调用的进程描述符，也就是父进程。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;atomic_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;real_cred&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;processes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;
        &lt;span class="n"&gt;task_rlimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RLIMIT_NPROC&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;capable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CAP_SYS_ADMIN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;capable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CAP_SYS_RESOURCE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;real_cred&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;INIT_USER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;bad_fork_free&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;在创建新进程的相关核心数据结构后，将会对这个新的进程进行检查，看是否超出了当前用户的进程数限制。如果超出限制了，并且没有相关的权限，也不是 init 用户，将会转跳到相关的失败处理指令处。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;did_exec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;delayacct_tsk_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="cm"&gt;/* Must remain after dup_task_struct() */&lt;/span&gt;
&lt;span class="n"&gt;copy_flags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;INIT_LIST_HEAD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;INIT_LIST_HEAD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;sibling&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;rcu_copy_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;vfork_done&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;spin_lock_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;alloc_lock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;init_sigpending&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这段代码首先将进程描述符p的did_exec值设置为0,以保证这个新创建的进程不会被运行。因为子进程和父进程实际上还是有区别的，所以，接着将会将子进程的进程描述符的部分内容清除掉并设置为初始的值。如上，新创建的进程的描述符中 children ，sibling 和等待的信号等值都被初始化了。然后，这段代码还调用了 copy_flags 函数，copy_flags 函数如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;copy_flags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;clone_flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;task_struct&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;new_flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;new_flags&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;=&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PF_SUPERPRIV&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;PF_WQ_WORKER&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;new_flags&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="n"&gt;PF_FORKNOEXEC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_flags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;copy_flags 函数将会更新这个新创建的子进程的标志，主要是清除 PF_SUPERPRIV 标志，这个标志表示一个进程是否使用超级用户权限。然后还有就是设置 PF_FORKNOEXEC 标志，表示这个进程还没有执行过 exec 函数。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;retval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;perf_event_init_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;bad_fork_cleanup_policy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;retval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;audit_alloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;bad_fork_cleanup_policy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cm"&gt;/* copy all the process information */&lt;/span&gt;
&lt;span class="n"&gt;retval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;copy_semundo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;bad_fork_cleanup_audit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;retval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;copy_files&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;bad_fork_cleanup_semundo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;retval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;copy_fs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;bad_fork_cleanup_files&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;retval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;copy_sighand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;bad_fork_cleanup_fs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;retval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;copy_signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;bad_fork_cleanup_sighand&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;retval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;copy_mm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;bad_fork_cleanup_signal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;retval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;copy_namespaces&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;bad_fork_cleanup_mm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;retval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;copy_io&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;bad_fork_cleanup_namespaces&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;retval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;copy_thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stack_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stack_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;bad_fork_cleanup_io&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;上面代码就是根据 clone_flags 集合中的值，共享或者复制父进程打开的文件，文件系统信息，信号处理函数，进程地址空间，命名空间等资源。这些资源通常情况下在一个进程内的多个线程才会共享，对于我们现在分析的 fork 系统调用来说，对于这些资源都会复制一份到子进程。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;init_struct_pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;retval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ENOMEM&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;alloc_pid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;nsproxy&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;pid_ns&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;bad_fork_cleanup_io&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;因为在 do_fork 函数中调用 copy_process 函数的时候，参数 pid 的值为 NULL，所以此时新建进程的 PID 其实还没有被分配。所以接下来的就是要给子进程分配一个 PID。&lt;/p&gt;
&lt;p&gt;最后，copy_process 函数做了一些清理工作，并且返回一个指向新建的子进程的指针给 do_fork 函数。&lt;/p&gt;
&lt;h4&gt;2.6 回到 do_fork&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;IS_ERR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;wake_up_new_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="cm"&gt;/* forking complete and child started to run, tell ptracer */&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unlikely&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;ptrace_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clone_flags&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;CLONE_VFORK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;wait_for_vfork_done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;vfork&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;ptrace_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PTRACE_EVENT_VFORK_DONE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;回到 do_fork 函数中，如果 copy_process 函数执行成功，没有错误，那么将会唤醒新创建的子进程，让子进程运行。自此，fork 函数调用成功执行。&lt;/p&gt;
&lt;h3&gt;3 具体例程分析&lt;/h3&gt;
&lt;p&gt;在这一部分，我将会结合相关的具体例程，进行一些简单的分析。&lt;/p&gt;
&lt;h4&gt;3.1 例程代码&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdlib.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;unistd.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;sys/types.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;

&lt;span class="cp"&gt;#define LEN 1024 * 1024&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;argc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;pid_t&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;malloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LEN&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="cm"&gt;/*parent process.*/&lt;/span&gt;
        &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;parent %d process get %d!It stores in %x.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;getpid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;parent have a piece of memory start from %x.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="cm"&gt;/*child process.*/&lt;/span&gt;
        &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;child %d process get %d!It stores in %x.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;getpid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;child have a piece of memory start from %x.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;){}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这个程序只是简单地调用了一次 fork ，创建了一个子进程，然后分别在父子进程中查看申请的一块内存的起始地址。此外还添加了一个 while 死循环，方便父子进程的进程控制块进行查看。&lt;/p&gt;
&lt;h4&gt;3.2 相关分析&lt;/h4&gt;
&lt;p&gt;这个程序执行的结果截图如下：&lt;/p&gt;
&lt;p&gt;&lt;img alt="执行结果" src="../images/linux-fork-analysis.webp"&gt;&lt;/p&gt;
&lt;p&gt;可以看到，通过对 pid 的值检测，我们让父子进程执行了不同的代码。&lt;/p&gt;
&lt;p&gt;通过 ps aux | grep a.out 指令，我们可以得到父子进程的 PID：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;$ps&lt;/span&gt; aux &lt;span class="p"&gt;|&lt;/span&gt; grep a.out
tonychow &lt;span class="m"&gt;32261&lt;/span&gt; &lt;span class="m"&gt;93&lt;/span&gt;.8  &lt;span class="m"&gt;0&lt;/span&gt;.0   &lt;span class="m"&gt;3056&lt;/span&gt;   &lt;span class="m"&gt;272&lt;/span&gt; pts/1    R+   &lt;span class="m"&gt;10&lt;/span&gt;:57   &lt;span class="m"&gt;4&lt;/span&gt;:11 ./a.out
tonychow &lt;span class="m"&gt;32262&lt;/span&gt; &lt;span class="m"&gt;93&lt;/span&gt;.3  &lt;span class="m"&gt;0&lt;/span&gt;.0   &lt;span class="m"&gt;3056&lt;/span&gt;    &lt;span class="m"&gt;52&lt;/span&gt; pts/1    R+   &lt;span class="m"&gt;10&lt;/span&gt;:57   &lt;span class="m"&gt;4&lt;/span&gt;:10 ./a.out
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;每个进程，在其生命周期期间，都会在 /proc/ 进程号 目录中保存相关的进程内容，我们可以查看里面的内容对这个进程进行分析。根据上面的运行结果，我们可以通过 ls -al /proc/32261 这个指令来查看该文件夹中的内容：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;$ls&lt;/span&gt; -al /proc/32261
总用量 &lt;span class="m"&gt;0&lt;/span&gt;
dr-xr-xr-x   &lt;span class="m"&gt;8&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;:59 .
dr-xr-xr-x &lt;span class="m"&gt;267&lt;/span&gt; root     root     &lt;span class="m"&gt;0&lt;/span&gt; 5月  &lt;span class="m"&gt;31&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt;:18 ..
dr-xr-xr-x   &lt;span class="m"&gt;2&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 attr
-rw-r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 autogroup
-r--------   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 auxv
-r--r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 cgroup
--w-------   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 clear_refs
-r--r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:02 cmdline
-rw-r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 comm
-rw-r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 coredump_filter
-r--r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 cpuset
lrwxrwxrwx   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 cwd -&amp;gt; /home/tonychow/code/c/fork-analysis
-r--------   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:03 environ
lrwxrwxrwx   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 exe -&amp;gt; /home/tonychow/code/c/fork-analysis/a.out
dr-x------   &lt;span class="m"&gt;2&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;:59 fd
dr-x------   &lt;span class="m"&gt;2&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 fdinfo
-r--------   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 io
-r--r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 latency
-r--r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 limits
-rw-r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 loginuid
-r--r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 maps
-rw-------   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 mem
-r--r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 mountinfo
-r--r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 mounts
-r--------   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 mountstats
dr-xr-xr-x   &lt;span class="m"&gt;6&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 net
dr-x--x--x   &lt;span class="m"&gt;2&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 ns
-rw-r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 oom_adj
-r--r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 oom_score
-rw-r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 oom_score_adj
-r--r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 pagemap
-r--r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 personality
lrwxrwxrwx   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 root -&amp;gt; /
-rw-r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 sched
-r--r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 schedstat
-r--r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 sessionid
-r--r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 smaps
-r--r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 stack
-r--r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:02 stat
-r--r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 statm
-r--r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:02 status
-r--r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 syscall
dr-xr-xr-x   &lt;span class="m"&gt;3&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 task
-r--r--r--   &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;0&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;:06 wchan
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;从上面的结果可以看到列出了一堆的信息文件，包括状态，io，限制，文件，命名空间等等这些属于这个进程的一大堆资源。分别查看这两个进程的 status 信息：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;$cat&lt;/span&gt; /proc/32261/status
Name:    a.out
State:  R &lt;span class="o"&gt;(&lt;/span&gt;running&lt;span class="o"&gt;)&lt;/span&gt;
Tgid:   &lt;span class="m"&gt;32261&lt;/span&gt;
Pid:    &lt;span class="m"&gt;32261&lt;/span&gt;
PPid:   &lt;span class="m"&gt;12747&lt;/span&gt;
...

&lt;span class="nv"&gt;$cat&lt;/span&gt; /proc/32262/status
Name:    a.out
State:  R &lt;span class="o"&gt;(&lt;/span&gt;running&lt;span class="o"&gt;)&lt;/span&gt;
Tgid:   &lt;span class="m"&gt;32262&lt;/span&gt;
Pid:    &lt;span class="m"&gt;32262&lt;/span&gt;
PPid:   &lt;span class="m"&gt;32261&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;从上面的结果可以看到，这两个进程都处于 running 状态，而进程32261是进程32262的父进程。接着查看一下内存映射信息：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;$cat&lt;/span&gt; /proc/32261/maps  
&lt;span class="m"&gt;08048000&lt;/span&gt;-08049000 r-xp &lt;span class="m"&gt;00000000&lt;/span&gt; fd:02 &lt;span class="m"&gt;20979068&lt;/span&gt;   /home/tonychow/code/c/fork-analysis/a.out
&lt;span class="m"&gt;08049000&lt;/span&gt;-0804a000 rw-p &lt;span class="m"&gt;00000000&lt;/span&gt; fd:02 &lt;span class="m"&gt;20979068&lt;/span&gt;   /home/tonychow/code/c/fork-analysis/a.out
4b94d000-4b96c000 r-xp &lt;span class="m"&gt;00000000&lt;/span&gt; fd:01 &lt;span class="m"&gt;793014&lt;/span&gt;     /usr/lib/ld-2.15.so
4b96c000-4b96d000 r--p 0001e000 fd:01 &lt;span class="m"&gt;793014&lt;/span&gt;     /usr/lib/ld-2.15.so
4b96d000-4b96e000 rw-p 0001f000 fd:01 &lt;span class="m"&gt;793014&lt;/span&gt;     /usr/lib/ld-2.15.so
4b970000-4bb1b000 r-xp &lt;span class="m"&gt;00000000&lt;/span&gt; fd:01 &lt;span class="m"&gt;809017&lt;/span&gt;     /usr/lib/libc-2.15.so
4bb1b000-4bb1c000 ---p 001ab000 fd:01 &lt;span class="m"&gt;809017&lt;/span&gt;     /usr/lib/libc-2.15.so
4bb1c000-4bb1e000 r--p 001ab000 fd:01 &lt;span class="m"&gt;809017&lt;/span&gt;     /usr/lib/libc-2.15.so
4bb1e000-4bb1f000 rw-p 001ad000 fd:01 &lt;span class="m"&gt;809017&lt;/span&gt;     /usr/lib/libc-2.15.so
4bb1f000-4bb22000 rw-p &lt;span class="m"&gt;00000000&lt;/span&gt; &lt;span class="m"&gt;00&lt;/span&gt;:00 &lt;span class="m"&gt;0&lt;/span&gt; 
b76a4000-b77a6000 rw-p &lt;span class="m"&gt;00000000&lt;/span&gt; &lt;span class="m"&gt;00&lt;/span&gt;:00 &lt;span class="m"&gt;0&lt;/span&gt; 
b77be000-b77c0000 rw-p &lt;span class="m"&gt;00000000&lt;/span&gt; &lt;span class="m"&gt;00&lt;/span&gt;:00 &lt;span class="m"&gt;0&lt;/span&gt; 
b77c0000-b77c1000 r-xp &lt;span class="m"&gt;00000000&lt;/span&gt; &lt;span class="m"&gt;00&lt;/span&gt;:00 &lt;span class="m"&gt;0&lt;/span&gt;          &lt;span class="o"&gt;[&lt;/span&gt;vdso&lt;span class="o"&gt;]&lt;/span&gt;
bf92a000-bf94b000 rw-p &lt;span class="m"&gt;00000000&lt;/span&gt; &lt;span class="m"&gt;00&lt;/span&gt;:00 &lt;span class="m"&gt;0&lt;/span&gt;          &lt;span class="o"&gt;[&lt;/span&gt;stack&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;结合上面程序的输出，可以看到 int 的类型的变量 num 存放在栈中，而通过 malloc 得到的则是存放在堆中。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;$ls&lt;/span&gt; -l /proc/32261/fd
总用量 &lt;span class="m"&gt;0&lt;/span&gt;
lrwx------ &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;64&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;:59 &lt;span class="m"&gt;0&lt;/span&gt; -&amp;gt; /dev/pts/1
lrwx------ &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;64&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;:59 &lt;span class="m"&gt;1&lt;/span&gt; -&amp;gt; /dev/pts/1
lrwx------ &lt;span class="m"&gt;1&lt;/span&gt; tonychow tonychow &lt;span class="m"&gt;64&lt;/span&gt; 6月  &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;:59 &lt;span class="m"&gt;2&lt;/span&gt; -&amp;gt; /dev/pts/1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;查看下该进程的文件描述符，可以看到主要是有标准输出，标准输入和标准输出这三个。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ cat /proc/32261/limits
limit                     soft limit           hard limit           units     
max cpu &lt;span class="nb"&gt;time&lt;/span&gt;              unlimited            unlimited          seconds   
max file size             unlimited            unlimited            bytes     
max data size             unlimited            unlimited            bytes     
max stack size            &lt;span class="m"&gt;8388608&lt;/span&gt;              unlimited            bytes     
max core file size        &lt;span class="m"&gt;0&lt;/span&gt;                    unlimited            bytes     
max resident &lt;span class="nb"&gt;set&lt;/span&gt;          unlimited            unlimited            bytes     
max processes             &lt;span class="m"&gt;1024&lt;/span&gt;                 &lt;span class="m"&gt;31683&lt;/span&gt;            processes 
max open files            &lt;span class="m"&gt;1024&lt;/span&gt;                 &lt;span class="m"&gt;4096&lt;/span&gt;                 files     
max locked memory         &lt;span class="m"&gt;65536&lt;/span&gt;                &lt;span class="m"&gt;65536&lt;/span&gt;                bytes     
max address space         unlimited            unlimited            bytes     
max file locks            unlimited            unlimited            locks     
max pending signals       &lt;span class="m"&gt;31683&lt;/span&gt;                &lt;span class="m"&gt;31683&lt;/span&gt;            signals   
max msgqueue size         &lt;span class="m"&gt;819200&lt;/span&gt;               &lt;span class="m"&gt;819200&lt;/span&gt;               bytes     
max nice priority         &lt;span class="m"&gt;0&lt;/span&gt;                    &lt;span class="m"&gt;0&lt;/span&gt;                    
max realtime priority     &lt;span class="m"&gt;0&lt;/span&gt;                    &lt;span class="m"&gt;0&lt;/span&gt;                    
max realtime timeout      unlimited            unlimited            us
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;通过 cat /proc/32261/limits 命令我们可以看到系统对这个用户的一些资源限制，包括 CPU 时间，最大文件大小，最大栈大小，进程数，文件数，最大地址空间等等的资源。&lt;/p&gt;
&lt;h2&gt;4 总结&lt;/h2&gt;
&lt;p&gt;经过这次对 Linux 系统的 fork 系统调用的分析，主要有以下几点总结：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fork 调用是 Linux 系统中很重要的一个创建进程的方式，系统级别的进程和线程都是通过 fork 系统调用来实现的，它的实现其实也依靠了 clone 系统调用;&lt;/li&gt;
&lt;li&gt;在 Linux 系统中，一个进程内多个线程其实就是共享了父进程大部分资源的子进程，内核通过 clone_flags 来控制创建这种特别的进程;&lt;/li&gt;
&lt;li&gt;Linux 其实也是一个软件，但是它是一个复杂无比的软件。虽然从源码来说，不同的部分分得挺清楚，但是具体到一个个函数的执行，对于我们新手而言，如果没有注释，有时候真的很难知道一个函数的参数是什么意思。这时候就要依靠搜索引擎的力量了。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;5 主要参考文献&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Robert Love,《Linux系统编程》,东南大学出版社&lt;/li&gt;
&lt;li&gt;Robert Love,《Linux内核设计与实现》,机械工业出版社&lt;/li&gt;
&lt;li&gt;Richard Steven,《Unix环境高级编程》,人民邮电出版社&lt;/li&gt;
&lt;li&gt;&lt;a href="http://zh.wikipedia.org/wiki/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F"&gt;维基百科.操作系统词条&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://zh.wikipedia.org/wiki/%E8%A1%8C%E7%A8%8B"&gt;维基百科.进程词条&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zh.wikipedia.org/wiki/%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8"&gt;维基百科.系统调用词条&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://zh.wikipedia.org/zh-cn/%E8%A1%8C%E7%A8%8B%E6%8E%A7%E5%88%B6%E8%A1%A8"&gt;维基百科.进程控制块词条&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.quora.com/Linux-Kernel/After-a-fork-where-exactly-does-the-childs-execution-start"&gt;RobertLove在Quora上面关于fork的一个回答&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="linux"></category><category term="system-call"></category><category term="fork"></category><category term="source-reading"></category></entry><entry><title>Python 函数的命名参数相关</title><link href="https://blog.tonychow.me/things-about-python-named-args.html" rel="alternate"></link><published>2013-05-15T00:00:00+08:00</published><updated>2013-05-15T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2013-05-15:/things-about-python-named-args.html</id><summary type="html"></summary><content type="html">&lt;h3&gt;起因&lt;/h3&gt;
&lt;p&gt;今天师弟问了一个关于 Python 函数参数的一个问题：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;#1&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="c1"&gt;#2&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;为啥第一个函数会把 l 每次调用完的值保留下来？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;起初我认为问的是这两个函数使用的时候，为何会保持对传入的参数 l 的修改。从这个方面来讲，是因为 Python 对于数据赋值的处理的原因。&lt;/p&gt;
&lt;!--more--&gt;

&lt;p&gt;在 Python 中，赋值是传引用的。一个列表，比如 [1, 2, 3]，或者一个字符串，'tonychow'，这些对象在创建的时候会在内存中分配一段空间。如果将这些对象赋值给一个变量名，那就会导致在 Python 的命名空间中该变量名指向内存中这个对象。对该变量名的操作就是对内存中这个对象的操作。所以如果尝试直接将一个变量 a 赋值给另外一个变量 b ，导致的后果是，命名空间中，这两个变量名 a 和 b 指向内存中同样一个对象，也就是所谓传引用赋值。对其中任意一个变量的操作，实质是对该对象进行操作，所以同样的操作后结果也会可以在另外一个变量中看到。如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; &lt;span class="nv"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;, &lt;span class="m"&gt;2&lt;/span&gt;, &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&amp;gt;&amp;gt;&amp;gt; &lt;span class="nv"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; a
&amp;gt;&amp;gt;&amp;gt; a.pop&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="m"&gt;3&lt;/span&gt;
&amp;gt;&amp;gt;&amp;gt; b
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;, &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&amp;gt;&amp;gt;&amp;gt; a
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;, &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&amp;gt;&amp;gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;从上面的代码可以看到，在将 a 赋值给 b 之后，对 a 列表调用 pop 方法，导致的是 b 列表也发生了变化。我们还可以通过 Python 内置的 globals 函数和 id 函数来加深这个理解。globals 函数将会返回一个字典，这个字典是当前的全局符号表。而 id 函数则会返回一个对象的标识，可以将其返回值看作是这个对象在内存中的地址。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;b&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;__builtins__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__builtin__&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;built&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;value&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__package__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;span class="s1"&gt;&amp;#39;key&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__doc__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__name__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__doc__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;3077280588&lt;/span&gt;&lt;span class="n"&gt;L&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;3077280588&lt;/span&gt;&lt;span class="n"&gt;L&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;可以看到，a 和 b 都在当前的全局字符表中，他们的值也都是一致的。此外，id 函数的结果明确地说明了 a 和 b 这两个变量名都是指向了内存中的同一个对象。而在 Python 中，调用函数的时候，传入参数，也是进行传引用的赋值。所以我师弟说的这两个函数都会保留对于传入参数的修改，也就是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bar&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bar&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;题外话，在 Python 内置的数据类型中，有两种不同的数据类型。一种是可变类型(mutable)，比如 list ， dict 等;另外一种就是不可变类型(immutable)，比如字符串或者 tuple。&lt;/p&gt;
&lt;p&gt;可是后来师弟贴出了另外一段代码：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;func2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;func2&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;func2&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这下我明白了，师弟说的不是我想到的那个问题，而是命名参数的问题。&lt;/p&gt;
&lt;h3&gt;解决&lt;/h3&gt;
&lt;p&gt;说实话这个问题一开始我也没有想到答案。大家在学习 Python 的时候，无论看的是哪本入门书，应该在开始的时候都会看到一句话“ Python 中一切都是对象”。看代码：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;test&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;function&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;__call__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__class__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__closure__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__code__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__defaults__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;__delattr__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__dict__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__doc__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__format__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__get__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;span class="s1"&gt;&amp;#39;__getattribute__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__globals__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__hash__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__init__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;span class="s1"&gt;&amp;#39;__module__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__name__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__new__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__reduce__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__reduce_ex__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;__repr__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__setattr__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__sizeof__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__str__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__subclasshook__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;func_closure&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;func_code&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;func_defaults&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;func_dict&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;func_doc&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;func_globals&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;func_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__code__&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="mh"&gt;0xb76c8410&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;func&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;对的，数字1是一个对象，字符串 'test' 也是一个对象，甚至一个函数也是一个类型为 function 的对象，也有一堆的属性和方法。对于 function 对象而言，有一个特殊属性 &lt;strong&gt;defaults&lt;/strong&gt; ，这个属性用一个元组保存了是这个 function 对象的命名参数的缺省值，如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__defaults__&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__defaults__&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;func_no&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;func_no&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__defaults__&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;如果一个函数有命名参数，则按顺序保存了命名参数的缺省值。如果这个函数命名参数没有缺省值或者没有命名参数，则为 None 。回到问题，为什么第一个函数中指定缺省值为 [] 会导致随着执行过程中，缺省参数的值会被保留下来呢？代码如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;其实通过上面的罗嗦一大堆，答案很容易就可以得到了：foo 是一个 function 类型的对象，这个对象中有个 &lt;strong&gt;defaults&lt;/strong&gt; 属性，保存了命名参数 l 的值，而在一次次的调用过程中，因为没有传入参数，所以实际上 foo 函数改变的是命名参数的缺省值。也就是师弟所说的这个函数在一次次调用中保留了对命名参数l的结果的修改。而师弟贴出的第二个函数的命名参数缺省值是 None ，实质上就是没有缺省值，所以l的值修改没有在调用中保留下来。是不是真的这样？我们来看下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;default_arg_addr:&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;changed_var_addr:&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__defaults__&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="mi"&gt;3077402860&lt;/span&gt;&lt;span class="n"&gt;L&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;default_arg_addr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3077402860&lt;/span&gt;
&lt;span class="n"&gt;changed_var_addr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3077402860&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;default_arg_addr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3077402860&lt;/span&gt;
&lt;span class="n"&gt;changed_var_addr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3077402860&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__defaults__&lt;/span&gt;
&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;上面这个函数 foo 有一个命名参数 l ，它的命名参数缺省值是一个空的列表，虽然是空列表，可是它确确实实是一个对象，已经在内存给它分配了空间。我们可以通过 id 函数的结果看出来。然后是两次的调用 foo 函数可以看到，因为没有传入参数，所以这两次修改的都是这个缺省的命名参数的值，所以可以得到所谓的对 l 的值的修改保留下来了的感觉。&lt;/p&gt;
&lt;h3&gt;深入&lt;/h3&gt;
&lt;p&gt;首先我们应该明白，在 Python 中，一个对象的实例化和初始化是不同的。一个对象实例化调用的是对象的 &lt;strong&gt;new&lt;/strong&gt; 函数，而初始化调用的是 &lt;strong&gt;init&lt;/strong&gt; 函数。所以，要深入地去看在 Python 中，函数在实例化的时候到底发生了什么，我们应该要去看 Python 源码。如下，源码版本为 Python2.7.4。&lt;/p&gt;
&lt;p&gt;Python2.7.4/Objects/funcobject.c, func_new, L436-L439&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defaults&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;Py_None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Py_INCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;newfunc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;func_defaults&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Python2.7.4/Include/object.h, L765-L767&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#define Py_INCREF(op) (                     \&lt;/span&gt;
&lt;span class="cp"&gt;_Py_INC_REFTOTAL  _Py_REF_DEBUG_COMMA       \&lt;/span&gt;
&lt;span class="cp"&gt;((PyObject*)(op))-&amp;gt;ob_refcnt++)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;上面第一段代码是 funcobject 的 func_new 中的代码，也就应该是 functions 对象的 &lt;strong&gt;new&lt;/strong&gt; 函数代码。可以看到，如果 defaults 不是 None，也就是说有值，而我们上面也提到 Python 中一切都是对象，所以就会对这个对象进行 Py_INCREF 操作，并且将这个 defaults 值设定为 func_defaults。Py_INCREF 操作是什么？从第二段代码可以看到，这是一个宏定义，将参数 op 的 ob_refcnt 值加一。ob_refcnt 是什么？refcnt----reference count，这样明白了，就是将该对象的引用计数值加一。在执行了函数函数之后，该命名函数的缺省值对象并没有被销毁，而是随着该函数对象的存在而存在。对这个缺省之对象的修改当然也会被保留下来。&lt;/p&gt;</content><category term="python"></category></entry><entry><title>Python 中 sqlite3 模块使用小记</title><link href="https://blog.tonychow.me/sqlite3-in-python.html" rel="alternate"></link><published>2013-05-12T00:00:00+08:00</published><updated>2013-05-12T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2013-05-12:/sqlite3-in-python.html</id><summary type="html"></summary><content type="html">&lt;h3&gt;前记&lt;/h3&gt;
&lt;p&gt;Python 的标准库中包含了对 sqlite 这个轻巧的数据库的支持模块，也就是 sqlite3 模块。sqlite 数据库的好处我就不多说了，小型而强大，适合很多小型或者中型的数据库应用。最近在使用 sqlite3 模块遇到一些问题，解决了，顺便就记下来。&lt;/p&gt;
&lt;h3&gt;问题&lt;/h3&gt;
&lt;p&gt;sqlite3 模块的使用很简单，如下这段测试代码，创建一个 person 数据表然后进行一次数据库查询操作。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/env pypthon&lt;/span&gt;
&lt;span class="c1"&gt;#_*_ coding: utf-8 _*_&lt;/span&gt;


&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sqlite3&lt;/span&gt;

&lt;span class="n"&gt;SCHEMA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;         CREATE TABLE person (&lt;/span&gt;
&lt;span class="s2"&gt;             p_id int,&lt;/span&gt;
&lt;span class="s2"&gt;             p_name text&lt;/span&gt;
&lt;span class="s2"&gt;         )&lt;/span&gt;
&lt;span class="s2"&gt;         &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;tony&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;jack&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;:memory:&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SCHEMA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;insert into person values(?, ?)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;error!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;#Do a query.&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;select * from person where p_name = ?&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;tony&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;运行这段代码，抛出了个异常，如下提示：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Traceback &lt;span class="o"&gt;(&lt;/span&gt;most recent call last&lt;span class="o"&gt;)&lt;/span&gt;:
      File &lt;span class="s2"&gt;&amp;quot;sqlite3_test.py&amp;quot;&lt;/span&gt;, line &lt;span class="m"&gt;32&lt;/span&gt;, &lt;span class="k"&gt;in&lt;/span&gt; &amp;lt;module&amp;gt;
          c.execute&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;select * from person where p_name = ?&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;tony&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          sqlite3.ProgrammingError: Incorrect number of bindings supplied. The current statement uses &lt;span class="m"&gt;1&lt;/span&gt;, and there are &lt;span class="m"&gt;4&lt;/span&gt; supplied.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;很莫名奇妙是不？明明我提供的占位符?绑定只有一个字符串参数，可是却说我提供了四个。再看仔细点，说提供了四个，正好字符串 'tony' 是四个字符。&lt;/p&gt;
&lt;h3&gt;解决&lt;/h3&gt;
&lt;p&gt;翻了翻文档，发现也给出了一个占位符查询的例子如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="n"&gt;RHAT&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="n"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;stocks&lt;/span&gt; &lt;span class="n"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;?’&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;所以将字符参数放到元组中就可以了，修改如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;select * from person where p_name = ?&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tony&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;结果依旧是抛出了同样的异常。再仔细看下，漏了个','，于是加上：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;select * from person where p_name = ?&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tony&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这次终于得到最终的结果了,其中的字符为 unicode 类型：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tony&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;原因&lt;/h3&gt;
&lt;p&gt;但是为什么？ Python 中的 sqlite3 模块提供了对 sqlite 数据操作的 API，执行查询的函数是在 sqlite3 模块源码中定义的，很明显想要知道为啥，最好的方式是去看 sqlite3 模块的源码。我手上的 Python 源码是 Python-2.7.4，在源码 Python-2.7.4/Modules/_sqlite/cursor.c 的函数 PyObject&lt;em&gt; _pysqlite_query_execute(pysqlite_Cursor&lt;/em&gt; self, int multiple, PyObject* args) 中 497-529 行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="cm"&gt;/* execute() */&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyArg_ParseTuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;O|O&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;second_argument&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyString_Check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyUnicode_Check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PyErr_SetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;operation parameter must be str or unicode&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;parameters_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyList_New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;parameters_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;second_argument&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;second_argument&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyTuple_New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;second_argument&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Py_INCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;second_argument&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyList_Append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parameters_list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;second_argument&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Py_DECREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;second_argument&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;Py_DECREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;second_argument&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;parameters_iter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyObject_GetIter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parameters_list&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;parameters_iter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;从这段源码中可以看到这段代码将参数 args 解析成为 Python 的一个元组作为 parameters_list ，然后最这个得到的元组进行 iter 操作，不断地读取这个元组的元素作为参数，而 Python 中对一个字符串进行 parse tuple 会怎样？我觉得 PyArg_ParseTuple 这个函数的操作和以下代码会是类似的：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;test&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;t&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;e&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;s&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;t&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;所以现在我们可以看到我们的答案了，sqlite3 模块中，cursor 对象的 execute 方法会接受两个参数，第二个参数会被 PyArg_ParseTuple 函数转化成为 Python中 的 tuple。而对一个字符进行 tuple parse 导致的结果是将这个字符串的每个字符作为 tuple 的一个元素，所以上面抛出错误的时候提示的提供了四个所以错误也可以理解了。那为什么'('tony')'同样是错误呢？如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tony&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;str&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tony&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;tuple&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;很明显，('tony')是一个 str 也就是一个字符串，相当于是 'tony'，而 ('tony',) 才是一个单元素的 tuple 。同样，因为：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tony&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tony&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;所以如果那一行查询执行改为：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;select * from person where p_name = ?&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tony&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;同样也是可以执行成功的。&lt;/p&gt;</content><category term="python"></category><category term="sqlite3"></category></entry><entry><title>Python 中的 New-style 和 Old-style classes</title><link href="https://blog.tonychow.me/class-in-python.html" rel="alternate"></link><published>2013-04-06T00:00:00+08:00</published><updated>2013-04-06T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2013-04-06:/class-in-python.html</id><summary type="html"></summary><content type="html">&lt;h3&gt;使用 super() 的错误&lt;/h3&gt;
&lt;p&gt;super 函数是 Python 中的一个内置函数,提供对继承的类的函数调用,特别是在子类中被 overridden 的父类函数,比如 &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;最近在使用 super 函数的时候出现了个错误,例如下:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;Traceback&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;
&lt;span class="ne"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;must&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;classobj&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;可以看到抛出了参数类型错误的错误.一开始完全不知所措,然后将出错信息 google 了一下,找到了解决方式:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;简单地将 Base 继承 object 就可以解决这个错误.其实这是 Python 中的 NewStyle classes 和 OldStyle classes 而导致的一个问题. super() 函数只适用于 NewStyle classes.&lt;/p&gt;
&lt;h3&gt;Newstyle 和 Oldstyle&lt;/h3&gt;
&lt;p&gt;Python 中,直至 Python2.1 ,类和类型是两种不相关的概念,例如:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__class__&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;__main__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="mh"&gt;0xb77373ec&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;instance&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;type&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;在这里 Test 类是 Oldstyle 的类.可以看到,Test 类的一个实例,它的类是 Test,但是类型却是 instance.这是因为 Oldstyle 的类与类型是不统一的概念, Oldstyle 类的实例是独立于它们的类,由一个 Python 内置类型 instance 实现的.&lt;/p&gt;
&lt;p&gt;从2.2开始, Python 开始使用 New-style 类来统一类和类型.对于一个 New-style 的类,它的实例的类型和类都是一致的.为了兼容之前的代码,在 Python2.2 之后,默认的类定义还是 Old-style 的类.而一个 New-style 的类可以通过继承一个 New-style 的类或者在类继承中最顶端继承 object 来实现,如下:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__class__&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nc"&gt;__main__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nc"&gt;__main__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;type&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;New-style 类的提出是为了统一 Python的 对象模型.在 Python3 中,Old-style 类已经完全移除了.&lt;/p&gt;
&lt;p&gt;参考资料:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;http://docs.python.org/2/library/functions.html#super&lt;/li&gt;
&lt;li&gt;http://docs.python.org/2/reference/datamodel.html#newstyle&lt;/li&gt;
&lt;li&gt;http://stackoverflow.com/questions/9698614/super-raises-typeerror-must-be-type-not-classobj-for-new-style-class&lt;/li&gt;
&lt;li&gt;http://stackoverflow.com/questions/9699591/instance-is-an-object-but-class-is-not-a-subclass-of-object-how-is-this-po/9699961#9699961&lt;/li&gt;
&lt;/ul&gt;</content><category term="python"></category></entry><entry><title>CSAPP 读书笔记-计算机系统中的抽象-操作系统</title><link href="https://blog.tonychow.me/csapp-reading-notes-abstract.html" rel="alternate"></link><published>2013-04-05T00:00:00+08:00</published><updated>2013-04-05T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2013-04-05:/csapp-reading-notes-abstract.html</id><summary type="html"></summary><content type="html">&lt;h3&gt;初言&lt;/h3&gt;
&lt;p&gt;我们使用着计算机系统提供的种种功能,安装不同的操作系统,使用不同的软件,听歌,上网,看视频,似乎理所当然.我们也知道,信息时代是建立在0和1的基础之上的,我们的计算机系统也是遵循着0和1的二进制.但是这两者是如何关联到一起的?当我们启动一个软件的时候,计算机系统底层是怎样的?我们打开一个网页如此的简单,但是这背后,计算机系统又发生了什么事情?&lt;/p&gt;
&lt;h3&gt;程序的执行&lt;/h3&gt;
&lt;p&gt;如果是计算机系的学生,或者对计算机技术有着兴趣的人,都会知道计算机操作系统的一些概念,也知道一个程序的执行其实到底是怎么一回事.无非就是将一段在硬盘上的二进制代码加载到内存中,然后由CPU执行相关的指令.程序的执行简单来说就是这么一回事,所以一个软件的启动和执行,也就是在这个简单的基础上再加上一些复杂的操作.
更深入点,我们知道操作系统也是软件,计算机关闭的时候操作系统的编译后的可执行对象也是保存在硬盘上.在计算机启动的时候,将操作系统加载到内存上,之后,操作系统就会一直运行直至计算机重新关闭.一般来说,我们将程序运行分为两种状态,用户的应用程序运行在用户态,而操作系统则是运行在内核态.&lt;/p&gt;
&lt;h3&gt;操作系统的抽象&lt;/h3&gt;
&lt;p&gt;计算机系统中的抽象其实应该是涉及两个方面.一个是处理器方面的,处理器的指令集对于硬件的抽象;而另一方面则是操作系统方面的抽象.&lt;/p&gt;
&lt;p&gt;正如上面提及到的,程序运行于两种状态,这是为了安全的考虑,用户态的用户程序是无法直接进行一些直接操作硬件的指令的.比如创建保存一个文件的操作,涉及到了IO操作,而保存在硬盘上也涉及到磁盘的寻道.这些操作完全交由用户来进行一方面是非常的不安全,另一方面,每个人都有自己的实现方式,那将会导致各种混乱的代码.所以,操作系统一般会通过提供一些系统调用函数给用户程序,用户程序通过系统API从而实现对系统代码的调用.而这些系统代码将会进行相关的底层操作.通过系统API,操作系统作为硬件和用户应用程序的中间层,对用户应用程序隐藏了对硬件的操作,将硬件的操作细节抽象为一个个系统调用.&lt;/p&gt;
&lt;p&gt;操作系统的抽象是计算机系统中非常重要的一个概念,总结来说大概有三个方面的抽象:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文件对于 IO 设备的抽象&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;IO 设备包括硬盘等设备,操作系统将这些设备都抽象为文件.比如硬盘上的数据保存是以0和1的方式保存在不同的磁道或者区域中的,操作系统将这些数据抽象成一个个文件.相关的IO操作也抽象成了文件的操作,复杂具体的底层操作隐藏在一个个简单的系统调用函数在之下.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;虚拟内存对于内存和硬盘的抽象&lt;/li&gt;
&lt;li&gt;进程对于处理器,内存和IO设备的抽象&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;-EOF-&lt;/p&gt;</content><category term="csapp"></category><category term="abstract"></category></entry><entry><title>Python 内置函数 reduce</title><link href="https://blog.tonychow.me/reduce-function-in-python.html" rel="alternate"></link><published>2013-03-18T00:00:00+08:00</published><updated>2013-03-18T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2013-03-18:/reduce-function-in-python.html</id><summary type="html"></summary><content type="html">&lt;h4&gt;原型&lt;/h4&gt;
&lt;p&gt;reduce 函数原型是 reduce(function, iterable[, initializer]),返回值是一个单值.使用例子如下:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="mi"&gt;15&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;可以看到通过传入一个函数和一个 list , reduce 函数返回的是这个 list 的元素的相加值.注意 lambda 函数是有两个参数的,如果我们改成一个参数会怎么样?如下:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;Traceback&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="ne"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;takes&lt;/span&gt; &lt;span class="n"&gt;exactly&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;argument&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="n"&gt;given&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;结果是抛出了错误,提示 &lt;lambda&gt;() 函数只接受一个参数缺给了两个参数.所以在 reduce 内部中,我们可以知道对于作为参数的 function ,接受了两个值作为参数的.&lt;/p&gt;
&lt;h4&gt;深入&lt;/h4&gt;
&lt;p&gt;第一个例子中, reduce 函数返回的是 list 变量元素的和,那 reduce 函数是如何实现将这个 list 变量元素相加起来呢?考虑到定义的匿名函数体中将 x 的值和 y 的值加起来了,所以应该和这个函数是相关的,那 reduce 函数给赋给这个 lambda 函数的两个参数分别是什么呢?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="mi"&gt;15&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;
&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;通过这个例子,可以看出答案已经很明显了.在 reduce 函数内部,对 lambda 函数的调用一共有四次:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;#x = 1, y = 2,x 是列表的第一个元素,y是第二个元素&lt;/span&gt;
&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;#x = 3, y = 3,x 是上一次调用返回值 1+2 , y 是第三个元素&lt;/span&gt;
&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;#x = 6, y = 4,同上, y 是第四个元素&lt;/span&gt;
&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;#x = 10, y = 5,同上, y 是第五个元素&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;最后得到 reduce 函数的返回值 15,也就是 fun 函数的第四次调用的返回值.所以现在我们知道了,reduce 函数对作为参数的函数是有要求的,要求这个函数接受两个参数.第一个参数的值是累积的值,而第二个参数的值是 reduce 函数参数中的序列的下一个元素.其实 reduce 函数中还有第三个可选的参数初始值,如果这个参数为空则初始值默认为序列的第一个元素,所以上面可以看到第一次调用这个函数是以序列的第一个和第二个元素作为参数的.最终,最后一次调用返回的值作为 reduce 函数的返回值.&lt;/p&gt;
&lt;h4&gt;定义&lt;/h4&gt;
&lt;p&gt;reduce 函数可以参考下面的定义(来自官网):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iterable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;initializer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iterable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;initializer&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;initializer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;StopIteration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;reduce() of empty sequence with no initial value&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;accum_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initializer&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;accum_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accum_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;accum_value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;reduce 函数对 function 的调用次数为 iterable 参数的长度n减1.&lt;/p&gt;
&lt;p&gt;参考资料:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://docs.python.org/2/library/functions.html#reduce"&gt;[1]官网: python build-in function reduce&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.secnetix.de/olli/Python/lambda_functions.hawk"&gt;[2]Python 函数式编程: python functional programming&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="python"></category></entry><entry><title>SQL 反模式读书笔记-AS</title><link href="https://blog.tonychow.me/antipattern-sql-as.html" rel="alternate"></link><published>2013-01-11T00:00:00+08:00</published><updated>2013-01-11T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2013-01-11:/antipattern-sql-as.html</id><summary type="html"></summary><content type="html">&lt;p&gt;P16&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;products_per_account&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Contacts&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;其中 Contacts 表是 Products 表和 Accounts 表的中间表，这个 SQL 查询语句的作用是查询每个账号相关的产品数量。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accounts_per_product&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;accounts_per_product&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Contacts&lt;/span&gt;
    &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accounts_per_product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accounts_per_product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这个 SQL 查询语句的作用是查询相关账号最多的产品。在这两个查询语句中我注意到的是 accounts_per_product 和 products_per_account 这两个本来不存在的字段。很明显这两个是通过 AS 得到的字段。AS 也就是 Alias (别名)，通过 Alias 可以方便组织多表查询特别是在涉及到自身对应自身表的时候，比如评论表如果想要父级和子级的结果查询，同时也可以用 Alias 给表的字段起一个别名，便于输出，比如上面的两个 SQL 查询。&lt;/p&gt;
&lt;p&gt;第一个 SQL 查询语句中，通过将 c.product_id，COUNT(*) 这个要查询的字段 alias 成 products_per_account，这样输出的结果类似于：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;products_per_account&lt;/span&gt;
&lt;span class="mi"&gt;7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;就很容易阅读了。&lt;/p&gt;
&lt;p&gt;第二个 SQL 查询语句中&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;accounts_per_product&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Contacts&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这样一段查询得到结果集合被 alias 成了 c ，此外其中也将根据 product_id 查询得到的 products 数量 alias 成了 accounts_per_product，所以 c 这个集合中也多了一个字段 accounts_per_product，通过这样的处理，想要得到关联账号最多的产品的 product_id 就简单得好像以下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;accounts_per_product&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; 
&lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="n"&gt;accounts_per_product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accounts_per_product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这个查询语句通过 AS，写得相当优雅，易懂。&lt;/p&gt;</content><category term="antipattern"></category><category term="sql"></category></entry><entry><title>CSAPP读书笔记- 一个C程序的编译</title><link href="https://blog.tonychow.me/csapp-reading-notes-how-program-compile.html" rel="alternate"></link><published>2012-10-09T00:00:00+08:00</published><updated>2012-10-09T00:00:00+08:00</updated><author><name>tonychow</name></author><id>tag:blog.tonychow.me,2012-10-09:/csapp-reading-notes-how-program-compile.html</id><summary type="html"></summary><content type="html">&lt;p&gt;CSAPP中，1.2节讲到了程序的编译:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Programs Are Translated By Other Programs into Different Forms.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;程序由其他程序翻译成不同的形式，其实看下面这张图应该可以很清晰地了解上面这一句：&lt;/p&gt;
&lt;p&gt;&lt;img alt="c程序编译" src="../images/c-program-compilation.webp"&gt;&lt;/p&gt;
&lt;p&gt;上图是一个 hello 的 C 程序由 GCC 编译器从源码文件 hello.c 中读取内容并将其翻译成为一个可执行的对象文件 hello 的过程。这个过程包含了几个阶段：&lt;/p&gt;
&lt;p&gt;首先是源文件，此时是处于文本类型：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;// C 代码&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;stdio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;argc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;argvs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt;     &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hello, world&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;然后是预处理阶段，将对以#开始的指令进行修改，比如对于&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;&lt;/code&gt;指令，预处理器将会读取系统头文件 stdio.h 内容，然后将其内容直接插入到程序源码文本中，经过预处理之后源码文件被翻译成 hello.i 文件，此时得到的仍然是一个文本类型的 C 源码文件：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;......&lt;/span&gt;
&lt;span class="mi"&gt;844&lt;/span&gt;
&lt;span class="mi"&gt;845&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;funlockfile&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;FILE&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;__stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;__attribute__&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;__nothrow__&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="mi"&gt;846&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="mi"&gt;938&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;/usr/include/stdio.h&amp;quot;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="mi"&gt;847&lt;/span&gt;
&lt;span class="mi"&gt;848&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;hello.c&amp;quot;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="mi"&gt;849&lt;/span&gt;
&lt;span class="mi"&gt;850&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;argc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;argvs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;851&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;852&lt;/span&gt;     &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hello, world&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="mi"&gt;853&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;以上部分代码可以看出除了&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;&lt;/code&gt;指令之外其他指令并未被改变。&lt;/p&gt;
&lt;p&gt;接下来的是编译阶段。在这个阶段中，前一阶段得到的c程序代码将会被编译器翻译成汇编语言的形式，每个汇编语言声明都对应一个机器语言指令。这个阶段得到的是一个文本类型的汇编语言源码文件 hello.s ：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; &lt;span class="mi"&gt;1&lt;/span&gt;     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;   &lt;span class="s"&gt;&amp;quot;hello.c&amp;quot;&lt;/span&gt;
 &lt;span class="mi"&gt;2&lt;/span&gt;     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;section&lt;/span&gt;    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rodata&lt;/span&gt;
 &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;LC0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="mi"&gt;4&lt;/span&gt;     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;hello, world&amp;quot;&lt;/span&gt;
 &lt;span class="mi"&gt;5&lt;/span&gt;     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
 &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;globl&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;
 &lt;span class="mi"&gt;7&lt;/span&gt;     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;   &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;
 &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="nl"&gt;main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;LFB0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="mi"&gt;10&lt;/span&gt;     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cfi_startproc&lt;/span&gt;
&lt;span class="mi"&gt;11&lt;/span&gt;     &lt;span class="n"&gt;pushq&lt;/span&gt;   &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;rbp&lt;/span&gt;
&lt;span class="mi"&gt;12&lt;/span&gt;     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cfi_def_cfa_offset&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;
&lt;span class="mi"&gt;13&lt;/span&gt;     &lt;span class="n"&gt;movq&lt;/span&gt;    &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;rbp&lt;/span&gt;
&lt;span class="mi"&gt;14&lt;/span&gt;     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cfi_offset&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;-16&lt;/span&gt;
&lt;span class="mi"&gt;15&lt;/span&gt;     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cfi_def_cfa_register&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
&lt;span class="mi"&gt;16&lt;/span&gt;     &lt;span class="n"&gt;subq&lt;/span&gt;    &lt;span class="n"&gt;$16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;rsp&lt;/span&gt;
&lt;span class="mi"&gt;17&lt;/span&gt;     &lt;span class="n"&gt;movl&lt;/span&gt;    &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;edi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;-4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;rbp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;18&lt;/span&gt;     &lt;span class="n"&gt;movq&lt;/span&gt;    &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;rsi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;-16&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;rbp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;19&lt;/span&gt;     &lt;span class="n"&gt;movl&lt;/span&gt;    &lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LC0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;edi&lt;/span&gt;
&lt;span class="mi"&gt;20&lt;/span&gt;     &lt;span class="n"&gt;call&lt;/span&gt;    &lt;span class="n"&gt;puts&lt;/span&gt;
&lt;span class="mi"&gt;21&lt;/span&gt;     &lt;span class="n"&gt;leave&lt;/span&gt;
&lt;span class="mi"&gt;22&lt;/span&gt;     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cfi_def_cfa&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
&lt;span class="mi"&gt;23&lt;/span&gt;     &lt;span class="n"&gt;ret&lt;/span&gt;
&lt;span class="mi"&gt;24&lt;/span&gt;     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cfi_endproc&lt;/span&gt;
&lt;span class="mi"&gt;25&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;LFE0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="mi"&gt;26&lt;/span&gt;     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;   &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;
&lt;span class="mi"&gt;27&lt;/span&gt;     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ident&lt;/span&gt;  &lt;span class="s"&gt;&amp;quot;GCC: (GNU) 4.4.4 20100726 (Red Hat 4.4.4-13)&amp;quot;&lt;/span&gt;
&lt;span class="mi"&gt;28&lt;/span&gt;     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;section&lt;/span&gt;    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GNU&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;progbits&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;之后是汇编器将上个阶段得到的汇编程序源码中的每条指令都翻译成机器代码，也就是 01 的形式，生成一个对象类型的文件 hello.o ，在这里用&lt;code&gt;objdump&lt;/code&gt;查看下这个文件的内容：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;o&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt; &lt;span class="n"&gt;elf64&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x86&lt;/span&gt;&lt;span class="mi"&gt;-64&lt;/span&gt;

&lt;span class="n"&gt;Contents&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="mo"&gt;0000&lt;/span&gt; &lt;span class="mf"&gt;554889e5&lt;/span&gt; &lt;span class="mi"&gt;4883&lt;/span&gt;&lt;span class="n"&gt;ec10&lt;/span&gt; &lt;span class="mi"&gt;897&lt;/span&gt;&lt;span class="n"&gt;dfc48&lt;/span&gt; &lt;span class="mf"&gt;8975f&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;bf&lt;/span&gt;  &lt;span class="n"&gt;UH&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;....}.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;
 &lt;span class="mo"&gt;0010&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="n"&gt;e8000000&lt;/span&gt; &lt;span class="mo"&gt;00&lt;/span&gt;&lt;span class="n"&gt;c9c3&lt;/span&gt;             &lt;span class="p"&gt;...........&lt;/span&gt;
&lt;span class="n"&gt;Contents&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;rodata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="mo"&gt;0000&lt;/span&gt; &lt;span class="mi"&gt;68656&lt;/span&gt;&lt;span class="n"&gt;c6c&lt;/span&gt; &lt;span class="mf"&gt;6f&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;c2077&lt;/span&gt; &lt;span class="mf"&gt;6f&lt;/span&gt;&lt;span class="mi"&gt;726&lt;/span&gt;&lt;span class="n"&gt;c64&lt;/span&gt; &lt;span class="mo"&gt;00&lt;/span&gt;        &lt;span class="n"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;Contents&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="mo"&gt;0000&lt;/span&gt; &lt;span class="mo"&gt;00474343&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;a202847&lt;/span&gt; &lt;span class="mf"&gt;4e552920&lt;/span&gt; &lt;span class="mf"&gt;342e342&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;GCC&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GNU&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;4.4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
 &lt;span class="mo"&gt;0010&lt;/span&gt; &lt;span class="mi"&gt;34203230&lt;/span&gt; &lt;span class="mi"&gt;31303037&lt;/span&gt; &lt;span class="mi"&gt;32362028&lt;/span&gt; &lt;span class="mi"&gt;52656420&lt;/span&gt;  &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="mi"&gt;20100726&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Red&lt;/span&gt;
 &lt;span class="mo"&gt;0020&lt;/span&gt; &lt;span class="mi"&gt;48617420&lt;/span&gt; &lt;span class="mf"&gt;342e342&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="mi"&gt;342&lt;/span&gt;&lt;span class="n"&gt;d3133&lt;/span&gt; &lt;span class="mi"&gt;2900&lt;/span&gt;      &lt;span class="n"&gt;Hat&lt;/span&gt; &lt;span class="mf"&gt;4.4.4&lt;/span&gt;&lt;span class="mi"&gt;-13&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;span class="n"&gt;Contents&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;eh_frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="mo"&gt;0000&lt;/span&gt; &lt;span class="mi"&gt;14000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;017&lt;/span&gt;&lt;span class="n"&gt;a5200&lt;/span&gt; &lt;span class="mo"&gt;017&lt;/span&gt;&lt;span class="mi"&gt;81001&lt;/span&gt;  &lt;span class="p"&gt;.........&lt;/span&gt;&lt;span class="n"&gt;zR&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;
 &lt;span class="mo"&gt;0010&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;b0c0708&lt;/span&gt; &lt;span class="mi"&gt;90010000&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;c000000&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;c000000&lt;/span&gt;  &lt;span class="p"&gt;................&lt;/span&gt;
 &lt;span class="mo"&gt;0020&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;b000000&lt;/span&gt; &lt;span class="mf"&gt;00410e10&lt;/span&gt; &lt;span class="mi"&gt;4386020&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;  &lt;span class="p"&gt;.........&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
 &lt;span class="mo"&gt;0030&lt;/span&gt; &lt;span class="mo"&gt;06560&lt;/span&gt;&lt;span class="n"&gt;c07&lt;/span&gt; &lt;span class="mi"&gt;08000000&lt;/span&gt;                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;V&lt;/span&gt;&lt;span class="p"&gt;......&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;最后一个阶段是链接阶段，链接程序将上一个步骤产生的hello.o文件与 C 编译器提供的 printf.o 文件合并到一起，因为 hello 代码中调用了标准 C 库中的 printf 函数。这两个对象文件将会被合并成为一个可执行的对象文件，这个文件可以加载到内存中执行。下面继续用&lt;code&gt;objdump&lt;/code&gt;查看下这个 hello 对象文件的内容:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nl"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt; &lt;span class="n"&gt;elf64&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x86&lt;/span&gt;&lt;span class="mi"&gt;-64&lt;/span&gt;

&lt;span class="n"&gt;Contents&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;interp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="mi"&gt;400200&lt;/span&gt; &lt;span class="mf"&gt;2f&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="n"&gt;c6962&lt;/span&gt; &lt;span class="mf"&gt;36342f&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="mi"&gt;642&lt;/span&gt;&lt;span class="n"&gt;d6c69&lt;/span&gt; &lt;span class="mf"&gt;6e75782&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;  &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib64&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ld&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;
 &lt;span class="mi"&gt;400210&lt;/span&gt; &lt;span class="mi"&gt;7838362&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="mf"&gt;36342e73&lt;/span&gt; &lt;span class="mf"&gt;6f2e3200&lt;/span&gt;           &lt;span class="n"&gt;x86&lt;/span&gt;&lt;span class="mf"&gt;-64.&lt;/span&gt;&lt;span class="n"&gt;so&lt;/span&gt;&lt;span class="mf"&gt;.2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;Contents&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ABI&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nl"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="mi"&gt;40021&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="mo"&gt;04000000&lt;/span&gt; &lt;span class="mi"&gt;10000000&lt;/span&gt; &lt;span class="mo"&gt;01000000&lt;/span&gt; &lt;span class="mf"&gt;474e5500&lt;/span&gt;  &lt;span class="p"&gt;............&lt;/span&gt;&lt;span class="n"&gt;GNU&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
 &lt;span class="mi"&gt;40022&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;02000000&lt;/span&gt; &lt;span class="mo"&gt;06000000&lt;/span&gt; &lt;span class="mi"&gt;12000000&lt;/span&gt;  &lt;span class="p"&gt;................&lt;/span&gt;
&lt;span class="n"&gt;Contents&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gnu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="mi"&gt;40023&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="mo"&gt;04000000&lt;/span&gt; &lt;span class="mi"&gt;14000000&lt;/span&gt; &lt;span class="mo"&gt;03000000&lt;/span&gt; &lt;span class="mf"&gt;474e5500&lt;/span&gt;  &lt;span class="p"&gt;............&lt;/span&gt;&lt;span class="n"&gt;GNU&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
 &lt;span class="mi"&gt;40024&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="n"&gt;b51099&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;bd53844&lt;/span&gt; &lt;span class="mi"&gt;69&lt;/span&gt;&lt;span class="n"&gt;dcba88&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;bf11585&lt;/span&gt;   &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="n"&gt;K&lt;/span&gt;&lt;span class="mf"&gt;.8&lt;/span&gt;&lt;span class="n"&gt;Di&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="n"&gt;K&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
 &lt;span class="mi"&gt;40025&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="mi"&gt;599&lt;/span&gt;&lt;span class="n"&gt;dda4e&lt;/span&gt;                             &lt;span class="n"&gt;Y&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;
&lt;span class="n"&gt;Contents&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gnu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="mi"&gt;400260&lt;/span&gt; &lt;span class="mo"&gt;01000000&lt;/span&gt; &lt;span class="mo"&gt;01000000&lt;/span&gt; &lt;span class="mo"&gt;01000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt;  &lt;span class="p"&gt;................&lt;/span&gt;
 &lt;span class="mi"&gt;400270&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt;           &lt;span class="p"&gt;............&lt;/span&gt;
&lt;span class="p"&gt;......&lt;/span&gt;
&lt;span class="p"&gt;......&lt;/span&gt;
 &lt;span class="mi"&gt;600788&lt;/span&gt; &lt;span class="mo"&gt;07000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mi"&gt;48034000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt;  &lt;span class="p"&gt;........&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;.....&lt;/span&gt;
 &lt;span class="mi"&gt;600798&lt;/span&gt; &lt;span class="mi"&gt;08000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mi"&gt;18000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt;  &lt;span class="p"&gt;................&lt;/span&gt;
 &lt;span class="mi"&gt;6007&lt;/span&gt;&lt;span class="n"&gt;a8&lt;/span&gt; &lt;span class="mi"&gt;09000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mi"&gt;18000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt;  &lt;span class="p"&gt;................&lt;/span&gt;
 &lt;span class="mi"&gt;6007&lt;/span&gt;&lt;span class="n"&gt;b8&lt;/span&gt; &lt;span class="n"&gt;feffff6f&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mi"&gt;28034000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt;  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;....(.&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;.....&lt;/span&gt;
 &lt;span class="mi"&gt;6007&lt;/span&gt;&lt;span class="n"&gt;c8&lt;/span&gt; &lt;span class="n"&gt;ffffff6f&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;01000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt;  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;............&lt;/span&gt;
 &lt;span class="mi"&gt;6007&lt;/span&gt;&lt;span class="n"&gt;d8&lt;/span&gt; &lt;span class="n"&gt;f0ffff6f&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mf"&gt;1e034000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt;  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;......&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;.....&lt;/span&gt;
 &lt;span class="mf"&gt;6007e8&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt;  &lt;span class="p"&gt;................&lt;/span&gt;
 &lt;span class="mf"&gt;6007f&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt;  &lt;span class="p"&gt;................&lt;/span&gt;
 &lt;span class="mi"&gt;600808&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt;  &lt;span class="p"&gt;................&lt;/span&gt;
 &lt;span class="mi"&gt;600818&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt;  &lt;span class="p"&gt;................&lt;/span&gt;
 &lt;span class="mi"&gt;600828&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt;  &lt;span class="p"&gt;................&lt;/span&gt;
 &lt;span class="mi"&gt;600838&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt;  &lt;span class="p"&gt;................&lt;/span&gt;
&lt;span class="n"&gt;Contents&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;got&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="mi"&gt;600848&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt;                    &lt;span class="p"&gt;........&lt;/span&gt;
&lt;span class="n"&gt;Contents&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="mi"&gt;600850&lt;/span&gt; &lt;span class="n"&gt;b8066000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt;  &lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;.............&lt;/span&gt;
 &lt;span class="mi"&gt;600860&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt; &lt;span class="n"&gt;be034000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt;  &lt;span class="p"&gt;..........&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;.....&lt;/span&gt;
 &lt;span class="mi"&gt;600870&lt;/span&gt; &lt;span class="n"&gt;ce034000&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt;                    &lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;.....&lt;/span&gt;
&lt;span class="n"&gt;Contents&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="mi"&gt;600878&lt;/span&gt; &lt;span class="mo"&gt;00000000&lt;/span&gt;                             &lt;span class="p"&gt;....&lt;/span&gt;
&lt;span class="n"&gt;Contents&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="mo"&gt;0000&lt;/span&gt; &lt;span class="mi"&gt;4743433&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="mi"&gt;2028474&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="mi"&gt;55292034&lt;/span&gt; &lt;span class="mf"&gt;2e342&lt;/span&gt;&lt;span class="n"&gt;e34&lt;/span&gt;  &lt;span class="nl"&gt;GCC&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GNU&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;4.4.4&lt;/span&gt;
 &lt;span class="mo"&gt;0010&lt;/span&gt; &lt;span class="mi"&gt;20323031&lt;/span&gt; &lt;span class="mi"&gt;30303732&lt;/span&gt; &lt;span class="mi"&gt;36202852&lt;/span&gt; &lt;span class="mi"&gt;65642048&lt;/span&gt;   &lt;span class="mi"&gt;20100726&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Red&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt;
 &lt;span class="mo"&gt;0020&lt;/span&gt; &lt;span class="mi"&gt;61742034&lt;/span&gt; &lt;span class="mf"&gt;2e342&lt;/span&gt;&lt;span class="n"&gt;e34&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;d313329&lt;/span&gt; &lt;span class="mo"&gt;00&lt;/span&gt;        &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="mf"&gt;4.4.4&lt;/span&gt;&lt;span class="mi"&gt;-13&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;以上就是一个简单 C 语言 hello 程序的编译过程，已夜，晚安。&lt;/p&gt;</content><category term="csapp"></category><category term="reading-notes"></category></entry></feed>