Linux嵌入式自学笔记(基于野火EBF6ULL):4.gcc
一、常见问题
比如你想安装一下vim,结果显示如下结果:
输入:
ps aux | grep -E 'apt|dpkg'
查看下面进程,比如我的显示结果为:
理论上这种系统自动更新进程,等待个5-10分钟就能结束,但是我的等待后依旧不停,那就只能手动代码关掉进程了
sudo kill -9 4086
sudo kill -9 4090
关掉之后发现还是安装不了,再重新输入:ps aux | grep -E 'apt|dpkg' ,发现只剩下自身这一个进程了:
接下来要删除锁文件,输入下面命令:
sudo rm /var/lib/dpkg/lock
sudo rm /var/lib/dpkg/lock-frontend
sudo rm /var/lib/apt/lists/lock
sudo rm /var/cache/apt/archives/lock
输入正确之后是没回复的
最后修复 dpkg 状态:
sudo dpkg --configure -a
并更新apt:
sudo apt update
这会再安装就可以发现可以正常安装了
二、GCC
首先,先安装gcc包
suda apt install gcc
首先,创建一个工作目录:workdir/example
mkdir -p ~/workdir/example/hello_c #创建hello_c目录
然后打卡虚拟机中对应文件夹位置
然后桌面上新建一个hello.c,
hello.c里的内容可以如下:
/* $begin hello */#include <stdio.h>int main(){printf("hello, world! This is a C program.\n");for(int i=0;i<10;i++ ){printf("output i=%d\n",i);}return 0;}/* $end hello */
3. GCC和Hello World — [野火]嵌入式Linux基础与应用开发实战指南——基于i.MX6ULL开发板 文档https://doc.embedfire.com/linux/imx6/linux_base/zh/latest/linux_app/gcc_hello_world/gcc_hello_world.html 然后直接把这个.c文件从windows桌面拖入虚拟机对应文件夹中
再在终端中输入命令进入这个文件夹:
cd ~/workdir/example/hello_c
可以输入代码看看hello.c里是否正确
vim hello.c
也可以用ls命令看看文件在不在
可以运行下面代码看看输出结果:
./hello
三、原理
在 Linux 环境下,GCC 的工作可以分为四个主要阶段:预处理、编译、汇编和链接。以下详细解释每个阶段的原理和作用:
1. 预处理
预处理是 GCC 编译的第一步,处理源代码文件(通常是 .c
文件)。这一阶段主要完成以下任务:
- 处理文件包含:将
#include
指定的头文件内容插入到源代码中,例如标准库的函数声明。 - 展开宏定义:将
#define
定义的宏替换为实际内容,例如将常量或简单函数展开。 - 处理条件编译:根据
#ifdef
或#ifndef
等指令,决定哪些代码需要保留或删除。 - 去除注释:删除源代码中的注释,简化后续处理。
预处理后的结果是一个纯文本文件,通常以 .i
为后缀,包含展开后的代码,为后续编译做准备。
2. 编译
编译阶段将预处理后的代码转换为汇编语言(.s
文件)。这是 GCC 的核心步骤,涉及以下子过程:
- 词法分析:将代码分解为一个个词法单元(token),如关键字、变量名、运算符等。
- 语法分析:检查代码是否符合 C 语言的语法规则,构建抽象语法树(AST)。
- 语义分析:验证代码的语义是否正确,例如变量是否已声明、类型是否匹配。
- 生成中间代码:将高级语言转换为中间表示,便于优化和跨平台处理。
- 代码优化:对中间代码进行优化,例如删除冗余计算、循环展开等,以提高程序性能。
这一阶段的输出是汇编语言文件,包含针对目标硬件(如 ARM 架构)的低级指令。
3. 汇编
汇编阶段将汇编语言文件(.s
)转换为机器码,生成可重定位目标文件(.o
文件)。这一过程由汇编器(通常是 GNU 的 as
工具)完成:
- 汇编器将汇编指令翻译为二进制机器码。
- 生成的目标文件包含代码段、数据段等,但尚未指定内存地址(因此称为“可重定位”)。
- 这些文件还包含符号表,记录函数和变量的引用,供后续链接使用。
4. 链接
链接阶段将多个可重定位目标文件(.o
)和库文件组合,生成最终的可执行文件。链接分为两种方式:静态链接和动态链接,GCC 通过调用链接器(通常是 ld
)完成这一步骤。
-
静态链接:
- 在编译时,将程序所需的所有库(如 glibc 的标准库函数)直接打包到可执行文件中。
- 优点:生成的可执行文件独立运行,不依赖外部库,兼容性强。
- 缺点:文件体积较大,且多个程序无法共享库代码,浪费内存。
-
动态链接:
- 在编译时,仅在可执行文件中记录所需库的引用,实际库代码在运行时由动态链接器(
ld-linux.so
)加载。 - 优点:可执行文件体积小,多个程序可共享同一库,节省内存;库更新无需重新编译程序。
- 缺点:运行时需要确保系统中有正确的库版本,依赖外部环境。
- 在编译时,仅在可执行文件中记录所需库的引用,实际库代码在运行时由动态链接器(
链接器的工作包括:
- 符号解析:将代码中的函数调用和变量引用与实际地址关联。
- 重定位:为代码和数据分配最终的内存地址。
- 生成可执行文件:输出 ELF(Executable and Linkable Format)格式的可执行文件,包含程序的完整结构。
在 Linux 系统中,编译后的可执行文件通过以下步骤运行,体现了 GCC 编译结果与操作系统的协作:
- 进程创建:用户在终端输入运行命令(如
./program
),Shell 使用fork()
创建新进程。 - 程序加载:通过
execve()
系统调用,内核的do_execve()
函数加载可执行文件到内存。 - 动态链接(若适用):动态链接器
ld-linux.so
(glibc 的一部分)加载共享库,完成符号解析和地址绑定。 - 初始化与执行:glibc 的
_start
函数作为程序入口,调用__libc_start_main()
进行初始化(如设置堆栈、环境变量),然后执行用户程序的主函数。 - 程序终止:执行完成后,调用 glibc 的
_exit()
函数结束进程。
GCC 的编译过程直接影响程序的执行效率和兼容性。例如,动态链接的程序依赖 glibc 的动态链接器,而静态链接的程序则包含所有依赖,适合在不同 Linux 系统间移植。