第十二章 APO编程语言

来源:互联网 发布:多功能网络性能测试仪 编辑:程序博客网 时间:2024/05/12 13:44

                第十二章      APO编程语言


     这章是基本的、重要的;我不得不反复、仔细、认真的修改,即使啰嗦、也力求能完整说明。系统安全是随时要考虑到的,除了用户指令须是安全的、用户进程只能走在自己声明的变量空间上外;对文件操作也是要设置一系列权限。对文件的操作权限,我们是参照UNIX、LIUNX;在实现上,做了一些简并和优化。APO操作系统:包括内核方法库,用户的API方法库,公共通用方法库,日志、文件、数据库、网络等服务进程,实时进程支持,动态优先级的进程调度,一个进程最多64K个线程的线程优先级调度,信号、消息处理,GPU的4D图形处理、显示,音频处理等等;APO的指令代码量不会到4KW,大约就是4千条指令吧。LINUX有的功能、APO都有,但在速度上要快得多,一些项目甚至要比LINUX快近百万倍。


一、变量空间说明


     为了系统安全,用户是没法知道其变量空间在本地内存中的实际地址的;也不能使用指针来指向内存的实际地址。就类似,我们不知道地球挂在相对宇宙中心的什么位置一样?宇宙中心在那?我们是无法知道的。但在自己声明的变量空间上,可以使用寄存器作为相对偏移指针;如D.A1.A2..Ai.X.R1L.W,大对象空间D中的成员变量A1子空间中的成员变量A2子空间中的.。。。。成员变量Ai子空间中的成员变量X子空间中的以寄存器R1L为指针的字变量位置。类似,银河系中的地球的中国的广西省的以寄存器R1L为县名指针的具体县位置。变量空间是靠我们声明的,你说多大就是多大了;是否能成功,那要看系统的运行情况。如果你声明300GB的变量空间,那么系统本地内存最多只是128GB的空间,显然、你会得到一个错误返回。对象的空间基本单位是行E,理论上对象空间最大可以声明到4GE = 32GW = 128GB。而对象空间下、或许有一大堆有层次、树状结构的成员变量,它们的大小要用32位表示、相对根对象的偏移也要用到32位来表示。对象空间下的某个成员变量可能是用位、或字符、或字、或双字来描述的,那么表示空间属性的行内编码,位需要8位、字符需要4位、字需要3位。所以,描述一个变量空间需要32位的大小、32位的偏移、10位的根对象号(间接表示在本地内存中的位置)、可能8位的行编码、可能16位的立即数赋值或1到2个的6位寄存器描述等等。所以,操作一个变量的指令码需要3W大小、耗时3ns。

 

1、空间分割

 

     变量空间分割成多个成员变量子空间,这是常见的;也可以说是合成吧,我们通常在一个表、或C语言结构、或数组、或向量、或集合、或类的属性表、或类的方法表中声明多个变量;在成员变量中又可以再分割下去,那要看需要。无论怎样,你只要给变量名字、声明其空间大小;在某个根对象中适合的位置声明;编译器总是能知道你的变量在根对象空间中的相对位置和大小;但编译器也不知道根对象在本地内存空间中的具体位置。一个应用程序最多可以声明992个根对象空间;所有的变量空间位置都是相对于某个根对象空间的。类方法表lf_tab根对象有32个,0是本类方法表、1-31是程序引用的公共方法类库DLL、或自己的方法类库DLL;API公共方法库是公开的、固定的、是无须安装就可使用的。线程类方法表thread_lf_tab有128个、为线程组run()入口和长度。我们声明的静态根对象属性表dx_tab(Di)有64个;而在代码中声明的、但动态分配内存空间的动态根对象可以有768个。

 

2、静态变量空间

 

     在进程开始运行时,申请内存分配的、由编译器指定的静态根对象空间;只有在进程完全退出时,才会释放掉。如果、你的变量是挂在某个静态根对象Di空间下的成员变量,那就是静态变量。要注意到、声明的静态变量空间在程序运行期间一直占据部分内存,直到程序退出时,才由系统释放。

 

3、动态变量空间

 

     在程序的方法中、声明的、和动态分配内存new()的变量是动态变量,属于动态根对象。动态根对象同样可以声明成复杂的、有层次树状结构的、有一大堆成员变量的结构空间。对于动态变量、当它们的引用计数为0时,会自动被系统回收、释放掉它们的空间。

 

4、变量的操作

    在代码中都是用变量名来操作变量的空间,一个变量空间中的部分内容拷贝到另一个变量空间中去,是由系统API中的COPY方法来完成的。变量的操作主要有:对变量空间中某个位置的内容进行寄存器、或16位立即数、或其中的一位的赋值;或某个位置的内容拷贝到某个寄存器中;或对变量空间中某个位置的位进行测试转移。某个位置有时,也可以用寄存器做指针。

 

5、变量的编译说明

 

       在用户程序中,不管是动态、静态声明的对象,或字符串常量;它们在编译器的符号表中都有对应的32位大小、32位相对根对象的偏移地址、所属的10位根对象号、空间性质:行内寻址(位8位、字符4位、字3位、行0位)项目。如下:

