UNIX高级环境编程 第14章

第14章 高级IO

14.1 引言

本章的概念和函数
1. 非阻塞I/O
2. 记录锁
3. I/O多路转接(select & poll)
4. 异步I/O
5. readv & writev函数
6. 存储映射I/O: mmap数组

14.2 非阻塞I/O

低速系统调用可能会使进程永久阻塞,包括:
--1. 若是数据并不存在,则读文件可能会使调用者永远阻塞·--例如读管道、终端设备和网络设备
--2. 若是数据不能当即被接受,则写这些一样的文件也会使调用者永远阻塞
--3. 在某些条件发生以前,打开文件会被阻塞·--例如以只写方式打开一个FIFO,
-----那么在没有其余进程已用读方式打开该FIFO时;
--4. 对已经加上强制性锁的文件进行读、写;
--5. 某些ioctl操做;
--6. 某些进程间通讯函数;网络

非阻塞IO调用open、read和write等IO操做使上述的慢速系统调用在不能当即完成的状况下,当即出错返回。异步

设置非阻塞IO方法:函数

open(pathname, O_NONBLOCK | O_... , S_...)
int status;
status = fcntl(fd, GETFL, 0);
status |= O_NONBLOCK;
fcntl(fd, SETFL, status);

14.3 记录锁

记录锁的功能是:
一个进程正在读或修改文件的某个部分时,能够阻止其余进程修改同一文件区域.测试

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

struct flock 结构以下:spa

struct flock{
    short l_type;
    short l_whence;
    off_t l_start;
    off_t l_len;
    pid_t l_pid;
};

结构说明操作系统

l_type:
F_RDLCK(共享读锁)、F_WRLCK(独占性写锁)或F_UNLCK(解锁一个区域)3d

加锁或解锁的区域:
l_whence : SEEK_SET, SEEK_CUR, SEEK_END
l_start: 相对于l_whence的起点
l_len: 区域的长度rest

注意
1. 该区域能够在当前文件尾端处开始或越过其尾端处开始,可是不能在文件起始位置以前开始或越过该起始位置。
2. 如若l_len为0,则表示锁的区域从其起点(由l_start和l_whence决定)开始直至最大可能位置为止。
---也就是无论添写到该文件中多少数据,它都处于锁的范围。
3. 为了锁整个文件,一般的方法是将l_start说明为0,l_whence说明为SEEK_SET,l_len说明为0code

fcntl cmd类型

F_GETLK:
决定由flockptr所描述的锁是否被另一把锁所排斥(阻塞)。
若是存在一把锁,它阻止建立由flockptr所描述的锁,则这把现存的锁的信息写到flockptr指向的结构中。
若是不存在这种状况,则除了将l_type设置为F_UNLCK以外,flockptr所指向结构中的其余信息保持不变。
这意味则利用F_GETLK不能获取由本进程维护的记录锁。

F_SETLK:
F_SETLK设置由flockptr所描述的锁。若是试图创建一把按上述兼容性规则并不容许的锁,则fcntl当即出错返回
此时errno设置为EACCES或EAGAIN。

F_SETLKW:
是F_SETLK的阻塞版本。若是因为存在其余锁,那么由flockptr所要求的锁不能被建立,则调用进程睡眠。若是捕捉到信号则睡眠中断。

注意
用F_GETLK测试可否创建一把锁,而后用F_SETLK和F_SETLKW企图创建一把锁,这二者不是一个原子操做。
在这两个操做之间可能会有另外一个进程插入并创建一把相关的锁,使原来测试到的状况发生变化.
若是不但愿在创建锁时可能产生的长期阻塞,则应使用F_SETLK,并对返回结果进行测试,以判别是否成功地创建了所要求的锁。

利用宏填充struct flock

int file_lock(int fd, int cmd, int type, off_t offset, int whence, off_t len)
{
    struct flock lock;
    lock.l_type = type;
    lock.l_whence = whence;
    lock.l_start = offset;
    lock.l_len =len;
    return (fcntl(fd,cmd,&lock));

}
#define file_read_lock(fd, offset, whence, len) \
           file_lock((fd),(F_SETLK),(F_RDLCK),(offset),(whence),(len))
#define file_readw_lock(fd, offset, whence, len) \
           file_lock((fd),(F_SETLKW),(F_RDLCK),(offset),(whence),(len))
