第二十二章 TCP/IP层的实现

                      第二十二章    TCP/IP层的实现

 

     我比较喜欢先难后易,如果把GPU显示管理、和网络管理拿下后;我会从头整理、改写一遍APO操作系统。这样,就会形成APO操作系统的锥形、也获得了全局观。内核CPU线路、和用户CPU线路,你可以将它们看成是独立的2个32位CPU核;内核CPU主要任务是实时处理、硬件中断,256个实时线程包含了一些中断程序的后半部。用户CPU主要是动态优先级进程、线程调度,各种应用程序的运行;2个核之间是通过消息交互、句柄提交、事件驱动。

     现有的实现网络驱动的程序都是非常复杂、啰嗦、可怕的,数据包的内存管理就可以吓到人。再加上学院派的那些玩不死你,不摆手的TCP协议;你有那么多精力吗?LINUX实现网络的程序,源代码据说38万行啊;难道要发扬愚公移山精神,需要几代人的炼化?对我来说,1000行代码量,已经感觉是一件很恐惧的事情啊;38万行,那会直接吓晕初哥。APO的TCP/IP层是在内核CPU线路实现的,MAC端口硬件也集成在内核CPU里。实现TCP/IP层的只是简单的编写3个实时线程:RECV(),SEND(),TSINT();及事件驱动层Message()的相关消息处理,代码量不到160行。我就想不通,那些狗屁砖家、叫兽之流的聪明仔想法?难道非要人为将问题复杂化才好?要知道,通常越复杂的事情、都是简单的实现!在基础科学领域里,那些家伙还要乱搞,狂妄自大;难道这就能体现你们的是聪明仔?作为人类的一员,我只能感到悲哀!无可奈何!值得庆幸的是自己是属于笨鸟一族。

 

      光明终会过去,落日黄昏之后,迎来的是黑暗;在黑暗的尽头,将是黎明、蕴含光明的希望;那永恒的黑白交替运动就是宇宙规则之一!半个多月啊,看完约3000章的小说,昏天黑地;由衷的佩服那些作者们,要知道我写一章都是那么艰难!那么无奈!这章将是又臭又长、没完没了的修改。


一、内核32位CPU

     大部分的硬件中断都是在内核32位CPU中体现,而本文仅是讨论集成在内核32位CPU中的辅助硬件MAC端口的MAC发送、接收软中断。相对于用户CPU来说,内核CPU的程序要简单得多。主程序就4行,代码量集中在中断程序的前本部,和中断程序后本部的实时线程。对于内核CPU的最多256个实时线程, APO 采用了先来先服务调度模式SCHED_FIFO( First-In, First-Out,FIFO )。

SCHED_FIFO:不同的实时线程根据静态优先级进行排队,谁先准备好运行就先调度谁,并且正在运行的实时线程不会被终止直到以下情况发生:

(1)发生硬件中断、用户的中断实时程序抢占CPU。

(2)自己因为资源请求而阻塞(调用sleep())。

(3)自己主动放弃CPU(调用yield())。

     当 policy 的值为 SCHED_FIFO 时,遵守 POSIX1.b 标准的 FIFO 调度规则。它会一直运行,直到有一个线程因 I/O 阻塞,或者主动释放 CPU ,或者是 CPU 被另一个具有更高 rt_priority 的实时中断程序抢先。

1、内核CPU主程序

Main{      // 主程序,占内存空间3W。

  init(); // 内核CPU初始化。

LOOP:

  Message(); // 消息处理,事件驱动层。

  RT_Thread_DD();// 实时线程调度方法。

  JMP  LOOP; // 无限循环。

}

    Message()消息处理、事件驱动层,起到承上启下的作用;是用户CPU和内核CPU通过异步消息通信的桥梁;后面还会详细讨论有关网络消息的处理。

     内核CPU没有集成多功能硬件模块,但集成有简化的位图检测功能。寻找256位图的最高位为1的索引时,需将位图赋值到H4专用行寄存器;之后,用MSS1指令。如果256位均为0,状态寄存器MSR的S1位会置1,索引值在任务状态寄存器MRR.7-0。

实时线程调度方法:RT_Thread_DD()       

占用:9W,

耗时:跳到相应的实时线程:8ns,无实时线程:7ns。

RT_Thread_DD(){ // 实时线程调度方法。

   R1 = #RTTETAB; // R1指向相应的实时线程入口表RTTETAB,

   H4.E = H5.E;   // H5行寄存器为256位的实时线程运行位图变量,

   MSS1;          // 实时线程位图索引

   H5.E = H4.E;  // 回传

   BT0  MSR.S1, SJP; // 有实时线程、跳。

   RET

SJP:

   R2L = MRR.7-0; MPC = R1.R2L.W; // 跳到相应的实时线程入口。

}

256个的实时线程入口表RTTETAB:H32—H63

256个的中断入口表:H64—H95

2、MAC硬件端口

    MAC硬件端口集成有发送、接收双缓冲区(CPU内部RAM):2*2*48E。发送、或接收的双缓冲区是通过乒乓模式来工作;每当发送、或接收一个数据包时,都产生一个软中断、只是对实时线程运行位图的相应位置1,发送:位图序号251、接收:位图序号250。程序使用的MAC接收缓冲区地址:H160—H207,程序使用的MAC发送缓冲区地址:H208—H255。MAC硬件端口接收到一个IP数据包时,软中断置位了实时接收线程RECV();数据内容就在行寄存器H160—H207中的一些行;IP数据包大小范围是:2E—48E(64B—1.5KB)。如果进入了实时接收线程RECV(),会据H160的参数进入各种分支程序(TCP数据包?ICMPAPO信令包?);如是信令包,那需据信令内容进行分支;如是TCP数据包,需要做IP数据包组装,拷贝到相应的用户流容器中。类同有,实时发送线程SEND();这只是简单的拷贝本地内存发送数据块缓冲区中的一个发送数据包到H208—H255中的一些行吧,最大耗时不到45ns。

   CRC、校验和的产生、检错这都是硬件实现的,如果有错、硬件自动丢弃IP数据包;还有自环、目标MAC地址判断、软中断产生等,也是MAC硬件端口的事情;这里就不多说了。

 二、实时发送线程SEND()

     APO的发送缓冲区有8个数据块:32—39号 本地内存数据块,发送缓冲队列IP数据包数最大64K包。TCP数据报分割成一些IP数据包是由用户进程提交的数据报文发送消息时,是Message()消息处理、事件驱动层的相应消息处理过程来实现的;类似的,opens()的消息也是一样。这意味着,实时发送线程SEND()并不负责IP数据包安装到发送缓冲队列;只管对发送缓冲队列的IP数据包数进行发送。对于连接建立时的信令包、或IP数据包的超时重发机制是由0.5秒定时器软中断实时线程TSINT()来实现。所以,TCP/IP层的发送TCP数据报文的实现有三部分:TSINT()、Message()、SEND()。

 

