sys_open(即调用号为2的系统调用)与sys_close(即调用号为3的系统调用)是 Linux 中的两个基本系统调用,用于对文件描述符进行相关操作。

sys_open会将被打开的文件路径转换为文件描述符int fd,该描述符可用于后续的读写操作。

  • sys_open

    • 函数原型

      1
      int open(const char * pathname, int flags, mode_t mode);
    • 参数

      • pathname: 要打开的文件路径(字符串)
      • flags: 打开标志,指定文件的打开方式(具体说明见下)
      • mode: 创建文件时的权限模式(仅在使用O_CREAT时有效)(具体说明见下)
    • 返回值

      • 成功时返回文件描述符(非负整数fd
      • 失败时返回-errno(如ENOENT, EACCES, EMFILE
  • sys_close

    • 函数原型

      1
      int close(int fd);
    • 参数

      • fd : 要关闭的文件描述符
    • 返回值

      • 成功时返回0
      • 失败时返回-errno(如EBADF, EINTR, EIO
  • flags的定义

    flags 的赋值通常是由“基本访问模式”与“附加标志”通过或逻辑所附加而来的。在 Linux 的头文件fcntl.h 中,这二者的定义如下:

    • 基本访问模式

      宏定义 含义
      O_RDONLY 0 只读打开
      O_WRONLY 1 只写打开
      O_RDWR 2 读写打开
    • 附加标志

      宏定义 值(八进制) 值(十六进制) 说明
      O_CREAT 0000100 0x40 文件不存在则创建
      O_EXCL 0000200 0x80 O_CREAT 一起用,文件存在则失败
      O_NOCTTY 0000400 0x100 不把文件作为控制终端
      O_TRUNC 0001000 0x200 截断已存在文件为0长度
      O_APPEND 0002000 0x400 写操作追加到文件末尾
      O_NONBLOCK 0004000 0x800 非阻塞 I/O
      O_NDELAY 0004000 0x800 O_NONBLOCK相同
      O_DSYNC 0010000 0x1000 数据同步写入
      O_SYNC 04010000 0x101000 数据+元数据同步写入
      O_RSYNC 04010000 0x101000 O_SYNC相同(兼容)
      FASYNC 0020000 0x2000 异步通知
      O_DIRECT 0040000 0x4000 直接 I/O,绕过内核缓存
      O_LARGEFILE 00100000 0x8000 大文件支持(32位)
      O_DIRECTORY 00200000 0x10000 必须是目录,否则出错
      O_NOFOLLOW 00400000 0x20000 不跟随符号链接
      O_NOATIME 01000000 0x40000 不更新时间戳
      O_CLOEXEC 02000000 0x80000 设置FD_CLOEXEC,exec 时关闭
      O_PATH 010000000 0x200000 只获取路径引用,不打开文件
      O_TMPFILE 020000000 0x400000 创建匿名临时文件(需O_DIRECTORY
      O_TMPFILE_MASK 03777700000 - 内部掩码,用于屏蔽O_TMPFILE

    上述所有宏在给某一flags 赋值时,使用按位或运算符|进行连接。如:

    1
    int flags = O_WRONLY | O_CREAT;

    flags 意为:打开文件时采用只写模式;且如果文件不存在,则自动创建之。

    可以想到,基本访问模式这三者只能独立存在,不能同时存在于一个表达式中。

  • 权限模式的定义

    Unix 系统的权限模式由三个部分所组成:所有者(owner)的权限、所有者所在用户组中的其他用户(group)的权限、非以上用户的其他用户(others)的权限,通过数字进行区分。

    Unix 系统的权限主要有三种:读(r)、写(w)、执行(x)。如下:

    权限 二进制 八进制
    --- 000 0
    --x 001 1
    -w- 010 2
    -wx 011 3
    r-- 100 4
    r-x 101 5
    rw- 110 6
    rwx 111 7

    其中- 表示用户不具有某一种权限。每一类用户通过八进制数来表示自身的权限。三类用户的排列顺序为:owner-group-others

    如一个文件对所有用户可读、可写、可执行,那么它的权限模式为:
    rwxrwxrwx ,亦即0777

  • 用例

    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
    .equ FLAGS, 0x241             # O_WRONLY | O_CREAT | O_TRUNC 

    .section .data
    filename:
    .asciz "/tmp/test.txt" # 要打开的文件路径
    buffer:
    .ascii "Hello from sys_open!\n\0"
    buffer_len = . - buffer

    .section .text
    .globl _start

    _start:
    # 调用 sys_open 创建/打开文件
    mov $2, %rax # 系统调用号 (2 = sys_open)
    lea filename(%rip), %rdi # const char *pathname = &filename
    mov $FLAGS, %rsi # 通过宏定义给 flags 赋值
    mov $0644, %rdx # mode = 0644 (rw-r--r--)
    syscall # 执行系统调用

    # 保存文件描述符
    mov %rax, %r8 # 保存文件描述符到 %r8

    # 使用 sys_write 向文件写入数据
    mov $1, %rax # 系统调用号 (1 = sys_write)
    mov %r8, %rdi # 使用刚才打开的文件描述符
    lea buffer(%rip), %rsi # const void *buf = &buffer
    mov $buffer_len, %rdx # count = buffer_len
    syscall # 执行系统调用

    # 使用 sys_close 关闭文件
    mov $3, %rax # 系统调用号 (3 = sys_close)
    mov %r8, %rdi # 关闭文件描述符
    syscall # 执行系统调用

    # 退出
    mov $60, %rax # 系统调用号 (60 = sys_exit)
    xor %rdi, %rdi # return 0
    syscall

    分析

    上述代码演示了如何使用sys_open系统调用创建并打开一个文件进行写入操作。

    程序首先定义了要打开的文件路径filename和要写入的数据buffer。在_start标签处,程序将系统调用号2sys_open)存入%rax寄存器,将文件路径地址加载到%rdi,设置打开标志为0x241O_WRONLY | O_CREAT | O_TRUNC,表示只写、如不存在则创建、截断为0长度),并设置文件权限为0644。这个操作,用 C 语言来写的话,就是:

    1
    int fd = open("/tmp/test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);

    在执行syscall指令后,系统调用会尝试打开指定的这个文件。如果成功,文件描述符会存储在%rax中;如果失败,%rax将包含负值。

    成功打开文件后,我们将文件描述符备份到%r8寄存器,以防止在后面调用到其他系统调用时覆盖了原本的文件描述符。然后,我们使用sys_write向该文件中写入我们准备好的数据。最后,程序使用sys_close(系统调用号为3)关闭文件描述符,释放系统资源,并调用sys_exit正常退出。