《UNUX环境高级编程》(14)高级I/O

1、引言

2、 非阻塞I/O

  • 系统调用分为两类:低速系统调用和其他系统调用。低速系统调用是可能会使进程永远阻塞的一类系统调用,包括:
    • 如果某些文件类型(如读管道、终端设备和网络设备)的数据并不存在,读操作可能使调用者永远阻塞。
    • 如果数据不能被相同的文件类型立即接受(如管道中无空间、网络流控制),写操作可能会使调用者永远阻塞。
    • 在某种条件发生之前打开某些文件类型可能会发生阻塞(例如以只写模式打开FIFO,那么在没有其他进程用读模式打开该FIFO时也要等待)
    • 对已经加上强制性记录锁的文件进行读写
    • 某些ioctl操作
    • 某些进程间通信函数
  • 非阻塞I/O使我们可以发出openreadwrite这样的I/O操作,并使这些操作不会永远阻塞。如果这种操作不能完成,则调用立即出错返回,表示该操作如果继续进行将阻塞
  • 对于一个非阻塞的描述符如果无数据可读,则read返回-1errnoEAGAIN
  • 非阻塞I/O指的是文件状态标志,即与文件表项有关。会影响使用同一文件表项的所有文件描述符(即使属于不同的进程)。
  • 注意,read函数阻塞的情况read函数只是一个通用的读文件设备的接口。是否阻塞需要由设备的属性和设定所决定。一般来说,读字符终端、网络的socket描述字,管道文件等,这些文件的缺省read都是阻塞的方式。如果是读磁盘上的文件,一般不会是阻塞方式的。但使用锁和fcntl设置取消文件O_NOBLOCK状态,也会产生阻塞的read效果。
  • 对于一个给定的文件描述符,有两种方式为其指定非阻塞I/O
    • 如果用open获得描述符,指定O_NONBLOCK标志
    • 对于一个已经打开的文件描述符,则可调用fcntl,由该函数打开O_NONBLOCK文件状态标志
      int flag = fcntl(fd, F_GETFL);  //获取文件状态标志
      flag |= O_NONBLOCK;
      int ret = fcntl(fd, F_SETFL, flag);  //设置文件状态标志
      
  • 实例: 一个非阻塞I/O的实例。它从标准输入读50000个字节,并试图将它们写到标准输出上。该程序先将标准输出设置为非阻塞的,然后用for循环进行输出,每次write调用的结果都在标准错误上打印。其中set_fl函数的介绍见3.14节,get_fl函数则类似于set_fl函数。
    #include "apue.h"
    #include <errno.h>
    #include <fcntl.h>char	buf[500000];int
    main(void)
    {int		ntowrite, nwrite;char	*ptr;ntowrite = read(STDIN_FILENO, buf, sizeof(buf));fprintf(stderr, "read %d bytes\n", ntowrite);set_fl(STDOUT_FILENO, O_NONBLOCK);	/* set nonblocking */ptr = buf;while (ntowrite > 0) {errno = 0;nwrite = write(STDOUT_FILENO, ptr, ntowrite);fprintf(stderr, "nwrite = %d, errno = %d\n", nwrite, errno);if (nwrite > 0) {ptr += nwrite;ntowrite -= nwrite;}}clr_fl(STDOUT_FILENO, O_NONBLOCK);	/* clear nonblocking */exit(0);
    }
    
    • 若标准输出是普通文件,则write一般只调用一次。这里的文件/etc/services大小为19605字节,小于程序中的50000字节,所以写入的大小也为19605字节。如果文件大小大于50000字节,那么写入的大小为50000字节。

      lh@LH_LINUX:~/桌面/apue.3e/advio$ ls -l /etc/services 
      -rw-r--r-- 1 root root 19605 1025  2014 /etc/services
      lh@LH_LINUX:~/桌面/apue.3e/advio$ ./nonblockw < /etc/services > temp.file
      read 19605 bytes
      nwrite = 19605, errno = 0
      lh@LH_LINUX:~/桌面/apue.3e/advio$ ls -l temp.file 
      -rw-rw-r-- 1 lh lh 19605 89 13:15 temp.file
      
    • 若标准输出是终端,则有时返回数字,有时返回错误,下面是运行结果。

      在这里插入图片描述

      • 该系统上,errno的值35对应的是EAGAIN。终端驱动程序一次能接受的数据量随系统而变。
      • 在此实例中,程序发出了9000多个write调用,但是只有500个真正输出了数据,其余的都只返回了错误。这种形式的循环称为轮询,在多用户系统上用它会浪费CPU时间。14.4节会介绍非阻塞描述符的I/O多路转换,这是进行这种操作的一种比较有效的方法。
      • 有时,可以将应用程序设计成多线程的,从而避免使用非阻塞I/O。如若我们能在其他线程中继续进行,则可以允许单个线程在I/O调用中阻塞。但线程间的同步的开销有时却可能增加复杂性,于是导致得不偿失的后果。

3、 记录锁

  • 当两个人同时编辑一个文件时,该文件的最后状态取决于写该文件的最后一个进程。但是对于有些应用程序如数据库,进程有时需要确保它正在单独写一个文件。因此可以使用记录锁机制
  • 记录锁的功能:当第一个进程正在读或者修改文件的某个部分时,使用记录锁可以阻止其他进程修改同一文件区
  • 其实应该称记录锁为 “字节范围锁” ,因为它锁定的只是文件中的一个区域(也可能是整个文件)

3.1、历史

  • 这部分的内容不重要,略过。

3.2、fcntl记录锁

  • 3.14节已经给出了该函数的原型

    int fcntl(int fd, int cmd, ... /* struct flock * flockptr */ );
    
    • 与记录锁相关的cmdF_GETLKF_SETLKF_SETLKW
      • F_GETLK
        • 判断由flockptr描述的锁是否会被另外一把锁排斥(阻塞)。如果存在一把锁阻止创建flockptr描述的锁,则该现有锁的信息将重写flockptr指向的信息。如果不存在这种情况,除了l_type设置为F_UNLCK之外,flockptr指向的结构中其他信息不变。
        • 注意由于调用进程自己的锁并不会阻塞自己的下一次尝试加锁(因为新锁将替换旧锁),因此F_GETLK不会报告调用进程自己持有的锁信息。因此不能用它来测试自己是否在某一文件区域持有一把锁。
      • F_SETLK
        • 设置由flockptr所描述的锁(共享读锁或独占写锁)。如果失败fcntl函数立即出错返回,errno设置为EACCESEAGAGIN
      • F_SETLKW
        • 这个命令是F_SETLK的阻塞版本(w表示等待wait。如果所请求的读锁或写锁因另一个进程当前已经对所请求部分进行了加锁而不能被授予,那么调用进程休眠。如果请求创建的锁已经可用,或者休眠被信号中断,则该进程被唤醒。
    • 第三个参数是一个指向flock结构的指针。
      struct flock{short int l_type;	/* 记录锁类型: F_RDLCK, F_WRLCK, or F_UNLCK.	*/short int l_whence;	/* SEEK_SET、SEEK_CUR、SEEK_END */__off_t l_start;	/* Offset where the lock begins.  */__off_t l_len;	/* Size of the locked area; zero means until EOF.  */__pid_t l_pid;	/* Process holding the lock.  */};
      
      • flock结构说明如下:
        • l_type所希望的锁类型F_RDLCK(共享读锁)、F_WRLCK(独占性写锁)、F_UNLCK(解锁一个区域)
        • l_whence指示l_start从哪里开始SEEK_SET(开头)、SEEK_CUR(当前位置)、SEEK_END(结尾)
        • l_start要加锁或解锁区域的起始字节偏移量
        • l_len要加锁或解锁区域字节长度
        • l_pid仅由F_GETLK返回,表示该pid进程持有的锁能阻塞当前进程
      • 关于加锁和解锁区域的说明还要注意以下事项:
        • 锁可以在当前文件尾端处开始或者越过尾端处开始,但是不能在文件起始位置之前开始
        • 如果l_len0,则表示锁的范围可以扩展到最大可能偏移量。这意味着不管向该文件中追加写了多少数据,它们都可以处于锁的范围内(不必猜测会有多少字节被追加写到了文件之后)
        • 为了对整个文件加锁,设置l_startl_whence指向文件起始位置,并且指定长度l_len0
  • fcntl可以操作两种锁:共享读锁F_RDLCK和独占性写锁F_WRLCK

    • 任意多个进程在一个给定的字节上可以有一把共享的读锁
    • 但是在一个给定字节上只能有一个进程有一把独占写锁。
    • 如果在一个给定字节上已经有一把或多把读锁,则不能在该字节上再加写锁
    • 如果在一个字节上已经有一把独占性写锁,则不能再对它加任何读锁。
    • 如果一个进程对一个文件区间已经有了一把锁,后来该进程又企图在同一文件区间再加一把锁,那么新锁将替换已有锁。比如一个进程在某文件的16-32字节区间有一把写锁,然后又试图在16-32字节区间加一把读锁,那么该请求成功执行,原来的写锁替换为读锁。
    • 加读锁时,描述符必须是读打开;加写锁时,描述符必须是写打开。
      在这里插入图片描述
  • 需要注意以下两点:

    • F_GETLK测试能否建立一把锁,然后用F_SETLKF_SETLKW企图建立那把锁,这两者不是一个原子操作。不能保证两次fcntl调用之间不会有另一个进程插入并建立一把锁
    • POSIX没有说明下列情况会发生什么:
      • 第一个进程在某文件区间设置一把读锁,第二个进程试图在同一文件区间加一把写锁时阻塞,然后第三个进程则试图在同一文件区间设置另一把读锁。如果允许第三个进程获得读锁,那么这种实现容易导致希望加写锁的进程饿死
  • 文件记录锁的组合和分裂

    • 在设置或释放文件上的一把锁时,系统按照要求组合或分裂相邻区。
      在这里插入图片描述
      • 例如在100-199字节是加锁区域,当需要解锁第150字节时,则内核将维持两把锁:一把用于100-149字节;另一把用于151-199字节。
      • 如果我们又对第150字节加锁,那么系统会把相邻的加锁区合并成一个区(100-199字节),和开始时又一样了。
  • 实例:请求和释放一把锁。为了每次都避免分配flock结构,然后又填入各项信息,可以用下图程序中的lock_reg来处理这些细节。
    在这里插入图片描述

    • 因为大多数锁调用时加锁或解锁一个文件区域(命令F_GETLK很少使用),故通常使用下列5个宏中的一个。这5个宏都定义在apue.h中(见附录B
      在这里插入图片描述
  • 实例:测试一把锁。如果存在一把锁,它阻塞由参数指定的锁请求,则此函数返回持有这把现有锁的进程的进程 ID,否则此函数返回0

    在这里插入图片描述在这里插入图片描述

    • 通过用下面两个宏来调用此函数(它们也定义在apue.h中)
      在这里插入图片描述
    • 注意,进程不能使用lock_test函数测试它自己是否在文件的某一部分持有一把锁F_GETLK命令的定义说明,返回信息指示是否有现有的锁阻止调用进程设置它自己的锁。因为F_SETLKF_SETLKW命令总是替换调用进程现有的锁(若已存在),所以调用进程绝不会阻塞在自己持有的锁上。于是,F_GETLK命令绝不会报告调用进程自己持有的锁。
  • 实例:死锁。该例中子进程对第0字节加锁,父进程对第1字节加锁。然后,它们中的每一个又试图对对方已经加锁的字节加锁。 程序中介绍了8.9节中介绍的父进程和子进程同步例程。

    #include "apue.h"
    #include <fcntl.h>static void
    lockabyte(const char *name, int fd, off_t offset)
    {if (writew_lock(fd, offset, SEEK_SET, 1) < 0)err_sys("%s: writew_lock error", name);printf("%s: got the lock, byte %lld\n", name, (long long)offset);
    }int
    main(void)
    {int		fd;pid_t	pid;/** Create a file and write two bytes to it.*/if ((fd = creat("templock", FILE_MODE)) < 0)err_sys("creat error");if (write(fd, "ab", 2) != 2)err_sys("write error");TELL_WAIT(); /*set things up for TELL_xxx & WAIT_xxx */if ((pid = fork()) < 0) {err_sys("fork error");} else if (pid == 0) {	    /* child */lockabyte("child", fd, 0);TELL_PARENT(getppid()); /*tell parent we're done */WAIT_PARENT();          /*and wait for parent*/lockabyte("child", fd, 1);} else {				    /* parent */lockabyte("parent", fd, 1);TELL_CHILD(pid);       /*tell child we're done */WAIT_CHILD();          /*and wait for parent*/ lockabyte("parent", fd, 0);}exit(0);
    }
    

    运行该实例可以得到

    lh@LH_LINUX:~/桌面/apue.3e/advio$ ./deadlock 
    parent: got the lock, byte 1
    child: got the lock, byte 0
    parent: writew_lock error: Resource deadlock avoided
    child: got the lock, byte 1
    
    • 检测到死锁时,内核必须选择一个进程接收出错返回。在本实例中,选择了父进程。选择父进程还是子进程出错返回随操作系统而定。

3.3、锁的隐含继承和释放

  • 关于记录锁的自动继承和释放有3条规则。
    • 锁与进程和文件两者相关联:当一个进程终止时,它所建立的锁全部释放;无论一个描述符何时关闭,该进程通过这一描述符引用的文件上的任何一把锁都会释放(这些锁都是该进程设置的)。
      • 例如,在close(fd)后,在fd1设置的锁被释放。dup函数的使用方法见3.12
        fd1 = open(pathname, ...);
        read_lock(fd1, ...);
        fd2 = dup(fd1);
        close(fd2);
        
      • 如果dup替换成open,其效果也一样:
        fd1 = open(pathname, ...);
        read_lock(fd1, ...);
        fd2 = open(fd1);
        close(fd2);
        
    • fork产生的子进程不继承父进程所设置的锁。因为对于父进程获得的锁而言,子进程被视为另一个进程。
    • 执行exec后,新程序可以继承原执行程序的锁。但是如果该文件描述符设置了close-on-exec标志,则exec之后释放相应文件的锁。

3.4、FreeBSD实现

  • 考虑一个进程,他执行下列语句(忽略出错返回)
    fd1 = open(pathname,...);
    write_lock(fd1,0,SEEK_SET,1); // 该函数是自定义的,父进程在字节0上设置写锁
    if((pid = fork()) > 0) { // 父进程fd2 = dup(f1);fd3 = open(pathname,...);
    } else if(pid == 0) { // 子进程read_lock(fd1,1,SEEK_SET,1); //该函数是自定义的,子进程在字节1上设置读锁
    }
    pause();
    
    在父进程和子进程暂停(执行pause())之后,数据结构的情况如下
    在这里插入图片描述
    • 可以看出来,文件记录锁信息是保存在文件v节点/inode节点上的(而不是在文件表项中的),其实现是通过一个链表记录该文件上的各个锁,因此能保证多个进程正确操作文件记录锁。在图中显示了两个lockf结构,一个是由父进程调用write_lock形成的,另一个则是子进程调用read_lock形成的。每一个结构都包含了相应的进程ID
    • 在父进程中,关闭fd1fd2fd3中的任意一个都将释放由父进程设置的写锁。内核会从该描述符锁关联的inode节点开始,逐个检查lockf链表中的各项,并释放由调用进程持有的各把锁。
  • 实例:守护进程可用一把文件锁来保证只有该守护进程的唯一副本在运行,其lockfile函数实现如下:守护进程可用该函数在文件整体上加独占写锁
    int lockfile(int fd) {struct flock fl;fl.l_type = F_WRLCK;fl.l_start = 0;fl.l_whence = SEEK_SET;fl.l_len = 0;return fcntl(fd,F_SETLK,&fl);
    }
    
    另一种方法是write_lock函数定义lockfile函数:
    #define lockfile(fd) write_lock((fd),0,SEEK_SET,0)
    

3.5、在文件的尾端加锁

  • 在对相对于文件尾端的字节范围加锁解锁必须特别小心。如下面代码:
    write_lock(fd,0,SEEK_END,0);
    write(fd,buf,1);
    un_lock(fd,0,SEEK_END);
    write(fd,buf,1);
    
    • 刚开始获得一把写锁,该锁从当前文件尾开始,包括以后可能追加写到该文件的任何数据。当文件偏移量处于文件尾时,write一个字节将文件延伸了一个字节,因此该字节被加写锁。
    • 但是其后的解锁是对当前文件尾开始包括以后可能追加写到该文件的任何数据进行解锁,因此刚才追加写入的一个字节保留加锁状态。之后又写入了一个字节,由此代码造成的文件锁状态如图。
      在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/82739.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

超导材料LK-99烧制工艺中高真空度及其气氛环境控制的解决方案

摘要&#xff1a;根据近期LK-99超导材料研究报道&#xff0c;我们分析此材料制备采用了真空烧结工艺。由于目前大部分复现研究所用的真空烧结技术和设备都非常简陋&#xff0c;使得LK-99的复现性很差。为此我们提出了真空度准确控制解决方案&#xff0c;其目的第一是实现烧结初…

网工内推 | 信息安全负责人,需8年安全经验,CISSP证书

01 上海鹰角网络 招聘岗位&#xff1a;信息安全负责人 职责描述&#xff1a; 1、负责公司总体的信息安全规划、信息安全管理体系、流程、制度的设计和优化&#xff0c;确保在运营、应用、信息和业务等方面的持续安全、稳定&#xff1b; 2、负责对系统&#xff0c;网络&#xf…

ad+硬件每日学习十个知识点(22)23.8.2(LDO datasheet手册解读)

文章目录 1.LDO的概述、features2.LDO的绝对参数&#xff08;功率升温和结温&#xff09;3.LDO的引脚功能4.LDO的电气特性5.LDO的典型电路&#xff08;电容不能真用1uF&#xff0c;虽然按比例取输出值&#xff0c;但是R2的取值要考虑释放电流&#xff09;6.LDO的开关速度和线性…

PoseFormer:基于视频的2D-to-3D单人姿态估计

3D Human Pose Estimation with Spatial and Temporal Transformers论文解析 摘要1. 简介2. Related Works2.1 2D-to-3D Lifting HPE2.2 GNNs in 3D HPE2.3 Vision Transformers 3. Method3.1 Temporal Transformer Baseline3.2 PoseFormer: Spatial-Temporal TransformerSpati…

人文景区有必要做VR云游吗?如何满足游客出行需求?

VR云游在旅游行业中的应用正在快速增长&#xff0c;为游客带来沉浸式体验的同时&#xff0c;也为文旅景区提供了新的营销方式。很多人说VR全景展示是虚假的&#xff0c;比不上真实的景区触感&#xff0c;人文景区真的有必要做VR云游吗&#xff1f;我的答案是很有必要。 如果你认…

【Windows】Windows开机密码重置

文章目录 前言一、问题描述二、操作步骤2.1 安装DaBaiCai_d14_v6.0_2207_Online.exe2.2 插入U盘2.3 打开大白菜&#xff0c;点击“一键制作USB启动盘”2.4 等待进度条走完2.5 重启电脑&#xff0c;开机按“F12”或者“F8”&#xff08;具体百度一下&#xff0c;对应品牌电脑开机…

MyCat配置文件schema.xml讲解

1.MyCat配置 1.1 schema标签 如果checkSQLschema配置的为false&#xff0c;那么执行DB01.TB_ORDER时就会报错&#xff0c;必须用use切换逻辑库以后才能进行查询。 sqlMaxLimit如果未指定limit进行查询&#xff0c;列表查询模式默认为100,最多只查询100条。因为用mycat后默认数…

JVM详情

JVM详情 一、JVM内存划分二、双亲委派模型&#xff08;重点考察&#xff09;三、 GC&#xff08;垃圾回收机制&#xff09;垃圾的判定算法垃圾回收算法 一、JVM内存划分 堆&#xff1a;存放new出来的对象&#xff1b;&#xff08;成员变量&#xff09; 方法区&#xff1a;存放…

LeetCode_01 精度丢失

1281. 整数的各位积和之差 给你一个整数 n&#xff0c;请你帮忙计算并返回该整数「各位数字之积」与「各位数字之和」的差。 示例 输入&#xff1a;n 234 输出&#xff1a;15 解释&#xff1a; 各位数之积 2 * 3 * 4 24 各位数之和 2 3 4 9 结果 24 - 9 15示例 …

C++20协程

目录 协程原理&#xff1a; 进程、线程和协程的区别和联系​编辑 协程在IO多路复用中 协程的目的&#xff1a; 协程的优势&#xff1a; ​ 协程原理&#xff1a; &#xff08;学习来源&#xff1a;幼麟实验室&#xff09; 线程是进程中的执行体&#xff0c;拥有一个…

RISCV 5 RISC-V调用规则

RISCV 5 RISC-V调用规则 1 Register Convention1.1 Integer Register Convention1.2 Floating-point Register Convention 2. Procedure Calling Convention2.1 Integer Calling Convention2.2 Hardware Floating-point Calling Convention2.3 ILP32E Calling Convention2.4 Na…

现代C++中的从头开始深度学习【1/8】:基础知识

一、说明 提及机器学习框架与研究和工业的相关性。现在很少有项目不使用Google TensorFlow或Meta PyTorch&#xff0c;在于它们的可扩展性和灵活性。也就是说&#xff0c;花时间从头开始编码机器学习算法似乎违反直觉&#xff0c;即没有任何基本框架。然而&#xff0c;事实并非…

正则表达式的使用

1、正则表达式-教程 正则表达式&#xff1a;文本模式&#xff0c;包括普通字符&#xff08;例如&#xff0c;a到z之间的字母&#xff09;和特殊字符&#xff08;称为元字符&#xff09;。 正则表达式使用单个字符串来描述&#xff0c;匹配一系列匹配某个句法规则的字符串。 2、…

A2C原理和代码实现

参考王树森《深度强化学习》课程和书籍 1、A2C原理&#xff1a; Observe a transition&#xff1a; ( s t , a t , r t , s t 1 ) (s_t,{a_t},r_t,s_{t1}) (st​,at​,rt​,st1​) TD target: y t r t γ ⋅ v ( s t 1 ; w ) . y_{t} r_{t}\gamma\cdot v(s_{t1};\mathbf…

如何在Spring MVC中使用@ControllerAdvice创建全局异常处理器

文章目录 前言一、认识注解&#xff1a;RestControllerAdvice和ExceptionHandler二、使用步骤1、封装统一返回结果类2、自定义异常类封装3、定义全局异常处理类4、测试 总结 前言 全局异常处理器是一种 &#x1f31f;✨机制&#xff0c;用于处理应用程序中发生的异常&#xff…

ROS入门核心教材重要节选

ROS核心教程 1、文件系统 使用下述命令查看包 rospack ros pack(age&#xff09; 如rospack find roscpp roscd ros cd 如roscd roscpp rosls ros ls 如rosls roscpp2、ROS节点 节点可以理解为人工定义一个机器人模块&#xff0c;然后抽象成可执行文件。 rosnode li…

TCP的四次挥手与TCP状态转换

文章目录 四次挥手场景步骤TCP状态转换 四次挥手场景 TCP客户端与服务器断开连接的时候&#xff0c;在程序中使用close()函数&#xff0c;会使用TCP协议四次挥手。 客户端和服务端都可以主动发起。 因TCP连接时候是双向的&#xff0c;所以断开的时候也是双向的。 步骤 三次…

LabVIEW开发3D颈动脉图像边缘检测

LabVIEW开发3D颈动脉图像边缘检测 近年来&#xff0c;超声图像在医学领域对疾病诊断具有重要意义。边缘检测是图像处理技术的重要组成部分。边缘包含图像信息。边缘检测的主要目的是根据强度和纹理等属性识别图像中均匀区域的边界。超声&#xff08;US&#xff09;图像存在视觉…

vue项目实战-脑图编辑管理系统kitymind百度脑图

前言 项目为前端vue项目&#xff0c;把kitymind百度脑图整合到前端vue项目中&#xff0c;显示了脑图的绘制&#xff0c;编辑&#xff0c;到处为json&#xff0c;png&#xff0c;text等格式的功能 文章末尾有相关的代码链接&#xff0c;代码只包含前端项目&#xff0c;在原始的…

微服务与Nacos概述

微服务概述 软件架构的演变&#xff1a;单体架构、垂直应用架构、流式计算架构 SOA、微服务架构和服务网格。 微服务是一种软件开发架构&#xff0c;它将一个大型应用程序拆分为一系列小型、独立的服务。每个服务都可以独立开发、部署和扩展&#xff0c;并通过轻量级的通信机…