glibc 知:手册13:底层输入/输出

1. 前言

The GNU C Library Reference Manual for version 2.35

2. 底层输入/输出

Low-Level Input/Output

本章描述了对文件描述符执行底层输入/输出操作的函数。这些函数包括输入/输出流中描述的高级 I/O 函数的原语,以及用于执行流上没有等效项的底层控制操作的函数。

流级 I/O 更灵活,通常更方便;因此,程序员通常只在必要时才使用描述符级别的函数。这些是一些常见的原因:

  • 用于读取大块的二进制文件。
  • 用于在解析之前将整个文件读入核心。
  • 执行数据传输以外的操作,这只能通过描述符来完成。(您可以使用 fileno 来获取与流对应的描述符。)
  • 将描述符传递给子进程。(孩子可以创建自己的流来使用它继承的描述符,但不能直接继承流。)

2.1. 打开和关闭文件

Opening and Closing Files

本节介绍使用文件描述符打开和关闭文件的原语。open 和 creat 函数在头文件 fcntl.h 中声明,而 close 在 unistd.h 中声明。

函数:int open (const char *filename, int flags[, mode_t mode])

Preliminary: | MT-Safe | AS-Safe | AC-Safe fd | See POSIX Safety Concepts.

open 函数为由 filename 命名的文件创建并返回一个新的文件描述符。最初,文件的文件位置指示符位于文件的开头。参数模式(请参阅访问权限的模式位)仅在创建文件时使用,但在任何情况下提供参数都没有坏处。

flags 参数控制如何打开文件。这是一个位掩码;您可以通过适当参数的按位或来创建值(使用 C 中的“|”运算符)。有关可用参数,请参阅文件状态标志

open 的正常返回值是一个非负整数文件描述符。在发生错误的情况下,将改为返回值 -1。除了通常的文件名错误(请参阅文件名错误)之外,还为此函数定义了以下 errno 错误条件:

EACCES

文件存在,但按照 flags 参数的要求不可读/可写,或者文件不存在且目录不可写,因此无法创建。

EEXIST

O_CREAT 和 O_EXCL 都已设置,并且命名文件已经存在。

EINTR

打开操作被信号中断。请参阅被信号中断的原语

EISDIR

flags 参数指定写权限,并且文件是一个目录。

EMFILE

该进程打开的文件过多。文件描述符的最大数量由 RLIMIT_NOFILE 资源限制控制;请参阅限制资源使用

ENFILE

整个系统,或者可能是包含该目录的文件系统,目前无法支持任何其他打开的文件。(这个问题在 GNU/Hurd 系统上不会发生。)

ENOENT

指定文件不存在,并且未指定 O_CREAT。

ENOSPC

无法扩展包含新文件的目录或文件系统,因为没有剩余磁盘空间。

ENXIO

O_NONBLOCK 和 O_WRONLY 都在 flags 参数中设置,由 filename 命名的文件是一个 FIFO(参见 Pipes 和 FIFOs),并且没有进程打开文件进行读取。

EROFS

该文件位于只读文件系统上,并且在 flags 参数中设置了 O_WRONLY、O_RDWR 和 O_TRUNC 中的任何一个,或者设置了 O_CREAT 并且该文件不存在。

如果在 32 位机器上使用 _FILE_OFFSET_BITS == 64 翻译源文件,则函数 open 返回以大文件模式打开的文件描述符,这使文件处理函数能够使用大小最大为 2^63 字节且偏移量为 -2^63 到 2^63。这对用户来说是透明的,因为所有低级文件处理功能都被同等替换了。

这个函数是多线程程序中的一个取消点。如果线程在调用 open 时分配了一些资源(如内存、文件描述符、信号量或其他),则会出现问题。如果线程被取消,这些资源将保持分配状态,直到程序结束。为避免这种情况,应使用取消处理程序保护对 open 的调用。

open 函数是创建流的 fopen 和 freopen 函数的底层原语。

函数:int open64 (const char *filename, int flags[, mode_t mode])

Preliminary: | MT-Safe | AS-Safe | AC-Safe fd | See POSIX Safety Concepts.

此功能类似于打开。它返回一个文件描述符,可用于访问由文件名命名的文件。唯一的区别是在 32 位系统上,文件是以大文件模式打开的。即,文件长度和文件偏移量可以超过 31 位。

当使用 _FILE_OFFSET_BITS == 64 翻译源代码时,此函数实际上在名称 open 下可用。即,使用 64 位文件大小和偏移量的新扩展 API 透明地替换了旧 API。

过时的函数:int creat (const char *filename, mode_t mode)

Preliminary: | MT-Safe | AS-Safe | AC-Safe fd | See POSIX Safety Concepts.

此功能已过时。来电:

creat (filename, mode)

相当于:

open (filename, O_WRONLY | O_CREAT | O_TRUNC, mode)

如果在 32 位机器上使用 _FILE_OFFSET_BITS == 64 转换源,则函数 creat 返回以大文件模式打开的文件描述符,这使文件处理函数能够使用最大为 2^63 且偏移量为 -2^ 的文件63 到 2^63。这对用户来说是透明的,因为所有低级文件处理功能都被同等替换了。

过时的函数:int creat64 (const char *filename, mode_t mode)

Preliminary: | MT-Safe | AS-Safe | AC-Safe fd | See POSIX Safety Concepts.

此功能类似于创建。它返回一个文件描述符,可用于访问由文件名命名的文件。唯一的区别是在 32 位系统上,文件是以大文件模式打开的。即,文件长度和文件偏移量可以超过 31 位。

要使用此文件描述符,不得使用正常操作,而是使用名为 *64 的对应操作,例如 read64。

当使用 _FILE_OFFSET_BITS == 64 翻译源代码时,此函数实际上在名称 open 下可用。即,使用 64 位文件大小和偏移量的新扩展 API 透明地替换了旧 API。

函数:int close (int filedes)

Preliminary: | MT-Safe | AS-Safe | AC-Safe fd | See POSIX Safety Concepts.

函数 close 关闭文件描述符文件。关闭文件会产生以下后果:

  • 文件描述符被释放。
  • 文件上进程拥有的任何记录锁都被解锁。
  • 当与管道或 FIFO 关联的所有文件描述符都已关闭时,任何未读数据都将被丢弃。

这个函数是多线程程序中的一个取消点。如果线程在调用 close 时分配了一些资源(如内存、文件描述符、信号量或其他),则会出现问题。如果线程被取消,这些资源将保持分配状态,直到程序结束。为避免这种情况,应使用取消处理程序保护对 close 的调用。

close 的正常返回值为 0;如果失败,则返回值 -1。为此函数定义了以下 errno 错误条件:

EBADF

filedes 参数不是有效的文件描述符。

EINTR

关闭通话被信号中断。请参阅被信号中断的原语。以下是如何正确处理 EINTR 的示例:

TEMP_FAILURE_RETRY (close (desc));

ENOSPC

EIO

EDQUOT

当 NFS 访问文件时,有时直到关闭才能检测到这些写入错误。有关其含义的详细信息,请参阅输入和输出原语

请注意,没有单独的 close64 函数。这不是必需的,因为此函数不确定也不依赖于文件的模式。执行关闭操作的内核知道描述符用于哪种模式并且可以处理这种情况。

要关闭流,请调用 fclose(请参阅关闭流),而不是尝试使用 close 关闭其底层文件描述符。这会刷新任何缓冲的输出并更新流对象以指示它已关闭。

函数:int close_range (unsigned int lowfd, unsigned int maxfd, int flags)

Preliminary: | MT-Safe | AS-Safe | AC-Safe fd | See POSIX Safety Concepts.

函数 close_range 将文件描述符从 lowfd 关闭到 maxfd(包括)。此函数类似于根据标志在指定的文件描述符范围内调用 close。

此功能仅在最近的 Linux 版本上受支持,并且 GNU C 库不提供任何回退(应用程序将需要处理可能的 ENOSYS)。

标志添加了有关文件如何关闭的选项。Linux 目前支持:

CLOSE_RANGE_UNSHARE

在关闭文件描述符之前取消共享文件描述符表。

CLOSE_RANGE_CLOEXEC

设置 FD_CLOEXEC 位而不是关闭文件描述符。

close_range 的正常返回值为 0;如果失败,则返回值 -1。为此函数定义了以下 errno 错误条件:

EINVAL

lowfd 值大于 maxfd 或使用了不支持的标志。

ENOMEM

要么没有足够的内存用于操作,要么进程的地址空间不足。只有在使用 CLOSE_RANGE_UNSHARED 标志时才会发生这种情况。

EMFILE

该进程打开的文件太多,只有在使用 CLOSE_RANGE_UNSHARED 标志时才会发生。文件描述符的最大数量由 RLIMIT_NOFILE 资源限制控制;请参阅限制资源使用

ENOSYS

内核没有实现所需的功能。

函数:void closefrom (int lowfd)

Preliminary: | MT-Safe | AS-Safe | AC-Safe fd | See POSIX Safety Concepts.

closefrom 函数关闭所有大于或等于 lowfd 的文件描述符。这个函数类似于为所有打开的文件描述符调用close,不小于lowfd。

已经关闭的文件描述符被忽略。

2.2. 输入和输出原语

Input and Output Primitives

本节介绍对文件描述符执行原始输入和输出操作的函数:read、write 和 lseek。这些函数在头文件 unistd.h 中声明。

数据类型:ssize_t

此数据类型用于表示可以在单个操作中读取或写入的块的大小。它类似于 size_t,但必须是有符号类型。

