Linux 应用程序如何操作底层硬件

本文梳理,在 Linux 系统下应用程序是如何一步步操作底层硬件设备的。了解这一流程,是理解 Linux 驱动开发的第一步。


01 用户空间与内核空间的隔离

在 Linux 系统中,出于对系统稳定性和安全性的保护,程序的运行空间被严格划分为了两部分:

  • 应用程序:运行在 用户空间(User Space)
  • Linux 驱动:属于内核的一部分,运行在 内核空间(Kernel Space)

⚠️ 用户空间是不能直接对内核空间进行操作的。

因此,应用程序必须通过 「系统调用」(System Call),实现从用户空间「陷入」(Trap)到内核空间,从而间接控制底层硬件。


02 file_operations 结构体

在 Linux 内核源码的 include/linux/fs.h 文件中,定义了一个至关重要的结构体:file_operations

💡 重点:它包含了所有的 Linux 内核驱动操作函数。可以说,它是连接应用层系统调用和底层驱动程序的核心「桥梁」。

在这里插入图片描述

核心源码剖析

struct file_operations {

  // 拥有该结构的模块的指针,一般为 THIS_MODULES
  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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

  // 初始化一个异步的写入操作
  ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

  // 仅用于读取目录,对于设备文件该字段为 NULL
  int (*readdir) (struct file *, void *, filldir_t);

  // 轮询函数,判断目前是否可以进行非阻塞的读写或写入
  unsigned int (*poll) (struct file *, struct poll_table_struct *);

  // 执行设备 I/O 控制命令
  int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

  // 不使用 BLK 文件系统时,使用此函数指针代替 ioctl
  long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

  // 在 64 位系统上,32 位的 ioctl 调用将使用此函数指针代替
  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 *, struct dentry *, int datasync);
  int (*aio_fsync) (struct kiocb *, int datasync);

  // 通知设备 FASYNC 标志发生变化
  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, unsigned long, unsigned long, unsigned long);
  int (*check_flags)(int);
  int (*flock) (struct file *, int, struct file_lock *);
  ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
  ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
  int (*setlease)(struct file *, long, struct file_lock **);

};

03 核心理念:一切皆文件

最基础的设备:字符设备

在 Linux 驱动硬件层中,最基本、最常见的就是字符设备(如常见的按键、IIC、SPI、LCD 屏幕等)。

工作特点:数据按字节流的方式进行读写(一个字节接着一个字节,严格区分先后顺序)。

Linux 核心哲学:「一切皆文件」

当与硬件相对应的驱动程序加载成功后,系统就会在 /dev 目录下自动生成对应的设备文件(例如 /dev/xxx)。

对于上层的业务应用程序来说
👉 操作这个文件,就等同于操作底层的硬件。

举个例子
应用程序使用 open 操作文件,使用 write 向文件写入数据(底层触发硬件动作,如点亮灯),使用 read 读取状态。底层硬件的复杂逻辑对应用层完全透明。

定期更新一些简单的技术干货或者开发踩坑历程,欢迎大家关注我~(/ω\)
在这里插入图片描述

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