【linux-IMX6ULL-字符设备驱动简单框架实验】

目录

  • 1. 字符设备驱动简介
    • 1.1 重要函数
    • 1.2 简单框架代码流程
    • 1.3 linux中关于驱动的重要命令
  • 2. 字符设备驱动简单框架编写
    • 2.1 添加LICENSE信息
    • 2.2 驱动模块的入口与出口
    • 2.3 入口和出口函数的编写
    • 2.4 设备操作结构体定义
      • 2.4.1 结构体函数内容填充
  • 3. 应用程序简介:
  • 4. 应用程序的编写思路

1. 字符设备驱动简介

  目前的驱动开发一般是分为三类,第一类就是字符设备驱动、块设备驱动、和网络驱动三类,其中字符设备驱动是最多最杂的,现在对字符设备驱动进行一个简要的介绍:
  字符设备驱动,是指那些以字节流进行数据传输的设备、IIC、SPI、LCD、按键、例如键盘、鼠标、打印机等,其中包含以下几个关键的部分:

  1. 设备注册和注销:通过设备注册使设备能被系统识别;注销则相反;
  2. 数据操作函数:通常包含open,read,write,realse等;
  3. 中断处理:处理设备产生的中断,以响应特定事件;

   通过字符型设备驱动,可以使系统方便统一管理不同的设备,这样就可以给上层应用提供相应的接口函数,方便应用程序与设备之间进行数据交换和通信;



   本次的实验是通过简单的实验建立一个设备驱动开发的基本框架,为后续的学习打下基础,其中会列出重要的函数以及重要的挂载指令,当然其中有一些函数是比较老的,例如要手动分配设备号,但是为了便于学习目前就以简单的为主,因为越是抽象的越简单,但是越抽象就越难以理解;

1.1 重要函数

  这里只是把本实验相关的重要函数给罗列出来了,主要的作用就是对本次实验的一个总结,如果没有做个这个实验,那么看着没啥感觉的;例如我下面罗列函数的顺序就是我们在编写驱动实验时整个流程的顺序,首先就是注册入口和出口函数,其次就是编写入口和出口函数,再次就是编写文件结构体的对应相关的函数内容;

  • MODULE-LICENSE("GPL"): 表示该内核模块遵循的许可协议是通用公共许可(GPL)。指定许可协议非常重要,它明确了该模块在使用、分发等方面的权利和限制。如果不写这个的话就会导致在装载设备驱动时出现警告;
  • module_init(*****_init):模块的入口函数,进行初始化,也就是加载模块时第一个就是运行这个函数注册的函数*******;对模块进行初始化设置;
  • module_exit(*****_exit):模块的出口函数,对模块进行卸载时就会执行这个函数注册的函数*******;
  • static int __init *****_init(void):设备加载函数,进行模块的初始化和加载的设置;这个函数要被入口函数进行注册后起作用
  • register_chrdev(unsigned int major, const char *name, const struct file_operations *fops):对设备进行注册;
  • static int __exit *****_exit(void):设备卸载函数、进行模块的卸载时这个函数内部的程序就会执行,不过要被出口函数进行注册
  • unregister_chrdev(unsigned int major, const char *name):对模块进行卸载;
  • static const struct file_operations **_fops : struct file_operations是一个很重要的结构体,其中定义了很多与文件操作有关的函数指针,例如read,write,realse,open等等,这些函数可以按需进行填充,这样就方便与设备之间进行文件的操作;下面进行实验时会进行一个详细的说明;

1.2 简单框架代码流程

  代码实验编写流程:


在这里插入图片描述


1.3 linux中关于驱动的重要命令

  • lsmod:显示有哪些模块被加载了,也就是显示所有的加载模块
  • lsmod:显示有哪些模块被加载了,也就是显示所有的加载模块
  • depmod:更新模块的依赖关系,也就是新加载一个模块时,要先运行一下这个命令,不然有错误
  • modprob ***.ko:这个命令的作用就是加载模块***.ko
  • mknod /dev/*** c 200 0:mknod:手动创建节点的命令。/dev/*** :创建设备的名称。c:字符型设备。200:主设备号,自己指定。0:次设备号,自己指定。
  • cat /proc/devices:这个命令的作用是查看所有加载的模块,显示所有设备号等;
  • rmmod ***.ko:卸载模块***.ko;
  • cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq:这个命令是显示当成CPU的频率;

2. 字符设备驱动简单框架编写

  上面已经介绍了驱动编写的则整体流程,以及介绍了重要的加载驱动的一些linux的命令,因此下面的代码就不做过多的介绍:

2.1 添加LICENSE信息

  我们需要在代码中添加LICENSE信息,否则编译会出错,不过我们还可以添加一些其他的信息,例如作者,邮箱等等;

MODULE_LICENSE("GPL");

2.2 驱动模块的入口与出口

  linux的驱动有两种加载形式,一就是编译进linux内核中,当内核启动,驱动也启动,另一种方法就是把启动编译成模块也就***.ko文件,通过insmode或者modprob的命令进行加载模块,这样做的好处就是便于调试,而且不用重启linux内核;代码如下,下面两个函数的作用就是对模块进行加载和卸载,并在加载和卸载函数中对字符设备进行注册,如我们裸机编程中系统中断函数对中断服务函数的注册一样;

/* * 模块入口和出口函数注册*/ 
module_init(chrdevbase_init);/*入口,加载模块*/
module_exit(chrdevbase_exit);/*出口,卸载模块*/

