VFS实现细节翻译 {https://tldp.org/LDP/lki/lki-3.html}
大部分机翻自【https://tldp.org/LDP/lki/lki-3.html】
3. 虚拟文件系统 (VFS)
3.1 inode 缓存和与 Dcache 的交互
为了支持多个文件系统,Linux 包含一个特殊的内核 接口级别称为 VFS(虚拟文件系统切换)。这与此类似 到 SVR4 衍生品中的 vnode/vfs 接口(最初它来自 BSD 和 Sun 的原始实现)。
Linux inode 缓存在单个文件中实现,该文件包括 的 977 行代码。有趣的是,没有太多变化 在过去的 5-7 年里制作出来的:人们仍然可以识别一些代码 将最新版本与 1.3.42 进行比较。fs/inode.c
Linux inode 缓存的结构如下:
- 全局哈希表 ,其中每个 inode 都由 超级块指针的值和 32 位 inode 编号。没有 superblock () 被添加到双向链表中 以 INSTEAD.匿名 inode 示例 是由 创建的套接字,通过调用 。
inode_hashtable
inode->i_sb == NULL
anon_hash_chain
net/socket.c:sock_alloc()
fs/inode.c:get_empty_inode()
- 全局类型 in_use list (),其中包含有效的 inode with 和 .新分配的 inode 由 和 添加到列表中。
inode_in_use
i_count>0
i_nlink>0
get_empty_inode()
get_new_inode()
inode_in_use
- 全局类型 unused list (),其中包含有效的 inode 跟。
inode_unused
i_count = 0
- 每个超级块类型的脏列表 (),其中包含有效的 具有 、 和 的 inode 。 标记 inode 时 dirty,如果它也经过哈希处理,则会将其添加到列表中。 维护每个超级块的 inode 脏列表允许快速 同步 inode。
sb->s_dirty
i_count>0
i_nlink>0
i_state & I_DIRTY
sb->s_dirty
- Inode 缓存正确 – 名为 .作为 inode 对象被分配和释放,它们被提取并返回到 这个 SLAB 缓存。
inode_cachep
类型列表锚定自 、哈希表来自 。每个 inode 都可以位于哈希表上,并且只能位于一个类型上 (in_use、未使用或脏)列表。inode->i_list
inode->i_hash
所有这些列表都受单个旋转锁保护: 。inode_lock
当从 中调用函数时,将初始化 inode 缓存子系统。该函数标记为 ,这意味着 它的代码稍后会被丢弃。它传递一个参数 – 系统上的物理页数。这样 inode 缓存就可以 根据可用内存量进行自我配置,即创建 如果有足够的内存,则使用更大的哈希表。inode_init()
init/main.c:start_kernel()
__init
有关 inode 缓存的唯一统计信息是未使用的 inode 的数量, 存储在 FILES 和 中,用户程序可通过 FILES 和 进行访问。inodes_stat.nr_unused
/proc/sys/fs/inode-nr
/proc/sys/fs/inode-state
我们可以检查运行在实时内核上的 gdb 中的一个列表,如下所示:
(gdb) printf "%d\n", (unsigned long)(&((struct inode *)0)->i_list) 8 (gdb) p inode_unused $34 = 0xdfa992a8 (gdb) p (struct list_head)inode_unused $35 = {next = 0xdfa992a8, prev = 0xdfcdd5a8} (gdb) p ((struct list_head)inode_unused).prev $36 = (struct list_head *) 0xdfcdd5a8 (gdb) p (((struct list_head)inode_unused).prev)->prev $37 = (struct list_head *) 0xdfb5a2e8 (gdb) set $i = (struct inode *)0xdfb5a2e0 (gdb) p $i->i_ino $38 = 0x3bec7 (gdb) p $i->i_count $39 = {counter = 0x0}
注意,我们从地址 0xdfb5a2e8 中扣除 8 得到 的 (0xdfb5a2e0) 根据 宏 的定义。struct inode
list_entry()
include/linux/list.h
要了解 inode 缓存的工作原理,让我们跟踪 inode 的生命周期 在 ext2 文件系统上的常规文件打开和关闭时:
fd = open("file", O_RDONLY); close(fd);
open(2) 系统调用在 function 中实现,而 真正的工作是由函数完成的,函数被分成 两部分:fs/open.c:sys_open
fs/open.c:filp_open()
open_namei()
:填充包含 dentry 的 nameidata 结构 和 vfsmount 结构.dentry_open()
:给定 dentry 和 vfsmount,此函数分配一个 new 并将它们链接在一起;它还调用 FileSystem 在 inode 时设置的特定方法 被读入(它通过 提供 inode)。struct file
f_op->open()
inode->i_fop
open_namei()
dentry->d_inode
该函数通过 与 dentry 缓存交互,该 反过来调用 ,后者调用特定于 FileSystem 的方法。 此方法的作用是在父级中查找条目 目录,然后执行以获取 相应的 inode – 这将我们带到 inode 缓存。当 inode 为 read in 时,dentry 通过 实例化 。而 我们正在努力,请注意,对于具有 on-disk inode number,则 Lookup 方法的工作是映射其字节序 设置为当前 CPU 格式,例如,如果原始(特定于 FS)目录中的 inode 编号 条目采用 little-endian 32 位格式,可以这样做:open_namei()
path_walk()
real_lookup()
inode_operations->lookup()
iget(sb, ino)
d_add(dentry, inode)
unsigned long ino = le32_to_cpu(de->inode); inode = iget(sb, ino); d_add(dentry, inode);
所以,当我们打开一个文件时,我们点击了 which 实际上是 ,它确实:iget(sb, ino)
iget4(sb, ino, NULL, NULL)
- 尝试查找具有匹配超级块和 inode 编号的 inode 在哈希表中受 保护。如果找到 inode,则 其引用计数 () 递增;如果它 在递增之前为 0 并且 inode 不是脏的,则会将其从任何内容中删除 键入 list () 它当前所在的位置(当然必须是 list)并插入到 type list 中;最后,是递减的。
inode_lock
i_count
inode->i_list
inode_unused
inode_in_use
inodes_stat.nr_unused
- 如果 inode 当前已锁定,我们会等到它被解锁,以便保证返回未锁定的 inode。
iget4()
- 如果在哈希表中找不到 inode,则这是我们第一次 遇到这个 inode,所以我们调用 ,将指针传递给它 添加到哈希表中应插入到的位置。
get_new_inode()
get_new_inode()
从 SLAB 分配新的 inode cache 但此操作可能会阻塞 ( allocation),因此它 必须删除保护哈希表的自旋锁。由于它 已删除自旋锁,则必须重试在 hashtable 之后;如果这次找到它,则返回(递增 引用 ) 在哈希表中找到的 ) 并销毁 新分配的 1。如果在哈希表中仍未找到它,则 然后,我们刚刚分配的新 inode 就是要使用的 inode; 因此,它被初始化为所需的值,并调用特定于 FS 的方法来填充 inode 的 inode 中。这将我们从 inode 缓存带回文件系统代码 – 请记住,当特定于 Filesystem-Specific Method 调用 时,我们来到了 inode 缓存。虽然 正在从磁盘读取 inode,则 inode 已锁定 (); 它在方法返回后解锁,并且它的所有 Waiters 都是 醒来。inode_cachep
GFP_KERNEL
inode_lock
__iget
sb->s_op->read_inode()
lookup()
iget()
s_op->read_inode()
i_state = I_LOCK
read_inode()
现在,让我们看看当我们关闭这个文件描述符时会发生什么。close(2) 系统调用是在 function 中实现的,该函数调用 which rips(替换为 NULL)的描述符 进程的文件描述符表,并调用执行 大部分工作。有趣的事情发生在 中,它检查 这是对文件的最后一次引用,如果是这样,则调用 which 调用 which 是与 dcache(因此使用 inode 缓存 – 请记住 dcache 是 inode 的主节点 缓存!发生。给我们带来的 返回 inode 缓存,让我们了解一下:fs/open.c:sys_close()
do_close(fd, 1)
filp_close()
fput()
fs/file_table.c:_fput()
__fput()
fs/dcache.c:dput()
dentry_iput()
iput(inode)
fs/inode.c:iput(inode)
- 如果传递给我们的 parameter 为 NULL,则我们绝对不执行任何操作并返回。
- 如果存在特定于 FS 的方法,则调用该方法 立即持有 spinlocks (因此它可以阻塞)。
sb->s_op->put_inode()
inode_lock
spinlock 被采用并被递减。如果这是 不是对这个 inode 的最后一个引用,那么我们只需检查 对它的引用太多,因此可以环绕 分配给它的 32 位,如果是这样,我们打印一个警告并返回。 请注意,我们在持有 spinlock 时调用 – 这很好,因为 can never block,因此它可以在 绝对任何上下文(甚至来自中断处理程序!i_count
i_count
printk()
inode_lock
printk()
- 如果这是最后一个活动的引用,则需要做一些工作。
对最后一个 inode 引用执行的工作相当复杂 因此,我们将它分成一个自己的列表:iput()
- 如果(例如,在我们打开文件时文件被取消链接) 然后从 HashTable 及其 type 列表中删除 inode;如果 此 inode 的页面缓存中保存了任何数据页,它们是 通过 删除。然后 调用特定于 Filesystem 的方法, 这通常会删除 inode 的磁盘上副本。如果文件系统没有注册方法(例如 ramfs) 然后我们调用 ,它会调用 如果 已注册,如果 inode 对应于块存储设备,则此设备的 引用计数被丢弃。
i_nlink == 0
truncate_all_inode_pages(&inode->i_data)
s_op->delete_inode()
s_op->delete_inode()
clear_inode(inode)
s_op->clear_inode()
bdput(inode->i_bdev)
- 如果,则我们检查同一 hash bucket 的 HASH 存储桶,如果没有,那么如果 inode 不是脏的,我们就删除 it 并从其类型 list 中将其添加到 list,从而递增 .如果同一个 hashbucket 中有 inode 然后我们将其从 type list 中删除并添加到 list。 如果这是一个匿名索引节点( NetApp .snapshot),则将其删除 并从类型列表中完全清除/销毁它。
i_nlink != 0
inode_unused
inodes_stat.nr_unused
inode_unused
3.2 文件系统注册/注销
Linux 内核提供了一种机制,用于编写新的文件系统 最小的努力。造成这种情况的历史原因是:
- 在人们仍然使用非 Linux 操作系统的世界中 为了保护他们对遗留软件的投资,Linux 必须提供 通过支持大量不同的 文件系统 – 其中大多数不值得独立存在 但仅用于与现有非 Linux 操作系统的兼容性。
- 文件系统写入器的接口必须非常简单,以便 人们可以尝试对现有的专有文件系统进行逆向工程 通过编写它们的只读版本。因此,Linux VFS 使 非常容易实现只读文件系统;95% 的工作是 通过添加 full write-support 来完成它们。举个具体的例子, 我花了大约 10 个小时为 Linux 编写了只读 BFS 文件系统,但它 花了几周时间完成它以获得完整的写入支持(并且 即使在今天,一些纯粹主义者也声称它并不完整,因为“它 不支持压缩”)。
- VFS 接口被导出,因此所有 Linux 文件系统都可以 作为模块实现。
让我们考虑一下在 Linux 下实现文件系统所需的步骤。 实现文件系统的代码可以是可动态加载的 模块或静态链接到内核中,以及在 Linux 非常透明。所需要做的就是填写一个结构并使用 VFS 将其注册到 VFS 函数如下例所示:struct file_system_type
register_filesystem()
fs/bfs/inode.c
#include <linux/module.h> #include <linux/init.h> static struct super_block *bfs_read_super(struct super_block *, void *, int); static DECLARE_FSTYPE_DEV(bfs_fs_type, "bfs", bfs_read_super); static int __init init_bfs_fs(void) { return register_filesystem(&bfs_fs_type); } static void __exit exit_bfs_fs(void) { unregister_filesystem(&bfs_fs_type); } module_init(init_bfs_fs) module_exit(exit_bfs_fs)
宏确保在将 BFS 编译为 模块、函数 和 分别变成 和;如果 BFS 静态链接到内核,则 代码会消失,因为它是不必要的。module_init()/module_exit()
init_bfs_fs()
exit_bfs_fs()
init_module()
cleanup_module()
exit_bfs_fs()
在 中声明 :struct file_system_type
include/linux/fs.h
struct file_system_type { const char *name; int fs_flags; struct super_block *(*read_super) (struct super_block *, void *, int); struct module *owner; struct vfsmount *kern_mnt; /* For kernel mount, if it's FS_SINGLE fs */ struct file_system_type * next; };
其字段解释如下:
- name:人类可读的名称,显示在 file 中 并用作按名称查找文件系统的键;这个相同的名称是 用于 mount(2) 中的文件系统类型,并且它应该是唯一的:那里 (显然)只能是一个具有给定名称的文件系统。对于模块, name 指向模块的地址空间而不是 copied:这意味着 cat 如果模块已卸载,但 filesystem 是 仍然注册。
/proc/filesystems
- fs_flags:一个或多个 (ORed) 标志:对于只能挂载在块设备上的文件系统,对于只能有一个超级块的文件系统,对于 无法通过 mount(2) 系统调用从用户空间挂载的文件系统:但是它们可以使用接口在内部挂载,例如 pipefs。
FS_REQUIRES_DEV
FS_SINGLE
FS_NOMOUNT
kern_mount()
- read_super:指向读取 super 的函数的指针 block 的 intent 命令。此功能是必需的:如果不是 ,挂载操作(无论是来自用户空间还是内核)都将 总是失败,除非它会在 中 Oops ,尝试使用 () 取消引用 NULL 指针。
FS_SINGLE
get_sb_single()
fs_type->kern_mnt->mnt_sb
fs_type->kern_mnt = NULL
- owner:指向实现此文件系统的模块的指针。 如果文件系统静态链接到内核中,则为 零。您无需手动设置,因为宏会自动执行正确的操作。
THIS_MODULE
- kern_mnt:仅适用于文件系统。这是由 (TODO: should reject to mount filesystems if 未设置)。
FS_SINGLE
kern_mount()
kern_mount()
FS_SINGLE
- 下一篇: 链接到以 (见) 为标题的单向链表 。该列表受读写旋转锁保护,函数通过链接和取消链接列表中的条目来修改它。
file_systems
fs/super.c
file_systems_lock
register/unregister_filesystem()
该函数的工作是填写超级块的字段, 分配根 inode 并初始化与 此挂载的文件系统实例。所以,通常情况下 做:read_super()
read_super()
- 从通过参数指定的设备中读取超级块, 使用 buffer cache 函数。如果它预计会读取一些 更多后续元数据块,然后 用于安排异步读取额外的块。
sb->s_dev
bread()
breada()
- 验证 superblock 是否包含有效的幻数和总体 “看起来”很理智。
- Initialise 指向 structure。此结构包含特定于文件系统的函数 实现 “Read inode”、“Delete inode” 等操作。
sb->s_op
struct super_block_operations
- 使用 分配根 inode 和根 dentry。
d_alloc_root()
- 如果文件系统未以只读方式挂载,则设置为 1 并将包含超级块的缓冲区标记为 dirty (TODO: why do we 执行此操作?我在 BFS 中这样做了,因为 MINIX 做到了……
sb->s_dirt
3.3 文件描述符管理
在 Linux 下,用户文件之间有几个级别的间接性 Descriptor 和内核 inode 结构。当进程进行 open(2) 系统调用时,内核会返回一个小的非负整数,该整数可以是 用于对此文件的后续 I/O 操作。此整数是一个索引 转换为指向 .每个文件结构都指向 通过 .每个 dentry 都通过 .struct file
file->f_dentry
dentry->d_inode
每个任务都包含一个字段,该字段是指向 中定义的指针:tsk->files
struct files_struct
include/linux/sched.h
/* * Open file table structure */ struct files_struct { atomic_t count; rwlock_t file_lock; int max_fds; int max_fdset; int next_fd; struct file ** fd; /* current fd array */ fd_set *close_on_exec; fd_set *open_fds; fd_set close_on_exec_init; fd_set open_fds_init; struct file * fd_array[NR_OPEN_DEFAULT]; };
这是一个引用计数,递增 (通常 调用 ) 并递减 和 。区别 之间 和 是 做更多的工作通常需要 对于常规文件,比如释放 flock 锁、释放 dentry 等,虽然只是操作文件表结构,即递减 count 的 API 中,从 中删除文件并将其添加到 、 在 spinlock 的保护下。file->count
get_file()
fget()
fput()
put_filp()
fput()
put_filp()
fput()
put_filp()
anon_list
free_list
files_lock
如果子线程 是使用 clone 标志中带有 set 的系统调用创建的 论点。这可以在 (called by ) 中看到,它只会增加 if 的设置 而不是 Time-Honoured 中通常的 copying file descriptor table 传统 UNIX 分支 (2)。tsk->files
clone()
CLONE_FILES
kernel/fork.c:copy_files()
do_fork()
file->count
CLONE_FILES
打开文件时,分配给它的文件结构将安装到插槽中,并在位图中设置一个位。所有这些都是在读写自旋锁的写保护下完成的。当描述符为 closed 的 a bit 被清除,并设置为 equal to 作为查找 First unused Descriptors 下次此进程想要打开文件时。current->files->fd[fd]
fd
current->files->open_fds
current->files->file_lock
fd
current->files->open_fds
current->files->next_fd
fd
3.4 文件结构管理
文件结构在 中声明:include/linux/fs.h
struct fown_struct { int pid; /* pid or -pgrp where SIGIO should be sent */ uid_t uid, euid; /* uid/euid of process setting the owner */ int signum; /* posix.1b rt signal to be delivered on IO */ }; struct file { struct list_head f_list; struct dentry *f_dentry; struct vfsmount *f_vfsmnt; struct file_operations *f_op; atomic_t f_count; unsigned int f_flags; mode_t f_mode; loff_t f_pos; unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin; struct fown_struct f_owner; unsigned int f_uid, f_gid; int f_error; unsigned long f_version; /* needed for tty driver, and maybe others */ void *private_data; };
让我们看看各个领域 :struct file
- f_list:此字段链接一个(且仅一个)上的文件结构 列表:a) 此文件系统上所有打开的文件的列表, 如果对应的 inode 不是匿名的,则(称为 by ) 将文件链接到此列表; b) 包含未使用的文件结构; c) 时,当它由 创建新的文件结构时,将放置在此列表中。所有这些列表都是 受 Spinlock 保护。
sb->s_files
dentry_open()
filp_open()
fs/file_table.c:free_list
fs/file_table.c:anon_list
get_empty_filp()
files_lock
- f_dentry:此文件对应的 dentry。绅士 在 Nameidata 查找时由 (或 而是它调用的),但实际字段由 设置为包含由此找到的 dentry。
open_namei()
path_walk()
file->f_dentry
dentry_open()
- f_vfsmnt:指向文件系统结构的指针 包含该文件。这是由 设置的,但作为 part 找到 of nameidata lookup by(或者更确切地说是它 调用)。
vfsmount
dentry_open()
open_namei()
path_init()
- f_op:指向的指针包含各种 方法。这是从 which 复制的,在 nameidata 查找期间通过特定于文件系统的方法将其放置在那里。我们将在本节后面详细介绍方法。
file_operations
inode->i_fop
s_op->read_inode()
file_operations
- f_count:由 操纵的引用计数。
get_file/put_filp/fput
- f_flags:来自 open(2) 系统调用的标志被复制到那里 (稍作修改 ) by 和 after 清除 、 、 、 – 没有意义 永久存储这些标志,因为它们不能被 fcntl(2) 调用修改(或查询)。
O_XXX
filp_open()
dentry_open()
O_CREAT
O_EXCL
O_NOCTTY
O_TRUNC
F_SETFL
F_GETFL
- f_mode:用户空间标志和模式的组合,将 由。转换的重点是存储 read 和 在单独的位中写入访问权限,以便可以执行简单的检查,例如 和 。
dentry_open()
(f_mode & FMODE_WRITE)
(f_mode & FMODE_READ)
- f_pos:下次读取或写入的当前文件位置 文件。在 i386 下,它是 类型 ,即 64 位值。
long long
- f_reada、f_ramax、f_raend、f_ralen f_rawin:支持 Readahead – 太复杂了,凡人无法讨论 😉
- f_owner:用于接收异步 I/O 的文件 I/O 的所有者 通过机制进行通知(请参阅 )。
SIGIO
fs/fcntl.c:kill_fasync()
- f_uid、f_gid – 设置为进程的用户 ID 和组 ID,该进程 打开了该文件,当文件结构在 中创建时。如果文件是 ipv4 netfilter 使用的套接字。
get_empty_filp()
- f_error:由 NFS 客户端用于返回写入错误。是的 set in 和 checked in 。
fs/nfs/file.c
mm/filemap.c:generic_file_write()
- f_version – 用于使缓存失效的版本控制机制, 每当更改时递增(使用 global )。
event
f_pos
- private_data:每个文件的私有数据,可供 文件系统(例如 Coda 在此处存储凭据)或设备驱动程序。 设备驱动程序(在存在 devfs 的情况下)可以使用此字段来 区分多个实例而不是经典实例 以 .
file->f_dentry->d_inode->i_rdev
现在让我们看看结构,它包含了 可以对文件调用。让我们回想一下,它是从方法设置的位置复制的。它声明为 :file_operations
inode->i_fop
s_op->read_inode()
include/linux/fs.h
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); };
- owner:指向 中子系统的模块的指针 问题。只有驱动程序需要将其设置为 ,文件系统可以 很高兴地忽略它,因为他们的模块数量被控制在 mount/umount 时间,而驱动程序需要在打开/释放时对其进行控制 时间。
THIS_MODULE
- llseek:实现 lseek(2) 系统调用。通常是 省略并使用,这会执行 正确的事情 (TODO: 强制所有当前将其设置为 NULL 的人使用 default_llseek – 这样我们就可以保存一个
fs/read_write.c:default_llseek()
if()
llseek()
) - read:实现 system 调用。文件系统可用于常规文件和 (仅返回) 对于目录。
read(2)
mm/filemap.c:generic_file_read()
fs/read_write.c:generic_read_dir()
-EISDIR
- write:实现 write(2) 系统调用。文件系统可以用于常规文件,而忽略它 目录。
mm/filemap.c:generic_file_write()
- readdir:由文件系统使用。对于常规文件,忽略 并实现目录的 readdir(2) 和 getdents(2) 系统调用。
- poll:实现 poll(2) 和 select(2) 系统调用。
- ioctl:实现特定于驱动程序或文件系统的 ioctls 的 API 中。请注意,像 , , 这样的通用文件 ioctl 是由更高级别实现的,因此它们永远不会读取 method。
FIBMAP
FIGETBSZ
FIONREAD
f_op->ioctl()
- mmap:实现 mmap(2) 系统调用。文件系统可以将 generic_file_mmap 用于常规文件,而忽略目录上的 THIS。
- open:在 open(2) 时由 调用 。文件系统 很少使用 this,例如 coda 尝试在 open 时在本地缓存文件 时间。
dentry_open()
- flush:在此文件的每个 close(2) 处调用,不一定 最后一个(见下面的方法)。唯一一个 使用 this is NFS 客户端刷新所有脏页。请注意,这可以 返回一个错误,该错误将被传递回进行 close(2) 系统调用的用户空间。
release()
- release:在此文件的最后一个 close(2) 时调用,即当达到 0 时。尽管定义为返回 int,但返回 值被 VFS 忽略(请参阅 )。
file->f_count
fs/file_table.c:__fput()
- fsync:直接映射到 fsync(2)/fdatasync(2) 系统调用, 最后一个参数指定它是 fsync 还是 fdatasync。 VFS 几乎没有做任何工作,除了映射文件 descriptor 添加到文件结构 () 和 down/up 信号量。Ext2 文件系统当前会忽略最后一个 参数,并且对 fsync(2) 和 fdatasync(2) 执行完全相同的操作。
file = fget(fd)
inode->i_sem
- fasync:当 changes 时调用该方法。
file->f_flags & FASYNC
- lock:POSIX fcntl(2) 文件区域锁定机制中特定于文件系统的部分。这里唯一的错误是,因为它是 在与 FS 无关的部分 () 之前调用,如果它 成功但标准 POSIX 锁定代码失败,则永远不会成功 在FS依赖级别解锁..
posix_lock_file()
- readv: 实现 readv(2) 系统调用。
- writev:实现 writev(2) 系统调用。
3.5 超级块和挂载点管理
在 Linux 下,有关挂载的文件系统的信息保存在两个单独的 结构 – 和 .这样做的原因是 Linux 允许在多个挂载下挂载相同的文件系统(块设备) 点,这意味着 same 可以对应于多个结构体。super_block
vfsmount
super_block
vfsmount
让我们先看一下 :struct super_block
include/linux/fs.h
struct super_block { struct list_head s_list; /* Keep this first */ kdev_t s_dev; unsigned long s_blocksize; unsigned char s_blocksize_bits; unsigned char s_lock; unsigned char s_dirt; struct file_system_type *s_type; struct super_operations *s_op; struct dquot_operations *dq_op; unsigned long s_flags; unsigned long s_magic; struct dentry *s_root; wait_queue_head_t s_wait; struct list_head s_dirty; /* dirty inodes */ struct list_head s_files; struct block_device *s_bdev; struct list_head s_mounts; /* vfsmount(s) of this one */ struct quota_mount_options s_dquot; /* Diskquota specific options */ union { struct minix_sb_info minix_sb; struct ext2_sb_info ext2_sb; ..... all filesystems that need sb-private info ... void *generic_sbp; } u; /* * The next field is for VFS *only*. No filesystems have any business * even looking at it. You had been warned. */ struct semaphore s_vfs_rename_sem; /* Kludge */ /* The next field is used by knfsd when converting a (inode number based) * file handle into a dentry. As it builds a path in the dcache tree from * the bottom up, there may for a time be a subpath of dentrys which is not * connected to the main tree. This semaphore ensure that there is only ever * one such free path per filesystem. Note that unconnected files (or other * non-directories) are allowed, but not unconnected diretories. */ struct semaphore s_nfsd_free_path_sem; };
结构中的各个字段包括:super_block
- s_list:所有活动超级块的双向链表;注意 我不说 “of all mounted filesystems”,因为在 Linux 下可以 具有多个挂载的文件系统实例,对应于 单个超级块。
- s_dev:用于需要挂载块的文件系统 on,即对于文件系统,这是 块设备。对于其他 (称为匿名文件系统),这是一个 integer,其中 是数组中第一个未设置的位,介于 1 和 255 之间(包括 1 和 255)。看。有人建议 很多时候,匿名文件系统不应该使用 field。
FS_REQUIRES_DEV
i_dev
MKDEV(UNNAMED_MAJOR, i)
i
unnamed_dev_in_use
fs/super.c:get_unnamed_dev()/put_unnamed_dev()
s_dev
- s_blocksize、s_blocksize_bits:blocksize 和 log2(blocksize)。
- s_lock:表示 superblock 当前是否被 锁定。
lock_super()/unlock_super()
- s_dirt:更改超级块时设置,并在 它被写回磁盘。
- s_type:指向 相应的文件系统。Filesystem 的方法不需要 要将其设置为 VFS 会为您设置 fs-specific 成功,如果失败,则重置为 NULL。
struct file_system_type
read_super()
fs/super.c:read_super()
read_super()
- s_op:指向结构体的指针,其中包含 读取/写入 inode 等的特定于 FS 的方法。这是 filesystem 的方法进行初始化。
super_operations
read_super()
s_op
- dq_op:磁盘配额操作。
- s_flags:superblock 标志。
- s_magic:文件系统的幻数。由 minix 文件系统使用 来区分自身的多种口味。
- s_root:文件系统根的 dentry。其工作是从磁盘中读取根 inode 并将其传递给以分配 dentry 并实例化它。一些 文件系统拼写 “root” 而不是 “/”,因此使用更通用的函数将 dentry 绑定到一个名称,例如 pipefs 挂载 本身在 “pipe:” 上作为自己的根而不是 “/”。
read_super()
d_alloc_root()
d_alloc()
- s_wait:等待超级块的进程的等待队列 解 锁。
- s_dirty:所有脏 inode 的列表。回想一下,如果 inode is dirty () 那么它就位于特定于 superblock 的 通过 链接的脏列表。
inode->i_state & I_DIRTY
inode->i_list
- s_files:此超级块上所有打开的文件的列表。有用 有关确定是否可以以只读方式重新挂载 FileSystem,请参见 Which goes through list 如果有打开的文件进行写入,则拒绝重新挂载 () 或具有待处理 取消链接 ()。
fs/file_table.c:fs_may_remount_ro()
sb->s_files
file->f_mode & FMODE_WRITE
inode->i_nlink == 0
- s_bdev:对于 ,这指向block_device 描述挂载文件系统的设备的结构。
FS_REQUIRES_DEV
- s_mounts:所有结构的列表,每个结构一个 mounted 实例。
vfsmount
- s_dquot:更多 diskquota 内容。
超级块操作在结构中进行了描述 声明于 :super_operations
include/linux/fs.h
struct super_operations { void (*read_inode) (struct inode *); void (*write_inode) (struct inode *, int); void (*put_inode) (struct inode *); void (*delete_inode) (struct inode *); void (*put_super) (struct super_block *); void (*write_super) (struct super_block *); int (*statfs) (struct super_block *, struct statfs *); int (*remount_fs) (struct super_block *, int *, char *); void (*clear_inode) (struct inode *); void (*umount_begin) (struct super_block *); };
- read_inode:从文件系统中读取 inode。它只是 从 (和 因此) 调用 。如果文件系统想要使用,则必须是 implemented – 否则将 panic。 读取 inode 时,它被锁定 () 。什么时候 函数返回,所有 Waiters 都被唤醒。工作 的方法是找到 包含需要读取的 inode,并使用 buffer cache 函数 读入并初始化 inode 结构的各个字段,用于 示例 和 以便 VFS 级别知道什么 可以对 inode 或相应的文件执行操作。 未实现的文件系统是 ramfs 和 pipefs 的例如,ramfs 有自己的 inode 生成函数,所有 inode 操作都会根据需要调用它。
fs/inode.c:get_new_inode()
iget4()
iget()
iget()
read_inode()
get_new_inode()
inode->i_state = I_LOCK
inode->i_wait
read_inode()
bread()
inode->i_op
inode->i_fop
read_inode()
ramfs_get_inode()
- write_inode:将 inode 写回磁盘。与此类似,它需要在 disk 并通过调用 .此方法在脏 inode 上调用 (标记为 dirty 的那些 )当 inode 需要 单独同步或作为同步的一部分同步 整个文件系统。
read_inode()
mark_buffer_dirty(bh)
mark_inode_dirty()
- put_inode:每当引用计数减少时调用。
- delete_inode:每当两者兼而有之且达到 0 时调用。Filesystem 会删除 inode 并调用 VFS inode 以“终止它 极端偏见”。
inode->i_count
inode->i_nlink
clear_inode()
- put_super:在 umount(2) 系统的最后阶段调用 调用以通知文件系统 应该释放有关此实例的文件系统。通常,此 包含超级块的块和任何 分配给 free 块、inode 等的位图
brelse()
kfree()
- write_super:当 superblock 需要时调用 写回磁盘。它应该找到包含 superblock(通常保留在 area)和 .它还应该清除 flag。
sb-private
mark_buffer_dirty(bh)
sb->s_dirt
- statfs:实现 fstatfs(2)/statfs(2) 系统调用。注意 传递给 AS 参数的指针是内核 pointer,而不是用户指针,因此我们不需要对 用户空间。如果未实现,则将失败并显示 。
struct statfs
statfs(2)
ENOSYS
- remount_fs:每当重新挂载文件系统时调用。
- clear_inode:从 VFS 级别 调用 。文件系统 将私有数据附加到 inode 结构(通过字段)必须 在这里免费。
clear_inode()
generic_ip
- umount_begin:在 forced umount 期间调用 filesystem,以便它可以尽最大努力确保 没有什么能让文件系统保持忙碌。当前仅由 NFS 使用。这 与通用 VFS 级别强制 umount 的想法无关 支持。
那么,让我们看看当我们在磁盘上挂载一个 () 时会发生什么 文件系统。mount(2) 系统调用的实现是 其中 只是一个复制选项的包装器 filesystem type 和 device name 的函数执行 真实工作:FS_REQUIRES_DEV
fs/super.c:sys_mount()
do_mount()
- 如果需要,将加载 Filesystem 驱动程序及其模块的引用计数 递增。请注意,在挂载操作期间,文件系统 Module 的引用计数递增两次 – 一次通过调用 IF WAS SUCCESSFUL,一次通过调用 IF 成功。第一个增量是为了防止 module 卸载,而第二个 increment 表示该模块正在被此挂载 实例。显然,在返回之前递减 count ,因此 总体而言,每次挂载后,计数仅增加 1。
do_mount()
get_fs_type()
get_sb_dev()
get_filesystem()
read_super()
read_super()
do_mount()
- 由于在我们的示例中为 true,因此 superblock 通过调用来初始化,该调用获取 对块设备的引用,并与文件系统的方法交互以填充超级块。如果一切顺利,结构将被初始化,我们有一个额外的引用 添加到文件系统的模块中,并引用底层块 装置。
fs_type->fs_flags & FS_REQUIRES_DEV
get_sb_bdev()
read_super()
super_block
- 分配新结构并链接到 list 和全局列表。该字段允许查找挂载在与此相同的 superblock 上的所有实例 一。该字段允许查找所有实例 superblocks 的 superblocks 中。该领域 指向此 superblock,并具有对 dentry 的新引用。
vfsmount
sb->s_mounts
vfsmntlist
vfsmount
mnt_instances
mnt_list
mnt_sb
mnt_root
sb->s_root
3.6 虚拟文件系统示例:pipefs
作为不需要块设备的 Linux 文件系统的简单示例 对于挂载,让我们考虑来自 的 pipefs 。文件系统的 preamble 相当简单,几乎不需要解释:fs/pipe.c
static DECLARE_FSTYPE(pipe_fs_type, "pipefs", pipefs_read_super, FS_NOMOUNT|FS_SINGLE); static int __init init_pipe_fs(void) { int err = register_filesystem(&pipe_fs_type); if (!err) { pipe_mnt = kern_mount(&pipe_fs_type); err = PTR_ERR(pipe_mnt); if (!IS_ERR(pipe_mnt)) err = 0; } return err; } static void __exit exit_pipe_fs(void) { unregister_filesystem(&pipe_fs_type); kern_umount(pipe_mnt); } module_init(init_pipe_fs) module_exit(exit_pipe_fs)
文件系统的类型为 ,这意味着它不能是 从用户空间挂载,并且只能在系统范围内有一个 superblock。该文件还意味着它必须通过 after 它是通过 成功注册的,这恰好是 中会发生什么。这个函数中唯一的错误是,如果失败(例如,因为失败),那么 filesystem 保持已注册状态,但模块初始化失败。这将 导致 cat /proc/filesystems 出现 Oops。(刚刚向 Linus 发送了一个补丁 提到虽然这在今天不是一个真正的错误,因为 pipefs 不可能 编译为一个模块,它应该以将来 它可能会变得模块化)。FS_NOMOUNT|FS_SINGLE
FS_SINGLE
kern_mount()
register_filesystem()
init_pipe_fs()
kern_mount()
kmalloc()
add_vfsmnt()
的结果是 is 被链接到 该列表,以便可以读取和找到 “pipefs” 条目中带有 “nodev” 标志,表示未设置。 该文件确实应该得到增强以支持所有新标志(为此我做了一个补丁),但不能这样做,因为它会 破坏使用它的所有用户应用程序。尽管 Linux 内核接口 在用户空间方面,每分钟都在变化(只为了更好) 兼容性,Linux 是一个非常保守的操作系统,它允许 许多应用程序需要长时间使用而无需重新编译。register_filesystem()
pipe_fs_type
file_systems
/proc/filesystems
FS_REQUIRES_DEV
/proc/filesystems
FS_
的结果是:kern_mount()
- 通过在位图中设置一个位来分配新的未命名(匿名)设备号;如果没有更多位,则失败。
unnamed_dev_in_use
kern_mount()
EMFILE
- 通过 分配新的超级块结构。 该函数遍历带头的超级块列表 by 并查找空条目,即 .如果没有 找到这样的空超级块,然后使用 AT Priority 分配一个新的超级块。系统范围内的最大 超级块被签入,因此如果它开始失败, 可以调整可调 。
get_empty_super()
get_empty_super()
super_block
s->s_dev == 0
kmalloc()
GFP_USER
get_empty_super()
/proc/sys/fs/super-max
- 调用特定于文件系统的方法(即 ),该方法分配 root inode 和 root dentry ,并设置为 。
pipe_fs_type->read_super()
pipefs_read_super()
sb->s_root
sb->s_op
&pipefs_ops
- 然后调用 分配新结构并将其链接到 和 中。
kern_mount()
add_vfsmnt(NULL, sb->s_root, "none")
vfsmount
vfsmntlist
sb->s_mounts
- 设置为 this new structure,并且 它被返回。的返回值是结构的原因是甚至可以挂载文件系统 多次,因此它们将指向同一事物 从多次调用返回 是愚蠢的。
pipe_fs_type->kern_mnt
vfsmount
kern_mount()
vfsmount
FS_SINGLE
mnt->mnt_sb
kern_mount()
现在文件系统已经注册并安装在内核中,我们可以使用它了。 pipefs 文件系统的入口点是 pipe(2) 系统调用 在 Arch 依赖的函数中实现,但真正的工作已经完成 通过可移植功能。那么让我们看看。 调用 allocate a new pipefs inode 时,会发生与 pipefs 的交互。对于此 inode,设置为 pipefs 的超级块,则文件操作设置为 和 读取器和写入器的数量(保存在 ) 设置为 1。为什么有一个单独的 inode 字段 保持 union 的原因是 pipes 和 FIFO 共享相同的 code 和 FIFO 可以存在于使用 other access 的其他文件系统上 paths 在同一个联合中,这是非常糟糕的 C,只能由 pure 运气。所以,是的,2.2.x 内核只能靠运气工作,并且会停止工作 只要稍微重新排列 inode 中的字段。sys_pipe()
fs/pipe.c:do_pipe()
do_pipe()
do_pipe()
get_pipe_inode()
inode->i_sb
pipe_mnt->mnt_sb
i_fop
rdwr_pipe_fops
inode->i_pipe
i_pipe
fs-private
每个 pipe(2) 系统调用都会递增挂载实例上的引用计数。pipe_mnt
在 Linux 下,管道不是对称的(双向或 STREAM 管道),即 文件的两端具有不同的操作 – 分别是 和 。读时写入端 返回,写入端读取也是如此。file->f_op
read_pipe_fops
write_pipe_fops
EBADF
3.7 磁盘文件系统示例:BFS
作为 ondisk Linux 文件系统的一个简单示例,让我们考虑 BFS。这 BFS 模块的序言位于 :fs/bfs/inode.c
static DECLARE_FSTYPE_DEV(bfs_fs_type, "bfs", bfs_read_super); static int __init init_bfs_fs(void) { return register_filesystem(&bfs_fs_type); } static void __exit exit_bfs_fs(void) { unregister_filesystem(&bfs_fs_type); } module_init(init_bfs_fs) module_exit(exit_bfs_fs)
使用了一个特殊的 fstype 声明宏,它 将 to 设置为 表示 BFS 需要 要挂载的 real block 设备。DECLARE_FSTYPE_DEV()
fs_type->flags
FS_REQUIRES_DEV
该模块的初始化函数使用 VFS 注册文件系统,并且 清理功能(仅在 BFS 配置为模块时存在) 取消注册它。
注册文件系统后,我们可以继续挂载它,这将 invoke out 方法(在 It 中实现)执行以下操作:fs_type->read_super()
fs/bfs/inode.c:bfs_read_super().
set_blocksize(s->s_dev, BFS_BSIZE)
:因为我们即将互动 对于通过缓冲区缓存的块设备层,我们必须初始化一些 事情,即设置区块大小,并通过字段和 .s->s_blocksize
s->s_blocksize_bits
bh = bread(dev, 0, BFS_BSIZE)
:我们读取设备的块 0 通过 传递。此块是文件系统的超级块。s->s_dev
- 超级块根据数字进行验证,如果有效,则存储 在 sb-private 字段中(实际上是 )。
BFS_MAGIC
s->su_sbh
s->u.bfs_sb.si_sbh
- 然后我们使用 and clear all 分配 inode bitmap bits 设置为 0,但前两个除外,我们将其设置为 1 以表示我们 不应分配 inode 0 和 1。inode 2 是 root 的,而 无论如何,几行后相应的位将设置为 1 – 该 文件系统在挂载时应该有一个有效的根 inode!
kmalloc(GFP_KERNEL)
- 然后我们初始化 ,这意味着我们可以从这一点开始 调用 inode 缓存,从而 被调用。这将找到包含指定 (by 和 ) inode 的块并将其读入。如果我们未能 获取根 inode,然后我们释放 inode 位图并释放超级块 buffer 返回到 buffer cache 并返回 NULL。如果读取根 inode 为 OK, 然后我们分配一个 name 为 (as becometh root) 的 dentry,并且 使用此 inode 实例化它。
s->s_op
iget()
s_op->read_inode()
inode->i_ino
inode->i_dev
/
- 现在我们遍历文件系统上的所有 inode 并将它们全部读取 order 在我们的内部 inode 位图中设置相应的位,并且 此外,还要计算一些其他内部参数,例如 last inode 和 last file 的 start/end 块。我们读取的每个 inode 通过以下方式返回到 inode 缓存 – 我们不持有引用 到它的时间比需要的时间长。
iput()
- 如果文件系统不是以只读方式挂载的,我们将超级块标记为 buffer dirty 并设置标志 (TODO: Why do I do this (WHY DO I DO THIS (TODO: WHY DO I DO THIS ? 最初,我这样做是因为这样做了,但两者都不是 minix BFS 似乎也没有修改 中的 superblock。
s->s_dirt
minix_read_super()
read_super()
- 一切都很好,所以我们将这个初始化的超级块返回给调用者 在 VFS 级别,即 .
fs/super.c:read_super()
函数成功返回后,VFS 会获取 通过调用 in 和引用块设备来引用 FileSystem 模块。read_super()
get_filesystem(fs_type)
fs/super.c:get_sb_bdev()
现在,让我们看看在文件系统上执行 I/O 时会发生什么。我们已经 研究了 inode 的读取方式、调用 iSCSI 的时间以及如何释放 inode 在 Reading inodes 上设置,除其他外,还有 ;打开文件将传播到 .iget()
iput().
inode->i_op
inode->i_fop
inode->i_fop
file->f_op
让我们检查一下 link(2) 系统调用的代码路径。实现 的系统调用位于 :fs/namei.c:sys_link()
- 用户空间名称通过执行错误检查的 function 复制到内核空间。
getname()
- 这些名称是使用 dcache 交互转换的 nameidata 的。结果存储在 and 结构体中。
path_init()/path_walk()
old_nd
nd
- 如果返回 “cross-device link” – 不能在文件系统之间链接,在 Linux 中,这转化为 – 不能在文件系统的挂载实例之间链接(或者,在 特别是文件系统之间)。
old_nd.mnt != nd.mnt
EXDEV
- 将创建一个与 对应的新 dentry。
nd
lookup_create()
- 调用一个泛型函数,该函数检查我们是否可以 在目录中创建一个新条目并调用该方法,该方法将我们带回特定于 FileSystem 的函数。
vfs_link()
dir->i_op->link()
fs/bfs/dir.c:bfs_link()
- 在 中,我们检查是否正在尝试链接目录,并且 如果是这样,则错误地拒绝。这与 standard (ext2) 的行为相同。
bfs_link()
EPERM
- 我们尝试将新目录条目添加到指定目录 通过调用遍历所有 条目查找未使用的插槽 (),并在找到时写入 将 name/inode 对输出到相应的块中并标记它 dirty (非 superblock 优先级)。
bfs_add_entry()
de->ino == 0
- 如果我们成功添加了目录条目,那么就没有办法了 为了使操作失败,我们递增、更新并将此 inode 标记为脏,并实例化 new dentry 替换为 inode。
inode->i_nlink
inode->i_ctime
其他相关的 inode 操作(如 etc)的工作方式类似 方式,因此通过详细研究它们并没有得到太多。unlink()/rename()
3.8 执行域和二进制格式
Linux 支持从磁盘加载用户应用程序二进制文件。更多 有趣的是,二进制文件可以以不同的格式存储,并且 操作系统通过系统调用对程序的响应可能偏离 norm(norm 是 Linux 的行为),以便模拟 格式,也可以在其他 UNIX 风格(COFF 等)中找到,也可以模拟 系统调用其他风格(Solaris、UnixWare 等)的行为。这是 执行域和二进制格式的用途。
每个 Linux 任务在其 () 中存储了一个个性。 当前存在的(在官方内核中或作为插件补丁) 个性包括对 FreeBSD、Solaris、UnixWare、OpenServer 和 许多其他流行的操作系统。 的值分为两部分:task_struct
p->personality
current->personality
- 高 3 字节 – 错误仿真:、 、 等。
STICKY_TIMEOUTS
WHOLE_SECONDS
- 低字节 – 个性 proper,一个唯一的数字。
通过改变个性,我们可以改变 操作系统处理某些系统调用的方式,例如 添加 to 会调用 select(2) 系统调用 保留 last 参数 (Timeout) 的值,而不是存储 未睡的时间。一些有问题的程序依赖于有缺陷的操作系统(非 Linux) 因此 Linux 提供了一种在源代码 不可用,因此无法修复 bug。STICKY_TIMEOUT
current->personality
执行域是由 单个模块。通常,单个执行域实现单个 个性,但有时可以实现 “接近” 个性 在单个模块中,没有太多条件。
执行域在 中实现并完全 与 2.2.x 相比,针对 2.4 内核重写。执行域列表 目前由内核支持,以及 Personality 的范围 他们支持,可通过读取文件获得。执行 除 1 个域外,域可以动态实现 可加载的模块。kernel/exec_domain.c
/proc/execdomains
PER_LINUX
用户界面是通过 personality(2) 系统调用来实现的,该调用将当前的 process“ 的 Personality 或返回 参数设置为 Impossible Personality 0xffffffff。显然, 这个系统调用的行为本身并不取决于个性..current->personality
用于执行域注册的内核接口由两个 功能:
int register_exec_domain(struct exec_domain *)
:注册 执行域,方法是将其链接到读写 spinlock 的写保护下的单链表。 成功时返回 0,失败时返回非零。exec_domains
exec_domains_lock
int unregister_exec_domain(struct exec_domain *)
:取消注册 执行域,再次在写入模式下使用 spinlock。成功时返回 0。exec_domains
exec_domains_lock
之所以是读写的,是因为只有注册 和注销请求会修改列表,同时执行 cat /proc/filesystems 调用,该 只需要对列表的读取访问权限。注册新的执行域 定义 “LCall7 处理程序” 和信号编号转换映射。实际上 ABI 补丁扩展了 exec domain 的这一概念,以包含额外的信息 (如 socket options、socket types、address family 和 errno maps)。exec_domains_lock
fs/exec_domain.c:get_exec_domain_list()
二进制格式以类似的方式实现,即单链接的 list formats 在 read-write lock 中定义并受其保护。与 一样,该 被读取 大多数情况下,除了二进制格式的注册/注销。 注册新的二进制格式可以增强 execve(2) 系统调用的新函数以及 .该方法仅由旧的 uselib(2) 系统调用使用,而 该方法由 实现 execve(2) 系统调用的 FROM 调用。fs/exec.c
binfmt_lock
exec_domains_lock
binfmt_lock
load_binary()/load_shlib()
core_dump()
load_shlib()
load_binary()
search_binary_handler()
do_execve()
进程的特性在二进制格式加载时由 使用一些启发式方法的相应格式的方法。 例如,要确定 UnixWare7 二进制文件,首先要标记二进制文件 使用 elfmark(1) 工具,它将 ELF 头文件的 值 0x314B4455,该值在 ELF 加载时检测到,并设置为 PER_UW7。如果此启发式方法失败,则 more 泛型的,例如将 ELF 解释器路径视为 或 to 表示使用 SVR4 二进制文件,并且 Personality 设置为 PER_SVR4。一 可以编写一个使用 Linux 的 ptrace(2) 功能的小工具程序 单步执行代码并强制正在运行的程序成为任何个性。load_binary()
e_flags
current->personality
/usr/lib/ld.so.1
/usr/lib/libc.so.1
一旦知道了个性(因此),系统 调用的处理方式如下。让我们假设一个进程构成一个系统 通过 lcall7 门指令调用 call。这会将控制权转移到 of,因为它是在 中准备的。在适当的堆栈布局之后 conversion 获取指向 from 的指针,然后获取 lcall7 处理程序在 (即 在 ASM 代码中硬编码为 4,因此您无法在 C 声明 ) 并跳转到它。所以,在 C 语言中,它会 如下所示:current->exec_domain
ENTRY(lcall7)
arch/i386/kernel/entry.S
arch/i386/kernel/traps.c:trap_init()
entry.S:lcall7
exec_domain
current
exec_domain
handler
struct exec_domain
static void UW7_lcall7(int segment, struct pt_regs * regs) { abi_dispatch(regs, &uw7_funcs[regs->eax & 0xff], 1); }
其中 是函数指针表的包装器 实现此个性的系统调用 。abi_dispatch()
uw7_funcs