一、文件系统

    文件系统的作用就是将文件组织成包含目录、连接等存在于物理块设备中的逻辑层次结构。它不关心底层的物理块设备的结构,当对文件进行操作时,由块设备驱动程序将对某个特定块的请求映射到正确的设备上去。
    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);