• 嵌入式知识总结:交叉编译、ARM汇编、驱动开发


    引言:嵌入式系统开始上课了,我要寄了!所以写下这篇文章作为我嵌入式系统开发和单片机开发的先导知识点

    参考书籍:

    • 《嵌入式Linux系统开发教程—贺丹丹》
    • 《ARM Linux内核源码剖析》
    • 《Linux驱动开发详解》
    • 《ARM体系结构与编程》

    工具链软件

    工具链由编译器、链接器、解释器和调试器组成,在x86的Linux主机上,交叉开发工具链除了能够编译生成在ARMMIPSPowerPC等硬件架构上运行的程序,还可以为x86平台上不同版本的Linux提供编译开发程序的功能。常用的工具链软件由:BinutilsGCCGlibcGdb

    构建工具链

    在裁剪用于嵌入式系统的Linux内核时,由嵌入式系统的存储大小有限,所以我们需要的链接工具页可根据嵌入式系统的特性进行制作,建立自己的交叉编译工具链,主要有三种方法

    • 分步编译和安装交叉编译工具链所需要的库和源代码,最终生成交叉编译工具链
    • 通过Crosstool脚本工具来实现一次编译,生成交叉编译工具链
    • 从网上直接下载已经制作好的交叉编译工具链

    分步构建交叉编译链

    选择BinutilsGCCGlibc等组件相互匹配的版本,选择与目标机系统的内核一致的内核,以免在目标机运行程序的时候产生冲突。

    在这里插入图片描述

    准备步骤如下

    • 准备工作:下载好所需要的软件包、准备好内核头文件、组织好目录。
    • 编译binutils:这个软件包的编译相对简单,一般容易实现。
    • 编译辅助gcc编译器:对gcc进行简单配置后,编译gcc,使其不依赖glibc,只对C语言支持,为后面glibc的编译做准备。
    • 编译glibc库:在这一步,首先对解压的内核头文件进行配置。在上一步的编译过程中,已经生成了arm-linux-gcc这个工具,利用这个工具去编译glibc库。
    • 重新编译完整的gcc:完整的gcc的编译需要glibc库的支持,在第一步的时候glibc还没有被编译,所以只能简单配置,生成辅助的gcc。而在这一步,glibc库已经编译并可以使用了,所以,就可以对gcc进行完整的编译了。
    • 编译gdb调试器:调试器与前面的那些软件包是相互独立的,所以放在最后编译。

    详细步骤

    新建目录

     4469  cd ~
     4470  ls
     4471  mkdir arm 
     4472  cd ./arm/
     4473  mkdir armlinux 
     4474  ls
     4475  cd ./armlinux/
     4476  ls
     4477  mkdir build-tools kernel tools 
     4478  ls
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • build-tools用来存放下载的binutils、gcc、glibc等源代码和用来编译这些源代码的目录。
    • kernel用来存放内核源代码。
    • tools用来存放编译好的交叉编译工具和库文件。

    建立环境变量

    建立环境变量主要是将其定义为经常使用的路径,这是Linux系统命令中的一大优点。(这里需要注意的是,用export声明的变量是临时变量)

    # Setting up environment variables
    
    export PRJROOT=/root/arm/armlinux
    export TARGET=arm-linux
    export PREFIX=$PRJROOT/tools
    export TARGET_PREFIX=$PRJROOT/$TARGET
    export PATH=$PREFIX/bin:$PATH
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    编译、安装Binutils

    Binutils是GNU工具之一,它包括连接器、汇编器和其他用于目标文件和档案的工具,是二进制代码的处理维护工具,安装Binutils工具包含的程序有addr2line、 ar、 as、cl lfilt、 gprof、ld、nm、objcopy、objdump、ranlib、readelf、size、strings、strip、libiberty、libbfd和libopcodes

    Binutils工具安装依赖于Bash、Coreutils、Diffutils、GCC、Gettext、Glibc、Grep、Make,Perl、Sed、Texinfo等工具。

    解压Binutils

     4494  cd $PRJROOT/build-tools
     4497  tar -jxvf binutils-2.16.1.tar.bz2
     4498  ls
    
    • 1
    • 2
    • 3

    配置Binutils工具,建立一个新的目录来存放配置及编译文件,这样可以使源文件和编译文件独立打开

     4500  mkdir build-binutils 
     4501  cd ./build-binutils/
     4502  ../binutils-2.16.1/configure --target=$TARGET --prefix=$PREFIX
    
    • 1
    • 2
    • 3

    其中选项–target的意思是指定生成的是arm-linux的工具,–prefix指出可执行文件安装的位置,执行上述操作会出现很多check信息,最后产生Makefile文件。

     4505  make
     4506  make install
    
    • 1
    • 2

    获得内核头文件

    编译器需要通过系统内核的头文件来获得目标平台所支持的系统函数,调用所需要的信息,对于Linux内核,最好的方法是下载一个合适的内核,然后复制获得头文件。

     4523  cd $PRJROOT/kernel/
     4529  tar -zxvf linux-2.6.15.tar.gz 
     4530  cd ./linux-2.6.15/
     # 配置编译内核使其生成正确的头文件
     4532  make ARCH=arm CROSS_COMPILE=arm-linux-menuconfig 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    配置完退出并保存,检查内核目录中的include/linux/version.h和include/ linux/autoconf.h文件是不是生成了,这是编译glibc时要用到的,如果这两个文件存在,说明生成了正确的头文件。

    复制头文件到交叉编译工具链的目录,首先需要在/home/arm/armlinux/tools/arm-linux目录下建立工具的头文件目录include,然后复制内核头文件到此目录下,具体操作如下。

     4533 mkdir -p $TARGET_PREFIX/include
     4534 cp -r $PRJROOT/kernel/linux-2.6.15/include/linux $TARGET_PREFIX/include
     4535 cp -r $PRJROOT/kernel/linux-2.6.15/include/asm-arm $TARGET_PREFIX/include/asm
    
    • 1
    • 2
    • 3

    编译安装boot-trap gcc

    这一步主要是建立arm-linux-gcc工具,注意这个gcc没有glibc库的支持,所以只能用于编译内核、BootLoader等不需要C库支持的程序,后面创建C库也要用到这个编译器,所以创建它主要是为创建C库做准备。

     4536 cd $PRJROOT/build-tools
     4536 tar-zxvf gcc-4.1.0.tar.gz
     4537 mkdir build-gcc
     4538 cd gcc-4.1.0
    
    • 1
    • 2
    • 3
    • 4

    由于是第一次安装ARM交叉编译工具,没有支持glibc库的头文件,所以需要修改t-linux文件。用vi或者gedit打开文件。

     4539 vi gcc/config/arm/t-linux
    
    • 1

    在gcc/config/arm/t-linux文件中给变量TARGET_LIBGCC2_CFLAGS增加操作参数选项-Dinhibit_libc和-D_gthr_posix_h来屏蔽使用头文件,否则一般默认会使用/usr/include头文件。将TARGET_LIBGCC2-CFLAGS=-fomit-frame-pointer -fPIC改为TARGET_LIBGCC2-CFLAGS=-fomit-frame-pointer -fPIC -Dinhibit_libc -D_gthr_posix_h

     4540 cd build-gcc
     # --enable-languages=c 只支持C语言
     # --disable-threads 去掉thread功能
     # --disable-shared 只进行静态编译,不支持共享库编译
     4541 ../build-gcc/configure --target=$TARGET --prefix=$PREFIX --enable-languages=c --disable-threads --disable-shared
    
    • 1
    • 2
    • 3
    • 4
    • 5
     4542 make
     4543 make install
    
    • 1
    • 2

    建立glibc库

    glibc是GUN C库,它是编译Linux系统程序很重要的组成部分。

     4544 cd $PRJROOT/build-tools
     4545 tar-zxvf glibc-2.3.2.tar.gz
     4546 tar -zxvf glibc-linuxthreads-2.3.2.tar.gz --directory=glibc-2.3.2
    
    • 1
    • 2
    • 3

    进行编译配置

     4547 cd $PRJROOT/build-tools
     4548 mkdir build-glibc
     4549 cd build-glibc
     4550 CC=arm-linux-gcc ../glibc-2.3.2/configure --host=$TARGET --prefix="/usr" --enable-add-ons --with-headers=$TARGET_PREFIX/include
    
    • 1
    • 2
    • 3
    • 4

    编译和安装

     4551 make 
     4552 make install
    
    • 1
    • 2

    编译安装完整的gcc

     4553 cd $PRJROOT/build-tools/gcc-4.1.0
     4554 ./configure --target=arm-linux --enable-languages=c, c++ --prefix=$PREFIX
     4555 make
     4556 make install
    
    • 1
    • 2
    • 3
    • 4

    安装完成后会发现在SPREFIX/bin目录下又多了arm-linux-gl+、arm-linux-c++t等文件

     4557 ls $PREFIX/bin
     arm-linux-addr2line arm-linux-g77 arm-linux-gnatbind arm-linux-ranlib arm-linux-ar
     arm-linux-gcc arm-linux-jcf-dump arm-linux-readelf arm-linux-as arm-linux-gcc-4.1.0
     arm-linux-jv-scan arm-linux-size  arm-linux-c++ arm-linux-gccbug arm-linux-ld
     arm-linux-strings arm-linux-c++filt arm-linux-gcj arm-linux-nm arm-linux-strip
     arm-linux-cpp arm-linux-gcjh arm-linux-objcopy grepjar arm-linux-g++
     arm-linux-gcov rm-linux-objdump jar
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    制作交叉调试器

    交叉调试器不是工具链的必须工具,但是它与工具链配套使用,gdb的调试能力和BUG修正也因为版本的不同而不同,建议尽量使用最新的版本,这样可以避免后面编译过程中出现BUG

     4558 tar -jxf gdb-6.5.tar.bz2
     4559 mkdir build-gdb
     4560 cd ./build-gdb
     4561 ../gdb-6.5/configure \
     > --target=arm-linux
     > --prefix=/home/arm/crosstool/toolchain
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    测试交叉编译工具链

    #include 
    
    int main() {
    	printf("Hello World!\n");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    通过以下命令进行编译,编译后生成名为hello的可执行文件,通过file命令可以查看文件的类型

     4562 arm-linux-gcc -o hello hello.c
     4563 file hello
     hello: ELF 32-bit LSB executable, ARM, version 1 (ARM), for GNU/Linux 2.4.3, 
     dynamically linked (uses shared libs), not stripped
    
    • 1
    • 2
    • 3
    • 4

    使用Crosstool工具构建交叉工具链

    Crosstool是由美国人Dan Kegel开发的一套可以自动编译不同匹配版本的gcc和glibc,并作测试的脚本程序。用Crosstool构建交叉工具链要比上述的分步编译容易得多,并且也方便许多。用Crosstool生成交叉编译工具遇到的问题比手动编译出的问题少,但是需要一定的Shell脚本知识来修改bash shell文件。对于仅仅为了工作需要构建交叉编译工具链的读者,建议使用此方法。

    解压

     4547  wget http://kegel.com/crosstool/crosstool-0.43.tar.gz 
     4548  tar -zxvf crosstool-0.43.tar.gz
    
    • 1
    • 2

    基本过程

    使用Crosstool构建交叉编译工具链的制作过程和上一节中分步构建过程的原理相似

    • 准备工作:下载好所需要的软件包、准备好内核头文件、组织好目录。
    • 建立脚本文件:修改针对ARM9架构的脚本文件。
    • 建立配置文件:主要用于定义配置文件、定义生成编译工具链的名称以及定义编译选项等
    • 执行脚本文件:执行建立好的脚本文件,编译交叉编译工具。
    • 添加环境变量:将生成的编译工具链接路径添加到上一节中介绍到的环境变量PATH上去

    建立脚本文件

    假设针对的开发板的arm9架构的

     4551  cd ./crosstool-0.43/
     4553  vi demo-arm9tdmi.sh
    
    • 1
    • 2

    查看脚本文件

    #!/bin/sh
    # This script has one line for each known working toolchain
    # for this architecture.  Uncomment the one you want.
    # Generated by generate-demo.pl from buildlogs/all.dats.txt
    
    set -ex
    TARBALLS_DIR=$HOME/downloads   
    RESULT_TOP=/opt/crosstool
    export TARBALLS_DIR RESULT_TOP
    GCC_LANGUAGES="c,c++"
    export GCC_LANGUAGES
    
    # Really, you should do the mkdir before running this,
    # and chown /opt/crosstool to yourself so you don't need to run as root.
    mkdir -p $RESULT_TOP
    
    #eval `cat arm9tdmi.dat gcc-3.2.3-glibc-2.2.5.dat` sh all.sh --notest
    #eval `cat arm9tdmi.dat gcc-3.2.3-glibc-2.3.2.dat` sh all.sh --notest
    #eval `cat arm9tdmi.dat gcc-3.2.3-glibc-2.3.2-tls.dat` sh all.sh --notest
    #eval `cat arm9tdmi.dat gcc-3.3.6-glibc-2.2.5.dat` sh all.sh --notest
    #eval `cat arm9tdmi.dat gcc-3.3.6-glibc-2.3.2.dat` sh all.sh --notest
    #eval `cat arm9tdmi.dat gcc-3.3.6-glibc-2.3.2-tls.dat` sh all.sh --notest
    #eval `cat arm9tdmi.dat gcc-3.4.5-glibc-2.2.5.dat` sh all.sh --notest
    #eval `cat arm9tdmi.dat gcc-3.4.5-glibc-2.3.2.dat` sh all.sh --notest
    #eval `cat arm9tdmi.dat gcc-3.4.5-glibc-2.3.2-tls.dat` sh all.sh --notest
    #eval `cat arm9tdmi.dat gcc-3.4.5-glibc-2.3.5.dat` sh all.sh --notest
    #eval `cat arm9tdmi.dat gcc-3.4.5-glibc-2.3.5-tls.dat` sh all.sh --notest
    #eval `cat arm9tdmi.dat gcc-3.4.5-glibc-2.3.6.dat` sh all.sh --notest
    #eval `cat arm9tdmi.dat gcc-3.4.5-glibc-2.3.6-tls.dat` sh all.sh --notest
    #eval `cat arm9tdmi.dat gcc-4.0.2-glibc-2.3.2.dat` sh all.sh --notest
    #eval `cat arm9tdmi.dat gcc-4.0.2-glibc-2.3.2-tls.dat` sh all.sh --notest
    #eval `cat arm9tdmi.dat gcc-4.0.2-glibc-2.3.5.dat` sh all.sh --notest
    #eval `cat arm9tdmi.dat gcc-4.0.2-glibc-2.3.5-tls.dat` sh all.sh --notest
    #eval `cat arm9tdmi.dat gcc-4.0.2-glibc-2.3.6.dat` sh all.sh --notest
    #eval `cat arm9tdmi.dat gcc-4.0.2-glibc-2.3.6-tls.dat` sh all.sh --notest
    #eval `cat arm9tdmi.dat gcc-4.1.0-glibc-2.3.2.dat` sh all.sh --notest
    eval `cat arm9tdmi.dat gcc-4.1.0-glibc-2.3.2-tls.dat` sh all.sh --notest
    
    echo Done.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    主要修改

    • TARBALLS_DIR:指定源码的路径,否则Crosstool在制作交叉编译工具时会自己使用wget下载制作过程中所需要的材料,并将下载的这些材料放在这个路径下
    • RESULT_TOP:交叉工具的目的目录。需要事先创建该目录
    • eval `cat arm9tdmi.dat gcc-4.1.0-glibc-2.3.2-tls.dat` sh all.sh --notest:选择gcc + glibc组合

    建立配置文件

    在demo-arm9tdmi.sh脚本文件中需要注意demo-arm9tdmi.dat和gcc-4.1.0-glibc-2.3.2.dat两个文件,这两个文件作为Crosstool编译的配置文件。

    其中demo-arm9tdmi.dat文件的内容如下,

    KERNELCONFIG=`pwd`/arm.config  # 内核的配置
    TARGET=arm-9tdmi-linux-gnu     # 编译生成的工具链名称
    GCC_EXTRA_CONFIG="--with-cpu=arm9tdmi --enable-cxx-flags=-mcpu=arm9tdmi"
    TARGET_CFLAGS="-O"  # 编译选项
    ~                   
    
    • 1
    • 2
    • 3
    • 4
    • 5

    gcc-4.1.0-glibc-2.3.2-tls.dat的文件内容如下,该文件主要定义编译过程中所需要的库以及它定义的版本,如果在编译过程中发现有些库不存在,Crosstool会自动在相关网站上下载,

    BINUTILS_DIR=binutils-2.16.1
    GCC_CORE_DIR=gcc-3.3.6
    GCC_DIR=gcc-4.1.0
    GLIBC_DIR=glibc-2.3.2
    LINUX_DIR=linux-2.6.15.4
    LINUX_SANITIZED_HEADER_DIR=linux-libc-headers-2.6.12.0
    GLIBCTHREADS_FILENAME=glibc-linuxthreads-2.3.2
    GDB_DIR=gdb-6.5
    GLIBC_EXTRA_CONFIG="$GLIBC_EXTRA_CONFIG --with-tls --with-__thread  --enable-kernel=2.4.18"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    执行脚本

     4554 cd crosstool-0.43
     4555 ./demo-arm9tdmi.sh
    
    • 1
    • 2

    经过数小时的漫长编译之后,会在/opt/crosstool目录下生成新的交叉编译工具。

    添加环境变量

    添加的方法是在系统文件/etc/bash.bashrc的最后添加下面一行

    export PATH=/opt/crosstool/gcc-4.1.0-glibc-2.3.2/arm-linux/bin:$PATH
    
    • 1

    设置完环境变量,也就意味着交叉编译工具链已经构建完成,然后就可以用上一节中的方法测试刚刚建立的工具链,此处就不再赘述。


    使用现成的交叉工具

    很多公司和网站免费提供现成的交叉工具。这些工具可以通过网络直接得到。

    嵌入式编程

    此处进行主要是C语言和ARM汇编的相互调用,ARM汇编可见链接

    ARM汇编语言

    一个可执行映像文件通常由以下几部分构成。

    • 一个或多个代码段,代码段的属性为只读(RO)。
    • 零个或多个包含初始化数据的数据段,数据段的属性为可读写(RW)。
    • 零个或多个不包含初始化数据的数据段,数据段的属性为可读写(RW)。
    	AREA Init , CODE , READONLY ;定义一个名为Init的只读代码段
    	ENTRY ;标识程序的入口点
    start
    	;
    
    • 1
    • 2
    • 3
    • 4

    AREA是一条伪指令,主要作用是定义一个段,可以是代码段也可以是数据段,并说明所定义段的相关属性。

    ENTRY也是一条伪指令,它的主要作用是标识程序的入口点,其后面主要为指令序列,程序的末尾为段的结束标志伪指令END,该伪指令告诉编译器源文件的结束,每一个汇编程序段都必须有一条END伪指令,指示代码段的结束。

    ARM汇编语言的语句格式

    {标号}{指令或伪指令助记符} {;注释}

    基于Linux下GCC的汇编语言程序结构

    Linux 下GCC的汇编程序语言程序是以程序段为单位进行组织的

    ARM汇编和C语言编程

    基本的ATPCS规则

    基本ATPCS规定了在子程序调用时的一些基本规则,包括下面四方面的内容

    • 各寄存器的使用规则及其相应的名称。
    • 数据栈的使用规则。
    • 参数传递的规则。
    • 子程序结果的返回规则。

    寄存器的使用规则及其相应的名称

    • 子程序间通过寄存器RO~R3来传递参数,被调用的子程序在返回前无需恢复寄存器RO~R3的内容。
    • 在子程序中,使用寄存器R4~R11保存局部变量,这时寄存器可以记作V1~V8。
    • 寄存器R12用作子程序间的Scratch寄存器(用于保存SP,在函数返回时使用该寄存器出栈),记作IP。
    • 寄存器R13用作数据栈指针,记作SP。在子程序中寄存器R13不能用作其他用途。寄存器SP在进入子程序时的值和退出子程序的值必须相等。
    • 寄存器R14称为链接寄存器,记作LR。它用作保存子程序的返回地址。如果在子程序中保存了返回地址,寄存器R14则可以用作其他用途。
    • 寄存器R15是程序计数器,记作PC。它不能用作其他用途。
    • ATPCS中的各寄存器在ARM编译器和汇编器中都是预定义的。
    寄存器别名特殊名称使用规则
    R15PC程序计数器
    R14LR连接寄存器
    R13SP数据栈指针
    R12IP子程序内部调用的Scratch寄存器
    R11v8ARM状态局部变量寄存器8
    R10V7s1ARM状态局部变量寄存器7,在支持数据检查的ATPCS中为数据栈限制指针
    R9V6SBARM状态局部变量寄存器6,在支持RWPI的ATPCS中为静态基址寄存器
    R8v5ARM状态局部变量寄存器5ARM状态局部变量寄存器4
    R7V4WRThumb状态工作寄存器
    R6V3局部变量寄存器3
    R5v2局部变量寄存器2
    R4V1局部变量寄存器1
    R3A4参数/结果/Scratch寄存器4
    R2A3参数/结果/Scratch寄存器3
    R1A2参数/结果/Scratch寄存器2
    ROA1参数/结果/Scratch寄存器1

    数据栈的使用规则

    • FD:Full Descending
    • ED:Empty Descending
    • FA:Full Ascending
    • EA:Empty Ascending

    子程序返回规则

    • 如果结果为一个32位的整数,可以通过寄存器返回。
    • 如果结果为一个64位整数,可以通过寄存器RO和R1返回,依此类推。
    • 如果结果为一个浮点数,可以通过浮点运算的寄存器F0、D0或S0返回。
    • 如果结果为复合型的浮点数(如复数),可以通过寄存器F0~FN或者D0~DN返回。
    • 对于位数更多的结果,需要通过内存来传递。

    C语言中内嵌汇编代码

    规则:

    • 在使用物理寄存器时,不要使用过于复杂的C表达式,避免物理寄存器冲突。
    • 不能直接向PC寄存器赋值,程序跳转要使用B或者BL指令。
    • R12和R13可能被编译器用来存放中间编译结果,计算表达式值时又能将R0到R3,R12及R14用于子程序调用,因此要避免直接使用这些物理寄存器。
    • 一般不要直接指定物理寄存器,而是让编译器进行分配。

    一般是使用内联汇编和嵌入式汇编

    内联汇编

    asm { // 内联汇编代码标识
    	// instruction
    }
    
    • 1
    • 2
    • 3

    嵌入式汇编

    asm int add(int m, int n) 
    {
    	// instruction
    }
    
    • 1
    • 2
    • 3
    • 4

    从汇编语言中访问C程序变量

    在汇编代码中调用C函数

    参数传递问题

    如果所传递的参数少于四个,则直接使用R0~R3来进行传递,如果参数多于四个,则必须使用栈来传递多余的参数。

    函数返回问题

    因为在编译C函数的时候,编译器会自动在函数入口的地方加上现场保护的代码(如部分寄存器入栈,返回地址入栈等),在函数出口的地方加入现场恢复的代码(即出栈代码)。

    在C语言中调用汇编函数

    参数传递

    在编译时,编译器将会对C函数的实参使用R0~R3进行传递(如果超过四个参数,则其余的参数使用栈进行传递),因此汇编函数可以直接使用R0-R3寄存器进行计算。

    函数返回

    由于汇编代码是不经过编译器处理的代码,所以现场保护和返回都必须由程序员自己完成。通常情况下现场保护代码就是将本函数内用到的R4~R12寄存器压栈保护,并且将R14寄存器压栈保护,汇编函数返回时将栈中保护的数据弹出。

    BootLoader详解和移植

    嵌入式BootLoader简介

    Bootloader,亦称引导加载程序,是系统加电后运行的第一段软件代码.

    关于Bootloader的简介自行查询

    Bootloader的操作模式

    • 启动加载模式(自主模式):是指Bootloader从目标机上的某个固件存储设备上将操作系统加载到RAM中运行,整个过程没有用户的介入。
    • 下载模式:目标机上的Bootloader将通过串口或者网络或者USB等其他通信手段从主机下载文件,比如下载内核镜像、根文件系统镜像等,从主机下载的文件通常首先被Bootloader保存到目标机的RAM中,然后被Bootloader写到目标机的Flash内固态存储设备中。

    Bootloader的依赖性:并不是有的Bootloader都是通用的,在嵌入式领域Bootloader对于不同的硬件体系结构是完全不同的

    首先,由于Bootloader中包含上电时的初始化硬件操作,所以毫无疑问Bootloader必须依赖于硬件实现。每种不同的体系结构的处理器都有不同的Bootloader与之匹配。不过现在也出现了支持多种体系结构的Bootloader,并且这也是目前的发展趋势。如U-Boot,最初只支持PowerPC,现在已经能很好地支持PowerPC、ARM、MIPS、x86等多种体系结构。

    Bootloader的启动方式:

    • 网络启动方式
    • 磁盘启动方式
    • Flash启动方式

    Bootloader的启动流程

    • 启动代码的第一步是设置中断和异常向量。
    • 完成系统启动所必需的最小配置
    • 设置看门狗
    • 配置系统所使用的存储器
    • 为处理器的每个工作模式设置栈指针
    • 变量初始化
    • 数据区准备

    此处介绍两种BootLoader

    • U-Boot支持大多CPU,可以烧写EXT2.JFFS2文件系统映像,支持串口下载、网络下载,并提供大量的命令。相对于Vivi,它的使用更复杂,但是可以用来更方便地调试程序。
    • Vivi是Mizi公司针对SAMSUNG的ARM架构CPU专门设计的,基本上可以直接使用,命令简单方便。不过其初始版本只支持串口下载,速度较慢。在网上出现了各种改进版本:支持网络功能、USB功能、烧写YAFFS文件系统映像等。
    Vivi

    跟其他的Bootloader一样,Vivi也有两种工作模式:启动加载模式和下载模式

    Vivi主要完成的工作如下

    • 检测开发板
    • 下载代码并保存到Flash中。
    • 初始化硬件
    • 将代码从Flash复制到RAM中并且启动代码。

    Vivi体系架构

    • arch:此目录包括了所有Vivi支持的目标板架构的代码文件,通过不同的子目录进行组织,例如其中就有s3c2440子目录。
    • include:头文件的公共目录,存放Vivi所有源码的头文件,其中的s3c2440.h定义了这块处理器的一些寄存器。include platform/GT2440.h定义了与开发板相关的资源配置参数,我们往往只修改这个文件就可以配置目标板的参数,如波特率、引导参数、物理内存映射等。
    • drivers:其中包括了引导内核需要的设备的驱动程序,包括MTD和串口两个部分,MTD目录下分map、nand和nor三个目录。
    • init:存放Vivi初始化代码文件。这个目录只有main.c和version.c两个文件。和普通的C程序一样,Vivi将从main函数开始执行。
    • lib:存放Vivi实现的库函数文件和一些平台公共的接口代码,比如time.c里的udelay()和mdelay()等。
    • scripts:存放Vivi的脚本配置文件。test:存放一些测试代码文件。net:存放一些网卡驱动的文件。util:存放一些存储单元的文件。

    内核定制

    定制内核移植

    各个目录的组成如下

    • arch:arch目录包括了所有和体系结构相关的核心代码。它下面的每一个子目录都代表Linux支持的一种体系结构,例如i386就是Intel CPU及与之兼容体系结构的子目录。
    • include:include目录包括编译核心所需要的大部分头文件,例如与平台无关的头文件在include/linux子目录下。
    • init:init目录包含核心的初始化代码(不是系统的引导代码),有main.c和Version.c两个文件。
    • mm:mm目录包含了所有的内存管理代码。与具体硬件体系结构相关的内存管理代码位于arch/*/mm目录下。
    • drivers:drivers目录中是系统中所有的设备驱动程序。它又进一步划分成几类设备驱动,每一种有对应的子目录,如声卡的驱动对应于drivers/sound。
    • ipc:ipc目录包含了核心进程间的通信代码。
    • modules:modules目录存放了已建好的、可动态加载的模块。
    • fs::fs目录存放Linux支持的文件系统代码。不同的文件系统有不同的子目录对应,如ext3文件系统对应的就是ext3子目录。
    • Kermel:Kernel内核管理的核心代码放在这里。同时与处理器结构相关的代码都放在arch/*/kernel目录下。
    • net:net目录里是核心的网络部分代码,其每个子目录对应于网络的一个方面。lib: lib目录包含了核心的库代码,不过与处理器结构相关的库代码被放在arch/*/libl目录下。
    • scripts:scripts目录包含用于配置核心的脚本文件。
    • documentation:documentation目录下是一些文档,是对每个目录作用的具体说明。一般在每个目录下都有一个depend文件和一个Makefile文件。这两个文件都是编译时使用的辅助文件。仔细阅读这两个文件,对弄清各个文件之间的联系和依托关系很有帮助。另外有的目录下还有Readme文件,它是对该目录下文件的一些说明,同样有利于我们对内核源码的理解。
    内核裁剪

    内核裁剪就是为了适应嵌入式系统的小体积、小存储的特点

    取消虚拟内存的支持

    虚拟内存一般并不需要,可以直接删除。进入“General setup”菜单项,取消选择“Support”进入“General setup”菜单项,取消选择“Support

    取消多余的调度器

    我们一般使用的调度器是默认的IO调度器,所以可以删除其他调度器。进入“Enable theblock layer”菜单项,再进入子菜单项“IO Schedulers”,取消选择“Anticipatory /O scheduler”、“Deadline I/O scheduler”和“CFQ IO scheduler”三项即可。

    取消对旧版本二进制文件的支持

    对旧版本二进制执行文件的支持这项功能一般也是多余的,可以删除。进入“Userspacebinary formats”菜单项,取消选择“Kernel support for a.out and ECOFF binaries”选项即可。

    取消不必要的设备支持

    一般也删除不需要的设备支持驱动

    取消不需要的文件系统支持

    对多余的文件系统,我们也会将其删除以减小内核的大小。

    嵌入式Linux驱动程序开发

    Linux系统有一套完整的设备管理机制,下面详细介绍其基本构成部分

    前置知识

    设备分类:块设备文件、字符设备文件、网络设备文件、杂项设备文件

    设备号

    在传统方式的设备管理中,除了设备类型(字符设备或块设备)以外,内核还需要一对参数(主、次设备号)才能唯一地标识设备。

    设备号是一个数字,它是设备的标志。如前所述,一个设备文件(也就是设备节点)可以通过mknod命令来创建,其中指定了主设备号和次设备号。主设备号表明是某一类设备,用于标识设备对应的驱动程序,一般对应着确定的驱动程序,主设备号相同的设备使用相同的驱动程序;次设备号一般用于区分,标明不同属性(例如不同的使用方法、不同的位置、不同的操作等),它标志着某个具体的物理设备。次设备号是一个8位数,用来区分具体设备的实例。

    root@huawei ~sys # ll                                                                                                                                                                                       [0]
    total 68K
    crw-r--r-- 1 root root     10, 235 Mar  6  2022 autofs
    drwxr-xr-x 2 root root         240 Mar  6  2022 block
    crw-rw---- 1 root disk     10, 234 Mar  6  2022 btrfs-control
    drwxr-xr-x 3 root root          60 Mar  6  2022 bus
    drwxr-xr-x 2 root root        3.5K Apr 30 01:25 char
    crw------- 1 root root      5,   1 Mar  6  2022 console
    lrwxrwxrwx 1 root root          11 Mar  6  2022 core -> /proc/kcore
    drwxr-xr-x 4 root root          80 Mar  6  2022 cpu
    crw------- 1 root root     10,  59 Mar  6  2022 cpu_dma_latency
    crw------- 1 root root     10, 203 Mar  6  2022 cuse
    drwxr-xr-x 6 root root         120 Mar  6  2022 disk
    drwxr-xr-x 3 root root          80 Mar  6  2022 dri
    crw------- 1 root root     10,  62 Mar  6  2022 ecryptfs
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    Linux驱动程序可以通过两种方式集成到内核中。

    • 将其直接编译到内核。
    • '将其编写成模块,在需要添加某种硬件的时候,内核可以将其调入。在配置Linux选择'Enable loadable module support'即可。

    常用的模块相关命令列表

    命令功能
    lsmod列出当前系统中加载的模块,其中左边第一列是模块名,第二列是该模块的大小,第三列则是该模块使用的数量
    rmmod用于将当前模块卸载
    insmod用于加载当前模块,但insmod不会自动解决依存关系
    modprobe根据模块间依存关系以及letc/modules.conf文件中的内容自动插入模块
    mknod用于创建相关模块

    另外,注意Linux 2.6内核对可加载内核模块规定了新的命名方法,使用的是“.ko”扩展名,而不是Linux 2.4内核采用的“.o”扩展名。

    驱动层次结构

    设备驱动程序的特点

    • 内核代码。设备驱动程序是内核的一部分,如果驱动程序出错,则可能导致系统崩溃。
    • 内核接口。设备驱动程序必须为内核或者其子系统提供一个标准接口。比如,一个终端驱动程序必须为内核提供一个文件IO接口;一个SCSI设备驱动程序应该为SCSI子系统提供一个SCSI设备接口,同时SCSI子系统也必须为内核提供文件的 IO接口及缓冲区。
    • 内核机制和服务。设备驱动程序使用一些标准的内核服务,如内存分配等。
    • 可装载。大多数的Linux操作系统设备驱动程序都可以在需要时装载进内核,在不需要时从内核中卸载。
    • 可设置。Linux操作系统设备驱动程序可以集成为内核的一部分,并可以根据需要把其中的某一部分集成到内核中,这只需要在系统编译时进行相应的设置即可。
    • 动态性。在系统启动且各个设备驱动程序初始化后,驱动程序将维护其控制的设备。如果该设备驱动程序控制的设备不存在,也不影响系统的运行,此时的设备驱动程序只是多占用了一点系统内存而已。

    驱动程序开发流程

    设备驱动程序可以使用模块的方式被动态加载到内核中去。

    • 模块在调用insmod命令时被加载,此时的入口点是init module函数,通常在该函数中完成设备的注册。
    • 模块在调用rmmod函数时被卸载,此时的入口点是cleanup_module函数,在该函数中完成设备的卸载
    • 在调用rmmod函数时被卸载,此时的入口点是cleanup_module函数,在该函数中完成设备的读写等

    在这里插入图片描述

    加载模块

    不做概述

  • 相关阅读:
    八年测开经验面试28K公司后,吐血整理出高频面试题和答案
    Excel 宏录制与VBA编程 —— 13、Excel内置函数的调用
    金仓数据库KingbaseES服务器应用参考手册--8. sys_rewind
    Unity Game FrameWork—模块使用—资源热更新
    jenkins
    开源模型应用落地-业务优化篇(八)
    nginx 做转发处理,指定目录访问和tp同级的目录
    C++ Date 类的编写
    3D点云数据入门(KITTI自动驾驶)
    用信号量实现进程互斥,同步【操作系统学习笔记】
  • 原文地址:https://blog.csdn.net/qq_48322523/article/details/126737011