Blog

布隆过滤器

Content #

布隆过滤器由一个初值都为 0 的 bit 数组和 N 个哈希函数组成,可以用来快速判断某个数据是否存在。其基本原理是什么?

当我们想标记某个数据存在时(例如,数据已被写入数据库),布隆过滤器会通过三个操作完成标记:

  1. 使用 N 个哈希函数,分别计算这个数据的哈希值,得到 N 个哈希值。
  2. 我们把这 N 个哈希值对 bit 数组的长度取模,得到每个哈希值在数组中的对应位置。
  3. 我们把对应位置的 bit 位设置为 1,这就完成了在布隆过滤器中标记数据的操作。

如果数据不存在(例如,数据库里没有写入数据),我们也就没有用布隆过滤器标记过数据,那么,bit 数组对应 bit 位的值仍然为 0。

Viewpoint #

From #

不再被使用的类

Content #

被认定为“不再被使用的类”需要同时满足哪三个条件?(Java)

  • 该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例。
  • 加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如OSGi、JSP的重加载等,否则通常是很难达成的。
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

Viewpoint #

From #

toc:Economics

Content #

Business #

见识 #

匠构战略

Content #

著名管理学家明茨伯格在研究企业战略时曾提出过一个“匠构战略”的观点,该观点具体指的是什么内容?

他认为企业总是预先就制定出一个完美的战略然后严格遵照其执行的想法是很不现实的,企业更应该一边行动一边形成和修正战略。这就像一个制陶工匠所做的,在陶器的制作过程中,每一个瞬间陶器的坯子都在变化着,这时工匠需要敏锐地觉察到它是怎么变化的,然后适时调整策略和构思。明茨伯格认为,这才是形成企业战略的正确方法。

Viewpoint #

From #

两步链接

两步链接 #

链接器的工作流程也主要分为两步:

第一步是,链接器需要对编译器生成的多个目标(.o) 文件进行合并,一般采取的策略是相似段的合并,最终生成共享文件 (.so) 或者可执行文件。

这个阶段中,链接器对输入的各个目标文件进行扫描,获取各个段的大小,并且同时会收集所有的符号定义以及引用信息,构建一个全局的符号表。当链接器构造好了最终的文件布局以及虚拟内存布局后,我们根据符号表,也就能确定了每个符号的虚拟地址了。

第二步是,链接器会对整个文件再进行第二遍扫描,这一阶段,会利用第一遍扫描得到的符号表信息,依次对文件中每个符号引用的地方进行地址替换。也就是对符号的解析以及重定位过程。

这就是链接器常用的两步链接 (Two-pass linking) 的步骤。简单来讲就是进行两遍扫描:第一遍扫描完成文件合并、虚拟内存布局的分配以及符号信息收集;第二遍扫描则是完成了符号的重定位过程。

Viewpoint #

From #

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

协程的要点

Content #

  1. 协程是一种轻量级的执行单元。
  2. 协程是协作式调度,进程和线程则是抢占式调度。
  3. 协程切换的本质依赖于栈的切换,每个协程的上下文都保存在自己的栈上。
  4. 结合IO多路利用,可让协程有更高的性能。

Viewpoint #

From #

栈切换的核心

Content #

栈切换的核心就是栈指针 rsp 寄存器的切换,只要我们想办法把 rsp 切换了就相当于换了执行单元的上下文环境。

栈往往和执行单元是一对一的关系,栈的活跃就代表着它所对应的执行单元的活跃。栈上的数据非常敏感,一旦被攻击,往往会造成巨大的破坏。

Viewpoint #

From #

用户态和内核态是怎么切换的

用户态和内核态是怎么切换的? #

内核态和用户态的切换依赖栈的切换。操作系统内核在运行的时候,也是需要栈的,这个栈称为内核栈,它与用户应用程序使用的用户态栈是不同的。只有高权限的内核代码才能访问它。而内核态与用户态的相互切换,其中最重要的一个步骤就是两个栈的切换。

中断发生时,CPU 根据需要跳转的特权级,去一个特定的结构中(不同的 CPU 会有所不同,比如 i386 就存在 TSS 中,但不管是什么 CPU,一定会有一个类似的结构),取得目标特权级所对应的 stack 段选择子和栈顶指针,并分别送入 ss 寄存器和 rsp 寄存器,这就完成了一次栈的切换。

然后,IP 寄存器跳入中断服务程序开始执行,中断服务程序会把当前 CPU 中的所有寄存器,也就是程序的上下文都保存到栈上,这就意味着用户态的 CPU 状态其实是由中断服务程序在系统栈上进行维护的。如图所示:

一般来说,当程序因为 call 指令或者 int 指令进行跳转的时候,只需要把下一条指令的地址放到栈上,供被调用者执行 ret 指令使用,这样可以便于返回到调用函数中继续执行。但上图中的内核态栈里有一点特殊之处,就是 CPU 自动地将用户态栈的段选择子 ss3,和栈顶指针 rsp3 都放到内核态栈里了。这里的数字 3 代表了 CPU 特权级,内核态是 0,用户态是 3。

当中断结束时,中断服务程序会从内核栈里将 CPU 寄存器的值全部恢复,最后再执行"iret"指令(注意不是 ret,而是 iret,这表示是从中断服务程序中返回)。而 iret 指令就会将 ss3/rsp3 都弹出栈,并且将这个值分别送到 ss 和 rsp 寄存器中。这样就完成了从内核栈到用户栈的一次切换。同时,内核栈的 ss0 和 rsp0 也会被保存到前文所说的一个特定的结构中,以供下次切换时使用。

Viewpoint #

From #

05 | 栈的魔法:从栈切换的角度理解进程和协程