2.3 入口和出口函数的编写

  注意,入口函数是通过__init来修饰(注意是两个杠),而对于出口函数通过__exit进行修饰,在出入口函数中对设备进行注册,CHRDEVBASE_MAJOR是设备号,CHREEVBAScE_NAME是设备名,chrdevbase_fops是文件操作结构体;出口函数是类似的;

#define CHRDEVBASE_MAJOR 200  
#define CHREEVBASE_NAME "chrdevbase"static int __init chrdevbase_init(void)
{printk("chrdevbase_init3\r\n");/*注册字符设备*/register_chrdev(CHRDEVBASE_MAJOR, CHREEVBASE_NAME,&chrdevbase_fops);return 0;
}
static void __exit chrdevbase_exit(void)
{printk("chrdevbase_exit\r\n");unregister_chrdev(CHRDEVBASE_MAJOR,CHREEVBASE_NAME);
}

2.4 设备操作结构体定义

  对于设备的操作file_operations结构体,也称为文件操作结构体,这也是为什么linux下一切皆文件,我们的操作大部分都是通过文件来进行管理,我们可能用不到那么多的功能,因此我们要用到什么功能就进行对应的添加和书写就行,代码如下,注意这里使用的是=:

static const struct file_operations chrdevbase_fops={.owner	= THIS_MODULE,.open	= chrdevbase_open,.release	= chrdevbase_release,.read	= chrdevbase_read,.write	=	chrdevbase_write,
};

2.4.1 结构体函数内容填充

  可以看到,我们对上面的文件操作结构体中定义了四个函数指针,分别是:chrdevbase_open、chrdevbase_release、chrdevbase_read、chrdevbase_write,接下来就是对这四个函数进行内容填充:

static char readbuf[100];/*读缓冲*/
static char writebuf[100];
static char kerneldata[]={"kernel data!"};/************/
static ssize_t chrdevbase_read(struct file * file, char * buf, size_t count, loff_t *off)
{int ret=0;memcpy(readbuf,kerneldata,sizeof(kerneldata));ret = copy_to_user(buf,readbuf,count);if(ret<0){printk("Error!");}else{}return 0;
}
/************/
static ssize_t chrdevbase_write(struct file * file, const char * buf, size_t count,loff_t *off)
{int ret =0;ret = copy_from_user(writebuf,buf,count);printk("Kernel recevdata:%s\r\n",writebuf);if(ret = 0){printk("Kernel recevdata:%s\r\n",writebuf);}return 0;
}
static int chrdevbase_release(struct inode * inode, struct file * file)
{printk("chrdevbase_ralease\r\n");return 0;
}
static int chrdevbase_open(struct inode * inode, struct file * file)
{printk("chrdevbase_open\n");return 0;
}

3. 应用程序简介:

  当驱动程序编写完毕后,要通过应用程序进行调用驱动函数的一些接口函数,这些功能的实现是在应用程序中实现的,这样就实现了应用程序与驱动程序的分离可以这样类比,我们写应用程序就相当于我们在windows系统上写C程序,我们写C程序时也没有关注下层,这就是一种分离,不过这两者是可以相互进行数据交换的,不过要用特定的方法;注意驱动程序和应用程序的编译是不一样的:对于应用程序的编译是下面的指令:

  • arm-linux-gnueabihf-gcc ***APP.c -o ***APP:应用程序的编译指令
  • $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules:驱动程序的编译:Make部分核心指令

   从上面的编译指令中就可以看出区别,对于驱动的编译要用到一系列的库,这些库包含板子的信息,以及一些arm中的一些库等等,最终生成一个驱动模块,属于下层的驱动文件;
  而对于应用程序的编译则是用到了一个.c文件以及基础库,就像我们编译C语言一样,只不过我们写一个hello.c文件用的是gcc编译器,生成的是x86架构的可执行文件,同理,我们使用arm-linux-gnueabihf-gcc交叉编译器生成的是arm架构的可执行文件,这明显是上层的应用程序,不过我们写上层应用程序时通过传参命令的形式就可以与下层的驱动进行数据文件交换;

4. 应用程序的编写思路

  编写测试APP就是编写Linux应用程序,需要用到C库和文件操作相关的一些函数,open、read、write 和 close 这四个函数;这些函数可以根据下面的命令进行找详细帮助:“man 1”通常是用户命令的手册页;“man 2”一般是系统调用的手册页;“man 3”可能是 C 库函数的手册页;

wyj@BK:~$ man 2 read
wyj@BK:~$ man 2 write
wyj@BK:~$ man 2 close
wyj@BK:~$ man 2 open

  因此在应用程序中编写程序就是如何对驱动程序进行调用和使用,不过要注意的是对于驱动的编写属于内核态,而对于应用程序的编写属于应用态,应用态不能直接操作内核态,要通过一定的程序从而间接操作内核态

int mian(int argc,char *argv[])
{int ret = 0;int fd = 0;char *filename;char readbuf[100];char writebuf[100];static char usrdata[]={"Usr data!Usr data!Usr data!"};filename=argv[1];fd = open(filename, O_RDWR);if(fd<0){printf("Can't open file %s\r\n",filename);}/*read*/if(atoi(argv[2])==1){ret=read(fd, readbuf, 50); printf("\r\nAPP read data:%s\r\n",readbuf);}/*write*/if(atoi(argv[2])==2){memcpy(writebuf,usrdata,sizeof(usrdata));ret = write(fd, writebuf,50);}/*close*/ret = close(fd);if(ret<0){printf("Can't close %s\r\n",filename);}return 0;
}

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

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

相关文章

Design to code(2)

【碎碎念】从七点到十一点&#xff0c;累计用时4个小时完成的代码翻译Σ(&#xffe3;。&#xffe3;ノ)ノ DCDS图 顺序图&#xff08;支付过程&#xff09; 交互图&#xff08;订单&#xff09; 我的代码 Payment public class Payment { //定义支付订单金额 private…

static的了解

【关键字】static 使用总结_c static关键字-CSDN博客 本文来自上面的文章&#xff0c;这里用于学习&#xff0c;谢谢大佬的分享&#xff01;&#xff01;&#xff01; 非原创&#xff01;&#xff01;&#xff01; 1.一个项目中创建main.cpp和demo.cpp &#xff08;1&#…

FL Studio2025中文最新版本专业编曲软件有哪些新功能?

FL Studio 21&#xff0c;也被音乐制作爱好者亲切地称为“水果编曲软件”&#xff0c;是比利时的Image-Line公司研发的一款完整的音乐制作环境或数字音频工作站&#xff08;DAW&#xff09;。自从1990年代推出以来&#xff0c;FL Studio 以其直观的用户界面、丰富的插件支持和强…

Rust分割字符串的常见操作方法

在Rust编程语言中&#xff0c;分割字符串是一个常见的操作&#xff0c;可以通过多种方式实现。以下是一些常用的方法&#xff1a; 使用split方法&#xff1a; split方法可以按照指定的字符或字符序列来分割字符串。它返回一个迭代器&#xff0c;可以迭代分割后的字符串片段。 l…

玩机社区 - 2024年最美社区源码开源

玩机社区 - 2024年最美社区源码开源 教程源码文档都内置到压缩包了 https://pan.baidu.com/s/1xwcscTne-JMbmKEntiuAuA?pwd78oi

逻辑分析仪 - 采样率/采样深度

采样深度&#xff08;Sampling Depth&#xff09; 采样深度指的是逻辑分析仪在一次捕获过程中可以记录的最大样本数量。简单来说&#xff0c;采样深度越大&#xff0c;逻辑分析仪可以记录的数据量就越多。这对于分析长时间的信号变化或复杂的信号序列非常重要。 采样率&#…

2024年5月23日 (周四) 叶子游戏新闻

《Unclogged》Steam页面上线 马桶主题恐怖逃脱解谜Brody制作并发行&#xff0c;一款奇葩创意马桶主题恐怖逃脱解谜新游《Unclogged》Steam页面上线&#xff0c;本作暂不支持中文。 Meta人工智能主管杨立昆 大语言模型不会达到人类智能水平IT之家今日&#xff08;5月23日&#x…

QEMU启动Linux内核

在QEMU环境下启动linux内核命令如下&#xff1a; QEMU_AUDIO_DRVnone qemu-system-arm -m 256M -nographic -M versatilepb -kernel /home/yukeyang/myfile/linux-6.6.30/arch/arm/boot/zImage -append "consolettyAMA0 rdinit/bin/sh" -dtb arch/arm/boot/dts/arm/…

