本节讲述的是利用fcntl函数来实现不同进程间的上锁,不管这些进程有没有亲缘关系。前面讲述过有名信号量同样也是可以用在没有亲缘关系的进程间上锁的。而针对线程上锁的一些机制,想要用在不同进程间上锁,就需要把锁放在进程共享内存区操作。记录上锁主要是用到fcntl 函数。
fcntl 函数
#include<unistd.h>
#include<fcntl.h>
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd ,struct flock* lock);
返回值:若成功,返回依赖于cmd的值;若出错,则返回-1,错误原因存于errno
参数:
- fd:文件描述符
cmd: 命令有很多,这里介绍和记录锁有关的几个命令。
- F_GETLK :取得文件锁的状态。
- F_SETLK :设置文件锁定的状态。如果无法建立锁定,则返回-1。
- F_SETLKW:和F_SETLK 作用相同,但是无法建立锁定时,此调用会一直等到锁定动作成功为止。
flock 结构体:
//POSIX只定义fock结构中必须有以下的数据成员,具体实现可以增加 ,而且是不区分顺序的 ,所以不能按照顺序初始化。
struct flock {
short l_type; /* 锁的类型: F_RDLCK, F_WRLCK, F_UNLCK */
short l_whence; /* 加锁的起始位置:SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start; /* 加锁的起始偏移,相对于l_whence */
off_t l_len; /* 指定从该偏移开始的连续字节*/
pid_t l_pid; /* 已经占用锁的PID(只对F_GETLK 命令有效) */
/*...*/
};
l_type 有三种状态:
- F_RDLCK :读锁
- F_WRLCK :写锁
- F_UNLCK :删除之前建立的锁
l_whence 也有三种方式:
- SEEK_SET: 以文件开头为起始位置
- SEEK_CUR:以当前文件读写位置为起始位置
- SEEK_END:以文件结尾为起始位置
l_start:加锁的起始偏移,相对于l_whence
- l_len:指定从该偏移开始的连续字节,0 表示起始偏移到文件偏移的最大肯能值,即不管在后面增加多少数据都在锁的范围内
- l_pid:已经占用锁的PID(只对F_GETLK 命令有效)
锁住整个文件有两种方式:
- 指定1_whence 成员为SEEK_SET,1_start 成员为0,1_len 成员为0
- 使用lseek把读写指针定位到文件头,然后指定 1_whence 成员为 SEEK_CUR,1_start成员为0,1_len 成员为0 。
第一种比较方便,建议使用。
示例程序:
/* lock_fcntl.c*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#define FILENAME "lock_file"
#define LOCK
#ifdef LOCK
void my_lock(int fd)
{
struct flock lock;
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0; //锁住整个文件
if (fcntl(fd, F_SETLKW, &lock) == -1)
{
perror("fcntl");
exit(-1);
}
}
void my_unlock(int fd)
{
struct flock lock;
lock.l_type = F_UNLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0; //解锁整个文件
if (fcntl(fd, F_SETLK, &lock) == -1)
{
perror("fcntl");
exit(-1);
}
}
#else
void my_lock(int fd)
{
}
void my_unlock(int fd)
{
}
#endif
int main (int argc, char **argv)
{
pid_t pid;
long int count = 0;
char buff[128] = {0};
int i,n;
int fd = open(FILENAME,O_RDWR | O_CREAT,0777);
if(fd < 0)
{
perror("open");
}
for (i = 0; i < 7; i++)
{
my_lock(fd); //上锁
if (lseek(fd, 0, SEEK_SET) < 0) //将读写位置移到文件开头
{
perror("lseek");
exit(-1);
}
if (n = read(fd, buff, sizeof(buff) ) < 0)
{
perror("read");
exit(-1);
}
//sleep(1); //让实验效果更加明显
//buff[n] = '\0';
sscanf(buff, "%ld\n", &count); //把字符串转化成整数保存到count中
printf("%s: pid = %ld, count = %ld\n", argv[0], (long)getpid(), count);
count++;
memset(buff,0,sizeof(buff));
snprintf(buff, sizeof(buff), "%ld\n", count); //把数字转化成字符串存储到buff数组中
if (lseek(fd, 0, SEEK_SET) < 0) //将读写位置移到文件开头
{
perror("lseek");
exit(-1);
}
if (write(fd, buff, strlen(buff)) < 0)
{
perror("write");
exit(-1);
}
my_unlock(fd); //解锁
}
return 0;
}
关掉宏 LOCK 编译,即不加记录锁,同时在后台运行两个程序,结果如下:
ubuntu:~/test/process_test/lock$ gcc lock_fcntl.c -o lock_fcntl
ubuntu:~/test/process_test/lock$ rm lock_file
ubuntu:~/test/process_test/lock$ ./lock_fcntl & ./lock_fcntl &
./lock_fcntl: pid = 83095, count = 0
./lock_fcntl: pid = 83094, count = 0
./lock_fcntl: pid = 83095, count = 1
./lock_fcntl: pid = 83095, count = 2
./lock_fcntl: pid = 83094, count = 2
./lock_fcntl: pid = 83095, count = 3
./lock_fcntl: pid = 83095, count = 4
./lock_fcntl: pid = 83094, count = 4
./lock_fcntl: pid = 83095, count = 5
./lock_fcntl: pid = 83094, count = 5
./lock_fcntl: pid = 83095, count = 6
./lock_fcntl: pid = 83094, count = 6
./lock_fcntl: pid = 83094, count = 7
./lock_fcntl: pid = 83094, count = 8
例子讲述主要有三个步骤:
- 读取文件的数据写到count变量中
- count自加1
- 把count里面的数据写入文件
在上面的操作中,如果不加锁,则我们无法保证当前进程和其他进程这几个步骤是否会交叉。比如,进程一执行到步骤2,读取到的count变量的值为0,count自加1,count值为1,此时进程二执行到步骤1,读取到的count值为0。接着进程一把count写到文件中。此时,文件的数据是1,此后进程二也把count变量的值写数据到文件,文件的数据还是1。如果进程一和进程二先后执行的时间足够长,那么文件的数据就会是2。以此类推,我们执行程序后的文件的数据是小于等于13,一般来说是小于13。而当我们加上锁之后,因为这几个步骤是不会出现交叉的行为,所以每次文件里面的数据肯定是13。结果如下所示。
打开宏 LOCK 编译,同时在后台运行两个程序,结果如下:
ubuntu:~/test/process_test/lock$ gcc lock_fcntl.c -o lock_fcntl
ubuntu:~/test/process_test/lock$ rm lock_file
ubuntu:~/test/process_test/lock$ ./lock_fcntl & ./lock_fcntl &
./lock_fcntl: pid = 83115, count = 0
./lock_fcntl: pid = 83115, count = 1
./lock_fcntl: pid = 83115, count = 2
./lock_fcntl: pid = 83115, count = 3
./lock_fcntl: pid = 83115, count = 4
./lock_fcntl: pid = 83115, count = 5
./lock_fcntl: pid = 83115, count = 6
./lock_fcntl: pid = 83116, count = 7
./lock_fcntl: pid = 83116, count = 8
./lock_fcntl: pid = 83116, count = 9
./lock_fcntl: pid = 83116, count = 10
./lock_fcntl: pid = 83116, count = 11
./lock_fcntl: pid = 83116, count = 12
./lock_fcntl: pid = 83116, count = 13