从 0 开始学习 Linux 系列之「14.文件锁定」

IO Lock

版权声明:本文为 cdeveloper 原创文章,可以随意转载,但必须在明确位置注明出处!

文件锁简介

锁是保护共享资源的一种方法。在许多 UNIX 系统上,如果一个文件同时被多个进程编辑,该文件的最后状态取决于写该文件的最后一个进程。但是对于一些特殊的程序,如数据库有时需要独占一个文件,这时就不能让这个文件被多个进程同时操作了。于是 UNIX 系统提供了文件锁来实现这种独占文件的功能。

文件锁:当一个进程正在读或者或者修改文件的某个部分时,使用锁可以阻止其他进程修改同一个文件区域,文件锁可以锁定整个文件或者文件中的一个区域。

文件锁 API

早期的 BSD 只支持 flock 函数,该函数只能对整个文件加锁,不能对文件中的一部分加锁。但是 POSIX 标准的加锁使用 fcntl,这个函数不仅能够对整个文件加锁,也能对文件区域加锁,还能实现其他的文件控制操作。

这个 fcntl 函数也非常重要,来认真学习下这个函数,你可以通过 man fcntl 来查看帮助手册。

fcntl 函数

函数声明如下:

#include <unistd.h>
#include <fcntl.h>

/*
 * fd: 文件描述符
 * cmd:F_GETLK,F_SETLK,F_SETLKW
 * arg:一个取决于 cmd 的可选参数
 * return:成功返回 0,失败返回 -1
 */
int fcntl(int fd, int cmd, ... /* arg */ );

其中 cmdarg 参数比较重要,我们详细介绍这两个参数。

arg 参数

这个参数是一个可选参数,指定文件加锁的详细信息,在加锁文件时我们都会指定这个参数。这个参数是一个指向 struct flock 结构的指针:

struct flock {
    ...
    short l_type;    
    short l_whence; 
    off_t l_start; 
    off_t l_len;  
    pid_t l_pid; 
    ...
};

其中 5 个参数比较重要:

  1. l_type:希望的锁的类型,F_RDLCK(共享读锁),F_WRLCK(独占性写锁),F_UNLCK(解锁)
  2. l_whence:与 lseek 的参数相同,指定 l_start 从文件的何处开始偏移,SEEK_SET,SEEK_CUR, SEEK_END
  3. l_start:加锁或者解锁的字节偏移量,与 l_whence 配合使用
  4. l_len:要加锁或者解锁的字节长度
  5. l_pid:进程 ID 为 l_pid 的进程能够阻塞加锁操作。

cmd 参数

这个参数有 3 种取值方式:

  1. F_GETLK:判断当前文件是否被加锁,如果有锁则将现有锁的信息复制到 struct flock 中,否则设置 l_type = F_UNLCK
  2. F_SETLK:设置由 arg 所描述的锁
  3. F_SETLKW:如果请求的锁不能被授予,那么调用进程会被置为休眠

总体来讲:用 F_GETLK 来测试能够获得一把锁,然后用 F_SETLK 或者 F_SETLKW 尝试加上一把锁。

文件锁分类

按照锁的类型和系统中锁的属性可以分为下面的 2 类。

读锁(L_RDLCK)和写锁(L_WRLCK)

这两个锁的区别是:任意多个进程在一个给定的字节上可以有一把共享的读锁,但是在一个给定的字节上只能有一个进程有一把独占写锁

如下表所示:

table

但是这个规则只适用不同进程之间的提出的锁请求。在单个进程下,如果在已有的锁上加新锁,则新锁会替换旧锁,也不管锁是何种类型。例如:读锁可以替换写锁,写锁也可以替换读锁。

强制锁和建议锁

  1. 强制锁:内核管理的底层锁,一个进程锁定后其他用户不能打开
  2. 建议锁:用户管理的上层锁,一个进程锁定后其他用户可以打开

Linux 默认加的是建议锁,如果需要加强制锁,则需要在文件系统上用 mount 命令的 -o mand 选项来打开强制锁机制,参考 man fcntl

实例:fcntl 锁定文件

为了更好的理解锁的机制,我们来写一个实际的加锁程序来锁定一个文件,然后测试是否加锁成功。

1. 对文件加建议锁

