免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1005 | 回复: 0
打印 上一主题 下一主题

[内核入门] papaya内核笔记 4 网卡收包及其它 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2016-06-30 20:10 |只看该作者 |倒序浏览
本帖最后由 weiweishuo 于 2016-06-30 20:55 编辑

【Rx Buffer里的package们】
  注意,在Rx buffer里,接收到的packet一个接一个的躺着,它们之间并非密不透缝。每个packet最前面,有4个字节,struct { u16 status; u16 size}。其中status描述这个包的接收状态::它是个广播包,组播包,还是MAC matched包?它的CRC校验和正确吗?等等。。最关键的当然是ROK这个bit。
  除了包开头有一个4字节的header,包末尾还有4字节的CRC,CRC后面还可能有1~3个字节的padding,是为了下一个包对齐到4字节(8139芯片的特性,手册上说是word aligned, 但pseudo code里暗示的4字节对齐)。当然,这一切都是芯片完成的,驱动只管去读。
  Realtek的这种“给收到的数据包加header”的设计,其实不出我们的预料。因为8139在网卡收包上放弃了descriptor based的设计,但每个收到的包都要有相应的描述信息,这一点是跑不了的。于是收包时,顺便把这些信息组织在包的开头。
  忘了说了,package header里的size字段是描述这个包的长度,包括CRC,但不包括header本身,也不包括最末尾可能存在的填充字节。(当然不该包括)
  

【别忘了打开IMR的所有bit】
  我们在测试初期,会只打开IMR的ROK和TOK位。
  基本的测试通过后,要赶紧把IMR上别的bit都打开。我这儿实在是在废话,但今天就在这上面浪费了一天:
  测试机上的内核从网线上收到一个包之后,我再怎么ping它,都没反应。连中断都没有。  但是按键盘还有反应,可见硬中断是开着的。
  最后发现,原来IMR只打开了TOK,ROK两个位,收发包的结果可不仅是这两个,那当然没反应————这一回是Rx Buffer Overflow。

【CBA, CAPR 和CMD.BUFF_EMPTY】
  在我的内核里,CBA叫CursorToWrite, CAPR叫CursorToRead。它们是两个指针(或者叫偏移)寄存器。
  CBA指向Rx buffer里躺着的最后一个包的末尾,若新的包来了,8139就往CBA指的地方继续写,所以叫它CursorToWrite。 驱动程序用不着CBA寄存器,它是8139自己维护的。
  CAPR的作用是:你驱动程序从Rx buffer里读包,你读到哪儿了,你得告诉我芯片(就通过CAPR),如果我发现你已经读完最后一个包了,我就把CMD.BUFF_EMTPY位置1;
  具体的讲,如果是CAPR追到和CBA相等,就意味着我们驱动已经读完了读完了Rx Buffer上的所有package。
  但写驱动时,我们每读完一个package,不能耿直的把CAPR更新到那个package的末尾。你可能问,CBA也是指在package的末尾,不这样做,哪有机会让CAPR和CBA相等呢?
  这就是硬件为什么脏。我先说明,我们想的没错,我们想的就是正常人的思路。
  但是,Realtek的设计里,CAPR == CBA会触发Buffer Overflow中断!
  我们先放下我们的疑惑,站在Realtek的角度。
  Realtek考虑到另一个问题:如果你驱动一直不读Rx Buffer,而CBA会随着硬件收包不停的推移(到Rx Buffer的末尾会Wrap),当CBA的值再次追到和CAPR相等的时候,整个Rx Buffer里都是崭新的需要驱动去读的package。再没有空间收包了,这就是Buffer Overflow。
  现在,我们再回到刚才的问题,就觉得不是那么怒气冲冲了。
  那,怎么才能标识CAPR已经读完了最后一个包呢? Realtek的设计是,你驱动每次读完一个package,就把CAPR更新到离这个包末尾还差16个字节的地方。当8139发现CAPR逼近CBA只差16个字节,就知道Rx Buffer里的包读完了————16个字节是肯定放不下一个包的。
  这就分析完了。但我有一点不懂,Realtek一旦这样设计,那CBA就永远没有跟CAPR相等的机会了。还怎么检测over flow?

【调试】
  现在发现最好的调试方式,是一台裸机测试内核,用一条网线连到装有linux系统的机器上(像比自己正在写程序的电脑)。bash自带的命令几乎就够用了,linux真是为程序员而生的。
  后期往tcp上层调试的时候,再考虑用虚拟机。

