1.文件IO

2016-12-12  本文已影响0人  大雄good

文件IO

Unix系统中的文件I/O通常只用到5个函数:openreadwritelseekclose。这一节描述的函数都是不带缓冲的I/O,不带缓冲是指每个readwrite都会执行一个系统调用,而不是从缓冲中读取数据。

1.文件描述符

对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符是一个非负数,范围0~OPEN_MAX-1。默认情况下0,1,2对应进程标准输入、输出和错误,这些魔法数宏定义在<unistd.h>

2.函数open和openat

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

#include<fcntl.h>
int open(const char *path, int oflag,.../* mode_t mode */);
int openat(int fd, const char *path, int oflag,.../* mode_t mode */);

path是打开或创建的文件名字,oflag是函数模式(比如只读),通过位与|操作设定,mode用于指定插入模式(比如从尾端追加)。两个函数的区别在于fd,共3种可能:

openat的提出主要是解决:

3.函数creat

creat同样用于创建一个新文件:

#include<fcntl.h>
int creat(const char *path, mode_t mode);
/* 返回值:成功返回文件描述符,失败返回-1 */

函数等效于:

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

creat的缺陷是它以只写方式打开创建的文件。

4.函数close

close函数用于关闭打开的文件:

#include<unistd.h>
int close(int fd);
/* 返回值:成功0,失败-1 */

关闭文件时,会释放该进程加在该文件上的所有记录锁。当一个进程终止时,内核自动关闭它所有打开的文件,所以很多程序并没显式调用close

5.lseek

lseek用于指定读写操作的偏移量处(不指定默认为0):

#include<unistd.h>
off_t lseek(int fd, off_t offset, int whence);
/* 返回值:成功返回偏移量,失败返回-1 */

whence有3个值:

利用lseek测试输入是否能使用偏移值:

#include<apue.h>
int main()
{
    if(-1 == lseek(STDIN_FILENO, 0, SEEK_CUR))
        printf("cannot seek\n");
    else
        printf("seek OK\n");
    exit(0);
}

管道,FIFO或网络套接字都不能使用lseek,所以结果为(只测试了管道和普通文件):

lseek测试

注意:

空洞文件测试:

#include "apue.h"
#include <fcntl.h>

char    buf1[] = "abcdefghij";
char    buf2[] = "ABCDEFGHIJ";

int
main(void)
{
    int     fd;

    if ((fd = creat("file.hole", FILE_MODE)) < 0)
        err_sys("creat error");

    if (write(fd, buf1, 10) != 10)
        err_sys("buf1 write error");
    /* offset now = 10 */

    if (lseek(fd, 16384, SEEK_SET) == -1)
        err_sys("lseek error");
    /* offset now = 16384 */

    if (write(fd, buf2, 10) != 10)
        err_sys("buf2 write error");
    /* offset now = 16394 */

    exit(0);
}

运行该程序可以得到:


hole

6.read和write

read函数用于打开文件中读取数据:

#include<unistd.h>
ssize_t read(int fd, void *buf, size_t nbytes);
/* 返回值:读取的字节数,若到尾部返回0;若出错返回-1 */

write函数用于向打开文件写数据:

#include<unistd.h>
ssize_t write(int fd, const void *buf, size_t nbytes);
/* 返回值:若成功,返回写入字节数;出错返回-1 */

7.文件共享

内核使用三种数据结构表示打开文件,它们间关系决定了文件共享方面一个进程对另一个进程的影响。

打开文件的内核数据结构

从上图可以看出,每个进程都有一个进程表项,里面存放文件表项的指针;文件表项包含当前文件状态(读、写、同步、阻塞等),偏移值v-node表项指针v-node表项包括v-node信息(文件类型和操作函数),以及inode(索引节点),索引节点包含文件信息(文件所有者、长度、指向实际磁盘位置等),Linux中没有v-node只有索引节点。

如果两个独立进程各种打开同一个文件,则有下图关系:

共享文件的进程

假定第一个进程在文件描述符3上打开该文件,另一个在文件描述符4上打开该文件,这样每个进程各自获得一个文件表项(这样做,可以让每个进程拥有自己的偏移值),但是这两个文件表项指向相同的v-node。

8.原子操作

Unix为文件IO提供原子操作,例如在打开文件时设置O_APPEND,这样使得内核每次写操作时都将当前偏移量设置为文件尾端。

同时提供preadpwrite函数,他们分别执行的是lseek后调用read和write,但是在调用函数时,无法中断其定位和读写操作。

9.dup和dup2

下面两个操作可以用来复制一个现有文件描述符:

#include<unistd.h>
int dup(int fd);
int dup2(int fd, int fd2);
/* 返回值:成功返回文件描述符;失败返回-1*/

dup2是一个原子操作。

10.fcntl

fcntl函数可以改变已经打开文件的属性:

#include<fcntl.h>
int fcntl(int fd, int cmd, ... /* int arg */)
/* 返回值:若成功,则依赖于cmd,若出错,返回-1 */

fcntl函数有以下5个功能(cmd控制):

11.其他函数

其余函数ioctl/dev/fd没怎么见过,前者可以实现各种功能,后者通过打开文件的方式复制文件描述符

小结

这节内容挺多的,主要是对文件I/O进行介绍,很多函数功能介绍,不太容易记住,所以需要多实验。

上一篇 下一篇

猜你喜欢

热点阅读