• 嵌入式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
    ", 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
    ");
    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
    ");
     8     return 0;
     9 }
    10 module_init(math_init);
    11 
    12 static void __exit math_exit(void)
    13 {
    14     printk(KERN_WARNING "math exit
    ");
    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)

  • 相关阅读:
    php自定义函数call_user_func和call_user_func_array详解
    微信开发(一) 服务器配置
    6487. 【GDOI2020模拟02.29】列强争霸war
    关于循环顺序对时间影响的一点研究
    6486. 【GDOI2020模拟02.25】向日葵人生
    6485. 【GDOI2020模拟02.25】沙塔斯月光
    6478. 【GDOI2020模拟02.19】C(上下界费用流、费用流消负环)
    6461. 【GDOI2020模拟02.05】生成树(矩阵树及其扩展、二维拉格朗日插值)
    上下界网络流&费用流
    6467. 【GDOI2020模拟02.09】西行寺无余涅槃(FWT的性质)
  • 原文地址:https://www.cnblogs.com/zc110747/p/12747213.html
Copyright © 2020-2023  润新知