BU3W  STE{ // symbol table entry 符号表项

   BU32 GDX; // 所属根对象变量
// GDX.31 static; 1、是静态编译的对象,0、是动态对象。

// GDX.30 gdxm;  1、本变量就是根对象,0、是根对象的成员变量。

// GDX.29-28 ladd; 行内寻址、空间性质:0、行,1、字,2、字符,3、位。

// GDX.9-0 gdxd;  根对象号。

// 行寻址模式时:
// GDX.27-10  0; 这些位为0。

// 位寻址模式时:
// GDX.27-26  control; 指令模式:0、位赋值0,1、位赋值1,
// 2、位为0测试跳,3位为1测试跳。
// GDX.25-18 0; 这些位为0。
// GDX.17-10 bitadd; 位地址。

// 字、字符的立即数寻址模式时:
// GDX.27-14  0; 这些位为0。
// GDX.13-10 Zadd; 字符、4位、字、3位的行内寻址。

// 字、字符的寄存器寻址模式时:
// GDX.27-26  control; 指令模式:0、变量到寄存器,1、寄存器到变量,
// 2、变量的寄存器寻址后到寄存器,3、寄存器到变量的寄存器寻址后。
// GDX.25-14 RDiRNi; 表示是什么寄存器,R0H、R0L、R0、R1H--R31。
// GDX.13-10 Zadd; 字符4位、字3位的行内寻址。

  BU32  ROFF;// 32位相对根对象的偏移地址。

  BU32  LENGTH;// 32位变量的大小。

}

 

       这些变量值都是编译器知道的,我们可以用编译器方法获得变量的长度、偏移地址、对象号等。如:R1 = getdx(变量名字); 就得到了根对象号,R1 = getlen(变量名字);就得到了变量的长度。要注意到,如果你声明的变量是 BU32那就是32、如果是BU1W、或BU1Z,或BU1E、那都是返回1。字符串用BUnZ声明,返回的就是字符串长度n了。也可以获得相对根对象的偏移地址,用getoff(变量名); 实际上、编译器是根据你的方法从符号表中、赋值相应的32位数到你声明获得结果的寄存器;不外是编译成一条32位立即数对寄存器的赋值指令吧,2W、2ns。在方法中,你要变量的大小、或长度、或对象号做传入参数,那要写成getlen(变量名字)、或getoff(变量名)、或getdx(变量名字)。如果参数直接写变量名,那编译器将是编译成一条3W、3ns的内部专用指令pointer;硬件总线控制器内部的专用寄存器PITR将是对象的实际地址、和LENG是大小。对于应用程序来说、除了会多耗3W的指令空间和3ns的时间、没什么用处;用户代码不在系统代码区域,读PITR、LENG会报错、被灭。


6、操作根对象

 

       根对象空间可看作是一个位、或字符、或字、或行的线性空间,没有32位的偏移、也不需要32位的大小(大小值已经在对象号对应的项里)。只是10位的根对象号,空间单位性质说明2位(位、字符、字、行),行内寻址(位8、字符4、字3、行0)。对象名就对应这根对象号,但有时候是不一定有名字的、只有根对象号,比如一些它方进程提交的动态对象;这时、编译器无能为力了。如果,一个网络进程完成了一个完整数据包的接收组装、将该数据包容器通过系统提交给相对应的用户进程;其过程是:1、使用用户进程的pid号、向系统申请一个用户进程的动态根对象号、并使其指针指向完整数据包、其大小值为完整数据包的大小。2、向用户进程发接收数据包消息,消息中含提交的用户进程的动态根对象号。3、用户进程使用该动态根对象号操作数据包,完成后、回消息给网络进程。4、网络进程向系统释放该动态对象号、和数据包对象。不用名字操作变量空间的情形就类似,R1.A1.A0..Ai.X.12.Z= 22;的情形;这时,编译器将把R1的低10位当作是根对象号、.A1.A0..Ai.X.12计算其32位的行偏移、.X计算其空间大小、.12.Z计算其空间性质放在R1寄存器的其它位、编译成一条3W、耗时3ns的寄存器对象号间接指令。

 

       还有一种方法是利用文件号,用户程序是使用open一个文件号给网络进程、就当网络进程是一个磁盘空间文件系统;但关联文件号的流容器并非用户进程提供,而是网络进程做关联。当收到文件号已经关联到一个完整数据包流容器的消息时,用户进程只是使用文件号fd、就可操作该完整数据包了。当操作完成后、用户进程就用pid、fd发相关消息回给网络进程;网络进程就可重新使用该流容器用于其它方面。当用文件号操作其关联流容器时,格式一定是fd.XX.XX..XX.N.Z/W/E;不能Ri.XX...,也不能:变量名字.XX...;那样、编译器将无所适从。fd是保留字、使用fd.编译器才能正确编译成是用文件号操作关联流容器;否则可能会认为是操作根对象。fd.模式的指令是4W、4ns;对其流容器成员的操作可以赋值32位立即数。fd是一个用户可以使用的专用寄存器,其实是有2个:fd、fd1。用户的线程让步后,需要考虑保存你的文件号;因为fd、fd1是公用的。fd、fd1实际上就是寄存器R24H、R24L,注意啊,不要随便操作R24;那也是系统使用的寄存器。


