原子操作的内核源代码剖析 (三)

操作的内核源代码剖析 (三)

****************************************************************
原子操作的第二个方面: 逻辑运算。32位平台和64位平台
***************************************************************
原子操作的第二个方面: 逻辑运算,32位平台
----------------------------------------------------------------
------------------\asm-i386\bitops.h----------------------------
----------------------------------------------------------------
_I386_BITOPS_H
----------------------------------------------------------------
#ifndef _I386_BITOPS_H
#define _I386_BITOPS_H

#include <linux/config.h>
#include <linux/compiler.h>
/*
* These have to be done with inline assembly: that way the *bit-setting is guaranteed to be atomic. All bit operations *return 0 if the bit was cleared before the operation and != 0 *if it was not.
*bit 0 is the LSB of addr; bit 32 is the LSB of (addr+1).
*/
----------------------------------------------------------------
LOCK_PREFIX 
----------------------------------------------------------------
#ifdef CONFIG_SMP
#define LOCK_PREFIX "lock ; "
#else
#define LOCK_PREFIX ""
#endif
为多处理器时,LOCK_PREFIX为锁定总线。
为单处理器时,LOCK_PREFIX为空操作
----------------------------------------------------------------
ADDR
----------------------------------------------------------------
#define ADDR (*(volatile long *) addr)

自己认为:
typedef unsigned long u32;
#define ADDR (*(volatile u32 *) addr)
----------------------------------------------------------------
内核注释 :原子操作的第二个方面:逻辑运算,32位平台中的各个函数
----------------------------------------------------------------
内核注释:
1.set_bit - Atomically set a bit in memory
2. 参数解释:
@nr: the bit to set
@addr: the address to start counting from
3.This function is atomic and may not be reordered.
这些函数是原子操作且不能被重排序。
4.See __set_bit() if you do not require the atomic guarantees.
5.Note: there are no guarantees that this function will not be reordered on non x86 architectures, so if you are writting portable code, make sure not to rely on its reordering guarantees.
注意:在非x86体系结构上,不能保证函数不被重排序。
6.Note that @nr may be almost arbitrarily large; this function is not restricted to acting on a single-word quantity.
nr可以很大,而非只能是单个词的数量的限制。
----------------------------------------------------------------
set_bit()
----------------------------------------------------------------
static inline void set_bit(int nr, volatile unsigned long * addr)
{
__asm__ __volatile__( LOCK_PREFIX
"btsl %1,%0"
:"=m" (ADDR)
:"Ir" (nr));
}
1.先来看intel的相应的指令:
bt oprd1,oprd2 位测试指令,被测试位送CF 
btc oprd1,oprd2 位测试并取反指令,被测试位送CF并且被测试位取反
btr oprd1,oprd2 位测试并复位指令,被测试位送CF并且被测试位清0
bts oprd1,oprd2 位测试并置位指令,被测试位送CF并且被测试位置1
oprd1:可以是16位或32位通用寄存器和16位或32位存储单元,用于指定要测试的内容。
oprd2:必须是8位立即数或与操作数oprd1长度相等的通用寄存器,用于指定要测试的位。
例: bh bl
mov bx, 4567H==> 0100 0101 0110 0111
mov ecx,3
bt bx, cx =====>CF=0 0100 0100 0110 0111
btc bx, 3 =====>CF=0 0100 0100 1110 0111
btr bx, cx =====>CF=0 0100 0100 0110 0111
bts ebx,ecx=====>CF=0 0100 0100 0110 1111

2.AT&T格式汇编:
功能一样,只是源操作数,目的操作数顺序相反。
bt oprd2,oprd1 oprd1为要测试的内容,oprd2为指定要测试的位
其他指令:btc,btr,bts顺序也同上。
以上指令改写为:
btw cx,bx
btcw 3,bx
btrw cx,bx
btsl ecx,ebx

btsl %1,%0 <=====> btsl nr,ADDR<===> btsl nr,*(volatile u32 *)addr

3.I:表示立即数
----------------------------------------------------------------
__set_bit()
----------------------------------------------------------------
static inline void __set_bit(int nr, volatile unsigned long * addr)
{
__asm__(
"btsl %1,%0"
:"=m" (ADDR)
:"Ir" (nr));
}
1.Unlike set_bit(), this function is non-atomic and may be reordered.
2.无LOCK_PREFIX,不会锁定内存总线,对多处理器而言,不会是原子操作
----------------------------------------------------------------
clear_bit()
----------------------------------------------------------------
内核注释:
1.功能:clear_bit - Clears a bit in memory
参数:@nr: Bit to clear
@addr: Address to start counting from

2.clear_bit() is atomic and may not be reordered. clear_bit()是原子操作,不会被重排序。 

3.However, it does not contain a memory barrier, so if it is used for locking purposes, you should call smp_mb__before_clear_bit() and/or smp_mb__after_clear_bit() in order to ensure changes are visible on other processors.
如果是用于多处理器上的锁定目的,当用smp_mb_before_clear_bit()/smp_mb_after_clear_bit()来使数据对其他的处理器也是可见的。

static inline void clear_bit(int nr, volatile unsigned long * addr)
{
__asm__ __volatile__( LOCK_PREFIX
"btrl %1,%0"
:"=m" (ADDR)
:"Ir" (nr));
}
1.btr oprd1,oprd2 位测试并复位指令,被测试位送CF并且被测试位清0

2.btrl %1,%0 <==========> btrl nr,ADDR
%1 == nr: 将要设置的位
%0 == ADDR: 要被设置的操作数
----------------------------------------------------------------
__clear_bit()
----------------------------------------------------------------
static inline void __clear_bit(int nr, volatile unsigned long * addr)
{
__asm__ __volatile__(
"btrl %1,%0"
:"=m" (ADDR)
:"Ir" (nr));
}
功能同clear_bit(),只是没有LOCK_PREFIX前缀,不能用于多处理器的情况。
----------------------------------------------------------------
smp_mb__before_clear_bit() smp_mb__after_clear_bit()
----------------------------------------------------------------
#define barrier() __asm__ __volatile__("": : :"memory")

#define smp_mb__before_clear_bit() barrier()
#define smp_mb__after_clear_bit() barrier()
----------------------------------------------------------------
__change_bit()
----------------------------------------------------------------
内核注释:
1.功能:__change_bit - Toggle a bit in memory
@nr: the bit to change
@addr: the address to start counting from

2.Unlike change_bit(), this function is non-atomic and may be reordered.非原子操作,可以被编译器重排序

3.If it's called on the same region of memory simultaneously, the effect may be that only one operation succeeds.

static inline void __change_bit(int nr, volatile unsigned long * addr)
{
__asm__ __volatile__(
"btcl %1,%0"
:"=m" (ADDR)
:"Ir" (nr));
}
1.btc oprd1,oprd2 位测试并取反指令,被测试位送CF并且被测试位取反

2.btcl %1,%0 <==========> btcl nr,ADDR
%1 == nr: 将要设置的位
%0 == ADDR: 要被设置的操作数
----------------------------------------------------------------
change_bit()
----------------------------------------------------------------
内核注释:
1.功能:change_bit - Toggle a bit in memory
参数:@nr: Bit to change
@addr: Address to start counting from

2. change_bit() is atomic and may not be reordered. It may be reordered on other architectures than x86.
不能被重排序,是原子操作,但在非x86体系结构上可能会被编译器秩序重排序。

3.Note that @nr may be almost arbitrarily large; this function is not restricted to acting on a single-word quantity.

static inline void change_bit(int nr, volatile unsigned long * addr)
{
__asm__ __volatile__( LOCK_PREFIX
"btcl %1,%0"
:"=m" (ADDR)
:"Ir" (nr));
}
----------------------------------------------------------------
test_and_set_bit()
----------------------------------------------------------------
内核注释:
1.功能:test_and_set_bit - Set a bit and return its old value
实际上:当指定的该位为0时,返回0;当指定的该位为1时,返回-1,源代码有误,作者在计算机上设计一个主函数调用之,证实了这一想法。

2.参数:@nr: Bit to set
@addr: Address to count from
3.This operation is atomic and cannot be reordered.It may be reordered on other architectures than x86.It also implies a memory barrier.

static inline int test_and_set_bit(int nr, volatile unsigned long * addr)
{
int oldbit;

__asm__ __volatile__( LOCK_PREFIX
"btsl %2,%1\n\tsbbl %0,%0"
:"=r" (oldbit),"=m" (ADDR)
:"Ir" (nr) 
:"memory");
return oldbit;
}
分析:
1.汇编指令bts,btsl: 
(Intel指令) bts oprd1,oprd2 位测试并置位指令,被测试位送CF并且被测试位置1

(AT&T指令) btsl %2,%1 <=====> btsl nr,ADDR
将ADDR操作数中的第nr位(从0始)送CF,并且第nr位置1。

2.汇编指令sbb,sbbl:
(Intel指令) sbb oprd1,oprd2
oprd1 <-- oprd1 - oprd2 - CF

(AT&T指令) sbbl oprd1,oprd2
oprd2 <-- oprd2 - oprd1 - CF

3.sbbl %0,%0 <===> sbbl oldbit,oldbit
即为:oldbit <-- oldbit - oldbit - CF ===> oldbit <-- -CF
而CF为标志位,只有一位,为0或为1,
若第nr位为0,则送往CF中的为0,经过sbbl后,oldbit == -0 == 0
若第nr位为1,则送往CF中的为1,经过sbbl后,oldbit == -1 == 1的反码(为1111..110)加一,变成了111...111
综上所述:oldbit里面保存的是操作数中第nr位的原始值0或为-1。
所以本test_and_set_bit()函数的功能是将操作数ADDR中的第nr位设置为1(用btsl指令实现),用oldbit保存该位的原始值(用sbbl %0,%0来实现)并返回之。
注意:
"btsl %2,%1\n\tsbbl %0,%0"
返回值:
当该位为0的时候,返回0
当该位为1的时候,返回-1
认为源代码中有误,并非为1的时候去返回1,而是返回了-1。
作者将源函数为主要函数,自己再编写一个主函数来调用它,经调试认为的确是源代码中有误。
----------------------------------------------------------------
__test_and_set_bit()
----------------------------------------------------------------
内核注释:
1.功能:
__test_and_set_bit - Set a bit and return its old value
注意返回值:当指定的该位为0时,返回0;当指定的该位为1时,返回-1,源代码有误,作者在计算机上设计一个主函数调用之,证实了这一想法。
2.参数:
@nr: Bit to set
@addr: Address to count from

3.This operation is non-atomic and can be reordered. 
非原子操作,可以被重排序。

4.If two examples of this operation race, one can appear to succeed but actually fail. You must protect multiple accesses with a lock.

static inline int __test_and_set_bit(int nr, volatile unsigned long * addr)
{
int oldbit;

__asm__(
"btsl %2,%1\n\tsbbl %0,%0"
:"=r" (oldbit),"=m" (ADDR)
:"Ir" (nr));
return oldbit;
}
类似test_and_set_bit(),只是没有LOCK_PREFIX而矣。
----------------------------------------------------------------
test_and_clear_bit()
----------------------------------------------------------------
内核注释:
1.功能:
test_and_clear_bit - Clear a bit and return its old value
注意返回值:当指定的该位为0时,返回0;当指定的该位为1时,返回-1,源代码有误,作者在计算机上设计一个主函数调用之,证实了这一想法。
2.参数:
@nr: Bit to clear
@addr: Address to count from

3.This operation is atomic and cannot be reordered.It can be reorderdered on other architectures other than x86.It also implies a memory barrier.

static inline int test_and_clear_bit(int nr, volatile unsigned long * addr)
{
int oldbit;

__asm__ __volatile__( LOCK_PREFIX
"btrl %2,%1\n\tsbbl %0,%0"
:"=r" (oldbit),"=m" (ADDR)
:"Ir" (nr) 
: "memory");
return oldbit;
}
分析类似上面,只是这里为btrl指令。
btr oprd1,oprd2 位测试并复位指令,被测试位送CF并且被测试位清0
----------------------------------------------------------------
__test_and_clear_bit()
----------------------------------------------------------------
内核注释:
1.功能:
__test_and_clear_bit - Clear a bit and return its old value
注意返回值:当指定的该位为0时,返回0;当指定的该位为1时,返回-1,源代码有误,作者在计算机上设计一个主函数调用之,证实了这一想法。
2.参数:
@nr: Bit to clear
@addr: Address to count from

3.This operation is non-atomic and can be reordered. 

4.If two examples of this operation race, one can appear to succeed but actually fail. You must protect multiple accesses with a lock.

static inline int __test_and_clear_bit(int nr, volatile unsigned long *addr)
{
int oldbit;

__asm__(
"btrl %2,%1\n\tsbbl %0,%0"
:"=r" (oldbit),"=m" (ADDR)
:"Ir" (nr));
return oldbit;
}
分析类似上面。
----------------------------------------------------------------
__test_and_change_bit()
----------------------------------------------------------------
/* WARNING: non atomic and it can be reordered! */
虽有"memory",只是说明CPU寄存器中的数据可能与内存单元中的数据不一致,由于没有LOCK_PREFIX,故不能保证其原子操作,且可能会被编译器重排序。

注意返回值:当指定的该位为0时,返回0;当指定的该位为1时,返回-1,源代码有误,作者在计算机上设计一个主函数调用之,证实了这一想法。

static inline int __test_and_change_bit(int nr, volatile unsigned long *addr)
{
int oldbit;

__asm__ __volatile__(
"btcl %2,%1\n\tsbbl %0,%0"
:"=r" (oldbit),"=m" (ADDR)
:"Ir" (nr) 
: "memory");
return oldbit;
}
分析:
btc oprd1,oprd2 位测试并取反指令,被测试位送CF并且被测试位取反
btcl %2,%1 为操作数%1的第%2位送CF,然后将%1操作数的第%2位取反。
在sbbl %0,%0作用下,将操作数%1的第%2位的原始值返回之。
----------------------------------------------------------------
test_and_change_bit()
----------------------------------------------------------------
This operation is atomic and cannot be reordered. It also implies a memory barrier.

注意返回值:当指定的该位为0时,返回0;当指定的该位为1时,返回-1,源代码有误,作者在计算机上设计一个主函数调用之,证实了这一想法。

static inline int test_and_change_bit(int nr, volatile unsigned long* addr)
{
int oldbit;

__asm__ __volatile__( LOCK_PREFIX
"btcl %2,%1\n\tsbbl %0,%0"
:"=r" (oldbit),"=m" (ADDR)
:"Ir" (nr) 
: "memory");
return oldbit;
}
----------------------------------------------------------------
test_bit()
----------------------------------------------------------------
1.Fool kernel-doc since
2.#if 0,条件永不满足。
3.怎么回事,现在还没弄明白。???
#if 0 /* Fool kernel-doc since it doesn't do macros yet */
/**
* test_bit - Determine whether a bit is set
* @nr: bit number to test
* @addr: Address to start counting from
*/
static int test_bit(int nr, const volatile void * addr);
#endif
----------------------------------------------------------------
constant_test_bit()
----------------------------------------------------------------
注意:nr虽为int型变量,但这里nr应当为一个常量,与下面的函数进行对照。因为这里nr为常量,所以这里就用一句C语言来实现,而variable_test_bit()中的nr为int型变量,就用汇编指令去实现了。

功能:查看unsigned long型4个字节中第nr位是否为1

static inline int constant_test_bit(int nr, const volatile unsigned long *addr)
{
return ((1UL << (nr & 31)) & (addr[nr >> 5])) != 0;
}
精彩!!!
我的想法:
return ((1UL << (nr & 31)) & addr[0]) != 0;

分析:
1.const volatile unsigned long * addr 表示addr所指向的unsigned long型地址单元中的数据不能被更改。
具体参考:Linux Kernel内核字节序源代码分析-swab.h和big_endian.h一份资料。

2.代码分析:
addr 为unsigned long *型,故为4B,计32位

nr为int型整数,nr取值范围可以很大,但由于addr只有4B,计32位,所以只有当0 <= nr <= 31 nr的数值才是有效的。

31这个十进制数化为二进制后,为11111,计5位二进制数

nr & 31就是将4B的int型整数,将高于第4位(从第0位起,不含第4位)全部清了0,这样就使大于31的整数nr转化到小于或等于31。也即:只保留了低5位二进制位。本质就是使nr <= 31。

1UL << (nr & 31) :当nr在[0,31]区间时候,将unsigned long型4B的00000000 00000000 00000000 00000001中的1移动到第nr位(从0始-->31位止),其余位为0。

nr >> 5:即将低5位,表示数的范围为0-31,用来描述这4B,32位(0-31)的的部分清0。

若nr在[0,31]区间内,则(nr >> 5)后的结果为0,则addr[nr >> 5]<==>addr[0],即取出unsigned long addr[0]这个字节,然后在(1UL << (nr & 31)) & (addr[nr >> 5])来看这4个字节中第nr位是否为1。这是最常见的情况。

若nr > 31,则(nr >> 5)后其低5位(第0位-->第4位)被清0,留下第5位-->第31位的数据,并且全部右移了5位。这种情况发生于nr超出于32这个数字大小。然后addr[nr >> 5]则是取出(nr >> 5)这个数组下标的unsigned long 型的4B数据去参于与运算。比如:当nr == [32,63]中的一个数值时候,nr >> 5得到(nr >> 5)为1,则addr[nr >> 5]<==>addr[1];当nr == [64,95]中的一个数值时候,nr >> 5得到(nr >> 5)为2,则addr[nr >> 5]<==>addr[2],相当于nr/32,以32为单位换算成unsigned long 型数组的数组元素下标。然后取出这个unsigned long 型数组元素,共4B,来看第(nr & 31)位是否为1。但这种情况后果不定,应当尽量避免。
----------------------------------------------------------------
variable_test_bit()
----------------------------------------------------------------
这里的nr为一个变量:
static inline int variable_test_bit(int nr, const volatile unsigned long * addr)
{
int oldbit;

__asm__ __volatile__(
"btl %2,%1\n\tsbbl %0,%0"
:"=r" (oldbit)
:"m" (ADDR),"Ir" (nr));
return oldbit;
}
分析:
bt oprd1,oprd2 位测试指令,被测试位送CF 
----------------------------------------------------------------
test_bit()
----------------------------------------------------------------

#define test_bit(nr,addr) \
(__builtin_constant_p(nr) ? \
constant_test_bit((nr),(addr)) : \
variable_test_bit((nr),(addr)))

分析:
1.代码分析:
如果nr为一个常量,__builtin_constant_p(nr)返回1,则调用constant_test_bit()
如果nr为一个变量,__builtin_constant_p(nr)返回0,则调用variable_test_bit()

2.(参考gcc手册)
__builtin_constant_p (exp):
1)函数功能:
You can use the built-in function __builtin_constant_p to determine if a value is known to be constant at compile-time and hence that GCC can perform constantfolding on expressions involving that value. 

2)参数:
The argument of the function is the value to test. 
返回 1: if the argument is known to be a compiletime constant 
返回 0 :if it is not known to be a compile-time constant. 

3)使用场合:
You would typically use this function in an embedded application where memory was a critical resource. If you have some complex calculation, you may want it to be folded if it involves constants, but need to call a function if it does not. 
用于内存资源为关键资源的地方和复合运算的地方。
For example:
#define Scale_Value(X) \
(__builtin_constant_p (X) \
? ((X) * SCALE + OFFSET) : Scale (X))

4)宏和内联函数都可以用:
You may use this built-in function in either a macro or an inline function. 

5)初始化中的应用:
You may also use __builtin_constant_p in initializers for static data.
For instance,you can write
static const int table[] = 
{
__builtin_constant_p (EXPRESSION) ? (EXPRESSION) : -1,
/* . . . */
};
This is an acceptable initializer even if EXPRESSION is not a constant expression.GCC must be more conservative about evaluating the built-in in this case, because it has no opportunity to perform optimization.
Previous versions of GCC did not accept this built-in in data initializers. The earliest version where it is completely safe is 3.0.1.
----------------------------------------------------------------
find_first_zero_bit()
----------------------------------------------------------------
内核注释:
1.功能:
find_first_zero_bit - find the first zero bit in a memory region
找到第一个为0的位,并将该位的序号返回

2.参数:
@addr: The address to start the search at
@size: The maximum size to search,以位为单位

3.返回值:
Returns the bit-number of the first zero bit, not the number of the byte containing a bit.

分析前的准备工作:
1.汇编指令:
1)repe:
repe:要单独写一行,不要和它修饰的指令(如:movsb,stosb)写在同一行。
repe是重复前缀。当两串相等,即ZF=1时,则结束循环。它可以使串指令反复执行(CX不等于0 就执行),每执行一次,CX的内容减1。

2)scasl:
scasl:
intel汇编指令:scansd ;
串扫描指令,以双字为单位,scansd指令使用eax,
在DF=0时,每次执行操作后相应指针加4
在DF=1时,每次执行操作后相应指针减4
若只使用16位指针,则串中源指针为DS:SI,目的指针为:ES:DI
若只使用32位指针,则串中源指针为DS:ESI,目的指针为:ES:EDI
此外,可以通过段前缀超越改变源串使用的段寄存器,但不能改变目的串的段寄存器。
功能:以ES:EDI/DI指向目标串的首地址,每执行一次则扫描目标串的一个双字是否与EAX中的内容相等,如果相等则置ZF=1,否则置ZF=0.每扫描一次EDI都要按DF的值自动加4或减4.
若加上前缀REPZ/REPE,则表示未扫描完(ECX不为零),且由EDI所指的串元素与EAX的值相等(ZF=1)则继续扫描;如果ZF=0或ECX=0则停止扫描 
AT&T格式汇编指令scasl类似scansd

3)bsfl:
intel汇编指令:bsf oprd1,oprd2;
顺向位扫描(bit scan forward)
从右向左(从位0-->位15或位31)扫描字或双字操作数oprd2中第一个含"1"的位,并把扫描到的第一个含'1'的位的位号送操作数oprd1
AT&T格式汇编指令bsfl类似bsf,只是源操作数和目的操作数顺序相反。

4)shll:
先对比intel的汇编指令:shl
shl oprd,m ; 逻辑左移指令(shift logic left)
sal oprd,m ; 算术左移指令(shift arithmetic left)
算术左移和逻辑左移进行相同的动作,但为了方便提供有两个助记符,但要记住,shl和sal本质一样,只有一条机器指令。把操作数oprd左移m位。每移动一位,右边用0补足一位,移出的最高位进入标志位CF。

AT&T格式汇编:shll m,oprd,本质一样,只是源操作数和目的操作数顺序相反。

5):AT&T格式:
a:表示要求使用寄存器eax

b:表示要求使用寄存器ebx

c:表示要求使用寄存器ecx

d:表示要求使用寄存器edx

D:表示要求使用寄存器edi

&:在一般情况下,gcc将把输出操作数和一个不相干的输入操作数分配在同一个寄存器中,因为它假设在输出产生之前所有的输入都补消耗掉了。但是如果汇编程序中有多条汇编指令的时候,这种假设就不再正确。这时,在输出操作数前加上一个“&”就可以保证输出操作数不会覆盖输入,即:gcc将为此输出操作数分配一个输入操作数还没有使用的寄存器。

%0,%1...:表示使用寄存器的样板操作数,而对寄存器则使用双%%以示区分。

\n:在汇编指令中插入换行
\t:在汇编指令中插入TAB

-4(%ebp): [ebp-4]
-4(%%edi):edi-4

1f:向前跳
1b:向后跳

static inline int find_first_zero_bit(const unsigned long *addr, unsigned size)
{
int d0, d1, d2;
int res;

if (!size) return 0;
/* This looks at memory. Mark it volatile to tell gcc not to move it around */
__asm__ __volatile__(
"movl $-1,%%eax\n\t"
"xorl %%edx,%%edx\n\t"
"repe; scasl\n\t"
"je 1f\n\t"
"xorl -4(%%edi),%%eax\n\t"
"subl $4,%%edi\n\t"
"bsfl %%eax,%%edx\n"
"1:\tsubl %%ebx,%%edi\n\t"
"shll $3,%%edi\n\t"
"addl %%edi,%%edx"
:"=d" (res), "=&c" (d0), "=&D" (d1), "=&a" (d2)
:"1" ((size + 31) >> 5), "2" (addr), "b" (addr) 
: "memory");
return res;
}
正式分析:
1.源代码分析:

