sys_lseek (调用号为8)是 Linux 中一个用于操作文件描述符读写位置的系统调用。它的核心功能是重新定位(移动)一个已打开文件的读写偏移量(offset)。这个偏移量决定了下一次读或写操作将从文件的什么位置开始。通过它,我们可以实现文件的随机访问(即跳转到文件任意位置进行读写),而不仅仅是顺序读写。

  • sys_lseek

    • 函数原型

      1
      off_t lseek(int fd, off_t offset, int whence);
    • 参数

      • fd: 文件描述符,指向一个已打开的文件。
      • offset: 相对于 whence 参数的偏移量,以字节为单位。
      • whence: 决定偏移量计算起点的位置,可取以下值:
        • SEEK_SET (0): 将偏移量设置为从文件开始算起的 offset 字节。
        • SEEK_CUR (1): 将偏移量设置为从当前位置算起加上 offset 字节(offset 可为负数,表示向前移动)。
        • SEEK_END (2): 将偏移量设置为从文件末尾算起加上 offset 字节(offset 可为负数,表示向前移动)。
    • 返回值

      • 成功时返回新的、移动后的文件偏移量(从文件开头计算的字节数)。
      • 失败时返回 -errno(如 EBADFEINVAL )。
  • 用例

    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
    68
    	.section .data
    filename:
    .ascii "/tmp/text.txt\0"
    content:
    .ascii "HelloWorld\0" # 文件内容
    len = . - content
    buffer:
    .space 5 # 用于读取数据的缓冲区
    .section .text
    .globl _start
    _start:
    # 1. 打开文件 (sys_open)
    mov $2, %rax # 系统调用号 (2 = sys_open)
    lea filename(%rip), %rdi # 文件名
    mov $0101, %si # 标志位 (O_CREAT | O_WRONLY)
    mov $0666, %dx # 模式
    syscall
    mov %rax, %r8 # 保存文件描述符到 %r8

    # 2. 写入内容 content (sys_write)
    mov $1, %rax # 系统调用号 (1 = sys_write)
    mov %r8, %rdi # 文件描述符
    lea content(%rip), %rsi # 要写入的数据
    mov $len, %rdx # 数据长度
    syscall

    # 3. 关闭文件 (sys_close)
    mov $3, %rax # 系统调用号 (3 = sys_close)
    mov %r8, %rdi
    syscall

    # 4. 重新以只读方式打开文件 (sys_open)
    mov $2, %rax
    lea filename(%rip), %rdi
    xor %rsi, %rsi # 标志位 (O_RDONLY)
    syscall
    mov %rax, %r8 # 保存新的文件描述符

    # 5. 使用 sys_lseek 跳转到第6个字节('W'的位置)
    mov $8, %rax # 系统调用号 (8 = sys_lseek)
    mov %r8, %rdi # 文件描述符
    mov $5, %rsi # offset = 5 (从起点后第5个字节,即第6个)
    mov $0, %rdx # whence = SEEK_SET (0)
    syscall

    # 6. 从新位置读取5个字节 (sys_read)
    mov $0, %rax # 系统调用号 (0 = sys_read)
    mov %r8, %rdi
    lea buffer(%rip), %rsi # 读取缓冲区
    mov $5, %rdx # 读取5个字节
    syscall

    # 7. 将读取到的内容输出到标准输出 (sys_write)
    mov $1, %rax
    mov $1, %rdi # 文件描述符 (1 = stdout)
    lea buffer(%rip), %rsi
    mov $5, %rdx
    syscall

    # 8. 关闭文件并退出
    mov $3, %rax # sys_close
    mov %r8, %rdi
    syscall

    mov $60, %rax # sys_exit
    xor %rdi, %rdi
    syscall

    分析

    上述代码演示了 sys_lseek 的一个典型用法:跳转到文件中间进行读取。

    程序首先创建并打开一个名为 test.txt 的文件,写入字符串 "HelloWorld"(共 10 字节),然后关闭它。然后,重新以只读模式打开该文件。此时文件偏移量在开头 (0)。此时,程序开始调用 sys_lseek。我们首先将,系统调用号 8 存入 %rax ,文件描述符存入 %rdi ,偏移量 5 存入 %rsi(我们希望跳过前5个字节 "Hello"),whence 参数 SEEK_SET (0) 存入 %rdx,表示从文件开头计算偏移。

    执行后,文件偏移量被设置为 5。紧接着调用 sys_read,它会从新的偏移量(第 5 字节后,即第 6 个字节)开始读取。这里读取了 5 个字节,内容将是 "World"。随后,程序开始将这个结果 "World" 写入标准输出。最终效果是,程序没有读取文件的开头,而是直接读取并输出了文件的后半部分。