fcntl函数

fcntl文件介绍一:
(1)fcntl函数说明
前面的这5个基本函数实现了文件的打开、读写等基本操作,这一节将讨论的是,在文 件已经共享的情况下如何操作,也就是当多个用户共同使用、操作一个文件的情况,这时,Linux 通常采用的方法是给文件上锁,来避免共享的资源产生竞争的状态。
文件锁包括 建议性锁 强制性锁
建议性锁要求每个上锁文件的进程都要检查是否有锁存,并且尊重已有的锁。在一般情况下,内核和系统都不使用建议性锁。强制性锁是由内 核执行的锁,当一个文件被上锁进行写入操作的时候,内核将阻止其他任何文件对其进行读写操作。采用强制性锁对性能的影响很大,每次读写操作都必须检查是否有锁存在。
在 Linux 中,实现文件上锁的函数有lock和fcntl,其中flock用于对文件施加建议性锁,而fcntl不仅可以施加建议性锁,还可以施加强制锁。同时,fcntl还能对文件的某一记录进行上锁,也就是记录锁。
记录锁又可分为 读取锁写入锁,其中读取锁又称为共享锁,它能够使多个进程都能在文件的同一部分建立读取锁。而写入锁又称为排斥锁,在任何时刻只能有一个进程在文件的某个部分上建立写入锁。当然,在文件的同一部分不能同时建立读取锁和写入锁。
注意:
fcntl是一个非常通用的函数,它还可以改变文件进程各方面的属性,在本节中,主要介绍它建立记录锁的方法,关于它其他用户感兴趣的读者可以参看fcntl手册。
(2)fcntl函数格式
用于建立记录锁的fcntl函数格式如表6.6 所示。
表6.6 fcntl函数语法要点所需头文件
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
函数原型 int fcnt1(int fd, intcmd, struct flock *lock)
fd:文件描述符
F_DUPFD:复制文件描述符
F_GETFD:获得fd的close-on-exec标志,若标志未设置,则文件经过exec函数之后仍保持打开状态
F_SETFD:设置close-on-exec标志,该标志以参数arg的FD_CLOEXEC位决定
F_GETFL:得到open设置的标志
函数传入值
cmd
F_SETFL:改变open设置的标志
F_GETFK:根据lock描述,决定是否上文件锁
F_SETFK:设置lock描述的文件锁
F_SETLKW:这是F_SETLK的阻塞版本(命令名中的W表示等待(wait))。
如果存在其他锁,则调用进程睡眠;如果捕捉到信号则睡眠中断
F_GETOWN:检索将收到SIGIO和SIGURG信号的进程号或进程组号
F_SETOWN:设置进程号或进程组号
函数返回值
Lock:结构为flock,设置记录锁的具体状态,后面会详细说明
成功:0
-1:出错
这里,lock的结构如下所示:
Struct flock{
short l_type;
off_t l_start;
short l_whence;
off_t l_len;
pid_t l_pid;
}
lock结构中每个变量的取值含义如表6.7 所示。
表6.7 lock结构变量取值
F_RDLCK:读取锁(共享锁)
l_type F_WRLCK:写入锁(排斥锁)
F_UNLCK:解锁
l_stat 相对位移量(字节)
SEEK_SET:当前位置为文件的开头,新位置为偏移量的大小
SEEK_CUR:当前位置为文件指针的位置,新位置为当前位置加上偏移量
l_whence:相对位移量的起点(同lseek 的whence)。
SEEK_END:当前位置为文件的结尾,新位置为文件的大小加上偏移量的大小
l_len 加锁区域的长度
小技巧:
为加锁整个文件,通常的方法是将l_start 说明为0,l_whence 说明为SEEK_SET,l_len 说明为0。
(3)fcntl使用实例
下面首先给出了使用fcntl 函数的文件记录锁函数。在该函数中,首先给flock 结构体的对应位赋予相应的值。接着使用两次fcntl函数分别用于给相关文件上锁和判断文件是否可以上锁,这里用到的cmd值分别为F_SETLK 和F_GETLK。
这个函数的源代码如下所示:
/*lock_set函数*/
void lock_set(int fd, int type)
{
struct flock lock;
lock.l_whence = SEEK_SET;//赋值lock结构体
lock.l_start = 0;
lock.l_len =0;
while(1){
lock.l_type = type;
/*根据不同的type值给文件上锁或解锁*/
if((fcntl(fd, F_SETLK, &lock)) == 0){
if( lock.l_type == F_RDLCK )
printf("read lock set by %d\n",getpid());
else if( lock.l_type == F_WRLCK )
printf("write lock set by %d\n",getpid());
else if( lock.l_type == F_UNLCK )
printf("release lock by %d\n",getpid());
return;
}
/*判断文件是否可以上锁*/
fcntl(fd, F_GETLK,&lock);
/*判断文件不能上锁的原因*/
if(lock.l_type != F_UNLCK){
/*/该文件已有写入锁*/
if( lock.l_type == F_RDLCK )
printf("read lock already set by %d\n",lock.l_pid);
/*该文件已有读取锁*/
else if( lock.l_type == F_WRLCK )
printf("write lock already set by %d\n",lock.l_pid);
getchar();
}
}
}
下面的实例是测试文件的写入锁,这里首先创建了一个hello文件,之后对其上写入锁,最后释放写入锁。代码如下所示:
/*fcntl_write.c测试文件写入锁主函数部分*/
#include <unistd.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int fd;
/*首先打开文件*/
fd=open("hello",O_RDWR | O_CREAT, 0666);
if(fd < 0){
perror("open");
exit(1);
}
/*给文件上写入锁*/
lock_set(fd, F_WRLCK);
getchar();
/*给文件接锁*/
lock_set(fd, F_UNLCK);
getchar();
close(fd);
exit(0);
}
为了能够使用多个终端,更好地显示写入锁的作用,本实例主要在PC 机上测试,读者可将其交叉编译,下载到目标板上运行。下面是在PC 机上的运行结果。为了使程序有较大的灵活性,笔者采用文件上锁后由用户键入一任意键使程序继续运行。建议读者开启两个终端,并且在两个终端上同时运行该程序,以达到多个进程操作一个文件的效果。在这里,笔者首先运行终端一,请读者注意终端二中的第一句。
终端一:
[root@localhost file]# ./fcntl_write
write lock set by 4994
release lock by 4994
终端二:
[root@localhost file]# ./fcntl_write
write lock already set by 4994
write lock set by 4997
release lock by 4997
由此可见,写入锁为互斥锁,一个时刻只能有一个写入锁存在。
接下来的程序是测试文件的读取锁,原理同上面的程序一样。
/*fcntl_read.c测试文件读取锁主函数部分*/
#include <unistd.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int fd;
fd=open("hello",O_RDWR | O_CREAT, 0666);
if(fd < 0){
perror("open");
exit(1);
}
/*给文件上读取锁*/
lock_set(fd, F_RDLCK);
getchar();
/*给文件接锁*/
lock_set(fd, F_UNLCK);
getchar();
close(fd);
exit(0);
}
同样开启两个终端,并首先启动终端一上的程序,其运行结果如下所示:
终端一:
[root@localhost file]# ./fcntl2
read lock set by 5009
release lock by 5009
终端二:
[root@localhost file]# ./fcntl2
read lock set by 5010
release lock by 5010
读者可以将此结果与写入锁的运行结果相比较,可以看出,读取锁为共享锁,当进程5009已设定读取锁后,进程5010 还可以设置读取锁。
 
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操作(如下面的描述)的描述符。针对cmd的值,fcntl能够接受第三个参数int arg。

