25、简化垃圾回收器与底层编程探索

简化垃圾回收器与底层编程探索

在编程领域,垃圾回收是一项重要的技术,它能帮助我们更高效地管理内存。同时,了解计算机底层的指令格式和电子原理,也有助于我们深入理解计算机的工作机制。下面我们将详细介绍一个简化的垃圾回收器,并探讨计算机底层的相关知识。

简化垃圾回收器

这个简化的垃圾回收器有诸多限制,主要用于让大家对垃圾回收的工作原理有一个直观的认识。

限制条件
  • 必须在程序开始时通过调用 gc_init 显式初始化回收器。
  • 需通过调用 gc_scan 显式请求回收操作。
  • 所有指针必须按 16 字节对齐。
  • 所有指针必须存在于栈或堆中才能被正确扫描。
  • 所有指针必须指向一段内存的起始位置才能被正确扫描。
  • 仅适用于单栈(即单线程代码)。
  • 不能与其他分配器一起使用。
  • 在扫描过程中,该实现会临时使已分配的内存翻倍。
代码组织

代码主要由以下几个文件组成:
- gc_defs.s :包含程序其余部分所需的 .equ 指令。

.globl BRK_SYSCALL
.globl HEADER_SIZE, HDR_IN_USE_OFFSET, HDR_SIZE_OFFSET
.equ BRK_SYSCALL, 12
.equ HEADER_SIZE, 16
.equ HDR_IN_USE_OFFSET, 0
.equ HDR_SIZE_OFFSET, 8
  • gc_globals.s :包含所有垃圾回收代码共享的全局变量。
.include "gc_defs.s"
.globl heap_start, heap_end, stack_start, stack_end
.globl pointer_list_start, pointer_list_end, pointer_list_current
.section .data
heap_start:
    .quad 0
heap_end:
    .quad 0
stack_start:
    .quad 0
stack_end:
    .quad 0
.equ pointer_list_start, heap_end # These are the same
pointer_list_end:
    .quad 0
pointer_list_current:
    .quad 0
  • gc_init.s :包含 gc_init 函数的代码,用于初始化垃圾回收器。
.globl gc_init
.section .text
gc_init:
    # Assume %rbp has the previous stack frame,
    # and is properly aligned
    movq %rbp, stack_start
    # Save the location of the heap
    movq $BRK_SYSCALL, %rax
    movq $0, %rdi
    syscall
    movq %rax, heap_start
    movq %rax, heap_end
    ret
  • gc_allocate.s :包含分配器函数 gc_allocate ,其工作方式与常见的分配器函数类似,还会将内存清零以避免意外引用指针。
.include "gc_defs.s"
.globl gc_allocate
.section .text
# Register usage:
#  - %rdx - size requested
#  - %rsi - pointer to current memory being examined
#  - %rcx - copy of heap_end
allocate_move_break:
    # Old break is saved in %r8 to return to user
    movq %rcx, %r8
    # Calculate where we want the new break to be
    # (old break + size)
    movq %rcx, %rdi
    addq %rdx, %rdi
    # Save this value
    movq %rdi, heap_end
    # Tell Linux where the new break is
    movq $BRK_SYSCALL, %rax
    syscall
    # Address is in %r8 - mark size and availability
    movq $1, HDR_IN_USE_OFFSET(%r8)
    movq %rdx, HDR_SIZE_OFFSET(%r8)
    # Actual return value is beyond our header
    addq $HEADER_SIZE, %r8
    movq %r8, %rax
    ret
gc_allocate:
    enter $0, $0
    pushq $0   # Keep stack aligned
    pushq %rdi # Save for later
    call gc_allocate_internal
    # Zero out the block to eliminate false pointers
    movq %rax, %rdx # Save original pointer
    popq %rcx       # Get the size of the block
zeroloop:
    movb $0, (%rdx)
    incq %rdx
    loop zeroloop
    leave
    ret
gc_allocate_internal:
    # Save the amount requested into %rdx
    movq %rdi, %rdx
    # Actual amount needed is actually larger
    addq $HEADER_SIZE, %rdx
    # Align %rdx to a 16-byte boundary
    addq $16, %rdx                   # Advance 16 bytes
    andq $0xfffffffffffffff0, %rdx   # Clear last bits
    # Put heap start/end in %rsi/%rcx
    movq heap_start, %rsi
    movq heap_end, %rcx
allocate_loop:
    # If we have reached the end of memory
    # we have to allocate new memory by
    # moving the break.
    cmpq %rsi, %rcx
    je allocate_move_break
    # is the next block available?
    cmpq $0, HDR_IN_USE_OFFSET(%rsi)
    jne try_next_block
    # is the next block big enough?
    cmpq %rdx, HDR_SIZE_OFFSET(%rsi)
    jb try_next_block
    # This block is great!
    # Mark it as unavailable
    movq $1, HDR_IN_USE_OFFSET(%rsi)
    # Move beyond the header
    addq $HEADER_SIZE, %rsi
    # Return the value
    movq %rsi, %rax
    ret
try_next_block:
    # This block didn't work, move to the next one
    addq HDR_SIZE_OFFSET(%rsi), %rsi
    jmp allocate_loop
  • gc_scan.s :包含垃圾回收函数 gc_scan ,其具体细节进一步划分到其他文件中。
.globl gc_scan
.section .text
# Parameters - none
# Registers - none
gc_scan:
    enter $0, $0
    # Setup space for pointer list
    call gc_scan_init
    # Unmark all objects
    call gc_unmark_all
    # Get initial set of pointers from base objects
    # (stack, data)
    call gc_scan_base_objects
    # Walk pointer list
    call gc_walk_pointers
    # Give back space from pointer list
    call gc_scan_cleanup
    leave
    ret
  • gc_scan_init.s :包含初始化扫描的代码,记录当前栈位置并分配足够的内存来存储潜在指针列表。
  • gc_unmark_all.s :将所有分配标记为“未使用”,当检测到指向它们的指针时,会重新标记为已使用。
  • gc_scan_memory.s :对内存位置及其大小进行扫描,查找指向堆的潜在指针。
  • gc_scan_base_objects.s :扫描基础对象(栈和数据段),查找潜在的有效指针。
  • gc_walk_pointers.s :遍历指针列表,检查每个指针是否指向有效的堆分配内存且当前未标记,如果是,则标记该内存为正在使用,并继续扫描该内存以查找更多指针。
  • gc_is_valid_ptr.s :包含判断给定指针是否实际指向堆内存分配起始位置的代码。
  • gc_scan_cleanup.s :释放为保存指针列表而分配的内存。
  • gc_test.c :一个简短的测试程序,展示了如何在 C 代码中使用垃圾回收器。
#include <stdio.h>
void *gc_allocate(int);
void gc_scan();
void gc_init();
volatile void **foo;
volatile void **goo;
int main() {
    gc_init();
    foo = gc_allocate(500);
    fprintf(stdout, "Allocation 1: %x\n", foo);
    goo = gc_allocate(200);
    foo[0] = goo; // Hold reference to goo so it won't go away
    fprintf(stdout, "Allocation 2: %x\n", goo);
    gc_scan();
    goo = gc_allocate(300);
    fprintf(stdout, "Allocation 3: %x\n", goo);
    gc_scan();
    goo = gc_allocate(200);
    fprintf(stdout, "Allocation 4: %x\n", goo);
    gc_scan();
    // This will be put in  the same spot as allocation 3
    goo = gc_allocate(200);
    fprintf(stdout, "Allocation 5: %x\n", goo);
    gc_scan();
    foo = gc_allocate(500); //  No longer holding reference to allocations 1 
& 2
    fprintf(stdout, "Allocation 6: %x\n", foo);
    gc_scan();
    // This will be put in the same spot as allocation 1
    goo = gc_allocate(10);
    fprintf(stdout, "Allocation 7: %x\n", goo);
    // This will be put in the same spot as allocation 2
    foo = gc_allocate(10);
    fprintf(stdout, "Allocation 8: %x\n", foo);
}

要编译并运行这个程序,可以使用以下命令:

gcc gc_*.* -o gc_test
./gc_test
计算机底层知识

除了垃圾回收器,了解计算机底层的指令格式和电子原理也很有必要。

指令格式

在计算机中,一切都以数字形式存储,包括操作计算机的指令。在 x86 - 64 指令集架构中,指令的长度可变,从单字节到 15 字节不等。

指令的基本格式如下:
| 部分 | 说明 | 长度 |
| ---- | ---- | ---- |
| 前缀 | 修改指令的操作,某些情况下可视为指令的一部分 | 每个前缀 1 字节 |
| 操作码 | 告诉处理器要执行的指令 | 1 - 3 字节 |
| ModR/M | 若操作码需要,则指定使用的寄存器和寻址模式 | 1 字节 |
| SIB | 若操作码需要,用于索引寻址模式 | 1 字节 |
| 位移量 | 若操作码需要,是各种寻址模式中使用的固定值 | 1、2、4 或 8 字节 |
| 立即数 | 若操作码需要,是立即模式的值 | 1、2、4 或 8 字节 |

常见前缀有 rep 系列前缀(如 rep repe 编码为 0xf3, repne 编码为 0xf2)、 LOCK 前缀(0xf0)和 REX 系列前缀。操作码与助记符不完全匹配,但较为接近,例如 mov 系列指令的操作码会根据操作数的大小和类型而变化。

