最全的李慧芹APUE-文件系统笔记(上)

发布时间 2023-11-04 21:06:15作者: tmpnam()

文件系统

注: 李慧芹老师的视频课程请点这里, 本篇为文件系统一章的笔记(上, 剩余内容和李慧芹老师课上提到的myls的实现预计会一起在下中放出), 课上提到过的内容基本都会包含, 上一章为系统调用IO

本章内容

  1. 目录和文件

    1. 获取文件属性(实现类ls)

    2. 文件访问权限

    3. umask

    4. 更改/管理文件权限(chmod, fchmod)

    5. 粘住位

    6. 文件系统: FAT vs UFS

    7. 硬链接与符号链接

    8. 时间相关(如: utime)

    9. 目录的创建和销毁

    10. 更改当前工作路径

    11. 分析目录/读取目录内容

  2. 系统数据文件和信息

  3. 进程环境

目录和文件

获取文件属性

作业:

编写C程序myls, 实现与ls命令尽量相似的功能

(至少要支持-l, -a, -i, -n选项)

其实, -l-n选项非常相似: -l会获取文件的属主名和属组名, -n则是获取这两者的ID

面试题:

使用touch命令, 创建一个名为"-a"的文件

# -- 表示选项的结束, 后面的内容都是非选项的传参
touch -- -a
# 当然, 也可以
touch ./-a

要获取文件属性, 可以使用stat一族:

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

int stat(const char *path, struct stat *buf);
// f代表使用文件描述符
int fstat(int fd, struct stat *buf);
// l代表在处理链接文件时与stat有不同
int lstat(const char *path, struct stat *buf);

stat()会获取一个文件的属性信息, 然后将信息填入到struct stat类型指针* buf中:

struct stat {
    dev_t     st_dev;         /* 包含当前文件的设备ID号 */
    ino_t     st_ino;         /* Inode number */
    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;     /* 一个块(扇区)的大小(一般为512个字节) */
    blkcnt_t  st_blocks;      /* 当前文件占用的512字节大小块的数量 */

    /* Since Linux 2.6, the kernel supports nanosecond
        precision for the following timestamp fields.
        For the details before Linux 2.6, see NOTES. */

    struct timespec st_atim;  /* Time of last access */
    struct timespec st_mtim;  /* Time of last modification */
    struct timespec st_ctim;  /* Time of last status change */

    #define st_atime st_atim.tv_sec      /* Backward compatibility */
    #define st_mtime st_mtim.tv_sec
    #define st_ctime st_ctim.tv_sec
};

重写 fsize

fsize.c:

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

static off_t fsize(const char *fname);

int main(int argc, char **argv)
{
        if (argc < 2)
        {
                fprintf(stderr, "Usage: %s <file name>\n", argv[0]);
                exit(1);
        }

        printf("%ld\n", fsize(argv[1]));

        exit(0);
}

static off_t fsize(const char *fname)
{
        struct stat statres;

        if (stat(fname, &statres) < 0)
        {
                perror("stat()");
                return -1;
        }

        return statres.st_size;
}

Makefile:

CFLAGS+=-D_FILE_OFFSET_BITS=64 -Wall

文件大小

struct stat中, st_blksizest_blocks两个属性真正决定了文件占据的磁盘空间大小(而不是st_size)

bigfile.c:

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
        int fd;

        if (argc < 2)
        {
                fprintf(stderr, "Usage: %s <file_name>\n", argv[0]);
                exit(1);
        }

        fd = open(argv[1], O_WRONLY|O_CREAT|O_TRUNC);
        if (fd < 0)
        {
                perror("open()");
                exit(1);
        }

        if (lseek(fd, 5LL*1024*1024*1024-1, SEEK_SET) < 0)
        {
                perror("lseek()");
                close(fd);
                exit(1);
        }

        write(fd, "", 1);

        close(fd);

        exit(0);
}

执行以下命令:

make bigfile
./bigfile ./big
ls -li ./big

结果:

524376 ---x-w---- 1 ecegjoker ecegjoker 5368709120 Sep 30 12:25 big

而如果使用stat ./big查看文件大小:

  File: ./big
  Size: 5368709120      Blocks: 8          IO Block: 4096   regular file
Device: fd00h/64768d    Inode: 524376      Links: 1
Access: (0120/---x-w----)  Uid: ( 1000/ecegjoker)   Gid: ( 1000/ecegjoker)
Access: 2023-09-30 12:25:34.869734022 +0000
Modify: 2023-09-30 12:25:34.869734022 +0000
Change: 2023-09-30 12:25:34.869734022 +0000
 Birth: 2023-09-30 12:25:34.869734022 +0000

就会发现5G大小的文件所占空间为4k!

空洞文件

上述由bigfile创建的文件就是空洞文件, cp命令支持对空洞文件的拷贝:

cp big big.bck
stat big
stat big.bck

执行上述命令, 会发现: bigbig.bck两个文件的文件大小(size)/数据/亚数据完全一致, 但是big.bck所占空间为0!

当然, 结果可能依环境不同而不同

st_mode

在执行ls -l时, 文件信息最前面的一串(文件类型与权限):

-rw-r--r--

就存放在st_mode字段中, 该字段的类型mode_t代表16位的位图 (文件类型3位, 文件权限9位, u+s位1位, g+s位1位, 粘著位(t位)1位, 共15位, 需要用16位整数存储)

  • Linux文件类型
符号 类型 用于判断文件类型的宏
d 目录/directory S_ISDIR(m)
c 字符设备/character device S_ISCHR(m)
b 块设备文件/block device S_ISBLK(m)
- 常规文件/regular S_ISREG(m)
l 符号链接文件/link S_ISLNK(m)
s 网络套接字文件/socket S_ISSOCK(m)
p 命名管道/named pipe S_ISFIFO(m)

m处传入mode_t类型变量

文件类型

ftype.c:

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


static int ftype(const char *fname)
{
        struct stat statres;

        if (stat(fname, &statres) < 0)
        {
                perror("stat()");
                exit(1);
        }

        if (S_ISREG(statres.st_mode))
                return '-';
        else if (S_ISDIR(statres.st_mode))
                return 'd';
        else if (S_ISSOCK(statres.st_mode))
                return 's';
        else if (S_ISCHR(statres.st_mode))
                return 'c';
        else if (S_ISBLK(statres.st_mode))
                return 'b';
        else if (S_ISLNK(statres.st_mode))
                return 'l';
        else if (S_ISFIFO(statres.st_mode))
                return 'p';
        else
                return '?';
}

int main(int argc, char **argv)
{
        if (argc < 2)
        {
                fprintf(stderr, "Usage: %s <file name>\n", argv[0]);
                exit(1);
        }

        printf("%c\n", ftype(argv[1]));

        exit(0);
}

文件权限

umask命令是由umask()封装而来的, 如果想在进程中查看文件信息, 可以使用该函数:

// man 2 umask:
#include <sys/types.h>
#include <sys/stat.h>

mode_t umask(mode_t mask);

可以利用chmod()改变某一文件的权限信息:

#include <sys/stat.h>

int chmode(const char *path, mode_t mode);
int fchmode(int fd, mode_t mode);

粘住位

粘住位(t位)原本的设计是对某一命令的使用痕迹进行保留(保留在内存中), 但是目前cache机制更加常用, 所以这一作用已被逐渐缩小

现在更加常用的是给目录增加t位(/tmp目录)

文件系统 FAT vs UFS

FAT与UFS是同一时期的文件系统, FAT不开源, UFS开源

  • FAT16/32本质: 静态(数组)存储的链表

链表节点:

struct node {
    int next;   // 下一个节点在数组中的位置
    add_t addr; // 存储文件数据的数据块在磁盘上的位置
}

由于:

  1. 数组长度是固定的

  2. 每个节点只对应磁盘上的一个块

因此FAT文件系统最大能够支持的磁盘大小也有限

另, 根据microsoft官网上的一段描述:

FAT文件系统的特点是文件分配表(FAT), 它实际上是一个位于卷最"顶部"的表; 为了保护卷, 当一个副本损坏时, 将保留 FAT 的两个副本; 此外, FAT 表和根目录必须存储在固定位置, 以便系统启动文件能够正确定位

上述数组是一式两份的存储在磁盘中的, 那么如果两份数据因意外不一致(其中某一份坏了), 那么怎样能够辨认出是哪一份坏了呢?

另外, 单链表本身也带有"前驱节点难找"的问题

:

FAT是支持随机存取的, 这里只是说单链表操作效率低

总结来看, FAT存在支持的磁盘大小有限/无容错/效率低的问题

  • UFS 文件系统

UFS文件系统如下图所示("unix的inode"是来自"操作系统概念(第七版)"(高等教育出版社)一书P367的插图):

image

其中, 一个inode中有12个直接块, 因此一个文件最大为(假设一个数据块或一个索引块大小为4k, 指针大小为4B): 4TB + 4GB + 4MB + 48kB