linux内核驱动——字符设备实现两个终端单向收发

linux内核驱动------字符设备实现两个终端单向收发

目录

参考

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!
相关推荐
小糖学代码26 分钟前
LLM系列:1.python入门:3.布尔型对象
linux·开发语言·python
shizhan_cloud30 分钟前
Shell 函数的知识与实践
linux·运维
Deng87234734833 分钟前
代码语法检查工具
linux·服务器·windows
霍夫曼3 小时前
UTC时间与本地时间转换问题
java·linux·服务器·前端·javascript
月熊3 小时前
在root无法通过登录界面进去时,通过原本的普通用户qiujian如何把它修改为自己指定的用户名
linux·运维·服务器
大江东去浪淘尽千古风流人物4 小时前
【DSP】向量化操作的误差来源分析及其经典解决方案
linux·运维·人工智能·算法·vr·dsp开发·mr
赖small强5 小时前
【Linux驱动开发】NOR Flash 技术原理与 Linux 系统应用全解析
linux·驱动开发·nor flash·芯片内执行
IT运维爱好者6 小时前
【Linux】LVM理论介绍、实战操作
linux·磁盘扩容·lvm
LEEE@FPGA6 小时前
ZYNQ MPSOC linux hello world
linux·运维·服务器
郝学胜-神的一滴6 小时前
Linux定时器编程:深入理解setitimer函数
linux·服务器·开发语言·c++·程序人生