📄 核心源码的物理布局.txt
字号:
VFS:超级块
程序和设备的执行只是fs目录的一部分。fs的多数文件,以及子目录中的所有文件,都
与文件相关的系统调用有关。更特别地:它们实现了所谓的VFS机制:虚拟文件系统(或
虚拟文件系统切换----这些解释有点互相矛盾)。
概念地说,VFS是Linux文件处理软件的一层。通过利用各种文件系统格式提供的特征,
这层提供了到文件的统一接口。在磁盘上布局信息的各种技巧可以通过VFS接口用一致的
方法访问。实际上,VFS减少到几个定义“操作”的结构。每个文件系统声明处理超级块
、inode、和文件的这些操作。我们在本书中已经使用的file_operations结构就是VFS接
口的一部分。
核心通过安装每个文件系统来访问它。mount的一个任务是从磁盘上搜取所谓的“超级块
”。超级块是一个文件系统中的主要数据结构。它的名字来自于一个事实,历史上,它
曾经是磁盘上的第一个物理块。文件super.c包括与超级块有关的有趣操作的源码:读取
的同步它们、安装和卸装文件系统、在引导时安装根文件系统。
除了这些有趣的(还有点复杂)操作,super.c也返回与文件系统有关的信息,包括由/p
roc/mounts和/proc/filesystems提供的信息。
函数register_filesystem和unregister_filesystem被模块化的文件系统类型使用;它
们也在super.c中定义。文件filesystems.c是一个#ifdef语句的短表。依赖于那些选项
被编译进核心,对应不同文件系统的各种init函数被调用。每个文件系统类型的init函
数调用register_filesystem,因此不需要别的条件编译选项。
Inode和高速缓存技术
VFS接口的下一块是inode。每个inode由一个由设备号和inode号组成的唯一的键值确定
。用户程序用文件名去访问文件系统中的结点,核心负责将文件名映射到唯一的键值。
为了获得更好的性能,Linux维护了两个与inode键值相关的高速缓存:inode高速缓存和
名字高速缓存(也叫目录高速缓存)。另外,核心还负责已经熟悉的缓冲高速缓存。
inode高速缓存是一个哈希表,用于从设备/ inode号键值查找inode结构。高速缓存的实
现,以及读写inode的例程,都在inode.c中。这个文件也实现了inode结构的锁机制以防
止可能的死锁。
名字高速缓存是一个表,它将inode号和文件名关联起来。当一个名字被连着用了几次,
高速缓存就可以避免重复的目录查询。源文件dcache.c包含管理高速缓存的软件机制。
使用名字高速缓存的多数系统调用和函数是namei.c(表示name-inode)的一部分,包括
sys_mkdir,sys_symlink,sys_rename,及类似的调用。
缓冲高速缓存是系统中最大的数据高速缓存,它的实现出现在巨大的文件buffer.c中。
至于文件,file_table.c负责file结构的分配和释放。这包括get_empty_filp,它被ope
n,pipe,socket调用。
open.c
fs中其它源文件中多数负责文件操作----与在驱动程序中需要实现的一样。第一个这样
的文件是open.c,它包括了很多系统调用的代码。它也包括sys_open,及它的低级的对
应者do_open,还有sys_close。这些系统调用都很直接,被映射到filp->f_op。
open.c包含修改inode的系统调用:chown和chmod,以及它们的对应者fchown和fchmod。
如果你对安全检查和不可变标志的使用感兴趣,你可以浏览源码,它可以被几乎所有的
Unix编程者理解。改变inode中的次数也被支持----utime和utimes在这里定义。
chroot,chdir,和fchdir也在open.c中找到,同时还有其它“改变”函数。
源码中定义的第一个函数是statfs和fstatfs,它们通过inode->I_sb->s_op->statfs被
分派文件系统特定的代码。
truncate和access调用也出现在这个文件中。后者用进程的实际uid和gid检查文件的权
限,暂时不考虑fsuid和fsgid。
read.c和readdir.c
正如其名字所示,read_write.c包含读和写,但它也包含了lseek和llseek(有人猜测这
个前导l的数目是每十年增加一个)。lseek是使用off_t(long)的标准调用,而llseek使
用loff_t(long long)。llseek系统调用映射到lseek文件操作,它被作为lseek的超集实
现。有趣的是注意到核心2.1版将这个方法改名为llseek,以与其实现保持一致。
read和write是很简单的函数,因为实际的数据传送是通过filp->f_op来分派的;read_w
rite.c也包含了readv和writev的代码,它们稍为复杂,因为多块数据的传输必须跨过核
心和用户边界。
Linux不允许你直接读一个目录文件,如果你尝试,将会返回-EISDIR。目录只能用readd
ir系统调用来读取,它是文件系统无关的;或使用getdents,即“取得目录项”。用get
dents读一个目录要快一点,因为一个调用可以返回很多目录项,而readdir一次只能返
回一项。不过,getdents只被libc-5.2或更新的库所支持。
select.c
select的完整实现居于select.c,除了select_wait线入函数。尽管select.c的代码很有
趣,但却很难阅读,因为数据结构太复杂(这在第五章“增强的字符设备驱动程序操作
”中“底层数据结构中讨论过”)。不过select.c倒是阅读核心代码的一个好的起始点
,因为它是自包含的;除了一些无关紧要的细节外它不依赖与其它源文件。
Pipe和fifos
pipe和fifo通信通道的实现与字符设备驱动程序非常类似。通过为两个通道使用同样的
文件操作来避免了代码重复;只有open方法不一样。除了fifo_open和fifo_init外所有
的函数都在pipe.c中定义。由于fifo与文件系统结点类似,除了file_operations结构,
它们还需要一组inode_operations与之相关。正确的结构在fifo.c中定义。
下一个有趣的事情是注意到在pipe和fifo的实现中,pipe.c定义了两个file_operation
结构:一个是为通道的可读侧的,一个是为可写侧的。这允许在read和write中跳过权限
检查。
控制函数
文件的“控制”系统调用在两个根据这个调用命名的文件中:fcntl.c和ioctl.c。前者
基本上是自包含的,因为为fcntl定义的所有的命令都是预定义的。由于其中一些只是du
p的包裹者,因此dup的实现也在fcntl.c中。另外,由于fcntl调用负责异步触发,kill_
fasync也可以在此找到。
ioctl.c包含ioctl系统调用的外部接口。它是一个短文件,当它收到不认识的命令时,
还要依赖于文件操作。
文件锁
Linux中实现了两类锁接口,flock系统调用和fcntl命令。后者是POSIX兼容的。
文件locks.c包含了处理两个调用的代码。它也包括对强制锁的支持,它在2.0.0之前是
一个编译选项,在2.1.4被改为一个安装(mount)时选项。
次要文件
fs的文件还支持磁盘定额(quota)。dquot.c实现了定额机制,而noquot.c包含空函数
;如果定额没有被包含在核心的配置中,它就代替dquot.c被编译。
最后,stat.c实现了stat、lstat、readlink系统调用。在2.0.x定义了stat和lstat的两
个实现,以保持与旧的x86库的后向兼容。
网络
Linux文件体系中的net目录是套接字抽象和网络协议的容库;这些特征使用了大量的代
码,因为Linux支持集中不同的网络协议。每个协议(IP,IPX等等)都居于它自己的子
目录中。Unix域的套接字被以一种网络协议对待,它们的实现可以在unix子目录找到。
有趣的是注意到2.0版的核心只包含了IPv4,而版本2.1包含了相当完整的IPv6的支持,
新来的标准解决了IPv4的编号问题。
Linux中的网络实现是基于用于设备文件的同样的文件操作。这很自然,因为网络连接(
套接字)是用一般文件描述符描述的。在核心中一个套接字是由一个结构socket(<linux
/net.h>)描述。文件socket.c是套接字文件操作的容库。它通过结构proto_ops分派系统
调用到其中的一个网络协议。这个结构被每个网络协议定义以将系统调用映射到低级的
数据处理。
net下的每个目录(除bridge,core,ethernet)都专用来实现一个网络协议。目录brid
ge包含符合IEEE规范的以太网桥的优化实现。core中文件实现了通用的网络特征如:设
备处理,火墙,选播和异名;这包括独立于底层协议(core/sock.c)的套接字缓冲区(c
ore/skbuff.c)和套接字操作的处理。最后,ethernet包含通用的以太网函数。
几乎每个net下的目录都有一个处理系统控制的文件;这样引出的信息可以通过/proc/sy
s文件树或通过sysctl系统调用来访问。到sysctl的核心接口允许对系统控制入口点动态
的增加和去除,在<linux/sysctl.h>中定义。
IPC和lib函数
进程间通信和库函数各有一个小的专用目录。
ipc目录包含一个名为util.c的通用文件,以及每种通信方式的一个源文件:sem.c,shm
..c,msg.c。msg.c负责消息队列和kerneld引擎,kerneld_send。如果IPC没有在编译时
打开,util.c引出一些空函数,它们通过返回-ENOSYS来实现IPC相关的系统调用。
库函数就象你在C程序中常见的一些工具和变量:sprintf,vsprintf,errno整数值,以
及被各种<linux/ctype.h>宏使用的_ctype数组。文件string.c包含字符串函数的可移植
实现,但只有在体系结构特定的代码不包含优化的线入函数时才能编译。如果线入函数
在头文件中定义,那么在string.c中的实现应该用#ifdef语句排除在外。
lib中最“有趣”的文件是inflate.c,它是gzip的“gunzip”部分,是从gzip本身展开
从而允许在引导时使用压缩的RAMdisk。这个技巧在需要的数据除了压缩,不能在一张软
盘上放下时使用。
Drivers
现在对Linux的drivers目录已经没什么可说的了。这个目录中的源文件在整部书里已经
被广泛引用;这也使我为什么在讨论源文件树时把它们留在最后。
字符设备、块设备、和网络驱动程序
尽管这些目录中大多数驱动程序是特定于某种特别的硬件的,还是有几个文件在系统设
置时起到比较通用的作用。
关于目录drivers/char,实现N_TTY行规则的代码就在这儿。N_TTY是系统tty的缺省行规
则,它在n_tty.c中定义。在drivers/char中的另一个设备无关的文件是misc.c,它提供
对“杂类”设备的支持。一个 “杂类”设备是有一个次设备号的简化的字符设备驱动程
序。
这个目录还包含了对PC控制台的支持,和其它一些体系结构相关的驱动程序;它实际上
包含了一些在其它地方都不合适的杂项的集合。
drivers/block则要清晰多了。它包含了多数快设备驱动程序的单个文件驱动程序,和全
特征的IDE驱动程序,它被劈成了多个文件。这个目录的资格文件提供了通用目的的支持
;genhd.c处理分区表,ll_rw_block.c负责与物理设备之间数据传送的低级机制。reque
st结构是ll_rw_block.c的主要部分。
drivers/net包含了PC网卡驱动程序的长列表,以及几个其它体系结构的驱动程序(例如
,sunlance.c是为了多数Sparc计算机的接口的)。有些驱动程序比它看起来要复杂一些
,这在第十四章“网络驱动程序”中介绍过。例如ppp.c声明自己的行规则。
在drivers/net下的通用目的源文件是Space.c和net_init.c。Space.c主要是包含了一个
可用网络设备的表。这个表包含了一个#ifdef项的长列表,它们在系统引导时被检差以
检测和初始化网络设备。net_init.c包含ether_setup,tr_setup,和类似的通用目的函
数。
SCSI驱动程序
如在第一章“Linux核心介绍”中所提到的,Linux中的SCSI驱动程序没有被包含在一般
的字符和块设备类中。这是因为SCSI接口总线有它自己的标准。因此,将SCSI设备和其
它驱动程序分开,开发者可以分离和共享公用代码。
drivers/scsi中多数文件是为特定SCSI控制器的低级驱动程序。通用目的的SCSI实现在
文件scsi_*.c中定义,还有sd.c是磁盘支持,sr.c是CD-ROM支持,st.c是SCSI磁带支持
,sg.c是通用SCSI支持。最后一个源文件对使用SCSI协议的设备定义
--
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -