嵌入式Linux

《嵌入式Linux》课程复习笔记。

1. 概述

嵌入式系统是“以应用为中心,以计算机技术为基础,并且软硬件可裁剪,适用于应用系统对功能、可靠性、成本、体积、功耗有严格要求的专用计算机系统”。一般由嵌入式微处理器、外围硬件设备、嵌入式操作系统以及用户的应用程序等部分组成,用于实现对其他设备的控制、监视或管理等功能。

分类:

  • 按表现形式分:(硬件范畴)芯片级嵌入(含程序或算法的处理器)、模块级嵌入(系统中的某个核心模块)、系统级嵌入
  • 按实时性要求分:(软件范畴)非实时系统、软实时系统、硬实时系统

嵌入式计算机系统同通用型计算机系统相比具有以下特点:

  1. 嵌入式系统通常是面向特定应用的。
  2. 嵌入式系统的硬件和软件都必须高效率地设计,量体裁衣、去除冗余。(资源受限)
  3. 为了提高执行速度和系统可靠性,嵌入式系统中的软件一般都固化在存储器芯片或单片机本身中,而不是存贮于磁盘等载体中。
  4. 嵌入式系统本身不具备自举开发能力,即设计完成以后用户通常不能对其中的程序功能进行修改,必须有一套开发工具和环境才能进行开发。

嵌入式处理器:

  1. 嵌入式微控制器( Microcontroller Unit,MCU)
  2. 嵌入式微处理器( Microprocessor Unit,MPU)
  3. 嵌入式DSP( Digital Signal Processor )处理器
  4. 嵌入式片上系统( System on Chip,SoC)

嵌入式处理器选择:够用原则、成本原则、参数原则(各项参数指标)、成熟度

嵌入式系统软件组成:


嵌入式系统软件主要开发工作:

嵌入式系统硬件开发工作:最小系统设计、外部接口设计、电源设计


2. OS

HelloWorld 程序的执行过程:

  1. 用户告诉操作系统执行hello程序
  2. 操作系统找到该程序,检查其类型
  3. 检查程序首部,找出正文和数据的地址
  4. 文件系统找到相应磁盘块
  5. 父进程需要创建一个新的子进程,执行hello程序
  6. 操作系统需要将执行文件映射到进程结构
  7. 操作系统设置CPU上下文环境,并跳到程序开始处
  8. 程序的第一条指令执行,失败,缺页中断发生
  9. 操作系统分配一页内存,并将代码从磁盘读入,继续执行
  10. 更多的缺页中断,读入更多的页面
  11. 程序执行系统调用,在文件描述符中写一字符串
  12. 操作系统找到字符串被送往的设备
  13. 设备是一个伪终端,由一个进程控制
  14. 操作系统将字符串送给该进程
  15. 该进程告诉窗口系统它要显示字符串
  16. 窗口系统确定这是一个合法的操作,然后将字符串转换成像素
  17. 窗口系统将像素写入存储映像区
  18. 视频硬件将像素表示转换成一组模拟信号控制显示器(重画屏幕)
  19. 显示器发射电子束
  20. 你在屏幕上看到hello world

计算机系统:

操作系统本身的安全可靠程度,决定了整个计算机系统的安全性和可靠性。是软件子系统的核心。

操作系统的主要任务:

  • 组织和管理计算机系统中的硬件及软件资源
  • 向用户提供各种服务功能

操作系统的主要功能:进程管理、存储管理、文件管理、设备管理

系统资源:

  • 硬件资源:CPU,内存,外部设备(I/O设备,外存,时钟,网络接口等)
  • 软件资源:硬盘上的文件,信息

嵌入式操作系统功能:

  1. 用户程序和系统硬件之间的桥梁,使用户专注于顶层程序;
  2. 任务管理、任务同步与通信、内存管理、定时器管理、中断管理、文件管理、外设管理等

嵌入式操作系统的特点:(分析思路:硬件资源受限、硬件多样化、扩展需求、性能需求)

  1. 良好的硬件适应性和移值性
  2. 小巧
  3. 模块化设计、可安装和卸载,可裁剪
  4. 固化代码(嵌入式操作系统+应用软件,固化到ROM中)
  5. 高可靠性和稳定性
  6. 统一的接口
  7. 实时功能

常见嵌入式实时操作系统:RTlinux及其他嵌入式实时Linux、uC/OS II、wind river systems公司的Vxworks、QNX software systems公司的QNX、pSOS、OS/9、VRTX、eCOS

常见嵌入式非实时操作系统:Microsoft公司的windows CE、Embedded windows xp、Palm公司的Palm OS、symbian公司的EPOC、一些嵌入式linux系统

