嵌入式Linux学习笔记(一) 启航、计划和内核模块初步体验

1.总结

  从事嵌入式行业多年,虽然因为工作原因接触过嵌入式Linux,也参与过相关产品的底层和应用功能开发,但对于嵌入式Linux的内核,驱动,以及上层开发,仍然停留在初级的水平,没有过系统深入的去总结整理,随着工作年限的递增,越来越感受到这种浮躁感带来的技术面瓶颈。既然发现了问题,自然就要去解决,回想起我踏入嵌入式行业来的经历,正是对STM32芯片以及网络部分的学习总结笔记支撑我走到如今的地步,那么沉淀下来,从嵌入式Linux入门开始整理,层层深入,对嵌入式Linux进行系统的总结也是最符合我目前现状的解决办法,这也是我下定决心放弃日常娱乐,开始本系列的由来。

  嵌入式Linux的掌握学习是很复杂的过程,从最基础的Linux安装,shell指令的学习和应用,交叉编译环境搭建,C语言开发,Linux内核接口,Linux系统接口,在掌握了前面所有知识后,才只是完成了产品开发的基础构建,这些知识不仅对于学习是难点,对于已经掌握的人来用文字描述清楚,特别是系统/软件版本引发的编译,调试问题,如果没有总结和整理,这部分经验是文字很难描述的,嵌入式Linux是一门应用开发技术,多练多总结才能积累足够的知识。另外如果遇到问题,不要着急,要善于使用搜索引擎,嵌入式Linux开发遇到的问题基本都能找到答案,但找到解决方法只是目的之一,如何从这些方法中总结经验,也是学习中的重要部分,这部分对于开发者更加重要,切记!这是我做嵌入式软件开发来最重要的经验。按照正常的预期流程,嵌入式Linux的学习应该是讲如何注册字符型设备,然后按照从易到难的顺序在掌握中断和时钟,文件系统,块设备,I2C驱动,LCD驱动,摄像头驱动,网络设备驱动,设备树,然后在讲述涉及上层的QT界面,远程访问的网络socket(B/S, C/S框架),以及应用端的Android平台开发,多线程,多进程同步等知识,这也是大部分开发板的例程方案,可从我经验来看,如果按照上面的流程是可以覆盖嵌入式Linux的主要工作需求的(可能部分知识是溢出的)。但是对于开发产品来说,这些只是基础的技术,而不是应用的产品方案,事实上,对于刚入门的来说,如何从学习思维转变为工程师开发思维这部分更加重要,从更高维的角度了解嵌入式Linux开发,这也是本系列的目的。我们先制定一个产品目标(可能不符合现有的产品模型),所有学习都围绕着此产品来开发。这个系列将不仅仅讲述学习嵌入式,而且也讲述我根据工作积累的开发经验,如何完成项目,也方便未踏入行业的人员什么是嵌入式软件开发。

题目1:基于串口(RS485/RS232)的局域网管理设备

系统架构

       

硬件说明

  正点原子的I.MX6U-ALPHA开发平台,256MB(DDR3)+256MB/512MB(NAND)核心板。涉及硬件 RS232,GPIO,I2C,SPI, ADC, DAC

学习笔记章节

  嵌入式Linux学习笔记(一) 启航、计划和体验

  嵌入式Linux学习笔记(二) 交叉编译环境和Linux系统编译、下载

  嵌入式Linux学习笔记(三) 字符型设备驱动--LED的驱动开发

  嵌入式Linux学习笔记(四) 设备树和UART驱动开发

   嵌入式Linux学习笔记(五) 通讯协议制定和下位机代码实现

  嵌入式Linux学习笔记(六) 上位机QT界面实现和通讯实现 

代码路径

  详细代码见:https://github.com/zc110747/remote_manage

软件说明

  1.上位机软件支持串口通讯,双机通讯需要制定协议(可使用自定义协议或者Modbus),支持界面化管理(目前定义使用QT开发, 与后续的完善计划有关)
  2.支持文件传输,文件传输支持断点重传(传输后文件位于指定文件夹,初步定义为/usr/download)
  3.能够查询内部的一些数据,除显示已经列出状态外,支持后期扩展查询其他状态

