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. 2019 校内赛 RPG的天赋分支(贪心)

    Problem Description 很多游戏都有天赋树的概念,天赋树的不同分支具有不同的属性加成,那么合理选择分支就非常重要了.Luke最近沉迷一款RPG游戏,它的天赋树机制如下:角色具有n个可选 ...

  2. 牛客OI周赛7-提高组 B小睿睿的询问(ST打表)

    链接:https://ac.nowcoder.com/acm/contest/371/B来源:牛客网 小睿睿的n个妹纸排成一排,每个妹纸有一个颜值val[i].有m个询问,对于每一个询问,小睿睿想知道 ...

  3. 20165223 week3蓝墨云测试总结

    1. 表达式0xaa | 0x55的值为 答案: 解析: 0xaa用二进制表示为10101010,0x55用二进制表示为01010101,按位或后为11111111,十进制表示为255,十六进制表示为 ...

  4. Navicat Premium 12 破解(MySQL、MariaDB、MongoDB、SQL Server、SQLite)

    打开注入到安装目录中的exe中 破解提示(还没好,继续看下去) 如果你安装的是中文版,选一下中文版(英文默认即可),获取一下key(名字和组织可以自定义) 打开Navicat,选择注册(第一次打开选注 ...

  5. GNOME下让QT应用使用adwaita主题统一外观

    效果展示 使用前 使用后 步骤 Arch Linux下使用AUR安装 sudo yaourt adwaita-qt4 adwaita-qt5 sudo pacman -S qtconfig-qt4 q ...

  6. Linux安装 火速入门

    一.基本简介 Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户.多任务.支持多线程和多CPU的操作系统. Linux能运行主要的UNIX工具软件.应用程序 ...

  7. TreeView CheckBox勾选联动

    http://www.cnblogs.com/excellently/p/TreeViewCheckBox.html 在C# Winform项目中用到了TreeView控件,并且需要勾选的功能.父子节 ...

  8. C# Winfom 中ListBox的简单用法

    https://www.cnblogs.com/xielong/p/6744805.html Winform控件ListBox的用法 1.如何添加listBox的值 this.listBox1.Ite ...

  9. linux 系统调用之文件操作

    fcntl 文件控制 open 打开文件 creat 创建新文件 close 关闭文件描述字 read 读文件 write 写文件 readv 从文件读入数据到缓冲数组中 writev 将缓冲数组里的 ...

  10. strut2 的数据验证

    数据验证 用户的输入验证,必须做,且工作量巨大. 1.验证的方式 客户端验证:javascript 服务端验证:逻辑验证(我们的代码) 注意:如果客户端和服务端二选一的话,服务器端的不能省. 实际开发 ...