Linux驱动入门——编写第一个驱动

目录

前言

驱动入门知识

1.APP 打开的文件在内核中如何表示

2.打开字符设备节点时,内核中也有对应的 struct file

编写 Hello 驱动程序步骤

1.流程介绍

2.驱动代码:

3.应用层代码:

4.本驱动程序的 Makefile 内容:

5.上机实验:


前言

在编译驱动程序之前要先编译内核,原因有三点:

  • 驱动程序要用到内核文件
  • 编译驱动时用的内核、开发板上运行到内核,要一致
  • 更换板子上的内核后,板子上的其他驱动也要更换

编译内核步骤看我之前写过的文章,编译替换内核_设备树_驱动_IMX6ULL-CSDN博客

驱动入门知识

1.首先我们通常都是在Linux的终端上打开一个可执行文件,然后可执行文件就会执行程序。那么这个可执行文件做了什么呢?

2.可执行文件先是在应用层读取程序,其中会有很多库函数,库函数是属于内核之中。而内核又会往下调用驱动层程序。最终驱动层控制具体硬件。

  • 其实应用程序到库是比较容易理解的,比如我们刚学习C语言的时候,使用了printf,scanf等等这些函数。而这些函数就在库中。
  • 库可以和系统内核相连接,具体怎么实现的我也不太清楚。
  • 我们写了一个驱动程序,就需要告诉内核,这个过程叫做注册。我们注册了驱动之后,内核里面就会有这个驱动程序的信息,然后上层应用就可以调用。

3.所以我们只需要知道,咱们需要编写两个程序,一个是驱动层的,一个是应用层的,最后驱动层需要注册进入内核,应用层才能够使用。其他的先不要管。

4.我们在应用层调用read函数,对应驱动层的read函数。write函数和write函数对应。open函数和open函数对应。close函数和release函数对应(这个为什么不一样我也不清楚)。

5.我们对Linux 应用程序对驱动程序的调用流程有一个简单了解之后,我得知道整个程序编写流程应该怎么做。至于流程为什么是这样的,我们记住即可。因为这些都是人规定的,如果之后学的深了再进行深究也不迟,现在我们主要是入门     

1.APP 打开的文件在内核中如何表示

APP 打开文件时,可以得到一个整数,这个整数被称为文件句柄。对于 APP 的每一个文件句柄,在内核里面都有一个“struct file”与之对应。

我们使用 open 打开文件时,传入的 flags、mode 等参数会被记录在内核中对应的 struct file 结构体里(f_flags、f_mode):

int open(const char *pathname, int flags, mode_t mode);

去读写文件时,文件的当前偏移地址也会保存在 struct file 结构体的 f_pos 成员里。

2.打开字符设备节点时,内核中也有对应的 struct file

注意这个结构体中的结构体:struct file_operations *f_op,这是由驱动程序提供的。

结构体 struct file_operations 的定义如下:

编写 Hello 驱动程序步骤

主要为一下七个步骤:

  1. 确定主设备号,也可以让内核分配
  2. 定义自己的 file_operations 结构体
  3. 实现对应的 drv_open/drv read/drv write 等函数,填入 file operations 结构体
  4. 把 file_operations 结构体告诉内核: register_chrdev
  5. 谁来注册驱动程序啊? 得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
  6. 有入口函数就应该有出口函数: 卸载驱动程序时,出口函数调用unregister_chrdev
  7. 其他完善:提供设备信息,自动创建设备节点: class_create,device_create

1.流程介绍

<1>我们首先需要编写一个file_operations类型的结构体,这个结构体用于管理驱动程序。之后我们将驱动程序注册进入内核之后,我们在应用层调用这个驱动,那么就可以直接通过这个结构体来操作驱动中的open,write,read等函数。

<2>实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体。这样我们在应用层调用open,write,read等函数,就是调用这个驱动了。

这个时候有人可能会问了,有这么多个驱动,我怎么知道open对应的是哪一个驱动?很简单,咱们在写应用层程序的时候,是不是第一个参数是需要传入一个设备号。系统根据这个设备号来判断是调用的哪一个驱动。

<3>把 file_operations 结构体告诉内核: register_chrdev。我们写了一个驱动,但是内核是不知道的。那么怎么办呢?我们就去注册他,内核就明白,有了这个驱动,然后给他分配一个设备号。之后应用层就可以根据这个设备号来调用驱动层了。

