今天我将总结一下标准I/O的一些内容。非阻塞I/O:
对于这个概念,我们得先知道低速系统调用,它在被调用后可能会使进程永远阻塞的一类系统调用。而非阻塞I/O,就是说在操作这些I/O的时候不会永远阻塞。如果这种操作不能完成,那么将立即出错返回,表示该操作如果继续执行那么将阻塞。
那么对于一个描述符制定非阻塞I/O的操作有两种:
(1)调用open函数获得描述符,则制定O_NONBLOCK标志
(2) 调用fcntl函数可以修改已经打开的描述符,由函数开启O_NONBLOCK文件状态标志。
记录锁:
记录锁的作用是防止在同一时间对同一个文件(更准确的是字节范围)的修改,也就是说在一个时间段内,只能有一个进程可以修改指定字节范围的数据。
对于记录锁的操作一般用函数fcntl:
对于记录锁,cmd取F_GETLK, F_SETLK. F_SETLKW。使用方法查看man fcntl。#include<fcntl.h> int fcntl( int fd, int cmd, .../* struct flock * flockptr*/);
对于flock结构体关于这个结构体详细信息问男人。。。。struct flock { short l_type; off_t l_start; short l_whence; off_t l_len; pid_t l_pid; };
注意:
1.加写锁的时候,必须是在已O_WRONLY,打开,加读锁必须是O_RDONLY打开。
2. 当设置、释放锁的时候,系统会组合或者裂开相邻区域。如下图
对于锁的隐含继承和释放有两点:
1. 当进程终止的时候,它锁建立的锁全部释放。
2. 当多个文件描述符指向同一个文件的时候,那么只要close任意一个文件那么该文件上的所有记录锁全部都释放。有不理解吗?看下图
看到了吗?关于锁的记录是放在i节点的哦?所以上面的两点就不难理解了撒。struct lockf记录的进程ID,fork后子进程和父进程pid就不同,当然锁就不会继承了撒。而当我们close一个文件的时候,就会到i节点上将所有该pid的struct lockf全部清空。
对于记录锁的尾端加锁,需要注意的是宏SEEK_END,SEEK_CUR是不断变化的,所以呢,我们必须是使用独立于当前文件偏移量或文件尾端而记住锁。
最后一个知识点了,对于建议锁和强制锁的理解
建议锁就是说,我们所有程序在执行的时候,我们按照约定,都先去获得对应文件锁后,在进行操作,但是如果有程序不这样做,那么就没有其它的约束了。由此就出现了强制性锁,它指的是使内核对每一个open、read、write系统调用都要检查是不是和某个锁有冲突。但是对于强制锁的实现,各个系统并没有统一起来,甚至还有系统不支持。对于linux中,如果要使用强制锁,则用命令_omand选项打开。有一种实现是这样对一个特定文件打开其设置组ID位并且关闭组执行位,则表示开启了强制性锁机制。
I/O多路复用:
还记得我们在系统调用read,write系统调用的读取终端或者是读取网络文件的时候,我们称这为慢系统调用,因为read或者write的时候可能会造成进程的永久阻塞。
那么如果我们要同时读和写多个文件,比如说telnet命令,它既要从终端读入数据,又要从守护进程读数据,并且写到终端中去
对于这种情况呢?我们不能使telnet命令简单的调用read,因为终端和守护进程都是输入,不能因为任何一个read而阻塞整个telnet,因为不能确定那一个输入才是程序想要的。
要解决这种有多种方式:
(1). 直接新建两个进程如下图:
虽然这种方法也可以实现该功能,但是它也有缺点,首先创建两个进程系统花销大,而且如果telnet结束,当子进程退出,会给父进程发送SIGHUP信号,那么可以实现两个进程都推出,但是呢,如果父进程先推出,就不会由系统产生信号通知子进程,这时就只能是由程序调度kill函数发送信号,或者通过IPC进行通信,这样就会大大加大对这个程序的复杂性。
(2)还可以通过信号机制实现如信号SIGIO。这里不详解了。
(3)就是马上要说到的I/O多路复用
对于I/O多路复用不得不说的三个函数select/pselect/poll。
关于这个函数的参数:#include <sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
nfds:是要查询文件描述符的最大值+1,这是为了减少内核查询次数,提高效率
readfds,writefds,exceptfds:分别表示的指向描述的指针,他们代表的是当前所关心的读写异常的描述符
timeval:表示的是超时时间。如果在timeval中没有条件满足,那么该函数不会在阻塞,会返回0,否则返回准备好的数目(详情man select)
这个函数作用,就是当我们在函数中fd_set设置的我们关心的文件描述符的情况满足,比如我们关心读的文件,现在可以read,并且不会发生阻塞的情况发生,那么该函数就会返回并且修改fd_set,将准备就绪的文件描述符位进行设置,那么我们就只需要检查文件描述位,就可以知道那些文件准备好了。它实现了同时监听多个文件描述符,并且在没有文件设备准备好情况下,会在指定时间返回。
其实这个函数和select函数用途是差不多的,只是在select上面进行了一些改进,有两点首先是超时时间进行了修改,由于select函数中的超时时间是可修改的,在不同系统对这个超时时间的操作不一样,有些系统在函数返回时,用这个结构表示还有多长时间未等待,有的不修改,而在pselect中将他们统一起来都不修改,并且设置为const类型。另外一点是添加了第六个参数,这是一个信号掩码表,它表示在进入这个函数的时候将进程掩码修改为sigmask,函数返回后则还原回来,这样有一个好处,就是在pselect函数的时候,不会被非期望的信号打断。#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);
这个函数和select函数功能都是差不多的。详情请man#include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout);
散布读和聚集写:
它就是说可以一次性读取或者写多个不连续的缓冲区。它用到连个函数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);
参数中iovec结构体如下
该结构体指定了多个缓冲区如下图所示struct iovec { void *iov_base; /* Starting address */ size_t iov_len; /* Number of bytes to transfer */ };
注意对于数组的大小要小于IOV_MAX
存储映射I/O:
Memory-mapped I/O 使一个磁盘文件与存储空间中的一个缓冲区相映射,于是当从缓冲区中读取数据,就相当于读文件相应字节。写也一样,但是为了不同的效果,要设置一些属性。
该函数如果成功则返回映射区地址,出错返回MAP_FAILED#include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
对于addr参数是指定存储区起始地址。通常设置为0,表示由系统选择起始地址。
fd则表示被映射文件的描述符
off:则表示fd指定文件的起始文件的相对位置
对于参数prot,依照下表:
注意哦,对存储区的操作权限是不能超过open文件所指定的权限的。
参数flag:
MAP_FIXED:表示函数返回值必须登陆addr。
MAP_SHARED:本进程对映射区的所有操作和操作原文件一样效果
MAP_PRIVATE:对映射区的操作导致创建一个该映射文件的一个私有副本,而不是原文件。
unix 高级I/O详解
最新推荐文章于 2020-02-08 20:21:46 发布