0%

实数(real number)有理数(rational number)无理数(irrational number)的总称,其中有理数包含了 、整数、整数分数等,其小数部分有限或循环;而无理数则包含了 等,其小数部分无限不循环。

实数域(real number field)可以理解为实数的集合,该集合具有连续性完备性有序性等性质。像 这样的负数,毫无疑问属于一个实数,但是对一个负数求平方根(如 )的运算在实数域中不闭合,也就是说这在实数域中并没有根,或者说找不到一个实数的平方()等于

复数(complex number)可以看作是对实数的扩展,实数域的代数闭包(algebraic closure)复数域(complex number field),复数的运算法则和计算性质与实数类似。

复数通常用符号 来表示,其基本形式是 ,其中 被定义为虚数单位(imaginary unit)为复数的实部(real part)为复数的虚部(imaginary part),实部与虚部都是实数,一个实数与虚数单位的乘积为一个虚数(imaginary),如 ,所以复数其实就是一个实数和一个虚数相加构成的,当一个复数的实部为零时,该复数被称为纯虚数(purely imaginary number),而虚部为零时,该复数就是一个实数。

可以从图像上来理解复数,实数的定义域可以用一个一维的实轴(Re)来表示,类似地,虚数也可用一个一维的虚轴(Im)表示,以实轴为横轴、虚轴为纵轴可构成一个复数平面,即复平面(complex plane),那么复数就是复平面上的一个点,该点与复平面原点的距离称为该复数的模(modulus or magnitude)

Complex_conjugate_picture.svg
Complex_conjugate_picture.svg
from: 复平面 - Wiki

可以说复数的概念,将实数从一维提升到了二维,一个复数可以携带“二维信息(即实部和虚部)”,对复数进行运算也可以保留其“二维信息”,复数是一个非常强力的数学工具,可用来简化很多计算、分析问题,具有非常广的应用(Complex Numbers in Real Life)。在实际应用中(特别是物理应用上),一个复数具有什么样的意义,取决于我们对复数的建模方法,给一个复数的实部和虚部赋予具体的含义,比如:

  • 在电磁学中,可以使用两个实数来描述电磁场(电场强度和磁场强度),但直接用一个复数来描述会更渐变,即电磁和磁场分量分别用复数的实部和虚部来表示

  • 一个电路中的阻抗,包含了电阻和电抗,也可以使用复数来描述,实部为电阻,虚部为电抗
  • 几何、图形处理中,可以用复数来表示平移、旋转、伸缩等变换

golang 提供轻量的协作式与非协作式抢占调度的用户级线程,也就是 golang coroutine(golang 协程),简称 goroutine,可以通过关键字 go 像下面这样很方便的启动 goroutine

1
2
3
4
go func() {
fmt.Println("im a coroutine")
}()
go worker()

内核级线程与用户级线程

在类 Unix 操作系统中,有一个叫内核调度实体(KSE,Kernel Scheduling Entity) 的概念,这是能被操作系统内核调度器进行调度的最小调度单元,也就是内核级线程(KLT,Kernel Level Thread) ,每个线程都有它自身的上下文(Context),操作系统的内核需要与硬件中断配合,在分配给线程的时间片到期后,由中断触发,在内核态中完成线程上下文的切换,从而实现多线程并发。

用户级线程(ULT,User Level Thread) 的管理全权由用户态的调度器完成,相比之下,更加轻量、上下文切换的成本更低,能更加灵活的分配和动态管理线程栈的大小,虽然用户级线程仍然需要内核级线程驱动,但是操作系统无法感知到用户态线程的存在。用户级线程要是实现抢占式调度的话,通常需要启动一个独立的内核级线程或者定时器来监控,这是因为用户级线程在用户态中,无法直接依赖中断切换上下文,当一个协程在运行时间超出被分到的时间片仍未结束或者主动让出执行权的时候,由监视器来辅助触发抢占,否则其它协程需要持续等待。

