欢迎光临
我们一直在努力

Linux系统编程-文件管理与UFS文件系统

目录

一. 进程与文件

1.1 进程管理结构体

1.1.1 进程描述符

1.1.2 task_struct 进程描述符结构体

1.2 文件管理结构体

1.2.1 files_struct文件描述符表结构体:

1.2.2 struct file 文件表项:

1.2.3 struct inode i节点结构体

1.3 各结构体之间的关系

1.3.1 关系总结

1.3.2 图示

二. 对文件执行各种操作时各结构体的关系

2.1. 文件共享

2.2 在一个进程中打开同一文件多次

2.3 dup操作

三. 文件系统

3.1 文件的存储

3.1.1 扇区与块

3.2 UFS文件系统

3.2.1 快组

1、inode的直接索引和间接索引

3.2.2 块的限制

3.3 目录文件

3.3.1 目录项

摘要:本文详细介绍了Linux系统中进程与文件管理的核心数据结构及其关系。主要内容包括: 进程管理结构体,文件描述符表, 文件管理结构体,进程通过文件描述符→file结构体→inode三级结构访问文件 图示了文件共享、多次打开和dup操作时结构体的变化。同时也详细介绍了UFS这个文件系统:UFS文件系统组织方式(块组、inode位图等),inode的多级索引机制(直接/间接指针),目录文件的存储结构及其路径解析原理等等。

一. 进程与文件

1.1 进程管理结构体

1.1.1 进程描述符

        每个进程在Linux内核中都有一个 task_struct 结构体(sched.h)来维护进程相关的信息,称为进程描述符(Process Descriptor),而在操作系统理论中称为进程控制块 (PCB,Process Control Block),内部成员有很多,重点是以下部分:

成员 描述
pid_t    pid; 进程id。系统中每个进程有唯一的id,在C语言中用pid_t类型表示,其实就是一个非负整数。
pid_t     tgid; 线程组ID
unsigned int     sessionid; 会话ID
const struct cred __rcu   *cred; 用户 ID 和组 ID
volatile  long  state; 进程的状态,有就绪、运行、挂起、停止等状态。
struct thread_struct  thread; 进程切换时需要保存和恢复的一些CPU寄存器。
struct mm_struct   *mm; 描述虚拟地址空间的信息。
struct fs_struct     *fs; 当前工作目录(Current Working Directory)+  umask掩码
struct files_struct    *files; 文件描述符表

和信号相关的信息:

struct signal_struct     *signal;

struct sighand_struct   *sighand;

sigset_t         blocked;

线程共享的信号信息

信号处理函数

阻塞的信号集

还有一些其他的信息,但是没有在结构体里面找到对应的信息:

        * 描述控制终端的信息。

        * 进程可以使用的资源上限(Resource Limit)。

下面是进程描述符结构体的部分详细信息,可以在头文件<sched.h>中找到定义:

1.1.2 task_struct 进程描述符结构体

//进程结构体
struct task_struct {
volatile longstate;/进程的状态
struct thread_structthread;/进程切换时需要保存和恢复的一些CPU寄存器在这里面
struct mm_struct*mm;/描述虚拟地址空间的信息
struct fs_struct*fs;/当前工作目录+根目录+umask掩码
struct files_struct*files;/文件描述符表
/和信号相关的信息
struct signal_struct*signal;/线程共享的信号信息
struct sighand_struct*sighand;/信号处理函数
sigset_tblocked;/阻塞的信号集
struct sigpendingpending;/本线程待处理信号
const struct cred __rcu*cred;/用户 ID 和组 ID
pid_tpid;/进程ID
pid_ttgid;/线程组ID
unsigned intsessionid/会话ID
}

        1、这里重点说明 struct files_struct  *files;(文件描述符表) ,*files这个指针指向一个struct files_struct类型的结构体,称为文件描述符表结构体,记录打开的所有文件。

        2、files_struct这个结构体的定义如下(在头文件fdtable.h中可以找到):

1.2 文件管理结构体

1.2.1 files_struct文件描述符表结构体:

        1、这里有一个 atomic_t count 需要注意,count 是一个引用计数器,表示共享此文件描述符文件表的进程数。fork共享时 ++,进程退出时 –,等于 0 释放 files_struct文件描述符表

struct files_struct {
atomic_t count;/count引用计数
bool resize_in_progress;
wait_queue_head_t resize_wait;
struct fdtable __rcu *fdt;
struct fdtable fdtab;
spinlock_t file_lock ____cacheline_aligned_in_smp;
unsigned int next_fd;
unsigned long close_on_exec_init[1];
unsigned long open_fds_init[1];
unsigned long full_fds_bits_init[1];
struct file __rcu * fd_array[NR_OPEN_DEFAULT];/文件描述符数组
};

        该表中有一个域 struct file __rcu * fd_array[NR_OPEN_DEFAULT] ,为数组,该数组的每个元素为指向已打开的文件的文件表项(file结构体)的指针(已打开的文件在内核中用 file结构体 表示,文件描述符数组中的元素就是指向这个结构体的指针)

        这个数组的下标就是文件描述符(fd),内容是指向 struct file 结构体的指针

1.2.2 struct file 文件表项:

这里面存储的是一个已经打开的文件的信息,比较重要的有下面几个:

f_path 文件路径
f_ionde 指向inode结构体的指针
f_op 指向文件操作函数结构体的指针
f_count

文件的引用计数,open时++,close时–

这个比较重要,例如在进行dup操作时,这个值会加一,close时会使这个值减一,为0时

才真正的释放此结构体,关闭文件,这个文章后面会详细画图讲解。

f_flags 文件的打开标志,如O_CREAT,O_APPEND,O_TRUNC,O_NONBLOCK等
f_mode 文件的打开方式权限,如O_RDONLY,O_WRONLY,O_RDWR
f_pos

文件偏移量位置,lseek函数操作的就是这个,对文件的读写等操作都会改变这个值

     

struct file {
union {
struct llist_nodefu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct pathf_path;/文件路径
struct inode*f_inode;/文件索引节点,定义文件在磁盘上的位置
const struct file_operations*f_op;/文件操作函数指针
spinlock_tf_lock;
enum rw_hintf_write_hint;
atomic_long_tf_count;/文件的引用计数 open时++ close时–
unsigned int f_flags;/文件打开标志,O_RDONLY等记录文件的打开权限
fmode_tf_mode;/文件访问权限
struct mutexf_pos_lock;/偏移量互斥锁,多线程同时修改文件偏移量 f_pos 时加锁,防止并发错乱
loff_tf_pos;/文件偏移量位置,lseek()修改的就是这个
}

1.2.3 struct inode i节点结构体

         1、i节点结构体中并不存储文件的数据,而是存储文件的inode号、文件的字节长度、文件的时间戳、文件数据的block位置等等

        2、这个结构体可以在头文件<fs.h>中找到,下面只列举了这里面比较重要的内容:

i_mode 文件类型+权限模式(注意与文件表项中的f_mode与f_flags区别),这里面的文件权限指的是磁盘文件上的属性,就是使用ls命令查看的文件权限:rwx rwx r–
i_ino 文件的inode号,文件的唯一标识
i_size 保存文件的有效字节长度
i_blocks 文件实际占用的磁盘块数量(一块512字节)

        在linux中使用stat命令查看文件信息时,显示的就是这些内容:

         

struct inode {
umode_ti_mode;/文件类型+权限模式
kuid_ti_uid;/文件拥有者ID
kgid_ti_gid;/拥有组ID
unsigned long i_ino;/inode号,文件的唯一标识
union {/硬链接计数,为0时才真正删除此文件
const unsigned int i_nlink;
unsigned int __i_nlink;
};
dev_ti_rdev;/设备文件的设备号,块,字符设备文件使用
loff_ti_size;/保存文件的有效字节长度
struct timespeci_atime;/访问时间(读文件)
struct timespeci_mtime;/修改时间(写文件)
struct timespeci_ctime;/状态改变时间(改权限改大小)
blkcnt_ti_blocks;/文件实际占用的磁盘块数量(一块512字节)
atomic_ti_dio_count;/inode引用计数,有多少地方在用这个文件

}

1.3 各结构体之间的关系

1.3.1 关系总结

总结:在一个进程中,内核使用了3种数据结构来表示打开的文件:

        (1)、每个进程在进程表中都有一个记录项,记录项中包含一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是:

                a. 文件描述符标志                 b. 指向一个文件表项的指针         (2)、内核为所有打开文件维持一张文件表。每个文件表项包含:

                a. 文件状态标志(读、写、添写、同步和非阻塞等)                 b. 当前文件偏移量;                 c. 指向该文件i节点表项的指针。         (3)、每个打开文件(或设备)都有一个i节点(i-node)结构。

                i节点包含了文件的所有者、文件长度、指向文件实际数据块在磁盘上所在位置的指针等

1.3.2 图示

下图详细画出了在一个进程中,各种关于文件的结构体之间的关系:

二. 对文件执行各种操作时各结构体的关系

        在了解了内核对一个进程中打开文件是如何管理的,现在开始讨论执行各种操作时,进程中各种关于文件的结构体将发生什么样的变化,比如文件共享,dup操作等等,以便对其更好的理解。

2.1. 文件共享

        文件共享:即在两个独立的进程中打开同一个文件

        下图表示了在两个独立的进程中打开同一个文件时各种有关文件结构体的情况:

        假定第一个进程在文件描述符3中打开此文件,第二个进程在文件描述符4中打开此文件,打开该文件的进程都得到一个文件表项,但对一个给定的文件只存在一个i节点项(每个文件都有唯一的i节点项)。每个进程都有一个文件表项的理由是,这种方式使得每个进程都有它自己对该文件的当前偏移量。

        1、在完成每个write操作后,在文件表项中的当前文件偏移量增加所写的字节数,当文件偏移量超过了当前的文件长度后,则i节点表项中的文件长度将被设置为当前文件的偏移量(也就是文件被加长了)

        2、如果使用O_APPEND的方式打开了一个文件,则相应的标志也被设置在了文件表中的文件状态标志(f_flags),每次对具有这种标志的文件进行写操作时,文件表项中的文件偏移量先被设置为i节点表项中的文件长度,这就使得每次写入的数据被写入文件的末尾

        3、如果一个文件使用lseek定位在了文件末尾,则文件表项中的文件偏移量被设置为i节点表项中的文件长度

        

2.2 在一个进程中打开同一文件多次

下面的图片显示了在同一个进程中对一个文件多次打开的场景:

        这种情况下,当对文件描述符进行close时,对谁close就释放谁的文件表项空间,讨论这种情况主要是为了好后面的一种情况做对比(注意与之后的dup操作的情况做对比)

2.3 dup操作

下图显示了在执行dup操作后,进程中各种文件相关结构体的情况:

        1、可以看到在执行dup后,返回的新的文件描述符一定是当前可用的最小的文件描述符。

        2、这个时候文件描述符3和4都指向同一个文件表项,在对文件描述符3或者4进行colse操作时,难道直接释放掉整个文件表项吗?答案肯定是不是的,前面提到过文件表项中有一个域,叫做文件的引用计数:

atomic_long_t   f_count;/文件的引用计数

        3、在执行了dup操作时,将会有两个文件表项指针指向同一个文件表项,这时这个文件表项中的文件引用计数将加一,变成了2,当执行close操作时,文件引用计数将减一,直到减为0时,此文件表项才真正的释放。

        4、所以在执行一次close时,并不会释放掉文件表项。

三. 文件系统

        在了解了一个进程是对文件如何进行管理的后,现在开始讨论操作系统的文件系统,这里主要讨论的是UFS这个文件系统

3.1 文件的存储

3.1.1 扇区与块

        文件储存在硬盘上,硬盘的最小存储单位叫做"扇区"(Sector)。每个扇区储存512字节(相当于0.5KB)。操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个"块"(block)。这种由多个扇区组成的"块",是文件存取的最小单位。“块"的大小,最常见的是4KB,即连续八个 sector组成一个 block。         文件系统会将文件的实际内容和属性分开存放:文件的属性保存在 inode 中(i 节点)中,每个 inode 都有自己的编号。每个文件各占用一个 inode。不仅如此,inode 中还记录着文件数据所在 block 块的编号;文件的实际内容保存在 data block 中(数据块),每个 block 都有属于自己的编号。当文件太大时,可能会占用多个 block 块。

3.2 UFS文件系统

