0%

1. 参考

白栎旸. 数字IC设计入门(微课视频版)[M]. 北京: 清华大学出版社,2023-09-01:第1章.

2. 数字IC设计流程

2.1. 三个主要流程:

  1. 前端设计: 用 Verilog 编写 RTL 设计文件(Register Transfer Level,RTL,寄存器传输层)。

  2. 综合(Synthesis):将 RTL 转换位实际电子元器件的连接。输出文件称为网表(netlist),也是纯文本文件。

  3. 后端布局布线(PR):将网表变为实际电路图,类似 PCB 图,称为版图(Layout)。版图是晶圆厂(Foundry)能识别的通用格式。

    版图的绘制:

    • 布局(Place):元器件如何摆放;
    • 布线(Route):元器件如何连接。

    数字版图的绘制大部分由计算机自动完成,称为自动布局布线(Automatic Place and Route,APR),很多时候也称为 PR 。

    流片(TapeOut):Foundry 根据版图制造芯片。

2.2. 验收和验证(SignOff):

对质量进行把控和最终验收。

前仿真:检查 RTL 设计文件;用测试平台(Testbench)文件对实际应用环境仿真。这是在版图成型之前,称为前仿。一旦发现错误,验证人员会反馈给设计者修改,如此反复迭代,直至交付到综合阶段的 RTL 文件都是合格的。

SignOff 检查:检查版图设计;对时序和功耗检查。如果检查不过,首先是数字后端工程师自己努力,重新绘制版图或微调元器件位置,若无法达到目的,再修改综合策略,重新综合。若仍无法达到目的,则反馈到前端 IC 设计,在 RTL 上调整。

后仿真:在 SignOff 合格后,需要将网表和延迟信息提交给验证人员,进行后仿。对成型版图的时序进行最后验证和验收。后仿就是版图成型之后的仿真。

2.3. 可测性设计(Design For Test,DFT)

一个附属工序,原理是在芯片中加入与主要功能无关的附属电路,用于在芯片的生产过程中使用 TestBench 快速判断芯片功能的完好行、内部结构的正常性。

如果芯片中包含 DFT 电路,则 TestBench 可以向芯片的某些引脚发送事先准备好的测试向量,在芯片的另一些引脚上采样芯片对测试向量的反应。将事先准备好的预期效果与实际采样到的结果进行对比,从而判断内部功能是否正常。

DFT 功能也常常被称为扫描(Scan),即用 TestBench 扫描芯片内部之意。DFT 的检查对象是生产差错造成的芯片损坏,它不能检查设计问题。设计问题应该交给验证工序。

DFT 对芯片来说不是必需的,一些低成本的芯片没有插入 DFT 电路,在设计流程中,直接从电路综合过渡到后端设计。

2.4. 前端设计 VS 后端设计

  • 前端设计:抽象电路,只描述功能,不是具体电路。
  • 后端设计:具体电路,不仅要知道哪些元器件,还要确认这些元器件的摆放位置。

综合是前端和后端的分界线,综合之前没有元器件而只有功能;综合之后,设计才有了具体化的元器件。

当然,前端工程师往往强调心中有电路,并非实际电路,而是心中有功能相似的概念性电路。这样可以认清设计架构,并避免潜在的设计隐患。

3. 模拟IC设计流程

  1. 绘制原理图(Schematic)

    手动绘制。

  2. 原理图仿真

    一般由原理图设计者亲自完成。原因是模拟电路的设计参数选择范围宽泛,需要验证的场景也复杂,仿真验收标准不十分明确。这就需要设计者搭建关键的应用环境和场景,对电路上产生的反应做出判断,随时调整电路结构和参数。

  3. 绘制版图(根据原理图)

    手动绘制。

    模拟版图工程师和模拟IC设计师一般是分开的两个职业。

    模拟IC设计师一般也都掌握一些绘制版图的方法,单熟练程度以及对一些物理问题的处理方面,需要求助专业的版图工程师。

    模拟版图工程师可以根据原理图及通用的绘图规则直接绘制。但是对诸如社评电路及高功率管之类有着特殊要求的设计,版图工程师需要与模拟IC设计师进行充分沟通,在模拟IC设计师的协助下进行绘制。

  4. 抽取寄生参数

  5. 版图后仿

  6. 设计规则检查(Design Rule Check,DRC)

  7. 版图和原理图的一致性检查(Layout Versus Schematics,LVS)

  8. 流片

模拟IC设计没有那么多自动化成分,需要手动绘制原理图、版图,电阻、电容等元器件需要人工确定。

模拟IC仿真,无法像数字IC那样通过仿真验证和FPGA验证两种方法来相互印证设计效果。模拟仿真速度慢、情况多,很难覆盖真是使用中可能遇到的全部情况,因此,模拟设计具有很高的不确定,芯片的实际效果与仿真结果存在明显差距是经常发生的。

但是模拟电路也有一些无法被数字电路取代的特性。

可以想象,数字电路只需要处理 0 和 1 两种信号,但是模拟电路的数值域是实数域,包括所有连续的整数、浮点数、正负数,数据有无穷个。因此,即使是很小的电路,元器件数量不多,也需要设计者和仿真者从数值域中挑选一部份值作为设计和仿真中用到的值,绝对不可能覆盖所有情况。

4. 芯片整体规划

版图布局规划(FloorPlan):将芯片整体规划,以及内部数字、模拟电路的位置、面积、形状等特征的规划。

FloorPlan示例
  • Pad: FloorPlan的周围是芯片引脚(Pad)。Pad实际指的是芯片引脚之外的一块金属。
  • IO: 包括Pad和内部逻辑在内的整个引脚设计。

一个完整的引脚设计如图:

20250116222905
  • 封装: 芯片外面的塑料壳子称为芯片的封装,大体分为两种:插针式(引脚如针)、表贴式(引脚扁平)。

5. IC设计工具

EDA 公司:电子设计自动化(Electronic Design Automation)公司,粗略来说,数字设计常用 Synopsys ,模拟设计常用 Cadence , Mentor 在一些细分领域有优势。

数字/模块 数字流程 常用软件 其他软件
数字 RTL 编写 Vim/Gvim 普通文本编辑器
数字 仿真 VCS (Synopsys) Incisive (Cadence)
数字 看波形 Verdi (Synopsys) DVE (Synopsys)
数字 设计检查 Spyglass (Synopsys) Simvision (Cadence)
数字 综合 DC (Synopsys) ModelSim (Mentor)
数字 时序仿真 PT (Synopsys) nLint (Cadence)
数字 自动布局布线 ICC2 (Synopsys) Genus (Cadence)
数字 设计版图形式验证 Formality (Synopsys) Tempus (Cadence)
数字 提取寄生参数 StarRC (Synopsys)
模拟 原理图/版图/仿真等 Virtuoso (Cadence) Calibre (Mentor)
模拟 寄生提取/DRC/LVS Calibre (Mentor)

仿真加速器: Palladium (Cadence) 、 ZeBu (Synopsys) 。

5.1. 数字IC设计工具

RTL 仿真工具:有 Synopsys 的 VCS ,Cadence 的 Incisive (也叫 irun )。这些工具可以胜任前仿、后仿、UVM 架构(Universal Verification Methodology,通用验证方法学)的仿真。Cadence 的 irun 与模拟设计工具 Virtuoso 工具中集成的 AMS 仿真工具相结合,支持数字模拟电路混合仿真。

波形查看工具:一般集成在仿真工具中。VCS 的波形软件叫 DVE , Incisive 的波形软件叫 SimVision 。但在这个领域,一家名为 Novas 的公司的软件,以其明快的界面、方便的功能、快捷的操作,异军突起,得到了广泛的认可,它就是 Verdi (以音乐家威尔第的名字命名,前身叫 Debussy ,以音乐家德彪西的名字命名)。 Verdi 现已被 Synopsys 收购。 Mentor 的仿真和看波形软件叫 ModelSim ,主要用于 FPGA 功能的仿真。

RTL 语法检查工具: Atrenta 公司的 Sypyglass ,可以检查语法、跨时钟域处理方案的可靠性,甚至可以在内部执行综合、功耗评估和简单的布局布线,使它能全方位地给出设计建议。现已被 Synopsys 收购。 Cadence 的对应检查工具是 nLint 。

综合工具:即将 RTL 转化为实际电路的工具,常用的的有 Synopsys 的 Design Compiler (DC) ,该工具内部还有一些 Synopsys 开发的库,能够帮助设计者减小面积,提高性能,例如加法器、乘法器等,这些设计好的子模块在 DC 中被称为 DesignWare (DW) 。可以是让工具自动从 RTL 中识别出可用 DW 替换的代码,也可以是设计者手动例化 DW 模块。 Cadence 相应的工具叫 Genus (原名叫 RTL Compiler )。

版图自动布局布线软件: Synopsys 有 ICC2 (旧版为 ICC ), Cadence 有 Innovus (原名 Encounter ),两个软件都被广泛使用。由于两个软件的操作命令不同,后端工程师往往只掌握其中一种。 Synopsys 为了增强客户的黏性,开发了一个银河( MilkyWay )流程,从前端到后端,通过专用的二进制文件 (db) 传输,占用空间小,处理效率高,但也有许多公司使用 DC 综合,再将网表导入 Innovus 进行布局布线。

SignOff 工具:即对整个设计的时序、功耗评估。 Synopsys 的 Prime Time (PT) , Cadence 的 Tempus 。目前, PT 已经称为业内 SignOff 的标准。实际上,时序分析在 DC 中也能做,但两者在分析方法、细节考虑全面度、分析速度等方面存在差异。在综合时使用 DC 检查,而在 SignOff 时,使用 PT 检查。

形式验证工具:也称为逻辑等效行检查( Logic Equivalece Check, LEC ),将 RTL 和网表进行一一对照。因为从 RTL 到综合网表,以及从综合网表到后端网表的过程,可能意外地改变原有功能和设计意图,所以需要进行检查。 Synopsys 的工具时 Formality , Candence 的工具是 Conformal 。

寄生参数的提取工具:进入设计版图阶段,可以确定走线的延迟。该值受信号负载、线路长短、粗细、周围线路等多重影响,需要用模型和查表进行计算,才能得到确切的值。该过程称为寄生参数的提取。一般使用 Synopsys 的 StarRC 。提取出来的信息可用于 PT 进行 SignOff 。

上述工具,最主要的控制语法是 TCL 语言, EDA 工具大多以该语言为基础,扩展出各种专用命令。

除了以上数字设计工具,还有一些更加细分的工具类型,比如仿真加速器,有 Cadence 的 Palladium 、 Synopsys 的 ZeBu 。

5.2. 模拟IC设计工具

主要是 Cadence 的 Virtuoso 。与数字设计中繁多的工具不同, Virtuoso 能满足大部分设计需求,例如绘制并仿真原理图、绘制并仿真版图、数模混合仿真等。实际上, Virtuoso 更像集成开发环境( Integrated Development Environment, IDE ),它包含很多独立的设计工具,如仿真工具 Spectre 等。

SignOff 工具:Mentor 的 Calibre 可以用来提取寄生参数、进行 DRC 和 LVS 检查。

5.3. 职位、分工与工具

大职位 细分职位 功能 常用工具
数字 数字 IC 设计 设计芯片中的数字电路 Vim, VCS, Verdi, Spyglass, DC 等
数字 数字 IC 验证 验证芯片中的数字电路功能 VCS, Verdi 等
数字 数字 IC 后端 将抽象电路转换为版图 ICC2, Innovus, Calibre, PT 等
数字 其他职位, 如 SignOff, DFT 等 负责在数字电路中插入 DFT、对最终的版图进行时序、面积、功耗的检查等 DC, PT, Formality 等
模拟 模拟 IC 设计 设计芯片中的模拟电路 Virtuoso 等
模拟 模拟版图 将电路原理图做成版图 Virtuoso, Calibre 等
软件 嵌入式软件工程师 (1) 参与芯片开发/验证 (2) 参与 SDK (3) 参与芯片应用方案 Keil, SourceInsight, Visual Studio 等
软件方案 测试工程师 (1) 对芯片设计性能的测试 (2) 芯片量产测试 兼用软件和硬件工具
硬方案 应用工程师 做电路板,为芯片找到合适的应用场景 Pads, Altium Designer, Cadence 等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
管理
├── 研发
│ ├── 数字IC设计
│ ├── 数字IC验证
│ └── 数字后端
│ └── 模拟IC设计
│ └── 模拟版图
├── 测试
│ ├── 研发类测试
│ │ ├── 方案测试
│ │ └── 量产测试
│ └── 客户测试
├── 销售
└── 方案
├── 硬件方案
└── 软件方案

5.4. EDA 与 Foundry 的关系

普通软件公司无力开发 EDA 软件。因为 EDA 厂商需要和 Foundry 厂商紧密合作,才能获得有关的生产细节数据,从而帮助用户进行更加准确的仿真、寄生参数的提取、规则的检查。不同的 EDA 工具抽取的寄生参数可能不同,原因可能是不同的工具获得的工艺数据不同。

国内 EDA 厂商要想打破垄断,不仅要靠软件技术,还要与各大 Foundry 厂达成战略合作,共享工艺数据,才能做出有实用价值的 EDA 工具。

6. 概念

6.1. 寄存器:

register,这是对功能强调的名称,其物理实体很多,比如触发器 (Flip-Flop, FF)和锁存器(Latch)等等。在数字设计中,主要提倡触发器。

寄存器是受控于时钟沿的元器件, Foundry 会在标准单元库中提供多种寄存器。

触发器是由边沿信号而非电平信号导致数据存储的。触发源一般是时钟信号。

锁存器较少使用,它一般以非时钟信号为存储控制,靠电平触发。一般不连接时钟,只是连接一根普通的信号线。这种锁存器一般很少被纳入时钟计算中,因为它既不属于组合逻辑,又无法像触发器一样作为时序路径的起终点,所以时序上无法通过工具保证,只能通过工程师来保证。

本书说的寄存器,基本可以替换为触发器。在数字芯片 EDA 工具链中,两个概念也是混用的,一个 reg 既可以指寄存器,也可以指触发器。另外,所谓时序逻辑门电路,也基本等同于触发器。

6.2. 设计(Design)的边界

20250116211433
  • Pad:芯片(封装)之外的引脚。

  • 元器件:指芯片内的组合逻辑门电路及时序逻辑门电路。这些标准的元器件也被称为标准单元(Standard Cell)。元器件也称为逻辑门。

  • 标准单元库/工艺库:元器件的集合。

  • I/O: 如前“FloorPlan”对IO的解释,I/O是包括Pad和内部逻辑在内的整个引脚设计。在标准单元库中是一种标准单元,但是它比较特殊,可能与其他标准单元不放在同一个标准单元库中。驱动电平也可能不同。

  • 通用单元库: 在综合时,有一个中间步骤是将 RTL 抽象逻辑先映射为通用单元(Generic Boolean),然后映射到标准单元。通用单元与工艺无关,也不包含物理特性,只有功能属性,与 RTL 描述类似。而标准单元既有功能属性,又有延迟、电压等工艺属性,与工艺和 Foundry 相关。

  • lef文件: Library Exchange Format. 描述特定流片工艺下元器件的物理属性。

  • lib文件: Library. 描述特定流片工艺下元器件的功能属性。lib文件常常要被编译为二进制的db文件才能在某些EDA工具上使用。 lef 和 lib 文件都是技术库。

    数字设计只能在 Foundry 提供的已有的选项中挑选元器件。

  • def文件: Design Exchange Format. 设计除了 RTL 外,也可以带有物理信息,保存为 def 文件,基础内容是设计的形状和尺寸,扩展内容包括端口的分布位置、RAM和ROM等硬核的放置位置、内部元器件的放置和布线位置等。 RTL 和 def 统称为设计。

自己写的模块可以称为设备(Device)或模块(Module)。

用设备一词主要强调它具备相对独立的功能,在介绍SoC架构等上层逻辑时较为常用,设备也经常被替换为单元(Unit)一词,它指人为设计的单元,而非标准单元。一个设计可以包含很多个设备。

用模块一词,主要强调它在设计文件结构上的独立性,在介绍通用设计和底层逻辑时较为常用。

VMWare 的 Linux 虚拟机共享 Windows 主机的 VPN

方案 2:NAT 模式 + Windows 网络共享
另一种方式是保持 VMware 使用 NAT 模式,并通过 Windows 主机的网络共享功能,将 VPN 流量共享给虚拟机。

步骤:
在 Windows 上启用网络共享:

确保 Clash 在 Windows 主机上运行,并已连接到 VPN。
打开 控制面板 > 网络和共享中心 > 更改适配器设置。
右键点击你的 VPN 连接(通常是一个以 Ethernet 开头的适配器),选择 属性。
在 共享 标签页,勾选 允许其他网络用户通过此计算机的 Internet 连接连接。
选择虚拟机使用的网络适配器(例如 VMware 的 VMnet8 适配器),然后点击确定。
配置虚拟机网络为 NAT:

在 VMware 中,确保虚拟机的网络适配器设置为 NAT(这通常是默认设置)。
在 NAT 模式下,虚拟机会通过 Windows 主机访问网络,但流量会被路由到共享的网络适配器。
验证虚拟机是否可以通过 VPN 访问网络:

启动虚拟机,测试网络连接。
如果虚拟机连接成功并通过 VPN 访问互联网,那么配置就完成了。

WSL1 通过主机的 clash 客户端访问网络

方法一:配置环境变量(适用于普通网络请求)

适合 curl、wget、apt、pip、git 等命令行工具走代理。

1
2
3
4
echo 'export http_proxy=http://127.0.0.1:7890' >> ~/.bashrc
echo 'export https_proxy=http://127.0.0.1:7890' >> ~/.bashrc
echo 'export all_proxy=socks5://127.0.0.1:7890' >> ~/.bashrc
source ~/.bashrc

验证代理是否有效:

1
curl cip.cc

这个网站会返回你的公网 IP。

1
curl https://www.google.com

应该会返回网页。

临时开启:

如果你只想暂时走代理一次,不想改 .bashrc,你可以直接这样运行:

1
http_proxy=http://127.0.0.1:7890 https_proxy=http://127.0.0.1:7890 curl https://www.google.com

方法二:使用 proxychains 全局转发(适用于所有程序,包括二进制)

安装 proxychains,让任意程序走 Clash 的代理。

  1. 安装 proxychains
1
2
sudo apt update
sudo apt install proxychains -y
  1. 编辑配置 /etc/proxychains.conf
1
sudo vim /etc/proxychains.conf

在文件末尾加一行或修改为:

1
socks5  127.0.0.1 7890

注意:不要改成 http,要用 socks5。

使用 proxychains(只代理你想代理的程序):

1
proxychains curl https://www.google.com

优点:

  • 精准控制哪些程序走代理。
  • 不影响系统其他设置。

缺点:

  • 每次都要手动加 proxychains。

方法三:为单独的程序配置代理

  1. 配置 git 使用代理

执行以下命令设置全局代理(只设置一次即可):

1
2
git config --global http.proxy http://127.0.0.1:7890
git config --global https.proxy http://127.0.0.1:7890

若你之后想关闭 git 代理,可运行:

1
2
git config --global --unset http.proxy
git config --global --unset https.proxy
  1. 配置 pip 使用代理

在主目录创建 pip 配置文件:

1
2
mkdir -p ~/.config/pip
vim ~/.config/pip/pip.conf

添加内容:

1
2
[global]
proxy = http://127.0.0.1:7890
  1. 配置 apt 使用代理

编辑 apt 的配置文件:

1
2
sudo mkdir -p /etc/apt/apt.conf.d
sudo nano /etc/apt/apt.conf.d/99proxy

粘贴以下内容(假设 HTTP 代理端口是 7890):

1
2
Acquire::http::Proxy "http://127.0.0.1:7890";
Acquire::https::Proxy "http://127.0.0.1:7890";

方法四:配置 iptables + redsocks(实现真正的全局转发)

适合高级用户,WSL2 使用 TUN 模式实现系统级全局代理。这比较复杂,建议在需要所有流量强制走代理时使用。TODO

线段树(Segment Tree)是一种将数组区间存储为树的数据结构 ^1

时间复杂度

查询连续子数组 或 查询范围内的最小元素:O(logn)

最简单形式的线段树

高阶版本的线段树

TODO

参考

OI wiki

参考源码

/usr/include/c++/11/bits/shared_ptr_base.h
/usr/include/c++/11/bits/shared_ptr.h

博客:https://zhiqiang.org/coding/std-shared-ptr.html

std::shared_ptr 的性质

  1. 复制构造、析构是线程安全的。

标准库使用原子操作实现无锁的线程安全性。

  1. 写操作(例如 reset 、 赋值 operator= )是线程不安全的。

写操作和复制构造、析构的主要区别是:

  • 复制构造、析构函数中,单个线程只处理一个对象,复制构造函数将其他对象复制过来之后,不会改动其他对象的资源(引用计数、所管理的内存)。
  • 但是写操作可能多个线程都在处理该 shared_ptr 。例如多个线程都对同一个 shared_ptr 进行赋值:
1
2
3
shared_ptr<int> sp1 = make_shared<int>(1);
sp1 = sp2; // 线程 1
sp1 = sp3; // 线程 2

对比源码分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
     __shared_count&
operator=(const __shared_count& __r) noexcept
{
_Sp_counted_base<_Lp>* __tmp = __r._M_pi;
if (__tmp != _M_pi)
{
if (__tmp != nullptr)
__tmp->_M_add_ref_copy();
if (_M_pi != nullptr)
_M_pi->_M_release(); // 注意:线程 1 和线程 2 持有相同的 _M_pi!
_M_pi = __tmp; // 注意:线程 1 和线程 2 持有相同的 _M_pi!
}
return *this;
}

以上代码中两个“注意”可能同时在发生,例如:
线程 1 release 的时候,线程 2 在给 _M_pi 赋值;
或者两个线程同时在 release 或同时在给 _M_pi 赋值。

要点

  1. 管理的内存和引用计数都应该动态分配到堆( heap )上,这样多个 shared_ptr 对象才能更新同一份数据。
  2. 需要同时维护强引用计数和弱引用计数。
  3. 引用计数本身应该是一个控制块类,使用 delete this 来自动删除(析构)引用计数。

永远不要手动 delete use_count,因为其他线程可能此时正在使用该资源,例如解引用 *use_count

SharedPtr.hppview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
template<_Lock_policy _Lp = __default_lock_policy>
class _Sp_counted_base
: public _Mutex_base<_Lp>
{
void
_M_add_ref_copy()
{ __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); }

void
_M_release() noexcept
{
// Be race-detector-friendly. For more info see bits/c++config.
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_use_count);
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1)
{
_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_use_count);
_M_dispose();
// There must be a memory barrier between dispose() and destroy()
// to ensure that the effects of dispose() are observed in the
// thread that runs destroy().
// See http://gcc.gnu.org/ml/libstdc++/2005-11/msg00136.html
if (_Mutex_base<_Lp>::_S_need_barriers)
{
__atomic_thread_fence (__ATOMIC_ACQ_REL);
}

// Be race-detector-friendly. For more info see bits/c++config.
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count,
-1) == 1)
{
_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);
_M_destroy();
}
}
}

private:
_Sp_counted_base(_Sp_counted_base const&) = delete;
_Sp_counted_base& operator=(_Sp_counted_base const&) = delete;

// using _Atomic_word = int;
_Atomic_word _M_use_count; // #shared
_Atomic_word _M_weak_count; // #weak + (#shared != 0)
};

template<_Lock_policy _Lp>
class __shared_count
{
_Sp_counted_base<_Lp>* _M_pi; // 引用计数的指针

__shared_count(const __shared_count& __r) noexcept
: _M_pi(__r._M_pi)
{
if (_M_pi != nullptr)
_M_pi->_M_add_ref_copy(); // TODO: 有没有可能此时的 _M_pi 所引对象已经被释放了呢
}

~__shared_count() noexcept
{
if (_M_pi != nullptr)
_M_pi->_M_release(); // 没有直接 delete _M_pi,
// 而是使用 _M_pi 的成员函数来释放它。
}

__shared_count&
operator=(const __shared_count& __r) noexcept
{
_Sp_counted_base<_Lp>* __tmp = __r._M_pi;
if (__tmp != _M_pi)
{
if (__tmp != nullptr)
__tmp->_M_add_ref_copy();
if (_M_pi != nullptr)
_M_pi->_M_release(); // 没有直接 delete _M_pi,
// 而是使用 _M_pi 的成员函数来释放它。
_M_pi = __tmp;
}
return *this;
}
};

参考

https://gcc.gnu.org/onlinedocs/libstdc++/manual/ext_concurrency.html

在测试leetcode的 815.公交路线 一题时,发现一个现象,当我使用vector<vector<bool>>作为图的邻接矩阵的类型时,运行速度比使用vector<vector<int>>慢了近1倍。使用bool运行速度为600ms左右,使用int时运行速度为300ms左右。

以下是ChatGPT的回答:

在C++中,bool类型并不一定比int类型慢,但在某些情况下,它们的性能表现可能有所不同。以下是一些与性能差异相关的考虑:

  1. 内存大小
    bool类型在标准中只需要表示 true(非0)和 false(0),通常占用1个字节(8位),而 int 通常占用4个字节(32位)。因此,bool类型可以节省内存,在某些情况下会带来缓存效率的提升,尤其是在处理大量布尔值时(例如布尔数组)。
  2. 对齐和内存访问
    在某些架构上,处理单字节的bool类型时,可能会有额外的开销,因为处理器通常优化对齐访问。如果内存没有正确对齐,处理器可能需要额外的操作来处理非对齐的字节,导致性能下降。相比之下,int的对齐通常与系统的内存总线对齐更好(如32位或64位系统上),因此读取和写入可能更高效。
  3. 指令集和操作
    现代处理器在处理布尔值时通常会将其扩展为完整的寄存器大小(例如32位或64位)。因此,虽然bool类型的存储可能更小,但在实际操作中,处理器可能将它提升为int或更大的类型来执行逻辑操作。处理布尔值可能涉及隐式类型转换,这在某些极端情况下可能带来开销。
  4. 寄存器使用
    int 类型可以直接映射到处理器寄存器中进行操作,而 bool 类型的操作有时可能会涉及额外的逻辑运算(如掩码操作)以确保布尔值的正确性。
  5. 编译器优化
    现代编译器在优化时会考虑到bool与int的差异,并尽量优化代码,因此在大多数应用场景下,你不会感受到显著的性能差异。但在一些特定的场合(如大规模数组、频繁内存访问等),bool类型的使用确实可能带来性能影响,尤其是在处理器需要对齐或执行额外的操作时。
    总结
    bool类型通常并不会比int慢,实际的性能差异取决于平台、内存对齐、编译器优化以及应用场景。在大多数情况下,差异是微不足道的,但在对性能非常敏感的程序中,这种差异可能会显现。

启用调试模式

  1. 命令行启用调试:
1
2
#使用 -x 和 -v 选项运行 main.sh 脚本。
bash -xv main.sh

注意:调试输出是通过 stderr ,在试图重定向时要注意。

  1. 在脚本中启用调试:
1
set -xv
Short notation Long notation Result
set -f set -o noglob Disable file name generation using metacharacters (globbing).
set -v set -o verbose Prints shell input lines as they are read.
set -x set -o xtrace Print command traces before executing command.

参考链接

其他选项:

  • set +e:忽略错误继续运行(bash)。
  • set -e:遇错停止执行(bash)。
  1. 环境变量 PS4
1
PS4='+ ${LINENO}: '

可以搭配使用 PS4 环境变量,以在调试输出中打印命令所在的文件行号。

PS4 是一个 Bash 环境变量,用于定义调试模式下的提示符格式。
当你启用调试模式(通过 set -x)时,Bash 会在每一行执行之前打印调试信息,而 PS4 决定了这些调试信息的格式。
$LINENO表示打印行号。

trap DEBUG

设置DEBUG陷阱,该陷阱在每一条“简单语句”执行完毕之后会被调用一次。

1
2
3
4
5
6
7
8
9
10
11
set -o functrace
track_var="my_var"
old_value=""
check_var_change() {
new_value="${!track_var}"
if [[ "$new_value" != "$old_value" ]]; then
echo "$track_var changed to \"$new_value\" at $BASH_LINENO"
old_value="$new_value"
fi
}
trap check_var_change DEBUG

说明:监控my_var变量的变化,如果变化,则打印新值。

注意:实际测试中,我们发现trap监控变量变化似乎会延迟,而set -x打印的信息是可靠的。

特殊变量

参考:https://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html

  • BASH_LINENO: 这是一个数组变量,‌用于表示当前执行的脚本或函数中每一行的行号。‌它特别适用于调试,‌因为它可以追踪到函数调用栈中每一层的行号。‌通过访问BASH_LINENO数组的不同索引,‌可以获取到不同函数调用层级对应的行号信息‌。
  • LINENO: 获取当前脚本行号‌,‌而不涉及函数调用栈的追踪‌。
  • FUNCNAME: 函数名
  • BASH_SOURCE

判断文件是否存在

相关函数

  • access
  • stat
  • inotify_node
  • opendir
  • readdir

注意事项

文件系统并不会实时刷新缓存,尤其是在网络文件系统中。这会导致文件即使已经创建,accessstat函数依然返回”No such file”。
但是ls可以看到文件,这是因为lsaccess函数的实现机制不同。

例如:

NFS文件系统中,创建文件之后立即调用stat命令查看文件,stat会报告“文件不存在”。
删除文件之后立即调用stat命令查看文件,发现stat依然可以看到该文件。

我们可以改用readdir来读取目录,因为目录会被更快地更新缓存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$ which rpcgen
/bin/rpcgen
$ rpcgen
usage: rpcgen infile
rpcgen [-abkCLNTM][-Dname[=value]] [-i size] [-I [-K seconds]] [-Y path] infile
rpcgen [-c | -h | -l | -m | -t | -Sc | -Ss | -Sm] [-o outfile] [infile]
rpcgen [-s nettype]* [-o outfile] [infile]
rpcgen [-n netid]* [-o outfile] [infile]
options:
-a generate all files, including samples
-b backward compatibility mode (generates code for SunOS 4.1)
-c generate XDR routines
-C ANSI C mode
-Dname[=value] define a symbol (same as #define)
-h generate header file
-i size size at which to start generating inline code
-I generate code for inetd support in server (for SunOS 4.1)
-K seconds server exits after K seconds of inactivity
-l generate client side stubs
-L server errors will be printed to syslog
-m generate server side stubs
-M generate MT-safe code
-n netid generate server code that supports named netid
-N supports multiple arguments and call-by-value
-o outfile name of the output file
-s nettype generate server code that supports named nettype
-Sc generate sample client code that uses remote procedures
-Ss generate sample server code that defines remote procedures
-Sm generate makefile template
-t generate RPC dispatch table
-T generate code to support RPC dispatch tables
-Y path directory name to find C preprocessor (cpp)

For bug reporting instructions, please see:
<http://www.gnu.org/software/libc/bugs.html>.

Note:

% 可以用来转义某行,任何句首带有 % 的行将被视作字符串,直接放入到输出文件中。
注意:rpcgen 可能改变该行的位置,所以应该在输出文件中对这些行进行仔细检查。

rpcgen provides an additional preprocessing feature: any line that begins with a percent sign (%) is passed directly to the output file, with no action on the line’s content. Use caution because rpcgen does not always place the lines where you intend. Check the output source file and, if needed, edit it.

例如

1
#include "abc.h"

当执行 rpcgen infile 命令时,由于 “abc.h” 不存在,可能会报错。

但是如果在句首添加一个 % 符号,则可以绕过检查。

1
%#include "abc.h"

From ChatGPT:

In general, when a program enters a signal handler in a multi-threaded environment, the behavior regarding other threads depends on how the signal handler is set up and the specific signal that is being handled.

  1. Default Behavior: By default, when a signal is delivered to a process, it interrupt the thread that is currently running and executes the signal handler in the context of that thread. Other threads in the process continue running unless they are also interrupted by signals.
  2. Thread-Specific Signal Handling: Some signals, such as SIGINT (interrupt signal), SIGTERM (termination signal), or SIGABRT (abort signal), are typically delivered to the entire process, which means they can interrupt any thread. However, other signals, like SIGSEGV (segmentation fault) or SIGILL (illegal signal), are usually delivered to the specific thread that caused the signal.
  3. Signal Masking: In a multi-threaded program, you can use signal masking (sigprocmask in POSIX systems) to block certain signals in specific threads. This can affect whether a signal handler interrupts a particular thread or not.
  4. Asynchronous-Signal-Safe Functions: Signal handlers should only execute functions are considered “asynchronous-signal-safe” according to POSIX standards. These functions are designed to be safe to call from within a signal handler. Using non-safe functions in a signal handler can lead to undefined behavior.