[返回值]
fcntl()的返回值
与命令有关。如果出错,所有命令都返回-1,如果成功则返回某个其他值。下列三个命令有特定返回值:F_DUPFD , F_GETFD , F_GETFL以及F_GETOWN。
    F_DUPFD   返回新的文件描述符
    F_GETFD   返回相应标志
    F_GETFL , F_GETOWN   返回一个正的进程ID或负的进程组ID

 

fcntl函数有5种功能: 
1. 复制一个现有的描述符(cmd=F_DUPFD). 
2. 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD). 
3. 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL). 
4. 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN). 
5. 获得/设置记录锁(cmd=F_GETLK , F_SETLK或F_SETLKW).

1. cmd值的F_DUPFD : 
F_DUPFD    返回一个如下描述的(文件)描述符:
        ·最小的大于或等于arg的一个可用的描述符
        ·与原始操作符一样的某对象的引用
        ·如果对象是文件(file)的话,则返回一个新的描述符,这个描述符与arg共享相同的偏移量(offset)
        ·相同的访问模式(读,写或读/写)
        ·相同的文件状态标志(如:两个文件描述符共享相同的状态标志)
        ·与新的文件描述符结合在一起的close-on-exec标志被设置成交叉式访问execve(2)的系统调用

实际上调用dup(oldfd);
等效于

        fcntl(oldfd, F_DUPFD, 0);

而调用dup2(oldfd, newfd);
等效于
        close(oldfd);
        fcntl(oldfd, F_DUPFD, newfd);

