Ⅰ. ext2文件系统

Linux系统编程笔记(5)——文件与I/O(4)-萤火
  1. 文件系统中存储的最小单位是块(Block),一个块究竟多大是在格式化时确定的,例如 mke2fs 的-b 选项可以设定块大小为 1024、2048 或 4096 字节。
  2. 启动块(Boot Block)
    大小就是 1KB,由PC标准规定,用来存储磁盘分区信息和启动信息,任何文 件系统都不能使用该块。
  3. 超级块(Super Block)
    描述整个分区的文件系统信息,例如块大小、文件系统版本号、上次 mount 的时间等等。超级块在每个块组(Blcok Group)的开头都有一份拷贝。
  4. 块组描述符表(GDT, Group Descriptor Table)
    由很多块组描述符组成,整个分区分成多少个块组就对应有多少个块组描述符。每个块组描述符存储一个块组的描述信息,包括 inode 表(inode table)哪里开始,数据块(Data Blocks)哪里开始,空闲的 inode 和数组块还有多少个等。块组描述符表在每个块组的开头也都有一 份拷贝,这些信息是非常重要的,因此它们都有多份拷贝。
  5. 块位图(Block Bitmap)
    块位图就是用来描述整个块组中哪些块已用哪些块空闲的,本身占一个块,其中的每个bit代表本块组中的一个块,这个bit为1表示该块已用,这个bit为0表示该块空闲可用。
  6. inode位图(inode Bitmap)
    和块位图(Block Bitmap)类似,本身占一个块,其中每个bit表示一个inode是否空闲可用。
  7. inode表(inode Table)
    文件类型(常规、日录、符号链接等) ,权限,文件大小,创建/修改/访问时间等信息存在inode中,每个文件都有一个inode。
  8. 数据块(Data Block)
    • 常规文件:文件的数据存储在数据块中。
    • 目录:该日录下的所有文件名和目录名存储在数据块中。(注意:文件名保存在它所在目录的数据块中,其它信息都保存在该文件的inode中,可以通过存储的inode地址获取到)
    • 符号链接:如果目标路径名较短则直接保存在inode中以便更快地查找,否则分配一个数据块来保存
    • 设备文件、FIFO和socket等特殊文件:没有数据块,设备文件的主设备号和次设备号保存在inode中

Ⅱ. stat(2)

读取文件的inode,然后把inode中的各种文件属性填入一个struct stat结构体传出给调用者。stat(1)命令是基于stat函数实现的。stat需要根据传入的文件路径找到inode

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

int stat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);

假设一个路径是/opt/file,则查找的顺序是:

  1. 读出inode表中第2项,也就是根目录的inode,从中找出根目录数据块的位置
  2. 从根目录的数据块中找出文件名为opt的记录,从记录中读出它的inode号
  3. 读出opt目录的inode,从中找出它的数据块的位置
  4. 从opt目录的数据块中找出文件名为file的记录,从记录中读出它的inode号
  5. 读出file文件的inode
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char* argv[]) {
    struct stat st;

    if (argc < 2) {
        printf("usage: cmd + filename/dirname\n");
    }

    stat(argv[1], &st);
    // if (S_ISDIR(st.st_mode)) {
    //     printf("direcory\n");
    // } else {
    //     printf("other file type\n");
    // }
    switch (st.st_mode & S_IFMT) {
        case S_IFBLK:
            printf("block device\n");
            break;
        case S_IFCHR:
            printf("character device\n");
            break;
        case S_IFDIR:
            printf("directory\n");
            break;
        case S_IFIFO:
            printf("FIFO/pipe\n");
            break;
        case S_IFLNK:
            printf("symlink\n");
            break;
        case S_IFREG:
            printf("regular file\n");
            break;
        case S_IFSOCK:
            printf("socket\n");
            break;
        default:
            printf("unknown?\n");
            break;
    }

    return 0;
}

Ⅲ.其它

下面的系统调用都和inode有关:

  1. access(2)
  2. chmod(2) / fchmod(2)
  3. chown(2) / fchown(2) / lchown(2)
  4. utime(2)
  5. truncate(2) / ftruncate(2)
  6. link(2)
  7. unlink(2)
  8. rename(2)
  9. readlink(2)
  10. mkdir(2)
  11. rmdir(2)

Ⅳ. opendir(3)

opendir()用来打开参数name指定的目录,并返回一个DIR *指针代表这个目录,它是一个类似FILE *指针的句柄,接下来对目录的读取和搜索都要使用此返回值。

#include<sys/types.h>
#include<dirent.h>

DIR *opendir(const char *name);
  1. 返回值:成功则返回DIR* 指针,打开失败则返回NULL并设置errno。
  2. name:路径名

Ⅴ. readdir(3)

readdir()返回参数dir目录流的下个目录进入点。 结构体struct dirent定义如下:

struct dirent {
    ino_t          d_ino;       /* Inode number */
    off_t          d_off;       /* Not an offset; see below */
    unsigned short d_reclen;    /* Length of this record */
    unsigned char  d_type;      /* Type of file; not supported
                                   by all filesystem types */
    char           d_name[256]; /* Null-terminated filename */
};
  1. d_ino:此目录进入点的inode
  2. d_off:目录文件开头至此目录进入点的位移
  3. d_reclen:该记录的长度,不包含NULL字符
  4. d_type:d_name所指的文件类型,不是所有文件系统类型都被支持
  5. d_name:文件名
#include<sys/types.h>
#include<dirent.h>

struct dirent *readdir(DIR *dir);

Ⅵ.closedir(3)

closedir用于关闭使用opendir(3)返回的DIR* 句柄

#include<sys/types.h>
#include<dirent.h>

int closedir(DIR *dir);
  1. 返回值:成功返回0,失败返回-1并设置errno
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>

/*
打印目录argv[1]下的所有文件和文件夹
*/
int main(int argc, char* argv[]) {
    if (argc < 2) {
        printf("usage: cmd + path\n");
        return -1;
    }

    DIR* dir;
    struct dirent* dp;

    dir = opendir(argv[1]);
    if (!dir) {
        perror("opendir");
        exit(-1);
    }

    while (dp = readdir(dir)) {
        printf("%s\t", dp->d_name);
    }
    putchar(10);

    closedir(dir);

    return 0;
}
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

void printDir(char* dir_name) {
    DIR* dir;
    struct dirent* dp;
    char path_name[1024];
    struct stat st;

    dir = opendir(dir_name);
    if (!dir) {
        perror("opendir");
        exit(-1);
    }

    while (dp = readdir(dir)) {
        if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) {
            continue;
        }

        sprintf(path_name, "%s/%s", dir_name, dp->d_name);
        if (stat(path_name, &st) < 0) {
            perror("stat");
            exit(-2);
        }
        if (S_ISDIR(st.st_mode)) {
            printDir(path_name);
        }

        printf("%s\t", dp->d_name);
    }
    putchar(10);

    closedir(dir);
}
/*
递归打印目录argv[1]下的所有文件和文件夹
*/
int main(int argc, char* argv[]) {
    if (argc < 2) {
        printf("usage: cmd + path\n");
        return -1;
    }
    printDir(argv[1]);

    return 0;
}

Ⅶ. VFS

Linux支持各种各样的文件系统格式,然而这些文件系统都可以mount到某个目录下,使我们看到一个统一的目录树,各种文件系统上的目录和文件我们用ls命令看起来是一样的,读写操作用起来也都是一样的,这是怎么做到的呢? Linux内核在各种不同的文件系统格式之上做了一个抽象层,使得文件、目录、读写访问等概念成为抽象层的概念,因此各种文件系统看起来用起来都一样,这个抽象层称为虚拟文件系统(VFS, VirtualFileSystem)。

Linux系统编程笔记(5)——文件与I/O(4)-萤火

Ⅷ. dup / dup2

dup和dup2都可用来复制一个现存的文件描述符,使两个文件描述符指向同一个file结构体。如果两个文件描述符指向同一个file结构体, File Status Flag和读写位置只保存一份在file结构体中,并且file结构体的引用计数是2。如果两次open同一文件得到两个文件描述符,则每个描述符对应一个不同的file结构体,可以有不同的File Status Flag和读写位置。请注意区分这两种情况:

#include <unistd.h>

int dup(int oldfd);

dup()用来复制参数oldfd所指的文件描述词,并将它返回。此新的文件描述词和参数oldfd指的是同一个文件,共享所有的锁定、读写位置和各项权限或flag

  1. 返回值:当复制成功时,则返回最小及尚未使用的文件描述词。若有错误则返回-1,并设置errno
  2. oldfd:要复制的文件描述符
#include <unistd.h>

int dup2(int oldfd, int newfd);

dup2()用来复制参数oldfd所指的文件描述词,并将它拷贝至参数newfd后一块返回。若参数newfd为一已打开的文件描述词,则newfd所指的文件会先被关闭。

  1. 返回值:当复制成功时,则返回最小及尚未使用的文件描述词。若有错误则返回-1,并设置errno
  2. oldfd:要复制的文件描述符
  3. newfd:指定要复制的目标文件描述符
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    int fd, save_fd;
    if ((fd = open("test.txt", O_RDWR)) < 0) {  // fd == 3
        perror("open");
        exit(-1);
    }
    save_fd = dup(1);  //保存一下stdout, save_fd == 4,与标准输入指向相同地方
    dup2(fd, 1);       //将标准输入指向fd
    close(fd);
    write(1, "hello world\n", 12);  //往标准输出写东西,实际上是写到test.txt中,即实现了标准输出的重定向
    dup2(save_fd, 1);               //把标准输出还原,1原本指向的文件test.txt会被关闭
    write(1, "hello world\n", 12);  //往标准输出写东西

    return 0;
}
Linux系统编程笔记(5)——文件与I/O(4)-萤火