嵌入式linux与物联网进阶之路四:嵌入式驱动开发思路

程序诗人 程序诗人     2022-11-29     575

关键词:

前言

荔枝派nano这块板子,从本章开始,将会发挥它最大的价值,藉由它来带领我们进入嵌入式linux驱动开发的大门。

想必大家在玩linux类型的板子之前应该或多或少的都尝试过其他类型的板载系统的开发,诸如裸跑C语言程序的51单片机;基于Arduino开发套件的ESP8266,亦或是一直大火的STM32等等,不一而足。虽然说芯片不一样,开发模式不一样,但是从中或多或少我们能够窥见针对不同芯片的驱动开发思路,哪怕是一个小小的LED等,在这些不同芯片上的驱动方式或多或少都有相同或者不同的地方。

而基于linux平台的嵌入式驱动开发,则和上面几种驱动开发模式,都不太一样。

我们知道,在进行linux内核编译的时候,我们可以看到有很多文件,然后通过设置arm平台框架和交叉编译链等选项,来使得这些源码文件构建成镜像文件。但是假如我们是设备制作商,现在设计了一款基于linux的板子,也针对linux做了定制化设计并提供了一些扩展提供给用户,其实最简单的方式,无非就是我写好了驱动文件,然后放到内核中和内核文件一起编译,然后烧写到板子上。这种方式非常简便,当需求设计不多的时候,这种做法更直接有效,但是一旦需要实现的驱动设计需求较多而且改动频繁的时候,这种思想显然是费时费力的。

在软件架构领域,目前一直在推崇微服务化等思想,其核心目的就是让服务尽可能的小,尽可能的去中心化,然后来实现各个模块的解耦。而这种思想,其实对于嵌入式linux驱动开发来说,也具有相当强的影响力。与其将驱动和内核绑定在一起,那么有没有什么机制,让驱动实现和内核剥离,驱动可以随意实现,内核只需要加载驱动就行了呢?

答案是肯定的。而且这个设计思想很早就在linux系统上进行了实现,而且保留至今。

Linux下嵌入式驱动开发思想

在Linux系统中,主要将存储器和外设分为三种类型,即:字符设备、块设备和网络设备。其中字符设备是指那些必须以串行顺序依次进行访问的设备,比如触摸屏,鼠标等等。块设备则可以按照任意顺序进行访问,以块为单位进行操作,比如硬盘,eMMC等。字符设备和块设备的驱动设计有很大的差别,但是对于用户而言,他们都要使用文件系统的操作接口,比如open(),close(),read(),write()等进行访问。而网络设备是面向数据包的传输设计的,并不倾向于对应于文件系统的节点。

linux内核模块结构

一个Linux内核模块主要由以下几个部分组成

 

1. 模块加载函数

当通过insmode或者modprobe命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作。

Linux内核模块加载函数,一般以__init标记来声明,典型的模块加载函数形式如下:

static int __init hello_init(void)

        printk(KERN_EMERG "hello, init\\n");
        return 0;

module_init(hello_init);

模块加载函数以“module_init(函数名)”的形式被指定。它返回int型。若初始化成功,返回0,初始化失败时,则返回相应的错误编码。在Linux内核中,错误编码是一个接近于0的负值。

在Linux内核中,可以使用request_module函数加载内核模块,驱动开发人员可以通过调用如下代码:

request_module(module_name);

来灵活的加载其他模块。

在Linux中,所有标识为__init的函数,如果直接编译进入内核,成为内核镜像的一部分,在连接的时候,都会放在.init.text这个区段内。

 

2. 模块卸载函数

当通过rmmod命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模块卸载函数相反的功能。

典型代码如下:

static void __exit hello_exit(void)

        printk(KERN_EMERG "hello, exit\\n");

module_exit(hello_exit);

模块卸载函数在模块卸载的时候执行,而不返回任何职,且必须以"module_exit"的形式来指定。通常来说,模块卸载函数需要完成与模块加载函数相反的功能。

我们用__exit来修饰模块卸载函数,可以告诉内核如果相关的景象被直接编译进内核(即built-in),则cleanup_function函数会被忽略,直接不链接进最后的景象,既然模块被内置了,就不可能卸载它了,卸载函数也就没有存在的必要了。

 