2. cmd值的F_GETFD和F_SETFD:      
F_GETFD    取得与文件描述符fd联合的close-on-exec标志,类似FD_CLOEXEC。如果返回值和FD_CLOEXEC进行与运算结果是0的话,文件保持交叉式访问exec(),否则如果通过exec运行的话,文件将被关闭(arg 被忽略)  
      
F_SETFD    设置close-on-exec标志,该标志以参数arg的FD_CLOEXEC位决定,应当了解很多现存的涉及文件描述符标志的程序并不使用常数 FD_CLOEXEC,而是将此标志设置为0(系统默认,在exec时不关闭)或1(在exec时关闭)    

在修改文件描述符标志或文件状态标志时必须谨慎,先要取得现在的标志值,然后按照希望修改它,最后设置新标志值。不能只是执行F_SETFD或F_SETFL命令,这样会关闭以前设置的标志位。 

3. cmd值的F_GETFL和F_SETFL:   
F_GETFL    取得fd的文件状态标志,如同下面的描述一样(arg被忽略),在说明open函数时,已说明
了文件状态标志。不幸的是,三个存取方式标志 (O_RDONLY , O_WRONLY , 以及O_RDWR)并不各占1位。(这三种标志的值各是0 , 1和2,由于历史原因,这三种值互斥 — 一个文件只能有这三种值之一。) 因此首先必须用屏蔽字O_ACCMODE相与取得存取方式位,然后将结果与这三种值相比较。       
F_SETFL    设置给arg描述符状态标志,可以更改的几个标志是:O_APPEND,O_NONBLOCK,O_SYNC 和 O_ASYNC。而fcntl的文件状态标志总共有7个:O_RDONLY , O_WRONLY , O_RDWR , O_APPEND , O_NONBLOCK , O_SYNC和O_ASYNC

可更改的几个标志如下面的描述:
    O_NONBLOCK   非阻塞I/O,如果read(2)调用没有可读取的数据,或者如果write(2)操作将阻塞,则read或write调用将返回-1和EAGAIN错误
    O_APPEND     强制每次写(write)操作都添加在文件大的末尾,相当于open(2)的O_APPEND标志
    O_DIRECT     最小化或去掉reading和writing的缓存影响。系统将企图避免缓存你的读或写的数据。如果不能够避免缓存,那么它将最小化已经被缓存了的数据造成的影响。如果这个标志用的不够好,将大大的降低性能
    O_ASYNC      当I/O可用的时候,允许SIGIO信号发送到进程组,例如:当有数据可以读的时候

4. cmd值的F_GETOWN和F_SETOWN:   
F_GETOWN   取得当前正在接收SIGIO或者SIGURG信号的进程id或进程组id,进程组id返回的是负值(arg被忽略)     
F_SETOWN   设置将接收SIGIO和SIGURG信号的进程id或进程组id,进程组id通过提供负值的arg来说明(arg绝对值的一个进程组ID),否则arg将被认为是进程id

 5. cmd值的F_GETLK, F_SETLK或F_SETLKW 获得/设置记录锁的功能,成功则返回0,若有错误则返回-1,错误原因存于errno。
F_GETLK    通过第三个参数arg(一个指向flock的结构体)取得第一个阻塞lock description指向的锁。取得的信息将覆盖传到fcntl()的flock结构的信息。如果没有发现能够阻止本次锁(flock)生成的锁,这个结构将不被改变,除非锁的类型被设置成F_UNLCK    
F_SETLK    按照指向结构体flock的指针的第三个参数arg所描述的锁的信息设置或者清除一个文件的segment锁。F_SETLK被用来实现共享(或读)锁(F_RDLCK)或独占(写)锁(F_WRLCK),同样可以去掉这两种锁(F_UNLCK)。如果共享锁或独占锁不能被设置,fcntl()将立即返回EAGAIN     
F_SETLKW   除了共享锁或独占锁被其他的锁阻塞这种情况外,这个命令和F_SETLK是一样的。如果共享锁或独占锁被其他的锁阻塞,进程将等待直到这个请求能够完成。当fcntl()正在等待文件的某个区域的时候捕捉到一个信号,如果这个信号没有被指定SA_RESTART, fcntl将被中断

当一个共享锁被set到一个文件的某段的时候,其他的进程可以set共享锁到这个段或这个段的一部分。共享锁阻止任何其他进程set独占锁到这段保护区域的任何部分。如果文件描述符没有以读的访问方式打开的话,共享锁的设置请求会失败。

独占锁阻止任何其他的进程在这段保护区域任何位置设置共享锁或独占锁。如果文件描述符不是以写的访问方式打开的话,独占锁的请求会失败。

结构体flock的指针:
struct flcok 

