Linux设备驱动开发
上QQ阅读APP看书,第一时间看更新

第1章 内核开发简介

Linux起源于芬兰的莱纳斯·托瓦尔兹(Linus Torvalds)在1991年凭个人爱好开创的一个项目。这个项目不断发展,至今全球有1000多名贡献者。现在,Linux已经成为嵌入式系统和服务器的必选。内核作为操作系统的核心,其开发不是一件容易的事。

和其他操作系统相比,Linux拥有更多的优点。

·免费。

·丰富的文档和社区支持。

·跨平台移植。

·源代码开放。

·许多免费的开源软件。

本书尽可能做到通用,但是仍然有些特殊的模块,比如设备树,目前在x86上没有完整实现。那么话题将专门针对ARM处理器,以及所有完全支持设备树的处理器。为什么选这两种架构?因为它们在桌面和服务器(x86)以及嵌入式系统(ARM)上得到广泛应用。

本章涉及以下主题。

·开发环境设置。

·获取、配置和构建内核源码。

·内核源代码组织。

·内核编码风格简介。

1.1 环境设置

在开始任何开发之前,都需要设置开发环境。用于Linux开发的环境相当简单,至少在基于Debian的系统上是这样:


$ sudo apt-get update 
$ sudo apt-get install gawk wget git diffstat unzip texinfo \ 
gcc-multilib build-essential chrpath socat libsdl1.2-dev \ 
xterm ncurses-dev lzop

本书部分代码与ARM片上系统(System on Chip,SoC)兼容,应该安装gcc-arm:


sudo apt-get install gcc-arm-linux-gnueabihf

我在华硕RoG上运行Ubuntu 16.04,使用的是Intel Core i7(8个物理内核),16 GB内存,256 GB固态硬盘和1 TB磁盘驱动器。我最爱用的编辑器是Vim,读者可以使用任意一款自己熟悉的编辑器。

1.1.1 获取源代码

在早期内核(2003年前)中,使用奇偶数对版本进行编号:奇数是稳定版,偶数是不稳定版。随着2.6版本的发布,版本编号方案切换为X.Y.Z格式。

·X:代表实际的内核版本,也被称为主版本号,当有向后不兼容的API更改时,它会递增。

·Y:代表修订版本号,也被称作次版本号,在向后兼容的基础上增加新的功能后,它会递增。

·Z:代表补丁,表示与错误修订相关的版本。

这就是所谓的语义版本编号方案,这种方案一直持续到2.6.39版本;当Linus Torvalds决定将版本升级到3.0时,意味着语义版本编号在2011年正式结束,然后采用的是X.Y版本编号方案。

升级到3.20版时,Linus认为不能再增加Y,决定改用随意版本编号方案:当Y值增加到手脚并用也数不过来时就递增X。这就是版本直接从3.20变化到4.0的原因。

现在内核使用的X.Y随意版本编号方案,这与语义版本编号无关。

源代码的组织

为了本书的需要,必须使用Linus Torvald的Github仓库。


git clone https://github.com/torvalds/linux 
git checkout v4.1 
ls

·arch/:Linux内核是一个快速增长的工程,支持越来越多的体系结构。这意味着,内核尽可能通用。与体系结构相关的代码被分离出来,并放入此目录中。该目录包含与处理器相关的子目录,例如alpha/、arm/、mips/、blackfin/等。

·block/:该目录包含块存储设备代码,实际上也就是调度算法。

·crypto/:该目录包含密码API和加密算法代码。

·Documentation/:这应该是最受欢迎的目录。它包含不同内核框架和子系统所使用API的描述。在论坛发起提问之前,应该先看这里。

·drivers/:这是最重的目录,不断增加的设备驱动程序都被合并到这个目录,不同的子目录中包含不同的设备驱动程序。

·fs/:该目录包含内核支持的不同文件系统的实现,诸如NTFS、FAT、ETX{2,3,4}、sysfs、procfs、NFS等。

·include/:该目录包含内核头文件。