二、指令、方法使用简介


       APO编程语言是基于汇编语言和面向对象编程。基本指令只有6种:赋值指令、BTX(位X测试为1、或0转移)指令、查表跳转指令switch(RN){….}、寄存器的移位与循环指令S、寄存器的三操作数运算指令、调用与返回指令。所有的指令大小、除了32位立即数赋值是2字、和含有对象、变量的指令是3字外;其它都是32位,一个字。 指令执行时间,除了2W指令是2ns、3W的是3ns外;其它指令都是1ns。 应用程序只能使用R0-R4,R8-R23的21个寄存器作为高速的寄存器局部变量;其中R0-R4作为方法参数寄存器,R0通常为返回结果寄存器。


       通常,子程序的形式用C语言表达就是函数;用所谓面向对象语言表达的是函数或方法。APO使用的是汇编语言,通常用伪指令(一组指令的缩写形式宏)来描述调用指令CALL;如:方法名称(参数1,参数2,参数3,参数4,参数5); 由编译器翻译成相关的最多6条汇编指令。其实,参数i是需要对应写入寄存器Ri(Ri = R0----R4);这就需要若干指令;以上的缩写编译器会自动展开成对应的一组指令的。参数可以有一个或多个,或者没有;没有参数的形式:方法名称();编译后就是一条指令CALL。方法是先压入PUSH、再Ri = 参数i; 的;这样,返回时就能恢复调用前的R1-R4。 返回的结果:数值可以写到寄存器R0;也可以写到寄存器R8-R31或静态变量中去。 CALL是压入H0的R0—R4及PSR、PRR、PPC;而RET是弹出R1—R4及PSR、PRR、PPC。R0是不被覆盖的! 所以,R1-R4可看作是自动变量。指针或数据入口参数也可以先写入R8-R31;之后,再调用方法。注意:寄存器R24-R31是系统方法使用的局部变量;用户应用程序使用时;应注意到调用系统方法时,它们可能会被改写。

方法(子程序)的编写形式如下:


方法名称:

    指令1;

……

    指令n;

    RET;

或:方法名称{…}


     一些方法的集合就构成了方法库,方法库是放在只读的保护区域;由内核管理。一些公共的方法库是常驻在保护区域的。用户进程的方法库是动态存在于保护区域,当用户进程退出时,如果用户方法库的引用计数为0;就会使 用户方法库被踢出保护区域。方法库就是一个文件形式。所谓面向对象语言表达的接口概念其实就是指对象的一个方法库。


      CALL指令用来调用一个方法; 此时,下一条指令地址PPC会被压入堆栈,是压入行寄存器H0:R0—R4及PSR、PRR、PPC;以备返回时能恢复执行下条令, RET指令用来从一个方法返回;之前CALL保存的下条指令地址会从栈内弹出到H0行寄存器中,RET是弹出H0的R1—R4及PSR、PRR、PPC。程序转到CALL之前的下条指令处执行。


调用方法格式:

方法名称(参数1,参数2,参数3,参数4,参数5);

与C函数的区别:

1)、无须指明函数返回值类型;返回的值取决于方法或编程员的说明。

2)、没有复杂的形参、实参、指针、Void、数组引用等说法;全看作是32位寄存器参数。你认为是什么样的参数,那是由你决定的。

3)、无须头文件、无须函数原型声明。

4)、除了寄存器变量,就只有你声明的静态变量。


      立即数可以8位或16位一组用“.”号隔开。已经声明的变量,可直接代入寄存器相关位置;实际上,是变量的地址代入寄存器相关位置。如:方法1(3, 5.5, ,6.0.1.5 ,AAA); 入口参数4个,写入寄存器R0、R1、R3、R4; R2不管;其中R4为变量AAA的地址。

 

      R0-R4是方法内的自动变量,方法返回时被调用前的覆盖。通常是,在方法中,R0-R4只是作为只读的入口参数;而输出结果可用寄存器:R8—R31。在APO中,栈的用途除了CALL,RET就没有什么用了。APO只有寄存器型自动变量、静态变量(代码中申请内存分配的动态变量,本质上还是静态变量)。可以直接:如,R17 = 89; 用户自定义;不一定非要编译器分配寄存器变量空间。毕竟,寄存器空间有限;自己分配更为清晰些。使用寄存器变量的好处是无须写变量声明等;如果需要更大的空间来使用,你只能申请内存分配的动态变量了;BUxE  new(YYY); 就可得到x行的内存空间了,对该空间的初始化方法得自己编写了。YYY的释放你无须考虑,YYY一旦不用,系统会立即回收。最好,还是一开始就分配成静态变量并初始化了,省事。


三、安全性


1、变量与空间


      声明一个变量,实际上就是分配一个一定大小的存储空间;编译器会把变量名字翻译成该存储空间的相对逻辑地址。寄存器变量的地址是固定的实际地址,大小是32位的位容器;所以,无须声明;直接使用就行了。存储空间是有单位的,如BU1 [32]  A; 声明变量A的空间是32个的1位数组,与BU32  A; 声明变量A的空间是32位的位容器,与BU1W  A; 声明变量A的空间是32位字的位容器,与BU2Z  A; 声明变量A的空间是2个16位的字符位容器;它们的意义是一样的。变量空间的寻址,是应该用修饰符.Z、.W、.E说明;比如BU256  A;  那么A.0= 1,是变量A的第0个位的位地址内容?还是变量A的第0个字符地址内容?还是变量A的第0个字地址内容?还是变量A的第0个行地址内容?编译器通常认为是第0位。如果说A.0.Z,那就清楚了,是变量A的第0个字符内容赋值为1; A.3.W =1, 就是变量A的空间以字为单位,是第3个字地址内容赋值为1。寄存器变量是32位的位容器;R3.0.Z简写为R3H高半字;R3.1.Z简写为R3L低半字。


      用户程序只是能操作自己声明的变量空间、和寄存器变量R0-R4、R8-R31;所以,没有指针变量。指针操作,如(RX)、(变量)、在用户应用程序中是不允许的;编译器会报错;指针操作会带来风险。寄存器都是32位的,如R2.230、R3.3.W、(R16)等等、编译器都是报错。但也可以分为高低2个半字操作,如R1H、R1L,和行寄存器方式H1: R8-R15, H2: R16-R23, H3:R24-R31。要注意到H3行寄存器,在调用系统方法时,会被改写;所以,尽可能不使用R24-R31。变量是有空间大小的,赋值操作不能超出其范围;否则编译器报错。


