- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
前面内容:
1 Linux驱动—内核模块基本使用
2 Linux驱动—内核模块参数,依赖(进一步讨论)
3 字符设备驱动
先学习下虚拟串口设备是啥?
在进一步实现字符设备驱动之前,我们先来讨论一下这本书中用到的一个虚拟串口设备
。这个设备是驱动代码虚拟出来的,不能实现真正的串口数据收发,但是它能够接收用户想要发送的数据,并且将该数据原封不动地环回给串口的收端,使用户也能从该串口接收数据。也就是说,该虚拟串口设备是一个功能弱化之后的只具备内环回作用的串口
,如图3.3所示。
这一功能的实现
主要是在驱动中实现了一个FIFO,驱动接收用户层传来的数据,然后将之放入FIFO,当应用层要获取数据时,驱动将FIFO中的数据读出,然后复制给应用层。
一个更贴近实际的形式应该是在驱动中有两个FIFO,一个用于发送,一个用于接收,但是这并不是实现这个简单的虛拟串口设备驱动的关键,所以为了简单起见,这里只用了一个FIFO。
内核中已经有了一个关于FIFO的数据结构struct kfifo
,相关的操作宏或函数的声明、
定义都在“include/inux/kfifo.h"头文件中,下面将最常用的宏罗列如下。
DEFINE_KFIFO(fifo,type,size)
kfifo_from_user(fifo,from,len,copied)
kfifo_to_user(fifo,to,len,copied)
DEFINE_ KFIFO
用于定义并初始化一个FIFO,这个变量的名字由fifo参数
决定, type是FIFO中成员的类型
,size 则指定这个FIFO有多少个元素
,但是元素的个数必须是2的幂。
kfifo_from_user
是将用户空间的数据(from) 放入FIFO中,元素个数由len
来指定,实际放入的元素个数由copied返回
。
kfifo_to_user
则是将FIFO中的数据取出,复制到用户空间(to)。 len 和copied的含义同kfifo_ from_ user 中对应的参数。
字符设备驱动除了前面搭建好的框架外,接下来最重要的是实现设备的操作方法。
#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 1
#define VSER_DEV_NAME "vser"
static struct cdev vsdev;
DEFINE_KFIFO(vsfifo, char, 32);
static int vser_open(struct inode *inode, struct file *filp)
{
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;
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;
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");
新的代码驱动在代码DEFINE_KFIFO(vsfifo, char, 32);
定义并初始化了一个名叫vsfifo
的structu kfifo对象,每个对象的类型为char,共有32个元素的空间。
static int vser_open(struct inode *inode, struct file *filp)
{
return 0;
}
static int vser_release(struct inode *inode, struct file *filp)
{
return 0;
}
代码实现了设备的打开和关闭函数,分别对应于file_operations
内的open
和release
方法。
因为是虚拟设备,所以这里并没有需要特别处理的操作,仅仅返回0表示成功。
这两个函数都有两个相同的形参,
在前面的章节中我们已经较深入地分析了这两个对象的作用。
在这里之所以叫release而不叫close是因为一个文件可以被打开多次,那么vser_ open
函数相应地会被调用多次,但是关闭文件只有到最后一个close操作才会导致vser_ release函数被调用,所以用release更贴切。
代码
static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
unsigned int copied = 0;
kfifo_to_user(&vsfifo, buf, count, &copied);
return copied;
}
是read系统调用的驱动实现,这里主要把FIFO中的数据返回给用户层,使用了kfifo_to_user
这个宏。返回给用户层。
read 系统调用要求用户返回实际读取的字节数,而copied变量的值正好符合这一要求。
代码第36行到第43行是对应的write系统、
static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
unsigned int copied = 0;
kfifo_from_user(&vsfifo, buf, count, &copied);
return copied;
}
调用的驱动实现,同read系统调用一样,只是数据流向相反而已。
读和写函数引入了3个新的形参,分别是buf, count 和pos,根据上面的代码,已经不难发现它们的含义。buf 代表的是用户空间的内存起始地址; count 表示用户想要读写多少个字节的数据:而pos是文件的位置指针
,在虚拟串口这个不支持随机访问的设备中,该参数无用。_user是提醒驱动代码编写者,这个内存空间属于用户空间。
代码
static struct file_operations vser_ops = {
.owner = THIS_MODULE,
.open = vser_open,
.release = vser_release,
.read = vser_read,
.write = vser_write,
};
这里是将file_operations
的函数指针分别指向上面定义的函数
你看 .read指向vser_read函数
这样在驱动这样在应用层发生相应的系统调用后,在驱动里面的函数就会被相应地调用。
上面这个示例实现了一个功能非常简单,但是基本可用的虚拟串口驱动程序。
按照下面的步骤可以进行验证。
先创建设备号
sudo mknod /dev/vser0 c 256 0
然后编译运行
make
make modules_install
sudo modpeobe vser
然后 往这个设备中dev/vser0写入数据echo "vser deriver test" > /dev/vser0
然后查看
cat /dev/vser0
最后
会出现
vser deriver test
通过实验结果可以看到,对/dev/vser0 写入什么数据,就可以从这个设备读到什么数据,和一个具备内环回功能的串口是一致的。
为了方便读者对照查阅,特将file_operations 结构类型的定义代码列出。从中我们可以看到,还有很多接口函数还没有实现,在后面的章节中,我们会陆续再实现一些接口。
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*mremap)(struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
};
显然,一个驱动对下面的接口的实现越多,它对用户提供的功能就越多,但这也不是说我们必须要实现下面的所有函数接口。比如串口不支持随机访问,那么lseek函数接口自然就不用实现。
iphone设备UDID、iphone设备ID和iphone设备Token之间有什么区别? 通常,当我们使用苹果推送通知服务时,会使用 iPhone 设备 token 。 但我的目标只是识别唯一的 i
我们使用 firebase 从服务器向 Android 和 IOS 设备发送通知,并且我们使用旧版 FCM 发送通知。但是当我们的应用程序在后台时,通知由系统本身处理,因此我们无法通过应用程序处理它。
在 Google 上搜索后,我发现人们说只能通过“MFi 程序”将 iOS 设备与非 iOS 设备连接起来。这是真的吗? 我的项目主要集中于直接通过蓝牙与Arduino设备发送和接收信息。 iOS和非
所以我有一个通用应用程序,我正在设置 UIScrollView 的内容大小。显然,iPhone 和 iPad 上的内容大小会有所不同。如何为 iPad 设置某种尺寸,为 iPhone 和 iPod t
问题:如何在 pod 中使用连接到主机的原始设备作为 block 设备。 我尝试使用类型为“BlockDevice”的“hostPath” volumes: - my-data: hostPath
Implemented GCKDeviceScannerListener Singleton Class on ViewController, however its delegate methods
我有一个 (PhoneGap) 应用程序,它将成功获得 Passbook 通行证,并且还将成功接收与 Passbook 分开的推送通知(当伪造设备 ID 时)。 我遇到的问题是发送给注册设备的设备 I
我正在尝试找到一种方法,通过我目前正在使用的 iOS 应用程序访问我的信标的电池电量。我正在使用 Kontakt 的 iBeacon 设备。我浏览了 Estimote iOS SDK,他们提供了一种实
我正在努力让 CUDA 应用程序也能监控 GPU 的核心温度。可通过 NVAPI 访问该信息。 问题是我想确保在运行代码时监控的是同一个 GPU。 但是,似乎有信息表明我从 NvAPI_EnumPhy
从沙箱模式到生产模式,设备 token 有何不同? 我认为我已将一些设备 token 锁定为生产模式,并且无法将它们从开发中插入。 关于如何检查有什么想法吗? 最佳答案 当您使用开发证书构建应用程序时
目录 /run/user/1000/gvfs 和 ~/.gvfs 分别是空的和不存在的。我的图形文件管理器 (Thunar) 能够检测和访问设备的内部和外部存储器。 命令 gvfs-mount -l
我有一个 Android 平板电脑,它有一个迷你 USB 端口和一个 USB 端口,我想编写一个与 USB key 通信的应用程序。我写了一个demo来找出U盘,但是没有任何反应。 令我不安的是,如果
我们将 PHP 版本从 5.4.25 更改为 5.4.45,并在服务器上安装了 MS SQL 驱动程序。在更改服务器之前,一切正常,但在更改服务器之后,我遇到了 Web 服务问题。我们的身份验证 So
我想知道是否有人使用此 API 在 Android 设备上同时从 2 个后置摄像头捕获图像或视频:https://source.android.com/docs/core/camera/concurr
我正在为客户构建一个物联网解决方案,网络管理员坚持要求设备仅通过访客网络进行连接,该网络有一个强制门户,其中的服务条款必须通过按下 UI 按钮来接受,然后才能获得外部互联网访问。到目前为止,我见过的大
我无法弄清楚这里的格式规则..在我的示例中,代码行太多,无法为每行添加 4 个空格,因此这里是我需要帮助的代码的链接 http://nitemsg.blogspot.com/2011/01/heres
如果我在我的设备上接受推送通知,并且不保存设备 token ,那么我如何在自定义 View 中查看设备 token 或恢复警报 View ? 我删除了应用程序并重新安装,但看不到设备 token 警报
我试图找出在尝试并行比较和复制设备 block 与 pthreads 时我做错了什么。看起来我正在脱离同步并且比较阶段无法正常工作。任何帮助将不胜感激 #ifndef __dbg_h__ #defin
我刚刚写完所有这些内容,但这个红色的小栏告诉我我不能发布图片或两个以上的链接。因此,如果您可以引用 this Imgur album , 那简直太好了。谢谢。 我在这里相对较新,甚至对 android
我需要启用 mysql 常规日志并将其通过 nsf 移动到我系统中的另一个驱动器/设备! 所以,我在 my.cnf 中启用了它: general_log = 1 general_log_fi
我是一名优秀的程序员,十分优秀!