文章目录
- 一、I/O系统调用
- 1.open() 打开文件
- 1.1 所需基础知识
- 1.2. open() 详解
- 1.3 示例代码
- 2.read() 读取文件
- 2.1.基础知识
- 2.2.read() 详解
- 2.3. 读入所有字节
- 3.write() 写文件
- 3.1. 基础背景知识
- 3.2.write() 详解
- 3.3.示例代码
- 3.4.注意点
- 3.4.1.同步IO
- 1. fsync() 和fdatasync()
- 2.sync()
- 4.close() 关闭文件
- 5.lseek() 查找
- 6.定位读写
- 6.1代码示例
- 7.截短文件
- 7.1 代码详解
- 8.I/O多路复用
- 8.1.基础知识
- 8.2.select() 实现同步I/O多路复用
一、I/O系统调用
1.open() 打开文件
1.1 所需基础知识
Linux遵循一切皆是文件的理念,因此,很多的交互工作都是通过读取和写入文件来完成。
文件必须被打开才能被访问。文件可以以只读方式或者只写方式打开,或者两者兼有。一个打开的文件通过唯一的文件描述符进行引用,该描述符是打开文件的元数据至其本身的映射。在Linux内核中,这个描述符,用一个整数表示 (int) ,简写为fd。文件描述符在用户空间中共享,允许用户程序用文件描述符直接访问文件。
1.2. open() 详解
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//调用成功返回文件描述符,失败返回-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 |
1.3 示例代码
以打开 /etc/services 文件为例
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
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;
}
效果图:
2.read() 读取文件
2.1.基础知识
ssize_t 其实是有符号整数类型
size_t 是无符号整数类型
2.2.read() 详解
#include <unistd.h>
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 <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
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 <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
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 <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
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;
}
效果图:
4.调用返回-1
2.3. 读入所有字节
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
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;
}
效果图:
3.write() 写文件
3.1. 基础背景知识
一个write() 调用从由文件描述符fd引用文件的当前位置开始,将buf中至多count个字节写入文件中。不支持定位的文件(像字符设备)总是从“开头”开始写。
成功时,返回写入字节数,并更新文件位置。错误时,返回-1,并将errno设置为相应的值。
3.2.write() 详解
#include <unistd.h>
ssize_t write(int fd, const void* buf, size_t count);
3.3.示例代码
测试: 从 /etc/services 文件中读取100字节,并把这100字节写入到a.txt 文件中。
text 1.不以追加模式写
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
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 <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
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;
}
效果图:
3.4.注意点
当一个write()调用返回时,内核已将所提供的缓冲区数据复制到了内核缓冲区中。但没有保证数据已写到了目的文件。你可以强制文件缓存写回,甚至可以将所有的写操作同步。
3.4.1.同步IO
1. fsync() 和fdatasync()
最简单的确认数据写入磁盘的方法是使用fsync()系统调用,
#include <unistd.h>
int fsync(int fd);
调用fsync()可以保证fd对应文件的脏数据回写到磁盘上。
该调用回写数据以及建立的时间戳和inode中的其它属性等元数据。在驱动器确认数据已经全部写入之前不会返回。
#include <unistd.h>
int fdatasync(int fd);
这个系统调用完成的事情和fsync()一样,区别在于它仅写入数据。不保证元数据同步到磁盘上。故此可能快一些。一般来说这就够用了。
返回值情况
成功时,两个调用都返回0.失败时,都返回-1.并将errno设置为一下三个值之一:
名称 | 含义 |
---|---|
EBADF | 给定的文件描述符不是一个可以写入的合法描述符 |
EINVAL | 给定的文件描述符对应的对象不支持同步 |
EIO | 在同步时发生了一个底层I/O错误 |
2.sync()
sync()系统调用可以用来对磁盘上所有的缓冲区进行同步。尽管效率不高,但仍然被广泛使用。
#include <unistd.h>
void sync();
该函数没有参数,也没有返回值。它总是成功返回,并确保所有的缓冲区(包括数据和元数据)都可以写入磁盘。
4.close() 关闭文件
程序完成对某个文件的操作后,可以使用close()系统调用将文件描述符和对应的文件解除关联。
#include <unistd.h>
//调用成功返回0.错误返回-1,并设置errno为相应值
int close(int fd);
5.lseek() 查找
lseek()系统调用能够对给定文件描述符引用的文件位置设置指定值。除了更新文件位置,没有其它的行为,并无论如何不初始化任何I/O
#include <sys/types.h>
#include <unistd.h>
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值。
6.定位读写
Linux提供了两种read()和write()的变体来代替lseek(),每个调用都以需要读写的文件位置为参数。完成时,不修改文件位置。
#define _XOPEN_SOURCE 500
#include <unistd.h>
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
6.1代码示例
示例文件 a.txt,内容为
abcdefghigklmnopqrstuvwxyz
ABCDEFGHIGKLMNOPQRSTUVWXYZ
1.测试pread
距文件开始位置5的位置开始读26个字节
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
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 <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
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;
}
效果图:
如果原来位置上有数据,那么写入的数据会把原来的数据覆盖掉
7.截短文件
7.1 代码详解
#include <sys/types.h>
int ftruncate (int fd, off_t len);
#include <unistd.h>
#include <sys/types.h>
int truncate (const char* path, off_t len);
两个系统调用都将文件截短到len指定的长度。
二者都在成功时返回0,错误时返回-1,并设置errno为相应值。
8.I/O多路复用
8.1.基础知识
I/O多路复用允许应用在多个文件描述符上同时堵塞,并在其中某个可以读写时收到通知。
I/O多路复用的设计遵循以下原则:
- 当任何文件描述符准备好I/O时通知我
- 在一个或更多文件描述符就绪前始终处于睡眠状态
- 唤醒:哪个准备好了?
- 在不阻塞的情况下处理所有I/O就绪的文件描述符
- 返回第一步,重新开始
8.2.select() 实现同步I/O多路复用
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select (int n, fd_set* readfds, fd_set* writefds,fd_set* exceptfds,
struct timeval* timeout);
FD_CLR(int fd, fd_set* set);
FD_ISSET(int fd, fd_set* set);
FD_SET(int fd, fd_set* set);
FD_ZERO(fd_set* set);
监测的文件描述符 | 功能 |
---|---|
监测 readfds 集合中的文件描述符 | 确认其中是否有可读数据 |
监测 writefds 集合中的文件描述符 | 确认其中是否有一个写操作可以不阻塞地完成 |
监测 exceptefds 中的文件描述符 | 确认其中是否出现异常发生或者出现带外数据 |
指定集合为NULL | select对此不监听 |