zoukankan      html  css  js  c++  java
  • 嵌入式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)

  • 相关阅读:
    Oracle SQL语句收集
    SqlParameter In 查询
    SQL 性能优化
    Entity Framework
    【XLL API 函数】 xlfSetName
    【XLL API 函数】xlfUnregister (Form 2)
    【XLL API 函数】xlfUnregister (Form 1)
    【Excel 4.0 函数】REGISTER 的两种形式以及VBA等效语句
    【Excel 4.0 函数】REGISTER
    【Bochs 官方手册翻译】 第一章 Bochs介绍
  • 原文地址:https://www.cnblogs.com/zc110747/p/12747213.html
Copyright © 2011-2022 走看看