Linux驱动简介和开发流程--Linux驱动学习(1)

Linux驱动简介和开发流程

【学习笔记】

Linux 驱动的分类

Linux三大设备驱动

1、字符设备驱动

IO的传输过程是以字符为单位的,没有缓冲。比如I2C,SPI都是字符设备

2、块设备驱动

IO传输过程中是以块为单位的。跟存储相关的,都属于块设备,比如:tf卡

3、网络设备驱动

与前两个不一样,是以socket套接字来访问的。

其中,理解和掌握字符设备驱动的概念最重要,因为在工作中我们遇到大部分都是字符设备

驱动的组成

驱动分为四个部分

(1)头文件

(2)驱动模块的入口和出口

(3)声明信息

(4)功能实现

具体成分

第一步,包含头文件

include <linux/init.h> 包含了宏定义的头文件

include <linux/module.h> 包含了初始化加载模块的头文件

第二步,驱动模块的入口和出口

module_init();
module_exit();

第三步,生命模块具有开源许可证

MODULE_LICENSE("GPL");

第四步,功能的实现

实例

简单的helloworld驱动程序编写

/两个必要的头文件
#include <linux/init.h>
#include <linux/module.h>

//内核加载的时候打印hello world,内核模块卸载的时候打印bye bye
static int hello_init(void){
	printk("hello world\n");  //内核里不能使用c语言库,所以不能用printf
	return 0;
}
static void hello_exit(void){
	printk("bye bye\n");
}

//入口和出口
module_init(hello_init);
module_exit(hello_exit);

//声明许可证
MODULE_LICENSE("GPL");

Linux驱动编译成模块

方法一

把驱动编译成模块,然后使用命令把驱动加载到内核里面。

步骤

1、编译成模块

(1)先写一个makefile

在linux下

touch Makefile #创建Makefile文件
obj-m +=HelloWorld.o #obj-m 意为把驱动编译为模块
KDIR:={core_path} #PATH根据开发板内核路径来填写
PWD?=$(shell pwd) #自动获取当前位置路径
all:
	make -C $(KDIR) M=$(PWD) modules #切换到内核路径,用make把代码编译成模块

(2)编译驱动

编译驱动之前需要注意的问题:

  • 内核源码一定要编译通过

  • 编译驱动用的内核源码一定要和开发板上运行的内核镜像是同一套

  • 看一下开发代码的ubuntu环境是不是arm,如果不是,要改成arm环境

查看环境的 方法

在内核源码的目录下输入

make menuconfig #显示系统环境
#左上角如果显示是X86,而不是arm,则输入
export ARCH=arm #改为arm环境

为了以防万一,编译前,先命令行输入两行代码,设置环境和编译器

export ARCH=arm
export CROSS_COMPILE={编译器名称} #可以直接输入arm查看
#如果查看到:arm-linux-gnueabihf-gcc-4.9.4

编译成功后就可以看到 ko 文件了,这个 ko 文件就是编译好的驱动。

image

在开发板上加载驱动用insmod

insmod HelloWorld.ko

查看加载的模块,使用lsmod命令

lsmod

卸载驱动模块

rmmod HelloWorld #注意这里没有后缀

卸载时,如果提示没有相应的目录,直接在对应的目录下创建就可以了

方法二

直接把驱动编译到内核。

在下面有实际的例子。

移植

如果芯片的内核不支持设备的驱动,那么就需要把这个驱动移植到内核。

移植驱动需要驱动源码和makefile。

移植需求分析

(1)先去内核源码搜索,如果有的话,可以直接选择这个驱动,然后直接使用。

(2)假如没有这个驱动,则许哟啊自己编译一个驱动,然后加载到内核里边去运行。

make menuconfig 图形化配置

1、如何进入make menuconfig图形化配置

首先进入到内核源码的路径下,然后输入make menuconfig 即可打开这个界面。

2、make menuconfig图形化界面的操作

(1)搜索功能