名称 特点 实时性 优缺点
uC/OS II 可剥夺实时多任务内核 实时 代码尺寸小,易学易移植;但功能不全
Windows Embedded 家族 能提供与pc 机类似的图形界面和主要的应用程序 非实时
VxWorks 基于微内核,可裁剪可配置 实时 为追求系统的实时性而设计的,并不是以通用OS为设计目标;适用于硬实时系统
嵌入式Linux Y/N 支持多种CPU、开放源代码(Open source)、强大的网络功能、可移植性、使用GNU tools

嵌入式操作系统选型原则:市场进入时间、可移植性、可利用资源、系统定制能力、成本、中文内核支持

运行时,嵌入式linux系统包含以下软件组件:启动装载程序、内核(内核和驱动)、根文件系统、应用程序。以上组件需交叉编译(GCC)

建立目标板linux系统有四个重要的步骤:决定系统组件、配置及建立内核、建立根文件系统、设置引导软件与配置


3. GNU开发工具链介绍

GNU Tools:

名字 标签 内容
GCC GNU 编译器集
Binutils 辅助 GCC 的主要软件 二进制工具程序集
Gdb 调试器
make 软件工程工具
diff, patch 补丁工具
CVS 版本控制系统

一般情况下,c程序的编译过程为

  1. 预处理。对于一个包含了多个头文件的源文件,只要用头文件的内容替换掉源文件中的对应的include语句,就可以得到预处理后的源文件。
  2. 编译成汇编代码
  3. 汇编成目标代码
  4. 链接

makefile主要定义了

  1. 依赖关系 即有关哪些文件的最新版本是依赖于哪些别的文件产生或者组成的
  2. 需要用什么命令来产生目标文件的最新版本
  3. 以及一些其他的功能

makefile的工作流程?


5. Boot Loader

引导加载程序是系统加电后运行的第一段软件代码。包括固化在固件(firmware)中的 boot 代码(可选)和 Boot Loader 两大部分。

在嵌入式系统中,Boot Loader 的作用为:

  1. 初始化硬件设备
  2. 建立内存空间的映射图
  3. 将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境
  4. 加载操作系统内核映象到RAM中,并将系统的控制权传递给它(内核镜像、根文件系统镜像)

Boot Loader 依赖于CPU体系结构和具体的嵌入式板级设置配置

一个同时装有 Boot Loader、内核的启动参数、内核映像和根文件系统映像的固态存储设备的典型空间分配结构图:

大多数 Boot Loader 包含两种不同的操作模式:启动加载(Boot loading)模式和下载(Downloading)模式

  • 启动加载模式也称为自主(Autonomous)模式,Boot Loader 从目标机上的某个固态存储设备上将操作系统加载到 RAM 中运行,整个过程并没有用户(开发人员)的介入
  • 下载模式:目标机的 Boot Loader 通过串口或网络等通信手段从主机(Host)下载文件(比如内核映像和根文件系统映像,Host –> target ram –> target FLASH)。该模式的使用时机:通常在第一次安装内核与根文件系统时被使用,也用于此后的系统更新。

BootLoader 与主机之间进行文件传输所用的通信设备及协议:串口(xmodem/ymodem/zmodem协议)、以太网(TFTP协议)

大多数 Boot Loader 都分为 stage1 和 stage2 两大部分

  • Stage1 直接运行在固态存储设备上,依赖于 CPU 体系结构,如设备初始化代码,通常用汇编语言实现(start.s),短小精悍,包括以下步骤:
    • 硬件设备初始化(屏蔽所有的中断、设置 CPU 的速度和时钟频率、RAM 初始化、初始化 LED、关闭 CPU 内部指令/数据 cache)
    • 为加载 Boot Loader的stage2准备RAM空间(空间大小:stage2 可执行映象的大小+堆栈空间。此外,最好对齐到memory page大小(通常是 4KB)的整数倍)必须进行有效性测试
    • 拷贝 Boot Loader的stage2到RAM 空间中
    • 设置好堆栈,sp=(stage2_end-4)
    • 跳转到 stage2 的 C 入口点
  • Stage2 通常用C语言,可以实现复杂功能,代码具有较好的可读性和可移植性
    • 初始化本阶段要使用到的硬件设备
    • 检测系统内存映射(memory map)
    • 将 kernel 映像和根文件系统映像从 flash 上读到 RAM 空间中
    • 为内核设置启动参数
    • 调用内核

对于ARMLinux系统,在跳转之前必须满足:

CPU寄存器的设置:

  • R0=0;
  • R1=机器类型 ID;
  • R2=传递给内核的启动参数起始地址;

CPU模式:

  • 必须禁止中断(IRQs和FIQs);
  • CPU必须处于SVC模式;

Cache和 MMU的设置:

  • MMU必须关闭;
  • 指令 Cache可以打开也可以关闭;
  • 数据 Cache必须关闭;

