有人可以向我解释ARM按位操作吗?

时间:2012-01-28 11:10:45

标签: arm bit-manipulation bit-shift

有人可以向我解释ARM位移,就像我五岁一样吗?我对涉及非十进制数系统的任何事物的理解都很差,因此理解位移和位运算符的概念对我来说很难。

以下每种情况会发生什么以及为什么(最终会在R3中发生什么以及在位级别的幕后会发生什么?)

/** LSL **/
mov r0, #1
mov r3, r0, LSL#10

/** LSR **/
mov r0, #1
mov r3, r0, LSR#10

/** ORR **/
mov r0, #1
mov r1, #4
orr r3, r1, r0

/** AND **/
mov r0, #1
mov r1, #4
and r3, r1, r0

/** BIC **/
mov r0, #1
mov r1, #4
bic r3, r1, r0

PS。不要用C位运算符来解释它。我不知道他们做了什么(>><<|&

2 个答案:

答案 0 :(得分:19)

真值表,两个输入,左边的两个数字和一个输出,右边的数字:

OR

a b  c     
0 0  0
0 1  1
1 0  1
1 1  1

左边的两个输入a和b代表四种可能的输入组合,不多于列表中的输入。

考虑1表示真,0表示假。在这种情况下,单词OR表示如果OR b为真,则c为真。正如您在表格中看到的那样,横向如果a或b为真,则c为真。

a b  c
0 0  0
0 1  0
1 0  0
1 1  1

并且意味着如果AND b都为真则它们都必须为真,则c为真。上面只有一种情况。

现在取两个字节0x12和0x34,十进制为18和52,但我们并不十分关心十进制。我们关心二进制0x12是0b00010010而0x34是0b00110100。汇编语言中的AND和OR和XOR等按位运算符意味着从每个操作数中取一位,并将结果赋予相同的位位置。它不喜欢添加你有这样的东西加上等于等等的东西。

所以我们排列位

0b00010010    0x12
0b00110100    0x34

所以倾斜你的头部,就像你要用左手拿着一个炸玉米饼咬一口,然后想象上面的真相表。如果我们看右边的两位,它们是0和0,接下来的两位是1和0,依此类推。因此,如果我们想要进行OR运算,则规则是a或b为真,则c,结果为真

   0b00010010
   0b00110100
OR ==========
   0b00110110

磁头向右倾斜,最低有效位(数字中一列中的位)0或0 = 0,两者均未设置。下一列(两列)1或0 = 1至少有一个为真。等等

0x12或0x34 = 0x36

在手臂组装中

mov r0,#0x12
mov r1,#0x34
orr r2,r0,r1

之后,或者操作r2将保持值0x36。

现在让我们和那些数字

    0b00010010
    0b00110100
AND ==========
    0b00010000

记住我们的真值表和a和b必须为真的规则(a 1)我们向右倾斜头,0和0是0,两者都不是真的。通过检查,只有一列的输入带有1,即16s列。这给我们留下了0x12和0x34 = 0x10

在手臂组装中

mov r0,#0x12
mov r1,#0x34
and r2,r0,r1

现在我们进入BIC指令。哪个代表逐位清晰,希望稍微有点意义。胳膊上的Bic是a bded而不是b。不是另一个真值表,而是只有一个输入和一个输出

不是

a  c
0  1
1  0

只有一个输入我们只有两个选择0和1,1个是真0是假。 NOT表示如果不是则c为真。当a不为真时c为真,当a为真时c不为真。基本上它反转了。

bic所做的是有两个输入a和b,操作是c = a AND(NOT b)所以真值表是:

a AND(NOT b)

a b  c
0 1  0
0 0  0
1 1  0
1 0  1

我开始使用AND真值表,然后记下b位,其中b在AND真值表中为0,我将其设为1,其中b在AND真值表中为1,我将其设为0。

因此,0x12和0x34上的双操作是

    0b00010010
    0b00110100
BIC ==========
    0b00000010

为什么它被称为有点清楚?了解这使得它更容易使用。如果你查看真值表并考虑第一和第二输入。在第二个b输入为1的情况下,输出为0.其中第二个输入b为0,输出本身未经修改。所以那个真值表或操作正在做的是说任何地方b被设置为清零或者是否为A中的那些位。所以如果我有数字0x1234并且我想将低8位归零,那么我将用0x00FF进行BIC。你的下一个问题是为什么不和那个0xFF00? (分析AND真值表,看看无论b是1,你都保持一个值,而b是0,你输出为零)。 ARM至少在传统上使用32位寄存器和固定的32位指令集。直接指示

mov r0,#0x12

在手臂中限制为8个非零位移位数字内的任何位置,将稍微移位。所以,如果我的值为0x12345678,并希望将低8位清零,我可以这样做

; assume r0 already has 0x12345678
bic r0,r0,#0xFF

; assume r0 already has 0x12345678
mov r1,#0xFF000000
orr r1,r1,#0x00FF0000
orr r1,r1,#0x0000FF00
;r1 now contains the value 0xFFFFFF00
and r0,r0,r1

; assume r0 already contains 0x12345678
ldr r1,my_byte_mask
and r0,r0,r1
my_byte_mask: .word 0xFFFFFF00

这与使用移动和两个orrs相比并不可怕,但是仍然比bic解决方案消耗更多的时钟周期,因为你烧掉额外的内存周期从ram中读取my_byte_mask,这可能需要一段时间。

; assume r0 already contains 0x12345678
mvn r1,#0xFF
and r0,r0,r1

最后一个不妥协。请注意,arm文档中的mvn是按位而不是立即,这意味着rx = NOT(立即)。这里的立即是0xFF。 NOT(0xFF)表示反转所有位,它是一个32位寄存器,这意味着0xFFFFFF00是NOT(0xFF)的结果,这就是寄存器r1在执行和之前得到的。

这就是为什么bic在ARM指令集中占有一席之地,因为有时使用bic指令掩盖(掩码= AND用于使某些位为零)需要更少的指令或时钟周期而不是和指令。

我使用掩码这个词作为一个概念,使得数字为零,而其他人则不然。 orring可以被认为是在第一个中创建位而只留下其他位,如果你看到OR真值表,任何时候b是1然后c是1.所以0x12345678 OR 0x000000FF导致0x123456FF第二位中的位操作数已设置。是的,无论何时在OR真值表中设置a然后设置输出都是正确的,但是很多时候当你使用这些按位操作时你有一个操作数你要做什么,设置一定数量的位如果没有修改其余部分或将一定数量的比特设置为零而不修改其余部分,或者除了一定数量的比特之外你想要将所有比特归零。当以这种方式使用时,你有一个操作数进入你想要操作的那个,你根据你想要的整体效果创建第二个操作数,例如在C中我们只想保留低位字节在一个参数out函数中有一个参数:

unsigned int keep_lower_byte ( unsigned int a )
{
    return(a&(~0xFF));
}

〜表示不是这样~0xFF,对于32位数字意味着0xFFFFFF00然后&amp;意味着AND,所以我们返回&amp; 0xFFFFFF00。 a是唯一真正的操作数,我们根据我们想要的操作发明了第二个...大多数按位操作你可以在指令中交换操作数,一切都很好,像ARM&amp; bic这样的指令虽然操作数是按某种顺序排列的,但就像减法一样,你必须使用正确的操作数顺序。

转移......有两种,逻辑和算术。逻辑是最简单的,是你使用&gt;&gt;时得到的或&lt;&lt;在C.

从0x12开始,即0b00010010。向左移动三个位置(0x12 <&lt;&lt; 3)意味着

00010010 < our original number 0x12
0010010x < shift left one bit location
010010xx < shift left another bit location
10010xxx < shift left a third bit location

什么位得到&#34;转移&#34;在空的位置,上面的x,根据操作而变化。对于C编程,它总是为零:

00010010 < our original number 0x12
00100100 < shift left one bit location
01001000 < shift left another bit location
10010000 < shift left a third bit location

但有时候(通常每个指令集都支持旋转和移位)还有其他方法可以移动,差异与你转移到空位的位有关,有时候你移位了结束并不总是消失,有时你将它保存在一个特殊的位持有者位置。

某些指令集只有一位移位意味着对于您编程的每条指令,您只能移位一位,因此上面将是3条指令,一次一位。其他指令集(如arm)允许您使用单个指令,并在指令中指定要在该方向上移位多少位。所以左转三个

mov r0,#0x12
mov r3,r0,lsl#3 ; shift the contents of r0 3 bits to the left and store in r3

在lsr和asr,逻辑右移和算术右移之间展示了你移入的变化(你会发现没有asl,算术移位因为没有意义,一些汇编程序会允许你使用一个asl指令,但将其编码为lsl)。

LOGICAL右移:

00010010 - our original number 0x12
x0001001 - shifted right one bit
xx000100 - shifted right another bit
xxx00010 - shifted right another bit

与C一样,有一个版本以零移位,即逻辑右移,以零移位

00010010 - our original number 0x12
00001001 - shifted right one bit
00000100 - shifted right another bit
00000010 - shifted right another bit

ARITHMETIC shift right意味着保留&#34;符号位&#34;标志位是什么?如果你没有,你还需要学习两个补码。基本上,如果您将位模式/值视为二进制补码数,则最高位(左侧的位)是符号位。如果为0,则数字为正数,1为数字为负数。您可能已经注意到,一位左移与二乘相同,右移与二乘相同.0x12&gt;&gt; 1 = 0x9,18>&gt; 1 = 9但是如果我们将减2移到右边,减去2是使用字节的0xFE或0b11111110。使用C风格逻辑右移0xFE&gt;&gt; 1 = 0x7F,或十进制-2&gt;&gt; 1 = 0x127。不幸的是,我们无法在单个操作中用C解决这个问题,但是在汇编中我们可以使用算术移位,假设你的指令集有一个,那么arm就是

ARITHMETIC右移

s1100100 - our starting value s is the sign bit whatever that is 0 or 1
ss110010 - one shift right
sss11001 - another shift right
ssss1100 - another shift right

因此,如果我们开始时符号位s是0,那么如果数字是01100100那么

01100100 - our starting value
00110010 - one shift right
00011001 - another shift right
00001100 - another shift right

但如果那个标志位是一个

11100100 - our starting value
11110010 - one shift right
11111001 - another shift right
11111100 - another shift right

我们可以解决右移的0xFE:

11111110 - 0xFE a minus 2 in twos complement for a byte
11111111 - shifted right one

所以在伪代码0xFE ASR 1 = 0xFF,-2 ASR 1 = -1。 -2除以2 = -1

您需要自己阅读的最后一件事与旋转和/或从末端移位的位发生的事情有关。向右移动lsbit移动&#34;离开末端&#34;这个数字就像块滑动了一个桌子而掉落的那个可能只是进入了#34; bit&#34; (以太,天堂或地狱,当这些世界从这个世界消失时,其中一个地方会死亡)。但是某些指令集中的某些指令会将该位移位并将其置于进位标志中(在加和减时读取),不是因为它必然是进位而是因为在alu和进位中有状态位是有道理的。现在旋转是什么,让我们说你有一个8位处理器,你旋转了一位,这个位从结束位移落到了Carry位, AND 在另一侧的位移是什么在操作之前是在进位中。基本上它是音乐椅,位于椅子周围,一个人站着站立,站立的人是携带位,椅子上的人是寄存器中的位。为什么这有用呢?假设我们有像Atmel AVR这样的8位处理器,但是想要进行64位移位。 64位需要8位,8位寄存器,比如说我在这8个寄存器中有64位数,我想做一个64位左移一位。我将从最低有效字节开始,并执行一个将零移入的lsl,但是移位的位进入进位位。那么下一个最重要的字节我做一个rol,向左旋转一位,进入的位是从前一个字节开始的位,然后输出的位进入进位。我重复其他字节的rol指令,查看16位移位:

00100010 z0001000 - our original number
00100010 z 0001000 - lsl the least significant byte, the ms bit z is in carry
0100010z 00010000 - rotate left the most significant byte pulling the z bit from carry

00100010z0001000 - if it had been a 16 bit register
0100010z00010000 - a logical shift left on a 16 bit with a zero coming in on the left

这就是旋转的目的,这就是为什么装配手册无法告诉你在执行逻辑操作时修改了哪些标志。

答案 1 :(得分:3)

我会做第一个,然后你可以尝试使用类似的方法解决剩下的问题:

/** LSL **/
mov r0, #1            ; r0 = 0000 0000 0000 0000 0000 0000 0000 0001
mov r3, r0, LSL#10    ; r3 = r0 logically shifted left by 10 bit positions 
                           = 0000 0000 0000 0000 0000 0100 0000 0000
                                                       ^           ^
                                                       +<<<<<<<<<<<+
                                                     shift left 10 bits

但请注意,如果您还不了解布尔运算,例如OR(|),AND(&)等,那么您将很难理解相应的ARM说明(ORRAND等)。