初始化情况:
"1" ((size + 31) >> 5):size: The maximum size to search,而%1是指"=&c" (d0),即为ECX计数器,也就是将最在搜索长度size(以位计算)按4B,32位换算一下有多少个双字。

"2" (addr):"2"是指"=&D" (d1),也即EDI,

"b" (addr):EBX中存放参数addr(要开始搜索的地址,也就是该字符串起始地址)

指令执行分析:
1)movl %-1,%%eax; :将eax = -1,而-1的补码为11111111 11111111 11111111 11111111,所以eax的内容就为这32个1
movl $-1 ,%%eax;这里用movl $0xffffffffh,%%eax;也行,但这里用$-1有其高明之处,对于$-1来说,无论%%eax是32位,还是将来扩充到64位,或是变成其它多少位,均能使其全部填充滿'1';而对$0xffffffffh,则%%eax必须是32位,一旦位数有变将又得相应改变$0xff...fh的个数或数值。仔细思考,发现这个地方虽似简单,实则是Linux内核开发人员经过认真设计了的,十分巧妙,佩服!!!

2)xorl %%edx,%%edx; :edx清0

3)repe; scasl; :以ES:EDI指向目标串的首地址,每执行一次则扫描目标串的一个双字是否与EAX中的内容相等,如果相等则置ZF=1,否则置ZF=0.每扫描一次EDI都要按DF的值自动加4或减4.由于这里加上了前缀REPE,则表示如果未扫描完(ECX不为零),且由EDI所指的串元素与EAX的值相等(ZF=1)则继续扫描,即:循环条件为ECX != 0 && ZF == 1;如果ZF == 0(由EDI所指的串元素与EAX的值不相等) 或 ECX == 0(已经扫描完)则停止扫描.

4)je 1f; :当跳出循环时候,若为ECX == 0,已经扫描完这种情况则此时ZF == 1,je条件满足,要跳转;若为ZF == 0(由EDI所指的串元素与EAX的值不相等)这种情况时候,则je条件不满足,不会跳转。
由于EAX 为32个二进制的‘1’,在repe; scasl;作用下,每次比较ES:EDI所扫描目标串的一个双字是否与EAX中的内容相等,即扫描ES:EDI目标串的一个双字是否为全'1',是全'1',不跳,EDI <=== EDI +/- 4,再比较下一个双字是否为全'1',直到ECX == 0,全比较完了或找到一个双字中含有一个或多个'0'时,repe循环结束。

5)xorl -4(%%edi),%%eax; :当执行这条语句时候,je没有跳转,即:ZF == 0(由EDI所指的串元素与EAX的值不相等),也就是找到了一个双字,其中含有一个或多个'0'。由于%%eax全1,通过xorl运算,将eax中的为1的位全部清0,而在[edi - 4]中原来为0的位(也就是我们要找的)就使得%%eax中的对应的位的'1'不变,这个变换结果存放在%%eax中。注意:该双字中含有一个或多个'0'的位所对应的%%eax中的位上的'1'都不动,维持'1'不变,而该双字中原来为'1'的位就与%%eax中的相应为'1'的位转化为了'0',结果要找寻[edi-4]中的第一个为'0'的位的任务就转化为找寻%%eax中的第一个为'1'的位。

6)subl $4,%%edi; :由于%edi每次比较后都会+/-(这里是+)4B,故找到含'0'的双字后,跳出循环前还会+4,所以这里要将%%edi的值减去个4。

7)bsfl %%eax,%%edx; :从右向左(从位0-->位15或位31)扫描双字操作数%%eax中第一个含"1"的位,并把扫描到的第一个含'1'的位的位号送%%edx中保存。(EDX中保存了在ES:EDI所指向的目标串中的在repe循环中找到的一个含有一个或多个'0'的双字中的第一个含'0'的位置(以位计算),注意此时已经转化为%%eax中第一个含'1'的位置(以位计算))

8)1:\tsubl %%ebx,%%edi; 
情况1:
当ECX == 0(已经扫描完),则会跳出循环,并且在下一条je 1f;指令下跳到这里。
此时,ECX计数器为零,%%edi == %%edi初始值 + 4*ECX的初始计数值,对ES:EDI所指向的字符串,EDI已经指向其字符串的未尾的再后面的4B。由初始化情况知:EBX中存放参数addr(要开始搜索的地址,也就是该字符串起始地址),则这里subl %%ebx,%%edi;则是%%edi == %%edi - %%edx,也就是指向ES:EDI所指向的字符串的结尾处再后面4个字节的尾指针减去该字符串的首指针,得出该字符串的总长度。该字符串的长度保存在%%edi中。

情况2:
EDI指向已经找到的含'0'的双字处,(edi在前面的指令中已经减了4,调回来指向这个含'0'的双字处)。由初始化情况知:EBX中存放参数addr(要开始搜索的地址,也就是该字符串起始地址),则这里subl %%ebx,%%edi;则是%%edi == %%edi - %%edx,也就是指向已经找到的含'0'的双字处的指针减去该字符串的首指针,得出该所找到的含'0'的双字处距离该字符串的首部的长度,这是个距离。这个距离保存在%%edi中。
以上的字符串的长度和所找到的含'0'的双字处距离该字符串的首部的长度,这个距离均是EDI和EBX地址之差,而计算机中的地址是以字节为单位的,故这里的长度与距离均是指多少个字节。

9)shll $3,%%edi; :
若%%edi为该字符串的总长度(以字节计),这里将该字节数转换为位,edi = edi * 8,即:%%edi为该字符串的总长度(以位计).

若%%edi为距离(以字节计),也将该字节数转换为位,edi = edi * 8。
即%%edi为前面的不含'0'的双字的总位数。或字符串的总长度的总位数。
%%edi为前面的不含'0'的双字的总位数.

10)addl %%edi,%%edx;:
若是%%edi为该字符串的总长度(以位计),则%%edx在前面被清零了,
若是%%edi为前面的不含'0'的双字的总位数,则%%edx为保存了在ES:EDI所指向的目标串中的在repe循环中找到的一个含有一个或多个'0'的双字中的第一个含'0'的位置(以位计算)。此时,将前面的不含'0'的双字的总位数加在这个在所找到的含'0'的双字中的第一个含'0'的位置,就得出了在ES:EDI指向目标串中的第一个含'0'的位置(以位计算)。