<4> 这个时候,有人就有疑问了,谁来注册这个结构体?于是我们需要一个入口函数来进行注册,安装驱动程序时,就会去调用这个入口函数。

<5>有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev。

<6>最后需要加入GPL协议。因为Linux是遵顼GPL协议的,所以你如果需要使用Linux其他的驱动层函数,就必须遵顼GPL协议,强制要求开源代码。根据这个协议,你可以要求所有使用Linux的厂商提供驱动层源代码,同时别人也可以要求你公开你的驱动层代码,这个是相互的。不过很多厂商为了规避这个协议,驱动源代码很简单,复杂的东西放在应用层。至于还有一个作者名字的添加,随便写不写。                           

2.驱动代码:

hello_drv.c

#include <linux/module.h>#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>/* 1. 确定主设备号                                                                 */
static int major = 0;
static char kernel_buf[1024];
static struct class *hello_class;#define MIN(a, b) (a < b ? a : b)/* 3. 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);err = copy_to_user(buf, kernel_buf, MIN(1024, size));return MIN(1024, size);
}static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);err = copy_from_user(kernel_buf, buf, MIN(1024, size));return MIN(1024, size);
}static int hello_drv_open (struct inode *node, struct file *file)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}static int hello_drv_close (struct inode *node, struct file *file)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}/* 2. 定义自己的file_operations结构体                                              */
static struct file_operations hello_drv = {.owner	 = THIS_MODULE,.open    = hello_drv_open,.read    = hello_drv_read,.write   = hello_drv_write,.release = hello_drv_close,
};/* 4. 把file_operations结构体告诉内核:注册驱动程序                                */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init hello_init(void)
{int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);major = register_chrdev(0, "hello", &hello_drv);  /* /dev/hello */hello_class = class_create(THIS_MODULE, "hello_class");err = PTR_ERR(hello_class);if (IS_ERR(hello_class)) {printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);unregister_chrdev(major, "hello");return -1;}device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); /* /dev/hello */return 0;
}/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数           */
static void __exit hello_exit(void)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);device_destroy(hello_class, MKDEV(major, 0));class_destroy(hello_class);unregister_chrdev(major, "hello");
}/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */module_init(hello_init);
module_exit(hello_exit);MODULE_LICENSE("GPL");

3.应用层代码:

hello_drv_test.c


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>/** ./hello_drv_test -w abc* ./hello_drv_test -r*/
int main(int argc, char **argv)
{int fd;char buf[1024];int len;/* 1. 判断参数 */if (argc < 2) {printf("Usage: %s -w <string>\n", argv[0]);printf("       %s -r\n", argv[0]);return -1;}/* 2. 打开文件 */fd = open("/dev/hello", O_RDWR);if (fd == -1){printf("can not open file /dev/hello\n");return -1;}/* 3. 写文件或读文件 */if ((0 == strcmp(argv[1], "-w")) && (argc == 3)){len = strlen(argv[2]) + 1;len = len < 1024 ? len : 1024;write(fd, argv[2], len);}else{len = read(fd, buf, 1024);		buf[1023] = '\0';printf("APP read : %s\n", buf);}close(fd);return 0;
}

怎么把.c 文件编译为驱动程序.ko?

这要借助内核的顶层 Makefile,先设置好交叉编译工具链,编译好你的板子所用的内核,然后修改 Makefile 指定内核源码路径,最后即可执行 make 命令编译驱动程序和测试程序。

4.本驱动程序的 Makefile 内容:

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88all:make -C $(KERN_DIR) M=`pwd` modules$(CROSS_COMPILE)gcc -o hello_drv_test hello_drv_test.cclean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -f hello_drv_testobj-m   += hello_drv.o

5.上机实验:

执行 make 命令编译驱动程序和测试程序

启动单板后,可以通过 NFS 挂载 Ubuntu 的某个目录,访问该目录中的程序。

打开内核打印:echo "7 4 1 7" > /proc/sys/kernel/printk

关闭内核打印:echo 0       4       0      7  > /proc/sys/kernel/printk

insmod 就是install module的缩写(载入模块)

