ARM裸机的知识点总结--------- 3、安装交叉编译工具链 、使用优化 、Makefile 、mkv210_image.c
Author: 想文艺一点的程序员
自动化专业 工科男
再坚持一点,再自律一点
CSDN@想文艺一点的程序员
来自朱老师嵌入式的学习笔记
目录
- 1.分析 USB 启动 和 SD 卡启动的差别
- 2.mkv210_image.c的作用:为BL1添加校验头
- 3、mkv210_image.c文件详解
- 4、./mkx210 led.bin 210.bin 和 int main (int argc, char *argv[])的关系
一、安装交叉编译工具链(toolchain)
1、Windows中装软件的特点
Windows中装软件使用安装包,安装包解压后有2种情况:一种是一个安装文件(.exe .msi),双击进行安装,下一步直到安装完毕。安装完毕后会在桌面上生成快捷方式,我们平时使用快捷方式来启动这些程序;另一种是所谓的绿色软件、免安装软件。这种不用安装,直接解压开里面就有exe可以直接双击执行。
2、linux中装软件的特点
linux中安装软件比windows中复杂。linux中安装软件一般有以下几种方法:
第一种:在线安装。譬如ubuntu中使用apt-get install vim来安装vim软件。
第二种:自己下载安装包来安装。这种方式的缺陷就是你不知道你下载的安装包和你的系统是否匹配。
第三种:最装逼的一种方式,就是源代码安装。
3、自己安装交叉编译工具链(采用的第二种)
我们选择交叉编译工具链的原则:和我们所使用的目标平台(给哪款SoC编程)尽量去匹配。譬如我们开发S5PV210的程序就是用arm-2009q3这个版本,因为三星官方在开发S5pv210时就使用这个版本的交叉编译工具链,这样可以最大限度的避免稀奇古怪的问题出现。
步骤1:打开虚拟机,在/usr/local/下创建/usr/local/arm文件夹
步骤2:先将安装包从Windows中弄到linux中去。可以用共享文件夹
步骤3:将共享文件夹中的安装包复制到 /usr/local/arm/ 目录下。
aston@ubuntu:/usr/local/arm$ cp /mnt/hgfs/winshare/arm-2009q3.tar.bz2 ./ 分析: 1、当前目录 /usr/local/arm ,在这个目录下面使用 cp 命令 2、需要复制的目标文件目录: /mnt/hgfs/winshare/arm-2009q3.tar.bz2 3、复制到当前目录: ./
步骤3:解压。
tar -jxvf arm-2009q3.tar.bz2
到此相当于程序已经安装完毕,真正的应用程序安装在
/usr/local/arm/arm-2009q3/bin目录下
注:linux中的目录管理方法。技术角度来讲,linux中所有目录性质都是一样的,所以技术角度来讲我们把软件安装到哪里都行。但是因为如果胡乱放置,将来程序可能不好找。所以久而久之大家就总结了一个文件放置的一般定义,譬如说/bin目录放置一些系统自带的用户使用的应用程序,/sbin目录下存放的是系统自带的系统管理方面的应用程序。
那我们装软件放在哪里? 一般都在/usr目录下。我们安装arm-linux-gcc,就在/usr/local/底下创建一个arm文件夹,然后装到里面。
4、安装后的测试
到真正的应用程序的安装目录下(也就是/usr/local/arm/arm-2009q3/bin),去执行arm-linux-gcc -v
执行方法是:./arm-none-linux-gnueabi-gcc -v
执行后可以得到一长串输出,其中有“gcc version 4.4.1 ”字样,即表示安装成功。
二、使用的优化
我们如果直接想用我们的交叉工具链去编译,在使用这个命令的时候,必须将这个命令的全路径带上。(操作系统才可以找到这个命令。)
/usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi-gcc a.c
我们的希望只用 程序命令 这个名字来进行编译
arm-none-linux-gnueabi-gcc a.c linux 当中只会搜索某一个特定文件下的命令,并不会全盘搜索。-----所以我们使用环境变量
1、将工具链导出到环境变量 PATH
1、环境变量的意义
环境变量就是操作系统的全局变量。每一个环境变量对操作系统来说都是唯一的,名字和所代表的意义都是唯一的。linux系统可以有很多个环境变量。其中有一部分是linux系统自带的,还有一些是我们自己来扩充的。
我们这里涉及到的一个环境变量是PATH。PATH这个环境变量是系统自带的,
2、PATH环境变量的含义
它的含义就是系统在查找可执行程序时会搜索的路径范围。
3、打印当前的 PATH 包含的路径
root@ubuntu:/# echo $PATH /usr/local/arm/arm-2009q3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games 注意: 一个 冒号 代表一个路径 /usr/local/arm/arm-2009q3/bin /usr/local/bin /usr/sbin:/usr/bin /bin /usr/games
4、将工具链导出到环境变量
export PATH=/usr/local/arm/arm-2009q3/bin:$PATH
在一个终端中执行以上命令后,该终端中就可以直接使用arm-linux-gcc了,但是只要关掉这个终端再另外打开一个立马就不行了。原因是我们本次终端中执行时的操作只是针对本终端,以后再打开的终端并未被执行过这个命令所以没导出。
5、解决终端重新开启的问题
解决方案是在~/.bashrc中,添加export PATH=/usr/local/arm/arm-2009q3/bin:$PATH 即可。
注意:我们导出这个环境变量是在当前用户,如果你登录时在其他用户下是没用的。
1、切换到宿主目录下面。
cd /root 或者 cd ~ root@ubuntu:/# cd /root root@ubuntu:~# 注意:root 的宿主目录就是根目录下的 root用户(/root)
2、利用 ls -a 显示 bashrc 这个隐藏文件
root@ubuntu:~# ls -a . .. .bash_history .bashrc .profile .viminfo
注: .bashrc的作用:
每个用户在每次终端打开的时候,就会提前执行。
3、将命令添加到这个文件的后面 即可
# enable programmable completion features (you don't need to enable # this, if it's already enabled in /etc/bash.bashrc and /etc/profile # sources /etc/bash.bashrc). #if [ -f /etc/bash_completion ] && ! shopt -oq posix; then # . /etc/bash_completion #fi 导出工具链到 PATH 环境变量 (作用只会在当前用户起作用) export PATH=/usr/local/arm/arm-2009q3/bin:$PATH
2、为工具链创建arm-linux-xxx符号链接
原因:因为原来的命令太长了 arm-none-linux-gnueabi-gcc 所以我们需要创建一个符号链接。
1、单个的快捷方式创建
ln ----(目标文件路径) -s ----(快捷方式名字)
ln arm-none-linux-gnueabi-addr2line -s arm-linux-addr2line 在当前目录下 创建了一个名字为 arm-linux-addr2line 的符号链接
2、多个快捷方式的快速创建。-----(写一个脚本)
因此我们在当前目录(/usr/local/arm/arm-2009q3/bin)建立一个.sh的脚本文件,在当前目录***./xx.sh***执行即可。之后我们就可以使用比如 arm-linux-gcc 的命令进行输入了。
ln arm-none-linux-gnueabi-addr2line -s arm-linux-addr2line ln arm-none-linux-gnueabi-ar -s arm-linux-ar ln arm-none-linux-gnueabi-as -s arm-linux-as ln arm-none-linux-gnueabi-c++ -s arm-linux-c++ ln arm-none-linux-gnueabi-c++filt -s arm-linux-c++filt ln arm-none-linux-gnueabi-cpp -s arm-linux-cpp ln arm-none-linux-gnueabi-g++ -s arm-linux-g++ ln arm-none-linux-gnueabi-gcc -s arm-linux-gcc ln arm-none-linux-gnueabi-gcc-4.4.1 -s arm-linux-gcc-4.4.1 ln arm-none-linux-gnueabi-gcov -s arm-linux-gcov ln arm-none-linux-gnueabi-gdb -s arm-linux-gdb ln arm-none-linux-gnueabi-gdbtui -s arm-linux-gdbtui ln arm-none-linux-gnueabi-gprof -s arm-linux-gprof ln arm-none-linux-gnueabi-ld -s arm-linux-ld ln arm-none-linux-gnueabi-nm -s arm-linux-nm ln arm-none-linux-gnueabi-objcopy -s arm-linux-objcopy ln arm-none-linux-gnueabi-objdump -s arm-linux-objdump ln arm-none-linux-gnueabi-ranlib -s arm-linux-ranlib ln arm-none-linux-gnueabi-readelf -s arm-linux-readelf ln arm-none-linux-gnueabi-size -s arm-linux-size ln arm-none-linux-gnueabi-sprite -s arm-linux-sprite ln arm-none-linux-gnueabi-strings -s arm-linux-strings ln arm-none-linux-gnueabi-strip -s arm-linux-strip
需要注意的是Windows下和Linux的换行符不一样,建议在Linux中使用Vim写脚本文件。
三、makefile
1. 为什么要使用Makefile?
在一个正式的软件项目中,由很多个.c和.h文件构成,此时如果直接在命令行编译,就会像这样:
gcc a.c b.c c.c d.c e.c f.c g.c -o exe 有多少文件都要罗列出来
每次编译都要输入一堆东西很麻烦,这时就需要Makefile来解决,这样就能实现只需要写一次即可实现每次都能同时编译多个文件。
2. Makefile使用示例
创建一个简单的Makefile
创建Makefile文件:命令行直接输入touch Makefile,然后vi Makefile进入到Makefile文件中。
在Makeflie文件中写入要编译的内容,格式如下,并保存退出。
exe(目标): a.c b.c(依赖) gcc a.c b.c -o exe(命令)
加工 依赖 得到 目标 。 想要得到目标就 make 目标(make exe)
目标: 目标顶格写,后面是冒号(冒号后面是依赖)。
依赖: 依赖是用来产生目标的原材料。
命令: 命令前面一定是Tab,不能是顶格,也不能说多个空格。命令就是要生成那 个目标需要做的动作。
然后直接make即可一键编译。
3.Makefile工作原理
其一,当我们执行 make xx 的时候,Makefile会自动执行xx这个目标下面的命令语句。
其二,当我们make xx的时候,是否执行命令是取决于依赖的。依赖如果成立就会执行命令,否则不执行。
其三,我们直接执行make 和make 第一个目标 效果是一样的。(第一个目标其实就是默认目标)
exe: a.c b.c gcc a.c b.c -o exe clean: rm exe // 方式1:make (仅make时,默认执行第一个目标exe,可等效为make exe) // 方式2:make clean (执行clean目标下的命令,即rm exe)
在实际的项目中,裸机程序中的Makefile是把程序的编译和链接过程分开的。
平时我们用gcc a.c -o exe这种方式来编译时,实际上把编译和链接过程一步完成了。
但是在内部实际上编译和链接永远是分开独立进行的,编译要使用编译器gcc,链接要使用链接器ld,对于交叉编译器来说arm-linux-ld即是用来链接的。
用一个实际项目举例:
led.bin: start.o //这里描述了目标和依赖,也是make默认执行的地方 arm-linux-ld -Ttext 0x0 -o led.elf $^ //将下面的生成的所有 .o 文件链接成一个可执行程序 arm-linux-objcopy -O binary led.elf led.bin //制作镜像文件 arm-linux-objdump -D led.elf > led_elf.dis //得到反汇编程序 gcc mkv210_image.c -o mkx210 //编译 mkv210_image.c 生成可执行文件 ./mkx210 led.bin 210.bin //执行 %.o : %.S //编译: 所有的 .o 文件 都是由 .s 文件编译而来。 arm-linux-gcc -o $@ $< -c //arm-linux-gcc -o (只编译不链接) %.o : %.c //编译: 所有的 .o 文件 都是由 .c 文件编译而来。 arm-linux-gcc -o $@ $< -c clean: rm *.o *.elf *.bin *.dis mkx210 -f
代码解读:
(1)生成在 linux 里面可执行文件 elf
arm-linux-ld -Ttext 0x0 -o led.elf arm-linux-ld:是用来链接的; -Ttext 0x0:指定代码段的起始地址从0x0开始(从反汇编中可以看出); -o led.elf:链接生成的文件名为led.elf。 这里.elf文件为可执行文件,在Linux操作系统下就可以直接执行了。
(2)制作烧录到裸机镜像文件(bin文件)
arm-linux-objcopy -O binary led.elf led.bin arm-linux-objcopy:用来制作镜像文件的; -O binary:表示生成2进制文件; led.elf led.bin:表示将elf文件生成为bin文件。 这里.bin文件为可烧写文件,可以通过USB串口或SD卡烧写到板卡中执行,在嵌入式裸机中需要bin文件才能进行板卡的烧写。
(3)制作反汇编文件。(将 elf 可执行文件,反汇编为汇编代码)
arm-linux-objdump -D led.elf > led_elf.dis arm-linux-objdump:由编译链接好的elf格式的可执行程序反过来得到汇编源代码; -D表示反汇编 > 左边的是elf的可执行程序(反汇编时的原材料) > 右边的是反汇编生成的反汇编程序(相当于由左边生成右边)。
(4)编译 mkv210_image.c 文件
gcc mkv210_image.c -o mkx210 //编译 mkv210_image.c 生成可执行文件 ./mkx210 led.bin 210.bin //执行 (1)gcc mkv210_image.c -o mkx210 :这里是利用 linux 当中的 gcc 来进行编译的。 说明: mkv210_image.c 这个文件在 linux 当中执行,而不是在开发板里面执行。 (2)./mkx210 led.bin 210.bin 格式: 命令:./mkx210 参数:led.bin 210.bin 目的: 就是由 led.bin 来得到 210.bin。 led.bin ----------> USB启动 210.bin ----------> SD卡启动
四、mkv210_image.c (在linux中执行,不在开发板上)
目的 :就是由 led.bin 来得到 210.bin。
1.分析 USB 启动 和 SD 卡启动的差别
分析启动过程可知;210启动后先执行内部iROM中的BL0,BL0执行完后会根据OMpin的配置选择一个外部设备来启动(有很多,我们实际使用的有2个:usb启动和SD卡启动)。
在usb启动时:内部BL0读取到BL1后不做校验,直接从BL1的实质内部0xd0020010开始执行,因此usb启动的景象led.bin不需要头信息,因此我们从usb启动时直接将镜像下载到0xd0020010去执行即可,不管头信息了;
从SD启动时:BL0会首先读取sd卡得到完整的镜像(完整指的是led.bin和16字节的头),
然后BL0会自己根据你的实际镜像(指led.bin)来计算一个校验和checksum(自动计算),
然后和你完整镜像的头部中的checksum(我们自己写的)来比对。
如果对应则执行BL1,如果不对应则启动失败(会转入执行2st启动,即SD2启动。如果这里已经是2st启动了,这里校验通不过就启动失败)。
2.mkv210_image.c的作用:为BL1添加校验头
整个程序中首先申请一个16KB大小的buffer,然后把所有内容按照各自的位置填充进去,最终把填充好的buffer写入到一个文件(名叫210.bin)就形成了我们想要的镜像。
3、mkv210_image.c文件详解
/* * mkv210_image.c的主要作用就是由usb启动时使用的led.bin制作得到由sd卡启动的镜像210.bin * * 本文件来自于友善之臂的裸机教程,据友善之臂的文档中讲述,本文件是一个热心网友提供,在此表示感谢。 */ /* 在BL0阶段,Irom内固化的代码读取nandflash或SD卡前16K的内容, * 并比对前16字节中的校验和是否正确,正确则继续,错误则停止。 */ #include <stdio.h> #include <string.h> #include <stdlib.h> #define BUFSIZE (16*1024) #define IMG_SIZE (16*1024) #define SPL_HEADER_SIZE 16 //#define SPL_HEADER "S5PC110 HEADER " #define SPL_HEADER "****************" int main (int argc, char *argv[]) { FILE *fp; char *Buf, *a; int BufLen; int nbytes, fileLen; unsigned int checksum, count; int i; // 1. 3个参数 if (argc != 3) { printf("Usage: %s <source file> <destination file>\n", argv[0]); return -1; } //2. 分配16K(BL1)的buffer BufLen = BUFSIZE; Buf = (char *)malloc(BufLen); if (!Buf) { printf("Alloc buffer failed!\n"); return -1; } memset(Buf, 0x00, BufLen); // 3. 读源210.bin到buffer // 3.1 打开源210.bin fp = fopen(argv[1], "rb"); if( fp == NULL) { printf("source file open error\n"); free(Buf); return -1; } // 3.2 获取源bin长度 fseek(fp, 0L, SEEK_END); // 定位到文件尾 fileLen = ftell(fp); // 得到文件长度 fseek(fp, 0L, SEEK_SET); // 再次定位到文件头 // 3.3 源bin长度不得超过16K-16byte (前16字节是校验头) count = (fileLen < (IMG_SIZE - SPL_HEADER_SIZE)) ? fileLen : (IMG_SIZE - SPL_HEADER_SIZE); // 3.4 buffer[0~15]存放"S5PC110 HEADER " (填写校验头) memcpy(&Buf[0], SPL_HEADER, SPL_HEADER_SIZE); // 3.5 读源210.bin到buffer[16] nbytes = fread(Buf + SPL_HEADER_SIZE, 1, count, fp); if ( nbytes != count ) { printf("source file read error\n"); free(Buf); fclose(fp); return -1; } fclose(fp); // 4. 计算校验和 (就是统计有多少个字节) // 4.1 从第16byte开始计算,把buffer中所有的字节数据加和起来得到的结果 a = Buf + SPL_HEADER_SIZE; for(i = 0, checksum = 0; i < IMG_SIZE - SPL_HEADER_SIZE; i++) checksum += (0x000000FF) & *a++; // 4.2 将校验和保存在buffer[8~15] a = Buf + 8; // Buf是210.bin的起始地址,+8表示向后位移2个字,也就是说写入到第3个字 *( (unsigned int *)a ) = checksum; // 5. 拷贝buffer中的内容到目的bin // 5.1 打开目的bin fp = fopen(argv[2], "wb"); if (fp == NULL) { printf("destination file open error\n"); free(Buf); return -1; } // 5.2 将16k的buffer拷贝到目的bin中 a = Buf; nbytes = fwrite( a, 1, BufLen, fp); if ( nbytes != BufLen ) { printf("destination file write error\n"); free(Buf); fclose(fp); return -1; } free(Buf); fclose(fp); return 0; }
4、./mkx210 led.bin 210.bin 和 int main (int argc, char *argv[])的关系
main函数两个形参的作用
main函数接收2个形参:argc和argv。
(1)argc:是用户(通过命令行来)执行这个程序时,实际传递的参数个数。注意这个个数是包含程序执行本身的。
./mkx210 led.bin 210.bin------------> 这里就是传输了 3 个参数
(2)argv是一个字符串数组,这个数组中存储的字符串就是一个个的传参。
譬如我们执行程序时使用./mkx210 led.bin 210.bin
则argc = 3 则argv[0] = “./mkx210” argv[1] = led.bin argv[2] = 210.bin
- arm-linux-gdb 交叉编译工具的安装使用
- arm-linux-gdb 交叉编译工具的安装使用
- 交叉编译工具和qt安装,终端使用qmake编译arm版本可执行文件的方法
- 交叉编译工具 arm-linux-gcc 安装
- 使用crosstool-0.43构建arm交叉编译工具链
- Ubuntu18.04安装arm-linux-gcc交叉编译工具(附arm-linux-gcc 5.4.0包)
- arm-linux-gcc交叉编译工具的安装
- 汇总(之四):交叉编译工具链arm-none-linux-gnueabi-, Qt交叉编译安装和移植,Qt桌面版编译安装,QTcreator安装和配置
- Ubuntu系统如何安装arm-linux-gnueabi交叉编译工具?
- 安装交叉编译工具arm-linux-gcc
- Ubuntu12.04安装交叉编译工具链arm-linux-gcc
- 安装交叉编译工具,执行arm-linux-gcc –v命令出现提示找不到该文件或目录?解决方法
- arm-linux-gdb调试工具的安装与交叉编译gdbserver
- 安装交叉编译工具,执行arm-linux-gcc –v命令出现提示找不到该文件或目录?解决方法
- UBUNTU安装交叉编译工具arm-liunx-gcc-4.4.3
- Ubuntu16.04安装配置嵌入式交叉编译工具链arm-linux-gcc
- 使用uclibc的buildroot制作交叉编译工具链 ,arm-linux-gcc
- Ubuntu 9.04安装arm-linux-gcc交叉编译工具
- 交叉编译工具arm-none-linux-gnueabi-gcc安装
- Red Hat Enterprise Linux 5中安装交叉编译工具arm-linux-gcc-4.3.2.tgz