七、文件IO——I/O处理方式和文件锁

7.1 I/O 处理方式

7.1.1 I/O处理的五种模型

  • 阻塞I/O模型
    • 若所调用的 I/O 函数没有完成相关的功能就会使进程挂起,直到相关数据到达才会返回。如 终端、网络设备的访问。  
  • 非阻塞模型
    • 当请求的 I/O 操作不能完成时,则不让进程休眠,而且返回一个错误。如 open read write 访问
  • I/O 多路转接模型
    • 如果请求的 I/O 操作阻塞,且他不使真正阻塞 I/O,而且让其中一个函数等待,在这期间,I/O 还能进行其他操作。如 select 函数  
  • 信号驱动 I/O 模型
    • 在这种模型下,通过安装一个信号处理程序,系统可以自动捕获特定信号的到来,从而启动 I/O  
  • 异步 I/O 模型
    • 在这种模型下,当一个描述符已准备好,可以启动 I/O,进程会通知内核。由内核进行后续处理,这种用法现在较少 

7.1.2 非阻塞I/O

  • 低速系统调用时,进程可能会阻塞
  • 非阻塞I/O确定操作(read, open, write)不阻塞,如果操作不能完成,则出错返回
  •   设定非阻塞方式
    • 使用 open 打开文件,设置 O_NONBLOCK 标志
    • 如果一个文件已经打开,则使用 fcntl 修改文件状态标志为 非阻塞  

 7.1.3 例子

  nonblock_read.c

 1 /* 从标准输入读取信息,然后在屏幕上输出
 2  * 测试:
 3  *     (1)在睡眠 5s 内 按ctrl + d 屏幕会输出 read finished
 4  *             ctrl + d 是给程序发送读取结束的信号,即读到了文件末尾
 5  *     (2)运行程序后,等待5s 输出 read error,程序不会阻塞在那里,没有输入,size < 0
 6  *     (3)在5 s 内输入字符,会正常输出字符
 7  */
 8 
 9 #include <sys/types.h>
10 #include <sys/stat.h>
11 #include <fcntl.h>
12 #include <unistd.h>
13 #include <string.h>
14 #include <errno.h>
15 #include <stdlib.h>
16 #include <stdio.h>
17 #include <fcntl.h>
18 #include "io.h"
19 
20 int main(int argc, const char *argv[])
21 {
22     char buff[4096] = {'\0'};
23     ssize_t size = 0;
24 
25     //设置非阻塞 IO
26     sleep(5);
27     set_fl(STDIN_FILENO, O_NONBLOCK);
28 
29     /* read 函数未设置 O_NONBLOCK, 默认是阻塞状态的 */
30     size = read(STDIN_FILENO, buff, sizeof(buff));
31     if(size < 0) {
32         perror("read error");
33         exit(1);
34     } else if (size == 0) {
35         printf("read finished!!\n");
36     } else {
37         if(write(STDOUT_FILENO, buff, size) != size) {
38             perror("write error");
39         }
40     }
41 
42     return 0;
43 }

7.2 文件锁

7.2.1 文件锁介绍

  • 当多个用户共同使用、操作一个文件的时候,Linux 通常采用的方法是给文件上锁,来避免共享资源产生竞争的状态。
  • 谁获得了锁,就可以对文件进行操作
  • 文件锁按功能分为共享读锁 和独占写锁:
    • 共享读锁:
      • 文件描述符必须只读打开
      • 一个进程上了读锁,其他进程也可以上读锁进行读取
    • 独占写锁:
      • 文件描述符必须只写打开
      • 一个进程上了写锁,其他进程就不能上写锁和读锁进行读写操作
  • 文件锁按类型分为建议锁和强制性锁
    • 建议性锁要求上锁文件的进程都要检测是否由锁存在,并尊重已由的锁
    • 强制性锁由内核和系统执行的锁。文件挂载就是强制性锁  
  • fcntl 不仅可以实施建议性锁,而且可以实施强制性锁

  这里用到  fcntl 函数

1 #include <unistd.h>
2 #include <fcntl.h>
3 int fcntl(int fd, int cmd, struct flock *lock);

  cmd:F_SETLK      F_GETLK       F_SETLKW,前两种默认是非阻塞的,后面一种是阻塞时候用的