insmod hello_drv.ko装载驱动

ls /dev/hello -l // 驱动程序会生成设备节点 驱动程序会生成设备节点

lsmod 确认驱动已经安装

我们知道驱动已经安装好了,那么我们需要知道这个驱动的设备号

cat /proc/devices,查看当前已经被使用掉的设备号

驱动名字与我们在驱动层使用register_chrdev()函数的第二个参数有关

./hello_drv_test // 查看测试程序的用法

./hello_drv_test -w zglnb // 往驱动程序中写入字符串

./hello_drv_test -r // 从驱动程序中读出字符串

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

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

相关文章

4fiddler抓包工具的使用

一、定义 1.1 抓包的定义 说明&#xff1a;客户端向服务器发送请求以及服务器响应客户端的请求&#xff0c;都是以数据包来传递的。 抓包(packet capture)&#xff1a;通过工具拦截客户端与服务器交互的数据包 1.2 fiddler的介绍 Fiddler是一个http协议调试代理工具&#…

市场全局复盘 20231208

一、板块成交额排名&#xff1a; 资金流入前三个板块K 线&#xff1a; 行业成交额排名&#xff1a; 个股资金流入排名&#xff1a; select 成交额排名 ,近日指标提示 ,短线主题 ,涨停分析,CODE,名称,DDE大单净量,现价,量比,连板天,周涨停,月涨停,年涨停天,连涨天,…

【每日一题】—— B. StORage room(Codeforces Round 912 (Div. 2))(位操作符)

&#x1f30f;博客主页&#xff1a;PH_modest的博客主页 &#x1f6a9;当前专栏&#xff1a;每日一题 &#x1f48c;其他专栏&#xff1a; &#x1f534; 每日反刍 &#x1f7e1; C跬步积累 &#x1f7e2; C语言跬步积累 &#x1f308;座右铭&#xff1a;广积粮&#xff0c;缓称…

使用阿里云国际CDN加速后网站无法访问的排查步骤

使用阿里云国际CDN加速后网站无法访问的排查步骤&#xff0c;下面是一些常见的问题&#xff0c;以&#xff1a;www.c.9he.com为例&#xff0c;如果解决不了来信服务器厂商解决。 检查CDN访问异常是CDN节点的问题还是源站问题 如果是源站访问异常&#xff0c;请直接排查源站服务…

Faster R-CNN

Faster R-CNN是作者Ross Girshick继Fast R-CNN后的又一力作。同样使用VGG16作推理速度在GPU上达到5fps(包括候选区域的生成)&#xff0c;准确率为网络的backbone&#xff0c;也有进一步的提升。在2015年的ILSVRC以及COCO竞赛中获得多个项目的第一名。 算法流程 右边这部分和Fa…

算法Day27 身材管理(三维背包)

身材管理&#xff08;三维背包&#xff09; Description Input Output Sample 代码 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scanner new Scanner(System.in);int n scanner.nextInt(); // 输入n的值int money sca…

KaiOS 运营商相关文件operator_variant_manager.js代码功能和调试

gaia/apps/system/js/operator_variant_manager.js at master mozilla-b2g/gaia GitHub js文件接口功能 No 接口/常量 功能 1 OperatorVariantManager var OperatorVariantManager function(core) 2 OperatorVariantManager.IMPORTS OperatorVariantManager.I…

搜集怎么绘制三维曲线和曲面?

1、针对函数对象是单一变量、两个函数的情况。用plot3函数&#xff1b;&#xff08;三维曲线&#xff09; 看一下matlab官方的例子&#xff1a; t 0:pi/50:10*pi; st sin(t); ct cos(t); plot3(st,ct,t) 绘制出来的曲线&#xff1a; 几个比较关键的点&#xff1a; &…

【Marp】基于Markdown-Marp快速制作PPT

【Marp】基于Markdown-Marp快速制作PPT 文章目录 【Marp】基于Markdown-Marp快速制作PPT零、参考资料一、Marp基本语法&#xff08;创建分页&#xff0c;排版图片&#xff0c;更换主题&#xff0c;Marp扩展指令修改样式&#xff09;1、创建新的PPT页面2、插入图片 & 排版图…

解决删除文件后 WSL2 磁盘空间不释放的问题

