ARM-linux系统驱动复习(一)

作者:swing
目前就读二本公立排名末尾学校一名大三学生
前言:我认为写复习笔记在不借助资料的情况下可以对自己之前不懂的地方进行扫盲,然后盲点就看之前笔记就行了,框架比敲代码更重要(我自己认为的啊)。做一个东西时候有了框架我就知道我该干嘛,然后通过脑海里面的框架再对细节进行详细的写。

一、上电流程

CPU启动后做的事(凭自己理解随便画的):在这里插入图片描述

SOC上电后会去读取ROM的数据,读取后写入RAM里面,再从RAM里面读取数据(当时学习的时候在想,为啥不直接RAM和ROM整一起。我猜应该是早期的时候因为掉电可保存存储器速度慢的原因吧,然后这个习惯就一直保存下来了,我猜的)。

二、启动流程

Linux流程框图(按照自己理解的框架,也有可能是错的,随便画的):
在这里插入图片描述

u-boot其实就是一个boot load,也就类似STM32F1那个.s头文件
他引导系统启动,告诉linux内核,你所需要的东西在哪里。
这个头文件里面对内存读取的首地址一定是各类中断向量表,,地址内容是各类向量的地址。
u-boot可以启动很多种架构的芯片,x86,r-v,RT-OS等等之类的。
复习完驱动再来复习这两个u-boot和linux内核
CPU在读完ROM里面内容(uboot和linux内核)后写入RAM地址里面,进行启动,然后再去读取ROM的根文件。
*

三、系统流程

Linux实现框图(按照自己理解随便画的):
在这里插入图片描述

linux应用要调用外设资源,有三种方法:1、系统调用 2、系统异常(中断)、陷入。linux驱动层提供给给应用层接口。驱动层去访问读写外设时候,需要得到物理内存相对应的片内外设寄存器地址,而linux在驱动层访问物理内存时候,需要先得到虚拟内存所映射的物理内存地址,得到虚拟内存映射的地址,就可以直接改写。虚拟内存地址大小范围在32位里面是232,一个地址对应4个字节。但是问题来了,物理内存如果只有1g,而物理内存显然比232小一些,多出来的虚拟内存算谁的?为啥虚拟内存比物理内存还大?这里会扯到一个MMU的概念(我没有深究,我也不知道是啥玩意)。我学linux之前有个疑问,stm32F103都是arm架构,能不能跑linux,stm32F103没有mmu这玩意,没法进行虚拟内存映射。

四、驱动层与应用层

应用层和驱动层的相互图(按照自己理解随便画的):在这里插入图片描述

驱动层里面会为应用层提供接口,open,write,read,release等等······
应用程序只需要调用驱动层提供的接口,就可以直接实现功能了
就像那个树莓派,你在他官方自带的python编程软件import一个库,直接调用库的东西就可以轻松实现点灯啊,点灯啊,点灯啊啥的。你写了的东西库里面函数就自动的帮你把调用驱动层的接口进行实现。(吐槽一下,这树莓派官方的内核是不开源的,这树莓派就是拿来做应用开发为主的,所以树莓派在以前买了后玩了一天,学了linux后就吃灰去了)
以字符设备为例
按照自己理解随便画的:
在这里插入图片描述

五、字符设备驱动

驱动分为三大类:字符设备,块设备,网络设备。
驱动在系统里面,首先初始化驱动,再注册设备号,注册节点,然后卸载的时候,要卸载设备号和节点。
驱动里面看左老师有个很好的比喻,把linux驱动比喻成个阴阳,有注册必有卸载,有读必有写。我把他比喻成,你一下,我一下,大家都平衡了。

(1) 驱动初始化

入口函数:
module_init(初始化对象函数);
在这里插入图片描述

上图是调用”原型”,因为我不知道为啥vscode不给我跳,里面应该是一个指针指向这个函数地址
他来自于module.h
上面说了有阴必有阳,所以要提供一个模块的出口函数

module_exit(初始化对象函数);

(2) 入口函数和出口函数

module_init(new_module_init); //假设入口函数叫	new_module_init
module_exit(new_module_exit);//出口函数叫		new_module_exit
static int __init new_module_init(void) ;