6. U-Boot分析

在U-BOOT中

  • 主目录中的Makefile是对整个工程的编译链接规则进行描述。其中有板子配置的定义
  • 子目录中的Makfile主要是编译一些源文件并进行归档,生成一些静态库。
    • config.mk定义了主目录和子目录makefile通用的变量。
    • Mkconfig 是个脚本文件,负责对主目录中makefile进行配置的文件。创建一些符号链接,并在include目录下创建了两个文件:config.mk和 config.h。

U –Boot移植的一般步骤:

  • 检查U –Boot工程是否支持目标平台
  • 分析目标平台类似目录结构
  • 分析目标平台代码
  • 建立新的开发平台目录
  • 对照手册修改平台差异部分代码
  • 调试代码

配置开发板:$ make fs2410_config,编译U-Boot:make

U_Boot中,每一个命令定义一个cmd_tbl_t结构体

在U_Boot中添加新命令????


7. 构建嵌入式 Linux 系统

(1)在嵌入式Linux系统开发中,存在3种主机/目标机开发体系结构

  • 连接式:目标板和主机通过一个物理线路(如串行线或者以太网连接)永久的连接在一起
    • 好处: 目标代码的传送无需 物理存储设备参与, 只需要上述连接就足 够了


第二种以远程组件来简化目标板的开发工作:通过TFTP下载内核;此外,根文件系统还 可以通过NFS安装, 而不必在目标板中使 用存储介质。

  • 使用可移动存储设备:主机和目标板之间没有实际的连接。 先由主机将数据写入存储设备,然后将存储设备转接到目标板,并使用该存储设备引导目标板

  • 独立开发式

(2)调试方式:串行线(速度是硬伤)、网络接口、特殊调试硬件(JTAG)

  • 无法使用网络连接对Linux内核进行调试。因为网络协议栈本身在Linux内核里。相对而言,内核的调试通常可以通过串行连接来进行

(3)嵌入式Linux系统的一般架构

使用内核的目的是希望以一致的方式管理硬件,以及为用户软件提供高层抽象层。 内核大致可以分成两个部分:底层接口层和高层抽象层.

  • 通常底层部分会处理CPU特有的操作、架构特有的内存操作以及设备的基本I/O

Linux内核至少需要一个具有合适结构的根文件系统。Linux内核会从中加载第一个应用程序、加载模块并为进程提供工作目录

(4) 系统启动过程

在系统启动过程里,有3个主要软件组件参与其中:引导加载程序、内核、Init进程

  • 引导加载程序在完成底层硬件初始化工作后会接着跳到内核的启动程序代码执行
  • 内核一开始的启动程序代码会因架构不同而有很大的差异,而且在为C程序代码设置合适的执行环境之前,它会先为自己进行初始化工作。完成以上工作后,内核会跳到与架构无关的start_kernel函数执行,此函数会初始化高层内核功能,安装根文件系统,以及启动init进程
  • 启动各种应用程序(根据相关启动脚本设置)。Init 进程首先进行一系列的硬件初始化,并挂载根文件系统。最后 init 进程会执行用户传递过来的“init=”启动参数执行用户指定的命令,或者执行几个进程之一,由内核态变为用户态
    • 标准的system V 初始化
    • BusyBox初始化。主要执行下列任务:
      • 初始化init的信号处理函数
      • 初始化console控制台
      • 解释/etc/inittab文件
      • 运行系统初始化脚本,BusyBox缺省使用 /etc/init.d/rcS
      • 运行所有inittab的阻塞式命令(该类命令会导致init暂停)
      • 运行所有inittab中的一次性执行命令
      • 完成上述任务之后,init就进入一个死循环,在这个死循环中执行下列任务:1、运行所有必须再生的命令;2、运行所有必须被请求才能响应的命令

(5)引导配置的类型

  • 固态存储媒体:最初的引导加载程序、配置参数、内核、根文件系统
    • 嵌入式Linux系统在开发的不同阶段可能会使用不同的引导配置,但大部分在开发完成后使用固态存储媒体
  • 磁盘:内核和根文件系统位于磁盘上
    • 可以用于嵌入式系统的开发阶段
  • 网络:网络引导配置方式中,存在两种情况:
    • 内核位于固态存储设备上或磁盘上,需要通过NFS安装根文件系统
    • 只有内核加载程序位于目标板的存储设备上,需要通过TFTP下载内核和根文件系统(或NFS)
    • 往往用于开发初期

Linux内核从配置到安装大致有如下步骤:

  • 清理:make mrproper
  • 配置:make config/menuconfig/xconfig
  • 建立依赖关系:make dep
  • 编译:make或make zImage
  • 安装:make install

8. 根文件系统的建立