内核级线程多数操作需要从用户态进入内核态进行,完成之后再回到用户态,在创建、销毁、上下文切换和体积上,成本相对用户级线程都显得更高,要想创建巨量的工作线程,由于操作系统的限制,对于内核级线程来说,几乎是不可能的,而用户级线程则可以,而且在同等数量级别的情况下,给操作系统带来的负担比内核级线程要更轻,所以在有高并发需求的时候,用户级线程可能是更优的选择。

用户级线程在设计时与内核级线程的关系模型主要有下面三种:

  • One to One(1:1):一个用户级线程对应一个内核级线程
  • Many to One(M:1):多个用户级线程对应一个内核级线程
  • Many to Many(M:N):用户级线程与内核级线程多对多,这是一种兼顾上面两种模型优点的混合型模型,也是 goroutine 底层使用的模型

GMP模型

文章使用的 golang 版本是:go version go1.20.10 linux/amd64

可以用 go env GOROOT 确认源码位置

主要相关联的代码有: - src/runtime/proc.go - src/runtime/runtime2.go - src/runtime/os_linux.go - src/runtime/sys_linux_amd64.s - src/runtime/asm_amd64.s

golang使用的是类 plan9 风格的汇编(assembly )指令(https://go.dev/doc/asm)

阅读全文 »

这有一张 M.2 接口的 NVM Express SSD(Non-Volatile Memory Express,这是一种用于固态存储设备的协议和接口标准,一般主板通过 M.2 接口走 PCIe 总线来进行连接) 需要插在旧主板 Asus Z87-A 上使用,但是这张主板并没有 M.2 接口,UEFI BIOS 也没有 NVME DXE 驱动,所以在开机引导的时候无法识别 NVME SSD。

让这张旧板使用 NVME SSD 跑系统的基本思路是,使用 M.2 转 PCIe 的转接卡来把硬盘接到主板上,然后将 NVMeExpressDxE 驱动刷进 Z87-A 最新的 UEFI BIOS 固件中,并更新主板的固件,使 BIOS 在选择引导设备的时候能够识别到 NVME SSD 设备。

编译 NVM Express UEFI DXE 驱动

NVM Express 驱动的官方目录在 NVM Express Drivers,源码位置在 NvmExpressDxe,DXE 驱动使用一种叫 EBC(EFI Byte Code)的虚拟指令集,是跨平台的,可通过以下方式获得:

最新的 Z87-A UEFI BIOS 固件包

Z87-A BIOS & 固件 下载 Z87-A BIOS 2103,这是官方最新版本的 .cap 固件包(AMI Aptio Capsule),包名为 Z87-A-ASUS-2103.CAP

将 NVMeExpressDxE.ffs 模块插入到固件内

这里使用 UEFITool 来操作(UEFITool 0.28.0,注意带 NE 后缀的包不能修改该固件,0.28.0 已经是有修改能力的最新包了,具体可看该 issue),不过这个工具在插入 ffs 的时候没有提供压缩功能,如果需要对模块进行压缩再插入的话可以使用 MMTool,或者直接使用压缩过的 ffs。

1. 使用 UEFITool 打开 cap 包

阅读全文 »

这篇文章会随时更新

Build

合理利用multi-stage

multi-stage可以带来:

  • 减少Image体积
  • 并行构建
  • 划分目标产出

下面以官方guide中的例子来逐步看这几个特性

1
2
3
4
5
6
7
8
9
# syntax=docker/dockerfile:1
FROM golang:1.20-alpine
WORKDIR /src
COPY go.mod go.sum .
RUN go mod download
COPY . .
RUN go build -o /bin/client ./cmd/client
RUN go build -o /bin/server ./cmd/server
ENTRYPOINT [ "/bin/server" ]
1
2
3
4
$ docker build --tag=buildme .
$ docker images buildme
REPOSITORY TAG IMAGE ID CREATED SIZE
buildme latest c021c8a7051f 5 seconds ago 150MB

以上是例子原始的dockerfile,这是一个Golang项目,它的产出 /bin/client/bin/server,包含了客户端和服务端的目标可执行文件,入口点是 /bin/server,构造出来的镜像有 150MB ,由于这个镜像的最终产出只有client和server两个目标可执行文件,所以说这个镜像大小相对来说有些大了,包含了非必须的部分。

减少Image体积

阅读全文 »