因为有了C语言的标准,操作文件就更简单了,C标准中提供的标志I/O库[stdio],其中实现了一个跨平台用户缓冲的解决方按,功能强大,简化了很多的底层操作,所以在操作文件时打开、关闭、读写都是由C标准库完成。
有了C标准库的支持,在操作文件时就不用操作文件表述符了,取代的是文件指针,但是在C库中,文件指针也是映射到文件描述符。文件指针是FILE,也是在stdio中定义的。所以在标准I/O中,一个打开的文件叫流【stream】,也就是说可以用流来读或者写。
所以打开文件是:FILE *fopen(const char *filepath,const char *mode);//mode is:"r" or "w" or "+r" or "a"//
一般系统用不同的方式对待文本和二进制文件,并且b模式指示文件用二进制打开。fopen成功返回一个合法的FILE指针,失败时返回NULL,并设置errno。
当然也可以使用文件描述符来打开,也就是将文件描述符转换成文件流的形式,其原型是:
FILE *fdopen(int fd,const char *mode);//需要注意的是该函数必须与原来打开文件描述符的模式匹配。还有文件描述没有被复制,而只是关联了一个流,关闭流也会关闭相应的文件描述符。所有在使用文件描述符的时候,将其转换成流时,只要关闭流就可以同时关闭文件描述符了。
既然打开了,就要关闭,关闭流就像关闭文件描述符一样的关闭,使用fclose(FILE *stream);所有被缓冲但还没有写出的数据会被先写出。
也可以使用fcloseall();来关闭所有的流。
C语言实现了三读取文件的方式:单字节、单行、二进制的读取,也就是必须以对应的方式来打开。
单自己读取使用: int fgetc();来进行读取,该函数返回的是一个整数,所有我们可以使用强转换来进行转换。
单行读取:int fgets(char *str,int size,FILE *stream);读到换行符时,读取结束;
读取二进制文件:fread(void *buf,size_t size,size_t nr,FILE *stream);//会从流中读取nr个数据,每个数据有size字节,并将数据放入buf的缓冲区中;
高级文件I/O:
I/O系统调用不仅是文件I/O的基础,也是LINUX下所有的通信方式的基础,下面是更多高级的I/O调用:
散步/聚集I/O I/O允许在单次调用中同时对多个缓冲区做读取或者写入操作,适合于聚集多个不同数据结构进行统一的I/O操作;
epoll poll和select的改进,在一个程序需要处理数百个文件描述符的时候很有用;
内存映射I/O 将文件映射到内存,可以通过简单的内存管理方式来处理;适合有特殊要求的I/O;
文件I/O提示 运行进程将文件I/O使用上的一些提示信息提供给内核;能提升I/O的性能;
异步I/O 允许进程发出多个I/O请求而且不必等待其完成操作,适用与不使用线程的情况下处理重负载I/O操作;
散步/聚集I/O:可以将单个数据流的内容写到多个缓冲区,或者把单个数据流读到多个缓冲区中,也就是聚集操作和散步操作。linux中实现了散步与聚集操作的函数是:readv and writev:
#include<sys/uio.h>
ssize_t readv(int fd,const struct iovec *iov,int count);//从fd中读取数据散布到iov中//
ssize_t writev(int fd,const struct iovec *iov,int count);//把多个iov的数据聚集存储到fd中//
每个iovec结构体描述一个独立的缓冲区:
struct iovec{
void *iov_base;
size_t iov_len;
};
EventPoll接口:
由于poll和select的局限,引入了event poll(epoll)机制,poll和select每次调用时都需要所有被监听的文件描述符,内核必须遍历所有被监视的文件描述符。这样的遍历,当描述符过大时,系统的性能会严重降低。epoll则把监听注册从实际监听中分离出来,从而解决了性能问题,一个系统调用初始化一个epoll上下文,另一个从上下文中加入或者删除监听的文件描述符,第三个执行事件的等待event wait。#include<sys/epoll.h>
创建epoll实例:int epoll_create(int size);//成功时返回与该实例相关联的描述符数,他与真正的文件没有关系,仅仅是后面的epoll使用,size参数告诉内核需要监听的文件描述符数目,但不是最大值:
int epfd=epoll_create(100);
if(epfd<0) perror("create fail!");
控制epoll:
epoll_ctl()可以像指定的epoll上下文中加入或者删除文件描述符:
成功调用将关联epoll实例和epfd,参数op指定对fd要进行的操作,event参数描述epoll更具体的行为。
op的有效值是:
EPOLL_CTL_ADD EPOLL_CTL_DEL EPOLL_CTL_MOD
events的有效值:
EPOLLERR EPOLLET EPOLLHUP EPOLLIN EPOLLONESHOT EPOLLOT EPOLLPRI
data是返回给用户的;
如添加描述符:
等待epoll事件:
epoll_wait等待给指定实例关联的文件描述符上的事件:
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);
epoll_wait()的调用等待epoll实例epfd中的文件fd上的事件,时限为timeout毫秒。成功返回,events指向包含epoll_event结构体的内存,且最多可以有maxevents个事件,返回值是事件数,如例子:
边沿触发事件和水平触发事件:
如果epoll_ctl的参数event中的events设置为EPOLLE,fd上的监听称为边沿触发,相反的称为水平触发;
存储映射:
内存和文件中数据是一一对应的,程序员可以通过内存来访问文件,就像是内存中的数据一样,这样的映射是,当我们对映射后的内存进行操作是,同时我们也在对文件进行操作了;mmpap()系统调用,该调用将对象映射到内存中。
mmap()调用请求内核将fd的文件从offset处开始的len个字节数据映射到内存,如果包含了addr,表明优先使用addr为内存中的开始地址。访问权限由port指定,flags指定了其他的操作行为,#include<sys/mman.h>
void *mmap(void *addr,size_t len,int port,int flags,int fd,off_t offset);
addr参数告诉内存映射文件的最佳地址,多数使用0;
port参数描述了对内存区域所请求的访问权限:PORT_READ PORT_WRITE PORT_EXEC
要求的访存权限不能和打开文件的访问模块冲突,必须保持一致;
flags参数描述了映射的类型和一些行为:MAP_FIXED MAP_PRIVATE MAP_SHARED后两个必须指定其一,但不能同时指定;映射导致描述符计数加1,如果关闭映射文件,你的进程依然可以访问该文件。
页大小:
页是内存中允许具有不同权限和行为的最小单元,页是内存映射的基本块,同时也是进程地址空间的基本块;
mmap调用操作页,add和offset参数都必须按页大小对齐,也就是说必须是页大小的整数倍。为了能够获得页大小,这也是能够根据页大小来设在映射的方法,获得页大小的函数是:sysconf();#include<unistd.h>
long sysconf(int name);
返回name的值,如果name无效,则返回-1,可以使用SC_PAGESIZE来获得;
long page_size=sysconf(_sc_PAGESIZE);
linux还提供了getpagesize();
int getpasesize(void);
当然还可以有<asm/pages.h>中的PAGE_SIZE宏来定义;
page_size=PAGE_SIZE;
进行了映射,那么在完成以后要释放了,使用munmap()函数来释放:
#include<sys/mman.h>
int munmap(void *addr,size_t len);
下面是存储映射的例子:
有了C标准库的支持,在操作文件时就不用操作文件表述符了,取代的是文件指针,但是在C库中,文件指针也是映射到文件描述符。文件指针是FILE,也是在stdio中定义的。所以在标准I/O中,一个打开的文件叫流【stream】,也就是说可以用流来读或者写。
所以打开文件是:FILE *fopen(const char *filepath,const char *mode);//mode is:"r" or "w" or "+r" or "a"//
一般系统用不同的方式对待文本和二进制文件,并且b模式指示文件用二进制打开。fopen成功返回一个合法的FILE指针,失败时返回NULL,并设置errno。
当然也可以使用文件描述符来打开,也就是将文件描述符转换成文件流的形式,其原型是:
FILE *fdopen(int fd,const char *mode);//需要注意的是该函数必须与原来打开文件描述符的模式匹配。还有文件描述没有被复制,而只是关联了一个流,关闭流也会关闭相应的文件描述符。所有在使用文件描述符的时候,将其转换成流时,只要关闭流就可以同时关闭文件描述符了。
既然打开了,就要关闭,关闭流就像关闭文件描述符一样的关闭,使用fclose(FILE *stream);所有被缓冲但还没有写出的数据会被先写出。
也可以使用fcloseall();来关闭所有的流。
C语言实现了三读取文件的方式:单字节、单行、二进制的读取,也就是必须以对应的方式来打开。
单自己读取使用: int fgetc();来进行读取,该函数返回的是一个整数,所有我们可以使用强转换来进行转换。
单行读取:int fgets(char *str,int size,FILE *stream);读到换行符时,读取结束;
读取二进制文件:fread(void *buf,size_t size,size_t nr,FILE *stream);//会从流中读取nr个数据,每个数据有size字节,并将数据放入buf的缓冲区中;
高级文件I/O:
I/O系统调用不仅是文件I/O的基础,也是LINUX下所有的通信方式的基础,下面是更多高级的I/O调用:
散步/聚集I/O I/O允许在单次调用中同时对多个缓冲区做读取或者写入操作,适合于聚集多个不同数据结构进行统一的I/O操作;
epoll poll和select的改进,在一个程序需要处理数百个文件描述符的时候很有用;
内存映射I/O 将文件映射到内存,可以通过简单的内存管理方式来处理;适合有特殊要求的I/O;
文件I/O提示 运行进程将文件I/O使用上的一些提示信息提供给内核;能提升I/O的性能;
异步I/O 允许进程发出多个I/O请求而且不必等待其完成操作,适用与不使用线程的情况下处理重负载I/O操作;
散步/聚集I/O:可以将单个数据流的内容写到多个缓冲区,或者把单个数据流读到多个缓冲区中,也就是聚集操作和散步操作。linux中实现了散步与聚集操作的函数是:readv and writev:
#include<sys/uio.h>
ssize_t readv(int fd,const struct iovec *iov,int count);//从fd中读取数据散布到iov中//
ssize_t writev(int fd,const struct iovec *iov,int count);//把多个iov的数据聚集存储到fd中//
每个iovec结构体描述一个独立的缓冲区:
struct iovec{
void *iov_base;
size_t iov_len;
};
EventPoll接口:
由于poll和select的局限,引入了event poll(epoll)机制,poll和select每次调用时都需要所有被监听的文件描述符,内核必须遍历所有被监视的文件描述符。这样的遍历,当描述符过大时,系统的性能会严重降低。epoll则把监听注册从实际监听中分离出来,从而解决了性能问题,一个系统调用初始化一个epoll上下文,另一个从上下文中加入或者删除监听的文件描述符,第三个执行事件的等待event wait。#include<sys/epoll.h>
创建epoll实例:int epoll_create(int size);//成功时返回与该实例相关联的描述符数,他与真正的文件没有关系,仅仅是后面的epoll使用,size参数告诉内核需要监听的文件描述符数目,但不是最大值:
int epfd=epoll_create(100);
if(epfd<0) perror("create fail!");
控制epoll:
epoll_ctl()可以像指定的epoll上下文中加入或者删除文件描述符:
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);
struct epoll_event{
_u32 events;
union{
void *ptr;
int fd;
__u32 u32;
__u64 u64;
}data;
};
成功调用将关联epoll实例和epfd,参数op指定对fd要进行的操作,event参数描述epoll更具体的行为。
op的有效值是:
EPOLL_CTL_ADD EPOLL_CTL_DEL EPOLL_CTL_MOD
events的有效值:
EPOLLERR EPOLLET EPOLLHUP EPOLLIN EPOLLONESHOT EPOLLOT EPOLLPRI
data是返回给用户的;
如添加描述符:
struct epoll_event event;
int ret;
event.data.f=fd;
event.events=EPOLLIN|EPOLLOUT;
ret=epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&event);
if(ret)
perror("epoll_ctl");
等待epoll事件:
epoll_wait等待给指定实例关联的文件描述符上的事件:
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);
epoll_wait()的调用等待epoll实例epfd中的文件fd上的事件,时限为timeout毫秒。成功返回,events指向包含epoll_event结构体的内存,且最多可以有maxevents个事件,返回值是事件数,如例子:
#define MAX_EVENTS 64
struct epoll_event *events;
events=maoll(sizeof(struct epoll_event)*MAX_EVENTS);
int nr_events,i,epfd;
if(!events){
perror("malloc error!");
return 1;
}
nr_event=epoll_wait(epfd,events,MAX_EVENTS,-1);
if(nr_events<0)
{
perror("epoll_wait error!");
free(events);
return 1;
}
for(i=0; i<MAX_EVENTS; ++i){
printf("events=%d on fd=%d\n",events[i].events,events[i].data.fd);
}
free(events);
边沿触发事件和水平触发事件:
如果epoll_ctl的参数event中的events设置为EPOLLE,fd上的监听称为边沿触发,相反的称为水平触发;
存储映射:
内存和文件中数据是一一对应的,程序员可以通过内存来访问文件,就像是内存中的数据一样,这样的映射是,当我们对映射后的内存进行操作是,同时我们也在对文件进行操作了;mmpap()系统调用,该调用将对象映射到内存中。
mmap()调用请求内核将fd的文件从offset处开始的len个字节数据映射到内存,如果包含了addr,表明优先使用addr为内存中的开始地址。访问权限由port指定,flags指定了其他的操作行为,#include<sys/mman.h>
void *mmap(void *addr,size_t len,int port,int flags,int fd,off_t offset);
addr参数告诉内存映射文件的最佳地址,多数使用0;
port参数描述了对内存区域所请求的访问权限:PORT_READ PORT_WRITE PORT_EXEC
要求的访存权限不能和打开文件的访问模块冲突,必须保持一致;
flags参数描述了映射的类型和一些行为:MAP_FIXED MAP_PRIVATE MAP_SHARED后两个必须指定其一,但不能同时指定;映射导致描述符计数加1,如果关闭映射文件,你的进程依然可以访问该文件。
页大小:
页是内存中允许具有不同权限和行为的最小单元,页是内存映射的基本块,同时也是进程地址空间的基本块;
mmap调用操作页,add和offset参数都必须按页大小对齐,也就是说必须是页大小的整数倍。为了能够获得页大小,这也是能够根据页大小来设在映射的方法,获得页大小的函数是:sysconf();#include<unistd.h>
long sysconf(int name);
返回name的值,如果name无效,则返回-1,可以使用SC_PAGESIZE来获得;
long page_size=sysconf(_sc_PAGESIZE);
linux还提供了getpagesize();
int getpasesize(void);
当然还可以有<asm/pages.h>中的PAGE_SIZE宏来定义;
page_size=PAGE_SIZE;
进行了映射,那么在完成以后要释放了,使用munmap()函数来释放:
#include<sys/mman.h>
int munmap(void *addr,size_t len);
下面是存储映射的例子:
#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 fail!");
return 1;
}
if(fstat(fd,&sb)==-1){
perror("fstat fail!");
return 1;
}
if(!S_ISREG(sd.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);
for(len=0; len<sb.st_size; ++len)
putcahr(p[len]);
munmap(p,sb.st_size);
return 0;
}