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 文件就是编译好的驱动。
在开发板上加载驱动用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/