Linux IO操作——RIO包

1.linux基本I/O接口介绍

ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, void *buf, size_t count);

以上两个是linux下的两个系统调用,用于对文件行基本的I/O操作。fd是非负文件描述符,其实相当于标识一个文件的唯一编号。默认标号0是标准输入(终端输入),1是标准输出(终端输出),2是标准错误。所以用户通过 open 能够打开的文件得到的文件描述符的最小编号是3。

在Linux中,read 和 write 是基本的系统级I/O函数。当用户进程使用read 和 write 读写linux的文件时,进程会从用户态进入内核态,通过I/O操作读取文件中的数据。内核态(内核模式)和用户态(用户模式)是linux的一种机制,用于限制应用可以执行的指令和可访问的地址空间,这通过设置某个控制寄存器的位来实现。进程处于用户模式下,它不允许发起I/O操作,所以它必须通过系统调用进入内核模式才能对文件进行读取。

从用户模式切换到内核模式,主要的开销是处理器要将返回地址(当前指令的下一条指令地址)和额外的处理器状态(寄存器)压入到栈中,这些数据到会被压到内核栈而不是用户栈。另外,一个进程使用系统调用还隐含了一点——调用系统调用的进程可能会被抢占。当内核代表用户执行系统调用时,若该系统调用被阻塞,该进程就会进入休眠,然后由内核选择一个就绪状态,当前优先级最高的进程运行。另外,即使系统调用没有被阻塞,当系统调用结束,从内核态返回时,若在系统调用期间出现了一个优先级更高的进程,则该进程会抢占使用了系统调用的进程。内核态返回会返回到优先级高的进程,而不是原本的进程。

虽然我们可以每次进行读写时都使用系统调用,但这样会增大系统的负担。当一个进程需要频繁调用 read 从文件中读取数据时,它便要频繁地在用户态与内核态之间进行切换,极端点地设想一个情景,每次read调用都只读取一个字节,然后循环调用read读取n个字节,这便意味着进程要在用户态和内核态之间切换n次,虽然这是一个及其愚蠢的编程方法,但能够毫无疑问说明系统调用的开销。下图是调用read(int fd, void *buf, size_t count)读取516,581,760字节,每次read可以读取的最大字节数量(count的值)的不同对CPU的存取效率的影响。

Linux IO操作——RIO包_第1张图片

这张表的运行结果是基于块大小为4096-byte的ext4文件系统上的,所以可以看到当 BUFFSIZE=4096时,System CPU 几乎达到了最小值,之后块大小若继续增加,System CPU时间减小的幅度很小,甚至还有所增加。这是若 BUFFSIZE 过大,其缓冲区便跨越了不同的块,导致存取效率降低。

2.RIO包

RIO,全称 Robust I/O,即健壮的IO包。它提供了与系统I/O类似的函数接口,在读取操作时,RIO包加入了读缓冲区,一定程度上增加了程序的读取效率。另外,带缓冲的输入函数是线程安全的,这与Stevens的 UNP 3rd Edition(中文版) P74 中介绍的那个输入函数不同。UNP的那个版本的带缓冲的输入函数的缓冲区是以静态全局变量存在,所以对于多线程来说是不可重入的。RIO包中有专门的数据结构为每一个文件描述符都分配了相应的独立的读缓冲区,这样不同线程对不同文件描述符的读访问也就不会出现并发问题(然而若多线程同时读同一个文件描述符则有可能发生并发访问问题,需要利用锁机制封锁临界区)。

另外,RIO还帮助我们处理了可修复的错误类型:EINTR。考虑readwrite在阻塞时被某个信号中断,在中断前它们还未读取/写入任何字节,则这两个系统调用便会返回-1表示错误,并将errno置为EINTR。这个错误是可以修复的,并且应该是对用户透明的,用户无需在意read 和 write有没

你可能感兴趣的:(unix网络编程,linux,io,标准)