Blog

LIBRARY_PATH和LD_LIBRARY_PATH的区别

LIBRARY_PATH和LD_LIBRARY_PATH的区别 #

这两个环境变量都是用来设置库文件的查找路径的,只不过使用的时机不一样。

其中 LIBRARY_PATH 是由链接器来使用的,一般系统默认是 gnu ld。对于大部分开发者来讲,如果 LIBRARY_PATH 没有设置好,在使用 gcc 或者 clang 这些编译器(其实它们都调用了 ld 这个链接器,真正做事情的是 ld)的时候,会碰到类似/usr/bin/ld: cannot find -lfoo的错误。LIBRARY_PATH 的一个等价的选项就是 -L 指定路径的选项。

而另一个 LD_LIBRARY_PATH 的环境变量是由动态链接器来使用的,即我们通过 ldd 看到的 ld-linux-x86-64.so.2 这个库。这个动态链接器是在程序加载运行时执行的。

因此,如果 LD_LIBRARY_PATH 没有设置好的话,会碰到类似./A.exe: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory的问题。

总结下来,LIBRARY_PATH 的使用时机是链接器在做链接的时候, LD_LIBRARY_PATH 的使用时机是在程序运行时。

Viewpoint #

From #

07 | 动态链接(上):地址无关代码是如何生成的?

静态变量的重定位

静态变量的重定位 #

还有一个比较特殊的是 static_var 变量。我们可以从 Sym. Name 里找到其余变量的符号,但 static_var 的符号没有出现,只有一个.data 的符号。

这是因为 static_var 变量本身是一个静态变量,只在本编译单元内可见,不会对外进行暴露,所以它是根据本编译单元的.data 段的地址来进行重定位。也就是说,static_var 的最终地址就是本编译单元的.data 段的最终地址。所以,它的重定义方法与 extern_var 等符号的重定位方法是一样的,区别仅仅在于它的符号被隐藏了。如下图所示:

你可能会有疑问,既然静态函数可以在编译的时候确定相对偏移,那为什么静态变量做不到这一点呢?

这是因为静态变量的位置是在 data 段,而对静态函数的访问是在 text 段。对应 text 段内部的偏移可以保证在链接的过程中不发生改变,但由于 text 段和 data 段分属不同的段,在链接的时候大概率会进行重新排布,所以它和引用它的地方之间的相对位置就发生变化了。所以静态变量的地址就需要链接器来进行重定位了。

Viewpoint #

From #

06 | 静态链接:变量与内存地址是如何映射的?

R_X86_64_PC32的重定位计算方式

R_X86_64_PC32的重定位计算方式 #

重定位表的数据结构是这样的:

typedef struct {
  Elf64_Addr   r_offset; /* 重定位表项的偏移地址 */
  Elf64_Xword  r_info;   /* 重定位的类型以及重定位符号的索引 */
  Elf64_Sxword r_addend; /* 重定位过程中需要的辅助信息 */
} Elf64_Rela;

其中,r_info 的高 32bit 存放的是重定位符号在符号表的索引,r_info 的低 32bit 存放的是重定位的类型的索引。符号表就是.symtab 段,可以把它看成是一个字典,这个字典以整数为 key,以符号名为value。

重定位表中,类型为 R_X86_64_PC32,其重定位计算方式为:S + A – P。

这里的 S 表示完成链接后该符号的实际地址。在链接器将多个中间文件的段合并以后,每个符号就按先后顺序依次都会分配到一个地址,这就是它的最终地址 S。

A 表示 Addend 的值,它代表了占位符的长度。

P 表示要进行重定位位置的地址或偏移,可以通过 r_offset 的值获取到,这是引用符号的地方,也就是我们要回填地址的地方,简单说,它就是我们上文提到的用 0 填充的占位符的地址。

这里 S 与 P 所表示的地址都是文件合并之后最终的虚拟地址,由于我们无法获取链接器中间过程的文件,所以,我们需要通过查看链接完成后的可执行文件,来寻找这两个地址。

我们以 extern_var 的变量为例,具体跟踪一遍重定位的过程。

00000000004004ad <main>:
  4004ad:       55                      push   %rbp
  4004ae:       48 89 e5                mov    %rsp, %rbp
  4004b1:       48 83 ec 20             sub    $0x20, %rsp
  4004b5:       8b 05 75 0b 20 00       mov    0x200b75(%rip), %eax # 601030 <extern_var>
  4004bb:       89 45 e8                mov    %eax, -0x18(%rbp)

上边输出部分是对生成可执行文件的反汇编。根据 S、A、P 的定义,我们知道对于 extern_var 来讲:

