Linux Device Drivers-第三章 字符设备驱动程序

看了半个月了,但感觉自己这样毫无目标的看效率太低,网上看到大佬十年前就看英文原版书籍了,同时在做读书笔记,博客:(自由泳的青蛙)实在惭愧。我还想着看懂了就看,看不懂就跳过去呢,不行,得定有输出才行。

看了前辈的文章,每篇文章不长,但都有自己的思考,总结,我也不想直接抄书,那样毫无效果,定个目标:每天我看完一些内容,如果这部分内容有代码实现效果的就先列出代码看效果,无法看代码效果的,如果有有价值的代码如源码,结构体,并介绍,如果不懂的就去百度,如果还不懂先放个链接,从今天开始,重新阅读总结第三章《字符设备驱动程序》,之前的简略笔记如下:


《第二章 构造和运行模块》

编写代码多用kalloc,为什么,因为栈空间很小。

  1. insmod工具与modprobe工具的区别:modprobe会把与当前module引用到的module也安装上(就是层叠驱动),用于安装标准moduel.相当于调用了多次insmod.

  2. 学习编写驱动代码时为什么#include <linux/module.h> (包含需要的大量符号和定义)和 #include <linux/init.h>是必须的。(37)

  3. MODULE_LICENSE("GPL") 内核能识别通用公用许可证。

  4. 内核API中 __ 两个下划线起始的函数标明是底层组件,谨慎使用,后果自负。

  5. __init 与 __exit对初始化函数和清除函数的作用:告诉内核运行完就可以释放函数释放的内存了,不是必须但建议使用。

  6. EXPORT_SYMBOL 声明的函数或变量就可以被其他模块调用了,可以通过 grep EXPORT_SYMBOL 了解一个驱动程序提供了哪些入口点。

  7. 内核代码为什么一般都有返回值需要检查? 因为大部分函数都需要kmalloc申请内存,如果没申请到指针可能指向了一段错误地址,内核将处于不稳定状态。

初始化过程错误处理:

  1. 使用goto很方便,其他时候虽然goto尽量少用。(模块初始化遇到错误时,必须自行清除已经注册的设备,否则内核处于不稳定状态)

  2. insmod和modprobe可以在装载模块时候为模块输入参数,有例程。

模块参数:

  1. 模块在通过insmod输入时可以同时手动传入参数。(实验手动指定打印多少次指定字符串)

快速参考:

将本章涉及到的函数接口以及标准目录等汇总,介绍。


《第三章 字符设备驱动程序》

scull的设计

  1. 对scull实现的设备类型做了讲解,不太懂???

主次设备号

  1. 如何访问字符设备:通过访问"特殊文件/设备文件"来访问字符设备,通常位于/dev目录,ls -l命令中输出的第一列中的'c'来识别(块设备为b)出这是个字符设备。

  2. ls -l 如何看字符设备的主次设备号

  3. 次设备号用于获得指向内核设备的直接指针

设备编号的内部表达

  1. dev_t 配合两个宏用来存取设备号,这个宏有32位,12位存储主设备好,20位存储次设备号。

分配和释放设备编号

  1. int register_chrdev_region(dev_t first, unsigned int count, char *name); 与 int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name); 这俩是分配设备编号的接口,之所以有"动态分配设备编号"接口是因为我们往往不知道设备将要使用哪些主设备号。

动态分配主设备号

讲解为什么要使用动态分配主设备号好。及代码。

2重要数据结构-文件操作

讲解了一堆内核接口

2重要数据结构-file结构

  1. <linux/fs.h>中定义的struct file与用户空间常用的在C库定义的FILE没有任何关系。file结构代表一个打开的文件,内核在open时创建,在close时关闭。

2重要数据结构-inode结构

  1. 内核用inode表示文件,对单个文件,可能有多个表示打开文件描述符的file结构,但他们都指向单个inode结构。

  2. 其中 dev_t i_rdev 表示设备文件的inode结构体,包含真正的设备编号。

  3. struct cdev *i_cdev;struct cdev是表示字符设备的内核的内部结构,当inode指向一个字符设备文件时,该字段包含了指向struct cdev结构的指针。

3字符设备的注册-新方法

  1. 已知struct cdev表示字符设备,故需要先申请,初始化,通知内核,移除。(涉及内核接口函数cdev_alloc,cdev_init, cdev_add,cdev_del);

