嵌入式Linux内核的基础分析
2020-01-11
关键字:
Linux内核与Linux系统并不是一个东西。Linux内核属于Linux系统。Linux内核指的是一个提供硬件抽象层、磁盘及文件系统控制、多任务等功能的系统软件。而通常我们所说的Linux操作系统指的则是包括Linux内核、工具集、各种库、桌面管理器、应用程序等一体的发布包。
嵌入式Linux内核源码中,最核心的目录是 arch 目录。这个目录下存放着的都是和 CPU 体系相关的代码。
printk
在内核调试过程中可以通过三种方式来打印信息:
1、puts
内核解压前。
2、printascii
console 初始化之前
3、printk
内核解压后,信息输出显示是在 console 初始化之后。在 console 初始化完成之前若调用了这个函数进行打印,打印信息会先缓存到一个内存区域内,待 console 初始化完成之后才一起打印到屏幕上。
内核信息的打印是有级别的。它的级别信息记录在: /proc/sys/kernel/printk 中。可以直接在串口通过 cat 或 echo 来查看与修改打印信息级别。打印信息的级别共有 8 种,如下图所示:
在内核开发过程中,如果遇到内核错误,如空指针错误等,错误堆栈信息中一般会打印出 PC 所在的函数名称。如果想要知道更精确的出错位置,除了自己加打印跟踪以外,还可以通过交叉编译工具链程序中的一个 'address2line' 工具来将错误堆栈信息中的地址值转换成具体的代码行数来。
内核源码的获取
可以直接在Linux官网上下载内核源码。
编译前需要先修改内核源码根目录下的 Makefile 文件中的 CROSS_COMPILE 配置,设置好交叉编译链工具。并指定好平台架构是 arm 还是 x86。
然后可以通过 make menuconfig 来配置内核配置信息。如果不想执行 make menuconfig 来配置,也可以通过命令 make xxxdefconfig 来编译生成配置信息,即 kernel/.config 文件。
在准备好 .config 文件后,就可以通过命令 make uImage 来编译内核了。
外设在内核中的配置
当开发板中要适配新设备时,首先要在内核中将该设备的信息配置好。配置步骤大致如下:
1、确认电路图,明确设备芯片型号;
2、配置好 menuconfig 文件,开放与设备芯片相关的功能驱动;
3、dts 中信息配置好;
外设芯片中有些会有“片选脚”。所谓片选就是指当这个引脚的信号满足芯片要求时,即表示 cpu 当前需要与该芯片通信的意思。
关于第三方驱动程序的移植,通常用以下三步来实现:
1、选择驱动代码的存放目录
2、修改对应目录下的 Makefile
3、修改对应目录下的 Kconfig
如果想在开发板上临时创建一个设备节点,即在 /dev 目录下创建驱动设备文件,可以使用以下命令:
mknod /dev/led c 501 1
这条命令表示创建一个字符设备 /dev/led,它的主设备号为 501,次设备号为 1。
关于主设备号与从设备号
在Linux中,用主设备号与从设备号共同确定某一硬件设备的信息。主设备号通常表示某一类设备,其范围较广。从设备号则具体到某一款设备。例如:希捷的某型号机械硬盘与三星的某型号机械硬盘,它们的主设备号可能是相同的,但是从设备号必不同。
主从设备号由 #include <linux/types.h> 中的 dev_t 结构体来描述。dev_t 是一个 32 位的整数,其中高 12 位表示主设备号,低 20 位表示从设备号。另外,如果要从一个确定的 dev_t 中获得整数形式的主从设备号,应通过 <linux/kdev_t.h> 中的宏来获得:MAJOR(dev_t) MINOR(dev_t)。相反,如果要将整数形式的主从设备号转变成 dev_t 形式,则可以使用 MKDEV(int major, int minor) 宏来获得。
关于 Kconfig 与 Makefile
Linux 内核驱动程序数量众多。为了方便具体驱动程序的编译与否,Linux 采用了 Kconfig 机制来配置驱动的编译信息。
在 Linux 内核开发中,Kconfig 仅负责配置信息,真正的编译操作则是由 Makefile 决定的。
Kconfig 中的信息在执行 make menuconfig 时会被统一读取并以一个图形界面展示出来。
Linux 内核驱动编译配置支持三种编译模式:1、编译;2、不编译;3、编译成模块。
在 Kconfig 中对模块以 bool 修饰的,则是只应用“编译”与“不编译”模式,如:
而以 tristate 修饰的,则表示除了“编译”与“不编译”两个选项外还存在第三个选项“编译成模块”,如:
这两种模式在 make menuconfig 以图形界面展示时的表现则是:以 bool 修饰的在模块选择那里只有中括号展示 "[ ]" 或 "[*]"。而以 tristate 修饰的则是以尖括号展示 "< >" 或 "<*>" 或 "<M>",如下图所示:
而在编译内核时使用 make uImage 编译的仅仅是那些确定会被编译的驱动文件,“编译成模块”的驱动是不编译的。如果要编译那些被指定生成为模块文件的驱动,则需要使用 make modules 编译指令。编译模块驱动文件时可以看到编译指令中加了有 [M] 字样,如下图所示:
驱动模块文件是以 .ko 为后缀的。
在 Linux 系统运行起来以后,可以通过 insmod xxx.ko 命令来加载某个驱动程序。insmod 命令会触发驱动程序中的 module_init() 函数的执行。lsmod 可以查看当前加载了的 ko 驱动程序。rmmod 用以卸载 ko 驱动程序。
Linux 驱动概览
Linux 的驱动设备大致可以分为两种:
1、字符设备;
2、平台设备;
根文件系统
根文件系统是用于存放运行、维护系统所必须的各种工具软件、库文件、脚本、配置文件和其它特殊文件的地方,同时它也可以用于安装外部软件。简单理解,根文件系统就是用于存储各种文件的地方。
Linux 根文件系统下的几个重要目录及含义如下所示:
1、/bin
可执行程序。这个目录下放的程序一般所有权限级别的用户都可以执行。
2、/dev
块、字符设备节点文件。
3、/etc
主要配置文件和初始化执行文件。
4、/lib
基本的库文件。
5、/mnt
挂载点,用于临时挂载文件系统。
6、/opt
附加的软件包。
7、/proc
虚拟文件系统,用于内核和进程间通讯。
8、/sbin
基本的系统管理程序。这个目录下的程序通常只有超级用户才可以执行。
9、/tmp
临时文件目录。
10、/usr
更多的用户程序。
11、/sys
虚拟文件系统 sysfs 挂载点。
12、/var
可变信息储存,如 log。
如何制作一个根文件系统?
首先我们需要下载一份 Busybox 的源码。Busybox 是一种嵌入式领域的软件工具包。它常用于制作 Linux 命令,如 cat, ls, cp, chmod, echo, grep 等等。
将源码下载好以后进入 busybox 源码根目录,执行 make menuconfig 进行配置。配置主要就是选择以动态库的形式编译以及配置一下交叉编译链工具的前缀。
接下来是编译,通过 make 命令完成。然后是将编译生成的文件聚合起来,通过 make install 命令实现,完成后所有编译生成的文件都会被放在源码根目录下的 _install 目录下。
busybox 源码编译完成后都可以在 _install 目录下看到有 bin/ , linuxrc , sbin/ , usr/ 几个嵌入式 Linux 基础目录了。这些目录里面就包含了常见的命令程序了。
接着是手动将剩余目录创建出来,mkdir dev etc mnt proc var tmp sys root。
然后为了能让程序正常运行,至少还需要将交叉编译链工具的库文件集成过来。cp ~/arc-xxx/lib/ . -a 。如此便会将交叉编译链的库目录集成到我们的根文件系统根目录下,并以 lib 命名。为了精简系统大小,一般可以将 lib/ 目录下的静态库文件以及用不上的库删去。
然后是在 etc 目录下填充文件,如集成文件配置表文件 fstab , profile , init.d/ 等。
在 dev/ 目录下创建 console 节点,mknode /dev/console c 5 1
如此,便完成了基础的根文件系统的制作。
根文件系统的格式
在嵌入式Linux领域,根文件系统有多种格式类型,它们的类型与特性如下图所示: