高级文件I/O
1、散布/聚集I/0
散步/聚集I/O :单个数据流内容->写入多个缓冲区/单个数据量内容读->多个缓冲区
优点:
1.效率 单个向量I/O替代多个线性I/O
2.性能 降低系统调用次数,比线性I/O性能更好
3.原子性 一个线程执行单个向量I/O
头文件:
#include <sys/uio.h>
操作函数:
readv()
:ssize_t readv(int fd,const struct iovec *iov,int count);
writev()
: ssize_t writev(int fd,const struct iovec *iov,int count);
2、epoll
event poll(epoll)
相比poll()
与select()
的最大差异监听注册从实际监听中分离出来,解决poll()
与select()
性能问题。
头文件:
#include <sys/epoll.h>
操作函数:
epoll_create()
:int epoll_create(int size)
功能:该函数生成一个epoll专用的文件描述符,其中的参数是指定生成描述符的最大范围;这个参数自从Linux 2.6.8之后开始就没有使用了(被忽略了)
返回错误 | 解析 |
---|---|
EINVAL | size 不是正数 |
ENFILE | 系统达到打开文件数的上限 |
ENOMEN | 没有足够的内存来完成该次操作 |
epoll_ctl()
: int epoll_ctl(int epfd,int op,int fd,struct epoll_event *evnet)
功能:用于控制某个文件描述符上的事件,可以注册事件,修改事件,删除事件。
@epfd:由 epoll_create 生成的epoll专用的文件描述符;
@op:要进行的操作,EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修改、EPOLL_CTL_DEL 删除;
@fd:关联的文件描述符;
@event:指向epoll_event的指针;
成功:0;失败:-1
struct epoll_event
{
__u32 events;
union
{
void *ptr;
int fd;
__u32 u32;
__u64 u64;
}data;
};
op参数有效值 | 解析 |
---|---|
EPOLL_CTL_ADD | 把fd指定的文件添加到epfd指定的epoll实例监听集中 |
EPOLL_CTL_DEL | 把fd指定的文件从epfd指定的epoll监听集中删除 |
EPOLL_CTL_MOD | 使用event改变在已有fd上的监听行为 |
epoll event结构体中的events参数列出了给定文件描述符上监听的事件
epoll_events.event | 解析 |
---|---|
EPOLLERR | 文件出错。即使没设置,这个事件也是被监听 |
EPOLLET | 在监听文件上开启边缘触发。(默认行为是水平触发) |
EPOLLHUP | 文件被挂起。即使没设置,这个事件也是被监听 |
EPOLLIN | 文件未阻塞,可读 |
EPOLLONESHOT | 在一次事件产生并被处理之后,文件不再被监听 |
EPOLLOUT | 文件未阻塞,可写 |
EPOLLPRI | 高优先级的带外数据可读。 |
epoll_ctl
成功后返回0.失败返回-1,并设置了一下errno值
errno值 | 解析 |
---|---|
EBADF | epfd不是一个有效epoll实例,或者fd不是有效文件描述符 |
EEXIST | op为EPOLL_CTL_ADD,但是fd已经与epfd关联 |
EINVAL | epfd不是一个epoll实例,epfd和fd相同,或者op无效 |
ENOENT | op是EPOLL_CTL_MOD,或者EPOLL_CTL_DEL,但是fd没有与epfd关联 |
ENOMEN | 没有足够内存完成进程请求 |
EPERM | fd不支持epoll |
在epfd实例中加入一个fd指定的监听文件
struct epoll_event event;
int ret;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLOUT;
ret = epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&event);
if(ret)
perror("epoll_ctl");
修改epfd实例中的fd上的监听事件
struct epoll_event event;
int ret;
event.data.fd = fd;
event.events = EPOLLIN;
ret = epoll_ctl(epfd,EPOLL_MOD,fd,&event);
if(ret)
perror("epoll_ctl");
从epfd中移除在fd上的一个监听事件
struct epoll_event event;
int ret;
event.data.fd = fd;
event.events = EPOLLIN;
ret = epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&event);
if(ret)
perror("epoll_ctl");
epoll_wait()
: int epoll_wait(int epfd,struct epoll_event *events,int maxevents, int timeout);
功能:该函数用于轮询I/O事件的发生;
@epfd:由epoll_create 生成的epoll专用的文件描述符;
@epoll_event:用于回传代处理事件的数组;
@maxevents:每次能处理的事件数;
@timeout:等待I/O事件发生的超时值,timeout =0 ,没用事件发生,调用也立即返回0,timeout = -1 ,调用一直等待到事件发生;
成功:返回发生的事件数;失败:-1
errno值 | 解析 |
---|---|
EBADF | epfd是无效文件描述符 |
EFAULT | 进程对events指向的内存无写权限 |
EINTR | 系统调用在完成前被信号中断 |
EINVAL | epfd不是有效epoll实例,或者maxevents大于EP_MAX_EVENTS或者小于等于0 |
epoll_wait()
例程:
#define MAX_EVENTS 64
struct epoll_event *events;
int nr_events,i,epfd;
events = malloc(sizeof(srtuct epoll_event) * MAX_EVENTS);
if(!events)
{
perror("malloc");
return 1;
}
nr_events = epoll_wait(epfd,events, MAX_EVENTS, -1);
if(nr_events < 0)
{
perror("epoll_wait");
free("events");
return 1;
}
for(i = 0;i < nr_events;i++)
{
printf("event=%ld on fd=%d\n",events[i].events,events.data.fd);
}
free(events);
边沿触发事件与水平触发事件
epoll_ctl()
的参数event中的events项配置为EPOLLE,fd上的监听为边沿触发,反之为水平触发。
假设:
1.生产者向管道写入1Kb数据。
2.消费者在管道调用epoll_wait()
,等待pipe出现数据,从而可读。
水平监听触发:在步骤2里对epoll_wait()的调用立即返回,以表pipe可读。
边沿监听触发:这个调用直到步骤1发生后才返回,即调用epoll_wait()时管道已经可读,调用仍然会等待直到数据写入,之后返回。
3、内存映射I/O
头文件:
#include <sys/mman.h>
函数:
mmap()
:void *mmap(void *addr,size_t len,int prot,int flags,int fd, off_t offset);
功能:该函数将fd表示的文件从offset处开始的len个字节数据映射到内存中;
@addr:内核映射文件的最佳地址,仅提示,非强制,大部分为0;
@len:映射到内存的字节数;
@prot:内存区域所请求的访问权限,PROT_READ 页面可读,PROT_WRITE 页面可写,PROT_EXEC 页面可执行;
@flag:指定操作行为;
@offset:映射文件的起始地址;
映射文件要求访问模式和打开文件的访问模式不能冲突
flag参数 | 解析 |
---|---|
MAP_FIXED | 告诉mmap()把addr看作强制性要求。如果内核无法映射文件到指定地址,调用失败 |
MAP_PRIVATE | 映射区不共享 |
MAP_SHARED | 和所有其他映射该文件的进程共享映射内存 |
MAP_SHARED和MAP_PRIVATE必须指定其中一个,但不能同时指定。
例子:
void *p;
p = mmap(0,len,PROT_READ,MAP_SHARED,fd,0);
if(p == MAP_FAILED)
{
perror("mmap");
}
mmap()
调用操作页。addr和offset参数必须按照页大小对齐。
系统获取页大小方法:
1.sysconf()
#include <unistd.h>
long sysconf(int name)
POSIX.1 name参数列表
name定义 | 解析 |
---|---|
_SC_ARG_MAX | 参数长度最大限制 |
_SC_CHILD_MAX | 每个user可同时运行的最大进程数 |
_SC_HOST_NAME_MAX | hostname最大长度,需小于_POSIX_HOST_NAME_MAX (255) |
_SC_LOGIN_NAME_MAX | 登录名的最大长度(字节数) |
_SC_NGROUPS_MAX | 进程同时拥有的组IDs的最大个数 |
_SC_CLK_TCK | 每秒对应的时钟tick数 |
_SC_OPEN_MAX | 一个进程可同时打开的文件最大数 |
_SC_PAGESIZE | 一个page的大小,单位byte |
_SC_PAGE_SIZE | 一个page的大小,单位byte |
_SC_RE_DUP_MAX | 当使用间隔表示法{m,n}时,regexec和regcomp函数允许的基本正则表达式的重复出现次数 |
_SC_STREAM_MAX | 一个进程能同时打开的标准I/O流数 |
_SC_SYMLOOP_MAX | 在解析路径名时可遍历的符号链接数 |
_SC_TTY_NAME_MAX | 终端设备名长度,包括终止字符null |
_SC_TZNAME_MAX | 时区名字节数 |
_SC_VERSION | POSIX版本 |
获取系统页大小:
long page_size = sysconf(_SC_PAGESIZE);
2.getpagesize()
头文件:
#include <unistd.h>
函数:
int getpagesize(void)
;
问题:并不是所有unix系统都支持这个函数。
3.<asm/pages.h>中宏PAGE_SIZE定义
编译的时候获得页大小。
mmap()
返回值和错误码
成功:返回映射区地址。
失败:返回MAP_FAILED。
mmap错误代码 | 解析 |
---|---|
EACESS | 给定文件描述符不是普通文件,或者打开模式和prot或者flags冲突 |
EAGAIN | 文件已被文件锁锁定 |
EBADF | 给定文件描述符无效 |
EINVAL | addr,len,off中的一个或者多个无效 |
ENFILE | 打开文件数达到系统上限 |
ENODEV | 文件所在的文件系统不支持存储映射 |
ENOMEM | 没有足够的内存 |
EOVERFLOW | addr+len的结果超过了地址空间大小 |
EPERM | 设定了PROT_EXEC,但是文件系统以不可执行方式挂载 |
与映射区域相关的信号:
信号 | 解析 |
---|---|
SIGBUS | 当进程试图访问一块已经无效的映射区域时,产生该信号。比如,文件在映射后被截短 |
SIGSEGV | 当进程试图写一块只读的映射区域时,产生该信号 |
munmap()
取消mmap()映射
头文件:
#include <sys/mman.h>
函数:
int munmap(void *addr,size_t len);
成功返回0;
失败返回-1;
实例:
if(munmap(addr,len) == -1) perror("munmap");
存储映射例子:
#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <sys/mman.h>int main(int argc,char *argv[]){ struct stat sb; off_t len; char *p; int fd; if(argc<2) { fprintf(stderr,"usage: %s <file>\n",argv[0]); return 1; } fd = open(argv[1],O_RDONLY); if(fd == -1) { perror("open"); return 1; } if(fstat(fd,&sb) == -1) { perror("fstat"); return 1; } if(!S_ISREG(sb.st_mode)) { fprintf(stderr,"%s is not a file\n",argv[1]); return 1; } p = mmap(0,sb.st_size,PROT_READ,MAP_SHARED,fd,0); if(p == MAP_FAILED) { perror("mmap"); return 1; } if(close(fd) == -1) { perror("close"); return 1; } for(len == 0;len<sb.st_size;len++) { putchar(p[len]); } if(munmap(p,sb.st_size) == -1) { perror("munmap"); return 1; } return 0;}
mmap()
优点:
1.使用read()或write()系统调用需要从用户缓冲区进行数据读写,而使用映射文件进行操作,可以避免多余的数据拷贝。
2.除了潜在的页错误,读写映射文件不会带来系统调用和上下文切换的开销。
3.当多个进程映射同一对象到内存中,数据在进程间共享。
4.在映射对象中搜索只需一般的指针操作。不必使用lseek();
mmap()
缺点:
1.映射区域大小通常是页大小的整数倍。映射文件大小与页大小的整数倍之间有空间浪费。
2.存储映射区域必须在进程地址空间内。
3.创建和维护映射以及相关的内核数据结构有一定开销。
调整映射的大小
头文件:
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/mman.h>
函数:
void * mremap(void *addr,size_t old_size,size_t new_size,unsigned long flags);
Comments | NOTHING