任务分解

  1. uboot,内核和文件系统的编译,下载和调试,并集成ssh方便传输应用文件调试
  2. 分模块完成驱动的开发调试,不过为了方便测试及后期集成,需要同步完成串口驱动,串口通讯协议定义及上位机的软件框架
  3. 后期的综合性功能调试和应用开发(如协议扩展问题,状态查询到界面显示,考虑到协议数据的复用, 后期该数据可能用于网页界面的状态显示或者QT界面的控制)

参考资料

  1. 宋宝华《Linux设备驱动开发详解 -- 基于最新的Linux4.0内核》第四章 Linux内核模块

内核模块初探

  本节作为整个系列的起点,重点当然是上面的项目规划和任务分解,不过为了让文章更丰富,我们可以初步体验下Linux下的应用和编程,下面代码将执行在Ubuntu系统,PC端,事实上PC端的Ubuntu可以验证很多实现,如加载驱动和设备,实现QT界面,进行网络通讯的应用端测试,所以一定不要忽略这个优势,本小节的代码都是在PC端测试完成,用于体验内核模块开发的特征。作为内核模块,可以通关Kernel编译时加入到内核中,也可以通过insmod/rmmod动态的加载到系统中,为了满足Linux系统的访问,内核模块就需要实现接口用于Linux访问,开发者只要按照规则用C语言实现这些需要的接口,在按照一定的规则编译后,就可以使用lsmod/rmmod来加载和移除自定义的模块,这套规则就是我们掌握内核模块需要学习的知识,按照功能分为以下接口:

必须模块

  模块加载函数:module_init(func)

  模块卸载函数: module_exit(func)

  模块许可声明:MODULE_LICENSE("xxx") 支持的许可有: "GPL", "GPL V2", "GPL and additional right", "Dual BSCD/GPL", "DUAL MPL/GPL", "Proprietary"

可选模块

  模块参数 -- 模块加载时传递变量 module_param(name, charp, S_IRUGO);

  模块导出符号 --用于将符号导出,用于其它内核模块使用。

    EXPORT_SYSMBOL(func)/EXPORT_SYSMBOL_GPL(func)

    注意:Linux内核2.6增加了函数校验机制,后续模块需要引入时要在Module.symvers下添加导入函数内核的路径和symbol。

  模块作者 -- MODULE_AUTHOR("xxx")

  模块描述 -- MODULE_DESCRIPTION("xxx")

  模块版本 -- MODULE_VERSION("xxx")

  模块别名 -- MODULE_ALIAS("xxx")

  模块设备表 -- MODULE_DEVICE_TABLE, 对于USB或者PCI设备需要支持,表示支持的设备,这部分比较复杂,这里就不在多说,后续如果用到,在详细去说明。

  在了解上述模块的基础上,就可以实现如下的模块代码:

 1 //hello.ko
 2 #include <linux/init.h>
 3 #include <linux/module.h>
 4 
 5 
 6 //extern int add_integar(int a, int b);
 7 static char *buf = "driver";
 8 module_param(buf, charp, S_IRUGO); //模块参数
 9 
10 static int __init hello_init(void)
11 {
12         int dat = 3; //int dat = add_integar(5, 6);
13         printk(KERN_WARNING "hello world enter, %s, %d\n", buf, dat);
14         return 0;
15 }
16 module_init(hello_init);  //模块加载函数
17 
18 static void __exit hello_exit(void)
19 {
20     printk(KERN_WARNING "hello world exit\n");
21 }
22 module_exit(hello_exit);              //模块卸载函数
23 
24 MODULE_AUTHOR("ZC");                //模块作者
25 MODULE_LICENSE("GPL v2");                     //模块许可协议
26 MODULE_DESCRIPTION("a simple hello module");  //模块许描述
27 MODULE_ALIAS("a simplest module");            //模块别名
View Code

