【驱动学习】驱动简介

一,驱动介绍

1,驱动跟操作系统的关系

系统和应用程序的关系

  首先,一个复杂的软件系统需要处理多个并发的任务,没有操作系统,想完成多任务并发是很困难的。
  其次,操作系统给我们提供内存管理机制。一个典型的例子是,对于多数含 MMU 的处理器而言, Windows、 Linux 等操作系统可以让每个进程都独立地访问 4GB 的内存空间。
  上述优点似乎并没有体现在设备驱动身上,操作系统的存在给设备驱动究竟带来了什么好处呢?
  简而言之,操作系统通过给设备驱动制造麻烦来达到给上层应用提供便利的目的。如果设备驱动都按照操作系统给出的独立于设备的接口而设计,应用程序将可使用统一的系统调用接口来访问各种设备。对于类 UNIX的 VxWorks、 Linux 等操作系统而言,应用程序通过 write()、 read()等函数读写文件就可以访问各种字符设备和块设备,而不用管设备的具体类型和工作方式,是非常方便的。

2,编写 Linux 设备驱动的技术基础

Linux 设备驱动的学习是一项浩大的工程,读者需要首先掌握以下基础。

  • 具有良好的硬件基础,懂得 SRAM、 Flash、 SDRAM、磁盘的读写方式, UART、 I2C、 USB 等设备的接口,轮询、中断、 DMA 的原理, PCI 总线的工作方式以及 CPU 的内存管理单元(MMU)等。
  • 具有良好的 C 语言基础,能灵活地运用 C 语言的结构体、指针、函数指针及内存动态申请和释放等。
  • 具有一定的 Linux 内核基础,虽然并不要求工程师对内核各个部分有深入的研究,但至少要了解设备驱动与内核的接口,尤其是对于块设备、网络设备、 Flash设备、串口设备等复杂设备。
  • 良好的多任务并发控制和同步的基础,因为在设备驱动中会大量使用自旋锁、互斥、信号量、等待队列等并发与同步机制。

二 linux启动引导

1,内核引导流程

引导 X86 PC 上的 Linux内核的过程和引导嵌入式系统上的 Linux 内核的过程基本类似,X86 PC 上有一个从 BIOS(基本输入/输出系统)转移到 Bootloader 的过程, 如图 3.10 所示, 而嵌入式系统往往复位后就直接运行 Bootloader。

内核引导流程

图 3.10 给出了在 X86 PC 上从上电/复位到运行 Linux 用户空间初始进程的流程。在进入与 Linux 相关代码之前,会经历如下阶段。

  • ① 当系统上电或复位时, CPU 会将 PC 指针赋值为一个特定的地址 0xFFFF0,并执行该地址处的指令。在 PC 中,该地址位于 BIOS 中,它保存在主板上的 ROM 或 Flash 中。
    ② BIOS 运行时按照 CMOS 的设置定义的启动设备顺序来搜索处于活动状态,并且可以引导的设备。若从硬盘启动, BIOS 会将硬盘 MBR(主引导记录)中的内容加载到 RAM。 MBR 是一个 512 字节大小的扇区,位于磁盘上的第一个扇区中(0 道 0 柱面 1 扇区)。当 MBR 被加载到 RAM 中之后, BIOS 就会将控制权交给 MBR。
    ③ 主引导加载程序查找并加载次引导加载程序。它在分区表中查找活动分区,当找到一个活动分区时,扫描分区表中的其他分区,以确保它们都不是活动的。当这个过程验证完成之后,就将活动分区的引导记录从这个设备中读入 RAM 中并执行它。
    ④ 次引导加载程序加载 Linux 内核和可选的初始 RAM 磁盘,将控制权交给 Linux 内核源代码。
    ⑤ 运行被加载的内核,并启动用户空间应用程序。

2,Bootloader

嵌入式系统中 Linux 内核的引导过程与之类似,但一般更加简洁。不论具体以怎样的方式实现,只要具备如下特征就可以称其为 Bootloader。

  • 可以在系统上电或复位的时候以某种方式执行,这些方式包括被 BIOS 引导执行、直接在 NOR Flash中执行、 NAND Flash 中的代码被 MCU 自动复制到内部或外部 RAM 执行等。
  • 能将 U 盘、磁盘、光盘、 NOR/NAND Flash、 ROM、 SD 卡等存储介质,或将网口、串口中的操作系统加载到 RAM,并把控制权交给操作系统源代码执行。

完成上述功能的 Bootloader 的实现方式非常多样化, 甚至本身也可以是一个简化版的操作系统。

3,详细分析

  下面对上述流程的第 5 个阶段进行更详细的分析, 它完成启动内核并运行用户空间的 init 进程的功能。
  当内核映像被加载到 RAM 之后, Bootloader 的控制权被释放。内核映像并不是可直接执行的目标代码,而是一个压缩过的 zImage(小内核)或 bzImage(大内核, bzImage 中的“b”是“big”意思)。
  但是,并非 zImage 和 bzImage 映像中的一切都被压缩了,映像中包含未被压缩的部分,这部分中包含解压缩程序,解压缩程序会解压缩映像中被压缩的部分。 zImage 和 bzImage 都是用 gzip 压缩的,它们不仅是一个压缩文件,而且在这两个文件的开头部分内嵌有 gzip 解压缩代码。
  如图 3.11 所示,当 bzImage(用于 i386 映像)被调用时,它从/arch/i386/boot/head.S 的 start 汇编例程开始执行。这个例程子进行一些基本的硬件设置, 并调用/arch/i386/boot/compressed/head.S 中的 startup_32 例程。
  startup_32 例程设置一个基本的运行环境(如堆栈)后清除 BSS 段,调用/arch/i386/boot/compressed/misc.c 中的 decompress_kernel()解压缩内核。

linux内核初始化

  内核被解压缩到内存中之后会再调用/arch/i386/kernel/head.S 文件中的 startup_32 例程,这个新的startup_32 例程(称为清除程序或进程 0)会初始化页表,并启用内存分页机制,接着为任何可选的浮点单元(FPU)检测 CPU 的类型,并将其存储起来供以后使用。
  这些都做完之后, /init/main.c 中的 start_kernel()函数被调用,进入与体系结构无关的 Linux 内核部分。
  start_kernel() 会 调 用 一 系 列 初 始 化 函 数 来 设 置 中 断 , 执 行 进 一 步 的 内 存 配 置 。 之 后 ,/arch/i386/kernel/process.c 中 kernel_thread()被调用以启动第一个核心线程,该线程执行 init() 函数,而原执行序列会调用 cpu_idle(),等待调度。
  作为核心线程的 init()函数完成外设及其驱动程序的加载和初始化,挂接根文件系统。 init()打开/dev/console设备,重定向 stdin、 stdout 和 stderr 到控制台。之后,它搜索文件系统中的 init 程序(也可以由“init=”命令行参数指定 init 程序), 并使用 execve()系统调用执行 init 程序。搜索 init 程序的顺序为/sbin/init、 /etc/init、 /bin/init和/bin/sh。在嵌入式系统中,多数情况下,可以给内核传入一个简单的 shell 脚本来启动必需的嵌入式应用程序。
  至此,漫长的 Linux 内核引导和启动过程就结束了,而 init()对应的由 start_kernel()创建的第一个线程也进入用户模式。

猜你喜欢

转载自blog.csdn.net/u012335044/article/details/79991088