03-系统调用与VFS.md 34 KB


title: 系统调用与VFS tags: [Linux内核, 系统调用, VFS, 嵌入式, 驱动开发] created: 2026-09-16

updated: 2026-09-17

系统调用与VFS

💡 关联知识: [[01-Cortex-A7架构详解|Cortex-A7架构详解]] | [[03-pinctrl与gpio子系统|pinctrl与gpio子系统]] | [[arm-assembly-basics|ARM汇编基础]]

一、系统调用概述

1.1 什么是系统调用

系统调用(System Call)是操作系统内核提供给用户空间程序的唯一合法入口。在 Linux 的保护模式下,用户态程序无法直接访问硬件或内核数据结构,必须通过系统调用请求内核代为执行特权操作。

核心特征:

  • 接口抽象:屏蔽硬件差异,同一系统调用在不同平台表现一致
  • 安全隔离:内核态执行特权指令,用户态只能通过软中断陷入
  • 资源管理:内核统一调度 CPU、内存、I/O 等资源
  • 错误处理:失败返回 -1 并设置 errno,用户态可据此判断错误原因

📖 延伸阅读: [[wiki/source-linux-kernel|Linux内核源码]] — 系统调用定义位于 include/linux/syscalls.h

1.2 系统调用 vs 库函数

特性 系统调用 库函数(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() 系统调用。

1.3 系统调用流程详解

以 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 = 返回值

1.4 系统调用号

每个系统调用都有唯一编号,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 不同。跨平台驱动开发时需注意平台差异。


二、系统调用实现

2.1 系统调用表(sys_call_table)

系统调用表是一个函数指针数组,以系统调用号为索引,存储对应的内核处理函数地址:

/* 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 */

查找过程:

  1. 内核从 SVC #0 中断获取系统调用号
  2. 以调用号为索引查找 sys_call_table
  3. 调用对应的内核函数(如 sys_open
  4. 返回结果存入 R0 寄存器

2.2 系统调用参数传递

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;
}

2.3 新增系统调用

在内核中添加自定义系统调用需要修改以下文件:

/* 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概述

3.1 什么是VFS

VFS(Virtual File System,虚拟文件系统)是 Linux 内核中的抽象层,为所有文件系统提供统一的接口。无论底层是 ext4、NFS、FAT32 还是 procfs,用户空间看到的都是相同的 open/read/write/close 接口。

VFS 核心价值:

特性 说明
统一接口 所有文件系统实现相同的操作函数集
设备文件 通过 /dev/* 节点统一管理字符设备、块设备
透明挂载 不同文件系统可无缝挂载到统一目录树
内核模块 文件系统可作为内核模块动态加载/卸载

3.2 VFS 架构

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子系统]] — 设备节点与驱动绑定

3.3 设备文件类型

类型 标志 特点 典型设备
字符设备 '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核心对象

4.1 四大核心对象

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

4.2 super_block(超级块)

超级块描述整个文件系统的元信息,挂载时从磁盘读取并常驻内存:

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,
};

4.3 inode(索引节点)

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) 可获取主次设备号,驱动通过此定位具体设备。

4.4 dentry(目录项)

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 缓存:

  • 内核维护 dentry 缓存(dentry cache),加速路径查找
  • 热点目录项常驻内存,避免重复磁盘 I/O
  • find_inode_number() 和路径解析都依赖 dentry 缓存

4.5 file(文件对象)

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 是最常用的五个函数指针。


五、VFS操作

5.1 文件操作(file_operations)

驱动开发者通过实现 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,
};

5.2 inode 操作

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,
};

5.3 目录操作

目录操作用于读取目录内容(如 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,
};

5.4 mount 操作

挂载操作将文件系统绑定到目录树:

/* 文件系统类型 */
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

六、跨平台对比

6.1 IMX6ULL vs STM32 vs RK3568

特性 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 架构差异:

  • Cortex-A7(IMX6ULL):ARMv7-A,支持用户/系统/IRQ/FIQ/SVC/ABT/UND/HYP 8 种模式
  • Cortex-M4(STM32):ARMv7-M,只支持特权/非特权两种模式,无 MMU
  • Cortex-A55(RK3568):ARMv8-A,支持 EL0-EL3 四个异常等级,AArch64/AArch32 双模式

📖 架构详解: [[01-Cortex-A7架构详解|Cortex-A7架构详解]] — 寄存器组、工作模式、异常处理


七、面试精选

题目1:系统调用的完整流程是怎样的?以 open() 为例

考察点: 系统调用机制、ARM 异常处理

参考答案

  1. 用户态准备:调用 glibc open(),glibc 设置 R7=5(__NR_open),R0=文件名指针,R1=flags
  2. 触发异常:执行 SVC #0,CPU 从用户态切换到 SVC 模式,保存 CPSR 到 SPSR_svc,PC 到 LR_svc
  3. 内核入口vector_swi 中断向量 → sys_call_table[R7] 查找处理函数 → 调用 sys_open()
  4. VFS 处理sys_open()do_sys_open()getname() 拷贝文件名 → do_filp_open() 路径解析 → 查找/创建 inode
  5. 驱动调用:VFS 根据 inode 中的 i_fop 调用具体驱动的 open(),如字符设备的 mydevice_open()
  6. 返回用户态:结果存入 R0,执行 MOVS PC, LR 恢复用户态,glibc 返回文件指针

题目2:VFS 的四大核心对象是什么?它们之间有什么关系?

考察点: 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。

题目3:字符设备驱动如何注册到 VFS?

考察点: 字符设备驱动框架

参考答案

/* 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 中的函数。

题目4:copy_to_user() 和 copy_from_user() 的作用是什么?

考察点: 用户空间与内核空间数据交互

参考答案

  • copy_to_user(to, from, n):将内核缓冲区数据拷贝到用户空间,用于 read() 等系统调用
  • copy_from_user(to, from, n):将用户空间数据拷贝到内核缓冲区,用于 write()/ioctl() 等系统调用

安全原因

  1. 用户空间地址不可信,可能指向非法地址
  2. 内核不能直接访问用户空间指针(可能触发页错误)
  3. 必须使用专门的拷贝函数,内核会检查地址合法性

返回值:成功返回 0,失败返回未拷贝的字节数,驱动应返回 -EFAULT

题目5:为什么嵌入式 Linux 使用 VFS 而不直接操作硬件?

考察点: VFS 设计哲学

参考答案

优势 说明
统一接口 open/read/write 接口屏蔽硬件差异,上层应用无需修改
安全保护 内核态执行特权操作,用户态无法直接访问硬件
资源管理 内核统一调度 I/O,避免多进程竞争
可扩展性 新增设备只需实现驱动,无需修改应用层代码
网络透明 NFS 等网络文件系统通过 VFS 实现透明访问

对比裸机开发:裸机直接操作寄存器,简单但不可移植;VFS 增加了抽象层开销,但提供了安全性和可维护性。


最后更新: 2026-09-17