linux I/O-记录锁(record lock)
记录锁(record lock)也称字节范围锁、文件范围锁、文件段锁,是一种在文件的某个字节、某个区域进行加锁的机制,记录锁总是和进程、文件相关。本篇博客介绍的是建议性记录锁。
1 记录锁的函数原型:
#include <unistd.h>#include <fcntl.h>int fcntl(int fd, int cmd, ... /* arg */ );
这个函数除了对文件记录锁进行操作外,还可以对文件其他属性进行操作。这里只讨论它的记录锁功能。使用记录锁时,此函数有三个参数,int fd,int cmd,struct flock *lock;第一个参数是打开的文件描述符,第二个参数是命令集合,主要有3个,第三个参数是指向记录锁结构的指针。记录锁结构如下所示:
struct flock {...short l_type; /* Type of lock: F_RDLCK,F_WRLCK, F_UNLCK */short l_whence; /* How to interpret l_start:SEEK_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(set by F_GETLK and F_OFD_GETLK) */...};
(1)l_type是指记录锁的的类型,有三种:F_RDLCK、F_WRLCK、 F_UNLCK,是共享读锁、独占写锁、解锁。加 读锁要求描述符要为读而打开,加写锁要求描述符为写而打开,最后一个是解锁。解锁的方法有三个:使用F_UNLCK、关闭文件描述符、进程终止。
(2)l_whence 是要加锁的文件位置,SEEK_SET代表从文件开始处,SEEK_CUR是从文件当前位置处、SEEK_END是代表从文件结尾处。
(3)l_start 锁的起始偏移值,在l_whence 值确定后,确定相对于l_whence 的起始偏移值,对于SEEK_CUR和SEEK_END,l_start可为负值。
(4)l_len 从起始偏移值开始的文件长度。如果l_len=0,代表从l_whence、l_start 确定的位置一直到文件末尾,无论向文件中添加多少数据。
(5)l_pid是进程pid值,只有cmd 为F_GETLK时,才使用此参数,代表加锁的进程ID。
2 第二个参数的三种情况
(1)F_GETLCK。测试一个指定的锁(即第三个参数lock)能否加上。如果能加上,则lock锁并不实际加锁到指定的位置,而是只是将lock.l_type设置未F_UNLCK,lock结构的其他参数保持不变。如果测试不能加上,则lock返回的是文件当前锁的l_type、l_whence 、l_start 、l_len和l_pid。锁的互斥性如下表所示。
当前区域状态 | 请求读锁 | 请求写锁 |
无锁 | 允许 | 允许 |
共享读锁 | 允许 | 拒绝 |
独占写锁 | 拒绝 | 拒绝 |
注意此函数的返回第三个参数并非一直是文件当前锁的信息,而是只有当尝试加锁失败时,lock才返回当前已加锁的锁信息。也就是测试锁的状态会有以下几种结果
(a)当前无锁,用读锁和写锁同时测试,则所得结果均为F_UNLCK
(b)当前为读锁,用读锁测试结果为F_UNLCK,用写锁测试结果为F_WRLCK
(c)当前为写锁,用读锁和写锁测试,结果均为F_WRLCK;
另外对于进程来说,
(a)一个进程对文件的一个位置加了锁,如果后续此进程在同一位置再加锁,则新锁将代替旧锁。
(b)一个进程解锁只能解自己加的锁,而不能解锁其它进程加的锁。
(c)利用fork产生的子进程,子进程不能继承父进程已有的锁,它需要重新获得自己的锁才行。
(d)进程不能测试自己的锁,因为进程对自己加的锁可以更换任何锁,尝试加锁总是成功的,所以进程测试自己的锁,得到的一直是F_UNLCK。
源程序locktest1.c(编程环境gcc 8.3.1,内核版本linu-4.18.0)
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <string.h>
#include <unistd.h>int lock_reg(int fd,int cmd,int type,off_t offset,int whence,off_t len)
{
struct flock lock;
lock.l_type=type;
lock.l_start=offset;
lock.l_whence=whence;
lock.l_len=len;
return(fcntl(fd,cmd,&lock));
}define read_lock(fd, offset, whence, len) \lock_reg((fd), F_SETLK, F_RDLCK, (offset), (whence), (len))
#define readw_lock(fd, offset, whence, len) \lock_reg((fd), F_SETLKW, F_RDLCK, (offset), (whence), (len))
#define write_lock(fd, offset, whence, len) \lock_reg((fd), F_SETLK, F_WRLCK, (offset), (whence), (len))
#define writew_lock(fd, offset, whence, len) \lock_reg((fd), F_SETLKW, F_WRLCK, (offset), (whence), (len))
#define un_lock(fd, offset, whence, len) \lock_reg((fd), F_SETLK, F_UNLCK, (offset), (whence), (len))
pid_t mylock_test(int fd,int type,off_t offset,int whence,off_t len,char *s)
{struct flock lock;lock.l_type=type;lock.l_start=offset;lock.l_whence=whence;lock.l_len=len;if(fcntl(fd,F_GETLK,&lock)<0){perror("fcntl error");exit(1);}if(lock.l_type==F_UNLCK)return (0);if(lock.l_type==F_RDLCK)strcpy(s,"F_RDLCK");else if(lock.l_type==F_WRLCK)strcpy(s,"F_WRLCK");return(lock.l_pid);
}
int main(void)
{
int fd,result;
char lockname[]="F_UNLCK";
int n;
pid_t pid;if((fd=open("templock",O_RDWR|O_CREAT|O_TRUNC,S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))<0)
{perror("open error");exit(1);
}
if((n=write(fd,"abcdefg",7))!=7)
{perror("wrtie error");exit(1);
}
// 0字节不加锁,1字节加读锁
if((result=read_lock(fd,1,SEEK_SET,1))<0)
{perror("read_lock error");exit(1);
}
//2字节加写锁
if((result=write_lock(fd,2,SEEK_SET,1))<0)
{perror("write_lock error");exit(1);
}if((pid=fork())<0)
{perror("fork error");exit(1);}
else if(pid==0){//子进程测试父进程加的锁if(mylock_test(fd,F_WRLCK,0,SEEK_SET,1,lockname)!=0) //用写锁测试0字节printf("test write lock,child found %s at 0th byte\n",lockname);else printf("test write lock,child found no lock at 0th byte\n");if(mylock_test(fd,F_RDLCK,1,SEEK_SET,1,lockname)!=0) //用读锁测试1字节printf("test read lock,child found %s at 1st byte\n",lockname);else printf("test read lock,child found no lock at 1st byte\n");if(mylock_test(fd,F_WRLCK,1,SEEK_SET,1,lockname)!=0) //用写锁测试1字节printf("test write lock,child found %s at 1st byte\n",lockname);else printf("test write lock,child found no lock at 1st byte\n");if(mylock_test(fd,F_RDLCK,2,SEEK_SET,1,lockname)!=0) //用读锁测试2字节printf("test read lock,child found %s at 2nd byte\n",lockname);else printf("test read lock,child found no lock at 2nd byte\n");if(mylock_test(fd,F_WRLCK,2,SEEK_SET,1,lockname)!=0) //用写锁测试2字节printf("test write lock,child found %s at 2nd byte\n",lockname);else printf("test write lock,child found no lock at 2nd byte\n");}
else {//父进程测试自己的锁,在0字节、1字节、2字节上均用写锁测试if(mylock_test(fd,F_WRLCK,0,SEEK_SET,1,lockname)!=0)printf("test write lock,parent found %s at 0th byte\n",lockname);else printf("test write lock,parent found no lock at 0th byte\n");if(mylock_test(fd,F_WRLCK,1,SEEK_SET,1,lockname)!=0)printf("test write lock,parent found %s at 1st byte\n",lockname);else printf("test write lock,parent found no lock at 1st byte\n");if(mylock_test(fd,F_WRLCK,2,SEEK_SET,1,lockname)!=0)printf("test write lock,parent found %s at 2nd byte\n",lockname);else printf("test write lock,parent found no lock at 2nd byte\n");if(result=waitpid(pid,NULL,0)!=pid){perror("waitpid error");exit(1);}
}
exit(0);
}
输出结果如下:
如上图所示,父进程测试自己的任何锁,得到的结果均为F_UNLCK;第一字节加的读锁,子进程用读锁测试得到的是F_UNLCK,用写锁测试得到的是F_RDLCK;第二字节加的写锁,子进程用读锁和写锁测试得到的结果均为F_WRLCK,所以用写锁测试能得到当前锁的真实状态。
(2)F_SETLK。对指定位置的区域进行加锁(l_type是F_RDLCK或F_WRLCK)或解锁(l_type是F_UNLCK)操作。如果根据锁的互斥性原则,导致加锁失败,则函数立即返回-1,并赋值errno为EACCES、EAGAIN。个人认为一般情况下,一个进程对一个描述符的特定位置解锁总是 成功的,因为默认情况下,进程对特定位置是不加锁的,同时它又不能解锁其他进程的锁,所以一般来说加解锁总是成功的。
(3)F_SETLKW,是加锁或解锁的阻塞版本,也就是当加锁失败时,函数将阻塞休眠,W代表wait;当下列条件发生时,进程被唤醒
(a)已具备加锁条件
(b)进程休眠期间,捕捉到一个信号并从信号处理程序中返回,则函数返回-1,并赋值errno为EINTR。
在此要说的是进程具备条件后,被唤醒的时间。源程序locktest2.c(只写主函数,其他参考locktest1.c)
int main(void)
{int fd,result;pid_t pid;int n;char lockname[]="F_UNLCK";if((fd=open("templock2",O_RDWR|O_CREAT|O_TRUNC,S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))<0){perror("open error");exit(1);}if((n=write(fd,"abcdefg",7))!=7){perror("wrtie error");exit(1);}
// 0字节加写锁
if((result=write_lock(fd,0,SEEK_SET,1))<0)
{perror("write_lock error");exit(1);
}if((pid=fork())<0){perror("fork error");exit(1);}
else if(pid==0){if(mylock_test(fd,F_WRLCK,0,SEEK_SET,1,lockname)!=0) //用写锁测试0字节printf("test write lock,child found %s at 0th byte\n",lockname);else printf("test write lock,child found no lock at 0th byte\n");if(writew_lock(fd,0,SEEK_SET,1)<0)//阻塞获得写锁{perror("child writew_lock error");exit(1);}elseprintf("child add write lock\n");close(fd);}
else {sleep(2);// 父进程休眠2秒钟,让子进程先执行,并阻塞if(un_lock(fd,0,SEEK_SET,1)<0){perror("parent un_lock error");exit(1);}printf("parent unlock 0th bytes\n");if(result=waitpid(pid,NULL,0)!=pid){perror("waitpid error");exit(1);}
close(fd);
}
exit(0);
}
程序执行结果如下:
从程序输出结果来看,父进程解锁0字节后,子进程被唤醒,并打印“child add write lock”,然后父进程继续执行并打印"parent unlock 0th bytes"。
3 其他说明
(1)函数返回值。刚才说了,根据第二个参数的不同,函数有不同的返回值,正常情况下返回0,出错情况下会返回-1,并赋值errno。常见的错误码如下:
(a)EACCESS或EAGAIN。加锁失败
(b)EBADF。文件描述符尚未打开或文件描述符打开的模式不对,操作无法执行
(c)EINTR:F_SETLK或F_SETLKW操作时,被信号中断
(d)EINVAL。第二个参数无法识别或参数无效
(2)如果进程关闭指向一个文件的任何一个描述符,则此进程加在文件的所有锁都会失效,不管进程是在哪个描述符上获得的。也就是说使用dup、open操作的使同一个文件,关闭这个文件上打开的任何一个描述符,锁均被释放。
(3) 进程的所有线程共享进程的锁。
修改locktest2.c为循环的情况,本程序locktest3.c
int main(void)
{int fd,result;pid_t pid;int n;char lockname[]="F_UNLCK";if((fd=open("templock2",O_RDWR|O_CREAT|O_TRUNC,S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))<0){perror("open error");exit(1);}if((n=write(fd,"abcdefg",7))!=7){perror("wrtie error");exit(1);}
// 0字节加写锁
if((result=write_lock(fd,0,SEEK_SET,1))<0)
{perror("write_lock error");exit(1);
}if((pid=fork())<0){perror("fork error");exit(1);}
else if(pid==0){if(mylock_test(fd,F_WRLCK,0,SEEK_SET,1,lockname)!=0) //用写锁测试0字节printf("test write lock,child found %s at 0th byte\n",lockname);else printf("test write lock,child found no lock at 0th byte\n");for(int i=0;i<5;i++){if(writew_lock(fd,0,SEEK_SET,1)<0)//阻塞获得写锁{perror("child writew_lock error");exit(1);}elseprintf("child add write lock\n");if(un_lock(fd,0,SEEK_SET,1)<0) //释放获得的锁{perror("child unlock error");exit(1);}}close(fd);}
else {sleep(2);// 父进程休眠2秒钟,让子进程先执行,并阻塞for(int i=0;i<5;i++){if(un_lock(fd,0,SEEK_SET,1)<0){perror("parent un_lock error");exit(1);}printf("parent unlock 0th bytes\n");if(readw_lock(fd,0,SEEK_SET,1)<0) //阻塞获得读锁{perror("parent readw_lock error");exit(1);}}if(result=waitpid(pid,NULL,0)!=pid){perror("waitpid error");exit(1);}close(fd);
}
exit(0);
}
输出结果如下:
从结果看出,程序不是交替输出,也就是子进程 获得写锁并释放后,父进程没有立即执行,而是由子进程进入下一个循环。子进程循环结束后,锁被释放,父进程才又开始执行。
参考资料:
1 https://man7.org/linux/man-pages/man2/fcntl.2.html
linux I/O-记录锁(record lock)相关推荐
- Mysql 死锁过程及案例详解之记录锁与间隔锁Record Lock Gap Lock
记录锁Record Lock与间隔锁GAP Lock 记录锁Record Lock 记录锁Record Locks又称为行锁,它同时包含索引和间隔锁.记录锁可以是共享锁也可能是排他锁.可以通过perf ...
- MySQL锁 —— 记录锁、间隙锁、临键锁、自增锁
MySQL的事务隔离级别有:未提交读.已提交读.可重复读.可串行化,除了串行化,在不同的隔离级别下,都会存在不同的并发问题,MySQL默认隔离级别是可重复读,在这种隔离级别下,存在幻读的问题,MySQ ...
- MySQL怎么运行的系列(十)Innodb中的锁:记录锁、临键锁、间隙锁、意向锁
本系列文章目录 展开/收起 MySQL怎么运行的系列(一)mysql体系结构和存储引擎 MySQL怎么运行的系列(二)Innodb缓冲池 buffer pool 和 改良版LRU算法 Mysql怎么运 ...
- mysql主键查询gap锁失效,mysql记录锁(record lock),间隙锁(gap lock),Next-key锁(Next-key lock)...
1. 什么是幻读? 幻读是在可重复读的事务隔离级别下会出现的一种问题,简单来说,可重复读保证了当前事务不会读取到其他事务已提交的 UPDATE 操作.但同时,也会导致当前事务无法感知到来自其他事务中的 ...
- Mysql INNODB引擎行锁的3种算法 Record Lock Next-Key Lock Grap Lock
Mysql INNODB引擎行锁的3种算法 InnoDB存储引擎有3种行锁的算法,其分别是: □ Record Lock:单个行记录上的锁 Record Lock总是会去锁住索引记录,如果InnoDB ...
- mysql 共享锁和排他锁 意向锁 记录锁 Gap Locks Next-Key Locks 插入意向锁介绍
文章目录 前言: 共享锁和排它锁 LOCK TABLES 和 UNLOCK TABLES 语句 意向锁 记录锁Record Locks 间隙锁 Gap Locks 下一键锁定 next-key 插入意 ...
- mysql临键锁_详解 MySql InnoDB 中的三种行锁(记录锁、间隙锁与临键锁)
详解 MySql InnoDB 中的三种行锁(记录锁.间隙锁与临键锁) 前言 InnoDB 通过 MVCC 和 NEXT-KEY Locks,解决了在可重复读的事务隔离级别下出现幻读的问题.MVCC ...
- 记录锁、间隙锁和临键锁
记录锁.间隙锁和临键锁 Record Lock A record lock is a lock on an index record. For example, SELECT c1 FROM t WH ...
- MySQL 锁全集(共享锁/排它锁、记录锁/间隙锁/临键锁)
提升工作效率利器: Mac App Store 上的"Whale - 任务管理.时间.卡片.高效率" 简介:锁是计算机协调多个进程或线程并发访问某一资源变得有序的机制. 一.锁分 ...
- MySQL的锁机制 - 记录锁、间隙锁、临键锁
记录锁(Record Locks) 记录锁是 封锁记录,记录锁也叫行锁,例如: SELECT * FROM `test` WHERE `id`=1 FOR UPDATE; 它会在 id=1 的记录上加 ...
最新文章
- Openstack_通用模块_Oslo_vmware 创建 vSS PortGroup
- 广药谋定中国农民丰收节交易会-万祥军:谋定乡村振兴基金
- 函数的参数-列表使用+=本质上是调用extend方法
- 网站攻击软件_如何防止网站建设中出现安全问题?
- python语法学习—打印九九乘法表
- vue-seamless-scroll在小米手机上显示不正常 显示出两行的问题
- 基于JAVA+SpringMVC+Mybatis+MYSQL的培训中心管理系统
- 地表径流分布数据/水文站点分布/降雨量分布/辐射分布数据
- Netty自带的心跳机制——IdleStateHandler
- 2021暑假Leetcode刷题——Two Pointers(1)
- Android studio Intent
- 外汇EA真的有用吗?外汇EA如何设置
- Unity制作UFO小游戏
- bootstrap table表格点击行checkbox勾选或取消勾选
- 计算机考研408真题(全国统考2009--2020)、985高校计算机考研资料(清北+北理+北邮+武大+华科+浙大+复旦+哈工大+西安交大+华南理工)、王道四件套、天勤四件套---百度网盘免费下载
- 计算机专业秃顶图片,大学被叫惨的三大专业,计算机秃顶是常事,医学专业这个就惨了!...
- 盘点中国顶级黑客Top10,雷军也名列其中!
- LINODE优惠码与服务器搭建
- UG如何把语言改成中文,UG如何把界面语言改成中文
- Linux下U盘变成只读
热门文章
- 旋转数组(右旋转,js实现,unshift,splicec实现)
- android 6.0 点亮屏幕,android6.0 otg连接设备 点亮屏幕(案例)
- php和xml区别,php和XML
- json为全局变量 vue_Vue-cli开发笔记二----------接口调用、配置全局变量
- lambda qt 参数 槽函数_C++中的lambda表达式用法
- java多线程【线程安全问题】
- 搜狗浏览器收藏夹在哪_安卓Edge浏览器最新版42.0.2轻体验,整体优良但无特别惊喜...
- 软件测试常见笔试面试题(一)
- linux 查看hadoop命令大全,linux下hadoop集群常用命令
- php retoken,laravel 5.5 关闭token的3种实现方式