Linux系统认知——驱动认知

news2024/9/23 23:29:47

文章目录

  • 一、驱动相关概念
    • 1.什么是驱动
    • 2.被驱动设备分类
    • 3.设备文件的主设备号和次设备号
    • 4.设备驱动整体调用过程
  • 二、基于框架编写驱动代码
    • 1.驱动代码框架
    • 2.驱动代码的编译和测试
  • 三、树莓派I/O口驱动的编写
    • 1.微机的总线地址、物理地址、虚拟地址介绍
    • 2.通过树莓派芯片手册确定需要配置的寄存器
    • 3.根据驱动框架编写树莓派Pin4引脚的驱动

一、驱动相关概念

1.什么是驱动

Linux内核驱动:是指一段代码,这段代码可以驱动底层硬件,即驱动就是对底层硬件设备的操作进行封装,并向上层提供函数接口。

2.被驱动设备分类

  • 字符设备
    指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后顺序。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等,字符设备驱动程序通常至少要实现open、close、read和write的系统调用,字符终端(/dev/console)和串口(/dev/ttyS0以及类似设备)就是两个字符设备,它们能很好的说明“流”这种抽象概念。
  • 块设备
    指可以从设备的任意位置读取一定长度数据的设备。块设备包括硬盘、磁盘、U盘和SD卡等。
  • 网络设备
    网络设备可以是一个硬件设备,如网卡; 但也可以是一个纯粹的软件设备, 比如回环接口(lo).一个网络接口负责发送和接收数据报文。

设备驱动框图

3.设备文件的主设备号和次设备号

Linux的设备管理是和文件系统紧密结合的,各种设备都以文件的形式存放在/dev目录下,称为设备文件,应用程序可以打开、关闭和读写这些设备文件,完成对设备的操作,就像操作普通的数据文件一样。为了管理这些设备,系统为设备编了号,每个设备号又分为主设备号和次设备号。主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备。
一个字符设备或者块设备都有一个主设备号和次设备号。主设备号和次设备号统称为设备号。主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各设备。例如一个嵌入式系统,有两个LED指示灯,LED灯需要独立的打开或者关闭。那么,可以写一个LED灯的字符设备驱动程序,可以将其主设备号注册成5号设备,次设备号分别为1和2。这里,次设备号就分别表示两个LED灯。
设备文件通常都在 /dev 目录下。
在这里插入图片描述

设备号的作用:
在内核空间中,存在一个驱动链表管理所用设备的驱动。驱动链表主要有两个功能,分别为添加(编写完驱动程序,加载到内核)功能和查找(调用驱动程序)功能。在这些过程中,驱动插入链表的顺序由设备号检索,这便是设备号的主要作用。

4.设备驱动整体调用过程

(1)C语言上层调用open函数。open(“dev/pin4”,O_RDWR);调用/dev下的pin4以可读可写的方式打开。对于上层open调用到内核时会发生一次软中断中断号是0X80,从用户空间进入到内核空间
(2)open会调用到system_call(内核函数),system_call会根据/dev/pin4设备名,去找出需要的设备号。
(3)再调到虚拟文件VFS ,调用VFS里的sys_open,sys_open会找到在驱动链表里面,根据主设备号和次设备号找到引脚4里的open函数,引脚4里的open是对寄存器操作及对硬件的操作。
在这里插入图片描述

二、基于框架编写驱动代码

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";   //模块名





static int pin4_open(struct inode *inode,struct file *file)
{
    printk("pin4_open\n");  //内核的打印函数和printf类似
    return 0;
}


static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
    printk("pin4_write\n");
    return 0;
}
static struct file_operations pin4_fops = {
    .owner = THIS_MODULE,
    .open  = pin4_open,//当应用层调用open函数时,内核会调用pin4_open.
    .write = pin4_write,//当应用层调用write函数时,内核会调用pin4_write.
};