2、变量寻址


     CPU到本地内存间是256位数据总线、32位的行地址总线(高16位为块号、低16位是块中的行号);所以,CPU到本地内存的读写是以行E为单位的。那么、变量的位寻址空间、字符寻址空间、字寻址空间、行寻址空间是如何实现呢?这时专门的总线硬件实现的,这时一种、读-修改-回写、的方法。先把变量所属的行内容全部读入,再据指令码的寻址模式描述,来决定是修改或读取那一个位、或那一个字符、或哪一个字、或就是该行;如果是修改、那就要回写;如果只是把变量内容赋值到寄存器,那就不用回写。


      那是如何实现A.B.C.5.W.H = 3;等指令? 我们知道任何一个变量都是或以对象、或以对象的成员的形式出现的。进程的i节点、对象头列表等等、都是属于系统管理的变量,用户是不能对系统变量进行直接操作的。A.B.C.5.W.H= 3中,编译器是把整条语句翻译成96位的3W指令,其中A翻译成10位对象号,B、C、5.W.H意思是B是A的字成员变量,C是B的字成员变量,5.W.H是C的第5个字成员变量中的高半字;编译器只是把.B.C.5.W.H翻译成相对于A的32位相对偏移行地址,和4位行内的字符地址,3是16位的立即数,指令码2位。比如对象A的大小是16E,B的大小是4E、偏移A是1E,C的大小是2E,偏移B是1E;那么、编译器就能算出C对于A的偏移是:+ 1 + 1  = 2E。32位的相对偏移地址就是行号,行内寻址编码对于字寻址就是3位行内字地址,对于字符寻址就是4位行内字符地址、对于位寻址就是8位行内位地址。.5.W.H 编译器就知道是行内字符寻址、行内寻址编码是2*5 = 10,即第2行的第9号字符。还有变量的32位大小、用于判断是否越界。对于变量 X = D.A0.A1...Ai.B.3.Z,那么D是对象号,A0-Ai是必须是以行为起始的对象、或表、或说变量,否则编译器可能计算会有错误。理论上,你可以声明4GE的大对象;在磁盘空间可能可以实现,但在只有4GE的本地内存空间是不可能的。


      对于每个进程,系统都分配有数据块,用来装进程的属性表等系统管理的全局变量。而当前进程的32位的对象头列表的基址(块地址、行地址)就放在专用寄存器A0;这个寄存器只能是进程成为当前进程时,由系统私有方法读写;任何非系统方法操作都异常中断。A0是进程中所有变量(对象)的基址,当执行指令A.B.C.5.W.H = 3;时,(A0 + A对象号)就得到了对象实际的块、行基址,和对象大小(用于硬件判断是否越界);以实际基址 + 32位相对行偏移得到变量的实际行地址,并读入该行内容到总线硬件内的行缓冲寄存器,之后把数值3赋值到相应的行缓冲寄存器中的相应字符寄存器;最后,回写行缓冲寄存器的内容到对象的相应行内存。总耗时、12个机器周期、3ns。使用(RN).n = 3;的寄存器指针模式,也能达到相同效果;耗时只是1ns;但该方式是属于超级指令,只能系统方法使用;应用程序只能使用变量模式,行走在自己的变量空间中。如果要对 对象.变量赋值32位立即数,那要先 RD = 立即数; 再: 对象.变量.W = RD; 要赋值对象.变量的一行;那么,先赋值H2,再对象.变量.E = H2;


     对象号相对应的安装地址内容都是块号、行号;动态变量、对象、表、流容器等的首地址也是块号、行号;当操作到对象下的成员变量时,对象空间的首地址是基址(块号、行号)、而成员变量可能是以对象空间为位、或字符、或字来定义的;这时、成员变量的相对偏移地址是32位的:对应位空间最多4G位,对应字符空间最多4G字符,对应字空间最多4G字。所以,要注意到、到达最终成员变量前的都应是行对象。如D.A0.A1…Ai.X.2.W,编译器编译D是对象号、A0-Ai是以行单位的成员变量,最后的X是以字空间为单位的、范围最多4GW;如D.A0.A1…Ai.X.5G.W,那编译器报错,X以字为空间、最多只有4G字,5G字则超出范围。编译器实际上是先计算出A0.A1…Ai.X的相对0的偏移行地址(16位的块号、16位的行号);再计算.2.W、编译。对于应用程序是没有指针的、变量必须完整赋值、或被赋值;声明了变量,我们在程序内就直接用变量名就行了。如何计算、那就是编译器和系统的事情了。我们要做的就是声明一个个对象空间,并为之规划、起成员变量、对象名字;之后、编程时、用它们的名字通过指令、方法操作空间。


3、安全性问题


      我的电脑满是病毒,很难清除;360等都没用。为修改这章,已经蓝屏好几次了。真的晕了,不就有时上下那种网站,就给毒得不清不楚。好了,我们得考虑下,病毒攻破系统的可能性。


      应用程序中的方法只能操控本程序中的变量,不能操控其它程序的变量;更不要说操控系统变量了。如何实现呢? R0-R4,R8-R31的寄存器间相互赋值是没问题的,但对R5(PSR)、R6(PRR)、R7(PPC)的赋值,问题就来了;我们不可能单纯依靠编译器的。其实,CPU内是有硬件监控的,如果不是系统方法区(本地内存第0、1号数据块)的指令来对R5-R7赋值、或含有(RN)的指令,会产生一个攻击系统异常中断;在这个中断程序里,会发信号、终结相应的非法进程,并报错;无须蓝屏。

 

      CPU中有“方法调用”硬件监视器,CALL时、新的PPC就是目的地,而(PSP).7.W中的PPC就是源地的下一条指令,即返回点。简单硬件监视源地、目的地;目的地是0号数据块,那么需要源地不大于1号数据块。如果目的地是1号数据块API,那么许可。0、1号的方法是系统方法不可能去调用用户方法的,用户方法只能最多是调用大于、等于1号数据块API库中的方法;不能调用内核方法;违者异常侍候。API中的方法可以调用0号数据块中的内核方法。通过API库隔离了内核,你可以调用API库、其它方法库;但不能调用到内核的方法。只是,能间接的通过API使用到一部分内核功能。即使是想调用它方的私有库,那也要通得过一系列权限检查才行。


