Linux中字符设备的打开、写入

一个内核模块应该由以下几部分组成。

第一部分,头文件部分。一般的内核模块,都需要 include 下面两个头文件:

复制代码
#include <linux/module.h>
#include <linux/init.h>

第二部分,定义一些函数,用于处理内核模块的主要逻辑。例如打开、关闭、读取、写入设备的函数或者响应中断的函数。

例如,logibm.c 里面就定义了 logibm_open。logibm_close 就是处理打开和关闭的,定义了 logibm_interrupt 就是用来响应中断的。再如,lp.c 里面就定义了 lp_read,lp_write 就是处理读写的。

第三部分,定义一个 file_operations 结构。设备是可以通过文件系统的接口进行访问的。对于某种文件系统的操作,都是放在 file_operations 里面的。例如 ext4 就定义了这么一个结构,里面都是 ext4_xxx 之类的函数。设备要想被文件系统的接口操作,也需要定义这样一个结构。

例如,lp.c 里面就定义了这样一个结构。

复制代码
static const struct file_operations lp_fops = {
  .owner    = THIS_MODULE,
  .write    = lp_write,
  .unlocked_ioctl  = lp_ioctl,
#ifdef CONFIG_COMPAT
  .compat_ioctl  = lp_compat_ioctl,
#endif
  .open    = lp_open,
  .release  = lp_release,
#ifdef CONFIG_PARPORT_1284
  .read    = lp_read,
#endif
  .llseek    = noop_llseek,
};

在 logibm.c 里面,我们找不到这样的结构,是因为它属于众多输入设备的一种,而输入设备的操作被统一定义在 drivers/input/input.c 里面,logibm.c 只是定义了一些自己独有的操作。

复制代码
static const struct file_operations input_devices_fileops = {
  .owner    = THIS_MODULE,
  .open    = input_proc_devices_open,
  .poll    = input_proc_devices_poll,
  .read    = seq_read,
  .llseek    = seq_lseek,
  .release  = seq_release,
};

第四部分,定义整个模块的初始化函数和退出函数,用于加载和卸载这个 ko 的时候调用。

例如 lp.c 就定义了 lp_init_module 和 lp_cleanup_module,logibm.c 就定义了 logibm_init 和 logibm_exit。

第五部分,调用 module_init 和 module_exit,分别指向上面两个初始化函数和退出函数。

第六部分,声明一下 lisense,调用 MODULE_LICENSE。有了这六部分,一个内核模块就基本合格了,可以工作了。

打开字符设备

在字符设备驱动的内核模块加载的时候,最重要的一件事情就是,注册这个字符设备。注册的方式是调用 __register_chrdev_region,注册字符设备的主次设备号和名称,然后分配一个 struct cdev 结构,将 cdev 的 ops 成员变量指向这个模块声明的 file_operations。然后,cdev_add 会将这个字符设备添加到内核中一个叫作 struct kobj_map *cdev_map 的结构,来统一管理所有字符设备。

写入字符设备

写入一个字符设备,就是用文件系统的标准接口 write,参数文件描述符 fd,在内核里面调用的 sys_write,在 sys_write 里面根据文件描述符 fd 得到 struct file 结构。

一个字符设备要能够工作,需要三部分配合。

第一,有一个设备驱动程序的 ko 模块,里面有模块初始化函数、中断处理函数、设备操作函数。这里面封装了对于外部设备的操作。加载设备驱动程序模块的时候,模块初始化函数会被调用。在内核维护所有字符设备驱动的数据结构 cdev_map 里面注册,我们就可以很容易根据设备号,找到相应的设备驱动程序。

第二,在 /dev 目录下有一个文件表示这个设备,这个文件在特殊的 devtmpfs 文件系统上,因而也有相应的 dentry 和 inode。这里的 inode 是一个特殊的 inode,里面有设备号。通过它,我们可以在 cdev_map 中找到设备驱动程序,里面还有针对字符设备文件的默认操作 def_chr_fops。

第三,打开一个字符设备文件和打开一个普通的文件有类似的数据结构,有文件描述符、有 struct file、指向字符设备文件的 dentry 和 inode。字符设备文件的相关操作 file_operations 一开始指向 def_chr_fops,在调用 def_chr_fops 里面的 chrdev_open 函数的时候,修改为指向设备操作函数,从而读写一个字符设备文件就会直接变成读写外部设备了。

此文章为11月Day12学习笔记,内容来源于极客时间《趣谈Linux操作系统》,推荐该课程。

相关推荐
jimy17 小时前
安卓里运行Linux
linux·运维·服务器
爱凤的小光8 小时前
Linux清理磁盘技巧---个人笔记
linux·运维
耗同学一米八9 小时前
2026年河北省职业院校技能大赛中职组“网络建设与运维”赛项答案解析 1.系统安装
linux·服务器·centos
知星小度S10 小时前
系统核心解析:深入文件系统底层机制——Ext系列探秘:从磁盘结构到挂载链接的全链路解析
linux
2401_8904430210 小时前
Linux 基础IO
linux·c语言
智慧地球(AI·Earth)11 小时前
在Linux上使用Claude Code 并使用本地VS Code SSH远程访问的完整指南
linux·ssh·ai编程
老王熬夜敲代码12 小时前
解决IP不够用的问题
linux·网络·笔记
zly350012 小时前
linux查看正在运行的nginx的当前工作目录(webroot)
linux·运维·nginx
QT 小鲜肉12 小时前
【Linux命令大全】001.文件管理之file命令(实操篇)
linux·运维·前端·网络·chrome·笔记
问道飞鱼13 小时前
【Linux知识】Linux 虚拟机磁盘扩缩容操作指南(按文件系统分类)
linux·运维·服务器·磁盘扩缩容