Skip to content

Int 13 中断功能

INT 13H,AH=00H 软、硬盘控制器复位

说明:
此功能复位磁盘(软盘和硬盘)控制器板和磁盘驱动器,它在磁盘控制器
芯片上完成复位操场作并在磁盘进行所需的操作之前做一系列用于磁盘校准的
磁盘操作。
当磁盘I/O功能调用出现错误时,需要调用此功能,此刻复位功能将使BIOS
象该磁盘重新插入一样检查驱动器中磁盘状态,并将磁头校准使之在应该在的
位置上。
此功能调用不影响软盘或硬盘上的数据。
入口参数:
AH=00H 指明调用复位磁盘功能。
DL 需要复位的驱动器号。
返回参数:
若产生错误,进位标志CF=1,错误码在AH寄存器。
详情请见磁盘错误状态返回码一文。
示例:
C_SEG SEGMENT PUBLIC
ASSUME CS:C_SEG,DS:C_SEG
ORG 100H
START: MOV AH, 00H
MOV DL, 80H
INT 13H
;复位硬盘 C
JC ERROR
……
ERROR: ……
C_SEG ENDS
END START

INT 13H,AH=02H 读扇区说明:
调用此功能将从磁盘上把一个或更多的扇区内容读进存贮器。因为这是一个
低级功能,在一个操作中读取的全部扇区必须在同一条磁道上(磁头号和磁道号
相同)。BIOS不能自动地从一条磁道末尾切换到另一条磁道开始,因此用户必须
把跨多条磁道的读操作分为若干条单磁道读操作。
入口参数:
AH=02H 指明调用读扇区功能。
AL 置要读的扇区数目,不允许使用读磁道末端以外的数值,也不允许
使该寄存器为0。
DL 需要进行读操作的驱动器号。
DH 所读磁盘的磁头号。
CH 磁道号的低8位数。
CL 低5位放入所读起始扇区号,位7-6表示磁道号的高2位。
ES:BX 读出数据的缓冲区地址。
返回参数:
如果CF=1,AX中存放出错状态。读出后的数据在ES:BX区域依次排列。
详情请参见磁盘错误状态返回码一文。
示例:
C_SEG SEGMENT PUBLIC
ASSUME CS:C_SEG,DS:C_SEG
ORG 100H
START: JMP READ
BUFFER DB 512 DUP(0)
READ: PUSH CS
POP ES
MOV BX, OFFSET BUFFER
MOV AX, 0201H
MOV CX, 0001H
MOV DX, 0000H
INT 13H
;读软盘A, 0面0道1扇区
;读出后数据在BUFFER中
JC ERROR
……
ERROR: ……
C_SEG ENDS
END START

INT 13H,AH=03H 写扇区

说明:
调用此功能将从磁盘上把一个或更多的扇区内容写入驱动器。因为这
是一个低级功能,在一个写入操作中的全部扇区必须在同一条磁道上(磁
头号和磁道号相同)。BIOS不能自动地从一条磁道末尾切换到另一条磁道
开始,因此用户必须把跨多条磁道的写操作分为若干条单磁道写操作。
入口参数:
AH=03H 指明调用写扇区功能。
AL 置要写的扇区数目,不允许使用超出磁道末端以外的数值,
也不允许使该寄存器为0。
DL 需要进行写操作的驱动器号。
DH 所写磁盘的磁头号。
CH 磁道号的低8位数。
CL 低5位放入所读起始扇区号,位7-6表示磁道号的高2位。
ES:BX 放置写入数据的存贮区地址。
返回参数:
如果CF=1,AX中存放出错状态。
详情请参见磁盘错误状态返回码一文。
示例:
C_SEG SEGMENT PUBLIC
ASSUME CS:C_SEG,DS:C_SEG
ORG 100H
START: JMP WRITE
BUFFER DB 512 DUP(0FFH)
WRITE: PUSH CS
POP ES
MOV BX, OFFSET BUFFER
MOV AX, 0301H
MOV CX, 0001H
MOV DX, 0000H
INT 13H
;写入软盘A, 0面0道1扇区
;把此扇区数据全部置为0FFH
JC ERROR
……
ERROR: ……
C_SEG ENDS
END START
INT 13H,AH=04H 检测扇区

说明:
这个功能检测磁盘上1个或更多的扇区。这个验证测试不是把磁盘上的
数据和内存中的数据进行比较,而只是简单地确定读出的数据有无CRC错误。
这个功能可用来验证驱动器中的软盘版。如果盘片的格式正确,CF=0。
入口参数:
AH=03H 指明调用检测扇区功能。
AL 置要检测的连续扇区数目,不允许使用超出磁道末端以外的
数值,也不允许使该寄存器为0。
DL 需要进行检测的驱动器号。
DH 磁盘的磁头号。
CH 磁道号的低8位数。
CL 低5位放入起始扇区号,位7-6表示磁道号的高2位。
返回参数:
如果CF=1,AX中存放出错状态。CF=0,检测正确。
详情请参见磁盘错误状态返回码一文。
示例:
C_SEG SEGMENT PUBLIC
ASSUME CS:C_SEG,DS:C_SEG
ORG 100H
START: MOV AX, 0401H
MOV CX, 0001H
MOV DX, 0000H
INT 13H
;检测软盘A, 0面0道1扇区
JC ERROR
……
ERROR: ……
C_SEG ENDS
END START

磁盘错误状态返回码:

磁盘错误状态

AH=
00H 未出错
01H 非法功能调用命令区。
02H 地址标记损坏,扇区标识(ID)无效或未找到。
03H 企图对有写保护的软盘执行写操作。
04H 所寻找的扇区没找到。
05H 复位操作失败。
06H 无介质。
07H 初始化错误,数据未存在DMA的64K缓冲区内。
08H DMA故障
09H DMA边界错误,数据未存在DMA的64K缓冲区内。
0AH 检测出错误码率的扇区标志。
0BH 所寻找的磁道没找到。
0CH 介质类型没发现。
0DH 扇区号有问题。
0EH 发现控制数据地址标记。
0FH 超出DMA边界
10H 读磁盘时奇偶校验错,且纠错码(EDC)不能纠正。
11H 读磁盘时奇偶校验错,但纠错码(EDC)已纠正错误。
20H 控制器错。
40H 查找操作无效。
80H 超时错误,驱动器不响应。
AAH 驱动器未准备好。
BBH 不明错误。
CCH 被选驱动器出现写故障。
E0H 错误寄存器是零
FFH 非法操作。

备注:
控制器的最后状态将会在磁盘操作完成后写入相应的BIOS数据区(40:41)

80386ASM程序设计基础

  80386 ASM程序设计基础,呵呵,这是最近一段时间我的业余爱好。本期将连续推出若干篇有关80386ASM程序设计的基础,主要介绍80386ASM指令的详细用法及如何在80386实模式下,保护模式下及虚拟8086模式编程以及我会详细介绍80386下的段页管理机制,我会将80386下的指令与8086下的相同指令进行比较。在你去看罗云彬的ASM编程之前,不妨先看看我的基础篇,希望有志于从事汇编语言的朋友,多提意见。
   80386处理器是Intel公司80x86发展史上的里程碑,它不但兼容先前的8086/8088,80186,80286处理器,而且也为后来的486,Pentium(586),Pentium Pro(686)的发展打下了坚实的基础,对于我们程序员来讲更重要的是:我们关心80386在指令上到底有哪些扩展呢?80386有哪些寻址方式呢?毫无疑问,它不但兼容了8086的所有指令,而且还对它们进行增强.
   呵呵,我知道有很多人问我CPU已经发展到PentiumIIII,没有必要学习80386的汇编。其实不然,80386处理器中的保护模式,虚拟8086模式以及地址的段页管理机制,虚拟内存这些都是以后处理器的核心。所以说80386是后续发展处理器的基础,比如说80486实质上80386+80387协处理,这块协处理器主要用于处理浮点运算,Pentium处理器在80386指令的基础上增加了57条指令,8个数据类型,8个64位的寄存器来处理多媒体。从这一点来看,完全有必要了解80386ASM,这就好像学习80386,必须先要熟练掌握8086。
   1.80386的的寄存器:
   80386的寄存器可以分为8组:通用寄存器,段寄存器,指令指针寄存器,标志寄存器,系统地址寄存器,控制寄存器,调试寄存器,测试寄存器,它们的宽度都是32位的。本篇主要介绍80386的寄存器。
   A1.General Register(通用寄存器)
   EAX,EBX,ECX,EDX,ESI,EDI,ESP,EBP,它们的低16位就是8086的AX,BX,CX,DX,SI,DI,SP,BP,它们的含义如下:
   EAX:累加器
   EBX:基址寄存器
   ECX:计数器
   EDX:数据寄存器
   ESI:源地址指针寄存器
   EDI:目的地址指针寄存器
   EBP:基址指针寄存器
   ESP:堆栈指针寄存器
   这些寄存器可以将低16位单独存取,也就是8086的AX,BX,CX,DX,SI,DI,SP,BP,在存取这些寄存器的低16位(AX,BX,CX,DX,SI,DI,SP,BP),它们的高16位不受影响,同时和8086一样对于AX,BX,CX,DX这四个寄存器来讲,可以单独存取它们的高8位和低8位(AH,AL,BH,BL,CH,CL,DH,DL)
  
   A2:Segment Register(段寄存器)
   除了8086的4个段外(CS,DS,ES,SS),80386还增加了两个段FS,GS,这些段寄存器都是16位的,它们的含义如下:
   CS:代码段(Code Segment)
   DS:数据段(Data Segment)
   ES:附加数据段(Extra Segment)
   SS:堆栈段(Stack Segment)
   FS:附加段
   GS 附加段
  
   A3:Instruction Pointer(指令指针寄存器)
   EIP,它的低16位就是8086的IP,它存储的是下一条要执行指令的地址。
  
   A4:Flag Register(标志寄存器)
   EFLAGS,和8086的16位标志寄存器相比,增加了4个控制位,不过这4个控制位它们在实模下不起作,这四个控制位分别是:
   a.IOPL(I/O Privilege Level),I/O特权级字段,它的宽度为2bit,它指定了I/O指令的特权级。如果当前的特权级别在数值上小于或等于IOPL,那么I/O指令可执行。否则,将发生一个保护性异常。
   b.NT(Nested Task):控制中断返回指令IRET,它宽度为1位。NT=0,用堆栈中保存的值恢复EFLAGS,CS和EIP从而实现中断返回;NT=1,则通过任务切换实现中断返回。
   c.RF(Restart Flag):重启标志,它的宽度是1位。它主要控制是否接受调试故障。RF=0接受,RF=1忽略。如果你的程序每一条指令都被成功执行,那么RF会被清0。而当接受到一个非调试故障时,处理器置RF=1。
   d.VM(Virtual Machine):虚拟8086模式(用软件来模拟8086的模式,所以也称虚拟机)。VM=0,处理器工作在一般的保护模式下;VM=1,工作在V8086模式下。
   其它16个标志位的含义和8086一样,在这里也重温一遍:
   e.CF(Carry Flag):进位标志位,由CLC,STC两标志位来控制
   f.PF(Parity Flag):奇偶标志位
   g.AF(Assistant Flag):辅助进位标志位
   h.ZF(Zero Flag):零标志位
   i.SF(Singal Flag):符号标志位
   j.IF(Interrupt Flag):中断允许标志位,由CLI,STI两条指令来控制
   k.DF(Direction Flag):向量标志位,由CLD,STD两条指令来控制
   l.OF(Overflow Flag):溢出标志位。
   控制寄存器,系统地址的寄存器,调试寄存器,测试寄存器将在介绍完80386分段,分页管理机制后介绍,请继续关注第二篇“80386存储器的寻址方式”。

   2.80386处理器的寻址方式
   在实式模式下,80386处理器的最大寻址空间仍然为1M,和8086/8088相似。即段地址*10H+段内偏移地址,从而形成20位地址。此种模式下,段基址是16的倍数,长度最大不超过64K。
   在保护模式下,80386处理器可以使用所有的物理内存。段基址可以是32位,也可以不是16的倍数,同时它的最大长度为4G,这与8086完全不同,在形成逻辑地址时用段基址直接加上段内偏移地址,而并不将段基址左移4位(乘以16)。通常情况下,除了访问堆栈外,默认的段都为DS,有跨段前缀就另当别论了。在以BP,EBP,ESP作为基址寄存器时,这时默认的段寄存器应该是SS,举几个简单的例子:
   MOV EAX,[SI];这里的段寄存器是DS
   MOV EAX,FS:[ESI];这里的段寄存器是FS,因为指令中使用跨段前缀显示指定了
   MOV EAX,[BP];这里的段寄存器是SS,因为指令中使用了BP作为基址寄存器
   MOV EAX,GS:[BP];这里段寄存器是GS,因为指令中使用跨段前缀显示指定了
   80386中32位数的操作的顺序是“高高低低”,即是说高16-》高16,高8-》高8,低16-》低16,低8-》低8,这和8086相似。同时80386微处理器兼容所有8086的寻址方式,而且对8086的寻址方式有很大的改进和扩展。在8086下,只允许BP,BX,SI,DI作为寻址寄存器,但在80386下,8个通用寄存器都可以作为寻址寄存器。不过有一点要注意的是在基址变址寄存器寻址方式或相对基址变址寻址方式中,段寄存器由基址寄存器来确定,而不是由变址寄存器来确定,同时除ESP外其它的7个通用寄存器都可以作为变址寄存器,用代码来表示就是:
   MOV EAX,[EBP+ESP+2];这条指令是错误的,因为不可以用ESP作为变址寄存器
   MOV EAX,[EBP+ESI+10H];这里的段寄存器应该有基址寄存器来决定。基址寄存器是BP,那么这里的段寄存就是SS
   MOV EAX,GS:[EBP+EDI+100H];不用看了,这里的段寄存器应该是GS,因为指令通过跨段前缀显示指定了
   80386支持的基地址+变址+位移量寻址进一步满足了高级语言支持的数据类型。对于C语言来讲,普通变量,数组,结构体,结构体的数组,数组的构体我们既可存放在栈中(静态定义-static definition),也可以存放在堆中(动态定义-dynamic definition),用ASM也一样可以实现。基址变址寄存器提供了两个可以改变的部分,而位移量则是静态的。看下面的例子:
   //Variables in C Programming-Language,the corresponding ASM will list below
   void main()
   {
   int a;//普通的变量,用ASM寻址时直接用DS:[一位移量],如DS:[2000],属于直接寻址方式
   int array[24];//数组,用ASM寻址时用DS:[BX+SI*4],4表示整型的长度,属于基址变址寻址方式
   struct abc
   {
   int a,b,c;
   float d;
   };
   struct abc aa;//结构体,用ASM寻址时DS:[BX+Shift],Shift代表位移量,属于寄存器相对寻址方式
   struct abc aa[100];//结构体数组,用ASM寻址时用DS:[BX+SI*sizeof(abc)+Shift],属于相对基址变址寻址方式
   struct cde
   {
   int array[100];
   float e,f,g;
   };
   struct cde ccc;//数组结构体,用ASM寻址时用DS:[BX+SI*4+Shift],属于相对基址变址寻址方式
   }
   80386与8086的寻址方式差不多完全一样,只不过80386的寻址方式更灵活,它的操作数有32位,16位,8位。
   让我们再重温一下8086的寻址方式:
   a.立即寻址,所谓立即寻址就是操作数就在指令中,比如说:MOV AX,5678H
   b.直接寻址,即直接包含操作数的有效地址EA,比如说MOV AX,[1234]
   c.寄存器间址寻址,用寄存器的内容来作为操作数的有效地址,比如说SI=1234,MOV AX,[SI],8086下可用的寄存器只有4个:BX,BP,SI,DI,80386下8个通用的寄存器都可以使用。
   d.寄存器相对寻址,即在寄存器间址寻址方式的基础上再加一个位移量,位移量可以是8位也可以是16位,比如说MOV AX,[BX+90H]。
   e.基址变址寻址,即操作数的有效地址由一基址寄存器和一变址寄存器产生,如MOV AX,[BX+SI]。那么在8086下,只有SI,DI可以作为变址寄存器,在80386下除ESP外的其它7个通用寄存器都可以作为变址寄存器,比如说MOV AX,[BX+SI]。
   f.相对基址变址寻址,在e寻址方式的基础上加上一位移量,比如说MOV AX,[BX+SI+100H]。
   在8086下,我们如进行字节或字操作,往往要加上伪指令WORD PTR或BYTE PTR。在80386下不用显示指定,处理器会自动处理,当发现目的操作为8位时,处理器就会进行8位操作,同理当发现目的操作为16位,处理器就会进行16位操作,80386下以目的操作数的长度为准,以下几条简单的传送指令:
   MOV AL,CS:[EAX];8位操作,段寄存器是CS,寻址方式是寄存器间址寻址
   MOV AL,ES:[BX];8位操作,段寄存器是ES,寻址方式是寄存器间址寻址
   MOV EDX,[EDX+EBX+1234H];32位操作,段寄存器是DS,寻址方式是相对基址变址寻址
   MOV AX,[EBX+ESI*4];16位操作,段寄存器是DS,寻址方式是基址变址寻址
   MOV BH,ES:[EBX+EDI+900H];8位操作,段寄存器是ES,寻址方式是相对基址变址寻址
   MOV DL,[EBP+ESI+1900H];8位操作,段寄存是SS,因为用了EBP作为基址寄存器。寻址方式是相对基址变址寻址

   80386的指令集包含了8086/8088,80186,80286的指令集,可以分为几个大类:数据传送指令,算术运算/逻辑运算指令,移位指令,控制转移指令,串操作指令,高级语言支持的指令,条件字节设置指令,位操作指令,处理器控制指令和保护方式指令。高级语言支持指令始于80186,保护方式指令始于80286,条件字节设置指令和位操作指令是80386新增的。
   本篇主要介绍数据传送指令,数据传送指令可以分为:通用数据传送,累加器专用传送,地址传送,标志传送,分别介绍如下:
   A.数值传送指令MOV,MOVZX,MOVSX,XCHG,PUSH,PUSHA,PUSHAD,POPA,POPAD,
   a.MOV,指令和8086相似,不过它支持32位操作。
   b.MOVZX,零扩展传送,格式--MOVZX DST,SRC,表示将源操作送给目的操作数,目的操作数空出的部分用0填补。
   c.MOVSX,符号扩展传送,格式--MOVSX DST,SRC,表示将源操作送给目的操作数,目的操作数空出的部分用SRC的符号位来填补,举个简单的例子来演示:
   MOV DL,90H;
   MOVSX AX,DL;AX=FF90H
   MOVZX AX,DL;AX=0090H
   MOVSX ESI,DL;ESI=FFFFFF90H
   MOVZX ESI,DL;ESI=00000090H
   事实上在8086中也有两条指令CBW,CWD可以对操作数进行扩展。MOVSX可以对有符号数进行扩展,MOVZX可以对无符号数进行扩展,看看CBW,CWD的用法:
   CBW将字节数据扩展成字,符号位扩展到AH中
   CWD将字数据扩展成双字,符号位放到DX中
   MOV AL,70H;
   CBW;//AX=0070
   CWD;//DX=0000,AX=0070
   d.XCHG,功能和8080相同,不过它支持8位,16位,32位操作,下面的语句均是合法的。
   XCHG AH,AL
   XCHG AX,AL
   XCHG ESI,EDI
   XCHG ESI,[EBX+EDI+1000H]
   e.PUSH,和8086不同的是,它支持立即数入栈,8位入栈,当然还有32位入栈,下面的语句均是合法的。
   PUSH AL
   PUSH BH
   PUSH 100H
   PUSH EAX
   PUSH EBX
   PUSH DWORD PTR [EAX]
   f.POP,功能和用法和8086一样。
   g.PUSHA,将8个通用寄存器全部进栈,进栈顺序为:AX,CX,DX,BX,SP,BP,SI,DI,然后SP指针寄存减16,不过SP入栈的内容是PUSHA指令执行前的内容。
   h.POPA,8个通用寄存器全部出栈,堆栈指针寄存器不是堆栈中弹出的内容,而是加16而得到的,虽然这样得到的值和从堆栈中弹出来的内容一样,但物理意义不一样。
   i.PUSHAD,将8个32位通用寄存器全部入栈,入栈顺序EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI,ESP的内容是执行指令PUSHAD之前的内容
   j.POPAD,8个32位通寄存器全部出栈,ESP的内容参见h
  
   B.地址传送指令LEA,LDS,LES,LFS,LGS,LSS
   a.LEA,取有效地址,功能,用法与8086相同,不过它支持32位操作。规则:目的操作必须是16位或32位通用寄存器,当目的操作数是16位时,那么只装入有效地址的低16位。事实上LEA指令相当于伪指令OFFSET,看例子:
   MOV EAX,12345678H
   MOV EBX,56784321H
   LEA ECX,[EAX+EBX];ECX=99999999H
   b.LDS,装入指针,功能,用法与8086相同,不过它支持32位操作。格式:LDS REG,OPRD。规则,目的寄存器必须是16位或32位的通用寄存器,OPRD必须是内存单元,不可以是立即数。如果目的寄存器是16位,那么源操作数OPRD含32位指针;如果目的寄存器是32位,那么源操作数有48位指针。该指令将目的操作数OPRD所指向的内存单存的4个或6个连续字节的内容送给助记符指令中指定的DS段寄存器和指令中目的寄存器。比如:
   LDS EAX,[1000H];这表明将偏移地址为1000,1001H这两个字节单元的内容送给段寄存器DS,将偏移地址1002,1003,1004,1005四个字节单元的内容送往EAX。
   LDS AX,[1000H];这表明将偏移地址为1000,1001H这两个字节单元的内容送给段寄存器DS,将偏移地址1002,1003H两个字节单元的内容送往EAX。
   c.LES,同LDS,不过段寄存器是ES。
   d.LFS,同LDS,不过段寄存器是FS。
   e.LGS,同LDS,不过段寄存器是GS。
   h.LSS,同LDS,不过段寄存器是SS。
  
   C.标志传送指令LAHF,SAHF,PUSHF,PUSHFD,POPF,POPFD
   a.LAHF,将标志寄存器的低8位送至AH中,包括SF,ZF,ZF,PF,CF。
   b.SAHF,与i的过程恰好相反
   c.PUSHF,将标志寄存器的EFLAGS低16位内容入栈,和8086相同
   d.PUSHFD,将标志寄存器EFLAGS的内容入栈
   e.POPF,将栈顶的一个字弹出,并将它送到标志寄存器EFLAGS的低16位
   f.POPFD,将栈顶的两个字弹出,并将它送到标志寄存器EFLAGS
  
   D.累加器传送指令IN,OUT,XLAT
   a.IN,和8086相同,但可以输入一个双字节,同样如果端口的范围位于00H-FFH,可以直接用,如果超出这个范围,则先要将端口号送至DX,下面的语句是合法的:
   IN AL,20H;从20H端口读入一个字节
   IN AX,20H;从20H端口读入一个字
   MOV DX,0378H
   IN EAX,DX;从20H端口读两个字节
   b.OUT,和8086相同,但可以输出一个双字节,同样如果端口的范围位于00H-FFH,可以直接用,如果超出这个范围,则先要将端口号送至DX,下面的语句是合法的:
   OUT 20H,AL;从20H端口输出一个字节
   IN 20H,AX;从20H端口输出一个字
   MOV DX,0378H
   IN EAX,DX;从20H端口输出两个字
   c.XLAT,查表指令,功能和用法与8086相同,不过基址寄存器用的是EBX,来看看XLAT的实现过程:XLAT以BX作为基址寄存器,以AL作为变址寄存进器对指定的缓冲区进行查表,将AL指定位置的内容送往AL,比如说我们在MS-DOS方式写一个小程序:
   C:\>Debug
   -A100
   MOV BX,0120
   SUB AL,AL
   MOV DL,AL
   MOV AH,2
   INT 21
   MOV AH,4C
   INT 21
   INT 20
  -E120 'ABCDEFGHIJKLLMMDDKDJDK'
  =G100
   屏幕上会显示A,如果AL=3,那么屏幕会显示D
   以上所有的指令均不影响EFLAGS的各标志位。

  算术运算指令,逻辑运算指令,移位指令
   AA.算术运算指令
   A.加减法运算ADD,ADC,INC,SUB,SBB,DEC,CMP,NEG
   a.ADD,和8086功能,用法相同,不过支持32位操作,下面的语句都是合法的。
   ADD ESI,EDI
   ADD EAX,DWORD PTR [1000H]
   b.ADC,带进位的加法指令,即OPRDS+OPRDD+CF,其中OPRDS代表源操作数,OPRDD代表目的操作,CF代表进位标志位,功能和用法与8086相同,支持32位操作。
   c.SUB,和8086相同,支持32位操作。
   d.SBB,带进位的减法指令,即OPRDD-OPRDS-CF,其中OPRDS代表源操作数,OPRDD代表目的操作数,CF代表进位标志位,功能和用法与8086相同,支持32位操作。
   e.DEC,减1操作,功能和用法与8086相同,支持32位操作。
   f.CMP,比较操作,功能和用法与8086相同,支持32位操作。
   g.NEG,求补操作,功能和用法与8086相同,支持32位操作。
   h.INC 加1操作,功能和用法与8086相同,支持32位操作。
  
   B.乘除法指令MUL,DIV,IMUL,IDIV
   a.MUL,无符号数乘法指令,和8086功能用法一样,即指令中只给出一个操作,被乘数已默认,如果指令给出的操作数是32位的话,被乘数默认为EAX,那么乘积将存放在EDX:EAX中,其中EDX存放高32位,EAX存放低32位,如果此时EDX=0,即高32位为0的话,那么OF=0,CF=0,否则被置1。如果指令给出的操数作是16位的话,被乘数默认为AX那么乘积将放在DX:AX中,其中DX中将存放高16位,AX中存放低16位。如果指令给出的操作数是8位的话,被乘数默认为AL,那么乘积将放在AX,AH中存放高8位,AL中存放低8位。
   b.DIV,无符号数的除法指令,和8086一样,指令给出一个操作数,被除数已默认。如果指令中给出的操作数为32,那么被除数将是EDX:EAX, 最终的商将存放在EAX, 余数将存放在EDX中。如果指令给出操作数为16位,那么被除数为EAX,最终得到的商放在AX,余数放在EAX的高16位。如果指令中给出的操作数为8位,那么被除数是16位,最终得到的商将放在AL中,余数放在AH中。
   c.IMUL,有符号数的乘法指令,除了具有8086的用法外,有新的形式:
   c1.IMUL DST,SRC;将源操作数SRC与目的操作DST相乘,并将结果送往DST。
   c2.IMUL DST,SRC1,SRC2;将源操作数SRC1与源操作数SRC2相乘,并将结果送往DST。
   使用这种形式必须遵守的规则,形式c1指令中目的操作数必须是16位或32位通寄存器,源操作数的长度必须与目的操作的长度一样(8位立即数除外,即00H-FFH或80H-7FH),源操作数可以是通用寄存器,也可以是存储单元或立即数。形式c2指令中的源操作数SRC1可以是通用寄存器也可以是存储单元,源操作数SRC2必须是立即数,DST必须是16位或32位通用寄存器。呵呵,对于这些规则无需去问为什么,这是硬件的特性决定的,如果一定要问为什么,那只能问INTEL公司的硬件工程师了:)。同时,有一点要注意的是:这两种形式的指令,目的寄存器的长度与源操作数长度一样(8位立即数除外),这样的话,该指令事实上对有符号数和无符号数是一样的,因为乘积的低位部分均存储在目的寄存器中,而高位部分在这两种形式的指令中不予以存储。
   d.IDIV,有符号数的除法指令,用法和8086相同,不过支持32位操作。
   C.符号扩展指令CBW,CWD,CWDE,CDQ
   a.CBW,前面已介绍,在第三篇。
   b.CWD,前面已介绍,在第三篇。
   c.CWDE,是80386新增的指令。格式:CWDE。功能:将AX的符号位扩展到EAX的高16位中。
   d.CDQ,是80386新增的指令。格式:CDQ。功能,将EAX的符号位扩展到EDX中。
   e.以上四条指令均不影响标志位。
   f.举例说明:
   ;If AX=1234H,EAX=99991234H
   CBW;After processing the instruction,AX=1234,DX=0000H
   CDQ;After processing the instruction,EAX=99991234H,EDX=FFFFFFFFH
   BB.逻辑运算指令和移位指令NOT,AND,OR,XOR,TEST,SAL,SAR,SHL,SHR,ROL,ROR,RCL,RCR,SHLD,SHRD
   a.NOT,AND,OR,XOR,TEST这些指令的功能和用法与8086完全相同,不过它们支持32位操作。
   b.TEST,测试指令,该指令测试的结果并不回送到目的操作数和源操数。之所以要使用这条的指令,主要是因为根据TEST指令得到的结果,进行程序的条件转移。
   c.SAL,算术左移,功能和8086一样,但在8086中,如果在移位的位数超过1位,那么一定要移位的位数放在CX寄存器中。在80386中,可以不用这样做,其它的移位指令也一样。除了这一点以外,用法和8086一样,当然也支持32位操作。以下的语句均是合法的。
   SHL AL,5;这在8086中是非法,但在80386中是合法的
   SHL WORD PTR [SI],3
   d.SAR,算术右移,将操作数右移指定的位数,但左边的符号位保持不变,移出的最低位进入CF标志位。
   e.SHL,逻辑左移,用法和功能与SAL一样。
   f.SHR,逻辑右移,将操作右移指定的位数,同时每移一位,左边用0补充,移出的最低位进入CF标志位。
   g.说明:在80386中,实际移位的位数是指令中移位位数的低5位,也就是说移位位数的范围在0-31或0-1FH,CF标志位总是保留着目的操作数最后被移出位的值。当移位位数大于操作数的长度时,CF被置0。如果移位位数为1,移位前后的结果的符号位都是一样,那么很明显的是该操作数经移位后没有移出,此时OF=0。这四条指令的移位示意图(我画的是16位操作数的移位示意图,8位和32依此类推),SAL,SHL相当于乘法;SAR,SHR相当于除法。
   SAL:
   |-------------------------------------------------------------------------------------------|
   |CF|<-|bit15|bit14|bit13|bit12|bit11|bit10|bit9|bit8|bit7|bit6|bit5|bit4|bit3|bit2|bit1|bit0|
   |-- ----------------------------------------------------------------------------------------|
   SHL:
   |-------------------------------------------------------------------------------------------|
   |CF|<-|bit15|bit14|bit13|bit12|bit11|bit10|bit9|bit8|bit7|bit6|bit5|bit4|bit3|bit2|bit1|bit0|
   |--- ---------------------------------------------------------------------------------------|
   SAR:
   |--------------------------------------------------------------------------------------------|
   |-|bit15|bit14|bit13|bit12|bit11|bit10|bit9|bit8|bit7|bit6|bit5|bit4|bit3|bit2|bit1|bit0|->|CF||
   | |---|----------------------------------------------------------------------------------------|
   | ^
   |-----|最高位保持不变
   SHR:
   |--------------------------------------------------------------------------------------------|
   0->|bit15|bit14|bit13|bit12|bit11|bit10|bit9|bit8|bit7|bit6|bit5|bit4|bit3|bit2|bit1|bit0|->|CF||
   |--------------------------------------------------------------------------------------------|
   h.ROL,循环左移,支持32位操作数,用法和8086一样。
   i.ROR,循环右移,支持32位操作数,用法和8086一样。
   j.RCL,带进位的循环左移,支持32位操作数,用法和8086一样。
   k.RCR,带进位的循环右移,支持32位操作数,用法和8086一样。
   l.ROL,ROR,RCL,RCR的移位示意图(仍然以16位操作数来画,8位/32位依次类推):
   ROL:
   |--------------------------------------------------------------------------------------------------|
   |<-|bit15|bit14|bit13|bit12|bit11|bit10|bit9|bit8|bit7|bit6|bit5|bit4|bit3|bit2|bit1|bit0|<--------|
   |--------------------------------------------------------------------------------------------------|
   |--------------------------------------------------------------------------------------------------|
   ROR:
   |-------------------------------------------------------------------------------------------|
   |->|bit15|bit14|bit13|bit12|bit11|bit10|bit9|bit8|bit7|bit6|bit5|bit4|bit3|bit2|bit1|bit0|->|
   |-------------------------------------------------------------------------------------------|
   |-------------------------------------------------------------------------------------------|
  RCL:
   |-------------------------------------------------------------------------------------------------|
   |<-|CF|<-|bit15|bit14|bit13|bit12|bit11|bit10|bit9|bit8|bit7|bit6|bit5|bit4|bit3|bit2|bit1|bit0|<-|
   |-------------------------------------------------------------------------------------------------|
   |-------------------------------------------------------------------------------------------------|
   RCR:
   |-------------------------------------------------------------------------------------------------|
   |->|CF|<-|bit15|bit14|bit13|bit12|bit11|bit10|bit9|bit8|bit7|bit6|bit5|bit4|bit3|bit2|bit1|bit0|->|
   |---- --------------------------------------------------------------------------------------------|
   |-------------------------------------------------------------------------------------------------|
   m.SHLD,80386新增的双精度左位指令,指令格式:SHLD OPRD1,OPRD2,M
   n.SHRD,80386新增的双精度右移指令,指令格式:SHRD,OPRD1,OPRD2,M
   o.m,n这两条指令的使用规则是:源操作数OPRD1可以是16位或32位通用寄存器或者16位存储单元或者32位存储单元,源操作数OPRD2必须是16位或32位通寄存器,M表示移位次数,可以是CL寄存器,也可以是8位立即数。功能:SHLD是将源操作数OPRD1移M位,空出的位用OPRD2高端的M位来填补,源操作数OPRD2的内容不变,最后移出的位放在CF中;SHRD将源操作数OPRD1移M位,空出的位用OPRD2低端M位来填补,源操作数OPRD2保持不变,最后移出的位放在CF中,对于这两条指令,当移位位数仅为1的话,移出和移后的符号位不变的话,那么OF=0,如果符号位不一样的话,那OF=1。
   p.这两条指令是80386新增的指令,举两个简单的例子加以说明:
   p1.SHLD:
   MOV AX,8321H
   MOV DX,5678H
   SHLD AX,DX,1
   SHLD AX,DX,2
   分析一下该指令的详细执行过程(用示意图, 第一个图画的就是AX的内容):
   AX=8321h
   |-------------------------------|
   |1|0|0|0|0|0|1|1|0|0|1|0|0|0|0|1|
   |-------------------------------|
   根据指令SHLD AX,DX,1,先左移一位,得到AX=0642H:
   |-------------------------------|
   |0|0|0|0|0|1|1|0|0|1|0|0|0|0|1|0| CF=1
   |-------------------------------|
   经过上一步的移位后,AX的最后一位(即bit0)空出来,其值为0;根据指令的用法将用DX的第15位填充,填充后AX的内容为:
   |-------------------------------|
   |0|0|0|0|0|1|1|0|0|1|0|0|0|0|1|0|
   |-------------------------------|
   同时由于移位后AX的符号位与移位前AX的符号位不同,所以在移位过程中产生了溢出,OF=1,最后结果AX=0642H。
   同理,SHLD AX,DX,2,执行完这条指令后,最后结果为AX=0644H
   p2.SHRD:
   MOV EAX,12345678H
   MOV EDX,99994599H
   SHRD AX,DX,1
   SHRD AX,DX,2
   分析一下该指令的详细执行过程(用示意图,第一个图画的是EAX的内容):
   EAX=12345678H
   |---------------------------------------------------------------|
   |0|0|0|1|0|0|1|0|0|0|1|1|0|1|0|0|0|1|0|1|0|1|1|0|0|1|1|1|1|0|0|0|
   |---------------------------------------------------------------|
   根据指令SHRD AX,DX,1,将AX右移一位得到EAX=091A2B3EH:
   |---------------------------------------------------------------|
   |0|0|0|0|1|0|0|1|0|0|0|1|1|0|1|0|0|0|1|0|1|0|1|1|0|0|1|1|1|1|1|0|
   |---------------------------------------------------------------|
   经过上一步的移位后,EAX的最高位(第31位)空出来用0填充,根据指令的用法,最EDX的第0位来填充,填充后EAX的内容为:
   |---------------------------------------------------------------|
   |1|0|0|0|1|0|0|1|0|0|0|1|1|0|1|0|0|0|1|0|0|1|1|0|0|1|1|1|1|0|0|0|
   |---------------------------------------------------------------|
   即EAX=891A2B3EH,CF=0,OF=0
   同理,指令SHRD AX,DX,2,执行完这条件指令后,最后结果为EAX=048D159C,CF=0,OF=0

  控制转移指令,串操作指令
   80386控制转移指令包括:转移指令,循环指令,过程调用和返回指令。
   A.转移指令包括无条件转移指令JMP和条件转移指令,无条件转移指令分为段内直接转移,段内间接转移,段间直接转移,段间间接转移。由于80386有保护模式和实模式,在实模式下,段内转移的范围在-128~127,段间转移最大范围为64K。在保护模式需要用48位指针,即CS:EIP(16位+32位)。条件转移指令有很多包括JCXZ,JECXZ,JBE,JAE,JA,JB等,其用法和8086相似。
  
   B.循环指令LOOP,LOOPZ,LOO0PE,LOOPNZ,LOOPNE,TASM支持助记符LOOP,LOOPWE,LOOPWZ,LOOPWNZ,LOOPWNE,LOOPD,LOOPWD,LOOPDE,LOOPDNE,LOOPDNZ。以CX作为计数器时,就可用LOOP,LOOPWE,LOOPWZ,LOOPWNZ,LOOPWNE;在以ECX作为计数器时,以LOOPD,LOOPDE,LOOPDZ,LOOPDNZ,LOOPDNE,下面的一段例子可以说明问题:
   ABC PROC
   MOV CX,100H
   AA:
   ;ADD YOUR CODES HERE
   LOOP AA
   ABC END
   C.过程调用和返回调用CALL,RET
   这两个指令与8086的用法相同,但由于80386下有实模式和保护模式下。在实模式下,无论是段内调用还是段间调用均采用32位指针,即CS:IP,它们的用法与8086下相同。在保护模式下,段间调用和段内调用均用48位指针,即ECS:IP。RET用于返回,具体实现过程会比较复杂,在介绍完80386的地址的管理机制后会作介绍,先介绍一下以下CALL指令在8086中的用法:
   a.段内直接转移,具体格式:CALL 过程名。此时CS不入栈,IP的内栈入栈,入栈后再将加上目的地址与CALL指令的下一条指令的偏移地址之差值就可以转移到目的地址,详细过程:
   SP-2=>SP;将堆栈指针SP减2
   (SP)<=IP;将IP进栈
   IP+偏移地址之差;转到目的地址
   b.段内间接转移,具体格式:CALL OPRD,那么在这里OPRD可以寄存器或内存单元,它的具体实现过程:
   SP-2=>SP;将堆栈指针SP减2
   (SP)<=IP;将IP进栈
   IP<=(OPRD);转到目的地址
   同a一样,CS不入栈
   c.段间直接转移,具体格式:CALL 过程名 [FAR],此时CS,IP均要入栈,详细的实现过程:
   SP-2=>SP;将堆栈指针减2
   (SP)<=CS;将CS入栈
   SP-2=>SP;将堆栈指针再减2
   (SP)<=IP;将IP入栈
   ;装入新的CS,IP
   IP<=过程入口的偏移地址
   CS<=过程入口的段地址
   d.段间间接转移,具体格式:CALL OPRD [FAR],此时CS,IP均要入栈,OPRD是32位,你知道在8086中没有32位寄存器。因此,这里的OPRD一定是存储单元,高16位是CS的值,低16位是IP值,详细的实现过程:
   SP-2=>SP;将堆栈指针减2
   (SP)<=CS;将CS入栈
   SP-2=>SP;将堆栈指针再减2
   (SP)<=IP;将IP入栈
   ;装入新的CS,IP
   IP<=(OPRD+2,OPRD+3)
   CS<=(OPRD,OPRD1)
   e.段内返回
   格式:RET。实际上它的实现过程:
   (SP)=>IP;从当前栈顶弹出一个字,将它送给IP指令计数器
   SP+2=>SP;SP
   f.段间返回
   格式:RET,实际上它的实现过程:
   (SP)=>IP;IP出栈
   SP+2=>SP;
   (SP)=>CS;CS出栈
   SP+2=>SP;
  
   D.中断返回指令IRET
   功能和用法与8086相同,这里顺便介绍一下8086的中断返回指令
   IRET,具体的实现过程:
   IP<=(SP);IP出栈
   SP+2=>SP;
   CS<=(SP);CS出栈
   SP+2=>SP;
   FLAGS<=(SP);标志寄存器出栈
   SP+2=>SP;
  
   E.串操作指令
   80386在串操作指令方面增加了双字操作,在8086五条指令的基础上增加了INS,OUTS。
   a.LOADSD,和8086的用法和功能相同,不过是对32位操作数操作。
   b.STOSD,和8086的用法和功能相同,不过是对32位操作数操作。
   c.CMPSD,和8086的用法和功能相同,不过是对32位操作数操作。
   d.SCANSD,和8086的用法和功能相同,不过是对32位操作数操作。
   e.MOVSD,和8086的用法和功能相同,不过是对32位操作数操作。
   f.重复前缀REP,和8086的功能与用法相同,仍以CX为计数器,看下面的一小程序:
   ROR ECX,2
   REP MOVSD;以CX为计数器,每次传送双字
   ROL ECX,1
   REP MOVSW;以CX为计数器,每次传送一字
   ROL ECX,1
   REP MOVSB;以CX为计数器,每个传送一个字节
   g.INSB,INSW,INSD,OUTSB,OUTSW,OUTSD
   g1.INSB,串输入指令,以字节单位,该指令的功能是从DX指定的端口读入一个字节到ES:DI指定的内存单元中。
   g2.INSW,串输入指令,以字单位,该指令的功能是从DX指定的端口读入一个字节到ES:DI指定的内存单元中。
   g3.INSD,串输入指令,以双字单位,该指令的功能是从DX指定的端口读入一个字节到ES:DI指定的内存单元中。
   g4.OUTSB, 串输出指令,以字节为单位,将DS:SI内存单元的内容送往DX指定的端口。
   g5.OUTSW, 串输出指令,以字为单位,将DS:SI内存单元的内容送往DX指定的端口。
   g6.OUTSD, 串输出指令,以双字为单位,将DS:SI内存单元的内容送往DX指定的端口。
   g7.串输入和串输出指令不影响标志寄存器中的各标志位,串操作指令可以与REP一起使用

  高级语言支持,条件字节设置指令
   AA.高级语言支持指令,开始于80186,主要是用来简化高级语言的某些特征,总共有3条指令:ENTER,LEAVE,BOUND
   a.ENTER,LEAVE,建立与释放堆栈框架命令。在C语言中,栈不仅用来向函数传递入口参数,而且在函数内部的局部变量也存放在栈中。为了准确地存取这些这些局变量和准确地获得入口参数,就需要建立堆栈框架,先看一个小程序:
   //C Programming-Language
   int sum(int x,int y)
   {
   int sum;
   sum=x+y;
   return sum;
   }
   //The corresponding ASM codes lists below
   _sum proc near;注意C语言中函数参数的入栈方式是从右向左,即先是参数y入栈,再是x入栈,再是函数的返回地址入栈
   push bp
   mov bp,sp;建立堆栈框架
   sub sp,2
   mov ax,word ptr [bp+4];取参数x
   add ax,word ptr [bp+6];加参数y
   mov word ptr [bp-2],ax
   mov ax,word ptr [bp-2]
   mov sp,bp;释放栈框架
   pop bp
   ret
   _sum endp
  此时栈顶的示意图是:
  |----------------------|
  | BP |<====SP
  |----------------------|
  | 函数返回地址 |<====BP+2
  |----------------------|
  | 参数x |<====BP+4
  |----------------------|
  | 参数y |<====BP+6
  |----------------------|
  | ...... |<====BP+8
  |----------------------|
  | ........ |<====BP+n,n是一能被2整除的数
  |----------------------|
  如果用建立和释放堆栈框架指令,那么对应的汇编程序应该是:
  _sum proc near
   enter 2,0;建立栈框架
   mov ax,word ptr [bp+4];取参数x
   add ax,word ptr [bp+6];加参数y
   mov word ptr [bp-2],ax
   mov ax,word ptr [bp-2]
   leave;释放栈框架
   ret
  _sum endp
  b.建立栈框架指令ENTER,格式如下:ENTER CNT1,CNT2。其中CNT1表示框架的大小,即子程序中需要放在栈中局部变量的字节数;CNT2是立即数,表示子程序嵌套级别,即从调用框架复制到当前框架的指针数。在立即数CNT2为0时,ENTER指令的实过程是:
  PUSH BP
  SP=>BP
  SP<=SP-CNT1
  c.释放栈框架指令LEAVE,其具体实现过程:
  8086:
  BP=>SP
  POP BP
  80386:
  EBP=>ESP
  POP EBP
  d.ENTER和LEAVE指令均不影响标志寄存器中的各标志位,同时LEAVE指令只负责释放栈框架,并不负责函数返回。因此,要在LEAVE指令后安排一条返回指令。
  BB.条件字节设置指令
  这是80386新增的一组指令集,将会在后面全部列表出来。条件字节设置指令的格式:
  SETxx OPRD
  xx是助记符的一部分,OPRD只能是8位的寄存器或存储单元。
  eg:
  SETO AL;表示当溢出标志位为1时,即OF=1,将AL置1,否则AL清0
  SETNC CH;表示当CF=0时,将CH置1,否则将CH清0
  SETNA BYTE PTR [100];表示当AF=0,将DS:[100]这一个字置1,否则将它清0
  a.SETZ OPRD;等于0时(ZF=1),置OPRD为1,否则清0
  b.SETE OPRD;同a
  c.SETNZ OPRD;不等于0时(ZF=0),置OPRD为1,否则清0
  d.SETNE OPRD;同c
  e.SETS OPRD;为负数时(SF=1)置OPRD为1,否则清0
  f.SETNS OPRD;同e正好相反(SF=0)
  g.SETO OPRD;OF=1,置OPRD为1,否则清0
  h.SETNO OPRD;同g正好相反
  i.SETP OPRD;偶(PF=1)置1
  j.SETPE OPRD;同i
  k.SETNP OPRD;奇(PF=0)置1
  l.SETPO OPRD;同k
  m.SETB OPRD;低于置OPRD为1,否则清0,这是针对无符号数的
  n.SETNAE OPRD;不高于即低于或等于时置OPRD为1,否则清0,这是针对无符号数的
  o.SETC OPRD;CF=1,置OPRD为1,否则清0
  p.SETNB OPRD;高于或等于时,置OPRD为1,否则清0,这是针对无符号数的
  q.SETAE OPRD;高于时置OPRD为1,否则清0,这是针对无符号数的
  r.SETNC OPRD;CF=0时,置OPRD为1,否则清0,这是针对无符号数的
  s.SETBE OPRD;低于或等于时,置OPRD为1,否则清0,这是针对无符号数的,CF|ZF=1
  t.SETNA OPRD;同s,这是针对无符号数的,CF|ZF=1
  u.SETNBE OPRD;高于时置OPRD为1,否则清0,这是针对无符号数的,CF OR ZF=0
  v.SETA OPRD;同u,这是针对无符号数的,CF OR ZF=0
  w.SETL OPRD;小于时,置OPRD为1,否则清0,这是针对有符号数的
  x.SETNGE OPRD;同w,这是针对有符号数的
  y.SETNL OPRD;大于或等于时,置OPR为1,否则清0,这是针对有符号数的
  z.SETGE OPRD;同y,这是针对有符号数的
  a1.SETLE OPRD;小于或等于时,置OPRD为1,否则清0,这是针对有符号数的
  a2.SETNG OPRD;同a1,这是针对有符号数的
  a3.SETNLE;大于时,置OPRD为1,否则清0,这是针对有符号数的
  a4.SETG;同a3,这是针对有符号数的

  位操作指令,处理器控制指令
  AA.位操作指令,8086新增的一组指令,包括位测试,位扫描。BT,BTC,BTR,BTS,BSF,BSR
  a.BT(Bit Test),位测试指令,指令格式:
   BT OPRD1,OPRD2,规则:操作作OPRD1可以是16位或32位的通用寄存器或者存储单元。操作数OPRD2必须是8位立即数或者是与OPRD1操作数长度相等的通用寄存器。如果用OPRD2除以OPRD1,假设商存放在Divd中,余数存放在Mod中,那么对OPRD1操作数要进行测试的位号就是Mod,它的主要功能就是把要测试位的值送往CF,看几个简单的例子:
  b.BTC(Bit Test And Complement),测试并取反用法和规则与BT是一样,但在功能有些不同,它不但将要测试位的值送往CF,并且还将该位取反。
  c.BTR(Bit Test And Reset),测试并复位,用法和规则与BT是一样,但在功能有些不同,它不但将要测试位的值送往CF,并且还将该位复位(即清0)。
  d.BTS(Bit Test And Set),测试并置位,用法和规则与BT是一样,但在功能有些不同,它不但将要测试位的值送往CF,并且还将该位置位(即置1)。
  e.BSF(Bit Scan Forward),顺向位扫描,指令格式:BSF OPRD1,OPRD2,功能:将从右向左(从最低位到最高位)对OPRD2操作数进行扫描,并将第一个为1的位号送给操作数OPRD1。操作数OPRD1,OPRD2可以是16位或32位通用寄存器或者存储单元,但OPRD1和OPRD2操作数的长度必须相等。
  f.BSR(Bit Scan Reverse),逆向位扫描,指令格式:BSR OPRD1,OPRD2,功能:将从左向右(从最高位到最低位)对OPRD2操作数进行扫描,并将第一个为1的位号送给操作数OPRD1。操作数OPRD1,OPRD2可以是16位或32位通用寄存器或存储单元,但OPRD1和OPRD2操作数的长度必须相等。
  g.举个简单的例子来说明这6条指令:
  AA DW 1234H,5678H
  BB DW 9999H,7777H
  MOV EAX,12345678H
  MOV BX,9999H
  BT EAX,8;CF=0,EAX保持不变
  BTC EAX,8;CF=0,EAX=12345778H
  BTR EAX,8;CF=0,EAX=12345678H
  BTS EAX,8;CF=0,EAX=12345778H
  BSF AX,BX;AX=0
  BSR AX,BX;AX=15
  BT WORD PTR [AA],4;CF=1,[AA]的内容不变
  BTC WORD PTR [AA],4;CF=1,[AA]=1223H
  BTR WORD PTR [AA],4;CF=1,[AA]=1223H
  BTS WORD PTR [AA],4;CF=1,[AA]=1234H
  BSF WORD PTR [AA],BX;[AA]=0;
  BSR WORD PTR [AA],BX;[AA]=15(十进制)
  BT DWORD PTR [BB],12;CF=1,[BB]的内容保持不变
  BTC DWORD PTR [BB],12;CF=1,[BB]=76779999H
  BTR DWORD PTR [BB],12;CF=1,[BB]=76779999H
  BTS DWORD PTR [BB],12;CF=1,[BB]=77779999H
  BSF DWORD PTR [BB],12;[BB]=0
  BSR DWORD PTR [BB],12;[BB]=31(十进制)
  BB.处理器控制指令
  处理器控制指令主要是用来设置/清除标志,空操作以及与外部事件同步等。
  a.CLC,将CF标志位清0。
  b.STC,将CF标志位置1。
  c.CLI,关中断。
  d.STI,开中断。
  e.CLD,清DF=0。
  f.STD,置DF=1。
  g.NOP,空操作,填补程序中的空白区,空操作本身不执行任何操作,主要是为了保持程序的连续性。
  h.WAIT,等待BUSY引脚为高。
  i.LOCK,封锁前缀可以锁定其后指令的操作数的存储单元,该指令在指令执行期间一直有效。在多任务环境中,可以用它来保证独占其享内存,只有以下指令才可以用LOCK前缀:
   XCHG,ADD,ADC,INC,SUB,SBB,DEC,NEG,OR,AND,XOR,NOT,BT,BTS,BTR,BTC
  j.说明处理器类型的伪指令
   .8086,只支持对8086指令的汇编
   .186,只支持对80186指令的汇编
   .286,支持对非特权的80286指令的汇编
   .286C,支持对非特权的80286指令的汇编
   .286P,支持对80286所有指令的汇编
   .386,支持对80386非特权指令的汇编
   .386C,支持对80386非特权指令的汇编
   .386P,支持对80386所有指令的汇编
   只有用伪指令说明了处理器类型,汇编程序才知道如何更好去编译,连接程序,更好地去检错。
   在后续的几篇里将详细介绍80386的段页管理机制及控制寄存器,调试寄存器,以及如何在386实模下和保护模式下编程。

  80386实模式下编程
  80386在实模式下是一个更快的8086,它不但可以进行32位操作,而且还可以进32位寻址,并且还可以使用80386的扩展指令。不过,由于是在实模下,寻址的最大空间为1M。在一个段内,段的最大长度不超过64K,否则就会发生异常。
  在8086下定义一个段的完整格式是:
  段名 [定位类型] [组合类型] [‘类别’]
  80386下定义一个段的完整格式是:
  段名 [定位类型] [组合类型] [‘类别’] [属性类型]
  说明:属性类型有两种:USE32和USE16,USE32表示32位段,USE16表示16位段。如果你在程序中用到伪指令.386,那么默认的属性类型就是USE32(32位段),如果没有用伪指令指定CPU的类型,那么默认的属性类型就是USE16,在实方式下只能使用16位段,即用USE16。
  eg:
   CSEG PARA PUBLIC USE32;定义一个32位的段
   AA DW ?
   BB DD ?
   CC DB ?
   DD DW ?
   EE DW 0,0,0.....
   CSEG ENDS
  由于在80386中用到了66H操作前缀和67H地址前缀,因此尽管在实式模式下,只要设定的CPU类型是80386,仍然可以进行32位操作,可以进行32位寻址,66H,67H这两个前缀无需程序员在程序中书写,汇编程序会自动加上的。只要在程序中对32位操作数进行访问,或进行32位寻址,那么就会加上操作数前缀66H和地址前缀67H。相反,如果在32位段中对16位或8位的访问,汇编程序中也会加上这两个前缀。
   下面将给出一个例子程序,演示一下在80386的实模式下编程的方法与技巧(这是从网上down的一个程序,不是我写的,但我会作详细的解剖,并与8086下的程序设计作出比较):
   用十进制,十六进制,二进制三种形式显示双字存储单元F000:1234中的内容

