上一篇介绍了一个最简单的驱动程序和驱动程序大体结构,但那还是用本地编译只能在Ubuntu上运行,我们该怎么编译一个能加载到开发板上呢,就需要交叉编译,交叉编译通常都是在嵌入式开发中使用到的。
交叉编译
理解交叉编译前先了解下本地编译:是指编译源代码的平台和执行源代码编译后程序的平台是同一个平台。例如在x86平台下编译的程序,就只能在x86平台下运行。
而我们现在是在Ubuntu下(x86)编译,到ARM开发板(arm)上去运行自然不行,所以交叉编译:是指编译源代码的平台和执行源代码编译后程序的平台是两个不同的平台,其中运行编译程序称为宿主机,运行编译程序所产生目标代码的称为目标机。
那为什么不在ARM开发板上编译程序呢,这样就不用转来转去了。之所以要有交叉编译,主要原因是:
1、目标机的运行速度往往比宿主机慢得多,许多专用的嵌入式硬件被设计为低成本和低功耗,没有太高的性能。
2、整个编译过程是非常消耗资源的,嵌入式系统往往没有足够的内存或磁盘空间。
3、 一个完整的Linux编译环境需要很多支持包,交叉编译使我们不需要花时间将各种支持包移植到目标机上。
交叉编译说完了,那怎么宿主机怎么给程序进行交叉编译呢,我们是站在巨人的肩膀上,自然是有现成的工具叫交叉编译器。
根据每个人使用的开发板不同需要下载不同的交叉编译器,大家可自行百度。
其实我们只需要在Makefile中指明交叉编译器的路径就行了,我们来看一个例子。
ifeq ($(KERNELRELEASE),)
#内核源代码路径
KERNELDIR ?= /home/xin/6818GEC/kernel
#交叉编译器路径
CROSS_PATH := /home/xin/6818GEC/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/bin/arm-eabi-
#模块源代码路径
PWD := $(shell pwd)
default:
$(MAKE) CROSS_COMPILE=$(CROSS_PATH) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.ko *.mod .*.cmd *.mod.* modules.order Module.symvers .tmp_versions
else
#obj-m表示编译生成可加载模块,obj-y表示直接将模块编译进内核。
obj-m := hello.o
endif
这里面的参数上一篇详细解释过了,看不懂可以去看看(初学者的第一个Linux驱动)。其中开发板内核源代码路径和交叉编译器路径需要根据自己的存放位置去改变。
这是Ubuntu上开发板内核源代码的路径和内容。
这是Ubuntu上交叉编译器的路径和内容。 路径中只需要交叉编译器的前缀arm-eabi-
内核打印函数 printk
正常当我们在写应用程序时,都会使用printf函数或相关的打印函数来输出信息,帮助我们调试代码或者打印日志。那内核的驱动程序又没有应用层的库函数,这时候就需要使用我们的printk函数了。先来看一段代码和现象。
#include <linux/init.h>
#include <linux/module.h>
//加载函数
int printktest_init(void)
{
//内核打印语句
printk("<0>""printk level 0!\n");
printk("<1>""printk level 1!\n");
printk("<2>""printk level 2!\n");
printk("<3>""printk level 3!\n");
printk("<4>""printk level 4!\n");
printk("<5>""printk level 5!\n");
printk("<6>""printk level 6!\n");
printk("<7>""printk level 7!\n");
printk("printk no level!\n");
return 0;
}
//卸载函数
void printktest_exit(void)
{
printk("<0>""printk level 0!\n");
printk("<1>""printk level 1!\n");
printk("<2>""printk level 2!\n");
printk("<3>""printk level 3!\n");
printk("<4>""printk level 4!\n");
printk("<5>""printk level 5!\n");
printk("<6>""printk level 6!\n");
printk("<7>""printk level 7!\n");
printk("printk no level!\n");
}
//声明为模块的入口和出口
module_init(printktest_init);
module_exit(printktest_exit);
MODULE_LICENSE("GPL");//GPL模块许可证
MODULE_AUTHOR("xin");//作者
MODULE_VERSION("1.0");//版本
MODULE_DESCRIPTION("printk module!");//描述信息
我们发现代码printk中有0~7,8个数字,而我们加载模块却只打印前5条语句,这是为什么呢?
之前我们说过printk和printf等打印函数用法十分相似,但printk多了打印级别的设置。内核打印通过printk函数,printk打印的内容能否显示取决于打印级别。
printk函数有8个级别,0-7(数字越小优先级越高)
#define KERN_EMERG "<0>" /*系统不可用信息*/
#define KERN_ALERT "<1>" /* 必须立即处理的错误*/
#define KERN_CRIT "<2>" /*严重的错误信息*/
#define KERN_ERR "<3>" /*错误信息*/
#define KERN_WARNING "<4>" /*警告信息*/
#define KERN_NOTICE "<5>" /*需要注意的情况信息*/
#define KERN_INFO "<6>" /*普通信息*/
那什么级别的prink函数中的内容才能显示呢。在Linux中有一个文件用来存放内核默认的打印级别。/proc/sys/kernel/printk,其内容为:
解释一下其中数字含义
5 表示内核打印级别(只有printk打印级别高于5才能显示)
7 表示printk函数默认打印级别(使用printk函数不设置打印级别默认为7)
1 内核打印级别最小值
7 默认内核打印级别
通常只需要改变内核打印级别比printk低就行了。
我们可以直接修改里面的内容。
但这种方法在系统关机或重启后就会失效。我们可以写一个脚本在每次启动时去修改里面的值。有两个方法实现永久修改。
方法1:
写一个shell脚本,内容很简单
然后再放到环境变量(/etc/profile)中去。内容为source 路径/printk.sh。
这样每次启动开发板就都会重新把内容写入/proc/sys/kernel/printk文件里了。
方法2:
在uboot的bootargs中加入“loglevel=X”的语句。首先进入uboot界面。这个可能每个开发板的操作都不太一样,这里演示一下GEC6818。
设置完了要记得保存一下,不然不生效。
通过以上两种方法都能成功设置打印级别。
我们再从新编译加载一下模块,看看效果吧。
Makefile
ifeq ($(KERNELRELEASE),)
#内核源代码路径
KERNELDIR ?= /home/xin/6818GEC/kernel
#交叉编译器路径
CROSS_PATH := /home/xin/6818GEC/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/bin/arm-eabi-
#模块源代码路径
PWD := $(shell pwd)
default:
$(MAKE) CROSS_COMPILE=$(CROSS_PATH) -C $(KERNELDIR) M=$(PWD) modules
iclean:
rm -rf *.o *.ko *.mod .*.cmd *.mod.* modules.order Module.symvers .tmp_versions
else
#obj-m表示编译生成可加载模块,obj-y表示直接将模块编译进内核。
obj-m := print.o
endif
print.c
#include <linux/init.h>
#include <linux/module.h>
//加载函数
int printktest_init(void)
{
//内核打印语句
printk("<0>""printk level 0!\n");
printk("<1>""printk level 1!\n");
printk("<2>""printk level 2!\n");
printk("<3>""printk level 3!\n");
printk("<4>""printk level 4!\n");
printk("<5>""printk level 5!\n");
printk("<6>""printk level 6!\n");
printk("<7>""printk level 7!\n");
printk("printk no level!\n");
return 0;
}
//卸载函数
void printktest_exit(void)
{
printk("<0>""printk level 0!\n");
printk("<1>""printk level 1!\n");
printk("<2>""printk level 2!\n");
printk("<3>""printk level 3!\n");
printk("<4>""printk level 4!\n");
printk("<5>""printk level 5!\n");
printk("<6>""printk level 6!\n");
printk("<7>""printk level 7!\n");
printk("printk no level!\n");
}
//声明为模块的入口和出口
module_init(printktest_init);
module_exit(printktest_exit);
MODULE_LICENSE("GPL");//GPL模块许可证
MODULE_AUTHOR("xin");//作者
MODULE_VERSION("1.0");//版本
MODULE_DESCRIPTION("printk module!");//描述信息
以上就是Linux驱动交叉编译把驱动文件放入开发板,以及printk函数打印级别的全部内容,有什么说的不对或者觉得不清楚地方欢迎在评论区提出来。