14(高级IO)

高级IO包括非阻塞IO、记录锁、系统V流机制、IO多路转接(select和poll)、readv和writev以及存储映射IO(nmap)

1 非阻塞IO

非阻塞IO使我们可以调用open,read和write这样的操作,并使这些操作不会永远阻塞。如果这些操作不能完成,则立即出错返回。
对于一个指定的描述符有两种方法对其指定非阻塞IO
(1)如果调用open获得描述符,则可指定O_NONBLOCK
(2)对于一个一打开的文件描述符,则可调用fcntl,由该函数打开O_NONBLOCK文件状态标识

2 记录锁

记录锁的功能是:一个进程正在读或修改文件的某个部分时,可以阻止其他进程修改同一文件区,即其锁定的是文件的一个区域或整个文件。
记录锁有两种类型:共享读锁,独占写锁。基本规则是:多个进程在一个给定的字节上可以有一把共享的读锁,但在一个给定字节上的写锁只能有一个进程独用。即:如果在一个给定的字节上已经有一把读或多把读锁,则不能在该字节上再加写锁;如果在一个字节上已经有一把独占性的写锁,则不能再对它加任何读锁。
(1)fcntl记录锁

#include <fcntl.h>
int fcntl(int filedes, int cmd, ... /* struct flock *flockptr */ );
        Returns: depends on cmd if OK (see following), 1 on error

对于记录锁,cmd取值为F_GETLK, F_SETLK, 和F_SETLKW。
struct flock结构信息如下:

struct flock {
     short l_type;   /* F_RDLCK, F_WRLCK, or F_UNLCK */
     off_t l_start;  /* offset in bytes, relative to l_whence */
     short l_whence; /* SEEK_SET, SEEK_CUR, or SEEK_END */
     off_t l_len;    /* length, in bytes; 0 means lock to EOF */
     pid_t l_pid;    /* returned with F_GETLK */
};

F_SETLK: 获取或释放由arg指向的flock结构所描述的锁,若无法获取锁,则立刻返回一个EACCES或EAGAIN错误而不阻塞。
F_SETLKW : 与F_SETLK不同的是,如果无法获取的锁,将阻塞直到获取锁为止,W即wait
F_GETLK: 检查由arg指向的锁以确定是否有某个已存在的锁。若不存在锁在将arg指向的l_type成员设置为F_UNLCK,若存在锁,则返回arg所指向的flock结构信息。

3 流机制Streams

Streams的所有输入输出都基于消息。
struct strbuf结构如下:

struct strbuf
     int maxlen;  /* size of buffer */
     int len;     /* number of bytes currently in buffer */
     char *buf;   /* pointer to buffer */
};

putmsg 和putpmsg函数

#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);
        Both return: 0 if OK, 1 on error

putmsg 和putpmsg用于将Streams消息写至流中。两者的区别在于后者可以指定优先级

4 IO多路转接

从一个描述符读,写到另外一个描述符,可在下列循环中使用阻塞IO

while ((n = read(STDIN_FILENO, buf, BUFSIZ)) > 0)
     if (write(STDOUT_FILENO, buf, n) != n)
         err_sys("write error");

但如果是从两个描述符读,情况又如何呢?比较好的方法就是使用IO多路转接
先构造一张有关描述符的列表,然后调用一个函数,直到这些描述符中的一个已准备好进行IO时,该函数才返回。在返回时,它告诉进程哪些描述符已经准备好
(1)select
传向select的参数告诉内核

(1) 我们所关心的描述符
(2) 对于每个描述符我们所关心的状态(是否读或写一个给定的描述符?是否关心一个描述符的异常状态)
(3) 愿意等待多少时间

从select返回时内核告诉我们:

(1) 已准备好的描述符数量
(2) 对于读、写、异常这三个异常状态中的每一个,哪些描述符已准备好

select函数如下:

#include <sys/select.h>
int select(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict tvptr);
        Returns: count of ready descriptors, 0 on timeout, 1 on error

最后一个参数tvptr,指定愿意等待的时间。结构为

struct timeval {
     long tv_sec;     /* seconds */
     long tv_usec;    /* and microseconds */
};

有三种情况:
tvptr==NULL
永久等待。当所指定的描述符中的一个已经准备好或捕捉到一个信号则返回。如果捕捉到一个信号,则select返回-1,errno设置为EINTR。
tvptr->tv_sec==0 &&tvptr->tv_usec==0
完全不等待。这是得到多个描述符状态而不阻塞select函数的轮询方法。
tvptr->tv_sec!=0 || tvptr->tv_usec!=0
等待指定的秒数和微秒数。当指定的描述符之一已准备好,或当指定的时间值已经超时,或捕捉到信号时,函数返回。在linux下,若在该时间值尚未超过时select就返回,那么将用余留时间更新tvptr指向的结构。

