现在的位置: 首页 > 综合 > 正文

Linux下的文件I/O编程小结

2013年05月15日 ⁄ 综合 ⁄ 共 6722字 ⁄ 字号 评论关闭

1.1  文件描述符
    文件描述符(fd)相当于windows编程中的文件句柄,使一个非负整数,引用一个打开的文件。
    Unix的惯例是文件描述符0(STDIN_FILENO)是标准输出,1(STDOUT_FILENO)是标准输出,2(STDERR_FILENO)是标准错误输出。

1.2  文件的打开与关闭
1.2.1   相关函数
    int open(const char *pathname, int flags);
    int open(const char *pathname, int flags, mode_t mode);
    int creat(const char *pathname, mode_t mode);
    int close(int fd);
1.2.2   flag的选项
    O_RDONLY、O_WRONLY和O_RDWR,这三个标志中只能指定一个。另外还有以下常数可以选择:O_APPEND、O_CREAT、O_EXEL(与O_CREAT同时指定,而文件已存在则出错,否则创建)、O_TRUNC、O_NONBLOCK(非阻塞方式)、O_SYNC(同步写,每次write都等到物理I/O完成)等常用选项。
1.2.3   创建新文件
    当flags指定O_CREAT时,需要指定第三个参数mode,说明新文件的存取权限。也可以用creat函数,creat等价于:
    open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode)
    
1.3  改变文件的偏移量
1.3.1   相关函数
    off_t lseek(int fildes, off_t offset, int whence);
1.3.2   设置文件位移量
    参数offset指定偏移量,参数whence可指定为SEEK_SET(相对于文件头)、SEEK_CUR(相对于当前位置)、SEEK_END(相对于从文件尾)。
    因为位移量可能是负值,比较lseek的返回值时必须谨慎,不要测试它是否小于0,而要测试它是否等于-1。
1.3.3   文件空洞
    文件的位移量可以大于文件当前长度,这种情况下,对该文件的下一次写操作将延长该文件。形成一个空洞,空洞中的字节都为0。

1.4  文件的读写
1.4.1   相关函数
    ssize_t read(int fd, void *buf, size_t count);
    ssize_t write(int fd, const void *buf, size_t count);
1.4.2   读文件
    read函数的返回值是实际读到的字节数,他可能少于要求读的字节数count:读到文件尾了;或者读的是终端设备、网络设备或者某些面向记录的设备时。
1.4.3   写文件
    写操作从当前位移开始。若打开文件时设置了O_APPEND选项,则每次写之前,都会将当前位移量设为文件尾。
1.4.4   效率
    使用不同的BUFFSIZE将数据从标准输入读入然后写到标准输出。在下面的测试中,标准输入重定向到一个7175K的文件,标准输出重定向到/dev/null。
    BUFFSIZE为1:
    real    0m15.945s
    user    0m5.420s
    sys     0m10.500s
    
    BUFFSIZE为4:
    real    0m4.025s
    user    0m1.570s
    sys     0m2.460s
    
    BUFFSIZE为16:
    real    0m1.057s
    user    0m0.200s
    sys     0m0.860s
    
    BUFFSIZE为256:
    real    0m0.153s
    user    0m0.030s
    sys     0m0.120s
    
    BUFFSIZE为1024:
    real    0m0.093s
    user    0m0.000s
    sys     0m0.100s
    
    BUFFSIZE为4096:
    real    0m0.081s
    user    0m0.010s
    sys     0m0.080s
    
    BUFFSIZE为16384:
    real    0m0.078s
    user    0m0.000s
    sys     0m0.080s
    
    BUFFSIZE为65536:
    real    0m0.080s
    user    0m0.000s
    sys     0m0.080s
继续增大BUFFSIZE对系统时间并没有影响。

1.5  复制文件描述符
1.5.1   相关函数
    int dup(int oldfd);
    int dup2(int oldfd, int newfd);
1.5.2   说明
    这些函数返回一个和oldfd共享同一文件表项的新fd。不同之处是dup2可以指定新fd的值,并且如果newfd已经打开,则先将其关闭。他们可以用fcntl(oldfd,F_DUPFD, 0/newfd)实现,不同的只是errno,并且dup2是一个原子操作。

