字符设备驱动开发实验

news2024/12/26 2:37:55

我们以 chadev 这个虚拟设备为 例,完整的编写一个字符设备驱动模块。chadev 不是实际存在的一个设备,是为了方 便讲解字符设备的开发而引入的一个虚拟设备设备有两个缓冲区,一个为读缓冲 区,一个为写缓冲区,这两个缓冲区的大小都为 100 字节。在应用程序中可以向 chadev 设 备的写缓冲区中写入数据,从读缓冲区中读取数据。

1、编写实验程序

驱动程序chardevirs.c

#include <linux/module.h> //所有模块都需要的头文件
#include <linux/init.h> // init&exit 相关宏
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/fs.h>



#define  CHRDEVBASE_MAJOR    200   //主设备号
#define  CHRDEVBASE_NAME     "chadev"  //驱动名字

static char readbuf[100];  //读缓存
static char writebuf[100];  //写缓存
static char kerneldata[] = {"kernel data!"};

static int  char_open(struct inode *inode, struct file *file)
{
       // printk("chardevbase_open\n");
        return 0;
}

static ssize_t  char_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{     
       int ret = 0;  
      // printk("chardevbase_read\n");
       memcpy(readbuf,kerneldata,sizeof(kerneldata));
      //向用户空间发送数据 
       ret = copy_to_user(buf,readbuf,count);
       if(ret == 0)
       {
           
       }else
       {
        
       }

       
       return 0;

}

static int  char_release(struct inode *inode, struct file *file)
{

    //printk("chardevbase_release\n");
    return 0;
}


static ssize_t   char_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
        int ret = 0;
        //printk("chardevbase_write\n");
        /* 接收用户空间传递给内核的数据并且打印出来 */
        ret = copy_from_user(writebuf,buf,count);
        if(ret ==0)
        {
              printk("kernel recevdata:%s\n",writebuf);  
        }else
        {
                
        }

        
        return 0;

}

//设备操作函数结构体
static  struct   file_operations   chrdebase_fops = {
          .owner      = THIS_MODULE,
          .open       = char_open,
          .release    = char_release,
          .read       = char_read,
          .write      = char_write,
};

//驱动入口函数
static int __init  hello_init (void)
{       
          int  ret;
          printk("Hello module init\n");
          ret = register_chrdev(CHRDEVBASE_MAJOR,CHRDEVBASE_NAME,&chrdebase_fops);  //注册字符设备驱动
          if(ret < 0)
          {
               printk("chardevbase init failed\n");

          }
          
          return 0;
}
//驱动出口函数
static void __exit hello_exit (void)
{
          printk("Hello module exit\n");
          unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);  //注销字符设备驱动
}

module_init(hello_init);
module_exit(hello_exit);

//LICENSE 和作者信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("hsj");

char_open 函数,当应用程序调用 open 函数的时候此函数就会调用, 本例程中我们没有做任何工作,只是输出一串字符,用于调试。这里使用了 printk 来输出信息, 而不是 printf!因为在 Linux 内核中没有 printf 这个函数。printk 相当于 printf 的孪生兄妹printf 运行在用户态,printk 运行在内核态。在内核中想要向控制台输出或显示一些内容,必须使用 printk 这个函数。

char_read 函数,应用程序调用 read 函数从设备中读取数据的时候此函 数会执行。参数 buf 是用户空间的内存,读取到的数据存储在 buf 中,参数 cnt 是要读取的字节 数,参数 offt 是相对于文件首地址的偏移。kerneldata 里面保存着用户空间要读取的数据,先将 kerneldata 数组中的数据拷贝到读缓冲区 readbuf 中,通过函数 copy_to_user 将 readbuf 中的数据复制到参数 buf 中。因为内核空间不能直接操作用户空间的内存,因此需要借 助 copy_to_user 函数来完成内核空间的数据到用户空间的复制

static inline long copy_to_user(void __user *to, const void *from, unsigned long n)

参数 to 表示目的,参数 from 表示源,参数 n 表示要复制的数据长度。如果复制成功,返 回值为 0,如果复制失败则返回负数。