对于SEND(),我们需要下面的参数变量:
BU512KE  SENDBUF; // 发送队列缓冲区32—39号本地内存数据块。

SENDPV{

  BU16     

  BU16  PACKETSN;// 发送队列的数据包数。

  BU32  IDLELN; // 发送队列缓冲区的空闲行数。

  BU32  IDLEDPP; // 发送队列缓冲区的首空闲数据包指针。

  BU32 SENDPP; // 发送队列缓冲区的发送数据包指针。

}

     发送队列循环缓冲区中,最后面的或许有些行没用;如果IP数据包安装到发送队列时,IP数据包的长度 + 首空闲数据包指针 的值X大于缓冲区尾行,那么先把首空闲数据包指针的内容行设为0,再首空闲数据包指针设为SENDBUF,之后才安装IP数据包。如果值X大于SENDPP发送队列缓冲区的发送数据包指针,那么、发送队列满,该事件过程延缓处理;等待SEND()一些数据包后再进行。

实时发送线程SEND()。    

出口:

发送一个IP数据包,PACKETSN发送队列的数据包数减一,IDLELN发送队列缓冲区的空闲行数增加为已经发送的数据包行长度,SENDPP发送队列缓冲区的发送数据包指针为指向下一个发送数据包。

占用:22W。

耗时:最大 45ns。

SEND(){ // 实时发送线程。

   R1 = #SENDPV; // R1指向SENDPV。

   if R1.PACKETSN.Z- != 0 goto sen1;// 发送队列的数据包数减一非零跳。

   MSR.SENDD = 1;// 此后无数据包发送,MAC硬件端口不再置发送软中断。

sen1:

   R2 = R1.SENDPP.W; // 提取发送数据包指针给R2。

   BT1 R2.E, sen2;  // 行内容非0、跳。

   R2 = #SENDBUF;   // 重设发送数据包指针为缓冲区起始。

sen2:

   R3 = R2.TYPE.Z.10-5; // 提取数据包的TYPE参数的行长度值。

   R4 = R2 + R3;  // R4为下一个发送数据包指针。

   if  R4<= SENDBUFMAX  goto  sen3; // 没有越界、跳。

   R4 = #SENDBUF; // 重设下一发送数据包指针为缓冲区起始。

sen3:

   R1.SENDPP.W = R4; // 设置下一个发送数据包指针。

   R4 = #H208; // R4指向 MAC发送缓冲区地址。

   COPY.E( R4, R2, R3L ); // 拷贝当前需发送的IP数据包到MAC。

   RET

}


三、实时接收线程RECV()

     我们并不需要指定的接收缓冲区,描述socket连接的内存v节点中,就有关于该连接的接收流容器参数。如果接收到一个与TCP数据报文内容相关的IP分段数据包,只是简单的拷贝到接收流容器中吧。实时接收线程RECV()是要复杂一些,它除了处理TCP数据包,还有各种信令分支、及连接状态迁移。不管怎样,RECV()都需要在不到0.3us内完成。对于IP分组数据包的组装,我们是需要一种更为简洁的方法。

     假设客户端A和服务端B建立了一个连接,如果A请求读B的一个100GB的文件;我们设想一下合理的通信过程。B将A的请求消息提交给B端的用户服务进程,那么用户服务进程处理该消息、首先要将文件的内容从磁盘读入到文件流容器,接着、将文件流容器提交给TCP/IP层,通信的事情就不用管了;直到TCP/IP层回给完成消息,才读入新的文件内容到流容器,重复该过程直到100GB的文件内容全部传输完成。为了加快速度,我们也可以建立2个文件流容器、以乒乓方式进行,在提交一个文件流容器到TCP/IP层时,使用另一个文件流容器来读入新的文件内容,用并行方式来提高速度。那么,文件流容器的大小是多少合适呢?这取决于服务端的用户服务进程;在建立连接时,除了要协商MSS、还需确定文件流容器的大小是多少个IP数据包。APO规定文件流容器的大小单位是256个IP数据包,最大32单位、即8K个IP数据包;文件流容器的大小行数最大64KE(2MB)。这就意味着用户进程提交的一个TCP数据报文最大是2MB,100GB的文件就需提交50K次。如果MSS = 32E,文件流容器是64KE,用户进程提交的一个TCP数据报文是2MB;那么、TCP数据报文被分割成2K个IP数据包。所以,一个TCP通信的分段IP数据包的包头;需要指明其是第几号数据包,第几号TCP数据报文,以及每一个TCP数据报文的IP数据包数。我们使用流标签来标识TCP数据报文流,一个TCP数据报文流最大为64K*2MB = 128GB。对于用户进程来说,每次通信就是一个TCP数据报文。如果TCP数据报文的分割IP数据包数是n,那么该报文的总字节数 = ( n – 1 )*MSS*32B + 最后的第n包的有效内容字节数。