/*
 * file_lock.c 
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>

int main() {
    // 打开文件 
    int fd = open("./hello.txt", O_RDWR | O_CREAT, 0666)  ; 
    if(fd < 0) {
        printf("file open fail.\n");
        exit(1);
    }

    // 建立一个独占性写锁
    struct flock lock;
    lock.l_type = F_WRLCK;
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;
    lock.l_pid = getpid(); 
    
    // 对 hello.txt 加锁
    if (fcntl(fd, F_SETLK, &lock) < 0)
        printf("fcntl error.\n");
    
    printf("Process %d has lock.\n", lock.l_pid);   

    while(1); 
}


编译运行:

# 编译
gcc file_lock.c -o file_lock

# 运行
 ./file_lock 
Process 11080 has lock.

# Ctrl - C 结束程序 

然后我们启动另一个终端,尝试向 hello.txt 中写入数据,观察是否会阻塞:

echo hello_world >> hello.txt

cat hello.txt

# 写入成功了
hello_world

我们发现竟然写入成功了,我们不是已经加了锁了吗?其实这是因为默认加的是建议锁,建议锁可以被其他进程直接打开,因此可以写入成功。但是我们在程序中一般都是先F_GETLCK 主动判断要打开的文件是否有锁,例如下面这个例子:

2. 测试文件是否加锁成功

/*
 * lock_test.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>

int main() { 
    int fd = open("./hello.txt", O_RDWR | O_CREAT, 0666)  ; 
    
    if(fd < 0) {
        printf("file open fail.\n");
        exit(1);
    }

    // 如果发现有进程已经对这个文件加锁
    // 那么现有锁的信息将重写下面的结构
    struct flock lock;
    lock.l_type = F_WRLCK;
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;
    lock.l_pid = getpid(); 

    // 判断 hello.txt 上是否已经加锁
    if (fcntl(fd, F_GETLK, &lock) < 0)
        printf("fcntl error.\n");
    
    // 如果没有进程对文件加锁,只将 l_type 设置为 F_UNLCK,其他信息不变
    if (lock.l_type == F_UNLCK)
        printf("No process has the lock.\n");   
    else 
        printf("Process %d has the lock.\n", lock.l_pid);

    return 0;
}

我们编译运行:

gcc lock_test.c -o lock_test

./lock_test
No process has the lock.

发现当前 hello.txt 文件上没有锁,我们运行上一个 ./file_lock 程序:

./file_lock

Process 11080 has lock.
# while (1) 阻塞

这时进程 11080 已经对 hello.txt 加上了建议的独占性写锁,我们再次用 ./lock_test 测试这个文件是否被加锁:

./lock_test
Process 11080 has the lock.

可以看到这个文件已经被 11080 进程加锁了,我们之后就不要操作这个文件了。

记住在使用 fcntl 时,一定主要主动判断文件是否被加锁。

结语

文件锁是一个非常重要的话题,这篇博客主要介绍了一个重要的 fcntl 函数和文件锁的相关概念,一定要掌握这个函数的基本用法,其他重要的锁话题,例如死锁等,我们后面再议。

最后,感谢你的阅读,我们下次再见 :)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,064评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,606评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,011评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,550评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,465评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,919评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,428评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,075评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,208评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,185评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,191评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,914评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,482评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,585评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,825评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,194评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,703评论 2 339

推荐阅读更多精彩内容

  • linux资料总章2.1 1.0写的不好抱歉 但是2.0已经改了很多 但是错误还是无法避免 以后资料会慢慢更新 大...
    数据革命阅读 12,121评论 2 34
  • 2016-03-01 非阻塞io 系统调用分为低速调用系统和其他,低速系统调用时可能会使进程永远阻塞的一类系统调用...
    千里山南阅读 482评论 0 0
  • Linux 进程管理与程序开发 进程是Linux事务管理的基本单元,所有的进程均拥有自己独立的处理环境和系统资源,...
    JamesPeng阅读 2,442评论 1 14
  • 一日傍晚跟朋友去散步,走在乡下的石板路上,两旁村舍林林立立,荷塘莲花飘香,稻田里新苗渐渐长大,淳朴的乡人...
    秋日麦田阅读 275评论 0 0
  • 姓名:罗江汉 公司:东莞市旭瑞光电科技有限公司 组别:第230期努力二组 【日精进打卡第 22天】 【知~学习】 ...
    罗江汉阅读 148评论 0 0