【Linux】深入剖析Linux文件系统

 🔥博客主页: 我要成为C++领域大神
🎥系列专栏【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】
❤️感谢大家点赞👍收藏⭐评论✍️

本博客致力于分享知识,欢迎大家共同学习和交流。

磁盘与扇区

下图是电脑中的磁盘,一圈一圈的就是磁道,而磁道又用竖线分成一条条扇区,每个扇区都是512Byte。不管是外面的扇区还是里面的扇区都只能装512Byte的数据,密度不同而已。

在安装系统时,系统会对磁盘进行格式化,将磁盘分成若干BLOCK(块),BLOCK大小可以设置为1024 2048 4096,默认4096--4kb IO块。每一个块是一个操作单元。stat查看Block大小。1Block=8磁盘扇区 1磁盘扇区=512byte 1Block=8*512*8bit=32768bit。多个Block用Block Group管理。

下图是ext2文件系统的的Block Group

启动块

Boot Block 位于存储介质的起始位置,记录了磁盘分了几个区,用于存储引导加载程序(Bootloader)和引导记录(Boot Record)等引导信息。Boot Block 的完整性和正确性对于系统的启动和运行至关重要。任何对 Boot Block 的损坏或篡改都可能导致系统无法正常启动。

超级块

超级块(Superblock)只有一个块,占用了4kb,是文件系统中的一个重要数据结构,用于存储关于文件系统的元数据信息。超级块通常位于文件系统的开头。

超级块包含了文件系统的重要信息,例如:

  1. 文件系统的类型(如 ext4、NTFS 等)。
  2. 文件系统的总容量、已用空间和可用空间。
  3. 记录文件系统的块大小和节点大小。
  4. 文件系统的块组信息(如果适用)。
  5. 文件系统的mount时间和最近一次检查时间。
  6. 其他与文件系统相关的元数据信息。

超级块的损坏或丢失可能会导致文件系统无法正常挂载或访问,因此它通常会被文件系统备份并存储在多个位置。文件系统工具通常会使用超级块来识别文件系统并执行文件系统的检查和修复操作。

GDT

块组描述符表(Block Group Descriptor Table)是在 ext2、ext3 和 ext4 等文件系统中用于存储关于文件系统块组的信息的数据结构。

块组描述符表存储了每个块组的描述符

  1. Block bitmap起始位置。
  2. 块组中空闲inode(理论存储文件数量)和已分配inode的数量。
  3. inode bitmap起始位置。
  4. inode table起始位置。
  5. data block起始位置。

块组描述符表通常存储在文件系统的固定位置,以便文件系统在挂载时能够快速访问。它是文件系统的元数据之一,与超级块(Superblock)一起用于管理文件系统的布局和分配。

data block

数据块(Data Block),用于存储文件内容的基本单位。在文件系统中,文件被分割成一个个数据块进行存储。

例如当我们写入"Hello World"到文件中去,系统会从数据块中找一个未被使用的块,来存放我们的文件内容。

inode table

inode table(节点表),用于inode信息的表。每个文件系统都有一个inode table

每个inode包含了文件属性数据块指针(存放文件内容的地址),(类似于链接)

每个inode节点是128byte,一个块是4KB,一个块可以放32个inode,每个文件都需要一个inode

数据块指针

32位操作系统下,一个指针占4个字节。数据块指针可以存放15个指针,序号0~11的指针,一个指针指向一个数据块,有12个data block,即48kb。当文件超过48kb时,就要用到序号12的指针了。序号12的指针不用来存放文件内容,用来存放文件地址,每一个地址都指向一个数据块(4KB/4B=1024个地址,即指向1024个数据块),大小最大是4MB,存放文件内容。

block[12]一级间接寻址,可以指向4MB
block[13]二级间接寻址,指向一个块,块内存放地址,每一个地址都再指向一个块,块内存放文件地址,每一个地址都指向一个数据块存放文件内容,大小最大是4GB
block[14]三级间接寻址,同上,大小最大是4T

所以,ext2文件系统存放的文件大小最大4TB+4GB+4MB

inode bitmap

inode bitmap(inode位图),用于记录inode的分配情况。在Unix文件系统中,每个文件和目录都对应一个inode,inode bitmap记录了文件系统中每个inode的分配情况,即哪些inode已经被分配给文件或目录,哪些inode是空闲的。

inode bitmap是一个位图,其中的每个位对应一个inode。如果某个位被设置为1,则表示对应的inode已经被分配;如果被设置为0,则表示该inode是空闲的,可以被分配给新创建的文件或目录。

当需要创建新的文件或目录时,文件系统会在inode bitmap中查找空闲的inode,并将其分配给新的文件或目录。同样,当文件或目录被删除时,相应的inode会被释放,并在inode bitmap中标记为空闲状态

block bitmap

Block bitmap(块位图),用于记录block的分配情况。在Unix文件系统中,文件和目录的数据被存储在数据块中,而块位图记录了文件系统中每个数据块的分配情况,即哪些数据块已经被分配给文件或目录,哪些数据块是空闲的。

块位图是一个位图,其中的每个位对应一个数据块。如果某个位被设置为1,则表示对应的数据块已经被分配;如果被设置为0,则表示该数据块是空闲的,可以被分配给新创建的文件或目录。

当需要为新创建的文件或目录分配存储空间时,文件系统会在块位图中查找空闲的数据块,并将其分配给新的文件或目录。同样,当文件或目录被删除时,相应的数据块会被释放,并在块位图中标记为空闲状态

文件存储流程

1、先来到BootBlock,找到磁盘的起始位置

2、来到GDT块组描述符表,找到Block bitmap起始位置,inode bitmap起始位置。inode table起始位置。data block起始位置。

3、从inode bitmap找一个空闲的位置(0),在inode table中找到对应的节点。将文件属性写入到inode节点内,将inode bitmap对应标志位变为1,代表节点被使用。

4、从block bitmap找一个空闲的位置(0),将我们的文件内容写入到data block,并将bitmap对应标志位置1。将我们data block内存放数据的地址写入到刚才使用的inode节点的数据块指针里。

这样我们的文件就保存完了。

读文件流程

1、先来到BootBlock,找到磁盘的起始位置

2、来到GDT块组描述符表,找到Block bitmap起始位置,inode bitmap起始位置。inode table起始位置。data block起始位置。

3、找到inode table,当我们读取文件时,文件夹里会存放文件的inode编号,可以根据inode编号进行偏移,找到文件对应的inode,获取到文件属性和数据块指针。

删除文件流程

当我们在进行拷贝一个较大的文件时,可能需要很久,10分钟20分钟。但是同样的文件,当我们进行删除时却很快,为什么?

1、先来到BootBlock,找到磁盘的起始位置

2、来到GDT块组描述符表,找到Block bitmap起始位置,inode bitmap起始位置

3、将Block bitmap,inode bitmap对应标志位置0即可,删除完成。

实际文件占用的空间、存储的数据还存放在我们的磁盘中,只要我们未使用到这块磁盘空间,这部分数据就还在。除非我们存储一个较大的文件时,才会覆盖到这部分磁盘;或者格式化磁盘。

同样的,浏览器的无痕浏览也不是真的“无痕”,只是存放在服务器内。

文件记录项

每个文件夹都有一个对应的文件去存储这个文件夹的信息和文件里面所有文件的相关信息,叫做记录项。每创建一个空的文件夹,文件夹大小都是4K,一个block。

文件记录项包含 文件名 inode号 文件类型 记录长度,根据inode号可以从inode table找到对应文件属性和数据块指针,进而进行读写操作。

vi命令不仅可以打开文件,也可以打开文件夹。

可以看到文件夹的路径,文件夹的创建者,所有文件记录项的名字,还有文件夹下面的所有列表

可以在横线停留的地方按下回车,进入文件列表

如何查看文件编号

stat +文件名/文件夹名

Access:最近访问时间
Modify:最近更改内容时间
Change:最近修改文件属性时间

这里我们创建了一个对aa的软链接和硬链接

用stat命令查看一下aa aah 和aas的信息

硬链接没有创建新的inode,直接使用链接的inode

硬链接和符号链接的区别:

硬链接在文件系统中共享相同的inode,也就是说,所有硬链接的文件名都指向相同的inode。一个文件的多个硬链接实际上是同一个文件,它们都指向相同的物理数据块。

符号链接有自己的inode和数据块,其数据块中存储的是目标文件或目录的路径。

stat函数

stat用于获取文件的详细信息,包括文件的属性、大小、权限和时间等。

函数原型

#include <sys/stat.h>
#include <unistd.h>

int stat(const char *pathname, struct stat *buf);//根据文件路径查看
int fstat(int fd, struct stat *buf);//根据文件描述符查看
int lstat(const char *pathname, struct stat *buf);//不跟踪符号链接

参数

pathname:文件的路径。

fd:文件描述符(仅用于fstat函数)。

buf:指向struct stat结构体的指针,用于存储文件的信息。

结构体 stat

struct stat是一个包含文件信息的结构体,其定义如下:

struct stat {
    dev_t     st_dev;         /* 文件所在设备的ID */
    ino_t     st_ino;         /* inode号 */
    mode_t    st_mode;        /* 文件的类型和权限 */
    nlink_t   st_nlink;       /* 硬链接数 */
    uid_t     st_uid;         /* 所有者用户ID */
    gid_t     st_gid;         /* 所有者组ID */
    dev_t     st_rdev;        /* 设备ID(如果是特殊文件) */
    off_t     st_size;        /* 文件大小(以字节为单位) */
    blksize_t st_blksize;     /* 文件系统I/O块大小 */
    blkcnt_t  st_blocks;      /* 分配给文件的块数 */
    time_t    st_atime;       /* 最后访问时间 */
    time_t    st_mtime;       /* 最后修改时间 */
    time_t    st_ctime;       /* 最后状态改变时间 */
};

返回值

成功时返回0。

失败时返回-1,并设置errno来指示错误。

demo

用stat函数追踪符号链接不追踪符号链接查看文件

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
int main(){

        struct stat st;
        if(stat("aas",&st)<0){
        perror("stat fail");
        return -1;
        }
    //追踪查看
        printf("size=%d,inode=%d\n",st.st_size,st.st_ino);

        if(lstat("aas",&st)<0){
        perror("stat fail");
        return -1;
        }
    //不追踪查看
        printf("size=%d,inode=%d\n",st.st_size,st.st_ino);
        return 0;
}

access函数

access用于检查调用进程对指定文件的访问权限。它可以用来确定当前用户是否具有读取、写入或执行某个文件的权限。

函数原型

#include <unistd.h>

int access(const char *pathname, int mode);

参数

pathname:文件的路径。

mode:指定要检查的访问权限。可以是以下常量的组合:

  • F_OK:检查文件是否存在。
  • R_OK:检查文件是否可读。
  • W_OK:检查文件是否可写。
  • X_OK:检查文件是否可执行。

返回值

成功时返回0。

失败时返回-1,并设置errno来指示错误。

demo

#include<stdio.h>
#include<unistd.h>

int main(){

        if(access("sda",F_OK)<0){
        perror("access fail");
                return -1;
        }
        return 0;
}

当前目录下没有叫sda的文件,所以会提示错误信息

chmod函数

chmod函数用于更改文件的权限,修改文件或目录的访问权限。

函数原型

#include <sys/stat.h>

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

参数

pathname:指向要修改权限的文件或目录的路径。

mode:新文件权限的位掩码。可以是以下常量的组合,定义在 <sys/stat.h> 中:

  • S_IRUSR(用户读权限)
  • S_IWUSR(用户写权限)
  • S_IXUSR(用户执行权限)
  • S_IRGRP(组读权限)
  • S_IWGRP(组写权限)
  • S_IXGRP(组执行权限)
  • S_IROTH(其他用户读权限)
  • S_IWOTH(其他用户写权限)
  • S_IXOTH(其他用户执行权限)

返回值

成功时返回0。

失败时返回-1,并设置errno来指示错误。

demo

下面是用chmod函数实现对一个文件权限的修改:

#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
int convertMod(int o){
        int mod;
        int count=1;
        while(o>0){

                mod+=o%10*count;
                count*=8;
                o/=10;
        }
        printf("mod=%d\n",mod);
        return mod;
}

int main(int argc,char *argv[]){
        //参数不合法
        if(argc < 3){
        printf("paramater error\n");
        return -1;
        }
        //判断文件是否存在
        if(access(argv[2],F_OK)){
        perror("access fail");
        return -1;
        }
        else {
        //文件存在
        //将第2个参数转化为数字
        int mod=atoi(argv[1]);//十进制
        //把八进制的777转换成十进制511
        mod=convertMod(mod);
        //修改文件权限
        if(chmod(argv[2],mod)<0){
        perror("change fail");
        return -1;
        }
        }



        return 0;
}

编译运行:

当我们输入参数数量不够时会报错

我们对aa文件的权限进行修改:

修改成功!

chown函数

更改文件的所有者和所有者组

函数原型

int chown(const char *path, uid_t owner, gid_t group); 
int fchown(int fd, uid_t owner, gid_t group); 
int lchown(const char *path, uid_t owner, gid_t group);

chown函数使用时,必须要用root权限

参数

path: 指向文件路径名的指针。

owner: 新的文件所有者的用户ID。如果不想更改所有者,可以将其设置为-1。

group: 新的文件所有者组的组ID。如果不想更改组,可以将其设置为-1。

返回值

成功时返回0。

失败时返回-1,并设置全局变量errno来表示错误的具体原因。

demo

使用chown函数来更改文件的所有者和组:

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <errno.h>

int main(int argc ,char *argv[]) {
    uid_t newOwner = atoi(argv[2]); // 新的所有者ID
    gid_t newGroup = atoi(argv[3]); // 新的组ID

    // 更改文件所有者和组
    if (chown(argv[1], newOwner, newGroup) == -1) {
        perror("chown failed");
        return 1;
    }

    printf("File ownership changed successfully.\n");
    return 0;
}

utime函数

utime函数可以修改文件的访问和修改时间。

函数原型

#include <utime.h>
int utime(const char *filename, const struct utimbuf *times);

参数

filename: 指向要更改时间戳的文件路径的指针。

times: 指向struct utimbuf结构体的指针。如果传递NULL,则文件的时间戳会被设置为当前时间。

结构体 utimbuf

该结构体用于指定文件的新时间:

struct utimbuf {
    time_t actime;  // 文件的最后访问时间
    time_t modtime; // 文件的最后修改时间
};

返回值

成功时返回0。

失败时返回-1,并设置全局变量errno来表示错误的具体原因。

demo

使用utime函数来更改文件的访问和修改时间:

#include <stdio.h>
#include <utime.h>
#include <time.h>
#include <errno.h>

int main(int argc ,char *argv[]) {
    // 创建一个文件
    FILE *file = fopen(argv[1], "w");
    if (file == NULL) {
        perror("Failed to create file");
        return 1;
    }
    fprintf(file, "Hello, World!\n");
    fclose(file);

    // 设置新的访问和修改时间
    struct utimbuf new_times;
    new_times.actime = time(NULL) - 86400; // 设置为当前时间的前一天
    new_times.modtime = time(NULL) - 86400; // 设置为当前时间的前一天

    if (utime(argv[1], &new_times) == -1) {
        perror("utime failed");
        return 1;
    }

    printf("File times updated successfully.\n");
    return 0;
}

文件不存在则创建文件。文件存在则对时间进行修改

truncate函数

truncate 用于改变一个文件的大小

如果文件比新的大小大,则超出的部分将被删除。如果文件比新的大小小,则在文件末尾添加空字节以扩展文件。

函数原型

#include <unistd.h>

int truncate(const char *path, off_t length);

参数

path:指向要截断的文件路径的指针。

length:文件的新大小,以字节为单位。

返回值

成功时返回0。

失败时返回-1,并设置 errno 来指示错误。

demo

使用 truncate 函数来更改文件的大小:

#include <stdio.h>
#include <unistd.h>

int main(int argc ,char *argv[]) {
    off_t new_length = 100; // 新文件大小为 100 字节

    if (truncate(argv[1], new_length) == 0) {
        printf("Successfully truncated file to %ld bytes\n", (long)new_length);
    } else {
        perror("truncate failed");
    }

    return 0;
}

rename函数

rename 用于更改文件或目录的名称

函数原型

#include <stdio.h>

int rename(const char *oldpath, const char *newpath);

参数

oldpath:指向现有文件或目录路径的指针。

newpath:指向新名称路径的指针。

返回值

成功时返回 0。

失败时返回 -1,并设置 errno 来指示错误。

demo

#include <stdio.h>

int main(int argc ,char *argv[]) {
    if (rename(argv[1], argv[2]) == 0) {
        printf("Successfully renamed file to %s\n",argv[2]);
    } else {
        perror("rename failed");
    }

    return 0;
}

chdir函数

chdir 函数用于更改当前进程工作目录

函数原型

#include <unistd.h>

int chdir(const char *path);

参数

path:指向新工作目录路径的指针。

返回值

成功时返回 0。