3. 模块许可证声明

许可证(LICENSE)声明描述内核模块的许可权限,如果不生命LICENSE,模块被加载时,将收到内核被污染(Kenrel Tainted)的警告。在Linux内核模块领域,可以接受的LICENSE包括"GPL","GPL v2","GPC and additional rights","Dual BSD/GPL","Dual MPL/GPL"和"Proprietary"(关于模块是否可以采用非GPL许可权,如"Proprietary",这个在学术界和法律界都有争议)。大多数情况下,内核模块应该遵循GPC兼容协议许可,Linux内核模块最常见的是以MODULE_LICENSE(“GPL v2”)语句声明模块来表明采用GPL v2许可。

 

4. 模块参数(可选)

模块参数是模块被加载的时候可以传递给它的值,它本身对应模块内部的全局变量。

我们可以用“module_param(参数名,参数类型,参数读写权限)”为模块定义一个参数,例如下列diamante定义了1个整型参数和1个字符指针参数:

static char * book_name  = "dissecting Linux Device Driver";
module_param(book_name, charp, S_IRUGO);

static int book_num = 4000;
module_param(book_num, int, S_IRUGO);

参数类型可以是byte, short, ushort, int, uint, long, ulong, charp, bool或者invbool(布尔的反),在模块编译时,会将module_param中声明的类型与变量定义的类型进行比较,判断是否一致。

除此之外,模块也可以拥有参数数组,形式为"module_param_array"(数组名,数组类型,数组长,参数读写权限).

 

5. 模块导出符号(可选)

内核模块可以导出的符号(symbol,对应于函数或者变量),若导出,其他模块则可以使用本模块中的变量或者函数。

Linux的"/proc/kallsyms"文件对应着内核符号表,它记录了符号以及符号所在的内存地址。

模块可以使用如下宏导出符号到内核符号表中:

EXPORT_SYMBOL(符号名);
EXPORT_SYMBOL_GPL(符号名);

导出的符号可以被其他模块使用,只需要使用前提前声明一下即可。

 

6. 模块作者等信息声明(可选)

在Linux内核模块中,我们可以使用MODULE_AUTHOR,MODULEDESCRIPTION,MODULE_VERSION,MODULE_DEVICE_TABLE,MODULE_ALIAS分别标明模块作者,描述,版本,设备表和别名,例如:

MODULE_LICENSE("GPL");
MODULE_AUTHOR("feixiaoxing");
MODULE_DESCRIPTION("This is just a hello module!\\n");

对于USB,PCI等设备驱动,通常会创建一个MODULE_DEVICE_TABLE,以标明该驱动模块所支持的设备。

模块编译

看完了模块的组成,这里我们以一个简单的Hello.c的例子来展开,代码如下:

#include <linux/init.h>
#include <linux/sched.h>
#include <linux/module.h>
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("cxsr");
MODULE_DESCRIPTION("a hello module!\\n");
 
static int __init hello_init(void)

        printk(KERN_EMERG "hello, init\\n");
        return 0;

 
static void __exit hello_exit(void)

        printk(KERN_EMERG "hello, exit\\n");

 
module_init(hello_init);
module_exit(hello_exit);

之后我们在源码同级目录创建一个Makefile,注意名称大小写, 内容如下:

ifneq ($(KERNELRELEASE),)
obj-m := hello.o
 
else
PWD  := $(shell pwd)
KDIR := /home/scy/linux-mi/linux-f1c100s-480272lcd-test/
all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm
clean:
	rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions
endif

需要特别注意的是, all命令和clean命令,下面的命令行,前面一定要TAB缩进,不能是空格缩进,如果是空格缩进,会报如下的错误:

Makefile:8: *** missing separator. Stop.

这个时候,我们可以利用如下命令打开Makefile,然后删掉命令行前面的空格,用TAB替代即可:

nano Makefile

这里还需要注意的就是,KDIR目录,一定是我们编译到板子上的linux内核的目录,而all命令中的交叉编译链,必须是我们进行linux内核编译的交叉编译链,否则将会导致模块不能被加载。

完毕之后,在终端输入如下命令:

make

此时,就可以看到模块被制作:

同时,可以看到,原来目录下有俩文件,现在有了多个:

这里,我们需要拷贝到板子上的文件就是hello.ko文件了。

 

ko文件拷贝与安装

文件拷贝,我们可以使用minicom,也可以直接手动拷贝,由于我这里比较懒,就手动拷贝了。

插上usb插卡器,然后执行如下命令,进行拷贝:

sudo mkdir /mnt/sdb2             //创建一个临时目录
sudo mount /dev/sdb2 /mnt/sdb2   //将sdb2挂载到此临时目录
sudo cp hello.ko /mnt/sdb2/media //拷贝到sdb2/media目录下
sudo sync
sudo umount /dev/sdb2

之后,我们就可以拔掉tf卡,插到开发板上,进入media目录,进行模块的安装:

可以看到,我们写的简单的字符驱动成功打印出来了。

如果出现如下错误:

	 insmod: can\'t insert \'hello.ko\': invalid module format

此种错误,则表明内核版本和makefile使用的内核版本不一致,一定要保证板子的linux内核版本和Makefile中的内核版本一致且交叉编译链一致,即可。

参考

《Linux设备驱动开发详解:基于最新的Linux 4.0内核》

嵌入式linux与物联网进阶之路三:根文件系统制作

承接前篇,我们的linux内核终于制作好了,也顺利的加载起来了,但是由于没有根文件系统,所以说加载到最后,是无法进入系统的。而本节内容则是讲解如何来制作根文件系统的。BuildRoot创建根文件系统由于BuildRoot工具可以构... 查看详情

嵌入式操作系统与物联网演进之路

...术的重要组成部分,回顾其发展,其中不得不提的必然是嵌入式系统。传统的嵌入式系统与互联网的发展衍生出物联网,而在如今的物联网热潮之下,嵌入式系统也面临着全新的机遇与挑战。那么,两者的碰撞融合究竟会带来怎... 查看详情

如何成为高手?嵌入式开发进阶之路...

...达大家好,我是小麦,今天给大家分享一下看看嵌入式要掌握哪些技能。1嵌入式人才要求嵌入式行业需要什么样的技术人才?仔细观察各种招聘的岗位要求,无非是两方面:1、通用要求比如什么学历,多... 查看详情

2017-2018-120155302实验四外设驱动程序设计

...验四外设驱动程序设计任务一?学习资源中全课中的“hqyj.嵌入式Linux应用程序开发标准教程.pdf”中的第十一章?提交康奈尔笔记的照片(可以多张)完成情况:第十一章主要讲述了在Linux的内核空间的嵌入式Linux设备驱动的开发。L... 查看详情

201552292017-2018-1《信息安全系统设计基础》实验四外设驱动程序设计

2017-2018-120155229实验四实验目的学习嵌入式Linux设备驱动开发掌握设备驱动的运作过程。实验步骤实验四-外设驱动程序设计-1学习资源中全课中的“hqyj.嵌入式Linux应用程序开发标准教程.pdf”中的第十一章提交康奈尔笔记的照片(... 查看详情

2017-2018-12015521820155205实验四外设驱动程序设计

...容实验四外设驱动程序设计-1学习资源中全课中的“hqyj.嵌入式Linux应用程序开发标准教程.pdf”中的第十一章提交康奈尔笔记的照片(可以多张)实验四外设驱动程序设计-2在Ubuntu完成资源中全课中的“hqyj.嵌入式Linux应用程序开... 查看详情

2017-2018-120155216实验四:外设驱动程序设计

...设驱动程序设计-1实验要求:学习资源中全课中的“hqyj.嵌入式Linux应用程序开发标准教程.pdf”中的第十一章提交康奈尔笔记的照片(可以多张)实验结果:笔记照片:实验四外设驱动程序设计-2实验要求:在Ubuntu完成资源中全课... 查看详情

嵌入式开发工程师学习线路图

一、核心学习课程  1、应用软件基础      Linux基础      C语言  2、底层驱动程序开发      ARM编程(ARM体系结构、ARM汇编、开发板常见的外设裸机编程)      Linux驱动开发二、Android扩展  ... 查看详情

2017-2018-12015530420155332实验四外设驱动程序设计

...计实验四外设驱动程序设计-1学习资源中全课中的“hqyj.嵌入式Linux应用程序开发标准教程.pdf”中的第十一章提交康奈尔笔记的照片(可以多张)实验四外设驱动程序设计-2在Ubuntu完成资源中全课中的“hqyj.嵌入式Linux应用程序开... 查看详情

20155312201553252017-20181实验四外设驱动程序设计

实验四外设驱动程序设计-1学习资源中全课中的“hqyj.嵌入式Linux应用程序开发标准教程.pdf”中的第十一章提交康奈尔笔记的照片(可以多张)已提交至班课实验四外设驱动程序设计-2在Ubuntu完成资源中全课中的“hqyj.嵌入式Linux... 查看详情

2017-2018-120155222201552228实验四外设驱动程序设计

...容和要求外设驱动程序设计-1学习资源中全课中的“hqyj.嵌入式Linux应用程序开发标准教程.pdf”中的第十一章提交康奈尔笔记的照片(可以多张)外设驱动程序设计-2在Ubuntu完成资源中全课中的“hqyj.嵌入式Linux应用程序开发标准... 查看详情

2017-2018-120155338实验四外设驱动程序设计

...序设计任务一:实验要求:1)学习资源中全课中的“hqyj.嵌入式Linux应用程序开发标准教程.pdf”中的第十一章2)提交康奈尔笔记的照片完成情况:任务二:实验要求:1)在Ubuntu完成资源中全课中的“hqyj.嵌入式Linux应用程序开发... 查看详情

2017-2018-1201553334实验四外设驱动程序设计

...外设驱动程序设计实验目的:学习资源中全课中的“hqyj.嵌入式Linux应用程序开发标准教程.pdf”中的第十一章,提交康奈尔笔记的照片。在Ubuntu完成资源中全课中的“hqyj.嵌入式Linux应用程序开发标准教程.pdf”中的第十一章的test... 查看详情

2017-2018-12015522720155318实验四外设驱动程序设计

...。实验四外设驱动程序设计-1学习资源中全课中的“hqyj.嵌入式Linux应用程序开发标准教程.pdf”中的第十一章提交康奈尔笔记的照片(可以多张)笔记如下:20155227:20155318:实验四外设驱动程序设计-2在Ubuntu完成资源中全课中的... 查看详情

嵌入式linux课程设计

参考技术AⅠ跪求ARM嵌入式linux系统开发详解(珍藏版)pdf珍藏版啊。。Ⅱ学嵌入式linux需要先学什么刚入门的时候,淘宝买一块cortexm3开发板即可入手,通过项目,你需要了解:任务调度、进程间通信、内存管理、设备驱动、文... 查看详情

2017-2018-120155331《信息安全系统设计基础》实验四外设驱动程序设计

...计实验四外设驱动程序设计-1学习资源中全课中的“hqyj.嵌入式Linux应用程序开发标准教程.pdf”中的第十一章提交康奈尔笔记的照片(可以多张)实验四外设驱动程序设计-2在Ubuntu完成资源中全课中的“hqyj.嵌入式Linux应用程序开... 查看详情

2018-2019-120165211实验四外设驱动程序设计(代码片段)

...动程序设计任务一1.实验要求学习资源中全课中的“hqyj.嵌入式Linux应用程序开发标准教程.pdf”中的第十一章提交康奈尔笔记的照片(可以多张)2.任务完成任务二1.实验要求在Ubuntu完成资源中全课中的“hqyj.嵌入式Linux应用程序开... 查看详情

嵌入式linux第二部分-裸机开发/系统移植/驱动开发/内核开发

本部分主要专注构建从0到1的嵌入式Linux学习知识体系。主要涉及Linux环境配置,嵌入式Linux裸机开发,Linux文件系统及系统移植,驱动开发等部分。目前持续更新中,更新时间:2022年11月27日【嵌入式Linux】裸机开发篇LinuxC语言及M... 查看详情