首页 > 代码库 > 文件记录锁的释放
文件记录锁的释放
引言: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 child‘s 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 parent‘s write lock success.parent get child‘s write lock success.
思考:
1/在main函数中,父进程加锁的偏移量设置的为2,即加锁文件文件实际的第3个字节,能够得到预期的结果。但是当加锁偏移量设置为1的时候,即加锁文件实际的第2个字节,有父进程获取写锁不成功的情况,会不会是如果两个进程在文件相邻位置获取写锁,fcntl容易执行失败?