交叉编译

1.	交叉开发
	什么是交叉编译?
	项目的开发流程:
		编辑代码		-->		 编译代码		-->			运行及调试代码
		
	第一阶段的所有编译工作都由gcc编译器完成。
		如:
			gcc main.c   		-->			a.out
			gcc main.c -o main	-->			main
		
	交叉编译也是一种编译,也是将一种高级语言翻译成一种机器语言。

	交叉  cross-compiling
	
	在PC上面,我们可以做到编译环境(就是你写代码和编译代码的那个机器环境)和运行环境
	(运行代码的那个机器环境)是相同的。
	
	很多嵌入式的产品是不适合去写程序,也没有开发工具的。
	
	有人就提出把上面的项目开发流程的那三个步骤分开:
		就是写程序,编译程序我们还是放在PC上来完成
			--->	也就是编译环境还是由PC来提供
		
		最终运行环境我们放在目标板上来完成
			--->	也就是说运行环境由目标板来提供
			
	所以,交叉编译就是在一种环境(x86)下将代码编译成能在另一种环境(ARM)下运行的程序。
		
		那么我们通过另一条交叉编译指令来进行编译,如:
			arm-linux-gcc hello.c
			or
			arm-linux-gcc hello.c -o hello
	
	请注意:
		这条指令并不是Ubuntu这个linux操作系统自带的,而是通过交叉编译工具链得来的。
		交叉编译工具链一般是由厂家来进行提供,我们直接拿来用即可。
		
	交叉编译工具链的移植步骤如下:
		1)	把交叉编译工具链的压缩包,拷贝到你的linux系统下。
			如:
			在window下cp<gcc-linaro-5.5.0-2017.10-x86_64_arm-linux-gnueabi.>到share目录下。
	
		2)	解压到一个合适的目录下(不能是共享目录)
			如:
				sudo mkdir -p /usr/local/arm
				sudo tar -xvf gcc-linaro-5.5.0-2017.10-x86_64_arm-linux-gnueabi. -C /usr/local/arm

		3)	把交叉编译的命令的完整的路径加入到环境变量PATH中去
			有两种方式:
				1.	在终端上输入指令:
					export PATH=$PATH:/usr/local/arm/5.4.0/usr/bin
					上述方式只是临时有效。
				2.	把上面这条指令加入到~/.bashrc的最后一行
					vim ~/.bashrc
					如上方式永久有效
				
二、开发板的下载方式
	you的电脑需要通过SecureCRT先连接上开发板
	1)	通过串口下载文件到开发板中去
		输入指令:
			rx 要接受的文件名(这是通过xModem串口协议来接收文件)
			如:
				rx a.out
			
			接着点击 上方菜单栏中的 “传输”
				--> 在下拉的选项中点击"发送xModem"
				--> 在选项框中选中你要下载的文件。
				--> 接着等待下载完成即可。
		
	2)	通过tftp下载
		1.	先连接网线至路由器上
		2.	网络设置
			a.	先设置虚拟机的网络(IP地址)
				我们可以在linux上输入ifconfig指令查看当前设备的IP地址:192.168.31.72
				在window下调出命令台,输入ipconfig查看PC的IP地址:192.168.31.121
				
				虚拟机 --> 设置 --> 网络适配器 --> 桥接模式
				桥接模式:
					相当于将虚拟机作为一台独立的机器,虚拟机也将会有一个独立的与主机不
					一致的IP地址。
				
				接下来到Ubuntu中设置IP:
					系统设置 -->	网络 	-->		'+'		-->	 配置IP地址
		
			b.	ubuntu中tftpd服务器的配置
				安装tftpd:
					sudo apt-get install tftpd-hpa

				tftp的控制命令:
					启动:sudo service tftpd-hpa start
					重启:sudo service tftpd-hpa restart
					停止:sudo service tftpd-hpa stop
				
				配置tftp的服务:
					tftp配置文件在/etc/default/tftpd-hpa。
				
			c.	配置开发板的IP
				在开发板的/etc/init.d目录下,新建一个文件Start.sh
				
				系统每次重启的时候,都会自动执行/etc/init.d/Start.sh
				
			Start.sh中的内容为:
				#注意:xxx为分配给你的开发板的IP地址
				#! /bin/bash
				ifconfig eth0 down
				ifconfig eth0 hw ether 08:09:00:A0:25:xxx
				ifconfig eth0 192.168.31.xxx netmask 255.255.255.0 up
				telnetd &
				
			配置完成后,保存并退出,输入reboot或者按重启键重启开发板,重启完成后输入ifconfig
			看开发板的IP是否修改成功。
		
			d.	tftp命令下载或上传文件到服务器
				在开发板上运行命令,就可以下载或上传文件啦。
				
				tftp -g -r ex.c 192.168.31.185
				通过tftp的网络服务从服务器192.168.31.185上获取(-g :get)一个远程的文件
				(-r remote)文件名为ex.c
				
				tftp -p -l 1.mp3 192.168.31.185
				同伙tftp的网络服务上传(-p put)一个本地文件(-l local)文件名为1.mp3到
				192.168.31.185的服务器上面去.
				
三、GCC编译过程
	之前我们在执行编译指令时,都是通过:
		gcc main.c -o main
		or
		arm-linux-gcc main.c -o main
		如果有多个文件的话,那么则:
		gcc main.c 1.c 2.c -o main
		or
		arm-linux-gcc main.c 1.c 2.c -o main
	
	编译其实是分为很多步的,详情请见图<编译过程>。
	
	如果gcc命令后面不跟任何选项的话,会默认执行预处理、编译、汇编、链接整个过程。
	如果你的程序没有语法问题的话,将会默认生成一个a.out可执行文件。
	
	1)	预处理(preprocessing)
		预处理就是处理程序中以"#"开头的行,换句话说就是只要是以"#"开头的行就是预处理指令。
		
		预处理如何进行?
		只需要在gcc指令的后面加上-E选项即可
		
		-E: 提示编译器执行完预处理就停下来,后面的编译、汇编、链接不执行。
		
		gcc -E hello.c -o hello.i
		
		预处理指令有以下三种:
			a.	#include 文件包含命令
				#include ""
				#include <>
				
			b.	#define 宏定义
				仅替换
				
			c.	条件编译
				#if xxxx #endif
				#if yyyy #else #endif
				#ifdef xxx  #endif
				#ifndef xxx #define xxx #endif
				
				
			由上例子可知,预处理进行的操作:
			1.	将所有的#define删除,并且展开所有的宏定义
			2.	处理所有的条件编译指令
			3.	处理#include预编译指令,将被包含的头文件插入到该编译指令的位置
			4.	删除掉所有的注释 "//" "/**/"
			5.	添加行号和文件名标识,方便后面编译时编译器产生调试用的行号以及编译时
				产生编译错误或警号时能够显示行号。
				
	2)	编译(compiling)
		使用-S选项,编译编译操作执行完就结束。对应生成一个.s文件
		编译过程是整个程序构建的核心部分,编译成功,会将源代码由文本形式转换成汇编语言
		
		(arm-liux-)gcc -S hello.i(hello.c) -o hello.s 
			
	3)	汇编(assembling)
		把汇编程序汇编成一个目标程序(机器指令文件)。每一个汇编语句几乎都对应一个机器指令
		对应生成一个.o文件
		
		(arm-linux-)gcc -c hello.s -o hello.o
		
		(arm-linux-)as hello.s -o hello.o(as是汇编器)
		
		hello.o是二进制文件,但是还不能执行。
		
	4)	链接(linking)
		就是把各目标文件.o链接成一个可执行文件
		
		把各个目标文件的.test .data.....合在一起,解决地址中的冲突问题
		
			(arm-linux-)gcc hello.o ... 1.o 2.o -o hello

四、GDB调试程序
	GDB是一个单步调试的命令行工具,它可以使你的程序,在你输入命令后,才运行。
	而且可以打印中间变量的值。
	
	(1)	gcc -g main.c -o main
		-g表示在编译过程中,加入调试信息,以便于GDB来调试。
		
	(2)	调试程序
		gdb main
		
		GDB的调试命令:
		(gdb)b
			break 用来设置断点,什么是断点?
				程序执行到此处时,会暂停,等待用户的输入命令后,才继续运行。
			
			b line_num 	---> 设置line_num行号为一个断点
			b 符号(函数)---> 设置函数为一个断点
			
		(gdb)info b
			查看断点的情况
		
		(gdb)delete breakpoints 断点号(clear n代表删除第n行的断点)
			删除断点
		
		(gdb)r
			run  运行代码

		(gdb)p n
			print 打印变量n的值

		(gdb)n
			next 下一步 单步运行
			
		(gdb)s
			step 下一步 单步运行
			
			n和s的区别,仅在函数调用时有区别:
			n把函数调用当做是一步,直接把调用函数运行完
			s:step into 会进入函数一步一步的执行
		
		(gdb)display命令
			display sum
			跟踪查看sum变量的值,每次停下来都会显示它的值
			
		(gdb)l
			list 查看源文件

		(gdb)help 
			查看gdb命令的帮助文件
		
		(gdb)quit
			退出
			
五、动态库和静态库
	1)	库
		库是一种代码的二进制的封装形式,可以理解为库就是很多.c文件预先编译好的二进制代码
		的一种函数集合。在其他的源文件中你可以调用库,但是你可能并不知道它到底是如何实现
		的。
		
		linux下的库主要出于/lib和/usr/lib下。
		
		library : 库
	
		/lib 下的是与系统运行相关的库文件
		/usr/lib 下的一般是指第三方软件的库文件
		
		库又分为动态库和静态库
	
	2)	动态库
		动态库的使用步骤:
		a.	编辑源代码
			xxxx.c		功能函数的实现
			xxxx.h		函数以及数据等的声明  "接口文件"
			....
			
		b.	生成动态库
			(arm-linux-)gcc -shared -fpic -o libxxxx.so xxxx.c 
				-shared : 生成动态库(共享库)
				-fpic : 与位置无关
				-o : 指定生成的动态库的名字
					约定动态库的名字以lib为开头,以.so为结尾
				xxx.c :生成的动态库的C源代码文件列表
		
		c.	把头文件和库拷贝给需要的人
			拷贝完头文件和库之后,编写代码:
				#include "sum_lss.h"
				#include <stdio.h>

				int main()
				{
					int a = sum_lss(5,3);
					printf("sum = %d\n",a);
					return 0;
				}
			编写完代码之后,进行编译工作时要注意添加两个如下选项:
				-I(include) 后面接一个目录名,指定头文件的搜索路径,可以接多个-I
				
				-L(lib)	指定一个库的搜索路径,可以接多个-L
				
				-l 指定一个需要用到的库的名字(除去前面的lib和后面的.so)可以接多个-l
	
			比如:
				libabc.so ---> dir1
				libxyz.so ---> dir1
				xxxx.h	--->   dir2				
					-L dir1 -l abc -l xyz -I dir2
	
				libabc.so --> dir1
				libxyz.so --> dir2
					-L dir1 -l abc -L dir2 -l xyz
				
			如:		
				(arm-linux-)gcc main.c -I ./inc -L ./lib -l sum_lss -o main
				我们生成了一个可执行文件main,并且这个可执行文件main中会使用动态库中
				函数(代码)。但是,在生成main的时候,并没有把动态库libsum_lss.so的函数
				(代码)拷贝到可执行文件main中去,它只是标记了一下main这个可执行文件
				需要用到libsum_lss.so这个库里面的相关功能代码。
			
			当真正运行main的时候,它会去找它所依赖的库:
				./main
				./main: error while loading shared libraries: 
				libsum_lss.so: cannot open shared object file: No such file or directory
				
		d.	运行时刻,需要指定动态库的搜索路径
			在linux系统下,有一个环境变量LD_LIBRARY_PATH(load)
				LD_LIBRARY_PATH:加载库的路径
			
			系统就会在变量LD_LIBRARY_PATH指定的目录下去搜索所依赖的库
				export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:dir1
			接着运行即可
				./main
	
	3)	静态库
		静态库的使用步骤:
		a.	编辑库的源代码
			a.c(a.h)	-->		int sum_v1(int a,int b);
			b.c(b.h)	-->		int sub_v1(int a,int b);
			....
			
		b.	把各源文件编成.o文件
			a.c --> a.o
				(arm-linux-)gcc -c a.c -o a.o
			b.c --> b.o
				(arm-linux-)gcc -c b.c -o b.o	
			.....
		
		c.	用ar命令把所有的.o文件生成一个静态库
			(arm-linux-)ar -rc libyyy.a a.o b.o
			把a.o 和 b.o 打包生成一个静态库,这个静态库的名称为yyy
			一般约定静态库的名称以lib开头,以.a结尾。
			
		d.	把头文件和静态库拷贝给需要的人
			接着进行编译:
				(arm-linux-)gcc main.c -I ./inc -L ./lib -l yyy -o main
			需要注意的是:
				静态库有区别于动态库,当使用静态库时,编译可执行文件的时候,会把静态库中
			的内容编进可执行文件中去。
		
		e.	直接运行即可。
			因为在编译的时候已经把静态库的内容编写进了可执行文件中去了,所以执行的时候
			不需要再去搜索所依赖的库,直接运行即可。
		
	动态库和静态库的区别和相同点:
		相同点:两种库都是二进制的一种封装形式
		
		不同点:
			1.	对于动态库,编译可执行文件时,并没有把动态库的内容copy到可执行文件中去
			只是做了一个标记,表示可执行文件需要用到xxx动态库。这样的话,运行可执行文件
			时,会先去加载可执行文件所依赖的动态库。
				对于静态库,编译可执行文件时,会把静态库中的内容copy到可执行文件中去,运行
			可执行文件时,就不需要再去加载静态库了,但是这样也会导致最终可执行文件的代码量
			相对增多。相当于编译器将代码补充完整了,这样运行起来就会相对于快一点,但是有一个
			缺点:占用我们的磁盘和内存空间
				所以总的来说就是静态库牺牲了空间效率,换取了执行效率。
		
			2.	当函数接口不变时,更新动态库,不需要重新编译依赖该动态库的工程文件
				如果是静态库,修改,则需要重新编译依赖该静态库的工程文件。
			
六、Makefile
	1)	什么是Makefile
		Makefile是make的配置文件。
		
		make是一个智能化的自动编译的工程管理工具。
		它可以决定在一个工程里,哪些文件需要被编译,哪些不需要被编译,哪些可以先编译
		哪些可以后编译,而且他知道如何去编译他们。
		
		make如何去做是有依据的,那么make的依据来源于Makefile。
		Makefile告诉make去做什么以及如何去做,也就是说Makefile关系到整个工程的编译规则。
		
		Makefile它是make的配置文件,它是一个文本文件,一旦Makefile写好,此时只需要一个
		make命令,整个工程将依据Makefile中的编译规则自动编译,极大的提高了项目开发的效率。
		那么会不会写Makefile,从侧面说明了一个人是否具备完成大型工程的能力。
	
		Makefile作为一个文本文件,那这个文本文件该如何编辑?
	
	2)	Makefile文件格式
		a.	文件名
			Makefile or makefile
		
		b.	文件格式
			TARGET:PREREQUISITES//表示依赖关系
			<Tab键>COMMAND1//每一条命令的开头必须以Tab键开头
			<Tab键>COMMAND2
			....
			<Tab键>COMMANDn
			
			TARGET:目标名,通常是要生成的文件名
			PREREQUISITES:依赖文件列表
			当需要生成某个"TARGET"时,首先是看当前目录下是否已经有依赖文件,如果没有
			就看Makefile中是否有以依赖文件为目标,如果有,就去生成它。
			一个Makefile文件中通常包含一个或多个“Rules”-->目标。
		
		c.	运行make
			make 目标名
			make首先要在当前目录下去找Makefile/makefile,然后再到该文件中去找"目标名"
			,再找它的依赖文件...再执行达到此目的命令。
					
			make -f dir 目标名
				make首先会在指定的目录dir下去找Makefile/makefile......
				
			make
				make首先在当前目录下去找Makefile/makefile,然后再到该文件中去找“第一个”
				目标名,再去找它的依赖文件.....再执行达到此目的命令。
			
		d.	最简单的一个Makefile
			首先要链接一个Makefile中的变量,在Makefile中可以定义变量,但是Makfile中额
			变量时没有数据类型的,都是当成字符串来处理。
			
			在Makefile中利用#作为行注释,类似于C语言中的//
			
			引用变量:$(变量名)
				引用变量 --> 代表的变量的值
			
			a.	简单赋值   :=
				会在变量的定义点,按照被引用的变量的当前值进行展开,不会向后展开。
				例子:
					B := $(A)
					A := "12345"
					那么$(B) --> 为空
				
			b.	递归赋值   =
				如果变量的定义引用了其他变量,那么引用会一直展开下去,直到找到被引用的
				变量的最新的定义,也就是说会向后展开
				例子:
					B = $(A)
					A := "12345"					
					那么$(B) --> "12345" 
					
			c.	追加赋值 	+=
				当这个变量以前没有被定义过,+=和=是一样的。但是,当变量已经有定义的时候
				+=只是简单的进行字符的添加工作
					A := 123
					A += 456 --> A := $(A)456 ---> A := 123456
				
			d.	条件赋值 	?=
				当变量没有定义或没有值(定义了但是没有初始化),我们才给它赋值
				CC ?= arm-linux-gcc 
				
				如上是我们人为的去定义变量,在我们的Makefile中还有一些自动变量,
				自动变量-->早就有人帮我们定义好的变量
				
				自动变量是make内置的变量,make赋予这些变量一个特殊的含义:
				
				$@	目标文件的完整名称
				$+  所有依赖文件名,以空格分开,可能会包含有重复的依赖文件
				$^  所有不重复的依赖文件名,每一个文件之间以空格隔开
				$<  第一个依赖文件的名称
				$?	所有时间戳比目标文件晚(新)的依赖文件,并以空格分开

			例子:
				TARGET := display			#目标文件名
				CC := arm-linux-gcc 		#CC指定的编译器
				$(TARGET):a.o b.o c.o d.o 	#依赖关系
					$(CC) $^ -o $@			#生成规则,注意前面需要一个Tab键
					
				a.o:a.c						#依赖文件的生成规则
					$(CC) -c $< -o $@
					
				b.o:b.c
					$(CC) -c $< -o $@

				c.o:c.c
					$(CC) -c $< -o $@

				d.o:d.c
					$(CC) -c $< -o $@
			
			通配符:
				%.o:%.c(%表示任意多个字符,%应用于Makefile,*应用于系统中)
					$(CC) -c $< -o $@
		
		Makefile函数
			Makefile允许定义并调用函数:
				在Makefile中,调用内置的函数或者自定义的函数的格式如下:
				
				$(函数名 函数参数列表)
				
				例子:
					wildcard
					文件名展开函数,展开成一列所有符合由其参数描述的文件名,
					文件名之间以空格分隔开来,函数调用成功将会返回展开后的文件名列表。
					wildcard这个函数只带一个参数,指定文件名的通配方式。
					wildcard就是看你的文件名是否符合我参数定义的规则。
					如:	
						$(wildcard *.c)
	
					patsubst
					模式字符串替换函数
					$(patsubst <from>,<to>,<text>)
					把原始字符串<text>中的<from>替换成<to>
						$(patsubst %.c,%.o,"1.c 2.c")
							--> 1.o 2.o
					
					




		
			
			
			

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值