Linux高级编程--05.文件读写

缓冲I/O和非缓冲I/O

文件读写主要牵涉到了如下五个操作:打开、关闭、读、写、定位。在Linux系统中,提供了两套API,

  • 一套是C标准API:fopen、fclose、fread、fwrite、fseek,
  • 另一套则是POSIX定义的系统API:open、close、read、write、seek。

其中POSIX定义的API是系统API,而C标准API是基于系统API的封装,并且提供了额外的缓冲的功能。因此也可以把它们叫做缓冲I/O函数和非缓冲I/O函数。

除了前面介绍的这几个缓冲IO函数外,C标准库里面还提供了一系列封装的IO函数:如puts、putchar、printf等。

为什么要有增加缓冲区这个功能呢?主要是因为IO操作时,操作系统要从用户态转换为内核态的,而这个转换过程相对来说比较慢,因此可以通过缓冲的形式减少转换到内核态的次数。

那么,缓冲IO函数又是如何工作的呢?

  • 当用fopen打开文件时,除了分配文件句柄外,还额外申请了一个缓冲区。
  • 读文件时,会首先读到缓冲区中,然后返回用户需要的部分,多余的部分仍然放在缓冲区,下次再读的时候可以直接从缓冲区中返回。
  • 写文件时,会先写到缓冲区中,等缓冲区满后再统一写到文件中。

那么,我们该如何选择哪一组I/O函数呢?

  • 非缓冲I/O函数每次读写都要进内核,调一个系统调用比调一个用户空间的函数要慢很多,所以在用户空间开辟I/O缓冲区还是必要的。
  • 用缓冲I/O库函数要时刻注意I/O缓冲区和实际文件有可能不一致,在必要时需调用fflush()。

I/O函数也用于读写设备,比如终端或网络设备。此时通常需要更快的响应,一般不使用缓冲I/O函数。

PS:严格来讲,就算是POSIX的I/O函数,仍然是有内核I/O缓冲的,所以write也不一定是直接写到文件的,也可能写到内核I/O缓冲区中,至于究竟写到了文件中还是内核缓冲区中对于进程来说是没有太大差别的,我们不用太关注这一点。

阻塞I/O和非阻塞I/O

文件读写通常有阻塞和非阻塞两种方式,其中阻塞方式是我们比较常见的一种方式,此时函数会阻塞至操作完成。例如,对于如下一个等待用户输入字符串,并在屏幕上输出的例子:

#include <unistd.h>
#include <stdlib.h>

int main(void)
{
    char buf[10];
    int n = read(STDIN_FILENO, buf, 10);
    write(STDOUT_FILENO, buf, n);
    return 0;
}

执行该函数时,read函数会一直阻塞到在屏幕上输入数据并回车(此时STDIN有数据可用)为止。

阻塞IO有一个很大的问题是:无法实现并发。当同时进行多个IO操作的时候,前面的文件数据不可用的时候(往往是Socket之类的IPC操作),后面的IO操作无法执行。

非阻塞IO则可以很好的解决这个问题,要使用非阻塞IO操作,需要在open的时候制定O_NONBLOCK标志。这样,如果设备暂时没有数据可读就返回-1,调用者应该试着再读一次(again)。这种行为方式称为轮询(Poll),调用者只是查询一下,而不是阻塞在这里死等,这样可以同时监视多个设备:

#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>

int main(void)
{
    char buf[10];
    int fd, n;

    fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);

    while (1)
    {
        n = read(fd, buf, 10);
        if (n >= 0)
            break;

        sleep(1);
    }

    write(STDOUT_FILENO, buf, n);
    close(fd);
    return 0;
}

PS:为了示例函数简单,我这里没有考虑异常情况(如open失败)的处理,而这些是在实际项目中是必不可少的。

非阻塞I/O有一个缺点,如果所有设备都一直没有数据到达,调用者则需要反复查询,这样会一直占着cpu不放。因此,在使用非阻塞I/O时,通常不会在一个while循环中一直不停地查询(这称为Tight Loop),而是每延迟等待一会儿来查询一下,以免做太多无用功,在延迟等待的时候可以调度其它进程执行。

但是,这样又引入了一个新的问题,可能导致数据读取的不够及时,就拿我前面的例子来说,我在每次循环的时候Sleep了一秒。如果刚开始Sleep的时候数据可用,但此时却无法立即响应,需要到Sleep结束后钟才能输出结果。

要解圆满解决这个问题,则需要用到select函数,它可以阻塞地同时监视多个设备,还可以设定阻塞等待的超时时间,由于select多见于socket编程场景,这里不大好举例,后续如果会介绍socket编程的时候再详细介绍它,要了解它的工作原理可以看一下这篇文章select,多路同步I/O模型。

来自为知笔记(Wiz)

时间: 2024-08-08 18:41:13

Linux高级编程--05.文件读写的相关文章

Linux环境编程之文件I/O(三):文件的读写

(一) 当我们打开了一个文件后,一般对文件的操作就是读写.读写函数分别是read.write. #include <unistd.h> ssize_t read(int fd, void *buf, size_t count); 参数: fd:利用open.creat得到的文件描述符. buf:buf是void *类型,用于表示通用指针,此处指所读取到的数据的内存缓冲. count:需要读取的数据量. 返回值:成功执行时,返回所读取的数据的字节数,一般等于或小于所请求读取的数据字节数.若已到文

Linux 内核编程 or 内核模块编程的文件读写与信号传输问题

Linux内核编程时,内核代码执行只能直接访问内存上的数据,硬盘上的文件系统必须通过间接的方式才能被内核读写.一般内核操作文件读写的方式有三种:1.通过/proc/文件作为桥梁完成硬盘文件系统与内核的交互:2.通过ioctl方式实现交互:3.直接利用虚拟文件系统的函数vfs_read().vfs_write()读写文件.三种方式的具体实现方法网上有很多详细教程,可以参考.这里对三种方法做出比较. proc机制是一种很老的文件读写方式,通用性好,实现也算成熟,使用时需要自己实现内核上层的读写函数,

Linux环境编程之文件I/O(四):文件I/O的数据结构

(一) Linux系统支持不同进程间共享打开的文件.内核使用三种数据结构表示打开的文件:进程表项.文件表项.v节点表. 1.进程表项:每个进程在进程表中都有一个记录项,记录项中年包含有一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项.与每个文件描述符相关联的是: a.文件描述符标志 b.指向一个文件表项的指针 2.内核为所有打开文件维持一张文件表.每个文件表项包含: a.文件状态标志,如读写.添加.同步和非阻塞等. b.当前文件偏移量 c.指向该文件v节点表项的指针 3.每个打开的文

Linux环境编程之文件I/O(二):文件的打开与关闭

(一) Linux系统中,要对一个文件进行任何操作,必须首先获得它的文件描述符.而获得文件描述符的方式就是利用open/creat函数打开/创建该文件,open/creat函数返回文件描述符. #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char *pathname, int flags); int open(const char *pathname, in

Linux环境编程之文件I/O(一):文件描述符

(一) 首先,对于内核来讲,它是利用"文件描述符"来访问文件的.文件描述符一般是一个非负的整数.当我们用open打开已有的文件或者用creat创建新的文件时,都会返回一个文件描述符.有了文件描述符之后,我们就可以利用该文件描述进行文件的读写,即read.write系统调用都需要文件描述符fd(file descriptor)作为其参数.从以上描述可以看出,当我们想要用read.write等系统调用对文件进行读写等操作之前,必须用open或creat系统调用得到文件的描述符. 一般Uni

Linux系统编程_2_文件I/O

这里说的的文件I/O指的是不带缓存的: 1.关于带缓存的IO操作和不带缓存的IO操作,参见另一篇文章: 带缓存IO和不带缓存IO详解 Linux中的文件IO(不带缓存的IO)通常用的有以下几个: open    read    write    lseek   close 2.open的参数 int fd(filename, mode); mode 通常可以是O_RDONLY(只读)  O_WRONLY(只写)  O_RDWR(读写) 必须指定且只能指定一个! 其他的可供选择,可通过或运算添加:

Linux环境编程之文件I/O(六):文件属性

引言: 在Linux中使用ls -l filename命令查看filename的属性时,会列出文件的9种属性,例如:ls -l /etc/fstab -rw-r--r-- 1 root root 1102 2013-10-12 02:33 /etc/fstab 从左到右分别是类型与权限.文件个数.该文件或目录的拥有者.所属的组.文件大小.创建时间.文件名 以上这些文件属性的信息,都存放在一个stat的结构体中.下面就来分析一下这个结构体. 要想查看一个文件的stat结构体,可以通过stat类函数

Linux环境编程之文件I/O(七):目录文件及操作

什么是数据结构? 数据结构是相互之间存在一种或多种特定关系的数据元素的集合. 还有一些概念(数据.数据元素.数据项.数据对象.数据类型...) 传统上,我们把数据结构分为逻辑结构和物理结构. 逻辑结构:是指数据对象中数据元素之间的相互关系,也是我们今后最需要关注和讨论的问题. 物理结构:是指数据的逻辑结构在计算机中的存储形式. 逻辑结构分为以下四种: 1.集合:集合结构中的数据元素除了同属于一个集合外,之间没有任何关系. 2.线性结构:元素之间一对一. 3.树形结构:一对多. 4.图形结构:多对

Linux环境编程之文件I/O(五):fcntl函数

引言: 对于一个普通的文件,我们可以想到的对它的操作有,读取文件的内容.写数据到文件中,这些都是前面提到的read.write函数的作用.除此之外,还可以获取文件的其他性质,并对这些性质进行修改,比如文件的描述符.文件描述符标记.文件状态标志等等.这些对文件性质的修改就由fcntl函数完成. 函数介绍: #include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd, ... /* arg */ ); 参数: fd: