首页 > 代码库 > 文件记录锁的释放

文件记录锁的释放

引言:apue中提到文件记录锁的释放中的两条规则:当进程终止的时候,进程在文件上建立的记录锁会全部释放;当关闭文件,执行close(fd)函数的时候,进程释放描述符可以引用的文件上的任何一把锁。对于第一条规则的理解应该没有分歧。但对于第二条规则的理解,则会出现疑惑,执行close(fd)的时候,是仅仅释放closf(fd)调用进程在文件上的记录锁,还是会释放所有进程(包括非调用进程)在文件上的记录锁。本文通过具体相关程序对此问题进行验证。

思路:需要设计验证程序对此问题进行验证,通过fork产生不同进程。子进程对文件0字节加写锁,然后子进程执行close(fd)函数,同时父进程对文件1字节加写锁。完成以上操作后,发送信号,在信号处理程序中,父进程尝试对0字节进行加写锁,子进程尝试对1字节进行加写锁。

代码:

 

 1 /*  2  * =====================================================================================  3  *  4  *       Filename:  2.c  5  *  6  *    Description:    7  *  8  *        Version:  1.0  9  *        Created:  2014年08月21日 15时30分58秒 10  *       Revision:  none 11  *       Compiler:  gcc 12  * 13  *         Author:  3me   14  *   Organization:   15  * 16  * ===================================================================================== 17  */ 18 #include <stdlib.h> 19 #include <stdio.h> 20 #include <fcntl.h> 21 #include <signal.h> 22 #include <unistd.h> 23 #include <signal.h> 24 static int fd,fd_c,fd_p; 25 /*  26  * ===  FUNCTION  ====================================================================== 27  *         Name:  lock_reg 28  *  Description:   29  * ===================================================================================== 30  */ 31    int 32 lock_reg ( int fd, int cmd, int type, off_t offset, int whence, off_t len ) 33 { 34     struct flock lock; 35  36     lock.l_type = type; 37     lock.l_start = offset; 38     lock.l_whence = whence; 39     lock.l_len = len; 40  41     return fcntl(fd, cmd, &lock); 42 }   /* -----  end of function lock_reg  ----- */ 43 /*  44  * ===  FUNCTION  ====================================================================== 45  *         Name:  sig_usr 46  *  Description: 子进程和父进程共用的信号处理程序,子进程收到信号SIGUSR1后,尝试申请文件 47  *  第一个字节的写锁,父进程收到SIGUSR2后,尝试申请第0个字节的写锁.如果申请的锁未释放,则                                                                               48  *  申请的锁会失败,打印对应errno,可以验证close(fd)函数对文件锁的释放. 49  * ===================================================================================== 50  */ 51     static void 52 sig_usr ( int signo ) 53 { 54     if ( signo == SIGUSR1 ) { 55         if ( lock_reg(fd_c, F_SETLK, F_WRLCK, 2, SEEK_SET, 1) == -1 ) 56         { 57             perror("child lock_reg  1 error"); 58             return; 59         } 60         printf("child get parent‘s write lock success.\n"); 61     } 62     else if ( signo == SIGUSR2 ) { 63         if ( lock_reg(fd_p, F_SETLK, F_WRLCK, 0, SEEK_SET, 1) == -1 ) 64         { 65             perror("parent lock_reg  0 error"); 66             return; 67         } 68         printf("parent get child‘s write lock success.\n"); 69     } 70     else 71         printf("unexpected signo.\n"); 72 }   /* -----  end of function sig_usr  ----- */ 73  74 /*  75  * ===  FUNCTION  ====================================================================== 76  *         Name:  main 77  *  Description: 子进程申请第0个字节的写锁,然后子进程执行close(fd),父进程申请第一个字节的写锁。  78  *  如果close(fd)函数仅仅关闭该函数(close(fd))调用进程在文件上的记录锁,则在信号处理程序中,父进程可以 79  *  获得子进程的锁(因为已有子进程经过closf(fd)释放),子进程无法获得父进程的锁(因为在子进程中 80  *  执行close(fd)无法释放父进程的写锁).如果close(fd)函数能够关闭所有进程在文件上的记录锁, 81  *  则在信号处理程序中,父进程可以获得子进程的写锁,子进程可以获得父进程的写锁. 82  * ===================================================================================== 83  */ 84     int 85 main ( int argc, char *argv[] ) 86 { 87     pid_t pid; 88  89     if ( (fd = open("2.c", O_RDWR) == -1) ) 90             perror("open 2.c failed"); 91  92     if ( (pid = fork()) < 0 ) 93         perror("fork error"); 94                                    95     else if ( pid == 0 ) { 96         /*----------------------------------------------------------------------------- 97          *  进入进程,先设置对应SIGNO(SIGUSR1/SIGUSR2)的信号屏蔽字,等待进程占有文件的字节 98          *  锁后,才能释接受信号。避免进程占有文件锁之前进入信号处理程序,从而进入错误逻辑, 99          *  使得结果不可控(父进程也做同样处理).100          *-----------------------------------------------------------------------------*/101         sigset_t newmask, oldmask;102 103         sigemptyset(&newmask);104         sigaddset(&newmask, SIGUSR1);105         sigprocmask(SIG_BLOCK, &newmask, &oldmask);106         if ( signal(SIGUSR1, sig_usr) == SIG_ERR )107             perror("register sigusr1 error");108         sleep(2);109 110         if ( lock_reg(fd, F_SETLK, F_WRLCK, 0, SEEK_SET, 1) == -1 )111         {112             perror("child lock_reg  0 error");113             return EXIT_FAILURE;114         }115         printf("child lock 0 success.\n");116         fd_c = dup(fd);117         close(fd);118         printf("child : after close(fd).\n");119 120         printf("child:getppid = %d.\n", getppid());121         if ( kill(getppid(), SIGUSR2) < 0 )122             perror("child push sigusr2 error");123         printf("child:send sigusr2 success.\n");124 125         sigsuspend(&oldmask);126 127 128     }129 130     else {131         sigset_t newmask,oldmask;132 133         sigemptyset(&newmask);134         sigaddset(&newmask, SIGUSR2);135         sigprocmask(SIG_BLOCK, &newmask, &oldmask);136 137         if ( signal(SIGUSR2, sig_usr) == SIG_ERR )138             perror("register sigusr2 error");139 140         if ( lock_reg(fd, F_SETLK, F_WRLCK, 2, SEEK_SET, 1) == -1 )141         {142             perror("parent lock_reg  1 error");143             return EXIT_FAILURE;144         }145         printf("parenet lock_reg 1 byte success.\n");146         fd_p = dup(fd);147 //        close(fd);148 149 150         printf("parent:pid = %d.\n", pid);151 152         if ( kill(pid, SIGUSR1) < 0 )153             perror("parent push sigusr1 error");154         printf("parent:send sigusr1 success.\n");155         sigsuspend(&oldmask);156 157     }158 159 160 161 162 163     return EXIT_SUCCESS;164 }       /* ----------  end of function main  ---------- */165 

 分析:

