文件系统

目 录

  1. 文件系统
    1. 源码导读
    2. CD_ROM
    3. 文件页缓冲结构
    4. 块设备缓冲区结构
    5. 散列算法
    6. permission(inode,mask)
    7. IDE硬盘驱动器读写
    8. proc


文件系统

    众所周知,文件系统是Unix系统最基本的资源。最初的Unix系统一般都只支持一种单一类型的文件系统,在这种情况下,文件系统的结构深入到整个系统内核中。而现在的系统大多都在系统内核和文件系统之间提供一个标准的接口,这样不同文件结构之间的数据可以十分方便地交换。Linux也在系统内核和文件系统之间提供了一种叫做VFS(virtual file system)的标准接口。

    这样,文件系统的代码就分成了两部分:上层用于处理系统内核的各种表格和数据结构;而下层用来实现文件系统本身的函数,并通过VFS来调用。这些函数主要包括:

    * 管理缓冲区(buffer. c)。
    * 响应系统调用fcntl() 和ioctl()(fcntl.c and ioctl.c)。
    * 将管道和文件输入/输出映射到索引节点和缓冲区(fifo.c, pipe.c)。
    * 锁定和不锁定文件和记录(locks.c)。
    * 映射名字到索引节点(namei.c, open.c)。
    * 实现select( )函数(select . c)。
    * 提供各种信息(stat.c)。
    * 挂接和卸载文件系统(super.c)。
    * 调用可执行代码和转存核心(exec.c)。
    * 装入各种二进制格式(bin_fmt*.c)。

    VFS接口则由一系列相对高级的操作组成,这些操作由和文件系统无关的代码调用,并且由不同的文件系统执行。其中最主要的结构有inode_operations 和file_operations。file_system_type是系统内核中指向真正文件系统的结构。每挂接一次文件系统,都将使用file_system_type组成的数组。file_system_type组成的数组嵌入到了fs/filesystems.c中。相关文件系统的read_super函数负责填充super_block结构。


[目录]


源码导读

*Linux 如何维护它支持的文件系统中的文件
*描述了虚拟文件系统( Virtual File System VFS )
*解释了 Linux 核心中真实的文件系统如何被支持

    Linux 的一个最重要的特点之一使它可以支持许多不同的文件系统。这让它非常灵活,可以和许多其他操作系统共存。在写作本章的时候, Linux 可一直支持15 种文件系统: ext 、 ext2 、 xia 、 minix 、 umsdos 、 msdos 、vfat 、 proc 、 smb 、 ncp 、 iso9660 、 sysv 、 hpfs 、 affs 和 ufs ,而且不容置疑,随着时间流逝,会加入更多的文件系统。

    在 Linux 中,象 Unix 一样,系统可以使用的不同的文件系统不是通过设备标识符(例如驱动器编号或设备名称)访问,而是连接成一个单一的树型的结构,用一个统一的单个实体表示文件系统。 Linux 在文件系统安装的时候把它加到这个单一的文件系统树上。所有的文件系统,不管什么类型,都安装在一个目录,安装的文件系统的文件掩盖了这个目录原来存在的内容。这个目录叫做安装目录或安装点。当这个文件系统卸载的时候,安装目录自己的文件又可以显现出来。

    当磁盘初始化的时候(比如用 fdisk ),利用一个分区结构把物理磁盘划分成一组逻辑分区。每一个分区可以放一个文件系统,例如一个 EXT2 文件系统。文件系统在物理设备的块上通过目录、软链接等把文件组织成逻辑的树型结构。可以包括文件系统的设备是块设备。系统中的第一个 IDE 磁盘驱动器的第一个分区,IDE 磁盘分区 /dev/hda1 ,是一个块设备。 Linux 文件系统把这些块设备看成简单的线性的块的组合,不知道也不去关心底层的物理磁盘的尺寸。把对设备的特定的块的读的请求映射到对于设备有意义的术语:这个块保存在硬盘上的磁道、扇区和柱面,这是每一个块设备驱动程序的任务。一个文件系统不管它保存在什么设备上,都应该用同样的方式工作,有同样的观感。另外,使用 Linux 的文件系统
,是否这些不同的文件系统在不同的硬件控制器的控制下的不同的物理介质上都是无关紧要的(至少对于系统用户是这样)。文件系统甚至可能不在本地系统上,它可能是通过网络连接远程安装的。考虑以下的例子,一个 Linux 系统的根文件系统在一个 SCSI 磁盘上。

A E boot etc lib opt tmp usr

C F cdrom fd proc root var sbin

D bin dev home mnt lost+found

    不管是操作这些文件的用户还是程序都不需要知道 /C 实际上是在系统的第一个 IDE 磁盘上的一个安装的 VFAT 文件系统。本例中(实际是我家中的 Linux 系统), /E 是次 IDE 控制器上的 master IDE 磁盘。第一个 IDE 控制器是 PCI控制器,而第二个是 ISA 控制器,也控制着 IDE CDROM ,这些也都无关紧要。我可以用一个 modem 和 PPP 网络协议拨号到我工作的网络,这时,我可以远程安装
我的 Alpha AXP Linux 系统的文件系统到 /mnt/remote 。

    文件系统中的文件包含了数据的集合:包含本章源的文件是一个 ASCII 文件,叫做 filesystems.tex 。一个文件系统不仅保存它包括的文件的数据,也保存文件系统的结构。它保存了 Linux 用户和进程看到的所有的信息,例如文件、目录、软链接、文件保护信息等等。另外,它必须安全地保存这些信息,操作系统的基本的一致性依赖于它的文件系统。没有人可以使用一个随机丢失数据和文件的操作系统(不知道是否有,虽然我曾经被拥有的律师比 Linux 开发者还多的操作系统伤害过)。

    Minix 是 Linux 的第一个文件系统,有相当的局限,性能比较差。它的文件名不能长于 14 个字符(这仍然比 8.3 文件名要好),最大的文集大小是 64M 字节。第一眼看去, 64M 字节好像足够大,但是设置中等的数据库需要更大的文件大小。第一个专为 Linux 设计的文件系统,扩展文件系统或 EXT ( Extend File System ),在 1992 年 4 月引入,解决了许多问题,但是仍然感到性能低。所以, 1993 年,增加了扩展文件系统第二版,或 EXT2 。这种文件系统在本章稍后详细描述。

    当 EXT 文件系统增加到 Linux 的时候进行了一个重要的开发。真实的文件系统通过一个接口层从操作系统和系统服务中分离出来,这个接口叫做虚拟文件系统或 VFS 。 VFS 允许 Linux 支持许多(通常是不同的)文件系统,每一个都向VFS 表现一个通用的软件接口。 Linux 文件系统的所有细节都通过软件进行转换,所以所有的文件系统对于 Linux 核心的其余部分和系统中运行的程序显得一样。 Linux 的虚拟文件系统层允许你同时透明地安装许多不同的文件系统。

    Linux 虚拟文件系统的实现使得对于它的文件的访问尽可能的快速和有效。它也必须保证文件和文件数据正确地存放。这两个要求相互可能不平等。 Linux VFS 在安装和使用每一个文件系统的时候都在内存中高速缓存信息。在文件和目录创建、写和删除的时候这些高速缓存的数据被改动,必须非常小心才能正确地更新文件系统。如果你能看到运行的核心中的文件系统的数据结构,你就能够看到文件系统读写数据块,描述正在访问的文件和目录的数据结构会被创建和破坏,同时设备驱动程序会不停地运转,获取和保存数据。这些高速缓存中最重要的是 BufferCache ,在文件系统访问它们底层的块设备的时候结合进来。当块被访问的时候它们被放到 Buffer Cache ,根据它们的状态放在不同的队列中。 Buffer Cache 不仅缓存数据缓冲区,它也帮助管理块设备驱动程序的异步接口。

The Second Extended File System (EXT2)

    EXT2 被发明( Remy Card )作为 Linux 一个可扩展和强大的文件系统。它至少在 Linux 社区中是最成功的文件系统,是所有当前的 Linux 发布版的基础。 EXT2 文件系统,象所有多数文件系统一样,建立在文件的数据存放在数据块中的前提下。这些数据块都是相同长度,虽然不同的 EXT2 文件系统的块长度可以不同,但是对于一个特定的 EXT2 文件系统,它的块长度在创建的时候就确定了(使用
mke2fs )。每一个文件的长度都按照块取整。如果块大小是 1024 字节,一个1025 字节的文件会占用两个 1024 字节的块。不幸的是这一意味着平均你每一个文件浪费半个块。通常计算中你会用磁盘利用来交换 CPU 对于内存的使用,这种情况下, Linux 象大多数操作系统一样,为了较少 CPU 的负载,使用相对低效率的磁盘利用率来交换。不是文件系统中所有的块都包含数据,一些块必须用于放置描述文件系统结构的信息。 EXT2 用一个 inode 数据结构描述系统中的每一个文件,定义了系统的拓扑结构。一个 inode 描述了一个文件中的数据占用了哪些块以及文件的访问权限、文件的修改时间和文件的类型。 EXT2 文件系统中的每一个文件都用一个 inode 描述,而每一个 inode 都用一个独一无二的数字标识。文件
系统的 inode 都放在一起,在 inode 表中。 EXT2 的目录是简单的特殊文件(它们也使用 inode 描述),包括它们目录条目的 inode 的指针。

    只要提到文件系统,块设备都可以看作一系列能够读写的块。文件系统不需要关心自身要放在物理介质的哪一个块上,这是设备驱动程序的工作。当一个文件系统需要从包括它的块设备上读取信息或数据的时候,它请求对它支撑的设备驱动程序读取整数数目的块。 EXT2 文件系统把它占用的逻辑分区划分成块组( Block Group )。每一个组除了当作信息和数据块来存放真实的文件和目录之外,还复制对于文件系统一致性至关重要的信息。这种复制的信息对于发生灾难,文件系统需要恢复的时候是必要的。下面对于每一个块组的内容进行了详细的描述。

The EXT2 Inode ( EXT2 I 节点)

    在 EXT2 文件系统中, I 节点是建设的基石:文件系统中的每一个文件和目录都用一个且只用一个 inode 描述。每一个块组的 EXT2 的 inode 都放在 inode 表中,还有一个BITMap,让系统跟踪分配和未分配的 I 节点。显示了一个 EXT2 inode 的格式,在其他信息中,它包括一些域:

参见 include/linux/ext2_fs_i.h

    mode 包括两组信息:这个 inode 描述了什么和用户对于它的权限。对于EXT2 ,一个 inode 可以描述一个文件、目录、符号链接、块设备、字符设备或FIFO 。

    Owner Information 这个文件或目录的数据的用户和组标识符。这允许文件系统正确地进行文件访问权限控制

    Size 文件的大小(字节)

    Timestamps 这个 inode 创建的时间和它上次被修改的时间。

    Datablocks 指向这个 inode 描述的数据的块的指针。最初的 12 个是指向这个 inode 描述的数据的物理块,最后的 3 个指针包括更多级的间接的数据块。例如,两级的间接块指针指向一个指向数据块的块指针的块指针。的这意味着小于或等于 12 数据块大小的文件比更大的文件的访问更快。

    你应该注意 EXT2 inode 可以描述特殊设备文件。这些不是真正的文件,程序可以用于访问设备。 /dev 下所有的设备文件都是为了允许程序访问 Linux 的设备。例如 mount 程序用它希望安装的设备文件作为参数。

The EXT2 Superblock ( EXT2 超级块)

    超级块包括这个文件系统基本大小和形状的描述。它里面的信息允许文件系统管理程序用于维护文件系统。通常文件系统安装时只有块组 0 中的超级块被读取,但是每一个块组中都包含一个复制的拷贝,用于系统崩溃的时候。除了其他一些信息,它包括:

参见 include/linux/ext2_fs_sb.h

Magic Number 允许安装软件检查这是否是一个 EXT2 文件系统的超级块。对于当前版本的 EXT2 是 0xEF53 。

    Revision Level major 和 minor 修订级别允许安装代码确定这个文件系统是否支持只有在这种文件系统特定修订下才有的特性。这也是特性兼容域,帮助安装代码确定哪些新的特征可以安全地使用在这个文件系统上。

Mount Count and Maximum Mount Count 这些一起允许系统确定这个文件系统是否需要完全检查。每一次文件系统安装的时候 mount count 增加,当它等于maximum mount count 的时候,会显示告警信息“ maximal mount count reached , running e2fsck is recommended ”。

Block Group Number 存放这个超级块拷贝的块组编号。

Block Size 这个文件系统的块的字节大小,例如 1024 字节。

Blocks per Group 组中的块数目。象块大小一样,这是文件系统创建的时候确定的。

Free Blocks 文件系统中空闲块的数目

Free Inodes 文件系统中空闲的 inode

First Inode 这是系统中第一个 inode 的编号。一个 EXT2 根文件系统中的第一个 inode 是‘ / ’目录的目录条目


The EXT2 Group Descriptor ( EXT2 组描述符)

每一个块组都有一个数据结构描述。象超级块,所有得亏组的组描述符在每一块组都进行复制。每一个组描述符包括以下信息:

参见 include/linux/ext2_fs.h ext2_group_desc

Blocks Bitmap 这个块组的块分配位图的块编号,用在块的分配和回收过程中

Inode Bitmap 这个块组的 inode 位图的块编号。用在 inode 的分配和回收过程中。

Inode Table 这个块组的 inode table 的起始块的块编号。每一个 EXT2 inode数据结构表示的 inode 在下面描述

Free blocks count , Free Inodes count , Used directory count

    组描述符依次排列,它们一起组成了组描述符表( group descriptor
table )。每一个块组包括块组描述符表和它的超级块的完整拷贝。只有第一个拷贝(在块组 0 )实际被 EXT2 文件系统使用。其他拷贝,象超级块的其他拷贝一样,只有在主拷贝损坏的时候才使用。

EXT2 Directories ( EXT2 目录)

    在 EXT2 文件系统中,目录是特殊文件,用来创建和存放对于文件系统中的文件的访问路径。图 9.3 显示了内存中一个目录条目的布局。一个目录文件,是一个目录条目的列表,每一个目录条目包括以下信息:

参见 include/linux/ext2_fs.h ext2_dir_entry

这个目录条目的 inode 。这是个放在块组的 inode 表中的 inode 数组的索引。

Name length 这个目录条目的字节长度

Name 这个目录条目的名字

每一个目录中的前两个条目总是标准的“ . ”和“ .. ” , 分别表示“本目录”和“父目录”。

9.1.5 Finding a File in a EXT2 File System (在一个 EXT2 文件系统中查找一个文件)

    Linux 的文件名和所有的 Unix 文件名的格式一样。它是一系列目录名,用“ / ”分隔,以文件名结尾。一个文件名称的例子是 /home/rusling/.cshrc ,其中 /home 和 /rusling 是目录名,文件名是 .cshrc 。象其它 Unix 系统一样, Linux 不关心文件名本身的格式:它可以任意长度,由可打印字符组成。为了在EXT2 文件系统中找到代表这个文件的 inode ,系统必须逐个解析目录中的文件
名直到得到这个文件。

    我们需要的第一个 inode 是这个文件系统的根的 inode 。我们通过文件系统的超级块找到它的编号。为了读取一个 EXT2 inode 我们必须在适当的块组中的inode 表中查找。举例,如果根的 inode 编号是 42 ,那么我们需要块组 0 中的 inode 表中的第 42 个 inode 。 Root inode 是一个 EXT2 目录,换句话说root inode 的模式描述它是一个目录,它的数据块包括 EXT2 目录条目。

    Home 是这些目录条目之一,这个目录条目给了我们描述 /home 目录的 inode 编号。我们必须读取这个目录(首先读取它的 inode ,然后读取从这个 inode描述的数据块读取目录条目),查找 rusling 条目,给出描述 /home/rusling 目录的 inode 编号。最后,我们读取描述 /home/rusling 目录的 inode 指向的目录条目,找到 .cshrc 文件的 inode 编号,这样,我们得到了包括文件里信息的
数据块。

Changing the size of a File in an EXT2 File System (在 EXT2 文件系统中改变一个文件的大小)

    文件系统的一个常见问题是它趋于更多碎片。包含文件数据的块分布在整个文件系统,数据块越分散,对于文件数据块的顺序访问越没有效率。 EXT2 文件系统试图克服这种情况,它分配给一个文件的新块物理上和它的当前数据块接近或者至少和它的当前数据块在一个块组里面。只有这个失败了它才分配其它块组中的数据块。

    无论何时一个进程试图象一个文件写入数据, Linux 文件系统检查数据是否会超出文件最后分配块的结尾。如果是,它必须为这个文件分配一个新的数据块。直到这个分配完成,该进程无法运行,它必须等待文件系统分配新的数据块并把剩下的数据写入,然后才能继续。 EXT2 块分配例程所要做的第一个事情是锁定这个文件系统的 EXT2 超级块。分配和释放块需要改变超级块中的域, Linux 文件系统不能允许多于一个进程同一时间都进行改变。如果另一个进程需要分配更多的数据块,它必须等待,直到这个进程完成。等待超级块的进程被挂起,不能运行,直到超级块的控制权被它的当前用户释放。对于超级块的访问的授权基于一个先来先服务的基础( first come first serve ),一旦一个进程拥有了超级块的控制,它一直维持控制权到它完成。锁定超级块之后,进程检查文件系统是否有足够的空闲块。如果没有足够的空闲块,分配更多的尝试会失败,进程交出这个文件系统超级块的控制权。

    如果文件系统中有足够的空闲块,进程会试图分配一块。如果这个 EXT2 文件系统已经建立了预分配的数据块,我们就可以取用。预分配的块实际上并不存在,它们只是分配块的位图中的保留块。 VFS inode 用两个 EXT2 特有的域表示我们试图分配新数据块的文件: prealloc_block and prealloc_count ,分别是预分配块中第一块的编号和预分配块的数目。如果没有预分配块或者预分配被禁止,EXT2 文件系统必须分配一个新的数据块。 EXT2 文件系统首先查看文件最后一个数据块之后数据块是否空闲。逻辑上,这是可分配的效率最高的块,因为可以让顺序访问更快。如果这个块不是空闲,继续查找,在随后的 64 块中找理想的数据块。这个块,虽然不是最理想,但是至少和文件的其它数据块相当接近,在一个块组中。

参见 fs/ext2/balloc.c ext2_new_block()

    如果这些块都没有空闲的,进程开始顺序查看所有其它块组直到它找到空闲的块。块分配代码在这些块组中查找 8 个空闲数据块的簇。如果无法一次找到 8 个,它会降低要求。如果希望进行块预分配,并允许,它会相应地更新prealloc_block 和 prealloc_count 。

    不管在哪里找到了空闲的数据块,块分配代码会更新块组的块位图,并从buffer cache 中分配一个数据缓冲区。这个数据缓冲区使用支撑文件系统的设备标识符和分配块的块编号来唯一标识。缓冲区中的数据被置为 0 ,缓冲区标记为“ dirty ”表示它的内容还没有写到物理磁盘上。最后,超级块本身也标记位“ dirty ”,显示它进行了改动,然后它的锁被释放。如果有进程在等待超级块,那么队列中第一个进程就允许运行,得到超级块的排它控制权,进行它的文件操作。进程的数据写到新的数据块,如果数据块填满,整个过程重复进行,再分配其它数据块

The Virtual File System (虚拟文件系统 VFS )

    Linux 核心的虚拟文件系统和它的真实的文件系统之间的关系。虚拟文件系统必须管理任何时间安装的所有的不同的文件系统。为此它管理描述整个文件系统(虚拟)和各个真实的、安装的文件系统的数据结构。

    相当混乱的是, VFS 也使用术语超级块和 inode 来描述系统的文件,和EXT2 文件系统使用的超级块和 inode 的方式非常相似。象 EXT2 的 inode ,VFS 的 inode 描述系统中的文件和目录:虚拟文件系统的内容和拓扑结构。从现在开始,为了避免混淆,我会用 VFS inode 和 VFS 超级块以便同 EXT2 的 inode和超级块区分开来。

参见 fs/*

    当每一个文件系统初始化的时候,它自身向 VFS 登记。这发生在系统启动操作系统初始化自身的时候。真实的文件系统自身建立在内核中或者是作为可加载的模块。文件系统模块在系统需要的时候加载,所以,如果 VFAT 文件系统用核心模块的方式实现,那么它只有在一个 VFAT 文件系统安装的时候才加载。当一个块设备文件系统安装的时候,(包括 root 文件系统), VFS 必须读取它的超级块。
每一个文件系统类型的超级块的读取例程必须找出这个文件系统的拓扑结构,并把这些信息映射到一个 VFS 超级块的数据结构上。 VFS 保存系统中安装的文件系统的列表和它们的 VFS 超级块列表。每一个 VFS 超级块包括了文件系统的信息和完成特定功能的例程的指针。例如,表示一个安装的 EXT2 文件系统的超级块包括一个 EXT2 相关的 inode 的读取例程的指针。这个 EXT2 inode 读取例程,象所有
的和文件系统相关的 inode 读取例程一样,填充 VFS inode 的域。每一个 VFS超级块包括文件系统中的一个 VFS inode 的指针。对于 root 文件系统,这是表示“ / ”目录的 inode 。这种信息映射对于 EXT2 文件系统相当高效,但是对于其他文件系统相对效率较低。

    当系统的进程访问目录和文件的时候,调用系统例程,游历系统中的 VFSinode 。例如再一个目录中输入 ls 或者 cat 一个文件,让 VFS 查找代表这个文件系统的 VFS inode 。映为系统中的每一个文件和目录都用一个 VFS inode 代表,所以一些 inode 会被重复访问。这些 inode 保存在 inode cache ,这让对它们的访问更快。如果一个 inode 不在 inode cache 中,那么必须调用一个和文件系统相关的例程来读取适当的 inode 。读取这个 inode 的动作让它被放到了inode cache ,以后对这个 inode 的访问会让它保留在 cache 中。较少使用的VFS inode 会从这个高速缓存中删除。

参见 fs/inode.c

    所有的 Linux 文件系统使用一个共同的 buffer cache 来缓存底层的设备的数据缓冲区,这样可以加速对于存放文件系统的物理设备的访问,从而加快对文件系统的访问。这个 buffer cache 独立于文件系统,集成在 Linux 核心分配、读和写数据缓冲区的机制中。让 Linux 文件系统独立于底层的介质和支撑的设备驱动程序有特殊的好处。所有的块结构的设备向 Linux 核心登记,并表现为一个统一的,以块为基础的,通常是异步的接口。甚至相对复杂的块设备比如 SCSI 设备也是这样。当真实的文件系统从底层的物理磁盘读取数据的,引起块设备驱动程序从它们控制的设备上读取物理块。在这个块设备接口中集成了 buffer cache 。当文件系统读取了块的时候,它们被存放到了所有的文件系统和 Linux 核心共享的
全局的 buffer cache 中。其中的 buffer (缓冲区)用它们的块编号和被读取设备的一个唯一的标识符来标记。所以,如果相同的数据经常需要,它会从buffer cache 中读取,而不是从磁盘读取(会花费更多时间)。一些设备支持超前读( read ahead ),数据块会预先读取,以备以后可能的读取。

参见 fs/buffer.c

    VFS 也保存了一个目录查找的缓存,所以一个常用的目录的 inode 可以快速找到。作为一个试验,试着对于你最近没有列表的目录列表。第一次你列表的时候,你会注意到短暂的停顿,当时第二次你列表的时候,结果会立即出来。目录缓存本身不存储目录里的 inode ,这是 inode cache 负责的,目录缓存只是存储目录项目全称和它们的 inode 编号。

参见 fs/dcache.c

The VFS Superblock ( VFS 超级块)

每一个安装的文件系统都用 VFS 超级块表示。除了其它信息, VFS 超级块包括:


参见 include/linux/fs.h

Device 这是包含文件系统的块设备的设备标识符。例如, /dev/hda1 ,系统中的第一个 IDE 磁盘,设备标识符是 0x301

Inode pointers 其中的 mounted inode 指针指向该文件系统的第一个 inode 。

Covered inode 指针指向文件系统安装到的目录的 inode 。对于 root 文件系统,它的 VFS 超级块中没有 covered 指针。

Blocksize 文件系统块的字节大小,例如 1024 字节。

Superblock operations 指向一组本文件系统超级块例程的指针。除了其他类型之外, VFS 使用这些例程读写 inode 和超级块

File System type 指向这个安装的文件系统的 file_system_type 数据结构的一个指针

File System Specific 指向这个文件系统需要的信息的一个指针


The VFS Inode

象 EXT2 文件系统, VFS 中每一个文件、目录等等都用一个且只用一个 VFS inode 代表。每一个 VFS inode 中的信息使用文件系统相关的例程从底层的文件系统中获取。 VFS inode 只在核心的内存中存在,只要对系统有用,就一直保存在 VFS inode cache 中。除了其它信息, VFS inode 包括一些域:

参见 include/linux/fs.h

device 存放这个文件(或这个 VFS inode 代表的其它实体)的设备的设备标识符。

Inode nunber 这个 inode 的编号,在这个文件系统中唯一。 Device 和 inode number 的组合在整个虚拟文件系统中是唯一的。

Mode 象 EXT2 一样,这个域描述这个 VFS inode 代表的东西和对它的访问权限。

User ids 属主标识符

Times 创建、修改和写的时间

Block size 这个文件的块的字节大小,例如 1024 字节

Inode operations 指向一组例程地址的指针。这些例程和文件系统相关,执行对于这个 inode 的操作,例如 truncate 这个 inode 代表的文件

Count 系统组件当前使用这个 VFS inode 的数目。 Count 0 意味着这个 inode是空闲,可以废弃或者重用。

Lock 这个域用于锁定这个 VFS inode 。例如当从文件系统读取它的时候

Dirty 显示这个 VFS inode 是否被写过,如果这样,底层的文件系统需要更新。


File system specific information

Registering the File Systems (登记文件系统)

    当你建立 Linux 核心的时候,你会被提问是否需要每一个支持的文件系统。当核心建立的时候,文件系统初始化代码包括对于所有内建的文件系统的初始化例程的调用。 Linux 文件系统也可以建立成为模块,这种情况下,它们可以在需要的时候加载或者手工使用 insmod 加载。当家在一个文件系统模块的时候,它自身向核心登记,当卸载的时候,它就注销。每一个文件系统的初始化例程都向虚拟文件系统注册自身,并用一个 file_system_type 数据结构代表,这里面包括文件系统的名称和一个指向它的 VFS 超级块的读取例程的指针。file_system_type 数据结构被放到了由 file_systems 指针指向的一个列表中。每一个 file_system_type 数据结构包括以下信息:

参见 fs/filesystems.c sys_setup()

参见 include/linux/fs.h file_system_type

Superblock read routine 在这个文件系统的一个实例安装的时候,由 VFS 调用这个例程

File Systme name 文件系统的名称,例如 ext2

Device needed 是否这个文件系统需要一个设备支持?并非所有的文件系统需要一个设备来存放。例如 /proc 文件系统,不需要一个块设备

你可以检查 /proc/filesystems 来查看登记了哪些文件系统,例如:
ext2
nodev proc
iso9660

Mounting a File System (安装一个文件系统)

    当超级用户试图安装一个文件系统的时候, Linux 核心必须首先验证系统调用中传递的参数。虽然 mount 可以执行一些基本的检查,但是它不知道这个核心建立是可以支持的文件系统或者提议的安装点是否存在。考虑以下的 mount 命令:

$ mount –t iso9660 –o ro /dev/cdrom /mnt/cdrom

    这个 mount 命令会传递给核心三部分信息:文件系统的名称、包括这个文件系统的物理块设备和这个新的文件系统要安装在现存的文件系统拓扑结构中的哪一个地方。

    虚拟文件系统要做的第一件事情是找到这个文件系统。它首先查看file_systems 指向的列表中的每一个 file_system_type 数据结构,查看所有已知的文件系统。如果它找到了一个匹配的名称,它就直到这个核心支持这个文件系统类型,并得到了文件系统相关例程的地址,去读取这个文件系统的超级块。如果它不能找到一个匹配的文件系统名称,如果核心内建了可以支持核心模块按需加载,就可以继续。这种情况下,在继续之前,核心会请求核心守护进程加载适当的文件系统模块。

参见 fs/super.c do_mount()

参见 fs/super.c get_fs_type()

    第二步,如果 mount 传递的物理设备还没有安装,必须找到即将成为新的文件系统的安装点的目录的 VFS inode 。这个 VFS inode 可能在 inode cache 或者必须从支撑这个安装点的文件系统的块设备上读取。一旦找到了这个 inode ,就检查它是否是一个目录,而且没有其他文件系统安装在那里。同一个目录不能用作多于一个文件系统的安装点。

    这时,这个 VFS 安装代码必须分配以一个 VFS 超级块并传递安装信息给这个文件系统的超级块读取例程。系统所有的 VFS 超级块都保存在 super_block 数据结构组成的 super_blocks 向量表中,必须为这次安装分配一个结构。超级块读取例程必须根据它从物理设备读取得信息填充 VFS 超级块的域。对于 EXT2 文件系统而言,这种信息的映射或者转换相当容易,它只需要读取 EXT2 的超级块并填到 VFS 超级块。对于其他文件系统,例如 MS DOS 文件系统,并不是这么简单的任务。不管是什么文件系统,填充 VFS 超级块意味着必须从支持它的块设备读取描述该文件系统的信息。如果块设备不能读取或者它不包含这种类型的文件系统,mount 命令会失败。

    每一个安装的文件系统用一个 vfsmount 数据结构描述。它们在 vfsmntlist 指向的一个列表中排队。另一个指针, vfsmnttail 指向列表中最后一个条目,而 mru_vfsmnt 指针指向最近使用的文件系统。每一个 vfsmount 结构包括存放这个文件系统的块设备的设备编号,文件系统安装的目录和一个指向这个文件系统安装时所分配的 VFS 超级块的指针。 VFS 超级块指向这一类型的文件系统的file_system_type 数据结构和这个文件系统的 root inode 。这个 inode在这个文件系统加载过程中一直驻留在 VFS inode cache 中。

参见 fs/super.c add_vfsmnt()

Finding a File in the Virtual File System (在虚拟文件系统中查找一个文件)

    为了找到一个文件在虚拟文件系统中的 VFS inode , VFS 必须依次名称,一次一个目录,找到中间的每一个的目录的 VFS inode 。每一个目录查找都要调用和文件系统相关的查找例程(地址放在代表父目录的 VFS inode 中)。因为在文件系统的 VFS 超级块中总是有文件系统的 root inode ,并在超级块中用指针指示,所以整个过程可以继续。每一次查找真实文件系统中的 inode 的时候,都要检查这个目录的目录缓存。如果目录缓存中没有这个条目,真实文件系统要么从底层的文件系统要么从 inode cache 中获得 VFS inode 。

Unmounting a File System (卸载一个文件系统)

    我的工作手册通常把装配描述成为拆卸的反过程,但是对于卸载文件系统有些不同。如果系统有东西在使用文件系统的一个文件,那么这个文件系统就不能被卸载。例如,如果一个进程在使用 /mnt/cdrom 目录或它的子目录,你就不能卸载/mnt/cdrom 。如果有东西在使用要卸载的文件系统,那么它的 VFS inode 会在VFS inode cache 中。卸载代码检查整个 inode 列表,查找属于这个文件系统所占用的设备的 inode 。如果这个安装的文件系统的 VFS 超级块是 dirty ,就是它被修改过了,那么它必须被写回到磁盘上的文件系统。一旦它写了磁盘,这个VFS 超级块占用的内存就被返回到核心的空闲内存池中。最后,这个安装的vmsmount 数据结构也从 vfsmntlist 中删除并释放。

参见 fs/super.c do_umount()

参见 fs/super.c remove_vfsmnt()

The VFS Inode Cache

当游历安装的文件系统的时候,它们的 VFS inode 不断地被读取,有时是写入。虚拟文件系统维护一个 inode cache ,用于加速对于所有安装的文件系统的访问。每一次从 inode cache 读出一个 VFS inode ,系统就可以省去对于物理设备的访问。

参见 fs/inode.c

    VFS inode cache 用散列表( hash table )的方式实现,条目是指针,指向既有同样 hash value 的 VFS inode 列表。一个 inode 的 hash value 从它的inode 编号和包括这个文件系统的底层的物理设备的设备编号中计算出来。不论何时虚拟文件系统需要访问一个 inode ,它首先查看 VFS inode cache 。为了在inode hash table 中查找一个 inode ,系统首先计算它的 hash value ,然后用它作为 inode hash table 的索引。这样得到了具有相同 hash value 的 inode的列表的指针。然后它一次读取所有的 inode 直到它找到和它要找的 inode 具有相同的 inode 编号和相同的设备标识符的 inode 为止。

    如果可以在 cache 中找到这个 inode ,它的 count 就增加,表示它有了另一个用户,文件系统的访问继续进行。否则必须找到一个空闲的 VFS inode 让文件系统把 inode 读入到内存。如何得到一个空闲的 inode , VFS 有一系列选择。如果系统可以分配更多的 VFS inode ,它就这样做:它分配核心页并把它们分成新的、空闲的 inode ,放到 inode 列表中。系统中所有的 VFS inode 除了在 inode hash table 中之外也在一个由 first_inode 指向的列表。如果系统已经拥有了它允许有的所有的 inode ,它必须找到一个可以重用的 inode 。好的候选是哪些使用量( count )是 0 的 inode :这表示系统当前没有使用它们。真正重要的 VFS inode ,例如文件系统的 root inode ,已经有了一个大于 0 的使用量,所以永远不会选做重用。一旦定位到一个重用的候选,它就被清除。这个 VFS
inode 可能是脏的,这时系统必须等待它被解锁然后才能继续。在重用之前这个 VFS inode 的候选必须被清除。

    虽然找到了一个新的 VFS inode ,还必须调用一个和文件系统相关的例程,用从底层的真正的文件系统中毒取得信息填充这个 inode 。当它填充的时候,这个新的 VFS inode 的使用量为 1 ,并被锁定,所以在它填入了有效的信息之前除了它没有其它进程可以访问。

    为了得到它实际需要的 VFS inode ,文件系统可能需要访问其它一些inode 。这发生在你读取一个目录的时候:只有最终目录的 inode 是需要的,但是中间目录的 inode 也必须读取。当 VFS inode cache 使用过程并填满时,较少使用的 inode 会被废弃,较多使用的 inode 会保留在高速缓存中。


The Directory Cache (目录缓存)

    为了加速对于常用目录的访问, VFS 维护了目录条目的一个高速缓存。当真正的文件系统查找目录的时候,这些目录的细节就被增加到了目录缓存中。下一次查找同一个目录的时候,例如列表或打开里边的文件,就可以在目录缓存中找到。只有短的目录条目(最多 15 字符)被缓存,不过这是合理的,因为较短的目录名称是最常用的。例如:当 X 服务器启动的时候, /usr/X11R6/bin 非常频繁地被访问。

参见 fs/dcache.c

    目录缓存中包含一个 hash table ,每一个条目都指向一个具有相同的hash value 的目录缓存条目的列表。 Hash 函数使用存放这个文件系统的设备的设备编号和目录的名称来计算在 hash table 中的偏移量或索引。它允许快速找到缓存的目录条目。如果一个缓存在查找的时候花费时间太长,或根本找不到,这样的缓存是没有用的。

    为了保持这些 cache 有效和最新, VFS 保存了一个最近最少使用( LRU )目录缓存条目的列表。当一个目录条目第一次被放到了缓存,就是当它第一次被查找的时候,它被加到了第一级 LRU 列表的最后。对于充满的 cache ,这会移去LRU 列表前面存在的条目。当这个目录条目再一次被访问的时候,它被移到了第二个 LRU cache 列表的最后。同样,这一次它移去了第二级 LRU cache 列表前面的二级缓存目录条目。这样从一级和二级 LRU 列表中移去目录条目是没有问题的。这些条目之所以在列表的前面只是因为它们最近没有被访问。如果被访问,它们会在列表的最后。在二级 LRU 缓存列表中的条目比在一级 LRU 缓存列表中的条目更加安全。因为这些条目不仅被查找而且曾经重复引用。


The Buffer Cache


    当使用安装的文件系统的时候,它们会对块设备产生大量的读写数据块的请求所有的块数据读写的请求都通过标准的核心例程调用,以 buffer_head 数据结构的形式传递给设备驱动程序。这些数据结构给出了设备驱动程序需要的所有信息:设备标识符唯一标识了设备,块编号告诉了驱动程序读去哪一块。所有的块设备被看成同样大小的块的线性组合。为了加速对于物理块设备的访问, Linux 维护了一个块缓区的缓存。系统中所有的块缓冲区动保存在这个 buffer cache ,甚至包括那些新的、未使用的缓冲区。这个缓存区被所有的物理块设备共享:任何时候缓存区中都有许多块缓冲区,可以属于任何一个系统块设备,通常具有不同的状态。如果在 buffer cache 中有有效的数据,这就可以节省系统对于物理设备的访
问。任何用于从 / 向块设备读取 / 写入数据的块缓冲区都进入这个 buffercache 。随着时间的推移,它可能从这个缓存区中删除,为其它更需要的缓冲区让出空间,或者如果它经常被访问,就可能一直留在缓存区中。

    这个缓存区中的块缓冲区用这个缓冲区所属的设备标识符和块编号唯一标识。这个 buffer cache 由两个功能部分组成。第一部分是空闲的块缓冲区列表。每一个同样大小的缓冲区(系统可以支持的)一个列表。系统的空闲的块缓冲区当第一次创建或者被废弃的时候就在这些列表中排队。当前支持的缓冲区大小是 512 、 1024 、 2048 、 4096 和 8192 字节。第二个功能部分是缓存区( cache )本身。这是一个 hash table ,是一个指针的向量表,用于链接具有相同 hashindex 的 buffer 。 Hash index 从数据块所属的设备标识符和块编号产生出来。图 9.7 显示了这个 hash table 和一些条目。块缓冲区要么在空闲列表之一,要么在 buffer cache 中。当它们在 buffer cache 的时候,它们也在 LRU 列表中排队。每一个缓冲区类型一个 LRU 列表,系统使用这些类型在一种类型的缓冲区上执行操作。例如,把有新数据的缓冲区写到磁盘上。缓冲区的类型反映了它的状态, Linux 当前支持以下类型:

clean 未使用,新的缓冲区( buffer )

locked 锁定的缓冲区,等待被写入

dirty 脏的缓冲区。包含新的有效的数据,将被写到磁盘,但是直到现在还没有调度到写

shared 共享的缓冲区

unshared 曾经共享的缓冲区,但是现在没有共享

    不论何时文件系统需要从它的底层的物理设备读取一个缓冲区的时候,它都试图从 buffer cache 中得到一个块。如果它不能从 buffer cache 中得到一个缓冲区,它就从适当大小的空闲列表中取出一个干净的缓冲区,这个新的缓冲区会进入到 buffer cache 中。如果它需要的缓冲区已经在 buffer cache 中,那么它可能是也可能不是最新。如果它不是最新,或者它是一个新的块缓冲区,文件系统必须请求设备驱动程序从磁盘上读取适当的数据块。

    象所有的高速缓存一样, buffer cache 必须被维护,这样它才能有效地运行,并在使用 buffer cache 的块设备之间公平地分配缓存条目。 Linux 使用核心守护进程 bdflush 在这个缓存区上执行大量的整理工作,不过另一些是在使用缓存区的过程中自动进行的。

9.3.1 The bdflush Kernel Daemon (核心守护进程 bdflsuh )

    核心守护进程 bdflush 是一个简单的核心守护进程,对于有许多脏的缓冲区(包含必须同时写到磁盘的数据的缓冲区)的系统提供了动态的响应。它在系统启动的时候作为一个核心线程启动,相当容易混淆,它叫自己位“ kflushd ”,而这是你用 ps 显示系统中的进程的时候你会看得的名字。这个进程大多数时间在睡眠,等待系统中脏的缓冲区的数目增加直到太巨大。当缓冲区分配和释放的时候,就检查系统中脏的缓冲区的数目,然后唤醒 bdflush 。缺省的阈值是 60% ,但是如果系统非常需要缓冲区, dflush 也会被唤醒。这个值可以用 updage 命令检查和设置:

#update –d

bdflush version 1.4

0: 60 Max fraction of LRU list to examine for dirty blocks

1: 500 Max number of dirty blocks to write each time bdflush activated

2: 64 Num of clean buffers to be loaded onto free list by
refill_freelist

3: 256 Dirty block threshold for activating bdflush in refill_freelist

4: 15 Percentage of cache to scan for free clusters

5: 3000 Time for data buffers to age before flushing

6: 500 Time for non-data (dir, bitmap, etc) buffers to age before
flushing

7: 1884 Time buffer cache load average constant

8: 2 LAV ratio (used to determine threshold for buffer fratricide).

    不管什么时候写入了数据,成为了脏的缓冲区,所有的脏的缓冲区都链接在BUF_DIRTY LRU 列表中, bdflush 会尝试把合理数目的缓冲区写到它们的磁盘中。这个数目也可以用 update 命令检查和设置,缺省是 500 (见上例)。

The update Process ( update 进程)

update 命令不仅仅是一个命令,它也是一个守护进程。当以超级用户身份(系统初始化)运行的时候,它会定期把所有旧的脏缓冲区写到磁盘上。它通过调用系统服务例程执行这些任务,或多或少和 bdflush 的任务相同。当生成了一个脏缓冲区的时候,都标记上它应该被写到它自己的磁盘上的系统时间。每一次 update 运行的时候,它都查看系统中所有的脏的缓冲区,查找具有过期的写时间的缓冲区。每一个过期的缓冲区都被写到磁盘上。

参见 fs/buffer.c sys_bdflush()

The /proc File System

    /proc 文件系统真实地体现了 Linux 虚拟文件系统的能力。它实际上并不存在(这也是 Linux 另外一个技巧), /proc 和它的子目录以及其中的文件都不存在。但是为什么你可以 cat /proc/devices ? /proc 文件系统,象一个真正的文件系统一样,也向虚拟文件系统登记自己,但是当它的文件和目录被打开, VFS执行调用请求它的 inode 的时候, /proc 文件系统才利用核心中的信息创建这些文件和目录。例如,核心的 /proc/devices 文件是从核心描述它的设备的数据结构中产生出来的。

/proc 文件系统代表了一个用户可读的窗口,进入核心的内部工作空间。一些Linux 子系统,例如第 12 章描述的 Linux 核心模块,都在 /proc 文件系统中创建条目。

Device Special Files

    Linux ,象所有版本的 Unix 一样,把它的硬件设备表示成为特殊文件。例如, /dev/null 是空设备。设备文件不在文件系统中占用任何数据空间,它只是设备驱动程序的一个访问点。 EXT2 文件系统和 Linux 的 VFS 都把设备文件作为特殊类型的 inode 。有两种类型的设备文件:字符和块特殊文件。在核心内部本身,设备驱动程序都实现文件的基本操作:你可以打开、关闭等等。字符设备允许字符模式的 I/O 操作,而块设备要求所有的 I/O 通过 buffer cache 。当对于一个设备文件执行一个 I/O 请求的时候,它被转到系统中适当的设备驱动程序。通常这不是一个真正的设备驱动程序,而是一个用于子系统的伪设备驱动程序(pseudo-device driver )例如 SCSI 设备驱动程序层。设备文件用主设备编号(
标识设备类型)和次类型来引用(用于标识单元,或者主类型的实例)。例如,对于系统中的第一个 IDE 控制器上的 IDE 磁盘,主设备编号是 3 , IDE 磁盘的第一个分区的次设备编号应该是 1 ,所以, ls –l /dev/hda1 输出
$ brw-rw---- 1 root disk 3, 1 Nov 24 15:09 /dev/hda1

参见 /include/linux/major.h 中所有 Linux 的主设备编号

    在核心中,每一个设备用一个 kdev_t 数据类型唯一描述。这个类型有两个字节长,第一个包括设备的次设备编号,第二个包括主设备编号。上面的 IDE 设备在核心中保存为 0x0301 。代表一个块或者字符设备的 EXT2 inode 把设备的主和次设备号放在它的第一个直接块指针那里。当它被 VFS 读取的时候,代表它的VFS inode 数据结构的 I_rdev 域被设成正确的设备标识符。

参见 include/linux/kdev_t.h


[目录]


CD_ROM

对CD_ROM的访问操作

    分析ide_cd.c下的各个函数的功能,以及和其他函数之间的调用关系。首先是对CD_ROM的读写数据。函数cdrom_in_bytes()是读数据,函数cdrom_out_bytes()是写数据。实现缓冲区与CD_ROM之间的数据交换。这两个函数分别调用了input_ide_data()和output_ide_data()函数。input_ide_data()是通用的从IDE设备读数据的函数,output_ide_data()是通用的往IDE设备写数据的函数。即不仅是访问IDE CD_ROM用到这两个函数,而且访问其他IDE设备如软驱,也用到这两个函数。这两个函数提供了底层的访问IDE设备的功能。

    我们知道了用于缓冲区与CD_ROM之间直接进行数据交换的函数,同时在请求管理部分中讲到,文件系统对块设备的访问并非是直接对设备进行的,而是通过缓冲区实现的。于是下面讨论文件系统访问CD_ROM上的文件时,各个层次间函数的调用关系。

    首先在前面提到的系统核心初始化过程中,函数hwif_init()为CD_ROM分配了中断号,同时也指定了请求响应的列程。hwif_init()中通过以下语句实现。

//为CD_ROM分配了中断号
                if (!(hwif->irq = default_irqs[h])) {
                        printk("%s: DISABLED, NO IRQ\n", hwif->name);
                        return (hwif->present = 0);
//指定了请求响应的列程
        switch (hwif->major) {
        case IDE0_MAJOR: rfn = &do_ide0_request; break;
#if MAX_HWIFS > 1
        case IDE1_MAJOR: rfn = &do_ide1_request; break;
#endif
#if MAX_HWIFS > 2
        case IDE2_MAJOR: rfn = &do_ide2_request; break;
#endif
#if MAX_HWIFS > 3
        case IDE3_MAJOR: rfn = &do_ide3_request; break;
#endif
        default:
                printk("%s: request_fn NOT DEFINED\n", hwif->name);
                return (hwif->present = 0);
        }                blk_dev[hwif->major].request_fn = rfn;
…….

跟踪函数do_ide*_request(),不难发现对CD_ROM请求的具体实现。各函数之间的关系如图4。下面介绍主要函数的意义:
do_hwgroup_request()//contained in drivers\block\ide.c
do_hwgroup_request()首先屏蔽该设备所有可能发生的中断,避免竞争,然后调用ide_do_request() 。
ide_do_request() //contained in drivers\block\ide.c
函数首先用cli()屏蔽中断标志位。然后调用do_request()。

do_request() //contained in drivers\block\ide.c
do_request()对新的I/O请求做初始化。Do_request()带参数ide_hwif_t *hwif,根据&hwif->drives[unit]->media的不同,进入特定的请求处理例程,当&hwif->drives[unit]->media为ide_cdrom时,进入CD_ROM请求例程ide_do_rw_cdrom。
ide_do_rw_cdrom() //contained in drivers\block\ide_cd.c
本函数定义如下:
void ide_do_rw_cdrom (ide_drive_t *drive, unsigned long block)
{
        struct request *rq = HWGROUP(drive)->rq;

        if (rq -> cmd == PACKET_COMMAND || rq -> cmd == REQUEST_SENSE_COMMAND)
                cdrom_do_packet_command (drive);
        else if (rq -> cmd == RESET_DRIVE_COMMAND) {
                cdrom_end_request (1, drive);
                ide_do_reset (drive);
                return;
        } else if (rq -> cmd != READ) {
                printk ("ide-cd: bad cmd %d\n", rq -> cmd);
                cdrom_end_request (0, drive);
        } else
                cdrom_start_read (drive, block);
}
函数根据请求内容的不同,即rq->cmd的不同,执行相应的驱动函数。当rq->cmd为PACKET_COMMAND或REQUEST_SENSE_COMMAND时,执行cdrom_do_packet_command();当rq->cmd为READ时,执行cdrom_start_read()。在cdrom_do_packet_command()和cdrom_start_read ()中都激发了一个重要的函数:cdrom_transfer_packet_command(),该函数的参数定义如下:
        static int cdrom_transfer_packet_command (ide_drive_t *drive,
                                         char *cmd_buf, int cmd_len,
                                          ide_handler_t *handler)

该函数发一个包命令(packet command)给设备,设备在参数drive中注明,包命令用参数CMD_BUF和CMD_LEN表示。参数HANDLER是中断句柄,当包命令完成时,HANDLER将被调用。在函数cdrom_start_read()中cdrom_transfer_packet_command()被调用的形式如下:
                (void) cdrom_transfer_packet_command (drive, pc.c, sizeof (pc.c),
                                              &cdrom_read_intr);
在函数cdrom_do_packet_command()中cdrom_transfer_packet_command()被调用的形式如下:
        cdrom_transfer_packet_command (drive, pc->c,
                                       sizeof (pc->c), &cdrom_pc_intr);
以cdrom_start_read()为例,探讨系统访问块设备所采取的策略。以下为cdrom_start_read()的伪代码:
/*contained in drivers\block\ide_cd.c
* Start a read request from the CD-ROM.
*/
static void cdrom_start_read (ide_drive_t *drive, unsigned int block)
{
//获取drive的请求数据
        struct request *rq = HWGROUP(drive)->rq;
        int minor = MINOR (rq->rq_dev);
        如果请求是针对一个分区的,则使请求指向分区的绝对地址。
        /* We may be retrying this request after an error.  Fix up
           any weirdness which might be present in the request packet. */
        恢复可能被部分改变的请求结构rq,restore_request (rq);

        根据请求,在缓冲区内确认是否能满足,即先在缓冲区内查找请求所需的数据。如果请求被满足,则结束该请求cdrom_end_request (1, drive),返回;如果没有被满足,则继续。
        if (cdrom_read_from_buffer (drive))
                return;
        如果缓冲区无法满足请求,则将读请求送到设备,当请求完成后,执行相应的中断例程
/* Start sending the read request to the drive. */
        cdrom_start_packet_command (drive, 32768,
                                    cdrom_start_read_continuation);
}


IDE_CD的打开和关闭

打开设备的操作是ide_cdrom_open(),函数的内容很简单,如果是第一次打开,则检测CD_ROM的状态,否则不做任何事情。关闭设备的操作是ide_cdrom_release(),主要的操作是释放与该设备有关的内存空间。
IDE_CD的ioctl操作
IDE CD_ROM的ioctl操作内容较多。通过函数ide_cdrom_ioctl()实现,主要有以下操作:
                                        CDROMEJECT                //弹出
                                        CDROMCLOSETRAY        //关闭托盘
                                        CDROMEJECT_SW
                                        CDROMPAUSE                //暂停
                                        CDROMRESUME
                                        CDROMSTART
                                        CDROMSTOP
                                        CDROMREADTOCHDR
                                        CDROMREADTOCENTRY
                                        CDROMVOLCTRL
                                        CDROMVOLREAD
                                        CDROMMULTISESSION
                                        CDROMREADRAW

[目录]


文件页缓冲结构

    inode结构定义了操作数据文件的函数表i_fop,它是文件系统提供的面向用户的高级文件IO接口.inode结构还定义了i_mapping指针,用它来描述对文件的IO缓冲.i_mapping->a_ops是文件系统提供的一组低级文件IO函数表,a_ops与块设备接口.在通常情况下,i_fop并不直接与块设备接口,而是间接通过a_ops读写文件.文件的页缓冲(page cache)就是i_fop与a_ops之间的缓冲,它是块设备缓冲之上的更高一级缓冲,直接用于具体文件的读写.

    页缓冲用page_hash_table来索引不同文件不同位置上的页块.page_hash_table是页面结构指针(struct page *)组成的数组,它的键由i_mapping地址和文件的页块号组成,i_mapping指向inode结构中的i_data结构,它的值对不同的inode是不同的,它的尺寸分配和dentry_hashtable的分配类似,每4M内存分配1个4K页面,每个页面则1024项.

page_hash(mapping,index)
    以mapping:index为键返回page_hash_table中相应槽位的地址;

add_to_page_cache(page,mapping,index)
    将page链入以mapping:index为键值的page_hash_table中;

add_to_page_cache_unique(page,mapping,index,hash)
    在槽位hash上唯一地添加以mapping:index表示的页page;

__find_page_nolock(mapping,index,page)
    在page_hash_table中查询以mapping:index为键的page;

remove_page_from_hash_queue(page)
    将page从page_hash_table中删除;


typedef struct page {
        struct list_head list;
        struct address_space *mapping; 为inode->i_mapping的值
        unsigned long index; 文件的页块号
        struct page *next_hash; 用以形成hash链
        atomic_t count; 引用计数
        unsigned long flags;
        struct list_head lru;
        unsigned long age;
        wait_queue_head_t wait;
        struct page **pprev_hash; 指向hash链中前一页next_hash的地址
        struct buffer_head * buffers;
        void *virtual;
        struct zone_struct *zone;
} mem_map_t;
struct address_space {
        struct list_head        clean_pages; 加入page_hash_table后,与page->list相环接
        struct list_head        dirty_pages;
        struct list_head        locked_pages;
        unsigned long                nrpages; 表示该inode具有的文件缓冲页数量
        struct address_space_operations *a_ops;
        struct inode                *host;
        struct vm_area_struct        *i_mmap;
        struct vm_area_struct        *i_mmap_shared;
        spinlock_t                i_shared_lock;
};
struct address_space_operations {
        int (*writepage)(struct page *);
        int (*readpage)(struct file *, struct page *);
        int (*sync_page)(struct page *);
        int (*prepare_write)(struct file *, struct page *, unsigned, unsigned);
        int (*commit_write)(struct file *, struct page *, unsigned, unsigned);
        int (*bmap)(struct address_space *, long);
};

#define page_hash(mapping,index) (page_hash_table+_page_hashfn(mapping,index))

atomic_t page_cache_size = ATOMIC_INIT(0);
unsigned int page_hash_bits;
struct page **page_hash_table;
extern inline unsigned long _page_hashfn(struct address_space * mapping, unsigned long index)
{
#define i (((unsigned long) mapping)/(sizeof(struct inode)  ~ (sizeof(struct inode) - 1)))
#define s(x) ((x)+((x)>>PAGE_HASH_BITS))
        return s(i+index)  (PAGE_HASH_SIZE-1);
#undef i
#undef s
}

void add_to_page_cache(struct page * page, struct address_space * mapping, unsigned long offset)
{
        spin_lock(
        __add_to_page_cache(page, mapping, offset, page_hash(mapping, offset));
        spin_unlock(
}
static inline void __add_to_page_cache(struct page * page,
        struct address_space *mapping, unsigned long offset,
        struct page **hash)
{
        unsigned long flags;

        if (PageLocked(page))
                BUG();

        flags = page->flags  ~((1         page->flags = flags | (1         page_cache_get(page); 增加引用计数
        page->index = offset;
        add_page_to_inode_queue(mapping, page); 将page->list与inode->i_mapping->clean_pages相环接
        add_page_to_hash_queue(page, hash); 将page加入hash槽位
        lru_cache_add(page); 与active_list相环接
}
static inline void add_page_to_inode_queue(struct address_space *mapping, struct page * page)
{
        struct list_head *head =

        mapping->nrpages++;
        list_add( head);
        page->mapping = mapping; 让page->mapping指向inode->i_mapping
}
static void add_page_to_hash_queue(struct page * page, struct page **p)
{
        struct page *next = *p;

        *p = page;
        page->next_hash = next;
        page->pprev_hash = p;
        if (next)
                next->pprev_hash =
        if (page->buffers)
                PAGE_BUG(page);
        atomic_inc(
}
static inline struct page * __find_page_nolock(struct address_space *mapping, unsigned long offset, struct page *page)
{
        goto inside;

        for (;;) {
                page = page->next_hash;
inside:
                if (!page)
                        goto not_found;
                if (page->mapping != mapping)
                        continue;
                if (page->index == offset)
                        break;
        }
        /*
        * Touching the page may move it to the active list.
        * If we end up with too few inactive pages, we wake
        * up kswapd.
        */
        age_page_up(page);
        if (inactive_shortage() > inactive_target / 2  free_shortage())
                        wakeup_kswapd();
not_found:
        return page;
}
void add_to_page_cache_locked(struct page * page, struct address_space *mapping, unsigned long index)
{
        if (!PageLocked(page))
                BUG();

        page_cache_get(page);
        spin_lock(
        page->index = index;
        add_page_to_inode_queue(mapping, page);
        add_page_to_hash_queue(page, page_hash(mapping, index));
        lru_cache_add(page);
        spin_unlock(
}
void add_to_page_cache(struct page * page, struct address_space * mapping, unsigned long offset)
{
        spin_lock(
        __add_to_page_cache(page, mapping, offset, page_hash(mapping, offset));
        spin_unlock(
}


static int add_to_page_cache_unique(struct page * page,
        struct address_space *mapping, unsigned long offset,
        struct page **hash)
{
        int err;
        struct page *alias;

        spin_lock(
        alias = __find_page_nolock(mapping, offset, *hash);

        err = 1;
        if (!alias) {
                __add_to_page_cache(page,mapping,offset,hash);
                err = 0;
        }

        spin_unlock(
        return err;
}

static inline void remove_page_from_hash_queue(struct page * page)
{
        struct page *next = page->next_hash;
        struct page **pprev = page->pprev_hash;

        if (next)
                next->pprev_hash = pprev;
        *pprev = next;
        page->pprev_hash = NULL;
        atomic_dec(
}

static inline void remove_page_from_inode_queue(struct page * page)
{
        struct address_space * mapping = page->mapping;

        mapping->nrpages--;
        list_del(
        page->mapping = NULL;
}

void remove_inode_page(struct page *page)
{
        if (!PageLocked(page))
                PAGE_BUG(page);

        spin_lock(
        __remove_inode_page(page);
        spin_unlock(
}
/*
* Remove a page from the page cache and free it. Caller has to make
* sure the page is locked and that nobody else uses it - or that usage
* is safe.
*/
void __remove_inode_page(struct page *page)
{
        if (PageDirty(page)) BUG();
        remove_page_from_inode_queue(page);
        remove_page_from_hash_queue(page);
        page->mapping = NULL;
}


void __init page_cache_init(unsigned long mempages)
{
        unsigned long htable_size, order;

        htable_size = mempages;
        htable_size *= sizeof(struct page *);
        for(order = 0; (PAGE_SIZE                 ;

        do {
                unsigned long tmp = (PAGE_SIZE
                page_hash_bits = 0;
                while((tmp >>= 1UL) != 0UL)
                        page_hash_bits++;

                page_hash_table = (struct page **)
                        __get_free_pages(GFP_ATOMIC, order);
        } while(page_hash_table == NULL  --order > 0);

        printk("Page-cache hash table entries: %d (order: %ld, %ld bytes)\n",
               (1         if (!page_hash_table)
                panic("Failed to allocate page hash table\n");
        memset((void *)page_hash_table, 0, PAGE_HASH_SIZE * sizeof(struct page *));
}

[目录]


块设备缓冲区结构

    块设备缓冲区用buffer_head结构描述,系统中有NR_SIZES种不同尺寸的缓冲区,每种缓冲区的尺寸为512

grow_buffers(blocksize);
    在free_list[]上扩建一页块长为blocksize的备用缓冲区;

bh = create_buffers(page,blocksize,async);
    创建块长为blocksize的buffer_head结构来描述页面page.

create_empty_buffers(page,dev,blocksize);
    创建块长为blocksize,块设备为dev的buffer_head结构来描述页面page

bh = get_unused_buffer_head(async);
    取备用的buffer_head结构,async=1允许进程暂时睡眠.


struct buffer_head {
        struct buffer_head *b_next; 用于缓冲块索引的散列链
        unsigned long b_blocknr; 该缓冲区在块设备上的块号
        unsigned short b_size; 该缓冲区数据块尺寸
        unsigned short b_list; 在lru_list[]中的序号,表示该缓冲区的使用状态.
        kdev_t b_dev; 缓冲区所属的逻辑块设备

        atomic_t b_count;        引用计数
        kdev_t b_rdev; 所属的物理块设备
        unsigned long b_state;
        unsigned long b_flushtime;

        struct buffer_head *b_next_free; 指向下一备用缓冲块
        struct buffer_head *b_prev_free; 指向前一备用缓冲块
        struct buffer_head *b_this_page; 指向同一页面的缓冲块,形成环形链表
        struct buffer_head *b_reqnext; 用于块设备驱动程序

        struct buffer_head **b_pprev;        用于缓冲块散列链
        char * b_data;        指向缓冲块的数据区
        struct page *b_page; 缓冲块的数据区所在的页面
        void (*b_end_io)(struct buffer_head *bh, int uptodate);
        void *b_private;

        unsigned long b_rsector;
        wait_queue_head_t b_wait;

        struct inode *             b_inode;
        struct list_head     b_inode_buffers;
};

#define NR_SIZES 7

; 这是一张以2为底的简易对数表,用于块长度到free_list[]索引的转换
static char buffersize_index[65] =
{-1,  0,  1, -1,  2, -1, -1, -1, 3, -1, -1, -1, -1, -1, -1, -1,
  4, -1, -1, -1, -1, -1, -1, -1, -1,-1, -1, -1, -1, -1, -1, -1,
  5, -1, -1, -1, -1, -1, -1, -1, -1,-1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1,-1, -1, -1, -1, -1, -1, -1,
  6};

#define BUFSIZE_INDEX(X) ((int) buffersize_index[(X)>>9])
#define MAX_BUF_PER_PAGE (PAGE_CACHE_SIZE / 512) 每页最多的缓冲块数(8)
#define NR_RESERVED (2*MAX_BUF_PER_PAGE)
#define MAX_UNUSED_BUFFERS NR_RESERVED+20 /* don't ever have more than this
                                             number of unused buffer heads */

static struct buffer_head *lru_list[NR_LIST];
static int nr_buffers_type[NR_LIST]; 每种尺寸缓冲块的数量
static unsigned long size_buffers_type[NR_LIST]; 每种尺寸缓冲区的字节总数

static struct buffer_head * unused_list;
static int nr_unused_buffer_heads;

struct bh_free_head {
        struct buffer_head *list;
        spinlock_t lock;
};
static struct bh_free_head free_list[NR_SIZES];

static int grow_buffers(int size)
{
        struct page * page;
        struct buffer_head *bh, *tmp;
        struct buffer_head * insert_point;
        int isize;

        if ((size  511) || (size > PAGE_SIZE)) {
                printk("VFS: grow_buffers: size = %d\n",size);
                return 0;
        }

        page = alloc_page(GFP_BUFFER);
        if (!page)
                goto out;
        LockPage(page);
        bh = create_buffers(page, size, 0);
        if (!bh)
                goto no_buffer_head;

        isize = BUFSIZE_INDEX(size);

        spin_lock(
        insert_point = free_list[isize].list;
        tmp = bh;
        while (1) { 将页中的每一块缓冲区插入free_list[isize].list
                if (insert_point) {
                        tmp->b_next_free = insert_point->b_next_free;
                        tmp->b_prev_free = insert_point;
                        insert_point->b_next_free->b_prev_free = tmp;
                        insert_point->b_next_free = tmp;
                } else {
                        tmp->b_prev_free = tmp;
                        tmp->b_next_free = tmp;
                }
                insert_point = tmp;
                if (tmp->b_this_page)
                        tmp = tmp->b_this_page;
                else
                        break;
        }
        tmp->b_this_page = bh; 形成单向环形链表
        free_list[isize].list = bh;
        spin_unlock(

        page->buffers = bh; 表示该页与块设备缓冲区相关联
        page->flags  ~(1         lru_cache_add(page); 将该页加入页面LRU链表
        UnlockPage(page);
        atomic_inc(
        return 1;

no_buffer_head:
        UnlockPage(page);
        page_cache_release(page); 释放alloc_page()分配的页面
out:
        return 0;
}
static struct buffer_head * create_buffers(struct page * page, unsigned long size, int async)
{
        struct buffer_head *bh, *head;
        long offset;

try_again:
        head = NULL;
        offset = PAGE_SIZE;
        while ((offset -= size) >= 0) { 从页面的高端向低端分配地址
                bh = get_unused_buffer_head(async);
                if (!bh)
                        goto no_grow;

                bh->b_dev = B_FREE;  /* Flag as unused */
                bh->b_this_page = head;
                head = bh;

                bh->b_state = 0;
                bh->b_next_free = NULL;
                bh->b_pprev = NULL;
                atomic_set( 0);
                bh->b_size = size;

                set_bh_page(bh, page, offset);

                bh->b_list = BUF_CLEAN;
                bh->b_end_io = NULL;
        }
        return head;
/*
* In case anything failed, we just free everything we got.
*/
no_grow:
        if (head) { 如果在分配中途失败,则撤消已有分配
                spin_lock(
                do {
                        bh = head;
                        head = head->b_this_page;
                        __put_unused_buffer_head(bh);
                } while (head);
                spin_unlock(

                /* Wake up any waiters ... */
                wake_up( 唤醒下文因wait_event()而睡眠的那些进程
        }

        /*
        * Return failure for non-async IO requests.  Async IO requests
        * are not allowed to fail, so we have to wait until buffer heads
        * become available.  But we don't want tasks sleeping with
        * partially complete buffers, so all were released above.
        */
        if (!async)
                return NULL;

        /* We're _really_ low on memory. Now we just
        * wait for old buffer heads to become free due to
        * finishing IO.  Since this is an async request and
        * the reserve list is empty, we're sure there are
        * async buffer heads in use.
        */
        run_task_queue(

        /*
        * Set our state for sleeping, then check again for buffer heads.
        * This ensures we won't miss a wake_up from an interrupt.
        */
        wait_event(buffer_wait, nr_unused_buffer_heads >= MAX_BUF_PER_PAGE);
        ; 如果nr_unused_buffer_heads >= MAX_BUF_PER_PAGE 则wait_event返回,否则睡眠
        goto try_again;
}
static struct buffer_head * get_unused_buffer_head(int async)
{
        struct buffer_head * bh;

        spin_lock(
        if (nr_unused_buffer_heads > NR_RESERVED) {
                bh = unused_list;
                unused_list = bh->b_next_free;
                nr_unused_buffer_heads--;
                spin_unlock(
                return bh;
        }
        spin_unlock(

        /* This is critical.  We can't swap out pages to get
        * more buffer heads, because the swap-out may need
        * more buffer-heads itself.  Thus SLAB_BUFFER.
        */
        if((bh = kmem_cache_alloc(bh_cachep, SLAB_BUFFER)) != NULL) {
                memset(bh, 0, sizeof(*bh));
                init_waitqueue_head(
                return bh;
        }

        /*
        * If we need an async buffer, use the reserved buffer heads.
        */
        if (async) {
                spin_lock(
                if (unused_list) {
                        bh = unused_list;
                        unused_list = bh->b_next_free;
                        nr_unused_buffer_heads--;
                        spin_unlock(
                        return bh;
                }
                spin_unlock(
        }
#if 0
        /*
        * (Pending further analysis ...)
        * Ordinary (non-async) requests can use a different memory priority
        * to free up pages. Any swapping thus generated will use async
        * buffer heads.
        */
        if(!async
           (bh = kmem_cache_alloc(bh_cachep, SLAB_KERNEL)) != NULL) {
                memset(bh, 0, sizeof(*bh));
                init_waitqueue_head(
                return bh;
        }
#endif

        return NULL;
}
static void create_empty_buffers(struct page *page, kdev_t dev, unsigned long blocksize)
{
        struct buffer_head *bh, *head, *tail;

        head = create_buffers(page, blocksize, 1);
        if (page->buffers)
                BUG();

        bh = head;
        do {
                bh->b_dev = dev;
                bh->b_blocknr = 0;
                bh->b_end_io = NULL;
                tail = bh;
                bh = bh->b_this_page;
        } while (bh);
        tail->b_this_page = head;
        page->buffers = head;
        page_cache_get(page);
}
void set_bh_page (struct buffer_head *bh, struct page *page, unsigned long offset)
{
        bh->b_page = page;
        if (offset >= PAGE_SIZE)
                BUG();
        if (PageHighMem(page))
                /*
                * This catches illegal uses and preserves the offset:
                */
                bh->b_data = (char *)(0 + offset);
        else
                bh->b_data = page_address(page) + offset;
}
static __inline__ void __put_unused_buffer_head(struct buffer_head * bh)
{
        if (bh->b_inode)
                BUG();
        if (nr_unused_buffer_heads >= MAX_UNUSED_BUFFERS) {
                kmem_cache_free(bh_cachep, bh);
        } else {
                bh->b_blocknr = -1;
                init_waitqueue_head(
                nr_unused_buffer_heads++;
                bh->b_next_free = unused_list;
                bh->b_this_page = NULL;
                unused_list = bh;
        }
}

[目录]


散列算法

    系统在解析路径时,使用dentry_hashtable来缓冲每一节路径名对应的dentry目录项;
    dentry_hashtable是由list_head组成的数组,它们与dentry->d_hash相环接,形成短链,散列表中的dentry将均分布于这些短链上;
散列表的索引确定于父目录项地址和目录名的hash值;

dentry_hashtable的尺寸由系统内存大小分配,每4M内存分配一个页面,每个页面具有512项索引;
d_hash(dentry,hash)为散列函数,它将dentry地址和hash值相组合,映射到dentry_hashtable表中,返回相应的散列链;
d_rehash(dentry)将dentry加入散列表;
d_drop(dentry)将dentry从散列表中删除;
d_lookup(dentry,qstr)在散列中找出以dentry作为父目录项,名称为qstr的目录项.

    系统用下面的方法计算某节路径名的hash值:

        unsigned long hash;
        struct qstr this;
        unsigned int c;

        hash = init_name_hash();
        do {
                name++;
                hash = partial_name_hash(c, hash);
                c = *(const unsigned char *)name;
        } while (c  (c != '/'));

        this.len = name - (const char *) this.name;
        this.hash = end_name_hash(hash);
        ...

有关的代码如下:

static unsigned int d_hash_mask;
static unsigned int d_hash_shift;
static struct list_head *dentry_hashtable;

#define init_name_hash() 0

static __inline__ unsigned long partial_name_hash(unsigned long c, unsigned long prevhash)
{
        prevhash = (prevhash > (8*sizeof(unsigned long)-4));
        return prevhash ^ c;
}

/* Finally: cut down the number of bits to a int value (and try to avoid losing bits) */
static __inline__ unsigned long end_name_hash(unsigned long hash)
{
        if (sizeof(hash) > sizeof(unsigned int))
                hash += hash >> 4*sizeof(hash);
        return (unsigned int) hash;
}
#define D_HASHBITS     d_hash_shift
#define D_HASHMASK     d_hash_mask
static inline struct list_head * d_hash(struct dentry * parent, unsigned long hash)
{
        hash += (unsigned long) parent / L1_CACHE_BYTES;
        hash = hash ^ (hash >> D_HASHBITS) ^ (hash >> D_HASHBITS*2);
        return dentry_hashtable + (hash  D_HASHMASK);
}
void d_rehash(struct dentry * entry)
{
        struct list_head *list = d_hash(entry->d_parent, entry->d_name.hash);
        spin_lock(
        list_add( list);
        spin_unlock(
}
static __inline__ void d_drop(struct dentry * dentry)
{
        spin_lock(
        list_del(
        INIT_LIST_HEAD(
        spin_unlock(
}
struct dentry * d_lookup(struct dentry * parent, struct qstr * name)
{
        unsigned int len = name->len;
        unsigned int hash = name->hash;
        const unsigned char *str = name->name;
        struct list_head *head = d_hash(parent,hash);
        struct list_head *tmp;

        spin_lock(
        tmp = head->next;
        for (;;) {
                struct dentry * dentry = list_entry(tmp, struct dentry, d_hash);
                if (tmp == head)
                        break;
                tmp = tmp->next;
                if (dentry->d_name.hash != hash)
                        continue;
                if (dentry->d_parent != parent)
                        continue;
                if (parent->d_op  parent->d_op->d_compare) {
                        ; 如果文件系统提供了目录名比较的方法
                        if (parent->d_op->d_compare(parent,  name))
                                continue;
                } else {
                        if (dentry->d_name.len != len)
                                continue;
                        if (memcmp(dentry->d_name.name, str, len))
                                continue;
                }
                __dget_locked(dentry); 增加dentry的引用计数
                dentry->d_flags |= DCACHE_REFERENCED;
                spin_unlock(
                return dentry;
        }
        spin_unlock(
        return NULL;
}
static void __init dcache_init(unsigned long mempages)
{
        struct list_head *d;
        unsigned long order;
        unsigned int nr_hash;
        int i;

        /*
        * A constructor could be added for stable state like the lists,
        * but it is probably not worth it because of the cache nature
        * of the dcache.
        * If fragmentation is too bad then the SLAB_HWCACHE_ALIGN
        * flag could be removed here, to hint to the allocator that
        * it should not try to get multiple page regions.
        */
        dentry_cache = kmem_cache_create("dentry_cache",
                                        sizeof(struct dentry),
                                        0,
                                        SLAB_HWCACHE_ALIGN,
                                        NULL, NULL);
        if (!dentry_cache)
                panic("Cannot create dentry cache");

#if PAGE_SHIFT         mempages >>= (13 - PAGE_SHIFT);
#endif
        mempages *= sizeof(struct list_head);
        for (order = 0; ((1UL
        do {
                unsigned long tmp;

                nr_hash = (1UL                         sizeof(struct list_head);
                d_hash_mask = (nr_hash - 1);

                tmp = nr_hash;
                d_hash_shift = 0;
                while ((tmp >>= 1UL) != 0UL)
                        d_hash_shift++;

                dentry_hashtable = (struct list_head *)
                        __get_free_pages(GFP_ATOMIC, order);
        } while (dentry_hashtable == NULL  --order >= 0);
        ; 如果order太大,超过了__get_free_pages最大可分配尺寸,则减小order的值重试.

        printk("Dentry-cache hash table entries: %d (order: %ld, %ld bytes)\n",
                        nr_hash, order, (PAGE_SIZE
        if (!dentry_hashtable)
                panic("Failed to allocate dcache hash table\n");

        d = dentry_hashtable;
        i = nr_hash;
        do {
                INIT_LIST_HEAD(d);
                d++;
                i--;
        } while (i);
}
对opera的注释加点解释,读起来可能会更省力些。

1.为什么要用这个算法

    例如要构造一个文件 /usr/local/cross/my_comp
这时要沿着上面这个文件名开始依次找直到cross的数据结构,也就是要找到
  /usr/
/usr/local/
/usr/local/cross/
对应的数据结构dentry
    假定我们已经找到/usr/对应的dentry, 现在必须能够从local找到它对应的dentry,这时就要从名字---->dentry的快速映射,在Linux中般用哈希映射。

2. 查找方法
  首先,通过d_hash粗分类,找到"local"所在的链表,然后顺序向下一一匹配。

3.一些操作如opera所述

4.初始化

  首先通过__get_free_pages获得一些页面,这些页面构成了所有链表头数组。

[目录]


permission(inode,mask)

permission(inode,mask)用来测试对文件(inode)是否有(mask)访问权.


; fs/namei.c

int permission(struct inode * inode,int mask)
{
        if (inode->i_op  inode->i_op->permission) {
                ; 如果文件系统定义了自已的授权算法
                int retval;
                lock_kernel();
                retval = inode->i_op->permission(inode, mask);
                unlock_kernel();
                return retval;
        }
        return vfs_permission(inode, mask); 缺省的授权算法
}
int vfs_permission(struct inode * inode,int mask)
{
        int mode = inode->i_mode;

        ; 如果对只读文件系统中的普通文件,目录文件,符号链接请求写访问
        if ((mask  S_IWOTH)  IS_RDONLY(inode)
                (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode)))
                return -EROFS; /* Nobody gets write access to a read-only fs */
        ; 如果对免疫文件请求写访问
        if ((mask  S_IWOTH)  IS_IMMUTABLE(inode))
                return -EACCES; /* Nobody gets write access to an immutable file */
        if (current->fsuid == inode->i_uid)
                mode >>= 6; 如果自已是文件的拥有者,取文件对拥有者的访问权
        else if (in_group_p(inode->i_gid))
                mode >>= 3; 如果自已是文件所在组的成员,取文件对组成员的访问权
        ; 如果所请求的权限是实际对文件权限的子集或者被赋予了超越特权,则允许访问
        if (((mode  mask  S_IRWXO) == mask) || capable(CAP_DAC_OVERRIDE))
                return 0;

        ; 虽然自已对文件没有访问权限,但如果自已被赋予了读和检索的特权,
        ; 则允许读或检索目录.
        /* read and search access */
        if ((mask == S_IROTH) ||
            (S_ISDIR(inode->i_mode)   !(mask  ~(S_IROTH | S_IXOTH))))
                if (capable(CAP_DAC_READ_SEARCH))
                        return 0;

        return -EACCES;
}

; kernel/sys.c
/*
* Check whether we're fsgid/egid or in the supplemental group..
*/
int in_group_p(gid_t grp)
{
        int retval = 1;
        if (grp != current->fsgid)
                retval = supplemental_group_member(grp);
        return retval;
}
static int supplemental_group_member(gid_t grp)
{
        int i = current->ngroups;

        if (i) {
                gid_t *groups = current->groups;
                do {
                        if (*groups == grp)
                                return 1;
                        groups++;
                        i--;
                } while (i);
        }
        return 0;
}


[目录]


IDE硬盘驱动器读写

    Linux内核在缺省配置下最多支持10个IDE接口,IDE接口用ide_hwif_t结构来描述,每个IDE接口具有一对主-从驱动器接口,它们用ide_drive_t结构来描述,每个驱动器接口可接不同种类的IDE设备,如IDE硬盘,光驱等,它们用ide_driver_t结构来描述.
    每个驱动器接口包含一个命令请求队列,用request_queue_t结构来描述,具体的请求用request结构来描述.
    多个IDE驱动器可以共享一个中断,共享同一中断的驱动器形成一个组,用ide_hwgroup_t结构来描述.ide_intr()是所有的ide驱动器所共用的硬件中断入口,对之对应的ide_hwgroup_t指针将作为dev_id传递给ide_intr.
    每次在读写某个驱动器之前,需要用ide_set_handler()来设置ide_intr将要调用的中断函数指针.中断产生以后,该函数指针被自动清除.
    do_rw_disk(drive,rq,block) 从逻辑扇区号block开始向IDE硬盘驱动器drive写入rq所描述的内容.
    以下是硬盘PIO传输模式的有关代码.

; drivers/ide/ide-disk.c
static ide_startstop_t do_rw_disk (ide_drive_t *drive, struct request *rq, unsigned long block)
{
        if (IDE_CONTROL_REG)
                OUT_BYTE(drive->ctl,IDE_CONTROL_REG);
        OUT_BYTE(rq->nr_sectors,IDE_NSECTOR_REG);
        if (drive->select.b.lba) { 如果是逻辑块寻址模式
                OUT_BYTE(block,IDE_SECTOR_REG);
                OUT_BYTE(block>>=8,IDE_LCYL_REG);
                OUT_BYTE(block>>=8,IDE_HCYL_REG);
                OUT_BYTE(((block>>8)
        } else {
                unsigned int sect,head,cyl,track;
                track = block / drive->sect;
                sect  = block % drive->sect + 1;
                OUT_BYTE(sect,IDE_SECTOR_REG);
                head  = track % drive->head;
                cyl   = track / drive->head;
                OUT_BYTE(cyl,IDE_LCYL_REG);
                OUT_BYTE(cyl>>8,IDE_HCYL_REG);
                OUT_BYTE(head|drive->select.all,IDE_SELECT_REG);
        }
        if (rq->cmd == READ) {{
                ide_set_handler(drive,  WAIT_CMD, NULL); WAIT_CMD为10秒超时
                OUT_BYTE(drive->mult_count ? WIN_MULTREAD : WIN_READ, IDE_COMMAND_REG);
                return ide_started;
        }
        if (rq->cmd == WRITE) {
                ide_startstop_t startstop;
                OUT_BYTE(drive->mult_count ? WIN_MULTWRITE : WIN_WRITE, IDE_COMMAND_REG);
                if (ide_wait_stat( drive, DATA_READY, drive->bad_wstat, WAIT_DRQ)) {
                        printk(KERN_ERR "%s: no DRQ after issuing %s\n", drive->name,
                                drive->mult_count ? "MULTWRITE" : "WRITE");
                        return startstop;
                }
                if (!drive->unmask)
                        __cli();        /* local CPU only */
                if (drive->mult_count) { 如果允许多扇区传送
                        ide_hwgroup_t *hwgroup = HWGROUP(drive);
                        /*
                        * Ugh.. this part looks ugly because we MUST set up
                        * the interrupt handler before outputting the first block
                        * of data to be written.  If we hit an error (corrupted buffer list)
                        * in ide_multwrite(), then we need to remove the handler/timer
                        * before returning.  Fortunately, this NEVER happens (right?).
                        *
                        * Except when you get an error it seems...
                        */
                        hwgroup->wrq = *rq; /* scratchpad */
                        ide_set_handler (drive,  WAIT_CMD, NULL);
                        if (ide_multwrite(drive, drive->mult_count)) {
                                unsigned long flags;
                                spin_lock_irqsave( flags);
                                hwgroup->handler = NULL;
                                del_timer(
                                spin_unlock_irqrestore( flags);
                                return ide_stopped;
                        }
                } else {
                        ide_set_handler (drive,  WAIT_CMD, NULL);
                        idedisk_output_data(drive, rq->buffer, SECTOR_WORDS); 写入一扇区SECTOR_WORDS=512/4
                }
                return ide_started;
        }
        printk(KERN_ERR "%s: bad command: %d\n", drive->name, rq->cmd);
        ide_end_request(0, HWGROUP(drive));
        return ide_stopped;
}
void ide_set_handler (ide_drive_t *drive, ide_handler_t *handler,
                      unsigned int timeout, ide_expiry_t *expiry)
{
        unsigned long flags;
        ide_hwgroup_t *hwgroup = HWGROUP(drive);

        spin_lock_irqsave( flags);
        if (hwgroup->handler != NULL) {
                printk("%s: ide_set_handler: handler not null; old=%p, new=%p\n",
                        drive->name, hwgroup->handler, handler);
        }
        hwgroup->handler        = handler;
        hwgroup->expiry                = expiry;
        hwgroup->timer.expires        = jiffies + timeout;
        add_timer(
        spin_unlock_irqrestore( flags);
}
static inline void idedisk_output_data (ide_drive_t *drive, void *buffer, unsigned int wcount)
{
        if (drive->bswap) {
                idedisk_bswap_data(buffer, wcount);
                ide_output_data(drive, buffer, wcount);
                idedisk_bswap_data(buffer, wcount);
        } else
                ide_output_data(drive, buffer, wcount);
}
void ide_output_data (ide_drive_t *drive, void *buffer, unsigned int wcount)
{
        byte io_32bit = drive->io_32bit;

        if (io_32bit) {
#if SUPPORT_VLB_SYNC
                if (io_32bit  2) {
                        unsigned long flags;
                        __save_flags(flags);        /* local CPU only */
                        __cli();                /* local CPU only */
                        do_vlb_sync(IDE_NSECTOR_REG);
                        outsl(IDE_DATA_REG, buffer, wcount);
                        __restore_flags(flags);        /* local CPU only */
                } else
#endif /* SUPPORT_VLB_SYNC */
                        outsl(IDE_DATA_REG, buffer, wcount);
        } else {
#if SUPPORT_SLOW_DATA_PORTS
                if (drive->slow) {
                        unsigned short *ptr = (unsigned short *) buffer;
                        while (wcount--) {
                                outw_p(*ptr++, IDE_DATA_REG);
                                outw_p(*ptr++, IDE_DATA_REG);
                        }
                } else
#endif /* SUPPORT_SLOW_DATA_PORTS */
                        outsw(IDE_DATA_REG, buffer, wcount        }
}
int ide_multwrite (ide_drive_t *drive, unsigned int mcount)
{
        ide_hwgroup_t        *hwgroup= HWGROUP(drive);

        /*
        *        This may look a bit odd, but remember wrq is a copy of the
        *        request not the original. The pointers are real however so the
        *        bh's are not copies. Remember that or bad stuff will happen
        *
        *        At the point we are called the drive has asked us for the
        *        data, and its our job to feed it, walking across bh boundaries
        *        if need be.
        */

        struct request        *rq =

          do {
                unsigned long flags;
                  unsigned int nsect = rq->current_nr_sectors;
                if (nsect > mcount)
                        nsect = mcount;
                mcount -= nsect;
                ; 这时mcount为剩余的必需传送的扇区数
                idedisk_output_data(drive, rq->buffer, nsect                spin_lock_irqsave( flags);        /* Is this really necessary? */
#ifdef CONFIG_BLK_DEV_PDC4030
                rq->sector += nsect;
#endif
                if (((long)(rq->nr_sectors -= nsect))                         spin_unlock_irqrestore( flags);
                        break;
                }
                if ((rq->current_nr_sectors -= nsect) == 0) {
                        if ((rq->bh = rq->bh->b_reqnext) != NULL) {{
                                rq->current_nr_sectors = rq->bh->b_size>>9;
                                rq->buffer             = rq->bh->b_data;
                        } else {
                                spin_unlock_irqrestore( flags);
                                printk("%s: buffer list corrupted (%ld, %ld, %d)\n",
                                        drive->name, rq->current_nr_sectors,
                                        rq->nr_sectors, nsect);
                                ide_end_request(0, hwgroup);
                                return 1;
                        }
                } else {
                        /* Fix the pointer.. we ate data */
                        rq->buffer += nsect                 }
                spin_unlock_irqrestore( flags);
        } while (mcount);
        return 0;
}

; IDE接口共用中断入口
void ide_intr (int irq, void *dev_id, struct pt_regs *regs)
{
        unsigned long flags;
        ide_hwgroup_t *hwgroup = (ide_hwgroup_t *)dev_id;
        ide_hwif_t *hwif;
        ide_drive_t *drive;
        ide_handler_t *handler;
        ide_startstop_t startstop;

        spin_lock_irqsave( flags);
        hwif = hwgroup->hwif;

        if (!ide_ack_intr(hwif)) {
                spin_unlock_irqrestore( flags);
                return;
        }

        if ((handler = hwgroup->handler) == NULL || hwgroup->poll_timeout != 0) {
                /*
                * Not expecting an interrupt from this drive.
                * That means this could be:
                *        (1) an interrupt from another PCI device
                *        sharing the same PCI INT# as us.
                * or        (2) a drive just entered sleep or standby mode,
                *        and is interrupting to let us know.
                * or        (3) a spurious interrupt of unknown origin.
                *
                * For PCI, we cannot tell the difference,
                * so in that case we just ignore it and hope it goes away.
                */
#ifdef CONFIG_BLK_DEV_IDEPCI
                if (IDE_PCI_DEVID_EQ(hwif->pci_devid, IDE_PCI_DEVID_NULL))
#endif        /* CONFIG_BLK_DEV_IDEPCI */
                {
                        /*
                        * Probably not a shared PCI interrupt,
                        * so we can safely try to do something about it:
                        */
                        unexpected_intr(irq, hwgroup);
#ifdef CONFIG_BLK_DEV_IDEPCI
                } else {
                        /*
                        * Whack the status register, just in case we have a leftover pending IRQ.
                        */
                        (void) IN_BYTE(hwif->io_ports[IDE_STATUS_OFFSET]);
#endif /* CONFIG_BLK_DEV_IDEPCI */
                }
                spin_unlock_irqrestore( flags);
                return;
        }
        drive = hwgroup->drive;
        if (!drive) {
                /*
                * This should NEVER happen, and there isn't much we could do about it here.
                */
                spin_unlock_irqrestore( flags);
                return;
        }
        if (!drive_is_ready(drive)) {
                /*
                * This happens regularly when we share a PCI IRQ with another device.
                * Unfortunately, it can also happen with some buggy drives that trigger
                * the IRQ before their status register is up to date.  Hopefully we have
                * enough advance overhead that the latter isn't a problem.
                */
                spin_unlock_irqrestore( flags);
                return;
        }
        if (!hwgroup->busy) {
                hwgroup->busy = 1;        /* paranoia */
                printk("%s: ide_intr: hwgroup->busy was 0 ??\n", drive->name);
        }
        hwgroup->handler = NULL;
        del_timer(
        spin_unlock(

        if (drive->unmask)
                ide__sti();        /* local CPU only */
        startstop = handler(drive);                /* service this interrupt, may set handler for next interrupt */
        spin_lock_irq(

        /*
        * Note that handler() may have set things up for another
        * interrupt to occur soon, but it cannot happen until
        * we exit from this routine, because it will be the
        * same irq as is currently being serviced here, and Linux
        * won't allow another of the same (on any CPU) until we return.
        */
        set_recovery_timer(HWIF(drive));
        drive->service_time = jiffies - drive->service_start;
        if (startstop == ide_stopped) {
                if (hwgroup->handler == NULL) {        /* paranoia */
                        hwgroup->busy = 0;
                        ide_do_request(hwgroup, hwif->irq);
                } else {
                        printk("%s: ide_intr: huh? expected NULL handler on exit\n", drive->name);
                }
        }
        spin_unlock_irqrestore( flags);
}
; 单个扇区写入之后的中断处理
static ide_startstop_t write_intr (ide_drive_t *drive)
{
        byte stat;
        int i;
        ide_hwgroup_t *hwgroup = HWGROUP(drive);
        struct request *rq = hwgroup->rq;

        if (!OK_STAT(stat=GET_STAT(),DRIVE_READY,drive->bad_wstat)) {
                printk("%s: write_intr error1: nr_sectors=%ld, stat=0x%02x\n", drive->name, rq->nr_sectors, stat);
        } else {
                if ((rq->nr_sectors == 1) ^ ((stat  DRQ_STAT) != 0)) {
                        rq->sector++;
                        rq->buffer += 512;
                        rq->errors = 0;
                        i = --rq->nr_sectors;
                        --rq->current_nr_sectors;
                        if (((long)rq->current_nr_sectors)                                 ide_end_request(1, hwgroup);
                        if (i > 0) {
                                idedisk_output_data (drive, rq->buffer, SECTOR_WORDS);
                                ide_set_handler (drive,  WAIT_CMD, NULL);
                                return ide_started;
                        }
                        return ide_stopped;
                }
                return ide_stopped;        /* the original code did this here (?) */
        }
        return ide_error(drive, "write_intr", stat);
}
; 多重扇区写入后的中断处理
static ide_startstop_t multwrite_intr (ide_drive_t *drive)
{
        byte stat;
        int i;
        ide_hwgroup_t *hwgroup = HWGROUP(drive);
        struct request *rq =

        if (OK_STAT(stat=GET_STAT(),DRIVE_READY,drive->bad_wstat)) {
                if (stat  DRQ_STAT) {
                        /*
                        *        The drive wants data. Remember rq is the copy
                        *        of the request
                        */
                        if (rq->nr_sectors) {
                                if (ide_multwrite(drive, drive->mult_count))
                                        return ide_stopped;
                                ide_set_handler (drive,  WAIT_CMD, NULL);
                                return ide_started;
                        }
                } else {
                        /*
                        *        If the copy has all the blocks completed then
                        *        we can end the original request.
                        */
                        if (!rq->nr_sectors) {        /* all done? */
                                rq = hwgroup->rq;
                                for (i = rq->nr_sectors; i > 0;){
                                        i -= rq->current_nr_sectors;
                                        ide_end_request(1, hwgroup);
                                }
                                return ide_stopped;
                        }
                }
                return ide_stopped;        /* the original code did this here (?) */
        }
        return ide_error(drive, "multwrite_intr", stat);
}
; 读扇区的中断处理
static ide_startstop_t read_intr (ide_drive_t *drive)
{
        byte stat;
        int i;
        unsigned int msect, nsect;
        struct request *rq;

        /* new way for dealing with premature shared PCI interrupts */
        if (!OK_STAT(stat=GET_STAT(),DATA_READY,BAD_R_STAT)) {
                if (stat  (ERR_STAT|DRQ_STAT)) {
                        return ide_error(drive, "read_intr", stat);
                }
                /* no data yet, so wait for another interrupt */
                ide_set_handler(drive,  WAIT_CMD, NULL);
                return ide_started;
        }
        msect = drive->mult_count;

read_next:
        rq = HWGROUP(drive)->rq;
        if (msect) {
                if ((nsect = rq->current_nr_sectors) > msect)
                        nsect = msect;
                msect -= nsect;
        } else
                nsect = 1;
        idedisk_input_data(drive, rq->buffer, nsect * SECTOR_WORDS);
        rq->sector += nsect;
        rq->buffer += nsect        rq->errors = 0;
        i = (rq->nr_sectors -= nsect);
        if (((long)(rq->current_nr_sectors -= nsect))                 ide_end_request(1, HWGROUP(drive));
        if (i > 0) {
                if (msect)
                        goto read_next;
                ide_set_handler (drive,  WAIT_CMD, NULL);
                return ide_started;
        }
        return ide_stopped;
}
static inline void idedisk_input_data (ide_drive_t *drive, void *buffer, unsigned int wcount)
{
        ide_input_data(drive, buffer, wcount);
        if (drive->bswap)
                idedisk_bswap_data(buffer, wcount);
}
void ide_input_data (ide_drive_t *drive, void *buffer, unsigned int wcount)
{
        byte io_32bit = drive->io_32bit;

        if (io_32bit) {
#if SUPPORT_VLB_SYNC
                if (io_32bit  2) {
                        unsigned long flags;
                        __save_flags(flags);        /* local CPU only */
                        __cli();                /* local CPU only */
                        do_vlb_sync(IDE_NSECTOR_REG);
                        insl(IDE_DATA_REG, buffer, wcount);
                        __restore_flags(flags);        /* local CPU only */
                } else
#endif /* SUPPORT_VLB_SYNC */
                        insl(IDE_DATA_REG, buffer, wcount);
        } else {
#if SUPPORT_SLOW_DATA_PORTS
                if (drive->slow) {
                        unsigned short *ptr = (unsigned short *) buffer;
                        while (wcount--) {
                                *ptr++ = inw_p(IDE_DATA_REG);
                                *ptr++ = inw_p(IDE_DATA_REG);
                        }
                } else
#endif /* SUPPORT_SLOW_DATA_PORTS */
                        insw(IDE_DATA_REG, buffer, wcount        }
}


atomic_t queued_sectors;

#define blk_finished_io(nsects)                                \
        atomic_sub(nsects,                 \
        if (atomic_read(                 printk("block: queued_sectors                 atomic_set( 0);                \
        }

static inline void blkdev_dequeue_request(struct request * req)
{
        list_del(
}
void ide_end_request (byte uptodate, ide_hwgroup_t *hwgroup)
{
        struct request *rq;
        unsigned long flags;

        spin_lock_irqsave( flags);
        rq = hwgroup->rq;

        if (!end_that_request_first(rq, uptodate, hwgroup->drive->name)) {
                add_blkdev_randomness(MAJOR(rq->rq_dev));
                blkdev_dequeue_request(rq);
                hwgroup->rq = NULL;
                end_that_request_last(rq);
        }
        spin_unlock_irqrestore( flags);
}
int end_that_request_first (struct request *req, int uptodate, char *name)
{
        struct buffer_head * bh;
        int nsect;

        req->errors = 0;
        if (!uptodate)
                printk("end_request: I/O error, dev %s (%s), sector %lu\n",
                        kdevname(req->rq_dev), name, req->sector);

        if ((bh = req->bh) != NULL) {
                nsect = bh->b_size >> 9;
                blk_finished_io(nsect);
                req->bh = bh->b_reqnext;
                bh->b_reqnext = NULL;
                bh->b_end_io(bh, uptodate);
                if ((bh = req->bh) != NULL) {
                        req->hard_sector += nsect;
                        req->hard_nr_sectors -= nsect;
                        req->sector = req->hard_sector;
                        req->nr_sectors = req->hard_nr_sectors;

                        req->current_nr_sectors = bh->b_size >> 9;
                        if (req->nr_sectors current_nr_sectors) {
                                req->nr_sectors = req->current_nr_sectors;
                                printk("end_request: buffer-list destroyed\n");
                        }
                        req->buffer = bh->b_data;
                        return 1;
                }
        }
        return 0;
}
void end_that_request_last(struct request *req)
{
        if (req->sem != NULL)
                up(req->sem);

        blkdev_release_request(req);
}
void inline blkdev_release_request(struct request *req)
{
        request_queue_t *q = req->q;
        int rw = req->cmd;

        req->rq_status = RQ_INACTIVE;
        req->q = NULL;

        /*
        * Request may not have originated from ll_rw_blk. if not,
        * asumme it has free buffers and check waiters
        */
        if (q) {
                /*
                * we've released enough buffers to start I/O again
                */
                if (waitqueue_active(
                     atomic_read(                         wake_up(

                /*
                * Add to pending free list and batch wakeups
                */
                list_add(

                if (++q->pending_free[rw] >= batch_requests) {
                        int wake_up = q->pending_free[rw];
                        blk_refill_freelist(q, rw);
                        wake_up_nr( wake_up);
                }
        }
}
void inline blk_refill_freelist(request_queue_t *q, int rw)
{
        if (q->pending_free[rw]) {
                list_splice(
                INIT_LIST_HEAD(
                q->pending_free[rw] = 0;
        }
}

[目录]


proc

PROC文件系统是一个伪文件系统,它的文件和目录是由Linux 操作系统核心提供的,以文件系统的方式为访问系统内核数据的操作提供接口,它们不占用磁盘上的任何空间,有了这些文件和目录, 用户可以更容易的了解操作系统核心或各个进程的状态,并能对系统的一些参数进行配置。比如,一个系统内能打开的文件数最大缺省是1024,即系统最多能同时打开1024个文件,这在使用Linux做多用户的服务器时是不够用的,通过对/PROC下文件的修改可以在不修改核心,甚至不启动机器的情况下改变这个缺省值。由于系统的信息,如进程,是动态改变的,所以用户或应用程序读取PROC文件时,PROC文件系统是动态从系统内核读出所需信息并提交的。它的目录结构如下:

目录名称        目录内容
apm         Advanced power management info
Cmdline         Kernel command line
Cpuinfo        Info about the CPU
Devices        Available devices (block and character)
Dma        Used DMS channels
Filesystems        Supported filesystems
Interrupts        Interrupt usage
Ioports        I/O port usage
Kcore        Kernel core image
Kmsg        Kernel messages
Ksyms        Kernel symbol table
Loadavg        Load average
Locks        Kernel locks
Meminfo        Memory info
Misc        Miscellaneous
Modules        List of loaded modules
Mounts        Mounted filesystems
Partitions        Table of partitions known to the system
Rtc        Real time clock
Slabinfo        Slab pool info
Stat        Overall statistics
Swaps        Swap space utilization
Version        Kernel version
Uptime        System uptime

并不是所有这些目录在你的系统中都有,这取决于你的内核配置和装载的模块。另外,在/proc下还有三个很重要的目录:net,scsi和sys。Sys目录是可写的,可以通过它来访问或修改内核的参数,而net和scsi则依赖于内核配置。例如,如果系统不支持scsi,则scsi目录不存在。
除了以上介绍的这些,还有的是一些以数字命名的目录,它们是进程目录。系统中当前运行的每一个进程都有对应的一个目录在/PROC下,以进程的PID号为目录名,它们是读取进程信息的接口。而self目录则是读取进程本身的信息接口,是一个link。Proc文件系统的名字就是由之而起。进程目录的结构如下:

目录名称        目录内容
Cmdline        Command line arguments
Environ        Values of environment variables
Fd        Directory, which contains all file descriptors
Mem        Memory held by this process
Stat        Process status
Status        Process status in human readable form
Cwd        Link to the current working directory
Exe        Link to the executable of this process
Maps        Memory maps
Statm        Process memory status information
Root        Link to the root directory of this process

用户如果要查看系统信息,可以用cat命令。例如:
> cat /proc/interrupts
           CPU0
  0:    8728810          XT-PIC  timer
  1:        895          XT-PIC  keyboard
  2:          0          XT-PIC  cascade
  3:     531695          XT-PIC  aha152x
  4:    2014133          XT-PIC  serial
  5:      44401          XT-PIC  pcnet_cs
  8:          2          XT-PIC  rtc
11:          8          XT-PIC  i82365
12:     182918          XT-PIC  PS/2 Mouse
13:          1          XT-PIC  fpu
14:    1232265          XT-PIC  ide0
15:          7          XT-PIC  ide1
NMI:          0
2.修改内核参数
在/proc文件系统中有一个有趣的目录:/proc/sys。它不仅提供了内核信息,而且可以通过它修改内核参数,来优化你的系统。但是你必须很小心,因为可能会造成系统崩溃。最好是先找一台无关紧要的机子,调试成功后再应用到你的系统上。
要改变内核的参数,只要用vi编辑或echo参数重定向到文件中即可。下面有一个例子:
# cat /proc/sys/fs/file-max
4096
# echo 8192 > /proc/sys/fs/file-max
# cat /proc/sys/fs/file-max
8192
如果你优化了参数,则可以把它们写成脚本文件,使它在系统启动时自动完成修改。

PROC文件系统的初始化过程概述

PROC文件系统总的初始化流程如下图所示,是从INIT/MAIN.C的START_KERNEL()函数中开始的,首先是PROC_ROOT_INIT(),在这个函数中用PROC_DIR_EMTRY注册了/PROC及其目录下的各个文件及目录的基本信息,包括注册INODE NUMBER,文件名长度,文件名,操作权限,连接数,用户标识号,组标识号,允许的INODE OPERATIONS和兄弟、父母、子文件的指针等,并生成文件目录之间的相互关系,即目录树。接下来在SYSCLT_INIT()里把ROOT_TABLE的各项内容挂到/PROC树的PROC_SYS_ROOT下面,PROC_SYS_ROOT是一个特殊的PROC项,即/PROC/SYS目录,在此目录下的部分文件是可写的,可以通过修改这些文件内容来修改系统配置参数。然后是FILESYSTEM_SETUP(),在这里产生一个新的FILE_SYSTEM_TYPE:PROC_FS_TYPE,把这个文件系统类型挂到FILE_SYSTEMS链表的末尾,其中包含了读取PROC文件系统SUPER BLOCK的函数指针,PROC_READ_SUPER,接下来在装载了ROOT文件系统后,如果还要把PROC文件系统MOUNT上来则需通过此函数把PROC文件系统的SUPER BLOCK读进内存里的SUPER_BLOCKS链表。从上面可以看到,PROC文件系统的初始化流程主要分两步,即登记注册和挂载文件系统,其中的核心内容是在登记注册里,下面就具体分析一下这两部分的具体初始化操作。
三、PROC文件系统的登记注册过程
从程序中来看,PROC文件系统的文件可以分为两大类,一类是ROOT部分,另一类是BASE部分,体现在初始化程序中分别叫做PROC_ROOT_INIT()和PROC_BASE_INIT()。ROOT部分主要是针对/PROC目录下的一些名称位置相对固定的常规性文件,如 CPUINFO,MEMINFO,KMESG,MODULES,DEVICES,INTERRUPTS,FILESYSTEMS,PARTITIONS,DMA,IOPORTS,CMDLINE,MOUNTS等;而BASE部分则是针对系统中每个进程的,每个运行中的进程在/PROC下都有一个以自己的进程号为名字的目录,里面记载了关于此进程运行状态的信息,如STATUS,MEM,CWD,ROOT,FD,EXE,CMDLINE,STAT,STATM等。下面将会分别介绍这两部分的初始化过程,首先介绍一下基本的数据结构。
1.基本数据结构
在PROC        文件系统中最重要的数据结构是一个叫PROC_DIR_ENTRY的结构类型(include/linux/proc_fs.h),所有该文件系中的文件及目录都除了通用文件操作用的INODE,FILE,DENTRY等结构外,都必须首先注册自己的一个PROC_DIR_ENTRY,在其中定义了针对自己的各项属性和操作函数指针。
struct proc_dir_entry {
unsigned short low_ino;          /*注册inode号,实际inode的低16位*/
                unsigned short namelen;                   /*名称字符串的长度*/
        const char *name;                        /*文件名称*/
                mode_t mode;                           /*文件权限*/
        nlink_t nlink;                               /*连接到此文件的目录项个数*/
                uid_t uid;                                   /*用户标识*/
        gid_t gid;                               /*组标识*/
                unsigned long size;                       /*文件大小,均为0*/
        struct inode_operations * ops;              /*inode操作的函数指针*/
                int (*get_info)(char *, char **, off_t, int, int);
        void (*fill_inode)(struct inode *, int);         /*填补inode信息的函数指针*/
        struct proc_dir_entry *next, *parent, *subdir;    /*兄弟,父亲,子文件指针*/
void *data;
        int (*read_proc)(char *page, char **start, off_t off, int count,
int *eof, void *data);
        int (*write_proc)(struct file *file, const char *buffer,
                                  unsigned long count, void *data);
        int (*readlink_proc)(struct proc_dir_entry *de, char *page);
        unsigned int count;                                /*使用数*/
                int deleted;                                            /*删除标志*/
};
其次就是针对INODE,FILE和SUPER BLOCK的各种操作函数指针,这些结构与文件系统中使用的完全相同,此处不再赘述。
2.PROC_ROOT_INIT()
PROC_ROOT_INIT()函数的定义如下:
__initfunc(void proc_root_init(void))
{
        proc_base_init();
        proc_register(&proc_root, &proc_root_loadavg);
        proc_register(&proc_root, &proc_root_uptime);
        proc_register(&proc_root, &proc_root_meminfo);
        proc_register(&proc_root, &proc_root_kmsg);
        proc_register(&proc_root, &proc_root_version);
        proc_register(&proc_root, &proc_root_cpuinfo);
        proc_register(&proc_root, &proc_root_self);
#ifdef CONFIG_SYSCTL
        proc_register(&proc_root, &proc_sys_root);
#endif
#ifdef CONFIG_MCA
        proc_register(&proc_root, &proc_mca);
#endif

#ifdef CONFIG_DEBUG_MALLOC
        proc_register(&proc_root, &proc_root_malloc);
#endif
#ifdef CONFIG_MODULES
        proc_register(&proc_root, &proc_root_modules);
        proc_register(&proc_root, &proc_root_ksyms);
#endif
        proc_register(&proc_root, &proc_root_stat);
        proc_register(&proc_root, &proc_root_devices);
        proc_register(&proc_root, &proc_root_partitions);
        proc_register(&proc_root, &proc_root_interrupts);
        proc_register(&proc_root, &proc_root_filesystems);
        proc_register(&proc_root, &proc_root_fs);
        proc_register(&proc_root, &proc_root_dma);
        proc_register(&proc_root, &proc_root_ioports);
        proc_register(&proc_root, &proc_root_cmdline);
                ……
                }
__initfunc是在inlucde/linux/init.h中定义的一个宏,表示此函数仅在系统初始化时使用,使用完毕即释放申请的内存资源。
PROC_REGISTER()函数在fs/proc/root.c中定义,程序如下:
int proc_register(struct proc_dir_entry * dir, struct proc_dir_entry * dp)
{
        int        i;
        if (dp->low_ino == 0) {
                i = make_inode_number();
                if (i < 0)
                        return -EAGAIN;
                dp->low_ino = i;
        }
/*如果没有low_ino值,则产生一个新的赋给它*/
        dp->next = dir->subdir;
        dp->parent = dir;
        dir->subdir = dp;
/*赋给兄弟、父母及子女的指针*/
        if (S_ISDIR(dp->mode)) {
                if (dp->ops == NULL)
                        dp->ops = &proc_dir_inode_operations;
                dir->nlink++;
        } else if (S_ISLNK(dp->mode)) {
                if (dp->ops == NULL)
                        dp->ops = &proc_link_inode_operations;
        } else {
                if (dp->ops == NULL)
                        dp->ops = &proc_file_inode_operations;
        }
/*对于dp的不同属性调整操作函数指针*/
        return 0;
}
初始化时首先要为每个文件或目录创建一个PROC_DIR_ENTRY的实例变量,内容包括注册的INODE号,名称,操作权限,连接数和INODE操作函数指针等,具体方法如下所示:
struct proc_dir_entry proc_root = {
        PROC_ROOT_INO, 5, "/proc", S_IFDIR | S_IRUGO | S_IXUGO,
  2, 0, 0, 0, &proc_root_inode_operations, NULL, NULL, NULL,
        &proc_root, NULL };
struct proc_dir_entry proc_mca = {
        PROC_MCA, 3, "mca", S_IFDIR | S_IRUGO | S_IXUGO,
  2, 0, 0, 0, &proc_dir_inode_operations, NULL, NULL, NULL,
  &proc_root, NULL };
static struct proc_dir_entry proc_root_loadavg = {
        PROC_LOADAVG, 7, "loadavg",        S_IFREG | S_IRUGO, 1, 0, 0,
        0, &proc_array_inode_operations };
static struct proc_dir_entry proc_root_uptime = {
        PROC_UPTIME, 6, "uptime",        S_IFREG | S_IRUGO, 1, 0, 0,
        0, &proc_array_inode_operations };
static struct proc_dir_entry proc_root_meminfo = {
        PROC_MEMINFO, 7, "meminfo",        S_IFREG | S_IRUGO, 1, 0, 0,
        0, &proc_array_inode_operations };
static struct proc_dir_entry proc_root_kmsg = {
        PROC_KMSG, 4, "kmsg", S_IFREG | S_IRUSR, 1, 0, 0,
        0, &proc_kmsg_inode_operations };
......
PROC_REGISTER()函数首先检查这个新的PROC_DIR_ENTRY是否已有自己的PROC INODE号,如前所述,PROC文件系统是个“伪”文件系统,它并不存在于任何实际的物理设备上,所以它的文件INODE号与普通文件的INODE号的涵义是不同的,它不需要标识此文件的物理存在位置,只要能在本文件系统中唯一地标识这个文件即可。对于ROOT部分的文件在include/proc_fs.h中用一个枚举变量enum root_directory_inos来对其赋值,其中第一个文件即/PROC目录用PROC_ROOT_INO=1来定义。如果此文件在变量初始化时未赋值,LINUX用一个proc_alloc_map来表示INODE的使用情况,proc_alloc_map是一块有4096个bits的内存空间,每一位表示一个INODE的使用情况,如已经被使用则置位,放弃时恢复。分配时找其中第一位未使用的bit,在其位数上加上4096就是这个新登记文件的PROC INODE号。在此之后,PROC_REGISTER()调整父目录与子目录/文件之间的指针关系,及子目录/文件的INODE操作函数指针,最终结果是生成一棵以PROC_DIR_ENTRY为节点,PROC_ROOT为根的目录树,表明其相互之间的关系,在以后对文件或目录做标准操作(即用INODE,FILE,DENTRY等结构)的时候就可以从中获得需要的信息和函数指针。

在解释过PROC_REGISTER()函数后顺便再说明一下它的反向操作函数PROC_UNREGISTER(),即注销一个PROC_DIR_ENTRY项,其定义如下:

int proc_unregister(struct proc_dir_entry * dir, int ino)
{
        struct proc_dir_entry **p = &dir->subdir, *dp;
/*从dir的subdir指针开始进行搜索*/
        while ((dp = *p) != NULL) {          /*在dp指针尚不为空时*/
                if (dp->low_ino == ino) {    /*如果low_ino==ino,说明仅对root部分*/
                        *p = dp->next;
                        dp->next = NULL;
         /*兄弟指针置空*/
                        if (S_ISDIR(dp->mode))
                                dir->nlink--;
        /*如果dp是目录,其父目录的连接数要减去1*/
                        if (ino >= PROC_DYNAMIC_FIRST &&
                            ino < PROC_DYNAMIC_FIRST+PROC_NDYNAMIC)
                                clear_bit(ino-PROC_DYNAMIC_FIRST,
                                          (void *) proc_alloc_map);
        /*如果是在程序中生成的low_ino,则要清除对应的proc_allc_map中的*/
         /*位标志*/
                        proc_kill_inodes(ino);
                        return 0;
                }
                p = &dp->next;            /*p指向dp的兄弟指针,继续搜索*/
        }
        return -EINVAL;
}
在PROC_UNREGISTER()中调用了另一个函数PROC_KILL_INODE(),该函数的功能是把一个已经被注销的PROC文件系统的INODE消灭掉,定义如下:
static void proc_kill_inodes(int ino)
{
        struct file *filp;

        /* inuse_filps is protected by the single kernel lock */
        for (filp = inuse_filps; filp; filp = filp->f_next) {
     /*在正在使用中的文件链表中进行搜索*/
                struct dentry * dentry;
                struct inode * inode;

                dentry = filp->f_dentry;
                if (!dentry)
                        continue;
                if (dentry->d_op != &proc_dentry_operations)
                        continue;
     /*如果该项不属于PROC文件系统,则继续*/
                inode = dentry->d_inode;
                if (!inode)
                        continue;
                if (inode->i_ino != ino)
                        continue;
                filp->f_op = NULL;
     /*把该文件的操作函数指针置空,以后就无法使用了*/
        }
}
3.PROC_BASE_INIT()
PROC_BASE_INIT()函数在PROC_ROOT_INIT()中调用,BASE部分的初始化与ROOT部分基本相同,首先为每个目录和文件初始化一个PROC_DIR_ENTRY结构,然后调用PROC_REGISTER()函数进行注册,并生成BASE部分的关系树,程序如下:
struct proc_dir_entry proc_pid = {
        PROC_PID_INO, 5, "<pid>",        S_IFDIR | S_IRUGO | S_IXUGO, 2, 0, 0,
        0, &proc_base_inode_operations,        NULL, proc_pid_fill_inode,
        NULL, &proc_root, NULL
};

static struct proc_dir_entry proc_pid_status = {
        PROC_PID_STATUS, 6, "status",        S_IFREG | S_IRUGO, 1, 0, 0,
        0, &proc_array_inode_operations,        NULL, proc_pid_fill_inode,
};
static struct proc_dir_entry proc_pid_mem = {
        PROC_PID_MEM, 3, "mem",        S_IFREG | S_IRUSR | S_IWUSR, 1, 0, 0,
        0, &proc_mem_inode_operations,        NULL, proc_pid_fill_inode,
};
static struct proc_dir_entry proc_pid_cwd = {
        PROC_PID_CWD, 3, "cwd",        S_IFLNK | S_IRWXU, 1, 0, 0,
        0, &proc_link_inode_operations,        NULL, proc_pid_fill_inode,
};
static struct proc_dir_entry proc_pid_root = {
        PROC_PID_ROOT, 4, "root",        S_IFLNK | S_IRWXU, 1, 0, 0,
        0, &proc_link_inode_operations,        NULL, proc_pid_fill_inode,
};
static struct proc_dir_entry proc_pid_exe = {
        PROC_PID_EXE, 3, "exe",        S_IFLNK | S_IRWXU, 1, 0, 0,
        0, &proc_link_inode_operations,        NULL, proc_pid_fill_inode,
};
static struct proc_dir_entry proc_pid_fd = {
        PROC_PID_FD, 2, "fd",        S_IFDIR | S_IRUSR | S_IXUSR, 2, 0, 0,
        0, &proc_fd_inode_operations,        NULL, proc_pid_fill_inode,
};
static struct proc_dir_entry proc_pid_environ = {
        PROC_PID_ENVIRON, 7, "environ",        S_IFREG | S_IRUSR, 1, 0, 0,
        0, &proc_array_inode_operations,        NULL, proc_pid_fill_inode,
};
static struct proc_dir_entry proc_pid_cmdline = {
        PROC_PID_CMDLINE, 7, "cmdline",        S_IFREG | S_IRUGO, 1, 0, 0,
        0, &proc_array_inode_operations,        NULL, proc_pid_fill_inode,
};
static struct proc_dir_entry proc_pid_stat = {
        PROC_PID_STAT, 4, "stat",        S_IFREG | S_IRUGO, 1, 0, 0,
        0, &proc_array_inode_operations,        NULL, proc_pid_fill_inode,
};
static struct proc_dir_entry proc_pid_statm = {
        PROC_PID_STATM, 5, "statm",        S_IFREG | S_IRUGO, 1, 0, 0,
        0, &proc_array_inode_operations,        NULL, proc_pid_fill_inode,
};
static struct proc_dir_entry proc_pid_maps = {
        PROC_PID_MAPS, 4, "maps",        S_IFIFO | S_IRUGO, 1, 0, 0,
        0, &proc_arraylong_inode_operations,        NULL, proc_pid_fill_inode, };


__initfunc(void proc_base_init(void))
{
#if CONFIG_AP1000
        proc_register(&proc_pid, &proc_pid_ringbuf);
#endif
        proc_register(&proc_pid, &proc_pid_status);
        proc_register(&proc_pid, &proc_pid_mem);
        proc_register(&proc_pid, &proc_pid_cwd);
        proc_register(&proc_pid, &proc_pid_root);
        proc_register(&proc_pid, &proc_pid_exe);
        proc_register(&proc_pid, &proc_pid_fd);
        proc_register(&proc_pid, &proc_pid_environ);
        proc_register(&proc_pid, &proc_pid_cmdline);
        proc_regis
ter(&proc_pid, &proc_pid_stat);
        proc_register(&proc_pid, &proc_pid_statm);
        proc_register(&proc_pid, &proc_pid_maps);
#ifdef __SMP__
        proc_register(&proc_pid, &proc_pid_cpu);
#endif
};
这部分与ROOT部分的不同之处一是BASE部分的文件/目录的PROC INODE 号是用另一个枚举变量enum pid_directory_inos来赋值,其第一项PROC_PID_INO=2,即<pid>目录。由于ROOT部分的每个文件/目录在/PROC下只有唯一的一个实例,而BASE部分对每个进程均有相同的一份拷贝,所以它的实际INODE号必须对不同的进程予以区分,在LINUX中,这种区分是通过把进程PID做为INODE的高16位,PROC_DIR_ENTRY中的LOW_INO做为INODE的低16位来实现的,这样可以保证每个文件INODE号在文件系统中的唯一性。另一个不同之处是BASE部分的文件在注册其PROC_DIR_ENTRY的时候都增加了FILL_INODE函数指针,指向PROC_PID_FILL_INODE函数,该函数的主要功能是把实际INODE号右移16位,获得对应此目录或文件的进程PID号,再把此进程的用户标识和组标识赋回给INODE结构。还有一点区别就是PROC_BASE_INIT()初始化的这棵PROC_DIR_ENTRY树是相对独立的(如图2),它以<pid>目录,即PROC_PID项为根,并注册了该目录下各个文件及目录之间的相互关系,但它并不把PROC_PID挂到PROC_ROOT下面去,因为这是要在对PROC_ROOT做READDIR时动态加载的。
4.INODE OPERATIONS
有一点需要强调的是PROC文件系统在初始化时只是为每个目录和文件登记了一个PROC_DIR_ENTRY项,并为这些项生成了关系树,但对于VFS的通用文件操作做使用的数据结构,如INODE,FILE,DENTRY等此时都是不存在的,这些数据结构要等到在对PROC文件系统的文件做OPEN和READ等操作时才会根据PROC_DIR_ENTRY里定义的INODE OPERATION及其中的FILE OPERATIONS函数指针调用对应函数产生,这是PROC文件系统与其他基于设备的文件系统的一大区别之处。所以,在PROC_DIR_ENTRY中定义的INODE OPERATIONS决定了该文件的操作方式以及获取对应系统信息的渠道。举例来说,PROC_ROOT指向的PROC_ROOT_INODE_OPERATIONS中允许的INODE OPERATIONS函数是PROC_ROOT_LOOKUP,FILE OPERATIONS函数是PROC_ROOT_READDIR ;PROC_PID指向的PROC_BASE_INODE_OPERATIONS中允许的INODE OPERATIONS函数是PROC_LOOKUP,FILE OPERATIONS函数是PROC_ READDIR ;PROC_MEM_INFO指向的PROC_ARRAY_INODE_OPERATIONS中允许的INODE OPERATIONS函数均为空,FILE OPERATIONS函数是ARRAY_READ。下面我们来分析一下几个LOOKUP和READDIR函数,ARRAY_READ的功能主要是面向底层如何获取系统信息反馈上来,详见黄军同学的报告,这里就不做详述了。
PROC_LOOKUP、PROC_READDIR与PROC_ROOT_LOOKUP、PROC_ROOT_READDIR的功能基本上是相同的,只不过加上了ROOT后就加上了对<PID>目录的处理功能。
程序如下所示:
1) proc_lookup()
int proc_lookup(struct inode * dir, struct dentry *dentry)
{
        struct inode *inode;
        struct proc_dir_entry * de;
        int error;

        error = -ENOTDIR;
        if (!dir || !S_ISDIR(dir->i_mode))
                goto out;
/*如果dir空或dir不是目录,则退出*/
        error = -ENOENT;
        inode = NULL;
        de = (struct proc_dir_entry *) dir->u.generic_ip;
/*根据dir生成一个proc_dir_entry的指针*/
        if (de) {
                for (de = de->subdir; de ; de = de->next) {
         /*在de的子目录和文件中搜索*/
                        if (!de || !de->low_ino)
                                continue;
                        if (de->namelen != dentry->d_name.len)
                                continue;
                        if (!memcmp(dentry->d_name.name, de->name, de->namelen)) {
            /*如果dentry和由dir指向的proc_dir_entry名字相同*/
                                int ino = de->low_ino | (dir->i_ino & ~(0xffff));
                                error = -EINVAL;
                                inode = proc_get_inode(dir->i_sb, ino, de);
                 /*申请一个inode节点,对应的proc_dir_entry节点为de,节点号*/
                 /*为ino。同时把de的数据填入inode */
                                break;
                        }
                }
        }

        if (inode) {
                dentry->d_op = &proc_dentry_operations;
                d_add(dentry, inode);
    /*把dentry放到dentry_hash_table表中然后把inode的I_dentry和dentry的*/
    /*d_alias相连*/
    error = 0;
        }
out:
        return error;
}

   2) proc_root_lookup()
static int proc_root_lookup(struct inode * dir, struct dentry * dentry)
{
        unsigned int pid, c;
        struct task_struct *p;
        const char *name;
        struct inode *inode;
        int len;

        if (dir->i_ino == PROC_ROOT_INO) { /* check for safety... */
                dir->i_nlink = proc_root.nlink;

                read_lock(&tasklist_lock);  /*加读进程列表的锁*/
                for_each_task(p) {
                        if (p->pid)
                                dir->i_nlink++; /*对于每个进程都要把proc_root的link数加1*/
                }
                read_unlock(&tasklist_lock);  /*解读进程列表的锁*/
        }

        if (!proc_lookup(dir, dentry)) /*如果调用proc_lookup成功则返回*/
                return 0;
        /*如果调用proc_lookup不成功,说明要找的是pid部分的*/
        pid = 0;
        name = dentry->d_name.name;
        len = dentry->d_name.len;
        while (len-- > 0) {
                c = *name - '0';
                name++;
                if (c > 9) {
                        pid = 0;
                        break;
                }
                pid *= 10;
                pid += c;
 /*把目录名的字符串转换成整数型的进程号*/
                if (pid & 0xffff0000) {
                        pid = 0;
                        break;
                }
        }
        read_lock(&tasklist_lock);
        p = find_task_by_pid(pid);
        read_unlock(&tasklist_lock);
        inode = NULL;
        if (pid && p) {
                unsigned long ino = (pid << 16) + PROC_PID_INO;
                inode = proc_get_inode(dir->i_sb, ino, &proc_pid);
                if (!inode)
                        return -EINVAL;
                inode->i_flags|=S_IMMUTABLE;
        }

        dentry->d_op = &proc_dentry_operations;
        d_add(dentry, inode);
        return 0;
}

3) proc_readdir()
int proc_readdir(struct file * filp, void * dirent, filldir_t filldir)
{
        struct proc_dir_entry * de;
        unsigned int ino;
        int i;
        struct inode *inode = filp->f_dentry->d_inode;

        if (!inode || !S_ISDIR(inode->i_mode))
                return -ENOTDIR;
        ino = inode->i_ino;
        de = (struct proc_dir_entry *) inode->u.generic_ip;
        if (!de)
                return -EINVAL;
        i = filp->f_pos;
        switch (i) {
                case 0:
                        if (filldir(dirent, ".", 1, i, ino) < 0)
                                return 0;
                        i++;
                        filp->f_pos++;
                        /* fall through */
                case 1:
                        if (filldir(dirent, "..", 2, i, de->parent->low_ino) < 0)
                                return 0;
                        i++;
                        filp->f_pos++;
                        /* fall through */
                default:
                        ino &= ~0xffff;
                        de = de->subdir;
                        i -= 2;
                        for (;;) {
                                if (!de)
                                        return 1;
                                if (!i)
                                        break;
                                de = de->next;
                                i--;
                        }

                        do {
                                if (filldir(dirent, de->name, de->namelen, filp->f_pos, ino | de->low_ino) < 0)
                                        return 0;
                                filp->f_pos++;
                                de = de->next;
                        } while (de);
        }
        return 1;
}


4) get_pid_list()
static int get_pid_list(int index, unsigned int *pids)
{
        struct task_struct *p;
        int nr_pids = 0;

        index -= FIRST_PROCESS_ENTRY;
        read_lock(&tasklist_lock);
        for_each_task(p) {
                int pid = p->pid;
                if (!pid)
                        continue;
                if (--index >= 0)
                        continue;
                pids[nr_pids] = pid;
                nr_pids++;
                if (nr_pids >= PROC_MAXPIDS)
                        break;
        }
        read_unlock(&tasklist_lock);
        return nr_pids;
}


5) proc_root_readdir()
static int proc_root_readdir(struct file * filp,        void * dirent, filldir_t filldir)
{
        unsigned int pid_array[PROC_MAXPIDS];
        char buf[PROC_NUMBUF];
        unsigned int nr = filp->f_pos;
        unsigned int nr_pids, i;

        if (nr < FIRST_PROCESS_ENTRY) {
                int error = proc_readdir(filp, dirent, filldir);
                if (error <= 0)
                        return error;
                filp->f_pos = nr = FIRST_PROCESS_ENTRY;
        }

        nr_pids = get_pid_list(nr, pid_array);

        for (i = 0; i < nr_pids; i++) {
                int pid = pid_array[i];
                ino_t ino = (pid << 16) + PROC_PID_INO;
                unsigned long j = PROC_NUMBUF;

                do {
                        j--;
                        buf[j] = '0' + (pid % 10);
                        pid /= 10;
                } while (pid);

                if (filldir(dirent, buf+j, PROC_NUMBUF-j, filp->f_pos, ino) < 0)
                        break;
                filp->f_pos++;
        }
        return 0;
}


在这里通过inode_operation和file_operation注册的这些函数是在对/PROC下的文件及目录进行open及read操作时才被调用,生成对应的inode,file,dentry等数据结构并取得对应的系统信息反馈

PROC文件系统的挂载过程
FILESYSTEM_SETUP()主要就是调用了一系列的INIT_*_FS()函数(*代表各种不同的文件系统),对PROC文件系统就是INIT_PROC_FS(),在此函数中实例化了一个FILE_SYSTEM_TYPE类型的结构变量PROC_FS_TYPE,其中包括此文件系统的名字和PROC_READ_SUPER函数指针,然后通过REGISTER_FILESYSTEM函数把它挂到FILE_SYSTEMS链表的末尾。
/*  fs/filesystems.c  */
void __init filesystem_setup(void)
{
#ifdef CONFIG_EXT2_FS
        init_ext2_fs();     /*  初始化ext2文件系统  */
#endif
......
#ifdef CONFIG_PROC_FS
        init_proc_fs();     /*  初始化proc文件系统  */
#endif
......
} ;

/*  fs/proc/procfs_syms.c  */
static struct file_system_type proc_fs_type = {
        "proc",
        0 /* FS_NO_DCACHE doesn't work correctly */,
        proc_read_super,    /*针对本文件系统读取super_block的函数指针*/
        NULL } ;
int init_proc_fs(void)
{
        return register_filesystem(&proc_fs_type) == 0;
}

/*   fs/super.c   */
int register_filesystem(struct file_system_type * fs)
{
        struct file_system_type ** tmp;

        if (!fs)
                return -EINVAL;
        if (fs->next)
                return -EBUSY;
        tmp = &file_systems;
        while (*tmp) {
                if (strcmp((*tmp)->name, fs->name) == 0)
                        return -EBUSY;
                tmp = &(*tmp)->next;
        } /*遍历file_systems链表*/
        *tmp = fs;  /*把fs挂到file_systems链表末尾*/
        return 0;
}

在系统启动时如果需要把PROC文件系统挂载上来则要根据PROC_FS_TYPE中注册的PROC_READ_SUPER()函数把SUPER BLOCK读入内存,并加入SUPER_BLOCKS链表。对于一般基于硬设备上的文件系统,READ_SUPER函数的操作就是在内存中创建一个新的空SUPER BLOCK,然后从设备上把SUPER BLOCK读入。但对于PROC文件系统,其READ_SUPER操作的内容有所不同,因为PROC文件系统是不存在于任何设备上的,所以PROC_READ_SUPER函数就是在产生一个新的空SUPER_BLOCK之后在内存中锁住该块后直接对其进行修改了。
/*   fs/proc/inode.c   */
struct super_block *proc_read_super(struct super_block *s,void *data, int silent)
{
        struct inode * root_inode;

        lock_super(s);    /*锁住该super_block*/
        s->s_blocksize = 1024;
        s->s_blocksize_bits = 10;
        s->s_magic = PROC_SUPER_MAGIC;
        s->s_op = &proc_sops;
        root_inode = proc_get_inode(s, PROC_ROOT_INO, &proc_root);
        if (!root_inode)
                goto out_no_root;
        s->s_root = d_alloc_root(root_inode, NULL);
        if (!s->s_root)
                goto out_no_root;
        parse_options(data, &root_inode->i_uid, &root_inode->i_gid);
        unlock_super(s);
        return s;

out_no_root:
        printk("proc_read_super: get root inode failed\n");
        iput(root_inode);
        s->s_dev = 0;
        unlock_super(s);
        return NULL;
}

[目录]


[ 本文件由良友·收藏家自动生成 ]