        UFS(Unix File System) 是一种广泛应用于类Unix操作系统的磁盘文件系统。它提供了对存储设备的高效管理,支持大型文件和复杂的文件结构。UFS文件系统的主要特点包括层次化的目录结构、文件权限和所有权管理、以及强大的性能优化。

        UFS文件系统的磁盘布局遵循特定的层次结构,以确保数据的高效存取和管理。磁盘被划分为一个或多个分区,每个分区可以被视为一个独立的文件系统单元。分区的开始处包含一个引导块,随后是超级块(Superblock),它包含了文件系统的元数据信息

        在UFS文件系统中,会对磁盘进行分区,一个分区中会有许多柱面组,而一个柱面组里又会有许多的块组,这里对一个块组展开进行研究

3.2.1 快组

一个快组的组成基本如下:

快组描述性信息:存放描述快组的信息

inode位图:全部为0/1,用来表示哪个inode节点有没有被使用

块位图:全部是0/1,用来表示哪个块有没有被使用

inode:这里是一个结构体数组,每个元素存放的都是之前提到过的文件的inode节点结构体,里面

             存放着文件的属性,文件的inode号,文件的大小等等,还存放着许多数据块指针,这个下

             面会详细讲解

块:文件的实际内容保存的地方,一块的大小是4K

1、inode的直接索引和间接索引

        之前提到过inode节点结构体中保存文件的属性和大小,还有许多的数据块指针,这些数据块指针指向的就是文件实际内容存储的位置,也就是块的位置,即通过inode可以索引到具体的块的位置。

        详细信息如下:

为了方便说明,这里以一块为1K的大小来进行说明:

        直接数据块指针有12个,每个都指向了一个数据块,一块1K,故总共可表示12K大小的文件。

        若文件大于12K,则使用一级间接块指针,一级间接块指针指向了一个块,但是这个块并不存储文件的数据,而存储的是直接数据块的指针,假设32位系统,指针大小为4字节,则1K可存储256个直接数据块指针,则一级间接指针总共可表示256K大小的文件

        同理,二级和三级间接指针类似。

        则总共可以容纳最多文件大小:12+256+256*256+256*256*256=16GB

3.2.2 块的限制

block 的基本限制如下:

        1、原则上,block 的大小与数量在格式化完就不能够再改变了(除非重新格式化)         2、每个 block 内最多只能够放置一个文件的数据;         3、如果文件大于 block 的大小,则一个文件会占用多个 block 数量;         4、若文件小于 block ,则该 block 的剩余容量就不能够再被使用了(磁盘空间会浪费)。

由这里可以看出UFS文件系统的缺陷就是不善于管理小文件:若系统中全是小文件就有可能出现inode已经被沾满了,但是块还有大量空白

3.3 目录文件

        在了解了上述的文件存储后,这里提出一个问题,为什么到现在为止没有遇到过文件名,我们使用open函数打开文件时,明明传参传的是一个文件名,不是一个inode号,为什么可以正常打开,文件名和inode号有什么关系,文件名在哪里存储,这是接下来要解决的问题?

3.3.1 目录项

        Linux中:一切皆文件,其实目录也是一个文件,叫做目录文件,目录文件中的每一项叫做目录项,每一个目录项包含inode号与文件名,结构如下:

在linux中,也可以指令stat来查看一个目录的详细信息:

用户通过文件名打开文件。实际上,系统内部这个过程分成三步:

        1、从目录文件中的所有目录项找到和这个文件名path相匹配的目录项,从而找对对应的inode号;         2、通过inode号码,获取inode信息;         3、根据inode信息,找到文件数据所在的block,读出数据。

同时,使用目录文件也可以方便进行路径解析:

只要,知道了根目录的inode,就可以找到根下的目录文件,由根的目录文件,就可以找到根下所有文件与文件夹的inode,这样一层一层解析就可以找到所有的文件。

赞(0)
未经允许不得转载:171主机测评 » Linux系统编程-文件管理与UFS文件系统
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址