学科分类
目录
Linux编程

文件I/O

内核中存在一系列具备预定功能的函数,操作系统将这些函数功能抽象为一组被称为系统调用的接口,提供给应用程序使用,open()、read()、write()、lseek()、close()等都是系统调用中与I/O操作相关的接口。下面对系统调用级的I/O接口函数进行讲解。

1、 open()函数

open()函数的功能是打开或创建一个文件,该函数存在于系统函数库fcntl.h中,其函数声明如下:

int open(const char *pathname,int flags[,mode_t mode]);

open()函数的第一个参数通常为待打开文件的文件路径及名称;第二个参数为文件的访问模式,一般使用定义在函数库fcntl.h中的一组宏来表示,常用的宏及其含义如表1所示。

表1 文件访问模式相关宏定义

编号 说明
O_RDONLY 以只读方式打开文件
O_WRONLY 以只写方式打开文件
O_RDWR 以读写方式打开文件
O_CREAT 创建一个文件并打开,若文件已存在则会出错
O_EXCL 测试文件是否存在,若不存在,则创建文件
O_NOCTTY 若pathname为终端设备,则不会将该设备分配给对应进程作为控制终端
O_TRUNC 当以只写或读写方式成功打开文件时,将文件长度截断为0
O_APPEND 以追加的方式打开文件

其中编号1~3的宏必须使用,且一次只能使用1个;编号4~8的宏可有选择地使用管道符号“|”与前3个宏搭配使用。只有第二个参数flags=O_CREAT时,第三个参数才会被使用,该参数的作用是设置创建文件的权限,取值如表2所示。

表2 参数mode相关取值

mode 说明
S_IRWXU 文件所有者对文件具有读、写与执行权限
S_IRUSR 文件所有者对文件具有读权限
S_IWUSR 文件所有者对文件具有写权限
S_IXUSR 文件所有者对文件具有执行权限
S_IRWXG 文件所属组对该文件有读、写与执行权限
S_IRGRP 文件所属组对该文件有读权限
S_IWGRP 文件所属组对该文件有写权限
S_IXGRP 文件所属组对该文件有执行权限
S_IRWXO 其他人对该文件有读、写与执行权限
S_IROTH 其他人对该文件有读权限
S_IWOTH 其他人对该文件有写权限
S_IXOTH 其他人对该文件有执行权限

open()函数的返回值为一个整数,若函数调用成功,则会返回一个文件描述符,否则返回-1。

使用如下所示的open()函数可以创建一个文件:

open(pathname,O_WRONLY|O_CREAT|O_TRUNC,mode);

也可以使用系统调用中专门用于创建文件的函数——creat()函数,creat()的函数声明如下:

int creat(const char *pathname,mode_t mode);

该函数的第一个参数为路径名,第二个参数用于为文件设定权限,取值同表5-6。

creat()函数的返回值与open()函数相同,若文件创建成功,会返回一个文件描述符;否则返回-1。

2、 read()函数

read函数用于从已打开的设备或文件中读取数据,该函数存在于函数库unistd.h中,其函数声明如下:

ssize_t read(int fd, void *buf, size_t count);

read()函数基于文件描述符对文件进行操作,其中第一个参数为从open()函数或creat()函数获取的文件描述符;第二个参数为缓冲区;第三个参数为计划读取的字节数。调用read()函数后,该函数会从文件描述符fd对应的文件中读取count个字节的数据,存储到缓冲区buf中,并重新记录文件偏移量。

read()函数的返回值类型为ssize_t,表示有符号的size_t。read()函数的返回值可以是正数、0或者-1:若读取文件时出错,返回-1;若成功读取文件,则返回一个正数,该正数一般为本次请求读取的字节数,但在读取常规文件时,文件长度有限,若当前读写位置距文件末尾只有20个字节,但该函数请求读取30个字节,那么在第一次读取时,read()的返回值为20,第二次读取时,文件读写位置已在末尾,此时会返回0。

Linux系统中将一切都视为文件,因此read()函数也可以从设备或网络中读取数据。read()是一个阻塞函数,从常规文件中读取数据时,read()必定会在有限时间内返回,但从终端设备或网络端读取数据时,read()函数可能会阻塞。例如在程序中调用read()函数,该函数要求从终端读取数据,但终端写入的数据中没有回车,那么该数据就不会被传送给read()函数,read()函数就会一直阻塞;若要求read()函数从网络端读取数据,用于网络通信的socket文件中没有数据,read()函数同样会阻塞。

3、 write()函数

write()函数用于向已打开的设备或文件中写入数据,该函数存在于函数库unistd.h中,其函数声明如下:

ssize_t write(int fd, const void *buf, size_t count);

write()函数的第一个参数为文件描述符;第二个参数为需要输出的缓冲区;第三个参数为最大输出字节数。当write()函数调用成功时返回写入的字节数;否则返回-1,并设置errno。

同read()函数一样,write()在写常规文件时,会立刻返回请求写入的字节数count,但向终端或网络端写数据时,可能会进入阻塞状态。

