高级 I/O 函数

本文是《Linux 高性能服务器编程》阅读记录,供以后查阅参考。推荐阅读原书。

所有函数未标明需要包含什么头文件,可使用 man 命令自行查询。

1. pipe

函数声明:

1
2
3
4
5
/* Create a one-way communication channel (pipe).
If successful, two file descriptors are stored in PIPEDES;
bytes written on PIPEDES[1] can be read from PIPEDES[0].
Returns 0 if successful, -1 if not. */
extern int pipe (int __pipedes[2]) __THROW __wur;

fd[1] 用于 写入 数据;fd[0] 用于 读取 数据。且管道为单向通信,只能一端写入,另一端读取。需要双向通信时,可创建两个管道。管道默认为阻塞的,也即调用 read 函数读数据时,若管道内无数据写入,read 调用会阻塞,直到有数据可读。write 调用当管道已满时,也会阻塞,直到管道有足够的空闲空间可供写入数据。

如果写入端引用计数为 0,调用 read 会返回 0,表示 EOF(End Of File);如果读取端引用计数为 0,调用 write 将失败,并引发 SIGPIPE 信号。

另外,可使用 sockerpair 函数创建双向通信的管道,即:两端都可以读写:

1
2
3
4
5
6
/* Create two new sockets, of type TYPE in domain DOMAIN and using
protocol PROTOCOL, which are connected to each other, and put file
descriptors for them in FDS[0] and FDS[1]. If PROTOCOL is zero,
one will be chosen automatically. Returns 0 on success, -1 for errors. */
extern int socketpair (int __domain, int __type, int __protocol,
int __fds[2]) __THROW;

2. dup 和 dup2

函数原型:

1
2
3
4
5
6
/* Duplicate FD, returning a new file descriptor on the same file.  */
extern int dup (int __fd) __THROW __wur;


/* Duplicate FD to FD2, closing FD2 and making it open on the same file. */
extern int dup2 (int __fd, int __fd2) __THROW;

dup 函数用于创建一个新的指向同一个文件、管道或网络连接的文件描述符。dup2 关闭 fd2,并且将 fd2 指向 fd 表示的文件。

3. readv 和 writev

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* Read data from file descriptor FD, and put the result in the
buffers described by IOVEC, which is a vector of COUNT 'struct iovec's.
The buffers are filled in the order specified.
Operates just like 'read' (see <unistd.h>) except that data are
put in IOVEC instead of a contiguous buffer.

This function is a cancellation point and therefore not marked with
__THROW. */
extern ssize_t readv (int __fd, const struct iovec *__iovec, int __count);


/* Write data pointed by the buffers described by IOVEC, which
is a vector of COUNT 'struct iovec's, to file descriptor FD.
The data is written in the order specified.
Operates just like 'write' (see <unistd.h>) except that the data
are taken from IOVEC instead of a contiguous buffer.

This function is a cancellation point and therefore not marked with
__THROW. */
extern ssize_t writev (int __fd, const struct iovec *__iovec, int __count);

成功时返回读取的字节数;失败返回 -1。

writev 函数的一个可能的使用场景是:响应 HTTP 请求时,header 和 body 分别存储在两个缓冲区里。可以利用 writev 函数一次写两个缓冲区。

4. sendfile

sendfile 函数用于在两个文件描述符之间直接传递数据,从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,称之为零拷贝。函数声明如下:

1
2
3
4
5
6
7
8
/* Send up to COUNT bytes from file associated with IN_FD starting at
*OFFSET to descriptor OUT_FD. Set *OFFSET to the IN_FD's file position
following the read bytes. If OFFSET is a null pointer, use the normal
file position instead. Return the number of written bytes, or -1 in
case of error. */
#ifndef __USE_FILE_OFFSET64
extern ssize_t sendfile (int __out_fd, int __in_fd, off_t *__offset,
size_t __count) __THROW;
  • in_fd:待读出内容的文件描述符,必须时支持类似 mmap 函数的文件描述符,即它必须指向真实的文件,不能是 socket 和管道
  • out_fd:待写入内容的文件描述符,必须是一个 socket
  • offset:指定从读入文件描述符的哪个位置开始读;空表示从默认起始位置开始读
  • count:指定传输的字节数
  • 调用成功返回传输的字节数,-1 表示出错

