linux驱动的学习 驱动开发初识

news2024/11/26 23:39:16

1 设备的概念

在学习驱动和其开发之前,首先要知道所谓驱动,其对象就是设备

1.1 主设备号&次设备号:

在Linux中,各种设备都以文件的形式存在/dev目录下,称为设备文件最上层的应用程序可以打开,关闭,读写这些设备文件,从而完成对设备的操作

为了管理这些设备,系统为设备编了号,每个设备都拥有主设备号次设备号主设备号用于区分不同种类的设备,而次设备号用于区分同一类型的多个设备(对于常用的设备如硬盘,Linux赋予的主设备号一般是3)

  • 在/dev目录下输入“ls -l”,就可以看到设备文件对应的主次设备号

1.2 设备号的用处

在了解了什么是主次设备号之后,就要了解设备号的用处:

  • 用户态中:当用户调用了如open, read, write等函数想要操作设备文件时,需要两个参数,第一个是文件名,第二个就是设备号
  • 内核态中:存在着一个驱动链表,用于管理所有设备的驱动,而驱动在链表中的位置就由设备号来检索

2 驱动的概念

参考:最全Linux驱动开发全流程详细解析(持续更新)-CSDN博客

Q1:什么是驱动?

A:驱动与底层硬件直接打交道,充当了硬件(设备)与应用软件中间的桥梁


Q2:驱动的功能?

  1. 对设备初始化和释放
  2. 把数据从内核传送到硬件 & 从硬件读取数据
  3. 读取应用程序传送给设备文件的数据 & 回送应用程序请求的数据
  4. 检测和处理设备出现的错误

Q3:驱动的分类?

Linux驱动分为三个基础大类:字符设备驱动块设备驱动网络设备驱动

  • 字符设备(Char Device)
  1. 字符设备是个能够像字节流(类似文件)一样被访问的设备
  2. 对字符设备发出读/写请求时,实际的硬件I/O操作一般紧接着发生
  3. 字符设备驱动程序通常至少要实现open、close、read和write系统调用
  4. 比如我们常见的lcd、触摸屏、键盘、led、串口等等,他们一般对应具体的硬件都是进行出具的采集、处理、传输
  • 块设备(Block Device)
  1. 一个块设备驱动程序主要通过传输固定大小的数据(一般为512或1k)来访问设备
  2. 块设备通过buffer cache(内存缓冲区)访问,可以随机存取,即:任何块都可以读写,不必考虑它在设备的什么地方
  3. 块设备可以通过它们的设备特殊文件访问,但是更常见的是通过文件系统进行访问
  4. 只有一个块设备可以支持一个安装的文件系统
  5. 比如我们常见的电脑硬盘、SD卡、U盘、光盘等
  • 网络设备(Net Device)
  1. 任何网络事务都经过一个网络接口形成,即一个能够和其他主机交换数据的设备
  2. 访问网络接口的方法仍然是给它们分配一个唯一的名字(比如eth0),但这个名字在文件系统中不存在对应的节点
  3. 内核和网络设备驱动程序间的通信,完全不同于内核和字符以及块驱动程序之间的通信,内核调用一套和数据包传输相关的函(socket函数)而不是read、write等
  4. 比如我们常见的网卡设备、蓝牙设备

回顾上节Linux系统的构造:

可见,驱动位于内核态,上面是系统调用;下面是硬件(设备)

2.1 上层用户操控设备的流程:

每一个系统调用,在驱动中都有与之对应的一个驱动函数

以open为例:其系统调用为sys_open,那么驱动文件中就会有一个与其对应的“实现open的驱动函数:xxx_open()”,其大致步骤就是:用户层C库的open系统调用层的open驱动函数的open

以上是一个我作为初学者总结的大概流程,下图是一个更详细的流程:

2.2 Linux驱动的运行方式

  1. 驱动编译进 Linux 内核中,当 Linux 内核启动的时就会自动运行驱动程序
  2. 驱动编译成模块(Linux 下模块扩展名为.ko),并在Linux 内核启动以后使用相应命令加载驱动模块

3 驱动开发实战

尝试使用“将驱动编译成模块的方式”来编写“字符设备的驱动

驱动开发不是一件容易的事情,对于初学者,可以先根据一个固定的框架来学习驱动的开发:

3.1 基本的字符设备驱动框架

注释很重要,认真看

