前言
当我们要进行多进程编程的时候,经常存在多个进程需要访问同一个文件的情况,因此会产生进程间访问不一致的问题,那么我们可以用到fcntl函数,我们可以用它来对文件或者文件的一部分进行上锁。
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
fcntl 可以施加建议性锁,也可以施加强制锁。同时还能对文件的某一记录进行上锁,也就是记录锁。
所谓建议性锁,就是说它不具备强制性,只是作为程序员之间的约定,如果你愿意,仍然可以直接去对一个上锁的文件进行操作。
fcntl
这里我们只讲述加建议性锁,即arg为struct flock指针类型的时候,如下。
int fcntl(int fd, int cmd, struct flock*);
从fcntl的函数声明可以看到,它的参数是可变的。
第一个参数fd:
要操作的文件描述符,
第二个参数cmd:
要操作的指令类型,我们只考虑在第三个参数为flock结构体指针的情况下,cmd有三种取值情况:
F_GETLK 取得文件锁定的状态。
F_SETLK 设置文件锁定的状态。此时flcok 结构的l_type 值必须是F_RDLCK、F_WRLCK或F_UNLCK。如果无法建立锁定,则返回-1,错误代码为EACCES 或EAGAIN。
F_SETLKW F_SETLK 作用相同,但是无法建立锁定时,此调用会一直等到锁定动作成功为止。若在等待锁定的过程中被信号中断时,会立即返回-1,错误代码为EINTR。
第三个参数struct flock*:
flock结构体定义如下
struct flock
{
short int l_type;
short int l_whence;
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 以文件结尾为锁定的起始位置。
**l_start 表示相对l_whence位置的偏移量,两者一起确定锁定区域的开始位置。
l_len表示锁定区域的长度,若为0则表示整个文件的长度,即不管在后面增加多少数据都在锁的范围内。
返回值 成功返回依赖于cmd的值,若有错误则返回-1,错误原因存于errno.**
测试过程中发现的问题!!!
- 用fcntl获取锁的时候(F_GETLK),struct flock*参数的l_type取值必须为(F_RDLCK|F_WRLCK|F_UNLCK)中的一个,否则fcntl会执行失败,这个坑了我半天的时间,因为以为struct flock*只是作为接受锁信息的载体,没想到其type也必须要赋值才行。测试证明,l_type的取值跟获取锁的结果没有任何关系,其赋值的意义仅在于赋值合法而已。
- 读锁作为共享锁,是可以存在多个的,所以在A进程里设置读锁后,在B进程里是获取不到的。
- 进程A设置的锁对进程A是不可见的,也就是说进程A无法GET到自己获得的锁。
测试程序
/*fcntl_write.c测试文件写入锁主函数部分*/
#include <unistd.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
/*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;
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());
}
else
{
/*判断文件是否可以上锁*/
fcntl(fd, F_GETLK,&lock);
/*判断文件不能上锁的原因*/
if(lock.l_type == F_UNLCK)
printf("no lock by %d\n",lock.l_pid);
/*/该文件已有写入锁*/
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);
}
}
void lock_get(int fd)
{
struct flock lock;
lock.l_whence = SEEK_SET;//赋值lock结构体
lock.l_start = 0;
lock.l_len =0;
lock.l_type = F_RDLCK;
/*判断文件是否可以上锁*/
if(0 > fcntl(fd, F_GETLK,&lock))
{
printf("get lock failure by %d\n",getpid());
}
/*判断文件不能上锁的原因*/
if(lock.l_type == F_UNLCK)
printf("no lock by %d\n",lock.l_pid);
/*/该文件已有写入锁*/
else 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);
}
int main()
{
int fd;
/*首先打开文件*/
fd=open("hello",O_RDWR | O_CREAT, 0666);
if(fd < 0){
perror("open");
exit(1);
}
while(1)
{
char str[4];
int type;
scanf("%s", str);
switch(str[0]){
//write lock
case 'w':
type = F_WRLCK;
break;
//read lock
case 'r':
type = F_RDLCK;
break;
//unlock
case 'u':
type = F_UNLCK;
break;
//getlock
case 'g':
type = 0;
break;
}
if(type == 0)
lock_get(fd);
else
lock_set(fd, type);
}
close(fd);
return 0;
}