1)、病毒只能是应用程序形式,要冒充系统方法是行不通的;除非你自己把它编译到系统方法区中。那么,用系统级指令混入的应用程序病毒,也是不行;没运行在系统方法区的超级指令都是非法的,被灭。


2)、破坏其它进程,但应用程序只能行走在自己的对象、变量空间中;进程间只能通过信号、消息来互通;通过动态变量的提交来做大数据量的交换。进程的i节点、对象列表等保护性属性区、进程的方法区都是由系统来管理;当然,你可以调用公开的系统方法、和应用程序的方法;但这些方法同样是行走在应用程序自己的对象、变量空间中。当然,i节点中的一些属性、如文件名字等,还是可以调用系统方法来读写的;只要权限够。


3)、破坏文件数据,那你要有相应的权限才行;每次放出open一个文件的系统消息,系统都会检查你的权限,通得过才行。你要发出一个消息都得调用系统方法才行,没法去搞乱消息缓冲区。有你的消息,系统自会通知到你的相关线程、或过程。


4)、应用程序中的对象名、变量名、方法名、类名等,一旦编译后就不能修改;杜绝冒充。要修改只能重新编译。编译器的符号表、i节点、进程的系统管理的不变的属性区、全部一起生成CRC校验码,所有名字的哈希值表等等,形成一个版本号小型i节点比对文件。当应用程序安装时,系统会作版本号对比( CRC值对比,名字哈希值对比,公司签名、方法数、对象数、大小等等对比),符合才会通过注册。


5)、最后一招,引狼入室。通过网站引诱用户安装病毒程序;病毒运行后。


1、想法感染其它进程,这在APO中几乎不可能。进程的方法区、基本属性区是固定的;APO实现应用程序功能的代码量很少;即使要用到方法库.DDL文件。你可能说会伪装一个.DDL文件代替原来的,但别人的程序只会公开一部分公开方法啊;要通过系统验证也太难了。


2、自动复制更多的病毒.exe文件到其它目录,但在APO中,受到文件权限的限制太大了,对于自动复制程序文件的进程,日志防护文件会有详细的追根到底的记录。Windows对于文件的保护很不够,就在它的目录下;我的电脑有一大堆病毒文件。删除了,又换个文件乱名像鬼一样出现。


3、夺取最高权限,这难度是高;但病毒效果好。甚至可以利用最高权限,编译功能更大的病毒程序;抹掉痕迹,留下通过消息操作的后门。但APO中的日志防护文件是隐形的、在FLASH空间、最高权限也不能抹除;反汇编追踪也不行。最高权限的用户进程可读、不能写;除了私有的系统方法外,一写就关机。主人手动开机登录时,可以用另一个系统安装时的密码,来抢回最高权限。那么,被抢夺的那段时间里的一切,都会原形毕露。主人可以用还原命令,回溯到被抢前的状态。


四、指令简介:


、赋值指令:


1
、寄存器、或CPU片内变量赋值指令,除32位立即数外大小1W、耗时1ns。    

1)、位赋值:如R3.11 = 0; YJMK.3.E.5.W.3 = 0;

2)、16位赋值:如R4L = 11; YJMK.3.E.7.Z= 33; 等等。

寄存器的16位赋值要指明高H、低L半字。

3)、32位赋值:如R31 = 11; YJMK.3.E.9.W.W = 333;等大小2W、耗时2ns。

4)、行赋值:H2 = 0; 等; 行只有全0,或全1赋值,只限于行寄存器。

5)、运算赋值:只能是寄存器变量,如:R3 = ALU, #5; 实际是

R3 = R3 ALU, #5; 只能是16位的立即数。

ALU有:

     ADD 加法 + 、ADC 带进位加法、SUB 减法-、SBC带进位减法、CMP 比较、

          DADD 十进制调整、 BIC(!N and RD) 立即数求反后再“与” & !N、

          BIS(N or RD) “或|”、 AND “与&”、

          BIT(N and RD  “与”但不保存)、 XOR “异或^”、

          ORN(!N or RD) 立即数求反后再“或”、 RSB(N – RD)反减、

          MLU 乘*、DIV 除/、 SSAT饱和运算

另一些写法:RD = ALUN;  RD+、RD-; 等等,也可以。

6)、位段赋值:将一个寄存器的x开始的x1位赋值到另一个寄存器的x2开始处;可以设置为先对另一个寄存器清0、或全1后进行。如:R1H.3-0 =R5.30-27, 0; 结果是R1H.3-0就等于R5的30-27位,R1H.7-4的位都是0。


2
、变量赋值指令,大小3W、耗时3ns。

1)、位赋值:A.B.C.9.W.3 = 1; A.R1H.W.6 = 0; R1H是空间偏移指针等。

2)、16位赋值:A.B.C.5.W.H = 3; M.D.R3.1.Z = 16;

3)、变量、寄存器相互赋值:

R9 = A.B.R2.9.W;  R11H = A.B.3.Z;  C.9.W = R13; B.A.10.E = H3; 等。

应注意到位容器的大小;否则会有截断。通常以寄存器变量大小为准。


3)、文件号fd的流容器变量赋值指令,大小4W、耗时4ns。

1)、位赋值:fd.B.C.9.W.3 =1; fd.R1H.W.6 = 0; R1H是空间偏移指针等。

2)、16位赋值:fd1.B.C.5.W.H = 3;fd.D.R3.1.Z = 16;

3)、32位赋值:如 fd.3.E.1.W= 333;等

4)、变量、寄存器相互赋值:
R9 = fd.B.R2.9.W; R11H = fd.B.3.Z; fd.9.W = R13; fd.A.10.E = H3;等。
应注意到位容器的大小;否则会有截断。通常以寄存器变量大小为准。

 

BTX(位测试为X转移)指令

 

如:BT0  R3.7, #N; 如果R3的第7位为0跳到N执行。

    BT1 D.A1.A0.A3.5.W.31, #N; 对象D的最终成员变量A3的第5字

的31位为1、跳到标号N执行。

测试状态寄存器PSR的位后的通用指令,左右2种写法都可以。

JEQ/JZ  标号; 零位被置时转移到标号语句  等于时程序跳转       

BT1 PSR.Z,标号;

JNE/JNZ 标号; 零位复位时转移到标号语句  不等时程序跳转        

BT0 PSR.Z,标号;

JC/JHS 标号; 进位位被置时转移到标号语句 大于或等于时程序跳转 

BT1 PSR.C,标号;

JNC/JLO 标号; 进位位复位时转移到标号语句    小于时程序跳转    

BT0 PSR.C,标号;

JN   标号; 负位被置时转移到标号语句       为负时程序跳转     

BT1 PSR.N,标号;

JNN  标号; 负位被清零时转移到标号语句  大于或等于时程序跳转  

BT0 PSR.N,标号;

JV  标号;  溢出位被置时转移到标号语句                         

BT1 PSR.V,标号;

JNV 标号; 没有溢出时转移到标号语句                            

BT0  PSR.V,标号;

JCZ 标号;  C置位,Z清零时转移到标号语句  大于时程序跳转      

 BT1 PSR.CZ,标号;

JZC 标号;  C清零, Z置位时转移到标号语句  小于或等于时程序跳转 

BT0 PSR.CZ,标号;

JL  标号; N .xor. V =1 时转移到标号      带符号小于时程序跳转 

BT1 PSR.NV,标号;

JGE 标号; N .xor. V =0 时转移到标号 带符号大于或等于时程序跳转

BT0 PSR.NV,标号;

JGT 标号; Z清零且N或V置位, 或N清零、V清零  带符号大于时跳转

BT1 PSR.GT,标号;

JLE 标号; Z,或N置位且V零, 或N零且V 1 带符号小于或等时跳转   

BT0 PSR.GT,标号;

JMP 标号; 无条件转移到标号语句                                  

BT1 PSR.B1,标号;


可以结合比较指令、来进行跳转时,可使用类C格式。比如:

if  A >= 3 goto N; 等等。2条指令合一,编译时会展开的。2W,2ns。

 

程序状态寄存器PSR = R5:

31 29 28 27 26 25 24 23 22 21 20  19 18 17  16  13  12   8  7   0

N  Z   C  V  CZ ZC LT GE GTLE B1  YJ IN OUT  保留  执行标志 中断号

N:  负号置1标志。      为负数

NN:  N = 0。             非负数,大于或等于

Z:   结果为0置1标志。  相等(EQ)

NZ:  结果为非0,Z = 0。  结果非0或不等NE

C:   进位置1标志。      大于或等于(或HS)

NC:  无进位,C = 0。     小于LO

V:   溢出位置1标志。

NV:  没有溢出, V = 0。

CZ:  C置1、结果非0(Z = 0)。   大于HI

ZC:  C = 0, 或Z = 1。           小于或等于LS 

LT:  N XOR V = 1;N != V。        带符号小于LT

GE:  Z XOR V = 0;N = V。         带符号大于或等于GE

GT:  Z = 0,N或V置1;或N = 0、V = 0。       带负号大于GT

LE:  Z置位,或N置位且V = 0;或N = 0, V = 1。带符号小于或等于LE

B1:  为1。                       总是1, AL


、查表跳转指令:switch(RN){.};

 

     有时候,一个方法有n个分支,n值在寄存器RN;这样使用查表跳转指令,就会据RN寄存器中的值而进入到正确的分支了。RN为: R0-R4,R8-R31中的一个。

RN是一个16位或32位寄存器整形变量,可以从表达式得到。这类似于C语言switch()、Case的情形。


、移位与循环指令S:只能是寄存器变量


 S  RD,RN, #N; S为下列之一,N为移动的最多32的位数。

逻辑左移<<(变进位)LSL(C); 逻辑右移>>(变进位)LSR(C);

算术右移S>>(变进位)ASR(C); 循环右移(变进位)ROR(C);

连进位一起循环右移RRX;

如:LSLC  R1, R3, #6; R1 = R3 C<< 6,即R1 = R3左移6位,高位进C。

移位与循环指令也可以写成:RD = RN S N;

 

、三操作数运算指令:只能是寄存器变量


RD = RN1 ALU RN2 S #N; // RN2移位或循环N位后与RN1运算的结果存放到RD。


、调用与返回指令:


  调用CALL则是直接写方法名,方法返回是RET。

     如果要做浮点运算、或更多的其它功能就要用到系统库里的方法了;APO语言就7种基本指令。硬件模块的专用指令在用到再说,内核编程则还包含一些指针操作的指令。

 

五、面向对象编程


    面向对象编程在上一章已经介绍很多,这里只是举一些例子。

例子1:

    BU1W [100]  A;  // 数组变量A的成员都赋值为其下标。

