#define THETHIIRECV_PRIO21//* ethernet网卡接收线程(任务)优先级#define THETHIIRECV_STK_SIZE384 * 4 //* 接收线程栈大?。飧稣灰喽源笠恍』岜ù?#define THETHIIRECV_TIMESLICE 10//* 单次任务调度线程能够工作的最大时间片static void start_thread_ethernet_ii_recv(void *pvParam){rt_thread_t tid = rt_thread_create("EthRcv", thread_ethernet_ii_recv, pvParam, THETHIIRECV_STK_SIZE, THETHIIRECV_PRIO, THETHIIRECV_TIMESLICE);if(tid != RT_NULL)rt_thread_startup(tid);}
其余os与之类似 。我们启动的这个以太网接收线程完成实际的以太网层的报文接收及处理工作 。其轮询等待网卡接收中断函数发送的报文到达信号,收到信号则立即读取并处理到达的报文 。我们在后面讲述网卡接收函数的移植细节时还会提到这个接收线程 。
对于网卡发送函数 , 有一点需要注意的是——其原型必须符合协议栈的要求 , 因为我们在进行网卡注册时还要向协议栈注册发送函数的入口地址 。前面在介绍ethernet_add()注册函数时我们已经给出了发送函数的原型定义 , 也就是pfunEmacSend参数指向的函数原型 。协议栈的目标系统是资源受限的单片机系统,为了最大限度节省内存,协议栈采用了写时零复制(zero copy)技术 , 网卡发送函数需要结合协议栈的buf list机制编写实现代码,其伪代码实现如下:
int ethernet_send(SHORT sBufListHead, UCHAR *pubErr){SHORT sNextNode = sBufListHead;UCHAR *pubData;USHORT usDataLen;//* 调用buf_list_get_len()函数计算当前要发送的ethernet报文长度,其由协议栈提供UINT unEthPacketLen = buf_list_get_len(sBufListHead);//* 逐个取出buf list节点发送出去__lblGetNextNode:pubData = https://www.huyubaike.com/biancheng/(UCHAR *)buf_list_get_next_node(&sNextNode, &usDataLen); //* 获取下一个节点,buf_list_get_next_node()函数由协议栈提供if (NULL == pubData) //* 返回空意味着已经到达链表尾部,没有要发送的数据了,直接返回就可以了return (int)unEthPacketLen;//* 启动发送 , 将取出的数据发送出去,其中pubData指向要发送的数据,usDataLen为要发送的数据长度,这两个值已经通过buf_list_get_next_node()函数得到//* 在这里添加与具体目标网卡相关的数据发送代码……//* 取下一个数据节点goto __lblGetNextNode;}
关于buf list,其实现机制其实很简单 。以udp通讯为例,用户要发送数据到对端,会直接调用udp发送函数,将数据传递给udp层 。udp层收到用户数据后 , 为了节省内存,避免复制,协议栈直接将用户数据挂接到buf list链表上成为链表的数据节点 。接着,udp层会再申请一个节点把udp报文头挂接到数据节点的前面 , 组成一个拥有两个节点的完整udp报文链表——链表第一个节点挂载udp报文头 , 第二个节点挂载用户要发送的数据 。至此,udp层的报文封装工作完成,数据继续向ip层传递 。ip层会继续申请一个节点把ip报文头挂接到udp报文头节点的前面,组成一个拥有三个节点的完整ip 报文链表 。ip报文在ip层经过路由选择后被送达数据链路层,也就是ethernet层 。在这一层,协议栈再将ethernet ii报文头挂接到ip报文头节点的前面 。至此 , 整个报文的封装完成 。协议栈此时会根据网卡注册信息调用对应网卡的ethernet_send()函数将报文发送出去 。ethernet_send()函数的核心处理逻辑就是按照上述机制再依序取出链表节点携带的各层报文数据 , 然后顺序发送出去 。
网卡移植拼图的最后一块就是完成网卡接收函数,把网卡收到的数据推送给协议栈 。其伪代码实现如下:
//* 网卡接收函数 , 可以是接收中断服务子函数,也可以是普通函数,普通函数必须确保能够在数据到达的第一时间就能读取并推送给协议栈void ethernet_recv(void){EN_ONPSERR enErr;unsigned int unPacketLen;unsigned char *pubRcvedPacket;//* 在这里添加与具体网卡相关的代码,等待接收报文到达,如果数据到达将报文长度赋值unPacketLen变量,将报文首地址赋值给pubRcvedPacket…………//* 读取到达报文并将其推送给协议栈进行处理 , 首先利用协议栈mmu模块动态申请一块内存用于保存到达的报文unsigned char *pubPacket = (UCHAR *)buddy_alloc(sizeof(ST_SLINKEDLIST_NODE) + unPacketLen, &enErr);//* 申请成功 , 根据协议栈要求,刚才申请的内存按照PST_SLINKEDLIST_NODE链表节点方式组织并保存刚刚收到的报文PST_SLINKEDLIST_NODE pstNode = (PST_SLINKEDLIST_NODE)pubPacket;pstNode->uniData.unVal = unPacketLen;memcpy(pubPacket + sizeof(ST_SLINKEDLIST_NODE), (UCHAR *)pubRcvedPacket, unPacketLen);//* 将上面组织好的报文节点放入接收链表,这个接收链表由协议栈管理,ethernet_put_packet()函数由协议栈提供//* thread_ethernet_ii_recv()接收线程负责等待ethernet_put_packet()函数投递的信号并读取这个链表//* 参数l_pstNetifEth为前面注册网卡时由协议栈返回的PST_NETIF指针值ethernet_put_packet(l_pstNetifEth, pstNode);}
推荐阅读
- 2 onps栈移植说明——编译器及os适配层移植
- 1 onps栈移植说明——onps栈的配置及裁剪
- 开源网络协议栈onps诞生记
- <一>从指令角度了解函数堆栈调用过程
- stm32h750移植lvgl
- 3 Python全栈工程师之从网页搭建入门到Flask全栈项目实战 - 入门Flask微框架
- 都卷Java,你看看你得学多少技术栈才能工作!
- flutter系列之:flutter中可以建索引的栈布局IndexedStack
- SpringBoot+Vue3 AgileBoot - 手把手一步一步带你Run起全栈项目
- C++ 使用栈求解中缀、后缀表达式的值