title: 系统调用与VFS tags: [Linux内核, 系统调用, VFS, 嵌入式, 驱动开发] created: 2026-09-16
💡 关联知识: [[01-Cortex-A7架构详解|Cortex-A7架构详解]] | [[03-pinctrl与gpio子系统|pinctrl与gpio子系统]] | [[arm-assembly-basics|ARM汇编基础]]
系统调用(System Call)是操作系统内核提供给用户空间程序的唯一合法入口。在 Linux 的保护模式下,用户态程序无法直接访问硬件或内核数据结构,必须通过系统调用请求内核代为执行特权操作。
核心特征:
📖 延伸阅读: [[wiki/source-linux-kernel|Linux内核源码]] — 系统调用定义位于
include/linux/syscalls.h
| 特性 | 系统调用 | 库函数(glibc) |
|---|---|---|
| 执行环境 | 内核态 | 用户态 |
| 性能开销 | 高(上下文切换) | 低(用户态直接执行) |
| 移植性 | 平台相关(ARM/X86) | 跨平台(标准C库) |
| 典型函数 | open(), read(), write() |
fopen(), fread(), fwrite() |
| 缓冲机制 | 无缓冲 | 有用户态缓冲区 |
| 错误码 | 返回 -1,errno 被设置 | 返回 NULL,errno 被设置 |
调用关系示意:
用户程序: fopen("file.txt", "r")
↓
glibc: 分配用户态缓冲区 → 调用系统调用
↓
内核: sys_open → 真正的文件操作
↓
硬件: 磁盘 I/O
glibc 在用户态维护缓冲区,减少系统调用次数。例如 fwrite() 可能多次写入缓冲区后才触发一次 write() 系统调用。
以 ARM Cortex-A7(IMX6ULL)为例,完整流程如下:
sequenceDiagram
participant App as 用户程序
participant Lib as glibc库
participant Kernel as 内核
participant VFS as VFS层
participant Driver as 设备驱动
App->>Lib: 调用 open("/dev/mydev", O_RDWR)
Lib->>Lib: 设置系统调用号(R7=5)和参数
Lib->>Kernel: SVC #0 (软中断)
Note over Kernel: CPU切换到SVC模式<br/>保存现场到内核栈
Kernel->>Kernel: vector_swi → sys_call_table
Kernel->>Kernel: sys_open() 执行
Kernel->>VFS: 查找文件/设备节点
VFS->>Driver: 调用具体驱动的 open
Driver-->>VFS: 返回设备句柄
VFS-->>Kernel: 返回文件描述符
Kernel-->>Lib: 返回 fd (>=0)
Lib-->>App: 返回文件指针
关键步骤解析:
| 步骤 | 动作 | 寄存器变化 |
|---|---|---|
| 1. 用户态调用 | 调用 glibc open() |
无 |
| 2. 设置系统调用号 | R7 = 5 (sys_open) | R7 = __NR_open |
| 3. 设置参数 | R0 = 文件名地址, R1 = flags | R0-R3 = 参数 |
| 4. 触发软中断 | SVC #0 | PC = vector_swi |
| 5. 内核入口 | vector_swi → sys_call_table | 保存所有寄存器 |
| 6. 执行系统调用 | sys_open() | 根据 R7 索引表 |
| 7. 返回用户态 | 恢复寄存器,执行 MOVS PC, LR | R0 = 返回值 |
每个系统调用都有唯一编号,ARM32 架构定义在 arch/arm/include/asm/unistd.h:
/* ARM32 常用系统调用号 */
#define __NR_open 5
#define __NR_read 3
#define __NR_write 4
#define __NR_close 6
#define __NR_ioctl 54
#define __NR_mmap 192
#define __NR_munmap 91
#define __NR_mmap2 192
#define __NR_stat 196
#define __NR_fstat 197
/* 系统调用总数 */
#define __NR_syscalls 400
⚠️ 注意: 系统调用号在不同架构间不通用。ARM64 使用
asm-generic/unistd.h,编号与 ARM32 不同。跨平台驱动开发时需注意平台差异。
系统调用表是一个函数指针数组,以系统调用号为索引,存储对应的内核处理函数地址:
/* arch/arm/kernel/entry-armv.S */
.align 5
.global sys_call_table
sys_call_table:
.long sys_ni_syscall /* 0 - 未实现 */
.long sys_ni_syscall /* 1 - 未实现 */
.long sys_ni_syscall /* 2 - 未实现 */
.long sys_read /* 3 - __NR_read */
.long sys_write /* 4 - __NR_write */
.long sys_open /* 5 - __NR_open */
.long sys_close /* 6 - __NR_close */
.long sys_ni_syscall /* 7 */
.long sys_ni_syscall /* 8 */
.long sys_ni_syscall /* 9 */
.long sys_ni_syscall /* 10 */
.long sys_ni_syscall /* 11 */
.long sys_ni_syscall /* 12 */
.long sys_ni_syscall /* 13 */
.long sys_ni_syscall /* 14 */
.long sys_ni_syscall /* 15 */
.long sys_ni_syscall /* 16 */
.long sys_ni_syscall /* 17 */
.long sys_ni_syscall /* 18 */
.long sys_ni_syscall /* 19 */
.long sys_ni_syscall /* 20 */
.long sys_ni_syscall /* 21 */
.long sys_ni_syscall /* 22 */
.long sys_ni_syscall /* 23 */
.long sys_ni_syscall /* 24 */
.long sys_ni_syscall /* 25 */
.long sys_ni_syscall /* 26 */
.long sys_ni_syscall /* 27 */
.long sys_ni_syscall /* 28 */
.long sys_ni_syscall /* 29 */
.long sys_ni_syscall /* 30 */
.long sys_ni_syscall /* 31 */
.long sys_ni_syscall /* 32 */
.long sys_ni_syscall /* 33 */
.long sys_ni_syscall /* 34 */
.long sys_ni_syscall /* 35 */
.long sys_ni_syscall /* 36 */
.long sys_ni_syscall /* 37 */
.long sys_ni_syscall /* 38 */
.long sys_ni_syscall /* 39 */
.long sys_ni_syscall /* 40 */
.long sys_ni_syscall /* 41 */
.long sys_ni_syscall /* 42 */
.long sys_ni_syscall /* 43 */
.long sys_ni_syscall /* 44 */
.long sys_ni_syscall /* 45 */
.long sys_ni_syscall /* 46 */
.long sys_ni_syscall /* 47 */
.long sys_ni_syscall /* 48 */
.long sys_ni_syscall /* 49 */
.long sys_ni_syscall /* 50 */
.long sys_ni_syscall /* 51 */
.long sys_ni_syscall /* 52 */
.long sys_ni_syscall /* 53 */
.long sys_ioctl /* 54 - __NR_ioctl */
查找过程:
SVC #0 中断获取系统调用号sys_call_tablesys_open)ARM32 架构通过寄存器 R0-R3 传递前 4 个参数,超出部分通过栈传递:
/* 系统调用参数传递约定 (ARM32) */
asmlinkage long sys_write(unsigned int fd,
const char __user *buf,
size_t count)
{
/* R0 = fd, R1 = buf, R2 = count */
/* 如果参数超过4个,通过栈传递 */
}
/* 超过4个参数的系统调用示例 */
asmlinkage long sys_execve(const char __user *filename,
const char __user *const __user *argv,
const char __user *const __user *envp)
{
/* R0 = filename, R1 = argv, R2 = envp */
/* 使用 copy_from_user 从用户空间拷贝数据 */
}
用户空间数据拷贝:
/* 内核中访问用户空间数据的标准方式 */
#include <linux/uaccess.h>
ssize_t my_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
char kernel_buf[128];
/* 从设备读取数据到内核缓冲区 */
int ret = device_read(kernel_buf, count);
/* 拷贝到用户空间,失败返回 -EFAULT */
if (copy_to_user(buf, kernel_buf, ret))
return -EFAULT;
return ret;
}
在内核中添加自定义系统调用需要修改以下文件:
/* 1. 定义系统调用号 */
/* arch/arm/include/asm/unistd.h */
#define __NR_my_syscall 400
#define __NR_syscalls 401
/* 2. 实现系统调用函数 */
/* kernel/my_syscall.c */
#include <linux/syscalls.h>
SYSCALL_DEFINE2(my_syscall, int, arg1, const char __user *, arg2)
{
char buf[256];
/* 从用户空间拷贝数据 */
if (strncpy_from_user(buf, arg2, sizeof(buf)) < 0)
return -EFAULT;
printk(KERN_INFO "my_syscall: arg1=%d, arg2=%s\n", arg1, buf);
return 0; /* 成功返回0,失败返回负错误码 */
}
/* 3. 添加到系统调用表 */
/* arch/arm/kernel/entry-armv.S */
.long sys_my_syscall /* 400 - __NR_my_syscall */
💡 编译选项: 新增系统调用后需重新编译内核。用户空间可通过
syscall()函数或内联汇编调用新系统调用。
VFS(Virtual File System,虚拟文件系统)是 Linux 内核中的抽象层,为所有文件系统提供统一的接口。无论底层是 ext4、NFS、FAT32 还是 procfs,用户空间看到的都是相同的 open/read/write/close 接口。
VFS 核心价值:
| 特性 | 说明 |
|---|---|
| 统一接口 | 所有文件系统实现相同的操作函数集 |
| 设备文件 | 通过 /dev/* 节点统一管理字符设备、块设备 |
| 透明挂载 | 不同文件系统可无缝挂载到统一目录树 |
| 内核模块 | 文件系统可作为内核模块动态加载/卸载 |
flowchart TB
subgraph UserSpace["用户空间"]
App["应用程序"]
LibC["glibc"]
end
subgraph KernelSpace["内核空间"]
subgraph VFSLayer["VFS 虚拟文件系统层"]
SysCall["系统调用接口"]
VFSOps["VFS 操作集"]
end
subgraph FSImpl["具体文件系统实现"]
EXT4["ext4"]
NFS["NFS"]
FAT["FAT32"]
ProcFS["procfs"]
DevFS["devtmpfs"]
end
subgraph DeviceLayer["设备层"]
CharDev["字符设备驱动"]
BlockDev["块设备驱动"]
NetDev["网络设备"]
end
end
subgraph Hardware["硬件层"]
Disk["磁盘/存储"]
Dev["设备硬件"]
end
App --> LibC
LibC --> SysCall
SysCall --> VFSOps
VFSOps --> EXT4
VFSOps --> NFS
VFSOps --> FAT
VFSOps --> ProcFS
VFSOps --> DevFS
EXT4 --> BlockDev
NFS --> NetDev
FAT --> BlockDev
DevFS --> CharDev
CharDev --> Dev
BlockDev --> Disk
classDef user fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a5f
classDef vfs fill:#fef9c3,stroke:#ca8a04,stroke-width:2px,color:#713f12
classDef fs fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d
classDef dev fill:#fce7f3,stroke:#db2777,stroke-width:2px,color:#831843
classDef hw fill:#f3f4f6,stroke:#6b7280,stroke-width:2px,color:#374151
class App,LibC user
class SysCall,VFSOps vfs
class EXT4,NFS,FAT,ProcFS,DevFS fs
class CharDev,BlockDev,NetDev dev
class Disk,Dev hw
📖 设备文件详解: [[03-pinctrl与gpio子系统|pinctrl与gpio子系统]] — 设备节点与驱动绑定
| 类型 | 标志 | 特点 | 典型设备 |
|---|---|---|---|
| 字符设备 | 'c' |
字节流,不支持随机访问 | 串口、GPIO、LED |
| 块设备 | 'b' |
块数据,支持随机访问 | SD卡、eMMC、硬盘 |
| 网络设备 | '-' |
通过 socket 访问 | 以太网、WiFi |
设备号:
/* 主设备号和次设备号 */
dev_t dev;
int major, minor;
/* 从 dev_t 提取设备号 */
major = MAJOR(dev);
minor = MINOR(dev);
/* 合成设备号 */
dev = MKDEV(major, minor);
/* 动态分配设备号 */
alloc_chrdev_region(&dev, 0, 1, "mydevice");
major = MAJOR(dev);
VFS 通过四个核心数据结构管理所有文件系统:
classDiagram
class SuperBlock {
+s_dev: dev_t
+s_blocksize: unsigned long
+s_type: file_system_type*
+s_op: super_operations*
+s_root: dentry*
+mount()
+kill_sb()
}
class Inode {
+i_ino: unsigned long
+i_mode: umode_t
+i_rdev: dev_t
+i_op: inode_operations*
+i_fop: file_operations*
+i_sb: super_block*
+i_mapping: address_space*
}
class Dentry {
+d_name: qstr
+d_inode: inode*
+d_parent: dentry*
+d_sb: super_block*
+d_op: dentry_operations*
+d_mounted: int
+d_child: list_head
}
class File {
+f_pos: loff_t
+f_mode: fmode_t
+f_op: file_operations*
+f_inode: inode*
+f_dentry: dentry*
+f_private_data: void*
}
SuperBlock "1" --> "*" Inode : 包含多个
Inode "1" --> "1" SuperBlock : 所属
Dentry "1" --> "1" Inode : 指向
Dentry "1" --> "*" Dentry : 父子关系
File "1" --> "1" Dentry : 打开的文件
File "1" --> "1" Inode : 对应的inode
超级块描述整个文件系统的元信息,挂载时从磁盘读取并常驻内存:
struct super_block {
struct list_head s_list; /* 超级块链表 */
dev_t s_dev; /* 设备号 */
unsigned char s_blocksize_bits;/* 块大小位数 */
unsigned long s_blocksize; /* 块大小(字节) */
struct file_system_type *s_type; /* 文件系统类型 */
const struct super_operations *s_op; /* 超级块操作 */
struct dentry *s_root; /* 根目录项 */
struct rw_semaphore s_umount; /* 卸载信号量 */
atomic_t s_active; /* 活跃引用计数 */
/* 文件系统特定数据 */
void *s_fs_info; /* ext4等私有数据 */
};
/* 超级块操作 */
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
void (*destroy_inode)(struct inode *);
void (*dirty_inode)(struct inode *, int flags);
int (*write_inode)(struct inode *, struct writeback_control *wbc);
void (*evict_inode)(struct inode *);
void (*put_super)(struct super_block *);
int (*sync_fs)(struct super_block *sb, int wait);
int (*statfs)(struct dentry *, struct kstatfs *);
int (*show_options)(struct seq_file *, struct dentry *);
};
典型实现(ext4):
/* fs/ext4/super.c */
static const struct super_operations ext4_sops = {
.alloc_inode = ext4_alloc_inode,
.destroy_inode = ext4_destroy_inode,
.dirty_inode = ext4_dirty_inode,
.write_inode = ext4_write_inode,
.evict_inode = ext4_evict_inode,
.put_super = ext4_put_super,
.sync_fs = ext4_sync_fs,
.statfs = ext4_statfs,
.show_options = ext4_show_options,
};
inode 描述单个文件/目录的元信息,是 VFS 的核心数据结构:
struct inode {
umode_t i_mode; /* 文件类型和权限 */
unsigned short i_opflags;
kuid_t i_uid; /* 所有者 UID */
kgid_t i_gid; /* 所有者 GID */
unsigned int i_flags; /* 挂载标志 */
const struct inode_operations *i_op; /* inode 操作 */
struct super_block *i_sb; /* 所属超级块 */
struct address_space *i_mapping; /* 地址空间(页缓存) */
unsigned long i_ino; /* inode 编号 */
dev_t i_rdev; /* 实际设备号 */
loff_t i_size; /* 文件大小 */
struct timespec i_atime; /* 最后访问时间 */
struct timespec i_mtime; /* 最后修改时间 */
struct timespec i_ctime; /* inode 变更时间 */
unsigned int i_blkbits; /* 块大小位数 */
blkcnt_t i_blocks; /* 块数量 */
union {
struct pipe_inode_info *i_pipe; /* 管道 */
struct block_device *i_bdev; /* 块设备 */
struct cdev *i_cdev; /* 字符设备 */
char *i_link; /* 符号链接 */
unsigned i_dir_seq; /* 目录序列号 */
};
void *i_private; /* 文件系统私有数据 */
};
/* inode 操作 */
struct inode_operations {
struct dentry *(*lookup)(struct inode *, struct dentry *, unsigned int);
int (*create)(struct mnt_idmap *, struct inode *, struct dentry *, umode_t, bool);
int (*link)(struct dentry *, struct inode *, struct dentry *);
int (*unlink)(struct inode *, struct dentry *);
int (*symlink)(struct mnt_idmap *, struct inode *, struct dentry *, const char *);
int (*mkdir)(struct mnt_idmap *, struct inode *, struct dentry *, umode_t);
int (*rmdir)(struct inode *, struct dentry *);
int (*mknod)(struct mnt_idmap *, struct inode *, struct dentry *, umode_t, dev_t);
int (*rename)(struct mnt_idmap *, struct inode *, struct dentry *,
struct inode *, struct dentry *, unsigned int);
int (*permission)(struct mnt_idmap *, struct inode *, int);
int (*getattr)(struct mnt_idmap *, const struct path *,
struct kstat *, u32, unsigned int);
};
📖 inode 与设备驱动: inode 中的
i_rdev字段存储设备号,通过MAJOR(i_rdev)和MINOR(i_rdev)可获取主次设备号,驱动通过此定位具体设备。
dentry 建立文件名到 inode 的映射,是路径解析的关键:
struct dentry {
unsigned int d_flags; /* 目录项标志 */
seqcount_spinlock_t d_seq; /* 序列锁 */
struct hlist_bl_node d_hash; /* 哈希表节点(路径查找) */
struct dentry *d_parent; /* 父目录项 */
struct qstr d_name; /* 文件名 */
struct inode *d_inode; /* 指向的 inode */
unsigned char d_iname[DNAME_INLINE_LEN]; /* 短文件名内联存储 */
struct lockref d_lockref; /* 引用计数 */
const struct dentry_operations *d_op; /* dentry 操作 */
struct super_block *d_sb; /* 所属超级块 */
unsigned long d_time; /* revalidate 时间戳 */
void *d_fsdata; /* 文件系统私有数据 */
struct list_head d_lru; /* LRU 链表 */
struct list_head d_child; /* 子目录链表 */
struct list_head d_subdirs; /* 子目录链表头 */
struct hlist_node d_sib; /* 兄弟目录链表 */
};
/* dentry 操作 */
struct dentry_operations {
int (*d_revalidate)(struct dentry *, unsigned int);
int (*d_hash)(const struct dentry *, struct qstr *);
int (*d_compare)(const struct dentry *, unsigned int,
const char *, const struct qstr *);
int (*d_delete)(const struct dentry *);
void (*d_release)(struct dentry *);
void (*d_iput)(struct dentry *, struct inode *);
char *(*d_dname)(struct dentry *, char *, int);
};
dentry 缓存:
find_inode_number() 和路径解析都依赖 dentry 缓存file 表示一个已打开的文件,每个 open() 调用创建一个新的 file 对象:
struct file {
union {
struct llist_node fu_llist; /* 释放链表 */
struct rcu_head fu_rcuhead; /* RCU 释放头 */
} f_u;
struct path f_path; /* 路径(含 dentry 和 vfsmount) */
struct inode *f_inode; /* 缓存的 inode 指针 */
const struct file_operations *f_op; /* 文件操作 */
spinlock_t f_lock; /* 文件锁 */
atomic_long_t f_count; /* 引用计数 */
unsigned int f_flags; /* 打开标志(O_RDWR等) */
fmode_t f_mode; /* 文件模式 */
loff_t f_pos; /* 当前读写位置 */
struct fown_struct f_owner; /* 异步 I/O 所有者 */
void *private_data;/* 文件系统/驱动私有数据 */
struct address_space *f_mapping; /* 页缓存映射 */
};
/* 文件操作(驱动开发核心) */
struct file_operations {
struct module *owner;
loff_t (*llseek)(struct file *, loff_t, int);
ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter)(struct kiocb *, struct iov_iter *);
ssize_t (*write_iter)(struct kiocb *, struct iov_iter *);
int (*iopoll)(struct kiocb *, struct io_comp_batch *, unsigned int);
unsigned int (*poll)(struct file *, struct poll_table_struct *);
long (*unlocked_ioctl)(struct file *, unsigned int, unsigned long);
long (*compat_ioctl)(struct file *, unsigned int, unsigned long);
int (*mmap)(struct file *, struct vm_area_struct *);
int (*open)(struct inode *, struct file *);
int (*flush)(struct file *, fl_owner_t id);
int (*release)(struct inode *, struct file *);
int (*fsync)(struct file *, loff_t, loff_t, int datasync);
int (*fasync)(int, struct file *, int);
int (*lock)(struct file *, int, struct file_lock *);
ssize_t (*sendpage)(struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, ...);
int (*check_flags)(int);
int (*flock)(struct file *, int, struct file_lock *);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, ...);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, ...);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *, int, loff_t, loff_t);
void (*show_fdinfo)(struct seq_file *, struct file *);
};
⚠️ 驱动开发重点:
file_operations是字符设备驱动必须实现的结构体。read/write/ioctl/open/release是最常用的五个函数指针。
驱动开发者通过实现 file_operations 中的函数指针来响应用户空间的文件操作:
/* 驱动实现示例 */
static ssize_t mydevice_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
struct mydevice_data *data = filp->private_data;
/* 从设备读取数据 */
if (copy_to_user(buf, data->buffer, data->len))
return -EFAULT;
return data->len;
}
static ssize_t mydevice_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
struct mydevice_data *data = filp->private_data;
/* 从用户空间拷贝数据 */
if (copy_from_user(data->buffer, buf, count))
return -EFAULT;
data->len = count;
return count;
}
static long mydevice_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct mydevice_data *data = filp->private_data;
switch (cmd) {
case MYDEVICE_IOCTL_SET_SPEED:
data->speed = arg;
break;
case MYDEVICE_IOCTL_GET_SPEED:
if (copy_to_user((void __user *)arg, &data->speed, sizeof(int)))
return -EFAULT;
break;
default:
return -ENOTTY;
}
return 0;
}
static int mydevice_open(struct inode *inode, struct file *filp)
{
struct mydevice_data *data;
/* 通过次设备号找到设备数据 */
data = container_of(inode->i_cdev, struct mydevice_data, cdev);
filp->private_data = data;
return 0;
}
static int mydevice_release(struct inode *inode, struct file *filp)
{
/* 释放资源 */
return 0;
}
/* 文件操作结构体 */
static const struct file_operations mydevice_fops = {
.owner = THIS_MODULE,
.llseek = mydevice_llseek,
.read = mydevice_read,
.write = mydevice_write,
.unlocked_ioctl = mydevice_ioctl,
.open = mydevice_open,
.release = mydevice_release,
};
inode 操作处理文件系统层面的元数据操作:
/* 目录 inode 操作 */
static int myfs_mkdir(struct mnt_idmap *idmap,
struct inode *dir, struct dentry *dentry, umode_t mode)
{
struct inode *inode;
struct myfs_info *info = dir->i_sb->s_fs_info;
/* 分配新 inode */
inode = new_inode(dir->i_sb);
if (!inode)
return -ENOMEM;
inode->i_ino = info->next_ino++;
inode->i_mode = S_IFDIR | mode;
inode->i_op = &myfs_dir_inode_ops;
inode->i_fop = &myfs_dir_operations;
inode->i_sb = dir->i_sb;
/* 添加到目录 */
d_add(dentry, inode);
return 0;
}
static struct dentry *myfs_lookup(struct inode *dir,
struct dentry *dentry, unsigned int flags)
{
struct inode *inode;
/* 在磁盘上查找文件 */
inode = myfs_find_inode(dir->i_sb, dentry->d_name.name);
if (IS_ERR(inode))
return ERR_PTR(PTR_ERR(inode));
d_add(dentry, inode);
return NULL;
}
/* inode 操作结构体 */
static const struct inode_operations myfs_dir_inode_ops = {
.lookup = myfs_lookup,
.create = myfs_create,
.link = myfs_link,
.unlink = myfs_unlink,
.mkdir = myfs_mkdir,
.rmdir = myfs_rmdir,
.mknod = myfs_mknod,
.rename = myfs_rename,
};
目录操作用于读取目录内容(如 ls 命令):
/* 目录项 */
struct myfs_dentry {
char name[256];
unsigned int ino;
};
/* 目录读取回调 */
static int myfs_readdir(struct file *filp, struct dir_context *ctx)
{
struct inode *inode = file_inode(filp);
struct myfs_info *info = inode->i_sb->s_fs_info;
struct myfs_dentry entries[32];
int count, i;
/* 读取目录内容 */
count = myfs_read_dir(info, entries, 32);
/* 遍历目录项 */
for (i = 0; i < count; i++) {
/* 跳过已读取的位置 */
if (ctx->pos < i)
continue;
/* 填充目录项 */
dir_emit(ctx, entries[i].name, strlen(entries[i].name),
entries[i].ino, DT_UNKNOWN);
ctx->pos++;
}
return 0;
}
/* 目录操作结构体 */
static const struct file_operations myfs_dir_operations = {
.owner = THIS_MODULE,
.llseek = generic_file_llseek,
.read = generic_read_dir,
.iterate_shared = myfs_readdir,
};
挂载操作将文件系统绑定到目录树:
/* 文件系统类型 */
static struct file_system_type myfs_type = {
.name = "myfs",
.init_fs_context = myfs_init_context,
.parameters = myfs_fs_parameters,
.kill_sb = kill_litter_super,
.fs_flags = FS_USERNS_MOUNT,
};
/* 挂载上下文 */
struct myfs_context {
struct super_block *sb;
char *dev_name;
};
static int myfs_get_tree(struct fs_context *fc)
{
struct myfs_context *ctx = fc->fs_private;
struct super_block *sb;
struct inode *root;
/* 分配超级块 */
sb = sget_fc(fc, myfs_test_super);
if (IS_ERR(sb))
return PTR_ERR(sb);
/* 读取超级块信息 */
myfs_read_super(sb, ctx->dev_name);
/* 创建根目录 inode */
root = myfs_get_root_inode(sb);
if (IS_ERR(root))
return PTR_ERR(root);
sb->s_root = d_make_root(root);
if (!sb->s_root)
return -ENOMEM;
fc->root = dget(sb->s_root);
return 0;
}
用户空间挂载命令:
# 手动创建设备节点
sudo mknod /dev/mydevice c 200 0
# 自动创建(udev/mdev)
class_create(THIS_MODULE, "myclass");
device_create(myclass, NULL, dev, NULL, "mydevice");
# 挂载文件系统
sudo mount -t myfs /dev/sda1 /mnt/myfs
| 特性 | IMX6ULL (ARM Cortex-A7) | STM32 (Cortex-M4) | RK3568 (ARM Cortex-A55) |
|---|---|---|---|
| 系统调用 | 支持 SVC #0 | 不支持(无 MMU) | 支持 SVC #0 |
| VFS | 完整 VFS 支持 | 无 VFS | 完整 VFS 支持 |
| 用户/内核态 | 支持(特权级0/3) | 无(单地址空间) | 支持(EL0/EL1) |
| 文件系统 | ext4, NFS, FAT32 | FAT32(FatFs库) | ext4, Btrfs, XFS |
| 设备文件 | /dev/* 完整支持 | 无 /dev/ | /dev/* 完整支持 |
| 内存管理 | MMU(虚拟地址) | MPU(内存保护) | MMU(虚拟地址) |
| 进程管理 | 多任务调度 | 裸机/RTOS | 多任务调度 |
| 典型系统调用 | open, read, write, ioctl | 无 | open, read, write, ioctl |
ARM 架构差异:
📖 架构详解: [[01-Cortex-A7架构详解|Cortex-A7架构详解]] — 寄存器组、工作模式、异常处理
考察点: 系统调用机制、ARM 异常处理
参考答案:
open(),glibc 设置 R7=5(__NR_open),R0=文件名指针,R1=flagsSVC #0,CPU 从用户态切换到 SVC 模式,保存 CPSR 到 SPSR_svc,PC 到 LR_svcvector_swi 中断向量 → sys_call_table[R7] 查找处理函数 → 调用 sys_open()sys_open() → do_sys_open() → getname() 拷贝文件名 → do_filp_open() 路径解析 → 查找/创建 inodei_fop 调用具体驱动的 open(),如字符设备的 mydevice_open()MOVS PC, LR 恢复用户态,glibc 返回文件指针考察点: VFS 数据结构理解
参考答案:
| 对象 | 作用 | 关键字段 |
|---|---|---|
| super_block | 描述整个文件系统 | s_dev(设备号), s_root(根目录), s_op(操作) |
| inode | 描述文件元信息 | i_ino(编号), i_mode(权限), i_rdev(设备号) |
| dentry | 文件名到 inode 映射 | d_name(文件名), d_inode(指向的inode) |
| file | 已打开的文件实例 | f_pos(读写位置), f_op(操作), private_data |
关系:一个 super_block 包含多个 inode;一个 inode 可被多个 dentry 引用;每次 open() 创建一个 file 对象,指向同一个 dentry/inode。
考察点: 字符设备驱动框架
参考答案:
/* 1. 分配 cdev */
struct cdev *cdev = cdev_alloc();
/* 2. 初始化 cdev */
cdev_init(cdev, &mydevice_fops);
cdev->owner = THIS_MODULE;
/* 3. 注册到内核 */
dev_t dev = MKDEV(major, minor);
cdev_add(cdev, dev, 1);
/* 4. 创建设备节点(用户空间可见) */
class_create(THIS_MODULE, "myclass");
device_create(myclass, NULL, dev, NULL, "mydevice");
用户空间通过 open("/dev/mydevice", ...) 打开设备,VFS 根据设备号找到对应的 cdev,调用 mydevice_fops 中的函数。
考察点: 用户空间与内核空间数据交互
参考答案:
copy_to_user(to, from, n):将内核缓冲区数据拷贝到用户空间,用于 read() 等系统调用copy_from_user(to, from, n):将用户空间数据拷贝到内核缓冲区,用于 write()/ioctl() 等系统调用安全原因:
返回值:成功返回 0,失败返回未拷贝的字节数,驱动应返回 -EFAULT。
考察点: VFS 设计哲学
参考答案:
| 优势 | 说明 |
|---|---|
| 统一接口 | open/read/write 接口屏蔽硬件差异,上层应用无需修改 |
| 安全保护 | 内核态执行特权操作,用户态无法直接访问硬件 |
| 资源管理 | 内核统一调度 I/O,避免多进程竞争 |
| 可扩展性 | 新增设备只需实现驱动,无需修改应用层代码 |
| 网络透明 | NFS 等网络文件系统通过 VFS 实现透明访问 |
对比裸机开发:裸机直接操作寄存器,简单但不可移植;VFS 增加了抽象层开销,但提供了安全性和可维护性。
最后更新: 2026-09-17