Using the Windows Headers

From: MSDN

Windows API 头文件允许您创建32- 和64-位应用程序。它们包含了Unicode 和 ANSI 版本的 API声明。更多的信息可参见 Unicode in the Windows API。They use data types that allow you to build both 32- and 64-bit versions of your application from a single source code base. For more information, see Getting Ready for 64-bit Windows. Additional features include Header Annotations and STRICT Type Checking.

Microsoft Visual C++ includes copies of the Windows header files that were current at the time Visual C++ was released. Therefore, if you install updated header files from an SDK, you may end up with multiple versions of the Windows header files on your computer. If you do not ensure that you are using the latest version of the SDK header files, you will receive the following error code when compiling code that uses features that were introduced after Visual C++ was released: error C2065: undeclared identifier.

条件申明

某些函数在部分Windows版本才具备,使用条件代码来声明它们。您就可以使用编译器来检测这些函数是否被目标Windows版本支持。必须定义适当的宏来编译使用到这些函数的应用程序。否则,你会收到C2065的出错信息。

Windows头文件使用宏来决定

The Windows header files use macros to indicate which versions of Windows support many programming elements. Therefore, you must define these macros to use new functionality introduced in each major operating system release. (Individual header files may use different macros; therefore, if compilation problems occur, check the header file that contains the definition for conditional definitions.) For more information, see SdkDdkver.h.

下表描述了Windows 头文件中的首选宏。

最小系统需求 NTDDI_VERSION
Windows Server 2008 NTDDI_WS08
Windows Vista SP1 NTDDI_VISTASP1
Windows Vista NTDDI_VISTA
Windows Server 2003 SP1 NTDDI_WS03SP1
Windows Server 2003 NTDDI_WS03
Windows XP SP2 NTDDI_WINXPSP2
Windows XP SP1 NTDDI_WINXPSP1
Windows XP NTDDI_WINXP
Windows 2000 SP4 NTDDI_WIN2KSP4
Windows 2000 SP3 NTDDI_WIN2KSP3
Windows 2000 SP2 NTDDI_WIN2KSP2
Windows 2000 SP1 NTDDI_WIN2KSP1
Windows 2000 NTDDI_WIN2K

下表描述了在Windows头文件中使用的宏。

最小系统需求 _WIN32_WINNT 和 WINVER的最小值
Windows Server 2008 0x0600
Windows Vista 0x0600
Windows Server 2003 SP1, Windows XP SP2 0x0502
Windows Server 2003, Windows XP 0x0501
Windows 2000 0x0500
最小版本需求 _WIN32_IE的最小值
Internet Explorer 7.0 0x0700
Internet Explorer 6.0 SP2 0x0603
Internet Explorer 6.0 SP1 0x0601
Internet Explorer 6.0 0x0600
Internet Explorer 5.5 0x0550
Internet Explorer 5.01 0x0501
Internet Explorer 5.0, 5.0a, 5.0b 0x0500

