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


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

    参考

    1 简单轮询收发

    • 创建内核驱动文件chat_dev.c
    #include      //定义了module_init
    #include    //最基本的头文件,其中定义了MODULE_LICENSE这一类宏
    #include        // file_operations结构体定义在该头文件中
    #include     //class、device结构体的定义位置
    #include 
    #include     // copy_to_user & copy_from_user 
    #include 
    
    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");  //指定协议
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 编译并加载内核驱动,详细可以看参考

    • 创建发送端测试程序write.c

    #include 
    #include 
    #include 
    #include 
    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 创建接收端测试程序read.c
    #include 
    #include 
    #include 
    #include 
    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 编译两个测试程序,并分别在两个终端运行:
    gcc write.c -o write
    gcc read.c -o read
    
    • 1
    • 2
    • 结果如下:
    $ sudo ./write
    nihao
    qpwoeiruty
    12222222222222rrrrrrrrrrrrrrrrrrrrrrrrrrrr
    
    • 1
    • 2
    • 3
    • 4
    $ sudo ./read
    nihao
    qpwoeiruty
    12222222222222rrrrrrrrrrrrrrrrrrrrrrrrrrrr
    
    • 1
    • 2
    • 3
    • 4

    2 等待队列实现读取阻塞

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

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

    • 创建内核驱动文件chat_dev.c
    #include      //定义了module_init
    #include    //最基本的头文件,其中定义了MODULE_LICENSE这一类宏
    #include        // file_operations结构体定义在该头文件中
    #include     //class、device结构体的定义位置
    #include 
    #include     // copy_to_user & copy_from_user 
    #include 
    #include 
    
    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");  //指定协议
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 编译并加载内核驱动,详细可以看参考

    • 创建发送端测试程序write.c

    #include 
    #include 
    #include 
    #include 
    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 创建接收端测试程序read.c
    #include 
    #include 
    #include 
    #include 
    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 编译两个测试程序,并分别在两个终端运行:
    gcc write.c -o write
    gcc read.c -o read
    
    • 1
    • 2
    • 结果如下:
    $ 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:
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    $ sudo ./read
    B reads from A:
    ni hao
    
    B reads from A:
    hello
    
    B reads from A:
    Oh my God, I'm so handsome!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  • 相关阅读:
    STM32F1与STM32CubeIDE编程实例-NEC协议红外接收与解码
    测试人生 | 资深外包逆袭大厂测试开发:面试官的“歧视”表情深深刺痛了我
    使用Jenkins + docker打包部署后端服务
    【Apache Shiro】超详细笔记-1:功能、架构、集成Spring、认证
    信奥基本功:打字练习(盲打)
    北京智和信通亮相2023IT运维大会,共话数智浪潮下自动化运维新生态
    Mysql 查询表参考
    Docker安装教程
    05 doris 集群搭建
    使用 Lhotse 高效管理音频数据集
  • 原文地址:https://blog.csdn.net/weixin_43739110/article/details/136526280