int __init pin4_drv_init(void)  //真实的驱动入口
{

    int ret;
    devno = MKDEV(major,minor);  //创建设备号
    ret   = register_chrdev(major, module_name,&pin4_fops);  //注册驱动  告诉内核,把这个驱动加入到内核驱动的链表中

    pin4_class=class_create(THIS_MODULE,"myfirstdemo");//由代码在dev下自动生成设备
    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");`  

2.驱动代码的编译和测试

(1)进入Linux源码树目录下的驱动目录,因为驱动的是字符设备,所以进入的是驱动目录下的char目录。/home/zh/SYSTEM/linux-rpi-4.14.y/drivers/char

在这里插入图片描述
(2)将驱动代码放到上述目录下
在这里插入图片描述
(3)修改Makefile文件
在字符驱动目录下 打开Makefile文件,并以模块的方式将驱动载入:
在这里插入图片描述
(4)模块编译
进入源码树目录进行模块化编译,键入

 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules

(5)将编译好的驱动模块传到树莓派中

scp ./drivers/char/pin4driver.ko pi@192.168.169.221:/home/pi

在这里插入图片描述
加载内核驱动模块,在dev/目录下生成pin4驱动

 sudo insmod pin4driver.ko
 rmmod  pin4driver.ko //卸载驱动模块

在这里插入图片描述
(6)给pin4驱动加权限

sudo chmod 666 /dev/pin4

(7)驱动程序测试
pin4test.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 const *argv[])
{
	/* code */
	 int fd;
	fd = open("/dev/pin4",O_RDWR);
	write(fd,"hello",strlen("hello"));
	return 0;
}

执行测试程序后用dmesg 查看内核打印信息发现打印了驱动函数的内容

在这里插入图片描述

三、树莓派I/O口驱动的编写

这里以驱动树莓派pin4引脚置0或置1为例。

1.微机的总线地址、物理地址、虚拟地址介绍

(1)总线地址
地址总线(Address Bus)是一种计算机总线,是CPU或有DMA能力的单元,用来沟通这些单元想要访问(读取/写入)计算机内存组件/地方的物理地址。
地址总线决定了cpu所能访问的最大内存空间的大小,即cpu能访问的内存范围。
比如:装了32位的win 7 系统,内存8G,可系统最大只能识别3.29G,所以要使用4G以上大内存就要用windows x64位系统。装了32位的操作系统CPU的访问范围是2^32 bit,就是4194304kbit,就是4G。树莓派也是32位 ,一个G的内存,但它只能访问949M,剩下的另作他用。
(2)物理地址
在存储器里以字节为单位存储信息,为正确地存放或取得信息,每一个字节单元给以一个唯一的存储器地址,称为物理地址(Physical Address),又叫实际地址绝对地址

物理地址它是在地址总线上,以电子形式存在的,使得数据总线可以访问主存的某个特定存储单元的内存地址。在和虚拟内存的计算机中,物理地址这个术语多用于区分虚拟地址。尤其是在使用内存管理单元(MMU)转换内存地址的计算机中,虚拟和物理地址分别指在经MMU转换之前和之后的地址。
(3)虚拟地址
虚拟地址顾名思义即为逻辑地址(基于算法的地址)。

CPU启动保护模式后,程序运行在虚拟地址空间中。注意,并不是所有的“程序”都是运行在虚拟地址中。CPU在启动的时候是运行在实模式的,Bootloader以及内核在初始化页表之前并不使用虚拟地址,而是直接使用物理地址的。
如果CPU寄存器中的分页标志位被设置,那么执行内存操作的机器指令时,CPU(准确来说,是MMU,即Memory Management Unit,内存管理单元)会自动根据页目录和页表中的信息,把虚拟地址转换成物理地址,完成该指令。
使用了分页机制之后,4G的地址空间被分成了固定大小的页,每一页或者被映射到物理内存,或者被映射到硬盘上的交换文件中,或者没有映射任何东西。对于一般程序来说,4G的地址空间,只有一小部分映射了物理内存,大片大片的部分是没有映射任何东西。物理内存也被分页,来映射地址空间。

2.通过树莓派芯片手册确定需要配置的寄存器

在BCM2835芯片手册的第六章描述了General Purpose I/O (GPIO)外设相关寄存器。
这里驱动pin4引脚需要用到的寄存器有:
1.GPIO Function Select Registers (GPFSELn) 功能选择寄存器:
在这里插入图片描述
该寄存器共有五组,每个寄存器都有32位,以GPIO Alternate function select register 0为例,其中:
29-0位 :每三位对于一个引脚,比如29-27对应的是GPIO Pin 9,26-24对应的是GPIO Pin 8,且这三位取不同的值代表该三位对应的引脚选择不同的功能。比如,当29-27位为000时表示GPIO Pin 9是输入功能,29-27位为001时表示GPIO Pin 9是输出的功能。
2.GPIO Pin Output Set Registers (GPSETn) 置位寄存器:
在这里插入图片描述
该寄存器共两组,每个寄存器都有32位,将寄存器某一位置1即将对应的引脚置1。
3.GPIO Pin Output Clear Registers (GPCLRn) 清0寄存器:
与置位寄存器用法一至,将对应位数引脚置0.
4.所需寄存器的地址说明
在编写驱动程序时,IO空间的起始地址位0X3F000000,加上GPIO的偏移量0X200000,因此GPIO的物理地址是从0X3F200000开始的,而编程所需的地址是虚拟地址,需要通过MMU内存虚拟化管理将地址映射到虚拟地址上

3.根据驱动框架编写树莓派Pin4引脚的驱动

驱动代码
pin4driver.c

#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";   //模块名


//首先定义所要用的寄存器,为了防止地址被编译器优化需要用到volatile关键字
volatile unsigned int *GPFSEL0 = NULL;
volatile unsigned int *GPSET0 = NULL;
volatile unsigned int *GPCLR0 = NULL;

static int pin4_open(struct inode *inode,struct file *file)
{
    printk("pin4_open\n");  //内核的打印函数和printf类似

    //配置引脚4的寄存器,将其配置为输出模式,即将GPFSEL0寄存器的第14-12位配置成001
    *GPFSEL0 &= 0XFFFF9FFF;  //将第14,13位置0
    *GPFSEL0 |= 0X00001000; //将第12位置1
    return 0;
}


static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
    int usercmd;
    printk("pin4_write\n");
    copy_from_user(&usercmd,buf,count);//获取应用层write函数写入的内容
    if(usercmd == 1){
        printk("set 1\n");
        *GPSET0 |=(0x1 << 4); //将Pin4引脚置1
    }else if (usercmd == 0)
    {
        printk("set 0\n");
        *GPCLR0 |=(0X1 << 4);//将Pin4引脚置0
    }else{
        printk("undo\n");
    }
    return 0;
}

static struct file_operations pin4_fops = {
    .owner = THIS_MODULE,
    .open  = pin4_open,//当应用层调用open函数时,内核会调用pin4_open.
    .write = pin4_write,//当应用层调用write函数时,内核会调用pin4_write.
};

int __init pin4_drv_init(void)  //真实的驱动入口
{

    int ret;
    devno = MKDEV(major,minor);  //创建设备号
    ret   = register_chrdev(major, module_name,&pin4_fops);  //注册驱动  告诉内核,把这个驱动加入到内核驱动的链表中

    pin4_class=class_create(THIS_MODULE,"myfirstdemo");//由代码在dev下自动生成设备
    pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name);  //创建设备文件

    GPFSEL0 = (volatile unsigned int *)ioremap(0X3f200000,4);//需要将物理地址映射位虚拟地址 ipremap第一个参数需要被映射的物理地址。第二个参数位映射的字节数
    GPSET0  = (volatile unsigned int *)ioremap(0X3f20001C,4);//通过芯片手册可以看到该寄存器在基础地址上偏移了1C
    GPCLR0  = (volatile unsigned int *)ioremap(0X3f200028,4);//通过芯片手册可以看到该寄存器在基础地址上偏移了28
    return 0;
}

void __exit pin4_drv_exit(void)
{

    iounmap(GPFSEL0);
    iounmap(GPSET0);
    iounmap(GPCLR0);
    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");

测试代码():
pin4test.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 const *argv[])
{
        int fd,cmd;
        fd = open("/dev/pin4",O_RDWR);
        printf("inout 0 ro 1 , 0 :Pin4 Set 0,1:Pin4 Set 1\n");
        scanf("%d",&cmd);

        printf("cmd = %d \n",cmd);
        write(fd,&cmd,1);
        return 0;
}


将驱动代码编译后生成驱动模块放置在树莓派上进行测试

ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules

在这里插入图片描述
将生成的驱动模块拷贝至树莓派

scp ./drivers/char/pin4driver.ko pi@192.168.66.221:/home/pi

在树莓派上安装驱动并给驱动权限

sudo insmod pin4driver.ko
sudo chmod 666 /dev/pin4

运行测试程序:
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

zabbix部署

文章目录前言一、zabbix简介二、zabbix下载与部署三、部署完成、访问前端测试前言 一、zabbix简介 Zabbix 是一个企业级分布式开源监控解决方案。Zabbix 软件能够监控众多网络参数和服务器的健康度、完整性。Zabbix 使用灵活的告警机制&#xff0c;允许用户为几乎任何事件配置…

数据结构与算法——4时间复杂度分析(常见的大O阶)

这篇文章是时间复杂度分析的第二篇。在前一篇文章中&#xff0c;我们从0推导出了为什么要用时间复杂度&#xff0c;时间复杂度如何分析以及时间复杂度的表示三部分内容。这篇文章&#xff0c;是对一些常用的时间复杂度进行一个总结&#xff0c;相当于是一个小结论 1.常见的大O…

【LeetCode】剑指 Offer(11)

目录 题目&#xff1a;剑指 Offer 29. 顺时针打印矩阵 - 力扣&#xff08;Leetcode&#xff09; 题目的接口&#xff1a; 解题思路&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 写在最后&#xff1a; 题目&#xff1a;剑指 Offer 29. 顺时针…

西电计算机通信与网络(计网)简答题计算题核心考点汇总(期末真题+核心考点)

文章目录前言一、简答计算题真题概览二、网桥&#xff0c;交换机和路由器三、ARQ协议四、曼彻斯特编码和差分曼彻斯特编码五、CRC六、ARP协议七、LAN相关协议计算前言 主要针对西安电子科技大学《计算机通信与网络》的核心考点进行汇总&#xff0c;包含总共26章的核心简答。 【…

华为OD机试模拟题 用 C++ 实现 - 自动曝光(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 最多获得的短信条数(2023.Q1)) 文章目录 最近更新的博客使用说明自动曝光题目输入输出描述示例一输入输出说明示例二输入输出说明Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出࿰

Ubuntu搭建maven私服

1.安装JDK8 已经是JDK8的需要配置环境变量&#xff0c;如果是更高版本的JDK则需要修改nexus配置文件 2.下载nexus安装包 百度网盘下载&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1DfKqql8tZNQXEBxAEH7UyA 提取码&#xff1a;hx4p安装到有磁盘的目录如下所示&…

大数据框架之Hadoop:MapReduce(三)MapReduce框架原理——Join多种应用

3.7.1Reduce Join 1、工作原理 Map端的主要工作&#xff1a;为来自不同表或文件的key/value对&#xff0c;打标签以区别不同来源的记录。然后用连接字段作为key&#xff0c;其余部分和新加的标志作为value&#xff0c;最后进行输出。 Reduce端的主要工作&#xff1a;在Reduc…

C++杂谈(一)

前言 本系列也是慢更系列&#xff0c;主要收纳一些还不够单独成系列的C的杂项问题&#xff0c;或是一些与C有关&#xff0c;但不属于核心知识的一些旁系问题。 关于C与C的关系 「学C要先学C吗&#xff1f;」 「C和C是不是完全不同的两个语言&#xff1f;」 「这个语法是C的还…

Nginx网站服务——Nginx虚拟主机(基于域名、IP、端口)附带超详细实验步骤

文章目录一、基于域名的nginx虚拟主机1、基于域名的nginx虚拟主机的操作步骤2、实例操作&#xff1a;基于域名的nginx虚拟主机二、基于IP的nginx虚拟主机1、基于IP的nginx虚拟主机的操作步骤2、实例操作&#xff1a;基于IP的nginx虚拟主机三、基于端口的nginx虚拟主机1、基于端…

EasyRecovery16支持恢复文档表格图片音视频等各种不同的数据

