借助 fcntl函数来实现文件锁机制。操作文件的进程没有获得文件锁时,可以打开文件,但无法执行read、write操作。 注意,文件锁只能用于进程间同步!
fcntl函数:
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);//long 长整型
int fcntl(int fd, int cmd, struct flock *lock);
可见fcntl函数是一个可变参数函数(函数的形参数目和类型不只是一种),第三个参数的类型由第二个参数(固参)决定(可变参数函数的变参由固参决定)。
作用:1.实现文件描述符的拷贝(重定向),此时cmd参数为F_DUPFD;2. 用于实现:获得文件状态标记和设置文件状态标记。改变已经打开的文件的属性,如一个文件以只读的方式打开后,如果想要再不关闭重新打开文件的前提下可以向文件里追加写入内容,则可以使用fcntl函数来修改其属性,此时cmd参数为F_GETFL或F_SETFDL;3. 获得或设置文件锁,cmd参数为:F_GETLK、F_SETLK或F_SETLKW;4.获得文件描述符标记或设置文件描述符标记,此时参数为F_GETFD或F_SETFD;5.获得或设置异步I/O所有权,cmd参数为:F_GETOWN或F_SETOWN。
int fcntl(int fd, int cmd);
用于实现文件描述符的拷贝(重定向),功能与dup一样。cmd采用参数:F_DUPFD(宏定义)。 返回值则为复制的文件描述符,错误返回-1。
int fcntl(int fd, int cmd, long arg);
获得文件状态标记:cmd指定为F_GETFL,第三个参数arg直接为0即可,返回值为一个位图(整型),在位图中的每一位都代表了文件的一个状态,如:NON_BLOCK(非阻塞)、O_READ(只读)、O_APPEND(可追加)、O_WRONLY(只写打开)、O_RDWR(读写打开)、O_EXEC(执行打开)、O_SEARCH(搜索打开目录)、O_APPEND(追加写)等。例如,位图中表示O_APPEND的那一位为1,则说明这个文件可追加写;如果为0,则表示不可追加写。错误则返回-1。
设置文件状态标记(即在文件打开的情况下可修改文件属性):cmd参数为F_SETFL,第三个参数arg(长整型),即也是一个位图,将文件的各个状态都设置为位图中表示的状态。例如,增加文件的某个flags,比如文件是阻塞的,想设置成非阻塞:flags = fcntl(fd,F_GETFL,0); flags |= O_NONBLOCK; fcntl(fd,F_SETFL,flags); 即进行位或运算; 取消文件的某个flags,比如文件是非阻塞的,想设置成为阻塞:flags = fcntl(fd,F_GETFL,0); flags &= ~O_NONBLOCK; fcntl(fd,F_SETFL,flags); 即先位取反,再位于运算。成功0,失败-1。
int fcntl(int fd, int cmd, struct flock *lock);
F_SETLK:设置文件锁(类似于trylock);F_SETLKW:设置文件锁(类似于lock);F_GETLK:获取文件锁。注意:F_SETLK与F_SETLKW既用于对文件进行加锁,又用于对文件进行解锁,加锁时struct flock中的l_type成员设置为F_RDLCK (读锁)或F_WRLCK(写锁);解锁时struct flock中的l_type成员设置为F_UNLCK即可。在加锁时,F_SETLK加锁不成功则直接返回,不阻塞继续往下执行;F_SETLKW加锁不成功会阻塞等待。对于F_GETLK,第三个参数为传出参数;其余两个,第三个参数为传入参数。成功0,失败-1。
struct flock {
...
short l_type; 锁的类型:F_RDLCK 、F_WRLCK 、F_UNLCK
short l_whence; 偏移位置:SEEK_SET、SEEK_CUR、SEEK_END
off_t l_start; 起始偏移:1000
off_t l_len; 长度:0表示整个文件加锁
pid_t l_pid; 持有该锁的进程ID:(F_GETLK only)
...
};
该结构体中的:l_whence、l_start和l_len三个参数可以共同设置对文件的部分进行加锁。
多线程间共享文件描述符,而给文件加锁,是通过修改文件描述符所指向的文件结构体中的成员变量(如struct flock结构体等)来实现的。因此,多线程中无法使用文件锁。
//多个进程利用文件锁同步访问访问文件(两个子进程读锁,父进程写锁)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
void error_if( char *arg )
{
perror(arg);
exit(1);
}
int main( int argc, char *argv[ ] )
{
int fd, i;
pid_t pid;
struct flock file_lock;
if( argc < 2 )
{
printf("./a.out filename.\n");
exit(1);
}
fd = open(argv[1],O_RDWR | O_CREAT,0664);
if( fd == -1 )
error_if( "open error: " );
for( i=0; i<2; i++ )
{
pid = fork( );
if( pid == -1 )
error_if( "fork error: ");
else if( pid == 0)
break;
}
if( i < 2 ) //The child process
{
if( i == 0 )
sleep(1);
int ret;
file_lock.l_type = F_RDLCK; //子进程读锁
file_lock.l_whence = SEEK_SET;
file_lock.l_start = 0;
file_lock.l_len = 0;
ret = fcntl( fd, F_SETLKW, &file_lock);
if( ret == -1 )
error_if("fcntl error: ");
printf(" The %dth child process(%u) get read_lock.\n",i+1,getpid( ));
sleep(5); // 模拟对文件进行操作
file_lock.l_type = F_UNLCK;
printf("I will release the read_lock.\n");
ret = fcntl( fd, F_SETLKW, &file_lock);
if( ret == -1 )
error_if("fcntl error: ");
} else //The parent process
{
sleep(1);
int ret;
file_lock.l_type = F_WRLCK; //父进程写锁
file_lock.l_whence = SEEK_SET;
file_lock.l_start = 0;
file_lock.l_len = 0;
ret = fcntl( fd, F_SETLKW, &file_lock);
if( ret == -1 )
error_if("fcntl error: ");
printf(" The parent process(%u) get write_lock.\n",getpid( ));
sleep(5); // 模拟对文件进行操作
file_lock.l_type = F_UNLCK;
printf("I will release the read_lock.\n");
ret = fcntl( fd, F_SETLKW, &file_lock);
if( ret == -1 )
error_if("fcntl error: ");
wait( NULL );
wait( NULL );
}
return 0;
}
[root@localhost 02_pthread_sync_test]# ./file_lk rgf
The 2th child process(8164) get read_lock.
The 1th child process(8163) get read_lock.
I will release the read_lock.
I will release the read_lock.
The parent process(8162) get write_lock.
I will release the read_lock.
分析:
- 强调一点:多个进程使用文件锁同步访问一个文件时,每个进程都有自己的一把锁,即进程与进程之间锁不是共享的(这与互斥量和信号量用于进程间同步有这明显的区别);
- 由上可以看出:文件锁仍然遵循读共享、写独占的原则。如若进程不加锁直接操作文件,依然可访问成功,但数据势必会出现混乱。
- 多线程间共享文件描述符,而给文件加锁,是通过修改文件描述符所指向的文件结构体中的成员变量来实现的。因此,多线程中无法使用文件锁。