这一章涉及很多概念和函数,包括:非阻塞I/O、记录锁、I/O复用、异步I/O、readv和writev函数以及内存映射。

非阻塞I/O

在Unix中,可以将系统调用分为两种,一种是“低速”系统调用,另一种是其他系统调用。前一种是可能导致主调进程永久阻塞的一种系统调用,比如管道,当另一端没有准备好时,一端对其读或写可能会永久阻塞。

一旦一个进程可能被永久阻塞这就表明程序有可能在某点彻底瘫痪,为了预防这样的情况发生,可以使用非阻塞I/O来避免。非阻塞I/O能够使得进程不会陷入永久阻塞的陷阱,当操作不能立即完成时,非阻塞I/O不会阻塞,其将会立即以出错的形式返回,表示操作继续执行就会阻塞。

有两种办法来将一个给定的文件描述符指定为非阻塞I/O:

(1) 在调用open( )函数获得文件描述符时,通过O_NONBLOCK标志来设置;

(2) 对于已存在的文件描述符,通过fcntl函数来设置O_NONBLOCK标志。

记录锁

Unix提供了用于支持单独文件读写保证的服务,那就是记录锁。实际上,记录锁并不能保证是在单独写一个文件,这和普通锁是一样的道理,你必须先持有锁,然后再修改文件,关键点是所有参与修改的进程先设置锁,但是有的进程压根就不上锁,因此也就谈不上保证单独读写。事实上,这里有个前提,那就是参与读写同一文件的进程必须都先加记录锁,然后才能通过记录锁提供保证。

对打开的文件加持记录锁是通过fcntl( )函数来完成的。其头文件及函数原型如下:

#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* struct flock *flockptr */ );

第一个参数为已打开文件的文件描述符。

第二个参数取F_GETLK(获取锁)、F_SETLK(设置/清除锁、排斥时返回)、F_SETLKW(设置锁、排斥时阻塞)。

第三个参数是一个具体的锁指针,可以用来设置锁的具体条件,比如从何处开始加锁、加锁多少字节,加持读锁、写锁还是解锁。

对于加锁起始位置可以在文件尾端开始,或者尾端后面开始,但不能在文件起始位置之前。

如果一把锁的 l_start 和 l_whence 都指向文件开始位置,并且 l_len 为0,那么表示该文件全部加锁,包括后续追加的内容也是在锁范围。

同一个进程新加锁会替换旧锁,比如进程768第一次在16-32字节处加了读锁,则第二次在16-32字节处加读锁或者写锁时,第一次的锁会被清除,第二个锁会生效,也即一个进程只能对同一个文件的同一区间加一把锁。同样的,我们不能使用F_GETLK来测试自己的进程是否持有锁,因为自己不会对自己排斥,一定会用自己的新锁去替换旧锁。

对于F_SETLKW需要注意的是防止导致死锁,因为F_SETLKW在锁不能满足的时候是阻塞而不是出错返回,两个进程各持有一把锁,同时两个进程都试图获得对方进程的锁时也会死锁。

记录锁的继承和释放

记录锁的继承和释放有三个规则:

1. 锁与进程和文件两个同时有关:也即,当一个进程结束后,锁自动释放,当一个文件关闭后,锁也自动释放,二者必须同时有效,锁才正常,只要任意一个无效,锁就释放。对于文件关闭来说,文件的重复打开或者文件描述符的复制等同于原文件描述符,具体如下:

fd1 = open(pathname, ...);     //打开文件
read_lock(fd1, ...);   //加锁
fd2 = dup(fd1); //复制文件描述符
close(fd2); //关闭复制的文件描述符 fd1 = open(pathname, ...); //打开文件
read_lock(fd1, ...);  //加锁
fd2 = open(pathname, ...) //重复打开文件
close(fd2); //关闭复制的文件描述符

对于上面的重复打开文件或者文件描述符的复制操作,当关闭fd2之后,锁也失效,因为锁和文件有关,文件被关闭了一次,锁就失效了。

2. 对于fork产生的子进程不继承父进程的锁,因为锁与进程有关,子进程是一个新进程,与原锁无关。

3. 对于执行exec之后的新程序会继承原锁,因为锁与进程有关,新程序只是替换了进程内部数据,进程本身不变,锁依然有效,当然,如果进程打开的文件描述符设置了close_on_exec那么该文件描述符会在exec时被关闭,由于锁与文件也有关,因此文件描述符被关闭后,锁就会释放。

记录锁与进程有关这是没有任何疑问的,而记录锁与文件描述符无关与文件有关有点令人困惑,这是因为内核底层是通过v节点表项中的记录锁指针来记录记录锁,并不能知道具体是哪个文件描述符设置的,所有的文件描述符都指向同一个v节点表项,因此只要有一个文件描述符关闭了,内核就会去释放该v节点表项中的记录锁。

建议性锁和强制性锁

建议性锁和强制性锁不是真实存在的两种锁,它们是两种机制。如前所述,记录锁并不能保证是在单独写一个文件,所有参与修改同一文件的进程,如果其中存在未使用记录锁的进程,那么记录锁则等同虚设。为了能够使得记录锁发挥作用,应该在所有修改进程中进行记录锁的检查与设置,这就是建议性锁,也即所有的进程在读写文件之前先设置记录锁,所有这些进程合计称为合作进程。建议性锁只是一个规则,并不是真实存在的锁,如果不遵守该项机制,那么建议性锁并不能保证单独写一个文件。

和建议性锁相对的是强制性锁,强制性锁会使内核检查每个文件读写行为,即使某些进程并未使用记录锁。强制性锁也不是一种锁,它也是一种机制,该规则是在一个文件的设置组ID位打开,且组执行位关闭时触发生效。

UNIX环境高级编程 第14章 高级I/O的更多相关文章

  1. 《Unix 网络编程》14:高级 I/O 函数

    高级 I/O 函数 ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ...

  2. ASP.NET MVC5 高级编程 第5章 表单和HTML辅助方法

    参考资料<ASP.NET MVC5 高级编程>第5版 第5章 表单和HTML辅助方法 5.1 表单的使用 5.1.1 action 和 method 特性 默认情况下,表单发送的是 HTT ...

  3. ASP.NET MVC5 高级编程 第3章 视图

    参考资料<ASP.NET MVC5 高级编程>第5版 第3章 视图 3.1 视图的作用 视图的职责是向用户提供界面. 不像基于文件的框架,ASP.NET Web Forms 和PHP ,视 ...

  4. ASP.NET MVC5 高级编程 第2章 控制器

    参考资料<ASP.NET MVC5 高级编程>第5版 第2章 控制器 控制器:响应用户的HTTP 请求,并将处理的信息返回给浏览器. 2.1 ASP.NET MVC 简介 MVC 模式中的 ...

  5. UNIX环境高级编程 第8章 进程控制

    本章是UNIX系统中进程控制原语,包括进程创建.执行新程序.进程终止,另外还会对进程的属性加以说明,包括进程ID.实际/有效用户ID. 进程标识 每个进程某一时刻在系统中都是独一无二的,它们之间是用一 ...

  6. UNIX环境高级编程 第7章 进程环境

    本章涉及C/C++程序中main函数是如何被调用的.命令行参数如何传递给main函数.程序的内存空间布局.程序如何使用环境变量.程序如何终止退出. main函数 C程序或C++程序总是从main函数开 ...

  7. UNIX环境高级编程 第6章 系统数据文件和信息

    UNIX系统的正常运作需要用到大量与系统有关的数据文件,例如系统用户账号.用户密码.用户组等文件.出于历史原因,这些数据文件都是ASCII文本文件,并且使用标准I/O库函数来读取. 口令文件 /etc ...

  8. UNIX环境高级编程 第13章 守护进程

    守护进程daemon是一种生存周期很长的进程.它们通常在系统引导时启动,在系统关闭时终止.守护进程是没有终端的,它们一直在后台运行. 守护进程的特征 在Linux系统中,可以通过命令 ps -efj ...

  9. UNIX环境高级编程 第9章 进程关系

    在第8章学习了进程的控制原语,通过各种进程原语可以对进程进行控制,包括新建进程.执行新程序.终止进程等.在使用fork( )产生新进程后,就出现了进程父子进程的概念,这是进程间的关系.本章更加详细地说 ...

随机推荐

  1. vector(char*)和vector(string)

    vector<char*> ch; vector<string> str; for(int i=0;i<5;i++) { char *c=fun1();//通过这个语句产 ...

  2. URAL 1969. Hong Kong Tram

    有一个trick就是没想到,枚举第二段时间后,要检测该火车能否继续跑一圈来判断,不能先检测前半圈能不能跑加进去后在检测后半段: // **** 部分不能放在那个位置: 最近代码导致的错误总是找不出,贴 ...

  3. 如何在Eclipse 中查看(如Objects)的源代码

    今天突然想看Objects的源码,结果失败了,总结了一下,贴出来. 一般查看源码的流程: 使用快捷键Ctri+Shit+T出现Open Type的对话框,在里面直接输入类名,回车就行: 在Eclips ...

  4. BZOJ4727 [POI2017]Turysta 【竞赛图哈密顿路径/回路】

    题目链接 BZOJ4727 题解 前置芝士 1.竞赛图存在哈密顿路径 2.竞赛图存在哈密顿回路,当且仅当它是强联通的 所以我们将图缩点后,拓扑排序后一定是一条链,且之前的块内的点和之后块内的点的边一定 ...

  5. 如何在低速率网络中测试 Web 应用

    大家看到标题后的第一个问题可能是:“我们需要这样做吗?” 如果我们开发的是局域网 Web 应用的话,可能没有必要这样做.但如果我们的 Web 应用面向的是互联网上的成千上万的用户,这样做就很必要了.因 ...

  6. 关于未来IT职业教育的思考

    回首过去20年的IT教育,从基本的办公软件(应用软件)到基础设施培训(网络.ps.3d等)再到软件开发(java等),可以说是见证了中国计算机发展的整个阶段,随着时代的变迁,计算机从最初的普及阶段到深 ...

  7. MVP, MVVM In Android

    MVP, MVVM In Android(新手必看)安卓MVP的理解,看完你就会用mvp了 - zq019的博客 - 博客频道 - CSDN.NEThttp://blog.csdn.net/zq019 ...

  8. process.nextTick,Promise.then,setTimeout,setImmediate执行顺序

    1. 同步代码执行顺序优先级高于异步代码执行顺序优先级: 2. new Promise(fn)中的fn是同步执行: 3. process.nextTick()>Promise.then()> ...

  9. hdu 6311 欧拉回路

    题意:求一个图(不一定联通)最小额外连接几条边,使得可以一笔画出来 大致做法 1.找出联通块 2.统计每一个连通块里面度数为奇数的点的个数, 有一个性质 一个图能够用一笔画出来,奇数点的个数不超过2个 ...

  10. Feign来调用服务

    Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单.使用Feign,只需要创建一个接口并注解.它具有可插拔的注解特性,可使用Feign 注解和JAX-RS注解.Feign支持可 ...