- 使用 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函数接口自然就不用实现。
我想用 python 与我的串口通信。我为 linux 安装了 pyserial 和 uspp: import serial ser = serial.Serial('/dev/pts/1', 192
如何实现 IP 串行,反之亦然。 我听说可以用 SOCAT 做到这一点(9600 N,8,1)--> 串口 --> 网络 --> 串口 -->(9600 N81) 请求人们帮助我解决这个问题 最佳答案
ATtiny88初体验(三):串口 ATtiny88单片机不包含串口模块,因此只能使用软件方式模拟串口时序。 串口通信时序通常由起始位、数据位、校验位和停止位四个部分组成,常见的
我有一个 c# 应用程序,它通过串行端口将 pc 连接到设备。 当我向设备发送数据时,如果设备发回数据,则会触发 datareceived 事件。 我想问这个。 有没有办法模拟设备的数据发送? 我的意
所以我将数据从 Arduino 传输到 C# Winform,后者将数据输出到文本框并将其保存到文件中。传输数据格式如下18|25|999|100~;第一部分是以秒为单位的时间,它让我知道什么时候跳过
规范模式状态的 Termios 手册页 ( http://man7.org/linux/man-pages/man3/termios.3.html ): Input is made available
串口代码有问题。 我只是这样做: opencomm(); send(); closecomm(); ClearCommError()(在 recv() 内) 返回comstat.cbInQue 发送的
我想通过音频插孔使用串行端口获取数据。我对此一无所知。但是我找到了一个应用audioserial可以发送数据到。所以,我认为应该获取像 audioserial 这样的数据。 .是否有相同的项目或对此很
串口有问题 我写了一个程序,可以读取端口 COM1 到 COM9,但可以打开 COMXX(如 com10、com11 等) 我搜索并了解到 tCOM1–COM9 是 NT 命名空间中保留名称的一部分。
我正在尝试在 Linux 中使用串口组织 nob-blocking 读写功能。这是我的代码:http://pastebin.com/RSPw7HAi一切正常,但已缓冲。这意味着,如果我通过控制台 +
我想将出现在 Arduino 中的数据传输到我的 C# 应用程序,但不知道我的代码有什么问题。Arduino 代码来了: int switchPin = 7; int ledPin = 13; boo
我正在编写一个网络驱动程序,它应该使用串行通信将数据包发送到 Arduino。这是一项家庭作业,仅用于教育目的。请在建议一切都可以在用户空间中完成之前考虑到这一点。 这answer说明了 filp_o
我想在笔记本电脑和模块之间进行通信。为此,我创建了一个 python 文件,它将一些数据包发送到 UART,它必须读取它们。我有一个创建数据包的 python 脚本(笔记本电脑): SOF= '24'
正在寻找正确的方法来在主板启动消息期间检测一个关键字。检测到关键字后,一秒后发送 Enter 键。内核是Linux。 # Serial port inisialisation is finished
我尝试通过串口读取数据,但读取操作总是返回0。 // Opening COM port and m_fd returned a valid number m_fd = open (m_com_por
微 Controller :dsPIC33EP512MU810 编译器:MikroC 我正在尝试通过 UART 从远程设备请求多个字节。要获得所需的信息,您发送一个请求字节以接收一个数据字节。当请求超
我计划很快开始围绕串行设备的输入进行编码,很高兴找到 Ruby-serialport . API 看起来很容易使用,但我对如何采用基于事件的方法来接收数据有点困惑。 每当 \n 出现时,我想对数据做一
我想在 Linux 中实现一个驱动程序,它有一个以太网堆栈,但在硬件上输出的数据将是一个串行端口。基本上,我想将我的串行端口注册为以太网驱动程序。有谁知道这是否可能?我希望能够将 IPv6 和/或 U
我正在开发一个项目,其中有许多硬件传感器通过 RS232 串行端口连接到部署机器。 但是……我正在一台没有物理 RS232 串行端口的机器上进行开发,但我想制作假的串行端口,我可以连接到这些端口并
我正在制作一个非常简单的 c++ 程序,它通过串行端口向 arduino 发送一个角度,然后 arduino 将该角度应用于伺服电机。我知道 Unix 把串口设备看成一个文件,实际上这是 c++ 代码
我是一名优秀的程序员,十分优秀!