中间三个参数readfds、writefds和exceptfds是指向描述符集的指针。这三个描述符集说明了我们关心的可读、可写或出于异常条件的各个描述符。每个描述符集存放在一个fd_set数据类型中。这种数据类型为每一可能的描述符保持一位,其实现可如下图所示:
这里写图片描述
对于fd_set的处理有4个函数:

#include <sys/select.h>
int FD_ISSET(int fd, fd_set *fdset);
        Returns: nonzero if fd is in set, 0 otherwise
void FD_CLR(int fd, fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
void FD_ZERO(fd_set *fdset);

select的第一个参数maxfdp1的意思是“最大描述符加1”。在三个描述符集中找出最大的描述符加1就是这个参数的值,这样做可以让select搜索的效率提高。
其实就是描述符总数,因为编号从0开始。
(2)pselect

#include <sys/select.h>
int pselect(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, const struct timespec *restrict tsptr, const sigset_t *restrict sigmask);
            Returns: count of ready descriptors, 0 on timeout, 1 on error

pselect它与select的区别在于:
①pselect使用timespec结构指定超时值。timespec结构以秒和纳秒表示时间,而非秒和微秒。
②pselect的超时值被声明为const,这保证了调用pselect不会改变timespec结构。
③pselect可使用一个可选择的信号屏蔽字。在调用pselect时,以原子操作的方式安装该信号屏蔽字,在返回时恢复以前的信号屏蔽字。
(3)poll

#include <poll.h>
int poll(struct pollfd fdarray[], nfds_t nfds, int timeout);
            Returns: count of ready descriptors, 0 on timeout, 1 on error

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

struct pollfd {
     int   fd;       /* file descriptor to check, or <0 to ignore */
     short events;   /* events of interest on fd */
     short revents;  /* events that occurred on fd */
};

fdarray中元素的个数由nfds说明。
timeout参数说明愿意等待的时间:
1)timeout==-1,永久等待;
2)timeout==0,不等待;
3)timeout>0,等待timeout毫秒。注意:poll的timeout是以毫秒计的。

5 readv和writev

Readv和writev用于在一次函数中读写多个非连续缓冲区。有时也将这两个函数称为散布读和聚集写

#include <sys/uio.h>
ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
        Both return: number of bytes read or written, 1 on error

这两个函数的第二个参数是指向iovec结构数组的指针

struct iovec {
     void   *iov_base;   /* starting address of buffer */
     size_t  iov_len;    /* size of buffer */
};

6 存储映射IO(nmap)

存储映射IO使一个磁盘文件与存储空间中的一个缓冲区相映射。于是,当从缓冲区中取数据相当于读文件中的相应字节。与此类似,将数据存入缓冲区,相应的字节就自动的写入文件。
为了使用这种功能,应首先告诉内核将一个给定的文件映射到一个存储区域中。这是由nmap实现的。

#include <sys/mman.h>
void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off );
            Returns: starting address of mapped region if OK, MAP_FAILED on error

Addr参数用于指定映射存储区的起始位置,通常为0。此函数的返回地址就是该映射区的起始地址
Filedes指定被映射文件的描述符。
Len是映射的字节数
Off是映射字节在文件中的起始偏移量
这里写图片描述
Flag参数影响映射存储区的多种属性

MAP_FIXED   返回值必须等于addr。因为这不利于可移植性,所以不提倡用。
MAP_SHARED  这一标志说明了本进程对映射区所进行的存储操作的配置。此标志指定存储操作修改映射文件,相当于对文件write操作。必须指定本操作或下一个(MAP_PRIVATE),但不能同时指定两者
MAP_PRIVATE 本标志说明,对映射区的存储操作导致创建了该映射文件的一个私有副本。所有后来对该映射区的引用都是引用副本而不是原始文件

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

#include <sys/mman.h>
int mprotect(void *addr, size_t len, int prot);
Returns: 0 if OK, 1 on error

Prot的许可值与mmap中的prot参数一样。地址参数addr的值必须是系统页长的整数倍

如果在共享存储映射区中的页已经被修改,那么我们可以调用msync将该页冲洗到被映射
的文件中

#include <sys/mman.h>
int msync(void *addr, size_t len, int flags);
Returns: 0 if OK, 1 on error

进程终止时,或者调用munmap之后,存储映射区就会被自动解除映射。关闭文件描述符
filedes不能解除映射区

#include <sys/mman.h>
int munmap(caddr_t addr, size_t len);
Returns: 0 if OK, 1 on error
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值