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)在睡眠 5s 内 按ctrl + d 屏幕会输出 read finished
* ctrl + d 是给程序发送读取结束的信号,即读到了文件末尾
* (2)运行程序后,等待5s 输出 read error,程序不会阻塞在那里,没有输入,size < 0
* (3)在5 s 内输入字符,会正常输出字符
*/ #include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include "io.h" int main(int argc, const char *argv[])
{
char buff[] = {'\0'};
ssize_t size = ; //设置非阻塞 IO
sleep();
set_fl(STDIN_FILENO, O_NONBLOCK); /* read 函数未设置 O_NONBLOCK, 默认是阻塞状态的 */
size = read(STDIN_FILENO, buff, sizeof(buff));
if(size < ) {
perror("read error");
exit();
} else if (size == ) {
printf("read finished!!\n");
} else {
if(write(STDOUT_FILENO, buff, size) != size) {
perror("write error");
}
} return ;
}

7.2 文件锁

7.2.1 文件锁介绍

  • 当多个用户共同使用、操作一个文件的时候,Linux 通常采用的方法是给文件上锁,来避免共享资源产生竞争的状态。
  • 谁获得了锁,就可以对文件进行操作
  • 文件锁按功能分为共享读锁 和独占写锁:
    • 共享读锁:

      • 文件描述符必须只读打开
      • 一个进程上了读锁,其他进程也可以上读锁进行读取
    • 独占写锁:
      • 文件描述符必须只写打开
      • 一个进程上了写锁,其他进程就不能上写锁和读锁进行读写操作
  • 文件锁按类型分为建议锁和强制性锁
    • 建议性锁要求上锁文件的进程都要检测是否由锁存在,并尊重已由的锁
    • 强制性锁由内核和系统执行的锁。文件挂载就是强制性锁  
  • fcntl 不仅可以实施建议性锁,而且可以实施强制性锁

  这里用到  fcntl 函数

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

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

 struct flock {
short l_type; /* F_RDLCK, F_WRLCK, or F_UNLCK */
short l_whence; /* SEEK_SET, SEEK_CUR, SEEK_END */
__kernel_off_t l_start;
__kernel_off_t l_len;
__kernel_pid_t l_pid;
__ARCH_FLOCK_PAD };
  • 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

 #include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "io.h"
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h> #define BUFFER_LEN 1024 /* 文件的读写拷贝 */
void copy(int fdin, int fdout)
{
char buff[BUFFER_LEN];
ssize_t size; // printf("file length: %ld\n", lseek(fdin, 0L, SEEK_END));//将文件定位到文件尾部,偏移量为0L
// lseek(fdin, 0L, SEEK_SET);// 定位到文件开头 while((size = read(fdin, buff, BUFFER_LEN)) > ) { //从 fdin 中读取 BUFFER_LEN 个字节存放入 buff 中
// printf("current: %ld\n", lseek(fdin, 0L, SEEK_CUR)); if(write(fdout, buff, size) != size) {
fprintf(stderr, "write error: %s\n", strerror(errno));
exit();
}
}
if(size < ) {
fprintf(stderr, "read error:%s\n", strerror(errno));
exit(); // 相当于 return 1;
}
} void set_fl(int fd, int flag)
{
int val; //获得原来的文件状态标志
val = fcntl(fd, F_GETFL);
if(val < ) {
perror("fcntl error");
} //增加新的文件状态标志
val |= flag; //重新设置文件状态标志(val 为新的文件状态标志)
if(fcntl(fd, F_SETFL, val) < ) {
perror("fcntl error");
}
} void clr_fl(int fd, int flag)
{
int val; val = fcntl(fd, F_GETFL);
if(val < ) {
perror("fcntl error");
}
//清除指定的文件状态标志(设置为0)
val &= ~flag;
if(fcntl(fd, F_SETFL, val) < ) {
perror("fcntl error");
}
} 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

 #ifndef __IO_H__
#define __IO_H__ #include <sys/types.h> extern void copy(int fdin, int fdout); extern void set_fl(int fd, int flag);
extern void clr_fl(int fd, int flag); extern int lock_reg(int fd, int cmd, short type, off_t offset, short whence, off_t length); /* 共享读锁,阻塞版本 */
14 #define REAK_LOCKW(fd, offset, whence, length) \
15 lock_reg(fd, F_SETLKW, F_RDLCK, offset, whence, length)
/* 共享读锁,非阻塞版本 */
19 #define REAK_LOCK(fd, offset, whence, length) \
20 lock_reg(fd, F_SETLK, F_RDLCK, offset, whence, length)

/* 独占写锁,阻塞版本 */
23 #define WRITE_LOCKW(fd, offset, whence, length) \
24 lock_reg(fd, F_SETLKW, F_WRLCK, offset, whence, length)

/* 独占写锁,非阻塞版本 */
27 #define WRITE_LOC(fd, offset, whence, length) \
28 lock_reg(fd, F_SETLK, F_WRLCK, offset, whence, length)

/* 解锁,非阻塞版本 */
31 #define UNLOCK(fd, offset, whence, length) \
32 lock_reg(fd, F_SETLK, F_UNLCK, offset, whence, length)