误删重要文件这样的事想必大家都不陌生&#xff0c;有时粗心起来可能经常会出现这样的事情。现代人的生活和工作基本离不开电脑等多媒体设备&#xff0c;每天需要处理的文件量和数据量也是呈指数增长&#xff0c;所以一款能够实现误删数据修复的软件可以说是当代人的电脑必装了…

TOSCA自动化测试工具

TOSCA由德国公司Tricentis研发&#xff0c;提供英文和德语两种版本。 目前他们的网上培训课程大约是2000一套&#xff0c;从初级到高级&#xff0c;从工程师到BA&#xff0c;有技术&#xff0c;也有测试管理。 TOSCA的思想是&#xff0c;不用会编程的测试人员可以直接上手自动…

RK3568平台开发系列讲解(驱动基础篇)SMP(Symmetrical Multi-Processing)

🚀返回专栏总目录 文章目录 一、linux SMP 和 AMP二、linux SMP的启动流程三、CPU的描述:cpumask四、CPU之间的关系沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将介绍 SMP(Symmetrical Multi-Processing)。 一、linux SMP 和 AMP 目前支持多核处理器的实时操…

RabbitMQ延迟队列

目录 一、概念 二、使用场景 三、RabbitMQ 中的 TTL &#xff08;一&#xff09;队列设置 TTL &#xff08;二&#xff09;消息设置 TTL &#xff08;三&#xff09;两者的区别 四、整合SpringBoot实现延迟队列 &#xff08;一&#xff09;创建项目 &#xff08;二&am…

使用vscode+picgo+腾讯云搭建本地markdown编辑环境

本文主要介绍如何配置本地markdown编辑环境&#xff0c;主要使用vscodepicgo腾讯云&#xff0c;vscode为微软提供的免费的编辑器&#xff0c;picgo用于将图片方便上传到云端并生成链接&#xff0c;腾讯云提供存储环境。markdown语法可参考官方文档. 环境 windows10vscode 编辑…

【LeetCode】剑指 Offer 18. 删除链表的节点(题目一) p119 -- Java Version

题目链接&#xff1a;https://leetcode.cn/problems/shan-chu-lian-biao-de-jie-dian-lcof/ 1. 题目介绍&#xff08;18. 删除链表的节点&#xff09; 给定单向链表的头指针和一个要删除的节点的值&#xff0c;定义一个函数删除该节点。 返回删除后的链表的头节点。 注意&…

第52天|LeetCode84. 柱状图中最大的矩形

题目链接&#xff1a;84. 柱状图中最大的矩形 题目描述&#xff1a; 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 解法&#xff1a; ①此题求最大能…

Android开发 TextView

1.TextView控件 上一篇博客描述了安卓开发的整体结构&#xff0c;包括页面布局设计&#xff08;xml&#xff09;和程序逻辑设计(java&#xff09;&#xff0c; 开发一个APP&#xff0c;还需从最基础的控件入手&#xff0c;今天学习TextView控件 这是一个xml文件&#xff0c;描…

华为OD机试题,用 Java 解【压缩报文还原】问题

最近更新的博客 华为OD机试题,用 Java 解【停车场车辆统计】问题华为OD机试题,用 Java 解【字符串变换最小字符串】问题华为OD机试题,用 Java 解【计算最大乘积】问题华为OD机试题,用 Java 解【DNA 序列】问题华为OD机试 - 组成最大数(Java) | 机试题算法思路 【2023】使…

vue 依赖注入使用教程

vue 中的依赖注入&#xff0c;官网文档已经非常详细&#xff0c;笔者在这里总结一份 目录 1、背景介绍 2、代码实现 2.1、依赖注入固定值 2.2、 依赖注入响应式数据 3、注入别名 4、注入默认值 5、应用层 Provide 6、使用 Symbol 作注入名 1、背景介绍 为什么会出现依…

h5: 打开手机上的某个app

1、android端&#xff1a;直接通过URL Scheme方式打开。2、ios端&#xff08;2种&#xff09;&#xff1a;&#xff08;1&#xff09;使用URL Scheme方式打开。&#xff08;2&#xff09;使用Universal link方式打开。3、Universal link方式使用注意事项&#xff1a;&#xff0…