根文件系统内容包括:

  • 链接库:glibc、uClibc
  • 内核模块
  • 设备文件
  • 系统应用程序
  • 系统初始化文件
  • 内核映像(文件系统上是否有内核映像与引导加载程序有关)

BusyBox:它把许多常见应用程序缩微版本组合到一个单独的小巧的可执行程序中,一般含有比较少的选项,更小的体积,不过所包含的这些选项能够提供用户所需要的大部分功能。(小型化、模块化,易定制)

定制的应用程序可以放在两个目录:/bin 或 /project。第二种情况下,通常需要设置PATH环境变量,以便能够找到可执行文件:修改.bashrc,添加export。

描绘一个嵌入式文件系统的特性通常包括:

  • 可被写入:这个文件系统可被写入么?
  • 具有永久性:重引导后,这个文件系统可以保存修改过的内容么?
  • 具有断电可靠性:经变动的文件系统可以在断电之后恢复过来么?
  • 经过压缩:经安装的文件系统,其内容经过压缩么?
  • 存在RAM中:文件系统的内容在被安装之前会先从存储设备取出并放到RAM中么?

9. SkyEye 简介

SkyEye:嵌入式开发硬件模拟器

SkyEye的目标: 在通用的Linux和Windows平台上实现一个纯软件集成开发环境,模拟多种主流的嵌入式计算机系统。

SkyEye模拟硬件:CPU内核、存储器、存储器管理单元、缓存单元、串口、网络芯片、时钟等

  • MMU ( Memory Management Unit),存储器管理单元, 是用来管理虚拟内存系统的硬件。MMU的两个主要功能是: 1)将虚地址转换成物理地址; 2)控制存储器的存取权限。

MMU、CACHE、write/read buffer一般是高性能CPU的重要组成部分

SkyEye中运行 hello 程序:

将 hello 复制到 linux 的根文件系统映像 initrd.img 中

  • 挂载根文件系统映像
  1. mkdir root
  2. sudo mount -o loop initrd.img root
  • 将 hello 拷贝到根文件系统中
    sudo cp hello root/bin
  • 卸载根文件系统映像
    `sudo umount root`
    
  • 使用 skyeye 启动 linux,运行 hello
    skyeye -c skyeye.conf -e vmlinux
  • 进入 armlinux 之后,进入 bin 目录,运行 hello

中断和异常

IO方式:轮询、中断、DMA等

  • 中断——异步的: 由硬件随机产生,在程序执行的任何时候可能出现
    • 可屏蔽中断和非屏蔽中断(硬件故障或掉电引起的中断)
  • 异常——同步的: 在(特殊的或出错的)指令执行时由CPU控制单元产生
    • 处理器探测异常(探测到反常条件,如溢出)、编程异常(软中断,如系统调用)
  • 我们用“中断信号”来通称这两种类型的中断。异常处理程序可以被中断程序打断

CPU处理中断:

  1. 在进程的内核态堆栈保存程序计数器的当前值(即eip和cs寄存器)以便处理完中断的时候能正确返回到中断点,
  2. 把与中断信号相关的一个地址放入进程序计数器,从而进入中断的处理

当一个进程在执行时,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容被称为该进程的上下文。当内核需要切换到另一个进程时,它需要保存当前进程的上下文。

中断上下文:中断或异常处理程序执行的代码不是一个进程,它是一个内核控制路径,代表了中断发生时正在运行的进程执行。作为一个进程的内核控制路径,中断处理程序比一个进程要“轻”(中断上下文只包含了很有限的几个寄存器,建立和终止这个上下文所需要的时间很少)

发生异常时ARM微处理器会执行以下几步操作:

  1. 将下一条指令的地址存入相应连接寄存器LR,以便程序在处理异常返回时能从正确的位置重新开始执行。
  2. 将CPSR复制到相应的SPSR中。
  3. 根据异常类型,强制设置CPSR的运行模式位。
  4. 强制PC从相关的异常向量地址取下一条指令执行,从而跳转到相应的异常处理程序处。

这些工作是由ARM 硬件内核完成的,不需要用户程序参与。

异常返回:

  • 将连接寄存器LR的值减去相应的偏移量后送到PC中。
  • 将SPSR复制回CPSR中。
  • 若在进入异常处理时设置了中断禁止位,要在此清除。

这些工作必须由用户在中断处理函数中实现。

中断的必要条件:

  • 要让程序状态寄存器相应位支持
  • 要建立合适的中断异常向量表
  • 要有服务里程//???

发生中断时系统会跳到 vector_irq + stubs_offset处运行,这个位置实际上就是中断入口函数

注册和卸载中断:

  • 用户驱动程序通过request_irq函数向内核注册中断处理函数,request_irq函数根据中断号找到irq_desc数组项,然后在它的action链表添加一个表项。并在相应的action链表注册自己的中断服务例程。
  • 卸载中断处理函数这通过free_irq函数来实现,它与request_irq一样,也是在kernel/irq/mangage.c中

