记录锁的功能:当一个进程正在读或修改文件的某个部分是,它可以阻止其他进程修改同一文件区。对于这个功能阐述我认为有三点要解释的:

记录锁不仅仅可以用来同步不同进程对同一文件的操作,还可以通过对同一文件加记录锁,来同步不同进程对某一共享资源的访问,如共享内存,I/O设备。

对于劝告性上锁,当一个进程通过上锁对文件进行操作时,它不能阻止另一个非协作进程对该文件的修改。

即使是强制性上锁,也不能完全保证该文件不会被另一个进程修改。因为强制性锁对unlink函数没有影响,所以一个进程可以先删除该文件,然后再将修改后的内容保存为同一文件来实现修改。

1记录锁函数接口

记录上锁的POSIX接口函数fcntl如下:

/* Do the file control operation described by CMD on FD.

The remaining arguments are interpreted depending on CMD. */

int fcntl (int __fd, int __cmd, ...);

//根据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会执行不同的操作,fcnt根据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, 阻塞设置一把锁

*/

//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 命令有效) */

/*...*/

};

//Return value: 前面已经说明;

l F_SETLK:获取(l_type为F_RDLCK或F_WRLCK)或释放由lock指向flock结构所描述的锁,如果无法获取锁时,该函数会立即返回一个EACCESS或EAGAIN错误,而不会阻塞。

l F_SETLKW:F_SETLKW和F_SETLK的区别是,无法设置锁的时候,调用线程会阻塞到该锁能够授权位置。

l F_GETLK:IF_GETLK主要用来检测是否有某个已存在锁会妨碍将新锁授予调用进程,如果没有这样的锁,lock所指向的flock结构的l_type成员就会被置成F_UNLCK,否则已存在的锁的信息将会写入lock所指向的flock结构中

这里需要注意的是,用F_GETLK测试能否建立一把锁,然后接着用F_SETLK或F_SETLKW企图建立一把锁,由于这两者不是一个原子操作,所以不能保证两次fcntl之间不会有另外一个进程插入并建立一把相关的锁,从而使一开始的测试情况无效。所以一般不希望上锁时阻塞,会直接通过调用F_SETLK,并对返回结果进行测试,以判断是否成功建立所要求的锁。

2记录锁规则说明

前面我们说了记录锁相当于读写锁的一种扩展类型,记录锁和读写锁一样也有两种锁:共享读锁(F_RDLCK)和独占写锁(F_WRLCK)。在使用规则上和读写锁也基本一样:

  • 1 文件给定字节区间,多个进程可以有一把共享读锁,即允许多个进程以读模式访问该字节区;
  • 2 文件给定字节区间,只能有一个进程有一把独占写锁,即只允许有一个进程已写模式访问该字节区;
  • 3 文件给定字节区间,如果有一把或多把读锁,不能在该字节区再加写锁,同样,如果有一把写锁,不能再该字节区再加任何读写锁。

如下表所示:

需要说明的是:上面所阐述的规则只适用于不同进程提出的锁请求,并不适用于单个进程提出的多个锁请求。即如果一个进程对一个文件区间已经有了一把锁,后来该进程又试图在同一文件区间再加一把锁,那么新锁将会覆盖老锁。

下面进行测试;第一个程序是在同一进程中测试能否在加写锁后,继续加读写锁

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 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 main()

{

char *path="home/zhf/test1.txt";

int fd;

pid_t t,t1;

fd = fopen(path, "r");

writew_lock(fd);

t=lock_test(fd, F_WRLCK, SEEK_SET, 0, 0);

t1=lock_test(fd, F_RDLCK, SEEK_SET, 0, 0);

printf("the pid_t is %d\n",t);

printf("the pid_t is %d\n",t1);

unlock(fd);

return 0;

}

执行结果表明同一进程可以对已加锁的同一文件区间,仍然能获得加锁权限;

2 由fork产生的子进程不继承父进程所设置的锁。即对于父进程建立的锁而言,子进程被视为另一个进程。记录锁本身就是用来同步不同进程对同一文件区进行操作,如果子进程继承了父进程的锁,那么父子进程就可以同时对同一文件区进行操作,这有违记录锁的规则,所以存在这么一条规则。

int main()

{

int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);

writew_lock(fd);

if (fork() == 0)

{

cout<<lock_test(fd, F_WRLCK, SEEK_SET, 0, 0)<<endl;

cout<<lock_test(fd, F_RDLCK, SEEK_SET, 0, 0)<<endl;

exit(0);

}

sleep(3);

unlock(fd);

return 0;

}

我们知道在前面已经说过,同一个进程可以重复对同一个文件区间加锁,后加的锁将覆盖前面加的锁。那么再假设如果子进程继承了父进程的锁,那么子进程可以对该锁进行覆盖,那么在子进程内对该锁是否能获得权限的测试应该是可以,但测试结果为:

24791

24791

表明已经进程24791已经占用该锁,所以假设不成立,子进程不会继承父进程的锁;

3 记录锁的读和写的优先级

int main()

{

int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);

readw_lock(fd);

//child  1

if (fork() == 0)

{

cout<<"child 1 try to get write lock..."<<endl;

writew_lock(fd);

cout<<"child 1 get write lock..."<<endl;

unlock(fd);

cout<<"child 1 release write lock..."<<endl;

exit(0);

}

//child 2

if (fork() == 0)

{

sleep(3);

cout<<"child 2 try to get read lock..."<<endl;

readw_lock(fd);

cout<<"child 2 get read lock..."<<endl;

unlock(fd);

cout<<"child 2 release read lock..."<<endl;

exit(0);

}

sleep(10);

unlock(fd);

return 0;

}

