Linux高性能服务器编程(7)高级I/O函数

Linux高新能服务器编程(7)高级I/O函数

在特定的条件下能表现出优秀的性能。这些函数大致分为三类:
1.用于创建文件描述符的函数,包括pipe、dup/dup2函数;
2.用于读写数据的函数,包括readv/writev、sendfile、mmap/munmap、splice和tee函数;
3.用于控制I/O行为和属性的函数,包括fcntl函数。

pipe函数

pipe函数可用于创建一个管道,以实现进程间通信,管道内部传输的数据是字节流(可用fcntl函数来修改管道容量)。pipe定义如下:

#include<unistd.h>
int pipe(int fd[2]);

pipe函数的参数是一个包含两个int型整数的数组指针。该函数成功时返回0,并将一对打开的文件描述符值填入其参数指向的数组。如果失败,则返回-1并设置errno。
通过pipe函数创建的这两个文件描述符fd[0]和fd[1]分别构成管道的两端,往fd[1]写入的数据可以从fd[0]读出。并且,fd[0]只能用于从管道读出数据,fa[1]则只能用于往管道写入数据,而且不能反过来使用。如果要实现双向的数据传输,就应该使用两个管道。默认情况下,这一对文件描述符都是阻塞的。此时如果我们用read系统调用来读取一个空的管道,则read将被阻塞,直到管道内有数据可读;如果用write系统调用来往一个慢的管道中写入数据,则write将被阻塞,直到管道内有足够多的空闲空间可用。

socket的基础API中有一个socketpair函数。它能方便地创建双向管道。其定义如下:

#include<sys/types.h>
#include<sys/socket.h>
int socketpair(int domain,int type,int protocol,int fd[2]);

前三个参数与socket系统调用的三个参数完全相同,但domian只能使用UNIX本地域协议族AF_UNIX,因为仅仅只能在本地使用这个双向管道。最后一个参数和pipe系统调用的参数一样,只不过socketpair创建的这对文件描述符都是既可读又可写的。成功返回0,失败返回-1并设置errno。

dup和dup2函数

有时我们希望将标志输入重定向到一个文件,或者把标准输出重定向到一个网络连接。这可以通过下面的用于复制文件描述符dup和dup2函数来实现:

#include<unistd.h>
int dup(int file_descriptor);
int dup2(int file_descriptor_one,int file_descriptor_two)

dup函数创建一个新的文件描述符,该新文件描述符和原有文件描述符int file_descriptor指向相同的文件、管道或者网络连接。并且dup返回的文件描述符总是取系统当前可用的最小整数值。二者类似,不过它将返回第一个不小于file_descriptor_two的整数值。dup和dup2系统调用失败时返回-1并设置errno。

readv和writev函数

readv函数将数据从文件描述符读到分散的内存块中,即分散读;writev函数则将多块分散的内存数据一并写入文件描述符中,即集中写。它们的定义如下:

#include<sys/uio.h>
ssize_t readv(int fd,const struct iovec* vector,int count);
ssize_t writev(int fd,const struct iovec* vector,int count);

fd参数是被操作的目标文件描述符。vector参数的类型是iovec结构数组。iovec结构体描述一块内存区。count参数是vector数组的长度,即有多少块内存数据需要从fd读出或写到fd。readv和writev在成功时返回读出/写入fd的字节数,失败则返回-1并设置errno。它们相当于简版的recvmsg和sendmsg函数。

sendfile函数

sendfile函数在两个文件描述符之间直接传递数据(完全在内核中操作),从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,这被称为零拷贝。sendfile定义如下:

#include<sys/sendfile.h>
ssize_t sendfile(int out_fd,int in_fd,off_t* offset,size_t count);

in_fd参数是待读出内容的文件描述符,out_fd参数是待写入内容的文件描述符。offset参数指定从读入文件流的哪个位置开始读,如果为空,则使用读入文件流默认的起始位置。const参数指定在文件描述符in_fd和out_fd之间传输的字节数。sendfile成功时返回传输的字节数,失败则返回-1并设置errno。in_fd必须是一个支持类似mmap函数的文件描述符,即它必须指向真实的文件,不能是socket和管道;而out_fd则必须是一个socket。

mmap和munmap函数

mmap函数用于申请一段内存空间。我们可以将这段内存作为进程间通信得共享内存,也可以将文件直接映射到其中。munmap函数则释放mmap创建得这段内存空间。它们定义如下:

#include<sys/nman.h>
void* mmap(void *start,size_t length,int prot,int flags,int fd,off_t offset);
int munmap(void *start,size_t length);

start参数允许用户使用某个特定的地址作为这段内存的起始地址。如果它被设置成NULL,则系统自动分配一个地址。length参数指定内存段的长度。port参数用来设置内存段的访问权限。它可以取以下几个值得按位或:
PROT_READ内存段可读
PROT_WRITE内存段可写
PROT_EXEC内存段可执行
PROT_NONE内存段不能被访问
flags参数控制内存段内容被修改后程序的行为。它可以被设置为下表中的某些值(仅列出常见的值)的按位或(其中MAP_SHARED和MAP_PRIVATE是互斥的,不能同时指定)

常用值含义
MAP_SHARED在进程共享这段内存。对该内存段的修改将反应到被映射的文件中。它提供了进程间共享内存的POSIX方法
MAP_PRIVATE内存段为调用进程所私有。对该内存段的修改不会反应到被映射的文件中
MAP_ANONYMOUS这段内存不是从文件映射而来的。其内容被初始化为全0.这种情况下,mmap函数的最后两个参数将被忽略
MAP_FIXED内存段必须位于start参数指定的地址处。start必须是内存页面大小(4096字节)的整数倍
MAP_HUGETLB按照“大内存页面”来分配内存空间。“大内存页面”的大小可通过/proc/menminfo文件来查看

fd参数是被映射文件对应的文件描述符。它一般通过open系统调用获得。offset参数设置从文件的何处开始映射(对于不需要读入整个文件的情况)。
mmap函数成功时返回指向目标内存区域的指针,失败返回MAP_FAILED((void*) - 1)并设置errno。munmap函数成功时返回0,失败则返回-1并设置errno

splice函数

splice函数用于在两个文件描述符之间移动数据,也是零拷贝操作。splice函数定义如下:

#include<fctnl.h>
ssize_t splice(int fd_in,loff_t* off_in,int fd_out,loff_t* off_out,size_t len,unsigned int flags);

fd_in参数是待输入数据的文件描述符。如果fd_in是一个管道文件描述符,那么off_in参数必须被设置为NULL。如果fd_in不是一个管道文件描述符,那么off_in表示从输入数据流的何处开始读取数据。此时,若off_in被设置为NULL,则表示从输入数据流的当前偏移位置读入;若off_in不为NULL,则它将指出具体的偏移位置。fd_out/off_out参数的含义与fd_in/off_in相同,不过用于输出数据流。len参数指定移动数据长度;flags参数则控制数据如何移动,它可以被设置为下表中某些值的按位或。

常用值含义
SPLICE_F_MOVE如果合适的话,按整页内存移动数据。这只是给内核的一个提示。不过,因为它的实现存在BUG,所以自内核2.66.21后,它实际没有任何作用。
SPLICE_F_NONBLOCK非阻塞的splice操作,但实际效果还会受文件描述符本身的阻塞状态的影响
SPLICE_F_MORE给内核一个提示:后续的splice调用将读取更多数据
SPLICE_F_GIFT对splice没效果

使用splice函数时,fd_in和fd_out必须至少有一个是管道文件描述符。splice函数调用成功时返回移动字节的数量。它可能返回0,表示没有数据需要移动,这发生在从管道中读取数据(fd_in是管道文件描述符)而该管道没有被写入任何数据时。splice函数失败时返回-1并设置errno。常见errno如下表所示

错误含义
EBADF参数所指文件描述符有错
EINVAL目标文件系统不支持splice,或者目标文件以追加方式打开,或者两个文件描述符都不是管道文件描述符,或者某个offset参数被用于不支持随机访问的设备(比如字符设备)
ENOMEM内存不够
ESPIPE参数fd_in(或fd_out)是管道文件描述符,而off_in(或off_out)不为NULL

tee函数

tee函数在两个管道文件描述符之间复制数据,也是零拷贝操作。它不消耗数据,因此源文件描述符上的数据仍然可以用于后续的读操作。tee原型如下:

#include<fcntl.h>
ssize_t tee(int fd_in,int fd_out,size_t len,unsigned int flags);

该函数的参数的含义与splice相同。tee函数成功时返回在两个文件描述符之间复制的数据量(字节数)。返回0表示没有复制任何数据。tee失败时返回-1并设置errno。

fcntl函数

(file control)提供了文件描述符的各种控制操作。另外一个常见的控制文件符属性和常用的属性和行为的系统调用是ioctl,而且ioctl比fcntl能够执行更多的控制。但是对于控制文件描述符常用的属性和行为,fcntl函数是由POSIX规范指定的首选方法。fcntl函数定义如下:

#include<fcntl.h>
int fcntl(int fd,int cmd...);

fd参数是被操作的文件描述符,cmd参数指定执行何种类型的操作。根据操作类型的不同,该函数可能还需要第三个可选参数arg。fcntl函数支持的常用操作及其参数如下表。

在这里插入图片描述

fcntl函数成功时返回值如表所示,失败返回-1并设置errno。
在网络编程中,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;						/* 返回文件描述符旧的状态标志,以便日后恢复该状态标志 */
}

new_option = old_option | O_NONBLOCK;	/* 设置非阻塞标志 */
	fcntl(fd,F_SETFL,new_option);				
	return old_option;						/* 返回文件描述符旧的状态标志,以便日后恢复该状态标志 */
}

此外,SIGIO和SIGURG这两个信号与其他Linux信号不同,它们必须与某个文件描述符相关联方可使用:当被关联的文件描述符可读或可写时,系统触发SIGIO信号;当被关联的文件描述符(而且必须是一个socket)上有带外数据可读时,系统将触发SIGURG信号。将信号和文件描述关联的方法,就是使用fcntl函数为目标文件描述符指定宿主进程或进程组,那么被指定的宿主进程和进程组将捕获这两个信号。使用SIGIO时,还需利用fcntl设置其O_ASYNC标志(异步I/O标志,不过SIGIO信号模型并非真正意义上的异步I/O模型)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值