Linux文件I/O系统概述
虚拟文件系统(VFS)
Linux系统成功的关键因素之一就是具有与其他操作系统和谐共存的能力。Linux系统的文件系统由两层结构构建:第一层是虚拟文件系统(VFS),第二层是各种不同的具体的文件系统。
VFS就是把各种具体的文件系统的公共部分抽取出来,形成一个抽象层,是系统内核的一部分,它位于用户程序和具体的文件系统之间。它对用户提供了标准的文件系统调用接口,对具体的文件系统(如EXT2、FAT32等),它通过一系列的对不同文件系统公用的函数指针来实际调用具体的文件系统函数,完成实际的各有差异的操作。任何使用文件系统的程序必须经过这层接口来使用它。通过这样的方式,VFS就对用户屏蔽了底层文件系统的实现细节和差异。
VFS的作用:①对具体的文件系统的数据结构进行抽象,以一种统一的数据结构进行管理;②接受用户层的系统调用,如open()、read()、write()、stat()、link()等;③支持多种具体文件系统之间的相互访问,接受内核其他子系统的操作请求,例如,内存管理和进程调度。
VFS在linux系统中的位置如下图1所示:
通过命令:cat /proc/filesystems 可以查看系统中支持哪些文件系统
第一列说明文件系统是否需要挂接在一个块设备上。nodev表明后面的文件系统不需要挂接在块设备上。
第二列是内核支持的文件系统。
Linu中的文件及文件描述符
与windows不同,Linux操作系统都是基于文件概念的(这个很很重要啊),文件是以字符序列构成的信息载体。根据这一点,可以把I/O设备当做文件来处理。因此,与磁盘上的普通文件进行交互所用的同一系统调用可以直接用于I/O设备。这样大大简化了系统对不同设备的处理,提高了效率。
Linux中的文件主要分为4种:普通文件、目录文件、链接文件和设备文件,如下图:
内核如何区分和引用特定的文件呢?这里用到了一个重要的概念-------文件描述符。
在Linux中,所有对设备和文件的操作都是使用文件描述符来进行的。文件描述符是一个非负的整数,它是一个索引值,并指向在内核中每个进程打开文件的记录表。当打开一个现存文件或创建一个新文件时,内核就向程返回一个文件描述符;当需要读写文件时,也需要把文件描述符作为参数传递给相应的函数。(咱可以这样理解,只有当对文件进行操作时,该文件才会有文件描述符,进程没有用到的文件统统不给描述符)
通常,一个进程启动时,都会打开3个文件:标准输入、标准输出和标准出错处理。这3个文件分别对应描述符为0、1和2(也就是宏替换 STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO)。
基于文件描述符的I/O操作虽然不能直接移植到类Linux以外的系统上(如Windows),但它往往是实现某些I/O操作的唯一途径,如Linux中底层文件操作函数(下边会讲)、多路I/O、TC[/IP套接字编程接口等、同时,它们也很好地兼容Posix标准,因此,可以很方便地移植到任何Posix平台上。基于文件描述符的I/O操作是Linux中最常用的操作之一,下面就讲讲它。
底层文件I/O操作
这次主要介绍文件I/O操作的系统调用,主要用到5个函数:open()、read()、write()、lseek()和close()。这些函数的特点不带缓存,直接对文件(包括设备)进行读写操作。
1、基本文件操作
函数说明
● open()函数用于打开或创建文件,在打开或者创建文件时可以指定文件的属性及用户的权限等各种参数。
● close()函数用于关闭一个被打开的文件。当一个进程终止时,所有被它打开的文件都由内核自动关闭,很多程序都使用这一功能而不显示地关闭一个文件。
● read()函数用于将从指定的文件中读出的数据放到缓存区中,并返回实际读入的字节数。若返回0,则表示没有数据可读,即已到达文件尾。读操作从文件的当前指针位置开始。当从设备文件中读出数据时,通常一次最多读一行。
● write()函数用于向打开的文件写数据。写操作从文件的当前指针位置开始,对磁盘文件进行写操作,若磁盘已满或者超出该文件的长度,则write()函数返回失败。
● lseek()函数用于在指定的文件描述符中将文件指针定位到相应的位置。每一个已打开的文件都有一个读写位置,当打开文件时,其读写位置通常指向文件开头;若是以附加的方式打开文件(如O_APPEND),则读写位置会指向文件尾。当read()或者write()时,读写位置会随之增加,lseek()便是用来控制该文件的读写位置的。它只能用在可定位(可随机访问)文件操作中。管道、套接字和大部分字符设备文件是不可定位的,所以在这些文件的操作中无法使用lseek()调用。
函数格式
下面我以表格的形式将这5个函数的格式写出来,接下来再加上我的基础实验。
在open()函数中,flag参数可通过 “|” 组合构成,但前3个标志常量(O_RDONLY、O_WRONLY及O_RDWR)不能相互组合。perms是文件的存取权限,既可以用宏定义表示法,也可以用八进制表示法。
在读普通文件时,若读到要求的字节数前已到达文件的尾部,则返回的字节数会小于希望读书的字节数。
关于size_t和ssize_t的区别,如果有不懂的请看:http://blog.csdn.net/mybelief321/article/details/8992052,也可以简单的记住:ssize_t是有符号整形,size_t是无符号整形。
在写普通文件时,写操作从文件的当前指针位置开始。
下面是lseek较特别的使用
☆ 欲将读写位置移到文件开头时:lseek(int fd,0,SEEK_SET)
☆ 欲将读写位置移到文件尾时:lseek(int fd,0,SEEK_END)
☆ 想要取得目前文件位置时:lseek(ind fd,0,SEEK_CUR)
注意:Linux系统不允许lseek()对tty装置作用,此动作会令lseek()返回ESPIPE。
另外,其实还有一个文件创建函数creat(),它的函数原型是int creat(const char *pathname,int perms),它相当于使用下列的调用方式调用open():
open(const char *pathname,(O_CREAT|O_WRONLY|O_TRUNC));
基础实验1:
实验说明:主要是为了演示open()函数的使用方法。首先在自己的目录下使用命令:vi
open.c创建一个文件,如下图,我在路径/home/song/lianxi文件夹下创建的:
然后编写open.c文件内容,内容如下:
编辑并保存open.c后的文件夹所含全部文件,如下:
使用命令:gcc open.c -o open编译c文件,如下:
执行命令:./open 可以看到咱们的实验成功输出了:
使用命令:more temp查看一下temp的文件,里边有咱们使用write()写的内容:
我将这个文件内容上传到了:点此下载,可以自行下载
基础实验2
实验说明:基本功能是从一个文件(源文件)中读取最后2KB数据并复制到另一个文件(目标文件)。在实例中源文件是以只读方式打开的,目标文件是以只写方式(可以使读/写方式)打开的。若目标文件不存在,可以创建并设置权限的初始值为644,即文件所有者可读可写,文件所属组合其他用户只能读。
首先先后使用命令:vi copy_file.c和 vi src.c 在自己的实验目录下创建文件,如下图
然后再编辑copy_file.c的文件内容,如下
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>#define Buffer_Size 1024 /*每次读写缓存大小为1KB,大小不同,运行效率不同*/
#define Src_File_Name "/home/song/lianxi/src.c" /*源文件名,建议使用宏定义*/
#define Dest_File_Name "/home/song/lianxi/dest.c" //目标文件名*/
#define Offset 1024*2 //复制的数据大小,这里为2KB*/int main()
{
int src_fd,dest_fd; /*文件描述符*/
unsigned char buff[Buffer_Size]; /*定义用于缓冲数据的数组*/
int real_read_len; /*read()函数实际读取到的字节*/
/*以只读方式打开源文件*/
src_fd=open(Src_File_Name,O_RDONLY);
/* 以只写方式打开目标文件,若此文件不存在则创建该文件,访问权限为644*/
dest_fd=open(Dest_File_Name,O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
/*如果打开文件出错,则退出程序*/
if(src_fd<0||dest_fd<0)
{
printf("Open file error\n");
exit(1);
}
/* 将源文件的读/写指针移到最后2KB的起始位置*/
lseek(src_fd,-Offset,SEEK_END);
/* 读取源文件的最后2KB数据并写到目标文件中,每次读写1KB*/
while((real_read_len=read(src_fd,buff,sizeof(buff)))>0)
{
write(dest_fd,buff,real_read_len);
}
/* 关闭文件,释放资源*/
close(dest_fd);
close(src_fd);
return 0;
}
其中src.c的文件内容你可以自己随便的放内容,但是要注意文件内容要大于2KB。
这两个文件我上传到资源网站,可以自行下载:点此下载
编辑完之后,使用命令: gcc copy_file.c -o copy_file 编译文件,然后执行命令:./copy_file,可以看到自动生成了dest.c文件
使用命令:more dest.c可以看到文件的内容
使用命令:ls -l dest.c可以看到该文件的大小正好为2KB(2048)