第二十二章 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 RQ; 请求标志(SIGLL=1有效):1、请求报文段,0、响应报文段。
// trflag.2 SIGC; 1、丢失的数据包信息的信令。
// trflag.1-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数据包接收方法;占用:37W、R30-R31,最大耗时:3ns+?。
Getlinkd(); // R1接收分段报文的位图缓冲指针,数据包行长度R0H,
// R2 = H160,R3指向数据报文接收流容器,R4为包数+1、MSS。
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;//提取流容器单位数(位图行数)
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 TAP3; // TCP数据报文完全接收,跳。
RET // 没有全部接收TCP数据报文,那返回。
TAP3:
SUBDP(); // 向用户CPU的相关进程提交TCP数据报文完成消息的方法。
RET
TAP4:
BT1 H161.SIGC, TAP5; // 丢失的数据包信息的信令,跳。
R3 = (R31).Srecvstream_p; // R3指向接收的信令数据报流容器。
COPYH( R3, R2, R0H ); // 拷贝信令报文。
BT1 H161.RST, TAP3; // 复位连接跳。
(R30).FIN = 0; // 释放连接。
JMP TAP3;
TAP5:
BT1 H161.RQ, SENDBMP; // 丢失数据包信息的请求信令,跳发送位图缓冲。
RPACK(); // 根据丢失数据包信息位图,重发丢失的数据包。
}
1)、端口表
16位socket端口表, 理论上有64K项、每项的内容是一个字。端口表项内容,如果是客户端口:高8位是状态标志、低24位是文件号sockfd;对于服务端口,高8位是状态标志、低24位是0。因为一个服务端口就对应很多连接,我们将服务端口的描述每一个客户端连接的24位内存v节点号(socket文件号)放在IP数据包的TCP协议头中。
..........待续