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

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

参考

1 简单轮询收发

  • 创建内核驱动文件chat_dev.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

#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
#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;
}
  • 编译两个测试程序,并分别在两个终端运行:
gcc write.c -o write
gcc read.c -o read
  • 结果如下:
$ sudo ./write
nihao
qpwoeiruty
12222222222222rrrrrrrrrrrrrrrrrrrrrrrrrrrr
$ sudo ./read
nihao
qpwoeiruty
12222222222222rrrrrrrrrrrrrrrrrrrrrrrrrrrr

2 等待队列实现读取阻塞

改成read阻塞方式,可以及时收到消息,并且减少cpu占用。

等待队列参考:Linux驱动—等待队列

  • 创建内核驱动文件chat_dev.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

#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
#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;
}
  • 编译两个测试程序,并分别在两个终端运行:
gcc write.c -o write
gcc read.c -o read
  • 结果如下:
$ 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:
$ sudo ./read
B reads from A:
ni hao

B reads from A:
hello

B reads from A:
Oh my God, I'm so handsome!
  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值