首页 > 代码库 > 文件锁

文件锁

文件锁包括“建议性锁”和“强制性锁”。

Linux 系统的文件记录锁默认情况下是建议性的!建议性锁要求每个上锁的文件的进程都要检查是否有锁存在,并且尊重已有的锁。

考虑数据库存取例程库,如果数据库中所有函数都以一致的方法处理记录锁,则称使用这些函数存取数据库的 所有进程集为“合作进程”(cooperating process)。如果这些函数是惟一的用来存取数据库的函数,那么他们使用建议性锁是可行的。但是,如上面程序所看到的,同样的情况,建议性锁也不能阻 止对数据库文件有写许可权限的任何其他进程写数据库文件!不使用协同一致的方法( 数据库存取例程库 )来存取数据库的进程是一个非合作进程。

在强制性锁机制中,内核对每一个 open、read、和 write 都要检查调用进程对正在存取的文件是否违背了某一把锁的作用。一般情况下,内核和系统都不使用建议性锁。采用强制性锁对性能的影响很大,每次读写操作都要必须检查是否有锁存在。

共享锁和互斥锁则是在建议性锁或强制性锁中设置的“子项”,也就是在 struct flock 结构中对成员 l_type 的具体设置,F_RDLCK 是共享锁,F_WRLCK 是写入锁(排斥锁)
 
 

创建锁文件这用来控制独占式访问资源( 如串口 )或者一些不常访问的文件是不错的,但对于较大的共享型文件则不太适合。

假如 写了一个程序,而这个程序需要其它许多不同的程序对其进行读取操作更新。这里,可能会有一些问题,比如有一个程序连续一段较长的时间里都要访问数据,而其 它的程序同时也要对这些数据进行处理。当然不能让好几个程序要一直等到之前那个程序完成了自己的工作后( 甚至一直都会占用着 )才能访问数据,所以这里需要一些协调措施以满足同时访问同一个文件。

对于上述问题,调协的方法是,不能对文件中的数据都由一个程序全盘 进行锁定,而是要对一部分数据进行锁定,而其它部分不锁定以能让别的程序可以正常访问。比如一个文件文件分为A,B,C,D 四个部分。程序1 需要访问独占式的访问 B 部分,而其它 3 个部分不需要访问,那么程序1就只需要把 B 部分锁定住即可,让其余部分可以让别的程序可以进行访问。

那么,在 linux 里,有两种方式可以进行这种“区域锁” 的操作,即通过 fcntl()lockf() 函数来实现。

但是,需要注意的是,fcntl() 和 lockf() 函数不能同时混用,因为它们使用了不同的底层实现,所以一直只需要用其中的一个即可!

fcntl() 函数的原型为:

引用
#include <fcntl.h>

int fcntl(int fildes, int command, ...);

 

fcntl 用于文件区域锁
fcntl() 对已打开的文件描述符进行操作,并根据命令参数的不同能够执行不同的任务。关于文件锁的几个命令选项如下:
F_GETLK
F_SETLK
F_SETLKW当使用这 3 个参数时必须在 fcntl() 函数中指定一个指向struct flock 结构的指针,所以有效的原型为:
引用
int fcntl(int fileds, int command, struct flock *flock_structure)


flock 结构体在不同的版本里有不同的实现方法,但相同的是,它至少会包含以下的一些成员:
  •  short l_type;
  •  short l_whence;
  • off_t l_start;
  • off_t l_len;
  • pid_t l_pid;
上面,l_type 又有以下几个值,这定义在 fcntl.h 中。这几个值的含义分别是:

F_GETLK:

F_RDLCK
F_RDLCK 表示共享锁( 读操作 )。许多不同的进程可以在一个文件中的同一个区域处有一个“共享锁”。任一个进程在一块区域处设了“共享锁”,则其他进程就不能在这里设置 “独占锁”。如果需要得到一个共享锁,则文件就必须打开 ”读“ 或者 "读/写“ 访问权限

F_UNLCK
F_UNLCK 作用是解除锁定。

F_WRLCK
F_WRLCK 表示独占锁( 写操作 )。仅能有一个进程可以在一个特定区域拥有一个独占锁。则其它进程就无法在这块区域获得任何类型锁。若需要获得一个独占锁,文件必须打开 “写” 或 “读/写“ 访问权限。

l_whence,l_startl_len 这三个成员是用来定义一个区域的--即文件中一个连续的字节组合。

l_whence 必须是以下几个值之一( 在 unistd.h 中定义):
SEEK_SET : 文件开始位置
SEEK_CUR: 文件当前位置
SEEK_END: 文件末尾位置

这几个位置都是相对 l_start 的相对偏移而言。一般情况下,使用 SEEK_SET 比较多,所以此时 l_start 就是从文件头开始算起了。
l_len 参数定义了区域中的字节数( 偏移量,长度 )。

l_pid 参数用来指明是哪个进程正在实施封锁---参考 F_GETLK 描述。

文件中每一个字节在任何一个相同的时间里只能有一种类型锁,即不可能同时拥有共享锁,独占锁和解除锁定。

F_SETLK
这个命令用来实施 “锁定” 或 “解锁” 动作( 由文件描述符指定文件 )。flock 结构中的值( 不同于从 F_GETLK 中的值) 如下:
  • l_type
  • l_pid (未用)
l_type 有三个值:
F_RDLCK : 共享锁,只读用
F_WRLCK : 独占锁(写操作锁)
F_UNLCK : 解除锁定

F_GETLK 一样,区域锁定同样用 l_start, l_whence 和 l_len 这三个值( 在 flock 结构体中 )来定义。如果锁操作成功, fcntl() 返回一个值( 执行了马上就能返回 ),否则返回 -1 。

F_SETLKW
F_SETLKW 的作用和上面的 F_SETLK 一样,不同的是如果无法锁定那它就会一直等待直到可以锁定为止;一旦进入等待状态,则这个函数只有在被可以进行锁定或者是接收到信号时才会返回。

封锁状态下的读写操作
如 果对对一块锁区域进行读写的话,一定要注意,得使用底层读写函数 read() 和 write() 来访问其中数据,而不是上层的高级 fread() 和 fwrite() 函数。这是因为: fread() 和 fwrite() 会把数据缓冲到函数库里。所以执行一次 fread() 函数去读取一个文件中的 100 个字节数据可能( 事实上绝大部分也是肯定如此 ) 会读到多于 100 个字节的数据并且把多出的部分数据缓冲进函数库里。如果程序使用 fread() 来读下一个 100 字节数据,它实际上是将函数库里缓冲好了的数据读出,但不允许一个底层 read() 从文件里取出更多的数据。

假 设有一个文件,这个文件由 200 个字节数据组成,而且这些数据都是 0 。假设有一程序A,先封锁了文件中前 100 个字节的数据,然后它就用 fread() 来读取这 100 个字节的数据。当然,这个 100 个字节的数据肯定能被读出,但是与此同,fread() 还把剩下来的 100 个字节还读到缓冲区中去,为的是方便下次读取时更快速的拿出来,所以实际上程序A读取的是整个文件的内容。

现在又假设有一个程序B,它封 锁了后 100 个字节的数据,然后把 2 写到了这个 100 字节空间去,也就是说,这后面的 100 字节数据都从原来的 0 变成了 2;然后程序B 在解锁后便离开。接着,程序A 过来锁住后 100 字节数据,然后读取,然而结果是---读到的仍然是 0 ,而不是 2!这是因为,fread() 缓冲了数据的缘故,这些被缓冲的数据在函数库里;而100个字节 2 是在文件中,也就是在磁盘上。

这个问题在使用 read() 和 write() 函数不会产生!

下面两个程序,一个用来锁定文件,一个用来测试。

程序一(用来锁定)
引用
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>

const char *test_file = "/tmp/test_lock";

int main()
{
    int file_desc;
    int byte_count;
    char *byte_to_write = "A";
    struct flock region_1;
    struct flock region_2;
    int res;

    file_desc = open(test_file, O_RDWR | O_CREAT, 0666);
    if (!file_desc) {
        fprintf(stderr, "Unable to open %s for read/write\n", test_file);
        exit(EXIT_FAILURE);
    }
    /* Put some data in the file*/
    for( byte_count = 0; byte_count < 100; byte_count++) {
        (void)write(file_desc, byte_to_write, 1);
    }
    /* Set up region 1 with a shared lock, from bytes 10 to 30: */
    region_1.l_type = F_RDLCK;
    region_1.l_whence = SEEK_SET;
    region_1.l_start = 10;
    region_1.l_len = 20;
    /* Set up region 2 with an exclusive lock, from bytes 40 to 50: */
    region_2.l_type = F_WRLCK;
    region_2.l_whence = SEEK_SET;
    region_2.l_start = 40;
    region_2.l_len = 10;
    /* Now lock the file */
    printf("Process %d locking file\n", getpid());
    res = fcntl(file_desc, F_SETLK, &region_1);
    if (res == -1) fprintf(stderr, "Failed to lock region 1\n");
    res = fcntl(file_desc, F_SETLK, &region_2);
    if (res == -1) fprintf(stderr, "Failed to lock region 2\n");
    /* Wait for a while */
    sleep(60);

    printf("Process %d  closing file\n", getpid());
    close(file_desc);
    exit(EXIT_SUCCESS);
}

程序说明
先创建文件 /tmp/test_lock,然后写 100 个 A 到 文件中( 共 100 字节)。
然后对从第 10 个字节 到第 30 个字节的这部分数据区进行共享锁设定;对从第 40 个字节到第 50 这部分数据区进行独占锁设定。
然后休息 1 分钟,即保持这种设定状态 1 分钟,最后程序关闭文件描述符后退出
-------------------------------------------------------------------------------------------
程序二( 用来测试 )
引用
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>

const char *test_file = "/tmp/test_lock";
#define SIZE_TO_TRY 5

void show_lock_info(struct flock *to_show);

int main()
{
    int file_desc;
    int res;
    struct flock region_to_test;
    int start_byte;

    /* Open a file descriptor */
    file_desc = open(test_file, O_RDWR | O_CREAT, 0666);
    if (!file_desc) {
        fprintf(stderr, "Unable to open %s for read/write", test_file);
        exit(EXIT_FAILURE);
    }
   
    for (start_byte = 0; start_byte < 99; start_byte += SIZE_TO_TRY) {
        region_to_test.l_type = F_WRLCK;
        region_to_test.l_whence = SEEK_SET;
        region_to_test.l_start = start_byte;
        region_to_test.l_len = SIZE_TO_TRY;
        region_to_test.l_pid = -1;

        printf("Testing F_WRLCK on region from %d to %d\n",
            start_byte, start_byte + SIZE_TO_TRY);
        /* Now test lock on the file */
        res = fcntl(file_desc, F_GETLK, &region_to_test);
        if (res == -1) {
            fprintf(stderr, "F_GETLK failed\n");
            exit(EXIT_FAILURE);
        }
        if (region_to_test.l_pid != -1) {
            printf("Lock would fail. F_GETLK returned: \n");
            show_lock_info(&region_to_test);
        }
        else {
            printf("F_WRLCK - Lock would succeed\n");
        }
        /* Now repeat the test with a shared(read)lock. Set up the region you wish to test again */
        region_to_test.l_type = F_RDLCK;
        region_to_test.l_whence = SEEK_SET;
        region_to_test.l_start = start_byte;
        region_to_test.l_len = SIZE_TO_TRY;
        region_to_test.l_pid = -1;
        printf("Testing F_RDLCK on region from %d to %d\n", start_byte, start_byte + SIZE_TO_TRY);
        /* Test the lock on the file again */
        res = fcntl(file_desc, F_GETLK, &region_to_test);
        if (res == -1) {
            fprintf(stderr, "F_GETLK failed\n");
            exit(EXIT_FAILURE);
        }
        if (region_to_test.l_pid != -1) {
            printf("Lock would fail. F_GETLK return: \n");
            show_lock_info(&region_to_test);
        }
        else {
            printf("F_RDLCK - Lock would secceed\n");
        }
    }
    close(file_desc);
    exit(EXIT_SUCCESS);
}
void show_lock_info(struct flock *to_show)
  {
    printf("\tl_type %d, ", to_show->l_type);
    printf("l_whence %d, ", to_show->l_whence);
    printf("l_start %d, ", (int)to_show->l_start);
    printf("l_len %d, ", (int)to_show->l_len);
    printf("l_pid %d\n", to_show->l_pid);
}

程序说明
程序一次对 5 个字节的区域进行锁定测试。如果被测试区域之前已经被别的程序锁定(共享锁,独占锁),那么锁定测试就会失败,至于被锁定的类型可以从元素 l_type 中看出:l_type 为 0 时,是共享锁;l_ytpe 为 1 时是独占锁。
其实在 /usr/include/bits/fcntl.h 文件中其实定义了这里的 0 和 1 所代表的值:
引用
/* For posix fcntl() and `l_type‘ field of a `struct flock‘ for lockf().  */
#define F_RDLCK         0       /* Read lock.  */
#define F_WRLCK         1       /* Write lock.  */
#define F_UNLCK         2       /* Remove lock.  */

假如被测试区域没有被别的程序实施锁定操作,则返回可以对其锁定的提示信息。
l_pid 一开始被设置为 -1,这是非法值。如果被测试的区域被一个进程锁定,那么 l_pid 就会被修改为这个进程的标识符,反之就不会改变而仍然为 -1。
程序运行与输出
1、先在后台运行
./lock3.exe &
2、运行
./lock4.exe
输出结果
引用
beyes@linux-beyes:~/C> ./lock4.exe
Testing F_WRLCK on region from 0 to 5
F_WRLCK - Lock would succeed
Testing F_RDLCK on region from 0 to 5
F_RDLCK - Lock would secceed
Testing F_WRLCK on region from 5 to 10
F_WRLCK - Lock would succeed
Testing F_RDLCK on region from 5 to 10
F_RDLCK - Lock would secceed
Testing F_WRLCK on region from 10 to 15
Lock would fail. F_GETLK returned:
    l_type 0, l_whence 0, l_start 10, l_len 20, l_pid 15916
Testing F_RDLCK on region from 10 to 15
F_RDLCK - Lock would secceed
Testing F_WRLCK on region from 15 to 20
Lock would fail. F_GETLK returned:
    l_type 0, l_whence 0, l_start 10, l_len 20, l_pid 15916
Testing F_RDLCK on region from 15 to 20
F_RDLCK - Lock would secceed
Testing F_WRLCK on region from 20 to 25
Lock would fail. F_GETLK returned:
    l_type 0, l_whence 0, l_start 10, l_len 20, l_pid 15916
Testing F_RDLCK on region from 20 to 25
F_RDLCK - Lock would secceed
Testing F_WRLCK on region from 25 to 30
Lock would fail. F_GETLK returned:
    l_type 0, l_whence 0, l_start 10, l_len 20, l_pid 15916
Testing F_RDLCK on region from 25 to 30
F_RDLCK - Lock would secceed
Testing F_WRLCK on region from 30 to 35
F_WRLCK - Lock would succeed
Testing F_RDLCK on region from 30 to 35
F_RDLCK - Lock would secceed
Testing F_WRLCK on region from 35 to 40
F_WRLCK - Lock would succeed
Testing F_RDLCK on region from 35 to 40
F_RDLCK - Lock would secceed
Testing F_WRLCK on region from 40 to 45
Lock would fail. F_GETLK returned:
    l_type 1, l_whence 0, l_start 40, l_len 10, l_pid 15916
Testing F_RDLCK on region from 40 to 45
Lock would fail. F_GETLK return:
    l_type 1, l_whence 0, l_start 40, l_len 10, l_pid 15916
Testing F_WRLCK on region from 45 to 50
Lock would fail. F_GETLK returned:
    l_type 1, l_whence 0, l_start 40, l_len 10, l_pid 15916
Testing F_RDLCK on region from 45 to 50
Lock would fail. F_GETLK return:
    l_type 1, l_whence 0, l_start 40, l_len 10, l_pid 15916
Testing F_WRLCK on region from 50 to 55
F_WRLCK - Lock would succeed
Testing F_RDLCK on region from 50 to 55
F_RDLCK - Lock would secceed
Testing F_WRLCK on region from 55 to 60
F_WRLCK - Lock would succeed
Testing F_RDLCK on region from 55 to 60
F_RDLCK - Lock would secceed
Testing F_WRLCK on region from 60 to 65
F_WRLCK - Lock would succeed
Testing F_RDLCK on region from 60 to 65
F_RDLCK - Lock would secceed
Testing F_WRLCK on region from 65 to 70
F_WRLCK - Lock would succeed
Testing F_RDLCK on region from 65 to 70
F_RDLCK - Lock would secceed
Testing F_WRLCK on region from 70 to 75
F_WRLCK - Lock would succeed
Testing F_RDLCK on region from 70 to 75
F_RDLCK - Lock would secceed
Testing F_WRLCK on region from 75 to 80
F_WRLCK - Lock would succeed
Testing F_RDLCK on region from 75 to 80
F_RDLCK - Lock would secceed
Testing F_WRLCK on region from 80 to 85
F_WRLCK - Lock would succeed
Testing F_RDLCK on region from 80 to 85
F_RDLCK - Lock would secceed
Testing F_WRLCK on region from 85 to 90
F_WRLCK - Lock would succeed
Testing F_RDLCK on region from 85 to 90
F_RDLCK - Lock would secceed
Testing F_WRLCK on region from 90 to 95
F_WRLCK - Lock would succeed
Testing F_RDLCK on region from 90 to 95
F_RDLCK - Lock would secceed
Testing F_WRLCK on region from 95 to 100
F_WRLCK - Lock would succeed
Testing F_RDLCK on region from 95 to 100
F_RDLCK - Lock would secceed
关于共享锁
设立共享锁的目的是避免该锁定区域不被其它进程独占了去。但是共享锁并不是 read-only 只读的!其它进程如果有此文件的写权限,那么它仍然可以对此区域进行写操作。下面做个实验,锁定设置代码仍然使用上面的 lock3.c ,测试写共享锁区域的代码如下所示:
引用
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>

const char *test_file = "/tmp/test_lock";
const char buf[10] = {"hello"};
char read_buf[10];

int main()
{
    int file_desc;
    struct flock region_to_test;

    /* Open a file descriptor */
    file_desc = open(test_file, O_RDWR | O_CREAT, 0666);
    if (!file_desc) {
        fprintf(stderr, "Unable to open %s for read/write", test_file);
        exit(EXIT_FAILURE);
    }
    /* 定位文件到第10个字节处 */
    if (lseek(file_desc, 10, SEEK_SET) == -1){
        printf("can not locate\n");
        return 0;
    }
    if (write(file_desc, buf, 5) == -1){
        printf("Write shared lock area failed\n");
        return 0;
    }
    /* 重新定位到第10个字节处 */
     if (lseek(file_desc, 10, SEEK_SET) == -1){
                printf("can not locate\n");
                return 0;
    }
    if ( read(file_desc, read_buf, 5) == -1 ) {
        printf("can not read the file\n");
        return 0;
    }
    printf("After modify shared lock area and read it out:%s\n", read_buf);
    return 0;
}

说明
先运行 lock3.exe ,然后运行此程序,从输出可以看到,共享锁区域被改变了:
引用
beyes@linux-beyes:~/C> ./lock5.exe
After modify shared lock area and read it out:hello


用 cat 查看 /tmp/lock_test 文件亦可得知确实修改了:
引用
beyes@linux-beyes:~/Desktop> cat /tmp/test_lock
AAAAAAAAAAhelloAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
文件封锁中的竞争
如果一个程序对文件区域做了锁定,那另外的程序也要对这块区域做些操作的话,势必引起竞争关系。文件区域锁定设置代码还是用 lock3.c ,下面用一段测试程序说明文件封锁中的竞争现象,代码如下:
引用
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>

const char *test_file = "/tmp/test_lock";

int main()
{
    int file_desc;
    struct flock region_to_lock;
    int res;

    file_desc = open(test_file, O_RDWR | O_CREAT, 0666);
    if (!file_desc) {
        fprintf(stderr, "Unable to open %s for read/write\n", test_file);
        exit(EXIT_FAILURE);
    }
    region_to_lock.l_type = F_RDLCK;
    region_to_lock.l_whence = SEEK_SET;
    region_to_lock.l_start = 10;
    region_to_lock.l_len = 5;
    printf("Process %d, trying F_RDLCK, region %d to %d\n", getpid(),
        (int)region_to_lock.l_start, (int)(region_to_lock.l_start + region_to_lock.l_len));
    res = fcntl(file_desc, F_SETLK, &region_to_lock);
    if (res == -1) {
        printf("Process %d - failed to lock region\n", getpid());
    } else {
        printf("Process %d - obtained lock region\n", getpid());
    }

    region_to_lock.l_type = F_UNLCK;
    region_to_lock.l_whence = SEEK_SET;
    region_to_lock.l_start = 10;
    region_to_lock.l_len = 5;
    printf("Process %d, trying F_UNLCK, region %d to %d\n", getpid(),
        (int)region_to_lock.l_start,
        (int)(region_to_lock.l_start + region_to_lock.l_len));
   
    res = fcntl(file_desc, F_SETLK, &region_to_lock);
    if (res == -1) {
        printf("Process %d - failed to unlock region\n", getpid());
    } else {
        printf("Process %d - unlocked region\n", getpid());
    }

    region_to_lock.l_type = F_WRLCK;
    region_to_lock.l_whence = SEEK_SET;
    region_to_lock.l_start = 16;
    region_to_lock.l_len = 5;
    printf("Process %d, trying F_WRLCK, region %d to %d\n", getpid(),
        (int)region_to_lock.l_start,
        (int)(region_to_lock.l_start + region_to_lock.l_len));
    res = fcntl(file_desc, F_SETLK, &region_to_lock);
    if( res == -1 ) {
        printf("Process %d - failed to lock region\n", getpid());
    } else {
        printf("Process %d - obtained lock on region\n", getpid());
    }

    region_to_lock.l_type = F_RDLCK;
    region_to_lock.l_whence = SEEK_SET;
    region_to_lock.l_start = 40;
    region_to_lock.l_len = 10;
    printf("Process %d, trying F_RDLCK, region %d to %d\n", getpid(),
        (int)region_to_lock.l_start,
        (int)(region_to_lock.l_start + region_to_lock.l_len));
    if (res == -1) {
        printf("Process %d - failed to lock region\n", getpid());
    } else {
        printf("Process %d - obtained lock on region\n", getpid());
    }
   
    region_to_lock.l_type = F_WRLCK;
        region_to_lock.l_whence = SEEK_SET;
        region_to_lock.l_start = 16;
        region_to_lock.l_len = 5;
    printf("Process %d, trying F_RDLCK with wait, region %d to %d\n", getpid(),
                (int)region_to_lock.l_start,
                (int)(region_to_lock.l_start + region_to_lock.l_len));
    res = fcntl(file_desc, F_SETLKW, &region_to_lock);
        if (res == -1) {
                printf("Process %d - failed to lock region\n", getpid());
        } else {
                printf("Process %d - obtained lock on region\n", getpid());
        }
    printf("Process %d ending\n", getpid());
    close(file_desc);
    exit(EXIT_SUCCESS);
}

程序运行及输出
引用
beyes@linux-beyes:~/C> ./lock6.exe
Process 19307, trying F_RDLCK, region 10 to 15
Process 19307 - obtained lock region
Process 19307, trying F_UNLCK, region 10 to 15
Process 19307 - unlocked region
Process 19307, trying F_WRLCK, region 16 to 21
Process 19307 - failed to lock region
Process 19307, trying F_RDLCK, region 40 to 50
Process 19307 - failed to lock region
Process 19307, trying F_RDLCK with wait, region 16 to 21
Process 19307 - obtained lock on region
Process 19307 ending

说明
在 lock3.exe 中对文件的区域封锁情况为:10到30字节区域做 F_RDLCK 封锁;在 40到50字节区域做 F_WRLCK 封锁。这里先在后台运行lock3.exe (lock3.exe &)
在 lock6.exe 中,程序首先尝试对 10 到 15 字节区域这里进行共享锁定,由 lock3.c 知道,这里是个共享锁,所以允许有多重个共享锁并且锁定操作也成功了。
接着仍然对 10 到 15字节区域做 F_UNLCK 解除锁定设定,这里解除锁定的是自己的设置共享锁状态,完全可以;
再接着,尝试对 16 到 21字节区域做 F_WRLCK 独占式锁定,因为这块区域已经被 lock3.exe 设置了共享锁,因而这里无法再被别的程序设置独占锁了,故而设定失败;
接着,对 40 到 50 字节这块区域做 F_RDLCK 设定,因为这块区域已经被 loc3.exe 设置了独占锁设定,所以设置要失败;
再 接着,对 16 到 21 字节这块区域做 F_RWLCK 锁定,一开始锁定肯定失败了,然而在 fcntl 里设置的命令参数是 F_SETLKW ( 等到锁定解除才返回 ),所以程序会一直卡在这里,等到 lock3.exe 释放自己后这个 F_RWLCK 才能设定成功。
 
 
 
此外,fcntl 系统调用提供很多的对底层(low-level)文件描述符的操作方法。

用法
引用
#include <fcntl.h>
int fcntl(int fildes, int cmd);
int fcntl(int fildes, int cmd, long arg);


通过 fcntl 系统调用,你可以对打开文件描述符执行几种操作,包括建立它们的副本,得到并设置文件描述符标志以及文件状态标志,并且管理咨询文件锁定。

通过命令参数 cmd 不同的值可以选择不同的操作,cmd 定义在 fcntl.h。根据命令的选择,系统调用将需要第三个参数,arg :

