之前的驱动操作称为硬编
一、起源
仅devfs(dev目录),导致开发不方便以及一些功能难以支持:
-
热插拔(如何插入一个设备然后找到设备的驱动应用到程序中)
-
不支持一些针对所有设备的统一操作(如电源管理)
-
不能自动mknod
-
用户查看不了设备信息(必须cat /proc/devices 查看组设备号)
-
设备信息硬编码,导致驱动代码通用性差,即没有分离设备和驱动
二、新方案
总设计原则:
uevent机制:sysfs + uevent + udevd(上层app)
2.1 sysfs: 一种用内存模拟的文件系统,系统启动时mount到/sys目录
sysfs 是 Linux 内核提供的一种虚拟文件系统,它将内核中设备、驱动程序和总线等信息以文件和目录的形式呈现给用户空间。当有新的设备插入或移除时,相关的信息会被添加、修改或删除,并通过 uevent 发送到用户空间。
sysfs用途:(类似于windows的设备管理器)
-
建立系统中总线、驱动、设备三者之间的桥梁
-
向用户空间展示内核中各种设备的拓扑图
-
提供给用户空间对设备获取信息和操作的接口,部分取代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