00_linux_最简单构建字符设备 2.4版本之前使用

背景:怎么构建一个最简单的字符设备驱动并且可以使用app进行操作
2.4和2.6不同其实也就是构造设备节点的时候 一个是手动构造,一个是kobj构造

名称大致意思
设备proc/devices/设备名称 insmode驱动
设备节点/dev/xxx 对这个设备进行操作的文件 mknode使用主次设备号对设备关联
设备文件/sys/设备文件/设备属性 另一种操控驱动的方案,使用设备属性操作 设备文件关联 kobj
设备属性sys/设备文件/设备属性 一个设备文件有多个设备属性 设备属性关联 kobj_attr

大致方法
1.写驱动文件(file_operation),构造对应的read write 函数
2.分配设备名称(/proc/devices/设备名称)
3.注册进内核 使用主次设备号,设备名称 ,insmode 注册后 在/proc/devices/设备名称 就能找到对应设备
4.对这个设备进行设备文件注册,mknode 命令,生成 /dev/设备文件名称
5.应用程序打开 设备文件 获得设备的 file_operation 结构体 对设备进行操作

1实现底层驱动程序

linux中万物皆为文件
图中的一个设备会有各种调用参数 open read write 来组成这个设备的 file_operation结构体
内核层中 在驱动程序里面 各种调用参数来对底层的硬件进行操作 作为驱动程序注册进内核变成一个设备 也会有 open read write等操作接口

2 注册这个驱动程序到内核变成一个设备

开始有了驱动程序的 file_operation结构体
主次设备号 和 file_operation 结构体进行内核中的注册
注册到内核中 变成一个设备驱动程序

major = register_chrdev(DEV_MAJOR, DEV_NAME, &led_fops); //注册到内核

注册到内核后 有了 proc/devices/DEV_NAME

mknod [选项]… 名称 类型 [主设备号 次设备号]

2.1 驱动层注册详细描述

其实在驱动层中是构造了一个char_device_struct 结构体,char_device_struct 结构体继承了驱动结构体
char_device_struct 是驱动的基本对象, 把这个对象保存在内核中

static struct char_device_struct {
	//指向下一个链表节点
    struct char_device_struct *next;
	//主设备号
    unsigned int major;
	//次设备号
    unsigned int baseminor;
	//次设备号的数量
    int minorct;
	//设备的名称
    char name[64];
	//内核字符对象(已废弃)
    struct cdev *cdev;      /* will die */

} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
3 对设备构建设备文件

上一步已经有proc/devices/DEV_NAME
对这个DEV_NAME设备构建出对应的设备文件进行操作
使用mkmod /dev/test c 2 0 命令 构建对应设备的设备文件,通过查找内核设备所有的主次设备号挑选出对应的 设备
/dev/test 设备文件的文件名
c 文件为字符设备文件
2 主设备号
0 次设备号

4 应用程序使用设备文件进行操作

应用程序是一个进程
进程有自己的结构体 task_srtuct
在这里插入图片描述

task_srtuct->files_struct->fd_array[]->file_operations
//里面保存了打开的文件 inode 节点  对这个节点 进行 write read 操作

使用 open(/dev/xxx) 打开这个文件 把这个文件的file_operation 保存在 inode 数组里面
后续使用 read write 调用 inode节点的各种操作函数 对文件 /dev/xxx 这个设备文件进行操作

详细介绍

总结 : 写驱动程序 就是为了让app操作 驱动程序中的file_operation接口
所以需要把驱动程序中的file_operation结构体注册到内核 内核有两个哈希表chrdevs[256],和cdev_map->probe[256]表
chrdevs哈希表 记录 char_device_struct结构体指针 新增一个字符设备,需要查询哈希表里面有没有
cdev_map->probe哈希表 记录probe结构体指针 用于记录驱动里file_operattion 所以我们根据主次设备号就能找到 要操作的file_operation结构体
app使用open打开的文件 抽象为inode结构体 对这个文件的操作接口file_operation也记录在 inode结构体上
这样就能通过app进行操作了
下面用几个问答尽量说明白

怎么把自己写的file_operation结构体注册到内核

把file_operations文件操作接口注册到内核,内核通过主次设备号来登记记录它
struct cdev 记录操作接口 ,继承kobj

struct cdev {
	struct kobject kobj;
	struct module *owner;
	const struct file_operations *ops;
	struct list_head list;
	dev_t dev;
	unsigned int count;
};
//这个函数初始化cdev,赋值驱动里自己写的file_operation结构体
cdev_init(struct cdev *cdev, const struct file_operations *fops)

用 file_operation 的时候通过两个哈希表来找到 自己写的file_operation
这时候需要引入两个全局变量 chrdevs 和 struct kobj_map *cdev_map
里面保存了两个哈希表chrdevs[255],cdev_map->probe[255]

第一个哈希表 chrdevs[255]:登记设备号
注册方法 register_chrdev() 每次新增一个字符设备,需要查询哈希表里面有没有
__register_chrdev_region()
存在意义 : 保存字符设备,新增字符设备看放哪个地方,方便查询
第二个 cdev_map->probe:保存驱动基本对象struct cdev
注册方法 cdev_add()
存在意义:根据主次设备号,找到cdev,找到file_operation结构体
通过两个哈希表已经保存了cdev 保存了主次设备号 还有自己file_operation结构体
这时候需要 mknod指令+主从设备号 建立设备节点
把自己file_operation结构体和设备节点绑定
通过主次设备号在cdev_map中找到cdev->file_operations
把cdev->file_operations绑定到新的设备节点中

设备号和哈希表是什么关系,和/proc/devices关系

在这里插入图片描述
register_chrdev() cdev_add() 注册设备号后
已经注册了的设备号 可以使用cat /proc/device 个人感觉把哈希表1列出来
看见对应的名字和主设备号
内核希望一个驱动file_opera 占一个主设备号 多个次设备号

源码中怎么管理设备号(哈希表1)

char_device_struct 关键数据结构 作为linux哈希表1的链表组成

static struct char_device_struct {
	//指向下一个链表节点
    struct char_device_struct *next;
	//主设备号
    unsigned int major;
	//次设备号
    unsigned int baseminor;
	//次设备号的数量
    int minorct;
	//设备的名称
    char name[64];
	//内核字符对象(已废弃)
    struct cdev *cdev;      /* will die */

} *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; //初始化 指针数组  256的数组 都存了结构体指针 struct char_device_struct 

//下面是往表中注册新设备 根据主次设备号 名字 新增char_device_struct 
register_chrdev()	
	__register_chrdev_region(unsigned int major, unsigned int baseminor, int minorct, const char *name) 
										//保存新注册的设备号到chrdevs哈希表中,防止设备号冲突
											//这四个函数参数都用来构建 新char_device_struct 结构体 存入这个结构体到哈希表
		struct char_device_struct *cd, **cp;																	
		cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);//申请内存
		mutex_lock(&chrdevs_lock);//增加互斥锁,这个锁也是全局变量
		if (major == 0)
			major = find_dynamic_major();//为0在chrdevs表中找一个空闲位置
			
		//保存主次设备号和设备个数
		cd->major = major;
		cd->baseminor = baseminor;
		cd->minorct = minorct;
		//保存设备名称
		strlcpy(cd->name, name, sizeof(cd->name));
		//计算主设备号对应哈希表的位置
		i = major_to_index(major);
		//遍历链表元素指针 如果主设备号是0,遍历的就是chrdevs[0]->char_device_struct链表
		for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
			if ((*cp)->major > major ||
			    ((*cp)->major == major &&
			     (((*cp)->baseminor >= baseminor) ||
			      ((*cp)->baseminor + (*cp)->minorct > baseminor))))
				break;
		//主设备号相等,看次设备号是不是冲突
		/*  */
		if (*cp && (*cp)->major == major) {
			int old_min = (*cp)->baseminor;
			int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
			int new_min = baseminor;
			int new_max = baseminor + minorct - 1;
		//存入新的char_device_struct指针到哈希表中
		cd->next = *cp;
		*cp = cd;
怎么保存file_operation接口(哈希表2)

如果以后想使用fie_operation接口,可以通过设备号在probes哈希表(哈希表2)中
找到probe->data指向的cdev结构体,再找到file_operation

//struct cdev字符设备管理对象 继承kobj
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;
//哈希表probes
struct kobj_map {
	struct probe {  //内嵌结构体
		//指向下一个链表节点
		struct probe *next;
		//设备号
		dev_t dev;
		//次设备号的数量
		unsigned long range;
		struct module *owner;
		kobj_probe_t *get;
		int (*lock)(dev_t, void *);
		//空指针,内核常用技巧,用来保持sruct cdev 指针 就能拿到file_operation 结构体
		void *data;
	} *probes[255];  //这里也就是probes哈希表,第二个哈希表,用来记录file_operattion
	struct mutex *lock;
};
//往哈希表2增加新的设备
//这个函数初始化cdev,赋值file_operation结构体
cdev_init(struct cdev *cdev, const struct file_operations *fops)
	kobject_init(&cdev->kobj, &ktype_cdev_default); //初始化一个kobj(仅仅初始化,没有绑定目录等)
	cdev->ops = fops;//给cdev的ops指针赋值 自己的file_operation接口
//继续初始化一下cdev,把cdev放入probes哈希表
cdev_add(struct cdev *p, dev_t dev, unsigned count) //接受cdev结构体,设备号,数量
	p->dev = dev;  //保存设备号
	p->count = count; //保存设备号和设备号的数量
	error = kobj_map(cdev_map, dev, count, NULL, //把cdev结构体填充到probes哈希表上
							exact_match, exact_lock, p);
		unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;  //判断次设备号是否溢出
		if (n > 255) //判断主设备号是否大于255
		p = kmalloc_array(n, sizeof(struct probe), GFP_KERNEL); //申请内存
		//申请完后给probe结构体赋值
		p->owner = module;
		p->get = probe;
		p->lock = lock;
		p->dev = dev;  //保存设备号
		p->range = range; //保存本次设备号数量
		p->data = data; //这里保存了cdev结构体
		struct probe **s = &domain->probes[index % 255]; //保存在probes哈希表中
	kobject_get(p->kobj.parent);
自己怎么创建设备节点 怎么和file_operation连接

新版本中使用kobj创建设备节点
这里说老版本的 linux指令 mkmod /dev/test c 2 0
图呢? 结构体呢!!!
用户空间 : mknod命令->glibc库mknode()->sys_mkonde
内核空间 : sys_mknodat()->vfs_mknod()->dir->i_op->mknod(ext4_mknod)->init_special_inode()
init_special_inode()
inode类型 如果是字符设备类型 def_chr_fops作为该文件的操作接口
设备号记录在inode->i_rdev
自己构建的file_operation等在应用程序调用open函数之后,才会绑定在文件上
所以目前自己的设备节点连接的file_operation是 通用的def_chr_fops

struct inode {
	xxx......
	dev_t			i_rdev;
	struct inode_operations	*i_op;
	struct cdev		*i_cdev;
	}
app程序怎么找到驱动程序里的file_operation结构体

一个文件对应着一个inode 设备节点中的字符设备通用file_operation对inode里面的设备号查找
找出驱动程序真正的 file_operation结构体 结构体进行替换
再把真正的结构体保存在进程的数组 直接调用
在这里插入图片描述

fd = open("/dev/xxx",O_RDWR)
驱动层
sys_open()
	do_sys_open()
		get_unused_fd_flags()
		do_dentry_open(struct file *f,struct inode *inode,int (*open)(struct inode *, struct file *))
			/*把inode的i_fop赋值给struct file的f_op*/
			f->f_op = fops_get(inode->i_fop);
			open = f->f_op->open; //这里的f_op 是上面的def_chr_fops
			error = open(inode, f);
				def_chr_fops->chrdev_open()!!!!
					const struct file_operations *fops;
					struct cdev *p;
					struct kobject *kobj;
					struct cdev *new = NULL;
					/* 内核哈希表 cdev_map 中,根据设备号查找自己注册的sturct cdev,获取cdev中的file_operation接口
					        不懂怎么保存在哈希表的回去看 保持哈希表 */
					kobj = kobj_lookup(cdev_map, inode>i_rdev,&idx);//cdev_map全局变量,能知道内核哈希表
														现在得到cdev里面的kobj成员
					new = container_of(kobj, struct cdev, kobj);//从kobj成员得到cdev变量
					inode->i_cdev = p = new;//把cdev赋值给inode节点
					fops = fops_get(p->ops);//获取到自己设计的file_o
					peration结构体
					/*把cdev中的file_operation接口赋值给struct file的f_op*/
					replace_fops(filp, fops);
					/*调用自己实现的file_operation接口中的open函数*/
					filp->f_op->open(inode, filp);
					/* 后续对inode节点的操作,都是调用自己的file_operation结构体了 */
		fd_install()
新旧的差距在创建设备节点,那到底差哪
新的方法

class_create()创建目录 /sys/class/xxx 同时返回class对象
device_create()创建目录 创建/sys/class/xxx/yyy目录 同时返回device对象
虽然class_create() 和 device_create() 都能创建目录项
但device_create()不止创建了 /sys/class/xxx/yyy 还在yyy目录下创建了属性文件(/sys/class/xxx/yyy/dev)
属性文件记录硬件设备的设备号
创建完属性文件后 继续使用kobject_uevent()发送驱动添加的消息 给用户空间的udev守护进程
这时udev会找到创建的yyy 读取dev这个设备属性文件夹 获得硬件设备的设备号
udev 调用mknod()函数 来创建/dev/yyy的设备节点
新旧本质 到最后都调用了mknode()函数
只不过新方法的函数参数 放在(/sys/class/xxx/yyy/dev)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值