进程同步方式(一)--记录锁
记录锁
定义
一个进程正在读或修改文件的某个部分时,可以使用记录锁锁定文件的一个区域(也可能是整个文件)。它是一个字节范围内的锁。
接口声明
POSIX记录锁是使用fcntl 函数实现。Linux系统支持POSIX标准。
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
参数cmd 为F_GETLK,F_SETLK和F_SETLKW用于测试、获取、释放记录锁(也称为文件段或文件区锁)。 第三个参数lock是指向至少具有以下字段(未指定顺序)的结构的指针。其中,GETLK测试能否建立一把锁,F_SETLK设置由flock描述的锁,F_SETLKW是F_SETLK的阻塞版本。
struct flock {
...
short l_type; /* Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */
short l_whence; /* How to interpret l_start: EEK_SET, SEEK_CUR, SEEK_END */
off_t l_start; /* Starting offset for lock */
off_t l_len; /* Number of bytes to lock */
pid_t l_pid; /* PID of process blocking our lock (F_GETLK only) */
...
};
多进程提出锁的请求及结果
单进程提出锁的请求及结果
单进程如果在文件的某个区域加了锁,如果又在这个区域加一个锁,则原来的锁被覆盖。
建议性锁和强制性锁
建议性锁:建议性锁并不从内核限制程序访问文件,而是依赖各个合作进程(cooperating process)之间遵循相应的规则。
强制性锁:强制性锁会让内核检查每一个open、read、和write,验证调用进程是否违背了正访问的文件上的某一把锁。
强制性锁对其他进程的read和write的影响
测试例子
例子-测试文件所是否上锁
代码
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
pid_t lock_test(int fd, int type, off_t offset, int whence, off_t len)
{
struct flock lock;
lock.l_type = type; /* F_RDLCK or F_WRLCK */
lock.l_start = offset; /* byte offset, relative to l_whence */
lock.l_whence = whence; /* SEEK_SET, SEEK_CUR, SEEK_END */
lock.l_len = len; /* #bytes (0 means to EOF) */
if (fcntl(fd, F_GETLK, &lock) < 0){
printf("fcntl error\n");
exit(1);
}
if (lock.l_type == F_UNLCK)
return(0); /* false, region isn't locked by another proc */
return(lock.l_pid); /* true, return pid of lock owner */
}
int main(){
int fd = open("locktest.c", O_RDWR );
struct flock fl;
fl.l_type = F_WRLCK;
fl.l_start = 0;
fl.l_whence = SEEK_SET;
fl.l_len = 100;
fcntl(fd, F_SETLK, &fl);
printf("pid_t %d \n",lock_test(fd, F_WRLCK, 2,SEEK_CUR , 10));
sleep(1000);
}
运行
[root@bogon advio]# gcc locktest.c -o locktest1
[root@bogon advio]# gcc locktest.c -o locktest2
终端1运行locktest1 ,如下所示:
[root@bogon advio]# ./locktest1
pid_t 0
终端2运行locktest2 ,如下所示:
[root@bogon advio]# ./locktest2
pid_t 29423
说明
F_GETLK (struct flock *)
在输入这个调用时,flock描述了一个我们想要放在文件上的锁。 如果可以放置锁,则fcntl()实际上并不放置它,但在锁的l_type字段中返回F_UNLCK,并保持结构的其他字段不变。 如果一个或多个不兼容的锁会阻止这个锁被放置,那么fcntl()返回有关锁的l_type,l_whence,l_start和l_len字段中的其中一个锁的详细信息,并将l_pid设置为持有该锁的进程的PID。知道这一点很重要。
例子-加锁释放锁
代码
int main(){
int fd = open("locktest.c", O_RDWR );
struct flock fl,fl2;
fl.l_type = F_WRLCK;
fl.l_start = 0;
fl.l_whence = SEEK_SET;
fl.l_len = 100;
fcntl(fd, F_SETLK, &fl);
fl2.l_type = F_UNLCK;
fl2.l_start = 0;
fl2.l_whence = SEEK_SET;
fl2.l_len = 100;
fcntl(fd, F_SETLK, &fl2);
}
CentOS开启强制性锁
如果要开启强制性锁,要由以下两个步骤完成:
1)在文件系统mount的时候加上-o mand参数
2)打开文件的设置组ID位并且关闭其组执行位
在shell下可以这样打开
chmod g+s <filename>
chmod g-x <filename>
或者通过fchmod函数设置,参考下面的代码
fchmod(fd,(statbuf.st_mode & ~S_IXGRP) | S_ISGID)
测试代码
说明:以下代码从《高级UNIX编程》代码中拷贝来的,特此说明。
#include "apue.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
int fd;
pid_t pid;
char buf[5];
struct stat statbuf;
if (argc != 2) {
fprintf(stderr, "usage: %s filename\n", argv[0]);
exit(1);
}
if ((fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, FILE_MODE)) < 0)
err_sys("open error");
if (write(fd, "abcdef", 6) != 6)
err_sys("write error");
/* turn on set-group-ID and turn off group-execute */
if (fstat(fd, &statbuf) < 0)
err_sys("fstat error");
if (fchmod(fd, (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0)
err_sys("fchmod error");
TELL_WAIT();
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid > 0) { /* parent */
/* write lock entire file */
if (write_lock(fd, 0, SEEK_SET, 0) < 0)
err_sys("write_lock error");
TELL_CHILD(pid);
if (waitpid(pid, NULL, 0) < 0)
err_sys("waitpid error");
} else { /* child */
WAIT_PARENT(); /* wait for parent to set lock */
set_fl(fd, O_NONBLOCK);
/* first let's see what error we get if region is locked */
if (read_lock(fd, 0, SEEK_SET, 0) != -1) /* no wait */
err_sys("child: read_lock succeeded");
printf("read_lock of already-locked region returns %d\n", errno);
/* now try to read the mandatory locked file */
if (lseek(fd, 0, SEEK_SET) == -1)
err_sys("lseek error");
if (read(fd, buf, 2) < 0)
err_ret("read failed (mandatory locking works)");
else
printf("read OK (no mandatory locking), buf = %2.2s\n", buf);
}
exit(0);
}
测试结果及说明
[root@bogon advio]# ./mandatory temp.lock
read _lock of already-locked region returns 11
read OK (no mandatory locking), buf = ab
从上面的输出可以看出来CentOS默认是没有开启强制性锁,因为子进程能从temp.lock文件读出数据。
父进程加了写锁,子进程加读锁的时候出错,errno为11。
注意事项
1)当一个进程终止时,它建立的锁全部释放。2)进程使用文件锁,只能该进程解锁。其他进程不能解锁。
3)子进程不继承父进程的锁。
4)在执行exec函数后,新程序可以继承原执行程序的锁。
参考资料
[1]http://shareinto.github.io/2016/12/07/linux%E4%B8%AD%E7%9A%84%E6%96%87%E4%BB%B6%E5%BC%BA%E5%88%B6%E9%94%81/
[2].《UNIX高级编程》第三版