中断处理过程:

  • 根据需要进入核心态
  • 保存上下文
  • 调用asm_do_IRQ,根据中断号,找到中断处理函数处理
  • 恢复上下文
  • 根据需要返回用户态

14. 进程管理

进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。进程由进程控制块PCB、程序段、数据段三部分组成。

进程的组成:正文段(text)、用户数据段(user segment)和系统数据段(system segment)

  • 正文段中存放着进程要执行的指令代码,具有只读的属性,
  • 用户数据段是进程在运行过程中处理数据的集合,它们是进程直接进行操作的所有数据, 以及进程使用的进程堆栈
  • 系统数据段存放着进程的控制信息。其中包括进程控制块PCB(task_struct,任务结构体/进程描述符)。
    • 进程的任务结构体是进程存在的唯一标志
    • 进程描述符放在动态内存中而且和内核态的进程栈放在一个独立的8KB的内存区中
    • Linux为每个进程分配一个8KB大小的内存区域,用于存放该进程两个不同的数据结构:Thread_info、进程的内核堆栈

进程的特征:结构特征、动态性、并发行、独立性、异步性

  • 进程实体是一个能独立运行、独立分配资源和独立接受调度的基本单位,而程序则不是。
  • 结构特征:进程控制块(PCB)+程序段+相关的数据段=进程实体

存放在磁盘上的可执行文件的代码和数据的集合称为可执行映象(Executable Image)。当一个可执行映像装入系统中运行时,它就形成了一个进程。

用户态、核心态;进程空间、系统空间;进程上下文、系统上下文;用户栈、内核栈

  • 内核进程只有内核栈,没有用户栈。当进程从用户空间陷入到内核空间时,首先,操作系统在内核栈中记录用户栈的当前位置,然后将栈寄存器指向内核栈;内核空间的程序执行完毕后,操作系统根据内核栈中记录的用户栈位置,重新将栈寄存器指向用户栈。

//区别还是没搞清???

  • 进程上下文:把系统提供给进程的处于动态变化的运行环境总和称为进程上下文。系统中的每一个进程都有它自己的上下文。进程因时间片用完或因等待某个事件而阻塞时,进程调度需要把CPU的使用权从当前进程交给另一个进程,这个过程称为进程切换(procdss switching)。进程的切换又称为上下文切换(context switching).
  • 系统上下文:在系统内核为用户进程服务,例如进程执行一个系统调用时,进程的执行状态要从用户态转换为核心态。但是,此时内核的运行仍是进程的一部分,所以说这时内核是运行在进程上下文中。系统在完成自身任务时的运行环境称为系统上下文(system context)。 内核在系统上下文中执行时不会阻塞。

进程的状态:运行态、可运行态、等待态(睡眠态)、暂停态、僵死态

  • 可运行态:万事俱备只欠CPU
  • 等待态:等待某个事件或资源,可中断(由signal解除等待态)和不可中断(wake_up())
  • 暂停态:进程需要接受某种特殊处理而暂时停止运行所处的状态。例如,正在接受调试的进程就处于这种状态。
  • 僵死态:进程的运行已经结束,但它的任务结构体仍在系统中。

Task_struct结构的描述:

  • 进程标识
  • 进程状态(State)
  • 进程调度信息和策略
  • 进程通信有关的信息(IPC)
  • 进程链接信息(Links)
  • 时间和定时器信息(Times and Timers)
  • 文件系统信息(Files System)
  • 处理器相关的上下文信息

0号进程是所有进程的父进程, 0号创建init进程,init完成相关初始化,执行相关程序。

  • 进程标志:PID,内核通过管理一个pidmap-array位图来表示当前已分配的pid号和闲置的pid号
  • 为了对给定类型的进程(比如所有在可运行状态下的进程)进行有效的搜索,内核维护了几个进程链表;可运行状态的双向循环链表,也叫运行队列.在多处理器系统中,每个CPU都有它自己的运行队列,即自己的进程链表集。所有这些链表都有一个单独的prio_array_t数据结构来实现。
  • 进程等待由需要等待的进程自己进行(调用)
  • 本质上说进程切换由两步组成:
    • 切换页全局目录以安装一个新的地址空间;
    • 切换内核态堆栈和硬件上下文。硬件上下文提供了内核执行新进程所需要的所有信息,包括cpu寄存器

进程切换、任务切换、上下文切换

  • Fork,vfork和clone系统调用创建新进程
  • exec系统调用执行一个新程序
  • exit系统调用终止进程

写时拷贝 (copy on write):子进程在创建后共享父进程的虚存内存空间,写时复制技术允许父子进程能读相同的物理页。子进程在创建后执行的是父进程的程序代码。且pc指向同一位置

父进程执行fork()返值是子进程的PID值,子进程执行fork()的返值是0。

进程创建过程:

  • 为新进程分配任务结构体内存空间
  • 把父进程任务结构体拷贝到子进程任务结构体
  • 为新进程在其虚拟内存建立内核堆栈
  • 对子进程任务结构体中部分进行初始化设置
  • 把父进程有关信息拷贝给子进程,建立共享关系
  • 把子进程的counter设为父进程counter值的一半
  • 把子进程加入到可运行队列中
  • 结束do_fork()函数返回PID值

进程的撤销过程分为

  • 进程终止:释放进程占有的大部分资源
  • 进程删除:彻底删除进程的所有数据结构

衡量进程调度性能的指标有:周转时间、响应时间、CPU-I/O执行期。

进程调度算法:

  • 批处理系统:先入先出、最短CPU运行期优先调度算法(SCBF–Shortest CPU Burst First) 、最高优先权(FPF)优先调度算法(静态优先级、动态优先级)
  • 分时系统:时间片轮转法,系统将所有就绪进程按FIFO规则排队,按一定的时间间隔把处理机分配给队列 中的进程。这样,就绪队列中所有进程均可获得一个时间片的处理机而运行。

进程调度的功能:

  1. 记录系统中所有进程的执行情况
  2. 选择占有处理机的进程
  3. 进行进程上下文切换

调度时机:

  1. 进程状态发生变化时
  2. 当前进程时间片用完时
  3. 进程从系统调用返回到用户态时
  4. 中断处理后,进程返回到用户态时

Linux的进程调度是基于优先级的调度。Linux的进程分为普通进程和实时进程,在基于优先级的算法下实时进程的优先级高于普通进程。普通进程:时间片轮转(OTHER);实时进程:FIFO、时间片轮转(Round Robin)

Linux采取了加权的方法来保证实时进程优先于普通进程 普通进程的权值就是它的counter的值,而实时进程的权值是它的rt_priority的值加1000。

实时进程运行后会一直占用CPU资源,只有下列情况在会被另一进程替代:

  • 进程被更高优先级的实时进程取代
  • 进程执行了阻塞操作并进入睡眠
  • 进程停止或被杀死
  • 进程自愿放弃cpu(可以通过调用系统调用sched_yield()放弃CPU)
  • 进程是基于时间片轮转的实时进程,且用完了时间片

权值的计算goodness():
综合policy,priority,rt_priority和counter四项计算。
步骤:

  1. 区分实时进程和普通进程
  • 实时进程的权值:rt_priority
  • 普通进程的权值:与counter与Nice有关
  1. 权值:
  • 普通进程:weight = p->counter
  • 实时进程:weight = 1000 + rt_priority

进程空间:线性区0-4GB,不一定是连续的

每个进程有自己的线性空间,自己的内存块;程序执行时,这些代码被加载到不同的线性空间的页表里,即是隔离的。

如何实现内存分配?线性区先加载到进程地址空间中,其可以访问的有效的空间大小为0-4GB;然后改变当前进程的页表,使其可以覆盖这个线性区,即分配成功。若没有连续的线性区可以分配,则分配出错。

缺页异常

进程栈:

进程状态转换

*fs:文件查找

thread_info中有一个进程控制块指针。

switch 流程:1.保留当前上下文 2.CPU指向下一个进程 3.。。。


15. time arm

Linux内核提供两种主要的定时测量

  • 获得当前的时间和日期,把它们返回给用户程序或由内核本身把当前时间作为文件和网络包的时间戳
  • 维持定时器,这种机制告诉内核或用户程序某一时间间隔已过去了(参见软定时器和延迟函数)

  • 系统时钟sys_timer(arm的),时钟中断发生源

  • Jiffies变量:计数器,用来记录系统启动以来产生的节拍总数。每次时钟中断发生加一次。
  • Xtime变量:记录当前的时间和日期,每个节拍更新一次。

软定时器:设定期望时间,允许在将来的某个时刻,函数在给定的时间间隔用完时被调用。超时(time-out)表示与定时器相关的时间间隔已经用完的那个时刻。

  • 动态定时器(内核)
  • 间隔定时器(可以用户)

timer_jiffies 到期时间

以单核CPU为例,软定时器将timer_jiffies与当前值比较,若小于timer_jiffies,就说明该CPU上有函数到期;接下来,软定时器处理函数(do_softirq调用的函数)找到该函数,并运行。(软定时器只是软中断的一种)

Run_time_softirq被用作定时器软中断处理函数

内核专用的软中断处理函数:do_softirq

中断退出时,do_softirq会引起run_timer_softirq函数的执行


16. 系统调用

