一、文件系统
文件系统的作用就是将文件组织成包含目录、连接等存在于物理块设备中的逻辑层次结构。它不关心底层的物理块设备的结构,当对文件进行操作时,由块设备驱动程序将对某个特定块的请求映射到正确的设备上去。
Ext3文件系统将整个文件系统表示成一个单一的层次树,将文件保存在大小相同的数据块中。数据块越小,磁盘空间利用率越高,但由于内核需要进行的I/O操作次数增多,运行速度有所降低;数据块越大,速度有所提高,但磁盘利用率又降低了。
Ext3通过一个inode结构来描述文件,inode结构中包含文件的存取权限、修改时间、文件类型等信息。所有inode都保存在inode表中,目录文件仅仅是存储指向inode表项索引号的一个链表,前两个入口总是.和..,表示当前目录和父目录。
二、文件描述符和流
文件描述符是打开或创建文件时Linux内核分配的一个非负整数,用来跟踪打开的文件。有3个文件描述符有特定意义:0-标准输入,1-标准输出,2-标准错误,在POSIX标准中定义了3个常量来表示这3个文件描述符:STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO。要使用这些常量,必须包含头文件<unistd.h>
流是标准C语言I/O库提出来的概念,隐藏了文件描述符,以更抽象的概念代替。用标准I/O库函数打开或创建文件时,返回一个FILE结构的指针,该结构包含了文件描述符、缓冲区地址、缓冲区大小等信息。有3个预定义的流于3个预定义的文件描述符对应:stdin,stdout,stderr,要使用这3个流必须包含头文件<stdio.h>
三、文件操作
文件操作分为系统调用和标准C库函数两种。系统调用就是操作系统提供的一些调用接口,因此与操作系统相关,而标准C库是与操作系统无关的。这里主要介绍系统调用。
1.打开/关闭文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flag);
int open(const char *pathname, int flag, mode_t mode);
int creat(const char *pathname, mode_t mode);
int close(int fd);
其中flag参数为以下值:
O_RDONLY:只读
O_WRONLY:只写
O_RDWR:读写
O_APPEND:追加
O_CREAT:文件不存在则创建,需要制定mode参数
O_EXCL:如果指定了O_CREAT并且文件已存在则返回错误
O_TRUNC:如果文件存在并且以只写方式打开,则把文件内容清空
O_NDELAY:以非阻塞方式打开。正常文件位于本地磁盘,完成读写操作的时间是确定的,因此总是阻塞的。但是当进程对某些特殊文件如FIFO进行操作时,数据到达的时间是不确定的,因此读操作需要立即返回而不是阻塞。
O_SYNC:将缓冲区的数据同步到磁盘上
O_NOFOLLOW:如果pathname是一个符号连接文件,open()函数返回一个错误
O_DIRECTORY:如果pathname不是一个目录,open()函数返回一个错误
其中mode只有在指定了O_CREAT标志时才需要,指定新创建的文件的存取权限(mode & ~umask)
S_IRUSR:拥有者读权限
S_IWUSR:拥有者写权限
S_IXUSR:拥有者执行权限
S_IRWXU:拥有者读、写、执行权限
S_IRGRP:组读权限
S_IWGRP:组写权限
S_IXGRP:组执行权限
S_IRWXG:组读、写、执行权限
S_IROTH:其他用户读权限
S_IWOTH:其他用户写权限
S_IXOTH:其他用户执行权限
S_IRWXO:其他用户读、写、执行权限
2.读/写文件
#include <unistd.h>
size_t write(int fildes, const void *buf, size_t nbytes);
size_t read(int fildes, void *buf, size_t nbytes);
3.文件定位
#include <unistd.h>
#include <sys/types.h>
off_t lseek(int fildes, off_t offset, int whence);
其中whence可以有以下3个值,与标准C库是一致的:
SEEK_SET:相对于文件开头
SEEK_CUR:相对于当前位置
SEEK_END:相对于文件结尾
4.查询文件状态信息
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
int fstat(int fildes, struct stat *buf);
int stat(const char *path, struct stat *buf);
int lstat(const char *path, struct stat *buf);
其中fstat()与stat()功能是相同的,只是参数不同。当文件是一个符号链接时,lstat()将返回这个链接的信息,而stat()将返回这个链接指向的文件的信息。
stat是一个结构体,作为参数传递,包含以下成员:
st_mode 文件权限以及文件类型信息
st_ino 文件I节点信息
st_dev 文件所在的设备信息
st_uid 文件所有者的用户标识
st_gid 文件所有者的组标识
st_atime 上一次访问的时间
st_ctime 上一次对权限,所有者,组,或是内容的修改时间
st_mtime 上一次对内容的修改时间
st_nlink 到这个文件的硬链接数目
其中st_mode成员包含了文件权限和文件类型信息。
文件权限和open()函数中的mode参数的取值相同
文件类型可以取以下值:
S_IFBLK:实体是一个特殊的块设备
S_IFDIR:实体是一个目录
S_IFCHR:实体是一个特殊的字符设备
S_IFIFO:实体是一个IFIFO(命令管道)
S_IFREG:实体是一个常规文件
S_IFLNK:实体是一个符号链接
要测试这些标志,首先要将它们取出来,就需要用到掩码(mask):
S_IFMT:文件类型
S_IRWXU:用户读/写/执行权限
S_IRWXG:组读/写/执行权限
S_IRWXO:其他用户读/写/执行权限
这些掩码的作用就是把其他位都屏蔽掉,只留下要测试的标志位。
除了通过测试文件类型标志来获得文件类型,还有一些宏可以帮助我们来进行判断,使用也非常方便,把st_mode作为参数传进去就可以了:
S_ISBLK():为特殊的块文件测试
S_ISCHR():为特殊的字符文件测试
S_ISDIR():为目录测试
S_ISFIFO():为FIFO测试
S_ISREG():为常规文件测试
S_ISLNK():为符号链接测试
举例:
struct stat statbuf;
mode_t modes;
stat("filename", &statbuf);
modes = statbuf.st_mode;
用宏来测试:
if(!S_ISDIR(modes))//判断是否不是目录
用标志位来测试:
if((modes & S_IRWXU) & S_IXUSR)//判断是否有用户执行权限
5.文件描述符与流之间的转换
#include <stdio.h>
FILE *fdopen(int filedes, const char *mode);
mode参数与fopen()函数相同
6.高级主题
#include <fcntl.h>
int fcntl(int fildes, int cmd);
int fcntl(int fildes, int cmd, long arg);
fcntl()函数可以用来查询和改变一个已被打开的文件的属性。
cmd参数可以取以下值:
F_DUPFD:复制文件描述符,与dup()/dup2()函数功能相同
F_GETFD/F_SETFD:查询/设置文件描述符标志,通常只是FD_CLOEXEC标志
F_GETFL/F_SETFL:查询/设置文件状态标志,与open()函数的第二个参数取值相同
注意:由于O_RDONLY、O_WRONLY、O_RDWR标志并不是各占一位(O_RDWR=O_RDONLY|O_WRONLY),如果要测试文件的读写权限,需要先用O_ACCMODE与返回值相与,然后再与这3个标志进行比较
另外,文件的读写权限是不能被改变的,只能改变O_APPEND、O_NONBLOCK、O_SYNC、O_ASYNC这4个标志
#include <unistd.h>
#include <sys/ioctl.h>
int ioctl(int filedes, int request, ...);
凡是其他函数不能完成的I/O操作一般都由它来完成,request参数指定要对文件进行何种操作,操作不同,第三个参数也不同:
DIOxxx:磁盘I/O
FIOxxx:文件I/O
MTIOxxx:磁带I/O
SIOxxx:套接字I/O
TIOxxx:终端I/O
7.缓冲与无缓冲
(1)完全缓冲。标准C库函数对文件的操作一般是完全缓冲。只有当I/O缓冲区被填满(写操作)或者为空(读操作)时,内核才会进行真正的I/O操作。内核一般在对第一个流进行第一次I/O操作时调用malloc()一类的函数分配缓冲区。
(2)行缓冲。当文件实际是一个终端时,一般使用行缓冲。采用行缓冲后,只有遇到换行符时才进行真正的I/O操作。注意的是,由于I/O库中的缓冲区大小有限,因此当一行很长时有可能在没有遇到换行符时就已经进行I/O操作了。
(3)无缓冲。一般标准错误都是无缓冲的,输出的任何信息都会立即反应到文件中。
设置流的缓冲类型:
#include <stdio.h>
void setbuf(FILE *fp, char *buf);
void setvbuf(FILE *fp, char *buf, int mode, size_t size);
把setbuf()函数中的buf参数设置为NULL,就是无缓冲类型。要允许缓冲,则buf必须指向一个长度至少为BUFSIZE(定义在<stdio.h>中)的缓冲区。如果该流是一个终端,则被设置为行缓冲,否则为完全缓冲。
把setvbuf()中的mode参数设置为_IONBF,则为无缓冲类型,buf和size参数被忽略。把mode参数设置为_IOFBF(完全缓冲)或_IOLBF(行缓冲)时,size指定buf所指向的缓冲区的大小。如果buf是NULL则I/O库函数会根据stat结构中的st_blksize成员自动分配一个缓冲区。如果系统无法决定该值,如该文件是一个设备或管道时,缓冲区大小就是BUFSIZE。
强制输出缓冲区内容:int fflush(FILE *fp); 如果fp为NULL,则flush所有被打开的流。
8.错误处理
出错时通过设置全局变量errno的值来指示错误原因
将错误号映射为一个字符串
#include <string.h>
char *strerror(int errnum);
将错误信息输出到标准错误流,以“s: ”为前缀
#include <stdio.h>
void perror(const char *s);