【wireshark】
  wireshark用的感觉不错。网络调试时,图形界面的优势就显出来了。可以统计packet数量,往指定的packet跳转,查看字段时鼠标点击有提示(我承认裸眼看xxd是很牛),等等。
  多一个抓包工具是必要的,跟tcpdump的输出结果对照着看。 之前调ARP包,发现papaya发出的ARP包一直得不到回应,从tcpdump的抓包看,papaya发出的ARP包似乎不符合标准:
  tcpdump平时不显示14字节的ether头部,但对我的包却格外照顾,莫名奇妙的显示出最后两个字节,0x0806。
  检查了很久,终于决定用另一个抓包软件看看。在wireshark上很快确定ARP包的结构没问题(成员排列没问题)。

【目的IP为0的ARP报文】
  若linux接受到一个“目的IP为0”的ARP报文,linux会忽略它。
  必须把对方的IP写死在ARP报文里。似乎本来就该这样,我原先是看错了wiki上的一句话,以为把target IP设成0,那所有的局域网主机都会响应这个ARP报文。
   An ARP probe is an ARP request constructed with an all-zero sender IP address (SPA).


【arp协议的作用】
  arp协议应该是“最后100米”的寻址协议。
  当ip报文穿越最后一个路由器,进入局域网。停在交换机上:接下来就要靠MAC寻址了。这依赖于ARP协议。它维护着IP到MAC地址的映射。


【rtl8139_init_one() 和 rtl8139_open()】
   宏观的看,前者在我们运行"insmod 8139too.o"时被调用。后者是运行"ifconfig eth0 up"时被调用。
   两者都是做初始化,一先一后,_init_one侧重于PCI部分(网卡首先是一个PCI设备),_open侧重于网卡本职功能。
   我只能理解到这个程度了,其实两个函数还是有交集的,特别是初始化netdevice的工作。

【linux上的一段注释:bitfield & endian】
----------drbd.h----------
38 /* Although the Linux source code makes a difference between
39    generic endianness and the bitfields' endianness, there is no
40    architecture as of Linux-2.6.24-rc4 where the bitfields' endianness
41    does not match the generic endianness. */
42
43 #if __BYTE_ORDER == __LITTLE_ENDIAN
44 #define __LITTLE_ENDIAN_BITFIELD
45 #elif __BYTE_ORDER == __BIG_ENDIAN
46 #define __BIG_ENDIAN_BITFIELD
47 #else
48 # error "sorry, weird endianness on this box"
49 #endif
在网络传输中,大端主机先传送的是MSB,小端主机线传送的是LSB。
从结果上来说,这句话不错。 但说这句话的人,必然对endian存在误区。因为网络传输中,是不考虑CPU的endian的。 网卡只是忠实的把主机A上的一段内存印象,“拷贝”到主机B的某一段内存里去。

【bochs的usb_uhci failed】
  bochs启动"--enable-usb"选项的话,启动时会panic, "dlopen usb_uhci failed"。
  在一个邮件列表里,看到15年12月份有人说,最新的svn的版本已经打了相应的patch。
  因为源码在sourceforge,折腾了很久,还是不行。
  最后从镜像网站下的,虽然是2.6.8,但是是2015年5月的release。
  问题依旧。只好configure时把usb选项关掉。
  顺便贴上自己的配置选项:
./configure --with-x11 --with-wx --enable-ne2000 --enable-iodebug --enable-gdb-stub --enable-e1000 --enable-pci --enable-pcidev --enable-plugins --disable-docbook
  哦,我重新编译bochs,是想写e1000的驱动。