1、fcntl(fileds, F_DUPFD, newfd) : 这个调用返回一个等于或大于整数 newfd 的新的文件描述符值。新的描述符是 fildes 的一份拷贝。根据打开文件的数量以及 newfd 的值,这相当于 dup(fildes)

2、fcntl(fildes, F_GETFD) : 这个调用返回一个文件描述符标志(定义在 fcntl.h)。包含 FD_CLOEXEC,这决定了在成功调用了一个 exec 家族的系统调用后这个文件描述符是否被关闭。

3、fcntl(fildes, F_SETFD, flags) : 这个调用用来设置文件描述符标志,通常仅是 FD_LOEXEC .

4、fcntl(fildes, F_GETFL) 和 fcntl(fildes, F_SETFL, flags) : 这两个调用分别为得到与设置文件状态标志和访问模式。通过掩码 O_ACCMODE(定义在 fcntl.h中) 你可以提取(extract)文件访问模式。特别地,你无法通过使用 fcntl 来设置文件权限。
 
fcntl()-2
引用
#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd, long arg);


fcntl() 系统调用可以用来对已打开的文件描述符进行各种控制操作以改变已打开文件的各种属性。
上面的函数原型,fcntl() 的功能一句 cmd 值的不同而不同,具体有以下集中功能:

F_DUPFD
此时,fcntl() 的功能与 dup() 一样,可以复制由 fd 指向的文件描述符。调用成功后返回新的文件描述符,失败返回 -1,错误代码存入 errno 中。dup() 和 dup2() 见:http://www.groad.net/bbs/read.php?tid=560

F_GETFD
此 时,fcntl() 用来获取文件描述符的 close_on_exec 标志。调用成功返回标志值,若此标志的最后一位为 0 ,则该标志没有设置,即意味着在执行 exec 相关函数后文件描述符仍保持打开,否则在执行 exec 相关函数时将关闭该文件描述符。失败返回 -1。

F_SETFD
此时,fcntl() 用来设置文件描述符的 close-on-exec 标志为第三个参数 arg 的最后一位。成功返回 0,失败返回 -1。

F_GETFL
此时,fcntl() 用来获得文件的打开方式。成功返回 0 ,失败返回 -1。标志值的含义同 open() 系统调用一致。

F_SETFL
此时,fcntl() 用来设置文件打开的方式为第三个参数 arg 指定的方式。但 Linux 系统只能设置 O_APPEND, O_NONBLOCK, O_ASYNC 标志,它们的含义和 open() 系统调用一致。


测试代码

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

void my_err(const char *err_string, int line)
{
    fprintf(stderr, "line:%d ", line);
    perror(err_string);
    exit(1);
}

int main()
{
    int ret;
    int access_mode;
    int fd;
    if((fd = open("example", O_CREAT|O_TRUNC|O_RDWR, S_IRWXU)) == -1) {
        my_err("open", __LINE__);
    }
    if((ret = fcntl(fd, F_SETFL, O_APPEND)) < 0 ) {
        my_err("fcntl", __LINE__);
    }
    if((ret = fcntl(fd, F_GETFL, 0)) < 0) {
        my_err("fcntl", __LINE__);
    }
    access_mode = ret & O_ACCMODE;
    if(access_mode == O_RDONLY) {
        printf("example access mode: read-only");
    }else if(access_mode == O_WRONLY) {
        printf("example access mode: write-only");
    }else if(access_mode == O_RDWR) {
        printf("example access mode: read+write");
    }

    if(ret & O_APPEND) {
        printf(" ,append");
    }
    if(ret & O_NONBLOCK) {
        printf(" , nonblock");
    }
    if(ret & O_SYNC) {
        printf(" , sync");
    }
    printf("\n");

    return 0;
}


运行及输出

引用
beyes@linux-beyes:~/C> ./fcntl_access.exe
example access mode: read+write ,append


说明
程序中的几个操作文件的模式定义在 /usr/include/bits/fcntl.h        (OS: opensuse11.1)
 

引用
/* open/fcntl - O_SYNC is only implemented on blocks devices and on files
   located on an ext2 file system */
#define O_ACCMODE          0003
#define O_RDONLY             00
#define O_WRONLY             01
#define O_RDWR               02
#define O_APPEND          02000
#define O_NONBLOCK        04000
#define O_NDELAY        O_NONBLOCK
#define O_SYNC           010000
#define O_FSYNC          O_SYNC
#define O_ASYNC          020000