short int l_type; /* 锁定的状态*/

//以下的三个参数用于分段对文件加锁,若对整个文件加锁,则:l_whence=SEEK_SET, l_start=0, l_len=0
short int l_whence; /*决定l_start位置*/ 
off_t l_start; /*锁定区域的开头位置*/ 
off_t l_len; /*锁定区域的大小*/

pid_t l_pid; /*锁定动作的进程*/ 
};

l_type 有三种状态: 
F_RDLCK   建立一个供读取用的锁定 
F_WRLCK   建立一个供写入用的锁定 
F_UNLCK   删除之前建立的锁定

l_whence 也有三种方式: 
SEEK_SET   以文件开头为锁定的起始位置 
SEEK_CUR   以目前文件读写位置为锁定的起始位置 
SEEK_END   以文件结尾为锁定的起始位置


fcntl文件锁有两种类型:建议性锁和强制性锁
建议性锁是这样规定的:每个使用上锁文件的进程都要检查是否有锁存在,当然还得尊重已有的锁。内核和系统总体上都坚持不使用建议性锁,它们依靠程序员遵守这个规定。
强制性锁是由内核执行的:当文件被上锁来进行写入操作时,在锁定该文件的进程释放该锁之前,内核会阻止任何对该文件的读或写访问,每次读或写访问都得检查锁是否存在。

系统默认fcntl都是建议性锁,强制性锁是非POSIX标准的。如果要使用强制性锁,要使整个系统可以使用强制性锁,那么得需要重新挂载文件系统,mount使用参数 -0 mand 打开强制性锁,或者关闭已加锁文件的组执行权限并且打开该文件的set-GID权限位。
建议性锁只在cooperating processes之间才有用。对cooperating process的理解是最重要的,它指的是会影响其它进程的进程或被别的进程所影响的进程,举两个例子:
(1) 我们可以同时在两个窗口中运行同一个命令,对同一个文件进行操作,那么这两个进程就是cooperating  processes
(2) cat file | sort,那么cat和sort产生的进程就是使用了pipe的cooperating processes

使用fcntl文件锁进行I/O操作必须小心:进程在开始任何I/O操作前如何去处理锁,在对文件解锁前如何完成所有的操作,是必须考虑的。如果在设置锁之前打开文件,或者读取该锁之后关闭文件,另一个进程就可能在上锁/解锁操作和打开/关闭操作之间的几分之一秒内访问该文件。当一个进程对文件加锁后,无论它是否释放所加的锁,只要文件关闭,内核都会自动释放加在文件上的建议性锁(这也是建议性锁和强制性锁的最大区别),所以不要想设置建议性锁来达到永久不让别的进程访问文件的目的(强制性锁才可以);强制性锁则对所有进程起作用。

fcntl使用三个参数 F_SETLK/F_SETLKW, F_UNLCK和F_GETLK 来分别要求、释放、测试record locks。record locks是对文件一部分而不是整个文件的锁,这种细致的控制使得进程更好地协作以共享文件资源。fcntl能够用于读取锁和写入锁,read lock也叫shared lock(共享锁), 因为多个cooperating process能够在文件的同一部分建立读取锁;write lock被称为exclusive lock(排斥锁),因为任何时刻只能有一个cooperating process在文件的某部分上建立写入锁。如果cooperating processes对文件进行操作,那么它们可以同时对文件加read lock,在一个cooperating process加write lock之前,必须释放别的cooperating process加在该文件的read lock和wrtie lock,也就是说,对于文件只能有一个write lock存在,read lock和wrtie lock不能共存。

下面的例子使用F_GETFL获取fd的文件状态标志

#include<fcntl.h>
#include<unistd.h>
#include<iostream>
#include<errno.h>
using namespace std;

int main(int argc,char* argv[])
{
  int fd, var;
  //  fd=open("new",O_RDWR);
  if (argc!=2)
  {
      perror("--");
      cout<<"请输入参数,即文件名!"<<endl;
  }

  if((var=fcntl(atoi(argv[1]), F_GETFL, 0))<0)
  {
     strerror(errno);
     cout<<"fcntl file error."<<endl;
  }

  switch(var & O_ACCMODE)
  {
   case O_RDONLY : cout<<"Read only.."<<endl;
                   break;
   case O_WRONLY : cout<<"Write only.."<<endl;
                   break;
   case O_RDWR   : cout<<"Read wirte.."<<endl;
                   break;
   default  : break;
  }

 if (val & O_APPEND)
    cout<<",append"<<endl;

 if (val & O_NONBLOCK)
    cout<<",noblocking"<<endl;

 cout<<"exit 0"<<endl;

 exit(0);
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值