失败时返回 -1,并设置 errno 来指示错误。

demo

使用 chdir 函数来更改当前工作目录:

#include <stdio.h>
#include <unistd.h>

int main(int argc ,char *argv[]) {
    if (chdir(argv[1]) == 0) {
        printf("Successfully changed working directory to %s\n", argv[1]);
    } else {
        perror("chdir failed");
    }

    return 0;
}

path函数

pathconf 用于获取与文件系统路径相关的配置信息或限制

函数原型

#include <unistd.h>

long pathconf(const char *path, int name);

参数

path:指向文件路径的指针。

name:指定要查询的配置信息或限制,可以是以下常量之一:

  • _PC_LINK_MAX:目录中的最大链接数。
  • _PC_MAX_CANON:终端设备的最大规范输入行长度。
  • _PC_MAX_INPUT:终端设备的最大输入长度。
  • _PC_NAME_MAX:文件名的最大长度。
  • _PC_PATH_MAX:路径的最大长度。
  • _PC_PIPE_BUF:管道缓冲区的大小。
  • _PC_CHOWN_RESTRICTED:是否可以更改文件所有权。
  • _PC_NO_TRUNC:文件名长度超过限制时是否被截断。
  • _PC_VDISABLE:终端设备上禁用字符的值。

返回值

成功时返回指定配置信息或限制的值。

失败时返回 -1,并设置 errno 来指示错误。

demo

#include <stdio.h>
#include <unistd.h>

int main(int argc ,char *argv[]) {
    long name_max;
    name_max = pathconf(argv[1], _PC_NAME_MAX);
    if (name_max == -1) {
        perror("pathconf");
    } else {
        printf("Maximum file name length: %ld\n", name_max);
    }
    return 0;
}

link函数

link 用于创建一个新的硬链接到现有文件。(与之对应的是symlink,创建符号链接)

函数原型

#include <unistd.h>

int link(const char *oldpath, const char *newpath);

参数

oldpath:指向现有文件路径的指针。

newpath:指向新硬链接路径的指针。

返回值

成功时返回 0。

失败时返回 -1,并设置 errno 来指示错误。

demo

#include <stdio.h>
#include <unistd.h>

int main(int argc ,char *argv[]) {
    if (link(argv[1], argv[2]) == 0) {
        printf("Successfully created a hard link: %s -> %s\n", argv[1], argv[2]);
    } else {
        perror("link failed");
    }

    return 0;
}

readlink函数

readlink 用于读取符号链接所指向的目标路径。

  1. 如果是符号链接,删除符号链接
  2. 如果是硬链接,硬链接数减1,当减为0时,释放数据块和inode
  3. 如果文件硬链接数为0,但有进程已打开该文件,并持有文件描述符,则等该进程关闭该文件时,kernel才真正 去删除该文件
  4. 利用该特性创建临时文件,先open或creat创建一个文件,马上unlink此文件

函数原型

#include <unistd.h>

ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);

参数

pathname:指向符号链接路径的指针。

buf:用于存储符号链接目标路径的缓冲区。

bufsiz:缓冲区的大小。

返回值

成功时返回存储在 buf 中的字节数,不包括终止的空字符。

失败时返回 -1,并设置 errno 来指示错误。

demo

#include <stdio.h>
#include <unistd.h>

int main(int argc ,char *argv[]) {
    char buf[100];
    ssize_t len;
    len = readlink(argv[1], buf, sizeof(buf) - 1);
    if (len != -1) {
        buf[len] = '\0'; // Null-terminate the buffer
        printf("Symbolic link points to: %s\n", buf);
    } else {
        perror("readlink");
    }

    return 0;
}

unlink函数

unlink 用于删除文件。它实际上不删除文件数据,而是删除文件名(即目录中的目录项),从而减少文件的链接计数。当链接计数降到零并且没有进程打开该文件时,文件的数据块将被释放。

函数原型

#include <unistd.h>

int unlink(const char *pathname);

参数

pathname:指向要删除的文件路径名的指针。

返回值

成功时返回 0。

失败时返回 -1,并设置 errno 来指示错误。

demo

#include <stdio.h>
#include <unistd.h>

int main(int argc,char *argv[]) {
    int result;
    result = unlink(argv[1]);
    if (result == 0) {
        printf("File deleted successfully.\n");
    } else {
        perror("unlink");
    }
    return 0;
}

  • 30
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值