- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
前面内容:
前面内容:
1 Linux驱动—内核模块基本使用
2 Linux驱动—内核模块参数,依赖(进一步讨论)
3 字符设备驱动
4 虚拟串口设备驱动
每个设备都写一个驱动太麻烦了,所以要Linux驱动—实现一个驱动支持多个设备。
对于多设备引入的变化:
我们首先要向 内核注册多个设备号
其次就是在添加cdev对象时指明改cdev对象管理了多个设备
;
或者添加多个cdev对象,每个cdev对象管理一个设备
。
接下来最麻烦的部分在于读写操作,因为设备是多个,那么设备对应的资源也应该是多个(比如虚拟串口驱动中的FIFO)。 在读写操作时,怎么来区分究竟应该对哪个设备进行操作呢(对于虚拟串口驱动而言,就是要确定对哪个FIFO进行操作) ?
观察读和写函数,没有发现能够区别设备的形参。
再观察open接口,我们会发现有一个inode 形参,通过前面的内容我们知道,inode里面包含了对应设备的设备号以及所对应的cdev对象的地址。
因此,我们可以在open接口函数中取出这些信息(inode里面包含了对应设备的设备号以及所对应的cdev对象的地址),并存放在file结构对象的某个成员中,再在读写的接口函数中获取该file结构的成员,从而可以区分出对哪个设备进行操作。
下面首先展示用一个cdev实现对多个设备的支持
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>
#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 2
#define VSER_DEV_NAME "vser"
static struct cdev vsdev;
static DEFINE_KFIFO(vsfifo0, char, 32);
static DEFINE_KFIFO(vsfifo1, char, 32);
static int vser_open(struct inode *inode, struct file *filp)
{
switch (MINOR(inode->i_rdev)) {
default:
case 0:
filp->private_data = &vsfifo0;
break;
case 1:
filp->private_data = &vsfifo1;
break;
}
return 0;
}
static int vser_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
unsigned int copied = 0;
struct kfifo *vsfifo = filp->private_data;
kfifo_to_user(vsfifo, buf, count, &copied);
return copied;
}
static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
unsigned int copied = 0;
struct kfifo *vsfifo = filp->private_data;
kfifo_from_user(vsfifo, buf, count, &copied);
return copied;
}
static struct file_operations vser_ops = {
.owner = THIS_MODULE,
.open = vser_open,
.release = vser_release,
.read = vser_read,
.write = vser_write,
};
static int __init vser_init(void)
{
int ret;
dev_t dev;
dev = MKDEV(VSER_MAJOR, VSER_MINOR);
ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
if (ret)
goto reg_err;
cdev_init(&vsdev, &vser_ops);
vsdev.owner = THIS_MODULE;
ret = cdev_add(&vsdev, dev, VSER_DEV_CNT);
if (ret)
goto add_err;
return 0;
add_err:
unregister_chrdev_region(dev, VSER_DEV_CNT);
reg_err:
return ret;
}
static void __exit vser_exit(void)
{
dev_t dev;
dev = MKDEV(VSER_MAJOR, VSER_MINOR);
cdev_del(&vsdev);
unregister_chrdev_region(dev, VSER_DEV_CNT);
}
module_init(vser_init);
module_exit(vser_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
MODULE_DESCRIPTION("A simple character device driver");
MODULE_ALIAS("virtual-serial");
来分析下代码:
这里把CNT改成2 说明支持2个设备
static struct cdev vsdev;
static DEFINE_KFIFO(vsfifo0, char, 32);
static DEFINE_KFIFO(vsfifo1, char, 32);
定义了二个FIFO,vsfifo0和1 (这里用的是动态分配fifo要更好,但是后面会涉及内存分配的知识,所以先用静态的)
在open接口函数中根据次设备号的值来确定保存哪个FIFO结构的(vsfifo0还是1)地址到file结构中的private_data 的值,即FIFO结构的地址
static int vser_open(struct inode *inode, struct file *filp)
{
switch (MINOR(inode->i_rdev)) {
default:
case 0:
filp->private_data = &vsfifo0;
break;
case 1:
filp->private_data = &vsfifo1;
break;
}
return 0;
}
次设备号 0 选择存 vsfifo0
次设备号 1 选择存 vsfifo1
接下来演示如何将每一个cdev对象对应到一个设备来实现一个驱动对多个设备的支持
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>
#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 2
#define VSER_DEV_NAME "vser"
static DEFINE_KFIFO(vsfifo0, char, 32);
static DEFINE_KFIFO(vsfifo1, char, 32);
struct vser_dev {
struct kfifo *fifo;
struct cdev cdev;
};
static struct vser_dev vsdev[2];
static int vser_open(struct inode *inode, struct file *filp)
{
filp->private_data = container_of(inode->i_cdev, struct vser_dev, cdev);
return 0;
}
static int vser_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
unsigned int copied = 0;
struct vser_dev *dev = filp->private_data;
kfifo_to_user(dev->fifo, buf, count, &copied);
return copied;
}
static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
unsigned int copied = 0;
struct vser_dev *dev = filp->private_data;
kfifo_from_user(dev->fifo, buf, count, &copied);
return copied;
}
static struct file_operations vser_ops = {
.owner = THIS_MODULE,
.open = vser_open,
.release = vser_release,
.read = vser_read,
.write = vser_write,
};
static int __init vser_init(void)
{
int i;
int ret;
dev_t dev;
dev = MKDEV(VSER_MAJOR, VSER_MINOR);
ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
if (ret)
goto reg_err;
for (i = 0; i < VSER_DEV_CNT; i++) {
cdev_init(&vsdev[i].cdev, &vser_ops);
vsdev[i].cdev.owner = THIS_MODULE;
vsdev[i].fifo = i == 0 ? (struct kfifo *) &vsfifo0 : (struct kfifo*)&vsfifo1;
ret = cdev_add(&vsdev[i].cdev, dev + i, 1);
if (ret)
goto add_err;
}
return 0;
add_err:
for (--i; i > 0; --i)
cdev_del(&vsdev[i].cdev);
unregister_chrdev_region(dev, VSER_DEV_CNT);
reg_err:
return ret;
}
static void __exit vser_exit(void)
{
int i;
dev_t dev;
dev = MKDEV(VSER_MAJOR, VSER_MINOR);
for (i = 0; i < VSER_DEV_CNT; i++)
cdev_del(&vsdev[i].cdev);
unregister_chrdev_region(dev, VSER_DEV_CNT);
}
module_init(vser_init);
module_exit(vser_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
MODULE_DESCRIPTION("A simple character device driver");
MODULE_ALIAS("virtual-serial");
代码:
struct vser_dev {
struct kfifo *fifo;
struct cdev cdev;
};
这里定义了一个结构类型 vser_dev,代表一种具体的设备类
通常和设备有关的内容都可以跟cdev一起定义到一个结构中这样更容易
cdev 是所有字符设备的一个抽象,是一个基类,而一个具体类型的设备应该是由该基类派生出来的一个子类,子类包含了特定设备所特有的属性,比如vser_ dev 中的fifo,这样子类就更能刻画好一类具体的设备。
代码
static struct vser_dev vsdev[2];
创建了两个vser_dev 类型的对象,和C++不同的是,创建这两个对象仅仅是为其分配了内存,并没有调用构造函数来初始化这两个对象,但在代码的第74行到第77行完成了这个操作。
for (i = 0; i < VSER_DEV_CNT; i++) {
cdev_init(&vsdev[i].cdev, &vser_ops);
vsdev[i].cdev.owner = THIS_MODULE;
vsdev[i].fifo = i == 0 ? (struct kfifo *) &vsfifo0 : (struct kfifo*)&vsfifo1;
你看,初始化init cdev
查看内核源码,会发现这种面向对象的思想处处可见,只能说因为语言的特性,并没有把这种形式体现得很明显而已。
代码的第74行到第82行通过两次循环完成了两个cdev对象的初始化和添加工作,并且初始化了fifo 成员的指向。
for (i = 0; i < VSER_DEV_CNT; i++) {
cdev_init(&vsdev[i].cdev, &vser_ops);
vsdev[i].cdev.owner = THIS_MODULE;
vsdev[i].fifo = i == 0 ? (struct kfifo *) &vsfifo0 : (struct kfifo*)&vsfifo1;
ret = cdev_add(&vsdev[i].cdev, dev + i, 1);
if (ret)
goto add_err;
}
如果i为0是第一个,指向vsfifo0这个结构的地址
如果i为1是第二个,指向vsfifo1这个结构的地址
这里需要说明的是,用DEFINE_ KFIFO
定义的FIFO,每定义一个FIFO就会新定义一种数据类型,所以严格来说vsfifo0和vsfifo1是两种不同类型的对象,但好在这里能和struct kfifo类型兼容。
代码第26行
filp->private_data = container_of(inode->i_cdev, struct vser_dev, cdev);
用到了一个container_ of
宏,这是在Linux内核中设计得非常巧妙的一个宏,在整个Linux内核源码中几乎随处可见。
它的作用就是**根据结构成员的地址来反向得到结构的起始地址**
在代码中,inode->i_cdev 给出了struct vser_dev
结构类型中cdev成员的地址(见图3.2),通过container_ of宏就得到了包含该cdev的结构地址。
使用上面两种方式都可以实现一个驱动对多个同类型设备的支持。使用下面的命令可以测试这两个驱动程序。
完成
背景: 我最近一直在使用 JPA,我为相当大的关系数据库项目生成持久层的轻松程度给我留下了深刻的印象。 我们公司使用大量非 SQL 数据库,特别是面向列的数据库。我对可能对这些数据库使用 JPA 有一
我已经在我的 maven pom 中添加了这些构建配置,因为我希望将 Apache Solr 依赖项与 Jar 捆绑在一起。否则我得到了 SolarServerException: ClassNotF
interface ITurtle { void Fight(); void EatPizza(); } interface ILeonardo : ITurtle {
我希望可用于 Java 的对象/关系映射 (ORM) 工具之一能够满足这些要求: 使用 JPA 或 native SQL 查询获取大量行并将其作为实体对象返回。 允许在行(实体)中进行迭代,并在对当前
好像没有,因为我有实现From for 的代码, 我可以转换 A到 B与 .into() , 但同样的事情不适用于 Vec .into()一个Vec . 要么我搞砸了阻止实现派生的事情,要么这不应该发
在 C# 中,如果 A 实现 IX 并且 B 继承自 A ,是否必然遵循 B 实现 IX?如果是,是因为 LSP 吗?之间有什么区别吗: 1. Interface IX; Class A : IX;
就目前而言,这个问题不适合我们的问答形式。我们希望答案得到事实、引用资料或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the
我正在阅读标准haskell库的(^)的实现代码: (^) :: (Num a, Integral b) => a -> b -> a x0 ^ y0 | y0 a -> b ->a expo x0
我将把国际象棋游戏表示为 C++ 结构。我认为,最好的选择是树结构(因为在每个深度我们都有几个可能的移动)。 这是一个好的方法吗? struct TreeElement{ SomeMoveType
我正在为用户名数据库实现字符串匹配算法。我的方法采用现有的用户名数据库和用户想要的新用户名,然后检查用户名是否已被占用。如果采用该方法,则该方法应该返回带有数据库中未采用的数字的用户名。 例子: “贾
我正在尝试实现 Breadth-first search algorithm , 为了找到两个顶点之间的最短距离。我开发了一个 Queue 对象来保存和检索对象,并且我有一个二维数组来保存两个给定顶点
我目前正在 ika 中开发我的 Python 游戏,它使用 python 2.5 我决定为 AI 使用 A* 寻路。然而,我发现它对我的需要来说太慢了(3-4 个敌人可能会落后于游戏,但我想供应 4-
我正在寻找 Kademlia 的开源实现C/C++ 中的分布式哈希表。它必须是轻量级和跨平台的(win/linux/mac)。 它必须能够将信息发布到 DHT 并检索它。 最佳答案 OpenDHT是
我在一本书中读到这一行:-“当我们要求 C++ 实现运行程序时,它会通过调用此函数来实现。” 而且我想知道“C++ 实现”是什么意思或具体是什么。帮忙!? 最佳答案 “C++ 实现”是指编译器加上链接
我正在尝试使用分支定界的 C++ 实现这个背包问题。此网站上有一个 Java 版本:Implementing branch and bound for knapsack 我试图让我的 C++ 版本打印
在很多情况下,我需要在 C# 中访问合适的哈希算法,从重写 GetHashCode 到对数据执行快速比较/查找。 我发现 FNV 哈希是一种非常简单/好/快速的哈希算法。但是,我从未见过 C# 实现的
目录 LRU缓存替换策略 核心思想 不适用场景 算法基本实现 算法优化
1. 绪论 在前面文章中提到 空间直角坐标系相互转换 ,测绘坐标转换时,一般涉及到的情况是:两个直角坐标系的小角度转换。这个就是我们经常在测绘数据处理中,WGS-84坐标系、54北京坐标系
在软件开发过程中,有时候我们需要定时地检查数据库中的数据,并在发现新增数据时触发一个动作。为了实现这个需求,我们在 .Net 7 下进行一次简单的演示. PeriodicTimer .
二分查找 二分查找算法,说白了就是在有序的数组里面给予一个存在数组里面的值key,然后将其先和数组中间的比较,如果key大于中间值,进行下一次mid后面的比较,直到找到相等的,就可以得到它的位置。
我是一名优秀的程序员,十分优秀!