前面的__init是啥呢

然后我跳在这里插入图片描述
还有个attribute,就不跳了,看了一下应该是说他这个定义后面需要加.init.text类型的东西。
一直在套娃,我猜他这么套应该是为了区分一些函数的类型,毕竟linux内核的东西实在太多了,方便给别人开发。
写个static int __init new_module_init(void)//函数给module_init调用就行了。

(3) 注册设备号和设备名字和用户接口

头文件是fs.h
那么在流程里面就是已经注册了这个驱动,那么就需要给这个驱动名字和设备号,
设备号又分主设备号和次设备号,主设备号里面应该是一个寄存器,前12位是主设备号,后20位是次设备号。理论上一个主设备号有212的次设备号,次设备号有220个。
注册指定设备号和次设备号的数量和名字。

register_chrdev_region(new_led.devid,count,name);

函数原型:在这里插入图片描述

返回值是一个设备的主设备号和次设备号,如果<0的话,他就是没有注册
函数原型参数的第一个是一个dev_t类型的变量,第二个是一个int型变量,第三个是一个char类型指针,但是一般指定设备号用的不多,因为你这模块加载系统得指定设备号,万一这设备号被占用了咋办。注册不成了呗
下面这个向系统申请分配注册设备号函数用得最多
函数原型:
在这里插入图片描述

alloc_chrdev_region(&new_led.devid,0,count,name);

返回值是设备号,和上面一样的,失败就返回负的值
第一个参数是这个函数把这个值保存到哪里,我写了结构体,方便分类,头铁也可以写一堆变量;第二个就是设备号从开始多少;第三个就是要注册次设备号的数量;第四个是这驱动叫啥。
有注册必有卸载,卸载咋办。
在出口函数static void __exit new_module_exit(void)里面写个unregister_chrdev_region(new_led.devid,count);不解释啦和上面一样。
接下来就是初始化字符设备,有名字了,有设备号了,就得注册啦,我的理解就像你出生的时候,在医院记录了你这一天第多少个出生的,然后你爸妈给你命叫啥名了,就得去派出所注册户口了。

cdev_init(&new_led.cdev,&newled_fops);
cdev_add(&new_led.cdev,new_led.devid,count);

函数原型:在这里插入图片描述

第一个就是你驱动里面的cdev,cdev是个结构体。
在这里插入图片描述

里面有好多东西,但是写的时候一般就写个owner,属于谁就行了,其他看情况用。
new_led.cdev.owner = THIS_MODULE; 直接这样就行了。
第二个就是file_operations,也就是用户要调用的函数,open,write这些等等。
他要结构体形式,定义一个file_operations类型的结构体,然后把需要调用的东西写进去。
例如 :在这里插入图片描述
在这里插入图片描述

进去结构体里面是一堆指针函数,指针指向你定义的函数地址。
然后再这样

cdev_init(&new_led.cdev,&newled_fops);

&指针里面的取值,也就是取结构体里面所对应的值。

cdev_add(&new_led.cdev,new_led.devid,count);

然后就是你这样的设备要注册多少个。

(4) 注册类和节点

设备注册了之后,需要创建一个节点。我猜:用户要先去访问节点,节点里面告诉用户节点名字和设备号是啥。
节点创建需要先创建类

class_create(THIS_MODULE,name);

函数原型如下:
在这里插入图片描述

第一个参数就是拥有者,也就是这个模块;第二个参数叫啥名;
返回值是一个结构体,他返回这些玩意内容:
在这里插入图片描述

可以通过IS_ERR函数来判断返回值的结构体 类是否创建成功
创建完类之后就是驱动设备节点创建:
在这里插入图片描述
函数原型
创建驱动的节点函数

device_create(new_led.class,NULL,new_led.devid,NULL,name);

第一个参数就是结构体class类型(也就是上面创建的类);第二个是驱动的上级驱动(驱动里面也可以层层套娃,驱动套驱动);第三个就是驱动的设备号,第四个就是指针变量指定drv数据地址,第五个是驱动设备叫啥名。
设备树,子系统和系统异常过几天写了再发。

  • 11
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值