接上篇 linux IO —— 理解阻塞/非阻塞、同步/异步
linux中,比较常用的IO都是同步的,无论是否被设置为非阻塞IO,都会在一个数据拷贝的阶段等待。
这里列举一下常见的同步IO方式吧。
内核提供的同步IO
打开文件并同步读取
从磁盘读取到内核的高速缓存(若高速缓存中已经有了,直接读取),然后从内核读取到用户空间buffer,通过设置flag o_direct 可以跳过内核缓存直接读取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| * @brief 同步IO:通过linux系统调用 open & read 读取完整文件内容 * @return std::string */ std::string ReadBySyscallOpen(){ int fd = open("./test.txt", O_RDONLY); const int BUF_SIZE = 1024; char buf[BUF_SIZE]; int size = 0; std::string s; do{ size = read(fd, buf, BUF_SIZE); if(size <= 0) break; s.append(buf, buf + size);
} while (1); close(fd); return s; }
|
只读取数据,不移动指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| * @brief pread:只读取数据,不移动指针 * */ void PreadBySyscallOpen(){ int fd = open("./test.txt", O_RDONLY); const int BUF_SIZE = 10; char buf[BUF_SIZE]; int size = 0; for(int i=0;i<5;i++){ size = pread(fd, buf, BUF_SIZE, 0); if(size <= 0) break; printf("pread: %s\n", buf); } close(fd); }
|
读取内容到多个 buf 中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| * @brief readv:读取数据到多个buf中,一个填满了就下一个 * */ void ReadvBySyscallOpen(){ int fd = open("./test.txt", O_RDONLY); const int BUF_SIZE = 10; const int SEQ = 5; struct iovec iovecs[SEQ]; for(int i=0; i<SEQ; i++){ iovecs[i].iov_base = calloc(1, BUF_SIZE*(i+1)); iovecs[i].iov_len = BUF_SIZE*(i+1); } readv(fd, iovecs, SEQ); for(int i=0;i<SEQ;i++){ printf("readv: %s\n", iovecs[i].iov_base); } close(fd); }
|
读取内容到多个 buf 中,不移动指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| * @brief preadv:读取数据到多个buf中,一个填满了就下一个,不移动指针 * 这里有一点要注意的,同一次 preadv 读到的多个buf,还是内容顺序往前读的,只有多次调用 preadv 的时候,文件指针才是不变的 */ void PreadvBySyscallOpen(){ int fd = open("./test.txt", O_RDONLY); const int BUF_SIZE = 10; const int SEQ = 5; { struct iovec iovecs[SEQ]; for(int i=0; i<SEQ; i++){ iovecs[i].iov_base = calloc(1, BUF_SIZE*(i+1)); iovecs[i].iov_len = BUF_SIZE*(i+1); } preadv(fd, iovecs, SEQ, 0); for(int i=0;i<SEQ;i++){ printf("readv: %s\n", iovecs[i].iov_base); } } { struct iovec iovecs[SEQ]; for(int i=0; i<SEQ; i++){ iovecs[i].iov_base = calloc(1, BUF_SIZE*(i+1)); iovecs[i].iov_len = BUF_SIZE*(i+1); } preadv(fd, iovecs, SEQ, 0); for(int i=0;i<SEQ;i++){ printf("readv: %s\n", iovecs[i].iov_base); } } close(fd); }
|
glibc实现的带缓冲同步IO
与直接open、read相比,最重要的区别是,read每次调用都要在用户态→内核态中切换;而 fopen会在用户态创建一个缓冲区,一次性将一个默认大小(一般是一个内核高速缓存页的大小,如4096)的内容拷到用户态缓冲区,每次fgets的时候就不涉及切换了。
可以通过 setvbuf 修改这个缓冲区的大小。
std::string ReadByFOpen(){
FILE* fp= fopen("./test.txt", "r");
if(fp == nullptr) return "";
const int BUF_SIZE = 1024;
char buf[BUF_SIZE];
int size = 0;
std::string s;
while( fgets(buf, BUF_SIZE, fp) != NULL ) {
s.append(buf, buf+strlen(buf));
}
fclose(fp);
printf("%s\n", s.c_str());
return s;
}
glibc 还提供了一些其它的同步IO相关的函数,就不一一列举了。
练习代码
练习代码仓库:https://github.com/zouchengzhuo/linux-io-learn/tree/master/4.aio/sync_io
本文链接:https://www.zoucz.com/blog/2022/05/31/3dab26e0-e5b9-11ec-9fe7-534bbf9f369d/