14.高级IO

1.非阻塞IO

  • 设置方法
    • 1.open()时指定O_NONBLOCK标志
    • 2.已经打开的可用fcntl()打开O_NONBLOCK标志

    说明:POSIX标准规定无数据可读时read()返回-1,,errno=EAGAIN,文件结束返回0


2.记录锁(字节范围锁)

商用UNIX系统提供了记录锁机制(使用数据库的前提),POSIX标准的基础是fcntl()方法,linux3.2.0支持fcntl()、lockf()、flock()

  • fcntl()记录锁
    int fcntl(int fd, int cmd, ... /* arg */ );
    
    • cmd是F_GETLK、F_SETLK、F_SETLKW
    • 对三个参数是指向flock的指针
      struct flock {
          ...
          short l_type;    /* Type of lock: F_RDLCK,F_WRLCK, F_UNLCK */
          short l_whence;  /* How to interpret l_start: SEEK_SET, SEEK_CUR, SEEK_END */
          off_t l_start;   /* Starting offset for lock */
          off_t l_len;     /* Number of bytes to lock */
          pid_t l_pid;     /* PID of process blocking our lock (F_GETLK only) */
          ...
      };
      
      • l_len为0表示锁的范围可扩大最大可能偏移量
      • 加读锁时描述符必须是读打开,加写锁时必须是写打开
      • 同一进程不适用兼容性规则(会直接覆盖),故不能用F_GETLK来测试自己是否在文件的某一部分持有一把锁
      • 在设置或释放文件上的一把锁时,系统按要求组合或分裂相邻区
    • 死锁
      • 两个进程相互等待且不释放
      • 一个进程已经控制了文件中的一个加锁区域,又试图对另一个进程控制的区域加锁
  • 锁的隐含继承和释放
    • 锁与进程和文件两者相关联:
      • 进程终止时,建立的锁全部释放
      • 描述符关闭时,通过该描述符引用的文件上的任何一把锁都会释放
    • fork产生的子进程不会继承父进程的锁
    • exec()后,新进程基础原执行程序的锁(未设置O_CLOEXEC)
  • FreeBSD实现
    • 主要就是struct lockf结构是作为v节点的一个表项(v又是i_node的表项),这样就可以解释自动继承和释放的规则,并可以看出文件锁是非强制性锁
    • 前面提到的可以用文件锁保证守护进程只有一个实例在运行
    #define lockfile(fd) write_lock((fd),0,SEEK_SET,0)
    
  • 在文件尾端加锁
    • 需要注意的是因为还没有获得锁,所以不能用fstat来得到当前文件的长度
      • 内核会将相对偏移量(SEEK_CUR或SEEK_END)转换为绝对偏移量
  • 建议性锁和强制性锁
    • 使用一致函数访问数据库的进程集叫合作进程(建议性锁是可行的),但不能阻止对数据库文件有写权限的其他非合作进程。
      • 在linux上想使用,需要在各个文件系统的基础上使用mount命令的-o mand选项来打开
    • SVR3:对一个特定的文件打开其设置组ID位、关闭其组执行位便开启了对该文件的强制性锁
    • open()一个具有强制性记录锁的文件时指定了O_TRUNC或O_CREAT,会失败,errno=EAGAIN
    • 具有强制性记录锁的文件可以被删除(所有锁对某些编辑器没用)

3.I/O多路转接

3.1 函数select和pselect
/* According to POSIX.1-2001 */
#include <sys/select.h>

/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,
            fd_set *exceptfds, struct timeval *timeout);

void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

#include <sys/select.h>

int pselect(int nfds, fd_set *readfds, fd_set *writefds,
            fd_set *exceptfds, const struct timespec *timeout,
            const sigset_t *sigmask);
  • timeout不为空且超时事件未到时linux3.2.0将用剩余时间更新该值
  • 说明:一个描述符阻塞与否并不影响select是否阻塞
  • 除一下几点外,pselect与select相同
    • pselect使用timespec结构(秒和纳秒),时间更精确
    • timeout为const
    • 调用时以原子的方式安装信号屏蔽字,返回时恢复
3.2 函数select和pselect
#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

#define _GNU_SOURCE         /* See feature_test_macros(7) */
#include <poll.h>

int ppoll(struct pollfd *fds, nfds_t nfds,
        const struct timespec *timeout_ts, const sigset_t *sigmask);
