Linux遵循一切皆是文件的理念,因此,很多的交互工作都是通过读取和写入文件来完成。
文件必须被打开才能被访问。文件可以以只读方式或者只写方式打开,或者两者兼有。一个打开的文件通过唯一的文件描述符进行引用,该描述符是打开文件的元数据至其本身的映射。在Linux内核中,这个描述符,用一个整数表示 (int) ,简写为fd。文件描述符在用户空间中共享,允许用户程序用文件描述符直接访问文件。
#include
#include
#include
//调用成功返回文件描述符,失败返回-1
int open (const char* name, int flags);
int open (const char* name, int flags, mode_t mode);
flags参数必须是一下之一:
参数 | 含义 |
---|---|
O_RDONLY | 只读 |
O_WRONLY | 只写 |
O_RDWR | 读写模式 |
flags参数可以和以下一个或多个值进行按位或运算,用以修改打开文件请求的行为。
参数 | 含义 |
---|---|
O_APPEND | 追加模式打开,文件位置指针将被置于文件末尾 |
O_ASYNC | 当指定文件可写或者可读时产生一个信号(默认SIGIO) |
O_CREAT | 当指定的name文件不存在时,将由内核来创建 |
O_DIRECT | 打开文件用于直接I/O |
O_DIRECTORY | 如果name不是目录,open()调用将会失败,这个标志用于opendir()内部使用 |
O_EXCL | 和O_CREAT一起给出的时候,如果name给定的文件已经存在,则open()调用失败,用来防止文件创建时出现竞争 |
O_LARGEFILE | 给定文件打开时使用64位偏移量,这样大于2G的文件也能被打开 |
O_NOFOLLOW | 如果name是一个符号链接,open()调用会失败 |
O_NONBLOCK | 如果可以,文件将在非堵塞模式下打开 |
O_SYNC | 打开文件用于同步IO |
O_TRUNC | 如果文件存在,且为普通文件,并允许写,将文件的长度截断为0 |
以打开 /etc/services 文件为例
#include
#include
#include
#include
using namespace std;
int main()
{
int fd;
fd = open("/etc/services",O_RDONLY);
if(fd == -1)
{
cerr << "open() failed";
return 0;
}
else
{
cout << "open success" << endl;
cout << "fd = " << fd << endl;
}
close(fd);
return 0;
}
ssize_t 其实是有符号整数类型
size_t 是无符号整数类型
#include
ssize_t read (int fd, void* buf, size_t len);
2.2.1.功能:
该系统调用从有fd指向的文件的当前偏移量至多读len个字节到buf中。成功返回写入buf中的字节数。出错则返回-1,并设置errno
2.2.2.返回结果情况:
1. 调用返回一个等于len的值。
所有len个被读取字节存储在buf中。结果和预期一致。
以 读取 /etc/services 文件为例,读取文件中20个字节
#include
#include
#include
#include
#include
using namespace std;
int main()
{
int fd;
fd = open("/etc/services",O_RDONLY); //以只读方式打开文件
if(fd == -1) //文件打开失败,并提示错误
{
cerr << " open failed ";
return 0;
}
char* buf;
ssize_t length;
length = read(fd, buf, 20);
if(length == -1) //读文件失败
{
cerr << "read failed" << endl;
close(fd);
return 0;
}
else
cout << "读取字节数 = " << length << endl; //读文件成功
close(fd);
return 0;
}
2.调用返回一个大于0,但是小于len的值。
测试样例 : 测试文件数据本身少于指定读取字节数的情况。文件本身有abcdef,而指定读取20字节数据。
sample.txt 文件内容
abcdef
#include
#include
#include
#include
#include
using namespace std;
int main()
{
int fd;
fd = open("sample.txt",O_RDONLY); //以只读方式打开文件
if(fd == -1) //文件打开失败,并提示错误
{
cerr << " open failed ";
return 0;
}
char* buf;
ssize_t length;
length = read(fd, buf, 20);
if(length == -1) //读文件失败
{
cerr << "read failed" << endl;
close(fd);
return 0;
}
else
cout << "读取字节数 = " << length << endl; //读文件成功
close(fd);
return 0;
}
效果图:
文件内容为abcdef ,6字节的数据,为什么读取字节数为7?
内容也正确,是6个字节。为什么显示7? 难道是最后的 ‘\0’ 占了一个字节吗?
3.调用返回0 这标志着EOF。没有可以读入的数据。
测试:
读一个空文件。
代码: a.txt 为空文件
#include
#include
#include
#include
#include
using namespace std;
int main()
{
int fd;
fd = open("a.txt",O_RDONLY);
if(fd == -1)
{
cerr << " open failed ";
return 0;
}
char* buf;
ssize_t length;
length = read(fd, buf, 20);
if(length == -1)
{
cerr << "read failed" << endl;
close(fd);
return 0;
}
else
cout << "读取字节数 = " << length << endl;
cout << "内容 " << buf << endl;
close(fd);
return 0;
}
#include
#include
#include
#include
#include
#include
using namespace std;
int main()
{
int fd;
fd = open("/etc/services",O_RDONLY);
if(fd == -1)
{
cerr << " open failed ";
return 0;
}
char* buf;
ssize_t ret;
int len = 10240; //指明读取的大小
while(len != 0 && (ret = read(fd, buf, len) != 0))
{
if(ret == -1)
{
if(errno == EINTR)
continue;
cerr << "read failed" << endl;
break;
}
else
{
len -= ret;
buf += ret;
}
}
close(fd);
cout << "len=" << len << endl; //测试程序是否真正的读好几次。
return 0;
}
一个write() 调用从由文件描述符fd引用文件的当前位置开始,将buf中至多count个字节写入文件中。不支持定位的文件(像字符设备)总是从“开头”开始写。
成功时,返回写入字节数,并更新文件位置。错误时,返回-1,并将errno设置为相应的值。
#include
ssize_t write(int fd, const void* buf, size_t count);
测试: 从 /etc/services 文件中读取100字节,并把这100字节写入到a.txt 文件中。
text 1.不以追加模式写
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main()
{
int fd;
fd = open("/etc/services", O_RDONLY);
if(fd == -1)
{
cerr << "open()" << endl;
return 0;
}
char* buf;
int len = read(fd,buf,100);
if(len == -1)
{
cerr << "read()" << endl;
close(fd);
return 0;
}
int fd1;
fd1 = open("a.txt",O_WRONLY);
if(fd1 == -1)
{
cerr <<" open(a.txt)" << endl;
close(fd);
return 0;
}
int count = write(fd1,buf,len);
if(count == -1)
{
perror("write()");
close(fd);
return 0;
}
else if(count != len)
{
cerr << "write() 写入字数不对";
close(fd);
close(fd1);
return 0;
}
cout << "ok" << endl;
return 0;
}
text2.追加模式进行写,也就是以追加模式打开文件
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main()
{
const char* buf = "abcdefghijklmnopqrstuvwxyz";
int fd1;
fd1 = open("a.txt",O_WRONLY |O_APPEND);
if(fd1 == -1)
{
cerr <<" open(a.txt)" << endl;
return 0;
}
int count = write(fd1,buf,strlen(buf));
if(count == -1)
{
perror("write()");
close(fd1);
return 0;
}
else if(count != strlen(buf))
{
cerr << "write() 写入字数不对";
close(fd1);
return 0;
}
cout << "ok" << endl;
return 0;
}
当一个write()调用返回时,内核已将所提供的缓冲区数据复制到了内核缓冲区中。但没有保证数据已写到了目的文件。你可以强制文件缓存写回,甚至可以将所有的写操作同步。
最简单的确认数据写入磁盘的方法是使用fsync()系统调用,
#include
int fsync(int fd);
调用fsync()可以保证fd对应文件的脏数据回写到磁盘上。
该调用回写数据以及建立的时间戳和inode中的其它属性等元数据。在驱动器确认数据已经全部写入之前不会返回。
#include
int fdatasync(int fd);
这个系统调用完成的事情和fsync()一样,区别在于它仅写入数据。不保证元数据同步到磁盘上。故此可能快一些。一般来说这就够用了。
返回值情况
成功时,两个调用都返回0.失败时,都返回-1.并将errno设置为一下三个值之一:
名称 | 含义 |
---|---|
EBADF | 给定的文件描述符不是一个可以写入的合法描述符 |
EINVAL | 给定的文件描述符对应的对象不支持同步 |
EIO | 在同步时发生了一个底层I/O错误 |
sync()系统调用可以用来对磁盘上所有的缓冲区进行同步。尽管效率不高,但仍然被广泛使用。
#include
void sync();
该函数没有参数,也没有返回值。它总是成功返回,并确保所有的缓冲区(包括数据和元数据)都可以写入磁盘。
程序完成对某个文件的操作后,可以使用close()系统调用将文件描述符和对应的文件解除关联。
#include
//调用成功返回0.错误返回-1,并设置errno为相应值
int close(int fd);
lseek()系统调用能够对给定文件描述符引用的文件位置设置指定值。除了更新文件位置,没有其它的行为,并无论如何不初始化任何I/O
#include
#include
off_t lseek(int fd, off_t pos, int origin);
lseek() 的行为依赖于初始参数,可以为一下值之一:
名称 | 含义 |
---|---|
SEEK_CUR | 当前文件位置fd设置为当前值加上pos,pos可以为负值,零或正值 |
SEEK_END | 当前文件位置fd设置为当前文件长度加上pos,pos可以为负值,零或正值 |
SEEK_SET | 当前文件位置fd设置为pos。 |
调用成功时返回新文件位置。错误时返回-1,并设置适当的errno值。
Linux提供了两种read()和write()的变体来代替lseek(),每个调用都以需要读写的文件位置为参数。完成时,不修改文件位置。
#define _XOPEN_SOURCE 500
#include
ssize_t pread(int fd, void* buf, size_t count, off_t pos);
ssize_t pwrite(int fd,const void* buf,size_t count, off_t pos);
错误码
成功时,两个调用返回读或写的字节数。出错时,二者均返回-1
示例文件 a.txt,内容为
abcdefghigklmnopqrstuvwxyz
ABCDEFGHIGKLMNOPQRSTUVWXYZ
1.测试pread
距文件开始位置5的位置开始读26个字节
#include
#include
#include
#include
#include
using namespace std;
int main()
{
int fd;
fd = open("a.txt",O_RDONLY);
if(fd == -1)
{
cerr << " open failed ";
return 0;
}
char* buf;
ssize_t length;
length = pread(fd, buf, 26,5);
if(length == -1)
{
cerr << "pread failed" << endl;
return 0;
}
else
cout << "读取字节数 = " << length << endl;
cout << buf << endl;
return 0;
}
2.测试pwrite()
从距文件开始位置的26处,写入-Hello,Word-
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main()
{
const char* buf = "-Hello,Word-";
int fd1;
fd1 = open("a.txt",O_WRONLY);
if(fd1 == -1)
{
cerr <<" open(a.txt)" << endl;
return 0;
}
int count = pwrite(fd1,buf,strlen(buf),26);
if(count == -1)
{
perror("write()");
return 0;
}
else if(count != strlen(buf))
{
cerr << "write() 写入字数不对";
close(fd1);
return 0;
}
cout << "ok" << endl;
return 0;
}
效果图:
如果原来位置上有数据,那么写入的数据会把原来的数据覆盖掉
#include
int ftruncate (int fd, off_t len);
#include
#include
int truncate (const char* path, off_t len);
两个系统调用都将文件截短到len指定的长度。
二者都在成功时返回0,错误时返回-1,并设置errno为相应值。
I/O多路复用允许应用在多个文件描述符上同时堵塞,并在其中某个可以读写时收到通知。
I/O多路复用的设计遵循以下原则:
#include
#include
#include
//成功返回时,每个集合只包含对应类型的I/O就绪的文件描述符
// 参数n,等于所有集合中文件描述符的最大值加1.
int select (int n, fd_set* readfds, fd_set* writefds,fd_set* exceptfds,
struct timeval* timeout);
监测的文件描述符 | 功能 |
---|---|
监测 readfds 集合中的文件描述符 | 确认其中是否有可读数据 |
监测 writefds 集合中的文件描述符 | 确认其中是否有一个写操作可以不阻塞地完成 |
监测 exceptefds 中的文件描述符 | 确认其中是否出现异常发生或者出现带外数据 |
指定集合为NULL | select对此不监听 |
#include
struct timeval{
long tv_sec; //秒
long tv_usec; //微秒
};
如果这个参数不是null,即使此时没有文件描述符处于I/O就绪状态,select()调用也将在tv_sec秒tv_usec微秒后返回。
集合中的文件描述符
集合中的文件描述符是通过辅助宏来进行管理。
FD_CLR(int fd, fd_set* set); //从指定集合中移除一个文件描述符
FD_ISSET(int fd, fd_set* set); // 判断fd是否在set集合中,
FD_SET(int fd, fd_set* set); //向指定集合中添加一个文件描述符
FD_ZERO(fd_set* set); //从指定集合set中移除所有文件描述符
等待stdin的输入的阻塞时限设置为5秒钟。由于只监测了一个文件描述符,实际上不上I/O多路复用。
#include
#include
#include
#include
using namespace std;
#define TIMEOUT 5 //等待5秒
#define BUF_SIZE 1024
int main()
{
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)
{
cerr << "select()" << endl;
return 1;
}
else if(!ret)
{
cout << TIMEOUT << 's' << endl;
return 0;
}
if(FD_ISSET(STDIN_FILENO, &readfds))
{
char buf[BUF_SIZE + 1];
int len;
len = read(STDIN_FILENO, buf,BUF_SIZE);
if(len == -1)
{
cerr << "read()" << endl;
return 1;
}
if(len)
{
buf[len] ='\0';
cout << buf << endl;
}
return 0;
}
cerr << "this should not happen!" << endl;
return 0;
}
效果图
会等待5秒,然后才输出结果