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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章