1、linux提供了一些高级I/O函数,在特定的情况下表现出优秀的性能。这些函数大致分为3类
(1)用于创建文件描述符的函数,包括pipe、dup/dup2函数
(2)用于读写数据的函数,包括readv/writev、sendfile、mmap/munmap、splice和tee函数
(3)用于控制I/O行为和属性的函数,包括fcntl函数。
2、pipe函数(intpipe(int fd[2]))
(1)pipe函数的参数是一个包含两个int型整数的数组指针,函数成功时返回0,并将一对打开的文件描述符值填入其参数指向的数组。
(2)fd[0]表示进程可以从管道的fd[0]端读数据,fd[1]表示进程可以写数据进fd[1]端。
(3)若是这一对文件描述符都是阻塞的,那么假如管道为空,read调用将被阻塞直到有数据可以读。假如管道满了,调用write往管道写数据会阻塞直到管道有空余空间。
(4)若是这一对文件描述符是非阻塞的,那么假如管道的读端文件描述符fd[0]减少至0,即没有任何进程需要从管道读数据,这对该管道的write操作将失败,并触发SIGPIPE信号。假如管道的写端fd[1]文件描述符减少至0,即没有进程往管道写数据,则对该管道的read操作将返回0,即读取到了文件结束标记。
(5)管道内部传输的也是字节流,但是和TCP的字节流还是有细微的区别,应用程序能往一个TCP连接中写入多少字节的数据取决于对端的接收窗口和本端的拥塞窗口。而管道本身有一个固定的容量限制,一般为65536字节,也可以通过fcntl函数来修改。
(6)socket有个 socketpair函数,能够很方便的创建双向管道。并且创建的一对文件描述符都输即可读又可写。函数原型为:
int socketpair(int domain, inttype, int protocol, int fd[2])。注意domain只能为UNIX本地域的AF_UNIX,因为只能在本地使用双向管道。
3、dup函数和dup2函数
(1)当希望把标准输入重定向到一个文件,或者把标准输出重定向到一个网络连接,可以通过复制文件描述符的duo或dup2函数来实现。
int dup(int file_descriptor)
int dup2(intfile_descriptor_one, int file_descriptor_two)
(2)dup函数是创建一个新的文件描述符,和原有的文件描述符file_descriptor指向相同的文件、管道或者网络连接。并且dup返回的文件描述符总是取系统当前可用的最小整数值。dup2和dup函数类似,但是返回的是不小于file_descriptor_two的整数值。
(3)注意:使用dup和dup2并不继承原有文件描述符的属性,例如close-on-exec以及non-blocking。
4、readv和writev函数
(1)readv函数将数据从文件描述符读到分散的内存中,即分散读,writev函数则将多块分散的内存数据一并读入到文件描述符当中,即集中写。函数原型如下:
ssize_t readv(int fd,const struct iovec* vector,int count);
ssize_t writev(int fd, const struct iovec* vector,int count);
iovec结构成员有内存的初始地址和该内存的大小。count表示vector数组的长度,既有多少个iovec结构体类型数据。表示的是有多少块内存数据需要从fd读出或写到fd。两个函数调用成功返回读出/写入fd的字节数。失败则返回-1并设置errno。相当于简化版的recvmsg和sendmsg。
4、sendfile函数
ssize_t sendfile(int out_fd, int in_fd, off_t*offset,size_t count)
(1)in_fd参数是待读出内容的文件描述符,此文件描述符必须指向真实的文件(支持mmap函数),也就是说不能是socket和管道。Out_fd是待写入内容的文件描述符,此文件描述符必须是socket。因此此函数几乎是专门为在网络上传输文件而设计的。整体含义是in_fd表示从文件读取数据,out_fd表示往socket当中写。
(2)sendfile函数在两个文件描述符之间传递数据,但是其完全在内核中操作,从而避免了内核缓冲区和用户空间缓冲区之间的数据拷贝,效率很高,这被称为零拷贝。
(3)offset参数指定从读入文件描述符的哪个位置开始读,如果为空则使用读入文件流默认的起始位置。函数成功返回传输的字节数,失败返回-1并设置errno。
5、mmap函数和munmap函数
(1)mmap函数用于申请一段内存空间,可以将这段内存作为进程间通信的共享内存,也可以将文件直接映射到其中。munmap函数释放由mmap创建的内存空间。
void *mmap(void* start, size_t length, int prot,int flags, int fd,off_t offset)
int munmap(void* start,size_t length)
(2)start参数允许用户使某个待定的地址作为申请的内存的起始地址,若是被设置为NULL,则由系统自动分配一个地址。Length指定内存段的长度。prot参数用来设置内存段的访问权限。可以取以下几个值的按位或
PROT_READ,内存段可读
PROT_WRITE,内存段可写
PROT_EXEC,内存段可执行
PROT_NONE, 内存段不能被访问。
(3)flags参数控制内存段被修改后程序的行为(MAP_SHARED和MAP_PRIVATE互斥,不能同时指定)
(4)fd是被映射文件的文件描述符,一般通过open函数获得。Offset设置从文件的何处开始映射。
(5)mmap函数成功调用返回指向内存区域的指针,失败则返回MAP_FAILED((void*)-1)并设置errno。munmap函数成功返回0,失败则返回-1并设置errno。
6、splice函数
ssize_t splice(int fd_in, loff_t* off_in, intfd_out, loff_t* off_out, size_t len, unsigned int flags);
(1)整体含义是读文件描述符fd_in所对应的文件或socket或管道,然后写入文件描述符fd_out所对应的文件或socket或管道。并且,fd_in和fd_out必须至少有一个为管道文件描述符,当为管道文件描述符的时候,相应的off_in/off_out就必须为空(NULL),若不是管道文件描述符且off_in/off_out为NULL,表示从输入数据流的当前偏移位置读或者是写入输出数据流的当前偏移位置。len表示指定移动数据的长度。
(2)函数成功调用时返回移动字节的数量,可能返回0,返回0表示没有数据需要移动,这通常发生在从管道中读取数据(fd_in为管道文件描述符)而该管道没有写入任何数据时。函数调用失败则返回-1并设置errno。
7、tee函数
ssize_t tee(int fd_in, int fd_out, size_t len,unsigned int flags)
(1)tee函数在两个管道文件描述符之间复制数据,也是零拷贝操作并且不消耗数据,因此源文件描述符的数据仍然可以用于后续的读操作。
(2)注意,相应的两个文件描述符参数必须为管道文件描述符。成功调用则返回复制的数据大小。返回0表示没有复制任何数据,失败返回-1并设置errno。
8、fcntl函数
int fcntl(int fd, int cmd, …)
(1)fcntl函数提供了对文件描述符的各种控制操作(另一个ioctl函数比fcntl能够执行更多的控制)。
(2)cmd参数指定任何类型的操作,根据操作类型的不同,该函数可能还需要第三个可选参数arg。
(3)在网络编程中,fcntl函数通常用来将一个文件描述符设置为非阻塞的。见以下代码
int setnonblocking(int fd){
int old_option = fcntl(fd,F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl(fd,F_SETFL,new_option);
return old_option; //返回文件描述符旧的状态标志为了能够恢复该状态标标志
}
(4)这里重点讲两个比较特殊的信号,SIGIO和SIGURG信号,这两个信号必须与某个文件描述符相关联方可使用。当被关联的文件描述符可读或者可写时,系统将会触发SIGIO信号。当被关联的文件描述符(必须是一个socket)上有带外数据时,系统将触发SIGURG信号,将信号与文件描述符相关联的方法,就是使用fcntl函数为目标文件描述符指定宿主进程或进程组,那么被指定宿主进程或进程组将捕获这两个信号。使用SIGIO还得利用fcntl设置其O_ASYNC标志(异步I/O标志,但是SIGIO并非真正意义上的异步I/O模型)。