4、 lseek()函数

每个打开的文件都有一个当前文件偏移量(current file offset),该数值是一个非负整数,表示当前文件的读写位置,Linux系统中可以通过系统调用lseek()对该数值进行修改,lseek()函数位于函数库unistd.h中,其函数声明如下:

off_t lseek(int fd, off_t offset, int whence);

lseek()函数中第一个参数fd为文件描述符;第二个参数offset用于对文件偏移量的设置,该参数值可正可负;第三个参数whence用于控制设置当前文件偏移量的方法,该参数有3个取值:

(1) 若whence为SEEK_SET,文件偏移量将被设置为offset;

(2) 若whence为SEEK_CUR,文件偏移量的值将会在当前文件偏移量的基础上加上offset;

(3) 若whence为SEEK_END,文件偏移量的值将会被设置为文件长度加上offset。

lseek()函数的返回值类型与参数offset相同,若偏移量设置成功,则会返回新的偏移量,否则返回-1。

5、 close()函数

打开的文件在操作结束后应该主动关闭,Linux系统调用中用于关闭文件的函数为close()函数,该函数的使用方法很简单,只要在函数中传入文件描述符,便可关闭文件。close()函数位于函数库unistd.h中,其声明如下:

int close(int fd);

若函数close()成功调用,则返回0,否则返回-1。

Linux系统中文件相关的5个基础I/O函数已讲解完毕,下面通过一个案例,来演示这5个函数的使用方法。

案例2:使用open()函数打开或创建一个文件,将文件清空,再使用write()函数在文件中写入数据,并使用read()函数将数据读取并打印。

案例实现如下:

 1    #include <stdio.h>
 2    #include <stdlib.h>
 3    #include <unistd.h>
 4    #include <fcntl.h>
 5    #include <string.h>
 6    int main()
 7    {
 8        int fd=0;
 9        char filename[20]="./itheima/a.txt";
 10        //打开文件
 11        fd=open(filename,O_RDWR|O_EXCL|O_TRUNC,S_IRWXG);
 12        if(fd==-1){        //判断文件是否成功打开
 13            perror("file open error.\n");
 14            exit(-1);
 15        }
 16        //写数据
 17        int i=0,len=0;
 18        char buf[100]={0};
 19        while(i<3)
 20        {
 21            scanf("%s",buf);
 22            len=strlen(buf);
 23            write(fd,buf,len);
 24            i++;
 25        }
 26        close(fd);        //关闭文件
 27        printf("---------------------\n");
 28        //读取文件
 29        fd=open(filename,O_RDONLY);            //再次打开文件
 30        if(fd==-1){
 31            perror("file open error.\n");
 32            exit(-1);
 33        }
 34        off_t f_size=0;
 35        f_size=lseek(fd,0,SEEK_END);            //获取文件长度
 36        lseek(fd,0,SEEK_SET);                //设置文件读写位置
 37        while(lseek(fd,0,SEEK_CUR)!=f_size)    //读取文件
 38        {
 39            read(fd,buf,1024);
 40            printf("%s\n",buf);
 41        }
 42        close(fd);
 43        return 0;
 44    }

使用gcc工具编译以上代码,执行二进制文件,代码运行后根据题述输入数据,运行结果如下所示:

1:itheima
2:C/C++
3:Linux
---------------------
1:itheima2:C/C++3:Linux

由运行结果可知,read()成功读取指定文件中的数据。

Linux系统调用中的I/O又被称为无缓存I/O,除此之外,在程序编写时我们还可以使用一种有缓存的I/O。有缓存的I/O又被称为标准I/O,是符合ANSI C标准的I/O处理,标准I/O有两个优点,一是执行系统调用read()和write()的次数较少;二是不依赖系统内核,可移植性强。

系统调用中的I/O虽然被称为无缓存I/O,但并不是说它的整个操作过程没有使用缓存。在用户通过read()或write()向内核发送请求时,内核会先将要读写的数据写入系统内存的缓存区中,待系统的缓存区存满时,再对数据统一进行一次操作。系统内存区缓存的存在,减少了内存与磁盘之间的读写次数。

标准I/O在用户层建立了一个流缓存区,当用户进程调用标准I/O请求执行读写操作时,要读写的数据会先被写入流缓存区,当流缓存区写满或读写完毕时,内核再通过函数调用,将其中的数据写入内存缓存区中,如此便减少了内核调用read()和write()的次数。系统I/O与标准I/O与内存缓存区的关系如图1所示。

图1 系统I/O、标准I/O与内存缓存关系示意图

结合图1,若进行写操作,对于无缓存的系统I/O,数据走过的路径为:数据——内存缓存区——磁盘;对于标准I/O,数据走过的路径为:数据——流缓存区——内存缓存区——磁盘。标准I/O中常用的接口为:fopen()、fwrite()、fread()、fseek()、fclose()、fputs()、pgets()等,这些函数与系统I/O函数的使用方法大致相同,读者可参考相关资料自行学习,此处不再赘述。

点击此处
隐藏目录