3.3.2 嵌入汇编(摘自<linux内核完全剖析>)
内核C语言程序嵌入式汇编代码又叫内联汇编,具有输入和输出参数的嵌入汇编语句的基本格式为:
**************************************************
asm("汇编语句"
: 输出寄存器
: 输入寄存器
: 会被修改的寄存器);
**************************************************
除第一行外,后面带冒号的行若不使用就可以省略。其中,"asm"是内联汇编语句关键词;"汇编语句"是写汇编指令的地方;“输出寄存器”表示当这段嵌入式汇编执行完成后,哪些寄存器用于存放输出数据。这些寄存器会分别对应一个C语言表达式的值或一个内存地址;“输入寄存器”表示在开始执行汇编代码时,这里指定的寄存器中应存放输入值,他们也分别对应着一C语言变量或常数值。“会被修改的寄存器”表示你已经对其中列出的寄存器额值进行了改动,gcc编译器不能再依赖于它原先对这些寄存器加载的值。如果必要的话,gcc需要重新加载这些寄存器。因此我们要把那些没有在输入/输出寄存器部分列出,但是在汇编语句中明确使用到或隐含使用到的寄存器名列在这个部分中。
例子:
asm("cld\n\t"
"rep\n\t"
"stol"
/*没有输出寄存器*/
"c"(count-), "a"(fill_value), "D"(dest)
"%eax", "%edi");
1-3行汇编语句用于清零方向位,重复保存值。第4行说明这段嵌入式汇编程序没有用到输出寄存器。第5行的含义是:将count-1的值加载到ecx寄存器中(加载代码是"c"),fill_value加载到eax中,dest放到edi中。为什么要让gcc编译程序去做这些寄存器值的加载,而不让我们自己做呢?因为gcc在它进行寄存器分配时可以进行某些优化工作,例如fill_value值可能已经在eax中,这样就可以在每次循环中少用一个movl语句。最后一行是告诉gcc这些寄存器的值已经改变了。在gcc知道你拿这些寄存器做些什么后,能够对gcc的优化操作有所帮助。
常用寄存器加载代码说明
a - 使用寄存器eax b - 使用寄存器ebx c - 使用寄存器ecx d - 使用寄存器edx S - 使用esi D - 使用edi
q - 使用动态分配字节可寻址寄存器(eax,ebx,ecx,edx) r - 使用任意动态分配的寄存器 g - 使用通用有效的地址即可(eax,ebx,ecx,edx或内存变量)
A - 使用eax与edx联合(64位) + - 表示操作数可读可写 m - 使用内存地址 o - 使用内存地址并可以加偏移值
I - 使用常数0~31 J - 使用常数0~63 K - 使用常数0~255 L - 使用常数0~65535 M - 使用常数0~3 N - 使用1字节常数(0~255)
O - 使用常数0~31 = - 输出操作数,输出值将替换前值 & - 早起会变的(earlyclobber)操作数。表示在使用完操作数之前,内容会被修改。
下面这个例子不是让你自己制定哪个变量使用哪个寄存器,而是让gcc为你选择。
asm("leal (%1, %1, 4), %0"
: "=r"(y)
: ""(x);
注意,在执行代码时,如果不希望汇编语句被gcc优化而修改,就需要在asm符号后面添加关键词volatile:
asm volatile(.....);
或者更详细的说明为:
_asm_ _volatile_(.....);
这两种声明的区别在于程序兼容性方面。建议使用后一种声明方式。
关键词volatile也可以放在函数名前来修饰函数,用来通知gcc编译器该函数不会返回。这样就可以让gcc产生更好一些的代码。另外,对于不会返回的函数,这个关键词也可以用来避免gcc产生假的警告信息。
下面这个例子是从include/string.h文件中摘取的,是strncmp()字符串比较函数的一种实现。其中每行中的"\n\t"是用于gcc预处理程序输出好看而设置的,含义与C语言中相同。
extern inline int strncmp(const char *cs,const char *ct,int count)
{
register int _res;
_asm_("cld\n"
"1:\tdecl %3\n\t"
"js 2f\n\t"
"lodsb \n\t"
"scasb\n\t"
"jne 3f\n\t"
"testb %%al, %%al\n\t"
"jne 1b\n"
"2:\txorl %%eax,%%eax\n\t"
"jmp 4f\n"
"3:\tmovl $1, %%eax\n\t"
"jl 4f\n\t"
"negl %%eax\n"
"4:"
:"=a"(_res)
:"D"(cs),"S"(ct),"c"(count)
:"si","di","cx");
return _res;
}
下面对这段代码简单注释:
extern关键字将这个函数定义为外部函数,这个是C语言中的关键字,以便于其他文件中的代码来调用这个函数。inline关键字将这个函数定义为内联函数,当其他函数调用这个函数的时候,gcc会把该函数的代码集成到调用函数的代码中去。
函数体开始部分首先声明一个寄存器整型变量_res,接下来是嵌入式汇编部分,最后把这个_res做为返回值返回。汇编代码先从最后三行解释,倒数第三行"=a"表示将eax寄存器做为输出寄存器,并将eax中的值赋值给_res这个函数返回值,倒数第二行定义三个输入寄存器,分别将cs,ct这两个字符串指针赋值给edi和esi寄存器,然后将比较字符的数量count赋值给ecx寄存器。最后一行表示程序运行过程中除了输入输出寄存器以外,还会改变esi,edi和ecx的值,这三个寄存器虽然都已经定义为输入寄存器,但是在会被改变的寄存器列表中重复说明是为了让gcc编译器更容易进行优化。
接下来从汇编代码开始说明:
第一行,cld命令用来清理标志寄存器的方向位DF
第二行,定义了标号1,执行decl(decrement--减)命令,后缀 l 定义这个命令的操作数为long型(32位),%3代表输入输出寄存器列表里面的第三个寄存器,嵌入式汇编规定把输入输出寄存器统一按顺序编号,顺序是从输出寄存器序列从左到右,从上到下以%0开始,分别记为%0、%1、....%9。因此%3就表示%ecx寄存器,第二行代码的意思就是把ecx寄存器的值减一,结果会影响标志位,如果结果小于0就会把符号标志位SF设置为1,表示结果为负。
第三行,js指令根据SF符号标志位的值来决定要不要跳转,如果ecx的值为负就跳转到标号2处执行。
第四行,如果ecx的值不小于0就执行lodsb指令,该指令将ds:[esi]处的一个字节(也就是一个字符的长度)赋值给al寄存器,并将esi+1。
第五行,scabs是串扫描指令,将al寄存器内的字符和es:[edi]处的值进行比较(es:[edi] - al),并将edi+1,比较结果影响zf和sf标志位。
第六行,根据上一句代码的比较结果来决定要不要执行跳转,如果扫描结果是不相等(即zf的值为0),就跳转到标号3处执行进一步判断是大于还是小于。
第七行,testb指令将%al寄存器的值和自己相与,然后根据结果更新zf,sf标志位的值,就可以根据标志位的值判断al是否为NULL字符,也就是字符串结束字符。
第八行,如果标志位zf不为1,就表示al不是结束字符,而且ds:[esi]处的值和es:[edi]地址处的值相等,就跳转到标号1继续比较。
第九行,标号2,如果上一行判断al是空字符就执行xorl指令将eax和自己进行异或运算,结果就是把eax的值归零。在第二行如果count的值减一后小于0,也会跳转到这一行。
第十行,直接跳转到标号4,返回eax中的值,也就是0,然后结束程序。
第十一行,标号3,第五行扫描结果如果不相等,就执行movl将eax的值设置为1
第十二行,jl是如果小于才跳转,判断第五行扫描结果如果将符号标志位sf设置为1,就表示字符串1的值大于al中的字符串2的值,跳转至标号4,返回1。
第十三行,如果结果是字符串1小于字符串2(es:[edi] - al < 0),上一行jl就不会跳转,就会执行本行的negl,将eax中的1进行求补运算,就得到-1,并返回-1,结束程序。
3.3.2 嵌入汇编(摘自<linux内核完全剖析>)的更多相关文章
- Linux内核官方文档atomic_ops.txt【摘自Linux 内核文档】
摘自Linux内核文档 Documentation/atomic_ops.txt,不是本人原创 Semantics and Behavior of Atomic and Bitmask Operati ...
- 第三十二课 linux内核链表剖析
__builtin_prefetch是gcc扩展的,用来提高访问效率,需要硬件的支持. 在标准C语言中是不允许static inline联合使用的. 删除依赖的头文件,将相应的结构拷贝到LinuxLi ...
- 第32课 Linux内核链表剖析
1. Linux内核链表的位置及依赖 (1)位置:{linux-2.6.39}\\include\linux\list.h (2)依赖 ①#include<linux\types.h> ② ...
- linux内核链表剖析
1.移植linux内核链表,使其适用于非GNU编译器 2.分析linux内核中链表的基本实现 移植时的注意事项 清除文件间的依赖 剥离依赖文件中与链表实现相关的代码 清除平台相关的代码(GNU C) ...
- 《linux 内核全然剖析》 fork.c 代码分析笔记
fork.c 代码分析笔记 verifiy_area long last_pid=0; //全局变量,用来记录眼下最大的pid数值 void verify_area(void * addr,int s ...
- 《linux 内核全然剖析》编译linux 0.12 内核 Ubuntu 64bits 环境
我×.. . 最终好了,大概3 4个小时吧...各种毛刺问题.终究还是闯过来了.. .. ubuntu2@ubuntu:~/Downloads/linux-0.00-050613/linux-0.00 ...
- 《linux 内核全然剖析》sched.c sched.h 代码分析笔记
版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/u011368821/article/details/25129835 sched.c sched.h ...
- 《linux 内核全然剖析》 chapter 2 微型计算机组成结构
微型计算机组成结构 系统的基本组成: 软件是一种控制硬件操作和动作的指令流. 2.1 微型计算机的组成原理 当中CPU通过地址线,数据线,和控制信号线组成的内部总线与系统其它部分进行数据通信.地址线用 ...
- 《linux内核完全剖析》笔记03-进程创建
根据一下问题来看笔记 进程占多大的线形地址空间 进程实际分配多少物理内存 创建进程的开销在哪里 一. 从fork系统调用开始 kernel/sys_call.s第222行 _sys_fork: cal ...
随机推荐
- hdu 4407 Sum
http://acm.hdu.edu.cn/showproblem.php?pid=4407 题意:给定初始n个数1..n,两个操作,①1 x y p 询问第x个数到第y个数中与p互质的数的和; ② ...
- 重新定义malloc和free 防止内存泄漏
1, 定义供应用程序使用的头文件//libmem.h#ifndef _LIBMEM_H_#define _LIBMEM_H_ //声明自定义malloc及free函数extern void *my_m ...
- Qt经典—线程、事件与Qobject(耳目一新)
介绍 You’re doing it wrong. — Bradley T. Hughes 线程是qt channel里最流行的讨论话题之一.许多人加入了讨论并询问如何解决他们在运行跨线程编程时所遇到 ...
- 第23章 COM和ActiveX(COM可以实现跨进程跨机器的函数调用)
控件对象既可在EXE中实现,也可在DLL中实现.这种实现对于COM对象的用户来说是透明的.因为COM提供了调度服务(marshaling).COM调度机制能够化进程甚至跨机器的函数调用,这使得16位程 ...
- CSDN总结的面试中的十大算法
1.String/Array/Matrix 在Java中,String是一个包含char数组和其它字段.方法的类.如果没有IDE自动完成代码,下面这个方法大家应该记住: toCharArray() / ...
- Spark RDD Union
示例 Spark多个RDD(数据格式相同)“组合”为一个RDD 代码 from pyspark import SparkConf, SparkContext conf = SparkCon ...
- 挂载nfs系统问题之: Root-NFS: Server returned error -13 while mounting
今天换了个路由器,由于是自动分的IP,现在的IP和之前的不在同一网段.以前是192.168.0.xxx,现在是192.168.1.xxx.本以为将serverip,ipaddr,bootargs这些参 ...
- mongodb清洗数据
1,数据库连接超时:DBPool的连接时的配置: 自己进行设置: MongoClientOptions mco = new MongoClientOptions.Builder() ...
- PHP IDE 框架 服务器 相关
server:nginx 框架:一个比较老的项目用的ZendFramework,最近的新项目用的codeigniter IDE: zend studio Sublime Text https: ...
- Swift基础语法学习总结二
1.函数 1.1 func funcNmae()->(){} 这样就定义了一个函数,它的参数为空,返回值为空,如果有参数和返回值直接写在两个括号里就可以了 1.2 参数需要指明类型,而如果没有返 ...