一、内核驱动与启动流程
1. Linux内核驱动
Nor Flash: 可线性访问,有专门的数据及地址总线(与内存访问方式相同)。
Nand Flash: 不可线性访问,访问需要控制逻辑(软件)。
2. Linux启动流程
ARM架构:
IRAM (4KB): 内部RAM,用于存储初始引导程序。
Nor Flash (2M): 存储u-boot程序。
内存 (64M): 用于加载内核和根文件系统。
Nand Flash (256M): 存储内核、根文件系统等数据。
启动过程:
Bootloader (u-boot):
初始化CPU、异常向量表、栈、时钟、内存等。
关闭看门狗、中断、Cache、MMU。
初始化相关硬件和软件协议。
将内核加载到内存。
向内核传递参数(根文件系统类型、位置、控制台等)。
启动内核。
内核 (kernel):
文件管理、内存管理、进程管理、网络管理、设备管理。
启动到最后阶段加载根文件系统。
init
进程启动后台服务程序、加载配置、启动shell和应用程序。
根文件系统 (rootfs):
包含程序(应用、系统、命令)、配置文件、库文件、普通文件(txt、mp3)。
3. Windows与Linux对比
Windows: 使用BIOS启动。
Linux: 使用bootloader引导内核启动,内核加载rootfs。
4. 具体启动步骤
Nor Flash:
系统上电后,PC指向0地址,直接执行Nor Flash中的u-boot程序。
Nand Flash:
系统上电后,自动搬移u-boot前4KB程序到IRAM。
CPU执行IRAM中的代码,u-boot初始化内存并将剩余代码搬移到内存执行。
5. 内核与文件系统
内核 (uImage):
启动前u-boot向内核传递参数(
tag_list
)。Nand Flash: u-boot直接读取Nand Flash中的uImage并写入内存的0x30008000地址处,启动内核。
Ubuntu: 通过TFTP下载uImage到内存的0x30008000地址处,启动内核。
根文件系统 (rootfs):
Nand Flash: uImage启动到最后阶段时,直接挂载Nand Flash中的rootfs。
Ubuntu: uImage启动到最后阶段时,通过NFS挂载Ubuntu中的rootfs。
6. 前置步骤
向Nor Flash 0地址处烧写u-boot.bin。
拷贝uImage到Ubuntu的TFTP服务目录下。
将rootfs.tar.gz拷贝到Ubuntu的NFS服务目录下,并解压
sudo tar -xvf rootfs.tar.gz
7. U-Boot命令
环境变量管理:
printenv
: 打印环境变量。reset
: 重启。setenv serverip 192.168.1.3
: 设置环境变量。saveenv
: 保存环境变量到Nand Flash。setenv serverip
: 删除环境变量。
下载与启动:
tftp 0x30008000 uImage
: 通过TFTP协议下载uImage到内存的0x30008000地址处。bootm 0x30008000
: 启动内存0x30008000地址处的内核。go 0x30008000
: 运行内存0x30008000地址处的程序。
8. 设置启动参数
setenv bootargs console=ttySAC0,115200 root=/dev/nfs nfsroot=192.168.1.3:/home/linux/nfs/rootfs ip=192.168.1.123 init=/linuxrc
console: 控制台(终端)。
root: 根文件系统类型。
nfsroot: 根文件系统位置。
ip: 内核阶段使用的IP。
init: 指定init进程。
二、内核编译
1. 内核编译步骤
Kconfig: 定义
make menuconfig
的配置选项。make menuconfig: 内核配置。
.config: 配置文件,决定哪些文件被编译进内核。
CONFIG_SSL = n
: 不启用SSL。CONFIG_MM = y
: 启用内存管理。
makefile: 条件编译,编译内核。
2. 内核镜像类型
Image: 可以直接使用的内核镜像。
zImage: 一段解压代码 + Image的压缩文件。
uImage: 64字节的头信息 + zImage。
3. 地址相关代码
地址相关代码: 链接地址和加载地址一致。
地址无关代码: 链接地址和加载地址无关。
4. 跳转指令
相对跳转、短跳转:
b fun
绝对跳转、长跳转:
ldr pc, 0x00000000
5. 内核目录结构
6. 向内核新增文件
以向drivers/char
下添加demo.c
为例:
在
drivers/char
目录下新建并编辑demo.c
。修改同层目录下的
makefileMakefile
,添加:obj-$(CONFIG_DEMO) += demo.o
修改同层目录下的
Kconfig
,添加一个DEMO的配置。执行
make menuconfig
。执行
make uImage
。
7. 内核编译命令
配置内核
cp config_mini2440_t35 .config make menuconfig
编译内核
make uImage
8. 内核镜像说明
Image: 可直接使用的内核镜像。
zImage: 压缩的内核镜像,包含解压代码。
uImage: 带有64字节头信息的压缩内核镜像。
9. Makefile和Kconfig
每层目录都有
Makefile
和Kconfig
文件,用于配置和编译内核。
10. 编译流程总结
配置内核: 使用
make menuconfig
选择内核配置选项。编译内核: 使用
make uImage
生成内核镜像。验证内核: 确保生成的内核镜像可以正常启动。
11. 注意事项
配置文件:
.config
文件决定了哪些模块被编译进内核。条件编译: 使用
obj-$(CONFIG_XXX)
进行条件编译。目录结构: 每层目录都有
Makefile
和Kconfig
文件,确保编译过程正确。
三、驱动程序
1. 设备文件与驱动模块
设备文件: 用户空间程序通过设备文件与驱动程序交互。
示例:
open("/dev/led");
驱动模块: 内核中的驱动程序负责控制硬件设备。
示例:
sys_open(led)
调用驱动模块。
2. 设备驱动类型
字符设备驱动: 数据按顺序访问,90%以上的设备使用字符设备驱动。
块设备驱动: 可以随机访问,主要用于存储设备。
网络设备驱动: 网卡,集成复杂协议,通过套接字通信,没有设备号,靠名字维护。
3. 设备号
设备号: 用于标识设备。
主设备号 (高12位): 区分设备类型。
次设备号 (低20位): 区分同类的不同设备。
示例:
dev_t
是32位设备号。
4. 创建设备节点
使用
mknod
命令创建设备节点mknod /dev/demo3 c 255 0
/dev/demo3
: 设备节点名。c
: 字符设备。255
: 主设备号。0
: 次设备号。
5. 驱动模块结构
驱动模块: 包含
open
、read
、write
、ioctl
、close
等函数。示例:
drv_led
、drv_key
、drv_adc
分别对应LED、按键、ADC设备。
6. 设备驱动流程
应用程序调用设备文件:
open("/dev/led");
内核调用驱动模块:
sys_open(led)
→drv_led
。
驱动模块控制硬件设备:
drv_led
控制LED设备。
7. 设备号与驱动模块关系
每个设备号对应一个驱动模块。
内核通过设备号找到对应的驱动模块。
8. 设备号结构
设备类型 (魔幻数): 8位,标识设备类型。
命令编号: 8位,标识具体命令。
数据流向: 2位,标识数据方向。
参数大小: 14位,标识参数大小。
9. 设备驱动总结
字符设备: 顺序访问,适用于大多数设备。
块设备: 随机访问,适用于存储设备。
网络设备: 复杂协议,通过套接字通信。
设备号: 区分设备类型和具体设备。
驱动模块: 内核中的程序,负责控制硬件设备。
10. 关键命令
创建设备节点:
mknod
。设备文件操作:
open
、read
、write
、ioctl
、close
。
11. 注意事项
设备号分配: 确保主设备号和次设备号唯一。
驱动模块编写: 需要实现
open
、read
、write
等函数。设备文件操作: 应用程序通过设备文件与驱动模块交互。