随意的世界 - 网络与架构
type: channel
link: http://1.117.140.145/视频制作
items: 分布式事务
items: X-Engine
items: Linux socket
items: Docker与K8s笔记
items: 复制、容错原子存储（ABD）
items: 对称加密和非对称加密的应用
items: 南京大学操作系统课程学习4
items: 南京大学操作系统课程3
items: 南京大学操作系统课程2
items: 南京大学操作系统课程

分布式事务
type: item
date: 2025-12-08T21:23:00+08:00
description: 分布式事务https://www.bilibili.com/video/BV1U341157BX事务ACID在分布式场景下ACID特性含义会被扩展。原子性（Atomicity）一个事务内涉及到的分布式多个节点的操作要么一起成功，要么一起失败一致性（Consistency）：跨节点场景下也不能出现中间状态。隔离性（Isolation）：一个分布式事务在一个节点哪怕完成了，在整个分布式事务完成之前，也不能被其他事务读取使用持久性（Durability）事务提交完成后的数据不仅要在当前节点保存，还应该在分布式场景下有备份高克yognxing早期分布式事务的三个方向分别是俩阶段模型，XA事务模型TCC事务模型Saga事务模型常见的方案俩阶段提交一个直接的思路就是三个系统都等到整个交易快照完成时再一起提交。根据这个思路就诞生了俩阶段提交的方法，阶段1协调器收到客户端commit命令，决定提交T。然后协调器向各节点发送prepare T消息，各节点决定该节点上T是提交还是中止并返回给协调器。第二阶段协调器收到各个节点的ready或don't commit响应，如果都是ready，那么就决定提交T，并向所有节点发送commit消息；如果收到来自一个节点或多个节点的don't commit T的消息，就决定丢弃T，并向所有节点发送abort的消息。XA事务就是把俩阶段提交标准化后形成指定的数据库接口，再由一个事务管理器作为协调者统一管理俩阶段提交，相当于标准化的俩阶段提交。很多单机数据库，如oracle、DB2、Mysql支持XA接口，应用程序实现的一个事务管理器，然后跳读下面数据库的接口实现俩阶段提交，从而实现分布式事务。XA事务允许各个系统选择不同类型的数据库，只要它们都支持XA接口。TCC事务TCC将整个业务逻辑分为三块Try、comfirm，和cancel三个操作Try就是尝试扣除用户月、尝试扣除库存；confirm就是Try的结果都成功，可以证实把订单状态状态改为已支付，通知仓库发货；而cancel则是try失败，那么就要取消教育，回复月，回复库存。TCC是业务逻辑补偿的应用层设计实现。TCC的一个关键点是，Try阶段如果完成，那么很多状态就已经保存起来，再出现故障也不会影响事务的晚餐，也就是说，哪怕confirm阶段或cancel阶段失败，也会通过不断重试完成交易，而不是回滚。TCC的过程类似于俩阶段提交，Try过程和俩阶段prepare比较相似。但TCC没有事务管理的抽象，Try、confirm、cancel和业务场景密切绑定，要考虑具体业务来实现。SATA事务模型Saga本来是为解决长事务而出现的分布式事务模型，长事务的是需要太长时间而不允许它们保持其他事务所需要的锁的事务。电商交易如果采用俩阶段提交，则订单系统的子系统要等到用户确认收货交易完成才能提交，而物流的时间一般比较长，这个时间可能需要几天。而订单系统事务可能会拖住一些资源，如用户的余额，这个锁维持几天的时间，会导致用户无法消费，这显然是不可行的。Saga是把分布式事务看做一个个单机事务的合集，对每一个单机事务A，Saga都有一个补偿事务$A^{-1}$。如果我们执行A，又执行$A^{-1}$，那么锁产生的数据库状态同A和$A^{-1}$都未执行前一样。同样都是需要业务逻辑接入，其中TCC适用于对一致性要求高、性能敏感的场景。SAGA的核心的回滚，像一个执行-如失败反向补偿的厂业务流程，适用于执行流程长，业务流程复杂的场景。体现在分布式整体上，只要我们能确认一个单机事务都执行成功，那么整个分布式事务就是成功的，可以提交。如果任意一个单机事务不成功，那么就要把它和它之前的单机事务回滚掉，但它之前的单机事务已经提交过了，无法回滚，这时候就需要执行它们的补偿事务。电商交易系统的例子可以通过设计补偿事务，把库存和用户余额加回，来实现分布式事务。分布式事务模型传奇基地交易 ：： ShardingSphere在单机数据库的基础上是心爱一个中间层（proxy），中间层基于单机事务加saga来实现分布式事务。高可用通过单机数据库的主备复制实现。如ShardingSphere-Proxy中，可以通过配置将分布式事务管理委托给Seata。当ShardingSphere-Proxy接收到一个跨多个数据库节点的事务请求时，它会将事务信息发送给Seata的全局事务协调器。全局事务协调器根据事务类型（如Base事务）和配置规则，对事务进行协调和管理。在事务执行过程中，ShardingSphere-Proxy会负责将事务操作路由到相应的数据库节点，并监控事务的执行状态。一旦事务执行完成，Seata会根据事务的执行结果，进行全局的提交或回滚操作，确保分布式事务的原子性和一致性。原生数据库：它们基于俩…

