arm裸机【3】 --- 安装交叉编译工具链 , Makefile , mkv210_image.c文件详解

安装交叉编译工具链

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 *

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值