Unix I/O 概述
一个Unix文件就是一个m个字节的序列,所有的I/O设备(如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当作相应文件的读和写来执行。这种将设备映射为文件的方式,允许Unix内核引出一个简单、低级的应用接口,称为Unix I/O,这使得输入和输出都能以一种统一且一致的方式执行:
- 打开文件: 一个应用程序通过要求内核打开相应的文件,来宣告它想访问一个I/O设备。内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件。(内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符,用户程序只能通过文件描述符引用文件) 为了终端使用方便,shell启动一个程序时,该程序会自动继承3个打开的文件,描述符分别为0、1、2,分别为标准输入、标准输出、标准错误。所有这些都默认连接到终端,因此如果一个程序只读文件描述符0,只写描述符1、2,那么它不必打开任何其他文件就可以完成I/O操作。
- 改变当前文件的位置: 对于每个打开的文件,内核保持着一个文件位置k,初始为0。这个文件位置是从文件开头起始的字节偏移量。应用程序能够通过执行
seek
操作,显式的设置文件的当前位置为k。 - 读写文件: 读操作就是从文件拷贝n个字节到存储器(n>0,存储器一般指程序中的字符数组),从当前文件位置k开始,然后将k增加到k+n。给定一个大小为m字节的文件,当k>=m时执行读写会触发一个称为end-of-file(EOF)的条件,应用程序能检测到这个条件。(在文件结尾处并没有明确的
EOF
符号)。类似的,写操作就是从存储器拷贝n个字节到一个文件,从当前文件位置k开始,然后更新k。 - 关闭文件: 当应用完成了对文件的访问后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因而终止时,内核都会关闭所有打开的文件并释放它们的存储器资源。
一个FILE
指针指向一个结构,该结构包含了文件描述符和其他一些信息。宏fileno(fp)
定义在<stdio.h>
中,它返回文件描述符。更多相关信息:C I/O。
打开和关闭文件(删除)
进程通过调用open
来打开一个已存在文件或创建一个新文件。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// 成功返回文件描述符,若失败,则返回-1
int open(char *filename, int flags, mode_t mode);
-
open
将filename
转换为一个文件描述符,并且返回描述符。返回的描述符总是在进程中当前没有打开的最小描述符。 -
flags
指明了进程打算如何访问这个文件,flags
的参数可以是一个或更多位掩码的或,为此提供一些额外的提示。O_RDONLY
: 只读O_WRONLY
: 只写O_RDWR
: 可读可写O_CREAT
: 如果文件不存在,就创建它的一个截断的(truncated)(空)文件O_TRUNC
: 如果文件已经存在,就截断它O_APPEND
: 在每次写之前,设置文件位置到文件的结尾处(append,以追加方式写)
fd = open("foo.txt", O_RDONLY, 0); // 以只读方式打开一个已存在文件
fd = open("foo.txt", O_WRONLY|O_APPEND, 0); // 打开一个已存在文件,并在后面添加一些数据
-
mode
指定了新文件的访问权限位。作为上下文的一部分,每个进程都有一个umask
,它是通过调用umask
函数来设置的。当进程通过带某个mode
参数的open
函数调用来创建一个新文件时,文件的访问权限位被设置为mode & ~umask
。可读 可写 可执行 user S_IRUSR
S_IWUSR
S_IXUSR
group S_IRGRP
S_IWGRP
S_IXGRP
other S_IROTH
S_IWOTH
S_IXOTH
/*
* 创建新文件,文件拥有者有读写权限,而其他用户都有读权限
*/
#define DEF_MODE S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH
#define DEF_UMASK S_IWGRP|S_IWOTH
umask(DEF_UMASK); // 设置进程的umask
/*
* mode & ~umask
* = DEF_MODE & ~DEF_UMASK
* = 110.110.110 & ~000.010.010
* = 110.110.110 & 111.101.101
* = 110.100.100
*/
fd = open("foo.txt", O_CREAT|O_TRUNC|O_WRONLY, DEF_MODE);
一个程序可以同时打开的文件数有限制(典型为20个左右,参见<sys/param.h>
中NOFILE
)。进程通过调用close
关闭一个打开的文件(即终止文件名和文件描述符之间的联系,释放文件描述符供其它文件使用)。
#include <unistd.h>
int close(int fd); // 成功返回0, 失败返回-1
关闭一个已关闭的描述符会出错。
若程序通过exit
而终止或从主程序中返回将关闭所有打开的文件。
系统调用unlink从文件系统中删除文件
读和写文件
应用程序通过分别调用read
和write
来执行读写操作。
#include <unistd.h>
// 成功返回读的字节数,若EOF则为0,若出错返回-1
ssize_t read(int fd, void *buf, size_t n);
// 成功返回写的字节数,若出错则为-1
ssize_t write(int fd, const void *buf, size_t n);
read
从描述符为fd
的当前文件位置拷贝至多n
个字节到存储器位置buf
。write
从存储器位置buf
拷贝至多n
个字节到描述符fd
的当前文件位置。
size_t
被定义为unsigned int
,而为在出错时返回-1,ssize_t
被定义为int
。返回-1的可能性使得read
的最大值减小一半,从4GB减小到2GB
在某些情况下,read
和write
传送的字节比应用程序要求的少。这些不足值(short count)不表示错误。出现这种情况的原因如下:
- 读时遇到EOF: 假设我们准备读一个文件,该文件从当前位置开始只含有20个字节,而我们以50个字节的片进行读取。这样一来,下一个
read
返回的不足值为20,此后的read
将通过返回不足值0来发出EOF信号。 - 从终端读文本行: 如果打开的文件是与终端相关联的(如键盘和显示器),那么每个
read
函数将一次传送一个文本行,返回的不足值等于文本行的大小。 - 读和写网络套接字(socket)。
随机访问
文件的I/O操作通常是顺序的:每次在文件中的读和写都紧接在上一次操作之后,然而,如果必要,一个文件可以按任意顺序读写。
#include <unistd.h>
// 返回新的绝对位置,出错返回-1
off_t lseek(int fildes, off_t offset, int whence);
lseek
使得文件描述符所指定的文件的读写位置进行移动:
- whence: SEEK_SET (the file offset shall be set to offset bytes)
- whence: SEEK_CUR (the file offset shall be set to its current location plus offset)
- whence: SEEK_END (the file offset shall be set to the size of the file plus offset)
// 定位到文件尾
lseek(fp, 0L, SEEK_END);
// 重新返回到开始
lseek(fp, 0L, SEEK_SET);
// 取得当前位置
off_t pos = lseek(fp, 0L, SEEK_CUR);
使用lseek,可以像处理一个大数组一样处理文件,代价是访问速度变慢。
读取文件元数据
应用程序能够通过调用stat
和fstat
函数,检索到关于文件的信息。
#include <unistd.h>
#include <sys/stat.h>
// 成功返回0,失败返回-1
int stat(const char *filename, struct stat *buf);
int fstat(int fd, struct stat *buf);
stat
以文件名作为输入,fstat
以文件描述符作为输入,调用它们都会填写如下所示的一个stat
数据结构中的各个成员。
stat结构是i节点(innode)的一部分,该结构定义在<sys/stat.h>
中
/* Metadata returned by the stat and fstat function */
struct stat {
dev_t st_dev; /* Device of inode */
ino_t st_ino; /* inode number */
mode_t st_mode; /* Protection and file type (mode bite)*/
nlink_t st_nlink; /* Number of hard links to file */
uid_t st_uid; /* User ID of owner */
gid_t st_gid; /* Group ID of owner */
dev_t st_rdev; /* Device type (if inode device) */
off_t st_size; /* Total size, in bytes */
unsigned long st_blksize; /* Blocksize for filesystem I/O */
unsigned long st_blocks; /* Number of blocks allocated */
time_t st_atime; /* Time of last access (read) */
time_t st_mtime; /* Time of last modification (written or created)*/
time_t st_mtime; /* Time of last change */
}
st_size
包含了文件的字节数大小。
st_mode
则编码了文件访问许可位(同read
函数的mode
参数)和文件类型。
Unix识别大量不同文件类型:
- 普通文件: 包含某种类型的二进制或文本数据。对内核而言,文本文件和二进制文件毫无区别。
- 目录文件: 包含关于其他文件的信息。
- 套接字: 是一种用来通过网络和其他进程通信的文件。
Unix提供的宏指令根据st_mode
来确定文件的类型:
S_ISREG()
: 是普通文件吗?S_ISDIF()
: 是目录文件吗?S_ISSOCK()
: 是一个网络套接字吗?
- older
- Newer