·init/:该目录包含初始化和启动代码。

·ipc/:该目录包含进程间通信(IPC)机制的实现,如消息队列、信号量和共享内存。

·kernel/:该目录包含基本内核中与体系架构无关的部分。

·lib/:该目录包含库函数和一些辅助函数,分别是通用内核对象(kobject)处理程序和循环冗余码(CRC)计算函数等。

·mm/:该目录包含内存管理相关代码。

·net/:该目录包含网络(无论什么类型的网络)协议相关代码。

·scripts/:该目录包含在内核开发过程中使用的脚本和工具,还有其他有用的工具。

·security/:该目录包含安全框架相关代码。

·sound/:该目录包含音频子系统代码。

·usr/:该目录目前包含了initramfs的实现。

内核必须保持它的可移植性。任何体系结构特定的代码都应该位于arch目录中。当然,与用户空间API相关的内核代码不会改变(系统调用、/proc、/sys),因为它会破坏现有的程序。

本书使用的内核版本是4.1。因此,v4.11版本之前所做的任何更改都会涉及,至少会涉及框架和子系统。

1.1.2 内核配置

Linux内核是一个基于makefile的工程,有1000多个选项和驱动程序。配置内核可以使用基于ncurse的接口命令make menuconfig,也可以使用基于X的接口命令make xconfig。一旦选择,所有选项会被存储到源代码树根目录下的.config文件中。

大多情况下不需要从头开始配置。每个arch目录下面都有默认的配置文件可用,可以把它们用作配置起点:


ls arch/<you_arch>/configs/

对于基于ARM的CPU,这些配置文件位于arch/arm/configs/;对于i.MX6处理器,默认的配置文件位于arch/arm/configs/imx_v6_v7_defconfig;类似地,对于x86处理器,可以在arch/x86/configs/找到配置文件,仅有两个默认配置文件:i386_defconfig和x86_64_defconfig,它们分别对应于32位和64位版本。对x86系统,内核配置非常简单:


make x86_64_defconfig 
make zImage -j16 
make modules 
makeINSTALL_MOD_PATH </where/to/install> modules_install

对于基于i.MX6的主板,可以先执行ARCH=arm make imx_v6_v7_defconfig,然后执行ARCH=arm make menuconfig。前一个命令把默认的内核选项存储到.config文件中;后一个命令则根据需求来更新、增加或者删除选项。

在执行make xconfig时,可能会遇到与Qt4相关的错误,这种情况下,应该执行下列命令安装相关的软件包:


sudo apt-get install  qt4-dev-tools qt4-qmake

1.1.3 构建自己的内核

构建自己的内核需要指定相关的体系结构和编译器。这也意味着,不一定是本地构建:


ARCH=arm make imx_v6_v7_defconfig 
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make zImage -j16

输出如下:


[...] 
  LZO     arch/arm/boot/compressed/piggy_data 
  CC      arch/arm/boot/compressed/misc.o 
  CC      arch/arm/boot/compressed/decompress.o 
  CC      arch/arm/boot/compressed/string.o 
  SHIPPED arch/arm/boot/compressed/hyp-stub.S 
  SHIPPED arch/arm/boot/compressed/lib1funcs.S 
  SHIPPED arch/arm/boot/compressed/ashldi3.S 
  SHIPPED arch/arm/boot/compressed/bswapsdi2.S 
  AS      arch/arm/boot/compressed/hyp-stub.o 
  AS      arch/arm/boot/compressed/lib1funcs.o 
  AS      arch/arm/boot/compressed/ashldi3.o 
  AS      arch/arm/boot/compressed/bswapsdi2.o 
  AS      arch/arm/boot/compressed/piggy.o 
  LD      arch/arm/boot/compressed/vmlinux 
  OBJCOPY arch/arm/boot/zImage 
  Kernel: arch/arm/boot/zImage is ready

内核构建完成后,会在arch/arm/boot/下生成一个单独的二进制映像文件。使用下列命令构建模块:


ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make modules

