当前位置: 首页 > news >正文

Linux 内核学习(7) --- 字符设备驱动

字符设备驱动程序

Linux 中主要有三类设备的驱动程序,分别是字符设备驱动程序,块设备驱动程序和网络设备驱动程序
字符设备是指在 I/O 传输过程中以字符为单位进行传输的设备,例如键盘,打印机等,字符设备的驱动程序结构如下图所示:
字符设备驱动.png

字符设备可以通过文件节点来访问,设备文件和普通文件差别在于对普通文件的访问可以前后移动访问位置,而大多数字符设备是一个只能顺序访问的数据通道,当然也存在数据区特性的字符设备,访问它们可以前后移动访问位置,比如FrameBuffer Device 就是这样一个设备,app 可以用 mmap 或者 lseek 访问获取整个图像
字符设备文件类型是 c(char),设备文件没有文件大小,取而代之的是两个号:主设备号次设备号

字符设备驱动程序实现逻辑

Linux 内部一切设备皆文件,所有的硬件设备操作到应用层都会抽象为文件的操作,Linux 访问文件的基本逻辑如下:

  1. Linux 文件系统中,每一个文件都用一个struct inode 结构体来描述,这个结构体里面记录这个文件的所有信息,比如文件类型,访问权限等
  2. Linux 操作系统中,每个驱动程序在应用层 /dev 目录下都会有一个设备文件和它对应,称为设备节点,该设备节点存在主设备号和次设备号
  3. 每打开一次文件,LinuxVFS 都会分配一个 struct file 结构体来描述打开的文件,该结构体用于维护文件打开的权限,文件指针偏移值,私有的内存信息等
    驱动程序的主设备号和次设备号信息保存在 struct cdev 结构中
struct cdev {struct kobject kobj;struct module *owner;const struct file_operations *ops;struct list_head list;dev_t dev;unsigned int count;
};
字符设备驱动的调用逻辑:
  1. open 函数打开设备文件,可以根据设备文件对应的 struct inode 结构体描述的信息,判断当前的要操作的设备类型,还会分配一个 struct file 结构体
  2. 根据 struct inode 结构中记录的设备号,可以找到对应的驱动程序,Linux 中每个字符设备都有一个 struct cdev 结构体来对应struct cdev 描述了字符设备的所有信息,其中最重要的就是字符设备的操作接口
  3. 找到 struct cdev 结构体后,linux 内核会将 struct cdev 结构体所在的内存空间首地址记录在 struct inode 结构体的 i_cdev 成员中,将 struct cdev 结构体中记录的函数操作接口记录在 struct file 结构体的 fops 成员中
  4. 任务完成,VFS 会给应用层返回一个文件描述符,这个 fd 是和 struct file 结构体想对应的,上层应用程序的调用就可以通过 fd 找到对应的 struct file,由 struct file 找到操作字符设备接口的函数了

代码路径:/include/linux/fs.h

struct file 类型的实现:

struct file {union {struct llist_node	fu_llist;struct rcu_head 	fu_rcuhead;} f_u;struct path		f_path;struct inode		*f_inode;	/* cached value */u64			f_version;const struct file_operations	*f_op;.../* needed for tty driver, and maybe others */void			*private_data;.....
};

private_data 可以用于保存驱动的私有数据,用于在驱动的各个操作函数之间共享
file 数据结构中包含 inode 数据结构,struct inode *f_inode
struct inode *inode = file_inode(file);

struct inode {umode_t			i_mode;unsigned short		i_opflags;kuid_t			i_uid;kgid_t			i_gid;unsigned int		i_flags;.....dev_t			i_rdev; //保存字符设备驱动设备号
}

其中的 dev_t i_rdev; 包含了设备的主次设备号
主次设备号 是 一个32位的数 12 位表示主设备号 20 位表示次设备号
/include/linux/fs.h 提供下面操作设备号的函数

// inlude/linux/kdev_t.h
#define MINORBITS	20
#define MINORMASK	((1U << MINORBITS) - 1)#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))//从 inode 中获取主次设备号 /include/linux/fs.h
static inline unsigned iminor(const struct inode *inode)
static inline unsigned imajor(const struct inode *inode)

struct file_operations 里面包含了驱动操作函数的函数指针

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 *);......
}

可以看到驱动程序的操作函数中,参数都会带有struct file *,指向内核分配的struct file 类型

编写字符设备驱动

  1. 实现驱动模块的加载和卸载入口函数
module_init(drm_core_init);
module_exit(drm_core_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxxxx");
MODULE_DESCRIPTION("A module used for show task of each thread!");

module_init(fbmem_init);
linux 中,所有标志为 __init 的函数在连接的时候都放在 .init.text 这个区段,此外,所有的__init 函数
在区段 .initcall.init 中还保存了一份函数指针,在初始化内核会通过这些函数指针调用这些 __init 函数,并
在初始化完成之后,释放 init区段,(包括 .init.text .initcall.init )

module_exit(fbmem_exit);
一般以 __exit 标志命名。 主要完成的工作:

  • 注销设备
  • 对于申请的内存需要动态释放
  • 释放硬件资源 终端 DMA通道 I/O 端口 I/O 内存管理
  • 开启了硬件一定要关闭
  1. 申请主设备号
extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
extern int register_chrdev_region(dev_t, unsigned, const char *);
  1. 手动/自动创建设备节点
    设备节点可以手动创建 mknod /dev/hello 250 0
    自动创建设备节点:
    Linux 系统中还存在 udev,mdev机制,可以遍历 /sys/class/xxx 下的 uevent 文件,根据这些这些 uevent 文件获取创建设备节点的信息,然后调用 mknod 程序在 /dev 下创建设备节点,结束之后,udev就开始等待内核空间的event
    创建设备类的一般流程:
static struct class *cls;
static struct device *test_device;devno = MKDEV(major,minor);
cls = class_create(THIS_MODULE, "democlass");
if(IS_ERR(cls)) {unregister_chrdev(major,"hello");return result;
}
test_device = device_create(cls, NULL, devno, NULL, "hellodevice");
if(IS_ERR(test_device )) {class_destroy(cls);unregister_chrdev(major,"hello");return result;
}
  1. 实现 file_operation
  • 定义一个结构体 static struct file_operations变量,在其中定义一些设备的打开,关闭,读,写,控制函数
    字符设备提供给应用程序的流控制接口有 open,close,read,write,ioctl;添加一个字符设备驱动程序的过程,实际上就是给上述操作添加代码的过程,Linux 对这些设备操作统一做了抽象
struct file_operations s3c_rotator_fops = {.owner      = THIS_MODULE,.open       = dev_fifo_open,.release    = xxxx,.mmap       = xxx,.ioctl      = xxxx,.poll       = xxxx,
};

相关文章:

  • 蓝牙L2CAP协议概述
  • 前端日常 · 移动端网页调试
  • C——函数递归
  • Vue 项目中二维码生成功能全解析
  • 数智管理学(八)
  • 今日行情明日机会——20250507
  • MySQL 联合查询的使用教程
  • 【C/C++】ARM处理器对齐_伪共享问题
  • 【多种不同提交方式】通过springboot实现与前端网页数据交互(非常简洁快速)
  • 计算机硬件(南桥):主板芯片组FCH和PCH的区别
  • 【渗透测试】命令执行漏洞的原理、利用方式、防范措施
  • draw.io流程图使用笔记
  • 蓝桥杯青少 图形化编程——“星星”点灯
  • MySQL数据库高可用(MHA)详细方案与部署教程
  • hadoop中的序列化和反序列化(3)
  • C# WPF 颜色拾取器
  • AI与情感计算:如何让机器更好地理解人类情感与情绪?
  • CATIA高效工作指南——零件建模篇(二)
  • docker host模式问题
  • 二叉树与优先级队列
  • 《2025城市青年旅行消费报告》发布,解码青年出行特征
  • 世界人形机器人运动会将在北京“双奥场馆”举行
  • 默茨当选德国总理
  • 山东滕州市醉驾交通事故肇事人员已被刑拘
  • 网友建议平顶山请刘昊然任旅游宣传大使,市委:有此设想,正申请经费
  • 新加坡总理黄循财领导人民行动党胜选,外交部回应