linux c编程:记录锁
记录锁相当于线程同步中读写锁的一种扩展类型,可以用来对有亲缘或无亲缘关系的进程进行文件读与写的同步,通过fcntl函数来执行上锁操作。尽管读写锁也可以通过在共享内存区来进行进程的同步,但是fcntl记录上锁往往更容易使用,且效率更高。
记录锁的功能:当一个进程正在读或修改文件的某个部分是,它可以阻止其他进程修改同一文件区。如果我们在用读写锁对一个文件进行加锁。有2个读用户,一个写用户,在读用户读取的时候,写用户是不能操作该文件的。但是读用户也许只需要读取文件前面20个字节的内容,
而写用户需要操作文件末尾的20个字节,这种场景下其实读用户和写用户是可以同时进行的。但是读写锁却不能满足,因此拉长了处理时间。
因此记录锁有如下3个特点:
记录锁不仅仅可以用来同步不同进程对同一文件的操作,还可以通过对同一文件加记录锁,来同步不同进程对某一共享资源的访问,如共享内存,I/O设备。
• 对于劝告性上锁,当一个进程通过上锁对文件进行操作时,它不能阻止另一个非协作进程对该文件的修改。
• 即使是强制性上锁,也不能完全保证该文件不会被另一个进程修改。因为强制性锁对unlink函数没有影响,所以一个进程可以先删除该文件,然后再将修改后的内容保存为同一文件来实现修改
函数接口:
//根据cmd的不同有以下三种类型的调用
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
由函数名称可知fcntl的功能是对文件的控制操作,根据传入不同的操作类型命令cmd,fcntl会执行不同的操作,fcntl根据cmd不同,接收可变的参数。具体有以下五种类型的操作
/*
cmd = F_DUPFD,复制一个文件描述符;
*/
int fcntl(int fd, int cmd);
/*
cmd = F_GETFD,获得文件描述符标志;
cmd = F_SETFD,设置文件描述符标志;arg = 描述符标志的值,目前只定义了一个标志: FD_CLOEXEC
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
*/
/*
cmd = F_GETFL,获得文件状态标志;
cmd = F_SETFL,设置文件状态标志;arg = 状态标志的值
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
*/
/*
cmd = F_GETOWN,获得当前接收SIGIO和SIGURG信号的进程ID或进程组ID
cmd = F_SETOWN,设置接收SIGIO和SIGURG信号的进程ID或进程组ID;arg = 进程ID或进程组ID
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
*/
/*
Return value:
对于成功的调用,根据操作类型cmd不同,有以下几种情况:
F_DUPFD 返回新的文件描述符
F_GETFD 返回文件描述符标志
F_GETFL 返回文件状态标志
F_GETOWN 进程ID或进程组ID
All other commands 返回0
调用失败, 返回-1,并设置errno。
*/
上面四个功能都是fcntl提供的很常用的操作,关于记录锁的功能就是fcntl提供的第五个功能,具体使用如下:
int fcntl(int fd, int cmd, struct flock *lock);
/*
cmd = F_GETLK,测试能否建立一把锁
cmd = F_SETLK,设置锁
cmd = F_SETLKW, 阻塞设置一把锁
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 命令有效) */8
/*...*/
};
F_SETLK:获取(l_type为F_RDLCK或F_WRLCK)或释放由lock指向flock结构所描述的锁,如果无法获取锁时,该函数会立即返回一个EACCESS或EAGAIN错误,而不会阻塞。
F_SETLKW:F_SETLKW和F_SETLK的区别是,无法设置锁的时候,调用线程会阻塞到该锁能够授权位置。
F_GETLK:IF_GETLK主要用来检测是否有某个已存在锁会妨碍将新锁授予调用进程,如果没有这样的锁,lock所指向的flock结构的l_type成员就会被置成F_UNLCK,否则已存在的锁的信息将会写入lock所指向的flock结构中
这里需要注意的是,用F_GETLK测试能否建立一把锁,然后接着用F_SETLK或F_SETLKW企图建立一把锁,由于这两者不是一个原子操作,所以不能保证两次fcntl之间不会有另外一个进程插入并建立一把相关的锁,从而使一开始的测试情况无效。所以一般不希望上锁时阻塞,会直接通过调用F_SETLK,并对返回结果进行测试,以判断是否成功建立所要求的锁。
记录锁相当于读写锁的一种扩展类型,记录锁和读写锁一样也有两种锁:共享读锁(F_RDLCK)和独占写锁(F_WRLCK)。在使用规则上和读写锁也基本一样:
• 文件给定字节区间,多个进程可以有一把共享读锁,即允许多个进程以读模式访问该字节区;
• 文件给定字节区间,只能有一个进程有一把独占写锁,即只允许有一个进程已写模式访问该字节区;
• 文件给定字节区间,如果有一把或多把读锁,不能在该字节区再加写锁,同样,如果有一把写锁,不能再该字节区再加任何读写锁。
如下表所示:
需要说明的是:上面所阐述的规则只适用于不同进程提出的锁请求,并不适用于单个进程提出的多个锁请求。即如果一个进程对一个文件区间已经有了一把锁,后来该进程又试图在同一文件区间再加一把锁,那么新锁将会覆盖老锁
下面来进行测试:
测试一:在同一进程中测试能否在加写锁后,继续加读写锁
#include <fcntl.h>
#include <stdio.h>
void
lock_init(struct flock *lock, short type, short whence, off_t start,
off_t len)
{
if (lock
== NULL)
return;
lock->l_type
= type;
lock->l_whence
= whence;
lock->l_start
= start;
lock->l_len
= len;
}
int
readw_lock(int fd)
{
if (fd <
0)
{
return
-1;
}
struct
flock lock;
lock_init(&lock,
F_RDLCK, SEEK_SET, 0, 0);
if
(fcntl(fd, F_SETLKW, &lock) != 0)
{
return
-1;
}
return 0;
}
int
writew_lock(int fd)
{
struct
flock lock;
if (fd <
0)
{
return
-1;
}
lock_init(&lock,
F_WRLCK, SEEK_SET, 0, 0);
if
(fcntl(fd, F_SETLKW, &lock) != 0)
{
return
-1;
}
return 0;
}
int unlock(int
fd)
{
if (fd <
0)
{
return
-1;
}
struct
flock lock;
lock_init(&lock,
F_UNLCK, SEEK_SET, 0, 0);
if
(fcntl(fd, F_SETLKW, &lock) != 0)
{
return
-1;
}
return 0;
}
pid_t
lock_test(int fd, short type, short whence, off_t start, off_t len)
{
struct
flock lock;
lock_init(&lock,
type, whence, start, len);
if
(fcntl(fd, F_GETLK, &lock) == -1)
{
return
-1;
}
if(lock.l_type
== F_UNLCK)
return
0;
return
lock.l_pid;
}
#include
<stdio.h>
#include
<stdlib.h>
#include
<unistd.h>
#include
<sys/wait.h>
#include
<sys/types.h>
#include
"func1.h"
#define FILE_MODE (S_IRUSR
| S_IWUSR | S_IRGRP | S_IROTH)
int main()
{
char
*FILE_PATH='/home/zhf/codeblocks_prj/linux_c/1.txt';
pid_t
t1,t2;
int
fd=open(FILE_PATH,O_RDWR | O_CREAT,FILE_MODE);
writew_lock(fd);
t1=lock_test(fd,F_WRLCK,SEEK_SET,0,0);
t2=lock_test(fd,F_RDLCK,SEEK_SET,0,0);
printf("pid_t1=%ld\n",t1);
printf("pid_t2=%ld\n",t2);
unlock(fd);
return 0;
}
运行结果如下:可以看到反馈的进程号都是一样的。证明同一个进程锁是可以覆盖前一个的。
测试二:父进程中加写锁后,然后再子进程中测试能否继续加读写锁
int main()
{
char *FILE_PATH='/home/zhf/codeblocks_prj/linux_c/1.txt';
pid_t t1,t2,child;
int fd=open(FILE_PATH,O_RDWR | O_CREAT,FILE_MODE);
writew_lock(fd);
if(child=fork() == 0)
{
t1=lock_test(fd,F_WRLCK,SEEK_SET,0,0);
t2=lock_test(fd,F_RDLCK,SEEK_SET,0,0);
printf("pid_t1=%ld\n",t1);
printf("pid_t2=%ld\n",t2);
printf("pid_child=%ld\n",child);
}
sleep(3);
unlock(fd);
return 0;
}
测试结果如下:表明不同进程不能对已加写锁的同一文件区间,获得加锁权限;
前面的表格可以看出在已经获取写锁的情况,只能允许读锁,而拒绝写锁。下面这个代码来测试下这种情况
int main()
{
char *FILE_PATH='/home/zhf/codeblocks_prj/linux_c/1.txt';
pid_t t1,t2,child;
int ret;
int fd=open(FILE_PATH,O_RDWR | O_CREAT,FILE_MODE);
readw_lock(fd);
if(fork() == 0)
{
printf("child1 try to get write lock\n");
ret=writew_lock(fd);
if (ret >= 0)
{
printf("child1 get write lock\n");
unlock(fd);
printf("child1 release write lock\n");
exit(0);
}
else
{
printf("child1 get write lock failed\n");
}
exit(0);
}
if(fork() == 0)
{
sleep(3);
printf("child2 try to get read lock\n");
readw_lock(fd);
printf("child2 get read lock\n");
unlock(fd);
printf("child2 release read lock\n");
exit(0);
}
sleep(10);
unlock(fd);
return 0;
}
执行结果:
可以看出在父进程获取读锁的情况下,child1进程对文件同一区域获取写锁失败,而child2进程可以获取到读锁
下面的代码来测试父进程获得写锁,child1试图获取写锁,child2试图获取读锁
int main()
{
char *FILE_PATH='/home/zhf/codeblocks_prj/linux_c/1.txt';
pid_t t1,t2,child;
int ret1,ret2;
int fd=open(FILE_PATH,O_RDWR | O_CREAT,FILE_MODE);
writew_lock(fd);
if(fork() == 0)
{
printf("child1 try to get write lock\n");
ret1=writew_lock(fd);
if (ret1 >= 0)
{
printf("child1 get write lock\n");
unlock(fd);
printf("child1 release write lock\n");
exit(0);
}
else
{
printf("child1 get write lock failed\n");
}
exit(0);
}
if(fork() == 0)
{
sleep(3);
printf("child2 try to get read lock\n");
ret2=readw_lock(fd);
if(ret2 >= 0)
{
printf("child2 get read lock\n");
unlock(fd);
printf("child2 release read lock\n");
}
else
{
printf("child2 get read lock failed\n");
}
exit(0);
}
sleep(10);
unlock(fd);
return 0;
}
执行结果:
child1进程获取写锁失败,child2进程获取读锁失败
linux c编程:记录锁的更多相关文章
- Linux进程同步之记录锁(fcntl)
记录锁相当于线程同步中读写锁的一种扩展类型,可以用来对有亲缘或无亲缘关系的进程进行文件读与写的同步,通过fcntl函数来执行上锁操作.尽管读写锁也可以通过在共享内存区来进行进程的同步,但是fcntl记 ...
- inux c编程:记录锁
记录锁的功能:当一个进程正在读或修改文件的某个部分是,它可以阻止其他进程修改同一文件区.对于这个功能阐述我认为有三点要解释的: 记录锁不仅仅可以用来同步不同进程对同一文件的操作,还可以通过对同一文件加 ...
- linux 文件记录锁详解
一: linux记录锁更恰当的称呼应该是范围锁,它是对文件某个范围的锁定. 关于记录锁的功能就是fcntl提供的第五个功能,具体使用如下: int fcntl(int fd, int cmd, str ...
- Linux Socket编程
“一切皆Socket!” 话虽些许夸张,但是事实也是,现在的网络编程几乎都是用的socket. ——有感于实际编程和开源项目研究. 我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览 ...
- Linux Socket编程(不限Linux)【转】
转自:http://www.cnblogs.com/skynet/archive/2010/12/12/1903949.html “一切皆Socket!” 话虽些许夸张,但是事实也是,现在的网络编程几 ...
- Linux Socket编程(不限Linux)
"一切皆Socket!" 话虽些许夸张,但是事实也是,现在的网络编程几乎都是用的socket. --有感于实际编程和开源项目研究. 我们深谙信息交流的价值,那网络中进程之间如何通信 ...
- Linux系统编程@进程通信(一)
进程间通信概述 需要进程通信的原因: 数据传输 资源共享 通知事件 进程控制 Linux进程间通信(IPC)发展由来 Unix进程间通信 基于System V进程间通信(System V:UNIX系统 ...
- 【转】Linux Soclet编程
原文地址:http://www.cnblogs.com/skynet/archive/2010/12/12/1903949.html “一切皆Socket!” 话虽些许夸张,但是事实也是,现在的网络编 ...
- 高级I/O之记录锁
若两个人同时编辑一个文件,其后果将如何呢?在很多UNIX系统中,该文件的最后状态取决于写该文件的最后一个进程.但是对于有些应用程序(例如数据库),进程有时需要确保它正在单独写一个文件.为了向进程提供这 ...
随机推荐
- spring boot +mybatis+druid 多数据源配置
因为我的工程需要在两个数据库中操作数据,所以要配置两个数据库,我这里没有数据源没有什么主从之分,只是配合多数据源必须要指定一个主数据源,所以我就把 操作相对要对的那个数据库设置为主数据(dataBas ...
- CCNA2.0笔记_OSI模型
OSI模型分为七层: –由下到上分别为物理层.数据链路层.网络层.传输层.会话层.表示层.应用层. 特点:1.OSI模型每层都有自己的功能集:2.层与层之间相互独立又相互依靠:3.上层依赖于下层,下层 ...
- windows包管理
# windows包管理 ### 前言-----------------------------windows下好用的包管理程序类似于npm,安装curl程序方便.yum 的感觉 ### 包管理--- ...
- 自己动手搞定支付宝手机Wap网站支付接口 FOR ECShop
支付宝WAP网站版本的支付接口网上整合的比较少,看到很多网站在卖,顿觉无语. 主要是得自己查看支付宝官方提供的SDK中的开发文档. 支付宝sdk下载地址:https://doc.open.alipay ...
- linux控制台超时自动注销
仅让root用户超时退出: 编辑/root/.bash_profile文件,添加 export TMOUT=300 #300秒超时自动退出root 对所有用户设置自动注销: vi /etc/profi ...
- 第一百五十一节,封装库--JavaScript,表单验证--密码确认验证--回答验证--电子邮件验证加自动补全
封装库--JavaScript,表单验证--密码确认验证--回答验证--电子邮件验证加自动补全 效果图 html <div id="reg"> <h2 class ...
- java web学习笔记-jsp篇
1.java web简介 1.1静态页面与动态页面 表现形式 所需技术 静态网页 网页内容固定,不会更新 html,css 动态网页 网页内容由程序动态显示,自动更新 html,css,DB,ja ...
- Django实现组合搜索的方法示例
目录 一.实现方法 二.基本原理 三.代码样例 方法1:纯模板语言实现 方法二:使用simpletag实现 四.其他变化 1.model定义 2.处理函数变化 3.simpletag相应改变 一. ...
- 8782:乘积最大(划分dp)
8782:乘积最大 同洛谷 P1018 乘积最大 查看 提交 统计 提问 总时间限制: 1000ms 内存限制: 65536kB 描述 今年是国际数学联盟确定的“2000——世界数学年”,又恰逢我 ...
- mvc jsonresult 前台、后台解析
背景介绍:asp.net中使用json对象传参;mvc中可以直接使用jsonresult这个对象 一:怎么使用jsonresult public JsonResult text() { var str ...