函数:ssize_t read (int fields, void *buffer, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

read 函数从带有描述符的文件中读取最多 size 个字节,并将结果存储在缓冲区中。(这不一定是字符串,也没有添加终止空字符。)

返回值是实际读取的字节数。这可能小于大小;例如,如果文件中没有那么多字节,或者没有那么多字节立即可用。确切的行为取决于它是什么类型的文件。请注意,读取小于 size 字节不是错误。

零值表示文件结束(除非 size 参数的值也为零)。这不被视为错误。如果您在文件结束时继续调用 read,它将继续返回零并且什么也不做。

如果 read 返回至少一个字符,则无法判断是否已到达文件结尾。但是,如果您确实到达了末尾,则下一次读取将返回零。

如果发生错误,read 返回 -1。为此函数定义了以下 errno 错误条件:

EAGAIN

通常,当没有立即可用的输入时,read 会等待一些输入。但是如果为文件设置了 O_NONBLOCK 标志(参见文件状态标志),read 立即返回而不读取任何数据,并报告此错误。

兼容性说明:大多数 BSD Unix 版本为此使用不同的错误代码:EWOULDBLOCK。在 GNU C 库中,EWOULDBLOCK 是 EAGAIN 的别名,因此使用哪个名称并不重要。

在某些系统上,如果内核无法找到足够的物理内存来锁定用户的页面,则从字符特殊文件中读取大量数据也会因 EAGAIN 而失败。这仅限于通过直接内存访问传输到用户内存的设备,这意味着它不包括终端,因为它们总是在内核中使用单独的缓冲区。这个问题永远不会在 GNU/Hurd 系统上发生。

任何可能导致 EAGAIN 的条件都可能导致成功读取,该读取返回的字节数少于请求的字节数。立即再次调用 read 将导致 EAGAIN。

EBADF

filedes 参数不是有效的文件描述符,或者未打开以供读取。

EINTR

读取在等待输入时被信号中断。请参阅被信号中断的原语。信号不一定会导致 read 返回 EINTR;相反,它可能会导致成功读取,返回的字节数少于请求的字节数。

EIO

对于许多设备和磁盘文件,此错误代码表示硬件错误。

当后台进程尝试从控制终端读取数据时也会发生 EIO,并且通过向其发送 SIGTTIN 信号来停止进程的正常操作不起作用。如果信号被阻止或忽略,或者因为进程组是孤立的,则可能会发生这种情况。有关作业控制的更多信息,请参阅作业控制;有关信号的信息,请参阅信号处理

EINVAL

在某些系统中,从字符或块设备读取时,位置和大小偏移必须与特定的块大小对齐。此错误表明偏移未正确对齐。

请注意,没有名为 read64 的函数。这不是必需的,因为此函数不会直接修改或处理可能的宽文件偏移量。由于内核在内部处理此状态,因此 read 函数可用于所有情况。

这个函数是多线程程序中的一个取消点。如果线程在调用 read 时分配了一些资源(如内存、文件描述符、信号量或其他),则会出现问题。如果线程被取消,这些资源将保持分配状态,直到程序结束。为避免这种情况,应使用取消处理程序保护对读取的调用。

read 函数是所有从流中读取的函数的底层原语,例如 fgetc。

函数:ssize_t pread (int fields, void *buffer, size_t size, off_t offset)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

预读函数类似于读函数。前三个参数相同,返回值和错误码也对应。

不同之处在于第四个参数及其处理。数据块不是从文件描述符文件的当前位置读取的。而是从位置偏移开始的文件中读取数据。文件描述符本身的位置不受操作的影响。该值与调用前相同。

当使用 _FILE_OFFSET_BITS == 64 编译源文件时,pread 函数实际上是 pread64 并且类型 off_t 有 64 位,这使得处理长达 2^63 字节的文件成为可能。

pread 的返回值描述了读取的字节数。在错误情况下,它像 read 一样返回 -1 并且错误代码也相同,但添加了以下内容:

EINVAL

给定的偏移值是负数,因此是非法的。

ESPIPE

文件描述符filedes 与管道或FIFO 相关联,并且该设备不允许定位文件指针。

该函数是 Unix 单一规范版本 2 中定义的扩展。

函数:ssize_t pread64 (int fields, void *buffer, size_t size, off64_t offset)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此功能类似于 pred 功能。不同之处在于 offset 参数的类型是 off64_t 而不是 off_t,这使得在 32 位机器上可以寻址大于 2^31 字节和最多 2^63 字节的文件。文件描述符文件必须使用 open64 打开,否则 off64_t 可能的大偏移量将导致小文件模式下的描述符错误。

当在 32 位机器上使用 _FILE_OFFSET_BITS == 64 编译源文件时,这个函数实际上可以在名称下使用 pread 并且因此透明地替换了 32 位接口。

函数:ssize_t write (int fields, const void *buffer, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

write 函数将最多 size 个字节从缓冲区写入到具有描述符的文件中。缓冲区中的数据不一定是字符串,并且会像任何其他字符一样输出空字符。

返回值是实际写入的字节数。这可能是大小,但总是可以更小。您的程序应始终在循环中调用 write,迭代直到写入所有数据。

一旦写入返回,数据就会被排队写入并且可以立即读回,但不一定会立即写入永久存储。当您需要在继续之前确保您的数据已永久存储时,您可以使用 fsync。(系统批量连续写入并在方便时一次完成所有写入会更有效。通常它们总是会在一分钟或更短的时间内写入磁盘。)现代系统提供了另一个函数 fdatasync ,它只保证文件的完整性数据,因此速度更快。可以使用 O_FSYNC 开放模式,让 write 总是先把数据存到磁盘再返回;请参阅 I/O 操作模式

如果发生错误,write 返回 -1。为此函数定义了以下 errno 错误条件:

EAGAIN

通常,写块直到写操作完成。但是如果为文件设置了 O_NONBLOCK 标志(请参阅文件上的控制操作),它会立即返回而不写入任何数据并报告此错误。可能导致进程阻塞输出的情况示例是写入支持流控制的终端设备,其中输出已因接收到 STOP 字符而暂停。

兼容性说明:大多数 BSD Unix 版本为此使用不同的错误代码:EWOULDBLOCK。在 GNU C 库中,EWOULDBLOCK 是 EAGAIN 的别名,因此使用哪个名称并不重要。

在某些系统上,如果内核无法找到足够的物理内存来锁定用户的页面,则从字符特殊文件写入大量数据也会因 EAGAIN 而失败。这仅限于通过直接内存访问传输到用户内存的设备,这意味着它不包括终端,因为它们总是在内核中使用单独的缓冲区。在 GNU/Hurd 系统上不会出现这个问题。

EBADF

filedes 参数不是有效的文件描述符,或者未打开以供写入。

EFBIG

文件的大小将变得比实现可以支持的更大。

EINTR

写入操作在等待完成时被信号中断。信号不一定会导致 write 返回 EINTR;相反,它可能会导致成功写入,写入的字节数少于请求的字节数。请参阅被信号中断的原语

EIO

对于许多设备和磁盘文件,此错误代码表示硬件错误。

ENOSPC

包含该文件的设备已满。

EPIPE

当您尝试写入未打开以供任何进程读取的管道或 FIFO 时,将返回此错误。发生这种情况时,也会向进程发送一个 SIGPIPE 信号;请参阅信号处理

EINVAL

在某些系统中,当写入字符或块设备时,位置和大小偏移必须与特定的块大小对齐。此错误表明偏移未正确对齐。

除非您已安排防止 EINTR 失败,否则应在每次失败的 write 调用后检查 errno,如果错误是 EINTR,则应简单地重复调用。请参阅被信号中断的原语。执行此操作的简单方法是使用宏 TEMP_FAILURE_RETRY,如下所示:

nbytes = TEMP_FAILURE_RETRY (write (desc, buffer, count));

请注意,没有名为 write64 的函数。这不是必需的,因为此函数不会直接修改或处理可能的宽文件偏移量。由于内核在内部处理此状态,因此 write 函数可用于所有情况。

这个函数是多线程程序中的一个取消点。如果线程在调用 write 时分配了一些资源(如内存、文件描述符、信号量或其他),则会出现问题。如果线程被取消,这些资源将保持分配状态,直到程序结束。为避免这种情况,应使用取消处理程序保护对 write 的调用。

write 函数是所有写入流的函数的底层原语,例如 fputc。

函数:ssize_t pwrite (int fields, const void *buffer, size_t size, off_t offset)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

pwrite 函数类似于 write 函数。前三个参数相同,返回值和错误码也对应。

不同之处在于第四个参数及其处理。数据块没有写入文件描述符filedes的当前位置。相反,数据从位置偏移开始写入文件。文件描述符本身的位置不受操作的影响。该值与调用前相同。

但是,在 Linux 上,如果使用 O_APPEND 打开文件,则 pwrite 会将数据附加到文件的末尾,而不管 offset 的值如何。

当使用 _FILE_OFFSET_BITS == 64 编译源文件时,pwrite 函数实际上是 pwrite64 并且类型 off_t 有 64 位,这使得处理长达 2^63 字节的文件成为可能。

pwrite 的返回值描述了写入的字节数。在错误情况下,它像 write 一样返回 -1 并且错误代码也相同,但添加了以下内容:

EINVAL

给定的偏移值是负数,因此是非法的。

ESPIPE

文件描述符filedes 与管道或FIFO 相关联,并且该设备不允许定位文件指针。

该函数是 Unix 单一规范版本 2 中定义的扩展。

函数:ssize_t pwrite64 (int fields, const void *buffer, size_t size, off64_t offset)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数类似于 pwrite 函数。不同之处在于 offset 参数的类型是 off64_t 而不是 off_t,这使得在 32 位机器上可以寻址大于 2^31 字节和最多 2^63 字节的文件。文件描述符文件必须使用 open64 打开,否则 off64_t 可能的大偏移量将导致小文件模式下的描述符错误。

当在 32 位机器上使用 _FILE_OFFSET_BITS == 64 编译源文件时,这个函数实际上在名称 pwrite 下可用,因此透明地替换了 32 位接口。

2.3. 设置描述符的文件位置

Setting the File Position of a Descriptor

正如您可以使用 fseek 设置流的文件位置一样,您可以使用 lseek 设置描述符的文件位置。这指定了下一次读取或写入操作在文件中的位置。有关文件位置及其含义的更多信息,请参阅文件定位

要从描述符中读取当前文件位置值,请使用 lseek (desc, 0, SEEK_CUR)。

函数:off_t lseek (int filedes, off_t offset, int whence)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

lseek 函数用于更改带有描述符的文件的文件位置。

whence 参数指定应如何解释偏移量,与 fseek 函数的方式相同,它必须是符号常量 SEEK_SET、SEEK_CUR 或 SEEK_END 之一。

SEEK_SET

指定偏移量是从文件开头算起的字符数。

SEEK_CUR

指定偏移量是从当前文件位置算起的字符数。这个计数可能是正数或负数。

SEEK_END

指定偏移量是从文件末尾算起的字符数。负数指定文件当前范围内的位置;正数指定当前结束后的位置。如果您将位置设置为超过当前结束,并实际写入数据,您将使用零扩展文件到该位置。

lseek 的返回值通常是生成的文件位置,以文件开头的字节为单位。您可以将此功能与 SEEK_CUR 一起使用来读取当前文件位置。

如果要追加到文件,使用 SEEK_END 将文件位置设置为文件的当前结尾是不够的。另一个进程可能会在您搜索之后但在您写入之前写入更多数据,扩展文件以便您写入的位置会破坏他们的数据。而是使用 O_APPEND 操作模式;请参阅 I/O 操作模式

您可以将文件位置设置为超过文件的当前结尾。这本身并不会使文件更长;lseek 从不更改文件。但是该位置的后续输出将扩展文件。文件前一个结尾和新位置之间的字符用零填充。以这种方式扩展文件会创建一个“洞”:零块实际上并未在磁盘上分配,因此文件占用的空间比看起来要少;然后将其称为“稀疏文件”。

如果文件位置无法更改,或者操作在某些方面无效,则 lseek 返回值 -1。为此函数定义了以下 errno 错误条件:

EBADF

文件不是有效的文件描述符。

EINVAL

whence 参数值无效,或者生成的文件偏移量无效。文件偏移量无效。

ESPIPE

文件对应于无法定位的对象,例如管道、FIFO 或终端设备。(POSIX.1 仅针对管道和 FIFO 指定此错误,但在 GNU 系统上,如果对象不可搜索,您总是会得到 ESPIPE。)

当使用 _FILE_OFFSET_BITS == 64 编译源文件时,lseek 函数实际上是 lseek64 并且类型 off_t 具有 64 位,这使得可以处理长达 2^63 字节的文件。

这个函数是多线程程序中的一个取消点。如果线程在调用 lseek 时分配了一些资源(如内存、文件描述符、信号量或其他),则会出现问题。如果线程被取消,这些资源将保持分配状态,直到程序结束。为了避免这种对 lseek 的调用,应该使用取消处理程序来保护。

lseek 函数是 fseek、fseeko、ftell、ftello 和 rewind 函数的底层原语,它们对流而不是文件描述符进行操作。

函数:off64_t lseek64 (int fields, off64_t offset, int wherece)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数类似于 lseek 函数。不同之处在于 offset 参数的类型是 off64_t 而不是 off_t,这使得在 32 位机器上可以寻址大于 2^31 字节和最多 2^63 字节的文件。文件描述符文件必须使用 open64 打开,否则 off64_t 可能的大偏移量将导致小文件模式下的描述符错误。

当在 32 位机器上使用 _FILE_OFFSET_BITS == 64 编译源文件时,此函数实际上可以在名称 lseek 下使用,因此透明地替换了 32 位接口。

如果您多次打开文件,或者使用 dup 复制一个描述符,则同一文件可以有多个描述符。来自单独调用 open 的描述符具有独立的文件位置;在一个描述符上使用 lseek 对另一个描述符没有影响。例如,

{
  int d1, d2;
  char buf[4];
  d1 = open ("foo", O_RDONLY);
  d2 = open ("foo", O_RDONLY);
  lseek (d1, 1024, SEEK_SET);
  read (d2, buf, 4);
}

将读取从 foo 的第 1024 个字符开始的四个字符,然后再读取从第 1028 个字符开始的四个字符。

数据类型:off_t

这是一个有符号整数类型,用于表示文件大小。在 GNU C 库中,这种类型并不比 int 更窄。

如果源代码使用 _FILE_OFFSET_BITS == 64 编译,则此类型透明地被 off64_t 替换。

数据类型:off64_t

此类型的使用类似于 off_t。不同之处在于,即使在 32 位机器上(其中 off_t 类型将具有 32 位),off64_t 也具有 64 位,因此能够处理长达 2^63 字节的文件。

当使用 _FILE_OFFSET_BITS == 64 编译时,此类型在名称 off_t 下可用。

存在这些“SEEK_…”常量的别名是为了与旧 BSD 系统兼容。它们在两个不同的头文件中定义:fcntl.h 和 sys/file.h。

L_SET

SEEK_SET 的别名。

L_INCR

SEEK_CUR 的别名。

L_XTND

SEEK_END 的别名。

2.4. 描述符和流

Descriptors and Streams

给定一个打开的文件描述符,您可以使用 fdopen 函数为其创建一个流。您可以使用 fileno 函数获取现有流的基础文件描述符。这些函数在头文件 stdio.h 中声明。

函数:FILE * fdopen (int filedes, const char *opentype)

Preliminary: | MT-Safe | AS-Unsafe heap lock | AC-Unsafe mem lock | See POSIX Safety Concepts.

fdopen 函数为文件描述符文件返回一个新流。

opentype 参数的解释方式与 fopen 函数相同(请参阅打开流),但不允许使用“b”选项;这是因为 GNU 系统不区分文本文件和二进制文件。此外,“w”和“w+”不会导致文件截断;这些仅在打开文件时有效,在这种情况下,文件已经打开。您必须确保 opentype 参数与打开文件描述符的实际模式相匹配。

返回值是新的流。如果无法创建流(例如,如果文件描述符指示的文件模式不允许 opentype 参数指定的访问),则返回空指针。

在其他一些系统中,fdopen 可能无法检测到文件描述符的模式不允许 opentype 指定的访问。GNU C 库总是对此进行检查。

有关显示使用 fdopen 函数的示例,请参阅创建管道

函数:int fileno (FILE *stream)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数返回与流流关联的文件描述符。如果检测到错误(例如,如果流无效)或者如果流不对文件执行 I/O,则 fileno 返回 -1。

函数:int fileno_unlocked (FILE *stream)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

fileno_unlocked 函数等价于 fileno 函数,只是它在状态为 FSETLOCKING_INTERNAL 时不会隐式锁定流。

这个函数是一个 GNU 扩展。

unistd.h 中还为属于标准流 stdin、stdout 和 stderr 的文件描述符定义了符号常量;请参阅标准流

STDIN_FILENO

这个宏的值为 0,它是标准输入的文件描述符。

STDOUT_FILENO

这个宏的值为 1,它是标准输出的文件描述符。

STDERR_FILENO

这个宏的值为 2,它是标准错误输出的文件描述符。

2.5. 混合流和描述符的危险

Dangers of Mixing Streams and Descriptors

您可以将多个文件描述符和流(我们简称为流和描述符“通道”)连接到同一个文件,但您必须注意避免通道之间的混淆。有两种情况需要考虑:共享单个文件位置值的链接通道和具有自己文件位置的独立通道。

最好在程序中只使用一个通道来将实际数据传输到任何给定文件,除非所有访问都是用于输入。例如,如果您打开一个管道(您只能在文件描述符级别执行此操作),则使用描述符执行所有 I/O,或者使用 fdopen 从描述符构造一个流,然后使用该流执行所有 I/O .

2.5.1. 链接通道

Linked Channels

来自单个打开的通道共享相同的文件位置;我们称它们为链接频道。当您使用 fdopen 从描述符创建流时,当您从具有 fileno 的流中获取描述符时,当您使用 dup 或 dup2 复制描述符时,以及在 fork 期间继承描述符时,会产生链接通道。对于不支持随机访问的文件,例如终端和管道,所有通道都有效链接。在随机访问文件上,所有附加类型的输出流都有效地相互链接。

如果您一直在使用流进行 I/O(或刚刚打开流),并且想要使用与其链接的另一个通道(流或描述符)进行 I/O,则必须首先清理您一直在使用的流。请参阅清洗流

终止一个进程,或在该进程中执行一个新程序,会破坏该进程中的所有流。如果链接到这些流的描述符在其他进程中持续存在,则它们的文件位置将因此变得未定义。为了防止这种情况,您必须在销毁流之前清理它们。

2.5.2. 独立通道

Independent Channels

当您在可搜索文件上单独打开通道(流或描述符)时,每个通道都有自己的文件位置。这些被称为独立通道。

系统独立处理每个通道。大多数时候,这是非常可预测和自然的(尤其是对于输入):每个通道都可以在文件中自己的位置顺序读取或写入。但是,如果某些通道是流,则必须采取以下预防措施:

  • 您应该在使用后清理输出流,然后再执行任何可能从文件的同一部分读取或写入的操作。
  • 您应该在读取可能已使用独立通道修改的数据之前清理输入流。否则,您可能会读取流缓冲区中的过时数据。

如果您确实在文件末尾输出到一个通道,这肯定会使其他独立通道位于新结束之前的某个位置。在写入之前,您无法可靠地将它们的文件位置设置为文件的新结尾,因为在设置文件位置和写入数据之间,文件总是可以被另一个进程扩展。相反,使用附加类型的描述符或流;它们总是在文件的当前末尾输出。为了使文件结束位置准确,您必须清理您正在使用的输出通道,如果它是一个流。

对于不支持随机访问的文件,两个通道不可能有单独的文件指针。因此,用于读取或写入此类文件的通道始终是链接的,而不是独立的。附加类型的通道也总是链接的。对于这些频道,请遵循链接频道的规则;请参阅链接通道

2.5.3. 清洗流

Cleaning Streams

在大多数情况下,您可以使用 fflush 来清理流。

如果您知道流已经干净,则可以跳过 fflush。只要缓冲区为空,流就是干净的。例如,无缓冲的流始终是干净的。位于文件末尾的输入流是干净的。当最后一个字符输出是换行符时,行缓冲流是干净的。但是,刚刚打开的输入流可能不干净,因为它的输入缓冲区可能不是空的。

在一种情况下,在大多数系统上清理流是不可能的。这是当流从非随机访问的文件中进行输入时。这样的流通常会提前读取,并且当文件不是随机访问时,没有办法返回已经读取的多余数据。当输入流从随机访问文件中读取时, fflush 确实会清理流,但会将文件指针留在不可预知的位置;您必须在进行任何进一步的 I/O 之前设置文件指针。

关闭仅输出流也会执行 fflush,因此这是清理输出流的有效方法。

在使用其描述符进行控制操作(例如设置终端模式)之前,您无需清理流;这些操作不会影响文件位置,也不受文件位置的影响。您可以对这些操作使用任何描述符,并且所有通道都会同时受到影响。但是,已经“输出”到流但仍由流缓冲的文本在随后刷新时将受制于新的终端模式。为确保“过去”输出被当时有效的终端设置覆盖,请在设置模式之前刷新该终端的输出流。请参阅终端模式

2.6. 快速分散-聚集 I/O

Fast Scatter-Gather I/O

一些应用程序可能需要读取或写入数据到多个缓冲区,这些缓冲区在内存中是分开的。尽管这可以通过多次调用读取和写入来轻松完成,但效率低下,因为每次内核调用都会产生开销。

相反,许多平台提供了特殊的高速原语来在单个内核调用中执行这些分散-聚集操作。GNU C 库将在缺少这些原语的任何系统上提供仿真,因此它们不是可移植性威胁。它们在 sys/uio.h 中定义。

这些函数由 iovec 结构数组控制,这些结构描述了每个缓冲区的位置和大小。

数据类型:struct iovec
iovec 结构描述了一个缓冲区。它包含两个字段:

void *iov_base

包含缓冲区的地址。

size_t iov_len

包含缓冲区的长度。

函数:ssize_t readv (int fields, const struct iovec *vector, int count)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

readv 函数从文件中读取数据并将其分散到向量中描述的缓冲区中,该缓冲区被视为长计数结构。随着每个缓冲区被填满,数据被发送到下一个缓冲区。

请注意,不保证 readv 会填满所有缓冲区。出于与 read 相同的原因,它可能会在任何时候停止。

返回值是读取的字节数(不是缓冲区),0 表示文件结束,-1 表示错误。可能的错误与读取中的相同。

函数:ssize_t writev (int fields, const struct iovec *vector, int count)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

writev 函数从向量中描述的缓冲区收集数据,这些数据被视为长计数结构,并将它们写入文件。随着每个缓冲区的写入,它会移动到下一个缓冲区。

与 readv 一样,writev 可能会在与 write 相同的条件下中途停止。

返回值是写入的字节数,或 -1 表示错误。可能的错误与写入相同。

函数:ssize_t preadv (int fd, const struct iovec *iov, int iovcnt, off_t offset)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

该函数类似于 readv 函数,不同之处在于它增加了一个类似于 pread 的 off_t 类型的额外偏移参数。从位置偏移开始的文件中读取数据。文件描述符本身的位置不受操作的影响。该值与调用前相同。

当源文件使用 _FILE_OFFSET_BITS == 64 编译时,preadv 函数实际上是 preadv64 并且类型 off_t 有 64 位,这使得处理长达 2^63 字节的文件成为可能。

返回值是读取的字节数(不是缓冲区),0 表示文件结束,-1 表示错误。可能的错误与 readv 和 pread 中的相同。

函数:ssize_t preadv64 (int fd, const struct iovec *iov, int iovcnt, off64_t offset)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

该函数与 preadv 函数类似,不同之处在于 offset 参数的类型为 off64_t 而不是 off_t。它可以在 32 位机器上处理大于 2^31 字节和最多 2^63 字节的文件。文件描述符文件必须使用 open64 打开,否则 off64_t 可能的大偏移量将导致小文件模式下的描述符错误。

当在 32 位机器上使用 _FILE_OFFSET_BITS == 64 编译源文件时,这个函数实际上在名称 preadv 下可用,因此透明地替换了 32 位接口。

函数:ssize_t pwritev (int fd, const struct iovec *iov, int iovcnt, off_t offset)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

该函数类似于 writev 函数,不同之处在于它增加了一个类似于 pwrite 的 off_t 类型的额外偏移参数。数据从位置偏移开始写入文件。文件描述符本身的位置不受操作的影响。该值与调用前相同。

但是,在 Linux 上,如果使用 O_APPEND 打开文件,则 pwrite 会将数据附加到文件的末尾,而不管 offset 的值如何。

当使用 _FILE_OFFSET_BITS == 64 编译源文件时,pwritev 函数实际上是 pwritev64 并且类型 off_t 有 64 位,这使得处理长达 2^63 字节的文件成为可能。

返回值是写入的字节数(不是缓冲区),0 表示文件结束,-1 表示错误。可能的错误与 writev 和 pwrite 中的相同。

函数:ssize_t pwritev64 (int fd, const struct iovec *iov, int iovcnt, off64_t offset)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

该函数与 pwritev 函数类似,不同之处在于 offset 参数的类型为 off64_t 而不是 off_t。它可以在 32 位机器上处理大于 2^31 字节和最多 2^63 字节的文件。文件描述符文件必须使用 open64 打开,否则 off64_t 可能的大偏移量将导致小文件模式下的描述符错误。

当在 32 位机器上使用 _FILE_OFFSET_BITS == 64 编译源文件时,这个函数实际上在名称 pwritev 下可用,因此透明地替换了 32 位接口。

函数:ssize_t preadv2 (int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数类似于 preadv 函数,不同之处在于它添加了一个额外的 int 类型的 flags 参数。此外,如果 offset 为 -1,则使用并更新当前文件位置(如 readv 函数)。

支持的标志取决于底层系统。对于 Linux,它支持:

RWF_HIPRI

高优先级请求。这会添加一个标志,告诉文件系统这是一个值得轮询硬件的高优先级请求。该标志纯粹是建议性的,如果不支持可以忽略。fd 必须使用 O_DIRECT 打开。

RWF_DSYNC

每 IO 同步,就像使用 O_DSYNC 标志打开文件一样。

RWF_SYNC

每 IO 同步,就像使用 O_SYNC 标志打开文件一样。

RWF_NOWAIT

对这个操作使用非阻塞模式;也就是说,如果操作会阻塞,对 preadv2 的调用将失败并将 errno 设置为 EAGAIN。

RWF_APPEND

Per-IO 同步,就像使用 O_APPEND 标志打开文件一样。

当源文件使用 _FILE_OFFSET_BITS == 64 编译时,preadv2 函数实际上是 preadv64v2 并且类型 off_t 有 64 位,这使得处理长达 2^63 字节的文件成为可能。

返回值是读取的字节数(不是缓冲区),0 表示文件结束,-1 表示错误。可能的错误与 preadv 中的相同,但添加了:

EOPNOTSUPP

使用了不受支持的标志。

函数:ssize_t preadv64v2 (int fd, const struct iovec *iov, int iovcnt, off64_t offset, int flags)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

该函数与 preadv2 函数类似,不同之处在于 offset 参数的类型为 off64_t 而不是 off_t。它可以在 32 位机器上处理大于 2^31 字节和最多 2^63 字节的文件。文件描述符文件必须使用 open64 打开,否则 off64_t 可能的大偏移量将导致小文件模式下的描述符错误。

当在 32 位机器上使用 _FILE_OFFSET_BITS == 64 编译源文件时,这个函数实际上在名称 preadv2 下可用,因此透明地替换了 32 位接口。

函数:ssize_t pwritev2 (int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数类似于 pwritev 函数,不同之处在于它添加了一个额外的 int 类型的标志参数。此外,如果 offset 为 -1,则应使用和更新当前文件位置(如 writev 函数)。

支持的标志取决于底层系统。对于 Linux,支持的标志与 preadv2 的相同。

当使用 _FILE_OFFSET_BITS == 64 编译源文件时,pwritev2 函数实际上是 pwritev64v2 并且类型 off_t 有 64 位,这使得处理长达 2^63 字节的文件成为可能。

返回值是写入的字节数(不是缓冲区),0 表示文件结束,-1 表示错误。可能的错误与 preadv2 中的相同。

函数:ssize_t pwritev64v2 (int fd, const struct iovec *iov, int iovcnt, off64_t offset, int flags)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

该函数与 pwritev2 函数类似,不同之处在于 offset 参数的类型为 off64_t 而不是 off_t。它可以在 32 位机器上处理大于 2^31 字节和最多 2^63 字节的文件。文件描述符文件必须使用 open64 打开,否则 off64_t 可能的大偏移量将导致小文件模式下的描述符错误。

当在 32 位机器上使用 _FILE_OFFSET_BITS == 64 编译源文件时,这个函数实际上在名称 pwritev2 下可用,因此透明地替换了 32 位接口。

2.7. 在两个文件之间复制数据

Copying data between two files

提供了一种特殊功能,用于在同一文件系统上的两个文件之间复制数据。系统可以优化这样的复制操作。这在网络文件系统上尤为重要,否则数据必须通过网络传输两次。

请注意,此函数仅复制文件数据,而不会复制文件权限或扩展属性等元数据。

函数:ssize_t copy_file_range (int inputfd, off64_t *inputpos, int outputfd, off64_t *outputpos, ssize_t length, unsigned int flags)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数将文件描述符 inputfd 中的最大长度字节复制到文件描述符 outputfd。

该函数可以对当前文件位置(如读取和写入)和显式偏移量(如 pread 和 pwrite)进行操作。如果 inputpos 指针为空,则将 inputfd 的文件位置作为复制操作的起点,并在此过程中推进文件位置。如果inputpos不为null,则*inputpos作为复制操作的起点,*inputpos按复制的字节数递增,但文件位置保持不变。类似的规则适用于输出文件位置的 outputfd 和 outputpos。

flags 参数当前是保留的,必须为零。

copy_file_range 函数返回复制的字节数。如果输入文件包含的剩余字节数少于长度,或者发生读取或写入失败,这可能小于指定的长度。如果立即遇到输入文件的结尾,则返回值为零。

如果没有字节可以被复制,为了报告错误,copy_file_range 返回值 -1 并设置 errno。下表列出了此函数的一些错误情况。

ENOSYS

内核没有实现所需的功能。

EISDIR

描述符 inputfd 或 outputfd 中的至少一个是指目录。

EINVAL

描述符 inputfd 或 outputfd 中的至少一个是指非常规、非目录文件(例如套接字或 FIFO)。

之前的输入或输出位置在复制操作之后超出了实现定义的限制。

flags 参数不为零。

EFBIG

新文件大小将超过进程文件大小限制。请参阅限制资源使用

之前的输入或输出位置在复制操作之后超出了实现定义的限制。如果在 32 位计算机上未使用大文件支持 (LFS) 打开文件,则可能会发生这种情况,并且复制操作会创建一个大于 off_t 可以表示的文件。

EBADF

参数 inputfd 不是为读取而打开的有效文件描述符。

参数 outputfd 不是为写入而打开的有效文件描述符,或者 outputfd 已使用 O_APPEND 打开。

此外,copy_file_range 可能会因 read、pread、write 和 pwrite 使用的错误代码而失败。

copy_file_range 函数是一个取消点。在取消的情况下,输入位置(文件位置或 *inputpos 处的值)是不确定的。

2.8. 内存映射I/O

Memory-mapped I/O

在现代操作系统上,可以将文件映射(读作“em-map”)到内存区域。完成后,可以像程序中的数组一样访问文件。

这比读取或写入更有效,因为仅加载程序实际访问的文件区域。对 mmapped 区域的尚未加载部分的访问与换出页面的处理方式相同。

由于当物理内存不足时,可以将映射的页面存储回它们的文件,因此可以将文件映射到比物理内存和交换空间大几个数量级的数量级。唯一的限制是地址空间。32 位机器上的理论限制为 4GB - 但是,实际限制会更小,因为某些区域将保留用于其他目的。如果使用 LFS 接口,则 32 位系统上的文件大小不限于 2GB(带符号的偏移量将 4GB 的可寻址区域减少了一半);完整的 64 位可用。

内存映射仅适用于整个内存页面。因此,映射地址必须是页面对齐的,并且长度值将向上取整。要确定机器使用的页面的默认大小,应该使用:

size_t page_size = (size_t) sysconf (_SC_PAGESIZE);

在某些系统上,映射可以为某些文件使用更大的页面大小,并且应用程序也可以为匿名映射请求更大的页面大小(参见下面的 MAP_HUGETLB 标志)。

在 sys/mman.h 中声明了以下函数:

函数:void * mmap (void *address, size_t length, int protect, int flags, int fields, off_t offset)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

mmap 函数创建一个新映射,连接到文件中打开的文件中的字节(offset)到(offset + length - 1)。为由filedes 指定的文件创建一个新引用,关闭文件不会删除该引用。

address 给出映射的首选起始地址。NULL 表示没有偏好。该地址的任何先前映射都会被自动删除。您提供的地址可能仍会更改,除非您使用 MAP_FIXED 标志。

Protect 包含控制允许访问类型的标志。它们包括 PROT_READ、PROT_WRITE 和 PROT_EXEC。特殊标志 PROT_NONE 保留一个地址空间区域以供将来使用。mprotect 函数可用于更改保护标志。请参阅内存保护

flags 包含控制地图性质的标志。必须指定 MAP_SHARED 或 MAP_PRIVATE 之一。

它们包括:

MAP_PRIVATE

这指定了对区域的写入永远不应写回附加文件。相反,会为该进程制作一个副本,如果内存不足,该区域将正常交换。没有其他进程会看到这些更改。

由于私有映射在写入时有效地恢复为普通内存,因此如果将此模式与 PROT_WRITE 一起使用,则必须有足够的虚拟内存来复制整个映射区域。

MAP_SHARED

这指定对该区域的写入将被写回文件。所做的更改将立即与映射同一文件的其他进程共享。

请注意,实际写入可能随时发生。如果使用常规 I/O 的其他进程获得文件的一致视图很重要,则需要使用 msync,如下所述。

MAP_FIXED

这会强制系统使用 address 中指定的确切映射地址,如果不能,则失败。

MAP_ANONYMOUS

MAP_ANON

这个标志告诉系统创建一个匿名映射,而不是连接到一个文件。文件和偏移量被忽略,该区域用零初始化。

在某些系统上,匿名映射被用作扩展堆的基本原语。它们对于在不创建文件的情况下在多个任务之间共享数据也很有用。

在某些系统上,使用私有匿名 mmap 比使用 malloc 处理大块更有效。这不是 GNU C 库的问题,因为包含的 malloc 会在适当的地方自动使用 mmap。

MAP_HUGETLB

这要求系统使用大于映射的默认页面大小的替代页面大小。对于某些工作负载,增加大型映射的页面大小可以提高性能,因为系统需要处理的页面要少得多。对于需要在存储或不同节点之间频繁传输页面的其他工作负载,降低的页面粒度可能会由于页面大小的增加和更大的传输而导致性能问题。

为了创建映射,系统需要增加物理上连续内存大小的页面大小。因此,MAP_HUGETLB 映射会受到内存碎片的影响,即使系统中有足够的可用内存,它们的创建也会失败。

并非所有文件系统都支持增加页面大小的映射。

MAP_HUGETLB 标志特定于 Linux。

mmap 返回新映射的地址,或 MAP_FAILED 表示错误。

可能的错误包括:

EINVAL

要么地址不可用(因为它不是适用页面大小的倍数),要么给出了不一致的标志。

如果指定了 MAP_HUGETLB,则文件或系统不支持大页面大小。

EACCES

对于保护中指定的访问类型,filedes 未打开。

ENOMEM

要么没有足够的内存用于操作,要么进程的地址空间不足。

ENODEV

此文件属于不支持映射的类型。

ENOEXEC

该文件位于不支持映射的文件系统上。

函数:void * mmap64 (void *address, size_t length, int protect, int flags, int fields, off64_t offset)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

mmap64 函数等价于 mmap 函数,但 offset 参数的类型为 off64_t。在 32 位系统上,这允许与文件描述符关联的文件大于 2GB。filedes 必须是从调用 open64 或 fopen64 和 freopen64 返回的描述符,其中使用 fileno 检索描述符。

当使用 _FILE_OFFSET_BITS == 64 翻译源代码时,此函数实际上在名称 mmap 下可用。即,使用 64 位文件大小和偏移量的新扩展 API 透明地替换了旧 API。

函数:int munmap (void *addr, size_t length)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

munmap 删除从 (addr) 到 (addr + length) 的所有内存映射。length 应该是映射的长度。

在一个命令中取消映射多个映射是安全的,或者在范围中包含未映射的空间。也可以只取消现有映射的一部分。但是,只能删除整个页面。如果长度不是偶数页,则会向上取整。

它返回 0 表示成功,返回 -1 表示错误。

一个错误是可能的:

EINVAL

给定的内存范围超出了用户 mmap 范围或未对齐页面。

函数:int msync (void *address, size_t length, int flags)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

使用共享映射时,内核可以在删除映射之前的任何时间写入文件。为了确定数据实际上已经写入文件并且可以被非内存映射 I/O 访问,有必要使用这个函数。

它在区域地址上操作到(地址+长度)。它可以用于映射的一部分或多个映射,但是给定的区域不应包含任何未映射的空间。

标志可以包含一些选项:

MS_SYNC

此标志确保数据实际写入磁盘。通常 msync 只确保使用常规 I/O 访问文件反映最近的更改。

MS_ASYNC

这告诉 msync 开始同步,但不要等待它完成。

msync 返回 0 表示成功,返回 -1 表示错误。错误包括:

EINVAL

给出了无效的区域,或者标志无效。

EFAULT

在给定区域的至少一部分中不存在现有映射。

函数:void * mremap (void *address, size_t length, size_t new_length, int flag)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数可用于更改现有内存区域的大小。地址和长度必须覆盖在同一 mmap 语句中完全映射的区域。将返回具有相同特征的新映射,其长度为 new_length。

一个选项是可能的,MREMAP_MAYMOVE。如果它在标志中给出,系统可能会删除现有的映射并在另一个位置创建一个具有所需长度的新映射。

返回结果映射的地址,或 -1。可能的错误代码包括:

EFAULT

在原始区域的至少一部分中不存在现有映射,或者该区域覆盖了两个或多个不同的映射。

EINVAL

给出的地址未对齐或不合适。

EAGAIN

该区域已锁定页面,如果扩展,它将超过进程对锁定页面的资源限制。请参阅限制资源使用

ENOMEM

该区域是私有可写的,并且没有足够的虚拟内存来扩展它。此外,如果未给出 MREMAP_MAYMOVE 并且扩展将与另一个映射区域发生冲突,则会发生此错误。

此功能仅在少数系统上可用。除了执行可选优化之外,不应依赖此功能。

并非所有文件描述符都可以被映射。套接字、管道和大多数设备只允许顺序访问,不适合映射抽象。此外,一些常规文件可能无法映射,旧内核可能根本不支持映射。因此,使用 mmap 的程序应该有一个在失败时使用的备用方法。请参阅 GNU 编码标准中的 Mmap

函数:int madvise (void *addr, size_t length, int advice)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数可用于向系统提供有关从 addr 开始并扩展 length 字节的内存区域的预期使用模式的建议。

建议的有效 BSD 值是:

MADV_NORMAL

该地区不应受到进一步的特殊待遇。

MADV_RANDOM

该区域将通过随机页面引用访问。内核应该为每个页面错误分页最小数量的页面。

MADV_SEQUENTIAL

该区域将通过顺序页面引用进行访问。这可能会导致内核积极预读,期望在该区域内的任何页面错误之后进一步的顺序引用。

MADV_WILLNEED

该地区将是必要的。该区域内的页面可能已被内核预先插入。

MADV_DONTNEED

不再需要该区域。内核可能会释放这些页面,导致对页面的任何更改丢失,以及交换出的页面被丢弃。

MADV_HUGEPAGE

表明增加此映射的页面大小是有益的。这可以提高较大映射的性能,因为系统需要处理的页面要少得多。但是,如果部分映射在存储或不同节点之间频繁传输,则性能可能会受到影响,因为单个传输可能会因页面大小的增加而变得相当大。

此标志特定于 Linux。

MADV_NOHUGEPAGE

撤消先前 MADV_HUGEPAGE 建议的效果。此标志特定于 Linux。

POSIX 名称略有不同,但含义相同:

POSIX_MADV_NORMAL

这对应于 BSD 的 MADV_NORMAL。

POSIX_MADV_RANDOM

这对应于 BSD 的 MADV_RANDOM。

POSIX_MADV_SEQUENTIAL

这对应于 BSD 的 MADV_SEQUENTIAL。

POSIX_MADV_WILLNEED

这对应于 BSD 的 MADV_WILLNEED。

POSIX_MADV_DONTNEED

这对应于 BSD 的 MADV_DONTNEED。

madvise 返回 0 表示成功,返回 -1 表示错误。错误包括:

EINVAL

给出了无效的区域,或者建议无效。

EFAULT

在给定区域的至少一部分中不存在现有映射。

函数:int shm_open (const char *name, int oflag, mode_t mode)

Preliminary: | MT-Safe locale | AS-Unsafe init heap lock | AC-Unsafe lock mem fd | See POSIX Safety Concepts.

此函数返回一个文件描述符,可用于通过 mmap 分配共享内存。不相关的进程可以使用相同的名称来创建或打开现有的共享内存对象。

name 参数指定要打开的共享内存对象。在 GNU C 库中,它必须是小于 NAME_MAX 字节的字符串,以可选的斜杠开头,但不包含其他斜杠。

oflag 和 mode 参数的语义与 open 中的相同。

shm_open 成功时返回文件描述符,错误时返回 -1。失败时设置 errno。

函数:int shm_unlink (const char *name)

Preliminary: | MT-Safe locale | AS-Unsafe init heap lock | AC-Unsafe lock mem fd | See POSIX Safety Concepts.

此函数是 shm_open 的逆函数,它删除之前由 shm_open 创建的具有给定名称的对象。

shm_unlink 成功返回 0 或错误返回 -1。失败时设置 errno。

函数:int memfd_create (const char *name, unsigned int flags)

Preliminary: | MT-Safe | AS-Safe | AC-Safe fd | See POSIX Safety Concepts.

memfd_create 函数返回一个文件描述符,可用于使用 mmap 函数创建内存映射。在这些映射不受实际文件支持的意义上,它类似于 shm_open 函数。但是,memfd_create 返回的描述符不对应命名对象;name 参数仅用于调试目的(例如,将出现在 /proc 中),使用相同名称单独调用 memfd_create 不会返回同一内存区域的描述符。该描述符还可用于在同一进程中创建别名映射。

描述符最初是指零长度文件。在创建由内存支持的映射之前,需要使用 ftruncate 函数增加文件大小。请参阅文件大小

flags 参数可以是以下标志的组合:

MFD_CLOEXEC

描述符是使用 O_CLOEXEC 标志创建的。

MFD_ALLOW_SEALING

描述符支持使用 fcntl 函数添加封条。

MFD_HUGETLB

这要求使用返回的文件描述符创建的映射使用更大的页面大小。有关详细信息,请参阅上面的 MAP_HUGETLB。

此标志与 MFD_ALLOW_SEALING 不兼容。

memfd_create 成功时返回文件描述符,失败时返回 -1。

为此函数定义了以下 errno 错误条件:

EINVAL

在标志中指定了无效的组合,或者名称太长。

EFAULT

name 参数不指向字符串。

EMFILE

该操作将超过此进程的文件描述符限制。

ENFILE

该操作将超过系统范围的文件描述符限制。

ENOMEM

该操作没有足够的内存。

2.9. 等待输入或输出

Waiting for Input or Output

有时程序需要在输入到达时接受多个输入通道上的输入。例如,一些工作站可能具有通过普通异步串行接口连接的数字化板、功能按钮盒或拨号盒等设备;良好的用户界面风格需要立即响应任何设备上的输入。另一个例子是一个程序,它通过管道或套接字充当其他几个进程的服务器。

您通常不能为此目的使用 read,因为这会阻塞程序,直到某个特定文件描述符上的输入可用为止;其他通道上的输入不会唤醒它。您可以设置非阻塞模式并轮询每个文件描述符,但这非常低效。

更好的解决方案是使用 select 函数。这会阻塞程序,直到输入或输出在指定的文件描述符集上准备好,或者直到计时器到期,以先到者为准。该工具在头文件 sys/types.h 中声明。

在服务器套接字的情况下(请参阅侦听连接),我们说当存在可以接受的挂起连接时“输入”可用(请参阅接受连接)。接受服务器套接字块并与选择交互,就像读取正常输入一样。

选择函数的文件描述符集被指定为 fd_set 对象。这是数据类型的描述和一些用于操作这些对象的宏。

数据类型:fd_set

fd_set 数据类型表示选择函数的文件描述符集。它实际上是一个位数组。

宏:int FD_SETSIZE

此宏的值是 fd_set 对象可以保存有关信息的文件描述符的最大数量。在具有固定最大数量的系统上,FD_SETSIZE 至少是该数量。在某些系统上,包括 GNU,对打开的描述符数量没有绝对限制,但这个宏仍然有一个常数值,它控制 fd_set 中的位数;如果你得到一个值与 FD_SETSIZE 一样高的文件描述符,你就不能将该描述符放入 fd_set。

宏:void FD_ZERO (fd_set *set)

Preliminary: | MT-Safe race:set | AS-Safe | AC-Safe | See POSIX Safety Concepts.

该宏将文件描述符集初始化为空集。

宏:void FD_SET (int filedes, fd_set *set)

Preliminary: | MT-Safe race:set | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此宏将文件添加到文件描述符集集合。

因为它被多次评估,所以filedes 参数不能有副作用。

宏:void FD_CLR (int filedes, fd_set *set)

Preliminary: | MT-Safe race:set | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此宏从文件描述符集中删除文件。

因为它被多次评估,所以filedes 参数不能有副作用。

宏:int FD_ISSET (int filedes, const fd_set *set)

Preliminary: | MT-Safe race:set | AS-Safe | AC-Safe | See POSIX Safety Concepts.

如果filedes 是文件描述符集的成员,则此宏返回非零值(true),否则返回零(false)。

因为它被多次评估,所以filedes 参数不能有副作用。

接下来,这里是select函数本身的描述。

函数:int select (int nfds, fd_set *read-fds, fd_set *write-fds, fd_set *except-fds, struct timeval *timeout)

Preliminary: | MT-Safe race:read-fds race:write-fds race:except-fds | AS-Safe | AC-Safe | See POSIX Safety Concepts.

select 函数会阻塞调用进程,直到任何指定的文件描述符集有活动,或者直到超时期限到期。

检查由 read-fds 参数指定的文件描述符以查看它们是否已准备好读取;检查 write-fds 文件描述符以查看它们是否准备好写入;并且检查 except-fds 文件描述符是否存在异常情况。如果您对检查此类条件不感兴趣,可以为这些参数中的任何一个传递一个空指针。

如果读取调用不会阻塞,则认为文件描述符已准备好读取。这通常包括文件末尾的读取偏移量或要报告的错误。如果存在可以用 accept 接受的挂起连接,则认为服务器套接字已准备好读取;请参阅接受连接。客户端套接字在其连接完全建立时准备好写入;请参阅建立连接

“异常情况”并不意味着错误——执行错误的系统调用时会立即报告错误,并且不构成描述符的状态。相反,它们包括诸如在套接字上存在紧急消息之类的条件。(有关紧急消息的信息,请参阅套接字。)

select 函数只检查第一个 nfds 文件描述符。通常的做法是传递 FD_SETSIZE 作为这个参数的值。

timeout 指定等待的最长时间。如果您为此参数传递一个空指针,则意味着无限期地阻塞,直到其中一个文件描述符准备好。否则,您应该以 struct timeval 格式提供时间;请参阅时间类型。如果您想找出哪些描述符已准备好而无需等待(如果没有准备好),请将零指定为时间(一个包含全零的结构时间值)。

select 的正常返回值是所有集合中就绪文件描述符的总数。每个参数集都被有关已准备好进行相应操作的描述符的信息覆盖。因此,要查看特定描述符 desc 是否有输入,请在 select 返回后使用 FD_ISSET (desc, read-fds)。

如果 select 因为超时期限到期而返回,则返回值为零。

任何信号都会导致 select 立即返回。因此,如果您的程序使用信号,则不能依靠 select 来一直等待指定的全部时间。如果您想确保等待特定的时间,您必须检查 EINTR 并根据当前时间使用新计算的超时重复选择。请参见下面的示例。另请参见被信号中断的原语

如果发生错误,则 select 返回 -1 并且不修改参数文件描述符集。为此函数定义了以下 errno 错误条件:

EBADF

文件描述符集之一指定了无效的文件描述符。

EINTR

操作被信号中断。请参阅被信号中断的原语

EINVAL

超时参数无效;其中一个分量为负数或太大。

可移植性 注意:select 函数是一个 BSD Unix 特性。

这是一个示例,展示了如何使用 select 来建立从文件描述符读取的超时时间。input_timeout 函数会阻塞调用进程,直到文件描述符上的输入可用,或者直到超时期限到期。

#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>

int
input_timeout (int filedes, unsigned int seconds)
{
  fd_set set;
  struct timeval timeout;

  /* Initialize the file descriptor set. */
  FD_ZERO (&set);
  FD_SET (filedes, &set);

  /* Initialize the timeout data structure. */
  timeout.tv_sec = seconds;
  timeout.tv_usec = 0;

  /* select returns 0 if timeout, 1 if input available, -1 if error. */
  return TEMP_FAILURE_RETRY (select (FD_SETSIZE,
                                     &set, NULL, NULL,
                                     &timeout));
}

int
main (void)
{
  fprintf (stderr, "select returned %d.\n",
           input_timeout (STDIN_FILENO, 5));
  return 0;
}

字节流连接服务器示例中,还有另一个示例显示了使用 select 来多路复用来自多个套接字的输入。

2.10. 同步 I/O 操作

Synchronizing I/O operations

在大多数现代操作系统中,正常的 I/O 操作不是同步执行的。即,即使 write 系统调用返回,这并不意味着数据实际上已写入媒体,例如磁盘。

在需要同步点的情况下,您可以使用特殊函数来确保所有操作在返回之前完成。

函数:void sync (void)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

只要有尚未写入设备的数据,对该函数的调用就不会返回。内核中的所有脏缓冲区都将被写入,因此可以实现整体一致的系统(如果没有其他并行的进程写入数据)。

可以在 unistd.h 中找到同步的原型。

程序更经常希望确保写入给定文件的数据被提交,而不是系统中的所有数据。为此,同步是多余的。

函数:int fsync (int fildes)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

fsync 函数可用于确保与打开的文件 fildes 关联的所有数据都写入与描述符关联的设备。除非所有操作都完成,否则函数调用不会返回。

fsync 的原型可以在 unistd.h 中找到。

这个函数是多线程程序中的一个取消点。如果线程在调用 fsync 时分配了一些资源(如内存、文件描述符、信号量或其他),则会出现问题。如果线程被取消,这些资源将保持分配状态,直到程序结束。为避免这种情况,应使用取消处理程序保护对 fsync 的调用。

如果没有发生错误,函数的返回值为零。否则为 -1 且全局变量 errno 设置为以下值:

EBADF

描述符 fildes 无效。

EINVAL

由于系统没有实现同步,因此不可能进行同步。

有时甚至不需要写入与文件描述符关联的所有数据。例如,在大小不变的数据库文件中,将所有文件内容数据写入设备就足够了。元信息,如修改时间等,并不那么重要,不提交此类信息并不会阻止在出现问题时成功恢复文件。

函数:int fdatasync (int fildes)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

当对 fdatasync 函数的调用返回时,可以确保将所有文件数据写入设备。对于所有未决的 I/O 操作,保证数据完整性的部分已完成。

并非所有系统都实现 fdatasync 操作。在缺少此功能的系统上,fdatasync 通过调用 fsync 来模拟,因为执行的操作是 fdatasync 所需操作的超集。

fdatasync 的原型在 unistd.h 中。

如果没有发生错误,函数的返回值为零。否则为 -1 且全局变量 errno 设置为以下值:

EBADF

描述符 fildes 无效。

EINVAL

由于系统没有实现同步,因此不可能进行同步。

2.11. 并行执行 I/O 操作

Perform I/O Operations in Parallel

POSIX.1b 标准定义了一组新的 I/O 操作,可以显着减少应用程序等待 I/O 的时间。新功能允许程序启动一个或多个 I/O 操作,然后在并行执行 I/O 操作时立即恢复正常工作。如果 unistd.h 文件定义了符号 _POSIX_ASYNCHRONOUS_IO,则此功能可用。

这些函数是具有名为 librt 的实时函数的库的一部分。它们实际上不是 libc 二进制文件的一部分。这些功能的实现可以使用内核中的支持(如果可用)或使用基于用户级线程的实现来完成。在后一种情况下,除了 librt 之外,可能还需要将应用程序与线程库 libpthread 链接。

所有 AIO 操作都对之前打开的文件进行操作。一个文件可能运行任意多个操作。异步 I/O 操作使用名为 struct aiocb(AIO 控制块)的数据结构进行控制。它在 aio.h 中定义如下。

数据类型:struct aiocb

POSIX.1b 标准要求 struct aiocb 结构至少包含下表中描述的成员。实现可能会使用更多元素,但取决于这些元素是不可移植的并且被高度弃用。

int aio_fildes

此元素指定要用于操作的文件描述符。它必须是合法的描述符,否则操作将失败。

打开文件的设备必须允许查找操作。即,不能在 lseek 调用会导致错误的终端等设备上使用任何 AIO 操作。

off_t aio_offset

此元素指定执行操作(输入或输出)的文件中的偏移量。由于操作是按任意顺序执行的,并且可以针对一个文件描述符启动多个操作,因此不能期望文件描述符的当前读/写位置。

volatile void *aio_buf

这是指向要写入数据的缓冲区或存储读取数据的位置的指针。

size_t aio_nbytes

此元素指定 aio_buf 指向的缓冲区的长度。

int aio_reqprio

如果平台定义了 _POSIX_PRIORITIZED_IO 和 _POSIX_PRIORITY_SCHEDULING,则根据当前调度优先级处理 AIO 请求。然后可以使用 aio_reqprio 元素来降低 AIO 操作的优先级。

struct sigevent aio_sigevent

此元素指定操作终止后如何通知调用进程。如果 sigev_notify 元素是 SIGEV_NONE,则不发送通知。如果是 SIGEV_SIGNAL,则发送 sigev_signo 确定的信号。否则,sigev_notify 必须是 SIGEV_THREAD。在这种情况下,将创建一个线程,该线程开始执行 sigev_notify_function 指向的函数。

int aio_lio_opcode

此元素仅由 lio_listio 和 lio_listio64 函数使用。由于这些函数允许一次启动任意数量的操作,并且每个操作都可以输入或输出(或什么都没有),因此信息必须存储在控制块中。可能的值是:

LIO_READ

开始读操作​​。从位置 aio_offset 的文件中读取,并将下一个 aio_nbytes 字节存储在 aio_buf 指向的缓冲区中。

LIO_WRITE

开始写操作。将从 aio_buf 开始的 aio_nbytes 字节写入从位置 aio_offset 开始的文件中。

LIO_NOP

对此控制块不执行任何操作。当 struct aiocb 值的数组包含漏洞时,此值有时很有用,即,尽管将整个数组呈现给 lio_listio 函数,但某些值不得处理。

当在 32 位机器上使用 _FILE_OFFSET_BITS == 64 编译源代码时,这种类型实际上是 struct aiocb64,因为 LFS 接口透明地替换了 struct aiocb 定义。

为了与 LFS 中定义的 AIO 函数一起使用,定义了一个类似的类型,它将适当成员的类型替换为更大的类型,但在其他方面等效于 struct aiocb。特别是,所有成员名称都相同。

数据类型:struct aiocb64

int aio_fildes

此元素指定用于操作的文件描述符。它必须是合法的描述符,否则操作会由于明显的原因而失败。

打开文件的设备必须允许查找操作。即,不能在 lseek 调用会导致错误的终端等设备上使用任何 AIO 操作。

off64_t aio_offset

此元素指定在文件中的哪个偏移处执行操作(输入或输出)。由于操作以任意顺序进行,并且可以针对一个文件描述符启动多个操作,因此不能期望文件描述符的当前读/写位置。

volatile void *aio_buf

这是指向要写入数据的缓冲区或存储读取数据的位置的指针。

size_t aio_nbytes

此元素指定 aio_buf 指向的缓冲区的长度。

int aio_reqprio

如果为平台定义了 _POSIX_PRIORITIZED_IO 和 _POSIX_PRIORITY_SCHEDULING,则根据当前调度优先级处理 AIO 请求。然后可以使用 aio_reqprio 元素来降低 AIO 操作的优先级。

struct sigevent aio_sigevent

此元素指定操作终止后如何通知调用进程。如果 sigev_notify 元素是 SIGEV_NONE 则不发送通知。如果是 SIGEV_SIGNAL,则发送 sigev_signo 确定的信号。否则,sigev_notify 必须是 SIGEV_THREAD,在这种情况下会创建一个线程,开始执行 sigev_notify_function 指向的函数。

int aio_lio_opcode

此元素仅由 lio_listio 和 lio_listio64 函数使用。由于这些函数允许一次启动任意数量的操作,并且每个操作都可以输入或输出(或什么都没有),因此信息必须存储在控制块中。有关可能值的描述,请参见 struct aiocb 的描述。

当在 32 位机器上使用 _FILE_OFFSET_BITS == 64 编译源代码时,这种类型在名称 struct aiocb64 下可用,因为 LFS 透明地替换了旧接口。

2.11.1. 异步读写操作

Asynchronous Read and Write Operations

函数:int aio_read (struct aiocb *aiocbp)

Preliminary: | MT-Safe | AS-Unsafe lock heap | AC-Unsafe lock mem | See POSIX Safety Concepts.

该函数启动异步读取操作。它在操作入队或遇到错误后立即返回。

aiocbp->aio_fildes 为其描述符的文件的第一个 aiocbp->aio_nbytes 字节被写入从 aiocbp->aio_buf 开始的缓冲区。读取从文件中的绝对位置 aiocbp->aio_offset 开始。

如果平台支持优先级 I/O,则 aiocbp->aio_reqprio 值用于在请求实际入队之前调整优先级。

根据aiocbp->aio_sigevent值通知调用进程读取请求终止。

当 aio_read 返回时,如果在进程入队之前没有发生错误,则返回值为零。如果发现这样的早期错误,该函数将返回 -1 并将 errno 设置为下列值之一:

EAGAIN

由于(暂时)超出资源限制,请求未入队。

ENOSYS

aio_read 函数未实现。

EBADF

aiocbp->aio_fildes 描述符无效。在将请求排入队列之前不需要识别此条件,因此也可能会异步发出此错误信号。

EINVAL

aiocbp->aio_offset 或 aiocbp->aio_reqpiro 值无效。在将请求排入队列之前不需要识别此条件,因此也可能会异步发出此错误信号。

如果 aio_read 返回零,则可以使用 aio_error 和 aio_return 函数查询请求的当前状态。只要 aio_error 返回的值是 EINPROGRESS,操作还没有完成。如果 aio_error 返回零,则操作成功终止,否则该值将被解释为错误代码。如果函数终止,则可以通过调用 aio_return 获得操作结果。返回的值与对 read 的等效调用将返回的值相同。aio_error 返回的可能错误代码有:

EBADF

aiocbp->aio_fildes 描述符无效。

ECANCELED

在操作完成之前取消了操作(请参阅取消 AIO 操作

EINVAL

aiocbp->aio_offset 值无效。

当使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数实际上是 aio_read64,因为 LFS 接口透明地替换了正常的实现。

函数:int aio_read64 (struct aiocb64 *aiocbp)

Preliminary: | MT-Safe | AS-Unsafe lock heap | AC-Unsafe lock mem | See POSIX Safety Concepts.

此函数类似于 aio_read 函数。唯一的区别是在 32 位机器上,文件描述符应该以大文件模式打开。在内部,aio_read64 使用与 lseek64 等效的功能(请参阅设置描述符的文件位置)来正确定位文件描述符以进行读取,这与 aio_read 中使用的 lseek 功能相反。

当使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数在名称 aio_read 下可用,因此透明地替换了 32 位机器上小文件的接口。

为了将数据异步写入文件,存在一对具有非常相似接口的等效函数。

函数:int aio_write (struct aiocb *aiocbp)

Preliminary: | MT-Safe | AS-Unsafe lock heap | AC-Unsafe lock mem | See POSIX Safety Concepts.

该函数启动异步写操作。函数调用在操作入队后立即返回,或者如果在此之前遇到错误。

从 aiocbp->aio_buf 开始的缓冲区中的第一个 aiocbp->aio_nbytes 字节被写入以 aiocbp->aio_fildes 为描述符的文件,从文件中的绝对位置 aiocbp->aio_offset 开始。

如果平台支持优先级 I/O,则在请求实际入队之前使用 aiocbp->aio_reqprio 值调整优先级。

根据aiocbp->aio_sigevent值通知调用进程读取请求终止。

当 aio_write 返回时,如果在进程入队之前没有发生错误,则返回值为零。如果发现这样的早期错误,该函数将返回 -1 并将 errno 设置为下列值之一。

EAGAIN

由于(暂时)超出资源限制,请求未入队。

ENOSYS

aio_write 函数未实现。

EBADF

aiocbp->aio_fildes 描述符无效。在将请求排入队列之前可能无法识别此条件,因此也可能会异步发出此错误信号。

EINVAL

aiocbp->aio_offset 或 aiocbp->aio_reqprio 值无效。在将请求排入队列之前可能无法识别此条件,因此也可能会异步发出此错误信号。

在 aio_write 返回零的情况下,可以使用 aio_error 和 aio_return 函数查询请求的当前状态。只要 aio_error 返回的值是 EINPROGRESS,操作还没有完成。如果 aio_error 返回零,则操作成功终止,否则该值将被解释为错误代码。如果函数终止,则可以通过调用 aio_return 获得操作结果。返回的值与对 read 的等效调用将返回的值相同。aio_error 返回的可能错误代码有:

EBADF

aiocbp->aio_fildes 描述符无效。

ECANCELED

该操作在操作完成之前被取消。(见取消 AIO 操作

EINVAL

aiocbp->aio_offset 值无效。

当使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数实际上是 aio_write64,因为 LFS 接口透明地替换了正常的实现。

函数:int aio_write64 (struct aiocb64 *aiocbp)

Preliminary: | MT-Safe | AS-Unsafe lock heap | AC-Unsafe lock mem | See POSIX Safety Concepts.

该函数类似于 aio_write 函数。唯一的区别是在 32 位机器上文件描述符应该以大文件模式打开。在内部,aio_write64 使用与 lseek64 等效的功能(请参阅设置描述符的文件位置)来正确定位文件描述符以进行写入,这与 aio_write 中使用的 lseek 功能相反。

当使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数在名称 aio_write 下可用,因此透明地替换了 32 位机器上小文件的接口。

除了这些或多或少具有传统接口的函数外,POSIX.1b 还定义了一个函数,它可以一次启动多个操作,并且可以处理自由混合的读写操作。因此它类似于 readv 和 writev 的组合。

函数:int lio_listio (int mode, struct aiocb *const list[], int nent, struct sigevent *sig)

Preliminary: | MT-Safe | AS-Unsafe lock heap | AC-Unsafe lock mem | See POSIX Safety Concepts.

lio_listio 函数可用于一次将任意数量的读写请求排入队列。这些请求可以全部针对同一个文件,全部针对不同文件或两者之间的每个解决方案。

lio_listio 从 list 指向的数组中获取 nent 请求。要执行的操作由列表的每个元素中的 aio_lio_opcode 成员确定。如果此字段为 LIO_READ,则将读取操作排入队列,类似于对数组的此元素调用 aio_read(除了发出终止信号的方式不同,我们将在下面看到)。如果 aio_lio_opcode 成员是 LIO_WRITE,则写入操作入队。否则,aio_lio_opcode 必须是 LIO_NOP,在这种情况下,列表的这个元素将被简单地忽略。这种“操作”在具有固定的 struct aiocb 元素数组的情况下很有用,一次只需要处理其中的几个元素。另一种情况是在处理所有请求之前取消 lio_listio 调用(请参阅取消 AIO 操作)并且必须重新发出剩余的请求。

list 指向的数组的每个元素的其他成员必须具有适用于上述 aio_read 和 aio_write 文档中描述的操作的值。

mode 参数确定 lio_listio 在将所有请求排入队列后的行为方式。如果模式是 LIO_WAIT,它会一直等待,直到所有请求都终止。否则模式必须是 LIO_NOWAIT 并且在这种情况下,函数在将所有请求排入队列后立即返回。在这种情况下,调用者会根据 sig 参数收到所有请求终止的通知。如果 sig 为 NULL,则不发送通知。否则会发送信号或启动线程,正如 aio_read 或 aio_write 的描述中所述。

如果 mode 为 LIO_WAIT,则当所有请求成功完成时,lio_listio 的返回值为 0。否则函数返回 -1 并相应地设置 errno。要找出哪个请求或哪个请求失败,必须对数组列表的所有元素使用 aio_error 函数。

如果模式是 LIO_NOWAIT,如果所有请求都正确入队,则函数返回 0。可以使用 aio_error 和 aio_return 找到请求的当前状态,如上所述。如果 lio_listio 在此模式下返回 -1,则相应地设置全局变量 errno。如果请求尚未终止,则对 aio_error 的调用将返回 EINPROGRESS。如果该值不同,则请求完成并返回错误值(或 0),并且可以使用 aio_return 检索操作结果。

errno 的可能值为:

EAGAIN

将所有请求排队所需的资源目前不可用。必须检查列表中每个元素的错误状态以确定哪个请求失败。

另一个原因可能是超出了 AIO 请求的系统范围限制。由于不存在任意限制,因此在 GNU 系统上的实现并非如此。

EINVAL

mode 参数无效或 nent 大于 AIO_LISTIO_MAX。

EIO

一个或多个请求的 I/O 操作失败。应检查每个请求的错误状态以确定哪个请求失败。

ENOSYS

不支持 lio_listio 函数。

如果 mode 参数是 LIO_NOWAIT 并且调用者取消了一个请求,那么 aio_error 返回的这个请求的错误状态是 ECANCELED。

当使用 _FILE_OFFSET_BITS == 64 编译源代码时,这个函数实际上是 lio_listio64,因为 LFS 接口透明地替换了正常的实现。

函数:int lio_listio64 (int mode, struct aiocb64 *const list[], int nent, struct sigevent *sig)

Preliminary: | MT-Safe | AS-Unsafe lock heap | AC-Unsafe lock mem | See POSIX Safety Concepts.

该函数类似于 lio_listio 函数。唯一的区别是在 32 位机器上,文件描述符应该以大文件模式打开。在内部,lio_listio64 使用与 lseek64 等效的功能(请参阅设置描述符的文件位置)来正确定位文件描述符以进行读取或写入,这与 lio_listio 中使用的 lseek 功能相反。

当使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数在名称 lio_listio 下可用,因此透明地替换了 32 位机器上小文件的接口。

2.11.2. 获取 AIO 操作状态

Getting the Status of AIO Operations

正如上一节函数文档中已经描述的那样,必须能够获取有关 I/O 请求状态的信息。当操作真正异步执行时(如使用 aio_read 和 aio_write 以及模式为 LIO_NOWAIT 时使用 lio_listio),有时需要知道特定请求是否已经终止,如果是,结果是什么。以下两个函数可让您获取此类信息。

函数:int aio_error (const struct aiocb *aiocbp)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

该函数确定aiocbp指向的struct aiocb变量所描述的请求的错误状态。如果请求尚未终止,则返回的值始终为 EINPROGRESS。一旦请求终止,如果请求成功完成,则 aio_error 返回的值为 0;如果请求已使用读、写或 fsync 完成,则返回将存储在 errno 变量中的值。

如果未实现,该函数可以返回 ENOSYS。如果 aiocbp 参数不引用返回状态未知的异步操作,它也可能返回 EINVAL。

当使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数实际上是 aio_error64,因为 LFS 接口透明地替换了正常的实现。

函数:int aio_error64 (const struct aiocb64 *aiocbp)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数类似于 aio_error,唯一的区别是参数是对 struct aiocb64 类型变量的引用。

当使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数在名称 aio_error 下可用,因此透明地替换了 32 位机器上小文件的接口。

函数:ssize_t aio_return (struct aiocb *aiocbp)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

该函数可用于检索aiocbp指向的变量中描述的请求所执行操作的返回状态。只要 aio_error 返回的这个请求的错误状态是 EINPROGRESS,这个函数的返回值就是未定义的。

一旦请求完成,这个函数可以只使用一次来检索返回值。以下调用可能会导致未定义的行为。返回值本身就是 read、write 或 fsync 调用返回的值。

如果未实现,该函数可以返回 ENOSYS。如果 aiocbp 参数不引用返回状态未知的异步操作,它也可能返回 EINVAL。

当使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数实际上是 aio_return64,因为 LFS 接口透明地替换了正常的实现。

函数:ssize_t aio_return64 (struct aiocb64 *aiocbp)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数类似于 aio_return,唯一的区别是参数是对 struct aiocb64 类型变量的引用。

当使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数在名称 aio_return 下可用,因此透明地替换了 32 位机器上小文件的接口。

2.11.3. 进入一致状态

Getting into a Consistent State

在处理异步操作时,有时需要进入一致的状态。这意味着对于 AIO,人们想知道是否处理了某个请求或一组请求。这可以通过在操作终止后等待系统发送的通知来完成,但这有时意味着浪费资源(主要是计算时间)。相反,POSIX.1b 定义了两个有助于实现大多数一致性的函数。

只有在 unistd.h 中定义了符号 _POSIX_SYNCHRONIZED_IO 时,aio_fsync 和 aio_fsync64 函数才可用。

函数:int aio_fsync (int op, struct aiocb *aiocbp)

Preliminary: | MT-Safe | AS-Unsafe lock heap | AC-Unsafe lock mem | See POSIX Safety Concepts.

调用此函数会强制在函数调用时对文件描述符 aiocbp->aio_fildes 进行操作的所有 I/O 操作进入同步 I/O 完成状态(请参阅同步 I/O 操作)。aio_fsync 函数会立即返回,但通过 aiocbp->aio_sigevent 中描述的方法的通知只有在对该文件描述符的所有请求都已终止并且文件已同步后才会发生。这也意味着在同步请求之后排队的相同文件描述符的请求不受影响。

如果 op 为 O_DSYNC,则同步发生与调用 fdatasync 一样。否则 op 应该是 O_SYNC 并且同步发生与 fsync 一样。

只要没有发生同步,使用对 aiocbp 指向的对象的引用调用 aio_error 就会返回 EINPROGRESS。同步完成后,如果同步不成功,则 aio_error 返回 0。否则,返回的值是 fsync 或 fdatasync 函数设置 errno 变量的值。在这种情况下,无法假设写入此文件描述符的数据的一致性。

如果请求成功入队,则此函数的返回值为 0。否则返回值为 -1 并且 errno 设置为下列值之一:

EAGAIN

由于暂时缺乏资源,无法将请求加入队列。

EBADF

文件描述符 aiocbp->aio_fildes 无效。

EINVAL

该实现不支持 I/O 同步或 op 参数不是 O_DSYNC 和 O_SYNC。

ENOSYS

该功能未实现。

当使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数实际上是 aio_fsync64,因为 LFS 接口透明地替换了正常的实现。

函数:int aio_fsync64 (int op, struct aiocb64 *aiocbp)

Preliminary: | MT-Safe | AS-Unsafe lock heap | AC-Unsafe lock mem | See POSIX Safety Concepts.

此函数类似于 aio_fsync,唯一的区别是参数是对 struct aiocb64 类型变量的引用。

当使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数在名称 aio_fsync 下可用,因此透明地替换了 32 位机器上小文件的接口。

另一种同步方法是等到特定集合的一个或多个请求终止。这可以通过 aio_* 函数来实现,以通知启动进程终止,但在某些情况下,这不是理想的解决方案。在不断更新以某种方式连接到服务器的客户端的程序中,循环并不总是最好的解决方案,因为某些连接可能很慢。另一方面,让 aio_* 函数通知调用者也可能不是最好的解决方案,因为每当进程为客户端准备数据时,被通知中断是没有意义的,因为新客户端不会在为当前客户提供服务。对于这种情况,应该使用 aio_suspend。

函数:int aio_suspend (const struct aiocb *const list[], int nent, const struct timespec *timeout)

Preliminary: | MT-Safe | AS-Unsafe lock | AC-Unsafe lock | See POSIX Safety Concepts.

调用此函数时,调用线程将被挂起,直到数组列表的 nent 个元素所指向的请求中至少有一个完成。如果在调用 aio_suspend 时任何请求已经完成,则该函数立即返回。通过将请求的错误状态与 EINPROGRESS 进行比较来确定请求是否已终止。如果 list 的元素为 NULL,则简单地忽略该条目。

如果没有请求完成,则暂停调用进程。如果 timeout 为 NULL,则在请求完成之前不会唤醒进程。如果 timeout 不为 NULL,则进程保持挂起至少与 timeout 中指定的时间一样长。在这种情况下,aio_suspend 返回错误。

如果列表中的一个或多个请求已终止,则该函数的返回值为 0。否则函数返回 -1 并且 errno 设置为下列值之一:

EAGAIN

列表中的所有请求均未在 timeout 指定的时间内完成。

EINTR

一个信号中断了 aio_suspend 函数。该信号也可能由 AIO 实现发送,同时发出一个请求终止的信号。

ENOSYS

aio_suspend 函数未实现。

当使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数实际上是 aio_suspend64,因为 LFS 接口透明地替换了正常的实现。

函数:int aio_suspend64 (const struct aiocb64 *const list[], int nent, const struct timespec *timeout)

Preliminary: | MT-Safe | AS-Unsafe lock | AC-Unsafe lock | See POSIX Safety Concepts.

此函数类似于 aio_suspend,唯一的区别是参数是对 struct aiocb64 类型变量的引用。

当使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数在名称 aio_suspend 下可用,因此透明地替换了 32 位机器上小文件的接口。

2.11.4. 取消 AIO 操作

Cancellation of AIO Operations

当一个或多个请求被异步处理时,在某些情况下取消选定的操作可能很有用,例如,如果写入的数据很明显不再准确并且必须很快被覆盖。例如,假设一个应用程序在文件中写入数据的情况下,新的传入数据必须写入将由排队请求更新的文件中。POSIX AIO 实现提供了这样的功能,但该功能不能强制取消请求。由实现决定是否可以取消操作。因此使用这个函数只是一个提示。

函数:int aio_cancel (int fildes, struct aiocb *aiocbp)

Preliminary: | MT-Safe | AS-Unsafe lock heap | AC-Unsafe lock mem | See POSIX Safety Concepts.

aio_cancel 函数可用于取消一个或多个未完成的请求。如果 aiocbp 参数为 NULL,该函数将尝试取消所有将处理文件描述符 fildes(即其 aio_fildes 成员为 fildes)的未完成请求。如果 aiocbp 不为 NULL,aio_cancel 会尝试取消 aiocbp 指向的特定请求。

对于成功取消的请求,应发出关于请求终止的正常通知。即,根据控制它的 struct sigevent 对象,什么都不会发生,发送信号或启动线程。如果请求不能被取消,它会在执行操作后以通常的方式终止。

成功取消请求后,以该请求为参数调用 aio_error 将返回 ECANCELED,而对 aio_return 的调用将返回 -1。如果请求未被取消且仍在运行,则错误状态仍为 EINPROGRESS。

如果存在尚未终止且已成功取消的请求,则该函数的返回值为 AIO_CANCELED。如果还有一个或多个请求无法取消,则返回值为 AIO_NOTCANCELED。在这种情况下,必须使用 aio_error 来找出可能是多个请求中的哪一个(如果 aiocbp 为 NULL)未成功取消。如果在调用 aio_cancel 时所有请求都已终止,则返回值为 AIO_ALLDONE。

如果在执行 aio_cancel 期间发生错误,该函数将返回 -1 并将 errno 设置为下列值之一。

EBADF

文件描述符 fildes 无效。

ENOSYS

aio_cancel 未实现。

当使用 _FILE_OFFSET_BITS == 64 编译源时,这个函数实际上是 aio_cancel64,因为 LFS 接口透明地替换了正常的实现。

函数:int aio_cancel64 (int fildes, struct aiocb64 *aiocbp)

Preliminary: | MT-Safe | AS-Unsafe lock heap | AC-Unsafe lock mem | See POSIX Safety Concepts.

此函数类似于 aio_cancel,唯一的区别是参数是对 struct aiocb64 类型变量的引用。

当使用 _FILE_OFFSET_BITS == 64 编译源代码时,此函数在名称 aio_cancel 下可用,因此透明地替换了 32 位机器上小文件的接口。

2.11.5. 如何优化AIO实现

How to optimize the AIO implementation

POSIX 标准没有指定如何实现 AIO 功能。它们可以是系统调用,但也可以在用户级别模拟它们。

在撰写本文时,可用的实现是用户级实现,它使用线程来处理排队的请求。虽然这个实现需要对限制做出一些决定,但在 GNU C 库中最好避免硬限制。因此,GNU C 库提供了一种根据个人用途调整 AIO 实现的方法。

数据类型:struct aioinit

此数据类型用于将配置或可调参数传递给实现。程序必须初始化该结构的成员并使用 aio_init 函数将其传递给实现。

int aio_threads

该成员指定任何时候可以使用的最大线程数。

int aio_num

这个数字提供了对同时排队请求的最大数量的估计。

int aio_locks

未使用。

int aio_usedba

未使用。

int aio_debug

未使用。

int aio_numusers

未使用。

int aio_reserved[2]

未使用。

函数:void aio_init (const struct aioinit *init)

Preliminary: | MT-Safe | AS-Unsafe lock | AC-Unsafe lock | See POSIX Safety Concepts.

此函数必须在任何其他 AIO 函数之前调用。调用它完全是自愿的,因为它只是为了帮助 AIO 实现更好地执行。

在调用 aio_init 之前,必须初始化 struct aioinit 类型的变量的成员。然后对该变量的引用作为参数传递给 aio_init,它本身可能会或可能不会注意提示。

该函数没有返回值,也没有定义错误情况。它是遵循 Irix 6 中 SGI 实现的提议的扩展。POSIX.1b 或 Unix98 未涵盖它。

2.12. 文件控制操作

Control Operations on Files

本节介绍如何对文件描述符执行各种其他操作,例如查询或设置描述文件描述符状态的标志、操作记录锁等。所有这些操作都由函数 fcntl 执行。

fcntl 函数的第二个参数是一个命令,用于指定要执行的操作。命名与它一起使用的各种标志的函数和宏在头文件 fcntl.h 中声明。其中许多标志也被 open 函数使用;请参阅打开和关闭文件

函数:int fcntl (int filedes, int command, …)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

fcntl 函数对文件描述符filedes 执行command 指定的操作。有些命令需要提供额外的参数。这些附加参数以及返回值和错误条件在各个命令的详细描述中给出。

简而言之,这里是各种命令的列表。

F_DUPFD

复制文件描述符(返回另一个指向同一个打开文件的文件描述符)。请参阅复制描述符

F_GETFD

获取与文件描述符关联的标志。请参阅文件描述符标志

F_SETFD

设置与文件描述符关联的标志。请参阅文件描述符标志。

F_GETFL

获取与打开文件关联的标志。请参阅文件状态标志

F_SETFL

设置与打开文件关联的标志。请参阅文件状态标志。

F_GETLK

测试文件锁。请参阅文件锁

F_SETLK

设置或清除文件锁定。请参阅文件锁。

F_SETLKW

与 F_SETLK 类似,但要等待完成。请参阅文件锁。

F_OFD_GETLK

测试打开的文件描述锁。请参阅打开文件描述锁。特定于 Linux。

F_OFD_SETLK

设置或清除打开文件描述锁定。请参阅打开文件描述锁。特定于 Linux。

F_OFD_SETLKW

与 F_OFD_SETLK 类似,但在获得锁之前阻塞。请参阅打开文件描述锁。特定于 Linux。

F_GETOWN

获取进程或进程组 ID 以接收 SIGIO 信号。请参见中断驱动输入。

F_SETOWN

设置进程或进程组 ID 以接收 SIGIO 信号。请参见中断驱动输入

该函数是多线程程序中命令 F_SETLKW(和 LFS 类似的 F_SETLKW64)和 F_OFD_SETLKW 的取消点。如果线程在调用 fcntl 时分配了一些资源(如内存、文件描述符、信号量或其他),则会出现问题。如果线程被取消,这些资源将保持分配状态,直到程序结束。为了避免这种对 fcntl 的调用,应该使用取消处理程序来保护。

2.13. 复制描述符

Duplicating Descriptors

您可以复制一个文件描述符,或分配另一个引用与原始文件相同的打开文件的文件描述符。重复的描述符共享一个文件位置和一组文件状态标志(请参阅文件状态标志),但每个都有自己的一组文件描述符标志(请参阅文件描述符标志)。

复制文件描述符的主要用途是实现输入或输出的重定向:即更改特定文件描述符对应的文件或管道。

您可以使用 fcntl 函数和 F_DUPFD 命令执行此操作,但也有方便的函数 dup 和 dup2 用于复制描​​述符。

fcntl 函数和标志在 fcntl.h 中声明,而 dup 和 dup2 的原型在头文件 unistd.h 中。

函数:int dup(int old)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数将旧描述符复制到第一个可用的描述符编号(第一个当前未打开的编号)。它相当于 fcntl (old, F_DUPFD, 0)。

函数:int dup2(int old,int new)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

该函数将旧描述符复制到新的描述符编号。

如果 old 是一个无效的描述符,那么 dup2 什么也不做;它不会关闭新的。否则,old 的新副本将替换描述符 new 的任何先前含义,就好像 new 先关闭一样。

如果 old 和 new 是不同的数字,并且 old 是一个有效的描述符编号,那么 dup2 等价于:

close (new);
fcntl (old, F_DUPFD, new)

但是, dup2 以原子方式执行此操作;在调用 dup2 的过程中没有任何瞬间关闭 new 并且还不是 old 的副本。

宏:int F_DUPFD

此宏用作 fcntl 的命令参数,以复制作为第一个参数给出的文件描述符。

在这种情况下调用的形式是:

fcntl (old, F_DUPFD, next-filedes)

next-filedes 参数是 int 类型,并指定返回的文件描述符应该是大于或等于该值的下一个可用描述符。

使用此命令从 fcntl 返回的值通常是新文件描述符的值。返回值 -1 表示错误。为此命令定义了以下 errno 错误条件:

EBADF

旧的论点是无效的。

EINVAL

next-filedes 参数无效。

EMFILE

没有更多可用的文件描述符 - 您的程序已经使用了最大值。在 BSD 和 GNU 中,最大值由可以更改的资源限制控制;有关 RLIMIT_NOFILE 限制的更多信息,请参阅限制资源使用

ENFILE 不是 dup2 的可能错误代码,因为 dup2 不会创建文件的新打开;重复的描述符不计入 ENFILE 指示的限制。EMFILE 是可能的,因为它指的是在一个进程中使用的不同描述符数的限制。

这是一个展示如何使用 dup2 进行重定向的示例。通常,标准流(如标准输入)的重定向是由 shell 或类似 shell 的程序在调用其中一个 exec 函数(请参阅执行文件)以在子进程中执行新程序之前完成的。执行新程序时,它会创建并初始化标准流以指向相应的文件描述符,然后再调用其 main 函数。

因此,要将标准输入重定向到文件,shell 可以执行以下操作:

pid = fork ();
if (pid == 0)
  {
    char *filename;
    char *program;
    int file;
    …
    file = TEMP_FAILURE_RETRY (open (filename, O_RDONLY));
    dup2 (file, STDIN_FILENO);
    TEMP_FAILURE_RETRY (close (file));
    execv (program, NULL);
  }

还有一个更详细的示例展示了如何在 Launching Jobs 中的流程管道上下文中实现重定向。

2.14. 文件描述符标志

File Descriptor Flags

文件描述符标志是文件描述符的杂项属性。这些标志与特定的文件描述符相关联,因此如果您从一个文件的单次打开中创建了重复的文件描述符,则每个描述符都有自己的一组标志。

目前只有一个文件描述符标志:FD_CLOEXEC,如果您使用任何 exec… 函数,它会导致描述符关闭(请参阅执行文件)。

本节中的符号在头文件 fcntl.h 中定义。

宏:int F_GETFD

此宏用作 fcntl 的命令参数,以指定它应返回与 filedes 参数关联的文件描述符标志。

fcntl 使用此命令的正常返回值是一个非负数,可以解释为各个标志的按位或(目前只有一个标志可用)。

如果出现错误,fcntl 返回 -1。为此命令定义了以下 errno 错误条件:

EBADF

filedes 参数无效。

宏:int F_SETFD

此宏用作 fcntl 的命令参数,以指定它应该设置与 filedes 参数关联的文件描述符标志。这需要第三个 int 参数来指定新标志,因此调用的形式是:

fcntl (filedes, F_SETFD, new-flags)

使用此命令从 fcntl 的正常返回值是除 -1 以外的未指定值,表示错误。标志和错误条件与 F_GETFD 命令相同。

以下宏被定义用作 fcntl 函数的文件描述符标志。该值是可用作位掩码值的整数常量。

宏:int FD_CLOEXEC

此标志指定在调用 exec 函数时应关闭文件描述符;请参阅执行文件。当分配文件描述符时(如 open 或 dup),该位最初在新文件描述符上被清除,这意味着该描述符将在 exec 之后继续存在于新程序中。

如果要修改文件描述符标志,应使用 F_GETFD 获取当前标志并修改值。不要假设这里列出的标志是唯一实现的标志;您的程序可能会在几年后运行,然后可能会存在更多标志。例如,这是一个设置或清除标志 FD_CLOEXEC 而不更改任何其他标志的函数:

/* Set the FD_CLOEXEC flag of desc if value is nonzero,
   or clear the flag if value is 0.
   Return 0 on success, or -1 on error with errno set. */

int
set_cloexec_flag (int desc, int value)
{
  int oldflags = fcntl (desc, F_GETFD, 0);
  /* If reading the flags failed, return error indication now. */
  if (oldflags < 0)
    return oldflags;
  /* Set just the flag we want to set. */
  if (value != 0)
    oldflags |= FD_CLOEXEC;
  else
    oldflags &= ~FD_CLOEXEC;
  /* Store modified flag word in the descriptor. */
  return fcntl (desc, F_SETFD, oldflags);
}

2.15. 文件状态标志

File Status Flags

文件状态标志用于指定文件打开的属性。与文件描述符标志中讨论的文件描述符标志不同,文件状态标志由由单次打开文件产生的重复文件描述符共享。文件状态标志由打开的标志参数指定;请参阅打开和关闭文件

文件状态标志分为三类,将在以下部分中进行描述。

  • 文件访问模式,指定允许对文件的访问类型:读取、写入或两者兼而有之。它们由 open 设置并由 fcntl 返回,但不能更改。
  • 打开时间标志,控制开放将做什么的细节。这些标志在打开调用后不会保留。
  • I/O 操作模式,影响诸如读取和写入等操作的完成方式。它们由 open 设置,并且可以使用 fcntl 获取或更改。

本节中的符号在头文件 fcntl.h 中定义。

2.15.1. 文件访问模式

File Access Modes

文件访问模式允许文件描述符用于读取、写入或两者都用于读取、写入或两者都不使用。访问模式是在文件打开时确定的,永远不会改变。

宏:int O_RDONLY

打开文件以进行读取访问。

宏:int O_WRONLY

打开文件以进行写访问。

宏:int O_RDWR

打开文件进行读写。

宏:int O_PATH

获取文件的文件描述符,但不要打开文件进行读取或写入。打开文件时会跳过对文件本身的权限检查(但仍需要访问包含它的目录的权限),稍后使用描述符时会检查权限。

例如,此类描述符可以与 fexecve 函数一起使用(请参阅执行文件)。

此访问模式特定于 Linux。在 GNU/Hurd 系统上,可以显式使用 O_EXEC,或者根本不指定访问模式(见下文)。

可移植文件访问模式 O_RDONLY、O_WRONLY 和 O_RDWR 可能与各个位不对应。要使用 fcntl 确定文件访问模式,您必须使用 O_ACCMODE 掩码从检索到的文件状态标志中提取访问模式位。

宏:int O_ACCMODE

该宏是一个掩码,可以与文件状态标志值进行按位与运算,以恢复文件访问模式,假设正在使用标准文件访问模式。

如果使用非标准文件访问模式(例如 O_PATH 或 O_EXEC),使用 O_ACCMODE 进行屏蔽可能会产生不正确的结果。这些非标准访问模式由各个位标识,必须直接检查(无需先使用 O_ACCMODE 进行屏蔽)。

在 GNU/Hurd 系统上(但不是在其他系统上),O_RDONLY 和 O_WRONLY 是独立的位,可以按位或一起进行,并且对于设置或清除任何一个位都是有效的。这意味着 O_RDWR 与 O_RDONLY|O_WRONLY 相同。零文件访问模式是允许的;它不允许对文件进行输入或输出的操作,但允许其他操作,例如 fchmod。在 GNU/Hurd 系统上,由于“只读”或“只写”是用词不当,fcntl.h 为文件访问模式定义了附加名称。

宏:int O_READ

打开文件进行阅读。与 O_RDONLY 相同;仅在 GNU/Hurd 上定义。

宏:int O_WRITE

打开文件进行写入。与 O_WRONLY 相同;仅在 GNU/Hurd 上定义。

宏:int O_EXEC

打开文件执行。仅在 GNU/Hurd 上定义。

2.15.2. 开放时间标志

Open-time Flags

开放时间标志指定影响开放行为方式的选项。一旦文件打开,这些选项就不会保留。O_NONBLOCK 是个例外,它也是一种 I/O 操作模式,因此被保存。请参阅打开和关闭文件,了解如何调用 open。

开放时间标志指定了两种选项。

文件名转换标志影响 open 查找文件名以定位文件的方式,以及是否可以创建文件。
打开时间操作标志指定打开文件一旦打开就会对文件执行的额外操作。
这是文件名翻译标志。

宏:int O_CREAT

如果设置,如果文件不存在,将创建该文件。

宏:int O_EXCL

如果同时设置了 O_CREAT 和 O_EXCL,则如果指定的文件已存在,则打开失败。这保证永远不会破坏现有文件。

O_EXCL标志与O_TMPFILE结合有特殊意义;见下文。

宏:int O_DIRECTORY

如果设置,如果给定名称不是目录名称,则打开操作将失败。对于此错误情况,errno 变量设置为 ENOTDIR。

宏:int O_NOFOLLOW

如果设置,则如果文件名的最后部分引用符号链接,则打开操作将失败。对于这种错误情况,errno 变量设置为 ELOOP。

宏:int O_TMPFILE

如果指定了此标志,则开放族中的函数会创建一个未命名的临时文件。在这种情况下,open 系列函数的路径名参数(请参阅打开和关闭文件)被解释为创建临时文件的目录(从而确定为文件提供存储的文件系统)。O_TMPFILE 标志必须与 O_WRONLY 或 O_RDWR 结合使用,并且需要 mode 参数。

稍后可以使用 linkat 为临时文件命名,将其转换为常规文件。这允许原子创建具有特定文件属性(模式和扩展属性)和文件内容的文件。如果出于安全原因,不希望为文件指定名称,则可以将 O_EXCL 标志与 O_TMPFILE 一起指定。

并非所有内核都支持这个打开标志。如果不支持此标志,则创建未命名临时文件的尝试将失败,并出现 EINVAL 错误。如果底层文件系统不支持 O_TMPFILE 标志,则会导致 EOPNOTSUPP 错误。

O_TMPFILE 标志是 GNU 扩展。

宏:int O_NONBLOCK

这可以防止 open 长时间阻塞打开文件。这仅对某些类型的文件有意义,通常是串口等设备;当它没有意义时,它是无害的并被忽略。通常,在调制解调器报告载波检测之前,打开调制解调器的端口会阻塞;如果指定了 O_NONBLOCK,open 将立即返回,不带载体。

请注意,O_NONBLOCK 标志被重载为 I/O 操作模式和文件名转换标志。这意味着在 open 中指定 O_NONBLOCK 也会设置非阻塞 I/O 模式;请参阅 I/O 操作模式。要在不阻塞的情况下打开文件但执行阻塞的普通 I/O,您必须调用 open 并设置 O_NONBLOCK,然后调用 fcntl 以关闭该位。

宏:int O_NOCTTY

如果命名文件是终端设备,不要让它成为进程的控制终端。有关作为控制终端的含义的信息,请参阅作业控制

在 GNU/Hurd 系统和 4.4 BSD 上,打开文件永远不会使其成为控制终端,并且 O_NOCTTY 为零。但是,GNU/Linux 系统和其他一些系统使用 O_NOCTTY 的非零值并在您打开作为终端设备的文件时设置控制终端;所以为了便于携带,在避免这种情况很重要时使用 O_NOCTTY 。

以下三个文件名转换标志仅存在于 GNU/Hurd 系统上。

宏:int O_IGNORE_CTTY

不要将命名文件识别为控制终端,即使它引用了进程现有的控制终端设备。对新文件描述符的操作永远不会产生作业控制信号。请参阅作业控制

宏:int O_NOLINK

如果命名文件是符号链接,则打开链接本身而不是它所引用的文件。(新文件描述符上的 fstat 将返回链接名称上 lstat 返回的信息。)

宏:int O_NOTRANS

如果指定的文件是专门翻译的,不要调用翻译器。打开翻译器自己看到的裸文件。

开放时间操作标志告诉 open 执行与打开文件无关的其他操作。将它们作为 open 的一部分而不是在单独的调用中执行的原因是 open 可以原子地执行它们。

宏:int O_TRUNC

将文件截断为零长度。此选项仅对常规文件有用,对目录或 FIFO 等特殊文件无效。POSIX.1 要求您打开文件以使用 O_TRUNC 进行写入。在 BSD 和 GNU 中,您必须具有写入文件的权限才能截断它,但您不需要打开以进行写入访问。

这是 POSIX.1 指定的唯一开放时间操作标志。没有充分的理由通过 open 来完成截断,而不是之后调用 ftruncate。O_TRUNC 标志在 ftruncate 发明之前就存在于 Unix 中,并且为了向后兼容而保留。

其余的操作模式是 BSD 扩展。它们仅存在于某些系统上。在其他系统上,这些宏没有定义。

宏:int O_SHLOCK

获取文件的共享锁,与flock 一样。请参阅文件锁

如果指定了 O_CREAT,则在创建文件时自动完成锁定。您可以保证没有其他进程会首先获得新文件的锁定。

宏:int O_EXLOCK

获取文件的排他锁,与flock 一样。请参阅文件锁。这就像 O_SHLOCK 一样是原子的。

2.15.3. I/O 操作模式

I/O Operating Modes

操作模式会影响使用文件描述符的输入和输出操作的工作方式。这些标志由 open 设置,可以使用 fcntl 获取和更改。

宏:int O_APPEND

启用文件附加模式的位。如果设置,则所有写入操作都会在文件末尾写入数据,并对其进行扩展,而不管当前文件位置如何。这是附加到文件的唯一可靠方法。在追加模式下,您可以保证您写入的数据将始终到达文件的当前末尾,而不管其他进程写入文件。相反,如果您只是将文件位置设置为文件末尾并写入,那么另一个进程可以在您设置文件位置之后但在您写入之前扩展文件,导致您的数据出现在文件真正末尾之前的某个位置。

宏:int O_NONBLOCK

为文件启用非阻塞模式的位。如果设置了该位,则如果没有立即可用的输入,对文件的读取请求可以立即返回失败状态,而不是阻塞。同样,如果不能立即写入输出,写入请求也可以立即返回失败状态。

请注意,O_NONBLOCK 标志被重载为 I/O 操作模式和文件名转换标志;请参阅打开时间标志

宏:int O_NDELAY

这是 O_NONBLOCK 的一个过时名称,用于与 BSD 兼容。它不是由 POSIX.1 标准定义的。

其余的操作模式是 BSD 和 GNU 扩展。它们仅存在于某些系统上。在其他系统上,这些宏没有定义。

宏:int O_ASYNC

启用异步输入模式的位。如果设置,则当输入可用时将生成 SIGIO 信号。请参见中断驱动输入

异步输入模式是 BSD 的一个特性。

宏:int O_FSYNC

启用文件同步写入的位。如果设置,每次写入调用将确保数据在返回之前可靠地存储在磁盘上。

同步写入是 BSD 的一项功能。

宏:int O_SYNC

这是 O_FSYNC 的另一个名称。它们具有相同的价值。

宏:int O_NOATIME

如果设置了该位,读取将不会更新文件的访问时间。请参阅文件时间。这由执行备份的程序使用,因此备份文件不算作读取它。只有文件的所有者或超级用户可以使用该位。

这是一个 GNU 扩展。

2.15.4. 获取和设置文件状态标志

Getting and Setting File Status Flags

fcntl 函数可以获取或更改文件状态标志。

宏:int F_GETFL

此宏用作 fcntl 的命令参数,以读取具有描述符文件的打开文件的文件状态标志。

fcntl 使用此命令的正常返回值是一个非负数,可以解释为各个标志的按位或。由于文件访问模式不是单个位值,因此您可以使用 O_ACCMODE 屏蔽返回标志中的其他位以进行比较。

如果出现错误,fcntl 返回 -1。为此命令定义了以下 errno 错误条件:

EBADF

filedes 参数无效。

宏:int F_SETFL

该宏用作 fcntl 的命令参数,为对应于 filedes 参数的打开文件设置文件状态标志。此命令需要第三个 int 参数来指定新标志,因此调用如下所示:

fcntl (filedes, F_SETFL, new-flags)

您不能通过这种方式更改文件的访问模式;即,打开文件描述符是为了读取还是写入。

使用此命令从 fcntl 的正常返回值是除 -1 以外的未指定值,表示错误。错误条件与 F_GETFL 命令相同。

如果要修改文件状态标志,应使用 F_GETFL 获取当前标志并修改值。不要假设这里列出的标志是唯一实现的标志;您的程序可能会在几年后运行,然后可能会存在更多标志。例如,这是一个设置或清除标志 O_NONBLOCK 而不更改任何其他标志的函数:

/* Set the O_NONBLOCK flag of desc if value is nonzero,
   or clear the flag if value is 0.
   Return 0 on success, or -1 on error with errno set. */

int
set_nonblock_flag (int desc, int value)
{
  int oldflags = fcntl (desc, F_GETFL, 0);
  /* If reading the flags failed, return error indication now. */
  if (oldflags == -1)
    return -1;
  /* Set just the flag we want to set. */
  if (value != 0)
    oldflags |= O_NONBLOCK;
  else
    oldflags &= ~O_NONBLOCK;
  /* Store modified flag word in the descriptor. */
  return fcntl (desc, F_SETFL, oldflags);
}

2.16. 文件锁

File Locks

本节描述与进程关联的记录锁。还有一种不同类型的记录锁与打开的文件描述相关联,而不是与进程相关联。请参阅打开文件描述锁

其余的 fcntl 命令用于支持记录锁定,这允许多个协作程序相互防止以容易出错的方式同时访问文件的某些部分。

独占锁或写锁为进程提供了对文件指定部分的独占访问权限。当写锁到位时,没有其他进程可以锁定文件的该部分。

共享锁或读锁禁止任何其他进程在文件的指定部分请求写锁。但是,其他进程可以请求读锁。

read 和 write 函数实际上并不检查是否有任何锁到位。如果您想为多个进程共享的文件实现锁定协议,您的应用程序必须执行显式 fcntl 调用以在适当的点请求和清除锁定。

锁与进程相关联。一个进程只能为给定文件的每个字节设置一种锁。当该文件的任何文件描述符被进程关闭时,该进程对该文件持有的所有锁都将被释放,即使这些锁是使用其他保持打开状态的描述符创建的。同样,锁在进程退出时被释放,并且不会被使用 fork 创建的子进程继承(请参阅创建进程)。

做锁的时候,使用structflock来指定锁的种类和位置。此数据类型和 fcntl 函数的相关宏在头文件 fcntl.h 中声明。

数据类型:struct flock

此结构与 fcntl 函数一起用于描述文件锁。它有这些成员:

short int l_type

指定锁的类型;F_RDLCK、F_WRLCK 或 F_UNLCK 之一。

short int l_whence

这对应于 fseek 或 lseek 的 whence 参数,并指定偏移量相对于什么。它的值可以是 SEEK_SET、SEEK_CUR 或 SEEK_END 之一。

off_t l_start

这指定了应用锁的区域开始的偏移量,并以相对于 l_whence 成员指定的点的字节给出。

off_t l_len

这指定要锁定的区域的长度。值 0 被特殊处理;这意味着该区域延伸到文件的末尾。

pid_t l_pid

该字段是持有锁的进程的进程 ID(参见进程创建概念)。它是通过使用 F_GETLK 命令调用 fcntl 来填充的,但在生成锁时会被忽略。如果冲突锁是打开的文件描述锁(请参阅打开文件描述锁),则该字段将设置为 -1。

宏:int F_GETLK

此宏用作 fcntl 的命令参数,以指定它应该获取有关锁的信息。该命令需要将第三个 struct flock * 类型的参数传递给 fcntl,因此调用的形式为:

fcntl (filedes, F_GETLK, lockp)

如果已经有一个锁会阻塞由 lockp 参数描述的锁,则有关该锁的信息将覆盖 *lockp。如果现有锁与指定的新锁兼容,则不会报告现有锁。因此,如果您想了解读写锁,则应指定锁类型 F_WRLCK,如果只想了解写锁,则应指定 F_RDLCK。

可能有多个锁影响 lockp 参数指定的区域,但 fcntl 仅返回有关其中之一的信息。lockp 结构的 l_whence 成员设置为 SEEK_SET,设置 l_start 和 l_len 字段以标识锁定区域。

如果没有应用锁,则对 lockp 结构的唯一更改是将 l_type 更新为 F_UNLCK 的值。

使用此命令从 fcntl 正常返回的值是 -1 以外的未指定值,该值被保留以指示错误。为此命令定义了以下 errno 错误条件:

EBADF

filedes 参数无效。

EINVAL

lockp 参数没有指定有效的锁信息,或者与filedes关联的文件不支持锁。

宏:int F_SETLK

此宏用作 fcntl 的命令参数,以指定它应该设置或清除锁定。该命令需要将第三个 struct flock * 类型的参数传递给 fcntl,因此调用的形式为:

fcntl (filedes, F_SETLK, lockp)

如果进程已经在该区域的任何部分上拥有锁,则该部分上的旧锁将被新锁替换。您可以通过指定锁定类型 F_UNLCK 来移除锁定。

如果无法设置锁,则 fcntl 立即返回值 -1。该函数在等待其他进程释放锁时不会阻塞。如果 fcntl 成功,它返回一个非 -1 的值。

为此函数定义了以下 errno 错误条件:

EAGAIN

EACCES

无法设置锁定,因为它被文件上的现有锁定阻止。在这种情况下,一些系统使用 EAGAIN,而其他系统使用 EACCES;在 F_SETLK 之后,您的程序应该对它们一视同仁。(GNU/Linux 和 GNU/Hurd 系统总是使用 EAGAIN。)

EBADF

要么:filedes 参数无效;要么您请求了读取锁定,但文件未打开以进行读取访问;或者,您请求了写锁定,但文件未打开以进行写访问。

EINVAL

lockp 参数没有指定有效的锁信息,或者与filedes关联的文件不支持锁。

ENOLCK

系统文件锁资源耗尽;已经有太多的文件锁到位。

设计良好的文件系统永远不会报告这个错误,因为它们对锁的数量没有限制。但是,您仍然必须考虑到此错误的可能性,因为它可能是由于网络访问另一台机器上的文件系统而导致的。

宏:int F_SETLKW

此宏用作 fcntl 的命令参数,以指定它应该设置或清除锁定。它就像 F_SETLK 命令,但会导致进程阻塞(或等待),直到可以指定请求为止。

与 F_SETLK 命令一样,该命令需要 struct flock * 类型的第三个参数。

fcntl 返回值和错误与 F_SETLK 命令相同,但为该命令定义了这些额外的 errno 错误条件:

EINTR

该函数在等待时被信号中断。请参阅被信号中断的原语

EDEADLK

指定区域正被另一个进程锁定。但是该进程正在等待锁定当前进程已锁定的区域,因此等待锁定会导致死锁。系统不保证它会检测到所有此类情况,但它会让您知道它是否注意到了一种情况。

下面的宏被定义为 flock 结构的 l_type 成员的值。这些值是整数常量。

F_RDLCK

此宏用于指定读取(或共享)锁。

F_WRLCK

此宏用于指定写(或排他)锁。

F_UNLCK

此宏用于指定区域已解锁。

作为文件锁定有用的示例,考虑一个可以由多个不同用户同时运行的程序,它将状态信息记录到一个公共文件中。此类程序的一个示例可能是使用文件来跟踪高分的游戏。另一个示例可能是记录使用或记帐信息以用于计费目的的程序。

将程序的多个副本同时写入文件可能会导致文件内容混淆。但是您可以通过在实际写入文件之前对文件设置写锁定来防止此类问题。

如果程序还需要读取文件,并且想确保文件内容处于一致状态,那么也可以使用读锁。设置读锁后,没有其他进程可以锁定文件的该部分以进行写入。

请记住,文件锁只是用于控制对文件的访问的建议协议。不使用锁定协议的程序仍有可能访问该文件。

2.17. 打开文件描述锁

Open File Description Locks

与进程关联的记录锁(请参阅文件锁)相比,打开文件描述记录锁与打开文件描述相关联,而不是与进程相关联。

使用 fcntl 在已经具有通过相同文件描述符创建的现有打开文件描述锁定的区域上应用打开文件描述锁定将永远不会导致锁定冲突。

打开的文件描述锁也由子进程通过 fork 继承,或者使用 CLONE_FILES 集进行克隆(请参阅创建进程)以及文件描述符。

区分打开文件描述(打开文件的一个实例,通常由对 open 的调用创建)和打开文件描述符是很重要的,打开文件描述符是指打开文件描述的数值。此处描述的锁与打开的文件描述相关联,而不是与打开的文件描述符相关联。

使用 dup(请参阅复制描述符)复制文件描述符不会为您提供新的打开文件描述,而是复制对现有打开文件描述的引用并将其分配给新文件描述符。因此,在由 dup 克隆的文件描述符上设置的打开文件描述锁永远不会与在原始描述符上设置的打开文件描述锁冲突,因为它们引用相同的打开文件描述。但是,根据所涉及的锁的范围和类型,在这种情况下,可以通过 F_OFD_SETLK 或 F_OFD_SETLKW 命令修改原始锁。

打开文件描述锁总是与进程相关的锁冲突,即使是由同一个进程或在同一个打开文件描述符上获取的。

打开文件描述锁使用与进程关联的锁相同的结构群作为参数(请参阅文件锁),并且命令值的宏也在头文件 fcntl.h 中声明。要使用它们,必须在包含任何头文件之前定义宏 _GNU_SOURCE。

与进程相关的锁相比,任何用作打开文件描述锁定命令的参数的 structflock 都必须将 l_pid 值设置为 0。此外,当在 F_GETLK 或 F_OFD_GETLK 请求中返回有关打开文件描述锁定的信息时,l_pid structflock 中的字段将被设置为 -1 以指示锁与进程无关。

当同一个 structflock 在用于 F_OFD_GETLK 请求后被重新用作 F_OFD_SETLK 或 F_OFD_SETLKW 请求的参数时,需要检查 l_pid 字段并将其重置为 0。

宏:int F_OFD_GETLK

此宏用作 fcntl 的命令参数,以指定它应该获取有关锁的信息。该命令需要将第三个 struct flock * 类型的参数传递给 fcntl,因此调用的形式为:

fcntl (filedes, F_OFD_GETLK, lockp)

如果已经有一个锁会阻塞由 lockp 参数描述的锁,则有关该锁的信息将写入 *lockp。如果现有锁与指定的新锁兼容,则不会报告现有锁。因此,如果您想了解读写锁,则应指定锁类型 F_WRLCK,如果只想了解写锁,则应指定 F_RDLCK。

可能有多个锁影响 lockp 参数指定的区域,但 fcntl 仅返回有关其中之一的信息。在这种情况下返回哪个锁是未定义的。

lockp 结构的 l_whence 成员设置为 SEEK_SET,并且设置 l_start 和 l_len 字段以标识锁定区域。

如果不存在冲突锁,则对 lockp 结构的唯一更改是将 l_type 字段更新为值 F_UNLCK。

使用此命令从 fcntl 的正常返回值是 0(成功)或 -1(表示错误)。为此命令定义了以下 errno 错误条件:

EBADF

filedes 参数无效。

EINVAL

lockp 参数没有指定有效的锁信息,操作系统内核不支持打开文件描述锁,或者与filedes关联的文件不支持锁。

宏:int F_OFD_SETLK

此宏用作 fcntl 的命令参数,以指定它应该设置或清除锁定。该命令需要将第三个 struct flock * 类型的参数传递给 fcntl,因此调用的形式为:

fcntl (filedes, F_OFD_SETLK, lockp)

如果打开的文件已经对该区域的任何部分进行了锁定,则该部分上的旧锁将被新锁替换。您可以通过指定锁定类型 F_UNLCK 来移除锁定。

如果无法设置锁,则 fcntl 立即返回值 -1。该命令不等待其他任务释放锁。如果 fcntl 成功,则返回 0。

为此命令定义了以下 errno 错误条件:

EAGAIN

无法设置锁定,因为它被文件上的现有锁定阻止。

EBADF

要么:filedes 参数无效;要么您请求了读取锁定,但文件未打开以进行读取访问;或者,您请求了写锁定,但文件未打开以进行写访问。

EINVAL

lockp 参数没有指定有效的锁信息,操作系统内核不支持打开文件描述锁,或者与filedes关联的文件不支持锁。

ENOLCK

系统文件锁资源耗尽;已经有太多的文件锁到位。

设计良好的文件系统永远不会报告这个错误,因为它们对锁的数量没有限制。但是,您仍然必须考虑到此错误的可能性,因为它可能是由于网络访问另一台机器上的文件系统而导致的。

宏:int F_OFD_SETLKW

此宏用作 fcntl 的命令参数,以指定它应该设置或清除锁定。它就像 F_OFD_SETLK 命令一样,但会导致进程等待,直到请求可以完成。

与 F_OFD_SETLK 命令一样,该命令需要 struct flock * 类型的第三个参数。

fcntl 返回值和错误与 F_OFD_SETLK 命令相同,但为该命令定义了这些额外的 errno 错误条件:

EINTR

该函数在等待时被信号中断。请参阅被信号中断的原语

打开文件描述锁在与进程关联锁相同的情况下很有用。它们还可以用于同步同一进程内的线程之间的文件访问,方法是让每个线程执行自己的文件打开,以获得自己的打开文件描述。

因为打开文件描述锁仅在关闭引用打开文件描述的最后一个文件描述符时自动释放,所以这种锁定机制避免了由于库例程在应用程序不知道的情况下打开和关闭文件而无意释放锁的可能性。

与进程相关的锁一样,打开文件描述锁是建议性的。

2.18. 打开文件描述锁示例

Open File Description Locks Example

这是在线程程序中使用打开文件描述锁的示例。如果这个程序使用了进程关联锁,那么它会受到数据损坏,因为进程关联锁由进程内的线程共享,因此不能被一个线程用来锁定同一进程中的另一个线程。

为简洁起见,以下程序中省略了适当的错误处理。

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>

#define FILENAME        "/tmp/foo"
#define NUM_THREADS     3
#define ITERATIONS      5

void *
thread_start (void *arg)
{
  int i, fd, len;
  long tid = (long) arg;
  char buf[256];
  struct flock lck = {
    .l_whence = SEEK_SET,
    .l_start = 0,
    .l_len = 1,
  };

  fd = open ("/tmp/foo", O_RDWR | O_CREAT, 0666);

  for (i = 0; i < ITERATIONS; i++)
    {
      lck.l_type = F_WRLCK;
      fcntl (fd, F_OFD_SETLKW, &lck);

      len = sprintf (buf, "%d: tid=%ld fd=%d\n", i, tid, fd);

      lseek (fd, 0, SEEK_END);
      write (fd, buf, len);
      fsync (fd);

      lck.l_type = F_UNLCK;
      fcntl (fd, F_OFD_SETLK, &lck);

      /* sleep to ensure lock is yielded to another thread */
      usleep (1);
    }
  pthread_exit (NULL);
}

int
main (int argc, char **argv)
{
  long i;
  pthread_t threads[NUM_THREADS];

  truncate (FILENAME, 0);

  for (i = 0; i < NUM_THREADS; i++)
    pthread_create (&threads[i], NULL, thread_start, (void *) i);

  pthread_exit (NULL);
  return 0;
}

此示例创建三个线程,每个线程循环五次,附加到文件中。通过打开文件描述锁对文件的访问进行序列化。如果我们编译并运行上面的程序,我们最终会得到 /tmp/foo,其中有 15 行。

然而,如果我们将 F_OFD_SETLK 和 F_OFD_SETLKW 命令替换为它们与进程相关的锁等效项,则锁定本质上变成了一个 noop,因为它都是在同一个进程的上下文中完成的。这会导致数据损坏(通常表现为缺少行),因为一些线程争相进入并覆盖其他人写入的数据。

2.19. 中断驱动输入

Interrupt-Driven Input

如果您在文件描述符上设置 O_ASYNC 状态标志(请参阅文件状态标志),则只要该文件描述符上的输入或输出成为可能,就会发送 SIGIO 信号。可以使用 fcntl 函数的 F_SETOWN 命令来选择接收信号的进程或进程组。如果文件描述符是一个套接字,这也会选择当带外数据到达该套接字时传递的 SIGURG 信号的接收者;请参阅带外数据。(SIGURG 在 select 报告套接字具有“异常情况”的任何情况下发送。请参阅等待输入或输出。)

如果文件描述符对应一个终端设备,那么 SIGIO 信号被发送到终端的前台进程组。请参阅作业控制

本节中的符号在头文件 fcntl.h 中定义。

宏:int F_GETOWN

此宏用作 fcntl 的命令参数,以指定它应该获取有关发送 SIGIO 信号的进程或进程组的信息。(对于终端,这实际上是前台进程组 ID,您可以使用 tcgetpgrp 获得;请参阅控制终端访问的函数。)

返回值被解释为进程 ID;如果为负,则其绝对值为进程组 ID。

为此命令定义了以下 errno 错误条件:

EBADF

filedes 参数无效。

宏:int F_SETOWN

此宏用作 fcntl 的命令参数,以指定它应设置向其发送 SIGIO 信号的进程或进程组。此命令需要将 pid_t 类型的第三个参数传递给 fcntl,因此调用的形式为:

fcntl (filedes, F_SETOWN, pid)

pid 参数应该是进程 ID。您还可以传递一个绝对值为进程组 ID 的负数。

使用此命令从 fcntl 返回的值为 -1 以防出错,如果成功则为其他值。为此命令定义了以下 errno 错误条件:

EBADF

filedes 参数无效。

ESRCH

没有对应pid的进程或进程组。

2.20. 通用 I/O 控制操作

Generic I/O Control operations
GNU 系统可以根据一些文件原语——读、写和 lseek 来处理许多不同设备和对象上的大多数输入/输出操作。但是,大多数设备也有一些不适合此模型的特殊操作。如:

  • 更改终端上使用的字符字体。
  • 告诉磁带系统倒带或快进。(因为它们不能以字节增量移动,所以 lseek 不适用)。
  • 从驱动器中弹出磁盘。
  • 播放 CD-ROM 驱动器中的音轨。
  • 维护网络的路由表。

尽管某些此类对象(例如套接字和端子 3)具有它们自己的特殊功能,但为所有这些情况创建功能并不实际。

相反,这些称为 IOCTL 的次要操作被分配了代码编号并通过 sys/ioctl.h 中定义的 ioctl 函数进行多路复用。代码编号本身在许多不同的标题中定义。

函数:int ioctl (int filedes, int command, …)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

ioctl 函数对文件执行通用 I/O 操作命令。

通常存在第三个参数,可以是单个数字,也可以是指向结构的指针。此参数、返回值和任何错误代码的含义取决于所使用的命令。通常返回 -1 表示失败。

在某些系统上,不同设备使用的 IOCTL 共享相同的编号。因此,尽管使用不适当的 IOCTL 通常只会产生错误,但您不应尝试在未知设备上使用特定于设备的 IOCTL。

大多数 IOCTL 是特定于操作系统的和/或仅用于特殊的系统实用程序,因此超出了本文档的范围。有关使用 IOCTL 的示例,请参阅带外数据

3. 参考

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值