《UNIX网络编程:卷2》
强制性上锁
--------------------------------------------
/* * lockmain.c * 图9-2 文件上锁例子的main函数 */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <errno.h> #define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) #define MAXLINE 1024 #define SEQFILE "seqno" void my_lock(int fd); void my_unlock(int fd); int main(int argc, char *argv[]) { int fd; long i, seqno; // pid_t pid; ssize_t n; char line[MAXLINE + 1]; int times; if (argc != 2) { fprintf(stderr, "loopnone [num]\n"); exit(1); } times = atoi(argv[1]); // pid = getpid(); // 获取进程ID // 打开文件 if ((fd = open(SEQFILE, O_RDWR, FILE_MODE)) < 0) { fprintf(stderr, "open %s error: %s\n", SEQFILE, strerror(errno)); exit(1); } for (i = 0; i < times; i++) { my_lock(fd); // 锁定文件 lseek(fd, 0L, SEEK_SET); //定位到文件开始位置 if ((n = read(fd, line, MAXLINE)) < 0) { // 读取一行 fprintf(stderr, "read error: %s\n", strerror(errno)); exit(1); } line[n] = '\0'; n = sscanf(line, "%ld\n", &seqno); // 将字符转换成数字 // printf("%s: pid = %d, seq# = %ld\n", argv[0], pid, seqno); seqno++; // 加1 snprintf(line, sizeof(line), "%ld\n", seqno); // 将数字写回缓冲区 lseek(fd, 0L, SEEK_SET); // 定位到文件开始处 if (write(fd, line, strlen(line)) < 0) { // 将缓冲区内容写回文件 fprintf(stderr, "write error: %s\n", strerror(errno)); exit(1); } my_unlock(fd); // 解锁文件 } exit(0); } /* * locknone.c * P155 图9-1 不上锁的函数 */ void my_lock(int fd) { return; // 不做任何事情,直接返回 } void my_unlock(int fd) { return; // 不做任何事情,直接返回 }
---------------------------------------------------------------
/* * lockmain.c * 图9-2 文件上锁例子的main函数 */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <errno.h> #define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) #define MAXLINE 1024 #define SEQFILE "seqno" void my_lock(int fd); void my_unlock(int fd); int main(int argc, char *argv[]) { int fd; long i, seqno; // pid_t pid; ssize_t n; char line[MAXLINE + 1]; int times; if (argc != 2) { fprintf(stderr, "loopfcntl [num]\n"); exit(1); } times = atoi(argv[1]); // pid = getpid(); // 获取进程ID // 打开文件 if ((fd = open(SEQFILE, O_RDWR, FILE_MODE)) < 0) { fprintf(stderr, "open %s error: %s\n", SEQFILE, strerror(errno)); exit(1); } for (i = 0; i < times; i++) { my_lock(fd); // 锁定文件 lseek(fd, 0L, SEEK_SET); //定位到文件开始位置 if ((n = read(fd, line, MAXLINE)) < 0) { // 读取一行 fprintf(stderr, "read error: %s\n", strerror(errno)); exit(1); } line[n] = '\0'; n = sscanf(line, "%ld\n", &seqno); // 将字符转换成数字 // printf("%s: pid = %d, seq# = %ld\n", argv[0], pid, seqno); seqno++; // 加1 snprintf(line, sizeof(line), "%ld\n", seqno); // 将数字写回缓冲区 lseek(fd, 0L, SEEK_SET); // 定位到文件开始处 if (write(fd, line, strlen(line)) < 0) { // 将缓冲区内容写回文件 fprintf(stderr, "write error: %s\n", strerror(errno)); exit(1); } my_unlock(fd); // 解锁文件 } exit(0); } /* * lockfcntl.c * P155 图9-1 不上锁的函数 */ void my_lock(int fd) { struct flock lock; lock.l_type = F_WRLCK; // 写锁 lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; // 加锁整个文件 if (fcntl(fd, F_SETLKW, &lock) < 0) { fprintf(stderr, "fcntl error: %s\n", strerror(errno)); } return; // 不做任何事情,直接返回 } void my_unlock(int fd) { struct flock lock; lock.l_type = F_UNLCK; // 解锁 lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; // 解锁整个文件 if (fcntl(fd, F_SETLK, &lock) < 0) { fprintf(stderr, "fcntl error: %s\n", strerror(errno)); } return; // 不做任何事情,直接返回 }
----------------------------------------------------------------
运行程序:
$ cat > seqno 1 [^D Ctrl+D文件结束符] $ chmod g-x,g+s seqno 关掉组成员执行位,打开SGID位 $ ls -l seqno -rw-rwSr-- 1 user user 2 8月 24 16:45 seqno $ ./loopfcntl 10000 & ./loopnone 10000 & [1] 3126 [2] 3127 $ 等待,然后回车 [1]- 完成 ./loopfcntl 10000 [2]+ 完成 ./loopnone 10000 $ cat seqno 13111
当loopnone调用read后,内核切换到loopfcntl,loopfnctl增加文件中的数值,然后切换回loopnone,loopnone将之前读入的值加1后写入文件。此时问题就发生了。
如果多个进程在更新一个文件时,那么所有进程必须使用某种上锁形式协作。只要有一个进程违规就可能引发混乱。