linux fcntl函数

fcntl
功能描述:根据文件描述来操作文件的特性。
#include <unistd.h>
#include <fctl.h>
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);

http://blog.csdn.net/xiaoliangsky/article/details/40303245

描述
fctl针对(文件)描述提供控制。fd是被参数cmd操作(如下面描述)的描述符,针对cmd的值,fctl能够接受第三个参数arg。
返回值
fctl()的返回值与命令有关。如果出错,所有命令都返回-1,如果成功则返回某个其他值。下面三个命令有特定的返回值:
F_DUPFD 返回新的文件描述符
F_GETFD 返回相应标志
F_GETFL 返回文件状态标记
F_GETOWN 返回一个正的进程ID或负的进程组ID

fcntl函数有5中功能:
1)复制一个现有的描述符(cm=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_SET)

1. cmd=F_DUPFD
F_DUPFD 返回一个如下描述的(文件)描述符
        ·最小的大于或等于arg的一个可用的描述符
        ·与原始操作符一样的某对象的引用
        ·如果对象是文件(file)的话,则返回一个新的描述符,这个描述符与arg共享相同的偏移量(offset)
        ·相同的访问模式(读,写或读/写)
        ·相同的文件状态标志(如:两个文件描述符共享相同的状态标志)
        ·与新的文件描述符结合在一起的close-on-exec标志被设置成交叉式访问execve(2)的系统调用
实际上调用dup(oldfd);
等效于
fcntl(odlfd, 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进行与运算结果是,文件保持交叉式访问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_GETOWN
F_GETOWN 取得当前正在接受SIGIO或者SIURG信号的进程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指向的锁。取得的信息将覆盖传到fctl()的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 flock
{
    short int l_type;//锁定的状态
    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 processes能够在文件的统一部分建立读取锁;write lock被称为excluseive lock(排斥锁),因为任何时刻只能有一个cooperating processes在文件的某部分建立写入锁。如果cooperating processes对文件进行操作,那么它们可以同时对文件加read lock;在一个cooperating processes加write lock之前,必须释放别的cooperating processes加载该文件的read lock和write lock,也就是说,对于文件只能有一个write lock存在,read lock和write lock不能共存。

下面的例子使用F_GETFL得到文件状态信息:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>

int main()
{
	int   fd;
	int   var;

	if ((fd = open("test.txt", O_RDWR | O_CREAT | O_EXCL, 0664)) == -1)
	{
		perror("open error");
		exit(1);
	}

	if ((var = fcntl(fd, F_GETFL, 0)) < 0)
	{
		perror("fcntl error");
		close(fd);
		exit(1);
	}

	switch(var & O_ACCMODE)
	{
		case O_RDONLY:
			printf("Read only..\n");
			break;
		case O_WRONLY:
			printf("Write only..\n");
			break;
		case O_RDWR:
			printf("Read And Write..\n");
			break;
		default:
			printf("Do't know..\n");
			break;
	}

	if (var & O_APPEND)
	{
		printf("And Append..\n");
	}

	if (var & O_NONBLOCK)
	{
		printf("And Nonblocking..\n");
	}

	if (close(fd) == -1)
	{
		perror("close error");
	}
	
	return 0;
}

测试锁的例子

http://blog.csdn.net/xiaoliangsky/article/details/40303245

//读加写锁在同一个进程测试

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
	int              fd;
	int              fret;
	struct flock     rdflk;
	struct flock     wrflk;
	struct flock     tmflk;
	///*
	关于加锁:
	1)如果文件是以只读打开的,只能加读锁,加写锁时会失败返回-1
	2)如果文件是以只写打开的,只能加写锁,加读锁时会失败返回-1
	3)如果文件是以读写打开的,在同一个进程中,如果同时加了两种锁,后加的锁会覆盖前面加的锁。
	   比如先加写锁,后加读锁,结果文件加上的是读锁;如果先加读锁,后加写锁,文件最终加上的是写锁。
	//*

	if ((fd = open("test.txt", O_RDWR)) == -1)
	{
		perror("open error");
		exit(1);
	}

	if ((fret = fcntl(fd, F_GETFD)) == -1)
	{
		perror("fcntl error");
		exit(1);
	}

	fcntl(fd, F_SETFD, 1);//在exec时关闭

	rdflk.l_type   = F_RDLCK;
	rdflk.l_whence = SEEK_SET;
	rdflk.l_start  = 0;
	rdflk.l_len    = 100;
	wrflk          = rdflk;
	wrflk.l_type   = F_WRLCK;
	//文件先加读锁
	if (fcntl(fd, F_SETLK, &rdflk) == -1)
	{
		perror("fcntl set rdflk error");
		exit(1);
	}
	//后加写锁,最终写锁会覆盖读锁,文件加的是写锁
	if (fcntl(fd, F_SETLK, &wrflk) == -1)
	{
		perror("fcntl set wrflk error");
		exit(1);
	}
        
        //在这个用fcntl(fd, F_GETLK, &tmflk);
        return 0;
}

