下文是对工作中的零拷贝机制进行主要代码注释描述,以便之后能够快速记忆和理清其工作原理。
1、物理内存的分配,代码与内核一起编译,内核启动执行do_early_param进行分配
__initcall (capmem_init) //相当于module_init,内核启动流程do_initcalls()会遍历.initcall*.init段,依次执行各个级别的函数
capmem_init
create_proc_info_entry("capture_mem",0444,&proc_root,get_info)) //proc显示capmem大小等信息
//__setup宏用来指导建立obs_kernel_param结构,并编译到内核特定段中。在内核启动时,do_early_param将取出__setup_start到__setup_end之间obs_kernel_param结构并执行其中的函数
__setup("capmem=", capmem_setup);//获取grub、bootload等引导程序传给内核的参数,capmem设置待分配物理内存大小
capmem_setup
capmem = alloc_bootmem_low_pages(capmem_pages<<PAGE_SHIFT);//bootmem分配器分配内存,内存分配的任务,这个时候buddy系统,slab分配器等并没有被初始化好
2、使用内核模块注册proc文件系统,实现对内核缓存初始化、参数配置、对内核资源的控制、缓存映射等功能。
/*
缓存描述符中的映射缓存结构体,mmap将其映射到应用层,应用层有与之对应的相同的结构体
*/
typedef struct _mapbuffer
{
int dwWrite; // 读地址
int dwRead; // 写地址
int dwPacketSum; //能存放包的总数
DATAPACKET pDataPacket[MAX_PACKET_SUM];
}MAPBUFFER;
/*
缓存描述符,描述当前缓存是属于哪个网卡通道、几号内核缓存、缓存的的大小、缓存的过滤参数
读写指针位置、和当前存放的数据帧的个数等信息。描述符以链表形式挂接,链表描述多个内核缓存。
*/
typedef struct _kbuffer
{
MAPBUFFER *pMapBuffer; //存放映射buffer信息的地址
int dwKBNum;//内核缓存的计数值
int dwEthNum;//属于哪个网卡的标示符,以数字标示
char ethName[20]; //属于哪个网卡的标示符,以字符型数字标示
FILTER_ARG filter_arg;//过滤参数
unsigned char rece_flag; //接收标志
int pdwFlowIp1[FLOW_COUNT];//流量的ip地址1
int pdwFlowIp2[FLOW_COUNT];//流量的ip地址2
int flow_count;//流量的计数值
int flow_sum;//流量的种类种数
struct _kbuffer *next;//下一个通道缓存的地址
}KBUFFER;
mmap 在proc内核的实现
paddr = virt_to_phys((void*)((unsigned long)( pKB->pMapBuffer))); //找到其映射后对应的物理内存
remap_pfn_range(vma, vma->vm_start, paddr >> PAGE_SHIFT, size, PAGE_SHARED) //将内核空间的物理内存映射到用户空间
3、在网卡驱动中断处理rx将数据抛向协议栈之前,把sk_buf->data(数据帧)写入缓存
轮询缓存描述符链表,判断网卡通道是否与接收数据帧的netdev是否一致,如果相同就写入相对应
的映射缓存区。写入成功便将该skb释放,不再抛给协议栈,节省系统处理数据包的资源,减轻cpu压力
4、应用层的使用
fd=open("/proc/cap", O_RDWR) //open proc
ioctl(fd, INIT_KB, &kb_io) //ioctl设置网卡混杂模式
addr=mmap(0, sizeof(MAPBUFFER), PROT_READ|PROT_WRITE,MAP_SHARED|MAP_LOCKED, fd, iKBStart); //映射
pKBuffer->pMapBuffer = (MAPBUFFER *)adr; //映射数据缓存结构化,使用