一个可执行的二进制文件用nm命令可以输出如下信息,这里有个前提就是编译的时候不能去掉文件符号信息

0000000000f28100 R vendor/golang.org/x/text/unicode/norm..stmp_9
                 U vfprintf@@GLIBC_2.2.5
0000000000cf8450 T x_cgo_callers
0000000000cf8110 T x_cgo_init
000000000157c8f8 B x_cgo_inittls
0000000000cf8270 T x_cgo_mmap
0000000000cf82a0 T x_cgo_munmap
0000000000cf7f50 T x_cgo_notify_runtime_init_done
0000000000cf7f90 T x_cgo_set_context_function
0000000000cf82c0 T x_cgo_setenv
0000000000cf82f0 T x_cgo_sigaction
0000000000cf8090 T x_cgo_sys_thread_create
0000000000cf84a0 T x_cgo_thread_start
0000000000cf82e0 T x_cgo_unsetenv

其中前面是地址,中间是符号类型,最后是具体名称

符号类型如下:

符号类型说明
A该符号的值是绝对的,在以后的链接过程中,不允许进行改变。这样的符号值,常常出现在中断向量表中,例如用符号来表示各个中断向量函数在中断向量表中的位置。
B该符号的值出现在非初始化数据段(bss)中。例如,在一个文件中定义全局static int test。则该符号test的类型为b,位于bss section中。其值表示该符号在bss段中的偏移。一般而言,bss段分配于RAM中
C该符号为common。common symbol是未初始话数据段。该符号没有包含于一个普通section中。只有在链接过程中才进行分配。符号的值表示该符号需要的字节数。例如在一个c文件中,定义int test,并且该符号在别的地方会被引用,则该符号类型即为C。否则其类型为B。
D该符号位于初始话数据段中。一般来说,分配到data section中。例如定义全局int baud_table[5] = {9600, 19200, 38400, 57600, 115200},则会分配于初始化数据段中。
G该符号也位于初始化数据段中。主要用于small object提高访问small data object的一种方式。
I该符号是对另一个符号的间接引用。
N该符号是一个debugging符号。
R该符号位于只读数据区。例如定义全局const int test[] = {123, 123};则test就是一个只读数据区的符号。注意在cygwin下如果使用gcc直接编译成MZ格式时,源文件中的test对应_test,并且其符号类型为D,即初始化数据段中。但是如果使用m6812-elf-gcc这样的交叉编译工具,源文件中的test对应目标文件的test,即没有添加下划线,并且其符号类型为R。一般而言,位于rodata section。值得注意的是,如果在一个函数中定义const char *test = “abc”, const char test_int = 3。使用nm都不会得到符号信息,但是字符串“abc”分配于只读存储器中,test在rodata section中,大小为4。
S符号位于非初始化数据区,用于small object。
T该符号位于代码区text section。
U该符号在当前文件中是未定义的,即该符号的定义在别的文件中。例如,当前文件调用另一个文件中定义的函数,在这个被调用的函数在当前就是未定义的;但是在定义它的文件中类型是T。但是对于全局变量来说,在定义它的文件中,其符号类型为C,在使用它的文件中,其类型为U。
V该符号是一个weak object。
W该符号是弱符号,尚未专门标记为弱对象符号。
-该符号是a.out格式文件中的stabs symbol。
?该符号类型没有定义

一个程序本质上都是由 bss段、data段、text段三个组成的。

bss段:BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。

    BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。

data段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。

    数据段属于静态内存分配。

text段:代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区

    域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构

    也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量

    ,例如字符串常量等。

堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩

       减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被

       扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)

栈(stack):栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义

      的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,

      在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的

      返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现

      场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。

一般在初始化时bss段部分将会清零。bss段属于静态内存分配,即程序一开始就将其清零了。比如,在C语言之类的程序编译完成之后,已初始化的全局变量保存在.data 段中,未初始化的全局变量保存在.bss 段中。
text和data段都在可执行文件中(在嵌入式系统里一般是固化在镜像文件中),由系统从可执行文件中加载;而bss段不在可执行文件中,由系统初始化。

引用文章1
引用文章2

Varint编码

正常来说int在32位系统中,是4个字节长度,但如果你只需要存储100,1000这些比较小但数字但时候,依旧使用4个字节,这样其实就浪费了空间(其实提升了计算速度,因为不用计算)。换种思路,有没有一种编码能实现类似字符串的结构,不定长int呢,答案就是varint编码