...

读十六进制补码

Content #

计算一个数的补码非最常见的的方法是每位取反后再加1,另外一种做法是将向量分为两部分: \[[x_{w-1}, x_{w-2},\cdots,x_{k+1},1,0,\cdots,0]\] 只要\(x\ne 0\),就能找到这样的\(k\)。请问该值的非可以如何表示?

\[[\sim x_{w-1},\sim x_{w-2},\cdots,\sim x_{k+1},1,0,\cdots,0]\]

调试程序时遇到值0xfffffffa,其十进制下对应的值会是多少?(读补码)

0xf的补是0x0,0xa的补是0x5。求补码非的算法是,对每一位求补,再对结果加 1。0xfffffffa做补码非的结果是6,因此该数表示-6。

Viewpoint #

From #

大端与小端

Content #

如果把一个数看成一个字符串,比如11223344看成"11223344",那么它的尾端很显然是44,前面的高还是低就表示尾端放在高地址还是低地址,它在内存中的放法非常直观。

大端——高尾端,小端——低尾端。

x86使用小端存储。

各自的优势 #

小端模式 :强制转换数据不需要调整字节内容,1、2、4字节的存储方式一样。大端模式 :符号位的判定固定为第一个字节,容易判断正负

Viewpoint #

From #

惰性删除

Content #

Redis中惰性删除(lazy free)指的是什么? Redis 主线程启动后,会使用操作系统提供的 pthread_create 函数创建 3 个子线程,分别由它们负责 AOF 日志写操作、键值对删除以及文件关闭的异步执行。

主线程通过一个链表形式的任务队列和子线程进行交互。当收到键值对删除和清空数据库的操作时,主线程会把这个操作封装成一个任务,放入到任务队列中,然后给客户端返回一个完成信息,表明删除已经完成。

但实际上,这个时候删除还没有执行,等到后台子线程从任务队列中读取任务后,才开始实际删除键值对,并释放相应的内存空间。因此,我们把这种异步删除也称为惰性删除(lazy free)。此时,删除或清空操作不会阻塞主线程,这就避免了对主线程的性能影响。

Viewpoint #

From #

激活共同的身份认同

Content #

共同的基础在付出行为中是一种主要的影响因素。在一项实验中,英国的心理学家招募了曼联球队的球迷参加实验。这些球迷在从一栋建筑走向另一栋建筑的时候,看到一个跑步的人滑倒在草地上,捂着膝盖痛苦地尖叫。他们会帮助他吗?

这取决于他穿着什么。当他穿着一件普通T恤时,只有33%的人会帮忙;当他穿着一件曼联球队的T恤时,92%的人会帮忙。耶鲁大学的心理学家杰克·多维迪奥(Jack Dovidio)将其称为什么?

耶鲁大学的心理学家杰克·多维迪奥(Jack Dovidio)称之为“激活共同的身份认同”。当人们与他人共享了一种身份认同时,为这个人付出就有了一种利他且自利的属性。如果我们帮助了自己所在群体的成员,我们也是在帮助自己,因为我们让整个群体变得更好。

Viewpoint #

From #

PageCache及RSS内存与swap空间

Linux中的Page Cache、RSS内存与swap空间之间有何种关系? #

在有磁盘文件访问的时候,Linux 会尽量把系统的空闲内存用作 Page Cache 来提高文件的读写性能。在没有打开 Swap 空间的情况下,一旦内存不够,这种情况下就只能把 Page Cache 释放了,而 RSS 内存是不能释放的。

在 RSS 里的内存,大部分都是没有对应磁盘文件的内存,比如用 malloc() 申请得到的内存,这种内存也被称为匿名内存(Anonymous memory)。那么当 Swap 空间打开后,可以写入 Swap 空间的,就是这些匿名内存。

Viewpoint #

From #

Cache-Control头部

Content #

HTTP协议的Cache-Control头部的各个指令均有其自己的作用,并且可以用于广泛的场景之下。为了使问题简单化,Ilya Grigorik 这位工作于谷歌的开发者创建了一个流程图用来帮助决定在特定场景下该设置什么指令。

Viewpoint #

From #

Java调用相关的五种指令

Content #

Java 字节码中与调用相关的指令共有五种。

  1. invokestatic 用于调用静态方法。
  2. invokespecial 用于调用私有实例方法、构造器,以及使用 super 关键字调用父类的实例方法或构造器,和所实现接口的默认方法。
  3. invokevirtual 用于调用非私有实例方法。
  4. invokeinterface 用于调用接口方法。
  5. invokedynamic 用于调用动态方法。

Viewpoint #

From #