linux内核驱动------字符设备实现两个终端单向收发
目录
- linux内核驱动------字符设备实现两个终端单向收发
-
- 参考
- [1 简单轮询收发](#1 简单轮询收发)
- [2 等待队列实现读取阻塞](#2 等待队列实现读取阻塞)
参考
1 简单轮询收发
- 创建内核驱动文件
chat_dev.c
:
c
#include <linux/init.h> //定义了module_init
#include <linux/module.h> //最基本的头文件,其中定义了MODULE_LICENSE这一类宏
#include <linux/fs.h> // file_operations结构体定义在该头文件中
#include <linux/device.h> //class、device结构体的定义位置
#include <linux/sched/signal.h>
#include <linux/uaccess.h> // copy_to_user & copy_from_user
#include <linux/mutex.h>
static const char* device_name = "chat_driver"; // 定义设备名
static const char* device_path = "chat"; // 定义设备文件名 /dev/chat
static struct class *chat_class; //定义class结构体
static struct device *chat_dev; //定义device结构体
//定义了open函数
static int chat_drv_open (struct inode *inode, struct file *file)
{
printk("open\n");
return 0;
}
static char words[1000]; //聊天内容缓存
static int words_cnt = 0; //聊天内容缓存字节数
static struct mutex words_mutex; //互斥锁
//read函数
static ssize_t chat_drv_read(struct file *file, char __user *buf, size_t size, loff_t *fpos) {
printk("read begin\n");
ssize_t cnt = 0; //返回值,表示读取字节长度
mutex_lock(&words_mutex); //上锁
if(words_cnt > 0){
copy_to_user(buf, words, words_cnt); //数据拷贝到用户态
printk("words_cnt=%d\n", words_cnt);
cnt = words_cnt;
words_cnt = 0; //读取后缓存清空
}
mutex_unlock(&words_mutex); //解锁
printk("read finish\n");
return cnt;
}
//定义了write函数
static ssize_t chat_drv_write (struct file *file, const char __user *buf, size_t size, loff_t * ppos)
{
printk("write begin\n");
mutex_lock(&words_mutex); //上锁
copy_from_user(words, buf, size); //从用户态拷贝数据
words_cnt = size; //更新缓存字节长度
printk("words_cnt=%d\n", words_cnt);
printk("words=%s\n", words);
mutex_unlock(&words_mutex); //解锁
printk("write finish\n");
return 0;
}
//在file_operations中注册open和write函数
static struct file_operations chat_drv_fo =
{
.owner = THIS_MODULE,
//将对应的函数关联在file_operations的结构体中
.open = chat_drv_open,
.read = chat_drv_read,
.write = chat_drv_write,
};
static int dev_id = 0; //初始化的设备号0
//init驱动的入口函数
static int __init chat_drv_init(void)
{
//注册设备,实际是将file_operations结构体放到内核的制定数组中,以便管理
//在register_chrdev中制定dev_id作为主设备号,若dev_id为0则自动分配一个主设备号
dev_id = register_chrdev(dev_id, device_name , &chat_drv_fo);
chat_class = class_create(THIS_MODULE, device_path); //初始化class结构体,指定设备文件名
chat_dev = device_create(chat_class, NULL, MKDEV(dev_id, 0), NULL, device_path);// 根据class来初始化device,会创建出对应的设备文件
mutex_init(&words_mutex);
printk("init\n");
return 0;
}
//驱动的出口函数
static void __exit chat_drv_exit(void)
{
printk("exit\n");
unregister_chrdev(dev_id, device_name); //卸载设备,实际是将file_operations结构体从内核维护的相关数组中以主设备号作为索引删除
device_unregister(chat_dev); // 后创建的先卸载
class_destroy(chat_class);
}
//内核将通过这个宏,来直到这个驱动的入口和出口函数
module_init(chat_drv_init);
module_exit(chat_drv_exit);
MODULE_LICENSE("GPL"); //指定协议
-
编译并加载内核驱动,详细可以看参考
-
创建发送端测试程序
write.c
:
c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
int fd;
char words[1000];
fd = open("/dev/chat", O_RDWR); //根据设备描述符打开设备
if(fd < 0) //打开失败
printf("can't open\n");
while(gets(words)) //可以自己输入
{
size_t len = strlen(words);
words[len] = '\0';
// printf("%s\n", words);
write(fd, words, len+1); //根据文件描述符调用write
}
return 0;
}
- 创建接收端测试程序
read.c
:
c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
int fd;
char words[1000];
fd = open("/dev/chat", O_RDWR); //根据设备描述符打开设备
if(fd < 0) //打开失败
printf("can't open\n");
while(1)
{
int ret = read(fd, words, 5); //根据文件描述符调用write,这里的5实际上没有用处
if(ret > 0)printf("%s\n", words);
sleep(1);
}
return 0;
}
- 编译两个测试程序,并分别在两个终端运行:
sh
gcc write.c -o write
gcc read.c -o read
- 结果如下:
sh
$ sudo ./write
nihao
qpwoeiruty
12222222222222rrrrrrrrrrrrrrrrrrrrrrrrrrrr
sh
$ sudo ./read
nihao
qpwoeiruty
12222222222222rrrrrrrrrrrrrrrrrrrrrrrrrrrr
2 等待队列实现读取阻塞
改成read阻塞方式,可以及时收到消息,并且减少cpu占用。
等待队列参考:Linux驱动---等待队列
- 创建内核驱动文件
chat_dev.c
:
c
#include <linux/init.h> //定义了module_init
#include <linux/module.h> //最基本的头文件,其中定义了MODULE_LICENSE这一类宏
#include <linux/fs.h> // file_operations结构体定义在该头文件中
#include <linux/device.h> //class、device结构体的定义位置
#include <linux/sched/signal.h>
#include <linux/uaccess.h> // copy_to_user & copy_from_user
#include <linux/mutex.h>
#include <linux/wait.h>
static const char* device_name = "chat_driver"; // 定义设备名
static const char* device_path = "chat"; // 定义设备文件名 /dev/chat
static struct class *chat_class; //定义class结构体
static struct device *chat_dev; //定义device结构体
//定义了open函数
static int chat_drv_open (struct inode *inode, struct file *file)
{
printk("open\n");
return 0;
}
static char words[1000]; //聊天内容缓存
static int words_cnt = 0; //聊天内容缓存字节数
static struct mutex words_mutex; //互斥锁
static wait_queue_head_t wq; //等待队列
//read函数
static ssize_t chat_drv_read(struct file *file, char __user *buf, size_t size, loff_t *fpos) {
printk("read begin\n");
ssize_t cnt = 0; //返回值,表示读取字节长度
wait_event(wq, words_cnt > 0); //阻塞直至唤醒,且满足后面条件
mutex_lock(&words_mutex); //上锁
copy_to_user(buf, words, words_cnt); //数据拷贝到用户态
printk("words_cnt=%d\n", words_cnt);
cnt = words_cnt;
words_cnt = 0; //读取后缓存清空
mutex_unlock(&words_mutex); //解锁
printk("read finish\n");
return cnt;
}
//定义了write函数
static ssize_t chat_drv_write (struct file *file, const char __user *buf, size_t size, loff_t * ppos)
{
printk("write begin\n");
mutex_lock(&words_mutex); //上锁
copy_from_user(words, buf, size); //从用户态拷贝数据
words_cnt = size; //更新缓存字节长度
printk("words_cnt=%d\n", words_cnt);
printk("words=%s\n", words);
mutex_unlock(&words_mutex); //解锁
wake_up(&wq); //唤醒
printk("write finish\n");
return 0;
}
//在file_operations中注册open和write函数
static struct file_operations chat_drv_fo =
{
.owner = THIS_MODULE,
//将对应的函数关联在file_operations的结构体中
.open = chat_drv_open,
.read = chat_drv_read,
.write = chat_drv_write,
};
static int dev_id = 0; //初始化的设备号0
//init驱动的入口函数
static int __init chat_drv_init(void)
{
//注册设备,实际是将file_operations结构体放到内核的制定数组中,以便管理
//在register_chrdev中制定dev_id作为主设备号,若dev_id为0则自动分配一个主设备号
dev_id = register_chrdev(dev_id, device_name , &chat_drv_fo);
chat_class = class_create(THIS_MODULE, device_path); //初始化class结构体,指定设备文件名
chat_dev = device_create(chat_class, NULL, MKDEV(dev_id, 0), NULL, device_path);// 根据class来初始化device,会创建出对应的设备文件
mutex_init(&words_mutex);
init_waitqueue_head(&wq);
printk("init\n");
return 0;
}
//驱动的出口函数
static void __exit chat_drv_exit(void)
{
printk("exit\n");
unregister_chrdev(dev_id, device_name); //卸载设备,实际是将file_operations结构体从内核维护的相关数组中以主设备号作为索引删除
device_unregister(chat_dev); // 后创建的先卸载
class_destroy(chat_class);
}
//内核将通过这个宏,来直到这个驱动的入口和出口函数
module_init(chat_drv_init);
module_exit(chat_drv_exit);
MODULE_LICENSE("GPL"); //指定协议
-
编译并加载内核驱动,详细可以看参考
-
创建发送端测试程序
write.c
:
c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
int fd;
char words[1000];
fd = open("/dev/chat", O_RDWR); //根据设备描述符打开设备
if(fd < 0) //打开失败
printf("can't open\n");
printf("A writes to B:\n");
while(gets(words))
{
size_t len = strlen(words);
words[len] = '\0';
write(fd, words, len+1); //根据文件描述符调用write
printf("\nA writes to B:\n");
// printf("%s\n\n", words);
}
return 0;
}
- 创建接收端测试程序
read.c
:
c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
int fd;
char words[1000];
fd = open("/dev/chat", O_RDWR); //根据设备描述符打开设备
if(fd < 0) //打开失败
printf("can't open\n");
while(1)
{
int ret = read(fd, words, 5); //根据文件描述符调用write
if(ret > 0){
printf("B reads from A:\n");
printf("%s\n\n", words);
}
}
return 0;
}
- 编译两个测试程序,并分别在两个终端运行:
sh
gcc write.c -o write
gcc read.c -o read
- 结果如下:
sh
$ sudo ./write
A writes to B:
ni hao
A writes to B:
hello
A writes to B:
Oh my God, I'm so handsome!
A writes to B:
sh
$ sudo ./read
B reads from A:
ni hao
B reads from A:
hello
B reads from A:
Oh my God, I'm so handsome!