搜档网
当前位置:搜档网 › 链接器和加载器09

链接器和加载器09

链接器和加载器09
链接器和加载器09

第9章 共享库

$Revision: 2.3 $

$Date: 1999/06/15 03:30:36 $

程序库的产生可以追溯到计算技术的最早期,因为程序员很快就意识到通过重用程序

的代码片段可以节省大量的时间和精力。随着如Fortran and COBOL等语言编译器的发展,程序库成为编程的一部分。当程序调用一个标准过程时,如sqrt(),编译过的语言显式地使用库,而且它们也隐式地使用用于I/O、转换、排序及很多其它复杂得不能用内联代码解释的函数库。随着语言变得更为复杂,库也相应地变复杂了。当我在20年前写一个Fortran 7 7编译器时,运行库就已经比编译器本身的工作要多了,而一个Fortran 77库远比一个C++库要来得简单。

语言库的增加意味着:不但所有的程序包含库代码,而且大部分程序包含许多相同的

库代码。例如,每个C程序都要使用系统调用库,几乎所有的C程序都使用标准I/O库例程,如printf,而且很多使用了别的通用库,如math,networking,及其它通用函数。这就意

味着在一个有一千个编译过的程序的UNIX系统中,就有将近一千份printf的拷贝。如果所有那些程序能共享一份它们用到的库例程的拷贝,对磁盘空间的节省是可观的。(在一个没有共享库的UNIX系统上,单printf的拷贝就有5到10M。)更重要的是,运行中的程序如

能共享单个在内存中的库的拷贝,这对主存的节省是相当可观的,不但节省内存,也提高页交换。

所有共享库基本上以相同的方式工作。在链接时,链接器搜索整个库以找到用于解决

那些未定义的外部符号的模块。但链接器不把模块内容拷贝到输出文件中,而是标记模块来自的库名,同时在可执行文件中放一个库的列表。当程序被装载时,启动代码找到那些库,并在程序开始前把它们映射到程序的地址空间,如图1。标准操作系统的文件映射机制自动共享那些以只读或写时拷贝的映射页。负责映射的启动代码可能是在操作系统中,或在可执行体,或在已经映射到进程地址空间的特定动态链接器中,或是这三者的某种并集。

---------------------------------------------------------------------------------------------图9-1:带有共享库的程序

可执行程序,共享库的图例

可执行程序main,app库,C库

不同位置来的文件

箭头展示了从main到app,main到C,app到C的引用

---------------------------------------------------------------------------------------------在本章,我们着眼于静态链接库,也就是库中的程序和数据地址在链接时绑定到可执

行体中。在下一章我们着眼于更复杂的动态链接库。尽管动态链接更灵活更“现代”,但也比静态链接要慢很多,因为在链接时要做的大量工作在每次启动动态链接的程序时要重新做。同时,动态链接的程序通常使用额外的“胶合(g lu e)”代码来调用共享库中的例程。胶合代码通常包含若干个跳转,这会明显地减慢调用速度。在同时支持静态和动态共享库的系统上,除非程序需要动态链接的额外扩展性,不然使用静态链接库能使它们更快更小巧。

绑定时间

共享库提出的绑定时间问题,是常规链接的程序不会遇到的。一个用到了共享库的程

序在运行时依赖于这些库的有效性。当所需的库不存在时,就会发生错误。在这情况下,除了打印出一个晦涩的错误信息并退出外,不会有更多的事情要做。

当库已经存在,但是自从程序链接以来库已经改变了时,一个更有趣的问题就会发生。在一个常规链接的程序中,在链接时符号就被绑定到地址上而库代码就已经绑定到可执行体中了,所以程序所链接的库是那个忽略了随后变更的库。对于静态共享库,符号在链接时被绑定到地址上,而库代码要直到运行时才被绑定到可执行体上。(对于动态共享库而言,它们都推迟到运行时。)

一个静态链接共享库不能改变太多,以防破坏它所绑定到的程序。因为例程的地址和

库中的数据都已经绑定到程序中了,任何对这些地址的改变都将导致灾难。

如果不改变程序所依赖的静态库中的任何地址,那么有时一个共享库就可以在不影响程序对它调用的前提下进行升级。这就是通常用于小bu g修复的"小更新版"。更大的改变不可避免地要改变程序地址,这就意味着一个系统要么需要多个版本的库,要么迫使程序员在每次改变库时都重新链接它们所有的程序。实际中,永远不变的解决办法就是多版本,因为磁盘空间便宜,而要找到每个会用到共享库可执行体几乎是不可能的。

实际的共享库

本章余下的部分将关注于UNIX Sy stem V Re l ease 3.2 (COFF格式),较早的Lin ux系统(a.o u t格式),和4.4B S D的派生系统(a.o u t和E LF格式)这三者提供的静态共享库。这三者以几近相同的方式工作,但有些不同点具有启发意义。SV R3.2的实现要求改变链接器以支持共享库搜索,并需要操作系统的强力支持以满足例程在运行时的启动需求。Lin ux的实现需要对链接器进行一点小的调整并增加一个系统调用以辅助库映射。B S D/O S的实现不对链接器或操作系统作任何改变,它使用一个脚本为链接器提供必要的参数和一个修改过的标准C 库启动例程以映射到库中。

地址空间管理

共享库中最困难的就是地址空间管理。每一个共享库在使用它的程序里都占用一段固定的地址空间。不同的库,如果能够被使用在同一个程序中,它们还必须使用互不重叠的地址空间。虽然机械的检查库的地址空间是否重叠是可能的,但是给不同的库赋予相应的地址空间仍然是一种“魔法”。一方面,你还想在它们之间留一些余地,这样当其中某个新版本的库增长了一些时,它不会延伸到下一个库的空间而发生冲突。另一方面,你还想将你最常用的库尽可能紧密的放在一起以节省需要的页表数量(要知道在x86上,进程地址空间的每一个4MB的块都有一个对应的二级表)。

每个系统的共享库地址空间都必然有一个主表,库从离应用程序很远的地址空间开始。Lin ux从十六进制的60000000开始,B S D/O S从A0000000开始。商业厂家将会为厂家提供的库、用户和第三方库进一步细分地址空间,比如对B S D/O S,用户和第三方库开始于地址A08 00000。

通常库的代码和数据地址都会被明确的定义,其中数据区域从代码区域结束地址后的一个或两个页对齐的地方开始。由于一般都不会更新数据区域的布局,而只是增加或者更改代码区域,所以这样就使小更新版本成为可能。

每一个共享库都会输出符号,包括代码和数据,而且如果这个库依赖于别的库,那么通常也会引入符号。虽然以某种偶然的顺序将例程链接为一个共享库也能使用,但是真正的库使用一些分配地址的原则而使得链接更容易,或者至少使在更新库的时候不必修改输出符号的地址成为可能。对于代码地址,库中有一个可以跳转到所有例程的跳转指令表,并将这些跳转的地址作为相应例程的地址输出,而不是输出这些例程的实际地址。所有跳转指令的大小都是相同的,所以跳转表的地址很容易计算,并且只要表中不在库更新时加入或删除表

项,那么这些地址将不会随版本而改变。每一个例程多出一条跳转指令不会明显的降低速度,由于实际的例程地址是不可见的,所以即使新版本与旧版本的例程大小和地址都不一样,库的新旧版本仍然是可兼容的。

对于输出数据,情况就要复杂一些,因为没有一种像对代码地址那样的简单方法来增

加一个间接层。实际中的输出数据一般是很少变动的、尺寸已知的表,例如C标准I/O库中的FIL E结构,或者像errno那样的单字数值(最近一次系统调用返回的错误代码),或者

是t z name(指向当前时区名称的两个字符串的指针)。建立共享库的程序员可以收集到这些输出数据并放置在数据段的开头,使它们位于每个例程中所使用的匿名数据的前面,这样使得这些输出地址在库更新时不太可能会有变化。

共享库的结构

共享库是一个包含所有准备被映射的库代码和数据的可执行格式文件,见图9-2。

---------------------------------------------------------------------------------------------图9-2: 典型共享库的结构

文件头,a.o u t, COFF或E LF头

(初始化例程,不总存在)

跳转表

代码

全局数据

私有数据

---------------------------------------------------------------------------------------------一些共享库从一个小的自举例程开始,来映射库的剩余部分。之后是跳转表,如果它

不是库的第一个内容,那么就把它对齐到下一个页的位置。库中每一个输出的公共例程的地址就是跳转表的表项;跟在跳转表后面的是文本段的剩余部分(由于跳转表是可执行代码,

所以它被认为是文本),然后是输出数据和私有数据。在逻辑上b ss段应跟在数据的后面,

但是就像在任何别的可执行文件中那样,它并不在于这个文件中。

创建共享库

一个UNIX共享库实际上包含两个相关文件,即共享库本身和给链接器用的空占位库(st ub l i b rar y)。库创建工具将一个档案格式的普通库和一些包含控制信息的文件作为输入生

成了这两个文件。空占位库根本不包含任何的代码和数据(可能会包含一个小的自举例程),但是它包含程序链接该库时需要使用的符号定义。

创建一个共享库需要以下几步,我们将在后面更多的讨论它们:

确定库的代码和数据将被定位到什么地址。

彻底扫描输入的库寻找所有输出的代码符号(如果某些符号是用来在库内通信的,那么就会有一个控制文件是这些不对外输出的符号的列表)。

创建一个跳转表,表中的每一项分别对应每个输出的代码符号。

如果在库的开头有一个初始化或加载例程,那么就编译或者汇编它。

创建共享库。运行链接器把所有内容都链接为一个大的可执行格式文件。

创建空占位库:从刚刚建立的共享库中提取出需要的符号,针对输入库的符号调整这些符号。为每一个库例程创建一个空占位例程。在COFF库中,也会有一个小的

初始化代码放在占位库里并被链接到每一个可执行体中。

创建跳转表

最简单的创建一个跳转表的方法就是编写一个全是跳转指令的汇编源代码文件,如图3,并汇编它。这些跳转指令需要使用一种系统的方法来标记,这样以后空占位库就能够把这些地址提出取来。

对于像x86这样具有多种长度的跳转指令的平台,可能稍微复杂一点。对于含有小于6 4K代码的库,3个字节的短跳转指令就足够了。对于较大的库,需要使用更长的5字节的跳转指令。将不同长度的跳转指令混在一起是不能让人满意的,因为它使得表地址的计算更加困难,同时也更难在以后重建库时确保兼容性。最简单的解决方法就是都采用最长的跳转指令;或者全部都使用短跳转,对于那些使用短跳转太远的例程,则用一个短跳转指令跳转到放在表尾的匿名长跳转指令。(通常由此带来的麻烦比它的好处更多,因为第一跳转表很少

会有好几百项。)

---------------------------------------------------------------------------------------------图9-3:跳转表

... 从一个页边界起始

.a l ign 8; 为了变量长度而对其于8字节边界处

J UM P_read: j mp _read

.a l ign 8

J UM P_write: j mp _write

...

_read: ... c ode for read()

...

_write: ... c ode for write()

---------------------------------------------------------------------------------------------创建共享库

一旦跳转表和加载例程(如果需要的话)建立好之后,创建共享库就很容易了。只需要

使用合适的参数运行链接器,让代码和数据从正确的地址空间开始,并将自引导例程、跳转表和输入库中的所有例程都链接在一起。它同时完成了给库中每项分配地址和创建共享库文件两件事。

库之间的引用会稍微复杂一些。如果你正在创建,例如一个使用标准C库例程的共享数学库,那就要确保引用的正确。假定当链接器建立新库时需要用到的共享库中的例程已经建好,那么它只需要搜索该共享库的空占位库,就像普通的可执行程序引用共享库那样。这将让所有的引用都正确。只留下一个问题,就是需要有某种方法确保任何使用新库的程序也能够链接到旧库上。对新库的空占位库的适当设计可以确保这一点。

创建空占位库

创建空占位库是创建共享库过程中诡秘的部分之一。对于库中的每一个例程,空占位库中都要包含一个同时定义了输出和输入的全局符号的对应项。

数据全局符号会被链接器放在共享库中任何地方,获取它们的数值的最合理的办法就是创建一个带有符号表的共享库,并从符号表中提取符号。对代码全局符号,入口指针都在跳转表中,所以同样很简单,只需要从共享库中提取符号表或者根据跳转表的基地址和每一个符号在表中的位置来计算符号地址。

不同于普通库模块,空占位库模块既不包含代码也不包含数据,只包含符号定义。这些符号必须定义成绝对数而不是相对,因为共享库已经完成了所有的重定位。库创建程序从输入库中提取出每一个例程,并从这些例程中得到定义和未定义的全局变量,以及每一个全局变量的类型(文本或数据)。然后它创建空占位例程,通常都是一个很小的汇编程序,以跳转表中每一项的地址的形式定义每个文本全局变量,以共享库中实际地址的形式定义每个数据或b ss全局变量,并以“未定义”的形式定义没有定义的全局变量。当它完成所有空占位后,就对其进行汇编并将它们合并到一个普通的库档案文件中。

COFF空占位库使用了一种不同的、更简单的设计。它们是具有两个命名段的单一目标文件。“.l i b”段包含了指向共享库的所有重定位信息,“.init”段包含了将会链接到每一个客户程序去的初始化代码,一般是来初始化库中的变量。Lin ux 共享库更简单,a.o u t 文件中包含了带有设置向量(“set ve c tor”) 的符号定义, 我们将在后面的链接部分详细讨论设置向量。.

共享库的名称一般是原先的库名加上版本号。如果原先的库称为/l i b/l i b c.a,这通常是C库的名字,当前的库版本是4.0,空占位库可能是/l i b/l i b c_s.4.0.0.a,共享库就是/s h l i b/l i b c_s.4.0.0(多出来的0可以允许小版本的升级)。一旦库被放置到合适的目录下面,它们就可以被使用了。

版本命名

任何共享库系统都需要有一种办法处理库的多个版本。当一个库被更新后,新版本相对于之前版本而言在地址和调用上都有可能兼容或不兼容。UNIX系统使用前面提到的版本命名序号来解决这个问题。

第一个数字在每次发布一个不兼容的全新的库的时候才被改变。一个和4.x.x的库链接的程序不能使用3.x.x或5.x.x的库。第二个数是小版本。在Su n系统上,每一个可执行程

序所链接的库都至少需要一个尽可能大的小版本号。例如,如果它链接的是4.2.x,那么它就可以和4.3.x一起运行而4.1.x则不行(译者注:就是说得使用尽可能大的小版本号,确保可执行程序可以运行)。另一些系统将第二个数字当作第一个数字的扩展,这样的话使用一个4.2.x的库链接的程序就只能和4.2.x的库一起运行。第三个数字通常都被当作补丁级别。虽然任何的补丁级别都是可用的,可执行程序最好还是使用最高的有效补丁级别。

不同的系统在运行时查找对应库的方法会略有不同。Su n系统有一个相当复杂的运行时加载器,在库目录中查看所有的文件名并挑选出最好的那个。Lin ux系统使用符号链接而避免了搜索过程。如果库l i b c.so的最新版本是4.2.2,库的名字是l i b c_s.4.2.2,但是这个库也已经被链接到l i b c_s.4.2,那么加载器将仅需打开名字较短的文件,就选好了正确的版本。

多数系统都允许共享库存在于多个目录中。类似于LD_LIBR A R Y_P A TH的环境变量可以覆盖可执行程序中的路径,以允许开发者使用它们自己的库替代原先的库进行调试或性能测试(使用“set u ser ID”特性替代当前用户运行的程序将忽略LD_LIBR A R Y_P A TH以避免使用恶意用户添加了安全漏洞的“特洛伊木马”库)。

使用共享库链接

使用静态共享库来链接,比创建库要简单得多,因为几乎所有的确保链接器正确解析库中程序地址的困难工作,都在创建空占位库时完成了。唯一困难的部分就是在程序开始运行时将需要的共享库映射进来。

每一种格式都会提供一个小窍门让链接器创建一个库的列表,以便启动代码把库映射进来。COFF库使用一种残忍的强制方法;链接器中的特殊代码在COFF文件中创建了一个以库名命名的段。Lin ux链接器使用一种不那么残忍的方法,即创建一个称为设置向量的特殊符号类型。设置向量象普通的全局符号一样,但如果它有多个定义,这些定义会被放进一个以该符号命名的数组中。每个共享库定义一个设置向量符号__S H A R E D_LIBR A RI ES__,它是由库名、版本、加载地址等构成的一个数据结构的地址。 链接器创建一个指向每个这种数据结构的指针的数组,并称之为__S H A R E D_LIBR A RI ES__,好让启动代码可以使用它。B S D/O S共享库没有使用任何的此类链接器窍门。它使用she ll脚本建立一个共享的可执行程序,用来搜索作为参数或隐式传入的库列表,提取出这些文件的名字并根据系统文件中的列表来加载这些库的地址,然后编写一个小汇编源文件创建一个带有库名字和加载地址的结构数组,并汇编这个文件,把得到的目标文件加入到链接器的参数列表中。

在每一种情况中,从程序代码到库地址的引用都是通过空占位库中的地址自动解析的。

使用共享库运行

启动一个使用共享库的程序需要三步:加载可执行程序,映射库,进行库特定的初始化操作。在每一种情况下,可执行程序都被系统按照通常的方法加载到内存中。之后,处理

方法会有差别。系统V.3内核具有了处理链接COFF共享库的可执行程序的扩展性能,其内核会查看库列表并在程序运行之前将它们映射进来。这种方法的不利之处在于 “内核肿胀”,会给不可分页的内核增加更多的代码;并且由于这种方法不允许在未来版本中有灵活性和可升级性,所以它是不灵活的(系统V.4整个抛弃了这种策略,转而采用了我们下章会涉及到的E LF动态共享库)。

Lin ux增加了一个单独的u se l i b()系统调用,以获取一个库的文件名字和地址,并将它映射到程序的地址空间中。绑定到可执行体中的启动例程搜索库列表,并对每一项执行u se l i b()。

B S D/O S的方法是使用标准的mmap()系统调用将一个文件的多个页映射进地址空间,该方法还使用一个链接到每个共享库起始处的自举例程。可执行程序中的启动例程遍历共享库表,打开每个对应的文件,将文件的第一页映射到加载地址中,然后调用各自的自举例程,该例程位于可执行文件头之后的起始页附近的某个固定位置。然后自举例程再映射余下的文本段、数据段,然后为b ss段映射新的地址空间,然后自举例程就返回了。

所有的段被映射了之后,通常还有一些库特定的初始化工作要做,例如,将一个指针指向C标准库中指定的系统环境全局变量environ。COFF的实现是从程序文件的“.init”段收集初始化代码,然后在程序启动代码中运行它。根据库的不同,它有时会调用共享库中的例程,有时不会。Lin ux的实现中没有进行任何的库初始化,并且指出了在程序和库中定义相同的变量将不能很好工作的问题。

在B S D/O S实现中,C库的自举例程会接收到一个指向共享库表的指针,并将所有其它的库都映射进来,减小了需要链接到单独的可执行体中的代码量。最近版本的B S D使用E LF 格式的可执行体。E LF头有一个interp段,其中包含一个运行该文件时需要使用的解释器程序的名字。B S D使用共享的C库作为解释器,这意味着在程序启动之前内核会将共享C库先映射进来,这就节省了一些系统调用的开销。库自举例程进行的是相同的初始化工作,将库的剩余部分映射进来,并且,通过一个指针,调用程序的main例程。

ma ll o c ha c k和其它共享库问题

虽然静态共享库具有很好的性能,但是它们的长期维护是困难和容易出错的,下面给出一些轶事为例。

在一个静态库中,所有的库内调用都被永久绑定了,所以不可能将某个程序中所使用的库例程通过重新定义替换为私有版本的例程。多数情况下,由于很少有程序会对标准库中例如read()、str c mp()等例程进行重新定义,所以永久绑定不是什么大问题;并且如果它们自己的程序使用私有版本的str c mp(),但库例程仍调用库中标准版本,那么也没有什么大问题。

但是很多程序定义了它们自己的ma ll o c()和free()版本,这是分配堆存储的例程;如果在一个程序中存在这些例程的多个版本,那么程序将不能正常工作。例如,标准strd u p()例程,返回一个指向用ma ll o c分配的字符串指针,当程序不再使用它时可以释放它。如果库使用ma ll o c的某个版本来分配字符串的空间,但是应用程序使用另一个版本的free来释

放这个字符串的空间,那么就会发生混乱。

为了能够允许应用程序提供它们自己版本的ma ll o c和free,Sy stem V.3的共享C库使用了一种“丑陋”的技术,如图4所示。系统的维护者将ma ll o c和free重新定义为间接调用,这是通过绑定到共享库的数据部分的函数指针实现的,我们将称它们为ma ll o c_ptr和f ree_ptr。

e x tern void *(*ma ll o c_ptr)(si z e_t);

e x tern void (*free_ptr)(void *);

#define ma ll o c(s) (*ma ll o c_ptr)(s)

#define free(s) (*free_ptr)(s)

---------------------------------------------------------------------------------------------图9-4:ma ll o c ha c k

程序,共享C库的图例

ma ll o c指针和初始化代码

从库代码中的间接调用

---------------------------------------------------------------------------------------------然后它们重新编译了整个C库,并将下面的几行内容(或汇编同类内容)加入到占位库的. init段,这样它们就被加入到每个使用该共享库的程序中了。

#u ndef ma ll o c

#u ndef free

ma ll o c_ptr =&ma ll o c;

free_ptr =&free;

由于占位库将被绑定到应用程序中的,而不是共享库,所以它对ma ll o c和free的引用是在链接时解析的。如果存在一个私有版本的ma ll o c和free,它将指向私有版本函数的指针(译者注:指ma ll o c_ptr和free_ptr),否则它将使用标准库的版本。不管哪种方法,

库和应用程序使用的都是相同版本的ma ll o c和free。

虽然这种实现方法让库的维护工作更加困难了,而且只能用于少数手工选定的名字,

但只要它可以自动进行而不需要手工编写脆弱的源代码,这种在程序运行时通过指针进行解析进行库内例程调用的方法就是一个很好的主意,我们将会在下一章看到自动版本是如何工作的。

全局数据中的名字冲突仍然是遗留在共享库中的一个问题。考虑一下图5所示的小程序。如果你用任何一个我们本章描述过的共享库编译和链接它,他将打印一个值为0的状态代码而不是正确的错误代码。这是由于

int errno

创建了一个没有绑定到共享库中去的新的errono实例。如果你不将e x tern注释掉,这个程序就可以正常运行,因为它现在引用了一个未定义的全局变量,这将使链接器将其绑定到共享库中的errno。就像我们将要看到的,动态链接很好地解决了这个问题,但是会付出

一些性能的代价。

---------------------------------------------------------------------------------------------图9-5:地址冲突示例

#in c lu de

/* e x tern */

int errno;

main()

{

u n l ink("/non-e x istent-fi l e");

printf("S tat u s was %d\n", errno);

}

---------------------------------------------------------------------------------------------最后,即使UNIX共享库中的跳转表也会引起兼容性的问题。在共享库外的例程看来,

库中输出的每个例程的地址就是一个跳转表表项的地址。但是在库内部的例程看来,例程的地址可能是跳转表表项,也可能是跳转表要跳转到的实际入口点。有时为了处理某些特殊情况,一个库例程会比较作为参数传递给它的地址,看它是否是某个库例程的地址。

一种显而易见但是不完全有效的解决方案就是在建立共享库的过程中将例程的地址绑

定到跳转表表项,因为这样可以确保库中所有例程的符号引用都被解析到对应的表项。但是如果两个例程在同一个目标文件中,那么在这个目标文件中的引用通常是对例程文本段地址的相对引用。(由于是同一个目标文件,该例程地址已知;除了这种特殊情况,没有什么别的理由需要返回到同一目标文件中去引用一个符号例程)。虽然通过扫描可重定位的文本段引用来找到相应的输出符号的地址是可能的,但是实际中最常用的解决方法是“别那么做”,不要编写依赖于需要识别库例程地址的代码。

W indows的DLL库也存在相似的问题,因为在每一个E X E或者DLL内部,输入例程的地

址被认为是可以间接跳转到例程实际地址的占位例程地址(译者注:由于这里的地址并不是实际地址,所以才会被认为是一个问题)。同样,对这个问题最常采用的解决方法是“别那

么做”。

练习

如果你在一个带有共享库的UNIX系统上查看/sh l i b目录,你会发现每个库都会有3到4个版本,诸如l i b c_s.2.0.1、l i b c_s.3.0.0。为什么不使用最新的一个呢?

在一个空占位库中,为什么将每一个例程中的未定义全局符号都包含进来是非常重要的,即使在一个未定义的全局符号引用了该库中的另一个例程?

一个空占位库是包含了诸如在COFF或Lin ux中的所有库符号的单一可执行体,另一个是具有多个单独的模块的实际库,两者什么不同呢?

项目

我们要扩展链接器以支持静态共享库。这包括很多子项目,第一个就是建立共享库,然后就是使用共享库来链接可执行体。

在我们的系统中,共享库只是一个被链接了给定地址的目标文件。虽然它可以引用其它的共享库,但不会有重定位和未解析的全局符号引用。占位库是普通的目录格式或者文件格式的库,库中的每一项包含针对对应库成员的输出(绝对的)和输入符号,但是没有文本段或数据段。每一个占位库必须告诉链接器对应的共享库的名字。如果你使用目录格式的占位库,那么一个名为“LIBR A R Y N A M E”的文件将包含一行一行的文本。第一行是对应共享库的名称,剩下的行是该共享库依赖的其它共享库名称(空格避免了符号的名字冲突)。如果你使用文件格式的库,那么库的初始行要有些额外的域:

LIBR A R Y nnnn pppppp fffff ggggg hhhhh ...

这里fffff是共享库的名字,剩下的是它所依赖的其它共享库的名称。

项目9-1:让链接器可以从规则的目录格式或文件格式中生成静态共享库和占位库。如果你还没有那么做,你将需要给链接器增加一个标识(译者注:参数格式)来设置链接器分配的段基地址。输入是一个规则的库,和这个库所依赖的其它任何共享库的占位库。输出是一个包含所有输入库成员的段的可执行格式的共享库,和一个对每个包含输入库的成员都有对应占位成员的占位库。

项目9-2:扩展链接器以使用静态共享库生成可执行体。鉴于在一个执行体中引用共享库中的符号与在一个共享库中引用另一个共享库的符号的方法是相同的,所以项目9-1已经完成了搜索占位库符号解析的大多数工作。链接器需要将必要的库名放到输出文件中,以便运行时加载器知道需要加载什么库。让链接器建立一个名为.l i b的段,保存需要的共享库名称,这些名称之间以n ull字节标识间隔,以2个n ull字节标识结尾。建立一个名为_S H A R E D_LIBR A RI ES的符号,它引用.l i b段的开始地址,可以让库的初始化例程使用。

gcc编译器使用简明指南

gcc编译器使用简明指南 gcc对文件的处理需要经过预处理->编译->汇编->链接的步骤,从而产生一个可执行文件,各部分对应不同的文件类型,具体如下: file.c c程序源文件 file.i c程序预处理后文件 file.cxx c++程序源文件,也可以是https://www.sodocs.net/doc/0116847495.html, / file.cpp / file.c++ file.ii c++程序预处理后文件 file.h c/c++头文件 file.s 汇编程序文件 file.o 目标代码文件 gcc [选项]文件列表 -ansi 强制完全ANSI一致 -c 仅编译或汇编,生成目标代码文件,将.c、.i、.s等文件生成.o文件,其余文件被忽略 -S 仅编译,不进行汇编和链接,将.c、.i等文件生成.s文件,其余文件被忽略 -E 仅预处理,并发送预处理后的.i文件到标准输出,其余文件被忽略 -o file 创建可执行文件并保存在file中,而不是默认文件a.out -g 产生用于调试和排错的扩展符号表,用于GDB调试,切记-g和-O通常不能一起使用 -w 取消所有警告 -W 给出更详细的警告 -O [num]优化,可以指定0-3作为优化级别,级别0表示没有优化 -x language 默认为-x none,即依靠后缀名确定文件类型,加上-x lan确定后面所有文件类型,直到下一个-x出现为止 -D macro[=]类似于源程序里的#define,在-D macro中的macro可被源程序识别,例如gcc -D NUM -D FILE=\"bbs.txt\" hello.c -o hello,第一个-D选项定义宏NUM,在程序中可以使用#ifdef来检查是否被设置,第二个-D定义宏FILE,在源程序中可用 -U macro 类似于源程序开头定义#undef macro,也就是取消源程序中的某个宏定义

链接器和加载器09

第9章 共享库 $Revision: 2.3 $ $Date: 1999/06/15 03:30:36 $ 程序库的产生可以追溯到计算技术的最早期,因为程序员很快就意识到通过重用程序 的代码片段可以节省大量的时间和精力。随着如Fortran and COBOL等语言编译器的发展,程序库成为编程的一部分。当程序调用一个标准过程时,如sqrt(),编译过的语言显式地使用库,而且它们也隐式地使用用于I/O、转换、排序及很多其它复杂得不能用内联代码解释的函数库。随着语言变得更为复杂,库也相应地变复杂了。当我在20年前写一个Fortran 7 7编译器时,运行库就已经比编译器本身的工作要多了,而一个Fortran 77库远比一个C++库要来得简单。 语言库的增加意味着:不但所有的程序包含库代码,而且大部分程序包含许多相同的 库代码。例如,每个C程序都要使用系统调用库,几乎所有的C程序都使用标准I/O库例程,如printf,而且很多使用了别的通用库,如math,networking,及其它通用函数。这就意 味着在一个有一千个编译过的程序的UNIX系统中,就有将近一千份printf的拷贝。如果所有那些程序能共享一份它们用到的库例程的拷贝,对磁盘空间的节省是可观的。(在一个没有共享库的UNIX系统上,单printf的拷贝就有5到10M。)更重要的是,运行中的程序如 能共享单个在内存中的库的拷贝,这对主存的节省是相当可观的,不但节省内存,也提高页交换。 所有共享库基本上以相同的方式工作。在链接时,链接器搜索整个库以找到用于解决 那些未定义的外部符号的模块。但链接器不把模块内容拷贝到输出文件中,而是标记模块来自的库名,同时在可执行文件中放一个库的列表。当程序被装载时,启动代码找到那些库,并在程序开始前把它们映射到程序的地址空间,如图1。标准操作系统的文件映射机制自动共享那些以只读或写时拷贝的映射页。负责映射的启动代码可能是在操作系统中,或在可执行体,或在已经映射到进程地址空间的特定动态链接器中,或是这三者的某种并集。 ---------------------------------------------------------------------------------------------图9-1:带有共享库的程序 可执行程序,共享库的图例 可执行程序main,app库,C库 不同位置来的文件 箭头展示了从main到app,main到C,app到C的引用

路由器的工作原理

路由器的工作原理 路由器的是实现网络互连,在不同网络之间转发数据单元的重要网络设备。路由器主要工作在OSI参考模型的第三层(网络层),路由器的主要任务就是为经过路由器的每个数据帧寻找一条最佳传输路径,并将该数据有效地传送到目的站点。为了完成这项工作,在路由器中保存着各种传输路径的相关数据——路由表(Routing Table),供路由选择时使用。由此可见,选择最佳路径的策略即路由算法是路由器的关键所在。因此,当路由器接收到来自一个网络接口的数据包时,首先根据其中所含的目的地址查询路由表,决定转发路径(转发接口和下一跳地址),然后从ARP缓存中调出下一跳地址的MAC地址,将路由器自己的MAC 地址作为源MAC,下一跳地址的MAC作为目的MAC,封装成帧头,同时IP数据包头的TTL(Time To Live)也开始减数,最后将数据发送至转发端口,按顺序等待,传送到输出链路上去。在这个过程中,路由器被认为了执行两个最重要的基本功能:路由功能与交换功能。 路由功能 路由功能是指路由器通过运行动态路由协议或其他方法来学习和维护网络拓扑结构,建立,查询和维护路由表。 路由表里则保存着路由器进行路由选择时所需的关键信息,包含了目的地址、目的地址的掩码、下一跳地址、转发端口、路由信息来源、路由优先级、度量值(metric)等。 路由信息可通过多种协议的学习而来,其来源方式可分为直连路由、静态路由、缺省路由和动态路由。一个路由器上可以同时运行多个不同的路由协议,每个路由协议都会根据自己的选路算法计算出到达目的网络的最佳路径,但是由于选路算法不同,不同的路由协议对某一个特定的目的网络可能选择的最佳路径不同。此时路由器根据路由优先级(决定了来自不同路由来源的路由信息的优先权)选择将具有最高路由优先级(数值最小)的路由协议计算出的最佳路径放置在路由表中,作为到达这个目的网络的转发路径。(优先级顺序:直连路由>静态路由>动态路由(OSPF>RIP)) 而对于一个特定的路由协议,可以发现到达目的网络的所有路径,根据选路

gcc语言编译原理_CompilingBinaryFilesUsingACompiler

Making plain binary?les using a C compiler(i386+) Cornelis Frank April10,2000 I wrote this article because there isn’t much information on the Internet concerning this topic and I needed this for the EduOS project. No liability is assumed for incidental or consequential damages in connection with or arising out of use of the information or programs contained herein. So if you blow up your computer because of my bad“English”that’s your problem not mine. 1Which tools do you need? An i386PC or higher. A Linux distribution like Red Hat or Slackware. GNU GCC compiler.This C compiler usually comes with Linux.To check if you’re having GCC type the following at the prompt: gcc--version This should give an output like: 2.7.2.3 The number probably will not match the above one,but that doesn’t really matter. The binutils for Linux. NASM Version0.97or higher.The Netwide Assembler,NASM,is an80x86assembler designed for portability and modularity.It supports a range of object?le formats,including Linux‘a.out’and ELF,NetBSD/FreeBSD,COFF,Microsoft16-bit OBJ and Win32.It will also output plain binary?les.Its syntax is designed to be simple and easy to understand, similar to Intel’s but less complex.It supports Pentium,P6and MMX opcodes,and has macro capability. Normally you don’t have NASM on your system.Download it from: https://www.sodocs.net/doc/0116847495.html,/pub/Linux/devel/lang/assemblers/ A text editor like pico or emacs.

1、GCC编译器的使用

linux下gcc编译器的使用 1、文件后缀名 .c C 源程序 .C C++ 源程序 .cc C++ 源程序 .cxx C++ 源程序 .m Objective –C源程序 .i 预处理过的c源程序 .ii 预处理过的C++源程序 .s 组合语言源程序 .S 组合语言源程序 .h 头文件 .o 目标文件 .a 存档文件 2、GCC常用选项 -c 通知GCC取消链接步骤,即编译源码并在最后生成目标文件; -Dmacro定义指定的宏,使它能够通过源码中的#ifdef进行检验 #define -static 指定程序编译时采用静态编译的方法; -E 不经过编译预处理程序的输出而输送至标准输出; -g获得有关调试程序的详细信息,它不能与-o选项联合使用; -Idirectory在包含文件搜索路径的起点处添加指定目录; -llibrary提示链接程序在创建最终可执行文件时包含指定的库; -O、-O2、-O3将优化状态打开,该选项不能与-g选项联合使用; -S要求编译程序生成来自源代码的汇编程序输出; -v启动所有警报; -Wall发生警报时取消编译操作,即将警报看作是错误; -Werror在发生警报时取消编译操作,即把报警当作是错误; -w 禁止所有的报警。 目前Linux下最常用的C语言编译器是GCC(GNU Compiler Collection),它是GNU项目中符合ANSI C标准的编译系统,能够编译用C、C++和Object C等语言编写的程序。GCC不仅功能非常强大,结构也异常灵活。最值得称道的一点就是它可以通过不同的前端模块来支持各种语言,如Java、 Fortran、Pascal、Modula-3和Ada等。开放、自由和灵活是Linux的魅力所在,而这一点在GCC上的体现就是程序员通过它能够更好地控制整个编译过程。

VC++动态链接库创建和调用全过程详解

1.概论 先来阐述一下DLL(Dynamic Linkable Library)的概念,你可以简单的把DLL看成一种仓库,它提供给你一些可以直接拿来用的变量、函数或类。在仓库的发展史上经历了“无库-静态链接库-动态链接库”的时代。 静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib中的指令都被直接包含在最终生成的EXE文件中了。但是若使用DLL,该DLL不必被包含在最终EXE文件中,EXE文件执行时可以“动态”地引用和卸载这个与EXE独立的DLL文件。静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库。 对动态链接库,我们还需建立如下概念: (1)DLL 的编制与具体的编程语言及编译器无关 只要遵循约定的DLL接口规范和调用方式,用各种语言编写的DLL都可以相互调用。譬如Windows提供的系统DLL(其中包括了Windows的API),在任何开发环境中都能被调用,不在乎其是Visual Basic、Visual C++还是Delphi。 (2)动态链接库随处可见 我们在Windows目录下的system32文件夹中会看到kernel32.dll、user32.dll和gdi32.dll,windows的大多数API都包含在这些DLL中。kernel32.dll中的函数主要处理内存管理和进程调度;user32.dll中的函数主要控制用户界面;gdi32.dll中的函数则负责图形方面的操作。 一般的程序员都用过类似MessageBox的函数,其实它就包含在user32.dll这个动态链接库中。由此可见DLL对我们来说其实并不陌生。 (3)VC动态链接库的分类 Visual C++支持三种DLL,它们分别是Non-MFC DLL(非MFC动态库)、MFC Regular DLL(MFC规则DLL)、MFC Extension DLL(MFC扩展DLL)。 非MFC动态库不采用MFC类库结构,其导出函数为标准的C接口,能被非MFC或MFC编写的应用程序所调用;MFC规则DLL 包含一个继承自CWinApp的类,但其无消息循环;MFC扩展DLL采用MFC的动态链接版本创建,它只能被用MFC类库所编写的应用程序所调用。 由于本文篇幅较长,内容较多,势必需要先对阅读本文的有关事项进行说明,下面以问答形式给出。 问:本文主要讲解什么内容? 答:本文详细介绍了DLL编程的方方面面,努力学完本文应可以对DLL有较全面的掌握,并能编写大多数DLL程序。 问:如何看本文? 答:本文每一个主题的讲解都附带了源代码例程,可以随文下载(每个工程都经WINRAR压缩)。所有这些例程都由笔者编写并在VC++6.0中调试通过。

实验三 vi编辑器及GCC编译器的使用

实验三vi编辑器及GCC编译器的使用 【实验目的】 一、掌握文本编辑器vi的使用方法 二、了解GNU gcc编译器 三、掌握使用GCC编译C语言程序的方法 【实验内容】 一、vi的三种工作模式: 1、命令模式: 执行相关文本编辑命令 2、输入模式: 输入文本 3、末行模式: 实现查找、替换、保存、多文件操作等等功能 二、进入vi,直接在Shell提示符下键入vi [文件名称],如果该文件在当前目录不存在,则vi创建之。 三、退出vi 1、在命令模式下输入“: wq”,保存文件并退出vi 2、若不需要保存文件,输入“: q” 3、若文件已修改,但不保存,输入“:

q!”强制退出vi 4、其它一些不常用的方法在此省略。 四、命令模式下的常用编辑命令 1、启动vi后,进入的是vi的命令模式 2、按i键,进入输入模式,可以进行文本的编辑,在输入模式下,按esc 键,可切换回命令模式 i: 光标位置不变,可在光标左侧插入正文 a: 光标位置向后退一格,可在光标左侧插入正文 o: 在光标所在行的下一行增添新行 O: 在光标所在行的上一行增添新行 I: 光标跳到当前行的开头 A: 光标跳到当前行的末尾 3、光标的移动 k、j、h、l分别等同于上、下、左、右箭头键 Ctrl+b,向上翻一页

Ctrl+f,向下翻一页 nH,将光标移到屏幕的第n行 nL,将光标移到屏幕的倒数第n行 4、删除文本 nX,删除光标所指向的后n个字符 D,删除光标右侧的所有字符(包括光标所指向的字符)db,删除光标左侧的全部字符 ndd,删除当前行和当前行以后的n行内容 5、粘贴和复制 p,将缓冲区的内容粘贴到当前字符的右侧 P,将缓冲区的内容粘贴到当前字符的左侧 yy,复制当前行到内存缓冲区 nyy,复制n行内容到内存缓冲区 6、搜索字符串 /str1,正向搜索字符串str1 n,继续搜索 ?str2,反向搜索字符串str2 7、撤销和重复 u,撤销前一条命令的执行结果 .,重复最后一条命令

网站链接协议

网站链接协议 本网站链接协议(以下简称“协议”)于 [日期] 签订并生效。 签订协议的一方:[贵公司的名称](简称“许可方”),根据 [省/市] 的法律成立和存续的公司,其总部位于: [贵公司的完整地址] 另一方:[被许可方的名称](简称“被许可方”),根据 [省/市] 的法律成立和存续的公司,其总部位于: [完整地址] 事实陈述 鉴于,许可方是一个互联网网站的所有者和运营者,该网站致力于[描述],其域名地址如下:[地址](“许可方网站”): 鉴于,被许可方是一个互联网网站的所有者和运营者,该网站致力于于 [描述],其域名地址如下: [地址](“被许可方网站”): 鉴于,被许可方想在许可方的网站上添加一个图形链接,从而使许可方网站的用户可以通过点击该链接访问被许可方网站; 在获取本协议中规定的补偿的情况下,许可方愿意向被许可方提供指向被许可方网站的链接。 因此,考虑到本协议包含的承诺和谅解,许可方和被许可方达成协议如下: 1.链接图形和链接位置 许可方应将被许可方的图像放置在许可方网站的首页,以便用户在标准的 VGA 监视器上以 [分辨率] 使用标准的行业浏览器(最新版的网景和微软 Internet Explorer)在全屏模式下,加载许可方的首页就能看见被许可方的链接图形。在此配置下,被许可方图形的尺寸不应小于 [数量] 像素 X [数量] 像素。当用户点击被许可方的图像时,会通过用户的 Web 浏览器将用户从许可方的网站导至被许可方的网站。 被许可方应使放置于许可方网站上的链接导向被许可方的首页,不应链接一个自动重新加载的页面或未与用户进一步交互就自动转向另一个页面。

linux系统下C编译器GCC入门

linux系统下C编译器— gcc 入门 <一>gcc简介 Linux系统下的gcc(GNU C Compiler)是GNU推出的功能强大、性能优越的多平台编译器,是GNU的代表作品之一。gcc是可以在多种硬体平台上编译出可执行程序的超级编译器,其执行效率与一般的编译器相比平均效率要高20%~30%。gcc编译器能将C、C++语言源程序、汇程式化序和目标程序编译、连接成可执行文件,如果没有给出可执行文件的名字,gcc将生成一个名为 a.out的文件。在Linux系统中,可执行文件没有统一的后缀,系统从文件的属性来区分可执行文件和不可执行文件。而gcc则通过后缀来区别输入文件的类别,下面我们来介绍gcc所遵循的部分约定规则。 .c为后缀的文件,C语言源代码文件; .a为后缀的文件,是由目标文件构成的档案库文件; .C,.cc或.cxx 为后缀的文件,是C++源代码文件; .h为后缀的文件,是程序所包含的头文件; .i 为后缀的文件,是已经预处理过的C源代码文件; .ii为后缀的文件,是已经预处理过的C++源代码文件; .m为后缀的文件,是Objective-C源代码文件; .o为后缀的文件,是编译后的目标文件; .s为后缀的文件,是汇编语言源代码文件; .S为后缀的文件,是经过预编译的汇编语言源代码文件。 <二>gcc的执行过程 虽然我们称gcc是C语言的编译器,但使用gcc由C语言源代码文件生成可执行文件的过程不仅仅是编译的过程,而是要经历四个相互关联的步骤∶预处理(也称预编译,Preprocessing)、编译(Compilation)、汇编(Assembly)和连接(Linking)。命令gcc首先调用cpp进行预处理,在预处理过程中,对源代码文件中的文件包含(include)、预编译语句(如宏定义define等)进行分析。接着调用cc1进行编译,这个阶段根据输入文件生成以.o为后缀的目标文件。汇编过程是针对汇编语言的步骤,调用as进行工作,一般来讲,. S为后缀的汇编语言源代码文件和汇编,.s为后缀的汇编语言文件经过预编译和汇编之后都生成以.o为后缀的目标文件。当所有的目标文件都生成之后,gcc就调用ld来完成最后的关键性工作,这个阶段就是连接。在连接阶段,所有的目标文件被安排在可执行程序中的恰当的位置,同时,该程序所调用到的库函数也从各自所在的档案库中连到合适的地方。 <三>gcc的基本用法和选项 在使用gcc编译器的时候,我们必须给出一系列必要的调用参数和文件名称。g cc编译器的调用参数大约有100多个,其中多数参数我们可能根本就用不到,这里只介绍其中最基本、最常用的参数。

《Linux操作系统》实验十-UNIX gcc编译器的使用与编程环境

《Linux操作系统》 实验报告 实验十:UNIX gcc编译器的使 用与编程环境

一、实验目的 (1)掌握gcc和g++的用法; (2)了解目标代码、库函数的使用; (3)掌握静态库和共享库的构造与使用; (4)掌握多模块和多语言联合开发方法; (5)掌握make命令和Makefile文件的使用。 二、实验环境 一台装有Windows操作系统PC机,上装有虚拟机系统VMWare,实验过程通过VMWare系统启Linux系统工作。 三、实验内容与实验过程及分析 一、C/C++编程 1、C语言版“Hello World” 用vi编辑一个名为hello.c的文件,其内容为 #include main() { printf(”Hello World! C\n”); } 编译并执行程序。 编译方法为: cc hello.c // 生成可执行程序 a.out cc –o hello hello.c // 生成可执行程序hello cc –c hello.c //生成目标文件hello.o cc –S hello.c //生成汇编语言程序hello.s 执行程序: ./a.out #执行当前目录内,刚编译生成的a.out程序 ./hello #执行当前目录内,刚编译生成的hello程序

2、组合编程 设有C语言文件f1.c,内容为: #include f1(int arg){ printf(”f1: you passed %d\n”,arg); } C语言文件f2.c。内容为: #include f2(char *arg){ printf(”f2: you passed %s\n”,arg); } C语言文件m.c。内容为: #include main(){ f1(16); f2(”Hello World!”); } 请使用vi编辑并生成以上程序,分别用以下方法编译,观察生成文件或运行生成的可执行程序: cc –c f1.c f2.c #生成 f1.o 和 f2.o cc –S f1.c f2.c # 生成 f1.s 和 f2.s;可用vi或cat查看它们的内容cc –o mp m.c f1.c f2.c # 生成mp,执行方法为:./mp cc –o m m.c f1.o f2.o # 生成m,执行方法为:./m

链接地址和运行地址

1、运行地址<--->链接地址:他们两个是等价的,只是两种不同的说法。 2、加载地址<--->存储地址:他们两个是等价的,也是两种不同的说法。 运行地址:程序在SRAM、SDRAM中执行时的地址。就是执行这条指令时,PC应该等于这个地址,换句话说,PC等于这个地址时,这条指令应该保存在这个地址内。 加载地址:程序保存在Nand flash中的地址。 位置无关码:B、BL、MOV都是位置位置无关码。 位置有关码:LDR PC,=LABEL等类似的代码都是位置有关码。 下面我们来看看一个Makefile文件 sdram.bin : head.S leds.c arm-linux-gcc -c -o head.o head.S arm-linux-gcc -c -o leds.o leds.c arm-linux-ld -Ttext 0x30000000 head.o leds.o -o sdram_elf arm-linux-objcopy -O binary -S sdram_elf sdram.bin arm-linux-objdump -D -m arm sdram_elf > sdram.dis clean: rm -f sdram.dis sdram.bin sdram_elf *.o 我们可以看到sdram_elf的代码段是从0x30000000地址开始存放,这个地址我们称之为运行地址。为什么从这个地址开始存放,因为SDRAM的起始地址是0x30000000. 下面来看看一个启动代码 @************************************************************************* @ File:head.S @ 功能:设置SDRAM,将程序复制到SDRAM,然后跳到SDRAM继续执行 @************************************************************************* .equ MEM_CTL_BASE, 0x48000000 .equ SDRAM_BASE, 0x30000000 .text .global _start _start:

第3章 播种机械

第三节 播种机械 第一节 概述 播种是农业生产过程中六大环节之一,播种机械化是农业机械化过程中最为复杂,也是最为艰巨的工作。播种机械所面对的播种方式、作物种类、品种变化繁多,这就需要播种机械有较强的适应性和能满足不同种植要求的工作性能。 一、播种方法 我国地域辽阔,作物生产的环境、条件、种植方式等多种多样,南北方有着明显的差异。北方表现为旱地作业,以向土壤中播入规定量的种子为主要种植手段,所用机具为播种机械,这样可充分利用土壤中的水分和温度使之出苗、生长,适时播种成为关键。而南方则表现为水田作业,种植方式主要是幼苗移栽,所用机械为栽植机械或插秧机械。但是,近几年来有些作物的种植方式发生了逆转,如玉米、棉花出现了工厂化育苗然后进行移栽,且已证明在干旱缺水地区大有取代播种机的趋势。而世代以栽植为主要种植手段的水稻、地瓜等作物,由于种植技术的革新现在出现了直播(水稻须进行种子催芽处理,地瓜须进行防腐处理),可大大简化生产过程,降低作业周期和生产成本。上述一些先进的种植手段由于技术、设备、条件、环境等因素的限制,目前尚处于小范围试用阶段,真正用于现阶段农业生产的种植方式仍然是经典的和传统的,总结起来大致有以下几种方式: 1、条播:将种子按要求的行距、播量和播深成条的播入土壤中,

然后进行覆土镇压的方式。种子排出的形式为均匀的种子流,主要应用于谷物播种:小麦、谷子、高粱、油菜等。 2、穴播:(点播):按照要求的行距、穴距、穴粒数和播深,将种子定点投入种穴内的方式。主要应用于中耕作物播种:玉米、棉花、花生等。与条播相比,节省种子、减少出苗后的间苗管理环节,充分利用水肥条件,提高种子的出苗率和作业效率。 3、精密播种:按精确的粒数、间距、行距、播深将种子播入土壤的方式。是穴播的高级形式。 4、撒播:将种子按要求的播量撒布于地表的方式。一般作物播种很少使用这种方法,多用于大面积种草、植树造林的飞机撒播。 二、播种机械的类型和一般构造 1、类型:谷物条播机,中耕作物穴播机、精密播种机(该机在结构上与穴播机及其相似)。三种机型的辅助部件基本相同,只是其核心工作部件——排种器有较大差异。

arm-linux-gcc 常用参数讲解 gcc编译器使用方法

arm-linux-gcc常用参数讲解gcc编译器使用方法 我们需要编译出运行在ARM平台上的代码,所使用的交叉编译器为arm-linux-gcc。下面将arm-linux-gcc编译工具的一些常用命令参数介绍给大家。 在此之前首先介绍下编译器的工作过程,在使用GCC编译程序时,编译过程分为四个阶段: 1. 预处理(Pre-Processing) 2. 编译(Compiling) 3. 汇编(Assembling) 4. 链接(Linking) Linux程序员可以根据自己的需要让GCC在编译的任何阶段结束,以便检查或使用编译器在该阶段的输出信息,或者对最后生成的二进制文件进行控制,以便通过加入不同数量和种类的调试代码来为今后的调试做好准备。和其它常用的编译器一样,GCC也提供了灵活而强大的代码优化功能,利用它可以生成执行效率更高的代码。 以文件example.c为例说明它的用法 0. arm-linux-gcc -o example example.c 不加-c、-S、-E参数,编译器将执行预处理、编译、汇编、连接操作直接生成可执行代码。 -o参数用于指定输出的文件,输出文件名为example,如果不指定输出文件,则默认输出 a.out 1. arm-linux-gcc -c -o example.oexample.c -c参数将对源程序example.c进行预处理、编译、汇编操作,生成example.0文件 去掉指定输出选项"-o example.o"自动输出为example.o,所以说在这里-o加不加都可以 2.arm-linux-gcc -S -o example.sexample.c -S参数将对源程序example.c进行预处理、编译,生成example.s文件 -o选项同上 3.arm-linux-gcc -E -o example.iexample.c -E参数将对源程序example.c进行预处理,生成example.i文件(不同版本不一样,有的将预处理后的内容打印到屏幕上) 就是将#include,#define等进行文件插入及宏扩展等操作。 4.arm-linux-gcc -v -o example example.c 加上-v参数,显示编译时的详细信息,编译器的版本,编译过程等。 5.arm-linux-gcc -g -o example example.c -g选项,加入GDB能够使用的调试信息,使用GDB调试时比较方便。 6.arm-linux-gcc -Wall -o example example.c -Wall选项打开了所有需要注意的警告信息,像在声明之前就使用的函数,声明后却没有使用的变量等。 7.arm-linux-gcc -Ox -o example example.c -Ox使用优化选项,X的值为空、0、1、2、3 0为不优化,优化的目的是减少代码空间和提高执行效率等,但相应的编译过程时间将较长并占用较大的内存空间。 8.arm-linux-gcc -I /home/include -o example example.c -Idirname: 将dirname所指出的目录加入到程序头文件目录列表中。如果在预设系统及当前目录中没有找到需要的文件,就到指定的dirname目录中去寻找。 9.arm-linux-gcc -L /home/lib -o example example.c

分散加载描述文件

7.5 分散加载描述文件 在7.3节中已经简单介绍了映像的组成,也介绍了如何用命令选项来构建简单结构的映像。要构建映像的存储器映射,链接器必须有:描述节如何分组成区的分组信息、描述映像区在存储器映射中的放置地址的放置信息。 分散加载机制允许为链接器指定映像的存储器映射信息,可实现对映像组件分组和布局的全面控制。分散加载通常仅用于具有复杂存储器映射的映像(尽管也可用于简单映像),也就是适合加载和执行时内存映射中的多个区是分散的情况。本节将对armlink所使用的分散加载描述文件作详细介绍。 7.5.1 分散加载机制 7.5.1.1 何时使用分散加载机制 链接命令行选项提供了一些对数据和代码布局的控制,但如果要对布局进行全面控制则需要比命令行选项更详细的指令。对于以下一些情况,就需要或最好使用分散加载描述文件: 复杂存储器映射:代码和数据需要放在多个不同存储器区域,必须详细指明哪个节放在哪个存储器空间。 不同存储器类型:许多系统包含FLASH、ROM、SDRAM和快速SRAM。利用分散加载可将代码和数据放置在最适合的存储器类型中。例如,中断代码可能放在快 速SRAM中,以改进中断响应时间,而将不频繁使用的配置信息可能放在较慢的 FLASH中。 存储器映射I/O:分散加载机制可将数据节精确放在存储器的某个地址,便于访问外设映射内存。 固定位置的函数:可以将函数放在存储器中的一个固定位置,即使周围的应用程序已经被修改并重新编译。 使用符号识别堆和栈:链接程序时,分散加载机制可为堆和栈的位置定义符号。 在实现嵌入式系统时,通常会需要使用分散加载机制,因为这些系统一般都会使用ROM、RAM和存储器映射I/O。 注意,如果为Cortex-M3结构的处理器编译程序,此处理器结构有着一个固定的内存映射,可以使用分散加载文件来定义栈和堆。 链接时如要使用分散加载文件,则需使用链接命令选项--scatter description_file,详细内容参考7.2节。 7.5.1.2 为分散加载所定义的符号 当armlink使用分散加载描述文件创建映像时,它将创建一些区相关符号,在7.4节中已作详细介绍。仅当代码引用这些特殊符号时,链接器才创建它们。 当分散加载描述文件被使用时,7.4节中的符号Image$$RW$$Base、 Image$$RW$$Limit、Image$$RO$$Base、Image$$RO$$Limit、 Image$$ZI$$Base和Image$$ZI$$Limit不被定义。 若使用分散加载文件,但不指定任何区名并且不使用__user_initial_stackheap(),则库将生成一个错误信息。

农业机械的结构及原理

第一篇农业机械的结构与原理 绪论(3学时) 农业机械在农业现代化中的作用,农业机械的作业特点,农业科学、耕作制度和农业机械化的关系,农业机械学研究的主要领域,国内外农业机械的发展趋向,本课程的任务和学习方法。 要点:基本概念、国内外农业机械的发展趋向。 思考题: 1、精确农业的概念? 2、精确农业的工作过程? 3、精确农业的工艺流程图? 第一章耕地机械(8学时) 第一节铧式犁的基本构造和类型 一、铧式犁的主要类型 目前耕地机械的主要类型为铧式犁、圆盘犁、凿型犁。铧式犁的主要类型为牵引犁、半悬挂犁和悬挂犁等,根据农业生产的不同要求、自然条件变化、动力配备情况等,铧式犁在形式上又派生出一些具有现代特征的新型犁:双向犁、栅条犁、调幅犁、滚子犁、高速犁等。 二、铧式犁的基本组成 铧式犁主要由组成:犁架、主犁体、耕深调节装置、支撑行走装置、牵引悬挂装置等。主犁体为铧式犁的核心工作部件。 三、铧式犁的型号表达方式 部颁农机序列标准:1-耕整机械,2-种植施肥机械,3-田间管理和植保机械,4-收获机械,5-种子加工机械,6-农副产品加工机械,7-装卸运输机械,8-排灌机械,9-畜牧机械 四、主犁体的结构及功用犁铧:切开土垡引导土垡上升至犁壁,犁壁:破碎和翻扣土垡,犁侧板:平衡侧向力,犁柱:联结犁架与犁体曲面,犁托:联结犁体曲面与犁柱,犁踵:耐磨件,防止犁侧板尾部磨损,可更换。

第二节犁体曲面的工作原理 一、犁体曲面的类型 犁铧与犁壁共同组成了犁体曲面,由于曲面的参数不同、性能不同,犁体曲面可分为:翻土型、碎土型和通用型(又称:螺旋型、熟地型、半螺旋型)。二、犁体曲面的的工作原理 从两面楔到三面楔的工作过程,理想土垡的翻转过程,理想土垡的宽深比的确定。 第三节犁体曲面的形成原理及设计方法 一、犁体曲面的形成原理 犁体曲面的形状对加工土壤的质量有至关重要的影响。目前,所应用的犁体曲面的形状是经过长时间积累、不断修改、不断完善而形成的,是一个空间任意曲面,不可能用数学的方法来真实的描述,只能是用近似的方法,用做图原理来形成犁体曲面。犁体曲面的形成原理是由动线在空间按照一定的规律运动而成。 二、犁体曲面的设计要点 设计犁体曲面时所用的方法有三种:水平直元线法、倾斜直元线法、翻土曲线法。其中,水平直元线法技术最为成熟,应用最广。水平直元线、导曲线、元线角的变化规律是水平直元线形成犁体曲面的三大要素。犁体曲面的设计要点:1.首先了解当地农业生产中耕地作业的基本要求;2.根据农业要求确定可能出现的最大耕深;3.根据土壤性状及土垡稳定铺放原则确定宽深比K;4.根据作业要求确定犁体曲面的工作性能;5.进行设计计算和绘制设计工作图。 第四节犁体外载及犁耕牵引阻力 一、犁体外载特性 由于犁体曲面是一个既不规则又不对称的空间任意曲面,犁耕过程中,土壤对曲面的作用力成为一空间任意力系,在一般情况下,他们不可能简化成为一个合力。 二、犁耕牵引阻力及高略契金有理公式 犁耕牵引阻力:耕作时,作用在犁上的总阻力的纵向水平分力。该力与拖拉机前进方向相反,可由拖拉机的牵引力来平衡。高略契金有理公式的正确表达及主要含义。该公式的最大贡献是考虑了速度对犁耕牵引阻力的影响,但公式经常用

精密播种机的核心部件是排种器

精密播种机的核心部件是排种器,排种器按照工作原理可分为两大类:机械式排种器和气力式排种器。机械式精密排种器根据种子粒型和大小,利用排种器的型孔将种子从种群中分离出来,充种、清种和卸种等环节靠种子自重或机械装置来完成。具有结构简单,成本低等优点,在生产应用中得到了广泛应用[fls}。其缺点是对 种子尺寸要求严格,作业速度较低,常用于中小型播种机上。如阶梯型内窝孔式排 种器、内侧充种垂直圆盘排种器、转勺式排种器、磨纹式排种器、水平圆盘式和环 带式排种器。 气力式精密排种器通常由拖拉机动力输出带动风机产生真空吸力或空气压力,使种子按粒(单粒或多粒)贴附在型孔上,充种或清种环节靠气力来完成。气力式精密播种器按排种原理的不同可分为气吸式、气压式和气吹式等种类播种器〔19]。气力式排种器具有适应性强、通用性好、不伤种和对种子尺寸要求不严等优越性,适应高速播种作业,在国内外己广泛用在大中型精密播种机上,近年来气力式精密排种器在我国应用范围逐步扩大。 气力式播种是一种利用气流的吸附力或压附力,将种子从种子堆分离出来,达到单粒或双粒的精量播种技术[tzol。气吸式精密排种器就是利用气流压力差从种子室吸取单粒种子并依次将其排出,它是以气流为载体完成排种的,与机械式精量排种

相比,具有省种,不伤种,作业速度高诸多优点,而且对种子相对尺寸要求不严,伤种少,易实现单粒播种,通用性好,作业速度提高潜力大,所以气吸式精密排种 4器一直是国内外科研的攻关重点。 排种器可谓精密播种机的心脏,其性能直接影响到播种质量。通常配置在种子 箱的底部或侧壁式箱内。 2. 3影响吸种性能的因素分析 通过对种子在各区域内的运动进行受力分析,可知影响排种器吸种可靠性的主

2路由器的工作原理

2路由器的工作原理 路由器(Router,又称路径器或宽频分享器)是一种计算机网路设备,它能将数据包通过一个个网路传送至目的地,这个过程称为路由。路由工作在OSI模型的第三层(即网路层,例如Internet Protocol(IP)层)。 路由器就是连接两个以上网路线路的设备。由於位於两个或更多个网路的交汇处,从而可在它们之间传递分组(一种数据的组织形式)。路由器与交换机(Switch)在概念上有一定重叠但也有不同:交换机泛指工作於任何网路层次的数据中继设备(尽管多指网桥),而路由器则更专注於网路层。 3什么是多路复用技术?及分类 多路复用技术是把多个低信道组合成一个高速信道的技术,它可以有效的提高数据链路的利用率,从而使得一条高速的主干链路同时为多条低速的接入链路提供服务,也就是使得网络干线可以同时运载大量的语音和数据传输。常见的多路复用技术包括频分多路复用(FDM)、时分多路复用(TDM)、波分多路复用(WDM)和码分多路复用(CDMA)其中时分多路复用又包括同步时分复用和统计时分复用。 4.DCS与FCS区别 FCS系统的核心是总线协议,即总线标准;FCS系统的基础是数字智能现场装置;FCS系统的本质是信息处理现场化。主要是总线结构DCS称之为集散控制系统。 1RS485与RS232的区别 EIA-RS-232C 对电器特性、逻辑电平和各种信号线功能都作了规定。在TxD和RxD上:逻辑1(MARK)=-3V~-15V逻辑0(SPACE)=+3~+15V在RTS、CTS、DSR、DTR和DCD 等控制线上:信号有效(接通,ON状态,正电压)=+3V~+15V信号无效(断开,OFF状态,负电压)=-3V~-15V以上规定说明了RS-232C标准对逻辑电平的定义。对于数据(信息码):逻辑“1”(传号)的电平低于-3V,逻辑“0”(空号)的电平高于+3V;对于控制信号;接通状态(ON)即信号有效的电平高于+3V,断开状态(OFF)即信号无效的电平低于-3V,也就是当传输电平的绝对值大于3V时,电路可以有效地检查出来,介于-3~+3V 之间的电压无意义,低于-15V或高于+15V的电压也认为无意义,因此,实际工作时,应保证电平在±(3~15)V之间。EIA RS-232C 与TTL转换:EIA RS-232C 是用正负电压来表示逻辑状态,与TTL以高低电平表示逻辑状态的规定不同。因此,为了能够同计算机接口或终端的TTL器件连接,必须在EIA RS-232C 与TTL电路之间进行电平和逻辑关系的变换。实现这种变换的方法可用分立元件,也可用集成电路芯片。目前较为广泛地使用集成电路转换器件,如MC1488、SN75150芯片可完成TTL电平到EIA电平的转换,而MC1489、SN75154可实现EIA电平到TTL电平的转换。MAX232芯片可完成TTL←→EIA双向电平转换。RS-485 RS-485总线,在要求通信距离为几十米到上千米时,广泛采用RS-485 串行总线RS-485采用平衡发送和差分接收,因此具有抑制共模干扰的能力。加上总线收发器具有高灵敏度,能检测低至200mV的电压,故传输信号能在千米以外得到恢复。RS-485采用半双工工作方式,任何时候只能有一点处于发送状态,因此,发送电路须由使能信号加以控制。RS-485用于多点互连时非常方便,可以省掉许多信号线。应用RS-485 可以联网构成分布式系统,其允许最多并联32台驱动器和32台接收器。 DS18B20多点温度检测系统的设计 (2008-03-13 19:36) 本设计运用主从分布式思想,由一台上位机(PC微型计算机),下位机(单片机)多点温度数据采集,组成两级分布式多点温度测量的巡回检测系统.该系统采用 RS-232串行通讯标准,通过上位机(PC)控制下位机(单片机)进行现场温度采集.温度值既可以送回主控PC进行数据处理,由显示器显示.也可以由下位机单独工

gcc编译器 CFLAGS 标志参数说明

gcc编译器 CFLAGS 标志参数说明2012-11-14 15:10:28 分类:LINUX CFLAGS = -g -O2 -Wall -Werror -Wno-unused 编译出现警告性错误unused-but-set-variable,变量定义但没有使用,解决方法: 增加CFLAGS 或CPPFLAGS参数如下: CPPFLAGS=" -Werror -Wno-unused-but-set-variable" || exit 1 Gcc总体选项列表 后缀名所对应的语言 -S只是编译不汇编,生成汇编代码 -E只进行预编译,不做其他处理 -g在可执行程序中包含标准调试信息 -o file把输出文件输出到file里 -v打印出编译器内部编译各过程的命令行信息和编译器的版本 -I dir在头文件的搜索路径列表中添加dir目录 -L dir在库文件的搜索路径列表中添加dir目录 -static链接静态库 -llibrary连接名为library的库文件 ·“-I dir” 正如上表中所述,“-I dir”选项可以在头文件的搜索路径列表中添加dir目录。由于Linux 中头文件都默认放到了“/usr/include/”目录下,因此,当用户希望添加放置在其他位置的头文件时,就可以通过“-I dir”选项来指定,这样,Gcc就会到相应的位置查找对应的目录。 比如在“/root/workplace/Gcc”下有两个文件: #include int main() { printf(“Hello!!\n”); return 0; } #include

这样,就可在Gcc命令行中加入“-I”选项: [root@localhost Gcc] Gcc hello1.c –I /root/workplace/Gcc/ -o hello1 这样,Gcc就能够执行出正确结果。 小知识 在include语句中,“<>”表示在标准路径中搜索头文件,““”” 表示在本目录中搜索。故在上例中,可把hello1.c的“#include” 改为“#include “my.h””,就不需要加上“-I”选项了。 ·“-L dir” 选项“-L dir”的功能与“-I dir”类似,能够在库文件的搜索路径列表中添加dir目录。 例如有程序hello_sq.c需要用到目录“/root/workplace/Gcc/lib”下的一个动态库 libsunq.so,则只需键入如下命令即可: [root@localhost Gcc] Gcc hello_sq.c –L /root/workplace/Gcc/lib –lsunq –o hello_sq 需要注意的是,“-I dir”和“-L dir”都只是指定了路径,而没有指定文件,因此不能在 路径中包含文件名。 另外值得详细解释一下的是“-l”选项,它指示Gcc去连接库文件libsunq.so。由于在Linux 下的库文件命名时有一个规定:必须以lib三个字母开头。因此在用-l选项指定链接的库 文件名时可以省去lib三个字母。也就是说Gcc在对”-lsunq”进行处理时,会自动去链接 名为 libsunq.so的文件。 (2)告警和出错选项 Gcc的告警和出错选项如表3.8所示。 Gcc总体选项列表 选项含义 -ansi 支持符合ANSI标准的C程序 -pedantic 允许发出ANSI C标准所列的全部警告信息 -pedantic-error 允许发出ANSI C标准所列的全部错误信息 -w 关闭所有告警 -Wall 允许发出Gcc提供的所有有用的报警信息 -werror 把所有的告警信息转化为错误信息,并在告警发生时终止编译过程 下面结合实例对这几个告警和出错选项进行简单的讲解。 如有以下程序段: #include void main() { long long tmp = 1; printf(“This is a bad code!\n”);

相关主题