char_write 函数,应用程序调用 write 函数向设备写数据的时候此函数 就会执行。参数 buf 就是应用程序要写入设备的数据,也是用户空间的内存,参数 cnt 是要写入 的数据长度,参数 offt 是相对文件首地址的偏移。通过函数 copy_from_user 将 buf 中的 数据复制到写缓冲区 writebuf 中,因为用户空间内存不能直接访问内核空间的内存,所以需要 借助函数 copy_from_user 将用户空间的数据复制到 writebuf 这个内核空间中。

2、编写测试 APP

应用程序charapp.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>


int main(int argc,char *argv[])
{   
        char  buf[100];
        char  wbuf[100];
        char  writebuf[] = "hello kernel";
        int   ret;
        int   fd;
        char *filename;
        
        if(argc != 3)
        {
            printf("Error usage!\r\n");
            return -1;
        }
        filename = argv[1];

        fd = open(filename,O_RDWR);
        if(fd < 0)
        {         
                printf("open failed\n");
                return -1;
        }
         
        if(atoi(argv[2])==1);  //读
        { 
             ret = read(fd, buf, sizeof(buf));
             if(ret < 0)
            {

                printf("read failed\n");
                return -1;
            }
            else 
            {
                printf("APP read data: %s\r\n",buf);
            }
        }
     if(atoi(argv[2]) == 2)   //写
     {  
        memcpy(wbuf,writebuf,sizeof(writebuf));
        ret =  write(fd, wbuf, sizeof(writebuf));
        if(ret < 0)
        {

             printf("write failed\n");
            return -1;
        }else
        {
            
        }
    }

      close(fd); 
      return 0;   
}

判断运行测试 APP 的时候输入的参数是不是为 3 个,main 函数的 argc 参数表示 参数数量,argv[]保存着具体的参数,如果参数不为 3 个的话就表示测试 APP 用法错误。比如, 现在要从 chadev设备中读取数据,需要输入如下命令:

./app /dev/chadev 1

上述命令一共有三个参数“./chrdevbaseApp”、“/dev/chadev”和“1”,这三个参数分别 对应 argv[0]、argv[1]和 argv[2]。第一个参数表示运行 APP 这个软件,第二个参数表 示测试APP要打开/dev/chadev这个设备。第三个参数就是要执行的操作,1表示从chadev 中读取数据,2 表示向 chadev 写数据。

3、运行测试

加载驱动模块

1.输入如下命令加载 chardevirs.ko 驱动文件:

insmod chardevirs.ko 或 modprobe chardevirs.ko

2.输入如下命令查看当前 系统中有没有 chadev 这个设备:

cat /proc/devices

 3.创建设备节点文件

驱动加载成功需要在/dev 目录下创建一个与之对应的设备节点文件,应用程序就是通过操 作这个设备节点文件来完成对具体设备的操作。输入如下命令创建/dev/chadev 这个设备节 点文件

mknod /dev/chadev c 200 0

其中“mknod”是创建节点命令,“/dev/chadev”是要创建的节点文件,“c”表示这是个 字符设备,“200”是设备的主设备号,“0”是设备的次设备号。创建完成以后就会存在 /dev/chadev 这个文件,可以使用“ls /dev/chadev -l”命令查看

 

4.chadev设备操作测试

 

 

5.卸载驱动模块

如果不再使用某个设备的话可以将其驱动卸载掉,比如输入如下命令卸载掉
chardevirs 这 个设备

 rmmod chardevirs.ko

卸载以后使用 lsmod 命令查看 chardevirs 这个模块还存不存在

 此时系统已经没有任何模块了,chardevirs 这个模块也不存在了, 说明模块卸载成功。至此,chrdevbase 这个设备的整个驱动就验证完成了,驱动工作正常

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/496193.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring事务隔离级别详解

Spring有五大隔离级别&#xff1a; 1、ISOLATION_DEFAULT 2、ISOLATION_READ_UNCOMMITTED 3、ISOLATION_READ_COMMITTED 4、ISOLATION_REPEATABLE_READ 5、ISOLATION_SERIALIZABLE ISOLATION_DEFAULT 用底层数据库的设置隔离级别。 ISOLATION_READ_UNCOMMITTED 一个事…