Note that some features introduced in the latest version of Windows may be added to a service pack for a previous version of Windows. Therefore, to target a service pack, you may need to define _WIN32_WINNT with the value for the next major operating system release. For example, the GetDllDirectory function was introduced in Windows Server 2003 and is conditionally defined if _WIN32_WINNT is 0x0502 or greater. This function was also added to Windows XP SP1. Therefore, if you were to define _WIN32_WINNT 0x0501 to target Windows XP, you would miss features that are defined in Windows XP SP1.

You can define these symbols by using the #define statement in each source file, or by specifying the /D compiler option supported by Visual C++. To specify compiler options, go to the Projects menu and click Properties. Go to Configuration Properties, then C++, then Command Line. Enter the option under Additional Options.

控制结构包

Projects should be compiled to use the default structure packing, which is currently 8 bytes because the largest integral type is 8 bytes. Doing so ensures that all structure types within the header files are compiled into the application with the same alignment the Windows API expects. It also ensures that structures with 8-byte values are properly aligned and will not cause alignment faults on processors that enforce data alignment.

With Microsoft Visual C++ 2005, structure packing is controlled by the /Zp switch. When this switch is omitted from a project build, the compiler automatically uses the default structure packing size.

If you are using a packing setting other than the default, be sure to use a pragma pack statement as shown in the following example to ensure that the Windows header files are packed correctly.

 