可以通过下列命令安装编译好的模块:


ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make modules_install

modules_install目标需要指定一个环境变量INSTALL_MOD_PATH,指出模块安装的目录。如果没有设置,则所有的模块将会被安装到/lib/modules/ $ (KERNELRELEASE)/kernel/目录下,具体细节将会在第2章讨论。

i.MX6处理器支持设备树,设备树是一些文件,可以用来描述硬件(相关细节会在第6章介绍)。无论如何,运行下列命令可以编译所有ARCH设备树:


ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make dtbs

然而,dtbs选项不一定适用于所有支持设备树的平台。要构建一个单独的DTB,应该执行下列命令:


ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make imx6d-   sabrelite.dtb

1.2 内核约定

在内核代码的演化过程中应该遵守标准规则,这里只做简单介绍,后面会专门讨论。第3章~第13章会更全面地介绍内核开发的过程和要点。

1.2.1 编码风格

深入学习本节之前应该先参考一下内核编码风格手册,它位于内核源代码树的Documentation/CodingStyle目录下。编码风格是应该遵循的一套规则,如果想要内核开发人员接受其补丁就应该遵守这一规则。其中一些规则涉及缩进、程序流程、命名约定等。

常见的规则如下。

·始终使用8个字符的制表符缩进,每一行不能超过80个字符。如果缩进妨碍函数书写,那只能说明嵌套层次太多了。使用内核源代码scripts/cleanfile中的脚本可以设置制表符的大小和行长度:


scripts/cleanfile my_module.c

·可以使用intent工具正确缩进代码:


sudo apt-get install indent  
scripts/Lindent my_module.c

·每一个不被导出的函数或变量都必须声明为静态的。

·在带括号表达式的内部两端不要添加空格。s = size of (struct file);是可以接受的,而s = size of(struct file);是不被接受的。

·禁止使用typedef。

·请使用/* this */注释风格,不要使用// this。

·坏:// 请不要用这个。

·好:/* 内核开发人员这样用注释 */。

·宏定义应该大写,但函数宏可以小写。

·不要试图用注释去解释一段难以阅读的代码。应该重写代码,而不是添加注释。

1.2.2 内核结构分配和初始化

内核总是为其数据结构和函数提供两种可能的分配机制。

下面是其中的一些数据结构。

·工作队列。

·列表。

·等待队列。

·Tasklet。

·定时器。

·完成量。

·互斥锁。

·自旋锁。

动态初始化器是通过宏定义实现的,因此全用大写:INIT_LIST_HEAD()、DECLARE_WAIT_QUEUE_HEAD()、DECLARE_TASKLET()等。

这些将在第3章详细讨论。因此,表示框架设备的数据结构总是动态分配的,每个都有其自己的分配和释放API。框架设备类型如下。

·网络设备。

·输入设备。

·字符设备。

·IIO设备。

·类设备。

·帧缓冲。

·调节器。

·PWM设备。

·RTC。

静态对象在整个驱动程序范围内都是可见的,并且通过该驱动程序管理的每个设备也是可见的。而动态分配对象则只对实际使用该模块特定实例的设备可见。

1.2.3 类、对象、面向对象的编程

内核通过类和设备实现面向对象的编程。内核子系统被抽象成类,有多少子系统,/sys/class/下几乎就有多少个目录。struct kobj ect结构是整个实现的核心,它包含一个引用计数器,以便于内核统计有多少用户使用了这个对象。每个对象都有一个父对象,在sysfs(加载之后)中会有一项。

属于给定子系统的每个设备都有一个指向operations(ops)结构的指针,该结构提供一组可以在此设备上执行的操作。

1.3 总结

本章简要介绍了如何下载Linux源代码、构建第一个内核版本,以及一些常见概念。也就是说,本章很简短,不够详细,但是这只是一个简介,第2章将更详细地介绍内核构建过程、怎样实际编译外部或内核驱动程序,以及在启动内核开发之旅之前应该学习的一些基础知识。