输入“/”即可弹出搜索界面

(2)配置驱动状态

  • 把驱动编译成模块(用[ M ]来表示)

  • 把驱动编译到内核里(用[ * ]来表示)

  • 不编译(用[ ]来表示)

可以使用“空格”键来配置这三种不同的状态

(3)如何退出

分为保存退出和不保存退出,按照提示选择即可

(4)和make menuconfig 有关的文件

Makefile:里边是编译规则,高速我们在make的时候需要如何编译(相当于菜的做法)

Kconfig:内核配置的选项(相当于服务员给的菜单)

.config:配置完内核以后生成的配置选项(相当于我门勾选的菜单)

(5)make menuconfig会读取不同环境目录下的Kconfig文件

会读取Arch/$ARCH/目录下(目录下有很多不同的环境文件夹)的Kconfig文件(选择不同的环境,和export ARCH=arm效果类似)。

/arch/arm/configs 下面有很多配置文件(相当于特色菜,默认配置,在不知道如何配置的时候可以按照这个来),如果移植内核的时候,配置文件太多,就可以把这个文件夹下面的配置复制成.config里边(即默认系统的配置)【复制用cp命令】

(6)为什么要复制成.config,而不是其他名字

因为内核会默认读取Linux内核根目录下的.config作为默认的配置选项,所以不可以改名字

(7)如果复制的默认配置.config不满足我们的要求,如何解决

直接输入make menuconfig,进入Kconfig配置界面,来进行修改配置,保存退出,配置会自动更新到.config里面。

(8)配置文件选项怎样和Makefile文件建立联系

当make menuconfig保存退出以后,Linux会将所有的配置选项以宏定义的形式保存在include/generated/下面的autoconf.h头文件中。

把驱动编译到内核

Kconfig代码例子

#例子
source "drivers/redled/Kconfig"
config LED_4412
		tristate "Led Support for GPIO Led"
		depends on LEDS_CLASS
		help
		This option enable support for led
#解读
source "drivers/redled/Kconfig"#会让config菜单里边包含drivers/redled/这个路径下的驱动文件,方便我们对菜单进行管理
config LED_4412 #配置选项的名称,全名是CONFIG_LED_4412,这里做了一些省略
		tristate "Led Support for GPIO Led"
		#tristate表示驱动的状态:1.把驱动编译成模块 2.把驱动编译到内核 3.不编译。与之对用的还有bool关键字:表示编译到内核,和不编译两种状态。
		#"Led Support for GPIO Led"是make menuconfig里边的某个菜单的名字
		depends on LEDS_CLASS
        # A depends on B表示只有在选择B的时候才可以选择A,即A依赖于B。
        #比如想要去掉LED相关的驱动,我们虽然可以直接改.config文件,但是不推荐这样做。因为如果有依赖项的话,直接改.config文件是不成功的。
        #select:反向依赖,该选项被选中时,后面的定义也会被选中。
		help
		This option enable support for led   #显示帮助信息
		

实例

把HelloWorld驱动编译到内核。

(1)首先进入到内核源码的目录下(内核根目录)

(2)把HelloWorld.c驱动,复制到drivers/char/hello文件夹【char一般就是放字符设备的文件夹,hello文件夹使用mkdir命令创建】

(3)写Kconfig文件

mkdir hello #创建hello文件夹
cd hello/ #切换到hello文件夹
cp /home/pyma/HelloWorld.c #把驱动文件复制到hello文件夹中
touch Kconfig #创建一个Kconfig文件
vi Kconfig #打开Kconfig进行编辑

#写程序
config HELLO	#起个名字叫HELLO
	tristate "hello world"	#选择三种状态的方式,并把菜单名字命名为hello world
	#过于简单没有依赖,不用写
	help
	hello help	#帮助信息为hello help

保存退出

(4)写Makefile文件

touch Makefile #创建Makefile文件夹
vi Makefile #打开Makefile编辑

#写程序
obj-$(CONFIG_HELLO)+=HelloWorld.o

CONFIG_HELLO这个变量名字来源:是刚刚在Kconfig中命名的,但是Kconfig中相当于省略了CONFIG_,在这里相当于补上,即:makfile中要写全名。
$(CONFIG_HELLO)变量作用:会根据我们选择的状态来改变,(1)如果是选择把它编译到内核中,那么表示-y(即:obj-y)(2)如果选择编译成模块,那么它表示-m(即:obj-m)

到此,所有的文件都准备完成,接下来需要

(5)把hello驱动配置包,包含进去

需要修改上一级目录的Makefile和Kconfig

cd ..	#返回上一级目录,即路径.../char/
vi Makefile #修改Makefile,如果没有则需要自己创建,自己写

#写程序,在首行添加以下代码
obj-y +=hello/   #这里注意hello是个文件夹,因此最后要加"/"   
... #原有的makefile代码
#保存退出

vi Kconfig	#打开Kconfig文件,把刚才写的驱动代码的Konfig包含进config菜单界面中
#在前面相应位置添加好Kconfig的路径
source "drivers/char/hello/Kconfig" #Kconfig相对路径
#保存退出

(6)回到内核根目录下,打开菜单,选择不同状态

make menuconfig	#进入配置菜单

在界面中选择Device Drivers选项进入

再进入到Character devices目录下

就会看到新添加的hello world驱动菜单项(名字是在Kconfig中命名的)

用空格键选择状态:编译成模块;<*>:编译进内核;< >:不编译

(7)编译进内核,选择<*>(编译到内核)

保存退出,然后确保配置正确与否

打开.config文件,搜索

vi .config #打开配置文件
#搜索HELLO
#搜索命令
/HELLO
如果出现CONFIG_HELLO=y是编译到内核中了,m是编译成模块
这里可以看出,如果没有依赖项,也可以直接改.config文件的CONFIG_HELLO项

(8)编译前更改

这时还不能立马编译,因为编译会调用默认的的编译配置,需要做相应修改

这里为了理解的透彻,先看一下编译脚本:vi create.sh

#!/bin/bash

export ARCH=arm
...
make imx_v7_defconfig	#这个就是内核的默认用法,意思是在编译的时候会自己去找arch/arm/configs下面的imx_v7_defconfig配置文件,作为.config来编译内核。
...

虽然前面我们改了.config文件,但不是直接用它来编译的,而是使用默认的arch/arm/configs/imx_v7_defconfig文件来编译的。

因此还要再改一下

  • make distclean,清除所有的编译文件,也删掉了.config文件

  • 把缺省配置复制到.config中

cp arch/arm/configs/imx_v7_defconfig .config
  • make menuconfig 配置菜单选项

再进行第(6)步操作(这里感觉有点乱,步骤重复了?(6)、(7)两步可以不要)

(9)键修改后的.config复制回默认编译菜单里

cd arch/arm/configs/	#切换到默认配置目录
mv imx_v7_defconfig imx_v7_defconfig_noHELLO #更改默认配置的名字
cp ../../../.config imx_v7_defconfig #把修改好的配置,更改为默认的名称

(10)运行脚本,等待编译完成

./create.sh

(11)检验是否编译成功

方法一:烧写到开发板上,运行看看有没有加载驱动

cp arch/arm/boot/zImage /home/pyma/  #把镜像复制到好找的位置

方法二:进入到创建的驱动目录,查看有没有把.c文件编译成.o文件

cd drivers/char/hello/
ls

如果没有相应的.o文件,则说明编译出错,检查Makefile和其他文件。

修改好,再回到根目录编译

.o文件检查没有问题后,再回到根目录检查是否有zImage镜像

ls arch/arm/boot/

整理自嵌入式学习之Linux驱动篇

posted @ 2021-05-11 21:29  水鸽  阅读(1143)  评论(0编辑  收藏  举报