本文包含网络接口的硬件基本原理和VxWorks 6.6网络协议栈/驱动的分析,适用于VxWorks6.6以及之前的版本,并可用作其他版本协议栈的参考。
0. 概述
0.1 网络接口
以太网目前有两种模型,即ISO(国际标准化组织)定义的的OSI七层模型和IETF(互联网工程任务组)的TCP/IP五层模型,具体可参考TCP/IP协议详解。目前,TCP/IP为互联网的事实标准,具体分层如下所示:
上表中:
- 以太网报文格式与上表的分层对应:
- MAC又称为以太网控制器,既具有独立的链路层地址(MAC地址,又称为以太网地址),也就是通常意义上或者狭义上的网口,用于控制链路层数据的传输;
- PHY用于控制物理层传输,用于物理链路与MAC之间数据的编解码、物理链路控制、载波侦听、线序交换等功能,通常只包含PCS(物理编码子层)、PMD(物理介质相关子层)、PMA(物理介质附加子层)等,但是1000Mbps以上的高速链路需要更多的子层(DTE XGXS、PHY XGXS);
- MAC通过MII(狭义上的介质独立接口)/RGMII(简化的千兆介质无关接口)/SGMII(串行千兆介质无关接口)/XGMII(超高速介质无关接口)总线等连接到PHY上;
- 每个MAC通常只需要一个PHY,但是1000Mbps以上的高速网口的PHY设备需要可能还需要先通过内部PHY将TBI接口转换成SGMII接口或者将XGMII接口转换成XAUI接口,再连接到外部PHY,从而降低外部PHY的布线难度;
- TBI、XAUI和MII、RGMII、SGMII、XGMII、GMII、QGMII等接口都属于广义上的MII接口,对应于MII总线;
- PHY必须挂在MII总线上,通过MII控制器访问;
- MII接口有MDIO22和MIDO45两种标准,分别定义在802.3ap clasue22和Clasue 45中, 前者支持5位PHY地址和5位寄存器地址,后者增加了5位设备地址,允许控制PHY的不同子层;因此,每个MII控制器最多通过MII总线支持32个PHY设备;
- 广义上的网口指的是MAC、MII控制器和PHY的综合体。
0.2 约定
- socket即套接字,是BSD协议栈提出的网络接口,包含一个接收缓冲区,用于收发数据和控制数据传输;
- MBLK/Cluster/data是BSD协议栈提出的缓冲区模型,每个数据区
data
对应一个Cluster
,每个Cluster
关联到1个MBLK
上;MBLK
作为以太网报文的代表,可以将多个以太网报文分片串成一个链表,用以提升数据收发效率; - 分析以VxWorks6.6上的MPC5200 FEC网口为例,适用于VxWorks6.6以及之前的版本,并可用作其他版本协议栈的参考;
- 流程图中的非关键函数参数和非关键流程被省略以突出重点和降低工作量;
1. END驱动架构
END驱动整体架构如下所示:
其中:
1. 用户可以通过3种接口访问协议栈接口:
- socket:由sockLib库提供,包括
socket/bind/connect/listen/accept/getsockopt/setsockopt/recv/recvfrom/recvmsg/send/sendmsg/sendto()
等;其中: - 通用IO接口:由sockLib库提供,包括
close/ioctl/write/ioctl()
; - ipcom/ipnet接口:由ipcom/ipnet提供,包括
ifconfig
等;
2. sockLib库位于vxworks-6.6/target/src/wrn/coreip/sysdep/os/vxWorks/socket
,提供socket接口,并调用iosDrvInstall()
接口安装IO设备驱动,从而为每个socket套接字创建描述符以提供通用IO接口,其中:
- socket/accept接口除了调用ipcom/ipnet注册的
socketRtn/acceptRtn()
钩子函数,还需要调用iosLib库接口创建IO设备作为socket描述符; - 其余socket接口在进行简单的参数检查后直接调用ipcom/ipnet注册的对应钩子函数;
- 通用IO接口对应于sockLib库在安装IO设备驱动时注册的
socketClose/socketRead/socketWrite/socketIoctl()
接口,这些接口在进行简单的参数检查后直接调用ipcom/ipnet注册的对应钩子函数;
3. ipnet/ipcom位于components/ip_net2-6.6
,使用sockLibAdd()将AF_NET/AF_PACKET等协议栈及包含socket/bind/connect/listen/accept/getsockopt/setsockopt/recv/recvfrom/recvmsg/send/sendmsg/sendto()
等钩子的功能列表注册到sockLib的sockLibMap[];其中:
- ipcom提供OS封装层,用于屏蔽OS的不同,并绑定到MUX层,主要代码位于
components/ip_net2-6.6、ipcom/port/vxworks
; - ipnet/ipcom调用
muxLib
提供的muxDevStart/muxDevStop/muxIoctl/muxSend/muxDevLoad/muxDevUnload/muxBind/muxUnbind()
接口来完成协议栈绑定、、驱动加载、网口控制和数据收发功能;
4. muxLib位于vxworks-6.6/target/src/wrn/coreip/common/mux
,用于将协议栈绑定到不同的网口上,并提供网卡控制接口,包括:
- muxDevStart/muxDevStop/muxIoctl接口:主要通过调用
endLib
封装的网口驱动功能列表中相应的start/stop/ioctl
钩子来完成; - muxSend接口:首先通过
endLib
封装的网口驱动功能列表中的formAddress
钩子生成地址,然后调用endLib
封装的网口驱动功能列表中的packetDataGet
钩子和muxBind
接口安装的stackRcvRtn
来过滤本地数据包,最后调用endLib
封装的网口驱动功能列表中的send/formAddress/packetDataGet
钩子来完成发送功能; - muxReceive接口:通过
muxBind
接口安装的stackRcvRtn
将数据上传到ipnet/ipcom; - muxDevLoad接口:调用驱动提供的
xxxEndLoad
接口加载网口驱动,然后调用endLib
提供的endFlagsSet
设置END_MIB_2233标志(m5200FecEnd不支持2233 MIB),并使用ioctl判断end类型以设置pEnd->receiveRtn
为muxReceive
(m5200FecEnd类型为END_STYLE_END);
5. end层用于提供网卡驱动的封装结构和公用代码。
2. 网卡驱动全工作流程
2.1 初始化流程
网卡驱动初始化入口为usrNetworkInit():
- usrNetworkInit:该位于
<工程目录>/prjConfig.c
中,调用usrNetEndLibInit()
,用于初始化网卡驱动; - usrNetEndLibInit:该接口位于
vxworks-6.6/target/src/config/usrNetwork.c
或vxworks-6.6/target/config/comps/src/net/coreip/usrNetEndLib.c
中,关键流程如下所示:
其中:
- vxbDevMethodRun()遍历VxBus驱动,调用所有提供muxDevConnect()接口的网卡驱动,不适用于lite5200b的Legacy网卡驱动;
- endDevTbl[]数组包含Legacy网卡驱动,定义于BSP目录的configNet.h中:
#define FEC_LOAD_FUNC m5200FecEndLoad
...
#define FEC_LOAD_STR "-1:0x0:-1:-1:0x40:0x30:0x0:0xff:2:0x4:" \
FEC_CLOCK_SPEED(IPB_CLOCK_LITERAL)
#define FEC_BUFF_LOAN 1
...
END_TBL_ENTRY endDevTbl [] =
{
#ifdef INCLUDE_FEC_END
{ 0, FEC_LOAD_FUNC, FEC_LOAD_STR, FEC_BUFF_LOAN, NULL, FALSE},
#endif /* INCLUDE_FEC_END */
...
};
- endPollStatsInit()用于初始化网口驱动的统计信息。
3. muxDevLoad():位于vxworks-6.6/target/src/wrn/coreip/common/mux/muxLib.c
中,用于调用endDevTbl[]数组中Legacy网口驱动的初始化函数endLoad
,关键流程如下所示:
其中:
- 第一次调用endLoad时参数为空字符串,用于获取网口名称,例如’fec’;
- 第二次调用endLoad时参数为网口单元号与初始化字符串的组合,用于正式加载网口;
4. muxDevStart():位于vxworks-6.6/target/src/wrn/coreip/common/mux/muxLib.c
中,用于调用驱动注册的pEnd->pFuncTable->start()接口启动网口,并将网口设置为IFF_UP | IFF_RUNNING状态
;
5. m5200FecEndLoad():驱动接口,主要功能是注册到endLib
:
- 分配驱动信息结构和PHY信息结构;
- 调用
m5200FecInitParse
解析初始化字符串并保存在驱动信息结构中; - 调用
m5200FecInitMem
申请临时发送缓冲区,并创建netpool网络缓冲池; - 调用
m5200FecSdmaTaskInit
创建Bestcomm SDMA任务; - 调用
END_OBJ_INIT
即endObjInit
初始化END结构,注册网口驱动功能列表; - 调用
END_FLAGS_SET
设置多播和广播标志;
6. m5200FecStart():驱动接口,主要功能是启动网口:
- 调用
m5200FecReset
复位以太网控制器; - 调用
m5200FecTbdInit/m5200FecRbdInit
初始化收发缓冲区描述符环; - 调用
SYS_FEC_INT_CONNECT
即intConnect
挂接BestComm发送和接收任务中断以及通用中断处理函数; - 调用
SYS_FEC_INT_ENABLE
即intEnable
使能中断; - 调用
m5200FecPrePhyConfig
初始化MAC地址、设置中断事件掩码、清除中断事件、配置内部MII控制器接口等; - 调用
m5200FecPhyPreInit
根据驱动信息结构中的标志设置PHY信息结构中的工作模式标志; - 调用
_func_m5200FecPhyInit
即m5200FecPhyInit
初始化PHY,并根据PHY信息结构中的工作模式标志选择自协商模式或强制模式; - 调用
TaskIntClear
清除Bsetcomm接收任务中断; - 调用
TaskStart
启动Bsetcomm接收任务; - 调用
FEC_END_ETH_ENABLE
使能以太网控制器; - 调用
END_FLAGS_SET
将网口标志为IFF_UP | IFF_RUNNING
,即工作状态; - 调用
netJobAdd
将muxTxRestart
添加到tNet0任务维护的netJobQueueId
队列,进而调用绑定在网口上的协议栈的stackTxRestartRtn
接口以复位协议栈。
2.2 发送流程
发送流程的入口为socket库提供的send/sendto/sendmsg()和ios子系统提供的write():
- send/sendto/sendmsg():调用协议栈利用sockLibAdd()注册的SOCK_FUNC指针中的sendRtn/sendtoRtn/sendmsgRtn(),即ipcom_windnet_send/sendto/sendmsg()位于
vxworks-6.6/target/src/wrn/coreip/sysdep/os/vxWorks/socket/sockLib.c
; - write():调用socket库安装到ios子系统的socket驱设备的socketWrite()钩子,进而调用协议栈利用sockLibAdd()注册的SOCK_FUNC指针中的socketwriteRtn(),即ipcom_windnet_socketwrite(),位于
vxworks-6.6/target/src/wrn/coreip/sysdep/os/vxWorks/socket/sockLib.c
; - ipcom_windnet_send/sendto/sendmsg/socketwrite():调用ipcom_send/sendto/sendmsg/send(),位于
components/ip_net2-6.6/ipnet2/src/ipnet_sock.c
; - ipcom_send/sendto/sendmsg/send():调用ipcom_sendmsg(),位于
components/ip_net2-6.6/ipnet2/src/ipnet_sock.c
; - ipcom_sendmsg():调用sock->ops->send(),即ipnet_init()注册并由socket()接口使用ipcom_socket()安装的iptcp_send和ipnet_sock_udp_send接口;
- iptcp_send和ipnet_sock_udp_send():
- UDP分支:
- ipnet_sock_udp_send():调用ops->i.network_send(),即ipnet_ip4_sendto(),位于
components/ip_net2-6.6/ipnet2/src/ipnet_udp.c
;
- ipnet_sock_udp_send():调用ops->i.network_send(),即ipnet_ip4_sendto(),位于
- TCP分支:
- iptcp_send():调用iptcp_create_output_seg(),位于
components/ip_net2-6.6/ipnet2/src/iptcp.c
; - iptcp_create_output_seg():调用iptcp_output(),位于
components/ip_net2-6.6/ipnet2/src/iptcp.c
; - iptcp_output():调用iptcp_sendto(),位于
components/ip_net2-6.6/ipnet2/src/iptcp.c
; - iptcp_sendto():调用sock->ops->network_send(),即ipnet_ip4_sendto(),位于
components/ip_net2-6.6/ipnet2/src/iptcp.c
;
7.ipnet_ip4_sendto():调用netif->link_ip4_output(),即ipnet_eth_if_init安装的ipnet_eth_ip4_output(),位于components/ip_net2-6.6/ipnet2/src/ipnet_ip4.c
;
- iptcp_send():调用iptcp_create_output_seg(),位于
7. ipnet_eth_ip4_output():调用ipnet_if_output(),位于components/ip_net2-6.6/ipnet2/src/ipnet_eth.c
;
8. ipnet_if_output():调用ipnet_if_drv_output(),位于components/ip_net2-6.6/ipnet2/src/ipnet_netif.c
;
9. ipnet_if_drv_output():调用netif->ipcom.drv_output(),即ipcom_drv_eth_init安装的ipcom_drv_eth_output(),位于components/ip_net2-6.6/ipnet2/src/ipnet_netif.c
;
10. ipcom_drv_eth_output():调用muxSend(),位于components/ip_net2-6.6/ipcom/port/vxworks/src/ipcom_drv_eth.c
;
11. muxSend():位于components/ip_net2-6.6/ipcom/port/vxworks/src/ipcom_drv_eth.c
,使用pEnd->pFuncTable->send()
作为参数调用_muxTkSendEnd
进行通用处理,参数检查失败则直接释放pMBlk
发送缓冲区;
12. _muxTkSendEnd:位于components/ip_net2-6.6/ipcom/port/vxworks/src/ipcom_drv_eth.c
,进行通用处理:
- 更新2233 MIB中的发包统计信息;
- 如果目的MAC地址非空,则调用
pEnd->pFuncTable->formAddress
即m5200FecEndLoad
安装的endEtherAddressForm
构造以太网头并放到pMBlk
发送缓冲区; - 如果网口已绑定协议栈,则调用
pEnd->pFuncTable->packetDataGet
即endEtherPacketDataGet
获取pMBlk
发送缓冲区的数据指针,然后调用muxEndRcvRtn
接收数据到协议栈; - 调用
muxSend
传递过来钩子调用pEnd->pFuncTable->send()
,即m5200FecEndLoad
函数安装的m5200FecSend()
; - 如果发送阻塞即网口忙,则从
pMBlk
发送缓冲区移除以太网头,返回错误信息等待协议栈再次发送;
13. m5200FecSend():驱动接口,将数据写入发送缓冲区并发送,
- 检查参数和工作模式,失败则返回;
- 计算
pMBlk
发送缓冲区的分片数; - 如果发送缓冲区描述符个数为0,则调用
m5200FecTbdClean
清理发送缓冲区; - 如果送缓冲区描述符个数大于
pMBlk
发送缓冲区的分片数、缓冲区地址满足对齐要求且m5200FecForceCopy
为false,则调用m5200FecPktTransmit
;否则,调用m5200FecPktCopyTransmit
;
14. m5200FecPktTransmit():驱动接口,使用pMBlk
发送缓冲区进行零拷贝传输:
- 调用
m5200FecTbdListSet
获取发送缓冲区描述符列表; - 使用将
pMBlk
发送缓冲区各分片对应的数据指针设置发送缓冲区描述符的缓冲区指针并更新发送缓冲区描述符标志; - 调用
TaskStart
启动BestComm发送任务; - 调用
CACHE_PIPE_FLUSH
刷新写缓冲;
15. m5200FecPktCopyTransmit():驱动接口,从驱动创建的netpool中申请新的MBLK
用于保存pMBlk
发送缓冲区中的所有数据,然后发送:
- 调用
NET_BUF_ALLOC
申请cluster即数据缓冲区,申请失败则使用临时发送缓冲区,仍然失败则返回发送阻塞; - 对齐发送缓冲区数据地址到32字节;
- 调用
m5200FecTbdListSet
获取发送缓冲区描述符列表; - 使用将
pMBlk
发送缓冲区各分片对应的数据拷贝到申请的新发送缓冲区中; - 使用新发送缓冲区的指针设置发送缓冲区描述符的缓冲区指针并更新发送缓冲区描述符标志;
- 调用
TaskStart
启动BestComm发送任务; - 调用
CACHE_PIPE_FLUSH
刷新写缓冲;
16. m5200FecWdmaInt():驱动接口,BestComm发送任务完成后会触发BestComm发送任务中断,并调用该函数:
- 调用
SDMA_INT_DISABLE
关闭BestComm发送任务中断; - 调用
TaskIntClear
清除BestComm发送任务中断; - 调用
CACHE_PIPE_FLUSH
刷新写缓冲; - 调用
netJobAdd
将m5200FecTxHandle
添加到tNet0任务维护的netJobQueueId
队列,失败则调用SDMA_INT_ENABLE
使能BestComm发送任务
退出;
17. m5200FecTxHandle():清理发送缓冲区描述符和BestComm发送任务:
- 调用
m5200FecTbdClean
清理发送缓冲区; - 调用
intLock
锁中断; - 如果BestComm发送任务状态非空,则清除当前BestComm发送任务状态,调用
intUnlock
解锁中断,再次回到调用m5200FecTbdClean
清理发送缓冲区,直到BestComm发送任务状态为空再调用intUnlock
解锁中断并退出。
2.3 接收流程
接收流程的用户程序入口为socket库提供的recv/recvfrom/recvmsg()和ios子系统提供的read():
- recv/recvfrom/recvmsg():调用协议栈利用sockLibAdd()注册的SOCK_FUNC指针中的recvRtn/recvfromRtn/recvmsgRtn(),即ipcom_windnet_recv/recvfrom/recvmsg()位于
vxworks-6.6/target/src/wrn/coreip/sysdep/os/vxWorks/socket/sockLib.c
; - read():调用socket库安装到ios子系统的socket驱设备的socketRead()钩子,进而调用协议栈利用sockLibAdd()注册的SOCK_FUNC指针中的socketresdRtn(),即ipcom_windnet_socketread(),位于
vxworks-6.6/target/src/wrn/coreip/sysdep/os/vxWorks/socket/sockLib.c
; - ipcom_windnet_recv/socketread():调用ipcom_recv(),进而调用
ipcom_recvfrom
,位于components/ip_net2-6.6/ipnet2/src/ipnet_sock.c
; - ipcom_windnet_recvfrom/recvmsg():调用ipcom_recvfrom/recvmsg(),位于
components/ip_net2-6.6/ipnet2/src/ipnet_sock.c
; - ipcom_recvfrom():调用ipcom_recvmsg(),位于
components/ip_net2-6.6/ipnet2/src/ipnet_sock.c
; - ipcom_recvmsg():调用sock->ops->recv(),即ipnet_init()注册并由socket()接口使用ipcom_socket()安装的iptcp_recv和ipnet_sock_pkt_recv接口;
- iptcp_recv/ipnet_sock_pkt_recv():如果socket的接收缓冲区中没有报文,则根据用户传入的标志决定直接返回或者挂起任务等待报文到来;否则,则取出报文,处理后返还给用户程序入口。
接收流程的驱动程序入口为驱动的m5200FecRdmaInt:
- m5200FecRdmaInt():调用
m5200FecRxHandle
接收数据:
- 读取接收FIFO状态并打印调试信息;
- 调用
intLock
锁中断; - 调用
SDMA_INT_DISABLE
关闭接收DMA任务中断; - 调用
TaskIntClear
清除BestComm接收任务中断; - 调用
intUnlock
解锁中断; - 调用
CACHE_PIPE_FLUSH
刷新写缓冲; - 调用
netJobAdd
将m5200FecRxHandle
添加到tNet0任务维护的netJobQueueId
队列并退出;
2. m5200FecRxHandle():处理BestComm接收任务中断,
- 调用
m5200FecHandleRecvInt
接收数据; - 调用
intLock
锁中断; - 如果仍有BestComm接收任务中断待处理,则调用
TaskIntClear
清除BestComm接收任务中断,调用intUnlock
解锁中断,调用netJobAdd
将m5200FecRxHandle
添加到tNet0任务维护的netJobQueueId
队列,调用intLock
锁中断,并再次回到第一步调用m5200FecHandleRecvInt
接收数据,直到没有BestComm接收任务中断需要处理,才调用SDMA_INT_ENABLE
使能BestComm接收任务中断
并退出;
3. m5200FecHandleRecvInt():遍历接收缓冲区描述符环形队列,调用m5200FecReceive
接收数据并上送协议栈;
4. m5200FecReceive():接收数据并上送协议栈,
- 如果接收缓冲区描述符有错误,则调用
m5200FecRbdClean
清除接收缓冲区描述符并退出; - 从驱动的netpool缓冲池申请MBLK和cluster,并关联到接收缓冲区描述符的缓冲区指针,同时从驱动的netpool缓冲池申请新的缓冲区用以更新接收缓冲描述符的缓冲区指针;
- 调用
m5200FecRbdClean
清除接收缓冲区描述符; - 调用
END_RCV_RTN_CALL
上传数据到协议栈。
5. END_RCV_RTN_CALL:调用pEnd->receiveRtn
即muxReceive
,失败则调用netMblkClChainFree
释放发送缓冲区pMBLK
;
6. muxReceive():接收数据上传协议栈:
- 调用
pEnd->pFuncTable->packetDataGet
即endEtherPacketDataGet
获取pMBlk
接收缓冲区的数据指针; - 获取以太网头中的报文类型;
- 遍历绑定在网口上的协议栈,若匹配则调用协议栈的接收函数
pProto->rr.endRcv
即muxEndRcvRtn
将数据放入协议栈的缓冲区; - 更新2233 MIB接收统计信息;
- 调用
netMblkClChainFree
释放发送缓冲区`pMBLK。
7. muxEndRcvRtn():调用pBinding->stackRcvRtn,即协议栈调用muxBind
绑定的deng stackRcvRtn
函数指针将数据上传到协议栈的缓冲区。
3. ifconfig up/down流程
ifconfig的入口位于vxworks-6.6/target/src/wrn/coreip/wrapper/utilslib/ifconfig.c
,主要流程如下所示:
- ifconfig():调用ipnet_cmd_ifconfig(),位于
vxworks-6.6/target/src/wrn/coreip/wrapper/utilslib/ifconfig.c
; - ipnet_cmd_ifconfig():调用ipcom_socket()创建socket,然后使用IP_TRUE和IP_FALSE调用ipnet_ifconfig_if_change_state(),位于
components/ip_net2-6.6/ipnet2/src/ipnet_cmd_ifconfig.c
; - ipnet_ifconfig_if_change_state():使用IP_SIOCSIFFLAGS,并设置或清除IP_IFF_UP来调用ipcom_socketioctl(),位于
components/ip_net2-6.6/ipnet2/src/ipnet_ioctl.c
; - ipcom_socketioctl():调用ipnet_do_ioctl(),位于
components/ip_net2-6.6/ipnet2/src/ipnet_ioctl.c
; - ipnet_do_ioctl():调用ipnet_ioctl_if(),位于
components/ip_net2-6.6/ipnet2/src/ipnet_ioctl.c
; - ipnet_ioctl_if():使用IP_SIOCXOPEN或IP_SIOCXCLOSE调用ipnet_if_link_ioctl(),位于
components/ip_net2-6.6/ipnet2/src/ipnet_netif.c
; - ipnet_if_link_ioctl():调用netif->link_ioctl(),即通过ipnet_eth_if_init()安装的ipnet_eth_ioctl(),位于
components/ip_net2-6.6/ipnet2/src/ipnet_netif.c
; - ipnet_eth_ioctl():调用ipnet_if_drv_ioctl(),位于
components/ip_net2-6.6/ipnet2/src/ipnet_eth.c
; - ipnet_if_drv_ioctl():调用ipnet_if_clean_snd_queue()清理并复位当前接口上的队列,然后调用netif->ipcom.drv_ioctl(),即通过ipcom_drv_eth_init()安装的ipcom_drv_eth_ioctl(),位于
components/ip_net2-6.6/ipnet2/src/ipnet_netif.c
; - ipcom_drv_eth_ioctl():为IP_SIOCXOPEN调用ipnet_drv_eth_sync_with_end_flags(),为IP_SIOCXCLOSE调用netif->link_ioevent即ipnet_eth_ioevent()通知协议栈网口处于IP_EIOXSTOP,位于
components/ip_net2-6.6/ipnet2/src/ipnet_eth.c
; - ipnet_drv_eth_sync_with_end_flags():调用muxIoctl获取网口标志和状态,若网口处于活动状态度,则调用netif->link_ioevent即ipnet_eth_ioevent()通知协议栈网口处于IP_EIOXRUNNING状态。
综上所述,ifconfig up/down不会真正操作网口驱动,只是清理并复位当前网口上的队列,然后通知协议栈网口处于IP_EIOXRUNNING或IP_EIOXSTOP状态。