嵌入式C语言一
工具安装
ubuntu 安装vim
apt-get install vim
sudo apt-get install vim
ubuntu 安装gcc
apt-get install gcc
sudo apt-get install gcc
一个简单的C程序
#include <stdio.h>
int main(void){
printf("hello world\n");
return 0;
}
qrrk@qrrk-virtual-machine:~/桌面$ vim main.c
qrrk@qrrk-virtual-machine:~/桌面$ gcc -o hello main.c
qrrk@qrrk-virtual-machine:~/桌面$ ./hello
hello world
● -E:只对C源程序进行预处理,不编译。
● -S:只编译到汇编文件,不再汇编。
● -c:只编译生成目标文件,不进行链接。
● -o:指定输出的可执行文件名。
● -g:生成带有调试信息的debug文件。
● -O2:代码编译优化等级,一般选择2。
● -W:在编译中开启警告(warning)信息。
● -I:大写的I,在编译时指定头文件的路径。
● -l:小写的l(like首字母),指定程序使用的函数库。
● -L:大写的L(like首字母),指定函数库的路径。
使用make
在同一级目录下编写makefile
Git代码管理
Git | 且任容枯 (qierenrongku.github.io)
计算机体系结构和CPU工作原理
嵌入式开发很大一部分工作跟底层紧密相关,如系统移植、BSP开发、驱动开发等,和芯片、硬件打交道的地方比较多
BSP开发(BSP,全称Board Support Package,汉语意思即_板级支持包_,BSP工程师,顾名思义就是负责板级支持包的开发、调试和维护工作)
cpu内部结构及工作原理
CPU设计流程
- 设计芯片规格:设计出芯片基本的框架、功能,进行模块划分
- HDL代码实现:使用VHDL或Verilog硬件描述语言把要实现的硬件功能描述出来,接着通过EDA工具不断仿真、修改和验证,直到芯片的逻辑功能完全正确。这种仿真我们一般称为前端仿真,简称前仿。前仿只验证芯片的逻辑功能是否正确,不考虑延时等因素。芯片公司内部一般也会设有数字IC验证工程师岗位,招聘工程师专门从事这个工作。加法器
module adder(
input x, y,
output carry, out
);
assign {out, carry} = x + y;
endmodule
- 逻辑综合:通过EDA工具就可以将HDL代码转换成具体的逻辑门电路
- 仿真验证:通过逻辑综合生成的门级电路,已经包含了延时等各种信息,接下来还需要对这些门级电路进行进一步的静态时序分析和验证。为了提高工作效率,除了使用仿真软件,有时候也会借助FPGA平台进行验证。前端仿真发生在逻辑综合之前,专注于验证电路的逻辑功能是否正确;逻辑综合后的仿真,一般称为后端仿真,简称后仿。后端仿真会考虑延时等因素。后端仿真通过后,从HDL代码到生成门级网表电路,整个芯片的前端设计就结束了。
- 后端设计:对门级网表电路不断完善和优化,将其进一步设计成物理版图,也就是芯片代工厂做掩膜版需要的电路版图
1、 DFT:Design For Test,可测试性设计。芯片内部一般会自带测试电路,如插入扫描链、引出JTAG调试接口。2、 布局规划:各个IP电路模块的摆放位置、时钟线综合、信号线的布局等。3、物理版图验证:检查设计规则、连线宽度、间距是否符合工艺要求和电气规则
计算机体系结构
混合结构:SoC芯片内部的Cache层采用哈弗架构,集成了指令Cache和数据Cache;SoC芯片外部则采用冯·诺依曼架构,工程实现简单;先到这两个Cache中看看要读取的数据是不是已经缓存到这里了,如果没有缓存命中,再到内存中读取
CPU性能提升:流水线
总线和地址
地址:CPU管脚发出的信号,也就是存储单元对应的编号,即地址(CPU管脚发出的一组地址控制信号,在经过译码器译码)
总线:总线其实就是各种数字信号的集合,包括地址信号、数据信号、控制信号等
指令集和微架构
图灵原型机的基本思想是:任何复杂的运算都可以分解为有限个基本指令的组合来完成。我们的CPU在设计的时候就是这么干的,只支持有限个基本的运算指令,如加、减、乘、与、或、非、移位、跳转等。这些指令通过不同的组合,可以构成不同的指令序列(程序),实现不同的逻辑功能。
不同架构的处理器支持的指令类型是不同的。ARM架构的处理器只支持ARM指令,X86架构的处理器只支持X86指令。如果你在ARM架构的处理器上运行X86指令,就无法运行,报未定义指令的错误,因为ARM架构的处理器只支持ARM指令集中定义的指令。CPU支持的有限个指令的集合,我们称之为指令集。
微架构,对应的英文是Microarchitecture,也就是处理器架构。集成电路工程师在设计处理器时,会按照指令集规定的指令,设计具体的译码和运算电路来支持这些指令的运行;指令集在CPU处理器内部的具体硬件电路的实现,我们就称为微架构。一套相同的指令集,可以由不同形式的电路实现,可以有不同的微架构。
ARM体系结构与汇编语言
计算机的指令集一般可分为4种:复杂指令集(CISC)、精简指令集(RISC)、显式并行指令集(EPIC)和超长指令字指令集(VLIW)
ARM寻址方式
- 寄存器寻址:通过寄存器名就可以直接对寄存器中的数据进行读写
- 立即数寻址:在立即数寻址中,ARM指令中的操作数为一个常数。立即数以#为前缀,0x前缀表示该立即数为十六进制,不加前缀默认是十进制。
- 寄存器偏移寻址:
- 寄存器间接寻址:
- 基址寻址:
- 多寄存器寻址:
- 相对寻址:
c和汇编混合编程
下载交叉编译链 gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz
wget https://releases.linaro.org/components/toolchain/binaries/4.9-2017.01/arm-linux-gnueabihf/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz
创建一个文件夹并把编译链解压到该文件夹
sudo mkdir /usr/local/arm
sudo tar -vxf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz -C /usr/local/arm
配置环境变量
1、打开编辑~/.bashrc 文件
sudo vim ~/.bashrc
2、在最底部添加以下内容
export PATH=$PATH:/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin
3、使环境变量立即生效
source ~/.bashrc
安装其他库
sudo apt-get install lsb-core lib32stdc++6
验证编译器是否安装成功(查看版本号命令)
arm-linux-gnueabihf-gcc -v
程序的编译、链接、安装和运行
readelf -h a.out
ELF 头:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
类别: ELF32
数据: 2 补码,小端序 (little endian)
Version: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: EXEC (可执行文件)
系统架构: ARM
版本: 0x1
入口点地址: 0x10328
程序头起点: 52 (bytes into file)
Start of section headers: 68216 (bytes into file)
标志: 0x5000200, Version5 EABI, soft-float ABI
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 9
Size of section headers: 40 (bytes)
Number of section headers: 29
Section header string table index: 28
readelf -S a.out
There are 29 section headers, starting at offset 0x10a78:
节头:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 00010154 000154 000013 00 A 0 0 1
[ 2] .note.gnu.bu[...] NOTE 00010168 000168 000024 00 A 0 0 4
[ 3] .note.ABI-tag NOTE 0001018c 00018c 000020 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 000101ac 0001ac 00002c 04 A 5 0 4
[ 5] .dynsym DYNSYM 000101d8 0001d8 000050 10 A 6 1 4
[ 6] .dynstr STRTAB 00010228 000228 00004e 00 A 0 0 1
[ 7] .gnu.version VERSYM 00010276 000276 00000a 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 00010280 000280 000030 00 A 6 1 4
[ 9] .rel.dyn REL 000102b0 0002b0 000008 08 A 5 0 4
[10] .rel.plt REL 000102b8 0002b8 000020 08 AI 5 21 4
[11] .init PROGBITS 000102d8 0002d8 00000c 00 AX 0 0 4
[12] .plt PROGBITS 000102e4 0002e4 000044 04 AX 0 0 4
[13] .text PROGBITS 00010328 000328 0001b4 00 AX 0 0 4
[14] .fini PROGBITS 000104dc 0004dc 000008 00 AX 0 0 4
[15] .rodata PROGBITS 000104e4 0004e4 000134 00 A 0 0 4
[16] .ARM.exidx ARM_EXIDX 00010618 000618 000008 00 AL 13 0 4
[17] .eh_frame PROGBITS 00010620 000620 000004 00 A 0 0 4
[18] .init_array INIT_ARRAY 0002ff10 00ff10 000004 04 WA 0 0 4
[19] .fini_array FINI_ARRAY 0002ff14 00ff14 000004 04 WA 0 0 4
[20] .dynamic DYNAMIC 0002ff18 00ff18 0000e8 08 WA 6 0 4
[21] .got PROGBITS 00030000 010000 000024 04 WA 0 0 4
[22] .data PROGBITS 00030024 010024 000010 00 WA 0 0 4
[23] .bss NOBITS 00030034 010034 00000c 00 WA 0 0 4
[24] .comment PROGBITS 00000000 010034 000025 01 MS 0 0 1
[25] .ARM.attributes ARM_ATTRIBUTES 00000000 010059 00002a 00 0 0 1
[26] .symtab SYMTAB 00000000 010084 0006d0 10 27 85 4
[27] .strtab STRTAB 00000000 010754 00021d 00 0 0 1
[28] .shstrtab STRTAB 00000000 010971 000105 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
D (mbind), y (purecode), p (processor specific)
一个可执行文件通常由不同的段(section)构成:代码段、数据段、BSS段、只读数据段等。每个section用一个section header来描述,包括段名、段的类型、段的起始地址、段的偏移和段的大小等。一个可执行文件中的每一个section都有一个section header,将这些section headers集中放到一起,就是section header table,翻译成中文就是节头表。我们可以使用readelf-S命令来查看一个可执行文件的节头表。
一个可执行文件的基本构成:一个可执行文件由一系列section组成,section header table自身也是以一个section的形式存储在可执行文件中的。section header table里的各个section header用来描述各个section的名称、类型、起始地址、大小等信息。除此之外,可执行文件还会有一个文件头ELF header,用来描述文件类型、要运行的处理器平台、入口地址等信息。当程序运行时,加载器会根据此文件头来获取可执行文件的一些信息。
函数翻译成二进制指令放在代码段中,初始化的全局变量和静态局部变量放在数据段中,未初始化的全局变量和静态变量会放置在BSS段
程序中定义的一些字符串、printf函数打印的字符串常量则放置在只读数据段.rodata
预处理
通过#pragma预处理命令可以设定编译器的状态,指示编译器完成一些特定的动作。
● #pragma pack([n]):指示结构体和联合成员的对齐方式。
● #pragma message("string"):在编译信息输出窗口打印自己的文本信息。
● #pragma warning:有选择地改变编译器的警告信息行为。
● #pragma once:在头文件中添加这条指令,可以防止头文件多次编译。
符号表与重定位
readelf -s a.out
Symbol table '.dynsym' contains 5 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
2: 00000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.4 (3)
3: 00000000 0 FUNC GLOBAL DEFAULT UND abort@GLIBC_2.4 (3)
4: 00000000 0 FUNC GLOBAL DEFAULT UND _[...]@GLIBC_2.34 (2)
Symbol table '.symtab' contains 109 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00010154 0 SECTION LOCAL DEFAULT 1 .interp
2: 00010168 0 SECTION LOCAL DEFAULT 2 .note.gnu.build-id
3: 0001018c 0 SECTION LOCAL DEFAULT 3 .note.ABI-tag
4: 000101ac 0 SECTION LOCAL DEFAULT 4 .gnu.hash
5: 000101d8 0 SECTION LOCAL DEFAULT 5 .dynsym
6: 00010228 0 SECTION LOCAL DEFAULT 6 .dynstr
7: 00010276 0 SECTION LOCAL DEFAULT 7 .gnu.version
8: 00010280 0 SECTION LOCAL DEFAULT 8 .gnu.version_r
9: 000102b0 0 SECTION LOCAL DEFAULT 9 .rel.dyn
10: 000102b8 0 SECTION LOCAL DEFAULT 10 .rel.plt
11: 000102d8 0 SECTION LOCAL DEFAULT 11 .init
12: 000102e4 0 SECTION LOCAL DEFAULT 12 .plt
13: 00010328 0 SECTION LOCAL DEFAULT 13 .text
14: 000104dc 0 SECTION LOCAL DEFAULT 14 .fini
15: 000104e4 0 SECTION LOCAL DEFAULT 15 .rodata
16: 00010618 0 SECTION LOCAL DEFAULT 16 .ARM.exidx
17: 00010620 0 SECTION LOCAL DEFAULT 17 .eh_frame
18: 0002ff10 0 SECTION LOCAL DEFAULT 18 .init_array
19: 0002ff14 0 SECTION LOCAL DEFAULT 19 .fini_array
20: 0002ff18 0 SECTION LOCAL DEFAULT 20 .dynamic
21: 00030000 0 SECTION LOCAL DEFAULT 21 .got
22: 00030024 0 SECTION LOCAL DEFAULT 22 .data
23: 00030034 0 SECTION LOCAL DEFAULT 23 .bss
24: 00000000 0 SECTION LOCAL DEFAULT 24 .comment
25: 00000000 0 SECTION LOCAL DEFAULT 25 .ARM.attributes
26: 00000000 0 FILE LOCAL DEFAULT ABS crt1.o
27: 0001018c 0 NOTYPE LOCAL DEFAULT 3 $d
28: 0001018c 32 OBJECT LOCAL DEFAULT 3 __abi_tag
29: 00010328 0 NOTYPE LOCAL DEFAULT 13 $a
30: 00010364 0 NOTYPE LOCAL DEFAULT 13 $d
31: 00010618 0 NOTYPE LOCAL DEFAULT 16 $d
32: 000104e4 0 NOTYPE LOCAL DEFAULT 15 $d
33: 00030024 0 NOTYPE LOCAL DEFAULT 22 $d
34: 00000000 0 FILE LOCAL DEFAULT ABS crti.o
35: 0001036c 0 NOTYPE LOCAL DEFAULT 13 $a
36: 0001036c 0 FUNC LOCAL DEFAULT 13 call_weak_fn
37: 00010388 0 NOTYPE LOCAL DEFAULT 13 $d
38: 000102d8 0 NOTYPE LOCAL DEFAULT 11 $a
39: 000104dc 0 NOTYPE LOCAL DEFAULT 14 $a
40: 00000000 0 FILE LOCAL DEFAULT ABS crtn.o
41: 000102e0 0 NOTYPE LOCAL DEFAULT 11 $a
42: 000104e0 0 NOTYPE LOCAL DEFAULT 14 $a
43: 00000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
44: 000104e8 0 NOTYPE LOCAL DEFAULT 15 $d
45: 000104e8 0 OBJECT LOCAL DEFAULT 15 all_implied_fbits
46: 00010390 0 NOTYPE LOCAL DEFAULT 13 $a
47: 00010390 0 FUNC LOCAL DEFAULT 13 deregister_tm_clones
48: 000103b0 0 NOTYPE LOCAL DEFAULT 13 $d
49: 000103bc 0 NOTYPE LOCAL DEFAULT 13 $a
50: 000103bc 0 FUNC LOCAL DEFAULT 13 register_tm_clones
51: 000103e8 0 NOTYPE LOCAL DEFAULT 13 $d
52: 00030028 0 NOTYPE LOCAL DEFAULT 22 $d
53: 000103f4 0 NOTYPE LOCAL DEFAULT 13 $a
54: 000103f4 0 FUNC LOCAL DEFAULT 13 __do_global_dtors_aux
55: 00010418 0 NOTYPE LOCAL DEFAULT 13 $d
56: 00030034 1 OBJECT LOCAL DEFAULT 23 completed.0
57: 0002ff14 0 NOTYPE LOCAL DEFAULT 19 $d
58: 0002ff14 0 OBJECT LOCAL DEFAULT 19 __do_global_dtor[...]
59: 0001041c 0 NOTYPE LOCAL DEFAULT 13 $a
60: 0001041c 0 FUNC LOCAL DEFAULT 13 frame_dummy
61: 0002ff10 0 NOTYPE LOCAL DEFAULT 18 $d
62: 0002ff10 0 OBJECT LOCAL DEFAULT 18 __frame_dummy_in[...]
63: 00030034 0 NOTYPE LOCAL DEFAULT 23 $d
64: 00000000 0 FILE LOCAL DEFAULT ABS main.c
65: 0003002c 0 NOTYPE LOCAL DEFAULT 22 $d
66: 00030038 0 NOTYPE LOCAL DEFAULT 23 $d
67: 00010578 0 NOTYPE LOCAL DEFAULT 15 $d
68: 00010420 0 NOTYPE LOCAL DEFAULT 13 $a
69: 00010474 0 NOTYPE LOCAL DEFAULT 13 $d
70: 0003003c 4 OBJECT LOCAL DEFAULT 23 uninit_local_val.1
71: 00030030 4 OBJECT LOCAL DEFAULT 22 local_val.0
72: 00000000 0 FILE LOCAL DEFAULT ABS sub.c
73: 0001047c 0 NOTYPE LOCAL DEFAULT 13 $a
74: 00000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
75: 00010588 0 NOTYPE LOCAL DEFAULT 15 $d
76: 00010588 0 OBJECT LOCAL DEFAULT 15 all_implied_fbits
77: 00010620 0 NOTYPE LOCAL DEFAULT 17 $d
78: 00010620 0 OBJECT LOCAL DEFAULT 17 __FRAME_END__
79: 00000000 0 FILE LOCAL DEFAULT ABS
80: 0002ff18 0 OBJECT LOCAL DEFAULT 20 _DYNAMIC
81: 00030000 0 OBJECT LOCAL DEFAULT 21 _GLOBAL_OFFSET_TABLE_
82: 000102e4 0 NOTYPE LOCAL DEFAULT 12 $a
83: 000102f4 0 NOTYPE LOCAL DEFAULT 12 $d
84: 000102f8 0 NOTYPE LOCAL DEFAULT 12 $a
85: 00030038 4 OBJECT GLOBAL DEFAULT 23 uninit_val
86: 00000000 0 FUNC GLOBAL DEFAULT UND __libc_start_mai[...]
87: 00030024 0 NOTYPE WEAK DEFAULT 22 data_start
88: 0001047c 48 FUNC GLOBAL DEFAULT 13 add
89: 00000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.4
90: 00030034 0 NOTYPE GLOBAL DEFAULT 23 __bss_start__
91: 00030040 0 NOTYPE GLOBAL DEFAULT 23 _bss_end__
92: 00030034 0 NOTYPE GLOBAL DEFAULT 22 _edata
93: 000104dc 0 FUNC GLOBAL HIDDEN 14 _fini
94: 00030040 0 NOTYPE GLOBAL DEFAULT 23 __bss_end__
95: 0003002c 4 OBJECT GLOBAL DEFAULT 22 global_val
96: 00030024 0 NOTYPE GLOBAL DEFAULT 22 __data_start
97: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
98: 00030028 0 OBJECT GLOBAL HIDDEN 22 __dso_handle
99: 000104e4 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
100: 00030040 0 NOTYPE GLOBAL DEFAULT 23 _end
101: 00010328 0 FUNC GLOBAL DEFAULT 13 _start
102: 00030040 0 NOTYPE GLOBAL DEFAULT 23 __end__
103: 00030034 0 NOTYPE GLOBAL DEFAULT 23 __bss_start
104: 00010420 92 FUNC GLOBAL DEFAULT 13 main
105: 00030034 0 OBJECT GLOBAL HIDDEN 22 __TMC_END__
106: 000104ac 48 FUNC GLOBAL DEFAULT 13 sub
107: 00000000 0 FUNC GLOBAL DEFAULT UND abort@GLIBC_2.4
108: 000102d8 0 FUNC GLOBAL HIDDEN 11 _init
符号表主要用来保存源程序中各种符号的信息,包括符号的地址、类型、占用空间的大小
符号的类型主要有以下几种:
● OBJECT:对象类型,一般用来表示我们在程序中定义的变量。
● FUNC:关联的是函数名或其他可引用的可执行代码。
● FILE:该符号关联的是当前目标文件的名称。
● SECTION:表明该符号关联的是一个section,主要用来重定位。
● COMMON:表明该符号是一个公用块数据对象,是一个全局弱符号,在当前文件中未分配空间。
● TLS:表明该符号对应的变量存储在线程局部存储中。
● NOTYPE:未指定类型,或者目前还不知道该符号类型。
程序的安装
软件安装的过程其实就是将一个可执行文件安装到ROM的过程
在Linux环境下,我们一般将可执行文件直接复制到系统的官方路径/bin、/sbin、/usr/bin下,程序运行时直接从这些系统默认的路径下去查找可执行文件,将其加载到内存运行。
编写代码
#include <stdio.h>
int main(void){
printf("hello world\n");
return 0;
}
gcc main.c -o a.out
制作软件包
Linux操作系统一般可分为两派:Redhat系和Debian系。Redhat系使用RPM包管理机制,而Debian系,像Debian、Ubuntu等操作系统则使用deb包管理机制
一个成熟的发布软件里,除了可执行文件,一般还会有配套的文档说明、图标等,程序开发者将这些文档一起打包发布,提供自动安装的功能,更方便用户下载和安装。在制作deb包时,除了可执行文件,还需要一些控制信息来描述这个安装包,如软件的版本、作者、安装包要安装的路径等,这些控制信息放在一个叫作control的文件里
qrrk@qrrk-virtual-machine:~/桌面$ tree helloworld/
helloworld/
├── DEBIAN
│ └── control
└── usr
└── local
└── bin
└── helloworld # 可执行文件
# control文件
package:helloworld
version:1.0
architecture:i386
maintainer:wit
description: deb package demo
qrrk@qrrk-virtual-machine:~/桌面$ dpkg -b helloworld/ helloworld_1.0_i386.deb
qrrk@qrrk-virtual-machine:~/桌面$ sudo dpkg -i helloworld_1.0_i386.deb
正在选中未选择的软件包 helloworld:i386。
(正在读取数据库 ... 系统当前共安装有 218803 个文件和目录。)
准备解压 helloworld_1.0_i386.deb ...
正在解压 helloworld:i386 (1.0) ...
正在设置 helloworld:i386 (1.0) ...
qrrk@qrrk-virtual-machine:~/桌面$ helloworld
hello world
qrrk@qrrk-virtual-machine:~/桌面$ whereis helloworld
helloworld: /usr/local/bin/helloworld
# dpkg -P helloworld 卸载程序及配置文件
# dpkg -r helloworldr 卸载helloworld程序
main函数分析
编译器在编译一个工程时,默认的程序入口是_start符号,而不是main。符号main是一个约定符号,它用来告诉编译器在一个项目中哪里是程序的入口点。程序员在开发一个项目时,也会遵守这个约定,使用main()函数作为项目的入口函数。其实在main()函数运行之前,已经有“先头部队”代码提前运行了:它们主要完成运行main()函数之前的一些初始化工作,如初始化堆栈指针等
● C语言运行的基本堆栈环境、进程环境。
● 动态库的加载、释放、初始化、清理等工作。
● 向main()函数传参argc、argv,调用main()函数执行。
● 在main()函数退出后,调用exit()函数,结束进程的运行
在嵌入式系统裸机环境下,系统上电后要初始化时钟、内存,然后设置堆栈指针,而在普通的操作系统环境下,内存等各种硬件设备已经工作,堆栈环境也已经初始化完毕,不需要做这一部分工作了,保存一些上下文环境后就可以直接跳到第一个C语言入口函数:__libc_start_main
__libc_start_main函数大致流程如下:首先设置程序运行的进程环境,加载共享库,解析用户输入的参数,将参数传递给main()函数,最后调用main()函数运行。main()函数运行结束后,再调用exit函数结束整个进程
链接静态库
test.c
int add(int a, int b){
return a + b;
}
int sub(int a, int b){
return a - b;
}
int mul(int a, int b){
return a * b;
}
int div(int a, int b){
return a / b;
}
main.c
#include <stdio.h>
int add(int, int);
int main(void){
int sum = 0;
sum = add(1,2);
printf("sum=%d\n", sum);
return 0;
}
qrrk@qrrk-virtual-machine:~/桌面/study6$ cd ../study7
qrrk@qrrk-virtual-machine:~/桌面/study7$ gcc -c test.c
qrrk@qrrk-virtual-machine:~/桌面/study7$ ar rcs libtest.a test.o
qrrk@qrrk-virtual-machine:~/桌面/study7$ gcc main.c -L. -ltest
qrrk@qrrk-virtual-machine:~/桌面/study7$ ./a.out
sum=3
使用ar命令制作静态库时,一些常用的参数介绍如下。
● -c:禁止在创建库时产生的正常消息。
● -r:如果指定的文件已经在库中存在,则替换它。
● -s:无论库是否更新都强制重新生成新的符号表。
● -d:从库中删除指定的文件。
● -o:对压缩文档成员进行排序。
● -q:向库中追加指定文件。
● -t:打印库中的目标文件。
● -x:解压库中的目标文件。
动态链接
静态链接的缺点:生成的可执行文件体积较大,当多个程序引用相同的公共代码时,这些公共代码会多次加载到内存,浪费内存资源。
qrrk@qrrk-virtual-machine:~/桌面/study7$ gcc -fPIC -shared add.c sub.c mul.c div.c -o libtest.so
qrrk@qrrk-virtual-machine:~/桌面/study7$ gcc main.c libtest.so
qrrk@qrrk-virtual-machine:~/桌面/study7$ ./a.out
./a.out: error while loading shared libraries: libtest.so: cannot open shared object file: No such file or directory
qrrk@qrrk-virtual-machine:~/桌面/study7$ sudo cp libtest.so /usr/lib
[sudo] qrrk 的密码:
qrrk@qrrk-virtual-machine:~/桌面/study7$ ./a.out
sum=3
可执行文件a.out是采用动态链接生成的,所以在运行a.out之前,libtest.so这个动态链接库要放到/lib、/usr/lib等系统默认的库路径下,否则a.out就会动态链接失败,无法正常运行
在Linux环境下,当我们运行一个程序时,操作系统首先会给程序fork一个子进程,接着动态链接器被加载到内存,操作系统将控制权交给动态链接器,让动态链接器完成动态库的加载和重定位操作,最后跳转到要运行的程序
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~