用最少的头文件来快速编译

You can reduce the size of the Windows header files by excluding some of the less common API declarations as follows:

  • Define WIN32_LEAN_AND_MEAN to exclude APIs such as Cryptography, DDE, RPC, Shell, and Windows Sockets.
  • Define one or more of the NOapi symbols to exclude the API. For example, NOCOMM excludes the serial communication API. For a list of support NOapi symbols, see Windows.h.

在中间层驱动中处理IRP

Higher-level drivers have a different set of standard routines than lowest-level device drivers, with an overlapping subset of standard routines common to both types of drivers.

The set of routines for intermediate and highest-level drivers also varies according to the following criteria:

  • The nature of the underlying physical device
  • Whether an underlying device driver sets up device objects for direct or buffered I/O
  • The design of the individual higher-level driver

The following figure illustrates the path an IRP might take through the standard routines of an intermediate mirror driver layered somewhere over the lowest-level device driver described in the previous section.

下图展示的驱动有下面的特性:

  • The driver is layered over more than one physical device and possibly over more than one device driver.
  • The driver sometimes allocates additional IRPs for lower-level drivers, depending on the requested operation in the input IRP.
  • The driver has at least one file system driver layered above it, and that file system driver might be layered over other intermediate drivers at a higher level than this one.

4hiddirp.jpg

IRP Path through Intermediate Driver Routines

As the figure shows, the I/O manager creates an IRP and sends it to the driver's dispatch routine for the given major function code. Assuming the function code is IRP_MJ_WRITE, the dispatch routine is DDDispatchWrite. The intermediate driver's I/O stack location is shown in the middle, with an indefinite number of I/O stack locations for higher- and lower-level drivers shown shaded.

分派IRPs

The mirror driver's purpose is to send write requests to several physical devices, and to send read requests alternately to the drivers of these devices. For write requests, the driver creates duplicate IRPs for each device on which the data is to be written, assuming the parameters in the input IRP are valid.

The previous figure shows a call to IoAllocateIrp but higher-level drivers can call other support routines to allocate IRPs for lower-level drivers. See Creating IRPs for Lower-Level Drivers.

When the dispatch routine calls IoAllocateIrp, it specifies the number of I/O stack locations needed for the IRP. The driver must specify a stack location for each lower driver in the chain, getting the appropriate value from the device objects of each driver just below the mirror driver. Optionally, the driver can add one to this value when it calls IoAllocateIrp to get a stack location of its own for each IRP it allocates, as the driver in the previous figure does.

This intermediate driver's dispatch routine calls IoGetCurrentIrpStackLocation (not shown) with the original IRP, to check parameters.

It calls IoSetNextIrpStackLocation because it allocated its own stack location in each newly created IRP and IoGetCurrentIrpStackLocation to create a context for itself that it uses later in the IoCompletion routine.

Next, it calls IoGetNextIrpStackLocation with each newly created IRP so that it can set up the next lower-level drivers' I/O stack locations in the IRPs it allocated. The mirror driver's dispatch routine copies the IRP function codes and parameters (pointer to the transfer buffer, length in bytes to be transferred for IRP_MJ_WRITE) into the I/O stack locations for the next-lower drivers. These drivers, in turn, will set up the I/O stack locations for the drivers just below them, if any.

调用IoSetCompletionRoutine 和 IoCallDriver

The dispatch routine in the previous figure calls IoSetCompletionRoutine for each IRP it allocated. Because the driver in the previous figure must dispose of the IRPs it allocated, this driver sets its IoCompletion routine to be called when lower drivers complete its IRPs, whether the I/O operation completed successfully, failed, or was canceled.

Because the driver in the previous figure mirrors in parallel, it passes both IRPs that it allocated on to the next-lower-level drivers by calling IoCallDriver twice, once for each target device object representing a mirrored partition.

在驱动 IoCompletion 函数中处理 IRPs

When either set of lower-level drivers completes the requested operation, the I/O manager calls the intermediate mirror driver's IoCompletion routine. The mirror driver maintains a count in its own I/O stack location for the original IRP, to track when the lower drivers have completed all the duplicate IRPs.

Assuming that the I/O status block indicates that one set of lower drivers has completed the duplicate IRP shown in the previous figure successfully, the mirror driver's IoCompletion routine decrements its count but cannot complete the original IRP until it decrements the count to zero. If the decremented count is not yet zero, the IoCompletion routine calls IoFreeIrp with the first-returned IRP (DupIRP1 in Figure 4.5) that the driver allocated and returns STATUS_MORE_PROCESSING_REQUIRED.