执行以上代码,结果如下所示:

parenet lock_reg 1 byte success.parent:pid = 10484.parent:send sigusr1 success.child lock 0 success.child : after close(fd).child:getppid = 10483.child:send sigusr2 success.parent get childs write lock success.child lock_reg  1 error: Resource temporarily unavailable

从运行结果可以看出,在main函数中,父进程能够成功对1字节加锁,子进程能够成功对0字节加锁。在信号处理程序中,父进程能够成功对0自己加锁,说明在子进程中执行close(fd)确实释放了子进程的写锁。在信号处理程序中,子进程不能够对文件的1字节加锁,说明子进程中执行closf(fd),并不能释放父进程的写锁。因此,有理由推断,当执行closf(fd)的时候,仅仅释放该函数调用进程在此文件上的锁,不会影响其他进程载此文件上的锁。为了进一步验证此结论可以放开父进程中close(fd)的注释行,则在信号处理程序中,父进程能够对0字节加锁,子进程能够对1字节加锁。

执行结果如下:

parenet lock_reg 1 byte success.parent:pid = 10503.parent:send sigusr1 success.child lock 0 success.child : after close(fd).child:getppid = 10502.child:send sigusr2 success.child get parents write lock success.parent get childs write lock success.

思考:

1/在main函数中,父进程加锁的偏移量设置的为2,即加锁文件文件实际的第3个字节,能够得到预期的结果。但是当加锁偏移量设置为1的时候,即加锁文件实际的第2个字节,有父进程获取写锁不成功的情况,会不会是如果两个进程在文件相邻位置获取写锁,fcntl容易执行失败?