嵌入式C语言一
且任容枯 Lv4

工具安装

ubuntu 安装vim

1
2
apt-get install vim
sudo apt-get install vim

ubuntu 安装gcc

1
2
apt-get install gcc
sudo apt-get install gcc

一个简单的C程序

1
2
3
4
5
#include <stdio.h>
int main(void){
printf("hello world\n");
return 0;
}
1
2
3
4
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
Pastedimage20230517222907
在同一级目录下编写makefile
Pastedimage20230517223409
Git代码管理
Git | 且任容枯 (qierenrongku.github.io)

计算机体系结构和CPU工作原理

嵌入式开发很大一部分工作跟底层紧密相关,如系统移植、BSP开发、驱动开发等,和芯片、硬件打交道的地方比较多
BSP开发(BSP,全称Board Support Package,汉语意思即_板级支持包_,BSP工程师,顾名思义就是负责板级支持包的开发、调试和维护工作)

cpu内部结构及工作原理

Pastedimage20230517224041

CPU设计流程

Pastedimage20230517224334

  • 设计芯片规格:设计出芯片基本的框架、功能,进行模块划分
  • HDL代码实现:使用VHDL或Verilog硬件描述语言把要实现的硬件功能描述出来,接着通过EDA工具不断仿真、修改和验证,直到芯片的逻辑功能完全正确。这种仿真我们一般称为前端仿真,简称前仿。前仿只验证芯片的逻辑功能是否正确,不考虑延时等因素。芯片公司内部一般也会设有数字IC验证工程师岗位,招聘工程师专门从事这个工作。加法器
    1
    2
    3
    4
    5
    6
    module adder(
    input x, y,
    output carry, out
    );
    assign {out, carry} = x + y;
    endmodule
  • 逻辑综合:通过EDA工具就可以将HDL代码转换成具体的逻辑门电路
    Pastedimage20230517224926
  • 仿真验证:通过逻辑综合生成的门级电路,已经包含了延时等各种信息,接下来还需要对这些门级电路进行进一步的静态时序分析和验证。为了提高工作效率,除了使用仿真软件,有时候也会借助FPGA平台进行验证。前端仿真发生在逻辑综合之前,专注于验证电路的逻辑功能是否正确;逻辑综合后的仿真,一般称为后端仿真,简称后仿。后端仿真会考虑延时等因素。后端仿真通过后,从HDL代码到生成门级网表电路,整个芯片的前端设计就结束了。
  • 后端设计:对门级网表电路不断完善和优化,将其进一步设计成物理版图,也就是芯片代工厂做掩膜版需要的电路版图
    1、 DFT:Design For Test,可测试性设计。芯片内部一般会自带测试电路,如插入扫描链、引出JTAG调试接口。2、 布局规划:各个IP电路模块的摆放位置、时钟线综合、信号线的布局等。3、物理版图验证:检查设计规则、连线宽度、间距是否符合工艺要求和电气规则

    计算机体系结构

    Pastedimage20230517225616Pastedimage20230517225728

混合结构:SoC芯片内部的Cache层采用哈弗架构,集成了指令Cache和数据Cache;SoC芯片外部则采用冯·诺依曼架构,工程实现简单;先到这两个Cache中看看要读取的数据是不是已经缓存到这里了,如果没有缓存命中,再到内存中读取
Pastedimage20230517230032

CPU性能提升:流水线

Pastedimage20230517230446

总线和地址

地址: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
    1
    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
    创建一个文件夹并把编译链解压到该文件夹
    1
    2
    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
    2
    3
    4
    5
    6
    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
    安装其他库
    1
    sudo apt-get install lsb-core lib32stdc++6
    验证编译器是否安装成功(查看版本号命令)
    1
    arm-linux-gnueabihf-gcc -v

    程序的编译、链接、安装和运行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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,用来描述文件类型、要运行的处理器平台、入口地址等信息。当程序运行时,加载器会根据此文件头来获取可执行文件的一些信息。

Pastedimage20230522230824

函数翻译成二进制指令放在代码段中,初始化的全局变量和静态局部变量放在数据段中,未初始化的全局变量和静态变量会放置在BSS段
程序中定义的一些字符串、printf函数打印的字符串常量则放置在只读数据段.rodata

预处理

通过#pragma预处理命令可以设定编译器的状态,指示编译器完成一些特定的动作。
● #pragma pack([n]):指示结构体和联合成员的对齐方式。
● #pragma message(“string”):在编译信息输出窗口打印自己的文本信息。
● #pragma warning:有选择地改变编译器的警告信息行为。
● #pragma once:在头文件中添加这条指令,可以防止头文件多次编译。

符号表与重定位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
 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下,程序运行时直接从这些系统默认的路径下去查找可执行文件,将其加载到内存运行。

编写代码

1
2
3
4
5
6
7
#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的文件里

1
2
3
4
5
6
7
8
qrrk@qrrk-virtual-machine:~/桌面$ tree helloworld/
helloworld/
├── DEBIAN
│ └── control
└── usr
└── local
└── bin
└── helloworld # 可执行文件
1
2
3
4
5
6
# control文件
package:helloworld
version:1.0
architecture:i386
maintainer:wit
description: deb package demo
1
2
3
4
5
6
7
8
9
10
11
12
13
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int add(int, int);

int main(void){
int sum = 0;
sum = add(1,2);
printf("sum=%d\n", sum);
return 0;
}
1
2
3
4
5
6
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:解压库中的目标文件。

动态链接

静态链接的缺点:生成的可执行文件体积较大,当多个程序引用相同的公共代码时,这些公共代码会多次加载到内存,浪费内存资源。

1
2
3
4
5
6
7
8
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一个子进程,接着动态链接器被加载到内存,操作系统将控制权交给动态链接器,让动态链接器完成动态库的加载和重定位操作,最后跳转到要运行的程序