UNIX 文件I/O

UNIX 文件I/O

引言

  • 介绍UNIX系统可用的文件I/O函数---打开文件、读文件、写文件等
  • UNIX文件I/O常用函数:openreadwritelseekclose,不同缓冲长度对readwrite的影响
  • 不带缓冲的I/O,每个readwrite都调用内核中的一个系统调用
  • 多进程资源共享,原子操作

文件描述符

  • 对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数。
  • 依据惯例,标准输入、标准输出、标准错误的文件描述符分别为0(STFDIN_FILENO)、1(STFDOUT_FILENO)、2(STFDERR_FILENO),这些常量定义在头文件<unistd.h>中。
  • 文件描述符的变化范围是0~OPEN_MAX-1,早起UNIX支持的上限为19(允许每个进程最多打开20个文件),现在很多系统上限增加至63.

函数open和openat

  • openopenat函数可以打开或创建一个文件。

    #include<fcntl.h>
    int     open(const char * path, int oflag, ...);
    int     openat(int fd, const char * path, int oflag, ...);
    
    • 函数最后的...代表可变参数类型和数量,对于open函数来说,仅当创建新文件时才使用最后的这个参数
    • path是要打开或者创建文件的名字
    • oflag是打开文件的读写模式,常量定义在<fcntl.h>中,可以使用一个或者多个常量进行“或”运算。
      • 必选常量【必须选一个,且只能选一个】 O_RDONLY(只读打开)、O_WRONLY(只写打开)、O_RDWR(读写打开)、O_EXEC(只执行打开)、O_SEARCH(只搜索打开,应用于目录)
      • 可选常量
    • fd参数把openopenat函数区别开
      • path指定的是绝对路径,则fd参数被忽略,openat函数就相当于open函数
      • path参数是相对路径,fd参数指出相对路径名在文件系统中的开始地址。fd参数是通过打开相对路径名所在的目录来获取
      • path参数是相对路径,fd参数具有特殊值AT_FDCWD。此时,路径名在当前工作目录获取,openat函数在操作上与open函数类似
    • openat函数希望解决的问题
      • 1.让线程可以使用相对路径名打开目录中的文件,而不再只能打开当前工作目录
      • 2.避免time-of-check-to-time-of-use(TOCTTOU)错误
        • TOCTTOU错误基本思想:如果两个基于文件的函数调用,其中第二个依赖于第一个,两个调用不是原子操作,如果在两个函数调用之间文件改变了,那第一个函数调用的结果就可能不再有效,则后面的调用可能在错误的结果上。文件系统的TOCTTOU错误通常处理那些颠覆文件系统权限的小把戏,这些小把戏通过骗取特权文件程序降低特权文件的权限控制或者让特权文件打开一个安全漏洞等方式进行。
    • 文件名和路径名截断
      • 如果文件名或者路径名超过系统最大允许长度NAME_MAX(大多数等于255),常有两种方式处理
          1. 将文件名截断到最大支持长度
          • 这样处理将会影响文件名,且到达最大长度的文件名是否曾经被截断过也无法判断
          1. 返回出错状态 ENAMETOOLONG
      • POSIX.1中通过设置常量_POSIX_NO_TRUNC决定截断过长的文件名或者路径名还是返回错误状态

函数create

  • 调用create函数创建一个新文件

    #include<fcntl.h>
    int     creat(const char *path, mode_t mode)
    
  • 等价于open(path,O_WRONLY|O_CREATE|O_TRUNC,mode)

  • 早起的open函数不支持创建文件,现在open函数提供了相应的支持,实际上也就不再需要单独的create函数

  • mode,文件访问权限,将在下一节介绍

  • create函数的缺点:只能以只写方式打开所创建的文件,如果要读写文件,必学先createclose,然后再open,用调用open函数实现:

    open(path,O_RDWR|O_CREAT|O_TRUNC,mode);

函数close

  • close函数关闭一个打开文件,释放该进程加在该文件上的所有记录锁

  • 当一个进程终止时,内核自动关闭它所有的打开文件

    #include<unistd.h>
    int close(int fd);
    

函数lseek

  • 当前文件偏移量:每个打开文件都有一个与其相关联的偏移量,用以度量从文件开始处计量的字节数

  • 除非指定O_APPEND参数,否则偏移量为0

  • lseek函数显示地为一个打开文件设置偏移量

    #include<unistd.h>
    off_t lseek(int fd, off_t offset, int whence);
    
  • whence(从何处)参数与offset参数

    • whence=SEEK_SET:从文件开始处offset个字节;
    • whence=SEEK_CUR:从文件当前偏移值加上offset,offset可正可负;
    • whence=SEEK_END:从文件末尾偏移值加上offset,offset可正可负。
  • lseek仅将当前文件的偏移量记录在内核中,它并不引起I/O操作。该偏移量用于下一次读或写操作。

  • 文件的偏移量可以大于文件的长度,下一次写操作将加长文件的长度,并在文件中构成一个空洞,位于文件中但没有写过的字节都被读作0,空洞不要求在磁盘上占用存储区,具体处理方式与文件系统的实现有关。

    #include<apue.h>
    #include<error.h>
    #include<fcntl.h>
    
    char buf1[] = "abcdefghij";
    char buf2[] = "ABCDEFGHIJ";
    
    int main() {
        int fd;
    
        if ((fd = creat("file.hole", FILE_MODE)) < 0)
            err_sys("create error");
    
        if (write(fd, buf1, 10) != 10)
            err_sys("buf1 write error");
    
        if (lseek(fd, 16384, SEEK_SET) == -1)
            err_sys("lseek error");
    
        if (write(fd, buf2, 10) != 10)
            err_sys("buf2 write error");
    
        exit(0);
    }
```shell
(base) yuqdeiMac:3_fileio yuq$ ls -l file.hole
-rw-r--r--  1 yuq  staff  16394 Jun  1 21:30 file.hole
(base) yuqdeiMac:3_fileio yuq$ od -c file.hole
0000000    a   b   c   d   e   f   g   h   i   j  \0  \0  \0  \0  \0  \0
0000020   \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
*
0040000    A   B   C   D   E   F   G   H   I   J                        
0040012
```

函数read

  • 从打开文件中读取数据,返回读到的字节数,若到末尾返回0,失败返回-1
#include<unistd.h>
ssize_t read(int fd, void *buf, size_t nbytes);
  • 实际读到的字节数少于要求读的字节数:

    • 读到要求的字节数之前已经到文件末尾;
    • 从终端读取时,通常一次最多读一行(APUE第18章讲述如何改变这一点)
    • 从网络读取时,网络中的缓冲机制可能造成返回值小于所要求读的字节数
    • 从管道或FIFO读时,如若管道中包含的字节少于所需的数量,那么read将返回实际可用的字节数
    • 当从某些面向记录的设备读时,一次最多返回一个记录
    • 当一信号造成中断,而已经读了部分数据量时
  • read函数的经典定义

    int read(int fd, char *buf, unsigned nbytes);
    
    • POSIX.1对经典定义进行了更改
      • char *改为void *。在ISO C中,void *表示通用指针;
      • 返回值必须是一个带符号整型(ssize_t
      • 第3个参数历史上是一个无符号整型,size_t不带符号

函数write

  • 打开文件写数据,若成功,返回写的字节数,若失败,返回-1

     #include<unistd.h>
     ssize_t write(int fd, const void *buf, size_t nbytes);
    
  • 返回值通常与参数nbytes的值相同,否则表示出错

  • write函数常见出错的原因:1.磁盘写满;2.超过一个给定进程的文件长度限制;

I/O的效率

#include<apue.h>
#include<error.h>

#define BUFFSIZE 4096

int main(){
    int n;
    char buf[BUFFSIZE];

    while((n=read(STDIN_FILENO,buf,BUFFSIZE))>0)
        if(write(STDOUT_FILENO,buf,n)!=n)
            err_sys("write error");

    if(n<0)
        err_sys("read error");
    exit(0);
}
  • 上面这个程序应注意以下几点:
    • 它从标准输入读,写至标准输出。标准输入、输出、错误自动打开,允许用户利用shell的I/O重定向功能;
    • 进程终止时,UNIX系统内核会自动关闭进程的所有打开的文件描述符,所以此程序不必关闭输入和输出文件;
    • 对UNIX系统内核而言,文本文件与二进制代码文件并无区别。
  • 如何选取BUFFSIZE
    • linux ext4文件系统的磁盘块长度为4096,系统CPU时间的几个最小值也差不多会出现在BUFFSIZE为4096及以后的位置,继续增加缓冲区长度对此时间几乎没有影响
    • 大多数文件系统为改善性能都采用某种预读技术,当检验到正进行顺序读取时,系统就试图读入比应用要求更多的数据,并假想应用很快就会读这些数据。缓冲区小至32字节时时钟时间与拥有较大缓冲区长度时的时钟时间几乎一样

文件共享

  • UNIX系统支持在不同进程间共享打开文件
  • 内核使用3种数据结构打开文件,他们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响。
    • 每个进程在进程表中都有一个记录项,记录项种包含一张打开文件描述符表。与每个文件描述符表相关联的是:

      • 文件描述符标志
      • 指向一个文件表项的指针
    • 内核为所有打开文件维持一张文件表

      • 文件状态标志(读、写、追加、同步和非阻塞等)
      • 当前文件偏移量
      • 指向该文件v节点表项的指针
    • 每个打开文件(或设备)都有一个v节点(v-node)结构。v节点包含了文件类型和对此文件进行各种操作函数的指针。对于大多数文件,v节点还包含了该文件的i节点(i-node,索引节点)。这些信息是在打开文件时从磁盘读入内存的,所以,文件的相关信息都是随时可用的。例如:i节点包含了文件的所有者、文件长度、指向文件实际数据块在磁盘上所在位置的指针等。

      打开文件的内核数据结构

    • 创建v节点的目的是对一个计算机系统上的多文件系统类型提供支持。Sun把这种文件系统称为虚拟文件系统,把与文件系统无关的i节点部分称为v节点

    • 如果两个独立的进程各自打开了同一个文件,则有下图所示关系。


      两个独立进程各自打开同一个文件
  • 前文所述函数的进一步说明
    • write函数:每次写入完成后,文件表项中的当前文件偏移量增加所写入的字节数。如果当前文件偏移量超过文件长度,则将i节点表项中的当前文件长度相应更新;
    • O_APPEND:将打开标志更新到文件表项中的文件状态标志中,以追加操作打开文件时,将i节点表项中的当前文件长度设置到文件表项中的当前文件偏移量中;
    • lseek:若利用lseek定位到文件尾端,则文件表项中的当前文件偏移量首先会被设置为i节点表项中的当前文件长度(注意,这与用O_APPEND打开文件是不同的)
    • lseek函数只修改文件表项中的当前文件偏移量,不进行任何I/O操作

原子操作

  • 原子操作是指由多步组成的一个操作。如果该操作原子的执行,则要么执行完所有步骤,要么一步也不执行。

  • 任何要求多于一个函数调用的操作都不是原子操作

  • 先定位再执行I/O不是原子操作,UNIX的XSI扩展pread函数pwrite函数允许原子性地定位并执行I/O

    #include<unistd.h>
    ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
    ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);
    
  • pread:相当于调用lseek后调用read,返回读到的字节数,若到文件末尾,返回0;若出错,返回-1;

  • pwrite:相当于调用lseek后调用write,返回写入的字节数,若出错,返回-1;

  • 追加到一个文件需要原子操作,否则多进程处理同一个文件时,先定位得到的偏移值可能由于其他进程的操作导致失效。以O_APPEND标志打开文件是一种原子操作方法。

  • 创建一个文件的O_CREATO_EXCL选项,也是一个原子操作。

    if((fd = open(pathname, O_WRONLY))<0){
        if(errno==ENOENT){
            if((fd = creat(path,mode))<0)
                err_sys("create error");
        }else{
            err_sys("open error");
        }
    }
    
    • 如果在opencreat之间,另一个进程创建了该文件,并写入了一些数据,而此进程又执行creat函数,那么,之前进程写入的数据将会被擦去。

函数dup和dup2

  • 用来复制一个现有的文件描述符。若成功,返回新的文件描述符,若失败,返回-1

    #include<unistd.h>
    int dup(int fd);
    int dup2(int fd, int fd2);
    
  • dup:返回的新文件描述符一定是当前可用文件描述符中的最小数值

  • dup2:可以用fd2指定新描述符的值。如果fd2已经打开,则先关闭;如果fd2等于fd,则返回fd2,而不关闭它。否则,fd2FD_CLOEXEC文件描述符标志就被清除,这样fd2在进程调用exec时是打开状态。

  • 这些函数返回的新文件描述符与参数fd共享同一个文件表项

    dup(1)后的内核数据结构

  • 每个文件描述符都有它自己的一套文件描述符标志。文件描述符的执行时关闭(close-on-exec)标志总是由dup函数清除(后面会提到)。

  • fcntl函数:另一种复制描述符的方法

    dup(fd);
    //等价于
    fcntl(fd,F_DUPFD,0);
    //
    dup2(fd,fd2);
    //等价于(不完全等价,前面是原子操作,后面不是,而且dup2与fcntl有一些不同的errno)
    close(fd2);
    fcntl(fd,F_DUPFD,fd2);
    

函数sync、fsync和fdatasync

  • 延迟写:当我们向文件写入数据时,内核通常先将数据复制到缓冲区中,然后排入队列,晚些时候再写入磁盘。
  • 通常,当内核需要重用缓冲区来存放其他磁盘块数据时,它会将所有延迟写数据块写入磁盘
  • syncfsync以及fdatasync:为了保证磁盘上的实际文件系统与缓冲区中内容的一致性
   #include<unistd.h>
   int fsync(int fd);
   int fdatasync(int fd);
   
   void sync(void);
  • sync:将所有修改过的块缓冲区排入写队列,然后就返回,不等待实际写磁盘结束
    • 通常,称为update的系统守护进程周期性地调用(一般每个30s)sync函数。这就保证定期冲洗(flush)内核的块缓冲区。
  • fsync:只对由文件描述符fd指定的一个文件起作用,并且等待写磁盘操作结束才返回
  • fdatasync:类似fsync,但它只影响文件的数据部分,而fsync还会同步更新文件的属性

函数fcntl

  • 改变已经打开文件的属性,若成功则返回依赖于cmd,若出错,返回-1.

    #include<fcntl.h>
    int fcntl(int fd, int cmd, ... /* int arg */);
    
  • fcntl函数的5种功能

    • 复制一个已有的描述符(cmd=F_DUPFDF_DUPFD_CLOEXEC)
      • F_DUPFD
        • 复制文件描述符fd,结果由函数fcntl返回
        • 新文件描述符要求:尚未打开的描述符,数值大于fcntl函数的第三个整型参数
        • 新文件描述符与fd共享同一文件表项,但是有一套自己的文件描述符标志;其FD_CLOEXEC标志被清除(这表示该描述符在exec时仍然有效,close exec也就是执行时关闭的意思)
      • F_DUPFD_CLOEXEC
        • F_DUPFD一样,复制文件描述符
        • 设置与新描述符关联的FD_CLOEXEC文件描述符的值
    • 获取/设置文件描述符标志(cmd=F_GETFDF_SETFD)
      • F_GETFD
        • 获取fd的文件描述符标志。当前只定义了一个文件描述符标志FD_CLOEXEC
      • F_SETFD
        • 按照fcntl函数第三个整型参数设置fd文件描述符标志
        • exec时不关闭,FD_CLOEXEC=0exec时关闭,FD_CLOEXEC=1
    • 获取/设置文件状态标志(cmd=F_GETFLF_SETFL)
      • 状态也就是读写状态;例如只读、只写、追加、同步读写等
      • O_RDONLY(只读打开)、O_WRONLY(只写打开)、O_RDWR(读写打开)、O_EXEC(只执行打开)、O_SEARCH(只搜索打开,应用于目录);这5个标志必须选一个,也只能选一个,前三个标志分别是0、1、2。由于这5个值互斥,因此首先使用屏蔽字O_ACCMODE取得访问方式位,然后将结果与这5个值比较。
    • 获取/设置异步I/O所有权(cmd=F_GETOWNF_SETOWN)
      • 获取/设置接收SIGIOSIGURG信号的进程ID进程组ID正的arg表示指定一个进程ID负的arg表示指定一个进程组ID取绝对值
    • 获取/设置记录锁(cmd=F_GETLKF_SETLKF_SETLKW)
    #include<apue.h>
    #include<error.h>
    #include<fcntl.h>
    
    int main(int argc, char *argv[]) {
        int val;
    
        if (argc != 2)
            err_quit("usage: a.out <descriptor#>");
    
        if ((val = fcntl(atoi(argv[1]), F_GETFL, 0)) < 0)
            err_sys("fcntl error for fd %d", atoi(argv[1]));
    
        switch (val & O_ACCMODE) {
            case O_RDONLY:
                printf("read only");
                break;
            case O_WRONLY:
                printf("read only");
                break;
            case O_RDWR:
                printf("read write");
                break;
            default:
                err_dump("unknown access mode");
        }
    
        if (val & O_APPEND)
            printf(", append");
        if (val & O_NONBLOCK)
            printf(", nonblocking");
        if (val & O_SYNC)
            printf(", synchronous writes");
    
    #if !defined(_POSIX_C_SOURCE) && defined(O_FSYNC) && (O_FSYNC != O_SYNC)
        if(val & O_FSYNC)
            printf(", synchronous writes");
    #endif
        putchar('\n');
        exit(0);
    
    }
    
    • 修改文件描述符标志或文件状态标志
      • 先获得现有标志,然后按照期望设置(位操作)
      • 直接使用F_SETFDF_SETFL会关闭以前设置的标志
    #include<apue.h>
    #include<error.h>
    #include<fcntl.h>
    
    void set_fl(int fd, int flags) {
        int val;
        if ((val = fcntl(fd, F_GETFL, 0)) < 0)
            err_sys("fcntl F_GETFL error");
    
        val |= flags;//开启一个文件状态标志
    
        if (fcntl(fd, F_SETFL, val) < 0)
            err_sys("fcntl F_SETFL error");
    }
    
    void clr_fl(int fd, int flags) {
        int val;
        if ((val = fcntl(fd, F_GETFL, 0)) < 0)
            err_sys("fcntl F_GETFL error");
    
        val &= ~flags;//关闭一个文件状态标志
    
        if (fcntl(fd, F_SETFL, val) < 0)
            err_sys("fcntl F_SETFL error");
    }
    
    • 例如使用set_fl(STDOUT_FILENO,O_SYNC);开启同步标志

      • UNIX系统中,单纯使用write只是将数据排入队列,并不一定立即写入磁盘
      • 在数据库等系统中,需要使用O_SYNC标志确保数据已经写在磁盘上以免系统异常时丢失数据
      • 而在Linux系统中,不允许fcntl设置O_SYNC,所以设置无效,但是如果文件打开时能指定该标志,仍然应该遵从这个标志
      • Linux中设置O_SYNC之后还应该调用fsyncMACOSO_SYNC起到了应有的作用。
    • fcntl函数的5种功能

      • 复制一个已有的描述符(cmd=F_DUPFDF_DUPFD_CLOEXEC)
        • F_DUPFD
          • 复制文件描述符fd,结果由函数fcntl返回
          • 新文件描述符要求:尚未打开的描述符,数值大于fcntl函数的第三个整型参数
          • 新文件描述符与fd共享同一文件表项,但是有一套自己的文件描述符标志;其FD_CLOEXEC标志被清除(这表示该描述符在exec时仍然有效,close exec也就是执行时关闭的意思)
        • F_DUPFD_CLOEXEC
          • F_DUPFD一样,复制文件描述符
          • 设置与新描述符关联的FD_CLOEXEC文件描述符的值
      • 获取/设置文件描述符标志(cmd=F_GETFDF_SETFD)
        • F_GETFD
          • 获取fd的文件描述符标志。当前只定义了一个文件描述符标志FD_CLOEXEC
        • F_SETFD
          • 按照fcntl函数第三个整型参数设置fd文件描述符标志
          • exec时不关闭,FD_CLOEXEC=0exec时关闭,FD_CLOEXEC=1
      • 获取/设置文件状态标志(cmd=F_GETFLF_SETFL)
        • 状态也就是读写状态;例如只读、只写、追加、同步读写等
        • O_RDONLY(只读打开)、O_WRONLY(只写打开)、O_RDWR(读写打开)、O_EXEC(只执行打开)、O_SEARCH(只搜索打开,应用于目录);这5个标志必须选一个,也只能选一个,前三个标志分别是0、1、2。由于这5个值互斥,因此首先使用屏蔽字O_ACCMODE取得访问方式位,然后将结果与这5个值比较。
      • 获取/设置异步I/O所有权(cmd=F_GETOWNF_SETOWN)
        • 获取/设置接收SIGIOSIGURG信号的进程ID进程组ID正的arg表示指定一个进程ID负的arg表示指定一个进程组ID取绝对值
      • 获取/设置记录锁(cmd=F_GETLKF_SETLKF_SETLKW)
      #include<apue.h>
      #include<error.h>
      #include<fcntl.h>
      
      int main(int argc, char *argv[]) {
          int val;
      
          if (argc != 2)
              err_quit("usage: a.out <descriptor#>");
      
          if ((val = fcntl(atoi(argv[1]), F_GETFL, 0)) < 0)
              err_sys("fcntl error for fd %d", atoi(argv[1]));
      
          switch (val & O_ACCMODE) {
              case O_RDONLY:
                  printf("read only");
                  break;
              case O_WRONLY:
                  printf("read only");
                  break;
              case O_RDWR:
                  printf("read write");
                  break;
              default:
                  err_dump("unknown access mode");
          }
      
          if (val & O_APPEND)
              printf(", append");
          if (val & O_NONBLOCK)
              printf(", nonblocking");
          if (val & O_SYNC)
              printf(", synchronous writes");
      
      #if !defined(_POSIX_C_SOURCE) && defined(O_FSYNC) && (O_FSYNC != O_SYNC)
          if(val & O_FSYNC)
              printf(", synchronous writes");
      #endif
          putchar('\n');
          exit(0);
      
      }
      
      • 修改文件描述符标志或文件状态标志
        • 先获得现有标志,然后按照期望设置(位操作)
        • 直接使用F_SETFDF_SETFL会关闭以前设置的标志
      #include<apue.h>
      #include<error.h>
      #include<fcntl.h>
      
      void set_fl(int fd, int flags) {
          int val;
          if ((val = fcntl(fd, F_GETFL, 0)) < 0)
              err_sys("fcntl F_GETFL error");
      
          val |= flags;//开启一个文件状态标志
      
          if (fcntl(fd, F_SETFL, val) < 0)
              err_sys("fcntl F_SETFL error");
      }
      
      void clr_fl(int fd, int flags) {
          int val;
          if ((val = fcntl(fd, F_GETFL, 0)) < 0)
              err_sys("fcntl F_GETFL error");
      
          val &= ~flags;//关闭一个文件状态标志
      
          if (fcntl(fd, F_SETFL, val) < 0)
              err_sys("fcntl F_SETFL error");
      }
      
      • 例如使用set_fl(STDOUT_FILENO,O_SYNC);开启同步标志
        • UNIX系统中,单纯使用write只是将数据排入队列,并不一定立即写入磁盘
        • 在数据库等系统中,需要使用O_SYNC标志确保数据已经写在磁盘上以免系统异常时丢失数据
        • 而在Linux系统中,不允许fcntl设置O_SYNC,所以设置无效,但是如果文件打开时能指定该标志,仍然应该遵从这个标志
        • Linux中设置O_SYNC之后还应该调用fsyncMACOSO_SYNC起到了应有的作用。

函数ioctl

  • ioctlI/O操作的杂物箱,终端I/O是使用ioctl最多的地方
#include<unistd.h>
#include<sys/ioctl.h>
int ioctl(int fd, int request, ...);

/dev/fd

  • /dev/fd目录下是名为0、1、2、3、4、5等的文件,打开文件/dev/fd/n等效于复制描述符n(假定描述符n是打开的)
  • fd = open("dev/fd/0", mode)
    • 大多数系统忽略指定的mode
    • 另一些要求mode必须是所引用文件初始打开时所使用的打开模式的一个子集。例如初始以只读模式打开,那么之后用读写模式打开,也不能对文件进行写操作
  • Linux实现中的/dev/fd把文件描述符映射成指向底层物理文件的符号链接。当打开与标准输入关联的文件,返回的新文件描述符模式与/dev/fd文件描述符的模式其实并不相关

习题

  • 读写磁盘文件时,本章中描述的函数确实是不带缓冲机制的吗?

    • 不是,所有的磁盘I/O都要经过内核的块缓冲区(内核的缓冲区高速缓存),不带缓冲术语指的是在用户进程中对这两个函数不会自动缓冲
  • 写一个和dup2功能相同的函数,要求不调用fcntl函数,并且要有正确的错误处理

    • 网上的实现大部分如下面的代码,然而这个代码感觉有些问题,这个代码不是原子操作,在完成之前如果异常退出,则dup函数复制的fd1也就没有办法删除了
    #include<apue.h>
    #include<error.h>
    #include<unistd.h>
    
    typedef struct fdList {
        int fd;
        struct fdList *pre;
    } fdLink;
    
    int myDup2(int fd1, int fd2) {
        if (fd1 < 0 || fd2 < 0)
            err_sys("fd1 and fd2 must be non-negative integers\n");
        if (fd1 == fd2)
            return fd2;
        close(fd2);
    
        fdLink *pre = NULL;
        fdLink *node;
        int fd;
    
        while ((fd = dup(fd1)) != fd2) {
            node=(fdLink*)malloc(sizeof(fdLink));
            node->fd=fd;
            node->pre=pre;
            pre=node;
        }
        fdLink* tmp;
        while(pre!=NULL){
            close(pre->fd);
            tmp=pre;
            pre=pre->pre;
            free(tmp);
        }
    
        return fd2;
    }
    
  • 如果一个进程执行下面3个函数的调用:

    fd1 = open(path, oflags);
    fd2 = dup(fd1);
    fd3 = open(path,oflags);
    

    那么三个文件描述符之间的关系?文件表项是否共享?v节点表项的关系?

    • 每次调用open函数就会分配一个新的文件表项
    • 两次open函数打开的是同一个文件,所以指向相同的v节点
    • dup函数引用已经存在的文件表项
  • 如果使用追加标志打开一个文件以便读写,能否仍用lseek在任一位置开始读?能否用lseek更新文件中任一部分的数据?请编写一段程序验证。

    • 追加标志打开的文件执行的是原子操作,每次写都会定位到文件的末尾,所以不能更新任一部分数据
    • 但是读任一部分不影响
    • 测试示例:
    #include <fcntl.h>
    #include <unistd.h>
    #include <apue.h>
    #include <error.h>
    
    int main() {
        int fd;
        if ((fd = creat("./test.txt", S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)) < 0)
            err_sys("create file error\n");
    
        char *msg = "hello world";
        if (write(fd, msg, strlen(msg)) != strlen(msg))
            err_sys("write error\n");
        close(fd);
    
        int fd2 = open("./test.txt", O_RDWR | O_APPEND);
        //从开头偏移6个字母,显示读取结果
        off_t cur_pos = lseek(fd2, 6, SEEK_SET);
        if (cur_pos == -1)
            err_sys("cannot seek\n");
    
        char buf[4096];
        while (read(fd2, buf, 4096) > 0) {
            printf("%s", buf);
        }
        printf("\n");
    
        //从开头偏移6个字幕,执行写操作
        cur_pos = lseek(fd2, 6, SEEK_SET);
        if (cur_pos == -1)
            err_sys("cannot seek\n");
        char *msg2 = "! Love Coding";
        write(fd2, msg2, strlen(msg2));
    
        //将偏移调至开头,读取所有内容
        cur_pos = lseek(fd2, 0, SEEK_SET);
        if (cur_pos == -1)
            err_sys("cannot seek\n");
    
        while (read(fd2, buf, 4096) > 0) {
            printf("%s\n", buf);
        }
    }
    
    • 测试输出
    world
    hello world! Love Coding
    

Reference

  • 《Unix环境高级编程(第3版)》
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容