数组成员赋值方法(){ // 指令数7W,运行时间:502ns。

    R1 = 0;

 L1:

    A.R1 = R1;// 如果已经声明A是字数组,A.R1.W.W可简写为A.R1, 2W,2ns

    R1+;

    if R1 != #100 goto L1;

    RET

}

 

    汇编语言清晰、简单。可能有人会说,这么简单就要6行代码;其实不然,一些高级语言编译成汇编码,会远超过这里的。汇编语言,指令的空间、时间都可明确掌握、底层清晰;根本不是别的语言可以取代的!其实,简单的东西需要复杂的实现;复杂的东西只是简单的代码。这就是我们的真实世界规律!

例子2:排序


1、不带序号的数的位图式排序


     先介绍多功能硬件模块的比较指令CMP与位查询指令BIX。硬件模块的256E,可以分成2部分:输入128E,输出128E;如果输入128E中符合条件的结果全部给出在输出,那么标志PSR.IN置位,请求再输入128E;如果输出128E中的结果满,那么标志PSR.OUT置位,请求读出结果。这样,就无须多次启动SS1。对于范围在64K以内的无序数列,我们可以构建一个64K位的位图变量;让数列值映射到位图变量中的相应位;再利用多功能硬件模块的位查询指令BIX,很容易就能得到按小到大的有序数列;而且速度极快。对于超过64K、大小为x的无序数列,我们都可以构建有n个64K位的位图变量;让数列映射到位图变量中的相应位;之后,使用同样的方法进行。


   BU16 [X] M;// 声明一个16位的无序数组变量M、和装排序后结果。

   BU64K WTKJ = 0;// 声明一个位图变量,内存空间256H;编译器初始化为0

范围64K内无序数列排序(){// 占用:32W;耗时最大约:605us

   R2 = getlen(M);// 编译器方法,32位立即数赋值指令、2W、2ns。

// 获得变量M的大小到R2寄存器。类似的、R3L = getdx(M);是获得对象号等

// R1 = getoffadd(M);是获得M的相对偏移地址

   R16L = 0;  R1 = 0;

PTXU1:

   R16H = M.R16L.Z;// 读入无序数列M[X]的一项, 3w, 3ns

   WTKJ.R16H = 1;//映射到位图变量WTKJ空间中的一位,3w, 3ns

   R16L+;   // R2-,非零继续循环。耗时:9*R2 ns

   if R2- != 0 goto PTXU1;  // 2W、2ns 

   COPY.E( YJMK, WTKJ.0.E, 128 ); // API库中的方法。

// 拷贝位图变量WTKJ到硬件模块,1W、70ns

   BIX;   SS1;            // 位查询、排序。2W、11ns

   BT1  PSR.OUT, PTXU2;  // 输出满,跳。1W、1ns

PT1:

   COPY.E( YJMK, WTKJ.128E, 128 );

// 拷贝位图变量WTKJ的剩下128H到硬件模块,1W、70ns

   SS1; // 1w、1ns

   BT1  PSR.OUT, PTXU3;  // 输出满,跳。1w、1ns

PT2:

   COPY.E( M.R1.E, YJMK.128.E, 128 ); // 输出结果。

PT3:

   RET

PTXU2:

   COPY.E( M.0.E, YJMK.128.E, 128 ); // 输出结果。1w、70ns

   PT4();

   JMP PT1;

 

PTXU3:

   COPY.E( M.R1.E, YJMK.128.E, 128 ); // 输出结果。

   PT4();

   JMP  PT2;        // 跳,输出最后一次结果

PT4:

   R1 = +128; //  相对指针位移128,1W、1ns

   SS1; BT0  PSR.OUT, PT3; // 输出不满,跳返回。2W、11ns

   COPY.E( ,YJMK.128.E, 128 );  // 否,继续输出结果。1W、70ns

   JMP  PT4;  //循环、继续SS1、直到搜完整个位图。1W、1ns

}

       考虑M[64K]的无序不重复数组所花时间:位映射,600us;位排序,5000ns。所以,耗时还是在位映射600us,位排序只是5us。位图排序的方法,对于有重复数的情形下;只是保留其中一个数;所以,经位图排序后是数组的成员都是唯一的。位图排序需要使用到公共资源-硬件模块,要不调用系统方法key_YJMK();清YJMK、并锁住进程调度,要不位图排序升级为系统方法。在APO系统中有位图排序方法;但最大数只能到24位,这时要使用256个64K位的位图变量;占一个数据块。32位数的位图排序,应用程序只能先做256种数组分类,再调用系统位图排序方法WTPX( 无序数组变量,最大数值, 数组成员数 )。排序后,结果还是放在输入无序数组变量;至于位图的大小、建立那是方法据最大数值的事情。


2、带序号的数排序


      这不能再用位图式排序方法了;类似数据库表中,据某字段的数值大、或小顺序排序出相应的记录组来。这只能是基数排序方法;基数是半字,那么32位的无序数组排序就要来2次;基数是字节,就要来4次,但易于硬件实现;我还在研究中。

 

前面章节中,都说行的符号是H;写到本章时,发现行H与高半字的H冲突;所以,行的符号改为E。没法的,写新的一章;可能前面的章节都要改动。晕倒!


六、补充说明