1 struct flock {
2     short l_type;                /* F_RDLCK, F_WRLCK, or F_UNLCK */
3     short l_whence;                /* SEEK_SET, SEEK_CUR, SEEK_END */
4     __kernel_off_t    l_start;    
5     __kernel_off_t    l_len;
6     __kernel_pid_t    l_pid;
7     __ARCH_FLOCK_PAD
8     
9 };
  • l_type:
    • 锁类型,F_RDLCK(共享读锁)、F_WRLCK(独占性写锁)或F_UNLCK(解锁一个区域)
  • l_start、l_whence
    • 要加锁或解锁的区域的起始地址,由 l_start 和 l_whence 两者决定
    • l_start 是相对位移量,l_whence 则决定相对位移量的起点  
  • l_len 
    • 表示区域的长度 
  • 加锁解锁区域的注意点:
    • 该区域可以在当前文件尾端处开始或越过其尾端处开始,但是不能在文件起始位置之前开始或越过该起始位置
    • 若 l_len 为0,则表示锁的区域从其起点(由 l_start 和 l_whence 决定)开始直至最大可能位置为止。也就是不管添写到文件中多少数据,它都处于锁的范围
    • 为了锁整个文件,通常的方法是将 l_start 设置为0,l_whence 设置为 SEEK_SET,l_len 设置为0    

  锁的继承和释放:

  一个进程终止,它所建立的锁全部释放

  关闭一个文件描述符,此进程对该文件的所有的锁均释放

  子进程不继承父进程的锁

  执行 exec 以后,新程序可以选择是否继承原来执行进程的锁

7.2.2 例子

  两个进程进行相互排斥写

  io.c

 1 #include <sys/types.h>
 2 #include <sys/stat.h>
 3 #include <fcntl.h>
 4 #include <unistd.h>
 5 #include "io.h"
 6 #include <string.h>
 7 #include <errno.h>
 8 #include <stdlib.h>
 9 #include <stdio.h>
10 #include <fcntl.h>
11 
12 
13 #define BUFFER_LEN 1024
14 
15 /* 文件的读写拷贝 */
16 void copy(int fdin, int fdout)
17 {
18     char buff[BUFFER_LEN];
19     ssize_t size;
20 
21 //    printf("file length: %ld\n", lseek(fdin, 0L, SEEK_END));//将文件定位到文件尾部,偏移量为0L
22 //    lseek(fdin, 0L, SEEK_SET);// 定位到文件开头
23 
24     while((size = read(fdin, buff, BUFFER_LEN)) > 0) { //从 fdin 中读取 BUFFER_LEN 个字节存放入  buff 中
25 //        printf("current: %ld\n", lseek(fdin, 0L, SEEK_CUR));
26 
27         if(write(fdout, buff, size) != size) {
28             fprintf(stderr, "write error: %s\n", strerror(errno));
29             exit(1);
30         }
31     }
32     if(size < 0) {
33         fprintf(stderr, "read error:%s\n", strerror(errno));
34         exit(1); // 相当于 return 1;
35     }
36 }
37 
38 
39 void set_fl(int fd, int flag)
40 {
41     int val;
42 
43     //获得原来的文件状态标志
44     val = fcntl(fd, F_GETFL);
45     if(val < 0) {
46         perror("fcntl error");
47     }
48 
49     //增加新的文件状态标志
50     val |= flag;
51 
52     //重新设置文件状态标志(val 为新的文件状态标志)
53     if(fcntl(fd, F_SETFL, val) < 0) {
54         perror("fcntl error");
55     }
56 }
57 
58 void clr_fl(int fd, int flag)
59 {
60     int val;
61 
62     val = fcntl(fd, F_GETFL);
63     if(val < 0) {
64         perror("fcntl error");
65     }
66     //清除指定的文件状态标志(设置为0)
67     val &= ~flag;
68     if(fcntl(fd, F_SETFL, val) < 0) {
69         perror("fcntl error");
70     }
71 }
72 
73 
74 int lock_reg(int fd, int cmd, short type, off_t offset, short whence, off_t length)
75 {
76     struct flock flock;
77     flock.l_type = type;
78     flock.l_start = offset;
79     flock.l_whence = whence;
80     flock.l_len = length;
81     //flock.l_pid = getpid();
82     //l_pid:加锁、解锁进程的进程号(pid)
83     
84     if(fcntl(fd, cmd, &flock) < 0) {
85         perror("fcntl error");
86         return 0;
87     }
88 
89     return 1;
90 }

  io.h

 1 #ifndef __IO_H__
 2 #define __IO_H__
 3 
 4 #include <sys/types.h>
 5 
 6 extern void copy(int fdin, int fdout);
 7 
 8 extern void set_fl(int fd, int flag);
 9 extern void clr_fl(int fd, int flag);
