第6章 高级I/O函数
Linux高性能服务器编程学习笔记
6.1 pipe函数
用于创建一个管道
#include <unistd.h>
int pip( int fd[2] );
/* 成功返回0,失败返回-1置errno */
fd[0]
用于读,fd[1]
用于写。
管道容量的大小默认是65536字节。可以使用fcntl
函数来修改管道容量。
用socketpair
创建双向管道。
#include <sys/types.h>
#include <sys/socket.h>
int socketpair( int domain, int type, int protocol, int fd[2] );
domain
只能使用UNIX
本地域协议族AF_UNIX
。
6.2 dup函数和dup2函数
将标准输入重定向到一个文件,或者把标准输出重定向到一个网络连接。
#include <unistd.h>
int dup( int file_descriptor );
int dup2( int file_descriptor, int file_descriptor_two );
dup
创建一个新的文件描述符,和原来的文件描述符指向同一个文件、管道或网络连接。dup
返回系统当前可用的最小整数值。dup2
返回第一个不小于file_descriptor_two
的整数值。
注意:创建的文件描述符并不继承原文件描述符的属性,比如close-on-exec
和non-blocking
等。
6.3 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 );
/* 成功返回读写的字节数,失败返回-1置errno */
count
参数表示vector
数组的长度(内存块数量)
struct iovec
{
void *iov_base; // 内存起始地址
size_t iov_len; // 内存块长度
}
iovec
结构体封装了一块内存的起始位置和长度
6.4 sendfile函数
两个文件描述符之间直接传递数据(完全在内核中操作),避免了内存缓冲区和用户缓冲区之间的数据拷贝,效率很高,这称为零拷贝。
#include <sys/sendfile.h>
ssize_t sendfile( int out_fd, int in_fd, off_t* offset, size_t count );
/* 成功返回传输的字节数,失败返回-1置errno */
in_fd
是待读出内容的文件描述符。
out_fd
是待写入内容的文件描述符。
offset
指定从读入文件流的哪个位置开始读,为空则使用默认的起始位置。
count
是传输的字节数
6.5 mmap函数和munmap函数
mmap函数用于申请一段内存空间。我们可以将这段内存作为进程间通信的共享内存。
munmap函数释放申请的内存空间。
#include <sys/mman.h>
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *start, size_t length);
/* mmap成功返回指向目标内存区域的指针,失败返回MAP_FAILED((void*)-1)置errno
* munmap成功返回0,失败返回-1置errno */
start
运行用户使用某个特定的地址作为这段内存的起始地址。如果为NULL
,系统自动分配一个地址。
length
指定内存段长度。
prot
设置内存段的访问权限,它可以取以下几个值的按位或:
PROT_READ
,内存段可读PROT_WRITE
,内存段可写PROT_EXEC
,内存段可执行PROT_NONE
,内存段不能被访问
flags
控制内存段内容被修改后程序的行为,它可以取以下几个值的按位或(MAP_SHARED和MAP_PRIVATE互斥):
MAP_SHARED
,在进程间共享这段内存,对该内存段的修改将反映到被映射的文件中。MAP_PRIVATE
,内存段为调用进程私有,对该内存段的修改不会反映到被映射的文件中。MAP_ANONYMOUS
,这段内存不是从文件映射而来的,内容被初始化为0,最后两个参数被忽略。MAP_FIXED
,内存段必须位于start
参数指定的地址处。start
必须是内存页面大小(4096字节)的整数倍。MAP_HUGETLB
,按照大内存页面(/proc/meminfo查看)来分配空间。
fd
是被映射文件对应的文件描述符。一般通过open
系统调用获得。
offset
参数设置文件从何处开始映射。
6.6 splice函数
用于两个文件描述符之间移动数据,也是零拷贝操作
#include <fcntl.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
待输入数据的文件描述符。
off_in
,如果fd_in
是一个管道文件描述符,那么off_in
参数必须被设为NULL
。否则,off_in
表示从输入数据流的何处开始读取数据。
fd_out
和off_out
同上。fd_in
和fd_out
必须有一个是管道。
len
数据的长度。
flags
可以为以下值的按位或:
SPLICE_F_MOVE
,按整页内存移动数据。SPLICE_F_NONBLOCK
,非阻塞操作,实际效果会受到文件描述符本身的阻塞状态影响。SPLICE_F_MORE
,给内核一个提示,后续将读取更多数据。SPLICE_F_GIFT
,没有效果。
成功返回移动字节的数量。失败返回-1置errno
:
EBADF
,参数所指文件描述符有错。EINVAL
,目标系统不支持。ENOMEM
,内存不够。ESPIPE
,fd_in(fd_out)
是管道描述符,而off_in(off_out)
不为NULL
。
6.7 tee函数
两个管道文件描述符之间复制数据,也是零拷贝操作,它不消耗数据。
#include <fcntl.h>
ssize_t tee( int fd_in, int fd_out, size_t len, unsigned int flags );
/* 成功返回复制的数据字节数,失败返回-1置errno */
参数与splice
相同,但fd_in
和fd_out
必须都为管道。
6.8 fcntl函数
提供对文件描述符的各种控制操作。
#include <fcntl.h>
int fcntl( int fd, int cmd, ... );
fd
是被操作的文件描述符。
cmd
指定何种类型的操作。
常用操作:
操作分类 | 操作 | 第三个参数类型 | 成功时的返回值 |
---|---|---|---|
获取和设置文件 | F_GETFL | void | fd的状态标志 |
描述符的状态标志 | F_SETFL | long | 0 |
在网络编程中常用来将一个文件描述符设为非阻塞的:
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; // 返回旧状态以便日后恢复
}