X-Engine
type: item
date: 2025-12-08T17:29:14+08:00
description: X-Engine背景笔记来自 https://www.bilibili.com/video/BV1rS4y1N7YKX-Engine是阿里云数据库PolarDB的OLTP存储引擎（基于Mysql），主要针对电商 平台的工作负载开发，双11期间的工作负载有以下特征。活动开始时每秒交易量激增（TPS）称之为海啸问题（The tsunami problem）海啸问题：双十一零点时的TPS增加到原来的122倍，在2018年双十一的最高峰时间段，每秒钟同事进行491000笔交易，这意味着数据库同时在处理的事务达到了七千万，阿里团队希望这样的压力下依旧能维持稳定的响应时间。我们可以看到蓝色的线在红色的线夸张的涨幅下，仍旧维持了一个相对稳定的表现。缓存（Buffer）很快会被大量数据填满，我们称之为泄洪问题（the flood discharge problem）泄洪问题：随着写入量的迅速增加，存储引擎的内存缓存会被大量数据迅速填满。这时，我们就需要对新增的数据进行泄洪，也就是尽快把数据转移到持久化内存中。这也意味着我们需要一个写入能力很强的存储结构。随着促销活动的转变，数据的冷热会发生迅速的切换，称之为访问热点急速变化问题（The fast-moving current problem）数据访问局部性是缓存能够生效的关键，但是双十一中大量的整点秒杀活动，会让一些非常冷的数据变成热点数据。这时候一般数据库的存储引擎很难对这种快速的切换做出反应。从而造成缓存命中率的大幅度下降。X-Engine就是需要能够快速从持久化存储中取出新的热点数据，并且放到缓存中来解决这个问题。访问热点急速变化问题：双十一会有各种促销活动，比如整点秒杀（seckill）活动，会使当前数据的冷热急速变化。此时数据引擎需要对数据热点的变更做出反应，快速持久化存储中取出新的热点数据。LSM因为电商业务涉及到大量数据的插入和修改，所以X-Engine采用了有杰出性能的LSM-Tree。其写入的操作不做原地更新，而是以追加的方式写入内存。每一次写到一定程度，即冻结一层，写入持久化存储。一条数据->写入memtable->flush到持久化存储->经compaction操作进入持久化存储的更下层->覆盖同意key的老版本数据/同一key的新数据到来时丢弃。真正删除老数据是在后台的compaction（合并）过程中完成的。当多个SSTable文件合并时，系统和会保留最新版本的数据，删除旧版本的数据和墓碑标记，从而释放磁盘空间。compaction操作不断地把相邻的层次数据合并，并写入更低的层次。合并的过程实际上是吧相邻俩层数据读出，按key排序，想通的key之保留更新的版本。LSM-Tree的特点是写强而读弱，我们可以简单理解为LSM-Tree的写入能力比B+ Tree搞一个数量级，读能力弱一个数量级。这是因为LSM-Tree的数据可能存在多个版本。PceanBase和Tidb底层都是用的LSM-Tree。整体架构图中上面的数据方方框就是内存，下面的数据方框就是磁盘。内存中的数据都是红色的，表示热数据，橙色表示温数据，浅黄色表示冷数据。越热的数据会越往上层放。写入：写入中x-engine会和其他数据库一样做一个redo log落盘操作。然后把数据写在active memtable里面。这里是一个脏页的概念，一个active memtable写满后，他会变成一个Immutable memtable。这时候Memtable就不会做改变了，Immutable memtable 会等待一个Flash操作，进入磁盘中变成持久化存储。持久化存储越上面就是越昂贵的磁盘。比如最上面是NVMe，下层是SSD，下层是HDD。磁盘的价格是由贵到便宜，价格也是由高到底。下层之后就是依次往下合并变成一个冷数据。读取：首先我们要读取磁盘里面的active memtable和immutable memtable。X-Engine总体可以分为内存中的热数据层和持久化存储的 温、冷数据层。内存中有active memtable和多个Immutable memtable以及Read Cache和Index。硬盘中的数据分为多层，不同层的数据通过不同的存储设备存储，每一层数据以Extent为单位组织。X-Engine通过MVCC和2PL（二阶段锁）实现了SI（Snapshot Isolation 快照隔离）和RC隔离级别。SI的核心特点是：事务启动时获取一个数据库快照，该事物的所有操作都基于这个快照，不会看到其他事物的修改。提交时，只有当机遇该快照的任何并发修改与该事物的修改没有冲突时才会成功提交。SI避免了ANSI SQL-92标准定义的三种并发异常：脏读、不可重复读、幻读。读操作不会被阻塞，对于读多些少的应用场景性…

Linux socket
type: item
date: 2025-10-30T18:42:48+08:00
description: Linux socketsockethttps://www.bilibili.com/video/BV1VWtyeVEsVhttps://blog.valiantcheng.com/misc/socket-intro.html如果发送数据包要从HTTP到数据链路封包全部构建一遍，这样的操作成本无疑是非常高的。操作系统的构建者为我们提供了很好的支持，他们屏蔽了底层的细节，抽象出一个统一的网络操作接口。 来便于上层用户使用网络。这个统一的接口就是socket。socket直译是插槽，我们只需关心如何插入，而不需要关心内部的实现。socket是一套抽象接口体系，可以理解为一套使用协议或者是一套API。因为是操作系统聪明封装提供的机制，因此具体使用需要以系统调用的形式来实现。socket接口提供了非常多种类的socket，没种都有其使用场景。这是所有socket操作的第一步，create socket。getsocket和setsocket相对于查看获取和设置socket选项。很多和socket交互的细节都通过这个方法来进行控制。接下来是bind，绑定操作，相当于把电器插头插入插槽。也就是将机器上的某个IP地址和端口组合进行绑定。其他操作者无法使用这个组合，时一种声明网络所有权的操作类型。listen传输层语义的监听操作，被动等待某个其他的网络实体向自身（bind过的IP和端口组和）发起通讯（通常是connect操作）accept，传输层的确认操作：确认某个尝试建立连接的网络实体和自身之间的关系。一旦accept正式的传输就已经建立，可以开始后续的数据交互connect：传输层语义的连接操作，连接某个传输层实体（也就是协议+IP+端口号）协议可能是TCP、UDP或其他四层协议（对于TCP就是经典的三次握手过程）shutdown、close：传输层语义的关闭操作send、sendto：传输层语义的发送操作recvfrom：传输层语义的接收操作上述系统调用相当多都是传输层的，这说明socket很大一部分功能是提供四层接口、也就是传输层网络之上的接口。但是并非所有操作都是四层的。这些是常见的socket类型。socket逐渐从网络通信层面发展演变成了一个数据通信操作的标准接口体系。对大部分开发者来说有许多我们常用TCP的选项，如SOL_SOCKET，SO_REUSEADDR其他类型的socket也可以设置很多针对性的选项，比如广播和多播的可设置选项就非常多TCP常见场景机器正常下线哪怕是kill -9操作系统也会自动释放tcp连接。如果此时已经建立连接，正在写入数据，会收到EOF相关字样的报错如果还未成功收到连接会收到connection refused类报错连接被拒绝了。正常情况出现在对端机器正常，但对应的服务已经挂掉了，这种情况下收到了发向不可用服务短口的数据，这是宿主机就会为不可用的服务进程对应socket链接发送一个RST（reset）数据包通知对端本地服务不存在或终止了。机器异常下线如果机器网线被拉断了。由于对端服务所在的机器对我们来说已经不可用了。所以此时不会有任何响应发回。此时发送端会按照socket选项的默认配置，也就是keepAlive以及timeout相关的socket选项，进行发送的等待和重试。如果超过一定的发送次数和时间，会报错并中断连接。在 不设置socket timeout选项的情况下，发送后等待的默认重传次数一般是15次，时间大概是900秒左右。每次重试的中间间隔会依次按照指数退避算法进行而不是固定的重试频率。如果在日常遇到了这样的时间间隔，要足够敏感。如果想要尽快失败，可以调整内核参数tcp_retries2来修改这里的行为。这种情况下报错分为俩种情况，如果路由器没有替代应答本地会直接报timeout错误，如果路由器替代应答，会发回一个ICMP包，告知destination is unreachable，那么我们最终收到的是unreachable类的错误。如果在这个过程中机器又恢复了，恢复的机器后已经丢失了原先的信息（可能是被强制断电然后重启）因此会直接发送reset包。告知我们对端的机器拒绝接收当前发起或正在进行的连接。类似进程挂掉的情况。UDP对端不可用一般来讲默认的UDP通信在对端服务故障、不存在或丢包时是会无限阻塞住的。所以一般底层库都是会提供默认的超时值设置的。比如DNS解析是2S，如果没有，在编程时要注意自己控制超时。UDP发送的数据包目的端口被对端判断为无效（目标机器上没有这个端口对应的服务）或是其他不可达场景，对端通常会回复ICMP错误消息（不回复RST是因为UDP协议没有RST报文）。类似我们在上一部分提到的路由器代答错误。UDP本身可以不调用connect方法直接发送数据的，这一点和TCP…

Docker与K8s笔记
type: item
date: 2025-10-28T20:54:59+08:00
description: Docker与K8s笔记https://www.bilibili.com/video/BV13GmDY9EtVDocker问题在容器之类的技术出现前，用户直接将程序部署到物理机上运行。主要用ystemd类的守护程序管理业务程序。这种方式可以很方便的查看每个业务程序的运行效率，并进行手动自动化操作。但是很多时候程序可能伴随着各种依赖。比如动态链接库某个命令行工具第三方依赖特殊配置文件等在多台机器规模化的情况下，通过下发部署脚本来实现。然而业务程序的依赖是复杂容易变动的。同一个程序的多个版本的依赖可能是不同的。我们还要管理多个程序在同一个服务器上运行的情况，程序之间的依赖可能产生冲突。最简单的方式就是每个程序独占一个机器。当时虚拟机技术发展已经较为成熟了。这样做的本质就是把程序放在虚拟机中，但虚拟机过于庞大，启停速度过慢，虚拟化性能损耗大。解决方案镜像大小虚拟机之所以那么大，是因为存储了操作系统运行所需要的所有文件。对于同一系列操作系统的不同版本来说，往往存在大量相同的文件，比如动态链接库，内核等等。如果相同的文件只存一份就可以大大减少镜像大小。如果多个虚拟机同时修改文件怎么办，那么就拷贝一个文件副本给新的镜像。其实就是copy-on-write的思想。同样，物理机和虚拟机上也存在大量重复的文件。因此这个优化思路不仅限于虚拟机，也适用于物理机和部署在其上的镜像。该思想实现的具体技术就是UnionFS或UFS，在容器出现之前的2004年，这个技术就已经出现了。它的初衷是希望在多个文件系统上提供一个统一的文件系统视图。启动速度虚拟机启动慢因为把珍格格操作系统的流程都包含了进去。理论上如果我们只关心程序运行本身的化 ，启停是不需要的。因此就可以去掉操作系统的启停。因此必须要实现一个轻量化的虚拟机，把操作系统相关的启停开销去掉。Linux就是在这样的背景下，逐渐发展出了实现上述需求的技术。namespace和cgroup。要实现一个程序的隔离，主要是以下方面。文件系统进程，线程管理进程线程通信网络用户权限硬件资源使用如果一个运行的程序在以上几方面都有自己的转述隔离空间和配额。那么实际上就实现了我们所说的轻量化隔离目标。这也就可以称为容器了。我们把程序放在容器中运行，保持隔离的前提下，只运行程序本身。那么其启动速度显然会比虚拟机快很多，这就满足了我们对隔离后启停速度的要求。namespace和cgroup。在2008年基本发展成型。这早于docker的出现。Namespace和Cgroup是Linux内核提供的两项关键技术，它们共同构成了容器技术的基石。简单来说，Namespace主要负责提供隔离性，为进程创建一个独立的运行环境；而Cgroup主要负责提供限制性，对进程组可以使用的资源进行约束和管理这样我们解耦了操作系统本身冗余的部分，而只关注进程本身的启停。这样容器的速度就和进程本身没什么不同了。性能损耗虚拟机是一个完整的操作系统，例如linux有很多内核进程来协助内核做系统管理工作。如果有多个内核，每个内核都会有自己的一套内核管理进程运行。从整体的角度来看，这些重复工作的进程就是额外损耗。容器隔离相关的技术就解决了这个问题。因为本质上所有容器内部的进程都是直接使用共用内核的，不需要维护独立内核。此外，硬件损耗也是一部分，早期虚拟化技术对于硬件的损耗较大，不过随着虚拟化技术的发展，CPU，内存已经被降到很低了。但是GPU io设备的损耗依然存在。最后docker就是把上述方案都打包到一起，并提供了一个用户友好的使用界面的系统。Docker依赖容器基础技术，UFS，和轻量化隔离能力。在此之上，docker设计了容器镜像和运行的标准,提供了完整的易用工具。让程序和依赖进行绑定，作为独立单元来进行管理。docker的命名是集装箱，很符合其实现的功能。即软件的标准化交付，将程序本身和依赖打包为一个标准单元。外部的使用者不需要关心docker镜像中到底有什么。K8s简介在docker诞生和流行后，带来了相当大的技术红利。随着docker被大规模用于生产实践。单机上的容器可以使用docker很轻松的进行管理，但有一定规模的企业往往会有很多个应用，很多台机器。一旦应用机器的数量急剧增长。管理这些容器就变得非常麻烦了。如果没有任何手段，那么就只能编写运维脚本，然后批量下发到每台机器进行管理了。这种方案操作效率低：大规模重复的劳动，人为容易出错缺失全局记录信息：部署后无法感知多个机器的运行情况，需要自己建设一个系统来进行监视精细化控制成本高：比如实现一组应用程序受控分批逐步升降级、根据资源使用针对应用程序所申请的各类计算资源进行扩缩容等调整，一台机器宕机后对上面运行的应用程序进行恢复故障转移等。难以进行高效的资源管理使用，分配不高效。服务难以高效管理和治理…

复制、容错原子存储（ABD）
type: item
date: 2025-10-23T11:47:00+08:00
description: 复制、容错原子存储翻译自：Replicated/Fault-tolerant atomic storage零基础入门分布式系统 7.2 Linearizability-腾讯云开发者社区-腾讯云云计算设计在这些日子里展示了对复制容灾的喜爱。例如在在数据中心级别进行赋值存储的是GFS，Dynamo，Cassandra。在广域网级别有PNUTS，COPS和Walter。我一直在寻找这个主题的基础分布式算法, 发现了在复制原子存储的很好的论文: Reconfiguring Replicated Atomic Storage: A Tutorial, M. K. Aguilera, I. Keidar, D. Malkhi, J-P Martin, and A. Shraer, 2010.复制提供了对崩溃故障的屏蔽.然而这是一个有限的容错能力,除非你重新配置存储服务拉埃添加新的副本替换掉崩溃的节点.事实证明,由于设计并发性和容错性问题,复制存储服务的动态重新配置是一个微妙/复杂的问题. MSR@Slicon Valley的这个团队已经研究复制存储中的重新配置问题一段时间了。但是在这篇文章中我不会谈论重配置工作，只会关注，复制、容错原子存储部分。多数复制算法这是一个用于复制容错原子存储的优雅算法，我认为每一个分布式系统的学者和开发者都应该知道。他简单强大并且有趣。我承诺你学习这个多数复制算法后大脑中会感觉良好。这个原算法发表在 Sharing memory robustly in message-passing systems, H. Attiya, A. Bar-Noy, and D. Dolev, 1995。在这里，我将根据教程论文中的讨论总结算法。算法使用了多数复制来提供在同步模型下，崩溃故障下原子读取和写入操作。（当然，FLP结果表明在这种模型下不可能解决共识，但这是一个比解决共识更弱的问题。）这里原子意味着系统提供线性化，强一致性，保证读取返回最新版本的数据。这种单拷贝一致性比Amazon Dynamo的最终一致性更强.在CAP定理中这个算法在CP测；当大部分副本不可达时，可用性在这里被牺牲了。写操作每个存储节点保存起客户端存储的最新值的本地副本，一级指示该新鲜度的时间戳。一个vt-pair表示(value,timestamp)的一对，存储节点保存该数据。为了执行write(v)操作，客户端执行俩个阶段，他执行俩个操作：首先执行get阶段，然后执行set阶段。get 阶段：vt-set=从大多数存储节点读取kv对。选择唯一的t'让t'>max(t在vt-set中)set阶段：write_request(v,t')在存储节点。如果t'>他存储的t，存储节点存储vt'。存储节点发送ack，当大部分节点回复收到，返回OK通过将客户端id与时间戳相邻，可确保t'的唯一性，因此，时间戳由一对数字和客户端id组成，按字典顺序排列。读操作read()操作非常类似写请求。客户端还背靠执行get和set阶段。唯一的区别是，在设置阶段，客户端写会在它在获取阶段学习到的最大时间的vt对。get 阶段：vt-set=从大部分存储节点读取vt对set阶段：write_request(v,t')在存储节点。如果t'>他存储的t，存储节点存储vt'。存储节点发送ack，当大部分节点回复收到，返回 Vread()中的set阶段是必须的，用来防止由于存储节点故障导致的震荡读取，在这种情况下，在写入过程中，连续的读取会在新旧值之间震荡，这违反了原子性原则。set阶段确保后续的read()返回的值至少是当前的read().这里的关键直觉是，任何两个多数存储节点总是至少有一个共同的存储节点。因此，如果某个客户端将值v存储在大多数存储节点上，那么另一个客户端在查询任何多数节点时都可以看到v。与 Paxos的关系多数复制算法似乎与 Paxos 共识算法密切相关。vt-pair 中的 t 对应于 Paxos 中的选票编号。Get 和 Set 阶段对应于 Paxos 的 phase1 和 phase2。（当然，由于多数复制不是为了共识，所以没有与 Paxos 的 phase3：commit 对应的东西。最后，读取作对应于 Paxos 中的学习作。现在是差异。在多数复制中，客户端不协调写入作，而在 Paxos 中，领导者被限制重新提议/重写具有最高 t 的值。同样为了避免领导者决斗问题，Paxos 依赖于领导者选举服务，以便系统最终收敛到一个领导者，该领导者可以安全地锚定/最终确定一个值作为决策值。（Paxos 中的领导人选举服务需要一些部分同步才能取得进展，因此只有到那时才能达成共识。总之，多数复制是 Paxos 共识的基石。结束语这种优雅算法的好处是它可以容忍/屏蔽少数存储…

对称加密和非对称加密的应用
type: item
date: 2025-10-17T12:13:23+08:00
description: 对称加密和非对称加密的应用简介对称加密加密和解密使用同一把秘钥。特点：速度快效率高，适合加密大量数据。但核心问题是如何安全地将秘钥分发给通信双方。常见算法：AES（标准最常用）, DES（已经淘汰）, 3DES, ChaCha20。AES主要有三种秘钥长度AES-128、AES-192和AES-256。这里的数字代表密钥的比特数。以AES128为例使动用地球上所有的计算机进行数十亿年的暴力破解（尝试每一个可能的密钥），成功的机会也几乎为零。AES 算法通过对数据块（16字节）进行多轮重复的变换来实现加密。密钥越长，需要的“搅拌”轮数就越多，以确保其安全性：AES-128：使用 128 位密钥，需要执行 10 轮加密。AES-192：使用 192 位密钥，需要执行 12 轮加密。AES-256：使用 256 位密钥，需要执行 14 轮加密。从 10 轮增加到 14 轮，意味着 AES-256 需要比 AES-128 多进行 40% 的加密运算。优点速度快效率高，加密和解密过程的计算量小，算法简单。这使得它非常适用于大量数据，如整个硬盘，大文件，数据库内容加密强度高：在秘钥长度相同的情况下，对称加密比非对称加密更难以被暴力破解（例如，256位的AES被认为在可预见的未来都是极其安全的）。资源消耗低：对CPU和内存需求较小，适合资源受限的环境，如移动设备，嵌入式系统。缺点秘钥分发问题：如何将秘钥安全的传递给通讯的一方？如果被拦截怎么办？如果与N个不同对象通信，就需要管理和维护N个不同的秘钥。随着通信对象的增加，秘钥管理复杂度和成本会急剧上升。由于通信双方拥有相同的密钥，接收方可以伪造一条消息并声称来自发送方，发送方也可以否认自己发送过某条消息。无法提供法律意义上的数字签名功能。非对称加密简而言之可以公钥加密私钥解密。也可以私钥解密公钥加密。使用一对秘钥，即公钥和私钥。公钥可以公开，用于加密；私钥必须保密用于解密。优点解决了秘钥分发问题。公钥公开传播无需保密。只要确保私钥的安全，通信的机密性就能得到保障。通过数字签名（私钥签名，公钥验证），可以确认消息发送者的身份，并且发送方事后无法否认其签名过的行为。秘钥管理简单：每个用户只需要保管好自己的一个私钥，并将公钥公即可。与N个人通信，也只需要一对密钥。缺点速度非常慢：加密和解密过程涉及复杂的数学运算（如大数分解、离散对数），计算开销比对称加密大几个数量级。不适合用于加密大量数据。资源消耗大：对CPU和内存的需求较高。对称加密应用领域主要用于对大量数据进行保密性保护的场景。全盘加密：BitLocker (Windows), FileVault (macOS), VeraCrypt 等。需要加密整个硬盘分区。数据量巨大，对称加密（如 AES）的高速度是关键。文或者文件夹加密。压缩软件，WinRAR，7-Zip的加密功能数据库加密：对数据库中敏感字段进行加密存储。VPN通信IPsec VPN, SSL/TLS VPN（在握手成功后）。无线网络加密：接入点（路由器）和客户端设备使用非对称机制完成身份验证后，会协商出一个对称的会话密钥，用于加密所有的无线数据。非对称加密应用领域数字签名：发送方用自己的私钥对消息的摘要进行加密，生成签名。接收方用发送方的公钥解密签名，并与自己计算的消息摘要对比。SSL/TLS握手安全电子邮件PGP/GnuPG协议加密货币：用户钱包地址本质是公钥的hash值当用户要花费加密货币时,需要用自己的私钥对交易进行签名,网络中其他节点可以用该用户的公钥来验证交易的合法性.私钥是拥有资产的唯一证明SSH认证前向问题假如和朋友邪心你们都从这本密码本里查找对应的密码来加密信息。如果有一天小偷偷走了这本密码本，他不仅能解密未来的所有信息，还能把过去窃听并记录下来的所有加密信息全部解密。SSH工作原理如今通常用非对称加密进行对称加密秘钥交换，用非对称加密进行实际通讯。如何对server的公钥进行认证？在https中可以通过CA来进行认证，可是SSH的公钥和私钥都是自己生成的，没法公正。没法公证。只能通过Client端自己对公钥进行确认。通常在第一次登录的时候，系统会出现下面提示信息：The authenticity of host 'ssh-server.example.com (12.18.429.21)' can't be established. RSA key fingerprint is 98:2e:d7:e0:de:9f:ac:67:28:c2:42:2d:37:16:58:4d. Are you sure you want to continue connecting (yes/no)?之所以用fingerprint代替key，主要是key过于长（RSA算法生成的公钥有1…

南京大学操作系统课程学习4
type: item
date: 2025-08-19T20:06:30+08:00
description: 南京大学操作系统课程学习4计算机安全在操作系统API上，我们可以构建命令行工具、编译器、数据库，浏览器等丰富应用。在8086时代，每次的访问都是直接映射到地址空间中，任何程序可以访问任何硬件。百度可以复制自己，然后感染更多的计算机程序。我们希望计算机有confidentiality，integrity，availability。有一些软件硬件的协同设计。分页机制和进程隔离系统调用和访问控制鉴权和授权加密审计与日志机密计算（TEE）理论上应用程序如果不信任操作系统，硬件也提供了特权来执行应用程序的代码。访问控制进程+虚拟内存已经实现了隔离。进程只能以ELF规定的权限访问自己的虚拟空间地址。系统是唯一访问操作系统对象的途径。（前提是内核没有被漏洞攻破）操作系统还有一些虚拟化机制、容器。 访问控制：限制程序对操作系统对象的访问。拒绝越权访问和修改。理论上可以直接用一张表来做访问控制。然而表可能特别大。unix用整数表示身份uid=0就是root用户，gid是完全自由的，虽然一般0是root，mode规定了rwx的权限。操作系统没有用户名的概念，操作系统也不负责验证用户名。有个程序叫做login负责进行密码的验证。现在系统通常使用shadow文件存储密码的hash。chsh，passwd只是直接修改了文件。操作系统只管uid不管如何解读用户。如今的操作系统有很多uid：real uid（ruid）saved uid（suid）effective uid（euid）filesystem uid（uid） 如今已经没人用了。操作系统还有非常多的访问控制的方法access control list（ACL）SELinux、AppArmorcapabilities攻防假设我们运行这样一段程序会发生段异常。这个叫做undefined behavior。容器、虚拟机、微服务进程一直以来都是操作系统中的核心抽象。作为应用程序的主题，运行它的方式在多年的发展中经历了许多变化。操作系统理论上是可以无限套娃的，比程序完成整个操作系统。取指令，编译代码，执行但是它的性能，不及原生的10%。full system emulation1997年 brings back an idea popular in the 1970s： virtual machine monitorVMware在1998年把这个技术做成了产品。VMware会把应该在系统内部调用的程序放在操作系统直接运行。但是系统调用会被VMware程序劫持变成一个软件模拟的系统调用。wsl在windows下模拟了linux地址空间，如果发现systemcall来自linux，就用linux子系统完成。之后intel提供了许多虚拟化指令集。 VT-x 2005->VT-d 2006 ->EPT 2008一些特权指令没法直接执行，比如关闭中断。可以让虚拟机来控制中断。VT-d提供了堪比物理机一样的高效的IO：IO虚拟化。EPT提供页表，8级页表其中4级给操作系统用，4级给虚拟机用。Intel 的 EPT（Extended Page Tables，扩展页表） 是硬件辅助虚拟化技术的一部分，主要用于优化虚拟机（VM）中客户机物理地址（GPA）到宿主机物理地址（HPA）的转换。在没有 EPT 的情况下，虚拟机监控器（VMM/Hypervisor）需要通过软件模拟页表，处理客户机的内存访问请求（GPA → HPA），导致大量性能开销（称为“影子页表”问题）。EPT 在硬件层面提供第二层页表（EPT 页表），直接由 CPU 的 MMU（内存管理单元）完成 GPA 到 HPA 的转换，避免了 Hypervisor 的软件介入，大幅降低内存访问延迟。最早的时候通过虚拟机进行超卖。虚拟机导致更容易的管理状态了。最早的系统的版本休眠是可以掉电的。掉电之后打开还是可以继续之前的状态的。但是在物理机上实现这个功能是很复杂的，需要把所有进程都停下来。遍历设备程序驱动。dump到磁盘里。但是虚拟机很容易实现这个功能，因为虚拟机就是一个进程，直接做一个core dump就可以了。另一个技术叫做空间转移的技术：optimizing migration of virtual computers还可以使用虚拟化技术做操作系统内核的热更新。不同虚拟化技术的实现差异全虚拟化（Full Virtualization）：使用二进制翻译（如早期VMware）动态替换特权指令，或依赖硬件虚拟化扩展。Guest OS无需修改，但性能开销较大。半虚拟化（Paravirtualization）：Guest OS被修改，主动调用Hypervisor提供的API（如Xen的hypercall）代替特权指令，避免陷入开销。容器（Container）：共享主机内核…

南京大学操作系统课程3
type: item
date: 2025-08-18T19:44:00+08:00
description: 南京大学操作系统课程3设备和驱动程序插入u盘后会发现/dev下会发生变化/dev/下的对象不会凭空创建udisks2才是真正执行mount的程序实际上我们看到的计算机设备都是输入输出设备。有些设备既能做输入也可以做输出，比如u盘。输入输出设备是能够与CPU交换数据的接口、控制器。给寄存器一个内存地址（address docider），CPU就可以直接使用质量（in/out/MMIO）和设备交换数据。我们可以预先定义好寄存器中每个值被写入会触发什么动作，然后用io设备写入寄存器，触发预先设定的动作。比如一根线、一条质量（树莓派上就有），GIPO（general purpose input/output）极简的模型 memory-mapped I/O 直接读取电平信号。串口（Serial Port）是一种用于串行通信的硬件接口，常用于计算机与外部设备（如传感器、单片机、工业设备等）之间的数据传输。现在的键盘都是软件完成的，但是早期cpu性能受限，需要其他硬件来帮忙做一些事情。比如每按下一个键盘，都会给CPU发送一次中断操作。8042负责安排以什么频率将键盘上的信号发给cpu。最早的打印机有个小的解释器。把所有用来划线的指令都物理写出来。将回边语言翻译成机械部件动作的设备。如果cpu想要更多的IO设备。我们想要兼容位置的设备，因此就出现了总线。总线提供了设备的虚拟化。总线上会有许多设备。如芯片控制器，核显，独显，温度控制器，usb接口。usb总线上还会挂在许多设备。如鼠标，麦克风，键盘。如今pcie6.0 x16带宽达到了128GB/s。总线自带DMA。现代的告诉设备都是直插PCIE的。程序不能直接访问寄存器，设备可以在程序之间共享的。仔细想想CPU和内存也都是设备。因此我们需要有个新的机制实现设备的虚拟化。这个机制就是文件。对于硬件设备，它们都是文件和控制接口。操作系统可以直接用文件的API来操作这些设备。代码会将读写操作翻译成设备能够看懂的指令。这个程序就叫设备驱动程序。目前操作系统能帮忙自动安装驱动，但更早的时候设备驱动都需要自己安装。对于linux系统来说，如果想实现设备驱动程序，只需要实现file_operation的结构体即可。用来吧系统调用翻译成设备能听懂的数据。比如/dev/null这个目录写入的所有数据都会被吞掉。读取任何数据都读取不到。即丢弃所有写入它的数据。它底层实现方式就是读取直接返回0，写入直接返回count（收到字节什么都不做）。设备对应的文件都是虚拟的。 设备不仅仅是数据还有配置。有俩种实现的方法：控制最为数据流的一部分（ANSI Escape code）提供一个新接口（request-response）因此设备最复杂的部分，设备是通过IO ctl来控制的。每个设备都有自己的IO ctl。对于一个io设备来说，非数据的功能全部靠这个系统调用。设备的复杂性是无法降低的，就是有那么多功能.UNIX的复杂：复杂的hidden specification和procfs。linux /dev/kvm是干什么的/dev/kvm是 Linux 系统中用于硬件虚拟化加速的字符设备文件，主要作用是为 KVM（Kernel-based Virtual Machine）提供直接访问 CPU 虚拟化扩展（如 Intel VT-x 或 AMD-V）的能力。io ctl是一个万能接口，用于处理设备特定操作，这些操作无法通过标准文件I/O实现。由于不同设备控制需求千差万别，ioctl必须高度定制化，导致七参数和用法高度依赖具体驱动实现。存储设备原理持久设备需要能够寻址+用电路改写。最早的存储设备是磁带。纸袋（今天是塑料）上均匀黏上铁磁性颗粒只需要一个机械部件转动定位。读取：放大感应电流，写入：电磁头（电磁铁）改变磁畴磁化方向。通过电磁感应写入读取数据。通过切割磁感线得到感应电流。因为这是一维的结构因此卷起来存储很多数据。比如磁带正反面可以都印刷数据，这样就不需要进行倒带了，直接翻一下磁带即可。但是磁带不能够随机访问，需要通过转轴遍历数据。磁带价格低，容量高，可靠性高。顺序读写需要等待定位，随机读写完全不行。因此应用场景是冷数据的存档和备份。比如为了数据合规合法，需要保存这个数据。磁带在计算机早期是主力存储设备。之后出现了磁鼓。用旋转的二维平面存储数据（无法内卷，容量变小）读写延迟不会超过旋转周期。 磁盘在二维平面上防止许多磁带。因此磁头只需要一个自由度就可以了。磁盘是很稳定的，哪怕近距离用强磁铁都很难磁化。内部有屏蔽和保护机制。磁道的大小可以做到微米级。可以做到每秒5400转。只要bit排布的足够，磁盘就可以进行良好的随机读取。工程优化，把bit竖起来排布。磁盘正面反面都存数据磁盘：价格低：高密度，低成本，容量高（2.5d上万个磁道…

南京大学操作系统课程2
type: item
date: 2025-08-14T22:11:43+08:00
description: 南京大学操作系统课程2多处理器编程c程序的状态机模型初始状态为main(argc,argv,envp)状态迁移：执行一条语句（指令）多线程的状态机模型增加一个特殊系统调用：spawn()增加一个状态机，有独立的栈，但共享全局变量。在spawn后，一个进程就出现了俩个线程。可以随时让其中一个状态机执行下一条指令。并发是逻辑上的同时执行，可以由操作系统运行库模拟出轮流执行。（也可以是真正同时执行）并行：丧失确定性，顺序性，一致性真正意义上的同时执行，有共享内存的多个处理器。同时执行指令(load/store访问共享内存)。简化的线程apispawn(fn)创建一个入口函数是fn的线程，并立即开始执行。join()等待所有运行线程返回，main线程返回的时候默认会join所有线程。多线程并发会导致确定性丧失。除了系统调用，程序的行为是确定性的。并发每次可能选一个程序运行，非确定性的程序理解起来相当困难。在单线程时，程序通常假设没人能干涉程序的状态。编译器可以对一段顺序的程序做出任何它觉得合理的优化，但是多线程下这个优化可能就会成为问题。如果想控制编译器的行为，可以通过插入不可优化的代码块来实现。比如while(!flag){ asm volatile("":::"memory") } //或者 标记变量不可优化 int volatile flag; while(!flag)顺序性并发编程会失去全局顺序性。实际上这样的顺序性是不存在的。比如想象一个有多个cpu的主板，根据相对论每个cpu对其他cpu的操作实际顺序都有不同的看法。为了性能，会选择宽松的内存模型，存储写入本地内存，再慢慢同步给其他处理器。允许load到local memory的旧值。共享内存对其他处理器的可见时间点可能不同。处理器内部也会乱序，对同一个地址的store/load允许bypass不同地址的load/store允许重排。乱序执行（out-of-order excution）是现代高性能处理器最值得骄傲的特性之一。处理器本质上也会做类似的事情（改变指令执行的顺序）。现在处理器当发现俩个指令是没有绝对顺序的时候会乱序进行执行。cpu中有个叫分支预测期的东西，会猜到底会执行哪条指令（超过百分之99的准确率）。CPU设计者面临了难题x86把所有的写都排好顺序。arm的实现更简单，性能更好。因此在ARM上模拟x86是很困难。互斥用lock和unlock标记一个代码块，标记的代码块multually exclusive。被秒级的代码块的执行可以理解为一次状态迁移。按照悲观的版本， 加入一半的代码不能被并行，即便有1万个处理器，也就加速了一倍。任何操作都不能超过光速，所以局部性原理很重要。最早的dekker的算法就是错的（证明有问题） 。还有Peterson算法，它们都非常复杂。model checker能自动遍历状态机空间。是一个形式化验证的工具，核心思想是通过穷举状态空间来证明或者证反系统的正确性，无需人工构造证明。但是直接实现peterson算法，因为指令重排序（关闭内存屏障），会出现错误。在加了内存屏障后，编译器会阻止一些指令重排等优化行为。比如x86的mfencearm的dmb ishrisc-v的fence rw，rw但是peterson算法只能解决俩个线程的访问。实际上我们实现互斥是通过硬件解决方案。计算机系统也是我们造的，我们可以在load和store上实现互斥。这是computer science和自然科学不同，许多游戏规则是我们定的。有没有stop the world指令呢？有的兄弟有的，最早单线程cpu就是用cli（x86）指令来陷入内核。eflags里有一个bit是IF(0x200)，对于单机处理系统，死循环=计算机卡死。默认操作系统的中断是不允许关闭的。不然容易单线程死循环。但是对于多处理器的情况来说，事情变得复杂。这时就需要硬件的帮忙。因此实际上只需要实现can_go=√，然后设置为x的原子操作，就完成了stop the world现代操作系统中，许多CPU都提供了原子的CAS指令，无需切换到内核态。这就叫做原子指令。一个不会被打断的load+计算+store。同样的我们可以实现多把锁。自旋锁的scalability问题：除了获得锁的线程，其他线程都在空转，一核有难，八核围观。如果代码执行很久，不如把处理器让给其他线程。应用程序，不能中断，持有自旋锁的线程被切换，导致100%的资源浪费。java的synchronized的底层锁升级为重量级锁的时候，使用了systemcall。在jvm中mutex的底层在linux的环境下就使用了pthread mutex lock。但要注意这个锁默认是不可重入的。而atomicinteger等原子变量使用的是死…

南京大学操作系统课程
type: item
date: 2025-08-14T13:19:00+08:00
description: 南京大学操作系统课程硬件视角下的操作系统状态机一定要有初始状态。在初始状态之后，硬件就是个无情的执行指令的机器。systemcall的每一次调用都需要陷入内核态，因此会产生调用开销。在操作系统的API中有一些特殊的指令可以与外界发生交互。可以让内部的程序感知外置的世界。操作系统会定时中断用户设置的程序。 图中为树莓派和外部程序发生交互的方式。老计算机有reset按钮，按下reset之后会对计算机状态进行重置。每一个寄存器都存在一个默认状态，intel x86就有这样的手册，其中会展示每个计算器的值。几百页的手册，阅读起来非常的expensive。每个架构都有不同的Reset状态。ARM 规定了CPU Reset，但没有规定设备计算器的映射RISC-V对于初始PC无规定，这是为了省电路，软件能做的硬件绝对不管。 在硬件层面没有操作系统的概念。如果有多个处理器，就是每次选一个处理器执行一条命令。见什么指令执行什么指令。首款的多核商用CPU是2001年IMB发布的。05年后多核CPU才在消费市场被普及。人类处理单核cpu的时间是比多核cpu来的长的。操作系统启动的时候，操作系统就变成了软件的中断调度器。一旦执行了一行非法的命令，整个状态机就会陷入混乱。CPU在启动的时候必须指向一个合法的代码，才能执行程序。那么到底是执行了什么代码？代码是谁放在这里的？答案：系统厂商代码，把一个特殊的存储器memory-map到CPU reset后的代码这段代码出生就有机器完整的控制权。固件：厂商固定在计算机里的代码，早期固件是ROM，要升级必须换芯片（如今可以直接升级固件了）最开始的这段代码叫Firmware。运行程序前的计算机系统配置。如CPU电压、内存时序、接口开关。不严格地说Firmware加载操作系统。这些配置生效可能要重启计算机。固件会根据用户的输入加载操作系统，比如bios（basic input OutPut system）就是一种Firmware。bios就可以设置各种硬件的状态。Firmware就可以看做一个小的操作系统。 CPU reset后初始化硬件；对接操作系统boot loader。这个系统也需要具备基本的功能，比如上网，不然就不能从网络启动一个操作系统。早期电脑没有键盘甚至不能开机。BIOS分为legacy 和UEFIBIOS ：IBM PC 所有设备BIOS中断是由specification的16-bit DOS时代BIOS常驻内存，提供I/O功能UEFI（Unified Extensible Firmware interface）：提供更丰富的支持，如驱动设备程序：指纹锁、USB蓝牙转接器连接的蓝牙键盘（在BIOS下无法加载）。早期Firmware通常是只读的。Firmware非常重要，因为出现bug导致死循环就无法进入系统。所以才有固件更新烧成砖的情况。Intel 430TX芯片组允许写入PROM，有序主板有写保护的条线，但默认可写。史上影响最大的病毒之一，就是打开写保护，然后写一个死循环。因此今天的固件更新只允许信任的固件，比如RSA签名机制。1983年PC-DOS2.0，Firmware会加载磁盘的前512字节到0x7c00，如果这512字节最后是0x55,0xAA就会进行执行。Firmware通过加载固定位置的引导程序加载MBR，然后MBR再加载硬盘。理论上我们可以看到程序加载的过程。 我们可以写446字节的16-bit代码446=512-2（55 aa）- 64（分区表）Grand Unified Bootloader的例子扫描磁盘，找到附近的ELF文件头，加载到内存，根据文件系统可能会多一步。这个ELF文件是Grub；弹出熟悉的选择系统窗口加载Linux的内核。现在的UEFI要复杂一点，不限制512字节，可以写任意的大小。只要在efi分区中放可执行文件，这个可执行文件就可以引导系统启动。MBR有boot文件夹。数学视角下的操作系统程序=状态机,every thing is state meachinegdb 单步执行=状态迁移。程序是一种数学严格的对象f(s)=s'。当我们想证明一个程序的正确性的时候，理论上可以枚举所有可能的情况，也可以证明程序的正确新。操作系统也是状态机，操作系统是状态机的管理器，状态机是可以被数学定义的。包括systemcall也是对于状态机的修改。在陷入操作系统的时候，操作系统会冻结当前状态机的状态。操作系统是最实用的并发程序，操作系统要保证每个进程只能访问自己的状态机计算机系统的不确定性，多核处理器的选择是无法控制的，操作系统可以主动随机选择状态机执行一步。系统外的输入有不确定性，read返回的结果也有俩种可能性。系统的调度和io以外一切都是确定的操作系统所有不确定性的唯一来源，…