Varint编码是一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。这能减少用来表示数字的字节数。

Varint 中的每个 byte 的最高位 bit 有特殊的含义,如果该位为 1,表示后续的 byte 也是该数字的一部分,如果该位为 0,则结束。其他的 7 个 bit 都用来表示数字。因此小于 128 的数字都可以用一个 byte 表示。大于 128 的数字,会用两个字节。每8位的第一位是最高有效位(most significant bit - msb)

例如整数1的表示,仅需一个字节:

采用 Varint,对于很小的 int32 类型的数字,则可以用 1 个 byte 来表示。但反过来,采用 Varint 表示法,大的数字则需要 5 个 byte 来表示。从统计和使用的角度来说,一般不会所有的消息中的数字都是大数,而且一般使用的时候都是从小到大,因此大多数情况下,采用 Varint 后,可以用更少的字节数来表示数字信息。

由于负数的高位为1,所以采用这种压缩处理的时候必须负数转成正数。

对数字666进行varint编码,666用二进制表示为

0000 0000 0000 0000 0000 0010 1001 1010

Varint编码就是每次从低向高取7位再加上最高有效位变成

1000 0101 0001 1010

zigzag编码

对于负数来说,因为最高位符号位始终为1,使用varint编码就很浪费空间,zigzag编码就是解决负数的问题的,同时其对正数也没有很大的影响。

int类型zigzag变换的公式 (n << 1) ^ (n >> 31)

步骤:

  • 左移1位可以消去符号位,低位补0
  • 有符号右移31位将符号位移动到最低位,负数高位补1,正数高位补0
  • 按位异或
  • 对于正数来说,最低位符号位为0,其他位不变
  • 对于负数,最低位符号位为1,其他位按位取反

-1的二进制表示为11111111 11111111 11111111 11111111,zigzag变换后00000000 00000000 00000000 00000001,再用varint编码,是不是很小了。

1的二进制表示为00000000 00000000 00000000 00000001,zigzag变换后00000000 00000000 00000000 00000010,再用varint编码,依然很小。

引用文章1

应用场景

levelDB就是将int存储为varint

经常看到docker image 有这些tag Alpine, Slim, Stretch, Buster, Jessie, Bullseye,
之前一直知道最小选择Alpine,想稳定就选Stretch,一直没探究他们的区别,现在总结一下

stretch/buster/jessie

稳定的Debian发行版是10.7(2020-12-05),其代号是Buster。Stretch是所有版本9变体的代号,Jessie是所有版本8变体的代号。正在开发但尚未稳定的未来版本是Bullseye和Bookworm和Trixie。现在在一些新的DockerHub的映像tag列表中常常看到这些tag。
如果您的代码与Debian操作系统的特定版本兼容,请选择这些映像之一,但如果是做新项目,明确清楚程序里没依赖老系统的api,则用最新稳定版的tag.

slim

仅安装运行特定工具所需的最少软件包,如果有空间限制并且不需要完整版本,请使用此tag,但是使用前需要经过完整测试,如果没时间测试,就使用上面的完整版本

alpine

Alpine映像基于Alpine Linux专门为在容器内部使用而构建的操作系统,占用空间最少,但是出现问题的时候比较难调试,因为什么工具都没有,如果优先考虑空间大小,肯定选这种tag.缺点是它不包含你可能需要的某些软件包。主要是,它使用更小的musl-lib代替glibc。如果你的程序需要的glibc特性功能,那就别用他。

windowsservercore

一直没用过,而且镜像都是上G的,特别大,如果真要跑iis等微软系统才能跑等软件才去用他吧

选择套路

  • 没时间测试程序对系统对依赖和兼容性的话,直接用stretch/buster/jessie这些准没错
  • 有时间测试程序对系统对依赖和兼容性的话,可以选择slim和alpine,像golang,如果使用了cgo,一定不要选择alpine。当然如果要求空间极致,肯定优先选择alpine
  • 在k8s集群中,选取镜像最好是和主机os一致的分发版本
  • 依赖windows系统的没得选,只能选windowsservercore

套路总结

时间换空间?空间换时间?

Libc

Linux操作系统运行时的基础库,提供了各类操作的基本调用。它的实现也有多种,以适应不同的应用环境。本文就着重介绍它们之前的区别和联系。

Glib

glib是gtk+的基础库,它由基础类型、对核心应用的支持、实用功能、数据类型和对象系统五个部分组成,可以在其官方网站下载其源代码。这是一个综合用途的实用的轻量级的C程序库,它提供C语言的常用的数据结构的定义、相关的处理函数,有趣而实用的宏,可移植的封装和一些运行时机能,如事件循环、线程、动态调用、对象系统等的API。gtk+与glib都是可移植的,glib最有名的就是GNOME桌面环境了。

1、Glibc glibc = GNU C Library

是GNU项(GNU Project)目,所实现的C语言标准库(C standard library)。广泛存在于目前最常见的桌面和服务器中的GNU/Linux类的系统中,都是用的这套C语言标准库。它实现了常见的C库的函数,支持很多种系统平台,功能很全,但是也相对比较臃肿和庞大。如果出现漏洞也影响巨大,如 glibc 幽灵漏洞等。

2、uClibc 一个小型的C语言标准库,主要用于嵌入式。

其最开始设计用于uClinux(注:uClinux不支持MMU),因此比较适用于微处理器中。 对应的,此处的u意思是μ,Micro,微小的意思。 uClibc的特点: (1)uClibc比glibc要小很多。 (2)uClibc是独立的,为了应用于嵌入式系统中,完全重新实现出来的。和glibc在源码结构和二进制上,都不兼容。

3、EGLIBC EGLIBC = Embedded GLIBC

EGLIBC是(后来)glibc的原创作组织FSF所(新)推出的,glibc的一种变体,目的在于将glibc用于嵌入式系统。EGLIBC的目标是:

(1)保持源码和二进制级别的兼容于Glibc源代码架构和ABI层面兼容

如果真正实现了这个目标,那意味着你之前用glibc编译的程序,可以直接用eglibc替换,而不需要重新编译。这样就可以复用之前的很多的程序了。

(2)降低(内存)资源占用/消耗。

(3)使更多的模块为可配置的(以实现按需裁剪不需要的模块)。

(4)提高对于交叉编译(cross-compilation)和交叉测试(cross-testing)的支持
Eglibc的最主要特点就是可配置,这样对于嵌入式系统中,你所不需要的模块,比如NIS,locale等,就可以裁剪掉,不把其编译到库中,使得降低生成的库的大小了。

Eglibc的特点
1.写程序,需要用到很多c语言的库函数。所有的库函数加起来,就是对应的C语言(标准)函数库。

2.目前在普通GNU/Linux系统中所用的C语言标准库,叫做glibc。其功能很全,函数很多,但是代码太多,编译出来的函数库的大小也很大,即资源占用也很多。

3.而嵌入式系统中,也需要C语言写代码实现特定功能,也需要用到C语言函数库,但是由于嵌入式系统中,一般资源比较有限,所以不适合直接使用(太占用资源的)gLibc。

4.所以有人就又(没有参考glibc,而是从头开始,)重新实现了一个用于嵌入式系统中的,代码量不是很大的,资源占用相对较少的,C语言函数库,叫做uClibc。并且,uClibc不支持MMU(内存管理单元)。

5.而后来glibc的开发者又推出个Embedded glibc,简称eglibc,其主要目的也是将glibc用于嵌入式领域。相应最大的改动就在于,把更多的库函数,改为可配置的,这样如果你的嵌入式系统中不需要某些函数,就可以裁剪掉,不把该函数编译到你的eglibc库中,使得最终生成的eglibc库的大小变小,最终符合你的嵌入式系统的要求(不能超过一定的大小),这样就实现了,把glibc引用于嵌入式系统中的目的了。

这样复杂多变使得一些主流发行版本在它们之间切换,Debian就用GLIBC替代EGLIBC。

可以简单的理解为: glibc,uClibc,eglibc都是C语言函数库:

  1. uClibc是嵌入式系统中用的,glibc是桌面系统用的
  2. eglibc也是嵌入式系统中用的,是glibc的嵌入式版本,和glibc在源码和二进制上兼容。

4、Musl-libc

C语言标准库Musl-libc项目发布了1.0版。Musl是一个轻量级的C标准库,设计作为GNU C library (glibc)、 uClibc或Android Bionic的替代用于嵌入式操作系统和移动设备。它遵循POSIX 2008规格和 C99 标准,采用MIT许可证授权,使用Musl的Linux发行版和项目包括sabotage,bootstrap-linux,LightCube OS等等。