//只读打开,加写锁报错

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
	int              fd;
	int              fret;
	struct flock     rdflk;
	struct flock     wrflk;

	//关于加锁:
	//1)如果文件是以只读打开的,只能加读锁,加写锁时会失败返回-1
	if ((fd = open("test.txt", O_RDONLY)) == -1)
	{
		perror("open error");
		exit(1);
	}

	rdflk.l_type   = F_RDLCK;
	rdflk.l_whence = SEEK_SET;
	rdflk.l_start  = 0;
	rdflk.l_len    = 100;
	wrflk          = rdflk;
	wrflk.l_type   = F_WRLCK;
	//加写锁,会出现错误,因为文件是以只读打开的
	//fcntl set wrflk error: Bad file descriptor
	if (fcntl(fd, F_SETLK, &wrflk) == -1)
	{
		perror("fcntl set wrflk error");
		exit(1);
	}

	return 0;
}
同样以只写打开加读锁也出错:

//2)如果文件是以只写打开的,只能加写锁,加读锁时会失败返回-1
	if ((fd = open("test.txt", O_WRONLY)) == -1)
	{
		perror("open error");
		exit(1);
	}
	
	//加读锁,会出现错误,因为文件是以只写打开的
	//fcntl set wrflk error: Bad file descriptor
	if (fcntl(fd, F_SETLK, &rdflk) == -1)
	{
		perror("fcntl set wrflk error");
		exit(1);
	}

//以读写方式打开,在不同进程之间加锁
/*
当一个共享锁被set到一个文件的某段的时候,其他的进程可以set共享锁到这个段或这个段的一部分。共享锁阻止任何其他进程set独占锁到这段保护区的任何部分。如果文件描述符没有以读的访问方式打开,共享锁的设置请求会失败。
独占锁阻止任何其他的进程在这段保护区域任何位置设置共享锁或独占锁。如果文件描述符不是以写的方式打开,独占锁的请求会失败。
总结:如果有一个进程加写锁,其他进程不能加任何锁;如果是读锁,其他进程只能加读锁。
*/

代码:

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

#define BUF_SIZE 512

int main()
{
	int              fd;
	int              bytes_read;
	int              status;
	pid_t            pid;
	const char      *chbuf;
	char             buf[BUF_SIZE];
	struct flock     tmflk;
	struct flock     rdflk;
	struct flock     wrflk;

	chbuf          = "xxxxx";
	rdflk.l_type   = F_RDLCK;
	rdflk.l_whence = SEEK_SET;
	rdflk.l_start  = 0;
	rdflk.l_len    = 100;
	wrflk          = rdflk;
	tmflk          = rdflk;
	wrflk.l_type   = F_WRLCK;

	//在进程之间以读写方式打开,在不同进程之间加读写锁
	if ((fd = open("test.txt", O_RDWR | O_APPEND)) == -1)
	{
		perror("open error");
		exit(1);
	}
	
	if ((pid = fork()) < 0)
	{
		perror("fork error");
		exit(1);
	}
	else if (pid > 0)
	{	//父进程加读锁
		if (fcntl(fd, F_SETLK, &wrflk) == -1)
	    {
			perror("parent fcntl set rdflk error");
			exit(1);
	   	}
		//然子进程尝试加锁
		sleep(1);
		//写入数据
		if (write(fd, chbuf, sizeof(chbuf))  == -1)
		{
			perror("parent write error");
		}

		tmflk        = rdflk;
		tmflk.l_type = F_UNLCK;
		//然子进程尝试申请锁,以便测试在有读锁的情况下,其他进程不能加锁
		sleep(3);
		//释放锁
		if (fcntl(fd, F_SETLK, &tmflk) == -1)
		{
			perror("fcntl unlck error");
		}

		waitpid(pid, &status, 0);//等待子进程返回
	}
	else
	{
		sleep(1);
		
		tmflk = rdflk;
		//尝试枷锁
		while (1)
		{   //获取文件锁的信息
			if (fcntl(fd, F_GETLK, &tmflk) == -1)
			{
				perror("child fcntl getlk error");
			}

			if (tmflk.l_type == F_UNLCK)
			{
				break;
			}

			if (tmflk.l_type == F_RDLCK)
			{
				printf("process Id:%d has locked with F_RDLCK\n", tmflk.l_pid);
			}
			else if (tmflk.l_type == F_WRLCK)
			{
				printf("process Id:%d has locked with F_WRLCK\n", tmflk.l_pid);
			}

			sleep(1);
		}
		//加读锁
		if (fcntl(fd, F_SETLK, &rdflk) == -1)
		{
			perror("fcntl setlk rdflk error");
			exit(1);
		}
	
		printf("child fcntl get the rdflk\n");
		
		lseek(fd, rdflk.l_whence, rdflk.l_start);//重定位
		if((bytes_read = read(fd, buf, BUF_SIZE)) < 0)
		{
			perror("read error");
			exit(1);
		}

		buf[bytes_read-1] = '\0';
		write(STDOUT_FILENO, buf, bytes_read);
		printf("\n");

		tmflk        = rdflk;
		tmflk.l_type = F_UNLCK;
		
		fcntl(fd, F_SETLK, &tmflk);
	}

	close(fd);

	return 0;
}



下面是来自博文:http://blog.sina.com.cn/s/blog_4b226b9201011974.html写的也不错
参数:
fd:文件描述词。
cmd:操作命令。
arg:供命令使用的参数。
lock:同上。

有以下操作命令可供使用
一. F_DUPFD :复制文件描述词 。
二. FD_CLOEXEC :设置close-on-exec标志。如果FD_CLOEXEC位是0,执行execve的过程中,文件保持打开。反之则关闭。
三. F_GETFD :读取文件描述词标志。
四. F_SETFD :设置文件描述词标志。
五. F_GETFL :读取文件状态标志。
六. F_SETFL :设置文件状态标志。
其中O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_NOCTTY 和 O_TRUNC不受影响,
可以更改的标志有 O_APPEND,O_ASYNC, O_DIRECT, O_NOATIME 和 O_NONBLOCK。
七. F_GETLK, F_SETLK 和 F_SETLKW :获取,释放或测试记录锁,使用到的参数是以下结构体指针:
F_SETLK:在指定的字节范围获取锁(F_RDLCK, F_WRLCK)或者释放锁(F_UNLCK)。如果与另一个进程的锁操作发生冲突,返回 -1并将errno设置为EACCES或EAGAIN。

F_SETLKW:行为如同F_SETLK,除了不能获取锁时会睡眠等待外。如果在等待的过程中接收到信号,会立即返回并将errno置为EINTR。
F_GETLK:获取文件锁信息。
F_UNLCK:释放文件锁。

为了设置读锁,文件必须以读的方式打开。为了设置写锁,文件必须以写的方式打开。为了设置读写锁,文件必须以读写的方式打开。

八. 信号管理
F_GETOWN, F_SETOWN, F_GETSIG 和 F_SETSIG 被用于IO可获取的信号。
F_GETOWN:获取当前在文件描述词 fd上接收到SIGIO 或 SIGURG事件信号的进程或进程组标识 。
F_SETOWN:设置将要在文件描述词fd上接收SIGIO 或 SIGURG事件信号的进程或进程组标识 。
F_GETSIG:获取标识输入输出可进行的信号。
F_SETSIG:设置标识输入输出可进行的信号。

使用以上命令,大部分时间程序无须使用select()或poll()即可实现完整的异步I/O。

九. 租约( Leases)
F_SETLEASE 和 F_GETLEASE 被用于当前进程在文件上的租约。文件租约提供当一个进程试图打开或折断文件内容时,拥有文件租约的进程将会被通告的机制。

F_SETLEASE:根据以下符号值设置或者删除文件租约

1.F_RDLCK设置读租约,当文件由另一个进程以写的方式打开或折断内容时,拥有租约的当前进程会被通告。
2.F_WRLCK设置写租约,当文件由另一个进程以读或以写的方式打开或折断内容时,拥有租约的当前进程会被通告。
3.F_UNLCK删除文件租约。

F_GETLEASE:获取租约类型。

十.文件或目录改变通告
(linux 2.4以上)当fd索引的目录或目录中所包含的某一文件发生变化时,将会向进程发出通告。arg参数指定的通告事件有以下,两个或多个值可以通过或运算组合。
1.DN_ACCESS 文件被访问 (read, pread, readv)
2.DN_MODIFY 文件被修改(write, pwrite,writev, truncate, ftruncate)
3.DN_CREATE 文件被建立(open, creat, mknod, mkdir, link, symlink, rename)
4.DN_DELETE 文件被删除(unlink, rmdir)
5.DN_RENAME 文件被重命名(rename)
6.DN_ATTRIB 文件属性被改变(chown, chmod, utime[s])

返回说明:
成功执行时,对于不同的操作,有不同的返回值
F_DUPFD: 新文件描述词
F_GETFD: 标志值
F_GETFL: 标志值
F_GETOWN: 文件描述词属主
F_GETSIG: 读写变得可行时将要发送的通告信号,或者0对于传统的SIGIO行为

对于其它命令返回0。

失败返回-1,errno被设为以下的某个值
EACCES/EAGAIN: 操作不被允许,尚未可行
EBADF: 文件描述词无效
EDEADLK: 探测到可能会发生死锁
EFAULT: 锁操作发生在可访问的地址空间外
EINTR: 操作被信号中断
EINVAL: 参数无效
EMFILE: 进程已超出文件的最大可使用范围
ENOLCK: 锁已被用尽
EPERM:权能不允许

参看资料:

http://www.cnblogs.com/andtt/articles/2178875.html


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。