如何在 Ubuntu 服务器上安装 Shadowsocks-Rust
由于Unbinilium的Twist脚本长时间不更新且已删库,想自己维护但确实不太会写shell脚本,只好把他脚本里的内容一步步写下来,当作日记,也方便自己日后使用。
前置准备
1 | # 在tmp目录操作 |
由于Unbinilium的Twist脚本长时间不更新且已删库,想自己维护但确实不太会写shell脚本,只好把他脚本里的内容一步步写下来,当作日记,也方便自己日后使用。
1 | # 在tmp目录操作 |
工作依赖Win7共享文件夹,经常抽风死机,需要人工手动重启。想要将其迁移至Ubuntu。
后话:实践证明,ntfs挂载方式+wps linux办公会很慢,不合适。另外,wps linux居然不支持emoji
1 | sudo apt install samba |
1 | sudo fdisk -l |
记录该设备ID
简单讲解下树莓派5安装Android的过程
从KonstaKANG下载当前最新版的LineageOS 22.2 (Android 15) for Raspberry Pi 5

1 | // client send |
客户端发起握手请求,数据格式如上图所示,具体如下
1 | // |
服务端响应内容如上图所示,具体如下
go的垃圾回收原理官方说法是是三色标记法+混合写屏障,但是,理论上怎么说是一回事,具体实现又是另一回事了,或者说实现已经偏离文档描述。总的来说,GC这部份的内容要比GMP跟内存分配都要复杂的多且出人意料。当前go版本:1.24
Go的GC并不扫描整个内存,只关注以下区域:
| 触发类型 | 备注 |
|---|---|
| gcTriggerHeap | 堆大小触发,heap内存达到一个临界点触发(最常用) |
| gcTriggerTime | 时间触发,超时2min未执行GC则强制执行 |
| gcTriggerCycle | 手动触发,用户调用runtime.GC() |
| 角色 | 作用 | 工作阶段 | 备注 |
|---|---|---|---|
| Mark Worker(标记工作线程) | 遍历对象图,标记存活对象,防止被错误回收 | 标记阶段(Marking) | 运行时创建多个并发 Mark Worker,加快标记速度 |
| Sweeper(清扫器) | 清理未标记的对象,将其内存释放回空闲列表(mheap.free) | 清扫阶段(Sweeping) | 逐步清理,避免一次性 STW(Stop The World) |
| Scavenger(内存回收器) | 释放长期未使用的堆内存,归还给OS以减少RSS | 后台运行(定期触发) | 主要针对大对象或空闲mspans,减少物理内存占用 |
标记工作线程
其中,标记工作线程的会根据工作模式进一步区分,如下
| 标记工作线程模式 | 备注 |
|---|---|
| gcMarkWorkerNotWorker | 默认,未运行 |
| gcMarkWorkerDedicatedMode | 专用标记任务,最高优先级,非抢占 |
| gcMarkWorkerFractionalMode | 比例标记任务,跟其他g共享时间,可被抢占 |
| gcMarkWorkerIdleMode | 空闲时执行的低优先级标记任务,需要p空闲 |
CPU限制
标记工作线程的CPU使用率被限制在25%(GOGC=100时),假设系统使用一个6核CPU,那么GC在标记阶段大约会使用1.5个CPU资源:
Sweep Termination(清理终止)
Mark(标记)
Mark Termination(标记终止)
Sweep(清理)
满足触发条件,启动下一轮GC
简单的说,标记然后清扫
golang的内存分配机制最初是基于TCMalloc,演化至今已经有了很大差异。其原理是:slab + tiling algorithm + 层级内存分配。本文仅介绍如何通过mallocgc分配内存,不涉及栈内存分配管理、手动内存管理等内容。当前go版本:1.24
内存分配基本单位-mspan(即slab),内部再划分更小的块(即tiling)
小对象(<=32KB)按预定义的sizeclass分成不同的mspan,每个mspan最低有8KB,切割成指定大小的块。而大对象(>32KB)的sizeclass=0,不限制大小,mspan不分块。如下
1 | // mspann -> sizeclass=0 -> nKB -> | <----- nKB -----> | |
如make([]int, 5),创建一块40B大小的内存区域,经过计算这个内存块会从sizeclass=5的mspan分配(该mspan的每个元素大小为48B)
参考TCMalloc,内存分配时主要分为3级:mcache、mcentral、mheap
执行mallocgc分配内存时,mheap、mcentral、mcache的关系如下
1 | // mallocgc |
这里需要了解系统内存的几个状态,以及go内部是如何从系统分配、释放内存的
| 状态 | 含义 |
|---|---|
| None | 默认状态,未映射,地址空间未被保留或使用 |
| Reserved | 已保留,但未提交,即地址空间已经被申请,但尚未向操作系统请求实际物理内存 |
| Prepared | 已提交但未使用,已经向操作系统申请了物理内存,但可能未完全初始化 |
| Ready | 可用状态,内存已初始化,可用于分配 |
状态转换函数
| 函数 | 主要作用 | 是否分配物理内存 | 是否映射虚拟内存 | 内存状态转换 | 备注 |
|---|---|---|---|---|---|
sysAlloc |
直接申请内存 | ✅ 是 | ✅ 是 | None -> Ready | 可能会触发 mmap |
sysReserve |
保留虚拟地址空间,但不映射 | ❌ 否 | ✅ 是 | None -> Reserved | 预留地址,后续 sysMap |
sysMap |
将预留的虚拟地址映射为实际物理内存 | ✅ 是 | ✅ 是 | Reserved -> Prepared | 只有 sysReserve 过的地址能 sysMap |
sysUsed |
标记某段地址正在使用 | ✅ 是 | ✅ 是 | Prepared -> Ready | 可能会触发 madvise 让物理页生效 |
sysUnused |
标记某段地址未使用,可以回收物理内存 | ⚠️ 可能 | ❌ 否 | Ready -> Prepared | MADV_DONTNEED,内存仍属于进程 |
sysFault |
让一段地址变成不可访问 | ❌ 否 | ✅ 是 | Ready -> Reserved | mprotect(PROT_NONE),用于调试 |
sysFree |
释放虚拟内存,归还给 OS | ✅ 是 | ✅ 是 | -> None | munmap,这段内存不能再用 |
我对Go内存分配的理解
为了防止内存分配速度过快,导致GC跟不上,分配时会判断是否需要协助GC标记/清扫。每次都要判断是否需要协助标记,而清扫只发生在获取新的mspan和大对象分配场景下
本文仅介绍程序运行流程以及GMP如何寻找g并运行,其他如抢占、死锁、信号处理、profiling等内容不打算深入。当前go版本:1.24
先讲几个概念
进程、线程、协程
线程与协程的映射模型
GMP模型
go早期的M:N模型遇到一些性能问题,如锁竞争激烈、线程创建/销毁频繁、CPU缓存失效等,为了解决这些问题引入了P。在GMP模型中
go程序启动时的入口是_rt0_amd64,该函数是汇编代码,具体如下
1 | // src/runtime/asm_amd64.s |
runtime·rt0_go也是汇编代码,比较长,主要逻辑如下
Timer-一次性定时器,Ticker-周期性定时器。从1.23版本开始,将异步实现改为同步实现,但你仍然可以使用AfterFunc创建异步定时器,或者通过改变asynctimerchan变量启用异步实现
asynctimerchan变量可选项如下
| asynctimerchan | description |
|---|---|
| 0 | 同步实现,从1.23版本开始启用 |
| 1 | 旧版异步实现 |
| 2 | 同1,异步实现,但修复了1的问题,debug用 |
定时器的精确度因系统不同而不同,具体如下
| OS | resolution |
|---|---|
| Unix | ~1ms |
| >= Windows 1803 | ~0.5ms |
| < Windows 1803 | ~16ms |
深入了解源代码前,先了解其功能如何使用
1 | package main |
上述示例代码运行效果如下
1 | go run main.go |
1 | package main |
上述示例代码运行效果如下
1 | go run main.go |
netpoll是golang用来处理网络I/O事件的底层机制,主要通过操作系统的I/O多路复用机制如Linux的epoll、BSD的kqueue、Windows的IOCP等来实现
核心的数据结构是pollDesc,用于存储与文件描述符相关的事件数据,一般被放入如epoll的epoll_event.data来传递信息
1 | type pollDesc struct { |
pollDesc部份字段讲解如下
atomicInfo是一个无符号32位整型数,每位用途如下| 16bit | 11bit | 1bit | 1bit | 1bit | 1bit | 1bit |
|---|---|---|---|---|---|---|
| fdseq | unused | pollFDSeq | pollExpiredWriteDeadline | pollExpiredReadDeadline | pollEventErr | pollClosing |
注意:fdseq占据20位数据,但在atomicInfo里,fdseq要向左移位16位,看起来是数据丢失了,没搞明白。同样有问题的还有taggedPointerPack
rg和wg的状态列表如下| state_name | state_val | description |
|---|---|---|
| pdNil | 0 | 默认值 |
| pdReady | 1 | io可读,下一个状态是pdNil |
| pdWait | 2 | 准备挂起,下一个状态是G pointer-挂起,pdReady-io可读,pdNil-超时/关闭 |
| G pointer | 0xabc | goroutine指针-挂起,下一个状态是pdReady-io可读,pdNil-超时/关闭 |
time涉及的内容较多,如生成/存储时间、比较时间、获取时间信息、时区、夏令时等,本文仅介绍一些自己感兴趣的地方
日历计算基于格里高利历(公历),1年有365天(闰年有366天)。同时支持墙上时钟(wall clock)和单调时钟(monotonic clock),其中墙上时钟用于时间同步、报时(time-telling),单调时钟用于时间测量(time-measuring)。并不是所有函数都支持单调时钟,如字符串编码/解码函数就会舍弃单调时钟数据
注意:
当前go版本:1.24
1 | package main |
运行结果如下所示
1 | go run main.go |