3字符设备的注册-老方法(不用了快)

  1. register_chardev 与 unregister_chardev

4open和release

讲了scull设备的open函数,也有代码,但看完了不明白,很多概念不懂。

本章目标

编写一个模块化的字符设备驱动程序。取自scull(Simple Character Utility for Loading Localities), 我们将在计算机内存之上实现若干设备的抽象,让scull为编写真正的驱动程序提供一个样板。

基础知识

/proc/devices: 列出当前内核已经注册的所有字符设备和块设备的主设备号以及对应的设备名称。提供了内核对设备驱动程序的快照。作用是显示内核已经识别的设备类型。告诉你哪些类型的主设备号是可用的,以及这些设备号对应的设备类型。

/dev:

设备文件,也经常叫设备节点。代表系统中实际的设备实例,可以是字符设备或块设备。

在/proc/devices中列出的设备类型并不意味着在/dev/目录下一定存在对应的设备文件。例如,一个设备类型可能存在,但由于没有对应的硬件接入或驱动程序没有加载,因此没有在/dev/目录下创建相应的设备文件。

大佬博客:https://www.cnblogs.com/johnnyflute/p/3969774.html

1 主设备号与次设备号

linux的设备管理与文件系统关系密切,通过访问文件系统内的设备文件来完成对字符设备的访问,设备文件通常位于/dev目录。

  • ls -l 命令执行后列出第一列如果有c则是字符设备
  • 在/dev目录执行ls -l 后,列出的时间之前的两列分别是主设备号和次设备号
  • 主设备号表示哪一类设备
  • 次设备号区分同一类设备中的不同设备,由内核使用
  • 对于常用设备,linux有约定俗称的规定,如硬盘的主设备号是3

1.1 设备号的内部表达

内核中dev_t类型来保存设备号 。dev_t在2.6.0版本内核中用12位来表示主设备号其余位表示次设备号,但其他版本可不一定,我们得用<linux/kdev_t.h>中的宏。

c 复制代码
MAJOR(dev_t dev);//获得主设备号
MINOR(dev_t dev);//获得次设备号
MKDEV(int major, int minor);//将主设备与次设备转化成dev_t类型

1.2 释放和分配设备编号

设备号是内核为设备分配的唯一标识符。建立字符设备号第一步:给目标设备分个设备号。当然结束时还要释放设备号:

c 复制代码
int register_chrdev_region(dev_t first, unsigned int count, char *name);
dev_t first: 自己需要自己知道主设备号,次设备号一般为0
unsigned int count: 请求的连续的设备号的个数
char *name: 该编号范围关联的设备名称,将出现在/proc/devices和sysfs中
return: 成功返回0,否则返回负值

int alloc_chrdev_region(dev_t *dev, unsigned int firstninor, unsigned int count, char *name);
dev_t *dev: 输出参数,返回分配范围第一个编号
unsigned int firstninor: 请求的第一个次设备号,通常为0
return: 成功返回0,否则返回负值

void unregister_chardev_region(dev_t first, unsigned int count);

上面这些函数只是向内核申请了设备编号,但没有告诉内核拿这些设备号干嘛,想知道干嘛还得继续看。

1.3动态分配主设备号

这节主要讲解要使用动态分配的方法,即用alloc_chrdev_region而非register_chrdev_region。 若该驱动module被其他人广泛使用,那么无法保证通过后者注册的设备号是其他人的Linux系统中未分配使用的设备号。

2 一些重要数据结构

2.1 文件操作 file_operations(linux/fs.h)

  • 指向这类结构的指针被称为fops
  • 许多包含__user字符串的参数指示提醒该参数为用户态指针,无其他作用

2.2 file结构(linux/fs.h)

  • file仅仅出现在内核代码中
  • file结构与用户空间FILE无关
  • file结构表示一个打开的文件,系统中每个打开的文件在内核中都有一个对应的file结构(由内核在open时创建,在close时释放)
  • 指向struct file的指针被称为filp

一些重要结构如下:

c 复制代码
struct file {
	fmode_t			f_mode; //标记访问权限
	loff_t			f_pos;  //当前读写位置
	unsigned int		f_flags;	//检查用户是否是非阻塞操作
	struct inode		*f_inode;	/* cached value */
	const struct file_operations	*f_op;  //这个眼熟,open时赋值
	/* needed for tty driver, and maybe others */
	void			*private_data;

#ifdef CONFIG_EPOLL
	/* Used by fs/eventpoll.c to link all the hooks to this file */
	struct hlist_head	*f_ep;
#endif /* #ifdef CONFIG_EPOLL */
	struct address_space	*f_mapping;
	errseq_t		f_wb_err;
	errseq_t		f_sb_err; /* for syncfs */
} __randomize_layout

2.3 inode结构(linux/fs.h)

  • 对单个文件可能由许多表示打开文件描述符的file结构,但他们都指向同一个inode结构。
  • inode获得主次设备号应使用下面两个宏,不要直接操作inode的i_rdev指针。
  • 其中的 i_cdev 指针指向字符设备的内核的内部结构 struct cdev
c 复制代码
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);

一些重要结构罗列如下(这个结构体老长了):

c 复制代码
struct inode {
	dev_t			i_rdev;//设备文件的inode结构,包含了真正的设备编号
	union {
		struct pipe_inode_info	*i_pipe;
		struct cdev		*i_cdev; //字符设备的内核内部结构
		char			*i_link;
		unsigned		i_dir_seq;
	};

} __randomize_layout;

3 字符设备的注册

  • 刚说了内核内核使用'struct cdev' 表示字符设备,所以需要分配一个这样的结构。
  • <linux/cdev.h>中包含了与cdev结构分配相关的辅助函数,cdev结构体如下
c 复制代码
struct cdev {
	struct kobject kobj;
	struct module *owner;
	const struct file_operations *ops;
	struct list_head list;
	dev_t dev;
	unsigned int count;
} __randomize_layout;

分配和初始化上述结构体的方法有两种,第一种:

c 复制代码
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = my_fops;

/* 然后需要用cdev_init初始化 */
void cdev_init(strue cdev *cdev, struct file_operations *fops);
/* 然后初始化struct cdev的owner字段 */
my_cdev->owner = THIS_MODULE;
/* 然后告诉内核该结构的信息 */
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);

/* 从系统中删除一个字符设备 */
void cdev_del(struct cdev *dev);

第二种:

c 复制代码
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
int unregister_chrdev(unsigned int major, const char *name);
//major是主设备号的名字
//name是驱动程序的名字,将出现在/proc/devices中

4 open 和 release

4.1 open 方法

c 复制代码
int scull_open(struct inode *inode, struct file *filp)
{
	struct scull_dev *dev; /* device information */
	dev = container_of(inode->i_cdev, struct scull_dev, cdev);
	filp->private_data = dev; /* for other methods */
	/* now trim to 0 the length of the device if open was write only */
	if ( (filp->flags & O_ACCMODE) == O_WRONLY) {
		scull_trim(dev); /* ignore errors */
	}
	return 0;
}

4.2 release 方法

  • 释放由open分配的,保存在filp->private_data中的所有内容
  • 在最后一次关闭操作时关闭设备
c 复制代码
int scull_release(struct inode *inode, struct file *filp){
	return 0;
}
相关推荐
意疏3 分钟前
【Linux 篇】Docker 的容器之海与镜像之岛:于 Linux 系统内探索容器化的奇妙航行
linux·docker
BLEACH-heiqiyihu10 分钟前
RedHat7—Linux中kickstart自动安装脚本制作
linux·运维·服务器
我的K84092 小时前
Flink整合Hudi及使用
linux·服务器·flink
1900432 小时前
linux6:常见命令介绍
linux·运维·服务器
Camellia-Echo2 小时前
【Linux从青铜到王者】Linux进程间通信(一)——待完善
linux·运维·服务器
Linux运维日记2 小时前
k8s1.31版本最新版本集群使用容器镜像仓库Harbor
linux·docker·云原生·容器·kubernetes
我是唐青枫2 小时前
Linux dnf 包管理工具使用教程
linux·运维·服务器
编程修仙3 小时前
Collections工具类
linux·windows·python
芝麻团坚果3 小时前
对subprocess启动的子进程使用VSCode python debugger
linux·ide·python·subprocess·vscode debugger
写点什么啦4 小时前
[debug]不同的window连接ubuntu的vscode后无法正常加载kernel
linux·vscode·ubuntu·debug