When the mirror driver's IoCompletion routine is called again with the DupIRP2 shown in the previous figure, the IoCompletion routine decrements the count in the original IRP and determines that both sets of lower-level drivers have carried out the requested operations.

Assuming the I/O status block in DupIRP2 also is set with STATUS_SUCCESS, the IoCompletion routine copies the I/O status block from DupIRP2 into the original IRP and frees DupIRP2. It calls IoCompleteRequest with the original IRP and returns STATUS_MORE_PROCESSING_REQUIRED. Returning this status prevents the I/O manager from attempting any further completion processing on DupIRP2; because the IRP is not associated with a thread, its completion processing should end with the driver that created it.

If either set of lower-level drivers does not complete the mirror driver's IRPs successfully, the mirror driver's IoCompletion routine should log an error and attempt appropriate mirrored-data recovery.

让你的程序具备XP风格

  以上说的适用于MFC,如果用win32   sdk的话,还是应该参照MSDN.  
  只不过MFC7.1把以下这些自动化了。  
   
  大意是:(我用xpstyle代表项目名)  
   
  1。做一个xml文件:xpstyle.manifest     

 
<?xml   version="1.0"   encoding="UTF-8"   standalone="yes"?>
  <assembly   xmlns="urn:schemas-microsoft-com:asm.v1"   manifestVersion="1.0">
  <assemblyIdentity
          version="1.0.0.0"
          processorArchitecture="X86"
          name="CompanyName.ProductName.xpstyle"
          type="win32"
  />
  <description>Your   application   description   here.</description>
  <dependency>
          <dependentAssembly>
                  <assemblyIdentity
                          type="win32"
                          name="Microsoft.Windows.Common-Controls"
                          version="6.0.0.0"
                          processorArchitecture="X86"
                          publicKeyToken="6595b64144ccf1df"
                          language="*"
                  />
          </dependentAssembly>
  </dependency>
  </assembly>

   
   
  2.程序初始化时,运行  
 

InitCommonControls();  

   
  3.在resoure.h中加  
   

#define   IDR_MANIFEST CREATEPROCESS_MANIFEST_RESOURCE_ID    

   
  4.在xpstyle.rc中加  
   
 

#ifdef   _UNICODE  
  IDR_MANIFEST RT_MANIFEST "res\\xpstyle.manifest  

  #endif  
   
  5.最后还是要将项目设为UNICODE字符集编译。  
   
  6.另外,如果写dll,或者其他的,参照(MSDN2003-4)  
   
      ms-help://MS.MSDNQTR.2003APR.1033/shellcc/platform/commctls/userex/cookbook.htm#no_extensions

PDM系统中权限管理方法的研究与应用

    0  前言
    PDM系统对产品的整个形成过程进行控制并对在该过程中形成的或需要处理的数据和文档进行管理,同时为与产品 研发与制造相关的人员提供一个虚拟化、并行化的 协同工作环境。PDM系统是企业中产品数据的共享平台。
    数据共享和数据保护是企业实施PDM过程中需要同时协调解决的重要问题。正确有效的权限管理是妥善协调解决数据共享和数据保护的必要条件,是成功构建企业PDM系统的基础。PDM权限管理具有以下特点:
?    对象多样性
    PDM系统对整个产品形成过程中的数据进行管理,涉及到的产品数据对象种类繁多,对象之间的关系复杂,定义在每类对象上的操作也各不相同,产品数据对象呈现多样性的特点。
?    权限动态性
    PDM系统中的产品数据对象都具有多种状态,系统对于不同状态下的对象通常采取不同的权限控制策略,从而造成对象权限的动态变化性。
?    用户复杂性
    PDM是一个企业中的协作平台,涉及到的用户数量庞大。企业中用户的 组织形式多种多样,职责和权限各不相同,决定了PDM系统中的用户组织形式多样且复杂。
    由于上述特点的存在,PDM权限管理往往比其它应用系统的权限管理更为复杂和难以理解,本文给出了PDM权限管理中权限的定义和权限判定的数学表示,进而提出了一种实用的权限管理模型,最后给出了该模型的应用实例。

    1  PDM中权限管理的数学表示
    1.1权限的集合定义

    PDM系统中,所有的产品数据都被定义为类——产品数据类,具体的产品数据表示为产品数据对象。产品数据类的方法是PDM中权限管理的基本对象。PDM系统中所有的权限可以定义为集合A :
      A = A1∪A2∪A3∪A4,其中
      A1=(
      A2=(
      A3=(
      A4=(
    A被称作权限集合,A1、A2、 A3、A4分别被称之为权限子集合。
    A1是四元组的集合,A1中的一个四元组表示用户User在条件Condition满足的情况下,对于类Class的所有实例对象都具有执行Method方法的权限。同样,A2中的一个四元组表示工作组Workgroup中的所有用户具有执行Method方法的权限;A3中的一个四元组表示对象角色Object-Role具有执行Method方法的权限;A4中的一个四元组表示项目角色Project-Role具有执行Method方法的权限。
    1.2用户权限判定的函数表示
    用户权限判定指:在给定用户、对象和对象方法的前提下,判定用户是否具有权限执行该对象方法。用户权限判定可以定义为四元函数f(User, Object, Condition, Method),其中:
    (1)函数f():返回值为True或者False, True表明具有执行权限,False表明不具有执行权限。
    (2)User:表示待考查的用户。
    (3)Object:表示待考查方法所属的对象。
    (4)Condition:以多元组(数据仓库,状态,属性1,…,属性n)表示,其中数据仓库、状态和属性i(1..n)分别表示对象所在的数据仓库、对象的状态以及对象的若干属性。
    (5)Method:待考查的对象方法。
    (6)函数f()的推算过程:函数f()的推算过程就是将四元组自变量按照一定的规则与权限集合A中的四元组进行匹配的过程,如果和A中的一个四元组匹配成功,则函数返回TRUE。如果无法和A中任何一个四元组匹配成功,则函数返回FALSE。具体的匹配过程先后在权限子集合A1、A2、A3和A4中进行。在任何一个权限子集合找到一个四元组能够与自变量四元组匹配成功,就中止整个匹配过程,返回TRUE。如果在四个权限子集合中都匹配失败,则函数返回FALSE。
    图1是在A1中进行匹配的示意图。自变量(User, Object, Condition, Method)与A1中任一四元组匹配成功须同时满足以下四个条件:
?    自变量User与A1中四元组的User值相同。
?    自变量Object是A1中四元组的Class类的对象实例。
?    自变量Condition满足A1中四元组的Condition所定义的条件。
?    自变量Method方法与A1中四元组的Method相同。
    A2、A3、A4中的匹配条件与A1中的类似,只不过上述第一条不同。在A2中,第一条变为:自变量User是A2中四元组的Workgroup所确定工作组的成员。在A3中,第一条变为:自变量User是A3中四元组的Object-Role所确定的对象角色;在A4中,第一条变为:自变量User是A4中四元组的Project-Role所确定的项目角色。

图1  权限子集合A1中的匹配处理示意图

 

    2  基于产品数据二级分类的权限管理模型
    权限管理模型设计中,最难以处理的是协调数据共享与数据保护的矛盾。为妥善解决此问题,在本文提出的权限管理模型中,将产品数据分别按照所有者和管理方式进行两级分类,根据产品数据分别在两级分类中确定的类别,采用不同的权限控制策略进行管理。
    2.1 产品数据两级分类
    产品数据两级分类中,首先是按照管理模式将数据分为两大类:项目数据与非项目数据,项目数据是指按照项目模式管理的产品研制过程中形成的数据,比如:设计图纸、技术文件、零部件、源程序等;非项目数据指按照非项目方式管理的数据,通常不是产品研制过程中直接产生的数据,也不属于产品本身的设计数据,比如:标准件、 通用件和元器件数据等,这些数据往往是设计产品的参考和引用数据。
    将产品数据分为项目数据和非项目数据后,再按照所有者将数据进一步细分为三小类:私有数据、共享数据和归档数据。私有数据指PDM中用户个人工作区中的数据,是用户个人私有数据;共享数据指在PDM共享数据仓库中的数据,这些数据或者是处于审签流程中的数据,或者是需要在一定范围内共享的非正式数据;归档数据指在PDM归档数据仓库中的数据,属于正式发放的数据。
    按照两级分类管理,实际上将产品数据分为六小类,即按照项目模式管理的数据又分为私有、共享和归档三小类,按照非项目模式管理的数据也细分为私有、共享和归档三小类。
    现代的PDM系统中,所有的产品数据都被定义为对象来管理,上文所述产品数据二级分类方法,实际上也是产品数据对象的二级分类方法,相应地产品数据对象类别也称之为:项目数据对象、非项目数据对象或者私有数据对象、共享数据对象和归档数据对象。
    2.2 用户组织模式
    企业中人员的组织模式通常分为三种:静态组织机构型、动态项目型和混合型[3]。静态组织机构型指人员固定工作于企业中某一个部门;动态项目型指用户动态地被分配到项目组内工作,项目结束后,又被分配到另外一个项目中;混合型是前两种形式的结合,相当于企业中的矩阵式管理模式,人员一方面属于某一个具体的部门,同时又动态地属于某一项目。
    在本文所提出的权限管理模型中,用户的组织采取混合型的方式。具体采取以下三种组织形式的混合模式:
?    对象角色:定义属于对象实例本身的角色,模型中涉及到的角色有:对象的责任方和对象的查阅组。担任对象角色的用户,通常随对象的变化而变化。
?    项目角色:定义属于项目的角色,包括项目负责人、项目成员等等。担任项目角色的用户通常随项目的变化而变化。
?    工作组:工作组是一种静态的用户组织模式,工作组成员一经定义,不受对象与项目因素的影响。
    用户可以同时属于上述的三种形式的任何一个组织中。
    2.3 产品数据对象权限分类
    模型中,对PDM系统中实际的产品数据对象权限分类方式进行了简化,只分为两种权限:写权限和读权限。写权限泛指可以引起对象属性、所有者和状态变化的操作权限;读权限泛指获取对象信息,而不会引起对象属性、所有者和状态变化的操作权限。
    2.4产品数据对象写权限管理策略
    模型中,产品数据对象写权限的管理策略比较简单,即:写权限只赋予对象角色——责任方。对象刚刚创建时,由系统自动将创建者定义为对象的责任方。此后在对象的生存周期内,如果对象的设计负责人需要发生变化,按照以下规则进行变更:
?    对于项目数据对象,只有项目负责人和管理员有权限变更对象的责任方。
?    对于非项目数据对象,只有系统管理员有权限变更对象的责任方。
    采取将写权限赋予对象责任方的管理策略,简化了对象写权限控制逻辑,支持对象写权限拥有者的动态变化。在这种管理策略下,还实现了同时只有一个用户具有对象写权限,避免了由于多用户同时具有写权限造成的对象一致性失控问题。
    2.5 产品数据对象读权限管理策略
    按照两级分类方法,针对不同类别的产品数据对象采取不同的读权限管理模式。
    2.5.1 项目数据的读权限管理策略
    项目数据对象又可以细分为:项目私有数据对象、项目共享数据对象和项目归档数据对象。
    (1)项目私有数据对象读权限管理
    对于项目中的私有数据对象,读权限只赋予对象的责任方。
    (2)项目共享数据对象读权限管理
    当项目私有数据对象变为共享数据对象后,应该能够在项目组内部进行共享,以方便项目成员间的信息交流,但是这种共享是受控的,应该让对象的所有者决定哪些项目成员具有读权限。
    具体的权限设定是:将项目共享数据对象的读权限赋予对象责任方和对象的查阅组成员。对象的查阅组由对象的责任方负责定义,责任方定义对象查阅组成员时的可选范围是所有的项目角色成员,因此对象的查阅组总是对象所在项目的所有角色成员的子集。
    (3)项目归档数据对象读权限管理
    项目中的产品数据对象归档后,除了项目内部成员仍然具有读权限外,作为企业正式发放的产品数据,还应该有选择地使项目外的用户共享,这样才能实现设计复用和快速产品开发等先进的管理理念。因此项目中归档数据对象的读权限,除赋予对象责任方和对象的查阅组成员外,还赋予具有某商业密级的工作组。
    给项目数据对象增加一个属性定义:商业秘密等级,其可选值有:公开、普通秘密和核心秘密。同时设立三个商业密级工作组与之对应,将这三种商业秘密等级的归档数据对象的读权限分别赋予这三个工作组。将PDM的所有用户按照涉密 授权分别分配到这三个组中。这样,归档后的项目数据对象就实现了在项目外受控共享。
    如图2所示,项目数据由私有数据变为共享数据后,共享范围扩大到项目成员范围,变为归档数据后,共享范围进一步扩大到项目外的某一工作组范围。上述过程中,数据越来越正式,共享的范围也不断扩大。

图2   项目数据读权限管理示意图

    2.5.1 非项目数据对象的读权限管理策略
    如前所述,非项目数据对象不属于具体的产品设计数据,因此非项目数据通常包括企业内的基础数据,需要在企业范围内共享,以便设计人员参考和引用,因此读权限控制较松,管理简单。非项目数据中的私有数据对象读权限只赋予对象的责任方,共享数据对象和归档数据对象的读权限通常向所有的用户开放。
    3  应用实例
    本文提出的权限管理模型在某电子研究所PDM系统--Teamcenter Enterprise 4.0上获得实现。在系统设计时,将16种产品数据对象中的设计图纸、技术文件、产品组合、装配件、零件、软件、源程序、目标程序、逻辑设计包、PCB设计包和结构设计包共计10种对象按照项目数据对象的方式进行权限管理,将其余的标准件、元器件、产品设计更改审批单对象按照非项目数据对象的方式进行管理,较好地协调了数据共享和数据保护两方面的需要,满足了该所产品数据管理中权限控制的要求,取得了理想的工程应用效果。

    4  结论
    本文从归纳总结PDM系统中的权限管理特点入手,给出了PDM中权限的集合定义和用户权限判定的函数表示,同时提出了基于产品数据两级分类的权限管理模型,并介绍了模型在某电子研究所的具体实现情况。本模型较好地协调解决了数据共享和数据保护两方面的问题,能够满足企业中产品数据管理的需要,而且权限控制逻辑本身条理清楚,简单明了,用户容易接受和理解,工程上也易于实现。

交通标志——警告标志

十字交叉

T型交叉

T型交叉

T型交叉

Y型交叉

环型交叉

向左急弯路

向右急弯路

反向弯路

连续弯路

上陡坡

下陡坡

两侧变窄

右侧变窄

左侧变窄

窄桥

双向交通

注意行人

注意儿童

注意牲畜

注意信号灯

注意落石

注意横风

易滑

傍山险路

提坝路

村庄

隧道

渡口

驼峰桥

路面不平

过水路面

有人看守
铁道道口

50米

100米

150米

无人看守
铁路道口

叉形符号

斜杆符号

注意
非机动车

事故易发路段

a 左右绕行

b 左侧绕行

c 右侧绕行

慢行

注意障碍物

施工

注意危险

一个失败的项目研发经验

4年多前,具有综合专业背景的我离开信息产业部的研究所来到了一家民营的医疗器械企业,任研发中心的临床信息系统项目经理,但万万没想到,我所主持的技术研发项目的失败,却不是因技术不过硬的原因。

上层分歧给项目带来麻烦 我刚来的时候,这个项目已经立项了,只是一直没找到合适的人来实施。总觉得我是在受命组建一个新的部门,组织文化是空白,不会有任何问题的,可是上层的一些分歧却给我的工作带来了麻烦。

公司的总经理和技术副总在工作上有矛盾。作为总经理招来的部门级主管,我在上班前一直没与技术副总见过面,不知道这是不是算总经理犯的一个错误,虽然越级的人事安排也许有他的考虑。总经理介绍了他的一个同学作为我们的技术合作单位,我开始与其有一些初级的技术交流,技术副总一次偶然得知此事。于是我有了一大罪过:“你们探讨的问题都是代表公司的,到底谁是技术副总?

总经理找我谈话时,我好半天没缓过气来。如果就数据库内某个字段的定义、用哪个芯片更好点的问题都要请示技术副总,我这个岗位还有存在的价值吗?

我开始反思,认识到自己犯了一个很严重的错误:初来乍到,别人还没认清你的特点,信任度还没建立起来,怎么就没注意请示汇报呢!随后,我向副总承认了自己没有及时请示汇报的错误,并澄清了我们只是进行了一些技术交流,没有任何的越权行为。我开始注意调查,发现其实质是副总对该项目不是很热心,并因此与总经理产生了分歧,加上以前的许多事情,矛盾一下爆发了,我又是总经理招来的人,合作方又是总经理的熟人,这个事情就愈发的严重。

直接上司有时比董事长管用 无奈之下,我只好把合作方砍掉,并且事事早请示晚汇报。不能在同一个地方跌倒两次,我总结发现,直属上司的支持比董事长都好使。高层只能在战略上支持,战术上还是直属上司的配合。正应一句话“县官不如现管”。另外就是新到任的职业经理一定要对履新的岗位进行认真调查,入职前与未来的上司和下属进行沟通是非常必要的。

这次事件平息了,但有些事我不得不变得无所作为。很多审批权限在副总手里,于是项目一拖再拖。8个月后,在公司上层不断的施压下,项目组终于开始组建。

成也老专家,败也老专家 我们的团队中,有一位资深工程师,一位从业不久比较熟练的软件工程师,另外聘请了一位老专家作技术顾问。

随后的阶段,那位老专家起到了保驾护航的作用。然而,如果没有对老专家的“过度信任”,也不会有产品推出后的狼狈局面。因为该设备要用到手术室中,手术室的电磁环境不但频率复杂,强度还不小,可惜这是产品销售后才得到的发现,为时已晚。开发中,也曾有人提出这个问题,用不用考虑一下干扰问题,那位老专家一句话:“我们以前做过的都没什么问题。”问题是该老先生原来是做非手术室设备的,其电磁环境条件比手术室要好得多。我尝试着提了点反面意见,被很不屑地化解了。

 “阻碍社会进步的不是少年人的无知,而是老年人的固执”,终于有了切实的理解。在技术工作中,作为项目经理,别相信任何不经调查就得出的“没问题”的口头保证,尤其是所谓资深老专家对新问题的结论。昨天的经验往往是明天失败的导火索。在预研阶段,要把所有可能的试验结果都呈现出来,再做定夺。预研即使失败,损失也小得多。

研发人员指挥生产的恶果 产品开发过程平静而又顺利地结束了。我们决定采取用户购买后测试的方式。由于用户测试过于漫长,在进入这个测试之前,产品就归档转生产了。

在我供职的这家民营企业,其高层和80%的中层干部都来源于一家老牌的国有医疗器械企业。这家公司长期从事手术床设备的相关业务,后来的新人也都是为那两个项目配的,所以新项目除了我和我的团队之外,无人过问。我们求教于别人,经常得到友好而又歉意的一笑。

图纸完成了,生产部只安排了一个小伙子学习生产这个产品,没有安排专门的工艺人员转化工艺文件。这时手术床的设备产量已经不小了,并且中了几个大标,中标的机器都是成熟老机型,生产人员也较熟练,所以生产部就把主力都安排到那边去了。对生产部来讲,中标机器的生产是主要任务,我负责的机器就降到次要而又次要的地步,物流的供货也是优先别的机型。

我太急了,太需要有一个结果来证明,急功近利的结局就是不懂生产管理流程的项目部开发工程师全力配合生产,犯了瞎指挥的错误,导致生产过程检验不规范、装机不符合要求、生产检验文档不全、零件编号混乱,一句话,乱了,一切全乱了。

但是,机器还是源源不断地被发走。我暗自祷告,产量一大,我们开发的产品占主体时,一切会好起来的。

越俎代庖的代价刚开始的装机和培训,服务部门的工程师没有人特别熟悉这台机器。于是服务部的经理找到我求援,希望开发工程师能出差。为了确保机器能顺利推广开,我应承了下来,服务支持文档、服务工程师的培训就这样耽误了下来。如此周而复始,最后只要有客户咨询或投诉,就直接转到项目部,使项目部的很多具体工作进行不下去。

这还不是最糟糕的,开发工程师并未经过服务培训,而我们项目部的工程师对那些又不了解,其安装和用户培训却又要做,可想而知,结果自然是不到位,最后对机器、服务的抱怨都集中到了项目部上。

拆东墙补西墙的救火没能使流程理顺,这时又出现了机器抗干扰差的投诉,这原本通过预研可以提前预防,现在也尾大难调。只好找了一所大学的教授协助解决,虽有改进,结果也不是很理想。不过我实在没精力拿用户进行试验了,加上这番折腾,这个项目的利润被服务、退货、换货耗掉了一部分,销售渠道对产品的反应也很不好,抗干扰差、服务差、发错货,等等。面对危局,我无奈建议公司决策层停掉该项目。

这个故事结束半年了,每每回想,总是感慨:项目不是在最后才失败的,也许它一开始就是一个错误,也许它的路线方向就是错的。

IExtractImage

  1. void* CShellListCtrl::GetThumbnailImage(CString &amp;fileName, int longestEdge, int colorDepth)
  2. {
  3.  // divide the file name into a folder path and file name.
  4.  WCHAR wDirName[MAX_PATH];
  5.  WCHAR wFileName[MAX_PATH];
  6.  CString dir = fileName.Left(fileNam