使用Makefile文件如下:

 1 ifeq ($(KERNELRELEASE),)
 2 KDIR := /lib/modules/$(shell uname -r)/build
 3 PWD := $(shell pwd)
 4 modules:
 5         $(MAKE) -C $(KDIR) M=$(PWD) modules
 6 modules_install:
 7         $(MAKE) -C $(KDIR) M=$(PWD) modules_install
 8 clean:
 9         rm -rf *.o *.ko .depend *.mod.o *.mod.c modules.*
10 .PHONY:modules modules_install clean
11 else
12 obj-m :=hello.o
13 endif
View Code

保存后,使用Make即可编译,如果遇到编译错误,请先查看文章最后的备注,未包含问题请搜索或者留言,编译结果如图所示。

 

之后执行指令modinfo hello.ko即可查看当前的模块信息。

如果无法查看信息,可通过dmesg查看加载信息。

内核模块的跨模块调用

  上一节可以解决我们遇到的大部分内核实现问题,但某些时候我们可能需要一些公共内核模块,提供接口给大部分模块使用,这就涉及到内核模块的跨模块调用。

  对于跨核模块调用的实现,对于调用的模块,主要包含2步:

    1、在代码实现中添加extern int add_integar(int a, int b);

    2、在编译环境下修改Module.symvers, 添加被链接模块的地址,函数校验值(可通过查看被链接模块编译环境下的Module.symvers内复制即可)

  对于被链接的模块,代码实现如下:

 1 //math.ko
 2 #include <linux/init.h>
 3 #include <linux/module.h>
 4 
 5 static int __init math_init(void)
 6 {
 7     printk(KERN_WARNING "math enter\n");
 8     return 0;
 9 }
10 module_init(math_init);
11 
12 static void __exit math_exit(void)
13 {
14     printk(KERN_WARNING "math exit\n");
15 }
16 module_exit(math_exit);
17 
18 int add_integar(int a, int b)
19 {
20         return a+b;
21 }
22 EXPORT_SYMBOL(add_integar);
23 
24 int sub_integar(int a, int b)
25 {
26         return a-b;
27 }
28 EXPORT_SYMBOL(sub_integar);
29 
30 MODULE_LICENSE("GPL V2");
View Code

编译Makefile同上,需要将obj-m :=hello.o修改为obj-m :=math.o

执行make编译完成该文件,并通过insmod加载完模块后,可通过

grep integar /proc/kallsyms 查看加载在内核中的符号,状态如下:

然后加载insmod hello.ko, 即可跨文件调用该接口。如此,便初步完成对Linux内核模块的学习。

备注

1.内核编译名称必须为Makefile,否则编译会出错

 make[2]: *** No rule to make target `/usr/kernel/hello/Makefile'.  Stop.

 make[1]: *** [_module_/usr/kernel/hello] Error 2

 make[1]: Leaving directory `/usr/src/linux-headers-3.5.0-23-generic'

2.Makefile的内容,如果编译多个文件obj-m :=hello.o test.o

3.Makefile中,指令必须以Tab对齐,否则编译会异常。

4.printk不打印,一般来说输出的KERNEL_INFO为超过最大输出值,可直接通过dmesg,在系统信息内查看。

5.内核跨文件访问接口

除EXPORT_SYSMBOL外,在编译时Module.symvers需要包含对应函数的校验值,路径

0x13db98c9      sub_integar     /usr/kernel/math/math   EXPORT_SYMBOL

0xe1626dee      add_integar     /usr/kernel/math/math   EXPORT_SYMBOL

否则编译时报警告

WARNING: "add_integar" [/usr/kernel/hello/hello.ko] undefined!

安装模块时出错

[ 9091.025357] hello: no symbol version for add_integar

[ 9091.025360] hello: Unknown symbol add_integar (err -22)

posted @ 2020-04-21 19:35  心的起始  阅读(4112)  评论(0编辑  收藏  举报