您的位置:首页 > 运维架构 > Linux

Linux文件I/O(creat/open/read/write/lseek/close/dup/sync)

2016-02-03 14:27 671 查看
先来提一下文件标识符的概念,对内核而言,所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数,打开或创建一个文件的时候,内核向进程返回它的描述符。我们要进行读写操作的时候,把这个描述符传给read和write即可对文件内容进行操作。

按照惯例,UNIX系统shell把文件描述符0与进程的标准输入关联,1和标准输出关联,2和标准错误关联。在unistd.h中有相关的STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO宏定义。文件描述符的变化范围是0~OPEN_MAX-1。

open函数:

int open(const char *path, int oflag, ... /* mode_t mode */);
int openat(int fd, const char *path, int oflag, ... /* mode_t mode */);


两函数的返回值都是打开的文件描述符,如果打开失败,返回-1。

path:文件名称

oflag:选项

- - - 必选:

- O_RDONLY:只读打开;

- O_WRONLY:只写打开;

- O_RDWR:读写打开;

- O_EXEC:执行打开;

- O_SEARCH:搜索打开。

- - - 可选:

- O_APPEND:追加写入,每次写入的时候偏移量都会固定在最尾端,lseek和write组成了原子操作,每次写入的时候都调用一次(所以lseek不管用了),而不是之前想的打开后lseek就不变了,以后只write(请看附1代码);

- O_CREAT:创建文件时设定第三个参数mode,指定访问权限;

- O_DIRECTORY:保证path是个目录,如果不是目录就会报错;

- O_EXCL:将测试文件是否存在和创建文件组成原子操作,如果creat时文件已存在,出错(这样的设计就不会出现两个进程同时创建覆盖的后果了);

- O_NONBLOCK:如果path引用的是FIFO、块特殊文件或字符特殊文件,将I/O操作设置为非阻塞状态;

- O_SYNC:使每次write等待雾里I/O操作完成,包括由该write操作引起的文件属性更新所需的I/O;

- O_TRUNC:用写或读写方式打开时将文件长度截断为0(等同于创建覆盖文件)。

// 所有的参数都在fcntl.h中进行了宏定义

fd参数把open和openat函数区分开,共有三种可能:

1. path参数指定的是绝对路径,在这种情况下fd被忽略,openat就相当于open;

2. path参数指定的是相对路径,fd指出了相对路径名在文件系统中开始的位置(fd参数通过打开相对路径名所在的目录获取的O_SEARCH | O_DIRECTORY);

3. path参数指定的是相对路径,fd为特殊值AT_FDCWD,此时就在工作目录获取,与open一样。

【使用openat函数可以让我们使用相对路径去在不同的目录
4000
进行工作,解决了多线程工作在不同目录的问题】

creat函数:

int creat(const char *path, mode_t mode);
// open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
// 现在open添加了O_CREAT和O_TRUNC这些选项,也不再需要单独的creat了


如果创建成功,返回文件描述符,出错返回-1。

creat的不足是使用只写的方式打开文件,如果要读和写同时进行,就只能进行切换,十分麻烦。现在可以使用open(path, O_RDWR | O_CREAT | O_TRUNC, mode)来实现。

close函数:

int close(int fd);


当一个进程终止的时候,内核自动关闭它所有打开的文件,很多程序利用这一点而不去主动close。

lseek函数:

off_t lseek(int fd, off_t offset, int whence);


如果成功,返回文件新的偏移量,否则返回-1。通常open一个文件都会将偏移量设置为0,除非指定了O_APPEND选项。如果对FIFO或者套接字管道等设置偏移量,会返回-1并将errno设置为ESPIPE。

whence参数:SEEK_SET, SEEK_CUR, SEEK_END

文件的偏移量是可以大于文件长度的,这样对文件的下一次写将会加长该文件,并在中间构成一个用0填充的空洞(虽然在存储的时候跟文件系统压缩方式有关,但它就是有那么大)。

read函数:

ssize_t read(int fd, void *buf, size_t nbytes);


返回值是读取到的字节数,如果已经到达文件的尾端,返回0。需要使用返回值来确定长度而不是nbytes,因为有很多情况是读不满的。

write函数:

ssize_t write(int fd, void *buf, size_t nbytes);


返回值是成功写入的字节数,如果出错返回-1。与read不同,write返回值通常是与nbytes相等的,如果不相等则表示出了一些问题,如磁盘写满了,或者单进程文件写太长了受限。

dup函数:

int dup(int fd);
int dup2(int fd1,int fd2);


两个函数均为复制一个现存的文件的描述。若成功返回值为新的文件描述符,若出错为-1。由dup返回的新文件描述符一定是当前可用文件描述中的最小数值。用dup2则可以用fd2参数指定新的描述符数值。如果fd2已经打开,则先关闭。若fd1=fd2,则dup2返回fd2,而不关闭它。通常使用这两个系统调用来重定向一个打开的文件描述符(构成选择通道,筛选器等等)。

dup可以看作是close和fcntl的原子集合操作。

我们的系统还提供了/dev/fd目录,这个目录里有数字0、1、2等文件,在程序中打开文件就相当于复制对应的描述符,注意此时的mode必须为之前打开对应fd的mode的子集,不可越限。使用这种方法creat的时候可能会导致底层文件被截断,因为linux实现使用指向实际文件的符号链接。

fd = open("/dev/fd/0", O_RDONLY);


sync函数:

int fsync(int fd);
int fdatasync(int fd);
void sync(void);


由于大多数磁盘I/O都采用了缓冲区写入的办法,I/O操作都是内核对缓冲区的写入操作,只有在内核需要重用缓冲区来存放其他磁盘块数据时,才会把所有延迟写数据块写入磁盘。这时就会出现一些一致性的问题,所以UNIX提供了sync、fsync和fdatasync三个函数将缓冲区文件内容写入磁盘。通常系统守护进程update就是周期性的调用sync函数来保证定期flush缓冲区。

sync:将所有修改过的块缓冲区排入写队列,直接返回(不等待实际磁盘写操作结束);

fsync:只对fd一个文件起作用,并且等待写操作结束后才返回,适用于数据库等需要高度一致性的程序;

fdatasync:与fsync相同,但它同时还会更新文件的属性。

还有fnctl和ioctl函数可以执行多种操作,但由于太过复杂,并且功能大多使用前面的函数就可以完成,暂时不说了。详情查看APUE-3.14,APUE-3.15。

附1代码:append标识符实验(不知为何左键不能用了虚拟机,就直接截图)

在这里我是创建了一个文件,让程序写了两次,可以看到每次write的时候seek才会更新。



代码结果:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: