Linux高性能服务器编程学习笔记(二)

第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-execnon-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_outoff_out同上。fd_infd_out必须有一个是管道。

len数据的长度。

flags可以为以下值的按位或:

  • SPLICE_F_MOVE,按整页内存移动数据。
  • SPLICE_F_NONBLOCK,非阻塞操作,实际效果会受到文件描述符本身的阻塞状态影响。
  • SPLICE_F_MORE,给内核一个提示,后续将读取更多数据。
  • SPLICE_F_GIFT,没有效果。

成功返回移动字节的数量。失败返回-1置errno

  • EBADF,参数所指文件描述符有错。
  • EINVAL,目标系统不支持。
  • ENOMEM,内存不够。
  • ESPIPEfd_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_infd_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;							// 返回旧状态以便日后恢复
}
发布了136 篇原创文章 · 获赞 33 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/Radium_1209/article/details/105035172