数据防泄漏系统哪个好用,给文件加密的软件

数据防泄露&#xff08;Data Leakage Prevention&#xff0c;DLP&#xff09;是指通过一定的技术手段&#xff0c;防止组织指定&#xff08;重要或敏感的&#xff09;数据或信息资产以违反安全策略规定的形式流出组织的一种策略。 信息防泄露以文档加密技术为核心&#xff0c;…

顺序表及其应用

掌握顺序表的初始化&#xff0c;初始化、查找、插入、删除、遍历、查看实际长度等操作 内容 从键盘输入n个整数&#xff0c;创建顺序表。【创建长度为n的顺序表】从键盘输入1个整数x&#xff0c;在顺序表中查找x所在的位置。若找到&#xff0c;输出该元素所在的位置(即数组下标…

SQL开窗函数

文章目录 概念&#xff1a;语法&#xff1a;常用的窗口函数及示例&#xff1a;求平均值&#xff1a;AVG() &#xff1a;求和&#xff1a;SUM():求排名&#xff1a;移动平均计数COUNT():求最大MXA()/小MIN()值求分区内的最大/最小值求当前行的前/后一个值 概念&#xff1a; 开窗…

同旺科技 FLUKE ADPT 隔离版发布 ---- 说明书

所需设备&#xff1a; 1、FLUKE ADPT 隔离版 内附链接&#xff1b; 应用于&#xff1a;福禄克Fluke 12E / 15BMax / 17B Max / 101 / 106 / 107 应用于&#xff1a;福禄克Fluke 15B / 17B / 18B

利用文本图像对比模型进行虚假信息检测

Harnessing the Power of Text-image Contrastive Models for Automatic Detection of Online Misinformation 论文地址: CVPR 2023 Open Access Repositoryhttps://openaccess.thecvf.com/content/CVPR2023W/WMF/html/Chen_Harnessing_the_Power_of_Text-Image_Contrastive_…

51单片机学习(4)3-1 独立按键控制LED亮灭

#include<REGX52.H> void main() { //P20xFE; P2_01; while(1) { if(P3_10) { P2_00&#xff1b; } else { P2_01&#xff1b; } } }

力扣周赛398题解

特殊数组Ⅰ 如果数组的每一对相邻元素都是两个奇偶性不同的数字&#xff0c;则该数组被认为是一个 特殊数组 。 Aging 有一个整数数组 nums。如果 nums 是一个 特殊数组 &#xff0c;返回 true&#xff0c;否则返回 false。 示例 1&#xff1a; 输入&#xff1a;nums [1] …

SEO:屏蔽流氓蜘蛛抓取

解决屏蔽流氓蜘蛛抓取&#xff0c;如MJ12bot 、DotBot 、BLEXBot 、PetalBot 、DataForSeoBot 1、robots文件屏蔽 User-agent: MJ12bot Disallow: / User-agent:DotBot Disallow: / User-agent:BLEXBot Disallow: / User-agent:PetalBot Disallow: / User-agent:DataForSeoBot…

【C++】<知识点> 标准和文件的输入输出

目录 一、输入输出操作 1. 相关的类 2. 标准流对象 3. istream类的成员函数 二、流操纵算子 1. 整数流的基数 2. 浮点数精度的流操纵算子 3. 域宽的流操纵算子 4. 其他的流操纵算子 5. 用户自定义流操纵算子 三、文件读写 1. 文本文件的读写 2. 二进制文件的读写 3. 文件读写…

vue 点击复制文本到剪贴板

一、首先在vue文件的template中定义复制按钮 <div size"small" v-if"item.prop jadeCode" class"cell-container"><span>{{ scope.row.jadeCode }}</span> <button click"handleCopy(scope.row.jadeCode)" clas…

一周开发一个客服工单系统

开发一个客服工单系统在一周内完成&#xff0c;需要详细的计划和高效的执行。以下是一个详细的开发计划&#xff0c;涵盖每天的主要任务和技术栈选择&#xff1a; 演示效果&#xff1a;gofly.v1kf.com 技术栈选择 前端&#xff1a;React.js 或 Vue.js后端&#xff1a;Go (Gin)数…

K8s是如何Watch的?

1. 概述 进入 K8s 的世界&#xff0c;会发现几乎所有对象都被抽象为了资源(Resource)&#xff0c;包括 K8s Core Resources(Pod, Service, Namespace 等)、CRD、APIService 扩展的资源类型。同时 K8s 底层将这些资源统一抽象为了 RESTful 的存储(Storage)&#xff0c;一方面服…