一、起源
仅devfs,导致开发不方便以及一些功能难以支持:
1. 热插拔
2. 不支持一些针对所有设备的统一操作(如电源管理)
3. 不能自动mknod
4. 用户查看不了设备信息
5. 设备信息硬编码,导致驱动代码通用性差,即没有分离设备和驱动
二、新方案
uevent机制:sysfs + uevent + udevd(上层app)
2.1 sysfs: 一种用内存模拟的文件系统,系统启动时mount到/sys目录
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
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nPaB0xBV-1669021775446)(.\2018050710593348.gif)]](https://i-blog.csdnimg.cn/blog_migrate/9e434d67e41211244a5b138bbba4d873.png)
三、代码中自动mknod
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);来获得失败的返回错误码;
*/
void class_destroy(struct class *cls)
/*
* 功能:删除class_create生成目录
* 参数:
struct class *cls - class指针
* 返回值
*/
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
*/
void device_destroy(struct class *class, dev_t devt)
/*
* 功能:删除device_create生成目录
* 参数:
struct class *class - class指针
dev_t devt - 设备号
* 返回值
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/io.h>
#include "fs4412_key.h" //自己写的.h用“”引用,库用<>引用
#include <linux/delay.h>
#include <linux/device.h>
int major = 11; //主设备号
int minor = 0; //次设备号
int fs4412key2_num = 1; //设备数量
struct fs4412key2_dev //led设备结构体
{
struct cdev mydev; //设备结构体
int gpio; //设备gpio成员变量
int irqno; //中断
struct keyvalue data; //按键存储的数据
int newflag; //新数据到来的标志位
spinlock_t lock; //自旋锁
wait_queue_head_t rq; //读忙等待队列
struct class *pcls;
struct device *pdev;
};
struct fs4412key2_dev *pgmydev = NULL; //定义一个设备结构体变量,用于调用结构体成员
int fs4412key2_open(struct inode *pnode,struct file *pfile) //打开文件函数
{ //inode类型结构体中i_cdev是mydev的地址
pfile->private_data = (void *) (container_of(pnode->i_cdev,struct fs4412key2_dev,mydev));//知道成员地址可以得出结构体地址
return 0;
}
int fs4412key2_close(struct inode *pnode,struct file *pfile)
{
return 0;
}
ssize_t mychar_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos)
{
struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)pfile->private_data;
int size = 0;
int ret = 0;
if(count < sizeof(struct keyvalue))
{
printk("expect read size is invalde\n");
return -1;
}
spin_lock(&pmydev->lock); //上锁
if(!pmydev->newflag) //无数据时进入
{
if(pfile->f_flags & O_NONBLOCK)
{ //非阻塞
spin_unlock(&pmydev->lock);
printk("O_NONBLOCK NO Data Read\n");
return -1;
}
else
{ //阻塞
spin_unlock(&pmydev->lock);
ret = wait_event_interruptible(pmydev->rq,pmydev->newflag == 1); //等待条件是pmydev->newflag == 1也就是有数据到来
if(ret)
{
printk("Wake up by signal\n");
return -ERESTARTSYS;
}
spin_lock(&pmydev->lock);
}
}
if(count > sizeof(struct keyvalue)) //对读取数据的长度做一个限制
{
size = sizoef(struct keyvalue);
}
else
{
size = count;
}
ret = copy_to_user(puser,&pmydev->data,size); //将内核数据拷贝到用户
if(ret)
{
spin_unlock(&pmydev->lock);
printk("copy_to_user failed\n");
return -1;
}
pmydev->newflag = 0; //将数据标志物清零,为下次进入做准备
spin_unlock(&pmydev->lock); //开锁
return size;
}
unsigned int mychar_poll(struct file *pfile,poll_table *ptb) //决定什么时候能读数据,这儿好像没用着。app上没些poll函数
{
struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)pfile->private_data;
unsigned int mask = 0;
poll_wait(pfile,&pmydev->rq,ptb); //将读队列加入到表里,但是未休眠
spin_lock(&pmydev->lock); //上锁
if(pmydev->newflag) //当newflag为真是表示有数据,可读打开
{
mask |= POLLIN | POLLRDNORM; //读打开
}
spin_unlock(&pmydev->lock);
return mask;
}
struct file_operations myops = { //设备的操作函数,自己写的子函数必须在这儿与内核函数关联起来才能被调用
.owner = THIS_MODULE,
.open = fs4412key2_open,
.release = fs4412key2_close,
.read = mychar_read,
.poll = mychar_poll,
};
irqreturn_t key2_irq_handle(int no,void *arg) //按键中断服务函数
{
struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)arg;
int status1 = 0;
int status2 = 0;
int status = 0;
status1 = gpio_get_value(pmydev->gpio); //消抖
mdelay(1);
status2 = gpio_get_value(pmydev->gpio);
if(status1 != status2)
{
return IRQ_NONE;
}
status = status1;
spin_lock(&pmydev->lock); //上锁
if(status == pmydev->data.status)
{
spin_unlock(&pmydev->lock);
return IRQ_NONE;
}
pmydev->data.code = KEY2;
pmydev->data.status = status;
pmydev->newflag = 1;
spin_unlock(&pmydev->lock);
wake_up(&pmydev->rq);
return IRQ_HANDLED;
}
int __init fs4412key2_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major,minor); //将主次设备号合成一个32位的设备号
int ret = 0;
struct device_node *pnode = NULL; //定义一个变量用于存储设备树中的一个节点
pnode = of_find_node_by_path ("/mykey2_node"); //从设备树获得key2节点
if(NULL == pnode)
{
printk("fialed of_find_node_by_path\n");
return -1;
}
pgmydev = (struct fs4412key2_dev *)kmalloc(sizeof(struct fs4412key2_dev),GFP_KERNEL);//给设备结构体申请一块内存,kmalloc是申请小内存效率高。GFP_KERNEL是可以进行忙等待,因为这个是任务上下文
if(NULL == pgmydev) //申请失败
{
printk("kmalloc failed\n");
return -1;
}
pgmydev->gpio = of_get_named_gpio(pnode,"key2-gpio",0); //从设备树中提取gpio口
pgmydev->irqno = irq_of_parse_and_map(pnode,0); //获得设备树中的中断号并进行映射
/*申请设备号*/
ret = register_chrdev_region(devno,fs4412key2_num,"fs4412key2");
if(ret)
{
ret = alloc_chrdev_region(&devno,minor,fs4412key2_num,"fs4412key2"); //手动申请失败时自动申请
if(ret)
{
printk("get devno failed\n");
kfree(pgmydev); //申请失败时释放掉设备结构体
return -1;
}
major = MAJOR(devno);
}
/*给struct cdev对象指定操作函数集*/
cdev_init(&pgmydev->mydev,&myops);
/*将struct cdev对象添加到内核对应的数据结构里*/
pgmydev->mydev.owner = THIS_MODULE;
cdev_add(&pgmydev->mydev,devno,fs4412key2_num);
init_waitqueue_head(&pgmydev->rq); //对等待队列头做初始化
spin_lock_init(&pgmydev->lock); //对自旋锁做初始化,因为是异常上下文所以用自旋锁,他可以忙等待
ret = request_irq(pgmydev->irqno,key2_irq_handle,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,"fs4412key2",pgmydev);//中断申请函数,初始化要放在锁初始化后面,要不然进入中断服务程序锁没初始化会出问题,
if(ret)
{
printk("request_irq failed\n");
kfree(pgmydev);
pgmaydev = NULL;
return -1;
}
pgmydev.pcls = class_create(THIS_MODULE,"mysecond"); //创建设备目录
if(IS_ERR(gmydev.pcls))
{
printk("class_create failed\n");
free_irq(pgmydev->irqno,pgmydev);
cdev_del(&pgmydev->mydev);
unregister_chrdev_region(devno,fs4412key2_num);
kfree(pgmydev);
pgmydev = NULL;
return -1;
}
pgmydev.pdev = device_create(pgmydev.pcls,NULL,devno,NULL,"mysec"); //创建一个设备名为mysec
if(NULL == pgmydev.pdev)
{
printk("device_create failed\n");
class_destroy(gmydev.pcls);
free_irq(pgmydev->irqno,pgmydev);
cdev_del(&pgmydev->mydev);
unregister_chrdev_region(devno,fs4412key2_num);
kfree(pgmydev);
pgmydev = NULL;
return -1;
}
return 0;
}
void __exit fs4412key2_exit(void)
{
dev_t devno = MKDEV(major,minor);
device_destroy(gmydev.pdev,devno);
class_destroy(gmydev.pcls);
free_irq(pgmydev->irqno,pgmydev);
cdev_del(&pgmydev->mydev);
unregister_chrdev_region(devno,fs4412key2_num);
kfree(pgmydev);
pgmydev = NULL;
}
MODULE_LICENSE("GPL");
module_init(fs4412key2_init);
module_exit(fs4412key2_exit);
.h
#ifndef FS4412_KEY_H
#define FS4412_KEY_H
enum KEYCODE
{
KEY2 = 1002,
KEY3,
KEY4,
};
enum KEY_STATUS
{
KEY_DOWN = 0,
KEY_UP,
};
struct keyvalue
{
int code;
int status;
};
#endif
app
#include <stdio.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <unistd.h>
#include "leddrv.h"
#include <sys/stat.h>
#include <sys/ioctl.h>
int main(int argc,char *argv[])
{
int fd = -1;
if(argc < 2)
{
printf("The argument is too few\n");
return -1;
}
fd = open(argv[1],O_RDONLY); //
if(fd < 0)
{
printf("open %s failed\n",argv[1]);
return 3;
}
while((ret = read(fd,&keydata,sizeof(keydata))) == sizeof(keydata))
{
if(keydata.status == KEY_DOWN)
{
printf("Key2 is down!\n");
}
else
{
printf("Key2 is up!\n");
}
}
close(fd);
fd = -1;
return 0;
}
本文介绍了Linux系统中Sysfs文件系统和UEVENT机制的工作原理及其应用。详细探讨了Sysfs如何作为内核与用户空间间设备信息的桥梁,并解释了UEVENT如何配合Sysfs实现动态设备文件创建。
1379

被折叠的 条评论
为什么被折叠?