在Linux 2.6.18下执行结果如下:

child 1 try to get write lock...

child 2 try to get read lock...

child 2 get read lock...

child 2 release read lock...

child 1 get write lock...

child 1 release write lock...

可知在有写入进程等待的情况下,对于读出进程的请求,系统会一直给予的。那么这也就可能导致写入进程饿死的局面。

inux c编程:记录锁的更多相关文章

  1. linux c编程:记录锁

    记录锁相当于线程同步中读写锁的一种扩展类型,可以用来对有亲缘或无亲缘关系的进程进行文件读与写的同步,通过fcntl函数来执行上锁操作.尽管读写锁也可以通过在共享内存区来进行进程的同步,但是fcntl记 ...

  2. 高级I/O之记录锁

    若两个人同时编辑一个文件,其后果将如何呢?在很多UNIX系统中,该文件的最后状态取决于写该文件的最后一个进程.但是对于有些应用程序(例如数据库),进程有时需要确保它正在单独写一个文件.为了向进程提供这 ...

  3. Mysql加锁过程详解(9)-innodb下的记录锁,间隙锁,next-key锁

    Mysql加锁过程详解(1)-基本知识 Mysql加锁过程详解(2)-关于mysql 幻读理解 Mysql加锁过程详解(3)-关于mysql 幻读理解 Mysql加锁过程详解(4)-select fo ...

  4. 推荐:mysql锁 innodb下的记录锁,间隙锁,next-key锁

    你需要知道的 之前我们介绍了排他锁,其实innodb下的记录锁(也叫行锁),间隙锁,next-key锁统统属于排他锁. 行锁 记录锁其实很好理解,对表中的记录加锁,叫做记录锁,简称行锁. 生活中的间隙 ...

  5. 【Mysql】表锁 行锁 记录锁 间隙锁

    Mysql中的锁 基于锁的属性分类:共享锁.排他锁. 基于锁的状态分类:意向共享锁.意向排它锁 根据锁的粒度分类:全局锁.页锁.表级锁.行锁(记录锁.间隙锁.和临键锁),实际上的锁就这些,上面两种分类 ...

  6. [11]APUE:(文件)记录锁

    [a] 概念 建议锁:在遵循相同记录锁规则的进程间生效,通常用于保证某个程序自身多个进程间的数据一致性 强制锁:意在保证所有进程间的数据一致性,但不一定有效:如不能应对先 unlink 后建立同名副本 ...

  7. 非阻塞io与记录锁

    非阻塞io 1.对比 阻塞io的例子:scanf从终端获取输入时,如果不输入程序就会一直停在那; 对一个已经有写锁的文件请求读时, 会一直空等直到前面的进程释放锁... 非阻塞的例子:读取文件内容, ...

  8. fcntl记录锁实例

    fcntl 函数是一个相当常用的对文件进行加锁操作的函数. 文件锁包括强制锁.建议锁.记录锁, 不过一般系统和内核都是用的强制锁. 以下为记录锁的例子:------------------------ ...

  9. Linux进程同步之记录锁(fcntl)

    记录锁相当于线程同步中读写锁的一种扩展类型,可以用来对有亲缘或无亲缘关系的进程进行文件读与写的同步,通过fcntl函数来执行上锁操作.尽管读写锁也可以通过在共享内存区来进行进程的同步,但是fcntl记 ...

随机推荐

  1. NinePatch

    将图片保存为扩展名为.9.png的格式直接放入Android Studio中的drawable文件夹,拖拉选择拉伸区域,如下图,即可制作出可拉伸背景

  2. foreach 加& 什么意思?

    foreach 加&遍历的同时改变原数组即修改数据或者增加数据 foreach 加& 什么意思? 注意:如果我要改变数组某一个值 直接遍历的话原数组是不会变的 下面提供两种方法 1.我 ...

  3. Database returned an invalid value in QuerySet.datetimes(). Are time zone definitions for your datab

    Database returned an invalid value in QuerySet.datetimes(). Are time zone definitions for your datab ...

  4. 事务(Transaction)概念和特性

    http://baike.baidu.com/view/121511.htm 概念 事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit).事务通常由高级数据库 ...

  5. JDBC技术总结(二)

    上一节主要讲了JDBC的基本操作,这一节主要总结一下JDBC如何处理大文本.如何处理图片以及进行批处理. 1.JDBC处理大文本 在MySQL中,大文本是text类型,使用Java操作数据库中的大文本 ...

  6. 如何在aspx页面中使用ascx控件(用户自定义的一个控件)?

    aspx是页面文件ascx是用户控件,用户控件必须嵌入到aspx中才能使用. ascx是用户控件,相当于模板 其实ascx你可以理解为Html里的一部分代码,只是嵌到aspx里而已,因为aspx内容多 ...

  7. 正则表达式匹配 href 和text内容

    string pattern = @"<a[^>]*href=(""(?<href>[^""]*)""|' ...

  8. Java后台代码调用Spring的@Service Bean的方式

    比如:在我的project中有一个类CompassIndexOperation,以: @Service("CompassIndexOperation") @Transactiona ...

  9. CentOS LVS安装配置

    一般2.6.10以上内核版本都已经自带了ipvsadm,故不需要安装. Ipvs 1.25编译 ipvsadm-1.25编译不过 去掉netlink库的依赖:去掉libipvs/Makefile的CF ...

  10. php使用imagick模块实现图片缩放、裁剪、压缩示例

    PHP 使用Imagick模块 缩放,裁剪,压缩图片 包括gif图片 缩放 裁剪 复制代码代码如下: /**  * 图片裁剪  * 裁剪规则:  *   1. 高度为空或为零   按宽度缩放 高度自适 ...