<span style="font-size:18px;">#include <apue.h>
#include<stdio.h>
#define LINEMAX 256
int main(int ac,char **av)
{
int fd = -1,size = 0;
FILE *fst = NULL; //文件流指针,指针指向的是一个文件
char buf[LINEMAX];
if(1 >= ac){
return 1;
}
//close(1);
//close(2);
fst = fopen(av[1],"a+");
if(NULL == fst){
logerr("fopen [%s]\n",strerror(errno));
return 2;
}
fd = fileno(fst); //fileno()用来取得参数stream指定的文件流所使用的文件描述符
printf("-------------- 1 ------------------\n");
printf("fd:%d,%d\n",fd,fst->_file);
memset(buf,0x00,LINEMAX);
sprintf(buf,"%%0%dd\n",20);
printf("-------------- 2 ------------------\n");
printf(buf,fd);
size = strlen(buf);
write(fd,buf,size);
//sync();
memset(buf,0x00,LINEMAX);
lseek(fd,0,SEEK_SET);
fread(buf,1,size,fst);
printf("-------------- 3 ------------------\n");
//write(1,"-------------- 3 ------------------\n",30);
printf("read out : %s\n",buf);
fclose(fst);
close(fd);
return 0;
}</span>
#include <apue.h>
#define LINEMAX 256
int main(int ac,char **av)
{
int fd = -1,size = 0;
FILE *fst = NULL; //文件流指针,指针指向的是一个文件
char buf[LINEMAX];
if(1 >= ac){
return 1;
}
//close(1);
//close(2);
fst = fopen(av[1],"a+");
if(NULL == fst){
logerr("fopen [%s]\n",strerror(errno));
return 2;
}
fd = fileno(fst); //fileno()用来取得参数stream指定的文件流所使用的文件描述符
printf("-------------- 1 ------------------\n");
printf("fd:%d,%d\n",fd,fst->_file);
memset(buf,0x00,LINEMAX);
sprintf(buf,"%%0%dd\n",20);
printf("-------------- 2 ------------------\n");
printf(buf,fd);
size = strlen(buf);
write(fd,buf,size);
//sync();
memset(buf,0x00,LINEMAX);
lseek(fd,0,SEEK_SET);
fread(buf,1,size,fst);
printf("-------------- 3 ------------------\n");
//write(1,"-------------- 3 ------------------\n",30);
printf("read out : %s\n",buf);
fclose(fst);
close(fd);
return 0;
}
#if 0
文件IO
与不带流(缓存)的文件IO操作函数区别:
不带流的如:read,open,close,write 属于系统调用,实现方式是用了fctrl
ioctrl等等底层,是内核空间的,操作系统提供基本的IO服务,与操作系统绑定
,而标准IO在实现本身时,就要调用不带流
的IO操作函数,如标准IO函数fopen,fclose,fread,fwrite
在应用上,几乎没有区别,但在一些特殊文件如:管道文件只能不带流的操作
open ,不能fopen,
区别:
1. 标准IO采用了缓存机制。例如fopen不仅打开了一个文件,还建立了缓存区
还创建了一个包含文件和缓存区相关的数据结构(FILE ),而文件IO
则是用户自己分配缓存大小,
2. 其次,文件IO是操作的描述符, 主要对象是文件,读写磁盘
标准IO是针对的控制台,打印输出到屏幕,它操作的是字符流
流IO(标准IO)
标准IO处理很多的细节,如缓存区的分配,以优化的块长度执行IO,这些使得用户不必
担心如何选择正确的块长度(buf)
流 :相当于管道,媒介,程序从磁盘读 ,写 文件内容,大概有文件流,数据流,转换流
printf流,object流
文件流:当我们使用标准IO打开或创建一个文件时,已使一个流与文件相关联
有分为输入流:将数据以字符或字节形式从外部文件或数据库等读到内存
也就分字符输入流,字节输入流
输出流:从内存中的数据写到外部磁盘等
缓存:如果每次我们从磁盘中读或写一个字符,则对磁盘伤害太大,磁头与磁盘有物理接触
解决办法:在内存中建立一个buffer[](缓存区),写数据时,把缓存区写
满后,缓存区一次写给磁盘,读也是同理,这样减少了对磁盘的访问次数
buffer其实就是在内存中通过字符数组建立的
#define fileno(__p) ((__p)->_file)
因为FILE 结构体定义为
类似
typedef struct {
unsigned char *_ptr;
int _cnt;
unsigned char *_base;
unsigned char *_bufendp;
short _flag;
short _file;
int __stdioid;
char *__newbase;
void *_lock;
} FILE;
其中 _file就是文件描述符。
#endif
1.和操作一般意义上的文件一样,linux中对文件的操作只需要五个基本的函数:
open、close、read、write和ioctl
通过调用这几个函数就能对linux中的文件进行读、写等操作。不过,这种操作又分为系统调用和库函数调用。简单的说,系统调用是最直接的方式,
库函数调用最终也是通过系统调用实现的。可认为库函数调用是对系统调出于效率考虑而做出的优化。
库函数调用和系统调用的区别和联系请参看:linux系统调用和库函数调用的区别
我们用很少的函数就可以对文件和设备进行访问和控制。这些函数就是所谓的系统调用,由操作系统直接提供,他们是通向操作系统本身的接口。
操作系统的核心部分,既内核,其实就是一组设备驱动程序。这是一些对硬件进行控制的接口。
通过系统调用来访问文件是最直接的方式。系统调用函数直接作用于操作系统内核的设备驱动程序从而实现文件访问
文件描述符
在系统中需要处理的文件(读、写操作)需要一个标识,以便在其它地方能识别出这个文件,于是就产生了文件描述符。文件描述符是一些小值整数,简单的说就是
一个文件ID用于在系统中唯一的标识文件。文件描述符的总数也就是系统可以打开文件的最多个数,这取决于系统的配置情况。
当开始运行程序时,也就是系统开始运行时,它一般会有三个已经打开的文件描述符。他们是:
0:标准输入
1:标准输出
2:标准错误
其它文件的文件描述符,在调用文件打开函数open时返回。这就是说,每个设备对应着一个文件描述符。文件描述符由操作系统分配,每次分配最小的。
write系统调用
write,就是把缓冲区的数据写入文件中。注意,这里的文件时广泛意义的文件,比如写入磁盘、写入打印机等等。
Linux 中write()的函数原型:
size_t write(int fildes, const void *buf, size_t nbytes);
参数说明:
fildes:文件描述符,标识了要写入的目标文件。例如:fildes的值为1,就像标准输出写数据,也就是在显示屏上显示数据;如果为 2 ,则想标注错误写数据。
*buf:待写入的文件,是一个字符串指针。
nbytes:要写入的字符数。
函数返回值:size_t 返回成功写入文件的字符数。需要指出的是,write可能会报告说他写入的字节比你所要求的少。这并不一定是个错误。在程序中,你需要检查
read系统调用
系统调用read是从文件中读出数据。要读取的文件用文件描述符标识,数据读入一个事先定义好的缓冲区。他返回实际读入的字节数。
Linux中read的函数原型:
size_t read(int fildes, void *buf, size_t nbytes);
参数说明:
fildes:文件描述符,标识要读取的文件。如果为0,则从标准输入读数据。类似于scanf()的功能。
*buf:缓冲区,用来存储读入的数据。
nbytes:要读取的字符数。
返回值:size_t返回成功读取的字符数,它可能会小于请求的字节数。
open系统调用
系统调用open的作用是打开一个文件,并返回这个文件的描述符。
简单地说,open建立了一条到文件或设备的访问路径。如果操作成功,它将返回一个文件描述符,read和write等系统调用使用该文件描述符对文件或
设备进行操作。这个文件描述符是唯一的,他不会和任何其他运行中的进程共享。如果两个程序同时打开一个文件,会得到两个不同的问价描述符。如果
同时对两个文件进行操作,他们各自操作,互补影响,彼此相互覆盖(后写入的覆盖先写入的)为了防止文件按读写冲突,可以使用文件锁的功能。
Linux中open的函数原型有两个:
int open(const char *path, int oflags);
int open(const char *path, int oflags, mode_t mode );
参数说明。
path:准备打开的文件或设备名字。
oflags:指出要打开文件的访问模式。open调用必须指定如下所示的文件访问模式之一:
open调用哈可以在oflags参数中包括下列可选模式的组合(用”按位或“操作):
O_APPEDN: 把写入数据追加在文件的末尾。
O_TRUNC: 把文件长度设为零,丢弃以后的内容。
O_CREAT: 如果需要,就按参数mode中给出的访问模式创建文件。
O_EXCL: 与O_CREAT一起调用,确保调用者创建出文件。使用这个模式可防止两个程序同时创建一个文件,如果文件已经存在,open调用将失败。
close系统调用
close系统调用用于“关闭”一个文件,close调用终止一个文件描述符fildes以其文件之间的关联。文件描述符被释放,并能够重新使用。
close成功返回1,出错返回-1.
其他和文件管理有关的系统调用
还有许多其他的系统调用能对文件进行操作。
几个常用的如:lseek()对文件描述符fildes指定文件的读写指针进行设置,也就是说,它可以设置文件的下一个读写位置。
fstat,stat,lstat 是和文件描述符相关的函数操作,这里就不做介绍。
dup,dup2系统调用。dup提供了复制文件描述符的方法,使我们能够通过两个或者更多个不同的文件描述符来访问同一个文件。这可以用于
在文件的不同位置对数据进行读写。
库函数
在输入、输出操作中,直接使用系统调用效率会非常底。具体原因有二:
系统调用会影响系统性能。与函数调用相比,系统调用的开销大。因为在执行系统调用的时候,要切换到内核代码区执行,然后再返回用户代码。这必然就需要大量的时间开支。一种解决办法是:尽量减少系统调用的次数,让每次系统调用完成尽可能多的 任务。例如每次系统调用写入大量的字符而不是单个字符。
硬件会对系统调用一次能读写的数据块做一定的限制。例如,磁带机通常的写操作数据块长度是10k,如果缩写数据不是10k的整数倍,磁带机还是会以10k为单位绕磁带,这就在磁带上留下空隙。
为了提高文件访问操作的效率,并且使得文件操作变得更方便,Linux发行版提供了一系列的标准函数库。他们是一些由函数构成的集合,你可以在自己的程序方便的中使用它们,
去操作文件。提供输出缓冲功能的标准I/O库就是这样的例子。你可以高效的写任意长度的数据块,库函数则在需要的时候安排底层函数调用(系统调用)
也就是说,库函数在用户和系统之间,增加了一个中间层。如下图所示:
库函数是根据实际需要而包装好的系统调用,用户可在程序中方便的使用库函数,如标准I O库(稍后会讲
标准I/O库
标准I/O库及其头文件<stdio.h>为底层I/O系统调用提供了一个通用的接口。这个库现在已经成为ANSI标准C的一部分,而前面所讲的系统调用却不是。
标准I/O库提供了许多复杂功能的函数,用于格式化输出和扫描输入,它还负责满足设备的缓冲需求。
在许多方面,使用标准I/O库和使用底层文件描述符类似。需要先打开一个文件,已建立一个文件访问路径(也就是系统调用中的文件描述符)
在标准I/O库中,与文件描述符对应的叫 流(stream),它被实现为指向结构FILE的指针。
在启动程序时,有三个文件流是自动打开的。他们是:
stdin: 标准输入
stdout: 标准输出
stderr: 标准错误输出
下面会介绍一些常用的I/O库函数:
fopen函数
fopen函数类似于系统调用中的open函数。和open一样,它返回文件的标识符,只是这里叫做流(stream),在库函数里实现为一个指向文件的指针。
如果需要对设备的行为进行明确的控制,最好使用底层系统调用,因为这可以避免使用库函数带来的一些非预期的副作用,如输入/输出缓冲。
函数原型:
#include<stdio.h>
FILE *fopen(const char *filename, const char *mode);
参数说明:
*filename:打开文件的文件名
*mode:打开的方式
r 以只读方式打开文件,该文件必须存在。
r+ 以可读写方式打开文件,该文件必须存在。
rb+ 读写打开一个二进制文件,允许读数据。
rw+ 读写打开一个文本文件,允许读和写。
w 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件
w+ 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
fopen在成功是返回一个非空的FILE *指针。失败返回NULL
fread/fwrite函数
fread函数从文件流中读取数据,对应于系统调用中的read;fwrite函数从文件流中写数据,对应于系统调用中的write
函数原型:
#include<stdio.h>
size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream);
参数说明:
*ptr 要读取数据的缓冲区,也就是要存放读取数据的地方。
size:指定每个数据记录的长度。
nitems: 计数,给出要传输的记录个数。
返回值:成功读取到数据缓冲区的记录个数,当到达文件尾时,他的返回值可能会消耗与nitems,甚至可以是0
size_t fwrite(const coid *ptr, size_t size , size_t nitimes, FILE *stream);
他从指定的数据缓冲区ptr中把数据写入文件流,返回成功写入的记录个数。
fclose函数
fclose函数关闭指定的文件流stream,这个操作会使所有未写出的数据都写出。因为stdio库函数会对数据进行缓冲,所有调用fclose函数是很重要的。
如果程序需要确保数据已经全部写出,就应该调用fclose函数。虽然程序正常结束时,也会自动的调用fclose函数,但这样就不能检测出调用fclose所产生的错误了。
函数原型如下:
#include<stdio,h>
int fclose(FILE *stream);
fflush函数
fflush函数的作用是把文件流中所有未写出的数据全部写出。 处于效率考虑,在使用库函数的时候会使用数据缓冲区,当缓冲区满的时候才进行写操作。使用fflush函数
可以将缓冲区的数据全部写出,而不关心缓冲区是否满。fclose的执行隐含调用了fflush函数,所以不必再fclose执行之前调用fflush。
函数原型:
#include<stdio.h>
int fflush(FILE *stream);