文件记录锁的释放

引言: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.
");
 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.
");
 69     }
 70     else
 71         printf("unexpected signo.
");
 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.
");
116         fd_c = dup(fd);
117         close(fd);
118         printf("child : after close(fd).
");
119 
120         printf("child:getppid = %d.
", getppid());
121         if ( kill(getppid(), SIGUSR2) < 0 )
122             perror("child push sigusr2 error");
123         printf("child:send sigusr2 success.
");
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.
");
146         fd_p = dup(fd);
147 //        close(fd);
148 
149 
150         printf("parent:pid = %d.
", pid);
151 
152         if ( kill(pid, SIGUSR1) < 0 )
153             perror("parent push sigusr1 error");
154         printf("parent:send sigusr1 success.
");
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容易执行失败?

原文地址:https://www.cnblogs.com/3me-linux/p/3927899.html