#endif

  lock_write.c

 #include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include "io.h" int main(int argc, char *argv[])
{
if(argc < )
{
printf("Usage: %s content file lock | unlock\n", argv[]);
exit();
} ssize_t size = strlen(argv[]) * sizeof(char);
int fd = open(argv[], O_WRONLY | O_CREAT, );
if(fd < ) {
perror("open error");
exit();
} sleep(); printf("current pid pid: %d\n", getpid()); //如果要加锁,则加的是独占写锁,阻塞版本
//第二个进程想要对文件加文件锁(这里是独占写锁)
//必须要等前一个进程释放文件锁后方可加锁
if(!strcmp("lock", argv[])) {
WRITE_LOCKW(fd, , SEEK_SET, );//整个文件上锁
printf("lock success\n");
} //写字符串
char *p = argv[];
int i;
for(i = ; i < size; i++) {
if(write(fd, (p + i), ) != ) {
perror("write error");
exit();
} printf("%d success write one character\n", getpid());
sleep();
} //解锁
if(!strcmp("lock", argv[])) {
UNLOCK(fd, , SEEK_SET, );
printf("unlock success\n");
printf("unlock pid: %d", getpid());
} close(fd); return ;
}

  编译,编写后台运行脚本

  start.h

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

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

  

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

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

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

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

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

  

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

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

          

七、文件IO——I/O处理方式和文件锁的更多相关文章

  1. 树莓派学习笔记——使用文件IO操作GPIO SysFs方式

    0 前言     本文描写叙述假设通过文件IO sysfs方式控制树莓派 GPIO端口.通过sysfs方式控制GPIO,先訪问/sys/class/gpio文件夹,向export文件写入GPIO编号, ...

  2. Linux下用文件IO的方式操作GPIO(/sys/class/gpio)

    通过sysfs方式控制GPIO,先访问/sys/class/gpio目录,向export文件写入GPIO编号,使得该GPIO的操作接口从内核空间暴露到用户空间,GPIO的操作接口包括direction ...

  3. Linux下用文件IO的方式操作GPIO(/sys/class/gpio)(转)

    通过sysfs方式控制GPIO,先访问/sys/class/gpio目录,向export文件写入GPIO编号,使得该GPIO的操作接口从内核空间暴露到用户空间,GPIO的操作接口包括direction ...

  4. TX2 用文件IO的方式操作GPIO

    概述 通过 sysfs 方式控制 GPIO,先访问 /sys/class/gpio 目录,向 export 文件写入 GPIO 编号,使得该 GPIO 的操作接口从内核空间暴露到用户空间,GPIO 的 ...

  5. GPIO编程1:用文件IO的方式操作GPIO

    概述 通过 sysfs 方式控制 GPIO,先访问 /sys/class/gpio 目录,向 export 文件写入 GPIO 编号,使得该 GPIO 的操作接口从内核空间暴露到用户空间,GPIO 的 ...

  6. [转] Linux下用文件IO的方式操作GPIO(/sys/class/gpio)

    点击阅读原文 一.概述 通过 sysfs 方式控制 GPIO,先访问 /sys/class/gpio 目录,向 export 文件写入 GPIO 编号,使得该 GPIO 的操作接口从内核空间暴露到用户 ...

  7. 第七篇:两个经典的文件IO程序示例

    前言 本文分析两个经典的C++文件IO程序,提炼出其中文件IO的基本套路,留待日后查阅. 程序功能 程序一打印用户指定的所有文本文件,程序二向用户指定的所有文本文件中写入数据. 程序一代码及其注释 # ...

  8. 《UNIX环境高级编程》笔记——3.文件IO

    一.引言 说明几个I/O函数:open.read.write.lseek和close,这些函数都是不带缓冲(不带缓冲,只调用内核的一个系统调用),这些函数不输入ISO C,是POSIX的一部分: 多进 ...

  9. (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

随机推荐

  1. VSCode创建自定义代码段

    上一篇:PyCharm创建自定义代码段(JetBrains系列通用) 设置方法 很简单,快速过一下,F1,然后输入snippets 然后选择对应语言 Python案例 内容和使用: { // pref ...

  2. 解题:CF622F The Sum of the k-th Powers

    题面 TJOI2018出CF原题弱化版是不是有点太过分了?对,就是 TJOI2018 教科书般的亵渎 然而我这个问题只会那个题的范围的m^3做法 回忆一下1到n求和是二次的,平方求和公式是三次的,立方 ...

  3. 洛谷P4175 网络管理

    题意:链上带修第k大. 这毒瘤题...别看题意只有7个字,能把我吊打死... 介绍其中两种做法好了.其实思想上是一样的. 对于每一个点,建立权值线段树,维护它到根路径上的所有权值. 一条路径上的点集就 ...

  4. A1133. Splitting A Linked List

    Given a singly linked list, you are supposed to rearrange its elements so that all the negative valu ...

  5. Python 3 入门,看这篇就够了

    文章目录 简介 基础语法 运算符 变量 数据类型 流程控制 迭代器 生成器 函数 自定义函数 参数传递 可更改与不可更改对象 参数 匿名函数 变量作用域 模块 面向对象 错误和异常 文件操作 序列化 ...

  6. luogu3731 新型城市化

    题目链接 思路 这道题对于题意的转化很关键. 题目要求的是添上一条边,使得图中最大团的大小变大.给出的边是原图的补集,这就给我们了提示. 因为题目中说,原图中最多有两个团.所以给出的边一定形成了一个二 ...

  7. Mysql中INSERT ... ON DUPLICATE KEY UPDATE的实践

    转: Mysql中INSERT ... ON DUPLICATE KEY UPDATE的实践 阿里加多 0.1 2018.03.23 17:19* 字数 492 阅读 2613评论 2喜欢 1 一.前 ...

  8. request的基本应用

    一.安装 pip install requests (mac前面加sudo) 二.requests的一些参数 method:一般是用的那种请求方法,是get还是post,delete或者delete ...

  9. MooFest POJ - 1990 (树状数组)

    Every year, Farmer John's N (1 <= N <= 20,000) cows attend "MooFest",a social gather ...

  10. Linux中sed的用法实践

    Linux中sed的用法实践 参考资料:https://www.cnblogs.com/emanlee/archive/2013/09/07/3307642.html http://www.fn139 ...