struct pollfd {
    int   fd;         /* file descriptor */
    short events;     /* requested events */
    short revents;    /* returned events */
};
3.3 异步I/O
  • POSIX异步IO有下面这些局限
    • 三个可能产生错误的地方:
      • 操作提交部分
      • 操作本身的结果
      • 决定异步操作的状态函数中
  • System V异步I/O
    • 是streams的一部分(只对streams设备和管道起作用),信号是SIGPOLL
    • 启动需要调用ioctl,第二个参数为I_SETSIG,第三个参数见pdf421页或stropts.h
    • streams在SUSv4被废弃了,这里就不讨论了
  • BSD异步I/O
    • 我不写IOS系统程序这里就不讨论了
  • POSIX异步
    • 对不同类型的文件提供了一套一致的方法,现在所有的平台都被要求支持
    • 使用AIO控制块
      /* Asynchronous I/O control block.  */
      struct aiocb {
          int aio_fildes;       /* File desriptor.  */
          int aio_lio_opcode;       /* Operation to be performed.  */
          int aio_reqprio;      /* Request priority offset.  */
          volatile void *aio_buf;   /* Location of buffer.  */
          size_t aio_nbytes;        /* Length of transfer.  */
          struct sigevent aio_sigevent; /* Signal number and value.  */
          /* Internal members.  */
          struct aiocb *__next_prio;};
      
      • 异步IO必须显示指定偏移量(不会影响系统指定的偏移量),但是追加模式写数据偏移量会被忽略
      • 系统并不一定遵循aio_reqprio字段请求的提示顺序
      • aio_sigevent设置如何通知应用程序
        struct sigevent {
        
        int          sigev_notify; /* Notification method */
        int          sigev_signo;  /* Notification signal */
        union sigval sigev_value;  /* Data passed with notification */
        void       (*sigev_notify_function) (union sigval);
        /* Function used for thread notification (SIGEV_THREAD) */
        void        *sigev_notify_attributes;
        /* Attributes for notification thread (SIGEV_THREAD) */
        pid_t        sigev_notify_thread_id;
        /* ID of thread to signal (SIGEV_THREAD_ID) */
        };
        
  • 工作中用不到我就先不看啦,看了也记不住

4.函数readv()和writev()

也叫散步读和聚集写

#include <sys/uio.h>
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
ssize_t preadv(int fd, const struct iovec *iov, int iovcnt,
                off_t offset);
ssize_t pwritev(int fd, const struct iovec *iov, int iovcnt,
                off_t offset);
struct iovec {
    void  *iov_base;    /* Starting address */
    size_t iov_len;     /* Number of bytes to transfer */
};
  • 对于431页的性能测试,虽然结果上缓冲区复制后接一次write时间少于一次writev(),那是因为对于这种少量数据,使用writev的固定成本大于收益。
    • 总之,还是用尽量少的系统调用来完成任务

8.存储映射I/O

将一个磁盘文件映射到存储空间的一个缓冲区上

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
        int fd, off_t offset);
int munmap(void *addr, size_t length);
  • prot参数可以下面参数的按位与,不能超过open()时的权限:
    • PROT_READ
    • PROT_WRITE
    • PROT_EXEC
    • PROT_NONE
  • addr为0可获得最大的可移植性,
  • 基于上面的特性flag最好不要用MAP_FIXED
    • MAP_SHARED:
    • MAP_PRIVATE:创建文件的一个私有副本
    • off和addr通常是0
  • 这些在网络编程卷2有更详细的讨论,这里就不再赘述了
  • 需要注意的是:不能用mmap()将数据添加到文件中,必须先加长该文件,fork可以继承存储映射区但exec不能
  • 更改映射权限
    int mprotect(const void *addr, size_t len, int prot);
    
    • addr必须是页长的整数倍?page435
  • 即使指定了MAP_SHARED,脏页面何时写回由内核守护进程决定
    int msync(void *addr, size_t length, int flags);
    
    • flags
      • MS_SYNC:同步方式
      • MS_ASYNC:异步方式,返回后不一定同步了
  • 关于性能,在page437页linux 3.2.0对比中,可以看到使用read/write 总时长比mmap/memcpy要快将近4倍,但是CPU时间差不多(也可能CPU计算方式与我们理解的有误差),而在Solaris 10中第二种方式更快
©️2020 CSDN 皮肤主题: 黑客帝国 设计师:上身试试 返回首页