#include <linux/fs.h>		 //file_operations声明
#include <linux/module.h>    //module_init  module_exit声明
#include <linux/init.h>      //__init  __exit 宏定义声明
#include <linux/device.h>	 //class  devise声明
#include <linux/uaccess.h>   //copy_from_user 的头文件
#include <linux/types.h>     //设备号  dev_t 类型声明
#include <asm/io.h>          //ioremap iounmap的头文件
 
 
static struct class *pin4_class;  
static struct device *pin4_class_dev;
 
static dev_t devno;                //设备号
static int major =231;  		   //主设备号
static int minor =0;			   //次设备号
static char *module_name="pin4";   //模块名
 
 
//_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
    printk("pin4_open\n");  //内核的打印函数和printf类似
     
    return 0;
}
 
//_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
    printk("pin4_write\n");  //内核的打印函数和printf类似

    return 0;
}
 
static struct file_operations pin4_fops = { //结构体的类型是“file_operations”,名字可以自定义
//该结构体的成员就包含实现open和write的驱动函数
//当上层用户想要open或者write这个设备时,就会最终跳转到这个驱动代码中实现的open和write操作函数
//此处只赋值了该结构体中的三个成员变量(在keil中是不能这样写的,linux中可以),这个结构体其实有很多成员,如果想要实现更多的驱动函数,可以把更多的该结构体成员赋值并在这段代码中重写
    .owner = THIS_MODULE,
    .open  = pin4_open,
    .write = pin4_write,
};
 
int __init pin4_drv_init(void)   //真实驱动入口
{
 
    int ret;
    devno = MKDEV(major,minor);  //创建设备号
    ret   = register_chrdev(major, module_name, &pin4_fops);  //注册驱动,告诉内核:把这个驱动加入到内核驱动的链表中

    //以下两句代码目的是“生成设备文件”,也可以通过“mknod”命令手动生成,但是一般不会这样做
    pin4_class=class_create(THIS_MODULE,"myfirstdemo"); //先创建‘类’
    pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //再创建‘设备’
 
    return 0;
}
 
void __exit pin4_drv_exit(void)
{
    device_destroy(pin4_class,devno); //先销毁‘设备’
    class_destroy(pin4_class); //在销毁‘类’
    unregister_chrdev(major, module_name);  //卸载驱动
}
 
module_init(pin4_drv_init);  //入口,内核加载驱动的时候,这个宏会被调用
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");    //linux内核遵循GPL协议

在代码中添加大量“static”的原因是:内核的代码太多了,为了防止出现重名导致歧义,加入static可以保证变量的作用域只在当前代码中,从而不会影响到其他的代码。

file_operation 结构体是 Linux 内核驱动操作函数集合

比如,我在写某设备的驱动文件,并想为其实现一个read的驱动函数,那么我就需要:

  1. 定义一个类型为file_operation的结构体XXX,其中XXX是自定义的结构体名
  2. 在XXX结构体中使用“.read = XXX_read,”,其中XXX_read是自定义的函数名
  3. 在驱动代码里重写XXX_read函数,函数的参数格式和file_operation类型结构体中给出的write成员的参数格式保持一致,函数体就是想要实现的具体内容

3.2 驱动的编译

  • 打开虚拟机,进入Linux源码的路径:

  • 然后进入“drivers/char/”子目录(driver:驱动;char:字符型设备):

  • 在这个路径下创建一个新的C文件"mydriver_pin4.c",内容为刚刚的字符驱动框架:

  • 修改当前路径下的Makefile,确保这个新的驱动会被编译到:

  • 回到linux内核源码的路径,运行以下指令尝试编译:
ARCH=arm CROSS_COMPILE=/home/mjm/ras_CrossCompile/gcc-linaro-5.1-2015.08-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- KERNEL=kernel7 make -j4 modules

回顾在前几节学习内核编译时的代码:

ARCH=arm CROSS_COMPILE=/home/mjm/ras_CrossCompile/gcc-linaro-5.1-2015.08-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- KERNEL=kernel7 make -j4 zImage modules dtbs

区别就是:代码的最后少了“zImage”和“dtbs”。原因也很简单,现在只需要编译驱动,不需要再次生成镜像文件等其他文件。

  • 将编译好的“mydriver_pin4.ko”通过以下的scp命令发送到树莓派:
scp drivers/char/mydriver_pin4.ko pi@192.168.2.26:/home/pi/mjm_code

 此时,编译好的驱动文件就出现在树莓派上了:

3.3 驱动的加载(卸载)

由于现在刚刚把驱动编译成了.ko的模块,所以需要运行以下指令来加载驱动模块:

sudo insmod mydriver_pin4.ko

补充:

  • 驱动的卸载:
sudo rmmod mydriver_pin4.ko //此时驱动名字后不用加".ko"
  • 查看内核模块:
lsmod

 运行成功后,就可以在/dev下看到生成的设备文件“pin4”了:

如果用“ls -l” ,就可以看到这个设备文件的主次设备号,和框架代码中的设置相同:

 

3.4 驱动的测试

在树莓派下写一个测试驱动的C代码:

pin4_test.c:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

int main()
{
	int driver_fd;

	driver_fd = open("/dev/pin4",O_RDWR); //以可读可写打开的方式打开驱动
	if(driver_fd < 0){
		perror("fail to open driver file:");
	}else{
		printf("open driver file success!\n");
	}

	driver_fd = write(driver_fd,'a',1); //向驱动文件写一个字节
	if(driver_fd < 0){
		perror("fail to write to driver file:");
	}else{
		printf("write success!\n");
	}

	return 0;
}

编译然后运行:

1. gcc pin4_test.c -o pin4_test
2. sudo ./pin4_test

如果加了sudo还是没法运行,可以根据错误提示修改,可能需要给驱动文件一个执行权限:

sudo chmod 666 /dev/pin4
//666代表让所有用户都有所有权限

 可见,运行成功,没有报错!此时还可以另开一个窗口输入“dmesg”查看内核打印的信息:

 可见内核也按照框架代码中的printk成功打印了信息!驱动测试成功!

同时,结果也再次印证了:当用户在最上层对 驱动文件 调用C库的open函数后,最后的结果还是调用最底层 驱动文件里实现的open驱动函数

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

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

相关文章

1852_bash中的find应用扩展

Grey 全部学习内容汇总&#xff1a; https://github.com/GreyZhang/toolbox 1852_bash中的find应用扩展 find这个工具我用了好多年了&#xff0c;但是是不是真的会用呢&#xff1f;其实不然&#xff0c;否则也不会出现这种总结式的笔记。其实&#xff0c;注意部分小细节之后…

[AutoSar]基础部分 RTE 介绍

目录 关键词平台说明一、什么是RTE二、RTE的主要功能 关键词 嵌入式、C语言、autosar、EcuM、wakeup、flex 平台说明 项目ValueOSautosar OSautosar厂商vector芯片厂商TI编程语言C&#xff0c;C编译器HighTec (GCC) 一、什么是RTE RTE&#xff08;Run-Time Environment&…

Docker 的基本概念、优势、及在程序开发中的应用

Docker 是一种容器化平台,它通过使用容器化技术,将应用程序及其依赖性打包到一个独立的、可移植的容器中,从而实现应用程序的快速部署、可靠性和可扩展性。 下面是 Docker 的一些基本概念和优势: 容器:Docker 使用容器化技术,将应用程序及其依赖性打包到一个可移植的容器…

网络基础(十一):VRRP原理与配置

目录 前言&#xff1a; 1、VRRP的基本概述 2、VRRP的基本原理 2.1VRRP的基本结构 2.2设备类型 2.3状态机 2.4VRRP路由器的抢占功能 2.5VRRP路由器的优先级 2.6VRRP工作原理 2.7主备路由器的工作内容 3、VRRP的基本配置 3.1配置主路由器和备用路由器 3.2配置PC1与P…

DOM树和DOM对象与JS关系的深入研究

const和let使用说明 var不好用&#xff0c;我们如果用变量都是用let&#xff0c;如果用常量乃是不变的量&#xff0c;我们用const&#xff0c;见let const知变量是否可变。比如一个常量在整个程序不会变&#xff0c;但是你用let&#xff0c;是可以的。但是let最好与内部变量改…

【二分查找】【z型搜索】LeetCode240:搜索二维矩阵

LeetCoe240搜索矩阵 本文涉及的基础知识点 二分查找算法合集 题目 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。 每列的元素从上到下升序排列。 示例 1&#xff1a; 输入&#xff1a;…

爬虫练习-获取imooc课程目录

代码&#xff1a; from bs4 import BeautifulSoup import requests headers{ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0, }id371 #课程id htmlrequests.get(https://coding.imooc.com/class/chapter/id.html#Anchor,head…

【FPGA】电梯楼层显示(简易)

前言 这是作者室友的项目&#xff0c;本来不管作者事儿的&#xff0c;但是后来听到说是室友去网上找人花了80块买了个劣质的&#xff0c;不仅是从CSDN上抄的&#xff0c;而且使用的板子还不符合室友的要求。可叹作者心软啊&#xff0c;顺便给室友做了。 在代码实现部分会给出设…

手机上的python怎么运行,python在手机上怎么运行

大家好&#xff0c;本文将围绕python程序如何在手机端运行展开说明&#xff0c;python程序如何在手机上运行是一个很多人都想弄明白的事情&#xff0c;想搞清楚手机上的python怎么运行需要先了解以下几个事情。 如何用手机编程Python&#xff1f; 1.QPython3&#xff1a;这是一…

高性价比AWS Lambda无服务体验

前言 之前听到一个讲座说到AWS Lambda服务&#xff0c;基于Serverless无服务模型&#xff0c;另外官网还免费提供 100 万个请求 按月&#xff0c;包含在 AWS 免费套餐中是真的很香&#xff0c;对于一些小型的起步的网站或者用户量不大的网站&#xff0c;简直就是免费&#xff…

liunx之Samba服务器

环境&#xff1a;虚拟机CENTOS 7和 测试机相通 一、Samba服务器_光盘共享&#xff08;匿名访问&#xff09; 1.在虚拟机CENTOS 7安装smb服务&#xff0c;并在防火墙上允许samba流量通过 2. 挂载光盘 3.修改smb.conf配置文件&#xff0c;实现光盘匿名共享 4. 启动smb服务 5.在…

数据结构-猴子吃桃问题

一、需求分析 有一群猴子摘了一堆桃子&#xff0c;他们每天都吃当前桃子的一半且再多吃一个&#xff0c;到了第10天就只余下一个桃子。用多种方法实现求出原来这群猴子共摘了多少个桃子。要求&#xff1a; 1)采用数组数据结构实现上述求解&#xff1b; 2)采用链数据结构实现上述…

EM(Expectation-Maximum)算法

EM算法 简介 EM算法的核心分为两步 E步&#xff08;Expection-Step&#xff09;M步&#xff08;Maximization-Step&#xff09; 因为在最大化过程中存在两个参量 r , θ r,\theta r,θ&#xff0c;其中若知道 r r r&#xff0c;则知道 θ \theta θ&#xff1b;若知道 θ \…

04_Web框架之Django一

Web框架之Django一 学习目标和内容 1、能够描述Django的作用 2、能够使用Django创建应用 3、能够使用GET和POST请求方式进行传参 4、能够使用Django的函数式方法定义视图 5、能够进行Django的配置文件修改 6、能够基本使用Django的路由定义 一、Django相关介绍 1、什么是Djan…

运维实践|采集MySQL数据出现many connection errors

文章目录 问题出现问题分析当前环境问题分析 解决方案1 检查调度事件任务是否开启2 开启调度事件任务3 创建一张日志表4 创建函数存储过程5 创建事件定时器6 开启事件调度任务7 检查核实是否创建 总结 问题出现 最近在做OGG结构化数据采集工作&#xff0c;在数据采集过程中&am…

SIEM 解决方案的不同部署方式,如何选择SIEM 解决方案

安全信息和事件管理&#xff08;SIEM&#xff09;作为一种网络安全解决方案&#xff0c;是多种技术的融合&#xff0c;这些技术结合了包括安全信息管理和安全事件管理在内的流程。简单来说&#xff0c;SIEM 解决方案是一种重要的安全工具&#xff0c;它收集、存储和分析来自整个…

QT自带打包问题:无法定位程序输入点?metaobject@qsound

文章目录 无法定位程序输入点?metaobjectqsound……检查系统环境变量的配置&#xff1a;打包无须安装qt的文件 无法定位程序输入点?metaobjectqsound…… 在执行release打包程序后&#xff0c;相应的release文件夹下的exe文件&#xff0c;无法打开 如有错误欢迎指出 检查系…

Java架构师系统架构内部维度分析

目录 1 导语2.1 安全性维度概述2.2 流程安全性2.3 架构安全性2.4 安全维度总结3 伸缩性维度概述和场景思路3.1 无状态应用弹性伸缩3.2 阿里云Knative弹性伸缩3.3 有状态应用弹性伸缩3.4 伸缩性维度总结想学习架构师构建流程请跳转:Java架构师系统架构设计 1 导语

轻量封装WebGPU渲染系统示例<51>- 视差贴图(Parallax Map)(源码)

视差纹理是一种片段着色阶段增强材质表面凹凸细节的技术。 当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/material/src/voxgpu/sample/ParallaxTexTest.ts 当前示例运行效果: 此示例基于此渲染系统实现&#xff0c;当前示例TypeScript源码如…

Spring之容器:IOC(2)

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…