下面是一些指令编码的示例:
- movb $8, %ah 编码为 0xb4 0x08
- movq $8, %rbx 编码为 0x48 0xc7 0xc3 0x08 0x00 0x00 0x00

电子原理

计算机中的一切最终都通过电信号实现,由一系列微芯片组成,每个微芯片包含大量逻辑门,而逻辑门又由晶体管构成。如果对这方面感兴趣,可以参考以下书籍:
- 《Electronics for Beginners》by Jonathan Bartlett:适合初学者,有很多针对专业人士的内容。
- 《The Art of Electronics》by Paul Horowitz:专业人士必备,详细介绍了许多重要电路及其使用注意事项。
- 《Build Your Own Computer: From Scratch》by David Whipple:展示如何使用 FPGA 设计自己的简化 CPU。
- 《Computer Organization and Design》by David Patterson and John Hennessy 或 《Computer Architecture: A Quantitative Approach》by John Hennessy:是理解 CPU 组织层面工作原理的标准书籍。

通过了解这个简化的垃圾回收器和计算机底层知识,我们能更深入地理解编程和计算机的工作机制,为进一步的学习和实践打下坚实的基础。

简化垃圾回收器与底层编程探索

垃圾回收器工作流程分析

为了更清晰地理解垃圾回收器的工作过程,我们可以用 mermaid 流程图来展示 gc_scan 函数的工作流程:

graph TD;
    A[开始] --> B[调用 gc_scan_init];
    B --> C[调用 gc_unmark_all];
    C --> D[调用 gc_scan_base_objects];
    D --> E[调用 gc_walk_pointers];
    E --> F[调用 gc_scan_cleanup];
    F --> G[结束];

从这个流程图可以看出, gc_scan 函数主要分为几个关键步骤:
1. 初始化扫描 gc_scan_init 记录当前栈位置并分配内存存储潜在指针列表。
2. 标记重置 gc_unmark_all 将所有分配标记为“未使用”。
3. 查找初始指针 gc_scan_base_objects 扫描栈和数据段,查找潜在有效指针。
4. 遍历指针列表 gc_walk_pointers 检查指针是否指向有效堆内存并标记使用,继续扫描更多指针。
5. 清理内存 gc_scan_cleanup 释放为指针列表分配的内存。

指令格式深入探讨

在 x86 - 64 指令集架构中,指令格式的复杂性源于其历史发展。从 16 位平台发展到 32 位,再到如今的 64 位平台,同时还要保持向后兼容性,这使得指令格式变得独特。

我们可以通过一个表格来更详细地对比不同指令的编码情况:
| 指令 | 前缀 | 操作码 | ModR/M | SIB | 位移量 | 立即数 | 总编码 |
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
| movb $8, %ah | 无 | 0xb4 | 无 | 无 | 无 | 0x08 | 0xb4 0x08 |
| movq $8, %rbx | 0x48 | 0xc7 | 0xc3 | 无 | 无 | 0x08 0x00 0x00 0x00 | 0x48 0xc7 0xc3 0x08 0x00 0x00 0x00 |

从这个表格中可以更直观地看到不同指令在各个部分的编码差异。例如, movq 指令由于涉及 64 位操作,需要 REX 前缀(0x48)来使 32 位指令在 64 位平台上正常工作。

电子原理实践建议

对于想要深入了解计算机电子原理的读者,可以按照以下步骤进行实践:
1. 入门学习 :阅读《Electronics for Beginners》,了解基本电子电路知识,进行一些简单的电路实验,如搭建简单的 LED 电路。
2. 深入研究 :学习《The Art of Electronics》,掌握更多重要电路的原理和应用,尝试分析一些复杂电路的工作机制。
3. 实践操作 :参考《Build Your Own Computer: From Scratch》,使用 FPGA 尝试设计自己的简化 CPU。可以从简单的功能开始,逐步增加复杂度。
4. 理论提升 :阅读《Computer Organization and Design》或《Computer Architecture: A Quantitative Approach》,从理论层面深入理解 CPU 的工作原理和架构设计。

通过这些实践步骤,读者可以逐步建立起对计算机电子原理的深入理解,从理论到实践,全面提升自己的知识和技能。

总结

本文详细介绍了一个简化的垃圾回收器,包括其限制条件、代码组织和工作流程。同时,探讨了计算机底层的指令格式和电子原理,通过具体的示例和表格,让读者更直观地理解这些复杂的概念。对于想要深入学习编程和计算机底层知识的读者,这些内容提供了一个很好的起点。无论是垃圾回收器的实现,还是指令格式和电子原理的理解,都有助于我们更好地掌握计算机编程的核心知识,为未来的学习和实践打下坚实的基础。希望读者能够通过本文的介绍,激发对计算机底层技术的兴趣,进一步探索这个充满挑战和乐趣的领域。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值