找回密码
 注册
关于网站域名变更的通知
查看: 349|回复: 1
打印 上一主题 下一主题

Linux内核设计与实现之虚拟文件系统

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2021-1-19 17:27 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x

& C5 s9 ]+ D/ d8 ^5 g5 I3 X# N虚拟文件系统(VFS)是linux内核和具体I/O设备之间的封装的一层共通访问接口,通过这层接口,linux内核可以以同一的方式访问各种I/O设备。
. J# t+ P0 i; z7 P3 v. \
. ~5 `5 j& M" l虚拟文件系统本身是linux内核的一部分,是纯软件的东西,并不需要任何硬件的支持。
2 y" _: S* c$ B6 X9 s% E6 t& w, m3 w$ u$ }8 n5 i" u2 T3 x

! A8 Q( w  G& K3 J0 l) F
! d1 x! ~! V- e" v. s& E9 q主要内容:
7 ]# Q$ t! j% V3 x& Q4 L) K' \1 i
1 ~8 _& h1 [( K6 J5 [6 n虚拟文件系统的作用
& Q7 L) ?0 A5 \2 v# i虚拟文件系统的4个主要对象
, x2 P3 m) e: y. T  q* `文件系统相关的数据结构' G- i" v* x& y, k: ~, o
进程相关的数据结构
+ z( }9 a! Q& T9 e( t6 V小结
  |/ b" r/ ?0 u: }' _1 \. w# I # w' F6 B6 {# F- j9 o- P% z; j

- u8 Z! x/ m7 `1 u) o$ R1 K - s" m# q; X7 p# |

0 a6 l+ s  q) t; R1. 虚拟文件系统的作用- n6 L9 ~# d: i2 h
虚拟文件系统(VFS)是linux内核和存储设备之间的抽象层,主要有以下好处。
8 X5 s( D5 y7 Z* ^) o0 }# |, j  I8 T$ _9 x+ D# B% T( t
- 简化了应用程序的开发:应用通过统一的系统调用访问各种存储介质
' `( E* Y* E' H, s! D" Y: b+ @! K4 K6 P8 G$ \
- 简化了新文件系统加入内核的过程:新文件系统只要实现VFS的各个接口即可,不需要修改内核部分
0 }* w& f8 m) V0 i$ a  S; ~( K- p' d7 D: P8 j8 B; l: L* v! r

  n# {' }5 C" I! R3 |/ J! e
# o' C7 D  V% \: N: i3 _% \2. 虚拟文件系统的4个主要对象
1 a/ z7 @- L7 K, E3 x% W( {: y% d2 h! ?虚拟文件中的4个主要对象,具体每个对象的含义参见如下的详细介绍。# L* D! M8 g! y6 q) }
; X' G  O# V& ]# e, w+ p4 f
+ A+ i3 a1 A4 K" i% P

' c7 I/ y& Z, f/ V2.1 超级块; p# U+ n0 ^( F' X( A
超级块(super_block)主要存储文件系统相关的信息,这是个针对文件系统级别的概念。
0 g( e( n9 A( `. Y8 i4 p3 V8 X# [2 G: `. S0 T+ j
它一般存储在磁盘的特定扇区中,但是对于那些基于内存的文件系统(比如proc,sysfs),超级块是在使用时创建在内存中的。  {3 P, e9 W, [4 f0 \

, X" A, h9 r$ G0 h1 j1 x + _$ ?, ]0 B7 K9 j! c0 v! u& N+ N

- t8 |5 ]$ P- h超级块的定义在:<linux/fs.h>7 J  |$ i* w+ T
9 Q9 z& a# l) ?9 j' `2 P
复制代码8 y* g, A& B5 o, n: ?* g/ ^
/* 0 h* s& \, h! }& Q+ D3 m
* 超级块结构中定义的字段非常多,4 I& `8 G) L, W$ i+ c) a  P
* 这里只介绍一些重要的属性6 |3 M- r+ k0 w9 R! g1 N% `6 w
*/
1 u! ?* Q2 y2 V9 T8 c; Xstruct super_block {- a# I7 g" v6 T: g
    struct list_head    s_list;               /* 指向所有超级块的链表 */- d  O* S2 \% h7 O8 b
    const struct super_operations    *s_op; /* 超级块方法 */* {* x9 f& M7 [
    struct dentry        *s_root;           /* 目录挂载点 */
2 a2 }& \) s/ c/ y! |+ g    struct mutex        s_lock;            /* 超级块信号量 */
6 L# a; c+ W; o; G8 J- P! R  J/ h) k    int            s_count;                   /* 超级块引用计数 */4 U8 v" c0 V& u# C) k( V

4 x- I7 }3 V, Q2 O) [7 d- z: I+ H    struct list_head    s_inodes;           /* inode链表 */- p+ U* a, q3 Q/ P, D: h$ _
    struct mtd_info        *s_mtd;            /* 存储磁盘信息 */, j/ W: P, e* V; s0 Z- D# c
    fmode_t            s_mode;                /* 安装权限 */& C( D5 Y+ J* F* c$ b
};
. I3 b6 @& ~. h5 Y' T8 i9 w' W' S" D! e- Y+ x  X0 A
/*
4 K% s; Y3 ?7 T' {9 n. c, B * 其中的 s_op 中定义了超级块的操作方法
0 q( Q4 T2 l. a * 这里只介绍一些相对重要的函数) q5 a, L5 c- H* B* J2 k
*// w. G& \5 i9 J8 ~, {; R. u
struct super_operations {
6 X" c; I) ^7 m! A       struct inode *(*alloc_inode)(struct super_block *sb); /* 创建和初始化一个索引节点对象 */
/ [( N  I# e% l' G6 }    void (*destroy_inode)(struct inode *);                /* 释放给定的索引节点 */
7 ?( l  b/ T1 q! R% o
! q  d# @" k* @1 R! ^& s       void (*dirty_inode) (struct inode *);                 /* VFS在索引节点被修改时会调用这个函数 */) I5 o1 ]" n# L: M/ J2 S! y
    int (*write_inode) (struct inode *, int);             /* 将索引节点写入磁盘,wait表示写操作是否需要同步 */
# u" V6 U* k- G    void (*drop_inode) (struct inode *);                  /* 最后一个指向索引节点的引用被删除后,VFS会调用这个函数 */
! ^1 I3 m+ p+ r  U; l2 p    void (*delete_inode) (struct inode *);                /* 从磁盘上删除指定的索引节点 */
5 j2 N* }7 C- P! z4 C    void (*put_super) (struct super_block *);             /* 卸载文件系统时由VFS调用,用来释放超级块 */
" o' I& U  P3 ]    void (*write_super) (struct super_block *);           /* 用给定的超级块更新磁盘上的超级块 */" |; I& e) v# h
    int (*sync_fs)(struct super_block *sb, int wait);     /* 使文件系统中的数据与磁盘上的数据同步 */
  o5 J& r  q1 }  e    int (*statfs) (struct dentry *, struct kstatfs *);    /* VFS调用该函数获取文件系统状态 */
/ _' n* k5 n4 x3 b    int (*remount_fs) (struct super_block *, int *, char *); /* 指定新的安装选项重新安装文件系统时,VFS会调用该函数 */
" U, V" j4 ^. t+ r' ]1 B    void (*clear_inode) (struct inode *);                 /* VFS调用该函数释放索引节点,并清空包含相关数据的所有页面 */# Z3 I: l9 x$ |( C
    void (*umount_begin) (struct super_block *);          /* VFS调用该函数中断安装操作 */, m: I! X4 |( m0 [3 E1 s
};, v" K# W) v$ G' x# l3 g; O
复制代码4 Q$ T; E! H: b- s2 M' b" v

% E9 k9 E: N( w- h. P
* y& n" i6 O  \& V1 ~6 T2.2 索引节点1 @4 H. ~0 g) n0 Y
索引节点是VFS中的核心概念,它包含内核在操作文件或目录时需要的全部信息。
0 B, A! a4 @  @2 G& W
+ n# d" E" ^( [一个索引节点代表文件系统中的一个文件(这里的文件不仅是指我们平时所认为的普通的文件,还包括目录,特殊设备文件等等)。! m2 I5 G5 G- O

  q! y# W8 g9 `; p索引节点和超级块一样是实际存储在磁盘上的,当被应用程序访问到时才会在内存中创建。) w" l& p( i/ B  C$ _6 Z" y7 o
2 ^* I" F/ N# ]/ s/ {

5 {2 I0 t+ S7 Q2 D5 s) q0 f/ f4 x5 W, @  N3 ?
索引节点定义在:<linux/fs.h>
/ i, O( R" q; W. l/ r+ |4 |9 y' [  ?! [( K) J
复制代码% G& v+ f/ a0 n9 C' [
/* , z' b" T0 L7 ?2 ?+ ?
* 索引节点结构中定义的字段非常多,! _1 T0 \6 y! r1 d% t7 d. K0 w
* 这里只介绍一些重要的属性: O7 X1 l8 c' w8 j/ i7 Z- L; @
*/4 T: _, Q& A3 R  {$ S/ n
struct inode {
0 J% i4 }( t* D+ h    struct hlist_node    i_hash;     /* 散列表,用于快速查找inode */2 l* d5 E; D# R, @0 U
    struct list_head    i_list;        /* 索引节点链表 */; U4 Y* Z, f0 ?# o* g) O! [
    struct list_head    i_sb_list;  /* 超级块链表超级块  */
0 H" V7 g- f6 p1 s& x' R/ r    struct list_head    i_dentry;   /* 目录项链表 */4 f* A: g" k. j- H' v# O
    unsigned long        i_ino;      /* 节点号 */
% P) k" q8 \& R+ R; p0 i    atomic_t        i_count;        /* 引用计数 */* D2 p. a2 w2 W2 @
    unsigned int        i_nlink;    /* 硬链接数 */
- [) j2 ~" W$ n5 a    uid_t            i_uid;          /* 使用者id */
0 R9 m* B2 U2 P* U* \8 T5 \/ d    gid_t            i_gid;          /* 使用组id */, J  t  l& x% }$ a& q
    struct timespec        i_atime;    /* 最后访问时间 */
' O% M. |' W8 J9 _" I    struct timespec        i_mtime;    /* 最后修改时间 */9 ?6 g- g% X6 r
    struct timespec        i_ctime;    /* 最后改变时间 */
7 d0 Z! [0 ^/ R- }8 J0 ^    const struct inode_operations    *i_op;  /* 索引节点操作函数 */
1 S8 q; L& ]' k$ g/ E1 w- f5 @    const struct file_operations    *i_fop;    /* 缺省的索引节点操作 */
+ ?% C) j. n2 R" o, B) R    struct super_block    *i_sb;              /* 相关的超级块 */
5 K2 `2 `3 m- p* b& I0 O    struct address_space    *i_mapping;     /* 相关的地址映射 */
- W8 W' Z/ a/ i- j    struct address_space    i_data;         /* 设备地址映射 */
# z9 r; V' r  u0 g3 k' s9 |! R    unsigned int        i_flags;            /* 文件系统标志 */
; |$ G: ]/ ~6 I# [$ f    void            *i_private;             /* fs 私有指针 */7 d; {& x7 Q) ]  D- T
};
3 w0 F  }$ y0 Y+ [! `4 ]% s9 I0 l7 i; @# g. c6 g# m6 E
/*
" Z6 T$ R1 s( I8 c * 其中的 i_op 中定义了索引节点的操作方法- M# f+ A5 ~/ D; Y+ Q( ]- X
* 这里只介绍一些相对重要的函数: v0 w& l2 y! r6 v  h
*/+ f; Q5 L" e  L( S9 y
struct inode_operations {
& i( l# _: O( V" M; g9 r    /* 为dentry对象创造一个新的索引节点 */
) V, t# ^  F, C    int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
1 X% t+ D/ f5 p4 x6 w' T    /* 在特定文件夹中寻找索引节点,该索引节点要对应于dentry中给出的文件名 */
* W8 M2 Q" \( \3 `( k& d    struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);5 k$ {  A9 l) Y3 c2 ^7 M  W, G
    /* 创建硬链接 */
: \( B  B1 D0 \8 w# I  G    int (*link) (struct dentry *,struct inode *,struct dentry *);$ C9 ^6 Q; m$ f0 G% L
    /* 从一个符号链接查找它指向的索引节点 */& l0 E9 I3 c$ Q  `
    void * (*follow_link) (struct dentry *, struct nameidata *);
5 X( K& C2 Q# a) ?. h7 V    /* 在 follow_link调用之后,该函数由VFS调用进行清除工作 */
6 A7 |3 P# C" b9 t  U% x# `( x, R    void (*put_link) (struct dentry *, struct nameidata *, void *);- y1 a+ k3 Q* v: X! Q
    /* 该函数由VFS调用,用于修改文件的大小 */3 ~# f; K( b! w
    void (*truncate) (struct inode *);3 I) [+ e' n; s! Q% o# L
};# a1 ?( P/ Y) O) q9 S3 ^
复制代码
+ k6 J4 A, ^1 I& P$ ?" @. G% i ' q+ E' @% H8 T

. Y7 f) Q( P- e0 z2.3 目录项
3 q, A0 [* G; k和超级块和索引节点不同,目录项并不是实际存在于磁盘上的。
0 p. ~6 M. N8 z6 q- J+ E/ J# G1 H5 \# l, o
在使用的时候在内存中创建目录项对象,其实通过索引节点已经可以定位到指定的文件,$ _7 w% I/ Q6 J1 W
- b2 R" C5 ^8 s, m" y
但是索引节点对象的属性非常多,在查找,比较文件时,直接用索引节点效率不高,所以引入了目录项的概念。  q+ U0 o5 u$ b' p0 B. D6 ^

. z9 u% W1 U9 X, t ) B% G" [" P. w' |
* u1 I: u# w: O
路径中的每个部分都是一个目录项,比如路径: /mnt/cdrom/foo/bar 其中包含5个目录项,/ mnt cdrom foo bar
0 I  p9 o% X) e; Q5 i+ N7 f! q( \: C0 a8 ^+ V4 O# O3 @+ V
9 [# I8 ?* o- Y- ?
/ _* m: s$ ^. L8 h2 w& U0 Q
每个目录项对象都有3种状态:被使用,未使用和负状态
( B+ d* P7 E' V4 ^. ?. A
; u) I$ b! L5 Q5 z- 被使用:对应一个有效的索引节点,并且该对象由一个或多个使用者
+ N$ I5 A$ q: Z/ W& l( n7 b: M( h2 g$ m" Y
- 未使用:对应一个有效的索引节点,但是VFS当前并没有使用这个目录项: s6 [/ t6 T1 I% Q$ E2 |, j& g
8 S& W  y% m' V( y" N$ S
- 负状态:没有对应的有效索引节点(可能索引节点被删除或者路径不存在了)
! g& _( a# s) W9 J8 F6 m6 g* {' ?! z1 x# d  s

% Z/ H. @5 Q( V' n3 g2 H* s
0 Y9 O5 j* ]6 D: @5 o目录项的目的就是提高文件查找,比较的效率,所以访问过的目录项都会缓存在slab中。
1 I8 x4 g, a& V/ ]( K. u+ r% _4 [4 U* y) m
slab中缓存的名称一般就是 dentry,可以通过如下命令查看:
9 Z- {, c$ G) V9 a. R% P% f7 \1 d8 `' t* b/ y( ]1 A; _
[wangyubin@localhost kernel]$ sudo cat /proc/slabinfo | grep dentry
; [8 v1 p; e3 m) q0 M" o/ n8 Y6 mdentry            212545 212625    192   21    1 : tunables    0    0    0 : slabdata  10125  10125      0
3 ^- a$ {  p: z$ q. U5 l 2 F0 N( _- R- b! T2 A- O. I! S# z
4 I7 i. t% Y+ h
目录项定义在:<linux/dcache.h>
0 z; _  J) h0 {" p  x
+ \- o! Y3 ^+ p1 Z. a复制代码' K4 C+ g+ Y- X/ K/ X
/* 目录项对象结构 */7 c0 I. C0 I. W+ e
struct dentry {! `% L; j$ I* }" c( m& V" q
    atomic_t d_count;       /* 使用计数 */8 D4 C2 |& R) v. P& m$ J( C
    unsigned int d_flags;   /* 目录项标识 */
% [! a$ v% v9 j* o3 G, L    spinlock_t d_lock;        /* 单目录项锁 */
( h# ~/ I6 c6 z% d    int d_mounted;          /* 是否登录点的目录项 */
: O5 u! k# B% }; |) k    struct inode *d_inode;    /* 相关联的索引节点 */8 h% E7 u, E. d; ]# ]
    struct hlist_node d_hash;    /* 散列表 */
( G% i) A5 |# I7 E) Y8 H" J4 ~    struct dentry *d_parent;    /* 父目录的目录项对象 */7 ?- ]; d0 B7 u/ E3 R4 D
    struct qstr d_name;         /* 目录项名称 */
, I( t& x; S( K( D0 G+ B% t    struct list_head d_lru;        /* 未使用的链表 */% P! T' g5 V: y9 |1 `( H. T
    /*
. q9 \! _5 P3 H     * d_child and d_rcu can share memory
* L! H+ {' |: h3 s0 u% B9 q$ V     */
2 M2 Z  V$ a9 N- h4 K5 n+ \* J    union {9 n% A1 Y4 H% T
        struct list_head d_child;    /* child of parent list */0 }  Y$ T; o5 H' f$ N1 U) M+ c
         struct rcu_head d_rcu;
6 @' I$ V1 b+ W( z    } d_u;$ X$ k5 P& T1 x2 Z4 b+ o8 z
    struct list_head d_subdirs;    /* 子目录链表 */6 w, q7 r8 x9 @- u4 y
    struct list_head d_alias;    /* 索引节点别名链表 */
, _: R( g! O7 i    unsigned long d_time;        /* 重置时间 */
; @7 c; p3 u+ N, U7 U& i    const struct dentry_operations *d_op; /* 目录项操作相关函数 */4 M$ H1 Q8 H+ C- {1 K
    struct super_block *d_sb;    /* 文件的超级块 */
% G# t/ C  S$ K2 s) F1 t    void *d_fsdata;            /* 文件系统特有数据 */# H) J4 h5 ]5 D0 |2 K5 c
+ U8 y& }- x, z1 F# ]. I: ^% `
    unsigned char d_iname[DNAME_INLINE_LEN_MIN];    /* 短文件名 */
9 J$ w* v  [; I: Y- U( l" G};
9 {% Q# o# |4 y+ Y! I; h; k9 j6 ~& t! j  @* h
/* 目录项相关操作函数 */
0 A4 c/ W3 N% ]8 b$ Astruct dentry_operations {
" ^' N: e5 t: b3 ^# _    /* 该函数判断目录项对象是否有效。VFS准备从dcache中使用一个目录项时会调用这个函数 */
- j, Z- l  b. V& P4 ?- z    int (*d_revalidate)(struct dentry *, struct nameidata *);
" @$ Q4 T. n8 n% e    /* 为目录项对象生成hash值 */
! _% V; J; M2 y0 _( j    int (*d_hash) (struct dentry *, struct qstr *);8 [6 ^4 A9 c3 Y+ x$ ]+ e6 P
    /* 比较 qstr 类型的2个文件名 */* a" I; V" L% A% c6 j
    int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);  N# @3 h; p! R( O3 ^
    /* 当目录项对象的 d_count 为0时,VFS调用这个函数 */
) A2 q: r; a6 Q5 k( E. v, D    int (*d_delete)(struct dentry *);
9 ?0 w% i) g3 G, k    /* 当目录项对象将要被释放时,VFS调用该函数 */
3 o' c7 y3 G: K. s    void (*d_release)(struct dentry *);) g1 }. T7 N3 x& @  E: Y
    /* 当目录项对象丢失其索引节点时(也就是磁盘索引节点被删除了),VFS会调用该函数 */
" M4 Z+ e( f1 V4 z. d    void (*d_iput)(struct dentry *, struct inode *);
5 O7 u( y) F9 ^$ ^  F/ b    char *(*d_dname)(struct dentry *, char *, int);
$ ]2 M/ S: t* K9 [7 y7 t' m};
. ?, }; N7 j1 q7 y复制代码! d- Y7 E! M' z  q

& ?2 H3 P- z0 ^" ~6 ~
& ]! h7 f# S; j2.4 文件对象
0 A) y2 r; J: [) \文件对象表示进程已打开的文件,从用户角度来看,我们在代码中操作的就是一个文件对象。* b) r3 k1 a" ^* G' K0 x: s0 s
5 p! T, K8 f  v* X1 M3 O% i
文件对象反过来指向一个目录项对象(目录项反过来指向一个索引节点)
* ~6 L1 ?) p1 p5 ^- Q- w2 V6 f& C
7 [( t. M/ i, C0 ?! x其实只有目录项对象才表示一个已打开的实际文件,虽然一个文件对应的文件对象不是唯一的,但其对应的索引节点和目录项对象却是唯一的。
1 P( h4 l+ J: x7 c" x# z9 {
+ K' O( i6 t! ]* [! v1 Y, f ) L/ g) J4 u. E2 k- n7 @

6 U8 @. T9 h7 ?/ U文件对象的定义在: <linux/fs.h>
  D, W8 @2 b" x5 i5 n' {, K4 n# B5 i; B5 H# `0 Z9 \0 @; G8 k& _. e
复制代码  e6 Y% r3 `/ O, }0 B6 {% b
/*
( x3 d" f$ D) N) F* ~ * 文件对象结构中定义的字段非常多,; _, d8 v: \7 k6 a" G
* 这里只介绍一些重要的属性9 m7 h( J6 n# c) E
*/
. a" I) C$ z; H, Rstruct file {' q' K) b# ~3 W5 `- s8 t! T
    union {
" I, O4 Z+ R3 S3 Z. w8 {        struct list_head    fu_list;    /* 文件对象链表 */+ i8 J' `. _/ Q0 N5 `: A# a9 d
        struct rcu_head     fu_rcuhead; /* 释放之后的RCU链表 */
2 i, _% s$ Y1 G2 b    } f_u;
; _. t% ^5 b! @6 P2 f    struct path        f_path;             /* 包含的目录项 */
6 j6 Z+ r1 G/ i6 B* e& }    const struct file_operations    *f_op; /* 文件操作函数 *// ^( |. C+ D( M
    atomic_long_t        f_count;        /* 文件对象引用计数 *// j8 l  t, p6 C; |) H
};
5 C7 b0 C" Q! y- ?2 U
: B# y! e" z  d0 k5 J$ H( i5 U/*
5 z& \* T# i& T0 w# C * 其中的 f_op 中定义了文件对象的操作方法
3 j7 n) U8 a# z4 S* @ * 这里只介绍一些相对重要的函数: M1 g' s+ P: L3 C! s* u% M4 w
*/. f! N- ]7 S% ?( a; |
struct file_operations {% b3 w; v; X5 n  I+ E3 g
    /* 用于更新偏移量指针,由系统调用lleek()调用它 */; b( y' t* ^' k: n5 m# I0 n0 a
    loff_t (*llseek) (struct file *, loff_t, int);; p2 L) E% l% T6 }& W+ x! ~
    /* 由系统调用read()调用它 */
4 H* p0 a# @# D0 }: v% I    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);: Q* S' N5 f0 G' S$ c( E0 J$ Z
    /* 由系统调用write()调用它 */
& q1 |( N; f, M    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
6 Z; v! [- ]7 j% v, l# }    /* 由系统调用 aio_read() 调用它 */
  U9 v0 p; w$ P    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);5 T5 b% p4 x* K% e1 V0 ^
    /* 由系统调用 aio_write() 调用它 */
5 @0 N7 f9 `. x% r9 B& P4 v    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);) I( I9 Q7 J" w) A% Q3 E2 Q  b
    /* 将给定文件映射到指定的地址空间上,由系统调用 mmap 调用它 */( ~/ ^. I2 m, J7 B4 h3 ^$ ?; K
    int (*mmap) (struct file *, struct vm_area_struct *);3 _. L3 K4 Y! l7 i! k0 G
    /* 创建一个新的文件对象,并将它和相应的索引节点对象关联起来 */! ~! H% D) k3 ~$ C$ i) ^6 Y
    int (*open) (struct inode *, struct file *);& b- ^" F: J( d5 Y2 j. d
    /* 当已打开文件的引用计数减少时,VFS调用该函数 */, _$ v6 j7 A) s1 U# H5 W
    int (*flush) (struct file *, fl_owner_t id);
6 t1 S1 y9 b* {! `3 v$ Y};
, j% J# J8 E8 k2 j/ k复制代码
, ]% E9 S. I& U" w # u% `! K) Y9 W" A# p3 p& A

& {1 C) U+ o$ O' f& U' H% S2.5 四个对象之间关系图  [0 G  d" L  e& L3 }( w1 @
上面分别介绍了4种对象分别的属性和方法,下面用图来展示这4个对象的和VFS之间关系以及4个对象之间的关系。$ H% D- r, M, l' c. a2 U

/ r0 }. N+ j2 B(这个图是根据我自己的理解画出来的,如果由错误请帮忙指出,谢谢!)$ l8 c0 m9 R8 O, D

1 f7 A6 `, N! L# w ) C7 W4 u% a; ?' J7 M6 B

, _' S8 }( E2 s4 N( q ( u1 }- A6 ^% ^9 j  v
( V1 I/ T& m' \& b" W) e5 K, y

9 E; s7 G3 @- y# J. ?" ?+ [9 q2 n  E% l. h) B& Q
5 y$ G0 i# O' L) j  E1 N

; x; p) a# b& v3. 文件系统相关的数据结构
% ~3 H* O9 ~/ K7 D/ L7 p# k) h处理上面4个主要的对象之外,VFS中还有2个专门针对文件系统的2个对象,  V4 [* ?* h; F3 x0 N

# N5 l2 }1 C- _$ u0 O& }- struct file_system_type: 用来描述文件系统的类型(比如ext3,ntfs等等)
4 z, N  @; w* [3 `( R/ l' s3 o! S/ F2 o6 ^; Q9 O
- struct vfsmount        : 描述一个安装文件系统的实例
) |+ P  J3 y. n# ]7 u: e6 B6 b) O1 i& A, u
% e$ b: z7 r. j
1 J" a1 B0 b' c/ |: k/ H. n8 `) N
file_system_type 结构体位于:<linux/fs.h>
8 l1 F, f  v$ B+ I" \# ]$ B$ Y. v
复制代码0 j6 c8 e9 h; ?
struct file_system_type {
: |" ]+ a# C+ G9 W  S4 C& e    const char *name;   /* 文件系统名称 */' z* [3 R$ D% W" t
    int fs_flags;       /* 文件系统类型标志 */  M$ G1 u$ e( ~& Z1 }# R. X
    /* 从磁盘中读取超级块,并且在文件系统被安装时,在内存中组装超级块对象 */- ~- J2 I( F; }- d" Y) M5 P
    int (*get_sb) (struct file_system_type *, int,/ v* L# s, i# J/ I
               const char *, void *, struct vfsmount *);' {" N" X5 Z( ~/ W& U
    /* 终止访问超级块 */
' k+ z! J9 i  c! @/ E. N    void (*kill_sb) (struct super_block *);1 _; V: c% L' x" S/ U
    struct module *owner;           /* 文件系统模块 */# D: y, V* I7 `: v1 F- v
    struct file_system_type * next; /* 链表中下一个文件系统类型 */' u9 j; O$ c7 N" y7 F: c% H
    struct list_head fs_supers;     /* 超级块对象链表 */
: v7 Y. ^4 |- a2 }* a/ r- H9 O; A+ }' i5 \5 _( s
    /* 下面都是运行时的锁 */
) ]& x: h8 i6 |1 @    struct lock_class_key s_lock_key;" C9 W' }( \; y1 k+ l
    struct lock_class_key s_umount_key;3 g2 W5 w8 P+ Z, S( {
: G4 q" W# g2 d+ M
    struct lock_class_key i_lock_key;( X) T$ \3 b5 o% V) d  }6 `# v
    struct lock_class_key i_mutex_key;; H7 b- I5 `* `
    struct lock_class_key i_mutex_dir_key;( j- c/ V4 y8 Y, ^
    struct lock_class_key i_alloc_sem_key;" X& Z/ \, `$ s7 n% [2 I; n
};9 a! V* @7 Q; A, n5 `
复制代码
) x9 |" Y; d2 T& _; \* q每种文件系统,不管由多少个实例安装到系统中,还是根本没有安装到系统中,都只有一个 file_system_type 结构。' X7 G* l# Z# q. J0 [+ ]
$ |% L# z+ q$ j$ V! i8 P

8 d8 T- F6 ]6 b- g  l( Q
4 t5 j8 i6 |- C# J; Z3 F, |2 A当文件系统被实际安装时,会在安装点创建一个 vfsmount 结构体。5 W% a$ w" a' d8 Y+ z

5 Z* D6 K) p' l7 Q4 r, {结构体代表文件系统的实例,也就是文件系统被安装几次,就会创建几个 vfsmount
5 B9 F5 a' T7 {
& l; g6 \9 ?7 U; Pvfsmount 的定义参见:<linux/mount.h>  \9 S* M0 Q$ n' a  Y
9 Y* a) Y& w3 S
复制代码) u( f  |' m- ]) Q3 I
struct vfsmount {3 ~0 ^1 t% \7 ~
    struct list_head mnt_hash;      /* 散列表 */# m( m0 `5 C& _% [2 R
    struct vfsmount *mnt_parent;    /* 父文件系统,也就是要挂载到哪个文件系统 */
8 _  c, c! @( B3 s7 ^4 d/ |    struct dentry *mnt_mountpoint;    /* 安装点的目录项 */! v2 A2 R* k) y
    struct dentry *mnt_root;        /* 该文件系统的根目录项 */
$ R% C7 m$ ]& a* l* p    struct super_block *mnt_sb;        /* 该文件系统的超级块 */
. t8 O  ~5 P5 F    struct list_head mnt_mounts;    /* 子文件系统链表 */
4 v8 S( P! @$ I* A( l7 P    struct list_head mnt_child;        /* 子文件系统链表 */
3 n" J5 G; J: q* m5 `$ C5 x2 U    int mnt_flags;                  /* 安装标志 *// M% D8 k0 [5 e1 B- ~" Z
    /* 4 bytes hole on 64bits arches */; s! E; m3 X5 E2 t7 i3 E; l
    const char *mnt_devname;        /* 设备文件名 e.g. /dev/dsk/hda1 */& W& S' i9 E6 t! Q# _7 o1 F
    struct list_head mnt_list;      /* 描述符链表 */! N/ Q2 Q& E/ ~7 f' o) p; b
    struct list_head mnt_expire;    /* 到期链表的入口 */9 S- _5 d0 C3 f) W/ ?- t
    struct list_head mnt_share;        /* 共享安装链表的入口 */
  _" d1 {% m. x! C3 k  b  C    struct list_head mnt_slave_list;/* 从安装链表 */
) j) s8 W4 N+ O: Z* }  g& x    struct list_head mnt_slave;        /* 从安装链表的入口 */
8 {& W5 w8 P/ w7 @2 G  R    struct vfsmount *mnt_master;    /* 从安装链表的主人 */0 i9 x' V% V  O6 C! l* ~8 M& p& f
    struct mnt_namespace *mnt_ns;    /* 相关的命名空间 */
( J+ I1 h- h# p2 B4 y8 C4 l    int mnt_id;            /* 安装标识符 */8 ?  I8 V" X$ U. d, Q  ]
    int mnt_group_id;        /* 组标识符 */+ q; W4 H! K& o2 E- t
    /*8 ~8 k( C; j( d3 M! @" Y
     * We put mnt_count & mnt_expiry_mark at the end of struct vfsmount
* Y& g5 ]4 _% \' b8 L6 Y/ z6 c     * to let these frequently modified fields in a separate cache line+ m- V7 d5 c0 h' `* [- r7 o
     * (so that reads of mnt_flags wont ping-pong on SMP machines)
4 t# i0 S9 i& Y& l     */
4 ~; D2 A1 ~* E$ U% @    atomic_t mnt_count;         /* 使用计数 */
; I1 g' ^0 I& S" T* ]  S, O- x  v    int mnt_expiry_mark;        /* 如果标记为到期,则为 True */
7 H4 [% k# m; N: Y0 _    int mnt_pinned;             /* "钉住"进程计数 */
: e3 ]- I$ F& i* R9 O: @    int mnt_ghosts;             /* "镜像"引用计数 */* g5 M1 \% ?# e7 F. w5 [0 S7 ~% [6 R
#ifdef CONFIG_SMP9 h& m4 e9 j6 \* t# k: p
    int *mnt_writers;           /* 写者引用计数 */
5 q: n6 a( D+ u; J# x2 o#else4 {! }, t0 O& b4 V. L
    int mnt_writers;            /* 写者引用计数 */
3 Z$ E, Y; K: V$ L3 s$ r, x#endif
) e! M3 M9 |) @$ l* r/ ?};+ g$ P9 p9 d9 |; q2 X/ A
复制代码
' Y! w4 G7 K1 m% f
8 k' g4 c8 s, f$ U) {+ x1 G6 V: F4 h$ T4 h. C7 _
4. 进程相关的数据结构
, D5 O3 }1 C$ g, u; P以上介绍的都是在内核角度看到的 VFS 各个结构,所以结构体中包含的属性非常多。
3 m# ^; l/ o) I$ ^6 J/ G* |7 G* f2 x& |
而从进程的角度来看的话,大多数时候并不需要那么多的属性,所有VFS通过以下3个结构体和进程紧密联系在一起。' M: \9 \7 v/ B/ l
2 h( N8 N1 l) F* m) a
- struct files_struct  :由进程描述符中的 files 目录项指向,所有与单个进程相关的信息(比如打开的文件和文件描述符)都包含在其中。  T( z2 w- n4 S# [7 I
! m6 ~, I( C( g7 o
- struct fs_struct     :由进程描述符中的 fs 域指向,包含文件系统和进程相关的信息。( v& [1 `, e2 `8 P7 Z( H1 }  o1 O* d
4 Q/ Z; w9 F% y: Q
- struct mmt_namespace :由进程描述符中的 mmt_namespace 域指向。: g  ^) O& v8 j9 `( |& n; l" Q

1 V& ^# ^2 g7 K3 I 0 C2 P3 z6 c- }0 e2 f; ?' l1 C; j) }
+ `9 D- Q2 T: h* b
struct files_struct 位于:<linux/fdtable.h>/ Y$ }* ]/ ~. Q4 @" {
9 d! L0 g1 A; I- V' _
复制代码. e& G: k' r1 s$ B1 W3 q& r
struct files_struct {
% i. C' t0 e( F* L4 V$ G  ~    atomic_t count;      /* 使用计数 */( k/ j3 H' E, g6 N
    struct fdtable *fdt; /* 指向其他fd表的指针 */: o9 p/ Z/ I: h+ v" N
    struct fdtable fdtab;/* 基 fd 表 */
  s3 d! W( ^$ n0 z% f: ?# I( w) ?    spinlock_t file_lock ____cacheline_aligned_in_smp; /* 单个文件的锁 */
$ q0 E( r3 Y' D/ j4 j" T    int next_fd;                                       /* 缓存下一个可用的fd */
2 I5 q* r' @1 K* W    struct embedded_fd_set close_on_exec_init;         /* exec()时关闭的文件描述符链表 */6 [$ a- Z& i( h+ }
    struct embedded_fd_set open_fds_init;              /* 打开的文件描述符链表 *// P- X& I$ G+ L2 n: {
    struct file * fd_array[NR_OPEN_DEFAULT];           /* 缺省的文件对象数组 */
8 T" B1 w1 N" F; i9 v. h. E};) i8 E( r' L2 B' r
复制代码' j1 [* X% _1 ?; \$ ^
  ?, u0 [9 H2 R4 r

$ ^5 L% U0 L1 J* Mstruct fs_struct 位于:<linux/fs_struct.h>3 t; y3 U4 P; I4 J2 r

4 V9 }) ^8 E# ]  T  o复制代码
  _$ p" f; o: _2 C  Y3 [. R: tstruct fs_struct {
! Y- O1 _4 m; y1 h    int users;               /* 用户数目 */
- K' R9 ~1 G" C, f    rwlock_t lock;           /* 保护结构体的读写锁 */- H7 I6 j, D0 |0 h- i/ g" N
    int umask;               /* 掩码 */1 F+ g6 k' B# W! r
    int in_exec;             /* 当前正在执行的文件 */6 W- @. z' Q3 l6 E. q
    struct path root, pwd;   /* 根目录路径和当前工作目录路径 */
% t1 |0 K: D* P# K8 }0 U2 p};
: M# x. K$ U( u+ }3 c复制代码
% {! J9 u+ _2 ]5 q9 W* N* p9 C2 o
' T& F/ @9 ]( Y* B/ }$ E. t# L" Y* o! V0 V5 X7 j( ]7 e2 T  K+ A
struct mmt_namespace 位于:<linux/mmt_namespace.h>) I9 u& \" Y! h; ?( y8 W# q* I  x
' h* L8 i( r, {- f* k) h
但是在2.6内核之后似乎没有这个结构体了,而是用 struct nsproxy 来代替。
2 l$ H8 e+ K+ ]2 q. F) f: X# K$ r) G( P7 w# w. S1 T* ?! x
以下是 struct task_struct 结构体中关于文件系统的3个属性。1 w) e: H2 E8 _9 [3 i: c

' c: a) S$ K4 ystruct task_struct 的定义位于:<linux/sched.h>
, M- q: b: j0 X! {
2 }; o/ R' E$ q- a+ W复制代码& W/ x% W* x$ y- C& S3 e
/* filesystem information */2 i9 t0 w0 O6 Q0 ^5 i# q
    struct fs_struct *fs;
) k( ~7 B5 h) T& X0 s2 A4 m/* open file information */
0 u: D, y+ O$ W' H; e$ k    struct files_struct *files;$ ?' S) M' C" @; q& n
/* namespaces */
/ b3 D9 d2 x3 D    struct nsproxy *nsproxy;) K7 V& l8 ]6 e: m
复制代码! G- o+ e6 j0 S

2 \  |- s. \9 b$ g: q  [5 Z- ^8 o
5. 小结( c4 Y) W  F! ^
VFS 统一了文件系统的实现框架,使得在linux上实现新文件系统的工作变得简单。# \# g' u8 A9 t  M" R- a
! v4 ^2 e7 p: F" v6 {: G9 ~+ X
目前linux内核中已经支持60多种文件系统,具体支持的文件系统可以查看 内核源码 fs 文件夹下的内容。

该用户从未签到

2#
发表于 2021-1-19 18:23 | 只看该作者
Linux内核设计与实现之虚拟文件系统
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

推荐内容上一条 /1 下一条

EDA365公众号

关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

GMT+8, 2025-6-25 12:51 , Processed in 0.093750 second(s), 26 queries , Gzip On.

深圳市墨知创新科技有限公司

地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

快速回复 返回顶部 返回列表