fcntl() - Unix,Linux系统调用
广告
名称
fcntl - 操作文件描述符
概要
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
|
描述
fcntl() 对打开的文件描述符 fd 执行以下描述的操作之一。操作由 cmd 确定。
复制文件描述符
标签 | 描述 |
F_DUPFD | 找到大于或等于 arg 的最小可用文件描述符,并使其成为 fd 的副本。这与dup2(2) 不同,后者使用指定的精确描述符。 成功时,返回新的描述符。 更多详情请参见 dup(2)。 |
文件描述符标志
以下命令操作与文件描述符关联的标志。目前,只定义了一个这样的标志:FD_CLOEXEC,即关闭执行标志。如果FD_CLOEXEC 位为 0,则文件描述符将在execve(2) 中保持打开状态,否则它将被关闭。
标签 | 描述 |
F_GETFD | 读取文件描述符标志。 |
F_SETFD | 将文件描述符标志设置为 arg 指定的值。 |
文件状态标志
每个打开的文件描述符都有一些关联的状态标志,由open(2) 初始化,并可能由fcntl(2) 修改。复制的文件描述符(由dup()、fcntl(F_DUPFD)、fork() 等创建)引用相同的文件描述符,因此共享相同的文件状态标志。
文件状态标志及其语义在open(2) 中进行了描述。
标签 | 描述 |
F_GETFL | 读取文件状态标志。 |
F_SETFL | 将文件状态标志设置为 arg 指定的值。arg 中的文件访问模式(O_RDONLY、O_WRONLY、O_RDWR)和文件创建标志(即O_CREAT、O_EXCL、O_NOCTTY、O_TRUNC)将被忽略。在 Linux 中,此命令只能更改O_APPEND、O_ASYNC、O_DIRECT、O_NOATIME 和O_NONBLOCK 标志。 |
建议锁
F_GETLK、F_SETLK 和F_SETLKW 用于获取、释放和测试记录锁(也称为文件段锁或文件区域锁)的存在。第三个参数 lock 是指向至少具有以下字段(顺序未指定)的结构体的指针。
struct flock {
...
short l_type; /* Type of lock: F_RDLCK,
F_WRLCK, F_UNLCK */
short l_whence; /* How to interpret l_start:
SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start; /* Starting offset for lock */
off_t l_len; /* Number of bytes to lock */
pid_t l_pid; /* PID of process blocking our lock
(F_GETLK only) */
...
};
|
此结构体的 l_whence、l_start 和 l_len 字段指定我们希望锁定的字节范围。l_start 是锁定的起始偏移量,相对于以下任何一个进行解释:文件的开头(如果 l_whence 为SEEK_SET);当前文件偏移量(如果 l_whence 为SEEK_CUR);或文件的结尾(如果 l_whence 为SEEK_END)。在最后两种情况下,只要偏移量不位于文件的开头之前,l_start 可以为负数。l_len 是一个非负整数(但请参见下面的注释),指定要锁定的字节数。可以锁定文件末尾之后的字节,但不能锁定文件开头之前的字节。将 l_len 指定为 0 具有特殊含义:锁定从 l_whence 和 l_start 指定的位置开始到文件结尾的所有字节,无论文件增长多大。
l_type 字段可以用于在文件上放置读锁(F_RDLCK)或写锁(F_WRLCK)。任何数量的进程都可以在文件区域上持有读锁(共享锁),但只有一个进程可以持有写锁(独占锁)。独占锁排斥所有其他锁,包括共享锁和独占锁。单个进程只能在一个文件区域上持有锁的一种类型;如果将新的锁应用于已锁定的区域,则现有锁将转换为新的锁类型。(如果新锁指定的字节范围与现有锁的范围不完全一致,则此类转换可能涉及拆分、缩小或与现有锁合并。)
标签 | 描述 |
F_SETLK | 获取锁(当 l_type 为F_RDLCK 或F_WRLCK 时)或释放锁(当 l_type 为F_UNLCK 时),锁定的字节由 lock 的 l_whence、l_start 和 l_len 字段指定。如果另一个进程持有冲突的锁,则此调用返回 -1 并将 errno 设置为EACCES 或EAGAIN。 |
F_SETLKW | 与F_SETLK 相同,但是如果在文件上持有冲突的锁,则等待该锁释放。如果在等待时捕获到信号,则调用被中断,并(在信号处理程序返回后)立即返回(返回值为 -1,errno 设置为EINTR)。 |
F_GETLK | 对于此调用的输入,lock 描述我们想在文件上放置的锁。如果可以放置锁,fcntl() 不会实际放置它,而是在 lock 的 l_type 字段中返回F_UNLCK,并使结构体的其他字段保持不变。如果一个或多个不兼容的锁会阻止放置此锁,则fcntl() 会在 lock 的 l_type、l_whence、l_start 和 l_len 字段中返回其中一个锁的详细信息,并将 l_pid 设置为持有该锁的进程的 PID。 |
为了放置读锁,fd 必须以读取模式打开。为了放置写锁,fd 必须以写入模式打开。要放置两种类型的锁,请以读写模式打开文件。 记录锁除了通过显式的F_UNLCK删除外,还会在进程终止或关闭任何引用持有锁的文件的文件描述符时自动释放。这很糟糕:这意味着进程可能会丢失对诸如 /etc/passwd 或 /etc/mtab 等文件上的锁,因为某些库函数决定打开、读取和关闭它。
记录锁不会被通过fork(2) 创建的子进程继承,但会保留在execve(2) 中。
由于stdio(3) 库执行了缓冲,因此应避免在该包中的例程中使用记录锁定;而应该使用read(2) 和write(2)。
强制锁定(非 POSIX。)上述记录锁可以是建议锁或强制锁,默认情况下为建议锁。
建议锁不会被强制执行,仅在合作进程之间有用。
强制锁对所有进程强制执行。如果进程试图对具有不兼容强制锁的文件区域执行不兼容的访问(例如,read(2) 或write(2)),则结果取决于其打开的文件描述符是否启用了O_NONBLOCK标志。如果未启用O_NONBLOCK标志,则系统调用将被阻塞,直到锁被移除或转换为与访问兼容的模式。如果启用了O_NONBLOCK标志,则系统调用将失败,并出现EAGAIN或EWOULDBLOCK错误。
要使用强制锁,必须在包含要锁定的文件的系统和文件本身上启用强制锁定。使用mount(8) 的“-o mand”选项或mount(2) 的MS_MANDLOCK标志可以在文件系统上启用强制锁定。通过禁用文件的组执行权限并启用设置组 ID 权限位(请参见chmod(1) 和chmod(2))可以在文件上启用强制锁定。
管理信号
F_GETOWN、F_SETOWN、F_GETSIG 和F_SETSIG 用于管理 I/O 可用性信号
标签 | 描述 |
F_GETOWN | 获取当前接收文件描述符 fd 上事件的 SIGIO 和 SIGURG 信号的进程 ID 或进程组 ID。进程 ID 以正值返回;进程组 ID 以负值返回(但请参见下面的错误)。 |
F_SETOWN | 设置将接收文件描述符 fd 上事件的 SIGIO 和 SIGURG 信号的进程 ID 或进程组 ID。进程 ID 指定为正值;进程组 ID 指定为负值。最常见的情况是,调用进程将自身指定为所有者(即,arg 指定为getpid())。 如果在文件描述符上设置了O_ASYNC状态标志(通过使用open(2)调用提供此标志,或使用fcntl()的F_SETFL命令),则只要该文件描述符上的输入或输出成为可能,就会发送 SIGIO 信号。F_SETSIG 可用于获取除 SIGIO 之外的信号的传递。如果此权限检查失败,则信号将被静默丢弃。 发送信号到F_SETOWN指定的拥有者进程(组)受与kill(2)中描述的相同的权限检查约束,其中发送进程是使用F_SETOWN的进程(但请参见下面的错误)。 如果文件描述符 fd 指向套接字,F_SETOWN还会选择在套接字上到达带外数据时传递 SIGURG 信号的接收者。(在select(2) 将套接字报告为具有“异常情况”的任何情况下都会发送 SIGURG。) 如果在支持线程组的多线程进程(例如,NPTL)中将非零值赋给F_SETSIG,则赋给F_SETOWN的正值具有不同的含义:它不再是标识整个进程的进程 ID,而是标识进程内特定线程的线程 ID。因此,当使用F_SETSIG时,可能需要将gettid()的结果而不是getpid()传递给F_SETOWN才能获得合理的结果。(在当前的 Linux 线程实现中,主线程的线程 ID 与其进程 ID 相同。这意味着单线程程序在这种情况下可以使用gettid()或getpid()。)但是,请注意,本段中的语句不适用于为套接字上的带外数据生成的 SIGURG 信号:此信号始终发送到进程或进程组,具体取决于赋给F_SETOWN的值。另请注意,Linux 对可能排队到进程的实时信号数量有限制(参见getrlimit(2) 和signal(7)),如果达到此限制,则内核会恢复为传递 SIGIO,并且此信号将传递到整个进程而不是特定线程。 |
F_GETSIG | 获取输入或输出变为可能时发送的信号。值为零表示发送 SIGIO。任何其他值(包括 SIGIO)都是要发送的信号,在这种情况下,如果使用 SA_SIGINFO 安装,则信号处理程序可以获得其他信息。 |
F_SETSIG | 设置输入或输出变为可能时发送的信号。值为零表示发送默认的 SIGIO 信号。任何其他值(包括 SIGIO)都是要发送的信号,在这种情况下,如果使用 SA_SIGINFO 安装,则信号处理程序可以获得其他信息。 此外,将非零值传递给F_SETSIG会将信号接收者从整个进程更改为进程内的特定线程。有关更多详细信息,请参见F_SETOWN的说明。 通过使用非零值的F_SETSIG,并为信号处理程序设置 SA_SIGINFO(参见sigaction(2)),有关 I/O 事件的额外信息将通过siginfo_t 结构传递给处理程序。如果si_code字段指示源是 SI_SIGIO,则si_fd字段给出与事件关联的文件描述符。否则,不会指示哪些文件描述符处于挂起状态,您应该使用通常的机制(select(2)、poll(2)、设置了O_NONBLOCK的read(2) 等)来确定哪些文件描述符可用于 I/O。 通过选择实时信号(值 >= SIGRTMIN),可以使用相同的信号编号排队多个 I/O 事件。(排队取决于可用内存)。如果为信号处理程序设置了 SA_SIGINFO,则可以获得额外信息,如上所述。 |
使用这些机制,程序可以实现完全异步的 I/O,而无需大多数时候使用select(2) 或poll(2)。
O_ASYNC、F_GETOWN、F_SETOWN 的使用特定于 BSD 和 Linux。F_GETSIG 和F_SETSIG 是 Linux 特定的。POSIX 具有异步 I/O 和aio_sigevent 结构来实现类似的功能;这些功能也作为 GNU C 库 (Glibc) 的一部分在 Linux 中可用。
租约
F_SETLEASE 和F_GETLEASE(Linux 2.4 及更高版本)分别用于建立和检索调用进程对fd引用的文件的租约的当前设置。文件租约提供了一种机制,通过该机制,持有租约的进程(“租约持有者”)会在进程(“租约破坏者”)尝试open(2) 或truncate(2) 该文件时收到通知(通过传递信号)。
标签 | 描述 |
F_SETLEASE | 根据整数arg中指定的以下值之一设置或删除文件租约
标签 | 描述 |
F_RDLCK | 获得读取租约。这将导致调用进程在文件以写入方式打开或被截断时收到通知。读取租约只能放在以只读方式打开的文件描述符上。 |
F_WRLCK | 获得写入租约。这将导致调用者在文件以读取或写入方式打开或被截断时收到通知。只有在当前没有其他进程打开文件的情况下,才能在文件上放置写入租约。 |
F_UNLCK | 从文件中删除我们的租约。 |
|
进程在一个文件上只能持有(拥有)一种类型的租约。 |
租约只能在常规文件上获得。非特权进程只能在 UID 与进程的文件系统 UID 匹配的文件上获得租约。具有CAP_LEASE 功能的进程可以在任意文件上获得租约。 |
F_GETLEASE | 通过返回F_RDLCK、F_WRLCK 或F_UNLCK 来指示我们在fd引用的文件上持有什么类型的租约,分别表示调用进程在文件上持有读取租约、写入租约或没有租约。(省略fcntl() 的第三个参数。) |
当进程(“租约破坏者”)执行与通过F_SETLEASE 建立的租约冲突的open() 或truncate() 时,系统调用将被内核阻塞,并且内核通过向其发送信号(默认情况下为 SIGIO)来通知租约持有者。租约持有者应该响应收到此信号,执行准备让另一个进程访问文件所需的任何清理工作(例如,刷新缓存缓冲区),然后删除或降低其租约。通过执行指定arg 为F_UNLCK 的F_SETLEASE 命令来删除租约。如果我们当前在文件上持有写入租约,并且租约破坏者正在打开文件以进行读取,那么将租约降低到读取租约就足够了。这是通过执行指定arg 为F_RDLCK 的F_SETLEASE 命令来完成的。
如果租约持有者未能在/proc/sys/fs/lease-break-time中指定的时间(秒)内降低或删除租约,则内核将强制删除或降低租约持有者的租约。
一旦租约被自愿或强制删除或降低,并且假设租约破坏者尚未取消其系统调用的阻塞,内核就会允许租约破坏者的系统调用继续进行。
如果租约破坏者的阻塞open() 或truncate() 被信号处理程序中断,则系统调用将失败并出现EINTR错误,但其他步骤仍按上述说明进行。如果租约破坏者在open() 或truncate() 中被阻塞时被信号杀死,则其他步骤仍按上述说明进行。如果租约破坏者在调用open() 时指定了O_NONBLOCK标志,则调用将立即失败并出现EWOULDBLOCK错误,但其他步骤仍按上述说明进行。
用于通知租约持有者的默认信号是 SIGIO,但这可以使用fcntl() 的F_SETSIG 命令更改。如果执行了F_SETSIG 命令(甚至指定 SIGIO 的命令),并且使用 SA_SIGINFO 建立了信号处理程序,则处理程序将接收siginfo_t 结构作为其第二个参数,并且此参数的si_fd 字段将保存已被另一个进程访问的租约文件的描述符。(如果调用者对多个文件持有租约,这将很有用。)
文件和目录更改通知 (dnotify)
标签 | 描述 |
F_NOTIFY | (Linux 2.4 及更高版本) 当fd引用的目录或其包含的任何文件发生更改时提供通知。要通知的事件在arg中指定,arg 是通过将以下一个或多个位进行 OR 运算而指定的位掩码
位 | 描述(目录中的事件) |
DN_MODIFY | 文件被修改(写入、pwrite、 |
| writev、truncate、ftruncate) |
DN_CREATE | 文件被创建(open、creat、mknod、 |
| mkdir、link、symlink、rename) |
DN_DELETE | 文件被取消链接(unlink、将 rename 到 |
| 另一个目录、rmdir) |
DN_RENAME | 文件在这个目录中被重命名(rename) |
| 目录 (rename) |
DN_ATTRIB | 文件的属性被更改 |
| (chown、chmod、utime[s]) |
(为了获得这些定义,必须定义 _GNU_SOURCE 功能测试宏。)
目录通知通常是“一次性”的,应用程序必须重新注册才能接收进一步的通知。或者,如果arg中包含DN_MULTISHOT,则通知将一直有效,直到显式删除。 一系列F_NOTIFY请求是累积的,arg中的事件将添加到已监视的集合中。要禁用所有事件的通知,请进行F_NOTIFY调用,并将arg指定为 0。
通知通过传递信号来发生。默认信号是 SIGIO,但这可以使用fcntl() 的F_SETSIG 命令更改。在后一种情况下,信号处理程序将接收siginfo_t 结构作为其第二个参数(如果处理程序是使用 SA_SIGINFO 建立的),并且此结构的si_fd 字段包含生成通知的文件描述符(在对多个目录建立通知时很有用)。
尤其是在使用DN_MULTISHOT时,应该使用实时信号进行通知,以便可以排队多个通知。
注意:新的应用程序应该考虑使用inotify接口(从内核 2.6.13 开始可用),它提供了一个更优越的接口来获取文件系统事件的通知。参见inotify(7)。
|
返回值
对于成功的调用,返回值取决于操作
标签 | 描述 |
F_DUPFD | 新的描述符。 |
F_GETFD | 标志的值。 |
F_GETFL | 标志的值。 |
F_GETOWN | 描述符所有者的值。 |
F_GETSIG | 读取或写入变为可能时发送的信号的值,或者对于传统的 SIGIO 行为为零。 |
所有其他命令 | | 零。 |
发生错误时,返回 -1,并相应地设置errno。
错误
标签 | 描述 |
EACCES 或EAGAIN | 操作被其他进程持有的锁禁止。 |
EAGAIN | 操作被禁止,因为文件已被另一个进程内存映射。 |
EBADF |
fd不是打开的文件描述符,或者命令是F_SETLK 或F_SETLKW,并且文件描述符打开模式与请求的锁类型不匹配。 |
EDEADLK | 检测到指定的F_SETLKW命令将导致死锁。 |
EFAULT |
lock位于您可访问的地址空间之外。 |
EINTR | 对于F_SETLKW,命令被信号中断。对于F_GETLK和F_SETLK,在检查或获取锁之前,命令被信号中断。这最可能发生在锁定远程文件(例如,通过NFS锁定)时,但有时也可能在本地发生。 |
EINVAL | 对于F_DUPFD,arg为负数或大于最大允许值。对于F_SETSIG,arg不是允许的信号编号。 |
EMFILE | 对于F_DUPFD,进程已打开最大数量的文件描述符。 |
ENOLCK | 打开的段锁过多,锁表已满,或远程锁定协议失败(例如,通过NFS锁定)。 |
EPERM | 试图清除已设置追加属性的文件的O_APPEND标志。 |
注释
dup2()返回的错误与F_DUPFD返回的错误不同。
自内核2.0以来,flock(2)和fcntl(2)放置的锁的类型之间没有交互。
POSIX.1-2001允许l_len为负数。(如果是负数,则锁描述的区间覆盖从l_start+l_len到l_start-1的字节。)Linux从Linux 2.4.21和2.5.49开始支持此功能。
一些系统在struct flock中具有更多字段,例如l_sysid。显然,如果持有锁的进程可能位于不同的机器上,则仅l_pid将不太有用。
错误
某些架构(特别是x86)上的Linux系统调用约定存在一个限制,这意味着如果F_GETOWN返回的(负)进程组ID落在-1到-4095的范围内,则glibc会错误地将其解释为系统调用中的错误;也就是说,fcntl()的返回值将为-1,而errno将包含(正)进程组ID。
在Linux 2.4及更早版本中,当非特权进程使用F_SETOWN将套接字文件描述符的所有者指定为调用者以外的进程(组)时,可能会出现错误。在这种情况下,fcntl()可能会返回-1,并将errno设置为EPERM,即使所有者进程(组)是调用者有权向其发送信号的进程(组)。尽管返回此错误,但文件描述符所有者已设置,并且信号将发送到所有者。
符合标准
SVr4, 4.3BSD, POSIX.1-2001。只有F_DUPFD、F_GETFD、F_SETFD、F_GETFL、F_SETFL、F_GETLK、F_SETLK、F_SETLKW、F_GETOWN和F_SETOWN操作在POSIX.1-2001中指定。
F_GETSIG、F_SETSIG、F_NOTIFY、F_GETLEASE和F_SETLEASE是Linux特有的。(定义_GNU_SOURCE宏以获得这些定义。)
另请参见
广告
|