文件IO编程

来源:互联网 发布:php 返回html 编辑:程序博客网 时间:2024/05/02 04:52

在Linux中为了简化系统对不同设备的处理,提高效率,对目录和设备的操作都等同于文件的操作。Linux中的文件主要分为 4 种:普通文件、目录文件、链接文件和设备文件。
两种方式访问文件:
一、系统调用方式:依赖于操作系统
Linux系统调用概念:
是用户程序要使用内核服务时所经历的函数调用,他运行于内核空间。进行系统调用时,程序运行空间需要从用户空间进入内核空间,处理完后再返回用户空间。这是因为用户程序不能访问内核的数据和函数,只能使用用户空间的资源。
系统调用按照功能逻辑大致可分为进程控制、进程间通信、文件系统控制、系统控制、存储管理、网络管理、 socket 控制、用户管理等几类。
用户编程接口API:
我们知道,系统调用是编程过程中为实现程序员的某种“愿望”而存在的。但是必须明确一点,系统调用是在内核态下通过软中断或系统调用指令向内核发出一个明确的请求,内核将调用内核相关函数来实现(如sys_read(),sys_write(),sys_fork())。用户程序不能直接调用这些Sys_read,sys_write等函数。问题到这我们察觉到,程序员要通过某种桥梁来实现系统调用,这种桥梁就是“用户编程接口API”,例如open()、write()等等函数就叫做API接口函数。于是我们大人类就将直接和程序员打交道的这类函数叫做“用户编程接口API”。
系统调用和API之间的关系:

系统调用就是API和系统服务之间的桥梁,但并不是所有的函数都一一对应一个系统调用,有时,一个 API 函数会需要几个系统调用来共同完成函数的功能,甚至还有一些 API 函数不需要调用相应的系统调用(因此它所完成的不是内核提供的服务)。看图说话。

二、库函数调用方式这个概念通过下面的对比就可以知道了。

系统调用和库函数调用的区别:
系统调用依赖于具体的操作系统,不同平台要经过特定的移植,而库函数调用是与操作系统无关的,在任何操作系统下比如windows、vxworks、wince、powerpc、linux下他们的函数都是一模一样的,使用方法也是一模一样,他完全就是独立于具体操作系统平台的一种操作。另一个方面,我们知道每一次系统调用都要进行用户空间和内核空间之间的切换,而且这种切换对CPU的开销很大,如果大量使用系统调用会大大降低CPU的工作效率,如此致命的问题该怎么办呢?这时候“库函数调用”登台啦,他是一种带缓冲区的操作,当用户空间缓冲区满或者写操作结束时,才将用户缓冲区的内容写到内核缓冲区,同样的道理,当内核缓冲区满或写结束时才将内核缓冲区内容写到文件对应的硬件平台,如此一来就大大减少了系统调用的次数,大大提高CPU工作效率。

实验操作:简单文件 I/O 操作
(A)系统调用API方式:
主要用到5个函数:open()、read()、write()、lseek()和close()。
(1)open()函数:打开或创建文件,可指定这个文件的属性和用户权限

所需头文件:#include <sys/types.h>、#include <sys/stat.h>、#include <fcntl.h>

函数原型:int open(const char *pathname, int flags, int perms);

参数解析:pathname:文件路径名,要加双引号,例如:使用绝对路径"/home/clbiao/hello.c",默认前目录下"hello.c"

flags:文件打开的方式。看图。前三个属性标识不能组合,其他可以通过按位或“|”来进行组合

perms:这个参数只有在创建文件时才使用,即第二个参数中有O_CREAT时才用作用,可用宏如下:

S_IRUSR:文件所有者可读、S_IWUSR:文件所有者可写、S_IXUSR:文件所有者可执行

S_IRGRP:文件用户组可读、S_IWGRP:文件用户组可写、S_IXGRP:文件用户组可执行

S_IROTH:其他用户可读、S_IWOTH:其他用户可写、S_IXOTH:其他用户可执行

读、写、执行对应的权限值依次为4、2、1,因此可以使用3位的8进制数表示这个参数的权限设置情况“abc”,a:用户值、b用户组值、c其他用户值


(2)close()函数:根据文件描述符fd来关闭文件。
函数原型:int close(int fd);

(3)lseek()函数:用来控制该文件的读写位置。在fd对应的文件中,以whence为参考点,再根据offset的值(“+”基于参考点前移,“-”相反)为偏移来设置文件指针的位置

函数原型:off_t lseek(int fd, off_t offset, int whence);


(4)read()函数:在fd文件描述符指定的文件中,从文件指针处(原先文件指针位置或先用lseek函数设置),读出count个字节数到buf指针指向的存储区,这个存储区通常是自己去申请或定义一个数组

函数原型:ssize_t read(int fd, void *buf, size_t count);


(5)write()函数:写目标文件:由fd文件描述符指定。把buf指定的缓冲区中的数据,从文件开头位置(在没有使用lseek设置时)开始写入

函数原型:ssize_t write(int fd, void *buf, size_t count);

注:buf的大小直接影响到读和写的速度。


代码时间:
功能:从一个文件中拷贝10k字节的数据到另外一个文件

/* file_cp.c */#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <stdlib.h>#include <stdio.h>#defineBUFFER_SIZE1024/* 每次读写缓存大小 */#define OFFSET10240/* 拷贝的数据大小 */ int main(int argc,char **argv)  /* 执行elf文件时带两个参数,第一个参数为复制源文件,第二个为目标文件 */{int src_file, dest_file;           //用于保存文件描述符unsigned char buff[BUFFER_SIZE];   //定义文件读写缓冲区int real_read_len;if(argc!=3) //如果输入参数不是三个,格式出错{ printf("Usage:%s fromfile tofile\n",argv[0]); exit(1); } src_file = open(argv[1], O_RDONLY);//以只读方式打开源文件/* 以只写方式打开目标文件,若此文件不存在则创建, 访问权限值为644 */dest_file = open(argv[2], O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);if (src_file < 0 || dest_file < 0){printf("Open file error\n");exit(1);}/* 将源文件的读写指针移到最后10KB的起始位置*/lseek(src_file, -OFFSET, SEEK_END);/* 读取源文件的最后10KB数据并写到目标文件中,每次读写1KB */while ((real_read_len = read(src_file, buff, sizeof(buff))) > 0){write(dest_file, buff, real_read_len);}close(dest_file);close(src_file);return 0;}

函数运行结果:


(B)C库函数方式
主要的几个标准IO库函数:fopen()、fread()、fclose()、fseek()、fwrite()等等,这些函数基本都是定义于C语言标准库<stdio.h>头文件中
(1)fopen()函数:

函数原型:FILE * fopen(const char *path,const char *mode);

参数分析:这两个参数都是字符串。

path:文件的路径和文件名,缺省路径为当前目录下面

mode:用于指定要打开的文件的方式。字符串中带“b”表示打开的文件为二进制文件,但是Linux中不区分二进制文件和文本文件

“r”或“rb”:打开只读文件,文件必须要存在

“r+”或“r+b”:打开可读写文件,文件必须要存在

“w”或“wb”:打开只写文件,若文件存在,文件长度清为0,即丢弃内容。文件不存在就创建一个。

“w+”或“w+b”:打开可读可写文件,若文件存在,文件长度清为0,即丢弃内容。文件不存在就创建一个。

以文件尾部追加的方式打开,不存在则创建:

“a”或“ab”:可读可写文件

“a+”或“a+b”:只写文件

函数返回:成功:指向文件流的FILE指针,之后对文件的读写操作都是通过这一个指针来进行。

失败:返回NULL,并把错误代码存在errno中

(2)fclose()函数:fclose()用来关闭先前fopen()打开的文件。此动作会让缓冲区内的数据写入文件中,并释放系统所提供的文件资源

函数原型:int fclose(FILE *stream);

参数分析:stream:是fopen()函数返回的FILE文件指针

如果只是想将文件缓冲区中的数据保存下来而不关闭文件可以使用int fflush(FILE *stream);

函数返回:成功:0

失败:有错误发生时则返回EOF并把错误代码存到errno。错误代码 EBADF表示参数stream非已打开的文件

(3)fread()函数:从stream指向的文件中读出nmemb个字段,每个字段为size个字节(也就是总共读nmemb*size字节数的数据),并将读出的数据放到ptr指向的字符数组当中

函数原型:size_t fread(void *ptr,size_t size,size_t nmemb,FILE *stream);

函数返回:函数实际读取到的nmemb数目,即读取的字段数,注意是字段哦!

(4)fwrite()函数:把ptr指向的字符数组中的数据写到stream指向的文件流中,计划写入nmemb个字段,每个字段size个字节数据

函数原型:size_t fwrite(void *ptr,size_t size,size_t nmemb,FILE *stream);

函数返回:实际写入的字段数。

代码时间:  

#include <string.h>#include <strings.h>#include <stdio.h>#include <stdlib.h>#define BUFFER_SIZE 1024   /* 每次读写缓存大小 */int main(int argc,char **argv) {FILE *src_fd;    //源文件的FILE指针FILE *dest_fd;   //目标文件的FILE指针long file_len=0;char buffer[BUFFER_SIZE]; //定义缓冲区大小if(argc!=3) //如果输入参数不是三个,格式出错{ printf("Formal error!Right eg:%s src_file dest_file\n",argv[0]); exit(1); } /* 打开源文件 */ if((src_fd=fopen(argv[1],"rb"))==NULL) { printf("Open %s Error\n",argv[1]); exit(1); } /* 创建目的文件 */ if((dest_fd=fopen(argv[2],"wb"))==NULL) { printf("Open %s Error\n",argv[2]); exit(1); } /*检测文件大小*/fseek(src_fd,0L,SEEK_END);  //移动文件流的读写位置到文件结尾处file_len=ftell(src_fd);     //获得文件大小fseek(src_fd,0L,SEEK_SET);  //将文件流的读写位置移回文件开头处printf("source file size is=%d\n",file_len);//打印源文件的大小/*进行文件拷贝*/while(!feof(src_fd)) { fread(buffer,BUFFER_SIZE,1,src_fd);if(BUFFER_SIZE>=file_len){fwrite(buffer,file_len,1,dest_fd);}else {fwrite(buffer,BUFFER_SIZE,1,dest_fd);file_len=file_len-BUFFER_SIZE;}bzero(buffer,BUFFER_SIZE);} fclose(src_fd); fclose(dest_fd); exit(0); } 

实验结果:




0 0
原创粉丝点击