zoukankan      html  css  js  c++  java
  • 文件和目录

    • 文件系统概览

    目前,正在使用的UNIX文件系统有多种实现:

    传统的UNIX文件系统(UFS)、读写DOS格式软盘的文件系统(PCFS)、读CD的文件系统(HSFS)。

     1、UFS文件系统

    本文讨论UFS文件系统磁盘、分区和文件系统的关系见图1。

    图1 磁盘、分区和文件系统

    超级块存储对应文件系统的元数据,包括文件系统的大小、块大小、以及空闲及已使用块的数量。

    i节点图标识对应的i节点空间是否被使用。

    块位图标识对应的块是否已被使用。

     

    2、i节点和数据块的关系

    i节点和数据块的关系,如图2所示。

    图2  i节点和数据块的关系

    从图中可看到:

    • i节点包含了文件有关的所有信息:文件类型、文件访问权限位、文件长度和指向文件数据块的指针
    • 如图3所示,stat结构信息基本都是从i节点中获取的,除了文件名和i节点编号

    图3 stat结构

    • 数据块里存储的有文件数据(数据块)和目录项(目录块),目录项包含文件名i节点编号,i节点编号指向相应的i节点
    • 图2中有两个目录项指向同一个i节点,每个i节点都有一个链接计数,其值是指向该i节点的目录项数,存储在stat结构中的st_nlink成员中
    • 只有链接计数减少至0时,才可以删除文件,这也是删除一个目录项的函数被称为unlink而不是delete的原因
    • 上述链接是硬链接,符号链接与硬链接不同,符号链接文件的实际内容(数据块)包含所指向的文件的名字
    • 因为目录项中的i节点编号只能指向同一文件系统中的相应i节点,因此硬链接不能跨越文件系统;符号链接可以跨越文件系统
    • 文件重命名的过程:文件数据内容不需移动,构造一个指向现有i节点的新目录项,并删除老的目录项

    3、目录文件在文件系统的表现

    在当前工作目录中新建一个目录testdir,在文件系统中的表现如图4所示。

    编号2549的i节点为testdir目录的i节点,节点中的类型字段表示它是一个目录,该i节点指向的数据内容为目录块。

    目录块包含了两项内容,.项和..项,.项指向当前目录i节点,..项指向父目录i节点。

    编号1267的i节点为当前目录(假设为curdir)的i节点,节点中的类型字段表示它是一个目录,该i节点指向的数据内容为目录块。

    目录块包含了三项内容,.项、..项、testdir项,testdir项指向刚新建的目录testdir。

    图4 目录的链接计数

    可以看到(假设叶目录为testdir,当前工作目录为curdir,当前工作目录父目录为fatherdir:fatherdir->curdir->testdir):

    (1)每个目录文件目录块都至少包含两项目录项,.项和..项,.项指向当前目录,..项指向父目录

    (2)任何叶目录(testdir)i节点,其链接计数都是2,一个是当前目录(testdir)的.目录项,另一个是父目录(curdir)中命名它的testdir目录项

    (3)任何非叶目录(curdir)i节点,其链接计数至少是3,一个是当前目录(curdir)的.目录项,另一个是父目录(fatherdir)中命名它的curdir目录项,同时每增加一个子目录(testdir),就有一个..目录项指向它。

    • 硬链接

    1、创建硬链接

    如图2所示,任何一个文件,可以有多个目录项指向其i节点。

    这种链接属于硬链接,可使用link或linkat函数创建一个指向现有文件的链接。

    #include <unistd.h>
    
    int link(const char *existingpath, const char *newpath);
    int linkat(int efd, const char *existingpath, int nfd, const char *newpath, int flag);

    这两个函数实现的功能:

    (1)创建一个新的目录项newpath,该目录项的i节点编号指向existingpath文件的i节点

    (2)existingpath文件的i节点,链接计数st_nlink加1

    linkat函数的flag参数用来控制是否跟随符号链接。

    注:一般不允许对目录创建硬链接,如果系统支持的话,也仅限于超级用户。

    2、删除目录项

     可使用unlink函数,删除一个现有的目录项。

    #include <unistd.h>
    
    int unlink(const char *pathname);
    int unlinkat(int fd, const char *pathname, int flag);

     这两个函数实现的功能:

    (1)删除pathname目录项

    (2)pathname目录项引用的i节点,链接计数st_nlink减1

    unlink的pathname是符号链接,那么unlink删除符号链接,不跟随符号链接。这个与link函数不同。

    unlinkat函数的flag参数,当AT_REMOVEDIR标志被设置时,unlinkat可以类似与rmdir一样删除目录。

    remove函数也可以解除对一个文件或目录的链接。

    #include <stdio.h>
    
    int remove(const char *pathname);

    对于文件,remove的功能与unlink相同;对于目录,remove的功能与rmdir相同。

    注:为了解除对文件的链接,必须对包含该目录项的目录具有写和执行权限。

    文件内容被删除的条件有:

    (1)打开该文件的进程个数为0

    (2)该文件的i节点链接计数为0

    unlink的这个特性,经常被用来确保即使是在程序崩溃时,它所创建的临时文件也不会被遗留下来。

    if(open("tempfile", O_RDWR) < 0)
        err_sys("open error");
    if(unlink("tempfile" < 0)
        err_sys("unlink error");
    • 符号链接

    符号链接是对一个文件的间接指针,符号链接文件的实际内容(数据块)包含所指向的文件的名字。

    与硬链接不同,硬链接文件的目录项,直接指向文件的i节点。

    当使用以名字引用文件的函数时,需要了解函数是否跟随符号链接,图5是各个函数对符号链接的处理。

    图5 各个函数对符号链接的处理

    图5中没有列出mkdir/mkinfo/mknod/rmdir等函数,这些函数当路径名是符号链接时,都返回出错。

    以文件描述符作为参数的一些函数,对符号链接的处理是由返回文件描述符的函数(通常是open)进行的。

    chown函数是否跟随符号链接取决于实现,一般chown函数跟随符号链接。

     

    符号链接可能在文件系统中引入循环,但这样一个循环很容易消除。这也是unlink不跟随符号链接的原因。

    但是如果创建了一个构成循环的硬链接,那将很难消除,这也是link函数不允许构造指向目录的硬链接的原因。

     

    可以使用函数symlink或symlinkat创建一个符号链接

    #include <unistd.h>
    
    int symlink(const char *actualpath, const char *sympath);
    int symlinkat(const char *actualpath, int fd, const char *sympath);

    这两个函数创建一个新的目录项sympath,这个目录项指向的i节点指向的数据块为actualpath的路径。

    在创建符号链接的时候,并不要求actualpath已经存在。

     

    由于open函数跟随符号链接,需要打开该链接本身,比功能读该链接中路径名。

    readlink和readlinkat函数提供了这种功能

    #iinclude <unistd.h>
    
    ssize_t readlink(const char *restrict pathname, char *restrict buf, size_t bufsize);
    ssize_t readlinkat(int fd, const char* restrict pathname, char *restrict buf, size_t bufsize);
    • 重命名

    文件或目录可以用rename或者renameat函数进行重命名。

    #include <stdio.h>
    
    int rename(const char *oldname, const char *newname);
    int renameat(int oldfd, const char *oldname, int newfd, const char *newname);
    • 目录操作

    1、创建目录

    可以用mkdir和mkdirat函数创建目录:

    #include <sys/stat.h>
    
    int mkdir(const char*pathname, mode_t mode);
    int mkdirat(int fd, const char *pathname, mode_t mode);

    这两个函数创建一个新的空目录,其中.目录项和..目录项是自动创建的。mode指定权限位,由进程的屏蔽字修改。

    在没有mkdir函数时,进程需要调用mknod函数创建一个新目录,不过只有超级用户进程才能使用。

    2、删除目录

    可以用rmdir函数删除一个空目录,空目录只包含.目录项和..目录项:

    #include <unistd.h>
    
    int rmdir(const char *pathname);

    如果调用rmdir使目录(i节点)的链接计数为0,并且没有其他进程打开此目录,则释放此目录所占用的空间。

    如果链接计数为0,但有一个或多个进程打开此目录,则此函数返回前,删除最后一个链接及.目录项和..目录项,但不释放此目录。

    3、读目录

    读目录操作如图6所示:【将应用程序与目录格式中与实现相关的细节隔离】

    可以利用上述7个函数,去遍历文件层次结构。

    图6 读目录相关操作

    opendirfdopendir函数返回的指向DIR结构的指针由其他5个函数所使用。

    DIR结构是一个内部结构,保存当前正在被读的目录的有关信息。

    opendir执行初始化操作,是第一个readdir返回目录中的第一个目录项

    fdopendir创建时,readdir返回的第一项取决于fd相关联的文件偏移量

     

    dirent结构至少包含两个成员:

    ino_t d_ino; //i-node number
    char d_name[]; //null-terminated filename

    seekdir是设置目录项位置为loc,rewinddir是设置目录项位置重新返回开头,telldir是返回当前目录项位置。

     

    • 当前工作目录

    每个进程都有一个当前工作目录,此目录是搜索所有相对路径名的起点。

    进程调用chdir或fchdir函数可以更改当前工作目录:

    #include <unistd.h>
    
    int chdir(const char *pathname);
    int fchdir(int fd);

     因为当前工作目录是进程的一个属性,只影响调用chdir的进程本身,不影响其他进程。

    chdir跟随符号链接。

    可以调用getcwd函数获取当前工作目录完整的绝对路径名:

    #include <unistd.h>
    
    char *getcwd(char *buf, size_t size);

     缓冲区需要有足够的长度以容纳绝对路径名再加上一个终止null字节,否则返回出错。

    • 设备特殊文件

    stat结构的st_dev和st_rdev这两个字段:

    st_dev是文件系统的设备号,每个文件系统所在的存储设备都由其主、次设备号表示。

    可以使用两个宏:major和minor来访问主、次设备号。

    只有字符特殊文件和块特殊文件才有st_rdev值,此值包含实际设备的设备号。

    • 文件长度及文件截断

    1、stat结构成员st_size

     stat结构成员st_size表示以字节为单位的文件长度,如图3所示,此字段只对普通文件/目录文件/符号链接有意义。

    普通文件长度可以是0;

    目录文件长度通常是一个数(16或512的整数倍);

    符号链接文件长度是其链接文件名的实际字节数,如usr/lib。

    2、stat结构成员st_blksize和st_blocks

    st_blksize是对文件I/O较适合的块长度,为了提高效率,标准I/O库试图一次读写st_blksize个字节。

    st_blocks是所分配的实际512字节块块数【此值是不可移植的】。

    3、文件中的空洞

    普通文件可以包含空洞,空洞是由所设置的偏移量超过文件尾端,并写入了数据后造成的。

    也就是说:文件的长度大于文件所使用的磁盘空间字节块数。

    4、文件截断

    在打开文件的时候,使用O_TRUNC标志可以将长度截断为0;

    将现有文件长度截断为length:

    #include <unistd.h>
    
    int truncate(const char *pathname, off_t length);
    int ftruncate(int fd, off_t length); 

    如果文件之前长度大于length,则超过length以外的数据将不能再访问;

    如果文件以前的长度小于length,文件长度将增加,也就是有可能创建了一个空洞;

     

    • 文件时间

    图7 文件时间

    st_atim是文件数据的最后访问时间;

    st_mtim是文件数据的最后修改时间;

    st_ctim是文件i节点状态的最后更改时间,如更改文件的访问权限/更改用户ID/更改链接数等,此时文件数据没有被更改。

    ls默认(ls -l)按照文件修改时间(st_mtim)进行排序显示;

    ls -u,按照文件访问时间(st_atim)进行排序显示;

    ls -c,按照文件状态更改时间(st_ctim)进行排序显示;

    图8是各种函数对3个时间的作用:

    图8 各种函数对时间的作用

    可以用futimens/utimensat/utimes对文件访问时间(st_atim)和文件修改时间(st_mtim)进行更改。

    #include <sys/sta.h>
    
    int futimens(int fd, const struct timespec times[2]);
    int utimensat(int fd, const char *path, const struct timespec times[2], int flag);
    
    #include <sys/time.h>
    
    int utimes(const char *pathname, const struct timeval times[2]);

     

  • 相关阅读:
    HDU5320 : Fan Li
    BZOJ3069 : [Pa2011]Hard Choice 艰难的选择
    BZOJ4227 : 城市
    BZOJ4216 : Pig
    BZOJ1171 : 大sz的游戏
    BZOJ4182 : Shopping
    BZOJ3482 : [COCI2013]hiperprostor
    BZOJ3919 : [Baltic2014]portals
    BZOJ3711 : [PA2014]Druzyny
    BZOJ1580 : [Usaco2009 Hol]Cattle Bruisers 杀手游戏
  • 原文地址:https://www.cnblogs.com/songdechiu/p/10481518.html
Copyright © 2011-2022 走看看