1、在一个进程中,所有的用户类的方法、线程方法、进程中的方法都集中在同一个方法表里。一个进程的方法表只有512E的代码空间,我编写整个操作系统都用不完;你难道还不够用?你可能说会要用到64K个线程,但所有的线程对象只能是最多128个线程类中的一种;而这128个线程类的方法都在同一个方法表里。你编写的应用程序;编译时,是把程序里的所有用户定义的方法名字编译成:高16位类号1(this)、低16位为方法与方法表基址的相对偏移地址。类号0、是系统API类;你调用系统类的方法前面不要加类名字。其它、调用任何类的方法,你必须在方法名字前面加上类名。在一个类内、调用本类的方法,可以加上this.方法名(); 退一万步来说、你的应用程序牛b;要用到大量的方法,那只好使用动态方法库DLL了。方法库的类号范围是2-31,也就是说、除了本(this)1号类,你还可以调用到31个类库中的方法。一个类库的最大代码空间可到64KE,这样恐怖的代码量、可以飞天遁地了。64KE的代码量、按照每人一年16KE,需要4人编写一年。系统打开一个应用程序使之成为进程,是要做不少的准备工作的。先是对你的程序需要调用到的类库、如果系统没有副本、那要进行安装;之后、如果系统没有副本、对this类库安装;最后、是对各个对象进行内存空间分配和安装、i节点安装、分配进程号、初始化进程:如置第一次运行标志、优先级计数设置、可运行位图设置等等。


2、代码空间是32位的字空间、最大只是8K个数据块 = 512ME = 4GW = 16GB;所以、代码要运行只能处在本地内存空间中的前面8K个数据块。最前面的128个数据块是内核、动态方法库区域。跟着的64个数据块是8K个进程的方法表、每个进程512E。再跟着的64个数据块是系统服务进程区域。从第256块起为用户进程的系统变量、用户变量等等。


3、用户程序使用的都是虚地址,如:类名.方法名(); 编译后是:类号.方法相对地址。对象.变量; 编译后是:对象号.变量相对地址。只是当系统安装了应用程序成为一个进程后,进程才有了一个固定的基址,成为当前进程时、基址放在A0寄存器。这时,( A0 + 类号 )才得到该类的在内存中的实际基址;( A0 + 对象号 )才得到该对象的在内存中的实际基址。( A0 + 对象号 )的内容或说指向的实际地址、决不是用户所能更改的、那是系统安装时设定的;是由系统分配与释放的。如果对一个还没有定义或说没有安装的对象操作、因为该( A0 + 对象号 )的内容为0,所以、返回一个“空对象操作”错误。应用程序的编程通常有:对象名.变量名、寄存器名、类名.方法名()、对象名.方法名()。不存在的、或违规的、编译器都能指出错误。应用程序只能行走在自己的对象、变量空间中。


4、  R1 = 对象名.W; 只是对象空间的0号字地址内容赋值给R1寄存器;我们得不到对象的字地址空间的地址。对象名.W.0.W = R1; 成立,只是对对象的字地址空间的第0字的内容赋值。只要,不超出对象的空间范围;对对象空间的赋值是成立的。如果R1L是对象号,我们也可以:R2 = R1L.E.1.W; 即R1L.E为对象的行地址空间基址、默认第0行的第一个字的内容赋值给R2。每一个对象的行大小,系统都保存有;超出范围系统报错,并非完全依赖编译器。一个进程的所有对象的空间都合为一个大的连续块或行空间;由编译器给出、系统来分配。但动态对象空间除外,那可能是其它进程通过消息通信、提交过来的空间。比如、网络进程提交了一个大的数据包对象,文件日志进程提交了一个你读取的文件数据包等;这些动态对象的大小也是受系统监控的。所以,当( A0 + 对象号 )时,系统除了读入对象的实际地址外,还同时读取对象的大小、以便监控。任何空间内地址的内容,都必须通过对象来操作。


5、通常一个类,有方法表、和属性表;我们可以只是声明一个变量是属于类的方法表、或类的属性表。比如一个类T,一个表变量A用其属性表声明就是:T.U  A; 用其方法表声明就是:T.F  A;


6、对象号、类号、文件号等是与windows句柄类似的。我们是要对句柄进行封装的,而解码是由系统指令进行;句柄可以说是数据的间接指针和数据大小。句柄是对象的唯一标识,代表了对象的空间起始实际地址和空间大小。应用程序的代码只能用句柄来操作对象空间的数据,不能使用指针;系统代码可以使用指针。


7、进程间的通信:如果一个进程的其中一个线程的一个分支;要open一个文件,来进行编辑后再保存。有2种方式:


1)、代码中已经声明了一个流对象容器,给日志文件服务进程发一个open消息,意思是“文件服务进程,请帮我把文件的指定位置和大小的数据装入到,我告诉你的;流位容器中”。之后,pend返回;意思是让步挂起返回。紧急的就进程pend、无私的线程pend。当文件服务进程,通过认证,完成、并返回一个文件号的消息;如应用程序再次获得运行,处理到该消息、就是直接到线程分支的pend返回的后一条指令继续执行;而非是,线程的开始run();


2)、如果代码并不知道文件的大小,而又希望读入整个文件进行编辑。这时,发一个open消息、包含有动态对象申请。那么,文件服务进程将返回一个文件号、一个指向文件内容的动态对象号的消息。这样,应用代码就可根据动态对象号对文件的内容进行编辑了;这时、文件服务进程使用的是文件号、来读写文件内容,而用户代码是用动态对象号;2者都有相同的实际地址指针和大小。最后,发送close消息;系统将释放文件号、动态对象号、及相应的内存。


   用户代码也可以声明一个动态对象、空间由代码申请分配、而非是编译器分配。


8、说了那么多,实际的意思就是说;用户只能用句柄来操作对象空间的数据。如:

R1H.R2.E.5.W.3、等写法也是可以的;R1H是对象号,R2.E.5.W.3是对象空间的第R2行的第5字的第3位。


9、APO使用小端模式。



                                             

0 0
原创粉丝点击