栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

Linux | 高级I/O函数

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Linux | 高级I/O函数

文章目录
  • 创建文件描述符的函数
    • pipe函数
    • dup函数、dup2函数
  • 读取或写入数据
    • readv函数、writev函数
  • 零拷贝
    • sendfile函数
    • splice函数
    • tee函数
  • 进程间通信——共享内存
    • mmap函数 和 munmap函数
  • 控制文件描述符
    • fcntl函数


创建文件描述符的函数 pipe函数

不再赘述,详情见我的另一篇博客。

值得一提的是,socket 基础API中有一个 socketpair函数,能够方便地创建双向管道:

#include
#include
int socketpair(int domain, int type, int protocol, inf fd[2]);
// domain只能使用 UNIX 本地域协议族 AF_UNIX,因为我们仅能在本地使用这个双向管道。
// 成功时返回0,失败时返回-1并设置error。

dup函数、dup2函数

这两个函数会在 CGI服务器 中用到。CGI服务器: 主要是通过把服务器本地标准输入、输出或者文件重定向到网络连接中,以达到向标准输入、输出缓冲区中输入的信息,能在网络连接中发送的效果。

#include
int dup( int file_descriptor );
int dup2( int file_descriptor_one, int file_descriptor_two );
  • dup: 创建一个新的文件描述符,该文件描述符和原有文件描述符 file_descriptor 指向相同的文件、管道或网络连接。返回的文件描述符总是取系统当前可用的最小整数值。
  • dup2: 与 dup 类似,只是返回第一个不小于 file_descriptor_two 的整数值。

两个系统调用失败时都返回 -1,并设置 error。

通过 dup 和 dup2 创建的文件描述符并不继承原文件描述符的属性。比如:close-on-exec 和 non-blocking 等。

用 dup 实现一个基本的 CGI服务器 的局部代码:

close( STDOUT_FILENO );
dup( connfd );
printf( "hellon" );
close( connfd );

流程:

  1. 先关闭标准输出文件描述符 STDOUT_FILENO (其值是1);
  2. 通过 dup 复制 socket 文件描述符 connfd ;
  3. 由于 dup 总是返回系统中最小的可用文件描述符,所以它的返回值实际上是 1 ,即之前关闭的标准输出文件描述符的值;
  4. 服务器输出到标准输出的内容(“hello”)就会直接发送到与客户端对应的 socket 上,因此 printf 调用的输出将被客户端获得,而不是显示在服务器程序的终端上。

读取或写入数据 readv函数、writev函数
  • readv函数: 将数据从文件描述符读到分散的内存块中,即分散读;
  • writev函数: 将多块分配的内存数据一并写入文件描述符中,即集中写。

相当于简化版的 recvmsg 和 sendmsg 。

#include
ssize_t readv( int fd, const struct iovec* vector, int count );
ssize_t weitev( int fd, const struct iovec* vector, int count );
// fd:被操作的目标文件描述符
// vector:iovec结构数组,iovec封装了一块内存的起始位置和长度
// count:vector数组的长度,即有多少块内存数据需要从fd读出或写入到fd
// 成功时返回读出/写入fd的字节数,失败则返回-1并试着errno。

零拷贝 sendfile函数

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

#include
ssize_t sendfile( int out_fd, int in_fd, off_t* offset, size_t count );
// out_fd:待写入内容的文件描述符,必须是一个socket
// in_fd:待读出内容的文件描述符,必须是一个支持类似mmap函数的文件描述符,即必须指向真实的文件,不能是socket和管道
// offset:指定从读入文件流的哪个位置开始读,如果为空,则使用读入文件流默认的起始位置
// count:传输的字节数
// 成功时返回传输的字节数,失败返回-1,并设置errno

sendfile 几乎是专门为在网络上传输文件而设计的。


splice函数

splice函数: 本质就是借助管道描述符在两个文件之间移动数据,也是零拷贝。

#include
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;反之,如果fd_in不是一个管道描述符(如socket),那么off_in表示从输入数据流开始读取数据的起始位置。总而言之,若off_in不为NULL,则它将指出具体的偏移位置。
// fd_out/off_out:与fd_in/off_in类似,不过用于输出数据流。
// len:移动数据的长度
// flags:控制数据的移动方式

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

常见的 errno :

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

tee函数

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

#include
ssize_t tee(int fd_in, int fd_out, size_t len, unsigned int flags);
// 参数含义同splice,但 fd_in 和 fd_out 必须同时是管道文件描述符
// 成功时返回两个文件之间复制的数据数量(字节数),返回0表示没有复制任何数据,tee失败返回-1并设置errno。

tee 常和 splice 一起用,splice 将非管道与管道绑定,tee 将 splice 操作后得到的管道绑定在一起。


进程间通信——共享内存 mmap函数 和 munmap函数
  • mmap函数: 申请一段内存空间作为进程间通信的共享内存,也可以将文件直接映射到其中。
  • munmap函数: 释放由 mmap 创建的内存空间。
#include
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:指定内存段的长度。
// prot:设置内粗段的访问权限,可取以下值的按位或:
//  PROT_READ,内存段可读。
//  PROT_WRITE,内存段可写。
//  PROT_EXEC,内存段可执行。
//  PROT_NONE,内存段不能被访问。
// flags:控制内存段内容被修改后程序的行为。
// fd:被映射文件对应的文件描述符,一般通过open调用获得。
// offset:参数设置从文件的何处开始映射(对于不需要读入整个文件的情况)。
// 成功时返回0,失败返回-1并设置errno。

mmap 的 flags 参数的常用值及其含义:

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

控制文件描述符 fcntl函数

fcntl函数: 全名 file control ,提供了对文件描述符的各种控制操作(类似于系统调用 ioctl,但 ioctl 比 fcntl 提供的控制更多。)但是 fcntl 是 POSIX 规定的首选方法。

#include
int fcntl(int fd, int cmd, ...);
// fd:被操作的文件描述符
// cmd:执行何种操作
// 有可能需要第三个可选参数 arg
// 成功时返回值根据操作不同有所不同,失败时返回-1并设置errno

将文件描述符设置为非阻塞的:

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

题外话:SIGIO 和 SIGURG 这两个信号与其他 Linux 信号不同,他们必须与某个文件描述符相关联方可使用:

  • 被关联文件描述符可读或可写时,系统将触发 SIGIO 信号。
  • 被关联文件描述符是一个 socket 且有带外数据可读时,系统将触发 SIGURG 信号。

这两个信号就是通过 fcntl函数 与文件描述符关联的,具体做法是:fcntl函数 为目标文件描述符指定宿主进程或进程组,被指定的宿主进程或进程组去捕获这两个信号。

特别的,使用 SIGIO 时,还需利用 fcntl 设置其 O_ASYNC标志(异步I/O标志,不过SIGIO信号模型并非真正意义上的异步I/O模型)。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/341185.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号