查看 Linux distributions 打开 PowerShell 并执行如下命令&#xff1a; wsl -l -v 搜索并找到 ext4.vhdx 文件 我的 ext4.vhdx 文件如下&#xff1a; C:\Users\xxx\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu22.04LTS_79rhkp1fndgsc\LocalState\ext4.vhdx 由于…

软件开发流程分析

软件开发流程分析 相关概念1 原型设计2 产品设计3 交互设计4 代码实现详细步骤 相关概念 前端&#xff1a;自研API&#xff0c;调用第三放API 后端&#xff1a;自研API&#xff0c;第三方API 数据库&#xff1a;Mysql&#xff0c;数据采集&#xff0c;数据迁移 服务器&#xf…

数据结构:第13关:查找两个单词链表共同后缀的起始结点

任务描述编程要求 输入输出测试说明来源 任务描述 本关任务&#xff1a;假定采用带头结点的单链表保存单词&#xff0c;当两个单词有相同的后缀时&#xff0c;则可共享相同的后缀空间。 例如&#xff0c;“loading”和“being”的存储映像如下图所示&#xff1a; 设str1和str2…

【LLM】大模型之RLHF和替代方法(DPO、RAILF、ReST等)

note SFT使用交叉熵损失函数&#xff0c;目标是调整参数使模型输出与标准答案一致&#xff0c;不能从整体把控output质量&#xff0c;RLHF&#xff08;分为奖励模型训练、近端策略优化两个步骤&#xff09;则是将output作为一个整体考虑&#xff0c;优化目标是使模型生成高质量…

火山引擎边缘计算用硬核助力赛事直播

经过一个多月激烈争夺&#xff0c;2023英雄联盟全球总决赛终于在11月19日落下帷幕。精彩的对决和高热话题使得直播平台观赛人数暴增&#xff0c;给直播平台稳定性和资源储备提出了巨大的考验。

推荐3dmax常用15款插件,快来了解一下吧!

推荐3dmax常用15款插件&#xff0c;快来了解一下吧&#xff01; 插件是3ds MAX软件的重要组成部分&#xff0c;提供了太多便利&#xff0c;也提升了建模、渲染和动画的效率&#xff0c;下面就给大家推荐25款常用的3dMax插件。 1&#xff09;DashedShape DashedShape实线转虚线…

3c分支语句和循环语句(非重点)

文章目录 1. 什么是语句&#xff1f;2. 分支语句&#xff08;选择结构&#xff09;2.1 if语句2.1.1 悬空else2.1.2 if书写形式的对比 2.2 switch语句2.2.1 在switch语句中的 break2.2.2 default子句 3. 循环语句3.1 while循环3.1.1 while语句中的break和continue3.2 for循环3.2…

研表究明,文字的序顺并不定一能响影GPT-4读阅

深度学习自然语言处理 原创作者&#xff1a;yy 很多年前&#xff0c;你一定在互联网上看过这张图&#xff0c;展示了人脑能够阅读和理解打乱顺序的单词和句子&#xff01;而最近东京大学的研究发现&#xff0c;大语言模型&#xff08;LLMs&#xff09; 尤其是 GPT-4&#xff0c…

对象与对象数组

对象与对象数组 实验介绍 本章节主要介绍对象数组和对象成员。在实际的开发中&#xff0c;对象数组和对象成员是经常使用的&#xff0c;所以首先需要学习对象数组与对象成员的各种使用方法。 提示&#xff1a;为了方便课程讲解&#xff0c;示例代码使用类内定义的方式实现&a…

19 redis缓存数据同步问题

1、缓存穿透 指缓存和数据库中都没有的数据&#xff0c;而用户不断发起请求。由于缓存不命中&#xff0c;并且出于容错考虑&#xff0c;如果从存储层查不到数据则不写入缓存&#xff0c;这将导致这个不存在的数据每次请求都要到存储层去查询&#xff0c;缓存就没有意义了。 在…

掌控安全 -- header注入

http header注入 该注入是指利用后端验证客户端口信息&#xff08;比如常用的cookie验证&#xff09;或者通过http header中获取客户端的一些信息&#xff08;比如useragent用户代理等其他http header字段信息&#xff09;&#xff0c;因为这些信息是会重新返回拼接到后台中的&…