在很久很久以前,大概是在我们学习文件 IO 的时候吧,我们讲过 fcntl 函数,然后给了一个非常简单的例子告诉同学们如果使用 fcntl 函数对文件状态位 (比如 O_NONBLOCK 等等)进行设置。
那时候我们就知道 fcntl 的功能远远不止这些,只是局限于当时我们懂的东西太少,有些东西不太适合提前讲。然而,过去半年了,大家对 Unix/Linux 环境编程应该已经相当熟悉了,进程线程,同步互斥这些几乎你都已经掌握的七七八八了。所以,现在再把过去没讲完的东西再搬出来的时机到了。
1. 引言
本文的主要内容是探索一种对文件的部分字节或所有字节进行加锁的方法,在多进程之间对指定的字节进行互斥访问。
我相信,如果是对一个文件所有字节进行互斥访问,这很容易实现,在多进程中用信号量就可以完成(比如下面的代码),或者使用 flock 函数。如果只是对指定的字节互斥访问,实现起来是有一定难度的。
// process 1
P(S);
write(fd, buf, len);
V(S);
// process 2
P(S);
write(fd, buf, len);
V(S);
2. 记录锁
实际上,操作系统为我们提供了一种更便捷的方法——记录锁。实际上,记录锁这个名字有点误导人,更贴切的叫法应该是字节范围锁。既然大家都说记录锁,那我们也跟着叫吧,毕竟这个词已经被默认了几十年了(1980年至今)。
记录锁是通过 fcntl 函数来获取(acquire)和释放(release)锁的,fcntl 的执行命令(cmd 参数)是 F_SETLK (非阻塞)和 F_SETLKW (阻塞)。恐怕有的同学已经忘记 fcntl 函数样子了,这里贴一下:
int fcntl(int fd, int cmd, ...); // 设置文件标志位
如果你真的完全也不记得 fcntl 函数是怎么回事,请参考 《文件IO-fcntl函数》再复习一下。
在使用记录锁的时候,fcntl 的第三个参数(上面函数原型参数表中的省略号)是一个结构体类型指针,该结构体定义如下:
struct flock {
short l_type;
short l_whence;
off_t l_start;
off_t l_len;
pid_t l_pid;
}
我承认这个结构体有点复杂,但是很多字段相信你会猜得出,这和我们学习的 lseek 函数有点神似。
- l_type:是要获取锁呢,还是释放锁?它的值为 F_RDLCK,F_WRLCK,F_UNLCK.
- l_whence : SEEK_SET, SEEK_CUR, SEEK_END
- l_start:相对 l_whence 的偏移
- l_len:要加锁或解释的字节数
- l_pid:这个先不讲
上面列举的所有的知识点,除了 l_pid 你先别管,其它的根据我们已有知识应该都能理解。为什么加锁有两种方式?你还记得读写锁吗?其实都是一样的东西,这里不再赘述。不过,和读写锁有一点点区别,读写锁有防止写线程饥饿机制,而这里的记录锁不行。如果你不知道读写锁的三种状态,赶紧跳过去补补《读写锁 rwlock》.
关于 l_whence,学习过 lseek 函数的你一定知道这是干嘛的,如果忘记了,传送门在这儿 《文件IO-文件描述符与lseek》。
最后,你可能比较关心的是如何加锁解锁,请暂时休息休息,再复习一下你忘记的知识。
下回分解。
3. 总结
- 掌握一些必备的知识:读写锁、lseek 函数、fcntl 函数基本用法
- 知道记录锁是干嘛的