10 
11 extern int lock_reg(int fd, int cmd, short type, off_t offset, short whence, off_t length);
12 
13 /* 共享读锁,阻塞版本 */
14 #define REAK_LOCKW(fd, offset, whence, length) \
15     lock_reg(fd, F_SETLKW, F_RDLCK, offset, whence, length)
16 
17 
18 /* 共享读锁,非阻塞版本 */
19 #define REAK_LOCK(fd, offset, whence, length) \
20     lock_reg(fd, F_SETLK, F_RDLCK, offset, whence, length)
21 
22 /* 独占写锁,阻塞版本 */
23 #define WRITE_LOCKW(fd, offset, whence, length) \
24     lock_reg(fd, F_SETLKW, F_WRLCK, offset, whence, length)
25 
26 /* 独占写锁,非阻塞版本 */
27 #define WRITE_LOC(fd, offset, whence, length) \
28     lock_reg(fd, F_SETLK, F_WRLCK, offset, whence, length)
29 
30 /* 解锁,非阻塞版本 */
31 #define UNLOCK(fd, offset, whence, length) \
32     lock_reg(fd, F_SETLK, F_UNLCK, offset, whence, length)
33 
34 #endif

  lock_write.c

 1 #include <sys/types.h>
 2 #include <sys/stat.h>
 3 #include <fcntl.h>
 4 #include <unistd.h>
 5 #include <string.h>
 6 #include <errno.h>
 7 #include <stdlib.h>
 8 #include <stdio.h>
 9 #include <fcntl.h>
10 #include "io.h"
11 
12 int main(int argc, char *argv[])
13 {
14     if(argc < 4)
15     {
16         printf("Usage: %s content file lock | unlock\n", argv[0]);
17         exit(1);
18     }
19 
20     ssize_t size = strlen(argv[1]) * sizeof(char);
21     int fd = open(argv[2], O_WRONLY | O_CREAT, 0777);
22     if(fd < 0) {
23         perror("open error");
24         exit(1);
25     }
26 
27     sleep(5);
28 
29     printf("current pid pid: %d\n", getpid());
30 
31     //如果要加锁,则加的是独占写锁,阻塞版本
32     //第二个进程想要对文件加文件锁(这里是独占写锁)
33     //必须要等前一个进程释放文件锁后方可加锁
34     if(!strcmp("lock", argv[3])) {
35         WRITE_LOCKW(fd, 0, SEEK_SET, 0);//整个文件上锁
36         printf("lock success\n");
37     }
38 
39     //写字符串
40     char *p = argv[1];
41     int i;
42     for(i = 0; i < size; i++) {
43         if(write(fd, (p + i), 1) != 1) {
44             perror("write error");
45             exit(1);
46         }
47 
48         printf("%d success write one character\n", getpid());
49         sleep(1);
50     }
51 
52     //解锁
53     if(!strcmp("lock", argv[3])) {
54         UNLOCK(fd, 0, SEEK_SET, 0);
55         printf("unlock success\n");
56         printf("unlock pid: %d", getpid());
57     }
58 
59     close(fd);
60 
61     return 0;
62 }

  编译,编写后台运行脚本

  start.h

1 ./bin/lock_write aaaaaa demo.txt lock &
2 ./bin/lock_write AAAAAA demo.txt lock &

  执行start.h 执行结果如下:

  

  可以看见,一个进程对文件加锁之后,必须写完之后,并释放了锁之后,另一个进程才可以执行写操作。当一个进程执行完程序后,会自动释放锁,另一个进程再开始写,锁的释放完成是因为程序执行完或是 关闭了文件。

  对共享读锁来说,一个进程加了锁,另一个进程也可以加锁,没影响。

  修改下 start.h 脚本,让第二个进程不加锁

1 ./bin/lock_write aaaaaa demo.txt lock &
2 ./bin/lock_write AAAAAA demo.txt unlock &

  运行脚本后的运行结果如下:

  

  可以看到两个进程再交替进行写。第二个进程没有加锁,写代码区依然可以运行。

  这种锁就是建议性锁,要写文件可以建议加锁区写,但是没加锁也可以写,当前Linux默认是这样做的。

 

          

posted @ 2018-05-16 21:14  游戏进行中  阅读(810)  评论(0编辑  收藏  举报