参考文章

一开始直接top查看,发现pid=1334434的cpu占用过高

PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
1334434 root      20   0  493548  72236  28496 S 199.7  0.9   0:10.76 meetingBuyServi
1096315 root      20   0    2276   1236    948 S   1.0  0.0 120:01.83 bash
2373218 root      20   0 3849676   1.1g   3624 S   0.7 14.9 276:26.00 java

然后直接在main里接入pprof

import(
    "net/http"
    _ "net/http/pprof"
)

func main(){
....
     go func() {
       log.Println(http.ListenAndServe(":6060", nil))
    }()
}

编译发布到测试环境

本机执行go tool pprof http://x.x.x.x:6060/debug/pprof/profile

这个时候等待采集数据,这里需要注意到是,如果采集期间你的cpu没占满,采集结果是无法分析cpu的问题的。刚好我的这个服务一启动就是将cpu占满的

等待30秒左右后
自动进入pprof

(pprof) top10
Showing nodes accounting for 59.84s, 99.93% of 59.88s total
Dropped 19 nodes (cum <= 0.30s)
      flat  flat%   sum%        cum   cum%
    26.64s 44.49% 44.49%     26.64s 44.49%  sync.(*Mutex).Lock
    22.01s 36.76% 81.25%     22.01s 36.76%  sync.(*Mutex).Unlock
     6.37s 10.64% 91.88%      6.37s 10.64%  net.(*UDPConn).ReadFrom
     4.82s  8.05% 99.93%     59.84s 99.93%  github.com/micro/go-micro/v2/util/mdns.(*Server).recv

(pprof) traces
Type: cpu
Time: Jul 21, 2020 at 11:00am (CST)
Duration: 30.14s, Total samples = 59.88s (198.64%)
-----------+-------------------------------------------------------
    20.98s   sync.(*Mutex).Unlock
             github.com/micro/go-micro/v2/util/mdns.(*Server).recv
-----------+-------------------------------------------------------
       23s   sync.(*Mutex).Lock
             github.com/micro/go-micro/v2/util/mdns.(*Server).recv
-----------+-------------------------------------------------------
     1.17s   net.(*UDPConn).ReadFrom
             github.com/micro/go-micro/v2/util/mdns.(*Server).recv
-----------+-------------------------------------------------------
     1.03s   sync.(*Mutex).Unlock
             github.com/micro/go-micro/v2/util/mdns.(*Server).recv
-----------+-------------------------------------------------------
     950ms   github.com/micro/go-micro/v2/util/mdns.(*Server).recv
-----------+-------------------------------------------------------
     1.04s   sync.(*Mutex).Lock
             github.com/micro/go-micro/v2/util/mdns.(*Server).recv
-----------+-------------------------------------------------------
     1.43s   net.(*UDPConn).ReadFrom
             github.com/micro/go-micro/v2/util/mdns.(*Server).recv
-----------+-------------------------------------------------------
      10ms   runtime.sysmon
-----------+-------------------------------------------------------
     1.07s   github.com/micro/go-micro/v2/util/mdns.(*Server).recv
-----------+-------------------------------------------------------
     1.55s   net.(*UDPConn).ReadFrom
             github.com/micro/go-micro/v2/util/mdns.(*Server).recv
-----------+-------------------------------------------------------
     1.50s   sync.(*Mutex).Lock
             github.com/micro/go-micro/v2/util/mdns.(*Server).recv
-----------+-------------------------------------------------------
     1.03s   net.(*UDPConn).ReadFrom
             github.com/micro/go-micro/v2/util/mdns.(*Server).recv
-----------+-------------------------------------------------------
     1.44s   github.com/micro/go-micro/v2/util/mdns.(*Server).recv
-----------+-------------------------------------------------------
     1.10s   sync.(*Mutex).Lock
             github.com/micro/go-micro/v2/util/mdns.(*Server).recv
-----------+-------------------------------------------------------
     1.19s   net.(*UDPConn).ReadFrom
             github.com/micro/go-micro/v2/util/mdns.(*Server).recv
-----------+-------------------------------------------------------
     1.35s   github.com/micro/go-micro/v2/util/mdns.(*Server).recv
-----------+-------------------------------------------------------
      10ms   github.com/micro/go-micro/v2/util/mdns.(*Server).recv

发现cpu都在处理lock,找到问题,然后解决问题即可