1、通信协议TCP/IP/ICMP新规

      为了进一步的简化网络编程,APO的通信协议TCP/IP/ICMP作了改进。数据包格式:MAC头、TCP头、ICMPAPO头与内容或IPAICMP头与内容或TCP数据分段报文。从另一个角度看,可分为TCP数据分段报文、和信令报文;而信令报文又分为路由交换机必须接收处理的ICMPAPO协议(宣告、邻居请求、探查)信令报文,和只是转发的IPAICMP协议信令报文。而主机对2种信令报文都需处理;当客户端请求建立一个TCP连接时,使用ICMPAPO协议的探查信令报文,该报文到达服务端时、就包含了沿途的路由信息;服务端在ACK和MSS、servesockfd时,转发该信令报文;这样,客户端就知道了往返时间、路径信息、MSS、servesockfd等。

TYPE.15 = 0 是IP协议:

0、互联网控制报文协议ICMPAPO,

1、TCPAPO,  IPA为APO的IP协议,

2、IPAICMP,ICMP的差错报文、应答报文协议,

3、UDPAPO,

4、IPV4,

5、IPV6,

6-15 其它、或用户自定义。

IP{ // IP数据包,最小长度2E = 64B,最大长度 = 1526B

// 含头部最大47E = 1504B,分段数据报文内容最大45E = 1440B。

 BU1E MACIP{ // 以太网和IP头部、1E(1行) = 32B;路由器处理入口。

   BU48  MDA;   // 以太网目标地址

   BU48  MSA;   // 以太网源地址

   BU16  TYPE;  // 类型和长度,TYPE.10-0  LEN; 数据包字节长度。

   BU8   TTL;   // 跳数限制。

   BU8   TOS;   // 8位传输优先级、流量类型。

   BU64  SLADD; // 源链路地址。SLADD + MSA = 源IP地址

   BU64  DLADD; // 目标链路地址。DLADD + MDA = 目标IP地址

}

 TCP{ // APO的TCP协议头、1E;支持4GB的文件流传输。

   BU16  dflown; // TCP数据报文流的总报文数。

   BU16  doff;   // TCP数据报文的偏移号。

   BU32  sheetoff;// 低13位0-12是IP分段数据包的偏移号。

// sheetoff.31  astfp; 1、同时多流模式,0、时分多流模式。

// sheetoff.30-26  request; 上层协议的请求方法标识。Read、write、GET、

// POST、PUT、DELETE、TRACE、CONNECT、PATCH、ABOR、ACCT等等;或自定义。

// sheetoff.25-13  dpackn; 数据报文的数据包数,25-21位是流容器单位数。

   BU8   trflag; // 传输控制标志。

// trflag.7  MF; 更多分段标志(more fragment),0、最后一个分段

// trflag.6  DF; 分段标志:1、不分段,0、可以分段

// trflag.5  MULTICAST; 组播标志,1、有效。

// trflag.4  SIGLL; 信令标志:1、该报文段含有信令,0、DATA报文段传输。

// trflag.3  SIGC; 1、请求丢失的数据包信息。

// trflag.2-0 RES; 备用。

   BU24  flowlabel; // 24位流标签,标识不同的TCP数据报文流。

   BU8  conflag;   // 连接控制标志。

// conflag.7 CWR; 用来表明它接收到了设置ECE标志的TCP包。

// conflag.6  ECE; 网络拥挤标志。

// conflag.5 URG; 1、紧急指针字段有效。

// conflag.4  ACK; 1、确认应答。

// conflag.3  PSR; 1、推送,不等组装、直接提交报文段到应用层。

// conflag.2  RST; 1、复位连接。

// conflag.1  SYN; 1、请求建立连接。

// conflag.0  FIN; 1、释放连接。

   BU24  servesockfd; // 24位服务端的文件号。  

   BU16  SPORT;   // 源端口

   BU16  DPORT;   // 目的端口

   BU16  cheksum; // 校验和。

   BU16  urgentp; // 紧急指针。

   BU16  tranmode;// 高层协议的传输模式。

   BU16  staterp; // 高层协议的状态响应码,或错误码。

   BU32  usedata; // 用户自定义、或高层协议使用、或组播域定义。

}

 ICMP{ // APO的ICMP协议头、1E,无连接;或是TCP分段数据报文内容。

   BU8   imcptype; // 报文类型低4位:宣告、邻居请求、探查,或应答、

// 或差错报文。高4位是代码号,路由交换机只管低4位。

   BU8   segnum; // 路由交换机附带数据项印记指针(项数、每项16字节)。

   BU16  devtype;// 源设备类型(主机,2-3-4层交换机等)、网络中级数。 

   BU32  PTID;   // 源设备的进程号(或端口号)、线程号(或标识)。

   BU32  times;  // 报文发送时的时间戳us。

   BU16  MTU;    // 源设备的MTU。

   BU16  cheksum;// 校验和。

   BU16B icmpdata; // 第0项的附带数据,后面接着是更多的附带数据。

}
  // IP数据包的其它内容。

}

     对于一些应用来说,或许在同一个连接中,同时有多个流在交织传输;这时,TCP/IP层是不会组装IP数据包的,而是由应用程序来实现。当sheetoff.31 = astfp标志为1时,同时多流模式;TCP/IP层只是将收到的TCP报文的IP数据包,直接提交给用户进程;丢包重发,包组装、拆分等都是由用户进程来操控的。APO支持同时多流传输模式,也支持通常的时分多流模式。在时分多流模式中,IP数据包的组装、拆分、重复包、丢包重发等是由内核CPU的TCP/IP层来实现的。当然,也可以使用多个连接来实现同时流,而每个连接都是分时流;应用程序就无须理会TCP通信细节了。

2、信令报文

有3类信令报文:

1)、主机、路由器的宣告、邻居请求、探查报文ICMPAPO。

2)、路由器、或主机到主机的应答、差错报文IPAICMP。