5. mmap 和 munmap

mmap 函数用于申请一段内存空间。munmap 函数则释放由 mmap 函数创建的这段内存空间。声明如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* Map addresses starting near ADDR and extending for LEN bytes.  from
OFFSET into the file FD describes according to PROT and FLAGS. If ADDR
is nonzero, it is the desired mapping address. If the MAP_FIXED bit is
set in FLAGS, the mapping will be at ADDR exactly (which must be
page-aligned); otherwise the system chooses a convenient nearby address.
The return value is the actual mapping address chosen or MAP_FAILED
for errors (in which case `errno' is set). A successful `mmap' call
deallocates any previous mapping for the affected region. */

#ifndef __USE_FILE_OFFSET64
extern void *mmap (void *__addr, size_t __len, int __prot,
int __flags, int __fd, __off_t __offset) __THROW;


/* Deallocate any mapping for the region starting at ADDR and extending LEN
bytes. Returns 0 if successful, -1 for errors (and sets errno). */
extern int munmap (void *__addr, size_t __len) __THROW;
  • start:允许用户使用某个特定地址作为内存的起始地址,设置为 NULL 表示系统自动分配一个地址
  • len:指定内存端的长度
  • prot:设置内存端的访问权限,包括可读、可写、可执行和不能被访问
  • flags:控制内存段内容被修改后程序的行为
  • fd:被映射文件对应的文件描述符
  • offset:设置文件从何处开始映射

6. splice

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

1
2
3
4
5
6
7
/* Splice two files together.

This function is a possible cancellation point and therefore not
marked with __THROW. */
extern __ssize_t splice (int __fdin, __off64_t *__offin, int __fdout,
__off64_t *__offout, size_t __len,
unsigned int __flags);
  • fd_in:待输入数据的文件描述符
  • off_in:如果 fd_in 是管道文件描述符,则必须设置为 NULL;否则表示从输入数据流的何处开始读数据,为 NULL 表示从输入流的当前偏移位置读入
  • fd_out:使用 splice 时,fd_infd_out 必须至少有一个为管道文件描述符
  • off_out:和 in 相同,不过表示写入数据流
  • len :表示移动数据的长度
  • flags:控制数据如何移动

7. tee

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

1
2
3
4
5
6
/* In-kernel implementation of tee for pipe buffers.

This function is a possible cancellation point and therefore not
marked with __THROW. */
extern __ssize_t tee (int __fdin, int __fdout, size_t __len,
unsigned int __flags);

函数参数含义与 splice 相同,但是 fd_infd_out 都必须是管道文件描述符。

8. fcntl

fcntl (file control)函数提供了对文件描述符的各种控制操作。另外一个能够控制文件描述符属性和行为的系统调用是 ioctl,能够比 fcntl 执行更多的控制。fcntl 函数声明如下:

1
2
3
4
5
6
/* Do the file control operation described by CMD on FD.
The remaining arguments are interpreted depending on CMD.

This function is a cancellation point and therefore not marked with
__THROW. */
extern int fcntl (int __fd, int __cmd, ...);
  • fd:被操作的文件描述符
  • cmd:指定执行何种类型的操作
  • arg:可选参数

网络编程中,fcntl 函数常用于将文件描述符设置未非阻塞的:

1
2
3
4
5
6
7
8
#include <fcntl.h>

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; // 返回文件描述符旧的状态标志,可供恢复
}

高级 I/O 函数
https://arcsin2.cloud/2024/05/02/高级-I-O-函数/
作者
arcsin2
发布于
2024年5月2日
许可协议