java 数组创建的方法

数组是一个由一组元素组成的集合&#xff0c;我们可以用一个数组来表示集合。 java中最基本的数据类型是字符串&#xff0c;其长度是固定的&#xff0c;且不可变&#xff0c;一个字符串只能以一个数字开头。 在 Java中我们可以通过 myConst关键字来指定数组的长度。下面就看一下…

直线飙升到10万+star的AutoGpt,有多强?帮我写了个网页!

先来感受一下10万的star&#xff0c;到底有多强&#xff01; 从4月2日开始&#xff0c;直线飙升到10万star Auto-GPT是一个实验性的开源应用程序&#xff0c;展示了GPT-4语言模型的功能。这个程序由GPT-4驱动&#xff0c;将LLM“思想”链接在一起&#xff0c;以自主实现您设定的…

CTO解读:从“RSAC 2023”到“韧性数据安全”—Stronger Together

一年一度RSA Conference已落下帷幕。作为全球最具规模的安全大会&#xff0c;每年一届的RSAC都是安全行业的风向标。 2023年RSAC的主题是&#xff1a;Stronger Together&#xff0c;一起更强大。安全产品往往是场景化的&#xff0c;单点产品解决不同的问题&#xff0c;有机的整…

QT自制软键盘 最完美、最简单、跟自带虚拟键盘一样

QT自制软键盘 最完美、最简单、跟自带虚拟键盘一样 [1] QT自制软键盘 最完美、最简单、跟自带虚拟键盘一样一、本自制虚拟键盘特点二、windows打开系统自带软键盘三、让键盘界面保持在最上方、不改变底层界面焦点四、长按按键重复输入键盘内容五、模拟键盘点击事件完成虚拟键盘…

虹科方案|使用 HK-TRUENAS支持媒体和娱乐工作流程-2

一、支持 M&E 工作流程的HK-TRUENAS 屡获殊荣的 TrueNAS 存储解决方案支持单独的工作空间来存放可在现场或制作室访问的媒体资产。 TrueNAS 提供企业功能&#xff0c;支持多个物理和虚拟应用程序&#xff0c;并具有同步块和文件存储访问。 这些功能允许备份和重新利用视频、…

路由递归配置

路由递归原理 路由必须有直连的下一跳才能够指导转发,但是路由生成时下一跳可能不是直连的,因此需要计算出一个直连的下一跳和对应的出接口,这个过程就叫做路由递归 。 路由递归也被称为路由 迭代。 实验配置 路由器R1配置 interface GigabitEthernet0/0/0ip address 192.…

《编程思维与实践》1049.GPS数据处理

《编程思维与实践》1049.GPS数据处理 题目 思路 注记: UTC为世界标准时间(0时区),东区加,西区减. 分两步实现: 1.判断是否为有效的语句: 有效需要满足三个条件: ①语句含$GPRMC,可以通过strstr搜索来判断; ②状态已定位,可以用sscanf来读取判断; ③异或结果与校验值相同,校验值…

【Vue学习笔记6】好用的 Vueuse 工具包

1. 安装Vueuse VueUse 的官方&#xff08;https://vueuse.org/&#xff09;的介绍说这是一个 Composition API 的工具集合&#xff0c;适用于 Vue 2.x 或者 Vue 3.x&#xff0c;用起来和 React Hooks 还挺像的。 VueUse 插件的安装 npm install vueuse/core2. 实现全屏功能 …

【三十天精通Vue 3】第二十七天 Vue 3的实战案例-接口进度条

✅创作者:陈书予 🎉个人主页:陈书予的个人主页 🍁陈书予的个人社区,欢迎你的加入: 陈书予的社区 🌟专栏地址: 三十天精通 Vue 3 文章目录 引言一、安装进度条插件1.1 安装NProgress插件1.2 在Vue3中引入NProgress插件二、在路由中使用进度条2.1 在全局路由中使用进度…

ubuntu20.04安装搜狗输入法

搜狗输入法下载网址&#xff1a; https://shurufa.sogou.com/linux 选择x86_64版本 打开 系统设置——区域和语言——管理已安装的语言——在“语言”tab下——点击“添加或删除语言” 弹出“已安装语言”窗口&#xff0c;勾选中文&#xff08;简体&#xff09;&#xff0c;点…