最后,"=d" (res)将edx中所保存的最终结果保存在res中,由函数返回。

3.返回值:
1.若size == 0,则返回0
2.若字符串中不含'0',则返回整个字符串的长度(以位计),也就是size的以位计的数值
3.若找到了含'0'的位,返回其在整个字符串中的位置。

改写成C语言:

算法:
初始条件:
const unsigned long *addr:要比较的字符串的首地址 
unsigned int size:要比较的最大长度,以位计(与汇编指令保持一致)
ebx = addr ; //ebx表示所扫描字符串的首地址

说明:本C语言程序纯粹只是为了解说以上这个汇编程序的,所以变量名尽可能与以上寄存器名相似,以达到解说目的。

Step 1: 令eax = 0xffffffffh ,作为要比较的数 (eax为C语言的变量,unsigned long)
edx = edx ^ edx ,edx清零 (edx为C语言的变量,unsigned long)
edx主要用于在所找到的含'0'的双字中,'0'所在的位。如果是没找到,则是用于保存该字符串的总长度(用位来表示)

Step 2: size :表示最多要搜索的位数,unsigned int类型;
ecx = ((size + 31) >> 5) ,ecx位计数器换算成为字节计数器 (ecx为C语言的变量)

Step 3: edi为unsigned long *型指针
edi = addr; 
while( *(edi) == eax && ecx != 0)
{
ecx -= 4; //每次比较4B
edi++;
}
edi++; //这里是为了和汇编指令repe; scasl;一样,模拟其的运行
//如果是没有找到没找到含'0'的字符串,则edi将指向addr所指向字符串的尾部的再后面4个字节
//如果是找到了含'0'的字符串,则edi将指向所找到的含'0'的字符串的下4B的开始

Step 4: 
if(ecx == 0) //没找到含'0'的字符串
goto nofound; //这里使用goto是为了和汇编程序一至

Step 5: //找到了含'0'的字符串
eax = *(edi - 4) ^ eax;//调回edi指向含'0'的字符串的这个双字处,利用其中为0的位置eax中相应的位为1;而*(edi -4)中为1的位,
//则eax中相应的位置为0。于是找寻*(edi - 4)中第一个为0的位的问题转化为找寻eax中第一个为1的位
edi -= 4;

Step 6: 
//从右向左(从位0-->位15或位31)扫描eax中第一个含"1"的位,结果保存在edx中
scan_bit_rest_num = 0;
while( ((eax & (u32)1) == 0) && (scan_bit_rest_num <32) )
{
eax >>= 1;
scan_bit_rest_num++;
}
if( scan_bit_rest_num < 32 )//找到了第一个含"1"的位
edx = scan_bit_rest_num;//保存了addr所指向的字符串中含'0'的双字中的第一个含'0'的位置,以位计。
//即:也就是对应的的eax中第一个含"1"的位置,以位计。


nofound:
edi = edi - ebx;//如果是没找到含'0'的双字:得出该字符串的总长度,以字节计
//如果是找到了含'0'的双字:为含'0'的双字处距离该字符串的首部的长度,以字节计.
edi <<= 3; //将字节数换算成位数计
edx += edi;

C程序:
const unsigned long *addr;
unsigned long eax;
unsigned long edx;
unsigned long ecx;
unsigned long *edi;
unsigned long scan_bit_rest_num;
unsigned long *ebx;//ebx表示所扫描字符串的首地址

eax = 0xffffffffh;
edx = edx ^ edx;
ecx = ((size + 31) >> 5);
edi = addr;
ebx = addr ;

while( *(edi) == eax && ecx != 0)
{
ecx -= 4; //每次比较4B
edi++;
}
edi++;
if(ecx == 0) //没找到含'0'的字符串
goto nofound; //这里使用goto是为了和汇编程序一至

//找到了含'0'的字符串
eax = *(edi - 4) ^ eax;
edi -= 4;
scan_bit_rest_num = 0;
while( ((eax & (u32)1) == 0) && (scan_bit_rest_num <32) )
{
eax >>= 1;
scan_bit_rest_num++;
}
if( scan_bit_rest_num < 32 )//找到了第一个含"1"的位
edx = scan_bit_rest_num;
nofound:
edi = edi - ebx;
edi <<= 3; //将字节数换算成位数计
edx += edi;
return(edx);
----------------------------------------------------------------
茶余饭后一点小资料
----------------------------------------------------------------
周天阳发贴子:
我认为不论是在Windows/DOS下还是在Linux下完完全全用汇编编一个大型程序已经是不可能了,也不会有人愿意去这样做。在windows下我们可以用VC,在Linux/Xwindows下我们可以用C甚至C++ Builder,但是像VC、C++ Builder之类的工具尽量隐藏了底层的调用,同时也阻隔了成为高手的机会,因为编出来的程序无法了解它的执行过程也就使编程中最重要的“可预测”性变得很低。正因为如此汇编才有它存在的必要性,同时还有一个更重要的原因,正如《超级解霸》的作者梁肇新所说:“编程序的重点不是“编”,而是调试程序,理论上的完美在实现的时候会遇到很多细节问题,这些问题必须调试才能解决。我的编程习惯是一天写五天调试,《超级解霸》是调试出来的,而不是写出来的。调试就涉及到汇编的问题,不进行汇编级的调试是不彻底的,也不能让人放心。


优秀的,完整的BIOS 代码 page ,132 title . PROCESSOR_TIMER_PARITY_REFRESH_NMI TEST ;*****************************************************************; ;*****************************************************************; ;** **; ;** (C)Copyright 1985-1996, American Megatrends Inc. **; ;** **; ;** All Rights Reserved. **; ;** **; ;** 6145-F, Northbelt Parkway, Norcross, **; ;** **; ;** Georgia - 30071, USA. Phone-(770)-246-8600. **; ;** **; ;*****************************************************************; ;*****************************************************************; ;---------------------------------------; include mbiosequ.equ ; bios global constants include mbiosmac.mac ; bios coding macro definition include cf.equ ;---------------------------------------; extrn check_point_no_stack:near extrn check_point_stack:near public bios_suru public shutdown_0 public shutdown_77 public shutdown_88 extrn shutdown_1:near ; shutdown after memory error extrn shutdown_2:near ; v_mode exception intr error extrn _shutdown_33:near ; shutdown after memory test extrn shutdown_4:near ; boot loader request shutdown extrn shutdown_5:near ; user defined shutdown routine extrn _shutdown_66:near ; shutdown during memory test to display memory size extrn shutdown_7:near ; unused, (chipset memory detection) extrn shutdown_8:near ; unused, (soft reset) extrn shutdown_9:near ; shutdown after block move extrn shutdown_a:near ; user defined shutdown routine extrn init_8259_80287:near ; shut 4/5, init 8259, 80287 extrn disable_video:near extrn clear_64k_memory:near extrn int_isr:near extrn _power_on_delay:byte extrn _software_delay:byte extrn ram_segment:word extrn flush_all_cache:near extrn power_on_init:near public power_on_init_end extrn hreset_clear:near public hreset_clear_end extrn sreset_clear:near public sreset_clear_end extern StopUsbHostController(dummy_ret):near extrn dummy_ret:near extrn disable_all_cache:near extrn shutdown_init:near public shutdown_init_end extrn decompress_post_init:near ;---------------------------------------; ; C O D E S E G M E N T ; ;---------------------------------------; cgroup group _text _text segment word public 'CODE' assume cs:cgroup .486p ;---------------------------------------; public _BIOS_STARTS _BIOS_STARTS label byte ; marks start of module ;---------------------------------------; ; FLAGS TEST (SF,ZF,PF,CF) ; ; ;---;---;---;---;---;---;---;---; ; ; ; S ; Z ; x ; A ; x ; P ; x ; C ; ; ; ;---;---;---;---;---;---;---;---; ; ; HARD RESET OR SHUTDOWN RESET ; ;---------------------------------------; ; SHUTDOWN PROCESSING ; ;---------------------------------------; bios_suru: cli ; test under CLI mode cld ; ensure direction mov ax,cs mov ss,ax ; $$$CORE0036+ >>> extern wake_up(wake_up_end):near public wake_up_end jmp wake_up ; check for wakeup wake_up_end: ; control will come here only if wake up is not needed ; $$$CORE0036+ <<< jmp power_on_init power_on_init_end: in al,kb_stat_port ; if sys_flag bit is set test al,00000100b ; then soft reset else power on jnz shut_5 ; not power-on ;---------------------------------------; ; VANILLA MEMORY PATCH ; ;---------------------------------------; ifdef VANILLA_BIOS ;---------------------------------------; ; save CPUID in cmos 35h(DL), 36h(DH) extrn cmos_data_out:near extrn _refresh_value:byte mov ebp,edx mov al,0b5h mov ah,dl ret_sp cmos_data_out mov al,0b6h mov ah,dh ret_sp cmos_data_out ; start memory refresh.. mov al,00h ; initialise DMA-PAGE reg. out 8fh,al ; (used in MEMORY REFRESH) io_delay mov al,01010100b ; start CH_1 (REFRESH) out 43h,al ; one byte count used io_delay mov al,cgroup:_refresh_value; low byte count out 41h,al mov cx,100h ; 400h..01/11/95 xor di,di mov es,di wpulse1: stosb loop wpulse1 ;---------------------------------------; extrn vanilla_patch_offset:near cmp cgroup:word ptr vanilla_patch_offset,0ffffh jz ret_off ; no routine mov sp,offset cgroup:sp_ret_off jmp vanilla_patch_offset-1 even sp_ret_off: dw offset cgroup:ret_off dw 0f000h ret_off: endif ;---------------------------------------; jmp hreset_clear ; hard reset init (if any) ;;;;hreset_clear_end: ;;;; jmp shutdown_0x ; hard reset, goto regs. test shut_5: jmp sreset_clear ; GA20 disable and other.... sreset_clear_end: mov al,8fh ; shutdown address out cmos_addr_port,al ; mask NMI & select shut byte jcxz short $+2 ; i/o delay jcxz short $+2 ; i/o delay in al,cmos_data_port ; read shutdown code mov ah,00 mov si,ax ; save in (SI) mov al,8fh ; shutdown address jcxz short $+2 ; i/o delay out cmos_addr_port,al mov al,00 ; clear shutdown byte jcxz short $+2 ; i/o delay jcxz short $+2 ; i/o delay out cmos_data_port,al mov ax,cs mov ss,ax mov ax,si ; restore shutdown code cmp al,04h ; for BOOT loader shutdown jz shut_1 ; & for USER defined shutdown cmp al,05h ; initialize 8259 (#1, #2) jz shut_1 ; and (DS), (SS), (SP), STI cmp al,0ah ; if shut code > 10 jbe shut_2 ; bypass intr. init jmp short shutdown_0 ; then hard reset ;---------------------------------------; ; INIT 8259 for SHUTDOWN 04, 05 ; ;---------------------------------------; shut_1: mov bx,level_2_int*256+level_1_int ret_sp init_8259_80287 ; shut 4/5, init 8259, 80287 ;---------------------------------------; ; INTERNAL CACHE IS ALWAYS ON ; ;---------------------------------------; shut_2: jmp shutdown_init shutdown_init_end: mov ax,40h ; global data segment (ah) = 0 mov ds,ax ; setup (DS) mov al,00h ; global extra segment (ah) = 0 mov es,ax ; setup (ES) mov al,30h ; global stack segment (ah) = 0 mov ss,ax ; setup (SS) mov sp,0100h ; setup (SP) shl si,1 ; prepare index jmp [si+cgroup:shut_jmp_tbl]; brunch with intr. disabled ;---------------------------------------; ; SHUTDOWN JMP TABLE ; ;---------------------------------------; even shut_jmp_tbl label word dw offset cgroup:shutdown_0; hard reset dw offset cgroup:shutdown_1; used for block move internal shutdown dw offset cgroup:shutdown_2; v_mode exception intr error dw offset cgroup:_shutdown_33; shutdown after memory test dw offset cgroup:shutdown_4; shutdown for boot loader dw offset cgroup:shutdown_5; shutdown (with intr. init) dw offset cgroup:_shutdown_66; shutdown during memory test to display memory size dw offset cgroup:shutdown_7; unused, (chipset memory detection) dw offset cgroup:shutdown_8; unused, (soft reset) dw offset cgroup:shutdown_9; shutdown after block move dw offset cgroup:shutdown_a; shutdown (w/o intr. init) ;---------------------------------------; hreset_clear_end: shutdown_0x: ; hard reset mov al,8dh out cmos_addr_port,al ; NMI OFF ;---------------------------------------; shutdown_0: ; hard reset shutdown_77: ; unused shutdown_88: ; unused check_point_si 03h ; ======== 03 mov ax,cs mov ss,ax xor bp,bp mov ds,bp ; set (DS) = 0 mov es,bp ; set (ES) = 0 jmp_di disable_video in al,kb_stat_port ; if sys_flag bit is set test al,00000100b ; then soft reset else power on jz not_cnt_alt_del ; power on cmp ds:word ptr [0472h],1234h jnz cnt_alt_del yes_cnt_alt_del: or bp,soft_reset_bit ; msb used for soft reset jmp short cnt_alt_del not_cnt_alt_del: cmp ds:word ptr [0472h],1234h jz yes_cnt_alt_del or bp,power_on_bit cnt_alt_del: ;;;; ret_sp clear_64k_memory ; clear segment 0 mov ax,30h mov ss,ax ; set stack mov sp,100h check_point 05h ; ======== 05 ;; call StopUsbHostController ; disable USB host controller ;; call disable_all_cache ; disable all cache call disable_all_cache ; disable all cache call StopUsbHostController ; disable USB host controller mov ax,cs mov ss,ax ret_sp clear_64k_memory ; clear segment 0 mov ax,30h mov ss,ax ; set stack mov sp,100h check_point 06h ; ======== 06 call decompress_post_init extrn _bios_to_rm:near jmp _bios_to_rm ;---------------------------------------; ;*****************************************************************; ;*****************************************************************; ;** **; ;** (C)Copyright 1985-1996, American Megatrends Inc. **; ;** **; ;** All Rights Reserved. **; ;** **; ;** 6145-F, Northbelt Parkway, Norcross, **; ;** **; ;** Georgia - 30071, USA. Phone-(770)-246-8600. **; ;** **; ;*****************************************************************; ;*****************************************************************; ;-----------------------------------------------------------------------; ; COPY_CONTROL_TO_RAM ; ; this routine copies 64k code to ram segment and give control to ram. ; ; input : ; ; none ; ; stack available ; ; output: ; ; none ; ; register destroyed..ALL except DS, ES, BP ; ;-----------------------------------------------------------------------; extrn copy_64k_memory:near public copy_control_to_ram copy_control_to_ram proc near push es push ds mov es,cgroup:ram_segment push cs pop ds ; source segment call copy_64k_memory ; transfer control to ram segment.. push es ; ram_segment push offset cgroup:xxx retf xxx: pop ds pop es xxx_exit: ret copy_control_to_ram endp ;-----------------------------------------------------------------------; ; COPY_TO_SHADOW ; ;-----------------------------------------------------------------------; ; this routine copies the code to asked shadow. ; ; input : ; ; ES:DI destn seg:off ; ; DS:SI source segment:offset ; ; CX #of bytes to move ; ; 0000 = 64k copy ; ; stack available ; ; output: ; ; none ; ; register destroyed..ALL except DS, ES, BP ; ;-----------------------------------------------------------------------; extrn move_bytes:near extrn enable_shadow_write:near extrn disable_shadow_write:near public copy_to_shadow copy_to_shadow proc near pushf cli ; disable interrupt call enable_shadow_write ; enable write to shadow call move_bytes ; move bytes call flush_all_cache ; flush all cache call disable_shadow_write ; enable write to shadow popf ret copy_to_shadow endp ;-----------------------------------------------------------------------; extrn e000_read_rom_write_x:near extrn e000_read_ram_write_rom:near extrn e000_read_x_write_ram:near extrn f000_read_rom_write_x:near extrn f000_read_ram_write_rom:near extrn f000_read_x_write_ram:near public do_f000_read_rom_write_x public do_f000_read_ram_write_rom public do_f000_read_x_write_ram public do_e000_read_rom_write_x public do_e000_read_ram_write_rom public do_e000_read_x_write_ram ;---------------------------------------; ifdef VANILLA_BIOS do_f000_read_rom_write_x: call f000_read_rom_write_x mov al,15 ; offset to table of fn# jmp short vanilla_patch do_f000_read_ram_write_rom: call f000_read_ram_write_rom mov al,18 ; offset to table of fn# jmp short vanilla_patch do_f000_read_x_write_ram: call f000_read_x_write_ram mov al,21 ; offset to table of fn# jmp short vanilla_patch do_e000_read_rom_write_x: call e000_read_rom_write_x mov al,24 ; offset to table of fn# jmp short vanilla_patch do_e000_read_ram_write_rom: call e000_read_ram_write_rom mov al,27 ; offset to table of fn# jmp short vanilla_patch do_e000_read_x_write_ram: call e000_read_x_write_ram mov al,30 ; offset to table of fn# vanilla_patch: push bx mov bx,cgroup:word ptr vanilla_patch_offset inc bx jz dop_00 ; no routine dec bx cbw ; AX = offset to jmp table add bx,ax push cs push offset cgroup:dop_00 push cs push bx retf dop_00: pop bx ret ;---------------------------------------; else ; NORMAL BIOS CODE do_f000_read_rom_write_x: jmp f000_read_rom_write_x do_f000_read_ram_write_rom: jmp f000_read_ram_write_rom do_f000_read_x_write_ram: jmp f000_read_x_write_ram do_e000_read_rom_write_x: jmp e000_read_rom_write_x do_e000_read_ram_write_rom: jmp e000_read_ram_write_rom do_e000_read_x_write_ram: jmp e000_read_x_write_ram endif ;-----------------------------------------------------------------------; ;*****************************************************************; ;*****************************************************************; ;** **; ;** (C)Copyright 1985-1996, American Megatrends Inc. **; ;** **; ;** All Rights Reserved. **; ;** **; ;** 6145-F, Northbelt Parkway, Norcross, **; ;** **; ;** Georgia - 30071, USA. Phone-(770)-246-8600. **; ;** **; ;*****************************************************************; ;*****************************************************************; ;---------------------------------------; public _BIOS_ENDS _BIOS_ENDS label byte ; marks end of module ;---------------------------------------; _text ends end
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值