文件I/O
内核为每个进程维护了一个打开文件的列表,该表称为文件表。文件表由非负整数组成,其中每项都代表一个打开的文件信息。
子进程会得到一份和父进程一样的文件表,其中打开的文件列表、访问模式,当前文件的位置等信息都是一样的,子进程文件表的变化不会影响到其他进程的文件表。
文件描述符使用C语言的int表示,文件描述符从0开始,默认上限1024。负数是不合法的文件描述符,通常使用-1来表示函数不能返回合法的文件描述符的错误。
每个进程通常会有3个默认的文件描述符:0,1,2。除非进程显示的关闭了他们。文件描述符0表示标准输入,文件描述符1表示标准输出,文件描述符2表示标准错误。C标准库提供了预处理器宏:STDIN_FILENO,STDOUT_FILENO和STDERR_FILENO宏,取代上边的整数直接引用。
遵循一切皆文件的理念,任何能读写的东西都可以使用文件描述符来访问。
打开文件
通过open函数打开文件获取文件描述符
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
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);
open()系统调用会将file指定的文件与成功返回的文件描述符相关联,文件位置指针会被设置为0,文件根据oflag给出的标志位打开。
oflag参数必须是以下参数之一:O_RDONLY, O_WRONLY 或 O_RDWR。指明文件是以只读,只写,可读写方式打开。
在使用O_CREATE创建新文件时,mode参数提供新文件的权限。
具体参数使用可以参考man手册。
open()和creat()调用成功都返回一个合法的文件描述符,失败返回-1,并将errno设置为一个合适的值。
read()读取文件
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
该系统调用从fd所指向的文件的当前偏移量之多读取count个字节到buf中,成功时返回写入buf缓冲区的字节数,失败返回-1,并设置errno。该文件位置指针会向前移动,移动长度由所读取的字节数确定。
读入所有字节
ssize_t ret;
while(count!=0 && (ret = read(fd, buf, count))!=0){
if(ret == -1){
if(errno == EINTR){
continue;
}
perror("read");
break;
}
count -= ret;
buf += ret;
}
非阻塞读
在read()调用中如果文件中没有数据可读,read调用会阻塞,直到文件中有数据可读。有时候程序员不希望在read没有数据可读时调用阻塞,而是让调用立即返回,这种情况称为非阻塞I/O。
如果给出的文件描述符在非阻塞模式下打开(open()中给定O_NONBLOCK)并且没有可读数据,read()调用会返回-1,并且设置errno为EAGAIN,而不是阻塞。在进行非阻塞I/O时,必须检查EAGAIN,否则会出现数据缺失。
用write()写
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
一个write()调用由文件描述符引用文件的当前位置开始,将buf中至多count个字节写入文件。
成功时,返回写入的字节数。失败时,返回-1,并将errno设为对应的值。
同步I/O
最简单的确认数据写入磁盘的方法是使用fsync()调用.
#include <unistd.h>
int fsync(int fd);
int fdatasync(int fd);
调用fsync()可以保证fd对应文件的脏数据回写到磁盘上。文件描述符必须以写的形式打开。fdatasync()完成的事情和fsync()完成的事情一样,区别在于他仅写入数据,该调用不保证元数据同步在磁盘上,故此可能快一点。
成功时返回0,失败时返回1-,并设置errno。
关闭文件
#include <unistd.h>
int close(int fd);
close()调用解除了文件描述符和文件的关联,并分离进程和文件的关联。
成功时返回0,失败时返回1-,并设置errno。
lseek()查找
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
lseek()系统调用可以为给定的文件描述符引用的文件位置设定指定值。
whence参数有以下三种:
-
SEEK_SET 将当前文件位置设置为offset;
-
SEEK_CUR 将当前文件位置设置为当前位置加上offset,offset可以为负数;
-
SEEK_END 将当前文件位置设置到文件结尾加上offset,offset可以为负数;
在调用成功时返回新文件位置,失败时返回-1,并设置errno;
定位读写
Linux定义了两种read()和write()的变体来替代lseek();
读形式的调用pread()和写形式的调用pwrite();
#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
pread()调用:读取fd文件描述符所指文件中offest偏移的count字节数据写入到buf中。
pwrite()调用:给fd文件描述符所指的文件中offert偏移位置写入count字节的buf中的数据。
截短文件
Linux提供两个系统调用来截短文件
#include <unistd.h>
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
两个系统调用都将指定的文件截短到length长度。
二者都在成功时返回0,失败时返回-1,并设置errno;
I/O多路复用
I/O多路复用允许应用在多个文件描述符上同时阻塞,并在其中某个可以读写时收到通知。
Linux提供了三种多路复用:select(),poll(),epoll();
select()系统调用
select()系统调用提供了一种实现同步I/O多路复用的机制。
#include <sys/select.h>
int select(int nfds, fd_set *restrict readfds,
fd_set *restrict writefds, fd_set *restrict exceptfds,
struct timeval *restrict timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
在指定文件描述符准备好I/O之前,或者超过一定时间限制,select()系统调用就会阻塞。
监测的文件描述符可以分为三类,分别等待不同的事件。监测readfds中的文件描述符,确认其中是否有可读数据。监测writefds中文件描述符,确认其中是否有数据可写。监测exceptfds中的文件描述符,确认其中是否发生异常。
成功时每个集合只包含对应类型的I/O就绪的文件描述符。
第一个参数nfds等于所有文件描述符中最大值加一。
timeout指向一个timeval结构体。
FD_ZERO 从指定集合中移除所有文件描述符。
FD_SET 向指定集合中添加文件描述符。
FD_CLR 从指定集合中删除给定文件描述符。
FD_ISSET 测试一个文件描述符在不在指定集合中。
select()成功时返回三个集合中I/O就绪个数,失败时返回-1,应设置errno;
select示例程序
#include <bits/types/struct_timeval.h>
#include <stdio.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#define TIMEOUT 5
#define BUF_LEN 1024
int main(void){
struct timeval tv;
fd_set readfds;
int ret;
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds);
tv.tv_sec = TIMEOUT;
tv.tv_usec = 0;
ret = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv);
if(ret == -1) {
perror("select");
return 1;
}else if(!ret){
printf("timeout %d",TIMEOUT);
}
if(FD_ISSET(STDIN_FILENO, &readfds)){
char buf[BUF_LEN+1];
int len;
len = read(STDIN_FILENO, buf, BUF_LEN);
if(len == -1){
perror("read");
return 1;
}
if(len){
buf[len] = '\0';
printf("read:%s\n",buf);
}
return 0;
}
return 0;
}
poll()系统调用
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
poll使用nfds个pollfd结构体构成的数组,fds指向该数组。
结构体定义如下:
#include <sys/poll.h>
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events to watch */
short revents; /* returned events witnessed */
};
每个pollfd结构体指定单一的文件描述符。可以传递多个结构体,让poll监视多个文件描述符。
poll示例程序
#include <stdio.h>
#include <unistd.h>
#include <sys/poll.h>
#define TIMEOUT 5 /* poll timeout, in seconds */
int main (void){
struct pollfd fds[2];
int ret;
fds[0].fd = STDIN_FILENO;
fds[0].events = POLLIN;
fds[1].fd = STDOUT_FILENO;
fds[1].events = POLLOUT;
ret = poll (fds, 2, TIMEOUT * 1000);
if (ret == -1) {
perror ("poll");
return 1;
}
if (!ret) {
printf ("%d seconds elapsed.\n", TIMEOUT);
return 0;
}
if (fds[0].revents & POLLIN)
printf ("stdin is readable\n");
if (fds[1].revents & POLLOUT)
printf ("stdout is writable\n");
return 0;
}