【RPC、WebSocket】

文章目录 有HTTP协议为什么还要RPCTCPHTTP 和 RPCHTTP 和 RPC 有什么区别 有HTTP协议为什么还要WebSocket使用HTTP不断轮询长轮询WebSocket怎么建立WebSocket连接 有HTTP协议为什么还要RPC TCP TCP三个特点&#xff1a;面向连接、可靠、基于字节流。 基于字节流 字节流可以理…

Disabled PicPipeline: ImagesPipeline requires installing Pillow 4.0.0 or later

目录 一、scrapy是什么 二、问题以及原因 三、解决办法 1、确保系统已经安装了 Pillow 库。 2、安装 Pillow 库。 3、在项目根目录中添加 Pillow 的 .pth 文件。 一、scrapy是什么 Scrapy是一个用于从网站和Web应用中抓取数据的强大的Python库。Scrapy支持异步I/O和 Scr…

销售数据分析怎么做?这篇文章说清楚了

如何分析销售数据&#xff1f;分析销售数据有哪些指标&#xff1f;销售数据分析有什么作用&#xff1f; 销售数据是不是得通过数据分析软件啊&#xff1f; 本文将为您解答疑惑—— 一、分析销售数据的指标 从两个层面上来讲&#xff0c;一个是对销售情况的整体把控&#xf…

大学校友会管理APP系统开发 重温同学梦再叙校园情

互联网技术的深入发展&#xff0c;让各行各业对网络的依赖都逐渐加深&#xff0c;可以说网络在今天已经成为无数个你我他不可或缺的平台。学生时代是一生中最美好的时期&#xff0c;校友是社会高效重要的社会资本和无形资产&#xff0c;校友与校友之间信息交流也需要依靠互联网…

虚拟化转向容器的新方案,红帽正式推出虚拟化容器统一平台——openshift虚拟化

编辑 | 宋慧 出品 | CSDN 云计算 云原生、容器化&#xff0c;是近年 IT 界主要的话题之一。 数字化转型的浪潮下&#xff0c;技术在朝向更加有利于业务快速迭代的方向发展。据 CSDN 最新年度《中国开发者调查报告》数据显示&#xff0c;近一半的公司&#xff08;43%&#xff0…

爬虫|Python|ts格式的加密视频合并方法

前言&#xff1a; 爬虫的一些基本概念&#xff1a; 对于爬虫来说&#xff0c;没有道德&#xff08;比如&#xff0c;某些爬虫上w的并发&#xff0c;那么&#xff0c;一些小站可能就会崩溃&#xff0c;其实爬虫也是可以作为网络攻击的&#xff0c;假设有需要攻击的网站&#x…

Shell 脚本传递参数的两种方式:位置传参与指令式传参

Shell 脚本传递参数的两种方式 方式一&#xff1a;数字传参 直接传参 数字传递可以用$1 $2 $3 ......获取第一个第二个第三个参数&#xff0c;$0获取命令&#xff08;也就是你的文件名&#xff09;&#xff0c;$#可以查看总的参数个数 以下文件命名为param1&#xff08;shel…

rk3568平台调试typec口实现uvc输出,网络共享等功能

一、修改kernel相关配置 注意&#xff1a;一定要知道主控接线&#xff0c;那个物理口是otg的&#xff0c;然后要找准与之所连接的phy和控制器。然后处理CC1 CC2识别芯片&#xff0c;fusb302。默认sdk自带有驱动&#xff0c;需要配上中断脚和提供VBUS 5V的脚。用来判断角色是DF…

( “ 图 “ 之 拓扑排序 ) 210. 课程表 II ——【Leetcode每日一题】

❓210. 课程表 II 难度&#xff1a;中等 现在你总共有 numCourses 门课需要选&#xff0c;记为 0 到 numCourses - 1。给你一个数组 prerequisites &#xff0c;其中 prerequisites[i] [ai, bi] &#xff0c;表示在选修课程 ai 前 必须 先选修 bi 。 例如&#xff0c;想要学…