sys_brk(即调用号为12brk系统调用)是Linux中一个用于动态内存管理的核心系统调用。它直接操作进程的程序中断点(program break),即堆(heap)的结束地址。通过改变这个中断点的位置,程序可以动态地增加或减少其数据段的大小。尽管现代C库(如glibc)通常使用mmap来实现更灵活的内存分配(如malloc),但在底层,sys_brk仍然是扩展堆空间的基本机制。了解sys_brk有助于理解进程地址空间的布局,以及操作系统如何为程序提供可变大小的内存区域。

  • sys_brk

    • 函数原型

      1
      int brk(void *addr);
    • 参数

      • addr: 新的程序中断点地址。如果该地址比当前中断点高,则扩大堆;如果比当前中断点低,则缩小堆。如果传入0,系统调用会返回当前程序中断点的地址。
    • 返回值

      • 成功时返回新程序中断点地址(即参数addr)。但对于现代x86_64 Linux,为了与旧版内核兼容,返回值在rax中会是0,失败时会是1
      • 失败时返回-1,并设置errno(如ENOMEM)。
  • 用例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    	.equ SYS_BRK, 12
    .equ SYS_WRITE, 1
    .equ SYS_EXIT, 60
    .equ STDOUT, 1
    .equ PAGE_SIZE, 4096

    .section .data
    initial_brk:
    .quad 0x0

    message:
    .ascii "New memory allocated with sys_brk.\n\0"
    msg_len = . - message

    .section .text
    .globl _start

    _start:
    # 获取当前程序中断点地址
    mov $SYS_BRK, %rax
    xor %rdi, %rdi
    syscall

    # 检查返回值以确保成功
    cmp $0, %rax
    je exit_error

    # 将当前中断点地址保存起来
    mov %rax, initial_brk(%rip)

    # 计算新的中断点地址(增加一个页大小)
    add $PAGE_SIZE, %rax

    # 调用 sys_brk 扩展堆
    mov $SYS_BRK, %rax
    mov initial_brk(%rip), %rdi
    add $PAGE_SIZE, %rdi
    syscall

    # 检查返回值,如果失败则退出
    cmp $0, %rax
    jne exit_error

    # 将消息复制到新分配的内存区域
    mov initial_brk(%rip), %rdi
    mov $message, %rsi
    mov $msg_len, %rcx
    cld
    rep movsb

    # 使用 sys_write 打印新内存中的消息
    mov $SYS_WRITE, %rax
    mov $STDOUT, %rdi
    mov initial_brk(%rip), %rsi
    mov $msg_len, %rdx
    syscall

    # 退出程序
    exit_success:
    mov $SYS_EXIT, %rax
    xor %rdi, %rdi
    syscall

    exit_error:
    mov $SYS_EXIT, %rax
    mov $1, %rdi
    syscall

    分析

    上述代码演示了如何使用sys_brk系统调用来动态地扩大堆内存。

    首先,程序通过调用brk(0)来获取当前的程序中断点地址。在汇编中,这是通过将系统调用号12放入%rax并将%rdi设置为0来完成的。返回的地址会保存在%rax中,程序将其存储在initial_brk变量中。

    接着,程序将initial_brk的值增加一个页面大小(4096字节),得到新的中断点地址。然后,它再次调用sys_brk,并将这个新的地址作为参数传递。如果调用成功,内核会调整堆的大小,使程序可以使用这块新分配的内存。

    最后,程序将一个字符串消息复制到这个新分配的内存区域,并使用sys_write系统调用将其打印到标准输出。这证明了通过sys_brk分配的内存现在是可访问和可写入的。