第四章 嵌入式Linux设备驱动的基础知识
第四章 嵌入式Linux设备驱动的基础知识
4.1硬件基础
本小节将讲解底层驱动工程师必须要掌握的基础硬件知识,让学习嵌入式或者想提升嵌入式技术水平的读者们对嵌入式有更加深入的理解。本小节主要介绍处理器、存储器以及常规的总线和接口等,最后介绍一些与硬件相关的常规检测仪器。
4.1.1 嵌入式处理器
1.嵌入式处理器相关重要概念
在讲解嵌入式处理器的时候,需要先普及几个关于嵌入式处理器的相关概念:冯·诺依曼结构、哈佛结构、流水线技术以及CICS和RICS。
(1)冯·诺依曼结构
冯·诺依曼结构又被称为普林斯顿结构,它是一种将程序指令存储器和数据存储器合在一起的存储结构。由于程序指令和数据存储的位置是同一个存储器中不同的物理地址,因此它们的指令长度是相等的,例如Intel8086、ARM7和MIPS处理器就是使用的是冯·诺依曼结构的,如下图4.1所示为该结构的寄存器结构图。
图4.1 冯·诺依曼结构寄存器结构图
Figure 4.1 Von Neumann structure register structure diagram
(2)哈佛结构
对于哈佛结构与上述的冯·诺依曼结构有些差异,主要差异在于哈佛结构是一种将程序指令存储器和数据存储器分开的处理器结构,因此指令和数据可以是不同宽度的,具有更高的效率。如下的品牌的处理则是使用这种哈佛结构的,如摩托罗拉公司的MC68系列、Zilog公司的Z8系列和ARM10系列等。
(3)流水线技术
流水线技术是指能够实现在程序执行时能够将多条指令重叠进行执行的一种准并行处理技术,这个技术的本质是利用指令的运行时间的不同阶段会使用不同的硬件,并发的运行多条指令,因此此项技术能够提高时间效率。
如下图4.2所示为三条指令的同步运行的流程,指令仍旧是一条一条的执行,但是可以在提前取多条指令,同时在当前的指令尚未运行完时,提前启动后续指令的另一些操作步骤,这样可以加快程序的运行速度。
图4.2 三条指令的并行运行
Figure 4.2 Parallel running of three instructions
(4)CICS和RICS
在指令集的方面可以将中央处理器分为两类:RSCI(精简指令集计算机)和CISC(复杂指令系统计算机),CSIC设计的重点是强调指令的能力、减少目标代码的数目,可是这会带来如下问题:增加了指令的复杂度以及指令的周期比较长。对于RISC的设计则是强调尽量精简指令集和单周期执行指令,但是这样也存在弊端就是会使得目标代码比较臃肿。目前ARM、MIPS和PowerPC等处理器都是采用了RISC指令集。
2.嵌入式处理器的分类
嵌入式处理器主要可以分为如下种类:MPU、MCU、DSP和SOC。
(1)嵌入式处理器MPU
MPU是指更具实际的项目需求只保留和嵌入式相关的功能硬件,将多余的功能剔除,用最低的功耗和资源实现嵌入式应用的特殊要求。MPU通常情况下对工作温度、EMC抗干扰能力以及其他可靠性方面的性能对于常规计算机CPU来说都做了增强,目前流行的MPU处理器有:ARM和MIPS等。
(2)嵌入式处理器MCU
对于嵌入式开发工程师来说MCU肯定是不陌生了,它是将整个计算机系统都集成到了一块芯片中去了,例如单片机就是一种MCU。MCU一般都具备ROM、FLASH和D/A等必要的功能,具有体积小可靠性高的特点,常规的MCU有8051和STM32等。
(3)嵌入式处理器DSP
DSP是一种对系统结构和指令都进行了特殊设计的处理器,系统上的就够采用了哈佛结构和特制的硬件乘法器,目前DSP重点是针对通信、图像、语音等处理算法设计的,它具备快速的DSP指令系统,能够以高速的处理效率来完成复杂的运算处理任务。
(4)嵌入式处理器SOC
处理器SOC也是一种功能和性能强大的处理器,它集成了一个或多个处理器、存储器、模拟电路模块以及片上可编程逻辑模块等。SOC主要分为通用类型和专用两类,通用系列处理器有摩托罗拉公司的M-Core某些ARM系列器件等。
4.1.2 存储器
目前存储器分类主要可以分为ROM(只读存储器)、Flash(闪存)、RAM(随机存取存储器)和光/磁介质存储器。
1.RAM(随机存取存储器)
RAM存储单元的内容可以随意的进行存储和读取,同时读写速度也是很快速的,由于RAM在断电时会将其存储的内容丢失,因此RAM主要用于存储短时间使用的程序数据。
2.ROM(只读存储器)
ROM相对于RAM来说的区别在于ROM掉电后不会丢失数据,但是读写速度较RAM要慢些。发展到现在的ROM是EEPROM,它能够实现电可擦除,可以以Byte为最小单位进行修改,不必将所有的数据全部清除再写入。
3.Flash(闪存)
Flash其实大的方面来看也属于点可擦出的ROM,但是它与EEPROM不一样,它擦除时不在以字节为单位擦除,而是以块为单位。Flash可分为NOR Flash和Nand Flash ,NOR Flash由于具有独立的地址线和数据线,因此它能够在芯片内执行,可以用作boot;而对于Nand Flash是与IO设备的数据、地址以及控制线公用的,因此不适合直接作为boot使用。
4.1.3 接口与总线
在嵌入式系统的设计过程中经常需要将处理器与一定数量的外围部件进行连接,倘若如果每个部件和每种的外围部件都分别使用一组线路与处理器进行连接的话,那整个系统的接线将会非常复杂,甚至难以实现。因此为了能够简化硬件电路的设计和简化系统结构,通常用一组线来配置适当的接口电路,接着才与各个部件和外围设备进行接线,这样的一组线路就是所谓的总线。
嵌入式微机系统的总线一般分为内部总线、系统总线和外部总线。接下来将是针对这个分类进行介绍常规的总线。
1.内部总线
(1)I2C总线
I2C总线是一种两线式的串行总线,用于连接和控制外围设备的。I2C总线具有两个信号分别为数据信号SDA和时钟信号SCL,如下图4.3所示为I2C总线信号的时序图,当SCL置于高电平,SDA由高电平被拉低变为低电平时,I2C开始数据传输,反之SDA由低电平被拉高为高电平时,传输结束。综合上述可知I2C总线具有简单有效的接线方案,同时使得在PCB设计时空间小,占用芯片引脚数少等优点。
图4.3 I2C总线的开始和停止信号
Figure 4.3 I2C Bus start and stop signals
(2)SPI总线
SPI总线系统是一种同步串行外设接口,SPI接口一般使用4根线来进行控制通信的,包括SCLK(串行时钟线)、MISO(主机输入/丛机输出数据线)、MOSI(主机输出/丛机输入数据线)和SS(片选信号线)。如下图4.4所示为一个主机通过SPI接口联机并控制3个丛机的硬件接线,其中具体的SPI信号时序控制部分将在后续SPI框架驱动中进行详细讲解。
图4.4 SPI硬件接线图
Figure 4.4 SPI Hardware wiring diagram
2.系统总线
(1)PCI总线
PCI总线是当前比较流行的一种总线,它的标准是由Intel公司推出的一种局部总线。该总线定义了32位数据总线同时也可以扩展为64位。PCI总线具有体积小、功能强大等优点,能够支持并发读写操作以及最大传输速率可达132MB/s。
(2)ISA总线
ISA总线标准由IBM 公司在1984年为推出PC/AT机而建立的系统总线标准,所以也叫AT总线。它是对XT总线的扩展,以适应8/16位数据总线要求。它在80286至80486时代应用非常广泛,以至于现在奔腾机中还保留有ISA总线插槽。ISA总线有98只引脚。
3.外部总线
(1)RS-232-C总线
RS-232-C总线的标准是一种串行物理接口的标准,它设有25根信号线,其中包括主通道和辅通道,对于常规的双工通信,仅需要几根信号线就可以实现了。RS-232-C标准的数据传输的速率可为每秒50、75、100、150、300、9600、19200波特等。由于存在共地噪声和不能抑制共模干扰等问题,该总线一般用于20米以内的数据通信。
(2)RS-485总线
RS-485总线由于采用了平衡发送和差分接收数据的方式,因此它具有很强的抑制共模干扰的能力,能够使用在几十米到几千米的数据传输中。
由于RS-485总线工作方式是半双工的,因此RS-485总线构建的系统可以是分布式系统,允许最多并联32个驱动器和32台接收器。
(3)USB总线
USB总线具有外观简单、快速连接、方便用户使用的有点,同时快速也是USB技术的核心特点,最高可到12Mbps这比串口通信快的多了。
4.1.4 调试时常规检测仪器使用
在上述章节讲解完硬件知识后,本小节将介绍在日常开发中需要用到的仪器以及他们的使用方法,本小节主要介绍万用表和逻辑分析仪。
1.万用表
在电路开发板调试过程中必不可少的使用到万用表的两个功能,就是测量电流和电压,同时也利用万用表的蜂鸣档进行测试是否短路或是否有虚焊的情况。
2.逻辑分析仪
逻辑分析仪是利用时钟测试设备上采集数字信号并进行显示的仪器,它的主要功能是对时序的判断。与示波器不同的是,逻辑分析仪并没有多个电压等级,通常只有显示电压的高或低(1或0)。
当前市面上的逻辑分析仪有的是能够将显示和测量结合到一体的,但是价格比较昂贵;但也有比较便宜的,这种是可以通过USB等接口与PC机连接,分析软件在PC上工作,这也是一种虚拟的逻辑分析仪,如下图4.5为显示和测量于一体的逻辑分析仪,图4.6所示则为虚拟逻辑分析仪。
图4.5 一体的逻辑分析仪
Figure 4.5 Integrated Logic Analyzer
图4.6 虚拟逻辑分析仪
Figure 4.6 Virtual logic analyzer
4.1.7 小结
本节主要是对嵌入式开发中的硬件知识进行了讲解,主要包括处理器、存储器、接口和总线。同时在最后还对常规的一些仪器进行了简单的介绍,能够帮助读者在未来的嵌入式开发中能够对底层硬件有一个更直观的了解和认识。
4.2 linux内核编程讲解
在我们从小到大的生长历程中,不管是老师或者众多的教程往往会不厌其烦的会介绍该科目、知识点等等的发展历史,例如我们会知道被苹果砸中脑袋的牛顿、试验了很多材料才发明灯泡的爱迪生等等,了解他们的故事其实是有助我们在学习过程中增加趣味性以及能够让我们更好的理解这些定律、知识点以及发明的前因后果,可以更加有助于我们去掌握它们。因此本章将从Linux内核的发展史将其,带领读者们踏入Linux内核的大门。
4.2.1 linux内核的发展
谈到Linux不得不提就是他的父亲Linus,他可是个跟苹果之父乔布斯一样都是富有非凡的天赋,在1991年他借了很多钱才终于DIY了一台当时来说是性能超级棒的电脑,正当他想利用在计算机课程中学会的使用Minix操作系统来黑进学校的服务器来上网时,由于当时这个Minix系统仅仅授权用来教学使用同时价格也贵的让linus望而退步,于是这个电脑小伙秉着自己动手丰衣足食的精神,平时利用学习的空闲时间自己一点一点构建和调试自己的系统,终于这个神奇的小伙在1991年9月份将Linux内核的0.01版本发布在了FTP服务器上,从此将掀开了一个开源系统王国的帷幕。
从第一个版本还仅仅是一个内核的Linux系统,到现在linux系统已经迭代发展由原来瘦弱的小伙逐步成长为一名肌肉男,增加了shell、编译器以及许多工具库等。发展到今天这为肌肉***的发行版主要包括Debian、Ubuntu、RedHat等。
4.2.2 linux 内核的特点
Linux操作系统具有一个开放的和自由开发的内核,它的内核具有如下的特点:
1.一体化整体设计的内核
Linux内核是一个一体化设计的内核,又称为宏内核,它是集内核和系统于一身,而以往嵌入式实时系统常采用的微内核来说,宏内核更为强大。
2.具备很强的移植能力
Linux内核目前已经可以支持多个硬件平台上运行,它能够在不同体系结构的计算机上运行,目前Linux系统已经可以在主流硬件平台上运行有如下:x86、amd64、alpha和ARM等。
3.内核具备灵活的裁剪能力
Linux内核具备非常灵活的裁剪能力,可以根据项目需求的不同而进行内核裁剪,内核的大小可以小到只有几百k,大到就可达几百兆。
4.采用模块化的设计
Linux内核采用模块化设计能够为内核中需要的功能编译成一个个功能模块,可以在内核运行期间动态的加载或卸载驱动模块,无需重新编译系统,大大给内核开发工作提高了效率,节省时间。
5.内核支持完整的网络通讯协议
Linux内核支持了POSIX完整的网络协议栈,其网络功能完善。
6.稳定性强
尽管随着Linux内核的版本的增加,内核中的功能也不断增加,内核也变得复杂,但是Linux内核运行时的稳定性还是很强,非常适合在嵌入式系统。
4.2.3 Linux内核启动流程的讲解
在本节内容将讲解嵌入式Linux内核启动流程,让读者们对嵌入式linux内核有更加深入的了解,让后续的嵌入式开发思路有一个宏观的认识。
嵌入式Linux内核的启动主要可以分为以下两个阶段:
第一阶段:BROM引导
第二阶段:Bootloader引导阶段。
第三阶段:嵌入式Linux内核初始化及其启动阶段。
1.BROM引导
在ARM的主控芯片上电时,它的PC寄存器纸质首先指向一片ROM的起始位置,这片区域被称为“BROM”,此处区域是比较小的,一般仅仅32/64kb,由于不同的芯片的ShareRam的大小不一样,他们的引导程序也会有所差异。但是BROM引导程序的主要工作一般是相同的,如下为该引导程序的主要工作:
(1)CPU上电初始化设置
(2)启动介质的驱动设置
(3)提供固件更新操作
(4)BROM引导程序,主要完成从介质中读取和加载第二阶段引导程序。
(5)验证签名设置
2.Bootloader启动阶段
对于ARM架构的BROM引导程序有点类似与X86的BIOS程序,一般这个引导程序及功能是固定不变的,但是从第二阶段开始就可以实现定制化的引导启动阶段,该阶段一般是单独存放在介质中的一个分区中,该分区被称为boot分区或者MBR分区。在第二阶段启动阶段中主要分为三级引导进行的:
(1)FirstMBRC
第一级引导程序需要符合BROM引导所需的特定格式的,同时它会复制拷贝第二级引导程序SecondMBRC到ShareRam中进行校验比对,接着跳转执行,这一引导程序是独立的代码来实现的,一般使用汇编来完成。
(2)SecondMBRC
在这一级引导程序主要完成调用BROM中的驱动函数来拷贝MainMBRC到DD中进行校验比对,同时跳转执行。一般可以使用uboot的spl来完成。
(3)MainMBRC
第三级是主引导程序,前面两级的引导程序主要目的是为MainMBRC的执行创造条件,MainMBRC主要功能是显示启动LoGo,加载Kernel、dtb和根文件系统,同时启动Kernel。该级启动程序一般使用uboot来实现。
综上所述,在boot分区中,将烧入以下三部分代码:MBRC、Uboot-Spl和Uboot。
3.嵌入式linux内核初始化启动阶段
在经过前面Uboot引导程序的执行引导后,系统将进入到zImage中执行,zImage主要包括压缩代码和vmlinux的镜像,在该阶段主要分为三步来完成:
(1)zImage解压缩阶段
(2)vmlinux内核启动汇编阶段
该阶段的代码是从arch/arm/kernel/head.S开始的,执行起始函数是stext函数。入口函数是通过vmlinux.lds中的ENTRY(stext)指定的。需要特别注意的是:在汇编.S文件中也有ENTRY的宏定义,它需要和ENDPROC成对出现,表示定义的一个函数。
(3)vmlinux内核启动C语言阶段
kernel/init/main.c文件中对C语言的入口做出了定义,通过函数start_kernel开始执行,它会去调用与平台相关的函数,这部分函数的定义依然在kernel/arch/arm/mach-XXX目录中。
4.2.4 linux下C语言编程及其特点
4.2.4.1 标准C语言的特点
C语言是一种能够很好的应用到硬件上的编程语言,在1972年C语言诞生之际,到如今它已经迅速的发展成为了最受欢迎的编程语言之一,这主要归功于C语言具有如下特点:
1.C语言是一种中级语言,它能够很好的将高级语言的基本结构语句与低级语言结合起来,它能够实现像汇编语言那样可以对位、字节和地址进行操作,体现了C语言能够与硬件底层有一个良好的协调关系。
2.C语言是一种结构式语言。使用C语言编写出来的代码具有模块化的特点,即使用C语言模块化编程之后能够编写出独立性较强的功能函数接口,这种结构方式能够程序条理清晰和结构层次分明,便于开发和维护人员对后期代码的维护和调试。
3.C语言是一种功能齐全的编程语言。它具有种类丰富的数据类型,同时引入了非常重要的指针概念,这就使得程序的效率更加高了。同时C语言由于具有强大的图形功能,所以能够很好的支持多种显示器和驱动器。
4.2.4.2 Linux内核编程风格
Linux内核的编程风格具有如下特点:
1、Linux内核编程的缩进一般是8个字符。
2、在Linux内核编程中的执行语句的开始大括号是放在该行的末尾,而将结束的大括号另起一行放置,如下所示:
if(y == 1){ .......... }
而对于命名函数时则将大括号的起始都需要另起一行,如下所示:
void fun(int x) { ...... }
3、在Linux内核编程中的变量和函数等命名都需要尽量的简洁明了,这个与Windows编程中的命名风格正好相反。例如不应该命名变量ThisTemporaryCounter,而应该命名为tmp即可,这样的命名的优点在于书写简便便于理解。可是对于全局变量的命名而言,则需要使用描述性的名字了,例如count_static_users(),而不是cntusr()。
4、定义函数时,函数的功能应该最好只完成某一个功能,而不是与其他函数实现的功能有过多的交集而造成冗余,同时函数的参数不应该过多,一般不超过10个,否则这个时候应该将该函数优化分解为多个功能更为简单的函数来组合实现。
4.2.5交叉编译工具链概述
交叉编译工具产生的原因在于,我们实际项目使用到的嵌入式系统往往还不具备编译自身运行的程序的能力,因此需要在另外性能比较强大的系统上进行编译出合适的可执行文件;与此同时该两个系统往往是体系结构不同的两个平台,因此交叉编译工具由此产生满足:在一种平台上编译出能够运行在另外不同体系的的平台上的可执行文件。
交叉编译工具链是一个由编译器、连接器和解析器构成的编译环境,其中Linux的交叉编译工具链主要由Binutils、glibc和gcc三部分构成。
常用的交叉编译工具链有如下:
(1)arm-none-linux-gnueabi-gcc:该工具链是基于GCC的ARM交叉编译工具,是由Codesourcery 公司开发的,可用于交叉编译ARM系统的代码,包括裸机程序、u-boot等。
(2)arm-linux-gnueabihf-gcc:该工具链由 Linaro 公司基于GCC推出的的ARM交叉编译工具。可用于交叉编译ARM系统的代码,包括裸机程序、u-boot和App应用程序等。
(3)arm-none-eabi-gcc:是 GNU 推出的的ARM交叉编译工具。可用于交叉编译ARM MCU(32位)芯片,如ARM7、ARM9、Cortex-M/R芯片程序。
4.2.6嵌入式常用开发的工具概述
在嵌入式开发中往往选择合适的编程软件会提升开发的效率,如下为常用的开发工具:
1、SourceInsight:这是一款常用的而且功能强大的阅读代码和编写程序的工具,这款工具特点是能够帮助我们在比较庞大的代码中跳转阅读,查找特定的函数也是十分的便捷。
2、GDB工具:该工具是一款调试所使用的测试工具,能够为开发人员提供了单步调试和断点设置等功能,对于内核驱动调试也是十分好用的工具。
3、SVN和GIT工具:由于我们日常开发难免会需要对代码版本进行保存,此时SVN和GIT这两款工具恰恰能够为我们提供代码版本更新保存的功能,而且较直接复制粘贴保存备份来说,他们所占用的空间更小,我们也可以在日常开发中浏览之前版本与当前版本的差异。
4.2.7 小结
本小节主要是对Linux内核编程的相关知识进行了概述性的讲解,让读者能够对Linux内核的发展、特点以及内核的启动流程有一个直观的了解。接着读者能够了解到在后续的内核开发中常用的编译工具链以及常用的开发工具,这为读者后续从事开发工作选择开发工具提供了参考。
4.3 linux内核模块介绍讲解
在Linux系统中的驱动很多时候是以内核模块的形式存在的,因此学习内核模块的相关知识将对我们继续学习如何编写驱动起到奠定基础的作用,本节将带领读者们进入Linux世界的学习内核模块相关的知识。
4.3.1 linux内核模块简介
Linux系统包含了各种各样的功能,整体的规模是非常的大的,当然包含的相应的组件模块就非常的多,那我们该如何将我们需要的功能包含进系统之中呢?
方法是有两个的:(1)将我们需要的功能一同编译进内核之中,如果选择这种方法的话,将会面临两个问题,其一是编译出来的内核占据内存非常的大,其二是如果需要在内核中增加或者删减功能的话,则需要重新编译内核,这样会大大降低开发的效率。(2)Linux提供了一种机制,就是可以将各个功能能够以模块的形式进行编译,然后在系统运行的时候进行加载来实现功能。这种方法相对方法一来说是较为灵活的,而且每次修改功能时都不需要重新编译烧录内核,这样可以大大的提高了开发效率。
4.3.2 linux内核模块结构讲解
一个内核模块通常会包含如下几个组成部分:
(1)内核模块所需的头文件
在内核模块中头文件是必不可少的,同时由于功能的不同,各个模块所需的头文件也是不尽相同的,但是每个模块都需要如下两个头文件,主要完成模块的相关初始化声明等:
#include <linux/module.h> #include <linux/init.h>
(2)内核模块初始化及加载函数
内核模块初始化主要的职责是需要将内核模块向Linux内核中进行注册,如果没有注册的话,则无法使用该内核模块的各种功能。在内核模块初始化中可能还会涉及到一些该模块的硬件初始化操作,例如某些GPIO口需要初始化等。
通常情况下内核模块初始化和加载函数是一块实现的,当通过命令insmod 或 modprobe就可以调用加载函数加载内核模块并完成初始化。
(2)内核模块卸载函数
可通过命令rmmod调用模块的卸载函数将模块从内核中卸载,此时模块将调用卸载函数,完成清零一些模块相关的资源,例如模块一开始申请了的内存资源和GPIO资源等。
(3)内核模块许可申明
内核模块的许可申明是必须的,如果在内核模块中没有许可申明,则加载内核模块时会有Kernel tainted的警告。一般Linux可接受的许可申明有如下:“GPL”、“GPL v2”、“GPL and additional rights”等,通常情况下内核模块需要遵循GPL兼容许可权,可用如下语句进行模块许可申明:
MODULE_LICENSE( "GPL" );
(4)内核模块参数
加载内核模块时可以传递参数给内核模块,此时传递进去的内核模块参数则是对应模块内的全局变量。
(5)内核模块导出符号
模块导出符号有点类似于C语言的extern修饰全局变量,这样就可以在其他文件中使用了,相类似的内核模块导出符号也可以在其他内核模块中使用。
4.3.3 加载模块函数讲解
Linux内核模块加载函数通常是以__init来申明的,如下为常用的内核模块加载函数的形式:
//入口函数 static int __init func_init(void) { //初始化代码 } module_init(func_init);
有了模块加载函数的定义还是不够的,还需要有使用module_init(函数名)来告诉内核这个函数就是模块的入口函数。这个函数的返回值是整型值(int),模块加载成功返回0,否则返回相应的错误码。
4.3.4 卸载模块函数讲解
内核模块有了加载模块函数,对应着如果卸载模块肯定需要调用卸载函数来清理释放模块资源,一般情况下卸载函数用__exit来声明,类似加载函数也需要通过一个函数“module_exit(函数名)”来指定该函数为模块卸载函数。如下为卸载函数的常规形式:
static void _ _exit cleanup_function(void) { /* 释放代码 */ } module_exit(cleanup_function);
4.3.5 模块参数
内核模块参数一般使用如下形式来定义:
module_param(参数名, 参数类型, 参数读/写权限); //举例 static char *student_name = “Tony”; static int num = 53; module_param(num, int, S_IRUGO); module_param(student_name, charp, S_IRUGO);
上述则是定义了一个整型参数和一个字符指针参数。如果加装内核模块时,用户需要向模块传递参数的值,此时可以使用如下命令格式即可传递,如果不传递则为缺省值。
insmode 模块名 参数名=参数值
此处补充一下参数的类型有如下:byte、short、int、long、charp等,同时对于传进去的参数权限有如下:
#define S_IRWXU 00700 //用户可以读、写、执行 #define S_IRUSR 00400 //用户读 #define S_IWUSR 00200 //用户写 #define S_IXUSR 00100 //用户执行 #define S_IRWXG 00070 //组可以读、写、执行 #define S_IRGRP 00040 //组可读 #define S_IWGRP 00020 //组可写 #define S_IXGRP 00010 //组可执行 #define S_IRWXO 00007 //其他人可读、写、执行 #define S_IROTH 00004 //其他人可读 #define S_IWOTH 00002 //其他可写 #define S_IXOTH 00001 //其他可执行
其中S_IRUGO作为参数可以被所有人读取, 但是不能改变; S_IRUGO|S_IWUSR允许root来改变参数,这两个参数也是比较常用的。
4.3.6 编译模块的讲解
当我们编写完内核模块的程序文件后,如果需要将该模块应用到内核系统中,此时我们需要首先对内核模块进行编译,然后再将编译后的内核模块文件(通常为.ko文件)拷贝到linux系统中,使用如下命令来将内核模块加载进内核中:
对于编译一个普通的内核模块,以下给出一个简单的Makefile代码,使用这个Makefile即可对单个内核模块进行编译:
ifneq ($(KERNELRELEASE),) obj-m += test.o else KDIR := /lib/modules/$(shell uname -r)/build PWD:=$(shell pwd) all: make -C $(KDIR) M=$(PWD) modules clean: rm -f *.ko *.o *.symvers *.cmd *.cmd.o endif
上面的Makefile代码中的test.c应该需要位于与Makefile同一级目录下才能够顺利编译成功。在电脑端执行命令make,即可编译出test.ko文件,这个即是内核模块文件。
在实际的项目中往往是需要将多个.c文件编译成一个.ko文件的,因此如下给出Makefile的部分对应需要修改的代码:
obj-m := 内核模块名.o modulename-objs := 文件1名.o 文件2名.o
4.3.7 小结
本节主要是讲解了关于内核模块的相关基础的知识和它的相关编程方法。内核模块主要由头文件、加载函数(初始化)、卸载函数等组成,它也可以像平时的函数使用那样传入参数和导出参数。
因为在Linux中的驱动多为以这种内核模块的形式存在的,因此这一章的内容需要认真的学习和掌握,将对后续各种类型的驱动学习具有很大的意义。将Linux驱动的模块化是对整个项目开发具有很强实际意义,正式由于驱动以内核模块的形式存在,我们在修改维护驱动的时候,仅仅只需要编译模块而不需要将整个系统重新编译烧录来进行测试,这就大大提高了项目开发的效率。