1.6  fcntl函数
1.6.1   相关函数
    int fcntl(int fd, int cmd);
    int fcntl(int fd, int cmd, long arg);
    int fcntl(int fd, int cmd, struct flock *lock);
1.6.2   功能

  •     复制一个现存的描述符(cmd=F_DUPFD)。
  •     获得/设置文件描述符标志(cmd = F_GETFD或F_SETFD)。
  •     获得/设置文件状态标志(cmd d = F_GETFL或F_SETFL)。
  •     获得/设置异步I / O有权(cmd = F_GETOWN或F_SETOWN)。
  •     获得/设置记录锁(cmd = F_GETLK, F_SETLK或F_SETLKW)。

1.6.3   F_GETFL或F_SETFL
    对于这两种操作,可以获得或者设置文件的状态标志。F_GETFL时可以获得O_RDONLY、O_WRONLY、O_RDWR、O_APPEND、O_NONBLOCK、O_SYNC和O_ASYNC。对于前三种标志,必须使用屏蔽字O_ACCMODE,然后将结果与这三种标志一一比较。F_SETFL时只能设置后四种标志。
    accmode = val & O_ACCMODE;
    if (accmode == O_RDONLY) …
    else if (accmode == O_WRONLY) …
    else if (accmode == O_RDWR) …
    else err_dump("unknown access mode");
    
    if (val & O_APPEND) …
    if (val & O_NONBLOCK) …
    ……

1.7  记录锁
1.7.1   相关函数:
    int fcntl(int fd, int cmd);
    int fcntl(int fd, int cmd, struct flock *lock);
1.7.2   相关结构:
    struct flock {
        short l_type;         /* F_RDLCK, F_WRLCK, F_UNLCK */
        short l_whence;     /* SEEK_SET, SEEK_CUR, SEEK_END */
        off_t l_start;         /* Starting offset for lock */
        off_t l_len;       /* Number of bytes to lock */
        pid_t l_pid;       /* PID of process blocking our lock */
    };

    l_start和l_whence决定加锁或解锁区域的起始位置,l_len决定其长度。该区域可以越过文件尾端,但不能越过文件起始位置。l_len为0,则表示锁的区域从开始位置直至文件尾,而不管文件如何增长。锁整个文件的通常方法是:l_start为0,l_whence为SEEK_SET,l_len为0。
1.7.3   共享锁和独占锁
    共享锁,又叫读锁(RDLCK);独占锁,又叫写锁(WRLCK)。

表格 1 (读锁和写锁)

当前所有/要求

读锁

写锁

读锁(一把或多把)

可以

拒绝

写锁

拒绝

拒绝

1.7.4   加锁和解锁
    根据fcntl函数的cmd参数进行不同操作:
 

  • F_GETLK:检查是否可以建立参数lock描述的锁

  • F_SETLK:设置lock描述的锁,出错立刻返回
  • F_SETLKW:F_SETLK的阻塞版,若不能加锁则等待


1.7.5   锁的继承和释放
    进程终止时,它建立的锁全部释放。
    关闭一个文件描述符时,则与该描述符相关的文件上的所有由这个进程设置的锁都被释放,即使进程还有打开的描述符指向该文件。
    fork产生的子进程不继承父进程设置的锁。而exec调用后,新程序可以继承原执行程序的锁。
1.7.6   建议性锁和强制性锁
    建议性锁(Advisory lock)并不能保证其他对文件有写权限的进程读写文件,使用建议性锁的程序必须以一致的方法处理记录锁。使用强制性锁(Mandatory lock),则内核对每个open、read和write都要检查调用进程是否违背了某一把锁的作用。
    要使用强制性锁,首先文件所在的文件系统必须开启此功能(mount的时候加上-o mand参数);然后打开其set-guid,关闭其组执行位(chmod命令中的g-x和g+s)即可。强制性锁的使用和建议性锁基本相同。]
    强制性锁不是POSIX标准。

1.8  I/O多路转接
1.8.1   相关函数:
    int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
    int   pselect(int   n,   fd_set   *readfds,  fd_set  *writefds,  fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask)
    int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
    
    FD_CLR(int fd, fd_set *set);
    FD_ISSET(int fd, fd_set *set);
    FD_SET(int fd, fd_set *set);
    FD_ZERO(fd_set *set);
1.8.2   相关结构
    fd_set是一个fd的集合,有四个宏可对其进行操作:清空一个fd即(FD_ZERO),检测指定 fd是否在集中(FD_ISSET),加入(FD_SET)和移除(FD_CLR)一个fd。
    select使用结构timeval:
        struct timeval {
            long    tv_sec;         /* seconds */
            long    tv_usec;        /* microseconds */
        };
    pselect使用结构timespec:
        struct timespec {
            long    tv_sec;         /* seconds */
            long    tv_nsec;        /* nanoseconds */
        };
1.8.3   说明
    select等待指定的fd对应的文件准备就绪。对于readfds中的文件,等待起可读;对于writefds中的文件,等待其可写;对于exceptfds中的文件等待其发生异常。而n则是参数中最大的fd加1,可以设为FD_SETSIZE,但会影响效率。timeout则是等待的时间,为NULL则无限等待。返回值是就绪的fd的个数。
    在select函数中,如果在一个fd上碰到了文件结束,则select认为该fd是可读的,而不是指示一个异常。
    pselect和select类似,只是使用了timespec结构指定时间,另外和sigsuspend类似,在等待时会将信号屏蔽改为sigmask。
    poll是select函数的另一版本,不同之处是它构造一格pollfd结构数组,对每个元素指定一个fd及对其关心的条件。
1.8.4   使用
    select可以作为提供更精确的sleep函数使用。
    当必须读写多个fd时,使用select函数,可以避免长时间阻塞在一个fd上而另一个fd的数据却得不到及时处理的情况。

1.9  读写多个缓存
1.9.1   相关函数
    ssize_t readv(int fd, const struct iovec *vector, int count);
    ssize_t writev(int fd, const struct iovec *vector, int count);
1.9.2   相关结构
    struct iovec {
        void *iov_base;   /* Starting address */
        size_t iov_len;   /* Number of bytes */
    };
1.9.3   说明
    这两个函数用于读写多个缓存。读写的顺序是按数组的顺序。
1.9.4   性能
    两个缓存,一个512,一个1024,分别用三种方法将其写入文件10000遍,第一种是调用writev,第二种是每个循环调用两次write,第三种是声名一个1536的缓存,将前两个缓存的内容都拷贝入这个缓存中然后调用一次write,结果如下。右边是apue上的测试结果,他的缓存大小为100、200,机器是80386。
    one writev:
    real    0m0.318s      8.2s
    user    0m0.000s      0.3s
    sys     0m0.320s      7.8s
    
    two write:
    real    0m0.990s      13.7s
    user    0m0.000s      0.5s
    sys     0m0.330s      13.1s
    
    memcpy and one write
    real    0m0.255s      8.1s
    user    0m0.010s      0.7s
    sys     0m0.250s      7.3s

    很奇怪,调用2次write所用系统时间应该是调用一次write或writev的2倍,但我的出的结果却是调用一次writev和两次write所用系统时间差不多,而memcpy后调用一次write反而比一次writev还快。

1.10  I/O存储映射
1.10.1   概念
    存储映射使一个磁盘文件或设备同存储空间的一个缓存区相映射。于是从缓存中存取数据就相当于文件读写。
1.10.2   相关函数
    void  *  mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset);
    int munmap(void *start, size_t length);
    int msync(void *start, size_t length, int flags);
1.10.3   说明
    start参数一般设为0,即让系统选择映射缓存取的起始地址,这个地址通过返回值获得。fd,length和offset指定文件区域。prot指定对映射区的保护方式,flags则是一些标识位。MAP_SHARED表示和其他进程共享此映射,存取缓存取回直接反映到文件中;MAP_PRIVATE表示独享此进程,写入映射缓存区并不影响原文件。这两个标识只能指定一个。
    munmap解除存储映射。要注意的是,关闭文件描述符并不解除对此文件的映射。调用munmap也并不使映射区的内容写到文件中,同步文件和映射区可以调用msync函数。
1.10.4   使用
    使用mmap/memcpy进行文件操作比read/write快,因为前者直接对映射取缓存进行操作,而对于后者,内核需要将数据在用户缓存和它自己的缓存之间进行拷贝。
但是内存映射不能用在某些设备(如网络和终端设备)之间进行复制,并且操作时必须注意文件的长度是否改变。

抱歉!评论已关闭.