跳转至内容
  • 版块
  • 最新
  • 标签
  • 热门
  • Online Tools
  • 用户
  • 群组
折叠
品牌标识

D2Learn Forums

  1. 主页
  2. SubForums
  3. 现代C++ | mcpp论坛
  4. Linux系统编程(2)文件IO

Linux系统编程(2)文件IO

已定时 已固定 已锁定 已移动 现代C++ | mcpp论坛
linux 系统编程
3 帖子 2 发布者 16 浏览
  • 从旧到新
  • 从新到旧
  • 最多赞同
登录后回复
此主题已被删除。只有拥有主题管理权限的用户可以查看。
  • 月 离线
    月 离线
    月仁不吃五饼
    编写于 最后由 编辑
    #1

    Linux系统编程(2)文件IO

    最基本的文件访问方法是系统调用read()和write()。但是,在访问文件之前,必须先通过open()或creat()打开该文件。一旦完成文件读写,还应该调用系统调用close()关闭该文件。
    大致流程如下

    open/create->read/write->close
    

    open系统调用

    open系统调用整个流程如下:

    ┌──────────────┐
    │  Your Code   │
    │  open(...)   │
    └──────┬───────┘
           ▼
    ┌──────────────────┐
    │ glibc (libc.so)  │   ← 用户态库
    │ open() wrapper   │
    └──────┬───────────┘
           ▼
    ┌──────────────────┐
    │ syscall entry    │   ← 用户态 / 内核态边界
    │ SYS_openat       │
    └──────┬───────────┘
           ▼
    ┌──────────────────┐
    │ Linux Kernel     │
    │ sys_openat()     │
    └──────────────────┘
    

    而在使用上,我们需要先包含下述头文件

    #include <unistd.h>    // read, write, close, lseek
    #include <fcntl.h>     // open, O_RDONLY/O_CREAT/... flags
    #include <sys/stat.h>  // mode_t, 权限位 S_IRUSR 等(创建文件常用)
    #include <sys/types.h> // ssize_t, off_t 等(有时可省,现代系统常被间接包含)
    

    通过系统调用open(),可以打开文件并获取其文件描述符:
    open系统调用有两个函数签名

    int open(const char *name, int flags);
    int open(const char *name, int flags, mode_t mode);
    

    它们的区别是 第三个参数 mode 要不要传(后面补充这第三个参数的作用)

    open的返回值是文件描述符,linux下一切皆文件,如果返回-1代表发生错误


    open()的flags参数

    flags参数是由一个或多个标志位的按位或组合。它支持三种访问模式:O_RDONLY、O_WRONLY或O_RDWR,(其实是单词的缩写,不用死记硬背)这三种模式分别表示以只读、只写或读写模式打开文件。
    举个例子,以下代码以只读模式打开文件/home/kidd/madagascar:

    int fd =open("/home/kidd/madagascar",O_RDONLY);
    if(fd==-1){
    //这里是错误处理,要养成好习惯,随手处理错误
    }
    

    使用open打开文件之后,我们就获得了这个文件的句柄。用这个句柄就可以对这个文件施加操作了,但是要尤为注意的是,对这个文件能够施加怎样的操作,取决于打开方式,即flags的参数。
    那么问题来了,如果文件不存在怎么办? 这就涉及到flags其他可选参数:

    1) 创建/覆盖类

    • O_CREAT:文件不存在就创建(这时必须提供第三个参数 mode)

      open("a.txt", O_WRONLY | O_CREAT, 0644);
      
    • O_EXCL:配合 O_CREAT 使用;如果文件已存在则失败(避免覆盖)
      当和标志位O_CREAT一起使用时,如果参数name指定的文件已经存在,会导致open()调用失败。用于防止创建文件时出现竞争。如何没有和标志位O_CREAT一起使用,该标志位就没有任何含义。

    open("a.txt", O_WRONLY | O_CREAT | O_EXCL, 0644); // 存在则报 EEXIST
    

    上面涉及到O_CREAT的操作就需要第三个参数了0644,这个参数的意义如下:

    0 6 4 4
      ↑ ↑ ↑
      │ │ └─ 其他用户 (other)
      │ └── 同组用户 (group)
      └──── 文件所有者 (owner)
    

    0644 的三位分别代表谁?

    权限格式是:

    [ owner ][ group ][ other ]

    每一位是 0~7 的八进制数:

    数字 二进制 含义
    4 100 读 (r)
    2 010 写 (w)
    1 001 执行 (x)
    所以0644代表:文件所有者rw权限,同组用户和其他用户有r权限
    使用O_CREAT必须使用第三个权限参数


    • O_TRUNC:文件已存在且以写方式打开时,把文件长度截断为 0(清空重写)

      open("a.txt", O_WRONLY | O_TRUNC);

    2) 追加/定位类

    • O_APPEND:每次 write 都追加到文件末尾(内核保证追加定位的原子性)

      open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0644);

    3) 阻塞行为/同步类

    • O_NONBLOCK:非阻塞 I/O(对 pipe/终端/socket 影响大,对普通磁盘文件通常意义不大,后面需要时再讲)

      open("/dev/tty", O_RDONLY | O_NONBLOCK);

    • O_SYNC:更强的同步写语义(write 返回前尽量保证数据落到存储设备,性能会下降)

    • O_DSYNC:只保证数据(不强制元数据)同步(比 O_SYNC 轻一些)

    • O_RSYNC: O_RSYNC标志位指定读请求和写请求之间的同步。该标志位必须和O_SYNC或O_DSYNC一起使用。

    • O_LARGEFILE
      文件偏移使用64位整数表示,可以支持大于2GB的文件。64位操作系统中打开文件时,默认使用该参数。

    fd = open("a.txt",O_LARGEFILE | O_RDONLY);
    

    4) 安全/继承类

    • O_CLOEXEC:执行 exec() 时自动关闭该 fd,防止泄漏到子进程

      open("a.txt", O_RDONLY | O_CLOEXEC);
      (close execute的缩写)

    5) 其他你可能会见到的

    • O_NOCTTY:打开终端设备时不把它变成进程的控制终端

    • O_NOFOLLOW:不跟随符号链接(用于安全场景,避免被 symlink 攻击)

    由前面的例子可以明白,这些参数都是位操作符
    是可以做|运算把不同操作拼接在一起的

    小练习

    1. 只读打开
    int fd=open("a.txt",O_RDONLY);
    if(fd==-1){
      perror("open");
    }
    
    1. 覆盖写(不存在就创建,存在就清空)
    int fd =open("a.txt",O_WRONLY | O_CREAT | O_TRUNC,0644);
    if(fd==-1){
      perror("open");
    }
    
    1. 追加写日志
    int fd=open("a.txt",O_WRONLY | O_APPEND |O_CREAT,0644);
    if(fd==-1){
        perror("open");
    }
    
    1. 只允许“新建”,不允许覆盖
    int fd=open("a.txt",O_WRONLY |O_CREAT |O_EXCL,0644);
    
    

    这里额外讲一下,其实是有creat()系统调用的,但是用起来方便,支持的操作也比较少,一般用open就可以
    creat函数签名

    #include <fcntl.h>  
    int creat(const char *pathname, mode_t mode);
    
    1 条回复 最后回复
    1
    • sunrisepeakS 离线
      sunrisepeakS 离线
      sunrisepeak d2learn-dev
      编写于 最后由 编辑
      #2

      感觉可以把这个系列整理成一个GitHub 仓库 包含文档和对应的练习或演示代码
      让读者上手实操/改改东西 用 strace 等一些工具 追一追 系统调用 观察真实机器上的效果 之类的
      体验感应该能增强不少

      月 1 条回复 最后回复
      0
      • 月 离线
        月 离线
        月仁不吃五饼
        回复了sunrisepeak 最后由 编辑
        #3

        @sunrisepeak 在 Linux系统编程(2)文件IO 中说:

        感觉可以把这个系列整理成一个GitHub 仓库 包含文档和对应的练习或演示代码
        让读者上手实操/改改东西 用 strace 等一些工具 追一追 系统调用 观察真实机器上的效果 之类的
        体验感应该能增强不少

        时间比较少,有空可以整理一下

        1 条回复 最后回复
        1

        • 登录

        • 没有帐号? 注册

        • 登录或注册以进行搜索。
        d2learn forums Powered by NodeBB
        • 第一个帖子
          最后一个帖子
        0
        • 版块
        • 最新
        • 标签
        • 热门
        • Online Tools
        • 用户
        • 群组