应用编程接口(application program interface, API)和系统调用是不同的:API只是一个函数定义,系统调用通过软中断向内核发出一个明确的请求

当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数。

arm软中断指令SWI:SWI指令用于产生软件中断,从而实现从用户模式变换到管理模式,CPSR保存到管理模式的SPSR,程序执行转移到SWI向量。在其他模式下也可使用SWI指令,处理器同样地切换到管理模式。

系统调用表中保存了系统调用编号和其对应的服务例程地址。


驱动程序

驱动是应用软件和硬件的桥梁,它使得应用软件只需要调用系统软件的应用编程接口(API)就可让硬件去完成要求的工作。驱动针对的对象是存储器和外设(包括CPU内部集成的存储器和外设)。驱动把对设备的访问变成对文件的访问。

有操作系统的驱动程序 和 无操作系统的驱动程序

  • 相同点:都是对硬件时序和寄存器的操作
  • 不同点:有OS的 加了封装,面向内核

有操作系统的优点:实现任务并发、内存管理

Linux设备:字符设备、块设备、网络设备

内核模块:

  • 内核模块在内核空间运行,内核模块编程是在内核空间编程。
  • 内核模块可以引用内核空间导出的全局符号,因此内核模块编程与内核的版本密切相关。
  • 内核模块只能调用和使用内核提供的函数,不能使用相关的应用程序库函数。
  1. 模块的编译:编译、链接后生成的内核模块后缀为.ko。
  2. 模块的加载:编译好模块后用户可以利用超级用户的身份可以将内核模块加载到内核中。常见的实用程序insmod、rmmod、lsmod、modprobe

驱动程序向外设发出操作指令后,驱动程序采用两种方式等待操作完成:轮询模式(polling mode)、中断模式(interrupt mode)

中断程序在中断期间运行,有如下限制:

  • 不能向用户空间发送或接收数据
  • 不能执行有睡眠操作的函数
  • 不能调用调度函数

中断处理程序的上半部和下半部

  • 上半部分会立即被内核执行
  • 下半部分会被推迟执行:下半部的执行并不需要指明一个确切时间,只要把这些任务推迟,让它们在系统不太繁忙并且中断恢复后执行就可以了。

中断处理程序的上半部和下半部的划分:

  • 如果一个任务对时间非常敏感,将其放在上半部执行
  • 如果一个任务和硬件相关,将其放在上半部中执行
  • 如果一个任务要保证不被其他中断(特别是相同的中断)打断,将其放在上半部中执行
  • 其他所有任务,考虑放置在下半部执行

中断处理程序下半部的实现机制:

  • 软中断
    • 在softirq_vec数组中每一项对应一个软中断,系统中最多可以有32个软中断。触发软中断的函数为raise_softirq
  • Tasklet:基于软中断来实现,但比软中断接口简单,同步要求较低;软中断保留给执行频率及时间要求高的下半部使用。
    • Tasklet是I/O驱动程序中实现可延迟函数的首选方法
  • 工作队列

工作队列和tasklet这两种下半部机制的主要区别在于:

  • Tasklet在软中断的上下文中运行,所有的代码必须是原子的,不能睡眠、不能使用信号量或其它产生阻塞的函数
  • 工作队列在一个内核线程上下文运行,并且可以在延迟一段确定的时间后才执行;有更多的灵活性,它可以使用信号量等能够睡眠的函数。工作队列通过工作者线程(worker thread)这种内核线程来执行

Linux系统使用设备号来标识设备文件。设备号分为主设备号和从设备号。

  • 主设备号是同一类设备的标识,对应着一个驱动程序
  • 从设备号是在驱动程序中来指示某个物理设备的实例,从设备号使得不同的物理实例可以使用同一个驱动程序

设备文件包括:名字、类型(字符/块);设备号(主设备号:次设备号)。

注册一个设备驱动程序意味着把它与对应的设备文件连接起来

大部分的基础性的驱动操作包括 3 个重要的内核数据结构, 称为 file_operations, file, 和 inode.

  • file_operations文件操作
  • File打开的文件
  • inode由内核在内部用来表示文件

写驱动程序的步骤:

  1. 定义cdev
  2. 定义并实现file operation相关的函数
  3. 绑定 cdev与file operation
  4. 获取设备号,以设备号为索引,将cdev注册到内核中
  5. 根据注册cdev的设备号写一个设备文件

若设备号不合法或不存在,则动态生成一个设备号。

为什么访问设备文件时就会访问设备驱动程序?

普通的文件读写是怎么实现的?

  • 读文件控制块,创建系统打开表(系统打开表中包含file_operation),创建进程打开表,指针指向fd

open执行过程:

系统打开表在内存中,文件控制块在磁盘中;open查找文件控制块,然后将文件控制块的内容读入到系统打开表中;之后open看是否有空闲的fd存在,若有,则给空闲项赋值(指向刚刚创建的系统打开表),并返回该fd。

文件偏移量在系统打开表中。

VFS数据结构:超级块对象,索引节点对象,目录项对象,文件对象


内核驱动

Makefile由五个部分组成:

  • Makefile:根目录Makefile,它读取.config文件,并负责创建vmlinux(内核镜像)和modules(模块文件)。
  • .config:内核配置文件(一般由make menuconfig生成)。
  • arch/$(ARCH)/Makefile:目标处理器的Makefile。
  • scripts/Makefile.*:所有kbuild Makefile的规则,它们包含了定义/规则等。
  • kbuild Makefiles:每个子目录都有kbuild Makefile,它们负责生成built-in或模块化目标。

编译内核时Makefile的执行步骤包括两个过程:一是配置内核过程;二是编译生成内核目标文件的过程

运行编译指令make或make zImage会形成两种不同类型的内核:

  • vmlinux: 这是一个没有压缩ELF映像文件,含有符号表。
  • 合成内核压缩映像 ZImage:经过压缩的映像,该文件就是压缩后vmlinux 加上第二阶段相关boot引导程序组成。

ZImage的启动流程:

  • 假设所使用的引导装入程序版本为Redboot,当系统加电之后,引导装入程序即被调用并且开始加载操作系统
  • 当引导装入程序引导操作系统映像之后,引导装入程序就将系统的控制权交给了该映像的第二阶段引导装入程序中标签为start的head.o模块,完成内核解压
  • 运行严格意义上的内核的标签为start的head.o模块( arch/arm/kernel/head.s),之后进入start_kernel()函数开始执行

start_kernel()函数:

  • 调用 setup_arch()函数进行与体系结构相关的第一个初始化工作
  • 创建异常向量表和初始化中断处理函数;
  • 初始化系统核心进程调度器和时钟中断处理机制;
  • 初始化串口控制台(serial-console)
  • 创建和初始化系统 cache,为各种内存调用机制提供缓存,包括;动态内存分配,虚拟文件系统(VirtualFile System)及页缓存。
  • 初始化内存管理,检测内存大小及被内核占用的内存情况;
  • 初始化系统的进程间通信机制(IPC);

当以上所有的初始化工作结束后,start_kernel()函数会调用 rest_init()函数来进行最后的初始化,包括创建系统的第一个进程-init 进程来结束内核的启动。Init 进程首先进行一系列的硬件初始化,然后挂载根文件系统。最后 init 进程会执行用 户传递过来的“init=”启动参数执行用户指定的命令,或者执行几个进程之一:。


内核的根文件系统挂载

挂载过程:

  • 挂载虚拟的文件系统rootfs作为初始文件系统
  • 挂载一个真正的根文件系统替换rootfs

内核的initrd机制:initrd 是由 boot loader 初始化的内存盘。在 linux内核启动前, boot loader 会将存储介质中的 initrd 文件加载到内存,内核启动时会在访问真正的根文件系统前先访问该内存中的 initrd 文件系统。在 boot loader 配置了 initrd 的情况下,内核启动被分成了两个阶段,第一阶段先执行 initrd 文件系统中的“某个文件”,完成加载驱动模块等任务,第二阶段才会执行真正的根文件系统中的 /sbin/init 进程。

Initrd的格式:image-initrd、cpio-initrd



复习课:

00:00-03:30

Linux进程调度:

  • 基本思想(效率优先、任务公平、动态优先,对不同进程怎么处理;实时进程、交互进程、平均睡眠时间是什么)
  • 主要数据结构(进程控制块、进程队列里的数据结构)

04:30-

内核如何看到驱动程序?设备驱动怎么和文件绑定在一起。

13:40-

中断

访问文件fp

访问磁盘文件系统:根目录节点,根目录文件,创建根节点,文件控制块,(根文件系统挂载)gethub

读取文件:C库

系统调用、

上面是重点

根文件系统内容、根文件系统初始化


内核启动(小题)

交叉开发、交叉编译环境

嵌入式Linux主要开发工作

U_BOOT板移植、CPU移植;添加新内容

Linux移植

根文件系统挂载


程序下载到开发板上不能运行?缺库。怎么补全库?在主机上用ldd看文件系统的文件依赖,拷贝时拷贝依赖库。


30:00-

废话时间

积累1500个报错,并分析。

交叉编译环境的组成
交叉编译环境的主要工作
交叉编译环境搭建过程

u-boot

boot-loader如何编译、如何移植

内核怎么来的?如何移植?-> grub

文件系统(必要的设备文件、软连接、定制应用程序, i.e. minibox, opencv, QT、根文件系统初始化)

为什么bzbox会成为根文件系统的初始化目标