从用户空间open()到驱动open()的完整调用链深度解析

文章目录

    • 概述
    • 调用链全景图
    • [第一阶段:用户空间 - glibc 库处理](#第一阶段:用户空间 - glibc 库处理)
      • [1.1 应用程序调用](#1.1 应用程序调用)
      • [1.2 glibc 内部实现](#1.2 glibc 内部实现)
      • [1.3 宏展开过程](#1.3 宏展开过程)
      • [1.4 ARM64 汇编生成](#1.4 ARM64 汇编生成)
    • [第二阶段:内核入口 - 异常处理](#第二阶段:内核入口 - 异常处理)
      • [2.1 硬件自动处理](#2.1 硬件自动处理)
      • [2.2 异常向量表跳转](#2.2 异常向量表跳转)
      • [2.3 kernel_ventry 宏](#2.3 kernel_ventry 宏)
      • [2.4 el0_sync 处理](#2.4 el0_sync 处理)
    • 第三阶段:系统调用分发
      • [3.1 异常分发器](#3.1 异常分发器)
      • [3.2 系统调用处理](#3.2 系统调用处理)
      • [3.3 系统调用表查找](#3.3 系统调用表查找)
    • [第四阶段:VFS 层处理](#第四阶段:VFS 层处理)
      • [4.1 sys_openat 实现](#4.1 sys_openat 实现)
      • [4.2 核心打开函数](#4.2 核心打开函数)
      • [4.3 文件系统层处理](#4.3 文件系统层处理)
      • [4.4 路径解析和打开](#4.4 路径解析和打开)
    • 第五阶段:驱动层调用
      • [5.1 VFS 到驱动的桥梁](#5.1 VFS 到驱动的桥梁)
      • [5.2 关键分发点:do_dentry_open](#5.2 关键分发点:do_dentry_open)
      • [5.3 设备驱动示例](#5.3 设备驱动示例)
    • 调用链总结表
    • 关键数据结构
      • [1. file_operations 结构](#1. file_operations 结构)
      • [2. file 结构](#2. file 结构)
      • [3. inode 结构](#3. inode 结构)

概述

在 Linux 系统中,当应用程序调用 open() 函数打开一个设备文件时,会触发一系列复杂而精密的调用过程。这个过程横跨用户空间和内核空间,涉及库函数、系统调用、异常处理、虚拟文件系统(VFS)以及最终的设备驱动。本文将以 ARM64 架构为例,详细剖析从 glibc 库的 open() 到设备驱动 open() 方法的完整调用链。

调用链全景图

text 复制代码
应用层 open() → glibc库 → 系统调用触发 → 异常处理 → VFS层 → 驱动层
    │          │          │           │         │         │
    EL0        EL0        EL0→EL1     EL1       EL1       EL1
   用户态      用户态     特权切换    内核态    内核态    内核态

第一阶段:用户空间 - glibc 库处理

1.1 应用程序调用

c 复制代码
int fd = open("/dev/mydevice", O_RDWR);

1.2 glibc 内部实现

在 glibc 2.42 中,open() 实际上是 __libc_open() 的别名:

c 复制代码
// glibc-2.42/sysdeps/unix/sysv/linux/open64.c
int __libc_open(const char *file, int oflag, ...)
{
    int mode = 0;
    
    // 处理可变参数(文件创建时的权限)
    if (__OPEN_NEEDS_MODE(oflag)) {
        va_list arg;
        va_start(arg, oflag);
        mode = va_arg(arg, int);
        va_end(arg);
    }
    
    // 通过宏展开调用 openat 系统调用
    return SYSCALL_CANCEL(openat, AT_FDCWD, file, oflag, mode);
}

1.3 宏展开过程

c 复制代码
// 宏展开链
SYSCALL_CANCEL(openat, AT_FDCWD, file, oflag, mode)
→ INLINE_SYSCALL_CALL(openat, AT_FDCWD, file, oflag, mode)
→ __INLINE_SYSCALL4(openat, AT_FDCWD, file, oflag, mode)
→ INTERNAL_SYSCALL(openat, 4, AT_FDCWD, file, oflag, mode)

1.4 ARM64 汇编生成

最终生成的 ARM64 汇编代码:

assembly 复制代码
// 参数设置
mov x0, #AT_FDCWD    // 第一个参数:目录描述符
mov x1, 文件路径指针   // 第二个参数:文件路径
mov x2, 打开标志       // 第三个参数:打开标志
mov x3, 权限模式       // 第四个参数:权限模式
mov x8, #56          // 系统调用号:__NR_openat = 56

// 触发系统调用
svc #0               // 陷入内核,触发同步异常

第二阶段:内核入口 - 异常处理

2.1 硬件自动处理

当执行 svc #0 指令时,ARM64 硬件自动执行:

  1. 异常识别:识别为同步异常(Synchronous Exception)
  2. 来源判断:来自 EL0(用户态)
  3. 模式判断:64位执行模式
  4. 计算偏移:同步异常 + EL0 + 64位 = 向量表偏移 0x400

2.2 异常向量表跳转

c 复制代码
// arch/arm64/kernel/entry.S
// 异常向量表定义
.align 11
SYM_CODE_START(vectors)
    // ... 其他向量 ...
    kernel_ventry   0, sync          // 同步异常,64位EL0(偏移0x400)
    // ... 其他向量 ...
SYM_CODE_END(vectors)

// VBAR_EL1 寄存器指向 vectors
// 跳转到 vectors + 0x400 处执行

2.3 kernel_ventry 宏

assembly 复制代码
.macro kernel_ventry, el, label, regsize=64
    .if \el == 0
        // EL0 特殊处理
    .endif
    
    sub sp, sp, #S_FRAME_SIZE      // 分配栈空间
    b el\()\el\()_\label           // 跳转到处理函数
.endm

2.4 el0_sync 处理

assembly 复制代码
SYM_CODE_START_LOCAL_NOALIGN(el0_sync)
    kernel_entry 0                  // 保存用户态上下文
    mov x0, sp                      // pt_regs 指针作为参数
    bl el0_sync_handler            // 调用C处理函数
    b ret_to_user                  // 返回用户空间
SYM_CODE_END(el0_sync)

第三阶段:系统调用分发

3.1 异常分发器

c 复制代码
// arch/arm64/kernel/entry-common.c
asmlinkage void noinstr el0_sync_handler(struct pt_regs *regs)
{
    unsigned long esr = read_sysreg(esr_el1);  // 读取异常症状寄存器
    
    switch (ESR_ELx_EC(esr)) {      // 根据异常类别分发
    case ESR_ELx_EC_SVC64:          // 0x15 = 64位系统调用
        el0_svc(regs);              // 处理系统调用
        break;
    case ESR_ELx_EC_DABT_LOW:       // 数据中止异常
        el0_da(regs, esr);
        break;
    // ... 其他异常处理 ...
    }
}

3.2 系统调用处理

c 复制代码
static void noinstr el0_svc(struct pt_regs *regs)
{
    enter_from_user_mode();        // 用户模式进入内核准备
    do_el0_svc(regs);              // 执行系统调用
}

void do_el0_svc(struct pt_regs *regs)
{
    sve_user_discard();            // 处理SVE状态
    el0_svc_common(regs, regs->regs[8], __NR_syscalls, sys_call_table);
}

3.3 系统调用表查找

c 复制代码
// arch/arm64/kernel/sys.c
void *sys_call_table[NR_syscalls] = {
    [0 ... NR_syscalls-1] = sys_ni_syscall,
    #include <asm/unistd.h>       // 包含系统调用定义
};

// uapi/asm-generic/unistd.h
#define __NR_openat 56
__SYSCALL(__NR_openat, sys_openat)

第四阶段:VFS 层处理

4.1 sys_openat 实现

c 复制代码
// fs/open.c
SYSCALL_DEFINE4(openat, int, dfd, const char __user *, filename, 
                int, flags, umode_t, mode)
{
    if (force_o_largefile())
        flags |= O_LARGEFILE;
    return do_sys_open(dfd, filename, flags, mode);
}

long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
    struct open_how how = build_open_how(flags, mode);
    return do_sys_openat2(dfd, filename, &how);
}

4.2 核心打开函数

c 复制代码
static long do_sys_openat2(int dfd, const char __user *filename,
                           struct open_how *how)
{
    struct open_flags op;
    int fd;
    
    // 1. 构建打开标志
    fd = build_open_flags(how, &op);
    if (fd) return fd;
    
    // 2. 获取内核文件名
    struct filename *tmp = getname(filename);
    if (IS_ERR(tmp))
        return PTR_ERR(tmp);
    
    // 3. 分配文件描述符
    fd = get_unused_fd_flags(how->flags);
    if (fd >= 0) {
        // 4. 实际打开文件
        struct file *f = do_filp_open(dfd, tmp, &op);
        if (!IS_ERR(f)) {
            fsnotify_open(f);
            fd_install(fd, f);
        } else {
            put_unused_fd(fd);
            fd = PTR_ERR(f);
        }
    }
    
    putname(tmp);
    return fd;
}

4.3 文件系统层处理

c 复制代码
struct file *do_filp_open(int dfd, struct filename *pathname,
                          const struct open_flags *op)
{
    struct nameidata nd;
    struct file *filp;
    
    set_nameidata(&nd, dfd, pathname);
    
    // 尝试RCU快速路径
    filp = path_openat(&nd, op, flags | LOOKUP_RCU);
    if (unlikely(filp == ERR_PTR(-ECHILD)))
        filp = path_openat(&nd, op, flags);  // 慢速路径
    
    restore_nameidata();
    return filp;
}

4.4 路径解析和打开

c 复制代码
static struct file *path_openat(struct nameidata *nd,
                               const struct open_flags *op,
                               unsigned flags)
{
    struct file *file = alloc_empty_file(op->open_flag, current_cred());
    
    if (unlikely(file->f_flags & __O_TMPFILE)) {
        error = do_tmpfile(nd, flags, op, file);
    } else if (unlikely(file->f_flags & O_PATH)) {
        error = do_o_path(nd, flags, file);
    } else {
        // 普通文件打开
        const char *s = path_init(nd, flags);
        while (!(error = link_path_walk(s, nd)) &&
               (s = open_last_lookups(nd, file, op)) != NULL)
            ;
        if (!error)
            error = do_open(nd, file, op);
        terminate_walk(nd);
    }
    
    return file;
}

第五阶段:驱动层调用

5.1 VFS 到驱动的桥梁

c 复制代码
static int do_open(struct nameidata *nd, struct file *file,
                   const struct open_flags *op)
{
    // ... 权限检查、审计等 ...
    
    // 关键调用:vfs_open
    error = vfs_open(&nd->path, file);
    return error;
}

int vfs_open(const struct path *path, struct file *file)
{
    file->f_path = *path;
    return do_dentry_open(file, d_backing_inode(path->dentry), NULL);
}

5.2 关键分发点:do_dentry_open

c 复制代码
static int do_dentry_open(struct file *f,
                         struct inode *inode,
                         int (*open)(struct inode *, struct file *))
{
    // 1. 设置基础信息
    f->f_inode = inode;
    f->f_mapping = inode->i_mapping;
    
    // 2. 获取文件操作函数表
    f->f_op = fops_get(inode->i_fop);  // ⭐ 从inode获取f_op ⭐
    
    // 3. 安全检查
    error = security_file_open(f);
    if (error) goto cleanup_all;
    
    // 4. 文件锁处理
    error = break_lease(locks_inode(f), f->f_flags);
    
    // 5. ⭐⭐ 关键:调用设备驱动的open方法 ⭐⭐
    if (!open)
        open = f->f_op->open;         // 获取驱动注册的open函数指针
    if (open) {
        error = open(inode, f);       // 调用驱动open方法
        if (error) goto cleanup_all;
    }
    
    // 6. 标记文件已打开
    f->f_mode |= FMODE_OPENED;
    
    return 0;
}

5.3 设备驱动示例

c 复制代码
// 字符设备驱动示例
static const struct file_operations mydevice_fops = {
    .owner = THIS_MODULE,
    .open = mydevice_open,      // 驱动注册的open方法
    .read = mydevice_read,
    .write = mydevice_write,
    .release = mydevice_release,
    // ... 其他操作 ...
};

static int mydevice_open(struct inode *inode, struct file *filp)
{
    // 驱动特定的打开操作:
    // 1. 硬件初始化
    // 2. 资源分配
    // 3. 权限检查
    // 4. 私有数据设置
    struct mydevice_data *data = kmalloc(sizeof(*data), GFP_KERNEL);
    filp->private_data = data;
    
    // 初始化硬件
    writel(INIT_VALUE, device_base + CONTROL_REG);
    
    return 0;  // 成功返回0
}

// 设备注册
static int __init mydevice_init(void)
{
    dev_t dev = MKDEV(MAJOR_NUM, MINOR_NUM);
    
    // 注册字符设备
    register_chrdev_region(dev, 1, "mydevice");
    
    // 创建设备节点
    cdev_init(&mydevice_cdev, &mydevice_fops);
    cdev_add(&mydevice_cdev, dev, 1);
    
    // 自动创建设备文件
    device_create(mydevice_class, NULL, dev, NULL, "mydevice");
    
    return 0;
}

调用链总结表

阶段 关键函数/节点 所属层级 核心功能
用户空间 open() 应用层 应用程序标准接口
__libc_open() glibc库 参数处理,封装系统调用
svc #0 硬件指令 触发系统调用,用户态→内核态切换
异常处理 vectors + 0x400 异常向量表 同步异常入口
el0_sync 异常处理 保存上下文,调用分发器
el0_sync_handler() 异常分发 根据ESR寄存器分发异常类型
系统调用 el0_svc() 系统调用处理 SVC指令专门处理
el0_svc_common() 系统调用核心 通用处理逻辑
invoke_syscall() 系统调用执行 查表调用具体系统调用
VFS层 sys_openat() 系统调用实现 open系统调用的内核实现
do_sys_openat2() VFS核心 处理打开标志,分配文件描述符
do_filp_open() 文件系统接口 创建file结构,路径解析
path_openat() 路径处理 路径遍历,符号链接处理
do_open() 打开操作 权限检查,审计处理
vfs_open() VFS统一入口 连接路径和文件操作
do_dentry_open() VFS→驱动桥梁 关键分发点,获取f_op
驱动层 f->f_op->open 驱动接口 调用驱动注册的open方法
xxx_open() 驱动实现 设备特定的打开操作

关键数据结构

1. file_operations 结构

c 复制代码
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 *);
    int (*open) (struct inode *, struct file *);  // ⭐ 驱动open方法 ⭐
    int (*release) (struct inode *, struct file *);
    // ... 其他方法 ...
};

2. file 结构

c 复制代码
struct file {
    struct path f_path;
    struct inode *f_inode;
    const struct file_operations *f_op;  // ⭐ 关键:文件操作表 ⭐
    spinlock_t f_lock;
    atomic_long_t f_count;
    unsigned int f_flags;
    fmode_t f_mode;
    loff_t f_pos;
    void *private_data;  // 驱动私有数据
    // ... 其他字段 ...
};

3. inode 结构

c 复制代码
struct inode {
    umode_t i_mode;
    const struct inode_operations *i_op;
    const struct file_operations *i_fop;  // ⭐ 设备类型对应的操作表 ⭐
    struct address_space *i_mapping;
    // ... 其他字段 ...
};
相关推荐
188号安全攻城狮20 分钟前
【PWN】HappyNewYearCTF_8_ret2csu
linux·汇编·安全·网络安全·系统安全
Yana.nice2 小时前
openssl将证书从p7b转换为crt格式
java·linux
AI逐月2 小时前
tmux 常用命令总结:从入门到稳定使用的一篇实战博客
linux·服务器·ssh·php
小白跃升坊2 小时前
基于1Panel的AI运维
linux·运维·人工智能·ai大模型·教学·ai agent
跃渊Yuey2 小时前
【Linux】线程同步与互斥
linux·笔记
舰长1152 小时前
linux 实现文件共享的实现方式比较
linux·服务器·网络
zmjjdank1ng3 小时前
Linux 输出重定向
linux·运维
路由侠内网穿透.3 小时前
本地部署智能家居集成解决方案 ESPHome 并实现外部访问( Linux 版本)
linux·运维·服务器·网络协议·智能家居
VekiSon3 小时前
Linux内核驱动——基础概念与开发环境搭建
linux·运维·服务器·c语言·arm开发
zl_dfq3 小时前
Linux 之 【进程信号】(signal、kill、raise、abort、alarm、Core Dump核心转储机制)
linux