#define file_write_lock(fd, offset, whence, len) \
           file_lock((fd),(F_SETLK),(F_WRLCK),(offset),(whence),(len))
#define file_writew_lock(fd, offset, whence, len) \
           file_lock((fd),(F_SETLKW),(F_WRLCK),(offset),(whence),(len))

锁的隐含继承和释放

1. 当一个进程终止时,它所创建的锁所有释放;任什么时候候关闭一个描述符时,则该进程经过这一描述符能够存访的文件上的任何一把锁都被释放
2. 由fork产生的子程序不继承父进程所设置的锁。
3. 在执行exec后,新程序能够继承原执行程序的锁。

14.4 IO多路转接

14.4.1 函数select


#include <sys/select.h>
int select(int maxfdp1, fd_set* readfds, fd_set* restrict writefds, 
                        fd_set* restrict exceptfds, struct timeval * restrict tvptr);

参数tvptr的含义
1. timeout == NULL,永远等待
2. timeout->tv_sec == 0 && timeout->tv_usec == 0,不等待
3. timeout->tv_sec != 0 || timeout->tv_usec != 0,等待指定时间

操做

void FD_CLR(fd, fd_set *fdset);
void FD_ISSET(fd, fd_set *fdset);
void FD_SET(fd, fd_set *fdset);

void FD_ZERO(fd_set *fdset); // 初始化

第一个参数maxfdp1的意思就是“最大文件描述符编号值+1”

int pselect(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, 
                      fd_set *restrict errorfds, const struct timespec *restrict timeout, 
                                                 const sigset_t *restrict sigmask);

pselect可以使用可选信号屏蔽字,若是sigmask为null,则二者同样,可是sigmask指向屏蔽字的时候,将以原子操做形式安装屏蔽字

14.4.2 函数poll

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

struct pollfd 结构体

struct pollfd {
    int    fd;       /* file descriptor */
    short  events;   /* events to look for */
    short  revents;  /* events returned */
};

events 和 revents 类型
clipboard.png

14.5 异步I/O

异步IO使用AIO控制块来描述IO操做:

struct aiocb {
        int             aio_fildes;             /* File descriptor */
        off_t           aio_offset;             /* File offset */
        volatile void   *aio_buf;               /* Location of buffer */
        size_t          aio_nbytes;             /* Length of transfer */
        int             aio_reqprio;            /* Request priority offset */
        struct sigevent aio_sigevent;           /* Signal number and value */
        int             aio_lio_opcode;         /* Operation for list IO */
};

aio_fildes就是文件描述符
读写操做从aio_offset指定的偏移量位置开始,长度为aio_nbytes
aio_reqprio就是异步IO请求的顺序,当不必定遵照
aio_sigevent就是IO事件完成后如何通知

typedef struct sigevent
  {
    sigval_t sigev_value;
    int sigev_signo;
    int sigev_notify;

    union
    {
        int _pad[__SIGEV_PAD_SIZE];

        /* When SIGEV_SIGNAL and SIGEV_THREAD_ID set, LWP ID of the
        thread to receive the signal.  */
        __pid_t _tid;

        struct
        {
           void (*_function) (sigval_t);    /* Function to start.  */
           pthread_attr_t *_attribute;        /* Thread attributes.  */
        } _sigev_thread;
    } _sigev_un;
  } sigevent_t;

# define sigev_notify_function   _sigev_un._sigev_thread._function
# define sigev_notify_attributes _sigev_un._sigev_thread._attribute

APUE课本结构

struct sigevent {
        int  sigev_notify;                              /* Notification type */
        int  sigev_signo;                               /* Signal number */
        union sigval    sigev_value;                    /* Signal value */
        void (*sigev_notify_function)(union sigval);    /* Notification function */
        pthread_attr_t  *sigev_notify_attributes;       /* Notification attributes */
}

sigev_notify字段是通知类型
1. SIGEV_NONE 不通知进程
2. SIGEV_SIGNAL 异步IO完成后,产生sigev_signo指定的信号
3. SIGEV_THREAD 异步请求完成后,由sigev_notify_function指定的函数被调用

注意:异步操做不影响有操做系统维护的文件偏移量
注意:若以追加方式打开文件时,aio_offset被忽略

异步读写操做:

int aio_read(struct aiocb *aiocbp);
int aio_write(struct aiocb *aiocbp);

