一、多进程文件锁
当多个用户共同使用、操作一个文件的情况下,这时,Linux通常采用的方法是给文件上锁,来避免共享的资源产生竞争的状态
1.文件锁方式
对当前读写文件进行加锁处理,简单说下两种加锁方式:
flock():文件级别的锁,针对整个文件进行加锁。 flock用于对文件施加建议性锁。
fcntl()函数:段级别的锁,能够针对文件的某个部分进行加锁。 而fcntl不仅可以施加建议性锁,还可以施加强制性锁,还能对文件的某一记录进行上锁,也就是记录锁。
2.文件锁类型
文件锁包括建议性锁和强制性锁。
建议性锁要求每个上锁文件的进程都要检查是否有锁存在,并且尊重已有的锁。在一般情况下,内核和系统都不使用建议性锁。
强制性锁是由内核执行的锁,当一个文件被上锁进行写入操作的时候,内核将阻止其它任何文件对其进行读写操作。采用强制性锁对性能的影响很大,每次读写操作都必须检查是否有锁存在。
记录锁又分为读取锁和写入锁
读取锁又称共享锁,能使多个进程都在文件的同一部分建立读取锁。
写入锁又称为排斥锁,在任何时刻只能有一个进程在文件的某个部分建立写入锁
在文件的同一部分不能同时建立读取锁和写入锁。
3.fcntl函数说明
(1)fcntl函数格式 具体的知识可以参看unix环境高级编程
Fcntl函数语法要点:
所需头文件:#include<sys/types.h>
#include<unistd.h>
#include<fcntl.h>
函数原型:int fcntl(int fd,cmd,struct flock *lock)
函数传入值:fd:文件描述符
Cmd:F_DUPFD:复制文件描述符
F_GETFD:获得fd的close-on-exec标志,若标志未设置,则文件经过exec函数之后仍保持打开状态。
F_SETFD:设置close-on-exec标志,该标志以参数arg的FD_CLOEXEC位决定。
F_GETFL:得到open设置的标志
F_SETFL:改变open设置的标志
F_GETFK:根据lock描述,决定是否上文件锁
F_SETFK:设置lock描述的文件锁
F_SETLKW:这是F_SETLK的阻塞版本(命令名中的W表示等待(wait))。如果存在其它锁,则调用进程睡眠;如果捕捉到信号则睡眠中断
这三种是在文件锁时cmd 使用的三种命令
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结构变量取值:
L_type:F_RDLCK:读取锁(共享锁)
F_WRLCK:写入锁(排斥锁)
F_UNLCK:解锁
L_stat:相对位移量(字节)
L_whence:相对位移量的起点(同lseek的whence):SEEK_SET:当前位置为文件开头,新位置为偏移量的大小
SEEK_CUR:当前位置为文件指针位置,新位置为当前位置加上偏移量
SEEK_END:当前位置为文件的结尾,新位置为文件的大小加上偏移量大小
L_len:加锁区域长度
l_pid:具有阻塞当前进程的锁,其持有进程的进程号存放在l_pid中,仅由F_GETLK 返回
(2)fcntl 使用实例
下面首先给出了使用fcnt函数的文件记录锁功能的代码实现。在该代码中,首先给flock结构体的对应
赋予相应的值。
接着调用两次fcntl函数。用F_GETLK命令判断是否可以进行flock所描述的锁操作:若可以进行,则flock
结构体的l_type会被设置为F_UNLCK,其他域不变;若不行,则l_pid被设置为拥有文件锁的进程号,其他域不变。
用F_SETLK和F_SETLKW命令设置flock结构所描述的锁操作,后者是前者的阻塞版。
当第一次调用fcntl时,使用F_GETLK命令获得当前文件被上锁的情况,由此可以判断能不能上锁操作;当第二次调用fcntl时,使用F_SETLKW命令对指定文件进行上锁/解锁操作。因为F_SETLKW命令是阻塞式操作,因此当不能把上锁/解锁操作进行下去时,运行被阻塞,直到能够进行操作为止。
实例说明
开启两个终端,在两个终端上同时运行./write_lock 程序,
当为写入锁,同一时刻只能有一个写入锁存在,
同一时刻只能一个进程对同一个文件写
注意运行的结果
1.首先运行右边终端中的程序,提示 write lock set by 3638 进程号
2.容纳后运行左边终端中的程序,提示 write lock already set by 3683
3.右边终端敲 回车 ,释放读锁
4.左边终端敲 回车 , 提示 write lock set by 3639
set_lock.h
- #ifndef _LOCK_SET_H
-
#define _ LOCK_SET_H
-
#include<sys/types.h>
-
#include<unistd.h>
-
#include<fcntl.h>
-
#include<stdio.h>
-
int lock_set(int fd,int type);
- #endif
set_lock.c
- /* lock_set.c */
-
#include"lock_set.h"
-
int lock_set(int fd, int type)
-
{
- struct flock old_lock, lock;
- lock.l_whence = SEEK_SET;
- lock.l_start = 0;
- lock.l_len = 0;
- lock.l_type = type;
- lock.l_pid = -1;
-
- /* 判断文件是否可以上锁 */
- 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);
- }
- }
-
- /* l_type 可能已被F_GETLK修改过 */
- lock.l_type = type;
- /* 根据不同的type值进行阻塞式上锁或解锁 */
- if ((fcntl(fd, F_SETLKW, &lock)) < 0)
- {
- printf("Lock failed:type = %d\n", lock.l_type);
- return 1;
- }
-
- switch(lock.l_type)
- {
- case F_RDLCK:
- {
- printf("Read lock set by %d\n", getpid());
- }
- break;
- case F_WRLCK:
- {
- printf("Write lock set by %d\n", getpid());
- }
- break;
- case F_UNLCK:
- {
- printf("Release lock by %d\n", getpid());
- return 1;
- }
- break;
- default:
- break;
- }/* end of switch */
- return 0;
- }
write_lock.c
- /* write_lock.c */
-
#include <unistd.h>
-
#include <sys/file.h>
-
#include <sys/types.h>
-
#include <sys/stat.h>
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include "lock_set.h"
-
int main(void)
-
{
- int fd;
- /* 首先打开文件 */
- fd = open("hello", O_RDWR | O_CREAT, 0644);
- if(fd < 0)
- {
- printf("Open file error\n");
- exit(1);
- }
- /* 给文件上写入锁 */
- lock_set(fd, F_WRLCK);
- getchar();
- /* 给文件解锁 */
- lock_set(fd, F_UNLCK);
- getchar();
- close(fd);
- return 0;
- }
4.flock函数说明
简单介绍下flock()函数:
定义函数 int flock(int fd,int operation);
函数说明 flock()会依参数operation所指定的方式对参数fd所指的文件做各种锁定或解除锁定的动作。此函数只能锁定整个文件,无法锁定文件的某一区域。
参数 operation有下列四种情况:
LOCK_SH 建立共享锁定。多个进程可同时对同一个文件作共享锁定。
LOCK_EX 建立互斥锁定。一个文件同时只有一个互斥锁定。
LOCK_UN 解除文件锁定状态。
LOCK_NB 无法建立锁定时,此操作可不被阻断,马上返回进程。通常与LOCK_SH或LOCK_EX 做OR(|)组合。
单一文件无法同时建立共享锁定和互斥锁定,而当使用dup()或fork()时文件描述词不会继承此种锁定。
返回值 返回0表示成功,若有错误则返回-1,错误代码存于errno。
为了更好的移植性,对于文件的打开与关闭我选择了fopen和fclose的组合,但flock的第一个参数要求的是int类型的文件描述符。这里对fopen返回的FILE类型的文件指针进行转换,转换为int型的文件描述符(假设open函数返回的文件描述符为fd,而fopen返回的文件指针为*fp,则fd等价于fp->_fileno).
下面为两个进程的实例:
int main(void)
{
FILE *fp = NULL;
int i = 20;
if ((fp = fopen("./file_lock.test", "r+b")) == NULL) //打开文件
printf("file open error!\n");
if (flock(fp->_fileno, LOCK_EX) != 0) //给该文件加锁
printf("file lock by others\n");
while(1) //进入循环,加锁时间为20秒,打印倒计时
{
printf("%d\n", i--);
sleep(1);
if (i == 0)
break;
}
fclose(fp); //20秒后退出,关闭文件
flock(fp->_fileno, LOCK_UN); //文件解锁
return 0;
}
int main(void)
{
FILE *fp = NULL;
int i = 0;
if ((fp = fopen("./file_lock.test", "r+b")) == NULL) //打开文件
printf("file open error!\n");
flock(fp->_fileno, LOCK_EX); //文件加锁
while(1) //进入循环
{
printf("%d\n", i++);
sleep(1);
}
fclose(fp); //关闭文件
flock(fp->_fileno, LOCK_UN); //释放文件锁
return 0;
}