嵌入式系统移植

1. 常用二进制相关工具

     strip: 可以实现剔除可执行文件的符号表  (减少二进制文件的空间体积)

     objcopy : 将ELF文件的相关段拷贝成一个文件 (-d 反编译    -R 显示重定向的入口)

     readelf : 读取ELF格式的内容   -h 显示ELF信息  -S 查看ELF结构

  objdump :将ELF文件格式进行反汇编   (生成纯二进制数据文件)

      size :列出目标文件每一段的大小以及总体的大小

      nm:查看符号表    

    

 

 2. Makefile内置的变量

预定义变量

  LDFLAGS: 链接选项

  CFLAGS : 编译选项  (C编译器的选项,无默认值)

     AR:库文件维护程序的名称,默认值为ar

  AS 汇编程序的名称,默认值为as

  CC C编译器的名称,默认值为cc

  CPP C预编译器的名称,默认值为$(CC) –E

  CXX C++编译器的名称,默认值为g++

  FC FORTRAN编译器的名称,默认值为f77

  RM 文件删除程序的名称,默认值为rm -f

自动变量

  $@ 目标文件的完整名称

  $^ 所有不重复的目标依赖文件,以空格分开

  $< 第一个依赖文件的名称

  $* 不包含扩展名的目标文件名称

  $+ 所有的依赖文件,以空格分开,并以出现的先后为序,可能 包含重复的依赖文件

  $? 所有时间戳比目标文件晚的依赖文件,并以空格分开

  $% 如果目标是归档成员,则该变量表示目标的归档成员名称

 

变量定义方式:递归展开方式、简单方式 

递归展开eg.

bar=$(ugh)

查看变量:

echo $(foo)

优点:它可以向后引用变量

缺点:不能对该变量进行任何扩展

eg.以下代码会造成死循环

CFLAGS=$(CFLAGS) -O

 

 3. 配置linux内核时的变量

  ARCH : 选择CPU的体系结构

     *在Linux内核编译时,可以通过设置Makefile中ARCH变量的值来选择ARM体系还是MIPS体系进行内核编译

  CROSS_COMPILE : 指定交叉编译器的前缀

 

4. Linux内核编译的默认文件

  ARM体系:zImage  

  (image是内核镜像文件,zimage是压缩之后的内核镜像文件,uimage是在zimage基础之上加上头部一些信息,其实也是压缩之后的内核镜像,uImage镜像文件适合使用bootm命令启动)

  X86体系:bzImage

  Linux内核镜像文件可以是压缩格式也可以不压缩

 

 5. Linux内核源码目录

  net: 存放网络协议源码

  drivers/net : 存放网络驱动源码

 

6. Kconfig的常用关键字

  bool:配置源码编译进内核或不编译进内核

   tristate: 配置源码编译进内核、不编译进内核或以模块方式编译进内核

 *tristate意思是三态(3种状态,对应Y、N、M三种选择方式),bool是要么真要么假(对应Y和N)。所以tristate的意思就是这个配置项可以被三种选择,bool的意思是这个配置项只能被2种选择

  source表示包含下一级Kconfig

     depends on表示该配置选项依赖于其他配置选项的关系

 

7. busybox创建文件系统时,会创建的内容:init、ls、cd、ifconfig等命令 常用的shell命令)

 (默认不产生配置文件(手写的)(rcS、inittab),产生的是可执行文件)

 

8. Linux中可以自动创建设备节点的命令是:mdev (该工具由busybox自带)

 

9. 在嵌入式开发中,函数代码链接到.text段    (.text 就是指代码段)

 

10. 配置内核

  配置内核后,默认生成的文件名是.config

  make menuconfig表示以菜单形式配置Linux内核的命令 

 

11. inittab中常见动作
 inittab决定init进程如何工作,有以下2个动作:  

  sysinit : 系统第一个shell脚本执行的路径指定
  askfirst: 实现用户摁任意键进入控制台

 

12. 常见的Makefile,生成裸机程序的方法 ★

(1).Makefile 概述:

  本质上是一个“自动编译管理器”

  “自动”指它能够根据文件时间戳自动发现更新过的文件而减少编译的工作量

  通过读入Makefile文件的内容来执行大量的编译工作

