[C Linux 内核] 文件系统 #2

[C Linux 内核] 文件系统 #2

上文我们已经通过一个ext2文件系统的例子详细的查看了文件系统的各种属性, 数据是如何存储的

不过很显然有一个疑问: 如果一个inode只能存15个Block的索引项, 即之恩那个指向15个Datablock每一个Datablock大小又只是1KB, 那么一个文件的maxsize不就只有15KB了么, 下面就通过这个事实 , 对Linux ext文件系统的数据块寻址进行介绍

数据块寻址

对于我们上面那个例子里的ext2文件系统来说, 只有前面 12个块是直接指向一个存放数据的Datablock, 而 Block 12, 13, 14则都是间接寻址块 

所谓间接寻址块, 就是将本来用作存储文件数据的Datablock用作inode索引(即在这个Datablock中存储的是一个个Block index记录)

在上述sample filesystem中 , 总共有三个间接寻址块, Block 12 13 14  他们之间的关系如下图所示

inode search view

其中 浅蓝色表示真正存储数据的Datablock, 而浅绿色, 深绿色, 橙色 的 则表示一级, 二级, 三级Block索引, 以一级索引为例, 以及Block索引指向的是一个 Datablock, 这个Datablock 存放的是Block Index,而二级Block索引则指向一个Datablock ,这个Datablock存的是一系列的一级索引, 然后每一个一级索引又指向一个存放着Block Index的Datablock,以此类推 ,通过这个方法就大大扩充了一个文件的容量

文件&目录的操作函数

以下这些函数都是通过读取inode block, datablock 里相应的信息, 返回给调用者 / 对文件 or 目录进行操作

  •  只对inode进行操作的(取出/修改)
    • lstat, stat, fstat(取出)
    • access (取出)
    • chmod, fchmod(修改 st_mode)
    • chown, fchown, lchown(修改user, group)
    • utime(修改 atime, mtime)
  • 同时对inode, datablock进行操作的
    • truncate, ftruncate(截断文件(既可以向短截断,也可以向长截断))
    • symlink(符号链接, 创建一个新的inode, st_mode字段文件类型为symlink, 原文件路径根据长度可能保存在inode内或者分配一个datablock来存
    • unlink
    • rename
    • rmdir
  • 对目录进行操作的专用函数
    • opendir
    • readdir
    • closedir

关于每一个命令具体对应的磁盘上的inode, datablock的操作省略, 在《Linux C编程一站式学习》Chap29 中有介绍,本文也是对其的一个整理,总结

下面给出一个模拟 ls -R 的实现代码 参考K&R C

/*************************************************************************
    > File Name: dir_walk_example.c
    > Author: VOID_133
    > A small program demos the process "ls -R", potential buggy
    > Mail: ################### 
    > Created Time: Mon 09 May 2016 09:24:56 PM CST
 ************************************************************************/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/file.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<dirent.h>
#include<fcntl.h>
#include<errno.h>


#define MAX_PATH 2048
struct stat fs_stat;
VFS
void usage();
void get_size(char *filename);

int main(int argc, char *argv[])
{
    if(argc < 2)
    {
        usage();
    }
    else
    {
        while(--argc > 0)
            get_size(*++argv);
    }
}

void dir_walk(char *dir, void (*fsz)(char *))
{
    char file_name[MAX_PATH];
    DIR *dfd;
    struct dirent* pentry;
    printf("entering %s...\n", dir);
    if((dfd = opendir(dir)) == NULL)
    {
        perror("opendir");
        //exit(errno);
    }
    else
    {
        while((pentry = readdir(dfd)) != NULL)
        {
            if(!strcmp(pentry->d_name, ".") || !strcmp(pentry->d_name, ".."))
            {
                printf("%8ld %s\n", 0, pentry->d_name);
                continue;
            }
            if(strlen(dir) + strlen(pentry->d_name) + 2 > MAX_PATH)
            {
                fprintf(stderr, "dir_walk name %s/%s is too long", dir, pentry->d_name);
            }
            if(!strcmp(dir,"/"))
                sprintf(file_name, "/%s", pentry->d_name);
            else
                sprintf(file_name, "%s/%s", dir, pentry->d_name);
            fsz(file_name);
        }
        closedir(dfd);
    }
    return ;
}

void get_size(char *file_name)
{
    int retval = 0xdeadbeef;
    retval = lstat(file_name, &fs_stat);
    if(retval < 0)
        perror("stat");
    else
    {
        mode_t mode = fs_stat.st_mode;
        if(((mode & S_IFMT) == S_IFDIR) && ((mode & S_IFMT) != S_IFLNK))
        {
            dir_walk(file_name, get_size);
        }
        else
        {
            printf("%8ld %s\n", fs_stat.st_size, file_name);
        }
    }
}

void usage()
{
    printf("Usage: myls directory_name\n");
}

书上说这个代码有bug, 可能导致递归死循环,  我在这里将书上的一个地方进行了修改, 不知道是否解决了书上所说的问题, 代码中

retval = stat(file_name, &fs_stat);

我改成了

retval = lstat(file_name, &fs_stat);

这样遇到symlink就不会递归进去, 不过就算递归进入symlink, 如果有一个symlink链接到它的父目录的话, 也会由于 too many symlink这样的错误导致程序的停止, 不会死循环, 所以目前为止还不清楚具体是哪种情况会让上面的代码死循环

 

VFS

下面说一下 VFS , Linux支持各种各样的文件系统格式 ,如 ext2, ext3, ext4, ntfs 等, 那么按照上面对文件系统的分析来看,不同文件系统都需要实现一套不同的stat, link, opendir … 等操作相应文件系统的函数, 而当将某个文件系统挂载到相应的文件夹下, 进去查看之后发现, 不同的文件系统在linux 下的表现都是一样的,这就是VFS的效果, Linux内核在各种不同的filesystem格式上做了一个抽象层,使得inode, 目录,等概念成为抽象层的概念

 

Linux的VFS维护了以下几个数据结构, 达到抽象文件系统的效果

  • super_block
  • inode
  • dentry
  • file

对于每一个打开的文件(FILE结构体)都指向一个dentry结构体, 每一个dentry又指向一个inode, inode指向super_block,对于不同的文件系统, 他们inode结构体内的 inode_operation 还有 file内的file_operation可能指向不同的操作, 而这些对于使用的用户(即我们开发者) 在使用系统调用的时候, 是透明的, 我们只要使用某个操作函数(如stat)然后内核自动会去调用正确的函数将正确的信息返回给用户

 

文件的引用数,dup&dup2

我们用 open(2)打开的同一个文件享有不同的两个file结构体, 他们保存有不同的文件打开标志(FLAG)和读写位置, 而dup dup2这两个函数, 则提供了可以让两个不同的文件描述符指向同一个file结构体这样的功能 , 如果某一个file结构体被多于一个fd指向, 那么, 这时候, file结构体的引用数就是一个关键的属性了, 引用数表明, 该file被多少个fd指向(引用),当用close关闭这个文件的一个fd的时候, 只要file的引用数不为0, 这个file结构体不会被释放. dup是直接复制一个新的文件描述符出来, 不能指定新fd的值, 而dup2可以指定新的fd的值,如果该fd被打开,则先关闭该新fd,再重新dup它

 

 

One thought on “[C Linux 内核] 文件系统 #2

Leave a Reply

Your email address will not be published. Required fields are marked *

one × three =

This site uses Akismet to reduce spam. Learn how your comment data is processed.