3)、主机之间用于TCP通信的TCP信令报文。


     APO取消了广播信令,路由器的开机宣告信令报文必须是对其每一个端口都做发送;对于主机的开机宣告信令报文,只是发送一次。路由器的邻居请求信令报文只是对其每一个端口做一次发送,收到应答则更新路由表。对于主机的邻居请求信令报文,其相应的路由交换机,如果不是子网主管交换机则向上转发,同时也发送其管辖下的邻居信息MAC表;子网主管交换机最终是转发该邻居请求信令报文到其每一个下级端口,相应收到来自上级的邻居请求信令报文的路由交换机;如是有连接主机的端口,则发送其管辖下的邻居信息MAC表,否则,往下级转发该信令报文。对于探查报文,如果是非目的节点,则应该附加上其节点信息后再转发。ICMP本身是无重发机制的,需要其它辅助才行,信令报文丢了也就那样了。

    主机之间用于TCP通信的TCP信令报文,是有2次重发机制支持的;TCP信令协议镶嵌在IP数据包的TCP头里,但建立TCP连接时,也需要使用ICMPAPO协议的探查报文。TCP请求建立连接SYN,和对连接的确认应答ACK,这一对信令报文是在ICMPAPO协议的分支中处理的。

     TCP的数据分段报文的丢包重传机制是通过在连接已经建立的ESTABLISHED状态下,SIGC为1、传送丢失的数据包信息来实现的。通信的A、B双方,在A方发送了n个IP数据包后,跟着发送请求丢失的数据包信息;B方收到后回送丢失的数据包信息,A方收到后则据知再次发送,继续这一个过程、直到该TCP数据报文完成。任何时候,信令报文的3次发送失败、都会使连接被清除。TCP通信是较为复杂的,我们需要分解为一系列的简单动作,分解为多个层次。


3、实时接收线程RECV()  

    接收一个IP数据包,据接收的IP数据包类型TYPE.15-11数值跳转;APO当前只支持3种类型IP数据包:TCP的IP数据包(包含TCP信令包),路由信令IP数据包ICMPAPO,节点信令IP数据包IPAICMP(应答、差错的IP数据包)。其它保留作为兼容或将来扩展。


RECV(){ // 实时接收线程;占用:18W,耗时:3ns+。

  R0L = H160.TYPE.15-11; // 提取接收IP数据包类型。

  Switch(R0L){ // 据R0H的值跳转;PPCL = (PPCL + 1).R0H。

  ICMPAPO; TCPAPO; IPAICMP; RET(保留); RET; …. 共13个RET。

     }

}

4、TCP的IP数据包接收方法TCPAPO()

    TCP的IP数据包格式:MACIP头、TCP头、TCP内容。如果是TCP分段报文,提取TCP数据报文的偏移号,拷贝到用户进程的报文接收流容器的相应位置,如TCP数据报文的所有IP分段数据包都接收完毕、那就向用户进程发TCP数据报文提交消息;如果不分段,那么、拷贝到用户进程的报文接收流容器,并向用户进程发TCP数据报文提交消息。如果是含有TCP信令的IP数据包,还需做信令分支处理。

    每一个连接都有64E的信令包接收缓冲区,建立连接后,后32E就作为接收分段报文的位图缓冲。如果TCP报文是需要分段的,那么、提取第0号分段报文的sheetoff.25-13  dpackn; TCP报文的数据包数,25-21位是流容器单位数(位图行数);初始化接收分段报文的位图缓冲。以后,每收到一个分段数据包,位图的相应位清0。位图缓冲反映了TCP报文的分段数据包接收的状况;如果A方发送完TCP报文的所有分段数据包后,跟着就是请求传送丢失的数据包信息、即接收位图;B方收到请求后,回送位图缓冲,A方收到后则据知再次发送。。。。

TCPAPO(){// TCP的IP数据包接收方法;占用:45W、R30-R31,

// 最大耗时约:20ns+?。

   Getlinkd(); // R1接收分段报文的位图缓冲指针,数据包行长度R0H,

// R2 = H160,R3指向数据报文接收流容器,R4为数据包数+1、MSS。

// R30为端口表项字指针,R31指向连接的内存v节点。

   BT1 H161.SIGLL, TAP4; // 是含有TCP信令的IP数据包,跳。

   BT1 H161.astfp, TAP2; // 同时多流模式,跳完整提交。

   BT1 H161.DF, TAP2;    // IP数据包不分段,跳完整提交。

   R0H = -2;   // 去头部,只拷贝TCP分段数据报文的内容。

   R2 = H162;

   R0L = H161.sheetoff.12-0;// TCP数据报文的偏移号(数据包号)到R0L。

   R3 = +R0L*R4L;            // 指向接收流容器的相应位置。

   (R1).R0L = 0;     // 接收分段报文的位图缓冲的相应位清0。

   if R0L != 0  goto  TAP2; // 非第0号分段报文跳。

   R0L = H161.sheetoff.25-21;// 提取流容器单位数(位图行数)

   R0L+;                     // 位图行数取整。

TAP1:

   (R1).R0L.E = 1; // 位图缓冲初始化为全1

   if R0L- != 0 goto  TAP1; // R0L-1后非零继续

   (R1).0 = 0;     // 接收第0号分段报文的相应位清0。

TAP2:

   COPYH( R3, R2, R0H );// 拷贝数据报文到接收流容器的相应位置。

   BT1 H161.PSR, TAP3; // 如果推送,不等组装、直接提交,跳。

   if H161.dpackn > R4H  goto  RET; // TCP数据报文没接收完跳返回。

   (R30).W.rpstate = 1; // 置完全接收TCP报文的所有分段状态。

TAP3: 

   SUBDP();// 向用户相关进程提交TCP数据报文完成的消息方法、并发ACK。

   RET

TAP4:

   BT1 H161.ACK, ACKHANDLE;// 不同状态下的ACK信令处理。

   BT1 H161.SIGC, TAP6;  // 请求丢失的数据包信息的信令,跳。

   R3 = R31.Srecvstream_p; // R3指向接收的信令数据报流容器。

   COPYH( R3, R2, R0H ); // 拷贝信令报文。

   BT1 H161.RST, TAP3; // 复位连接跳、提交消息、发ACK。

TAP5:

   (R30).W.FIN = 0;      // 其它情形、释放连接。

   JMP  TAP3;

TAP6:  // 丢失数据包信息的请求信令,发送位图缓冲及ACK,不提交。

   SENDBMP();

   RET

ACKHANDLE://确认ACK响应帧处理,SYN请求建立连接及响应在ICMPAPO分支。

// 带ACK的信令包,意味着之前发送的信令,现收到确认应答ACK,有多项分支。

// 先禁用和复位发送超时定时器OVERTIME。

// SIGC收到丢失数据包信息位图,重发丢失的数据包并最后附加SIGC信令包。

// RST复位连接的ACK,没用返回RET。是应用进程初始化连接,发RST。

// FIN释放连接的ACK,这时、主动关闭方才释放本连接。

   R31.FLAG.TIME = 1; // 禁用发送超时定时器OVERTIME。

   R31.OVERTIME.Z = 0;// 复位发送超时定时器OVERTIME。

   BT1  H161.RST,RET;

   BT1 H161.FIN, TAP5; // 就算是3次握手释放连接吧。

   BT0 H161.SIGC, RET; // 其它情形返回。

   RPACK(); // 据丢失数据包信息位图,重发丢失的数据包及附加SIGC信令包。

}


