1
hronro 2020-12-14 00:51:34 +08:00
rust 默认不是静态编译吧
|
2
optional 2020-12-14 00:51:53 +08:00 via Android
go 推崇的是云原生。docker image 本来也不存在更新 so 的情况。
|
3
CrazyBoyFeng OP @hronro 是近几年 cargo 出来后才慢慢改为默认动态编译。不过一大批古早的 rust 项目都还保留着静态编译的习惯没变。甚至我看到很多 rust 教程都会教人改成静态编译,理由是“没有依赖的问题”。
|
4
hronro 2020-12-14 01:04:35 +08:00
@CrazyBoyFeng #3
古早的 Rust 和现在的 Rust 差不多可以当两门不同的语言来看了,最早 Rust 还有 GC 呢 |
5
cmostuor 2020-12-14 01:12:52 +08:00
go 本来就不是为嵌入式设备开发的 再说了 rust 和 go 是开源语言 能力强的可以把它魔改一个嵌入式版本的( github 上还真有个 go 语言的嵌入式版)
|
6
cmostuor 2020-12-14 01:15:57 +08:00
@cmostuor 动态编译生成 so 库是为了解决内存资源不足的问题而产生的技术现在这年代内存硬盘早就已经白菜价了好几十年为了那点空间而牺牲程序的运行速度 我个人觉得不值得
|
7
nightwitch 2020-12-14 01:40:43 +08:00 3
动态链接的技术发明都快五十年了,语言设计者怎么可能不知道这些缺点。以现在的存储设备和内存的价格,静态链接所增加的那点空间约等于不要钱。而动态链接不仅要增加运行时的开销,而且会符号错误而链接失败浪费程序员的时间,或者链接上了但是实际上 ABI 不兼容导致运行期间 segment fault 。
|
8
Jirajine 2020-12-14 01:41:33 +08:00 via Android
看标题还以为指静态类型。
你说的是链接,无论静态链接还是动态链接和编译器都没有关系,你想动态完全可以动态链接。 当然这些新语言自带的 build system 会默认以“静态”的方式链接同语言的库( c 库默认还是动态链接),内存硬盘增加、避免依赖问题且不说,主要还是因为 abi 稳定问题。 |
9
CrazyBoyFeng OP @cmostuor
动态静态不止是性能的问题,还有可维护性的考虑。主楼我就已经阐述了静态编译的其中一种维护灾难。 那个例子还可以再发散一下,如果当前所使用的静态应用没人维护了。那么它所包含的库的漏洞也就没法修复了。 一个程序只做一件事,可能有人会鄙夷这种 unix 哲学。不过在我看来,这是提升一个信息系统整体鲁棒性、安全性的很好的原则。各司其职可能在执行上效率不是最高的,但是当有问题发生时,能更好地处理。 动态静态其实还有一个授权协议的问题,不过这个问题不是十分紧要,foss 软件一般都不会触犯这个问题。 |
10
cheng6563 2020-12-14 01:52:10 +08:00 via Android
静态链接不等于静态便宜吧
|
11
chenqh 2020-12-14 01:56:37 +08:00
因为硬盘不值钱呀, 比内存还值钱
|
12
cmostuor 2020-12-14 02:03:02 +08:00
第 9 章 共享库
$Revision: 2.3 $ $Date: 1999/06/15 03:30:36 $ 程序库的产生可以追溯到计算技术的最早期,因为程序员很快就意识到通过重用程序 的代码片段可以节省大量的时间和精力。随着如 Fortran and COBOL 等语言编译器的发展, 程序库成为编程的一部分。当程序调用一个标准过程时,如 sqrt(),编译过的语言显式地使 用库,而且它们也隐式地使用用于 I/O 、转换、排序及很多其它复杂得不能用内联代码解释 的函数库。随着语言变得更为复杂,库也相应地变复杂了。当我在 20 年前写一个 Fortran 7 7 编译器时,运行库就已经比编译器本身的工作要多了,而一个 Fortran 77 库远比一个 C++ 库要来得简单。 语言库的增加意味着:不但所有的程序包含库代码,而且大部分程序包含许多相同的 库代码。例如,每个 C 程序都要使用系统调用库,几乎所有的 C 程序都使用标准 I/O 库例程, 如 printf,而且很多使用了别的通用库,如 math,networking,及其它通用函数。这就意 味着在一个有一千个编译过的程序的 UNIX 系统中,就有将近一千份 printf 的拷贝。如果所 有那些程序能共享一份它们用到的库例程的拷贝,对磁盘空间的节省是可观的。(在一个没 有共享库的 UNIX 系统上,单 printf 的拷贝就有 5 到 10M 。)更重要的是,运行中的程序如 能共享单个在内存中的库的拷贝,这对主存的节省是相当可观的,不但节省内存,也提高页 交换。 所有共享库基本上以相同的方式工作。在链接时,链接器搜索整个库以找到用于解决 那些未定义的外部符号的模块。但链接器不把模块内容拷贝到输出文件中,而是标记模块来 自的库名,同时在可执行文件中放一个库的列表。当程序被装载时,启动代码找到那些库, 并在程序开始前把它们映射到程序的地址空间,如图 1 。标准操作系统的文件映射机制自动 共享那些以只读或写时拷贝的映射页。负责映射的启动代码可能是在操作系统中,或在可执 行体,或在已经映射到进程地址空间的特定动态链接器中,或是这三者的某种并集。 --------------------------------------------------------------------------------------------- 图 9-1:带有共享库的程序 可执行程序,共享库的图例 可执行程序 main,app 库,C 库 不同位置来的文件 箭头展示了从 main 到 app,main 到 C,app 到 C 的引用 --------------------------------------------------------------------------------------------- 在本章,我们着眼于静态链接库,也就是库中的程序和数据地址在链接时绑定到可执 行体中。在下一章我们着眼于更复杂的动态链接库。尽管动态链接更灵活更“现代”,但也 比静态链接要慢很多,因为在链接时要做的大量工作在每次启动动态链接的程序时要重新做。 同时,动态链接的程序通常使用额外的“胶合(glue)”代码来调用共享库中的例程。胶合代 码通常包含若干个跳转,这会明显地减慢调用速度。在同时支持静态和动态共享库的系统上, 除非程序需要动态链接的额外扩展性,不然使用静态链接库能使它们更快更小巧。 绑定时间 共享库提出的绑定时间问题,是常规链接的程序不会遇到的。一个用到了共享库的程 序在运行时依赖于这些库的有效性。当所需的库不存在时,就会发生错误。在这情况下,除 了打印出一个晦涩的错误信息并退出外,不会有更多的事情要做。 当库已经存在,但是自从程序链接以来库已经改变了时,一个更有趣的问题就会发生。 在一个常规链接的程序中,在链接时符号就被绑定到地址上而库代码就已经绑定到可执行体 中了,所以程序所链接的库是那个忽略了随后变更的库。对于静态共享库,符号在链接时被 绑定到地址上,而库代码要直到运行时才被绑定到可执行体上。(对于动态共享库而言,它 们都推迟到运行时。) 一个静态链接共享库不能改变太多,以防破坏它所绑定到的程序。因为例程的地址和 库中的数据都已经绑定到程序中了,任何对这些地址的改变都将导致灾难。 如果不改变程序所依赖的静态库中的任何地址,那么有时一个共享库就可以在不影响 程序对它调用的前提下进行升级。这就是通常用于小 bug 修复的"小更新版"。更大的改变不 可避免地要改变程序地址,这就意味着一个系统要么需要多个版本的库,要么迫使程序员在 每次改变库时都重新链接它们所有的程序。实际中,永远不变的解决办法就是多版本,因为 磁盘空间便宜,而要找到每个会用到共享库可执行体几乎是不可能的。 实际的共享库 本章余下的部分将关注于 UNIX System V Release 3.2 (COFF 格式),较早的 Linux 系 统(a.out 格式),和 4.4BSD 的派生系统(a.out 和 ELF 格式)这三者提供的静态共享库。这三 者以几近相同的方式工作,但有些不同点具有启发意义。SVR3.2 的实现要求改变链接器以支 持共享库搜索,并需要操作系统的强力支持以满足例程在运行时的启动需求。Linux 的实现 需要对链接器进行一点小的调整并增加一个系统调用以辅助库映射。BSD/OS 的实现不对链接 器或操作系统作任何改变,它使用一个脚本为链接器提供必要的参数和一个修改过的标准 C 库启动例程以映射到库中。 地址空间管理 共享库中最困难的就是地址空间管理。每一个共享库在使用它的程序里都占用一段固 定的地址空间。不同的库,如果能够被使用在同一个程序中,它们还必须使用互不重叠的地 址空间。虽然机械的检查库的地址空间是否重叠是可能的,但是给不同的库赋予相应的地址 空间仍然是一种“魔法”。一方面,你还想在它们之间留一些余地,这样当其中某个新版本 的库增长了一些时,它不会延伸到下一个库的空间而发生冲突。另一方面,你还想将你最常 用的库尽可能紧密的放在一起以节省需要的页表数量(要知道在 x86 上,进程地址空间的每 一个 4MB 的块都有一个对应的二级表)。 每个系统的共享库地址空间都必然有一个主表,库从离应用程序很远的地址空间开始。 Linux 从十六进制的 60000000 开始,BSD/OS 从 A0000000 开始。商业厂家将会为厂家提供的 库、用户和第三方库进一步细分地址空间,比如对 BSD/OS,用户和第三方库开始于地址 A08 00000 。 通常库的代码和数据地址都会被明确的定义,其中数据区域从代码区域结束地址后的 一个或两个页对齐的地方开始。由于一般都不会更新数据区域的布局,而只是增加或者更改 代码区域,所以这样就使小更新版本成为可能。 每一个共享库都会输出符号,包括代码和数据,而且如果这个库依赖于别的库,那么 通常也会引入符号。虽然以某种偶然的顺序将例程链接为一个共享库也能使用,但是真正的 库使用一些分配地址的原则而使得链接更容易,或者至少使在更新库的时候不必修改输出符 号的地址成为可能。对于代码地址,库中有一个可以跳转到所有例程的跳转指令表,并将这 些跳转的地址作为相应例程的地址输出,而不是输出这些例程的实际地址。所有跳转指令的 大小都是相同的,所以跳转表的地址很容易计算,并且只要表中不在库更新时加入或删除表 项,那么这些地址将不会随版本而改变。每一个例程多出一条跳转指令不会明显的降低速度, 由于实际的例程地址是不可见的,所以即使新版本与旧版本的例程大小和地址都不一样,库 的新旧版本仍然是可兼容的。 对于输出数据,情况就要复杂一些,因为没有一种像对代码地址那样的简单方法来增 加一个间接层。实际中的输出数据一般是很少变动的、尺寸已知的表,例如 C 标准 I/O 库中 的 FILE 结构,或者像 errno 那样的单字数值(最近一次系统调用返回的错误代码),或者 是 tzname (指向当前时区名称的两个字符串的指针)。建立共享库的程序员可以收集到这些 输出数据并放置在数据段的开头,使它们位于每个例程中所使用的匿名数据的前面,这样使 得这些输出地址在库更新时不太可能会有变化。 |
13
cmostuor 2020-12-14 02:11:08 +08:00
第 10 章 动态链接和加载
$Revision: 2.3 $ $Date: 1999/06/15 03:30:36 $ 动态链接将很多链接过程推迟到了程序启动的时候。它提供了一系列其它方法无法获 得的优点: 动态链接的共享库要比静态链接的共享库更容易创建。 动态链接的共享库要比静态链接的共享库更容易升级。 动态链接的共享库的语义更接近于那些非共享库。 动态链接允许程序在运行时加载和卸载例程,这是其它途径所难以提供的功能。 当然这也有少许不利。由于每次程序启动的时候都要进行大量的链接过程,动态链接 的运行时性能要比静态链接的低不少,这是付出的代价。程序中所使用的每一个动态链接的 符号都必须在符号表中进行查找和解析( Windows 的 DLL 某种程度上有所改善,下面将会讲 到)。由于动态链接库还要包括符号表,所以它比静态库要大。 在调用的兼容性问题之上,一个顽固的麻烦根源是库语义的变化。和非共享或静态共享库而 言,变更动态链接库要容易很多。所以很容易就可以改变已存在程序正在使用的动态链接库。 这意味着即使程序没有任何改变,程序的行为也会改变。在微软 Windows 系统下这是一个常 见的问题所在,程序会使用大量的共享库,而这些库有不同的版本,库之间的版本控制非常 的复杂。多数程序在出货时都带有它们所需库的副本,而安装程序经常会不假思索的将安装 包中的旧版本共享库覆盖已存在的新版本库,这就破坏了那些依赖新版本库特性的程序。考 虑周全的安装程序会在使用旧版本库覆盖新版本库的时候弹出告警框提示,但这样的话,依 赖新版本库特性的那些应用程序又会发生旧版本库替换新版本库时发生的类似问题。 |
14
nuk 2020-12-14 02:21:34 +08:00 8
某一天,tls 爆出漏洞,楼主给自己的机器升级 ssl 的时候顺便更新了 libcrypt
然后楼主再也 ssh 不上这台机器了 这是发生在我公司的真实案例,还好客户就在本市,修复很快,不用赔钱 |
15
shyling 2020-12-14 02:43:25 +08:00 1
因为过去你说几 m 的库挺大,现在这个东西都算很小很小了
|
16
CrazyBoyFeng OP @nuk 所以,你们认为现代系统应该抛弃 so 、dll 库,崇尚发行更多的静态软件?
(不过刚才去查了一下还真有这种东西,stali 和 suckless,不知道实际体验怎么样) |
17
nuk 2020-12-14 03:09:49 +08:00
@CrazyBoyFeng 没有啊。。现代系统随便他怎么发展,只是静态链接对应用方便一点
不瞒你说,我这公司内有一些软件还依赖 openssl1.0,但是没人愿意花时间打 patch,后来就全部静态链接了 你说的也都有道理,可是禁不住大家想偷懒 动态库还有一个比较麻烦的问题,就是非 root 是没办法 LD_PRELOAD 啥的,而且 LD_PRELOAD 在 fork 的时候又容易有问题,搞个静态编译不就啥事都没了? 现在动态库的 ABI 破坏性更新太多了,又没有类似 windows 上 manifest 的技术,如果有的选,C 程序我是不想用静态编译的,一些库改成静态编译要花不少功夫。 |
18
akira 2020-12-14 03:50:14 +08:00
构建的时候有各种版本依赖是可以接受的。
运行环境带各种版本依赖才是灾难 |
19
lovestudykid 2020-12-14 05:17:25 +08:00
静态编译的话用户方便吧,下载用的流量和磁盘空间,比起向用户解释清楚各种依赖的麻烦不值一提
|
20
webshe11 2020-12-14 05:41:45 +08:00
一次编译,各种发行版 /docker image 运行
搭环境运行省事了,但是确实,如果库也要升级就麻烦了 |
21
waruqi 2020-12-14 07:24:14 +08:00 via Android
go 原本就不是给嵌入式设备用的,即使换成动态,光这个内存占用也够呛
|
22
holulu 2020-12-14 07:42:36 +08:00
go 就是为云而生的,而云讲就是部署效率。如果每部署个应用都得搭一遍环境,安装所有依赖的动态库,还有什么效率可言?当然是直接把应用一扔就能跑是最好的。要升级?没有的事,直接开个新的虚拟机或容器,把新版本应用一放,切流量,旧的虚拟机或容器直接删掉。
嵌入式还是老老实实用 C 吧,rust 现在也可以考虑。go 就算了,自带 GC 就已经注定它不适合用于嵌入式了。 |
23
popbones 2020-12-14 07:45:50 +08:00
各有各的好处,我觉得的问题在于试图论证一个方案比另一个方案绝对的好
|
24
whileFalse 2020-12-14 07:46:25 +08:00
见识过 java 你就知道企业根本不在乎内存。
|
25
xuanbg 2020-12-14 07:54:46 +08:00
@whileFalse 内存还是在乎的,毕竟主板上插槽有限,单条容量也有限。但硬盘是真的不在乎了……几百 T 随便整,不够用上千 T 也能整出来,也没几个钱。但实际上大多数应用场景几百 G 就够用了。
|
26
spadger 2020-12-14 08:12:13 +08:00
因为存储空间不再是问题,依赖是大问题
|
27
liuxey 2020-12-14 08:29:57 +08:00
随着硬件的发展,几 M 的大小基本可以忽略
随着软件复杂度的增加,依赖就是个黑洞 |
28
zengming00 2020-12-14 08:59:39 +08:00 1
一个占磁盘空间只有 20M 的 java 桌面程序一运行啥也没干,内存 300M 已经没了
|
29
love 2020-12-14 09:21:53 +08:00 via Android
包含在库里的系统软件和用户自己的软件还是不同的,系统软件当然是动态链接,用户自己分发的不但数量少且追求稳妥当然是静态链接,你看 snap appimage,这种分发格式都把依赖打包成一起了,不嫌浪费内存和硬盘。
|
30
virusdefender 2020-12-14 09:28:02 +08:00
现在的 go 默认就是动态的吧
|
31
hakono 2020-12-14 09:30:53 +08:00 via Android
你别,和动态链接造成的各种版本、依赖错误,一定的性能损失相比,静态链接这点问题根本不是问题
lz 说了半天也只说了静态链接库的共通依赖升级问题。这个我觉得并不是致命的,升级你系统里的软件也不过是一个包管理命令的事情罢了,都是自动完成的,至于下载数据量大,对现在的电脑和网络来说不过是小菜一碟罢了。更何况很多 go 项目都跑云里,运行在 docker 上,系统依赖什么不过是 Dockerfile 一行命令罢了 |
32
pmispig 2020-12-14 09:32:16 +08:00
动态链接在大部分场景都不适应这个时代了,静态链接才是符合潮流的。难以想象你装个软件还要先安装一堆 A,B,C 依赖。
|
33
dbskcnc 2020-12-14 09:37:05 +08:00
dll hell 的事情也很麻烦,针对 pc,服务器而言,静态连接部署太方便了
|
34
f6x 2020-12-14 10:02:58 +08:00
以前编译 c 程序出来是一两百 K, 现在编译出来就是几 M, 还依赖十几个 so, 很可能包括第三方 so
编一个 go 程序出来, 5.6M, 无视发行版,不管什么 kernel 版本, 直接跑. |
35
tabris17 2020-12-14 10:05:10 +08:00
动态库意味着库的版本冲突,从 ms 的 com 组件开始,就没有一个完美的解决方案
|
36
codehz 2020-12-14 10:18:22 +08:00 via Android 1
(没说到点子上,
动态链接 c 库肯定不是楼主想问的,那个确实都支持 自身编译成动态库才是新语言所缺少的( go 有 plugin,但是不是 first class 的) 这里的问题明明是语言自身缺少稳定的 ABI,没法把一些语言构造很好的导出,比方说 rust 泛型的导出( go 的 interface 导出,plugin 方式还是过于原始,再加上 go 以后也要基于 interface 做泛型了) |
37
linux40 2020-12-14 11:00:53 +08:00
> 第二天你需要升级所有依赖 tls 库的 go 、rust 应用
没有 ABI 兼容性这一说吗? |
38
fatedier 2020-12-14 11:03:17 +08:00
不会真有人敢随便更新生产环境的基础动态库吧?
|
39
whoami9894 2020-12-14 11:24:09 +08:00
静态编译和 RAM ROM 有啥关系
|
40
matrix67 2020-12-14 11:30:16 +08:00
从 dll hell 趟过来的!!!
从 py2 到 py3 搞一趟,就知道依赖不定死版本活该整天处理垃圾!!! 依赖一个类似 fastjson 的库活该整天升级!! 现在磁盘不值钱,网络不值钱,查问题的时间比较值钱!!!这条黑了仨!! |
41
zjsxwc 2020-12-14 12:02:26 +08:00
@waruqi
偏个题,现在嵌入式的定义已经改变了。 ``` 嵌入式新定义是在设计的时候可以把整机资源几乎占满。比方说设计的系统可以基本上 flash70%以上占用,CPU 平时长期 90%,内存占大半。因为功能固定需求固定,不会有其他因素干扰只要当前系统能跑起来就行,不用留手。 但是常规系统你让系统资源长期持续高负荷基本是不对的。 ``` 嵌入式用 java 也是很多的。 |
42
52coder 2020-12-14 12:05:49 +08:00
c 我记得默认是动态编译,参数加 static 可以搞成静态编译,一般在低版本 linux 编译的可以在高版本运行,反过来就跑不起来了,上周末学习了下 go,个人认为 go 当前的处理真的很好(想对比节省内存和磁盘,方便维护才是王道?),编个 go 出来,不用担心各种 glibc 版本依赖问题。(go 新手,有问题欢迎指出哈)
|
43
zhuangzhuang1988 2020-12-14 12:21:31 +08:00
还不是因为难,
c++表示 坑都是趟过的. |
44
aloxaf 2020-12-14 12:21:34 +08:00 3
LZ 你陷入了完美主义谬误:
涅槃谬误(英语:nirvana fallacy )或完美主义谬误( perfectionist fallacy )是一种非形式谬误,系宣称某个解决方案无法做到涅槃(完美),因此不可行。较白话的诠释是:“不能做到完美,就不应该做” 静态链接有缺点,但动态链接难道就是完美的?就像前面有人提到的,更新一个 libcrypt 把 ssh 炸了。虽然这个我没遇到过,但我遇到过不少乱动 glibc 把包管理器炸掉的,我系统里至今留着一个 pacman-static,就是预防我哪天手贱。又比如 A 依赖旧版的 C,B 依赖新版的 C,在静态链接下完全不是问题,但动态链接下多来几个就有够你受的了。 为啥现在都用静态链接?因为「大多数时候」静态链接更方便,仅此而已。 |
45
learningman 2020-12-14 12:28:58 +08:00
@nightwitch 感谢,一个查了半个月的问题发现是 ABI 不兼容
|
46
expy 2020-12-14 12:32:43 +08:00
希望大家对 Electron 也能这么友好。
|
47
cholerae 2020-12-14 12:35:32 +08:00
rust 不了解,go 原本设计上就没打算在嵌入式环境使用,不用考虑嵌入式环境的问题。
升级所有依赖 tls 库的应用我没觉得有啥问题,任何一个有完善构建系统的公司做到这个不是很容易吗?至于那点网络流量和内存实在不值一提。 |
48
JohnSmith 2020-12-14 12:57:38 +08:00
侧面反应“没有银弹”
|
49
FH0 2020-12-14 13:08:49 +08:00
本来静态编译和动态编译就是互补的,都有优点和缺点,看取舍。go 的主战场不是嵌入式,在嵌入式上能用就行。
|
51
tkl 2020-12-14 13:10:26 +08:00
40 楼没人提到交叉编译?
|
52
raptor 2020-12-14 13:11:50 +08:00
@aloxaf 哈哈哈。说到这个我想起来曾经在 FreeBSD 上把 pkg 搞坏了,最后就是靠 pkg-static 修回来的。
|
53
icyalala 2020-12-14 13:24:54 +08:00 1
新语言,尤其是这些正处在迅速发展中的语言,在最初版本不提供动态链接这个很好理解。Swift 从 5.0 之前一直不愿声明 ABI 稳定,当时作者解释,一旦 ABI 稳定,那后续语言特性就受制于 ABI 的设计,有一些新语言特性就难以实现了。所以每一版更新,Calling Convention 有变动,旧的动态库就无法兼容了。
静态链接和动态链接只是个[权衡],动态链接有你说的那些优势,静态链接同样也有很多优势。没有版本和依赖问题,容易发布容易维护,这是静态链接最大的优势,在 go 和 rust 的使用场景和生态下,静态链接是更优的选择。 |
54
YouLMAO 2020-12-14 14:12:17 +08:00
先问是不是,再问为什么,
cgo 动态 dll ``` import ( "fmt" "syscall" "unsafe" ) kernel32, _ = syscall.LoadLibrary("kernel32.dll") getModuleHandle, _ = syscall.GetProcAddress(kernel32, "GetModuleHandleW") ``` |
55
ysc3839 2020-12-14 18:47:58 +08:00 via Android
@CrazyBoyFeng #16 我认为能系统级共享的库应该保留。
但是那些只有一个可执行文件,不需要复用代码的程序则不应该带一堆 dll,应该静态链接。 大多数 Golang 程序显然属于后者,不过 Golang 内置库是应该考虑像 VC++ 那样发布一个安装包,安装到系统中。 |
56
salmon5 2020-12-14 20:54:21 +08:00
维护成本是长期大概率时间,其他安全等成本是小概率事件(而且这个小概率事件可以通过业务解耦解决);所以大部分静态编译是好的,先进的
|
57
cuishuang 2023-10-10 14:16:07 +08:00
@virusdefender 不用 CGO 啥的,肯定是静态的
|