【最后,贴两份“收包中断”的代码】
  我上次推荐的那篇8139入门的文章,在收包中断的处理上也有错误(而且是代码和解释都错了)。还是看手册上的pseudo code和linux的源码靠谱。  
  下面贴的两段,一个是linux2.0时期的源码,有删减(注意它虽然写的是8129。。);另一个是手册上的pseudo code,可谓字字如金,体现了一些未文档化但很重要的tips。

  1. /* The data sheet doesn't describe the Rx ring at all, so I'm guessing at the
  2.    field alignments and semantics. */
  3. static int rtl8129_rx(struct device *dev)
  4. {
  5.         struct rtl8129_private *tp = (struct rtl8129_private *)dev->priv;
  6.         long ioaddr = dev->base_addr;
  7.         unsigned char *rx_ring = tp->rx_ring;
  8.         u16 cur_rx = tp->cur_rx;

  9.         while ((inb(ioaddr + ChipCmd) & 1) == 0) {
  10.                 int ring_offset = cur_rx % RX_BUF_LEN;
  11.                 u32 rx_status = *(u32*)(rx_ring + ring_offset);
  12.                 int rx_size = rx_status >> 16;

  13.                 if (rx_status & RxTooLong) {
  14.                         ...
  15.                 } else if (rx_status &
  16.                                    (RxBadSymbol|RxRunt|RxTooLong|RxCRCErr|RxBadAlign)) {
  17.                         ...
  18.                 } else {
  19.                         /* Malloc up new buffer, compatible with net-2e. */
  20.                         /* Omit the four octet CRC from the length. */
  21.                         struct sk_buff *skb;

  22.                         skb = dev_alloc_skb(rx_size + 2);
  23.                         ...
  24.                         skb->dev = dev;
  25.                         skb_reserve(skb, 2);        /* 16 byte align the IP fields. */
  26.                         if (ring_offset+rx_size+4 > RX_BUF_LEN) {
  27.                                 int semi_count = RX_BUF_LEN - ring_offset - 4;
  28.                                 memcpy(skb_put(skb, semi_count), &rx_ring[ring_offset + 4],
  29.                                            semi_count);
  30.                                 memcpy(skb_put(skb, rx_size-semi_count), rx_ring,
  31.                                            rx_size-semi_count);
  32.                                 if (rtl8129_debug > 4) {
  33.                                         int i;
  34.                                         ...
  35.                                         memset(rx_ring, 0xcc, 16);
  36.                                 }
  37.                         } else {
  38.                                 eth_copy_and_sum(skb, &rx_ring[ring_offset + 4],
  39.                                                                  rx_size, 0);
  40.                                 skb_put(skb, rx_size);
  41.                                 ...
  42.                         }
  43.                         skb->protocol = eth_type_trans(skb, dev);
  44.                         netif_rx(skb);
  45.                         tp->stats.rx_bytes += rx_size;
  46.                         tp->stats.rx_packets++;
  47.                 }

  48.                 cur_rx = (cur_rx + rx_size + 4 + 3) & ~3;
  49.                 outw(cur_rx - 16, ioaddr + RxBufPtr);
  50.         }
  51.         ...
  52.         tp->cur_rx = cur_rx;
  53.         return 0;
  54. }
复制代码

  1. BOOLEAN PacketOK(        PPACKETHEADER pPktHdr        )
  2. {
  3.         BOOLEAN BadPacket = pPktHdr->RUNT ||
  4.         pPktHdr->LONG ||
  5.         pPktHdr->CRC ||        pPktHdr->FAE;
  6.         if( ( !BadPacket ) && ( pPktHdr->ROK ) )
  7.         {
  8.                 if ( (pPktHdr->PacketLength > RX_MAX_PACKET_LENGTH ) ||
  9.                 (pPktHdr->PacketLength < RX_MIN_PACKET_LENGTH ))                return(FALSE);        
  10.                 PacketReceivedGood++;
  11.                 ByteReceived += pPktHdr->PacketLength;#endif
  12.                 return TRUE ;
  13.         }
  14.         else        return FALSE;
  15. }
  16. BOOLEAN        RxInterruptHandler()
  17. {
  18.         unsigned char TmpCMD;
  19.         unsigned int PktLength;
  20.         unsigned char *pIncomePacket, *RxReadPtr;
  21.         PPACKETHEADER        pPacketHeader;
  22.         while (TRUE)
  23.         {
  24.                 TmpCMD = inportb(IOBase + CR);
  25.                 if (TmpCMD & CR_BUFE)        break;
  26.         do
  27.         {
  28.                 RxReadPtr        = RxBuffer + RxReadPtrOffset;
  29.                 pPacketHeader = (PPACKETHEADER)RxReadPtr;
  30.                 pIncomePacket = RxReadPtr + 4;
  31.                 PktLength = pPacketHeader->PacketLength;
  32.                 //this length include CRC
  33.                 if ( PacketOK( pPacketHeader ) )
  34.                 {
  35.                         if ( (RxReadPtrOffset + PktLength) > RX_BUFFER_SIZE )
  36.                                 //wrap around to end of RxBuffer
  37.                                 memcpy( RxBuffer + RX_BUFFER_SIZE , RxBuffer,
  38.                                                         (RxReadPtrOffset + PktLength - RX_BUFFER_SIZE) );
  39.                         //copy the packet out here
  40.                         CopyPacket(pIncomePacket,PktLength - 4);//don't copy 4 bytes CRC
  41.                         //update Read Pointer
  42.                         RxReadPtrOffset = (RxReadPtrOffset + PktLength + 4 + 3) & RX_READ_POINTER_MASK;
  43.                         //4:for header length(PktLength include 4 bytes CRC)
  44.                         //3:for dword alignment
  45.                         outport( IOBase + CAPR, RxReadPtrOffset - 0x10); //-4:avoid overflow
  46.                 }
  47.                 else
  48.                 {
  49.                         ResetRx();
  50.                         break;
  51.                 }
  52.                 TmpCMD = inportb(IOBase + CR);
  53.         }
  54.         while (!(TmpCMD & CR_BUFE));
  55.         }
  56.         return (TRUE);
  57. //Done
  58. }
复制代码
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP