MichaelFu

好记性不如烂笔头

ABI(Application Binary Interface),即应用程序二进制接口,定义了函数调用时参数和返回值如何传递。就像C语言 x86-64 系统中,返回值保存在寄存器 %rax 中,前6个参数分别通过寄存器 %rdi%rsi%rdx%rcx%r8 以及 %r9 传递。

但是 Go 语言使用了一套跨架构通用 ABI 设计,它定义了数据在内存上的布局和函数之间的调用规约,这个调用规约是不稳定的,是会随着 Go 的版本进行变换的,称之为 ABIInternal。如果我们想开发汇编代码,应该使用稳定的 ABI0。所有原代码中定义的 Go 函数都遵循 ABIInternal,两种调用规约下的函数可以通过透明的 wrapper 相互调用。

之所以有两套调用规约,并且一个是稳定的(ABI0,承诺向后兼容),一个是不稳定的(ABIInternal,不承诺向后兼容)是因为一开始Go的调用规约约定所有的参数和返回值都通过栈传递,并且很多Go内部的包中有很多基于这个机制编写的汇编代码,例如 math/big,如果现在想升级调用规约,那么这么多汇编代码都得重写,显然不是很现实。所以,比较好的办法是引入一种新的私有约定,不承诺向后兼容,但可以在多个调用规约之间透明互调。私有的调用规约用于Go代码最终汇编的生成,稳定的调用规约用于汇编代码开发,由编译器完成两者之间的自动互调用。更多的内容可以查看 Proposal: Create an undefined internal calling convention

Go1.17 Release Notes Compiler 就对原有的调用规约做了更新,从基于栈的参数传递更新成基于寄存器,基准测试发现,性能有 5% 的提升,二进制大小减少 2%,但是 Go1.17 只在 Amd64 平台上实现了。

Go1.18 Release Notes Compiler 开始支持 GOARCH=arm64OARCH=ppc64, ppc64le。在 64ARM64PowerPC 系统上,基准测试显示性能提升 10% 或更多。

也就是说,在Go的调用规约中,我们需要遵循以下这些点:

  • 如果想写汇编代码,那么可以基于 ABI0,通过栈传递参数,汇编中使用 FP 等伪寄存器传递和访问参数以及返回值;
  • ABI0 是当前的调用约定,它在堆栈上传递参数和结果,在调用时破坏所有寄存器,并且有一些平台相关的固定寄存器;
  • ABIInternal 不稳定,可能会随版本变化。最初的时候它是与 ABI0 相同的,但 ABIInternal 为扩展提供了更多的可能性;
阅读全文 »

请看正文一张图。

阅读全文 »

汇编语言是最接近机器代码的人类可读语言,通过阅读汇编代码,我们可以了解到自己所编写的高级语言代码最终生成的指令都是什么,以便更好的掌握高级语言和了解计算机系统。Go 语言的汇编器基于 Plan9 汇编器,并且在此基础之上定义了一些创新。

阅读全文 »

在Go语言中,为了控制低级别的 runtime 行为,官方提供了一些环境变量,主要有:

  • GOGC
  • GODEBUG
  • GOMAXPROCS
  • GORACE
  • GOTRACEBACK

初次之外,还有用于编译期的 GOROOTGOPATHGOOSGOARCH,以及用于玩转SSA的 GOSSAFUNC

阅读全文 »

ELF(Executable and Linking Format) 是linux系统下可执行文件,目标文件,共享链接库和内核转储文件的格式。维基百科中是这样描述的:

在计算机科学中,ELF文件是一种用于可执行文件、目标文件、共享库和核心转储(core dump)的标准文件格式。其中核心转储是指: 操作系统在进程收到某些信号而终止时,将此时进程地址空间的内容以及有关进程状态的其他信息写出的一个磁盘文件。这种信息往往用于调试。

  • 可重定位文件(relocatable file) 它保存了一些可以和其他目标文件链接并生成可执行文件或者共享库的二进制代码和数据;
  • 可执行文件(excutable file) 它保存了适合直接加载到内存中执行的二进制程序;
  • 共享库文件(shared object file 一种特殊的可重定位目标文件,可以在加载或者运行时被动态的加载进内存并链接。
  • 核心转储文件(core dump) 是操作系统在进程收到某些信号而终止运行时,将此时进程地址空间的内容以及有关进程状态的其他信息写入一个磁盘文件。这种信息往往用于调试。

ELF文件主要由四部分组成:

  • ELF Header:主要包括文件的类型,架构,程序入口地址,Program HeaderSection Header 的大小,数量,偏移量等;

  • Programe Header:列举所有有效的 segments 的属性,描述如何创建进程运行时内存镜像,当内核看到这些 segments 时,使用 mmap 将他们映射到虚拟地址空间,为程序的运行准备;

  • Section:在ELF文件中,数据和代码分开存放的,这样可以按照其功能属性分成一些区域,比如程序、数据、符号表等。这些分离存放的区域在ELF文件中反映成section

  • Section Header:定义ELF文件中所有的 section,用于链接和重定位。对于可执行文件,有四个主要部分:.text.data.rodata.bss

ELF 文件各个部分的布局如下:

阅读全文 »

UML 是一种开放的方法,用于说明、可视化、构建和编写一个正在开发的、面向对象的、软件密集系统的制品的开放方法。UML展现了一系列最佳工程实践,这些最佳实践在对大规模,复杂系统进行建模方面,特别是在软件架构层次已经被验证有效。

阅读全文 »

在Go语言中,实现并发编程相当简单,因此存在大量场景需要同步操作限制对临界区的修改,避免出现不可期望的情况。因此,Go 语言在 sync 中提供了大量的基本同步原语,例如,最常见的互斥锁 sync.Mutex,它的名字应该来源于:Mutual Exclusion 的前缀组合,它对外只暴露了两个方法:LockUnlock,本篇文章将详细了解加解锁背后的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// A Mutex is a mutual exclusion lock.
// The zero value for a Mutex is an unlocked mutex.
//
// A Mutex must not be copied after first use.
type Mutex struct {
state int32
sema uint32
}

// A Locker represents an object that can be locked and unlocked.
type Locker interface {
Lock()
Unlock()
}

// 一些状态
const (
mutexLocked = 1 << iota // 1:表示锁定状态
mutexWoken // 2: 表示当前锁被从正常模式唤醒
mutexStarving // 4: 表示当前锁进入互斥状态
mutexWaiterShift = iota // 3:左移3位存储正在等待获取锁的goroutine的个数
starvationThresholdNs = 1e6 // 正在获取锁的goroutine等待1ms之后,会让当前锁进入饥饿模式
)
阅读全文 »

本文借助 visual studio code 搭建本地的 dapr 应用开发环境,另外讲述本地调试技巧,便于问题定位。

还是来调试 dapr 为我们准备的示例应用,secretstore,我们先将本地的 dapr 按照官方指导运行起来,并且将示例应用克隆到本地打开,另外还需安装好 Dapr Visual Studio Code扩展 ,并且执行 npm install 命令安装将应用的扩展,当所有就绪之后你的工作区应该看起来如下所示:

阅读全文 »

Dapr 在 2021 年发布了 v1.0 生产可用版本,预示着这个号称分布式运行时的框架终于可以进入各种大企业。说是尝鲜那已经是晚了很多,讲内部实现目前还不了解,本文主要是记录自己在本地创建 k8s 集群并且跑起来我第一个基于dapr应用的辛酸过程,辛酸是因为对k8s及dapr都不熟悉,加之国内网络限制,M1 芯片对某些软件不支持导致。

阅读全文 »
0%