《UNIX网络编程:卷2》P167:图9-8
它获取某个完整文件的一个读出锁,然后fork两个子进程。第一个子进程首先尝试获取一个写入锁(它将阻塞,因为父进程已持有整个文件的一个读出锁),然后由第二个进程尝试获取一个读出锁。
----------------------------------------
/* * test2.c * P167 图9-8 确定在有一个写入锁待处理期间是否允许有另一个读出锁 */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <fcntl.h> #include <time.h> #include <sys/time.h> #define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) #define read_lock(fd, offset, whence, len) \ lock_reg(fd, F_SETLK, F_RDLCK, offset, whence, len) #define readw_lock(fd, offset, whence, len) \ lock_reg(fd, F_SETLKW, F_RDLCK, offset, whence, len) #define write_lock(fd, offset, whence, len) \ lock_reg(fd, F_SETLK, F_WRLCK, offset, whence, len) #define writew_lock(fd, offset, whence, len) \ lock_reg(fd, F_SETLKW, F_WRLCK, offset, whence, len) #define un_lock(fd, offset, whence, len) \ lock_reg(fd, F_SETLK, F_UNLCK, offset, whence, len) #define is_read_lockable(fd, offset, whence, len) \ !lock_test(fd, F_RDLCK, offset, whence, len) #define is_write_lockable(fd, offset, whence, len) \ !lock_test(fd, F_WRLCK, offset, whence, len) int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len); int lock_test(int fd, int type, off_t offset, int whence, off_t len); char *gf_time(void); int main(int argc, char *argv[]) { int fd; pid_t pid; /* * 父进程打开文件并获取读出锁 */ if ((fd = open("test1.data", O_RDWR | O_CREAT, FILE_MODE)) < 0) { fprintf(stderr, "open error: %s\n", strerror(errno)); exit(1); } if (read_lock(fd, 0, SEEK_SET, 0) < 0) { fprintf(stderr, "read_lock error: %s\n", strerror(errno)); exit(1); } printf("%s: parent has read lock\n", gf_time()); /* * 创建第一个子进程,睡眠1秒,然后阻塞,等待整个文件的一个写入锁 * 取得写入锁后,持有2秒,释放它 */ if ((pid = fork()) < 0) { fprintf(stderr, "fork error: %s\n", strerror(errno)); exit(1); } else if (pid == 0) { /* 第一个子进程 */ sleep(1); printf("%s: first child tries to obtain write lock\n", gf_time()); if (writew_lock(fd, 0, SEEK_SET, 0) < 0) { fprintf(stderr, "write_lockw error: %s\n", strerror(errno)); } printf("%s: first child obtains write lock\n", gf_time()); sleep(2); if (un_lock(fd, 0, SEEK_SET, 0) < 0) { fprintf(stderr, "unlock error: %s\n", strerror(errno)); } printf("%s: first child releases write lock\n", gf_time()); exit(0); } /* * 创建第二个子进程,睡眠3秒以允许第一个子进程的写入锁处于待处理状态 * 然后尝试获取文件的一个读出锁,该锁持有4秒时间 */ if ((pid = fork()) < 0) { fprintf(stderr, "fork error: %s\n", strerror(errno)); exit(1); } else if (pid == 0) { /* 第二个子进程 */ sleep(3); printf("%s: second child tries to obtain read lock\n", gf_time()); if (readw_lock(fd, 0, SEEK_SET, 0) < 0) { fprintf(stderr, "readw_lock error: %s: ", strerror(errno)); } printf("%s: second child obtains read lock\n", gf_time()); sleep(4); if (un_lock(fd, 0, SEEK_SET, 0) < 0) { fprintf(stderr, "un_lock error: %s\n", strerror(errno)); } printf("%s: second child releases read lock\n", gf_time()); exit(0); } /* 父进程持有读锁5秒后释放该锁 */ sleep(5); if (un_lock(fd, 0, SEEK_SET, 0) < 0) fprintf(stderr, "un_lock error: %s\n", strerror(errno)); printf("%s: parent release read lock\n", gf_time()); exit(0); } char *gf_time(void) { struct timeval tv; static char str[30]; char *ptr; if (gettimeofday(&tv, NULL) < 0) fprintf(stderr, "gettimeofday error: %s\n", strerror(errno)); ptr = ctime(&tv.tv_sec); strcpy(str, &ptr[11]); snprintf(str+8, sizeof(str) - 8, ".%06ld", tv.tv_usec); return(str); } int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len) { struct flock lock; lock.l_type = type; lock.l_start = offset; lock.l_whence = whence; lock.l_len = len; return(fcntl(fd, cmd, &lock)); } int lock_test(int fd, int type, off_t offset, int whence, off_t len) { struct flock lock; lock.l_type = type; lock.l_start = offset; lock.l_whence = whence; lock.l_len = len; if (fcntl(fd, F_GETLK, &lock) == -1) return(-1); if (lock.l_type == F_UNLCK) return(0); return(lock.l_pid); }
运行程序:
$ ./test2 19:32:34.733342: parent has read lock 19:32:35.734277: first child tries to obtain write lock 19:32:37.734414: second child tries to obtain read lock 19:32:37.734784: second child obtains read lock 19:32:39.734360: parent release read lock $ 19:32:41.734903: second child releases read lock 19:32:41.734920: first child obtains write lock 19:32:43.735088: first child releases write lock
即使已有来自第一个子进程的一个待处理写入锁请求,第二个子进程请求的读出锁也是立即给予的。这样,只要连续不断的发出读出锁请求,写入者就可能因获取不了写入锁而“挨饿”。