您的位置:首页 > 其它

ARM裸机的知识点总结--------- 3、安装交叉编译工具链 、使用优化 、Makefile 、mkv210_image.c

2020-07-29 17:07 211 查看

Author: 想文艺一点的程序员
自动化专业 工科男
再坚持一点,再自律一点
CSDN@想文艺一点的程序员
来自朱老师嵌入式的学习笔记

目录

  • 二、使用的优化
  • 2、为工具链创建arm-linux-xxx符号链接
  • 三、makefile
  • 四、mkv210_image.c (在linux中执行,不在开发板上)
  • 一、安装交叉编译工具链(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
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: