lv15 驱动高级设备模型 1

之前的驱动操作称为硬编

一、起源

仅devfs(dev目录),导致开发不方便以及一些功能难以支持:

  1. 热插拔(如何插入一个设备然后找到设备的驱动应用到程序中)

  2. 不支持一些针对所有设备的统一操作(如电源管理)

  3. 不能自动mknod

  4. 用户查看不了设备信息(必须cat /proc/devices 查看组设备号)

  5. 设备信息硬编码,导致驱动代码通用性差,即没有分离设备和驱动

二、新方案

总设计原则:

uevent机制:sysfs + uevent + udevd(上层app)

2.1 sysfs: 一种用内存模拟的文件系统,系统启动时mount到/sys目录

sysfs 是 Linux 内核提供的一种虚拟文件系统,它将内核中设备、驱动程序和总线等信息以文件和目录的形式呈现给用户空间。当有新的设备插入或移除时,相关的信息会被添加、修改或删除,并通过 uevent 发送到用户空间。

sysfs用途:(类似于windows的设备管理器)

  1. 建立系统中总线、驱动、设备三者之间的桥梁

  2. 向用户空间展示内核中各种设备的拓扑图

  3. 提供给用户空间对设备获取信息和操作的接口,部分取代ioctl功能

sysfs在内核中的组成要素 在用户空间/sys下的显示
内核对象(kobject,可以包含多个子目录) 目录
对象属性(attribute) 文件
对象关系(relationship) 链接(Symbolic Link)

四个基本结构

类型 所包含的内容 内核数据结构 对应/sys项
设备(Devices) 设备是此模型中最基本的类型,以设备本身的连接按层次组织 struct device /sys/devices/?/?/.../
驱动(Drivers) 在一个系统中安装多个相同设备,只需要一份驱动程序的支持 struct device_driver /sys/bus/pci/drivers/?/
总线(Bus) 在整个总线级别对此总线上连接的所有设备进行管理 struct bus_type /sys/bus/?/
类别(Classes) 这是按照功能进行分类组织的设备层次树;如 USB 接口和 PS/2 接口的鼠标都是输入设备,都会出现在/sys/class/input/下 struct class /sys/class/?/

目录组织结构:

/sys下的子目录 所包含的内容
/sys/devices 这是内核对系统中所有设备的分层次表达模型,也是/sys文件系统管理设备的最重要的目录结构;
/sys/dev 这个目录下维护一个按字符设备和块设备的主次号码(major:minor)链接到真实的设备(/sys/devices下)的符号链接文件;
/sys/bus 这是内核设备按总线类型分层放置的目录结构, devices 中的所有设备都是连接于某种总线之下,在这里的每一种具体总线之下可以找到每一个具体设备的符号链接,它也是构成 Linux 统一设备模型的一部分;
/sys/class 这是按照设备功能分类的设备模型,如系统所有输入设备都会出现在/sys/class/input 之下,而不论它们是以何种总线连接到系统。它也是构成 Linux 统一设备模型的一部分;
/sys/kernel 这里是内核所有可调整参数的位置,目前只有 uevent_helper, kexec_loaded, mm, 和新式的slab 分配器等几项较新的设计在使用它,其它内核可调整参数仍然位于sysctl(/proc/sys/kernel) 接口中;
/sys/module 这里有系统中所有模块的信息,不论这些模块是以内联(inlined)方式编译到内核映像文件(vmlinuz)中还是编译为外部模块(ko文件),都可能会出现在/sys/module 中
/sys/power 这里是系统中电源选项,这个目录下有几个属性文件可以用于控制整个机器的电源状态,如可以向其中写入控制命令让机器关机、重启等。

2.2 uevent

uevent 是一种内核事件通知机制,它负责在发生设备插拔事件时生成事件消息,并将消息发送给用户空间。这些消息可以包含有关设备的信息,例如设备的类型、标识符和状态等。

2.3 udevd

udevd 是一个在用户空间运行的守护进程,它监听来自内核的 uevent 消息,并根据这些消息来管理设备节点的创建和销毁。当 udevd 接收到 uevent 消息时,它会执行预定义的规则,以便为新插入的设备创建相应的设备节点,并在设备移除时进行清理工作。

三、代码中自动mknod

目的:自动在dev目录下创建设备,省去手工mknod

带有面向思想的内容去理解struct class

cpp 复制代码
struct class *class_create(struct module *owner, const char *name);
/*
 * 功能:在/sys/class生成一个目录,目录名由name指定
 * 参数:
    struct module *owner - THIS_MODULE
    const char *name - 目录名
 * 返回值  成功:class指针   失败:NULL
*/
/*
辅助接口:可以定义一个struct class 的指针变量cls来接受返回值,然后通过IS_ERR(cls)判断是否失败;
IS_ERR(cls);成功----------------->0
IS_ERR(cls);失败----------------->非0
PTR_ERR(cls);来获得失败的返回错误码;
*/

销毁struct classs

cpp 复制代码
void class_destroy(struct class *cls)
/*
* 功能:删除class_create生成目录
* 参数:
    struct class *cls - class指针
* 返回值
*/

创建节点 (重点关注*fmt和...最后两个参数,用法类似printk,可以方便创建设备)

cpp 复制代码
struct device *device_create(struct class *class, struct device *parent,
                 dev_t devt, void *drvdata, const char *fmt, ...)
/*
 * 功能:在/sys/class目录下class_create生成目录再生成一个子目录与该设备相对应,发uevent让应用程序udevd创建设备文件
 * 参数:
    struct class *class - class指针
    struct device *parent - 父对象,一般NULL
    dev_t devt - 设备号
    void *drvdata - 驱动私有数据,一般NULL
    const char *fmt - 字符串的格式
     ... - 不定参数
 * 返回值
    成功:device指针
    失败:NULL
 */

删除devicecreate生成的目录

cpp 复制代码
void device_destroy(struct class *class, dev_t devt)
/*
 * 功能:删除device_create生成目录
 * 参数:
    struct class *class - class指针
    dev_t devt - 设备号
 * 返回值
*/

3.1 利用second.c的代码修改

second.c

cpp 复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>


int major = 11;
int minor = 0;
int mysecond_num  = 1;

struct mysecond_dev
{
	struct cdev mydev;

	atomic_t openflag;//1 can open, 0 can not open

	struct timer_list timer;
	int second;

	struct class *cls;       //<-----------------
	struct device *dvs;       //<-----------------
};

struct mysecond_dev gmydev;

void timer_func(unsigned long arg)
{
	struct mysecond_dev * pmydev = (struct mysecond_dev *)arg;
	pmydev->second++;
	mod_timer(&pmydev->timer, jiffies + 1 * HZ);

}

int mysecond_open(struct inode *pnode,struct file *pfile)
{
	struct mysecond_dev *pmydev = NULL;

	pfile->private_data =(void *) (container_of(pnode->i_cdev,struct mysecond_dev,mydev));
	
	pmydev = (struct mysecond_dev *)pfile->private_data;

	//运算后结果为0则返回真,否则返回假表示设备已经被打开
	if(atomic_dec_and_test(&pmydev->openflag))
	{
		pmydev->timer.expires = jiffies + 1 * HZ;
		pmydev->timer.function = timer_func;
		pmydev->timer.data = (unsigned long)pmydev;
		add_timer(&pmydev->timer);

		return 0;
	}
	else
	{
		atomic_inc(&pmydev->openflag);
		printk("The device is opened already\n");
		return -1;
	}
}

int mysecond_close(struct inode *pnode,struct file *pfile)
{
	struct mysecond_dev *pmydev = (struct mysecond_dev *)pfile->private_data;

	del_timer(&pmydev->timer);
	atomic_set(&pmydev->openflag,1);

	return 0;
}

int mysecond_read(struct file *pfile,char __user *puser, size_t size, loff_t *p_pos)
{
	struct mysecond_dev *pmydev = (struct mysecond_dev *)pfile->private_data;
	int ret = 0;

	if(size < sizeof(int))
	{
		printk("the expect read size is invalid\n");
		return -1;
	}
	if(size >= sizeof(int))
	{
		size= sizeof(int);
	}

	ret = copy_to_user(puser, &pmydev->second, size);
	if(ret)
	{
		printk("copy to user failed\n");
		return -1;
	}

	return 0;
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mysecond_open,
	.release = mysecond_close,
	.read = mysecond_read,
};

int __init mysecond_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major,minor);

	/*申请设备号*/
	ret = register_chrdev_region(devno,mysecond_num,"mysecond");
	if(ret)
	{
		ret = alloc_chrdev_region(&devno,minor,mysecond_num,"mysecond");
		if(ret)
		{
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);//容易遗漏,注意
	}

	/*给struct cdev对象指定操作函数集*/	
	cdev_init(&gmydev.mydev,&myops);

	/*将struct cdev对象添加到内核对应的数据结构里*/
	gmydev.mydev.owner = THIS_MODULE;
	cdev_add(&gmydev.mydev,devno,mysecond_num);

	//初始化置位1
	atomic_set(&gmydev.openflag,1);

	//初始化定时器
	init_timer(&gmydev.timer);

	gmydev.cls = class_create(THIS_MODULE, "mysecond");    //<-----------------
	if(IS_ERR(gmydev.cls))
	{
		printk("class_create failed\n");
		cdev_del(&gmydev.mydev);
		unregister_chrdev_region(devno,mysecond_num);
		return -1;
	}
	
	gmydev.dvs = device_create(gmydev.cls, NULL, devno, NULL,"mysec");   //<-----------------
	if(gmydev.dvs == NULL)
	{
		printk("device_create failed\n");
		class_destroy(gmydev.cls);
		cdev_del(&gmydev.mydev);
		unregister_chrdev_region(devno,mysecond_num);
		return -1;
	}

	return 0;
}

void __exit mysecond_exit(void)
{
	dev_t devno = MKDEV(major,minor);

	device_destroy(gmydev.cls, devno);    //<-----------------
	class_destroy(gmydev.cls);            //<-----------------

	cdev_del(&gmydev.mydev);

	unregister_chrdev_region(devno,mysecond_num);
}


MODULE_LICENSE("GPL");

module_init(mysecond_init);
module_exit(mysecond_exit);

修改Makefile

cpp 复制代码
ifeq ($(KERNELRELEASE),)

ifeq ($(ARCH),arm)
KERNELDIR ?= /home/linux/Linux_4412/kernel/linux-3.14
ROOTFS ?= /opt/4412/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)


modules:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

modules_install:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_install

clean:
	rm -rf  *.o  *.ko  .*.cmd  *.mod.*  modules.order  Module.symvers   .tmp_versions

else

CONFIG_MODULE_SIG=n
obj-m += mychar.o
obj-m += mychar_poll.o
obj-m += openonce_atomic.o
obj-m += openonce_spinlock.o
obj-m += mychar_sema.o
obj-m += mychar_mutex.o
obj-m += second.o
#obj-m += leddrv.o
#obj-m += leddrv_dt.o
#obj-m += fs4412_key2.o
#obj-m += fs4412_key2_tasklet.o
#obj-m += fs4412_key2_workqueue.o

endif

编译

测试,不需要我们自己mknod

相关推荐
朱小弟cs69 小时前
Orange的运维学习日记--41.Ansible基础入门
linux·运维·学习·ci/cd·自动化·ansible·devops
CIb0la9 小时前
kali linux 2025.2安装WPS并设置无报错的详细步骤
linux·运维·wps
醉方休10 小时前
Node.js 精选:50 款文件处理与开发环境工具库
linux·运维·node.js
代码老y11 小时前
从裸机到云原生:Linux 操作系统实战进阶的“四维跃迁”
linux·运维·云原生
CMCST12 小时前
CentOS 7.9 升级 GLibc 2.34
linux·运维·centos
xiep143833351012 小时前
Rocky Linux 10 部署 Kafka 集群
linux·运维·kafka
笨鸟要努力16 小时前
Ubuntu 全盘备份
linux·运维·ubuntu
ChironW16 小时前
Ubuntu 22.04 离线环境下完整安装 Anaconda、CUDA 12.1、NVIDIA 驱动及 cuDNN 8.9.3 教程
linux·运维·人工智能·深度学习·yolo·ubuntu
轻松Ai享生活17 小时前
linux 日志详解
linux
小白的代码日记17 小时前
Linux常用指令
linux·运维·服务器