左小白的技术日常
Github
2016/10/18
Author: guoqzuo

apue 文件IO

UNIX系统中,文件I/O常用的5个函数: open,read,write,lseek,close。与标准I/O相比文件I/O通常称为不带缓冲的I/O(unbuffered)。一般所有I/O都要经过内核的块缓冲。read,write的数据也要被内核缓冲,这里不带缓冲的I/O指的是在用户进程用不会自动缓冲,每次都是系统调用。

apue_file_io_1.png

文件描述符 (file descriptor)

对内核而言,所有打开的文件都是通过fd来引用。

文件描述符0,1,2分别与进程的标准输入,输出,错误缓冲区相关联,分别对应宏STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO。这些宏都在unistd.h里定义,一般每个进程最多能打开64个文件

I/O函数概述

这里主要介绍open、create、read、write、lseek、close、fcntl等函数

open函数

类似printf,可变长参数,可以是2参或3参。打开文件,返回该文件的fd,错误返回-1,且修改errno值

int open(const char *path, int oflag, ...); // open or create a file for reading or writing

函数参数:

示例: open("1.c", O_RDONLY | O_CREAT, 0644) 打开文件,如果存在就打开,不存在创建,权限为0644,如果只用2参,写法如下

if ((fd = open("1.c", O_RDONLY)) < 0) {
    if (error == ENOENT) { // 如果文件打开失败,且错误为文件不存在
        creat("1.c", 0644);
    } else {
        perror("open()");
        return 1;
    }
}

系统调用函数一般是原子性(atomic operation)的,在执行的时候不会因为进程调度中断其执行而产生bug

create函数

// DESCRIPTION   This interface is made obsolete by: open(2).
// The creat() function is the same as: open(path, O_CREAT | O_TRUNC | O_WRONLY, mode);
int creat(const char *path, mode_t mode);

read函数

// 读取文件内容存入buf,返回读取的字节数,return 0 表示文件已读完
ssize_t read(int fildes, void *buf, size_t nbyte); // read input 

write函数

// 向文件写入内容
ssize_t write(int fildes, void *buf, size_t nbyte); // wirte output

lseek函数

// 移动文件偏移位置,之前写一个取反加密时用过.  
// whence有3种情况0,1,2分别对应SEEK_SET、SEEK_CUR、SEEK_END, 宏和数字怎么方便就怎么用
off_t lseek(int fildes, off_t offset, int whence); // reposition read/write file offset

close函数

// 关闭文件,官方解释为删除fd
int close(int fildes);  // delete a descriptor

fcntl函数

int fcntl(int fildes, int cmd, ...); // file control
// 可以修改已打开文件的fd标志,文件状态标志,复制fd,参数2决定它的功能
// cmd = F_GETFD/F_SETFD 获取或设置fd标志
// cmd = F_GETFL/F_SETFL 获取或设置文件状态标志,可追加O_APPED
// cmd = F_DUPFD,复制fd

内核I/O数据结构

当打开一个文件,内核以什么形式来保持/操作相关数据?(以下UNIX系统适用,具体实现可能有差异)

当一个进程执行时:

apue_file_io_2.png

0、1、2为进程默认代开的文件

每次write后,文件表里的文件偏移量会加入所写入的字节数,如国偏移量超过文件长度,i结点表的当前文件长度会修改为当前偏移量,文件增大。lseek没有进行任何I/O操作,只是修改了文件表中的当前文件偏移量

两个进程操作同一个文件

apue_file_io_3.png

文件描述符的copy(dup, dup2)

dup, dup2 -- duplicate an existing file descriptor

open()函数返回fd遵循最小分配原则,默认打开了0、1、2,最小分配为3,如果0、1、2有关闭的,下次open就会占用系统的这些值, 例如close(1)后open一个文件,那么这个文件就成了输出缓冲区

int dup(int oldfd); // 将oldfd再分配一个fd,最小分配原则
int dup2(int oldfd, int newfd); // 复制fd,可指定新fd的值,如果新的已打开,先关闭。

apue_file_io_4.png

dup(fd); // ==> fcntl(fd, F_DUPFD, 0);

dup(fd, fd2); // ==> close(fd2); 
fcntl(fd, F_DUPFD, fd2);  // dup2是一个原子性的操作