当两个函数返回时,异步IO请求被放在了等待处理队列中。但返回值与实际IO操做的结果没有任何关系。
若是想要强制全部等待中的异步操做不等待直接写入存储,则调用aio_fsync函数 :

int aio_fsync(int op, struct aiocb *aiocb);

注意: aio_fsync的返回时,读写操做也可能并未完成

获取一个异步读写的完成状态,调用aio_error函数 :

int aio_error(const struct aiocb *aiocbp);

返回值 :
0,     异步操做成功,使用aio_return函数得到返回值
-1,    对aio_error操做失败
EINPROGRESS,  读写操做仍处于等待状态

异步操做完成后,调用aio_return :

ssize_t aio_return(struct aiocb *aiocbp);

注意:记住在aio_error检查成功以前,不要使用aio_return函数
注意:每一个异步操做只能调用一次aio_return函数。

aio_suspend函数会阻塞当前进程直到list中的 任意一个 操做完成 :

int aio_suspend(const struct aiocb *const list[], int nent, const struct timespec *timeout);

返回值 :
0,     操做成功,某个异步操做完成
-1,    信号中断,设置errno为EINTR;超时,设置errno为EAGAIN

取消已经处于进行中的异步操做:

int aio_cancel(int fildes, struct aiocb *aiocbp);

返回值:
AIO_ALLDONE,全部操做已经完成
AIO_CANCELED,全部操做已经取消
AIO_NOtCANCELED,至少有一个请求没有取消
-1,对aio_cancel调用失败

把多个异步操做 集中处理 :

int lio_listio(int mode, struct aiocb* restrict const list[restrict], 
                int nent, struct sigevent *restrict sigev);

struct aiocb中的成员aio_lio_opcode指定了操做类型

14.6 函数readv和writev

两个函数用于在一次函数调用中读写多个非连续缓冲区

ssize_t readv(int d, const struct iovec *iov, int iovcnt); // iovcnt 为数组长度

ssize_t writev(int fildes, const struct iovec *iov, int iovcnt);

struct iovec结构体 :

struct iovec {
    char   *iov_base;  /* Base address. */
    size_t iov_len;    /* Length. */
};

clipboard.png

14.7 函数readn和writen

ssize_t readn(int fd, void* buf, size_t nbytes);
ssize_t writen(int fd, void* buf, size_t nbytes);

两个函数按需屡次调用read和write直至读或写N字节数据

14.8 存储映射I/O

存储映射IO能将一个磁盘文件映射到存储空间中的一个缓冲区上,因而,当从缓冲区中读取数据的时候,就等同于读取文件。

void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
// 建立匿名存储映射,对存储区的读写不涉及磁盘的读写,可是只能在相关进程之间进行通讯
mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONUMOUS, 0, 0);

内存地址空间: [addr, addr + len)
addr 默认指定为0,有系统指定内存区域

clipboard.png

描述符为fd的文件被映射区域: [offset, offset + len)

参数prot:映射区域的读写执行特性

clipboard.png

参数flag :
MAP_FIXED
返回值必须等于addr。由于这不利于可移植性,因此不鼓励使用此标志。
若是未指定此标志,并且addr非0,则内核只把addr视为什么处设置映射区的一种建议。
经过将addr指定为0可得到最大可移植性。

MAP_SHARED
这一标志说明了本进程对映射区所进行的存储操做的配置。
此标志指定存储操做修改映射文件—也就是,存储操做至关于对该文件write。
必须指定本标志或下一个标志(MAP_PRIVATE)。

MAP_PRIVATE
本标志说明,对映射区的存储操做致使建立该映射文件的一个副本。
全部后来对该映射区的存访都是存访该副本,而不是原始文件。

注意:不能将数据添加(不是修改)到文件中。必须先加长文件。

理解与映射区相关的信号有SIGSEGV和SIGBUS

更改现有映射的权限:

int mprotect(void *addr, size_t len, int prot); // addr页长的整数倍

当页已经修改完毕,能够调用msync函数冲洗到被映射的文件中。

int msync(void *addr, size_t len, int flags);

手动解除存储区的映射

int munmap(void *addr, size_t len);

该函数删除了指定地址的映射,若是继续对其进行读写会致使无效内存引用。而且这个函数不会冲洗缓冲区内容到文件。

相关文章
相关标签/搜索