(2).Makefile基本结构:

  由make工具创建的目标体(target),通常是目标文件或可执行文件

  要创建的目标体所依赖的文件(dependency_file)

  创建每个目标体时需要运行的命令 (command)

*命令行前面必须是一个“TAB键”,否则编译错误为:*** missing separator. Stop.

格式如下:

target : dependency_files
 <TAB>   command

eg.

hello.o : hello.c hello.h
    gcc -c hello.c -o hello.o

(3).make使用

 直接运行即可

make

  -C dir 读入指定目录下的Makefile

  -f file 读入当前目录下的file文件作为Makefile

  -i 忽略所有的命令执行错误

  -I dir指定被包含的Makefile所在目录

  -n 只打印要执行的命令,但不执行这些命令

  -p 显示make变量数据库和隐含规则

  -s 在执行命令时不显示命令

  -w 如果make在执行过程中改变目录,打印当前目录名

 Makefile 生成裸机程序的模板:

#前面是一堆变量的定义
OBJCOPY = arm-linux-objcopy CC = arm-linux-gcc #gcc是肯定要有的
#后面写Makefile规则
#生成裸机,最终目的一定是生成bin文件
xxx.bin:xxx #ELF文件与bin文件之间的关系 $(OBJCOPY) -O binary $^ $@ #objcopy是先有输入,再写输出,小写的o第一个一定是目标 xxx:xxx.o main.o  #链接:由.o文件打包生成ELF文件 $(CC) $(LDFLAGS) -o $@ $^ #需要加链接选项,LDFLAGS为默认值
#所有的linux项目都有的公共规则,不能写反~ %.o:%.c $(CC) $(CFLAGS) -c -o $@ $^ %.o:%.S $(CC) $(CFLAGS) -c -o $@ $^

 实例: 编写Makefile,将start.S生成为二进制文件

思路:首先,我们可以使用”arm-none-linux-gnueabbihf-gcc -c -o start.o start.S” 命令来生成.o文件。

   之后,我们可以使用“arm-none-linux-gnueabihf-gcc -nostolib -o abc start.o”命令来生成可执行文件,其中nostlib表示不需要链接库文件。

   最后,使用“arm-linux-gnueabihf-objcopy -O binary abc abc.bin”生成最后的二进制文件。

   将这3个步骤依次按照Makefile的语法格式进行编译即可生成二进制文件。

CROSS_COMPILE = /opt/Sourcery_CodeBench_Lite_for_Xilinx_GNU_Linux/bin/arm-linux-CC = gcc

OBJS += start.o

CFLAGS += -nostdlib

LDFLAGS += -Tmap.lds

TARGET := build.bin

all:$(TARGET)
$(TARGET):build
    $(CROSS_COMPILE)objcopy -S --gap-fill 0xff -O binary $^ $@

build:$(OBJS)
    $(CROSS_COMPILE)ld $(LDFLAGS) -o $@ $^

%.o:%.c
    $(CROSS_COMPILE)$(CC) -c $(CFLAGS) -o $@ $<
%.o:%.S
    $(CROSS_COMPILE)$(CC) -c $(CFLAGS) -o $@ $<
    
.PYHON:clean
clean:
    rm -f *.o build build.bin

 

13. bootloader的工作流程

  ①. bootloader一般分为boot阶段和loader阶段

  ②. boot阶段采用体系结构相关的汇编语言编写,主要是初始化CPU和内存设备

  ③. boot阶段为第二阶段初始化C语言运行前环境,设置SP寄存器

  ④. loader阶段根据系统镜像存储位置,初始化对应设备的驱动,拷贝系统镜像文件内容到内存加载地址

     ⑤. loader阶段根据内核启动要求,初始化R0,R1,R2寄存器的值,将PC指针指向内核镜像加载地址处

 

14. ext4格式镜像文件的制作过程

1.分配空间

  (1).制作64M的镜像文件,命名为:a9rootfs.ext3

  sudo dd if=/dev/zero of=a9rootfs.ext3 bs=1M count=64

  (2).用ext3格式化上一步的镜像文件

  sudo mkfs.ext3 a9rootfs.ext3

2.填充空间

  (1).挂载镜像文件到一个目录,建议放在同级目录下,假设目录名为mnt_fs

   sudo mount -t ext3 -o loop a9rootfs.ext3 mnt_fs/

  (2).建立根文件系统目录

  (3).利用busybox制作可执行文件

  (4).拷贝文件到mnt_fs目录下

  (5).拷贝动态库到根文件系统里

  (6).编写inittab、etc/rc.d/rcS、etc/fstab文件

3.卸载空间

   sudo umount mnt_fs

 

15. GNU格式的链接脚本

链接脚本:怎么把目标文件组合在一起

#SECTIONS: SECTION 表示段,S表示很多段
#4个段有先后关系,最好不要打乱它的顺序
#.text代码段、.rodata只读数据段、.data数据段、.bss全局初始化位清零段。语法为:段名:{*(同名段)}
#.text代码段是第一个,需要指定地址:start.o(.text)
#ALIGN(4) 4字节对齐

SECTIONS { .
= 0x20000000;
#要什么地址给什么地址 . = ALIGN(4); .text : { start.o(.text) *(.text) } . = ALIGN(4); .rodata : { *(.rodata) } . = ALIGN(4); .data : { *(.data) } . = ALIGN(4); .bss : { *(.bss) } }

 

16. make执行

  make执行时读取当前目录下的Makefile文件或makefile进行执行

  Makefile和makefile文件在make执行时的优先级是不一样的

  make中如果目标文件比依赖文件新,那么make将不执行对应的命令

 

17. uboot编译时,不仅仅会产生对应体系的文件,还可能产生x86体系的可执行文件

   uboot可以支持ARM、MIPS、PowerPC等众多CPU体系结构(uboot即是bootloader的一种,引导启动内核的)

 

18. 使用QEMU加载文件时,必须是ELF格式的文件

 

19.NFS

  网络启动的根文件系统称之为NFS

  使用NFS作为根文件系统时,linux内核需要加载网卡驱动

  

20. linux进程

     linux的proc目录下可以查看到进程信息

  linux的1号进程叫init进程

 

21. Linux内核编译时,objs-y的目标才被编译进内核镜像中

    objs-m 以模块方式编,其余的跟内核没有关系

 

22. busybox源码是没有体系结构相关的配置的 (它是应用程序)

 

==================分============割============线====================

 

23.烧写到系统的bootloader镜像文件是binary格式的文件

 

24.ramdisk根文件系统不可以烧写到flash上

 

25.Linux系统提供VFS(虚拟文件系统)层,方便访问其内核挂载的根文件系统

 

26.在嵌入式开发中,使用交叉编译器在主机上编译,在目标机上运行程序

 

27.bootloader在加载内核启动前,需要将内核镜像和内核启动参数搬移到系统内存中

 

28.函数指针

函数:一堆代码的集合,空间的首地址

  一个数据变量的内存地址可以储存在相应的指针变量中,同理,函数的首地址也可以储存在某个函数指针变量中,通过函数指针,我们就可以调用函数指针所指向的函数了。

函数指针定义的方式:

void(*abc)(int) //(*abc)先定义的地址

函数指针调用示例:

#include<stdio.h>
void (*ABC)(int);
void abc(int x);
int main()
{
    int a1=100;
    int a2=200;
    ABC=&abc;
    (*ABC)(a1); //通过函数指针变量调用函数     
    (*ABC)(a2);   
    return 0;
}
void abc(int x)
{
   printf("abc=%d\n",x); 
}

运行结果:

 

eg1.给一个绝对地址,如何访问:

 1.如果该地址是指向变量的地址:unsigned char  *p = xxx;直接定义指针p指向该地址即可通过指针的方式访问到该地址。

 2.如果该地址指向的是一段函数空间:定义指向函数的指针p,即可通过指针p访问到对应的函数空间。

void (*p)(void); //定义了一个可以指向一类函数地址的指针
p = (void (*)(void))xxx; //将给的地址进行强制类型转换成我们定义的指针,它能指向的函数类型的地址

 

eg2:汇编语言转为C语言表示:

汇编语言内容:

MOV R0,#1
MOV R1,#2
MOV R2,#3
MOV PC,#0x2000_8000

C语言代码:

void (*fun_t)(int,int,int)=(void(*)(int ,int,int))0x2000_8000;
fun_t(int a1,int a2,int a3);   //a1=#1,a2=#2,a3=#3

 

posted @ 2022-04-10 17:14  D5181  阅读(374)  评论(0编辑  收藏  举报