第一章 计算机系统漫游
第二章 信息的表示和处理
第三章 程序的机器级表示
第四章 处理器体系结构
第六章 存储器结构层次
第七章 链接
第一章 Linux快速入门
第二章 Linux基础命令
第三章 Linux下C基础编程
对于所学知识进行如下整理:
总线——贯穿整个系统的一组电子管道,它携带信息字节并负责在各个部件间传递。通常总线被设计成传送定长的字节块,也就是字(word)。各个系统中字长不尽相同。
I/O设备
I/O设备是系统与外界的联系通道。
控制器是I/O设备本身中或是系统的主印刷电路板(主板)上的芯片组,而适配器则是一块插在主板插槽上的卡。
主存——临时存储设备,在处理器执行程序时,用来存放程序和程序处理的数据。物理上来说,主存是由一组DRAM芯片组成的;逻辑上来说,存储器由一个线性的字节数据组成,每个字节都有自己惟一的地址(数组索引),这些地址以0开始的。一般来说,组成程序的每条机器指令都由不定量的字节构成。
处理器——CPU,是解释(或执行)存储在主存中指令的引擎。处理器的核心是一个被称为程序计数器(PC)的字长大小的存储设备(或寄存器)。在任何一个时间点上,PC都指向主存中的某条机器语言指令。
CPU在指令要求下可能会执行这些操作:
加载:从主存拷贝一个字节或者一个字到寄存器,覆盖寄存器原来的内容
存储:从寄存器拷贝一个字节或者一个字到主存的某个位置,覆盖这个位置上原来的内容
更新:拷贝两个寄存器的内容到ALU,ALU将两个字相加,并将结果存放到一个寄存器中,覆盖该寄存器中原来的内容
I/O读:从一个I/O设备中拷贝一个字节或者一个字到一个寄存器
I/O写:从一个寄存器中拷贝一个字节或者一个字到一个I/O设备 转移:从指令本身中抽取一个字,并将这个字拷贝到程序计数器中,覆盖PC中原来的值
操作系统内核是应用程序和硬件之间的媒介。它提供三个基本的抽象概念:文件是对I/O设备的抽象概念;虚拟存储器是对主存和磁盘的抽象概念;进程是处理器、主存和I/O设备的抽象概念。
进程是操作系统对运行程序的一种抽象。并发运行实际上是一个进程的指令和另一个进程的指令交错执行的,操作系统实现这种交错执行的机制称为上下文切换。操作系统保存进程运行所需的所有状态信息,这些状态称为上下文。
一个进程可由多个线程组成。每个线程都运行在进程的上下文中,并共享同样的代码和全局数据。
虚拟存储器 虚拟存储器是抽象概念,它为每个进程提供一个假象,好像每个进程都在独占地使用主存。每个进程看到的存储器都是一致的,称之为虚拟地址空间。
其基本思想是把一个进程虚拟存储器的内容存储在磁盘上,然后用主存作为磁盘的高速缓冲。
文件——是字节序列。每个I/O设备,包括磁盘,键盘,显示器,网络,等可以看成是文件。
我们重点研究三种数字:无符号数、补码和浮点数。
机器级程序将存储器视为一个非常大的字节数组,称为虚拟存储器(virtual memory)。存储器的每个字节都由一个唯一的数字来标识,称为地址(address),所有可能地址的集合就称为虚拟地址空间(virtual address space)。虚拟地址空间是一个展现给机器级程序的概念性映像(image),具体的实现使用的是随机访问存储器RAM,磁盘存储,特殊硬件和OS软件的结合,来为程序提供一个看上去统一的字节数组。
指值有两个方面:它的值和它的类型。它的值表示的是某个对象的位置,而它的类型表示那个位置上所存储对象的类型(如int,float)。
十六进制表示法 以0x或0X开头表示,字符A-F可大写可小写。
!掌握二进制和十六进制之间的转换,二进制四位与十六进制一位相对应。
每台计算机都有一个字长,指明整数和指针数据的标称大小。虚拟地址是以字来编码的。对于一个字长为n位的机器来说,虚拟地址的范围为0~2n-1,程序最多访问2n个字节。 如32位字长的计算机上,虚拟地址空间为4GB。
寻址和字节顺序
计算机和编译器使用不同的方式来编码数字。 (1)小端:低地址存放低字节。(2)大端:高地址存放低字节。
表示字符串、代码 a-z的ASC||码为0x61-0x7A。
布尔代数 用0、1表示真假,以研究逻辑推理的基本原则。 有:~=NOT;&=AND;|=OR;^=异或。
C语言中的位级运算
!!确定一个位级表达式的结果最好的方法,就是将十六进制的参数扩展成二进制标识并执行二进制运算,然后再转换回十六进制。
C语言中的逻辑运算
有:||、&&、!,分别对应于OR、AND和NOT运算。
注意:逻辑运算认为所有非零的参数都表示TRUE,而参数0表示FALSE。
C中的移位(移位运算从左至右结合。)
1)机器支持两种形式的右移:逻辑的算术的。逻辑右移k位在左端补k个0,[0,...0,xn-1,xn-2,...]。算术右移是在左端补k个最高有效位的拷贝,[xn-1,...xn-1,xn-2,...] 对于无符号数据,右移必须是逻辑的,有符号数则两者都可以,一般机器实现中实现为算术右移。
2)左移,就是移位后,在右边补0。
要将一个无符号数转换为一个更大的数据类型,只要简单的在开头添加0;这种运算称为0扩展(zero extension)。要将一个二进制补码数字转换为一个更大的数据类型,执行符号扩展(sign extension),在表示中添加最高有效位的值。
在数据转换中,由大数据转换成小数据可能发生截断。弃高k位。对于一个无符号数x,截断它到k位的结果就相当于计算x mod 2k。
范围0,≤x,y≤2w-1内的整数x,y,可以被表示成w位的无符号数字,它们的乘积x.y的取值范围为0~(2w-1)2,这可能需要2w位来表示。
在大多数机器上,整数乘法指令相当慢,需要12或更多时钟周期,其他整数运算,如加法,减法,移位和位级运算,只需要1个时钟周期。因此编译器使用的一项重要的优化就是试着用移位和加法运算的组合来代替乘以常数因子的乘法。 整数除法比乘法更慢,需要30或者更多的时钟周期,除以2的幂也可以用移位运算来实现。我们可右移。对于无符号数和二进制补码数,分别使用逻辑移位和算术移位来达到目的。
在当前的计算机中,整数是以补码表示的。
二进制小数 小数的二进制表示,二进制点左边的权形如2^i,而右边的数字的权形如1/2^i。
IEEE浮点表示
IA32处理器有特别的存储器元素,称为寄存器,当计算或使用浮点数时,用来保存浮点值。浮点数的取反就是对符号位取反。
IEEE标准定义了一些合理规则。定义1/-0将产生-∞,而定义1/+0会产生+∞。
IEEE标准中指定浮点运算行为方法的一个优势在于,它可以独立于任何具体的硬件或者软件实现。
浮点加法不具有结合性,这是缺少的最重要的群属性;浮点加法满足了单调性属性——如果a>=b,那么对于任何a\b以及x的值,除了NaN,都有x+a>=x+b。无符号或补码加法不具有这个实数(和整数)加法的属性。
Intel现在称其指令集为IA32,即Intel 32位体系结构(Intel Architecture 32-bit),这个处理器也俗称为“x86”。
Linux使用了平面寻址方式(flat addressing),在这种寻址方式中,程序员将整个存储空间看做一个大的字节数组。在平面寻址中,对特殊寄存器的需求已经大为降低了。在大多数情况下,前六个寄存器都可以看作是通用寄存器,对它们使用的没有限制。
机器级代码
两种抽象:
指令集体系结构ISA——机器级程序的格式和行为,定义了处理器状态、指令的格式以及每条指令对状态的影响。
机器级结构使用的存储地址虚拟地址,存储器系统的实际实现是将多个硬件存储器和操作系统软件组合起来。
一个过程调用包括将数据(以过程参数和返回值的形式)和控制从代码的一部分传递到另一部分。数据传递、局部变量的分配和释放是通过操纵程序栈来实现的。
反汇编器——查看目标代码文件的内容 带-d命令行标志的程序objdump可以实现:objdump –d code.o
过程
IA32程序用程序栈来支持过程调用。栈用来传递过程参数、存储返回信息、保存寄存器以供以后恢复之用,以及用于本地存储。为单个过程分配的那部分栈称为栈帧。栈帧的最顶端是以两个指针定界的,寄存器%ebp作为帧指针,而寄存器%esp作为栈指针。
esi、edi可以用来操纵数组,esp ebp用来操纵栈帧。 对于寄存器,特别是通用寄存器中的eax,ebx,ecx,edx,大家要理解32位的eax,16位的ax,8位的ah,al都是独立的,我们通过下面例子说明:
假定当前是32位x86机器,eax寄存器的值为0x8226,执行完addw $0x8266, %ax指令后eax的值是多少?
解析:0x8226+0x826=0x1044c, ax是16位寄存器,出现溢出,最高位的1会丢掉,剩下0x44c,不要以为eax是32位的不会发生溢出。
二进制文件可以用od命令查看,也可以用gdb的x命令查看。 有些输出内容过多,我们可以使用 more或less命令结合管道查看,也可以使用输出重定向来查看
od code.o | more
od code.o > code.txt
gcc -S 产生的汇编中可以把 以”.“开始的语句都删除了再阅读。
操作数指示符
操作数三种类型:
●立即数,即常数值。
●寄存器,表示寄存器的内容
●存储器引用,它会根据计算出来的地址(有效地址)访问某个存储器位置。
对齐
对齐策略为:任何k字节对象的地址都必须是k的倍数;特别地,要求一个double的地址应该是8的倍数。
分配存储器的例程(如malloc)的设计必须使得它们返回的指针能满足最糟糕的对齐限制,通常是4或者是8。
结构体的对齐策略,考虑到分配结构体数组,要求其大小是结构体中最大字节类型的整数倍。
存储器的越界引用和缓冲区溢出
!对抗缓冲区溢出攻击
a.栈随机化 b.栈破坏检测 c.限制可执行代码区域
缓冲区溢出的一个更加致命的使用就是让程序执行它本身不愿意执行的函数。
浮点运算单元(FPU),IA32浮点寄存器的宽都是80位。浮点运算器的行为可能依赖于值是保存在寄存器中,还是存储器中。
综合:理解指针
1)每个指针都对应一个类型
2)每个指针都有一个值
3)指针用&运算符创建
4)操作符用于指针的间接引用
5)数组与指针紧密联系
6)指针也可以指向函数
程序员可见的状态
Y86处理器有八个寄存器:%eax、%edx、%ecx、%ebx、%esi、%edi、%esp和%ebp。每个程序存储器存储一个字。
● 寄存器%esp被入栈、出栈、调用和返回指令作为栈指针。
● 3个一位的条件码:ZF、SF、OF;保存最近的算术或逻辑指令所造成影响的有关信息。
● 程序计数器(PC)存放当前正在执行指令的地址。
● 存储器:保存程序和数据。Y86用虚拟地址引用存储器位置。硬件和操作系统软件联合起来将虚拟地址翻译成实际或物理地址,指明数据实际保存的地方。虚拟存储器提供给Y86程序一个单一的字节数组映像。
● 状态码Stat,表明程序执行的总体状态。
Y86指令集
逻辑设计和硬件控制语言HCL
1)逻辑门——逻辑门只对单个位的数进行操作。
2)组合电路和HCLB布尔表达式
组合电路——将很多逻辑门组合成一个网,构建计算块。
!两个限制: a.两个或多个逻辑门的输出不能连接在一起,否则它们可能会使线上的信号矛盾,可能会导致一个不合法的电压或电路故障;b.这个网必须是无环的。回路会导致网络计算的函数有歧义。
两类存储设备:
1)寄存器(简称寄存器):存储单个位或字,时钟信号控制寄存器加载输入值。
2)随机访问存储器(简称存储器):存储多个字,用地址来选择该杜或该写哪个字。
通常,处理一条指令包括很多操作。
1)取指(fetch):取指阶段从存储器读入指令,地址为程序计数器PC的值。从指令中抽取出指令指示符字节的两个四位部分,称为icode(指令代码)和ifun(指令功能)。
2)解码(decode):解码阶段从寄存器文件读入最多两个操作数,得到值valA or/and valB。
3)执行(execute):在执行阶段,算术/逻辑单元(ALU)要么执行指令指明的操作(根据ifun的值),计算存储器引用的有效地址,要么增加或减少栈指针。
4)访存(memory):访存阶段可以将数据写入存储器,或者从存储器读出数据。读出的值为valM。
5)写回(write back):写回阶段最多可以写两个结果到寄存器文件。
6)更新PC(PC update,程序计数器):将PC设置成下一条指令的地址。
[ 指令执行过程,从PC中取出指令,然后沿着上述的几个步骤操作,周而复始的进行循环。]
SEQ硬件结构、SEQ的时序
SEQ的实现包括组合逻辑和两种存储器设备:时钟寄存器(程序计数器和条件码寄存器)和随机访问寄存器(寄存器文件、指令存储器和数据存储器)。
!!遵循以下原则组织计算:
处理器从来不需要为了完成一条指令的执行而读由指令更新的状态。
随机访问存储器(RAM,Random-access memory)分为两类-静态和动态。
静态RAM(SRAM)比动态RAM(DRAM)更快,但也贵很多。SRAM用来作为高速缓存存储器,即可以在CPU芯片上,也可以不在CPU芯片上。DRAM用来作为主存以及图形系统的帧缓冲区。
1)静态RAM
SRAM将每个位存储在一个双稳态(bistable)存储器单元(cell)中。每个单元是用一个六晶体管电路来实现的。这个电路的一个属性是:它可以无限制地保持在两个不同的电压配置(configuration)或状态(state)之一。其他任何状态都是不稳定的。
由于SRAM的双稳态特性,只要有电,它就会永远地保持它的值,即使有干扰,如电子噪音,当干扰消除,电路也能恢复到稳定值。
2)动态RAM
DRAM将每个位存储为对电容的充电。电容约为30×10-15F。
磁盘构造(磁盘是由盘片构成的。表面覆盖着磁性记录材料。)
磁盘结构:盘片、磁道、扇区、间隙、柱面;磁盘驱动器
内存可以看成字节数组、磁盘可以看成块数组)。现代磁盘构造一个B个扇区大小的逻辑块的序列,编号0—B-1。磁盘中有一个小的硬件/固件设备,成为磁盘控制器。可将一个逻辑块号翻译成一个(盘面、磁道、扇区)三元组。
局部性原理:一种倾向性,倾向于引用邻近于其他最近引用过的数据项的数据项,或者最近引用过的数据项本身。
局部性有两种不同的形式:时间局部性和空间局部性。
1) 对程序数据引用的局部性————随着步长的增加,空间局部性下降。
2) 取指令的局部性————代码区别于程序的一个重要属性是在运行时他不能被修改。当程序正在执行时,CPU只从存储器中读取他的指令,绝不会重写或修改这些指令。
步长为1的引用模式:就是顺序访问一个向量的每个元素,有时也被称为顺序引用模式,它是程序中 空间局部性常见和重要的来源。
使用高速缓存的过程成为缓存。
!存储器层次结构的中心思想:对于每个K,位于k层的更快更小的存储设备作为位于k+1层的更慢更大的存储设备的缓存。即每层存储设备都是下一层的“缓存”。
数据以块大小为传送单元。一般而言,层次结构中较低层的设备的访问时间较长,倾向于使用较大的快。
高速缓存结构可以用元组(S,E,B,m)来描述。高速缓存的大小(容量)C指的是所有块大小的和。标记位和有效位不包括在内。因此C=SEB。
高速缓存确定一个请求是否命中,然后抽取出被请求字的过程分为三步:(1)组选择;(2)行匹配;(3)字抽取。
存储器山
每台计算机都有表明他存储器系统的能力特色的唯一的存储器山——就是把存储器系统的性能用关于时间和空间局部性的山表示。
● 想要达成的目的:使得程序运行在山峰而不是低谷
● 目标:利用时间局部性,使得频繁使用的字从L1中取出;利用空间局部性,使得尽可能多的字从一个L1高速缓存行中访问到。
链接器将重定位目标文件(relocatable object files)组合成一个可执行目标文件。cpp(c previous processor,C预处理器);ccl(C编译器);as(汇编器)
为了创建静态链接,链接器完成两个主要任务:
1)符号解析:将每个符号引用和一个符号定义联系起来。
2)重定位:编译器和汇编器生成从0地址开始的代码和数据节。链接器通过把每个符号定义与一个存储器位置联系起来,然后修改所有对这些符号的引用,使得它们指向这个存储器位置,从而重定位这些节。
目标文件有三种形式:
● 可重定位目标文件
● 可执行目标文件
● 共享目标文件
C++和Java中能使用重载函数,是因为编译器将每个惟一的方法和参数列表组合编码成一个对链接器来说惟一的名字。这种编码过程叫做毁坏,而相反的过程叫恢复。
C++和Java使用的是兼容的毁坏策略。一个已毁坏类的名字是由名字中字符的整数数量,后面跟原始名字组成的。比如类Foo被编译成3Foo;方法被编译成:原始方法名 + __ + 已毁坏的类名+ 再加上每个类参数的一个字母。如Foo::bar(int, long)被编译成bar__3Fooil。毁坏全局变量和模板名字的策略是相似的。
链接器如何解析多处定义的全局符号
函数和已初始化的全局变量是强符号;未初始化的全局变量是弱符号。
Unix链接器使用下面的规则来处理多重定义的符号:
● 规则1: 不允许有多个强符号。
● 规则2:如果有一个强符号和多个弱符号,那么选择强符号。
● 规则3:如果有多个弱符号,那么从这些弱符号中任意选择一个。
unix中,静态库以一种称为存档(archive)的特殊文件格式存放在磁盘中。存档文件是一组连接起来的可重定位目标文件的集合,有一个头部描述每个成员目标文件的大小和位置;后辍名为.a。
链接器使用静态库来解析引用的过程
在符号解析时,链接器从左到右按照它们在编译器驱动程序命令行上出现的顺序来扫描可重定位目标文件和存档文件。在扫描中,链接器维持一个可重定位目标文件的集合E,这个集合中的文件会被合并起来形成可执行文件,和一个未解析(引用了但是尚未定义的符号)的符号集U,以及一个在前面输入文件中已定义的符号集合D。初始化,E,U,D都是空的。
ELF(Executable and Linkable Format)
代码段的地址总是比数据段的地址小。
加载可执行目标文件
任何Unix程序都可以通过调用execve函数来调用加载器。加载器将可执行目标文件中的代码和数据从磁盘拷贝到存储器中,然后通过跳转到程序的第1条指令,即入口点,来运行该程序。将程序拷贝到存储器并运行的过程叫做加载。
每个Unix程序都有一个运行时存储器映像。
共享库是一个目标模块,在运行时,可以加载到任意的地存储器地址,并在存储器中和一个程序链接起来。这个过程称为动态链接(dynamic linking),是由一个叫做动态链接器(dynamic linker)的程序执行的。
共享库也称为共享目标(shared object),在Unix系统中通常用.so后缀来表示。(在MS OS 中为DLL文件) 注意:静态链接与动态链接的区别:静态链接是把程序所需要的库代码和数据拷贝和嵌入到引用它们的可执行文件中;而动态链接是所有引用该库的可执行文件文件共享这个.so(dll)文件中的代码和数据。
从应用程序中加载和链接共享库 通过几个函数,dlopen加载和链接共享库,dlsym通过输入的共享库的符号名字,返回符号的地址;dlclose卸载共享库,dlerror返回前面函数执行情况的一个字符串。
共享库的一个主要目的就是允许多个正在运行的进程共享存储器中相同的库代码,因而节约存储器资源。
PIC:编译库代码,不需要链接器修改库代码,就可以在任何地址加载和执行这些代码。
在一个IA32系统中,对同一个目标模块中过程的调用不需要特殊处理的,因为引用是PC相关的,已知偏移量,就是PIC了。然而,对外部定义的过程调用和对全局变量的引用通常不是PIC,因为它们都要求在链接时重定位。
如何对全局变量生成PIC引用呢? 基于以下事实:无论我们在存储器中的何处加载一个目标模块(包括共享模块),数据段总是分配为紧随在代码段后面。因此,代码段中任何指令和数据段中任何变量之间的距离都是一个运行时常量。
GNU binutils包。如objdump,ar,ldd。
链接可以在编译时由静态编译器完成,也可以加载和运行时由动态编译器完成。链接器处理称为目标文件的二进制文件,它有三种不同的形式:可重定位的,可执行的,和共享的。可重定位的目标文件由静态链接器组合成一个可执行的目标文件,它可以加载到存储器中并执行。共享目标文件(共享库)是在运行时由动态链接器链接和加载的,或者隐含地在调用程序被加载和开始执行时,或者根据需要在程序中调用dlopen库的函数时。
Linux中命令格式为:
command [options] [arguments]
命令 [选项] [参数]
中括号代表是可选的,即有些命令不需要选项也不需要参数。
1) 选项是调整命令执行行为的开关,选项不同决定了命令的显示结果不同。
2) 参数是指命令的作用对象。
man是manul的缩写,我们可以通过man man来查看man的帮助
帮助文档包含:
1.Executable programs or shell commands(用户命令帮助)
2.System calls (系统调用帮助)
3.Library calls (库函数调用帮助)
4.Special files (usually found in /dev)
5.File formats and conventions eg /etc/passwd(配置文件帮助)
6.Games
7.Miscellaneous (including macro packages and conventions), e.g. man(7), groff(7)
8.System administration commands (usually only for root)
9.Kernel routines [Non standard]
man -k
man有一个-k 选项用起来非常好,这个选项让你学习命令、编程时有了一个搜索引擎,可以举一反三。结合后面学习的grep 命令和管道,可以多关键字查找:
man -k key1 | grep key2 | grep key3 | ...
“作弊命令”,简单备忘单 它提供显示Linux命令使用案例,包括该命令所有的选项和简短但尚可理解的功能。
和查找、搜索相关的核心命令还有find,locate,grep,whereis,which,其中:
1) find查找一个文件在系统中的什么位置,locate是神速版本的find(Windows下有个Everything工具和locate类似).
2) grep 可以对文件**全文检索**,支持**正则表达式**,正则表达式也是一个重要的元知识。
3) whereis,which告诉你使用的命令工具装在什么地方。
which比whereis更精确,直接定位到可执行文件。 ※grep 管道相关, 前面的输出作为后面的输入 在多关键字检索时,用** | grep **分隔开,这点见 man -k。
三种模式
● 普通模式
● 插入模式
● 命令行模式
快捷键-移动光标
[[ 转到上一个位于第一列的"{"
]] 转到下一个位于第一列的"{"
{ 转到上一个空行
} 转到下一个空行
gd 转到当前光标所指的局部变量的定义
GCC选项列表
1)常用选项
-c 只编译不链接,生成目标文件.o
-S 只编译不汇编,生成汇编代码
-E 只进行预编译,不做其他处理
-g 在可执行程序中包含标准调试信息
-o file 将file文件指定为输出文件
-v 打印出编译器内部编译各过程的命令行信息和编译器的版本
-I dir 在头文件的搜索路径列表中添加dir目录
2)库选项
-static 进行静态编译,即链接静态库,禁止使用动态库
-shared 1.可以生成动态库文件
进行动态编译,尽可能的链接动态库,没有动态库时才会链接同名静态库
-L dir 在库文件的搜索路径列表中添加dir目录
-lname 链接称为libname.a或者libname.so的库文件。
如果两个库文件都存在,根据编译方式是static还是shared进行链接
-fPIC 生成使用相对地址的位置无关的目标代码,
(-fpic) 然后通常使用gcc的-static选项从该pic目标文件生成动态库文件。
编译过程
1)预处理:gcc –E hello.c –o hello.i; gcc –E调用cpp 生成中间文件
2)编 译:gcc –S hello.i –o hello.s; gcc –S调用ccl 翻译成汇编文件
3)汇 编:gcc –c hello.s –o hello.o; gcc -c 调用as 翻译成可重定位目标文件
4)链 接:gcc hello.o –o hello ; gcc -o 调用ld** 创建可执行目标文件 -o后面是接的你给生成的文件指定的名字,如果不指定,则默认为a.out
静态链接
为了构造可执行文件,链接器必须完成两个主要任务:
• 符号解析
• 重定位
目标文件纯粹是字节块的集合,链接器将这些块链接起来,确定被连接块的运行时位置,并且修改代码和数据块中的各种位置。
静态库
所有的编译系统都提供一种机制,将所有相关的目标模块打包成为一个单独的文件,称为静态库,可以作为链接器的输入。当链接器构造一个输出的可执行文件时,它只拷贝静态库里被应用程序引用的目标模块。
在Unix系统中,静态库以一种称为存档的特殊文件格式存放在磁盘中。存档文件是一组连接起来的可重定位目标文件的集合,有一个头部用来描述每个成员目标文件的大小和位置。存档文件由后缀.a识别。
!创建静态库需用用到AR工具
※AR工具的使用:
1)ar基本用法
ar命令可以用来创建、修改库,也可以从库中提出单个模块。
下面是ar命令的格式:
ar [-]{dmpqrtx}[abcfilNoPsSuvV] [membername] [count] archive files...
{dmpqrtx}部分称为操作选项;[abcfilNoPsSuvV]部分称为任选项
{dmpqrtx}中的操作选项在命令中只能并且必须使用其中一个,它们的含义如下:
d:从库中删除模块。按模块原来的文件名指定要删除的模块。如果使用了任选项v则列出被删除的每个模块。
m:该操作是在一个库中移动成员。当库中如果有若干模块有相同的符号定义(如函数定义),则成员的位置顺序很重要。如果没有指定任选项,任何指定的成员将移到库的最后。也可以使用'a','b',或'i'任选项移动到指定的位置。
p:显示库中指定的成员到标准输出。如果指定任选项v,则在输出成员的内容前,将显示成员的名字。如果没有指定成员的名字,所有库中的文件将显示出来。
q:快速追加。增加新模块到库的结尾处。并不检查是否需要替换。'a','b',或'i'任选项对此操作没有影响,模块总是追加的库的结尾处。如果使用了任选项v则列出每个模块。 这时,库的符号表没有更新,可以用'ar s'或ranlib来更新库的符号表索引。
r:在库中插入模块(替换)。当插入的模块名已经在库中存在,则替换同名的模块。如果若干模块中有一个模块在库中不存在,ar显示一个错误消息,并不替换其他同名模块。默认的情况下,新的成员增加在库的结尾处,可以使用其他任选项来改变增加的位置。
t:显示库的模块表清单。一般只显示模块名。
x:从库中提取一个成员。如果不指定要提取的模块,则提取库中所有的模块。
【学习GCC的另外一个重点是:参考教材《深入理解计算机系统》 7.6,7.10节,学习静态库,动态库的制作。】
共享库是一个目标模块,在运行时可以加载到任意的存储器地址,并和一个在存储器中的程序链接起来。这个过程称为动态链接,是由一个叫做动态链接库的程序来执行的。
共享库也称为共享目标,在Unix系统中通常用.so后缀
构造创建共享库:(同静态库的范例)
gcc -shared -fPIC -o libvector.so addvec.c multvec.c
参数解析:
-fPIC 指示编译器生成与位置无关的代码
-shared 指示链接器创建一个共享的目标文件
-o 命名生成文件
动作是把.c文件编译成为.o文件,放入新建的共享库中,并且命名。
链接程序
gcc -o p2 main2.c ./libvector.so
这样会创建一个可执行目标文件p2,在运行时可以和动态库libverctor.so链接。
静态库和动态共享库的代码区别:
1)创建
静态库的创建需要调用归档工具ar:
gcc -c addvec.c multvec.c
ar rcs libvector.a addvec.o multvec.o
而动态库只需要gcc即可:
gcc -shared -fPIC -o libvector.so addvec.c multvec.c
2)链接
静态库的链接需要先编译生成.o文件,然后再和库链接,并且需要staric命令构建一个完全链接的可执行文件:
gcc -02 -c main2.c
gcc -static -o p2 main2.o ./libvector.a
动态库可以直接把.c文件和库文件链接
gcc -o p2 main2.c ./libvector.so
使用GCC编译时要加“-g”参数
1)GDB最基本的命令有:
gdb programm(启动GDB)
l 查看所载入的文件
b 设断点
info b 查看断点情况
run 开始运行程序
bt 打印函数调用堆栈
p 查看变量值
c 从当前断点继续运行到下一个断点
n 单步运行(不进入)
s 单步运行(进入)
quit 退出GDB
2)四种断点:
• 行断点
b [行数或函数名] <条件表达式>
• 函数断点
b [函数名] <条件表达式>
• 条件断点
b [行数或函数名] <if表达式>
• 临时断点
tbreak [行数或函数名] <条件表达式>
Makefile 基本规则
Makefile的一般写法:
一个Makefile文件主要含有一系列的规则,每条规则包含以下内容:
• 需要由make工具创建的目标体,通常是可执行文件和目标文件,也可以是要执行的动作,如‘clean’;
• 要创建的目标体所依赖的文件,通常是编译目标文件所需要的其他文件。
• 创建每个目标体时需要运行的命令,这一行必须以制表符TAB开头
格式为:
test(目标文件): prog.o code.o(依赖文件列表)
tab(至少一个tab的位置) gcc prog.o code.o -o test(命令)
即target: dependency_files
使用带宏的 Makefile
Makefile中的宏,也称作变量。
变量是在makefile中定义的名字,用来代替一个文本字符串,该文本字符串称为该变量的值。
定义变量的两种方式:
1)递归展开方式 VAR=var
2)简单方式 VAR:=var
使用变量的格式为: $(VAR)
变量的分类
• 用户自定义变量
• 预定义变量
• 自动变量
• 环境变量
!这周的实验内容一定要掌握。
从开始学习到现在,已经过去八周了。一开始,觉得任务量太大,无法在有限的时间内学完老师布置的任务。但是现在看来,我们不应该把这些只当做任务来完成。从不了解Linux,到现在对它有了基本、初步的认识,这是一个慢慢巩固的过程。前几周的知识,通过这次期中再次复习,有些以前不懂的点也逐渐有了思路。然后,在学习过程中,通过写博客,使用markdown,也学会了一些使用的小工具。所以也许积极主动地去学效果更好一些。
另外一点,就是我认识到了实验的重要性。在所学的这些知识中,我记忆最深刻的不是树上有写长篇大论或是概念什么的,而是gcc、gdb然后栈帧等实验内容。就是觉得可能是自己亲自动手实践了,并且找老师验收了,所以印象会更加深刻。以后要继续保持着一点。
这段学习时间以来,虽然认真地完成了老师布置的人物,但还有些不足:
首先,知识点不能完全理解,有时只是看过而已。以后需要再进一步进行深入理解,这样才能真正学会。 其次,在提问问题方面还比较欠缺。可能自己习惯于向同学请教,认为这样比较方便。以后应该多向老师提问,这样才能更好的得到反馈。比如不仅要在博客中写下问题,更要在课程小组中提出问题,让大家一起解决。
有一点建议就是希望老师以后在课堂上可以先给我们讲一下这周要学习的内容,理由就是我们自己如果完全自学可能不如这样效率高,会花费更多的时间。但是老师上节课已经说了以后会先讲,所以应该没有问题了。
参考资料:《深入理解计算机系统》
《嵌入式Linux应用程序开发标准教程》
每周博客
博客http://group.cnblogs.com/topic/73060.html