ASM存储lun映射怎么映射上去

&&&& oracle rac使用越来越广泛,对asm使用也越来越多,下面就asm的使用进行说明,主要是针对linux应用方面的
一、创建磁盘组
1、建立裸设备
&&& --redhat4的使用
&&&&首先划分lun vg00,然后在lun上面创建lv&&&&&&
&&&& # lvcreate -L 5000M -n ora_asm1 vg00
&&&& # lvcreate -L 5000M -n ora_asm2 vg00
&&&& # lvcreate -L 5000M -n ora_asm3 vg00
&&&& # lvcreate -L 5000M -n ora_asm4 vg00
&&&& # lvcreate -L 5000M -n ora_asm5 vg00
&&&& 绑定网卡
&&&& # raw& /dev/raw/raw1& /dev/vg00/ora_asm1
&&&& # raw& /dev/raw/raw1& /dev/vg00/ora_asm2
&&&& # raw& /dev/raw/raw1& /dev/vg00/ora_asm3
&&&& # raw& /dev/raw/raw1& /dev/vg00/ora_asm4
&&&& # raw& /dev/raw/raw1& /dev/vg00/ora_asm5
&& 在/etc/sysconfig/rawdevices中添加如下:
&&&& /dev/raw/raw1& /dev/vg00/ora_asm1
&&&& /dev/raw/raw1& /dev/vg00/ora_asm2
&&&& /dev/raw/raw1& /dev/vg00/ora_asm3
&&&& /dev/raw/raw1& /dev/vg00/ora_asm4
&&&& /dev/raw/raw1& /dev/vg00/ora_asm5
&&&&& 更改raw权限
&& # chown oracle:dba /dev/raw/raw*
&& # vi /etc/init.d/app
&&&&&& chown oracle:dba /dev/raw/raw*
&& # ln -s /etc/init.d/app /etc/rc5.d/S99mapp
&&&& --在redhat5、6中,使用udev的方式
&&&& 如果没有使用多路径,一般就如下就行了
[root@mytest1 rules.d]# pwd
/etc/udev/rules.d
[root@mytest1 rules.d]# more 99-oracle-asmdevices.rules
KERNEL=="sd?1", BUS=="scsi", PROGRAM=="/sbin/scsi_id -g -u -d /dev/$parent", RESULT=="955ad7db711db64828c6e", NAME="OCR1
", OWNER="grid", GROUP="asmadmin", MODE="0660"
KERNEL=="sd?1", BUS=="scsi", PROGRAM=="/sbin/scsi_id -g -u -d /dev/$parent", RESULT=="d75cb069f392fabe063c6e", NAME="OCR2
", OWNER="grid", GROUP="asmadmin", MODE="0660"
KERNEL=="sd?1", BUS=="scsi", PROGRAM=="/sbin/scsi_id -g -u -d /dev/$parent", RESULT=="fdfad5a8a0", NAME="DATA
1", OWNER="grid", GROUP="asmadmin", MODE="0660"
KERNEL=="sd?1", BUS=="scsi", PROGRAM=="/sbin/scsi_id -g -u -d /dev/$parent", RESULT=="a8a1f156b3688", NAME="OCR3
", OWNER="grid", GROUP="asmadmin", MODE="0660"
[root@mytest1 rules.d]# start_udev
Starting udev: [& OK& ]
2、配置ASM
&& # su - oracle
&& # export ORACLE_SID=+ASM
&&&&&& 选择Configure Automatic Storage Management
3、ASM参数文件信息
& #strings $ORACLE_HOME/dbs/spfile+ASM.ora
&&&&&&& +ASM.asm_diskgroups='ASM_DISKGROUP'#Manual Mount
&&&&&&& *.asm_diskgroups='ASM_DISKGROUP'
&&&&&&& *.background_dump_dest='/home/oracle/admin/+ASM/bdump'
&&&&&&& *.core_dump_dest='/home/oracle/admin/+ASM/cdump'
&&&&&&& *.instance_type='asm'
&&&&&&& *.large_pool_size=12M
&&&&&&& *.remote_login_passwordfile='SHARED'
&&&&&&& *.user_dump_dest='/home/oracle/admin/+ASM/udump'
&& 在$ORACLE_HOME/dbs存在两个文件:
&& 1)& ab_+ASM.dat
&&&&&&& SQL&shutdown
&&&&&&& $ rm ab_+ASM.dat
&&&&&&& SQL&startup
&&&&&& 重新生成该文件,RDBMS连接ASM实例时候,使用该文件确定相关的环境信息。如果文件删除,RDBMS不能连接ASM.
&& 2) hc_+ASM.dat
&&&&&& EM使用该文件进行健康监控,如果该文件删除,健康检查信息将不准确。
4、进程检查
&&& # ps -ef |grep asm
&&& # crsctl check css
&&& # ps -ef |grep d.bin&
二、asm磁盘信息查询
ASM 信息查询
1、查看diskgroup空间大小,以及ASM状态
SQL& select name, total_mb, free_mb, usable_file_mb, state
&&&&&&&&&& from v$asm_
NAME&&&&&&&&&&&&&&&&&&&&&&&&&&&& TOTAL_MB&&& FREE_MB&& USABLE_FILE_MB&& STATE
------------------------------ ------------------ --------------- -------------------------- -----------
ASM_DISKGROUP&&&&&&&&&&&& 25120&&&&& 24963&&&&&&&&&& 9969&&&&&&&&&&&&&&&&&&&&&&&& MOUNTED
2、进程数计算
SQL&show parameter process
&& processes=40
计算公式=25+(10+最大数量并行数据文件创建)*RDMS连接ASM数量
&&&&&&&&&&&&&&& = 25+(10+5)*1
&&&&&&&&&&&&&& =40
3、显示SGA值
SQL&show sga
4、ASM控制文件
SQL&show parameter control
control_files =& /home/oracle/app/ora10g/dbs/cntrl+ASM.dbf
该文件在目录中不存在,在ASM实例中没有任何意义
5、查看管理ASM用户
SQL& select * from v$pwfile_
USERNAME&&&&&&&&&&&&&&&&&&&&&& SYSDB&& SYSOP
--------------------------------- ------------ ----------
SYS&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& TRUE&&& TRUE
6、查看diskgroup中各个磁盘状态
SQL& select name, mount_status from v$asm_
NAME&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& MOUNT_S
----------------------------------&&&&&&& -----------------
ASM_DISKGROUP_0004&&&&&&&&&&&& CACHED
ASM_DISKGROUP_0003&&&&&&&&&&&& CACHED
ASM_DISKGROUP_0002&&&&&&&&&&&& CACHED
ASM_DISKGROUP_0001&&&&&&&&&&&& CACHED
ASM_DISKGROUP_0000&&&&&&&&&&&& CACHED
v$asm_disk_stat与v$asm_disk类似
v$asm_file显示ASM文件
v$asm_operation:显示ASM重新负载平衡操作过程
三、asm磁盘删除\添加
1、删除disk
SQL& select group_number, name from v$asm_
GROUP_NUMBER&&&&&&&& NAME
---------------------------- ------------------
&&&&&&&&&& 1&&&&&&&&&&&&&&&&&&&&&&&& ASM_DISKGROUP
SQL& select path, name from v$asm_disk where group_number=1;
PATH&&&&&&&&&&&&&&&&&&&&&&&&&& NAME
------------------------------ ------------------------------
/dev/raw/raw5&&&&&&&&&&&&&&&&& ASM_DISKGROUP_0004
/dev/raw/raw4&&&&&&&&&&&&&&&&& ASM_DISKGROUP_0003
/dev/raw/raw3&&&&&&&&&&&&&&&&& ASM_DISKGROUP_0002
/dev/raw/raw2&&&&&&&&&&&&&&&&& ASM_DISKGROUP_0001
/dev/raw/raw1&&&&&&&&&&&&&&&&& ASM_DISKGROUP_0000
SQL&alter diskgroup ASM_DISKGROUP drop disk asm_diskgroup_0004;
SQL&alter diskgroup ASM_DISKGROUP drop disk asm_diskgroup_0003;
2、创建新的diskgroup
SQL& select name, path, mode_status, state from v$asm_
NAME&&&&&&&&&&&&&&&&&&&&&&&&&& PATH&&&&&&&&&&&&&&&&&&&&&&&&&& MODE_ST STATE
------------------------------ ------------------------------ ------- --------
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& /dev/raw/raw6&&&&&&&&&&&&&&&&& ONLINE& NORMAL
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& /dev/raw/raw5&&&&&&&&&&&&&&&&& ONLINE& NORMAL
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& /dev/raw/raw4&&&&&&&&&&&&&&&&& ONLINE& NORMAL
ASM_DISKGROUP_0002&&&&&&&&&&&& /dev/raw/raw3&&&&&&&&&&&&&&&&& ONLINE& NORMAL
ASM_DISKGROUP_0001&&&&&&&&&&&& /dev/raw/raw2&&&&&&&&&&&&&&&&& ONLINE& NORMAL
ASM_DISKGROUP_0000&&&&&&&&&&&& /dev/raw/raw1&&&&&&&&&&&&&&&&& ONLINE& NORMAL
SQL&create diskgroup dg1& external redundancy disk '/dev/raw/raw4';
SQL& select name, state, type, total_mb, free_mb from v$asm_
NAME&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& STATE&&&&&&&&&&& TYPE&&&&&& TOTAL_MB&&& FREE_MB
------------------------------ ----------- ------ ---------- ----------
ASM_DISKGROUP&&&&&&& MOUNTED&&&& NORMAL&&&&& 15072&&&&& 14919
DG1&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& MOUNTED&&&& EXTERN&&&&&&&&& 5024&&&&&& 4974
SQL&drop diskgroup dg1;
SQL& create diskgroup dg1 normal redundancy
& 2& failgroup f1 disk '/dev/raw/raw4'&&&&&&&&&&&&&&&&&&&&&&&
& 3& failgroup f2 disk '/dev/raw/raw5';
alter diskgroup sysdg add failgroup DATA1 disk 'ORCL:ASMDSKA' add failgroup data2 disk 'ORCL:ASMDSKB';
select a.group_number,a.name,b.name,b.path,b.failgroup from v$asm_diskgroup a,v$asm_disk b where a.group_number = b.group_
select * from v$asm_
在创建diskgroup有三个选项:external, normal和high redunancy,具体在使用中,如果使用中高端存储设备,通常磁盘镜像在硬件级别就完成了,所以在创建diskgroup采用external,normal则至少需要两个磁盘,high则是三个磁盘,通常用于低端存储设备。
3、添加disk到现有的diskgroup
SQL&alter diskgroup ASM_DISKGROUP add disk
&&&&&&&&& '/dev/raw/raw6' name ASM_DISKGROUP_0003;
删除failure group 下的所有disk
SQL&alter diskgroup dg1 drop disks in failgroup f2;
检查disk状态,f2下的磁盘处于hung状态
SQL& select group_number,disk_number,name,failgroup,state from v$asm_
4、重新设置disk大小
alter diskgroup asm_diskgroup resize disk ASM_DISKGROUP_0001 size 800m;
5、将diskgroup进行dismount和mount
SQL& select name, state from v$asm_
NAME&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& STATE
------------------------------------- ---------------
ASM_DISKGROUP&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& MOUNTED
DG1&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& MOUNTED
SQL&alter diskgroup dg1
SQL& alter diskgroup asm_
SQL& select name, state from v$asm_
NAME&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& STATE
------------------------------------- ---------------
ASM_DISKGROUP&&&&&&&&&&&&&&&&& DISMOUNTED
DG1&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& DISMOUNTED
SQL& alter diskgroup dg1
SQL&alter diskgroup& asm_
SQL& select name, state from v$asm_
NAME&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& STATE
------------------------------------- ---------------
ASM_DISKGROUP&&&&&&&&&&&&&&&&& MOUNTED
DG1&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& MOUNTEDx`
6、冗余性考虑
&&&&&&& 我们在创建diskgroup没有指定failure group, failure group 自动在自己盘重建。但我们要注意指定failure group 时,每个failure group中的盘数量和大小要一致,避免空间浪费。
&&&&&&& 在ASM的diskgroup中,第一个被分配的file extents,作为primary extent,其他作为second extents,在10g中,缺省都是先读primary extents,但在11g,我们可以设置prefered read 特性,主要解决了RAC的两个节点中,节点1优先读取primary extent,节点2优先读取second extents,提高I/O性能
7、disk partnership
&&&& 在normal和high redundancy diskgroup中,磁盘故障切换备用盘的信息可查询x$kfdpartner.
SQL& select name, disk_number
&&&&&&&&&&& from v$asm_disk
&&&&&&&&&&& where GROUP_NUMBER=2
&&&&&&&&&&& order by 2;
NAME&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& DISK_NUMBER
----------------------------------- -------------------------
ASM_DISKGROUP_0000&&&&&&&&&&&&&&&&&&&&&& 0
ASM_DISKGROUP_0001&&&&&&&&&&&&&&&&&&&&&& 1
ASM_DISKGROUP_0002&&&&&&&&&&&&&&&&&&&&&& 2
SQL& select disk, NUMBER_KFDPARTNER from x$
&&&&& DISK&&&&&&& NUMBER_KFDPARTNER
----------------- -----------------------------------
&&&&&&&& 0&&&&&&&&&&&&&&&& 1
&&&&&&&& 0&&&&&&&&&&&&&&&& 2
&&&&&&&& 1&&&&&&&&&&&&&&&& 0
&&&&&&&& 1&&&&&&&&&&&&&&&& 2
&&&&&&&& 2&&&&&&&&&&&&&&&& 0
&&&&&&&& 2&&&&&&&&&&&&&&&& 1
可以看到磁盘1的parner是磁盘2和磁盘3.
四、条带化
&&&&&&& ASM的条带化有两种:coarse和fine-gained。AU是最小分配单元,缺省是1M,每个AU缺省由8个128K条带空间组成。
&&&&&&&& coarse条带化中一个extent映射为一个AU,缺省是1M大小,比如4个磁盘
#0~#3,分配extent时候,第一个AU分配给disk0,第二个AU给disk1,依此顺推。所以该条带化适合连续的I/O读写,比如全表扫描表。
&&&&&&& Fined-gained条带化,一个AU中的8个128K条带空间,平均打散在磁盘上,也就是说,4个磁盘,1个AU是平均分布在4个磁盘,每个磁盘2个128K。适合于对数据读写延迟比较敏感的文件。如redo日志,控制文件,spfile等。
&&&&&&& 10g版本AU和条带设置参数:_asm_ausize和_asm_stripesize,11g版本数据库在创建diskgroup时候,可以设置attribute 'au_size' = '16M'。
五、Rebalance
&&&&&&& 从diskgroup中添加或删除disk时候,将触发RBAL进程创建rebalance计划,并计算执行Rebalance所需要时间和工作要求,然后发消息给ASM Reblance (ARBx)进程处理该请求。ARBx进程的数量由参数ASM_POWER_LIMIT决定。COD (Continuting Operation Directory)用于记录rebalances情况。如果rebalance失败,在重启instance时候,将从COD读取记录,重新启动rebalance。ARBx进程对每个extents进行locked, relocated和unlocked操作,执行过程中可以参考v$asm_operation视图。
&&&& ASM_POWER_LIMIT范围是0~11,0:表示不进行rebalance,值越大, rebalance速度越快。
&&&&&& 在进行rebalance时候要注意以下几点:
&&&&& 1、每个disk的大小必须是相同,如果存在一个小盘,因为rebalance将对每个盘的分配相同比例的空间,可能造成rebalance时候空间不足。
&&&&& 2、rebalance仅仅在diskgroup发生改变时候才进行的,并不是定时执行
&&&&& 3、如果磁盘大小一样,仍然没有进行rebalance,需要查看asm_power_limit
&&&&& 4、如果rebalance执行过程中,server宕机,重启后会自动进行rebalance
&&&&& 5、影响rebalance速度因素有很多,最重要是I/O子系统
&&&&& 6、如果执行过程中,空闲空间不足,造成rebalance失败,将出现ORA-15041错误,需要再添加disk,。
&&&&& 7、如果需要频繁添加disk,每一次都可能造成数据的频繁移动,为提高效率,最好批量添加。
&&&& 从老的磁盘阵列切换和迁移disk到新的磁盘阵列命令如下:
SQL& alter diskgroup add disk '/dev/xxx/xxx'
&&&&&&&&&& drop disk [disk_name] rebalance power 8;
&&&回复&&&:
北京盛拓优讯信息技术有限公司. 版权所有 京ICP备号 北京市公安局海淀分局网监中心备案编号:10
广播电视节目制作经营许可证(京) 字第1234号 中国互联网协会会员C语言ASM汇编内嵌语法 - chenjx_ucs - 博客园
随笔 - 128, 文章 - 0, 评论 - 6, 引用 - 0
转载:http://www.cnblogs.com/latifrons/archive//1568198.html
.3 GCC Inline ASMGCC 支持在C/C++代码中嵌入汇编代码,这些汇编代码被称作GCC Inline ASM——GCC内联汇编。这是一个非常有用的功能,有利于我们将一些C/C++语法无法表达的指令直接潜入C/C++代码中,另外也允许我们直接写 C/C++代码中使用汇编编写简洁高效的代码。1.基本内联汇编GCC中基本的内联汇编非常易懂,我们先来看两个简单的例子:__asm__("movl %esp,%eax"); // 看起来很熟悉吧!或者是__asm__("movl $1,%eax // SYS_exitxor %ebx,%ebxint $0x80");或__asm__("movl $1,%eax\r\t" \"xor %ebx,%ebx\r\t" \"int $0x80" \);基本内联汇编的格式是__asm__ __volatile__("Instruction List");1、__asm____asm__是GCC关键字asm的宏定义:#define __asm__ asm__asm__或asm用来声明一个内联汇编表达式,所以任何一个内联汇编表达式都是以它开头的,是必不可少的。2、Instruction ListInstruction List是汇编指令序列。它可以是空的,比如:__asm__ __volatile__(""); 或__asm__ ("");都是完全合法的内联汇编表达式,只不过这两条语句没有什么意义。但并非所有Instruction List为空的内联汇编表达式都是没有意义的,比如:__asm__ ("":::"memory"); 就非常有意义,它向GCC声明:“我对内存作了改动”,GCC在编译的时候,会将此因素考虑进去。我们看一看下面这个例子:$ cat example1.cint main(int __argc, char* __argv[])&{&int* __p = (int*)__&(*__p) = 9999;&//__asm__("":::"memory");&if((*__p) == 9999)&return 5;&return (*__p);&}在 这段代码中,那条内联汇编是被注释掉的。在这条内联汇编之前,内存指针__p所指向的内存被赋值为9999,随即在内联汇编之后,一条if语句判断__p 所指向的内存与9999是否相等。很明显,它们是相等的。GCC在优化编译的时候能够很聪明的发现这一点。我们使用下面的命令行对其进行编译:$ gcc -O -S example1.c选项-O表示优化编译,我们还可以指定优化等级,比如-O2表示优化等级为2;选项-S表示将C/C++源文件编译为汇编文件,文件名和C/C++文件一样,只不过扩展名由.c变为.s。我们来查看一下被放在example1.s中的编译结果,我们这里仅仅列出了使用gcc 2.96在redhat 7.3上编译后的相关函数部分汇编代码。为了保持清晰性,无关的其它代码未被列出。$ cat example1.smain:&pushl %ebp&movl %esp, %ebp&movl 8(%ebp), %eax # int* __p = (int*)__argcmovl $9999, (%eax) # (*__p) = 9999&movl $5, %eax # return 5popl %ebp&ret参 照一下C源码和编译出的汇编代码,我们会发现汇编代码中,没有if语句相关的代码,而是在赋值语句(*__p)=9999后直接return 5;这是因为GCC认为在(*__p)被赋值之后,在if语句之前没有任何改变(*__p)内容的操作,所以那条if语句的判断条件(*__p) == 9999肯定是为true的,所以GCC就不再生成相关代码,而是直接根据为true的条件生成return 5的汇编代码(GCC使用eax作为保存返回值的寄存器)。我们现在将example1.c中内联汇编的注释去掉,重新编译,然后看一下相关的编译结果。$ gcc -O -S example1.c$ cat example1.smain:&pushl %ebp&movl %esp, %ebp&movl 8(%ebp), %eax # int* __p = (int*)__argcmovl $9999, (%eax) # (*__p) = 9999#APP&# __asm__("":::"memory")#NO_APPcmpl $9999, (%eax) # (*__p) == 9999 ?jne .L3 # false&movl $5, %eax # true, return 5&jmp .L2&.p2align 2&.L3:&movl (%eax), %eax&.L2:&popl %ebp&ret由于内联汇编语句__asm__("":::"memory")向GCC声明,在此内联汇编语句出现的位置内存内容可能了改变,所以GCC在编译时就不能像刚才那样处理。这次,GCC老老实实的将if语句生成了汇编代码。可能有人会质疑:为什么要使用__asm__("":::"memory")向GCC声明内存发生了变化?明明“Instruction List”是空的,没有任何对内存的操作,这样做只会增加GCC生成汇编代码的数量。确 实,那条内联汇编语句没有对内存作任何操作,事实上它确实什么都没有做。但影响内存内容的不仅仅是你当前正在运行的程序。比如,如果你现在正在操作的内存 是一块内存映射,映射的内容是外围I/O设备寄存器。那么操作这块内存的就不仅仅是当前的程序,I/O设备也会去操作这块内存。既然两者都会去操作同一块 内存,那么任何一方在任何时候都不能对这块内存的内容想当然。所以当你使用高级语言C/C++写这类程序的时候,你必须让编译器也能够明白这一点,毕竟高 级语言最终要被编译为汇编代码。你可能已经注意到了,这次输出的汇编结果中,有两个符号:#APP和#NO_APP,GCC将内联汇编语 句中"Instruction List"所列出的指令放在#APP和#NO_APP之间,由于__asm__("":::"memory")中“Instruction List”为空,所以#APP和#NO_APP中间也没有任何内容。但我们以后的例子会更加清楚的表现这一点。关于为什么内联汇编__asm__("":::"memory")是一条声明内存改变的语句,我们后面会详细讨论。刚才我们花了大量的内容来讨论"Instruction List"为空是的情况,但在实际的编程中,"Instruction List"绝大多数情况下都不是空的。它可以有1条或任意多条汇编指令。当 在"Instruction List"中有多条指令的时候,你可以在一对引号中列出全部指令,也可以将一条或几条指令放在一对引号中,所有指令放在多对引号中。如果是前者,你可以将 每一条指令放在一行,如果要将多条指令放在一行,则必须用分号(;)或换行符(\n,大多数情况下\n后还要跟一个\t,其中\n是为了换行,\t是为了 空出一个tab宽度的空格)将它们分开。比如:__asm__("movl %eax, %ebx&sti&popl %edi&subl %ecx, %ebx");&__asm__("movl %eax, % sti&popl % subl %ecx, %ebx");__asm__("movl %eax, % sti\n\t popl %edisubl %ecx, %ebx");都是合法的写法。如果你将指令放在多对引号中,则除了最后一对引号之外,前面的所有引号里的最后一条指令之后都要有一个分号(;)或(\n)或(\n\t)。比如:__asm__("movl %eax, %ebx&sti\n"&"popl %"&"subl %ecx, %ebx");&__asm__("movl %eax, % sti\n\t"&"popl % subl %ecx, %ebx");__asm__("movl %eax, % sti\n\t popl %edi\n""subl %ecx, %ebx");__asm__("movl %eax, % sti\n\t popl %" "subl %ecx, %ebx");都是合法的。上述原则可以归结为:任意两个指令间要么被分号(;)分开,要么被放在两行;&放在两行的方法既可以从通过\n的方法来实现,也可以真正的放在两行;&可以使用1对或多对引号,每1对引号里可以放任一多条指令,所有的指令都要被放到引号中。在基本内联汇编中,“Instruction List”的书写的格式和你直接在汇编文件中写非内联汇编没有什么不同,你可以在其中定义Label,定义对齐(.align n ),定义段(.section name )。例如:__asm__(".align 2\n\t"&"movl %eax, %ebx\n\t"&"test %ebx, %ecx\n\t"&"jne error\n\t"&"sti\n\t"&"error: popl %edi\n\t"&"subl %ecx, %ebx");上面例子的格式是Linux内联代码常用的格式,非常整齐。也建议大家都使用这种格式来写内联汇编代码。3、__volatile____volatile__是GCC关键字volatile的宏定义:#define __volatile__ volatile__volatile__ 或volatile是可选的,你可以用它也可以不用它。如果你用了它,则是向GCC声明“不要动我所写的Instruction List,我需要原封不动的保留每一条指令”,否则当你使用了优化选项(-O)进行编译时,GCC将会根据自己的判断决定是否将这个内联汇编表达式中的指 令优化掉。那么GCC判断的原则是什么?我不知道(如果有哪位朋友清楚的话,请告诉我)。我试验了一下,发现一条内联汇编语句如果是基本 内联汇编的话(即只有“Instruction List”,没有Input/Output/Clobber的内联汇编,我们后面将会讨论这一点),无论你是否使用__volatile__来修饰, GCC 2.96在优化编译时,都会原封不动的保留内联汇编中的“Instruction List”。但或许我的试验的例子并不充分,所以这一点并不能够得到保证。为了保险起见,如果你不想让GCC的优化影响你的内联汇编代码,你最好在前面都加上__volatile__,而不要依赖于编译器的原则,因为即使你非常了解当前编译器的优化原则,你也无法保证这种原则将来不会发生变化。而__volatile__的含义却是恒定的。2、带有C/C++表达式的内联汇编GCC允许你通过C/C++表达式指定内联汇编中"Instrcuction List"中指令的输入和输出,你甚至可以不关心到底使用哪个寄存器被使用,完全靠GCC来安排和指定。这一点可以让程序员避免去考虑有限的寄存器的使用,也可以提高目标代码的效率。我们先来看几个例子:__asm__ (" " : : : "memory" ); // 前面提到的__asm__ ("mov %%eax, %%ebx" : "=b"(rv) : "a"(foo) : "eax", "ebx");__asm__ __volatile__("lidt %0": "=m" (idt_descr));__asm__("subl %2,%0\n\t""sbbl %3,%1": "=a" (endlow), "=d" (endhigh): "g" (startlow), "g" (starthigh), "0" (endlow), "1" (endhigh));怎么样,有点印象了吧,是不是也有点晕?没关系,下面讨论完之后你就不会再晕了。(当然,也有可能更晕^_^)。讨论开始——带有C/C++表达式的内联汇编格式为:__asm__ __volatile__("Instruction List" : Output : Input : Clobber/Modify);从中我们可以看出它和基本内联汇编的不同之处在于:它多了3个部分(Input,Output,Clobber/Modify)。在括号中的4个部分通过冒号(:)分开。这4个部分都不是必须的,任何一个部分都可以为空,其规则为:如 果Clobber/Modify为空,则其前面的冒号(:)必须省略。比如__asm__("mov %%eax, %%ebx" : "=b"(foo) : "a"(inp) : )就是非法的写法;而__asm__("mov %%eax, %%ebx" : "=b"(foo) : "a"(inp) )则是正确的。&如果Instruction List为空,则Input,Output,Clobber/Modify可以不为空,也可以为空。比如__asm__ ( " " : : : "memory" );和__asm__(" " : : );都是合法的写法。&如 果Output,Input,Clobber/Modify都为空,Output,Input之前的冒号(:)既可以省略,也可以不省略。如果都省略,则 此汇编退化为一个基本内联汇编,否则,仍然是一个带有C/C++表达式的内联汇编,此时"Instruction List"中的寄存器写法要遵守相关规定,比如寄存器前必须使用两个百分号(%%),而不是像基本汇编格式一样在寄存器前只使用一个百分号(%)。比如 __asm__( " mov %%eax, %%ebx" : : );__asm__( " mov %%eax, %%ebx" : )和__asm__( " mov %eax, %ebx" )都是正确的写法,而__asm__( " mov %eax, %ebx" : : );__asm__( " mov %eax, %ebx" : )和__asm__( " mov %%eax, %%ebx" )都是错误的写法。&如果Input,Clobber/Modify为空,但Output不为空,Input前的冒号(:)既可以省略,也可以不省略。比如 __asm__( " mov %%eax, %%ebx" : "=b"(foo) : );__asm__( " mov %%eax, %%ebx" : "=b"(foo) )都是正确的。&如果后面的部分不为空,而前面的部分为空,则前面的冒号(:)都必须保留,否则无法说 明不为空的部分究竟是第几部分。比如, Clobber/Modify,Output为空,而Input不为空,则Clobber/Modify前的冒号必须省略(前面的规则),而Output 前的冒号必须为保留。如果Clobber/Modify不为空,而Input和Output都为空,则Input和Output前的冒号都必须保留。比如 __asm__( " mov %%eax, %%ebx" : : "a"(foo) )和__asm__( " mov %%eax, %%ebx" : : : "ebx" )。从上面的规则可以看到另外一个事实,区分一个内联汇编是基本格式的还是带有C/C++表达式格式的,其规则在于在"Instruction List"后是否有冒号(:)的存在,如果没有则是基本格式的,否则,则是带有C/C++表达式格式的。两种格式对寄存器语法的要求不同:基本格式要求寄存器前只能使用一个百分号(%),这一点和非内联汇编相同;而带有C/C++表达式格式则要求寄存器前必须使用两个百分号(%%),其原因我们会在后面讨论。1. OutputOutput用来指定当前内联汇编语句的输出。我们看一看这个例子:__asm__("movl %%cr0, %0": "=a" (cr0));这 个内联汇编语句的输出部分为"=r"(cr0),它是一个“操作表达式”,指定了一个输出操作。我们可以很清楚得看到这个输出操作由两部分组成:括号括住 的部分(cr0)和引号引住的部分"=a"。这两部分都是每一个输出操作必不可少的。括号括住的部分是一个C/C++表达式,用来保存内联汇编的一个输出 值,其操作就等于C/C++的相等赋值cr0 = output_value,因此,括号中的输出表达式只能是C/C++的左值表达式,也就是说它只能是一个可以合法的放在C/C++赋值操作中等号(=) 左边的表达式。那么右值output_value从何而来呢?答案是引号中的内容,被称作“操作约束”(Operation Constraint),在这个例子中操作约束为"=a",它包含两个约束:等号(=)和字母a,其中等号(=)说明括号中左值表达式cr0是一个 Write-Only的,只能够被作为当前内联汇编的输入,而不能作为输入。而字母a是寄存器EAX / AX / AL的简写,说明cr0的值要从eax寄存器中获取,也就是说cr0 = eax,最终这一点被转化成汇编指令就是movl %eax, address_of_cr0。现在你应该清楚了吧,操作约束中会给出:到底从哪个寄存器传递值给cr0。另外,需要特别说明的是,很多 文档都声明,所有输出操作的操作约束必须包含一个等号(=),但GCC的文档中却很清楚的声明,并非如此。因为等号(=)约束说明当前的表达式是一个 Write-Only的,但另外还有一个符号——加号(+)用来说明当前表达式是一个Read-Write的,如果一个操作约束中没有给出这两个符号中的 任何一个,则说明当前表达式是Read-Only的。因为对于输出操作来说,肯定是必须是可写的,而等号(=)和加号(+)都表示可写,只不过加号(+) 同时也表示是可读的。所以对于一个输出操作来说,其操作约束只需要有等号(=)或加号(+)中的任意一个就可以了。二者的区别是:等号(=)表示当前操作表达式指定了一个纯粹的输出操作,而加号(+)则表示当前操作表达式不仅仅只是一个输出操作还是一个输入操作。但无论是等号(=)约束还是加号(+)约束所约束的操作表达式都只能放在Output域中,而不能被用在Input域中。另外,有些文档声明:尽管GCC文档中提供了加号(+)约束,但在实际的编译中通不过;我不知道老版本会怎么样,我在GCC 2.96中对加号(+)约束的使用非常正常。我们通过一个例子看一下,在一个输出操作中使用等号(=)约束和加号(+)约束的不同。$ cat example2.cint main(int __argc, char* __argv[])&{&int cr0 = 5;&__asm__ __volatile__("movl %%cr0, %0":"=a" (cr0));&return 0;&}$ gcc -S example2.c$ cat example2.smain:&pushl %ebp&movl %esp, %ebp&subl $4, %esp&movl $5, -4(%ebp) # cr0 = 5#APP&movl %cr0, %eax&#NO_APP&movl %eax, %eax&movl %eax, -4(%ebp) # cr0 = %eaxmovl $0, %eax&leave&ret&这个例子是使用等号(=)约束的情况,变量cr0被放在内存-4(%ebp)的位置,所以指令mov %eax, -4(%ebp)即表示将%eax的内容输出到变量cr0中。下面是使用加号(+)约束的情况:$ cat example3.cint main(int __argc, char* __argv[])&{&int cr0 = 5;&__asm__ __volatile__("movl %%cr0, %0" : "+a" (cr0));&return 0;&}$ gcc -S example3.c$ cat example3.smain:&pushl %ebp&movl %esp, %ebp&subl $4, %esp&movl $5, -4(%ebp) # cr0 = 5movl -4(%ebp), %eax # input ( %eax = cr0 )#APP&movl %cr0, %eax#NO_APPmovl %eax, -4(%ebp) # output (cr0 = %eax )movl $0, %eaxleaveret从编译的结果可以看出,当使用加号(+)约束的时候,cr0不仅作为输出,还作为输入,所使用寄存器都是寄存器约束(字母a,表示使用eax寄存器)指定的。关于寄存器约束我们后面讨论。在Output域中可以有多个输出操作表达式,多个操作表达式中间必须用逗号(,)分开。例如:__asm__(&"movl %%eax, %0 \n\t"&"pushl %%ebx \n\t"&"popl %1 \n\t"&"movl %1, %2"&: "+a"(cr0), "=b"(cr1), "=c"(cr2));2、InputInput域的内容用来指定当前内联汇编语句的输入。我们看一看这个例子:__asm__("movl %0, %%db7" : : "a" (cpu-&db7));例中Input域的内容为一个表达式"a"[cpu-&db7),被称作“输入表达式”,用来表示一个对当前内联汇编的输入。像输出表达式一样,一个输入表达式也分为两部分:带括号的部分(cpu-&db7)和带引号的部分"a"。这两部分对于一个内联汇编输入表达式来说也是必不可少的。括 号中的表达式cpu-&db7是一个C/C++语言的表达式,它不必是一个左值表达式,也就是说它不仅可以是放在C/C++赋值操作左边的表达式, 还可以是放在C/C++赋值操作右边的表达式。所以它可以是一个变量,一个数字,还可以是一个复杂的表达式(比如a+b/c*d)。比如上例可以改为: __asm__("movl %0, %%db7" : : "a" (foo)),__asm__("movl %0, %%db7" : : "a" (0x1000))或__asm__("movl %0, %%db7" : : "a" (va*vb/vc))。引号号中的 部分是约束部分,和输出表达式约束不同的是,它不允许指定加号(+)约束和等号(=)约束,也就是说它只能是默认的Read-Only的。约束中必须指定 一个寄存器约束,例中的字母a表示当前输入变量cpu-&db7要通过寄存器eax输入到当前内联汇编中。我们看一个例子:$ cat example4.cint main(int __argc, char* __argv[])&{&int cr0 = 5;&__asm__ __volatile__("movl %0, %%cr0"::"a" (cr0));&return 0;&}$ gcc -S example4.c$ cat example4.smain:&pushl %ebp&movl %esp, %ebp&subl $4, %esp&movl $5, -4(%ebp) # cr0 = 5&movl -4(%ebp), %eax # %eax = cr0#APP&movl %eax, %cr0&#NO_APP&movl $0, %eax&leave&ret&我们从编译出的汇编代码可以看到,在"Instruction List"之前,GCC按照我们的输入约束"a",将变量cr0的内容装入了eax寄存器。3. Operation Constraint每一个Input和Output表达式都必须指定自己的操作约束Operation Constraint,我们这里来讨论在80386平台上所可能使用的操作约束。1、寄存器约束当你当前的输入或输入需要借助一个寄存器时,你需要为其指定一个寄存器约束。你可以直接指定一个寄存器的名字,比如:__asm__ __volatile__("movl %0, %%cr0"::"eax" (cr0));也可以指定一个缩写,比如:__asm__ __volatile__("movl %0, %%cr0"::"a" (cr0));如果你指定一个缩写,比如字母a,则GCC将会根据当前操作表达式中C/C++表达式的宽度决定使用%eax,还是%ax或%al。比如:unsigned short ____asm__ ("mov %0,%%bx" : : "a"(__shrt));由于变量__shrt是16-bit short类型,则编译出来的汇编代码中,则会让此变量使用%ex寄存器。编译结果为:movw -2(%ebp), %ax # %ax = __shrt#APPmovl %ax, %bx#NO_APP无论是Input,还是Output操作表达式约束,都可以使用寄存器约束。下表中列出了常用的寄存器约束的缩写。约束 Input/Output 意义&r I,O 表示使用一个通用寄存器,由GCC在%eax/%ax/%al, %ebx/%bx/%bl, %ecx/%cx/%cl, %edx/%dx/%dl中选取一个GCC认为合适的。&q I,O 表示使用一个通用寄存器,和r的意义相同。&a I,O 表示使用%eax / %ax / %al&b I,O 表示使用%ebx / %bx / %bl&c I,O 表示使用%ecx / %cx / %cl&d I,O 表示使用%edx / %dx / %dl&D I,O 表示使用%edi / %di&S I,O 表示使用%esi / %si&f I,O 表示使用浮点寄存器&t I,O 表示使用第一个浮点寄存器&u I,O 表示使用第二个浮点寄存器&2、内存约束&如果一个Input/Output操作表达式的C/C++表达式表现为一个内存地址,不想借助于任何寄存器,则可以使用内存约束。比如:__asm__ ("lidt %0" : "=m"(__idt_addr)); 或 __asm__ ("lidt %0" : :"m"(__idt_addr));我们看一下它们分别被放在一个C源文件中,然后被GCC编译后的结果:$ cat example5.c// 本例中,变量sh被作为一个内存输入int main(int __argc, char* __argv[])&{&char* sh = (char*)&__&__asm__ __volatile__("lidt %0" : : "m" (sh));&return 0;&}&$ gcc -S example5.c$ cat example5.smain:&pushl %ebp&movl %esp, %ebp&subl $4, %esp&leal 8(%ebp), %eax&movl %eax, -4(%ebp) # sh = (char*) &__argc#APP&lidt -4(%ebp)&#NO_APP&movl $0, %eax&leave&ret&$ cat example6.c// 本例中,变量sh被作为一个内存输出int main(int __argc, char* __argv[])&{&char* sh = (char*)&__&__asm__ __volatile__("lidt %0" : "=m" (sh));&return 0;&}&$ gcc -S example6.c$ cat example6.smain:pushl %ebpmovl %esp, %ebpsubl $4, %espleal 8(%ebp), %eaxmovl %eax, -4(%ebp) # sh = (char*) &__argc#APPlidt -4(%ebp)#NO_APPmovl $0, %eaxleaveret首先,你会注意到,在这两个例子中,变量sh没有借助任何寄存器,而是直接参与了指令lidt的操作。其次,通过仔细观察,你会发现一个惊人的事实,两个例子编译出来的汇编代码是一样的!虽然,一个例子中变量sh作为输入,而另一个例子中变量sh作为输出。这是怎么回事?原来,使用内存方式进行输入输出时,由于不借助寄存器,所以GCC不会按照你的声明对其作任何的输入输出处理。GCC只会直接拿来用,究竟对这个C/C++表达式而言是输入还是输出,完全依赖与你写在"Instruction List"中的指令对其操作的指令。由 于上例中,对其操作的指令为lidt,lidt指令的操作数是一个输入型的操作数,所以事实上对变量sh的操作是一个输入操作,即使你把它放在 Output域也不会改变这一点。所以,对此例而言,完全符合语意的写法应该是将sh放在Input域,尽管放在Output域也会有正确的执行结果。所 以,对于内存约束类型的操作表达式而言,放在Input域还是放在Output域,对编译结果是没有任何影响的,因为本来我们将一个操作表达式放在 Input域或放在Output域是希望GCC能为我们自动通过寄存器将表达式的值输入或输出。既然对于内存约束类型的操作表达式来说,GCC不会自动为 它做任何事情,那么放在哪儿也就无所谓了。但从程序员的角度而言,为了增强代码的可读性,最好能够把它放在符合实际情况的地方。约束 Input/Output 意义&m I,O 表示使用系统所支持的任何一种内存方式,不需要借助寄存器&3、立即数约束如果一个Input/Output操作表达式的C/C++表达式是一个数字常数,不想借助于任何寄存器,则可以使用立即数约束。由于立即数在C/C++中只能作为右值,所以对于使用立即数约束的表达式而言,只能放在Input域。比如:__asm__ __volatile__("movl %0, %%eax" : : "i" (100) );&立即数约束很简单,也很容易理解,我们在这里就不再赘述。约束 Input/Output 意义&i I 表示输入表达式是一个立即数(整数),不需要借助任何寄存器&F I 表示输入表达式是一个立即数(浮点数),不需要借助任何寄存器&4、通用约束约束 Input/Output 意义&g I,O 表示可以使用通用寄存器,内存,立即数等任何一种处理方式。&0,1,2,3,4,5,6,7,8,9 I 表示和第n个操作表达式使用相同的寄存器/内存。&通 用约束g是一个非常灵活的约束,当程序员认为一个C/C++表达式在实际的操作中,究竟使用寄存器方式,还是使用内存方式或立即数方式并无所谓时,或者程 序员想实现一个灵活的模板,让GCC可以根据不同的C/C++表达式生成不同的访问方式时,就可以使用通用约束g。比如:#define JUST_MOV(foo) __asm__ ("movl %0, %%eax" : : "g"(foo))JUST_MOV(100)和JUST_MOV(var)则会让编译器产生不同的代码。int main(int __argc, char* __argv[])&{&JUST_MOV(100);&return 0;&}&编译后生成的代码为:main:&pushl %ebp&movl %esp, %ebp&#APP&movl $100, %eax&#NO_APP&movl $0, %eax&popl %ebp&ret很明显这是立即数方式。而下一个例子:int main(int __argc, char* __argv[])&{&JUST_MOV(__argc);&return 0;&}&经编译后生成的代码为:main:&pushl %ebp&movl %esp, %ebp&#APP&movl 8(%ebp), %eax&#NO_APP&movl $0, %eax&popl %ebp&ret&这个例子是使用内存方式。一个带有C/C++表达式的内联汇编,其操作表达式被按照被列出的顺序编号,第一个是0,第2个是1,依次类推,GCC最多允许有10个操作表达式。比如:__asm__ ("popl %0 \n\t""movl %1, %%esi \n\t""movl %2, %%edi \n\t": "=a"(__out): "r" (__in1), "r" (__in2));此例中,__out所在的Output操作表达式被编号为0,"r"(__in1)被编号为1,"r"(__in2)被编号为2。再如:__asm__ ("movl %%eax, %%ebx" : : "a"(__in1), "b"(__in2));此例中,"a"(__in1)被编号为0,"b"(__in2)被编号为1。如 果某个Input操作表达式使用数字0到9中的一个数字(假设为1)作为它的操作约束,则等于向GCC声明:“我要使用和编号为1的Output操作表达 式相同的寄存器(如果Output操作表达式1使用的是寄存器),或相同的内存地址(如果Output操作表达式1使用的是内存)”。上面的描述包含两个 限定:数字0到数字9作为操作约束只能用在Input操作表达式中,被指定的操作表达式(比如某个Input操作表达式使用数字1作为约束,那么被指定的 就是编号为1的操作表达式)只能是Output操作表达式。由于GCC规定最多只能有10个Input/Output操作表达式,所以事 实上数字9作为操作约束永远也用不到,因为Output操作表达式排在Input操作表达式的前面,那么如果有一个Input操作表达式指定了数字9作为 操作约束的话,那么说明Output操作表达式的数量已经至少为10个了,那么再加上这个Input操作表达式,则至少为11个了,以及超出GCC的限 制。5、Modifier Characters(修饰符)等号(=)和加号(+)用于对Output操作表达式的修 饰,一个Output操作表达式要么被等号(=)修饰,要么被加号(+)修饰,二者必居其一。使用等号(=)说明此Output操作表达式是Write- Only的,使用加号(+)说明此Output操作表达式是Read-Write的。它们必须被放在约束字符串的第一个字母。比如"a="(foo)是非 法的,而"+g"(foo)则是合法的。当使用加号(+)的时候,此Output表达式等价于使用等号(=)约束加上一个Input表达式。比如__asm__ ("movl %0, %% addl %%eax, %0" : "+b"(foo)) 等价于__asm__ ("movl %1, %% addl %%eax, %0" : "=b"(foo) : "b"(foo))但如果使用后一种写法,"Instruction List"中的别名也要相应的改动。关于别名,我们后面会讨论。像 等号(=)和加号(+)修饰符一样,符号(&)也只能用于对Output操作表达式的修饰。当使用它进行修饰时,等于向GCC声明:"GCC不得 为任何Input操作表达式分配与此Output操作表达式相同的寄存器"。其原因是&修饰符意味着被其修饰的Output操作表达式要在所有的 Input操作表达式被输入前输出。我们看下面这个例子:int main(int __argc, char* __argv[])&{&int __in1 = 8, __in2 = 4, __out = 3;&__asm__ ("popl %0 \n\t""movl %1, %%esi \n\t""movl %2, %%edi \n\t": "=a"(__out): "r" (__in1), "r" (__in2));return 0;&}&此 例中,%0对应的就是Output操作表达式,它被指定的寄存器是%eax,整个Instruction List的第一条指令popl %0,编译后就成为popl %eax,这时%eax的内容已经被修改,随后在Instruction List后,GCC会通过movl %eax, address_of_out这条指令将%eax的内容放置到Output变量__out中。对于本例中的两个Input操作表达式而言,它们的寄存器约 束为"r",即要求GCC为其指定合适的寄存器,然后在Instruction List之前将__in1和__in2的内容放入被选出的寄存器中,如果它们中的一个选择了已经被__out指定的寄存器%eax,假如是__in1,那 么GCC在Instruction List之前会插入指令movl address_of_in1, %eax,那么随后popl %eax指令就修改了%eax的值,此时%eax中存放的已经不是Input变量__in1的值了,那么随后的movl %1, %%esi指令,将不会按照我们的本意——即将__in1的值放入%esi中——而是将__out的值放入%esi中了。&下面就是本例的编译结果,很明显,GCC为__in2选择了和__out相同的寄存器%eax,这与我们的初衷不符。main:&pushl %ebp&movl %esp, %ebp&subl $12, %esp&movl $8, -4(%ebp)&movl $4, -8(%ebp)&movl $3, -12(%ebp)&movl -4(%ebp), %edx # __in1使用寄存器%edxmovl -8(%ebp), %eax # __in2使用寄存器%eax#APP&popl %eax&movl %edx, %esi&movl %eax, %edi&#NO_APP&movl %eax, %eax&movl %eax, -12(%ebp) # __out使用寄存器%eaxmovl $0, %eax&leave&ret&为 了避免这种情况,我们必须向GCC声明这一点,要求GCC为所有的Input操作表达式指定别的寄存器,方法就是在Output操作表达式"=a" (__out)的操作约束中加入&约束,由于GCC规定等号(=)约束必须放在第一个,所以我们写作"=&a"(__out)。&下面是我们将&约束加入之后编译的结果:main:&pushl %ebp&movl %esp, %ebp&subl $12, %esp&movl $8, -4(%ebp)&movl $4, -8(%ebp)&movl $3, -12(%ebp)&movl -4(%ebp), %edx #__in1使用寄存器%edxmovl -8(%ebp), %eax&movl %eax, %ecx # __in2使用寄存器%ecx#APP&popl %eax&movl %edx, %esi&movl %ecx, %edi&#NO_APP&movl %eax, %eax&movl %eax, -12(%ebp) #__out使用寄存器%eaxmovl $0, %eax&leave&ret&OK!这下好了,完全与我们的意图吻合。&如 果一个Output操作表达式的寄存器约束被指定为某个寄存器,只有当至少存在一个Input操作表达式的寄存器约束为可选约束时,(可选约束的意思是可 以从多个寄存器中选取一个,或使用非寄存器方式),比如"r"或"g"时,此Output操作表达式使用&修饰才有意义。如果你为所有的 Input操作表达式指定了固定的寄存器,或使用内存/立即数约束,则此Output操作表达式使用&修饰没有任何意义。比如:__asm__ ("popl %0 \n\t"&"movl %1, %%esi \n\t"&"movl %2, %%edi \n\t"&: "=&a"(__out)&: "m" (__in1), "c" (__in2));&此例中的Output操作表达式完全没有必要使用&来修饰,因为__in1和__in2都被指定了固定的寄存器,或使用了内存方式,GCC无从选择。但如果你已经为某个Output操作表达式指定了&修饰,并指定了某个固定的寄存器,你就不能再为任何Input操作表达式指定这个寄存器,否则会出现编译错误。比如:__asm__ ("popl %0 \n\t"&"movl %1, %%esi \n\t"&"movl %2, %%edi \n\t"&: "=&a"(__out)&: "a" (__in1), "c" (__in2));&本例中,由于__out已经指定了寄存器%eax,同时使用了符号&修饰,则再为__in1指定寄存器%eax就是非法的。反过来,你也可以为Output指定可选约束,比如"r","g"等,让GCC为其选择到底使用哪个寄存器,还是使用内存方式,GCC在选择的时候,会首先排除掉已经被Input操作表达式使用的所有寄存器,然后在剩下的寄存器中选择,或干脆使用内存方式。比如:__asm__ ("popl %0 \n\t"&"movl %1, %%esi \n\t"&"movl %2, %%edi \n\t"&: "=&r"(__out)&: "a" (__in1), "c" (__in2));&本例中,由于__out指定了约束"r",即让GCC为其决定使用哪一格寄存器,而寄存器%eax和%ecx已经被__in1和__in2使用,那么GCC在为__out选择的时候,只会在%ebx和%edx中选择。前3 个修饰符只能用在Output操作表达式中,而百分号[%]修饰符恰恰相反,只能用在Input操作表达式中,用于向GCC声明:“当前Input操作表 达式中的C/C++表达式可以和下一个Input操作表达式中的C/C++表达式互换”。这个修饰符号一般用于符合交换律运算,比如加(+),乘(*), 与(&),或(|)等等。我们看一个例子:int main(int __argc, char* __argv[])&{&int __in1 = 8, __in2 = 4, __out = 3;&__asm__ ("addl %1, %0\n\t"&: "=r"(__out)&: "%r" (__in1), "0" (__in2));&return 0;&}在 此例中,由于指令是一个加法运算,相当于等式__out = __in1 + __in2,而它与等式__out = __in2 + __in1没有什么不同。所以使用百分号修饰,让GCC知道__in1和__in2可以互换,也就是说GCC可以自动将本例的内联汇编改变为:__asm__ ("addl %1, %0\n\t": "=r"(__out): "%r" (__in2), "0" (__in1));&修饰符 Input/Output 意义&= O 表示此Output操作表达式是Write-Only的&+ O 表示此Output操作表达式是Read-Write的&& O 表示此Output操作表达式独占为其指定的寄存器&% I 表示此Input操作表达式中的C/C++表达式可以和下一个Input操作表达式中的C/C++表达式互换&4. 占位符什么叫占位符?我们看一看下面这个例子:__asm__ ("addl %1, %0\n\t": "=a"(__out): "m" (__in1), "a" (__in2));这 个例子中的%0和%1就是占位符。每一个占位符对应一个Input/Output操作表达式。我们在之前已经提到,GCC规定一个内联汇编语句最多可以有 10个Input/Output操作表达式,然后按照它们被列出的顺序依次赋予编号0到9。对于占位符中的数字而言,和这些编号是对应的。由于占位符前面使用一个百分号(%),为了区别占位符和寄存器,GCC规定在带有C/C++表达式的内联汇编中,"Instruction List"中直接写出的寄存器前必须使用两个百分号(%%)。GCC 对其进行编译的时候,会将每一个占位符替换为对应的Input/Output操作表达式所指定的寄存器/内存地址/立即数。比如在上例中,占位符%0对应 Output操作表达式"=a"(__out),而"=a"(__out)指定的寄存器为%eax,所以把占位符%0替换为%eax,占位符%1对应 Input操作表达式"m"(__in1),而"m"(__in1)被指定为内存操作,所以把占位符%1替换为变量__in1的内存地址。也许有人认为,在上面这个例子中,完全可以不使用%0,而是直接写%%eax,就像这样:__asm__ ("addl %1, %%eax\n\t": "=a"(__out): "m" (__in1), "a" (__in2));和 上面使用占位符%0没有什么不同,那么使用占位符%0就没有什么意义。确实,两者生成的代码完全相同,但这并不意味着这种情况下占位符没有意义。因为如果 不使用占位符,那么当有一天你想把变量__out的寄存器约束由a改为b时,那么你也必须将addl指令中的%%eax改为%%ebx,也就是说你需要同 时修改两个地方,而如果你使用占位符,你只需要修改一次就够了。另外,如果你不使用占位符,将不利于代码的清晰性。在上例中,如果你使用占位符,那么你一 眼就可以得知,addl指令的第二个操作数内容最终会输出到变量__out中;否则,如果你不用占位符,而是直接将addl指令的第2个操作数写为%% eax,那么你需要考虑一下才知道它最终需要输出到变量__out中。这是占位符最粗浅的意义。毕竟在这种情况下,你完全可以不用。但对于这些情况来说,不用占位符就完全不行了:首 先,我们看一看上例中的第1个Input操作表达式"m"(__in1),它被GCC替换之后,表现为addl address_of_in1, %%eax,__in1的地址是什么?编译时才知道。所以我们完全无法直接在指令中去写出__in1的地址,这时使用占位符,交给GCC在编译时进行替 代,就可以解决这个问题。所以这种情况下,我们必须使用占位符。其次,如果上例中的Output操作表达式"=a"(__out)改为" =r"(__out),那么__out在究竟使用那么寄存器只有到编译时才能通过GCC来决定,既然在我们写代码的时候,我们不知道究竟哪个寄存器被选 择,我们也就不能直接在指令中写出寄存器的名称,而只能通过占位符替代来解决。5. Clobber/Modify有时候,你想通知GCC当前内联汇编语句可能会对某些寄存器或内存进行修改,希望GCC在编译时能够将这一点考虑进去。那么你就可以在Clobber/Modify域声明这些寄存器或内存。这 种情况一般发生在一个寄存器出现在"Instruction List",但却不是由Input/Output操作表达式所指定的,也不是在一些Input/Output操作表达式使用"r","g"约束时由GCC 为其选择的,同时此寄存器被"Instruction List"中的指令修改,而这个寄存器只是供当前内联汇编临时使用的情况。比如:__asm__ ("movl %0, %%ebx" : : "a"(__foo) : "bx");寄存器%ebx出现在"Instruction List中",并且被movl指令修改,但却未被任何Input/Output操作表达式指定,所以你需要在Clobber/Modify域指定"bx",以让GCC知道这一点。因 为你在Input/Output操作表达式所指定的寄存器,或当你为一些Input/Output操作表达式使用"r","g"约束,让GCC为你选择一 个寄存器时,GCC对这些寄存器是非常清楚的——它知道这些寄存器是被修改的,你根本不需要在Clobber/Modify域再声明它们。但除此之外, GCC对剩下的寄存器中哪些会被当前的内联汇编修改一无所知。所以如果你真的在当前内联汇编指令中修改了它们,那么就最好在Clobber/Modify 中声明它们,让GCC针对这些寄存器做相应的处理。否则有可能会造成寄存器的不一致,从而造成程序执行错误。在Clobber/Modify域中指定这些寄存器的方法很简单,你只需要将寄存器的名字使用双引号(" ")引起来。如果有多个寄存器需要声明,你需要在任意两个声明之间用逗号隔开。比如:__asm__ ("movl %0, %% popl %%ecx" : : "a"(__foo) : "bx", "cx" );这些串包括:声明的串 代表的寄存器&"al","ax","eax" %eax&"bl","bx","ebx" %ebx&"cl","cx","ecx" %ecx&"dl","dx","edx" %edx&"si","esi" %esi&"di", "edi" %edi&由上表可以看出,你只需要使用"ax","bx","cx","dx","si","di"就可以了,因为其它的都和它们中的一个是等价的。如 果你在一个内联汇编语句的Clobber/Modify域向GCC声明某个寄存器内容发生了改变,GCC在编译时,如果发现这个被声明的寄存器的内容在此 内联汇编语句之后还要继续使用,那么GCC会首先将此寄存器的内容保存起来,然后在此内联汇编语句的相关生成代码之后,再将其内容恢复。我们来看两个例 子,然后对比一下它们之间的区别。这个例子中声明了寄存器%ebx内容发生了改变:$ cat example7.cint main(int __argc, char* __argv[])&{&int in = 8;&__asm__ ("addl %0, %%ebx"&: /* no output */&: "a" (in) : "bx");&return 0;&}$ gcc -O -S example7.c$ cat example7.smain:pushl %ebpmovl %esp, %ebppushl %ebx # %ebx内容被保存&movl $8, %eax#APPaddl %eax, %ebx#NO_APPmovl $0, %eaxmovl (%esp), %ebx # %ebx内容被恢复leaveret下面这个例子的C源码与上一个例子除了没有声明%ebx寄存器发生了改变之外,其它都相同。$ cat example8.cint main(int __argc, char* __argv[])&{&int in = 8;&__asm__ ("addl %0, %%ebx"&: /* no output */&: "a" (in) );&return 0;&}$ gcc -O -S example8.c$ cat example8.smain:&pushl %ebp&movl %esp, %ebp&movl $8, %eax&#APP&addl %eax, %ebx&#NO_APP&movl $0, %eax&popl %ebp&ret仔细对比一下example7.s和example8.s,你就会明白在Clobber/Modify域声明一个寄存器的意义。另 外需要注意的是,如果你在Clobber/Modify域声明了一个寄存器,那么这个寄存器将不能再被用做当前内联汇编语句的Input/Output操 作表达式的寄存器约束,如果Input/Output操作表达式的寄存器约束被指定为"r"或"g",GCC也不会选择已经被声明在 Clobber/Modify中的寄存器。比如:__asm__ ("movl %0, %%ebx" : : "a"(__foo) : "ax", "bx");此例中,由于Output操作表达式"a"(__foo)的寄存器约束已经指定了%eax寄存器,那么再在Clobber/Modify域中指定"ax"就是非法的。编译时,GCC会给出编译错误。除 了寄存器的内容会被改变,内存的内容也可以被修改。如果一个内联汇编语句"Instruction List"中的指令对内存进行了修改,或者在此内联汇编出现的地方内存内容可能发生改变,而被改变的内存地址你没有在其Output操作表达式使用"m" 约束,这种情况下你需要使用在Clobber/Modify域使用字符串"memory"向GCC声明:“在这里,内存发生了,或可能发生了改变”。例 如:void * memset(void * s, char c, size_t count){__asm__("cld\n\t""rep\n\t""stosb": /* no output */: "a" (c),"D" (s),"c" (count): "cx","di","memory");}此 例实现了标准函数库memset,其内联汇编中的stosb对内存进行了改动,而其被修改的内存地址s被指定装入%edi,没有任何Output操作表达 式使用了"m"约束,以指定内存地址s处的内容发生了改变。所以在其Clobber/Modify域使用"memory"向GCC声明:内存内容发生了变 动。如果一个内联汇编语句的Clobber/Modify域存在"memory",那么GCC会保证在此内联汇编之前,如果某个内存的内 容被装入了寄存器,那么在这个内联汇编之后,如果需要使用这个内存处的内容,就会直接到这个内存处重新读取,而不是使用被存放在寄存器中的拷贝。因为这个 时候寄存器中的拷贝已经很可能和内存处的内容不一致了。这只是使用"memory"时,GCC会保证做到的一点,但这并不是全部。因为使用"memory"是向GCC声明内存发生了变化,而内存发生变化带来的影响并不止这一点。比如我们在前面讲到的例子:int main(int __argc, char* __argv[])&{&int* __p = (int*)__&(*__p) = 9999;&__asm__("":::"memory");&if((*__p) == 9999)&return 5;&return (*__p);&}本 例中,如果没有那条内联汇编语句,那个if语句的判断条件就完全是一句废话。GCC在优化时会意识到这一点,而直接只生成return 5的汇编代码,而不会再生成if语句的相关代码,而不会生成return (*__p)的相关代码。但你加上了这条内联汇编语句,它除了声明内存变化之外,什么都没有做。但GCC此时就不能简单的认为它不需要判断都知道 (*__p)一定与9999相等,它只有老老实实生成这条if语句的汇编代码,一起相关的两个return语句相关代码。当一个内联汇编 指令中包含影响eflags寄存器中的条件标志(也就是那些Jxx等跳转指令要参考的标志位,比如,进位标志,0标志等),那么需要在 Clobber/Modify域中使用"cc"来声明这一点。这些指令包括adc, div,popfl,btr,bts等等,另外,当包含call指令时,由于你不知道你所call的函数是否会修改条件标志,为了稳妥起见,最好也使用 "cc"。我很少在相关资料中看到有关"cc"的确切用法,只有一份文档提到了它,但还不是i386平台的,只是说"cc"是处理器平台 相关的,并非所有的平台都支持它,但即使在不支持它的平台上,使用它也不会造成编译错误。我做了一些实验,但发现使用"cc"和不使用"cc"所生成的代 码没有任何不同。但Linux 2.4的相关代码中用到了它。如果谁知道在i386平台上"cc"的细节,请和我联系。另外,还可以在 Clobber/Modify域指定数字0到9,以声明第n个Input/Output操作表达式所使用的寄存器发生了变化,但正如我们在前面所提到的, 如果你为某个Input/Output操作表达式指定了寄存器,或使用"g","r"等约束让GCC为其选择寄存器,GCC已经知道哪个寄存器内容发生了 变化,所以这么做没有什么意义;我也作了相关的试验,没有发现使用它会对GCC生成的汇编代码有任何影响,至少在i386平台上是这样。Linux 2.4的所有i386平台相关内联汇编代码中都没有使用这一点,但S390平台相关代码中有用到,但由于我对S390汇编没有任何概念,所以,也不知道这 么做的意义何在。

我要回帖

更多关于 存储映射给vmware里虚拟机 的文章

 

随机推荐