zoukankan      html  css  js  c++  java
  • Apue.2e Chapter14 Advanced I/O

    本章主要讲解高级I/O,是以后各章学习的基础。

    非阻塞I/O:指定文件打开方式时,oflag中添加O_NONBLOCK标志,或者对已经打开的文件描述符使用fcntl更换文件的打开标志。

    记录锁

    record locking的作用是:可以锁定文件中的一部分,阻止其他进程修改同一文件区。

    使用fcntl函数完成该功能,

    int fcntl(int filedes, int cmd, …/*struct flock *flockptr here*/);

    cmd可以是F_GETLK(填充flockptr指向的区域,如果无锁则设置l_type为F_UNLCK), F_SETLK(设置/清除锁,如果不能,返回-1,设置errno), F_SETFKW(w表示wait,阻塞版本)

    struct flock{

        short l_type; //F_RDCLK, F_WRLCK, F_UNLCK

        off_t l_start;//offset in bytes, relative to l_whence

        short l_whence;// SEEK_SET, SEEK_CUR, SEEK_END

        off_t l_len;    //length, in bytes; 0 means lock to EOF

        pid_t l_pid;    //returned with F_GETLK

    };

    读锁可以共享,写锁不行。

    对同一进程而言,在同一文件的同一区域只能同时持有一把锁,新加的锁会替换掉以前的锁。

    这里提出一种特殊情况:如果同一文件区域被很多读线程等待,那么等待的写线程可能会饿死…

    系统会自动断开或合并相邻的加锁区。

    锁的规则

    1. 进程终止时,其建立的锁会被全被释放;
    2. 任何时候关闭一个描述符,通过该描述符可以引用的文件的任何一把锁都被释放;
    3. fork产生的子进程不继承父进程所设置的锁。
    4. 在执行exec后,新程序可以继承原执行程序的锁,但如果文件描述符打开时设置了close-on-exec标志,则由于规则2,所有锁都会被释放。

    在文件尾端加锁:注意内核会将相对偏移量换算成绝对偏移量。

    建议性锁和强制性锁:前者不能阻止其他有写权限的进程对文件进行随意的写操作,后者则强迫内核对每次r/w系统调用都进行检查,可以通过setgid并关闭S_IXGRP的方法打开强制性锁(for linux)。强制性锁也有一些bug,甚至可以被设法避开(这块可能outdate了)

    由于大部分编辑器并不使用强制性记录锁,甚至大部分会在读取文件后关闭文件,所以事实上想要阻止多进程/用户编辑同一文件基本是不可能的。【现在应该可以通过版本控制系统解决类似的问题】。

    STREAMS

    这块比较难理解。

    streams是在用户进程和设备驱动之间构建了一条全双工通路,stream head是系统调用api,其下可以压入各个处理模块,从driver到stream head称为逆流,反之称为顺流,streams作为内核一部分被执行,可以使用文件访问函数访问streams,所有的STREAMS设备都是字符设备文件。

    streams的I/O都是基于消息机制的,

    struct strbuf{

        int maxlen;    //size of buffer

        int len;        //number of bytes currently in buffer

        char *buf;    //pointer to buffer

    };

    控制信息和数据都是存放在上述数据结构中的,len=-1,说明没有信息,len=0是允许的。

    对于用户层程序员而言,只涉及到三种消息类型,即:

    M_DATA(I/O USER DATA), M_PROTO(protocol control info),M_PCPROTO(high priority protocol info)

    #include <stropts.h>

    int putmsg(int filedes,const struct strbuf *ctlptr, const struct strbuf *dataptr,int flag);

    int putpmsg(int filedes, const struct strbuf *ctlptr, const struct strbuf *dataptr, int band, int flag);    //p指的是priority,可以指定优先级波段

    优先级分为三种:

    高,(最高,只能有一个)

    优先级波段指定(1~255);

    普通(0)

    流系统的处理函数主要使用ioctl,可执行40+中不同的操作,可以man 7 streamio查看,这里列举一些例子:

    #include <stropts.h>

    int isastream(int filedes);    //成功返回1,失败返回0

    测试环境(debian6 stable)下不支持streams相关操作…虽然可以编译通过(因为头文件中有函数原型),但是没有正常的实现。

    I/O多路转接

    如果需要同时从多个文件读取,可以使用的方法很多,这里给出一种专门用于解决此类问题的技术,及 I/O multiplexing。

    其机制类似于表驱动,函数查询等待一个表中感兴趣的I/O单元类,当该类单元中有一个已经准备好I/O时,函数返回并告之等待进程那些已经准备ok,可以进行I/O动作。

    #include <sys/select.h>

    int select(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds,

        struct timeval *restrict tvptr);        //tvptr表示超时时间;fd_set表示描述符集合,说明了处于可读、可写和异常状态的描述集指针;maxfdp1是最大描述符+1(后面三个集合中的)

    操作描述符集的函数(或宏):

    #include <sys/select.h>

    int FD_ISSET(int fd, fd_set *fdset);    //测试某指定位是否设置

    void FD_CLR(int fd, fd_set *fdset);    //清零某个位

    void FD_SET(int fd,fd_set *fdset);    //将某个位置位

    void FD_ZERO(fd_set *fdset);        //清零所有位

    如果3个描述符集参数都是NULL,原函数相当于一个定时器。

    select返回:-1,出错,不修改任何描述符集;

    返回0:没有描述符准备好,所有描述符集被清0;

    正返回值:已经准备好的描述符数目;后面三个描述符集中的描述符数目之和;

    类似的有pselect函数,不同的是可以设置信号屏蔽字;

    poll:

    #include <poll.h>

    int poll(struct pollfd fdarray[],nfds_t nfds, int timeout);

    poll不是以状态为分类构造描述符集合,而是构造一个pollfd结构数组,每个数组元素指定一个描述符编号以及对其所关心的状态;

    struct pollfd{

        int fd;

        short events;    //events of interest on fd

        short revents;    //events that occurred on fd

    };

    ntfds说明前面数组的元素数;timeout指定超时时间;

    events里面有POLLUP标志,标示已挂断,挂断后不能写入新数据,但可以读取以前的数据。

    挂断和文件结束是不同的,后者并不会触发任何错误标示,只是read返回0而已。

    select是否是可再启动的系统调用,取决于系统。

    异步I/O

    由于信号时以进程为单位的,因此如果要同时对几个描述符进行异步I/O,在接收到信号时并不知道该信号对应哪个描述符。

    在系统V中,异步I/O是通过STREAMS机制实现的,其信号是SIGPOLL;

    BSD系统,异步I/O是SIGIO和SIGURG两个信号的组合,前者是通用的,后者仅表示进程在网络连接上到达了带外的数据;实现方法是使用fcntl指定处理进程,并在进程中设置好信号处理函数,然后还需要设置文件状态标志为O_ASYNC;

    readv和writev用于在一次函数调用中读、写多个非连续缓冲区,返回读入/输出的字节数。

    对于管道、FIFO以及终端、网络等设备,一次read|write操作可能少于指定的字节数,因此可能需要多次I/O才能保证数据被完全处理。

    文中实现了readn和writen函数用来自动完成这一工作(就是一个循环)。

    存储映射I/O

    使文件与存储空间中的一个缓冲区相映射,这样读缓冲区就是读文件,写缓冲区就是写文件;

    #include <sys/mman.h>

    void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off);

    addr:指定存储区起始地址,一般设置为0,由系统自己选择;

    len:文件长度

    prot:对映射存储区的读写保护要求(不能高于文件描述符的打开权限);

    flag:一些属性,重要的有MAP_SHARED和MAP_PRIVATE,这两个标志是互斥的,前者表示修改缓冲区会被实际写入文件,后者表示操作的其实是副本,一切修改都会被放弃;

    off:起始偏移量,一般也设置为0;如果不是0,必须设置成为_SC_PAGESIZE的整数倍;

    相关信号:SIGSEGV和SIGBUS,前者一般指示进程试图访问对他不可用的存储区;如果访问映射区的某个部分,而访问时这个部分实际上不存在,则产生SIGBUS信号;

    fork后,子进程继承存储映射区。

    调用mprotect可以更改一个现存存储区的权限;

    可以使用msync重新修改的脏页面到文件中;

    使用munmap撤销映射。

    注意关闭文件描述符并不能撤销掉映射;

    使用内存映射文件操作文件的目的是节省时间。

  • 相关阅读:
    Python深入05 装饰器
    Python深入04 闭包
    Python深入03 对象的属性
    Ubuntu (虚拟机同样) 更换内核?
    .out
    GCC 编译详解
    linux 编译内核 /boot空间不足?
    Java Swing提供的文件选择对话框
    Java Swing 实时刷新JTextArea,以显示不断append的内容?
    为什么要编译Linux内核?
  • 原文地址:https://www.cnblogs.com/livewithnorest/p/2912777.html
Copyright © 2011-2022 走看看