zoukankan      html  css  js  c++  java
  • 七、文件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] = {''};
    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!!
    ");
    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
    ", 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
    ", lseek(fdin, 0L, SEEK_CUR));
    26 
    27         if(write(fdout, buff, size) != size) {
    28             fprintf(stderr, "write error: %s
    ", strerror(errno));
    29             exit(1);
    30         }
    31     }
    32     if(size < 0) {
    33         fprintf(stderr, "read error:%s
    ", 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
    ", 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
    ", getpid());
    30 
    31     //如果要加锁,则加的是独占写锁,阻塞版本
    32     //第二个进程想要对文件加文件锁(这里是独占写锁)
    33     //必须要等前一个进程释放文件锁后方可加锁
    34     if(!strcmp("lock", argv[3])) {
    35         WRITE_LOCKW(fd, 0, SEEK_SET, 0);//整个文件上锁
    36         printf("lock success
    ");
    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
    ", getpid());
    49         sleep(1);
    50     }
    51 
    52     //解锁
    53     if(!strcmp("lock", argv[3])) {
    54         UNLOCK(fd, 0, SEEK_SET, 0);
    55         printf("unlock success
    ");
    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默认是这样做的。

              

  • 相关阅读:
    八数码
    狂神说笔记——多线程05
    狂神说笔记——Java SE基础03
    从零开始的卷积神经网络
    深度学习与机器学习的区别
    模型的评估与选择
    经验风险VS风险函数
    常见的损失函数
    Erlang聊天室
    uniapp APP端 清除缓存
  • 原文地址:https://www.cnblogs.com/kele-dad/p/9038564.html
Copyright © 2011-2022 走看看