1、驱动
顾名思义,是驱使硬件动起来的设备;
种类:
1)、裸机驱动:需求分析,查看原理图,查看芯片手册,再而编写驱动程序;
2)、系统驱动:需求分析,查看原理图,查看芯片手册,设备树文件,编写对应的驱动模块,安装到linux内核中;
2、应用程序和驱动程序的区别?
从加载方式来看:应用程序是主动加载,驱动程序是被动加载;
从执行空间来看:应用程序是用户运行,驱动程序是内核来运行;
从执行权限来看:应用程序的执行权限低,驱动程序的执行权限高;
从程序的影响来看:应用程序的影响低,驱动程序的影响高;
从函数来源来看:应用程序可以是自定义的,库里自带的,也可以是来自系统调用的,驱动程序可以是自定义的,也可以是内核函数里面的。
3、模块
3.1、驱动模块:能够单独命名并且独立完成一定功能的程序语句的集合(程序代码和数据结构);
注意:、一个驱动模块就是一个完整的外设驱动程序。驱动模块被安装到linux内核中,当该驱动模块对应的设备要工作时,该驱动模块被调用。
3.2、如何写一个驱动模块?
test.c--->int main()
hello.c--->int main()
模块初始化函数:int 函数名1(void)
模块清除函数: void 函数名2(void)
模块加载函数:module_init(函数名1)--->sudo insmod hello.ko
模块退出函数:module_exit(函数名2)-->sudo rmmod hello.ko
遵守GPL规范:MODULE_LICENSE("GPL");
linux3.14源码+hello.c-->uImage
hello.c-->hello.ko--->安装到Linux内核中
3.3、如何编译驱动模块
gcc arm-none-linux-gnueabi-gcc
分两种情况:
1) 若想要驱动在ubuntu上测试,那么使用编译ubuntu内核的makefile去编译驱动代码
2 若想要驱动在开发板的uImage上测试,那么使用编译linux3.14内核的Makefile去编译驱动程序。
Makefile如下:
ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules//进入/lib/modules/3.5.0-23-generic/build下执行Makefile,将PWD路径下的代码编译成一个hello.o
else
obj-m := hello.o //将hello.o链接成hello.ko
endif
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module* modules*
3.4、将驱动模块安装到Linux内核中
sudo insmod hello.ko-->将驱动模块hello.ko安装到linux内核中
lsmod-->查看当前系统中所有已加载的驱动模块
dmesg |tail-->查看内核缓存区尾部10行的打印信息
dmesg |tail -20-->查看内核缓存区尾部20行的打印信息
modinfo hello.ko-->查看驱动模块hello.ko的所有信息描述
sudo rmmod hello.ko-->将hello.ko去内核中移除
3.5、将一个驱动代码分成两个文件
hello.c-->hello_init.c/hello_exit.c-->hello_init.o/hello_exit.o-->hello.ko
Makefile:
ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
//进入/lib/modules/3.5.0-23-generic/build下执行Makefile,将PWD路径下的代码编译成一个hello.o
else
obj-m := hello.o //将hello.o链接成hello.ko
hello-objs=hello_init.o hello_exit.o (新增)
endif
4、模块传参
应用程序传参:./test 1.txt 2.txt
应用程序接收传参:Int main(int argc,char *argv[])
驱动程序传参:sudo insmod hello.ko gtest=100
驱动程序接收传参:1)全局变量 int gtest 2)声明该全局变量支持shell终端的传参值
如何声明:
module_param(name, type, perm)
name:参数名,既是外部参数名,又是内部参数名
type:参数的数据类型
perm:访问权限,0644。0表示该参数在文件系统中不可见
module_param_string(name, string, len, perm)
name:参数名,外部参数名
string:内部参数名
len:数组的长度
perm:访问权限,0644。0表示该参数在文件系统中不可见
module_param_array(name, type, nump, perm)
name:数组参数名,既是外部参数名,又是内部参数名
type:参数的数据类型
nump:终端传给数组的实际元素个数(指针变量)
perm:访问权限,0644。0表示该参数在文件系统中不可见
测试步骤:
1) make--->hello.ko
2) sudo insmod hello.ko gtest=100
3) dmesg |tail ---->查看gtest的值
4) cd /sys/module/hello/parameters
ls -l
cat gtest--->100?
sudo chmod 777 gtest
echo 80 > gtest
cat gtest---->80?
5) sudo rmmod hello.ko
dmesg |tail
5 符号导出
5.1 什么是符号?
主要是指全局变量和函数
5.2 为什么要导出符号?
linux内核采用的是以模块化的形式管理内核代码。内核中的每个模块之间是相互独立的,也就是说A模块的全局变量和函数,B模块是无法访问的。若B模块想要使用A模块已有的符号,那么必须将A模块中的符号做符号导出,导出到模块符号表中,然后B模块将导出的符号表添加到自己的目录下,之后就可以在自己的代码中使用符号了。
5.3 如何导出符号?
Linux内核给我们提供了两个宏,用作符号导出
EXPORT_SYMBOL(符号)
EXPORT_SYMBOL_GPL(符号)
EXPORT_SYMBOL_GPL(gtest)
EXPORT_SYMBOL_GPL(func)
5.4 示例
A模块:导出符号--》导出到了本地符号表中(Module.symvers)
Module.symvers内容:
Addr---------------->符号名-------》模块名--------------》导出符号的宏
0x8f21315d
gtest /home/farsight/2022/22071/driver/day1/module_symbol/moduleA_export/hello EXPORT_SYMBOL_GPL
0xd1a68ac8
func /home/farsight/2022/22073/driver/day1/module_symbol/moduleA_export/hello EXPORT_SYMBOL_GPL
B模块:使用符号;B模块:使用导出的符号:1)将A模块导出的符号表拷贝到B模块的目录下 2) 在B模块中外部声明要使用的符号,然后使用之
注意:安装驱动模块时,先安装符号导出模块,再安装使用符号的模块;卸载时,先卸载使用符号的模块,再卸载导出符号的模块
注意:linux内核有两类符号表:
第一种:用户自定义模块导出的符号表,这些符号保存在本模块目录下的Module.symvers---》本地符号表
第二种:全局符号表 /proc/kallsyms
sudo cat /proc/kallsyms |grep printk
c15cd833
int (*pFunc)(const char *format,...)=0xc15cd833
pFunc("hellotest");