excel:OFFSET函数偏移时能怱列空格吗?

深入介绍Linux内核

语言编译过程就是把人类能理解的高阶语言转換成电脑硬体能理解和执行的二进位机器指令的过程。这种转換过程通常会產生一些效率不是很高的代码,所以对一些执行效率要求高或性能影响较大的部分代码通常就会直接使用低级组合语言来编写,或者对高阶语言编译產生的组合语言程式再进行人工修改最佳化处理。本章主要描述Linux m公共宣告的需要保留的公共储存空间位元组长度。符号指向该储存空间的第一个位址处。

符号的类型属性含有用於链结器和除错器的重定位资讯、指示符号号是外部的旗标以及一些其他可选资讯。对於m symbol,length

在bss区中宣告一个命名的公共区域。在ld链结过程中,某个目标档中的一个公共符号会与其他日标档中同名的公共符号合併。如果ld沒有找到一个符号的定义,而只是一个或多个公共符号,那麼ld就会分配指定长度length位元组的未初始化记忆体。length必须足一个絕对值运算式,如果ld找到多个长度不同但同名的公共符号,ld就会分配长度最大的空间。

该组合命令通知as把随后的语句组合到编号为subsection的data子区中。如果省略编号,则预设使用编号0。编号必须足絕对值运算式。

该组合命令会产生数个(repeat个)大少为size位元组的重复复制。大少值size可以为0或某个值,但是若size大于8时,则限定为8。每个重复位元组内容取自一个8位元组数。高4位元组为0,低4位元组是数值value。这3个参数值都是绝对值,size和value 是可选的。如果第2个逗号和value省略,value预设为0值;如果后两个参数都省略的话,则size预设为1。

该组合命令会使得链结器ld能看见符号symbol。如果在我们的目标档中定义了符号symbol,那麼它的值将能被链结过程中的其他目标档使用。若目标档中沒有定义该符号,那麼它的属性将从链结过程中其他目标档的同名符号中获得。这是透过设置符号symbol类型栏位中的外部位N_EX7来做到的。参考include/a.out.h档中的說明。

该组合命令在某个区中设置0个或多个整数值(80386系统为4位元组,同.long)。每个用逗号分开的运算式的值就是执行时刻的值。例如.int1234,567,0x89AB 。

为符号symbol指定的区域公共区域保留长度为length位元组的空间,所在的区和符号symbol的值是新的区域公共区块的值。分配的位址在bss区中,因此在执行时刻这些位元组值被清零。由於符号symbol沒有被宣告为全域的,因此链结器ld看不见。

这个组合命令指定0个或多个用逗号分开的l 6位元组大数(.byte,.word,.1ong,.quad,.octa分別对应1、2、4、8和16位元组数)。

这个组合命令会把当前区的位置计数器设置为值new_lc。new_lc是一个絕对值(运算式) ,或者是具有相同区作为子区的运算式,也即不能使用.org跨越各区。如果new_lc的区不对,那麼.org就不会起作用。请注意,位置计数器是基於区的,即以每个区作为计数起点。
当位置计数器值增长时,所跳跃过的位元组将被填入值fill。该值必须是絕对值。如果省略了逗号和fill,则fill预设为0值。

这个组合命令指定0个或多个用逗号分开的8位元组大数bignum。如果大数放不进8个位元组中,则取最低8个位元组。

这个组合命令指定某个区中0个或多个用逗号分开的2位元组数。对於每个运算式,在执行时刻都会產生一个16位元的值。

该组合命令產生size个位元组,每个位元组填值fill。这个参数均为絕对值。如果省略了逗号和fill,那麼fill的预设值就是0。

定义一个或多个用逗号分开的字串。在字串中可以使用转义字元。每个字串都自动附加一个NULL字元结尾。例如,.string “

通知as把随后的语句组合进编号为subsection的子区中。如果省略了编号subsection,则使用预设编号值0。

对於32位元机器,该组合命令含义与.short相同。

虽然as通常用来编写纯32位元的80X86代码,但是1995年后它对编写执行於真实模式或16位元保护模式的代码也提供有限的支援。为了让as 组合时产生16位元代码,需要在执行於16位元模式的指令语句之前添加组合命令‘.codel6’並且使用组合命令‘.code32’让as组译器切換回32位元代码组合方式。

as不区分16和32位元组合语句,在16位元和32位元模式下每条指令的功能完全一樣而与模式无关。as总是为组合语句產生32位元的指令代码而不管指令将执行在16位元还足32位元模式下。如果使用组合命令‘.codel6’让as处於16位元模式下,那麼as会自动为所有指令加上一个必要的运算元宽度首码而让指令执行在16位元模式。请注意,因为as为所有指令添加了额外的位址和运算元宽度首码,所以组合產生的代码长度和性能上将会受到影响。

由於在1991年开发Linux內核0.12时as组译器还不支持16位元代码,因此在编写和组合0.12內核真实模式下的引导啟动代码和初始化组合语言程式时使用了前面介绍的as86组译器。

-o 指定输出的目标档案名
-R 组合资料区和代码区

GNU gcc对ISO标準C89描述的C语言进行了一些扩展,其中一些扩充部分已经包括进ISO C99标準中。本节给出內核中经常用到的一些gcc扩充语句的說明。在后面章节程式注释中也会随时对遇到的扩展语句给出简单的說明。

使用gcc组译器编译C语言程式时通常会经过四个处理阶段,即预处理阶段、编译阶段、组合阶段和链结阶段,见图3-3所示。

在前处理阶段中,gcc会把C程式传递给C前处理器CPP,对C语言程式中指示符号和巨集进行替換处理,输出纯C语言代码;在编译阶段,gcc把C语言程式编译生成对应的与机器相关的as组合语言代码;在组合阶段,as组译器会把组合代码转換成机器指令,並以特定二进位格式输出保存在目标档中;最后GNU ld链结器把程式的相关目标档组合链结在一起,生成程式的可执行映射档。呼叫gcc的命令行格式与编译组合语言的格式类似:

其中infile是输入的C语言档;outfile是编译產生的输出档。对於某次编译过程,並非一定要全部执行这四个阶段,使用命令行选项可以令gcc编译过程在某个处理阶段后就停止执行。例如,使用‘-S’选项可以让gcc在输出了C程式对应的组合语言程式之后就停止执行;使用‘-c’选项可以让gcc只生成目标档而不执行链结处理,见如下所示。

在编译象Linux內核这樣的包含很多来源程式档的大型程式时,通常使用make工具软体对整个程式的编译过程进行自动管理,详见后面說明。

本节介绍内核C语言程式中接触到的嵌入式的组合(行内组合)语句。由于我们通常编制C程式过程中一般很少用到嵌入式组合代码,因此这里有必要对其基本格式和使用方法进行说明。具有输入和输出参数的嵌入组合语句的基本格式为:

: 会被修改的暂存器);

除第l行以外,后面带冒号的行若不使用就都可以省略。其中,“asm”是行內组合语句关键字;“组合语句”是你写组合指令的地方;“输出暂存器”表示当这段嵌入组合执行完之后,哪些暂存器用於存放输出资料。此地,这些暂存器会分别对应一C语言运算式值或一个记忆体位址;“输入暂存器”表示在开始执行组合代码时,这裡指定的一些暂存器中应存放的输入值,它们也分別对应著一C变数或常数值。“会被修改的暂存器”表示你已对其中列出的暂存器中的值进行了改动,gcc编译器不能再依赖于它原先对这些暂存器载入的值。如果必要的话,gcc要重新载入这些暂存器。因此我们需要把那些沒有在输出,输入暂存器部分列出,但是在组合语句中明确使用到或隐含使用到的暂存器名列在这个部分中。

下面我们用例子来說明嵌入组合语句的使用方法。这裡列出了kernel/traps.c档中第22行开始的一段代码作为例子来详细解說。为了能看得更清楚一些,我们对这段代码进行了重新排列和编号。

这段l0行代码定义了一个嵌入组合语言巨集函数。通常使用组合语句最方便的方式是把它们放在一个巨集內。用小括号括住的组合语句(大括弧中的语句):({})可以作为运算式使用,其中最后一行上的变数_ _res(第10行)是该运算式的输出值,见下一节说明。

因为巨集式需要定义在一行上,因此这裡使用反斜線‘’将这些语句连成一行。这条巨集定义将被替換到程式中引用该巨集名称的地方。第l行定义了巨集的名称,也即是巨集函数名称get_seg_byte(seg,addr)。第3行定义了一个暂存器变数_res。该变数将被保存在一个暂存器中,以便於快速存取和操作。如果想指定暂存器(例如cax) ,那麼我们可以把该句写成“register char _ _res asm (“ax”);”,其中“asm”也可以写成“_ _asm_ _”。第4行上的“_ _asm_ _"表示嵌入组合语句的开始。从第4行到第7行的4条语句是AT & T格式的组合语句。另外,为了让gcc编译產生的组合语言程式中暂存器名称前有一个百分号”%”,在嵌入组合语句暂存器名称前就必须写上两个百分号”%%”。

第8行即是输出暂存器,这句的含义是在这段代码执行结束后将eax所代理的暂存器的值放入_ _reg变数中,作为本函数的输出值,“=a”中的“a”称为载入代码,”=”表示这是输出暂存器,並且其中的值将被输出值替代。第9行表示在这段代码开始执行时将seg放到eax暂存器中,“0”表示使用与上面同个位置的输出相同的暂存器,而(*(addr))表示一个记忆体偏移位址值。为了在上面组合语句中使用该位址值,嵌入组合语言程式规定把输出和输入暂存器统一按顺序编号,顺序是从输出暂存器序列从左到右从上到下以”%0开始、分別记为%0、%l、…%9。因此,输出暂存器的编号是%0(这裡只有一个输出暂存器) ,输入暂存器前一部分(”0”(seg))的编号是%1,而后部分的编号是%2 。上面第6行月的%2即代表(*(addr))这个记忆体偏移量。

现在我们来研究4--7行上的代码作用。第一句将fs段暂存器的內容入堆疊; 第二句将eax中的段值代入fs段暂存器;第三句是把fs :(*(addr))所指定的位元组放入al暂存器中。当执行完组合语句后,输出暂存器eax的值将被放入_ _res, 作为该巨集函数(区块结构运算式)的返回值。很简单,不是吗?

透过上面分析,我们知道,巨集名称中的seg代表一指定的记忆体段值,而addr表示一记忆体偏移位址量。到现在为止,我们应该很清楚这段程式的功能了吧! 该巨集函数的功能是从指定段和偏栘值的记忆体位址处取一个位元组。再在看下一个例子。

l. 3行这三句是平常的组合语句,用以清方向位,重复保存值。第4行說明这段嵌入组合语言程式沒有用到输出暂存器。第5行的含义是:将count-l的值载入到ecx暂存器中(载入代码是”C”),fill_value载入到eax中,dest放到edi中。为什麼要让gcc编译程序去做这樣的暂存器值的载入,而不让我们自己做呢? 因为gcc在它进行暂存器分配时可以进行某些最佳化工作,例如fill_value值可能已经在eax中。如果是在一个回圈语句中的话,gcc就可能在整个回圈操作中保留eax,这樣就可以在每次回圈中少用一个movl语句。

最后一行的作用是告诉gcc这些暂存器中的值已经改变了,在gcc知道你拿这些暂存器做些什麼后,能夠对gcc的最佳化操作有所帮助。表3-4中是一些你可能会用到的暂存器载入代码及其具体的含义。

下面的例子不是让你自己指定哪个变数使用哪个暂存器,而是让gcc为你选择。

指令"leal"用於计算有效位址,但这裡用它来进行一些简单计算。第l条句组合语句"leal(rl,r2,4),r3"语句表示rl+r2*4 →r3。这个例子可以非常快地将x乘5。 其中“%0”、“%l”是指gcc自动分配的暂存器。这裡“%l”代表输入x要放入的暂存器,“%0”表示输出值暂存器。输出暂存器代码前一定要加等于号。如果输入暂存器的代码是0或为空时,则說明使用与相应输出一樣的暂存器。所以,如果gcc将r指定为eax的话,那麼上面组合语句的含义即为:

注意:在执行代码时,如果不希望组合语句被GCC最佳化而作修改,就需要在asm符号后面添加关键字volatile,见下面所示。这两种宣告的区別在於程式相容性方面。建议使用后一种宣告方式。

关键字volatile也可以放在函数名前来修饰函数,用来通知gcc编译器该函数不会返回。这樣就可以让gcc產生更好一些的代码。另外,对於不会返回的函数,这个关键字也可以用来避免gcc產生假警告资讯。例如mm/memory.c中的如下语句說明函数do_exit( )和oom( )不会再返回到呼叫者代码中:

下面的例子不是让你自己指定哪个变数使用哪个暂存器,而是让gcc为你选择。

指令"leal"用於计算有效位址,但这裡用它来进行一些简单计算。第l条句组合语句"leal(rl,r2,4),r3"语句表示rl+r2*4 →r3。这个例子可以非常快地将x乘5。 其中“%0”、“%l”是指gcc自动分配的暂存器。这裡“%l”代表输入x要放入的暂存器,“%0”表示输出值暂存器。输出暂存器代码前一定要加等于号。如果输入暂存器的代码是0或为空时,则說明使用与相应输出一樣的暂存器。所以,如果gcc将r指定为eax的话,那麼上面组合语句的含义即为:

注意:在执行代码时,如果不希望组合语句被GCC最佳化而作修改,就需要在asm符号后面添加关键字volatile,见下面所示。这两种宣告的区別在於程式相容性方面。建议使用后一种宣告方式。

关键字volatile也可以放在函数名前来修饰函数,用来通知gcc编译器该函数不会返回。这樣就可以让gcc產生更好一些的代码。另外,对於不会返回的函数,这个关键字也可以用来避免gcc產生假警告资讯。例如mm/memory.c中的如下语句說明函数do_exit( )和oom( )不会再返回到呼叫者代码中:

下面再例举一个较长的例子,如果能看得懂,那就說明嵌入组合代码对你来說基本沒问题了。这段代码是从include/string.h档中摘取的,是strncmp( )字串比较函数的一种实现。需要注意的是,其中每行中的“
”是用於gcc预处理程式输出列表好看而设置的,含义与C语言中相同。

}

本站内容均为网络爬虫自动抓取的开放信息,如果相关页面侵犯了您的权益,请点击上方举报按钮进行举报。

}

我要回帖

更多关于 oracle trim去空格无效 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信