安装交叉编译工具链
1.linux中装软件的特点
linux中安装软件比windows中复杂。linux中安装软件一般有以下几种方法:
第一种:在线安装。譬如ubuntu中使用apt-get install vim来安装vim软件。
第二种:自己下载安装包来安装。这种方式的缺陷就是你不知道你下载的安装包和你的系统是否匹配。
第三种:最装逼的一种方式,就是源代码安装。
我们安装交叉编译工具链(arm-linux-gcc)实际采用第二种安装方式。
2.交叉编译工具链的选择
我们选择交叉编译工具链的原则:和我们所使用的目标平台(给哪款SoC编程)尽量去匹配。譬如我们开发S5PV210的程序就是用arm-2009q3这个版本,因为三星官方在开发S5pv210时就使用这个版本的交叉编译工具链,这样可以最大限度的避免稀奇古怪的问题出现。
3.交叉编译工具链的安装
步骤1:打开虚拟机,在/usr/local/下创建/usr/local/arm文件夹
步骤2:先将安装包从Windows中弄到linux中去。可以用共享文件夹,也可以用Samba,也可以cuteftp。
步骤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 ”字样,即表示安装成功。
5.将工具链导出到环境变量PATH
什么是环境变量
环境变量就是操作系统的全局变量。每一个环境变量对操作系统来说都是唯一的,名字和所代表的意义都是唯一的。linux系统可以有很多个环境变量。其中有一部分是linux系统自带的,还有一些是我们自己来扩充的。我们这里涉及到的一个环境变量是PATH。PATH这个环境变量是系统自带的,它的含义就是系统在查找可执行程序时会搜索的路径范围。
导出环境变量
执行后可以得到一长串输出,其中有“gcc version 4.4.1 ”字样,即表示安装成功。
在一个终端中执行以上命令后,该终端中就可以直接使用arm-linux-gcc了,但是只要关掉这个终端再另外打开一个立马就不行了。原因是我们本次终端中执行时的操作只是针对本终端,以后再打开的终端并未被执行过这个命令所以没导出。解决方案是在~/.bashrc中,添加export PATH=/usr/local/arm/arm-2009q3/bin:$PATH 即可,因为每次打开终端时,都会执行.bashrc。
注意:我们导出这个环境变量是在当前用户,如果你登录时在其他用户下是没用的。比如你在Aston用户下的~/.bashrc给环境变量PATH添加了路径,在root用户下是没用的,这点需要注意!
6.为工具链创建arm-linux-xxx符号链接
为了在使用工具链的时候方便,建立符号链接简化我们输入命令的工作。因此我们在当前目录(/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 version 4.4.1 ”字样,即表示安装成功。
每次编译都要输入一堆东西很麻烦,这时就需要Makefile来解决,这样就能实现只需要写一次即可实现每次都能同时编译多个文件。
2. Makefile使用示例
创建一个简单的Makefile
创建Makefile文件:命令行直接输入touch Makefile,然后vi Makefile进入到Makefile文件中。
在Makeflie文件中写入要编译的内容,格式如下,并保存退出。
exe(目标): a.c b.c(依赖)
gcc a.c b.c -o exe(命令)
目标
: 目标顶格写,后面是冒号(冒号后面是依赖)
依赖
: 依赖是用来产生目标的原材料。
命令
: 命令前面一定是Tab,不能是顶格,也不能说多个空格。命令就是要生成那 个目标需要做的动作。
然后直接make即可一键编译。
3.Makefile工作原理
当我们执行 make xx 的时候,Makefile会自动执行xx这个目标下面的命令语句。
当我们make xx的时候,是否执行命令是取决于依赖的。依赖如果成立就会执行命令,否则不执行。
我们直接执行make 和make 第一个目标,效果是一样的。(第一个目标其实就是默认目标)
示例:
Makefile文件:
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即是用来链接的。(在gcc后加 -c即为只编译不链接, -o是用来指定名字的)
用一个实际项目举例:
led.bin: start.o //这里描述了目标和依赖,也是make默认执行的地方
arm-linux-ld -Ttext 0x0 -o led.elf $^ //链接
arm-linux-objcopy -O binary led.elf led.bin //制作镜像文件
arm-linux-objdump -D led.elf > led_elf.dis //得到反汇编程序
gcc mkv210_image.c -o mkx210 //编译生成可执行文件
./mkx210 led.bin 210.bin //执行
%.o : %.S
arm-linux-gcc -o $@ $< -c
%.o : %.c
arm-linux-gcc -o $@ $< -c
clean:
rm *.o *.elf *.bin *.dis mkx210 -f
代码解读:
(1)arm-linux-ld -Ttext 0x0 -o led.elf: arm-linux-ld:是用来链接的;-Ttext 0x0:指定代码段的起始地址从0x0开始(从反汇编中可以看出);-o led.elf:链接生成的文件名为led.elf。这里.elf文件为可执行文件,在Linux操作系统下就可以直接执行了。
(2)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)arm-linux-objdump -D led.elf > led_elf.dis: arm-linux-objdump:由编译链接好的elf格式的可执行程序反过来得到汇编源代码; -D表示反汇编 > 左边的是elf的可执行程序(反汇编时的原材料),>右边的是反汇编生成的反汇编程序(相当于由左边生成右边)。
mkv210_image.c文件详解
1.S5PV210的启动过程回顾
分析启动过程可知;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添加校验头
我们编译链接时只得到了led.bin,这个210.bin的得到和交叉编译工具链是完全无关的。由led.bin得到210.bin的过程是三星的S5PV210所特有的,因此需要我们自己去完成,为此我们写了mkv210_image.c来完成。
3.整个程序工作流分析
整个程序中首先申请一个16KB大小的buffer,然后把所有内容按照各自的位置填充进去,最终把填充好的buffer写入到一个文件(名叫210.bin)就形成了我们想要的镜像。
4.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的buffer
BufLen = BUFSIZE;
Buf = (char *)malloc(BufLen);
if (!Buf)
{
printf("Alloc buffer failed!\n");
return -1;
}
memset(Buf, 0x00, BufLen);
// 3. 读源bin到buffer
// 3.1 打开源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
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 读源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中共有几个1
// 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;
}
5.文件中的细节
main函数两个形参的作用
main函数接收2个形参:argc和argv。
argc是用户(通过命令行来)执行这个程序时,实际传递的参数个数。注意这个个数是包含程序执行本身的
argv是一个字符串数组,这个数组中存储的字符串就是一个个的传参。
譬如我们执行程序时使用./mkx210 led.bin 210.bin
则argc = 3
则argv[0] = “./mkx210” argv[1] = led.bin argv[2] = 210.bin
校验和的计算方法
算法:校验和其实就是需要校验的内存区域中,所有内存中的内容按照字节为单位来进行相加,最终相加的和极为校验和。
实现时大家要注意指针的类型为char *