1.Linux驱动的学习方法
Linux内核中有上百个驱动,知识点多且杂,对于想学习驱动的同学来说,需要尽快掌握基础知识:如开发板的基本使用,硬件基础知识,开发环境的搭建,Linux常用工具,内核的编译以及烧写,Linux shell命令,C语言基础,Linux内核的简单裁减和配置,Linux系统编程等等。
没有上面的基础知识,驱动的学习无疑是在建空中楼阁。
Linux操作系统相当于“一个球”,程序员要做的事情就是在这个球上添加驱动来实现具体的功能,不用去管这个球是从哪里开始旋转,转到什么地方了。更简单的理解就是,Linux只是一个工具,学会使用就可以了,就像学习汽车驾驶,没有教练会从发动机原理开始讲解,只会给你发一些指令如“方向盘右转一圈”“方向盘左转一圈”“拉手刹”“换挡”等。
当然学习Linux的最好的方法是阅读内核,但是在没有基础之前不要过多的去研究内核的东西。
在嵌入式Linux驱动工程师的工作中,移植驱动是必须掌握的技能,在学会了如何移植驱动,找到合适的工作之后,如果你对内核源码感兴趣,而且还有富余的时间,可以看一看内核中“精妙”的代码,不过这对于工作并没有太多直接的帮助,可以纯粹的作为一个兴趣爱好来研读。
2.Linux设备驱动的分类
Linux设备驱动程序在Linux的内核源代码中占有很大的比例,源代码的长度日益增加,主要是驱动程序代码的不断充实。在Linux内核的不断升级过程中,驱动程序的结构却是相对稳定的。
Linux系统的设备分为字符设备(char device),块设备(block device)和网络设备(network device)三种。字符设备是指存取时没有缓存的设备。块设备的读写都有缓存来支持,并且块设备必须能够随机存取(random access),字符设备则没有这个要求。典型的字符设备包括鼠标、键盘、串行口等。块设备主要包括硬盘软盘设备、CD-ROM等。一个文件系统要安装(mount)到操作系统的块设备上。
网络设备在Linux里做专门的处理。Linux的网络系统主要是基于BSD unix的socket机制。在系统和驱动程序之间定义有专门的数据结构(sk_buff)进行数据的传递。系统里支持对发送数据和接收数据的缓存,提供流量控制机制,提供对多协议的支持。
3.以模块的形式编译驱动模块加载函数
模块的加载函数“module_init(function)”,返回整数型,如果执行成功,则返回0。否则返回错误信息。
有时候芯片供应商并不提供芯片驱动的源码,只是提供驱动module的ko文件,这个时候就需要调用request_module(module_name)来加载驱动。
在Linux中,标示为_ _init的函数都是初始化函数,这些函数占用的内存空间,在Linux启动或者模块加载初始化之后,是会被释放掉的。除了函数,数据也是可以被定义为“_ _数据”,这些数据在Linux启动或者或者模块加载初始化之后,也是会被释放掉。这一部分的知识,在后面会逐渐使用到。
模块卸载函数
模块卸载就是模块加载的“逆向过程”,比较容易理解。
模块的卸载函数“module_init(function)”不返回任何值。
一般说来,我们在卸载函数中要完成和加载函数中相反的功能,例如你调用了系统或者硬件的资源,那么你在卸载函数中,就需要释放掉。
模块编译的流程
Linux模块一般是使用脚本语言来编译,脚本语言的种类非常多,语法丰富,不过我们只需要学会使用即可,能够仿照着写就可以了,这并不影响我们开发。
当执行执行make命令之后,调用Makefile文件进行Linux模块的编译。
Linux模块的编译分为两个条线。
红色的线:进入Linux源码中,调用版本信息以及一些头文件等。
这一条线经过的是整个Linux的源码文件。
橙色的线:搜集完Linux源码树的信息之后,Makefile继续执行,调用编译.KO文件的源码文件,这里是mini_linux_module.c这个“iTop4412_Kernel_3.0”整个源码。
这一条线走的是mini_linux_module.c,虽然都是源码,但是此源码非彼源码。
Makefile文件通过执行上面两条线,通过搜集到信息,最终编译生成.KO模块
这里我们要学习和理解的重点就是,编译模块也必须用到内核的源码,因为这涉及到内核版本以及头文件。如果版本不对,那么模块有可能无法加载和运行;如果没有头文件,编译就无法通过。
脚本文件Makefile
在单片机或者上位机编程的时候,都有集成开发工具。程序员按照开发工具的规则,将代码放入指定的位置,通常是一个main.c文件加上很多.c文件,代码写好了,开发工具中某个按钮一点,就给你自动编译成了二进制文件。
在Linux中,并没有这样的集成开发工具,这里还需要自己写编译文件Makefile。
编译文件一般是用脚本编写,脚本成千上万,脚本语言学也学不完,脚本的学习最好是用到哪里学到哪。
Makefile编译文件如下,下面的这个文件可以在Linux驱动视频目录“02_HelloDriverModule”下找到。
#!/bin/bash
#通知编译器我们要编译模块的哪些源码
#这里是编译itop4412_hello.c这个文件编译成中间文件mini_linux_module.o
obj-m += mini_linux_module.o
#源码目录变量,这里用户需要根据实际情况选择路径
#作者是将Linux的源码拷贝到目录/home/topeet/android4.0下并解压的
KDIR := /home/topeet/android4.0/iTop4412_Kernel_3.0
#当前目录变量
PWD ?= $(shell pwd)
#make命名默认寻找第一个目标
#make -C就是指调用执行的路径
#$(KDIR)Linux源码目录,作者这里指的是/home/topeet/android4.0/iTop4412_Kernel_3.0
#$(PWD)当前目录变量
#modules要执行的操作
all:
make -C $(KDIR) M=$(PWD) modules
#make clean执行的操作是删除后缀为o的文件
clean:
rm -rf *.o
上面内容就是编译mini_linux_module的脚本文件。下面详细的介绍每一句的含义。
#!/bin/bash
通知编译器这个脚本使用的是那个脚本语言
obj-m += mini_linux_module.o
这是一个标准用法,表示要将mini_linux_module.c文件编译成mini_linux_module.o文件,如果还需要编译其它的文件,则在后面添加即可。
KDIR := /home/topeet/android4.0/iTop4412_Kernel_3.0
这一行代码表示内核代码的目录,如果没有内核源码,那么模块的编译无法进行,因为会缺乏版本支持和头文件。KDIR是一个变量。
PWD ?= $(shell pwd)
这一句是提供一个变量,然后将当前目录的路径传给这个变量。pwd是一个命令,表示当前目录,PWD是一个变量。
all:
make -C $(KDIR) M=$(PWD) modules
在使用执行脚本编译命令“make”的时候,它会默认来寻找这一句,make -C 表示调用执行的路径,也就是变量KDIR,变量KDIR中有内核源码目录的路径。
PWD表示当前目录。
modules表示将驱动编译成模块的形式,也是就是最终生成KO文件。
clean:
rm -rf *.o
在重新修改了源码之后,可以执行“make clean”命令来清除一些无用的中间文件,这里选择的是清除后缀为“o”的文件。