5、端口表

 

     16位socket端口表, 理论上有64K项、每项的内容是一个字。端口表项内容,如果是客户端口:高8位是状态标志、低24位是文件号sockfd;对于服务端口,高8位是状态标志、低24位是0。因为一个服务端口就对应很多连接,我们将服务端口的描述每一个客户端连接的24位内存v节点号(socket文件号)放在IP数据包的TCP协议头中。

sock_port_tab{ // socket端口表。

  BU32 [64K]  tabitem;// 端口表项,高8位状态标志、低24位sockfd或0。

// tabitem.31  linkworks; 1、连接有效;释放连接CLOSED时,该位清0。

// tabitem.30  servelink; 1、是服务端连接LISTEN状态,0、是客户端。

// tabitem.29  SYN-SENT;  如是客户端,1、在SYN-SENT状态(sendSYN)。

// tabitem.29  SYN-RECEIVED; 如是服务端,1、在SYN-RECEIVED状态。收到

// SYN,send SYN + ACK进入该状态。

// tabitem.28  ESTABLISHED; 1、ESTABLISHED连接状态。如果是服务端,

// 在SYN-RECEIVED状态、收到ACK则进入该状态。如果是客户端,在SYN-SENT

// 状态、收到SYN + ACK就发ACK进入该状态。

// tabitem.27  FIN_WAIT;1、FIN_WAIT主动关闭等待状态。Send FIN进入。

// 收到ACK后,linkworks= 0、释放连接CLOSED。

// tabitem.26  TIME-WAIT;1、TIME-WAIT被动关闭等待状态。收到 FIN进入,

// 并发送ACK。

// tabitem.25  rpstate; 1、完全接收TCP报文的所有分段状态。

// tabitem.24  OVERTIME;1、超时定时器启动。



     主动关闭(active close)端应用程序调用close,于是其TCP层发出FIN请求主动关闭连接,之后进入FIN_WAIT状态;等待远程TCP的ACK确认,收到ACK则释放连接。如果1s内、并重发2次FIN请求,还是没有收到ACK;也释放连接。被动关闭(passive close)端TCP也是类似的;当被动关闭端TCP接到FIN后,就发出ACK以回应FIN请求(它的接收也作为文件结束符传递给上层应用程序),并进入TIME-WAIT:等待足够的时间(1s内、如果再次收到FIN,会重发ACK;最多2次),以确保远程TCP接收到连接断开请求的确认ACK。不管怎样,在1s后,都会自动释放被动关闭端的连接。这些都是在0.5秒的定时器软中断实时线程TSINT()来实现;信令数据包都有最多3次的发送,3次失败、连接就给清除。


Getlinkd()方法

出口:

R1接收分段报文的位图缓冲指针,数据包行长度R0H,R2 = H160,R3指向数据报

文接收流容器,R4为包数+1、MSS;R30为端口表项字指针,R31指向连接v节点。

占用:20W、R30-R31,

最大耗时:17ns+。

Getlinkd(){

   R0L= H161.DPORT.Z;   // 提取目标端口到R0L。

   R1 = #sock_port_tab;

   R30 = R1.R0L.W.; // R30为端口表项字指针,后面是.结尾、赋值地址。

   BT1 (R30).W.linkworks, GLD1; // 连接有效、跳。

GLD0:

   MSP = +1; RET   // 连接无效,POP后返回、丢弃包结束。

GLD1:

   R1 = (R30).W.23-0; // R1为v节点号。

   BT0 (R30).W.servelink, GLD2; // 是客户端、跳。

   R1 = H161.servesockfd; // 从IP数据包提取24位服务端的v节点号。

GLD2:

   R31 = getvnode( ,R1 );// R31指向连接v节点内存。

   R1 = R31.MDA.W;       // 该连接的对方主机MAC地址的32位。

   if  R1 !=H160.MSA.W  goto  GLD0;// 再次核对是否该连接,否丢包返回。

   R4H = R31.recepn+.Z;  // 接收数据包数加1并送R4H。

   R4L= R31.MSS.Z;

   R3 = R31.recvstream_p.W; // 接收的数据报流容器本地内存指针。

   R2 = #H160;

   R1 = R31.RECVSMC.W; R1 = +32; // R1接收分段报文的位图缓冲指针。

   RET

}

 

     没法,每天只能是龟爬式前进;老天已经收回激情,只剩下咬牙切齿的坚持。一天只能有约一小时用于这章的思考、码字,大部分时间都是通过玩游戏、看小说来积聚动力、念力。我们每一个人身体的每一个组成部分、或说每一个细胞中的基本粒子,都是源自宇宙的初生;它们只为了缘分的相聚,从遥远的古代走过来;生命是永恒的,消散的只是生命的印记,亦即灵魂。灵魂复制的梦想一直是我最大的动力,想起都激动万分;学院派们、醒醒吧,为加速人类的进步尽一点力量吧。


6、APO的重传机制


     APO主机可以达到每秒百万个IP数据包的速度,TCP报文可以分段为最大8K个IP数据包;为了那传说中的网络速度,就让我们一次发射几K个IP数据包吧!那管它网络瞬间洪水滔天?丢包率达到50%?我们只追求高速;不行?修改网络设备吧。如果一个文件流比较大,我们是想尽可能的做大TCP报文;但考虑到内存空间的限制等等因素,流容器的大小最大还是设在一个数据块(2MB);这样,最多分段为2K包。我们是希望一次就将TCP报文的所有分段IP数据包发射出去,如果丢包率高于50%,那只好改小一些TCP报文大小了。一次发射2K个IP数据包,对于1GBPS的主机MAC端口速度,也就约花2ms的时间;即使丢包率为50%,也传输了1K包,这时、做一次丢包信息的信令通信;第二次选择重传时,或许成功率就到80%、只剩200包需要重传,或许3次重传就能完全成功;当然,好网络一次就能OK。现有的TCP协议,如果每次IP数据包传输都来一次ACK,就算5次来一个ACK确认;也是非常愚蠢和低效率的。


    APO的TCP分段IP数据包重传机制,是通过丢包信息的TCP信令来实现的;对于分段IP数据包的组装、重复包的处理等是很自然的实现。通信双方都能透过数据包接收位图,了解通信状况。A方一次发射一个TCP报文的n个分段IP数据包,会在最后附加一个请求丢包信息的TCP信令包;B方收到第0号分段IP数据包,就建立和初始化数据包接收位图等;对于是否收到最后一个分段不重要,关键是能收到请求丢包信息的TCP信令包,如果超时(根据包数设定)、连接就会被释放;如收到TCP信令包则发送数据包接收位图的TCP信令包给A方。双方检查到TCP报文完全传输,则分别回消息给用户进程,跟着就是下一个TCP报文了。根据IP分段数据包的偏移号、即包号,和建立连接时协商的MSS值,很容易就能定位IP分段数据包内容在接收流容器中的位置,并存放。不过,对于不分段的TCP报文,或完全接收TCP报文的所有分段IP数据包,还是要回发ACK的;这时,就不需要丢包信息的TCP信令包了。


7、APO连接描述的内存v节点

     如何区别描述连接的socket文件和其它文件类型的内存v节点呢?我们修改非描述连接的文件类型的内存v节点如下:

BU4E  file_vnode{ // 文件对应的本地内存v节点。

  BU32 vnode;    // 索引i节点号、文件名字哈希值。

  BU16 v_mode;   // 描述文件的访问权限;文件的读、写、执行权限

//i_mode.15 socket; 1、socket文件,0、其它类型文件。

// i_mode.14-12  ftype; 文件类型: 0-符号连接文件,

// 1-普通文件, 2-块设备文件,3-字符设备文件,

// 4-文件系统根目录型文件。

// i_mode.11-0 FWQX;文件访问权限rwx-rwx-rwx-rwx、owner–root–grp-oth

  BU16 v_flags;  // 文件标志。

……

}

    这样,判断v_mode变量就可以知道是否socket文件了。 原来的i_mode.12标志改为v_flags.12   ACL;  文件访问权限是否由ACL文件描述。


BU4E socket_vnode{ // socket文件的网络v节点。

  BU2E{ // 2行的连接描述。

   BU32  PTID;   // 高16位关联的进程号、低16位关联的线程号。

   BU8   FLAG;    // 状态标志,FLAG.7 = 1为socket文件v节点。

// FLAG.6  TIME;  1、禁用发送超时定时器OVERTIME,0、允许。

// FLAG.5-0  RES; 备用。

   BU24  BACKLOG; // 最大连接数。

   BU8   ptype;   // 协议类型。

   BU24  RECVLEN; // 允许的最大报文长度单位E。

   BU32  recvstream_len;// 数据报流容器对象的大小,单位E

   BU32  recvstream_p;  // 接收的数据报流容器本地内存空间指针。

   BU32  sendstream_p;  // 发送的数据报流容器本地内存空间指针。

   BU32  Srecvstream_p; // 接收的信令数据报流容器内存空间指针。

   BU32  Ssendstream_p; // 发送的信令数据报流容器内存空间指针。

   BU16  recepn;  // 一个TCP报文的接收IP数据包的计数。

   BU16  sendpn;  // 一个TCP报文的发送IP数据包的计数。

   BU32  crtime; // 连接创建时间标识,单位10mS。

   BU16  OVERTIME;// 发送超时定时器。

   BU16  CMPTIME; // 发送超时定时器比较值。

   BU16  MSS;    // 协商的MSS。

   BU16  MTU;    // 本机的MTU。

   BU2W。。。。。待定

   BU32  SCOUNT; // 本连接的发送数据报总计数。

   BU32  RCOUNT; // 本连接的接收数据报总计数。

}

   BU1E  MACIP; // APO的发送IP头、1E。

   BU1E  TCP;   // APO的发送TCP协议头、1E。

}


四、重传定时器方法OVERTIME()

 

     重传定时器只是在信令数据报发送时设置,接收是不需要的。为了支持上千万个连接,对于每一个连接的重传定时器是一种软件定时器。0.5秒的硬件时钟定时器会设置状态寄存器MSR的TIME5标志,考虑到这并不需要实时处理,原先的0.5秒定时器软中断实时线程TSINT()将取消;不再是由内核CPU来处理,而是由用户CPU的10ms中断来进行。我们知道,内存v节点最多只是16M个,由256个64K位图来反映;也并非全部的内存v节点都是描述连接的。用户CPU的10ms中断程序一次只处理64K个内存v节点,就算这64K个全是描述连接,也就约2ms。16M个内存v节点就算有1.6千万个是描述连接,那么在0.5秒内也肯定能循环处理完。对于每一个内存v节点,先是判断是否socket文件,否下一个内存v节点、是再判断FLAG.6(TIME)= 0?如是1、禁用发送超时定时器,则下一个内存v节点;如是0、OVERTIME发送超时定时器增1,判断超时否、超时处理;循环64K次。这样,第一次超时或许是0.5秒 + x(x<0.5秒);这没关系的,对于超时重发我们并不需要那么精确;而是注重于每一个连接都要有一个信令发送超时定时机制。重传定时器方法OVERTIME()只是用户CPU的10ms中断程序中的一个方法。

    诶,过完年、心也乱了,需要一定时间恢复;慢慢来吧,估计代码量要超出预计的160行。

     内存v节点中模式空间16M个单位、单位4E( 128B, 64Z, 32W),子空间就是一个64K单位 = 4个内存数据块(UNITLEN = 4)。OVERTIME()方法每次处理一个子空间,我们需要一个标识子空间序号的变量SUBSPACE,和一个指示子空间处理的计数变量COUNT。

BU16  SUBSPACE; // 标识子空间序号,最初为0。

BU16  COUNT;// 子空间处理的计数,最初和循环结束时为VNODE_IMODE1.ACTN。


OVERTIME()方法

占用:36W、R26-R31,

最大耗时约:1.3ms。

OVERTIME(){ // 在用户进程调到模块中的信令重传定时器方法。

   R1 = #VNODE_IMODE1; // 指向内存v节点中模式空间的管理变量区。

   R3 = SUBSPACE;      // R3H = COUNT, R3L为SUBSPACE。

   R2 = R1.SSPN.R3L.W;  // R2指向子空间首地址。

   if R2 > 1  goto  OVE3;// 有效跳。

OVE1:      // 未分配、无效、下一64K个v节点循环、返回。

   R3L+; R3H-;

   if R3H = 0  goto  OVE2; // 循环结束,清标志TIME5。

   SUBSPACE = R3; // 保存

   RET

OVE2:

   R3H = R1.ACTN.Z; R3L = 0; // 初始化COUNT、SUBSPACE。

   SUBSPACE = R3; // 保存

   PSR.TIME5 = 0; RET

OVE3:

   R4L = 64K; // 设置循环计数。

OVE4:

   BT0 R2.i_mode.15, OVE5; // 非socket文件跳下一个v节点。

   BT1 R2.i_mode.14, OVE5; // 禁用发送超时定时器OVERTIME跳。

   R4H = R2.OVERTIME.Z+;    // 超时定时器增1。

   if R4H = 1  goto  OVE5; // 0.5秒 + x(x<0.5秒)才会第一次重发。

   if R4H <= 3  goto  RSPACK; // 跳、超时重发信令数据包。

   if R4H = R2.CMPTIME.Z  goto  RTIME; // 定时时间到处理。

   R31 = #sock_port_tab;

   R30 = R31.R0L.W.; // R30为端口表项字指针,后面是.结尾、赋值地址。

   (R30).W.FIN = 0;  // 2次重发失败、释放连接。

   R2.i_mode.14 = 1; // 禁用发送超时定时器OVERTIME。

OVE5:

   R2 = +4;  // 指向下一个v节点。

   if R4L- != 0  goto  OVE4; // 循环

   JMP OVE1; // 跳下一64K个v节点循环做准备。

RSPACK:

   INTDP(); // 安装发送信令数据包。

   JMP   OVE5;

RTIME:

…待定、不一定使用。

}


安装一个发送数据包的方法INTDP()。    

出口:

安装一个IP数据包到发送队列缓冲区,PACKETSN发送队列的数据包数加一,IDLELN发送队列缓冲区的空闲行数减少为发送的数据包行长度,IDLEDPP发送队列缓冲区的首空闲数据包指针为指向下一个发送空闲数据包位置。

占用:16W、R26-R29

最大耗时:38ns,通常信令包长度3E、耗时才16ns、平均估计20ns。

INTDP(){ // 安装一个发送数据包。

   R1 = #SENDPV;   // R1指向SENDPV。

   R1.PACKETSN.Z+; // 发送队列的数据包数加一。

   R27 = R1.IDLEDPP.W; // 提取发送首空闲数据包指针给R27。

   R28 = R2.Ssendstream_p.W; // R28指向发送信令包。

   R29 = R28.TYPE.Z.10-5; // 提取数据包的TYPE参数的行长度值。

   R26 = R27 + R29;  // R26为下一个空闲发送数据包指针。

   if  R26<= SENDBUFMAX  goto  IND1; // 没有越界、跳。

   R27 = #SENDBUF; R26 = R27 + R29; // 重设下一空闲发送数据包指针。

IND1:

   R1.IDLEDPP.W = R26; // 保存下一个空闲发送数据包指针。

   COPY.E( R27, R28, R29L );// 拷贝当前需发送的IP数据包到缓冲队列。

   RET

}



五、ICMPAPO信令分支处理

 

    ICMPAPO报文类型有3种:宣告、邻居请求、探查。所有网络上的节点都要处理这3种报文,主机可以主动发送这3种报文,但路由器、交换机只是能主动发送前2种。ICMPAPO信令报文的响应报文是IPAICMP信令报文,路由、交换机对于进入的IPAICMP信令报文只是转发。

ICMPAPO报文格式:

ICMPAPO_IP{ // ICMPAPO报文的IP数据包

  BU1E MACIP;    // APO的发送IP头,

  BU1E TCP;      // APO的发送TCP协议头,

  BU1E ICMPAPO{ // APO的ICMPAPO协议头、1E,无连接。

  BU16   imcptype;// 报文类型和项数。

// imcptype.8 search;  1、探查,0、宣告、或邻居请求。

// imcptype.9  declare; 1、宣告,0、邻居请求、

// imcptype.15-10 coden;  代码号,路由、交换机只管imcptype.9-8。

// imcptype.7-0  segnum; 附带数据项印记指针(项数、每项16字节)。

  BU16  devtype;// 源设备类型(主机,2-3-4层交换机等)、网络中的级数。 

  BU32  PTID;   // 源设备的进程号(或端口号)、线程号(或标识)。

  BU32  times;  // 报文发送时的时间戳us。

  BU16  MTU;    // 源设备的MTU。

  BU16  cheksum;// 校验和。

  BU16B icmpdata; // 第0项的附带数据,后面接着是更多的附带数据。

  }

}

 

1、主机发送ICMPAPO报文。


     宣告、邻居请求信令报文是系统内建的邻居进程和0号连接(0号内存v节点)来实现的,探查信令报文通常是用于应用进程的建立连接过程、在Message()消息处理、事件驱动层来实现。定时重传机制是较为独立的一个模块,通常的ICMP信令报文是没用重传机制的,但我们可以很容易在ICMPAPO探查信令报文增加定时重传机制。


2、主机接收ICMPAPO报文。


    路由、交换机没必要向主机发送宣告、邻居请求信令报文,所以不用管其接收;主要的是用于建立连接的探查信令报文接收。


ICMPAPO探查信令报文接收方法:

出口:

占用:17W、R26-R31

最大耗时:?ns,

ICMPAPO:

   BT1 H162.search, ICM1; // 探查跳。

   RET

ICM1:

   Getlinkd(); // R1接收信令报文的位图缓冲指针,数据包行长度R0H,

// R2 = H160,R3指向数据报文接收流容器,R4为数据包数+1、MSS。

// R30为端口表项字指针,R31指向连接的内存v节点。

   BT1 H161.SYN, ICM3;    // 连接信令,跳。

ICM2:                     // 非连接信令的探查报文提交。

   R3 = R31.Srecvstream_p;// R3指向接收的信令数据报流容器。

   COPYH( R3, R2, R0H );  // 拷贝信令报文。

   JMP  TAP3;

ICM3:

   BT1 H161.ACK, ICM4;  // 连接信令ACK,跳。

   H161.ACK = 1;  // 对方请求连接,设置ACK,转发该IP包。

   REV;           // 反转源、目标地址指令。

   R28 = H160;

   JMP INTDP1;      // 安装一个发送数据包方法。

ICM4:

   R31.FLAG.TIME = 1; // 禁用发送超时定时器OVERTIME。

   R31.OVERTIME.Z = 0;// 复位发送超时定时器OVERTIME。

   R0L= H162.MTU;

   R31.MSS.Z = R0L;   // 设置MSS。

   JMP  ICM2;

 

 

六、IPAICMP信令分支处理

 

    IPAICMP信令报文类型:宣告0的应答、邻居请求1的应答; 差错报文类型:2目标不可到达,超时3,源端抑制4,参数问题5。主机不发送IPAICMP信令报文,只是接收。IPAICMP信令报文格式与ICMPAPO的类同,只是参数imcptype不一样。路由、交换机只是转发目的地非自己的IPAICMP信令报文。对于TCP数据分段报文变为IPAICMP报文只是将报文内容的第一行覆盖IPAICMP信令报文头,并设置报文类型等。


  BU16  imcptype;// 报文类型和项数。

// imcptype.11-8   type; 报文类型0-5。

// imcptype.15-12  coden; 代码号。

// imcptype.7-0  segnum; 附带数据项印记指针(项数、每项16字节)。


IPAICMP信令报文接收方法:

出口:

占用:14W+、R26-R31

最大耗时:?ns,

IPAICMP:

   R0L= H162.TYPE.imcptype.11-8;// 提取接收IP数据包imcptype类型。

   Switch(R0L){ // 据R0L的值跳转;PPCL = (PPCL + 1).R0H。

   SEARCH; DECLARE; NOTREACH; OVERTIME;SREST; PPROB; RET; RET;

   }
SEARCH:

   RET   // 宣告的应答不管

DECLARE: // 由用户端交换机或路由器发出的邻居请求应答

   JMP  ICM2; // 提交

NOTREACH: //目标不可到达、释放连接、提交。

OVERTIME: //超时、释放连接、提交。

PPROB:// 参数问题、释放连接、提交。

   (R30).W.FIN = 0;      // 释放连接。

   JMP  ICM2; // 提交。

SREST: // 源端抑制处理。

….

 

 

七、Message()消息处理、事件驱动层

 

     请求分配和释放内存v节点(连接)都是在用户层实现的,opens( PORT )一个客户端连接(指定或不指定端口),实际上就是请求分配内存v节点(连接);建立一个服务端连接opens( PORT, BACKLOG, RECVLEN ) 也是类似,但请求分配的内存v节点(连接)是作为将要到来的客户端连接;当一个客户端请求连接信令包到来后,使用了该内存v节点、同时将该请求连接包提交消息给用户层,用户层处理该消息时再请求分配一个新的的内存v节点(连接)。。。

 

    释放连接信令包也同样需要提交给用户层,以便用户层释放内存v节点(连接)。所以,请求连接、释放连接信令包都需要提交消息给用户层,用户层必须处理这2种消息。用户层发送TCP数据报文sendto(sockfd, flags, msg, len ),是提交消息给内核的Message()消息处理、事件驱动层,由此层分段和安装IP数据包。这层并不复杂,也容易编写。


     跌跌撞撞的到此,剩下的代码不多了,我也没力气编写、检查和修改了;写了一个多月啊,我的心已不在,只好留待以后完善了。来自体内远古细胞的呼唤、来自外河系中孤独流浪多年的思感粒子的纠缠、来自心灵神国的呐喊,造成主线程经常性的中断;即使有漫天的小说,即使有军旗、飞行棋等简单游戏的阻挡,我也不得不暂停下来了;就让我的灵魂重新沉入微观世界,追寻神的足迹吧。


以后待续。。。

 




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值