函数简介
1 | int fcntl(int fd, int cmd, ... /* arg */ ) |
fcntl函数有5种功能:
- 复制一个现有的描述符(cmd=F_DUPFD).
- 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
- 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
- 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
- 获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).
功能/参数详解
复制一个现有的描述符
描述 | 说明 |
---|---|
cmd = F_DUPFD | 复制文件描述符,使用大于或等于arg的编号最小的可用文件描述符复制文件描述符fd。 这与dup2不同,dup2完全使用指定的文件描述符 |
cmd = F_DUPFD_CLOEXEC | 操作和F_DUPFD一样,但是会将新fd的close-on-exec置位 |
arg | 新fd的最小值 |
返回值 | 获得的新fd |
获得/设置文件描述符标记
描述 | 说明 |
---|---|
cmd = F_GETFD | 取得与文件描述符关联的标志,目前仅定义了close-on-exec |
arg | 无 |
返回值 | 与文件描述符关联的标志 |
cmd = F_SETFD | 设置与文件描述符关联的标志: flags |= FD_CLOEXEC; //打开标志位 flags &= ~FD_CLOEXEC; //关闭标志位 |
arg | 新flag |
返回值 | 0 |
获得/设置文件状态标记
描述 | 说明 |
---|---|
cmd = F_GETFL | 取得fd的文件状态标志,状态标志请参考open的flag参数 |
arg | 无 |
返回值 | 与文件描述符关联的文件状态标志 |
cmd = F_SETFL | 设置fd的文件状态标志,该命令仅能更改O_APPEND,O_ASYNC,O_DIRECT,O_NOATIME和O_NONBLOCK标志 |
arg | 新flag |
返回值 | 0 |
获得/设置异步I/O所有权
即信号管理功能,文件可能为设备,在设备驱动中尝试用信号SIGIO来实现和应用程序的异步通信
描述 | 说明 |
---|---|
cmd = F_GETOWN | 获取当前在文件描述符fd上接收SIGIO和SIGURG信号的进程ID或进程组ID |
arg | 无 |
返回值 | 进程ID以正值形式返回; 进程组ID作为负值返回 |
cmd = F_SETOWN | 设置将接收SIGIO和SIGURG信号的进程id或进程组id,进程组id通过提供负值的arg来说明 注意:除了设置接收SIGIO的进程ID外,还需要设置文件描述符的O_ASYNC标志位才能正确使用信号 |
arg | 进程ID或进程组ID(负值) |
返回值 | 0 |
补充:信号管理功能还有两个参数F_GETSIG/F_SETSIG,但是网上资料较少,只能贴上Linux的man手册原文以及自己的理解
个人理解:
- F_GETSIG:获取异步通信的信号值,即当输入或输出变为可能时,用哪个信号进行异步通信,默认为SIGIO
- F_SETSIG:设置异步通信的信号值,即当输入或输出变为可能时,设置指定的信号用于异步通信
原文如下(谷歌机翻):
1 | F_GETSIG |
获得/设置记录锁
概念
当两个人同时编辑一个文件时,其后果将如何呢?在很多UNIX系统中,该文件的最后状态取决于写该文件的最后一个进程。但是对于有些应用程序,例如数据库,有时进程需要确保它正在单独写一个文件。为了向进程提供这种功能,较新的UNIX系统提供了记录锁机制。
记录锁(record locking)的功能是:一个进程正在读或修改文件的某个部分时,可以阻止其他进程修改同一文件区
参数
操作记录锁时,函数原型如下
1 | int fcnt1(int fd,int cmd,.../* struct flock * flockptr */ ) |
struct flock
结构体在操作记录锁时,传入的arg参数为
struct flock
结构体,定义如下1
2
3
4
5
6
7
8struct flock{
short l_type; // F_RDLCK读锁,F_WRLCK写锁,F_UNLCK解锁
off_t l_start; // 锁偏移的字节
short l_whence;// 锁开始的位置,SEEK_SET,SEEK_CURorSEEK_END
off_t l_len; // 锁的范围,0表示从起始到文件末
pid_t l_pid; // 占有锁的进程ID(仅由F_GETLK返回)
};注意:
len单位时字节,0时表示从起始位置到文件末尾,就表示之后在解锁前加入的内容都在锁区域。
通常对通篇文件上锁的参数是 l_start = 0; l_whence = SEEK_SET ; l_len = 0;
上面提到了两种类型的锁:共享读锁(F_RDLCK)和独占写琐(F_WRLCK)。
基本规则是:多个进程在一个给定的字节 上可以有一把共享的读锁,但是在一个给定字节上的写锁则只能由一个进程独用。更进一步而言,如果在一个给定字节上已经有一把或多把读锁,则不能在该字节上再加写锁;如果在一个字节上已经有一把独占性的写锁,则不能再对它加任何读锁。在下图给出了这些规则。加读锁时,该描述符必须是读打开; 加写锁时,该描述符必须是写打开
cmd参数
描述 说明 cmd = F_GETLK 判断对应的结构体中给定区间中是否有对应结构体中的锁:
如果写锁存在,就会阻止我fcntl函数进行,并且会替换我给的结构体指针中的结构体内容。返回当前锁的信息,并且在l_pid 处返回锁的持有者。
如果写锁不存在,则将 _flock.l_type改为F_UNLCK ,其余不变arg struct flock结构体 返回值 成功返回0,失败返回-1 cmd = F_SETLK F_SETLK相对于F_GETLK就要常用很多。其目的顾名思义就是对文件指定区域加锁/解锁。
当加锁不成功时,即其上面本来就有规则不允许的条件的锁时,就会出错返回,errno会设置为EACCES或EAGAINarg struct flock结构体 返回值 成功返回0,失败返回-1 cmd = F_SETLKW F_SETLKW即F_SETLK的阻塞等待版,W即wait。
当fcntl 的请求不能满足时,就会进入休眠,当请求创建的锁可用时会被信号中断休眠,进程就会被唤醒arg struct flock结构体 返回值 成功返回0,失败返回-1
代码示例:
1 | //测试一把锁 |
锁区域
在设置或释放文件上的一把锁时,系统按需组合或裂开相邻区。例如,若对字节 0-99设置 一把读锁,然后对字节0-49设置一把写锁,则有两个加锁区: 0-49字节(写锁)及50-99(读锁)。又如,若100-199字节是加锁的区,需解锁第150字节,则内核将维持两把锁,一把用于100-149字节,另一把用于151-199字节
锁的继承和释放
关于记录锁的自动继承和释放有三条规则:
锁与进程、文件两方面有关。这有两重含意:第一重很明显,当一个进程终止时,它所建立的锁全部释放;第二重意思就不很明显,任何时候关闭一个描述符时,则该进程通过这一描述符可以存访的文件上的任何一把锁都被释放。这就意味着 如果执行下列四步:
1
2
3
4fd1 = open(pathname, ...);
read_lock(fd1, ...);
fd2 = dup(fd1);
close(fd2);则在
close(fd2)
后,在fd1上设置的锁被释放。如果将dup
代换为open
,其效果也一样:1
2
3
4fd1 = open(pathname, ...);
read_lock(fd1, ...);
fd2 = open(pathname, ...);
close(fd2);由fork产生的子程序不继承父进程所设置的锁。这意味着,若一个进程得到一把锁,然后调用fork,那么对于父进程获得的锁而言,子进程被视为另一个进程,对于从父进程处继承过来的任一描述符,子进程要调用 fcntl以获得它自己的锁。这与锁的作用是相一致的。锁的作用是阻止多个进程同时写同一个文件(或同一文件区域)。如果子进程继承父进程的锁,则父、子进程就可以同时写同一个文件。
在执行exec后,新程序可以继承原执行程序的锁。
POSIX.1没有要求这一点。但是,SVR4和4.3+BSD都支持这一点。
文件尾端加锁
我们先来看一段代码
1 | writew_lock(fd,0,SEEK_END,0);//从内容末到文件末阻塞上读锁 |
看起来并没有什么问题,但其实上述中的两个内容末并不是同一位置(如下图所示),我们在上锁后还进行了写入操作导致内容后移,所以当我们上文件末锁操作后还有解锁需求时,要记得相对偏移量。
