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. [JSOI2008]魔兽地图(树形dp)

    DotR (Defense of the Robots) Allstars是一个风靡全球的魔兽地图,他的规则简单与同样流行的地图DotA (Defense of the Ancients) Allst ...

  2. nuxt.js实战之用vue-i18n实现多语言

    一.实现步骤 1.安装vue-i18n并且创建store.js(vuex状态管理)文件 2.创建一个(middleware)中间件,用来管理不同的语言 3.创建不同语言的json文件作为语言包(例如: ...

  3. js jquery数组去重

    数组去重建议直接使用jquery的 $.unique(arr);方法,此外比较好的方法是本文中的unique3方法比较快用了一个hash表,就是所谓的空间换时间.本文还提供了很多其他写法,都是大同小异 ...

  4. A1143. Lowest Common Ancestor

    The lowest common ancestor (LCA) of two nodes U and V in a tree is the deepest node that has both U ...

  5. 用 Homebrew 带飞你的 Mac

    文章目录 资料 安装 基本用法 源镜像 Homebrew也称brew,macOS下基于命令行的最强大软件包管理工具,使用Ruby语言开发.类似于CentOS的yum或者Ubuntu的apt-get,b ...

  6. hihocoder1364 奖券兑换

    题目链接 思路 乍一看这是一个01背包的裸题.但是数据范围\(10^5\)是无法承受的. 但是发现\(p_i\)和\(w_i\)只有10,也就是说最多只有100种物品.所以可以对他们进行分组.然后用二 ...

  7. react-native中的scrollables

    这是一个点击切换的demo 先看效果 然后看实现的代码 import React from 'react'; import { Text, View } from 'react-native'; im ...

  8. JS验证身份证

    话不多说,直接看代码 JS部分 /** * 身份证15位编码规则:dddddd yymmdd xx p * dddddd:地区码 * yymmdd: 出生年月日 * xx: 顺序类编码,无法确定 * ...

  9. 第三十四篇-Palette(调色板)的使用

    由于屏幕录制图片转换关系,不甚清晰,还是附上效果图 可以看出,上面文字和背景颜色确实会根据图片的变化而变化. 里面有3个组件,toolbar,textview,imageview,其中textview ...

  10. 基于tensorflow的MNIST手写数字识别(二)--入门篇

    http://www.jianshu.com/p/4195577585e6 基于tensorflow的MNIST手写字识别(一)--白话卷积神经网络模型 基于tensorflow的MNIST手写数字识 ...