本節講述的是利用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