引言:嵌入式系统开始上课了,我要寄了!所以写下这篇文章作为我嵌入式系统开发和单片机开发的先导知识点
参考书籍:
工具链由编译器、链接器、解释器和调试器组成,在x86的Linux主机上,交叉开发工具链除了能够编译生成在ARM、MIPS和PowerPC等硬件架构上运行的程序,还可以为x86平台上不同版本的Linux提供编译开发程序的功能。常用的工具链软件由:Binutils、GCC、Glibc和Gdb
在裁剪用于嵌入式系统的Linux内核时,由嵌入式系统的存储大小有限,所以我们需要的链接工具页可根据嵌入式系统的特性进行制作,建立自己的交叉编译工具链,主要有三种方法
Crosstool脚本工具来实现一次编译,生成交叉编译工具链分步构建交叉编译链
选择Binutils、GCC和Glibc等组件相互匹配的版本,选择与目标机系统的内核一致的内核,以免在目标机运行程序的时候产生冲突。

准备步骤如下
详细步骤
新建目录
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
建立环境变量
建立环境变量主要是将其定义为经常使用的路径,这是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
编译、安装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
配置Binutils工具,建立一个新的目录来存放配置及编译文件,这样可以使源文件和编译文件独立打开
4500 mkdir build-binutils
4501 cd ./build-binutils/
4502 ../binutils-2.16.1/configure --target=$TARGET --prefix=$PREFIX
其中选项–target的意思是指定生成的是arm-linux的工具,–prefix指出可执行文件安装的位置,执行上述操作会出现很多check信息,最后产生Makefile文件。
4505 make
4506 make install
获得内核头文件
编译器需要通过系统内核的头文件来获得目标平台所支持的系统函数,调用所需要的信息,对于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
配置完退出并保存,检查内核目录中的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
编译安装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
由于是第一次安装ARM交叉编译工具,没有支持glibc库的头文件,所以需要修改t-linux文件。用vi或者gedit打开文件。
4539 vi gcc/config/arm/t-linux
在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
4542 make
4543 make install
建立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
进行编译配置
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
编译和安装
4551 make
4552 make install
编译安装完整的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
安装完成后会发现在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
制作交叉调试器
交叉调试器不是工具链的必须工具,但是它与工具链配套使用,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
测试交叉编译工具链
#include
int main() {
printf("Hello World!\n");
return 0;
}
通过以下命令进行编译,编译后生成名为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
使用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
基本过程
使用Crosstool构建交叉编译工具链的制作过程和上一节中分步构建过程的原理相似
建立脚本文件
假设针对的开发板的arm9架构的
4551 cd ./crosstool-0.43/
4553 vi demo-arm9tdmi.sh
查看脚本文件
#!/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.
主要修改
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" # 编译选项
~
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"
执行脚本
4554 cd crosstool-0.43
4555 ./demo-arm9tdmi.sh
经过数小时的漫长编译之后,会在/opt/crosstool目录下生成新的交叉编译工具。
添加环境变量
添加的方法是在系统文件
/etc/bash.bashrc的最后添加下面一行
export PATH=/opt/crosstool/gcc-4.1.0-glibc-2.3.2/arm-linux/bin:$PATH
设置完环境变量,也就意味着交叉编译工具链已经构建完成,然后就可以用上一节中的方法测试刚刚建立的工具链,此处就不再赘述。
使用现成的交叉工具
很多公司和网站免费提供现成的交叉工具。这些工具可以通过网络直接得到。
此处进行主要是C语言和ARM汇编的相互调用,ARM汇编可见链接
一个可执行映像文件通常由以下几部分构成。
AREA Init , CODE , READONLY ;定义一个名为Init的只读代码段
ENTRY ;标识程序的入口点
start
;
AREA是一条伪指令,主要作用是定义一个段,可以是代码段也可以是数据段,并说明所定义段的相关属性。
ENTRY也是一条伪指令,它的主要作用是标识程序的入口点,其后面主要为指令序列,程序的末尾为段的结束标志伪指令END,该伪指令告诉编译器源文件的结束,每一个汇编程序段都必须有一条END伪指令,指示代码段的结束。
ARM汇编语言的语句格式
{标号}{指令或伪指令助记符} {;注释}
基于Linux下GCC的汇编语言程序结构
Linux 下GCC的汇编程序语言程序是以程序段为单位进行组织的
基本的ATPCS规则
基本ATPCS规定了在子程序调用时的一些基本规则,包括下面四方面的内容
寄存器的使用规则及其相应的名称
| 寄存器 | 别名 | 特殊名称 | 使用规则 |
|---|---|---|---|
| R15 | PC | 程序计数器 | |
| R14 | LR | 连接寄存器 | |
| R13 | SP | 数据栈指针 | |
| R12 | IP | 子程序内部调用的Scratch寄存器 | |
| R11 | v8 | ARM状态局部变量寄存器8 | |
| R10 | V7 | s1 | ARM状态局部变量寄存器7,在支持数据检查的ATPCS中为数据栈限制指针 |
| R9 | V6 | SB | ARM状态局部变量寄存器6,在支持RWPI的ATPCS中为静态基址寄存器 |
| R8 | v5 | ARM状态局部变量寄存器5ARM状态局部变量寄存器4 | |
| R7 | V4 | WR | Thumb状态工作寄存器 |
| R6 | V3 | 局部变量寄存器3 | |
| R5 | v2 | 局部变量寄存器2 | |
| R4 | V1 | 局部变量寄存器1 | |
| R3 | A4 | 参数/结果/Scratch寄存器4 | |
| R2 | A3 | 参数/结果/Scratch寄存器3 | |
| R1 | A2 | 参数/结果/Scratch寄存器2 | |
| RO | A1 | 参数/结果/Scratch寄存器1 |
数据栈的使用规则
子程序返回规则
C语言中内嵌汇编代码
规则:
一般是使用内联汇编和嵌入式汇编
内联汇编
asm { // 内联汇编代码标识
// instruction
}
嵌入式汇编
asm int add(int m, int n)
{
// instruction
}
从汇编语言中访问C程序变量
在汇编代码中调用C函数
参数传递问题
如果所传递的参数少于四个,则直接使用R0~R3来进行传递,如果参数多于四个,则必须使用栈来传递多余的参数。
函数返回问题
因为在编译C函数的时候,编译器会自动在函数入口的地方加上现场保护的代码(如部分寄存器入栈,返回地址入栈等),在函数出口的地方加入现场恢复的代码(即出栈代码)。
在C语言中调用汇编函数
参数传递
在编译时,编译器将会对C函数的实参使用R0~R3进行传递(如果超过四个参数,则其余的参数使用栈进行传递),因此汇编函数可以直接使用R0-R3寄存器进行计算。
函数返回
由于汇编代码是不经过编译器处理的代码,所以现场保护和返回都必须由程序员自己完成。通常情况下现场保护代码就是将本函数内用到的R4~R12寄存器压栈保护,并且将R14寄存器压栈保护,汇编函数返回时将栈中保护的数据弹出。
Bootloader,亦称引导加载程序,是系统加电后运行的第一段软件代码.
关于
Bootloader的简介自行查询
Bootloader的操作模式
Bootloader的依赖性:并不是有的Bootloader都是通用的,在嵌入式领域Bootloader对于不同的硬件体系结构是完全不同的
首先,由于Bootloader中包含上电时的初始化硬件操作,所以毫无疑问Bootloader必须依赖于硬件实现。每种不同的体系结构的处理器都有不同的Bootloader与之匹配。不过现在也出现了支持多种体系结构的Bootloader,并且这也是目前的发展趋势。如U-Boot,最初只支持PowerPC,现在已经能很好地支持PowerPC、ARM、MIPS、x86等多种体系结构。
Bootloader的启动方式:
Bootloader的启动流程
此处介绍两种BootLoader
跟其他的Bootloader一样,Vivi也有两种工作模式:启动加载模式和下载模式
Vivi主要完成的工作如下
Vivi体系架构
各个目录的组成如下
内核裁剪就是为了适应嵌入式系统的小体积、小存储的特点
取消虚拟内存的支持
虚拟内存一般并不需要,可以直接删除。进入“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系统有一套完整的设备管理机制,下面详细介绍其基本构成部分
前置知识
设备分类:块设备文件、字符设备文件、网络设备文件、杂项设备文件
设备号
在传统方式的设备管理中,除了设备类型(字符设备或块设备)以外,内核还需要一对参数(主、次设备号)才能唯一地标识设备。
设备号是一个数字,它是设备的标志。如前所述,一个设备文件(也就是设备节点)可以通过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
...
Linux驱动程序可以通过两种方式集成到内核中。
Linux选择'Enable loadable module support'即可。常用的模块相关命令列表
| 命令 | 功能 |
|---|---|
| lsmod | 列出当前系统中加载的模块,其中左边第一列是模块名,第二列是该模块的大小,第三列则是该模块使用的数量 |
| rmmod | 用于将当前模块卸载 |
| insmod | 用于加载当前模块,但insmod不会自动解决依存关系 |
| modprobe | 根据模块间依存关系以及letc/modules.conf文件中的内容自动插入模块 |
| mknod | 用于创建相关模块 |
另外,注意Linux 2.6内核对可加载内核模块规定了新的命名方法,使用的是“.ko”扩展名,而不是Linux 2.4内核采用的“.o”扩展名。
驱动层次结构
设备驱动程序的特点
驱动程序开发流程
设备驱动程序可以使用模块的方式被动态加载到内核中去。

不做概述