Chinaunix首页 | 论坛 | 博客
  • 博客访问: 505356
  • 博文数量: 157
  • 博客积分: 3010
  • 博客等级: 中校
  • 技术积分: 1608
  • 用 户 组: 普通用户
  • 注册时间: 2008-08-16 09:30
文章存档

2010年(155)

2008年(2)

我的朋友

分类:

2010-03-11 20:56:01


四、网卡的数据接收

内核如何从网卡接受数据,传统的经典过程:
引用
1、数据到达网卡;
2、网卡产生一个中断给内核;
3、内核使用I/O指令,从网卡I/O区域中去读取数据;



我们在许多网卡驱动中,都可以在网卡的中断函数中见到这一过程。

但是,这一种方法,有一种重要的问题,就是大流量的数据来到,网卡会产生大量的中断,内核在中断上下文中,会浪费大量的资源来处理中断本身。所以,一个问题是,“可不可以不使用中断”,这就是轮询技术,所谓NAPI技术,说来也不神秘,就是说,内核屏蔽中断,然后隔一会儿就去问网卡,“你有没有数据啊?”……

从这个描述本身可以看到,哪果数据量少,轮询同样占用大量的不必要的CPU资源,大家各有所长吧,呵呵……

OK,另一个问题,就是从网卡的I/O区域,包括I/O寄存器或I/O内存中去读取数据,这都要CPU去读,也要占用CPU资源,“CPU从I/O区域读,然后把它放到内存(这个内存指的是系统本身的物理内存,跟外设的内存不相干,也叫主内存)中”。于是自然地,就想到了DMA技术——让网卡直接从主内存之间读写它们的I/O数据,CPU,这儿不干你事,自己找乐子去:
引用
1、首先,内核在主内存中为收发数据建立一个环形的缓冲队列(通常叫DMA环形缓冲区)。
2、内核将这个缓冲区通过DMA映射,把这个队列交给网卡;
3、网卡收到数据,就直接放进这个环形缓冲区了——也就是直接放进主内存了;然后,向系统产生一个中断;
4、内核收到这个中断,就取消DMA映射,这样,内核就直接从主内存中读取数据;



——呵呵,这一个过程比传统的过程少了不少工作,因为设备直接把数据放进了主内存,不需要CPU的干预,效率是不是提高不少?

对应以上4步,来看它的具体实现:
1、分配环形DMA缓冲区
Linux内核中,用skb来描述一个缓存,所谓分配,就是建立一定数量的skb,然后把它们组织成一个双向链表;

2、建立DMA映射
内核通过调用
dma_map_single(struct device *dev,void *buffer,size_t size,enum dma_data_direction direction)
建立映射关系。
struct device *dev,描述一个设备;
buffer:把哪个地址映射给设备;也就是某一个skb——要映射全部,当然是做一个双向链表的循环即可;
size:缓存大小;
direction:映射方向——谁传给谁:一般来说,是“双向”映射,数据在设备和内存之间双向流动;

对于PCI设备而言(网卡一般是PCI的),通过另一个包裹函数pci_map_single,这样,就把buffer交给设备了!设备可以直接从里边读/取数据。

3、这一步由硬件完成;

4、取消映射
dma_unmap_single,对PCI而言,大多调用它的包裹函数pci_unmap_single,不取消的话,缓存控制权还在设备手里,要调用它,把主动权掌握在CPU手里——因为我们已经接收到数据了,应该由CPU把数据交给上层网络栈;

当然,不取消之前,通常要读一些状态位信息,诸如此类,一般是调用
dma_sync_single_for_cpu()
让CPU在取消映射前,就可以访问DMA缓冲区中的内容。

关于DMA映射的更多内容,可以参考《Linux设备驱动程序》“内存映射和DMA”章节相关内容!

OK,有了这些知识,我们就可以来看e100的代码了,它跟上面讲的步骤基本上一样的——绕了这么多圈子,就是想绕到e100上面了,呵呵!


在e100_open函数中,调用e100_up,我们前面分析它时,略过了一个重要的东东,就是环形缓冲区的建立,这一步,是通过
e100_rx_alloc_list函数调用完成的:

[url=]view plain[/url][url=]print[/url][url=]?[/url]

[list=1][*]static
int e100_rx_alloc_list(struct nic *nic)  [*]{  [*]        struct rx *rx;  [*]        unsigned int i, count = nic->params.rfds.count;  [*]
[*]        nic->rx_to_use = nic->rx_to_clean = NULL;  [*]        nic->ru_running = RU_UNINITIALIZED;  [*]
[*]        /*结构struct rx用来描述一个缓冲区节点,这里分配了count个*/
[*]        if(!(nic->rxs = kmalloc(sizeof(struct rx) * count, GFP_ATOMIC)))  [*]                return -ENOMEM;  [*]        memset(nic->rxs, 0, sizeof(struct rx) * count);  [*]
[*]        /*虽然是连续分配的,不过还是遍历它,建立双向链表,然后为每一个rx的skb指针分员分配空间 [*]        skb用来描述内核中的一个数据包,呵呵,说到重点了*/
[*]        for(rx = nic->rxs, i = 0; i < count; rx++, i++) {  [*]                rx->next = (i + 1 < count) ? rx + 1 : nic->rxs;  [*]                rx->prev = (i == 0) ? nic->rxs + count - 1 : rx - 1;  [*]                if(e100_rx_alloc_skb(nic, rx)) {                /*分配缓存*/
[*]                        e100_rx_clean_list(nic);  [*]                        return -ENOMEM;  [*]                }  [*]        }  [*]
[*]        nic->rx_to_use = nic->rx_to_clean = nic->rxs;  [*]        nic->ru_running = RU_SUSPENDED;  [*]
[*]        return 0;  [*]}  [/list]
static int e100_rx_alloc_list(struct nic *nic){        struct rx *rx;        unsigned int i, count = nic->params.rfds.count;        nic->rx_to_use = nic->rx_to_clean = NULL;        nic->ru_running = RU_UNINITIALIZED;        /*结构struct rx用来描述一个缓冲区节点,这里分配了count个*/        if(!(nic->rxs = kmalloc(sizeof(struct rx) * count, GFP_ATOMIC)))                return -ENOMEM;        memset(nic->rxs, 0, sizeof(struct rx) * count);        /*虽然是连续分配的,不过还是遍历它,建立双向链表,然后为每一个rx的skb指针分员分配空间        skb用来描述内核中的一个数据包,呵呵,说到重点了*/        for(rx = nic->rxs, i = 0; i < count; rx++, i++) {                rx->next = (i + 1 < count) ? rx + 1 : nic->rxs;                rx->prev = (i == 0) ? nic->rxs + count - 1 : rx - 1;                if(e100_rx_alloc_skb(nic, rx)) {                /*分配缓存*/                        e100_rx_clean_list(nic);                        return -ENOMEM;                }        }        nic->rx_to_use = nic->rx_to_clean = nic->rxs;        nic->ru_running = RU_SUSPENDED;        return 0;}

[url=]view plain[/url][url=]print[/url][url=]?[/url]

[list=1][*]#define RFD_BUF_LEN (sizeof(struct rfd) + VLAN_ETH_FRAME_LEN)
[*]static
inline
int e100_rx_alloc_skb(struct nic *nic, struct rx *rx)  [*]{  [*]        /*skb缓存的分配,是通过调用系统函数dev_alloc_skb来完成的,它同内核栈中通常调用alloc_skb的区别在于, [*]        它是原子的,所以,通常在中断上下文中使用*/
[*]        if(!(rx->skb = dev_alloc_skb(RFD_BUF_LEN + NET_IP_ALIGN)))  [*]                return -ENOMEM;  [*]
[*]        /*初始化必要的成员 */
[*]        rx->skb->dev = nic->netdev;  [*]        skb_reserve(rx->skb, NET_IP_ALIGN);  [*]        /*这里在数据区之前,留了一块sizeof(struct rfd) 这么大的空间,该结构的 [*]        一个重要作用,用来保存一些状态信息,比如,在接收数据之前,可以先通过 [*]        它,来判断是否真有数据到达等,诸如此类*/
[*]        memcpy(rx->skb->data, &nic->blank_rfd, sizeof(struct rfd));  [*]        /*这是最关键的一步,建立DMA映射,把每一个缓冲区rx->skb->data都映射给了设备,缓存区节点 [*]        rx利用dma_addr保存了每一次映射的地址,这个地址后面会被用到*/
[*]        rx->dma_addr = pci_map_single(nic->pdev, rx->skb->data,  [*]                RFD_BUF_LEN, PCI_DMA_BIDIRECTIONAL);  [*]
[*]        if(pci_dma_mapping_error(rx->dma_addr)) {  [*]                dev_kfree_skb_any(rx->skb);  [*]                rx->skb = 0;  [*]                rx->dma_addr = 0;  [*]                return -ENOMEM;  [*]        }  [*]
[*]        /* Link the RFD to end of RFA by linking previous RFD to [*]         * this one, and clearing EL bit of previous.  */
[*]        if(rx->prev->skb) {  [*]                struct rfd *prev_rfd = (struct rfd *)rx->prev->skb->data;  [*]                /*put_unaligned(val,ptr);用到把var放到ptr指针的地方,它能处理处理内存对齐的问题 [*]                prev_rfd是在缓冲区开始处保存的一点空间,它的link成员,也保存了映射后的地址*/
[*]                put_unaligned(cpu_to_le32(rx->dma_addr),  [*]                        (u32 *)&prev_rfd->link);  [*]                wmb();  [*]                prev_rfd->command &= ~cpu_to_le16(cb_el);  [*]                pci_dma_sync_single_for_device(nic->pdev, rx->prev->dma_addr,  [*]                        sizeof(struct rfd), PCI_DMA_TODEVICE);  [*]        }  [*]
[*]        return 0;  [*]}  [/list]
#define RFD_BUF_LEN (sizeof(struct rfd) + VLAN_ETH_FRAME_LEN)static inline int e100_rx_alloc_skb(struct nic *nic, struct rx *rx){        /*skb缓存的分配,是通过调用系统函数dev_alloc_skb来完成的,它同内核栈中通常调用alloc_skb的区别在于,        它是原子的,所以,通常在中断上下文中使用*/        if(!(rx->skb = dev_alloc_skb(RFD_BUF_LEN + NET_IP_ALIGN)))                return -ENOMEM;        /*初始化必要的成员 */        rx->skb->dev = nic->netdev;        skb_reserve(rx->skb, NET_IP_ALIGN);        /*这里在数据区之前,留了一块sizeof(struct rfd) 这么大的空间,该结构的        一个重要作用,用来保存一些状态信息,比如,在接收数据之前,可以先通过        它,来判断是否真有数据到达等,诸如此类*/        memcpy(rx->skb->data, &nic->blank_rfd, sizeof(struct rfd));        /*这是最关键的一步,建立DMA映射,把每一个缓冲区rx->skb->data都映射给了设备,缓存区节点        rx利用dma_addr保存了每一次映射的地址,这个地址后面会被用到*/        rx->dma_addr = pci_map_single(nic->pdev, rx->skb->data,                RFD_BUF_LEN, PCI_DMA_BIDIRECTIONAL);        if(pci_dma_mapping_error(rx->dma_addr)) {                dev_kfree_skb_any(rx->skb);                rx->skb = 0;                rx->dma_addr = 0;                return -ENOMEM;        }        /* Link the RFD to end of RFA by linking previous RFD to         * this one, and clearing EL bit of previous.  */        if(rx->prev->skb) {                struct rfd *prev_rfd = (struct rfd *)rx->prev->skb->data;                /*put_unaligned(val,ptr);用到把var放到ptr指针的地方,它能处理处理内存对齐的问题                prev_rfd是在缓冲区开始处保存的一点空间,它的link成员,也保存了映射后的地址*/                put_unaligned(cpu_to_le32(rx->dma_addr),                        (u32 *)&prev_rfd->link);                wmb();                prev_rfd->command &= ~cpu_to_le16(cb_el);                pci_dma_sync_single_for_device(nic->pdev, rx->prev->dma_addr,                        sizeof(struct rfd), PCI_DMA_TODEVICE);        }        return 0;}
e100_rx_alloc_list函数在一个循环中,建立了环形缓冲区,并调用e100_rx_alloc_skb为每个缓冲区分配了空间,并做了
DMA映射。这样,我们就可以来看接收数据的过程了。

前面我们讲过,中断函数中,调用netif_rx_schedule,表明使用轮询技术,系统会在未来某一时刻,调用设备的poll函数:

[url=]view plain[/url][url=]print[/url][url=]?[/url]

[list=1][*]static
int e100_poll(struct net_device *netdev, int *budget)  [*]{  [*]        struct nic *nic = netdev_priv(netdev);  [*]        unsigned int work_to_do = min(netdev->quota, *budget);  [*]        unsigned int work_done = 0;  [*]        int tx_cleaned;  [*]
[*]        e100_rx_clean(nic, &work_done, work_to_do);  [*]        tx_cleaned = e100_tx_clean(nic);  [*]
[*]        /* If no Rx and Tx cleanup work was done, exit polling mode. */
[*]        if((!tx_cleaned && (work_done == 0)) || !netif_running(netdev)) {  [*]                netif_rx_complete(netdev);  [*]                e100_enable_irq(nic);  [*]                return 0;  [*]        }  [*]
[*]        *budget -= work_done;  [*]        netdev->quota -= work_done;  [*]
[*]        return 1;  [*]}  [/list]
static int e100_poll(struct net_device *netdev, int *budget){        struct nic *nic = netdev_priv(netdev);        unsigned int work_to_do = min(netdev->quota, *budget);        unsigned int work_done = 0;        int tx_cleaned;        e100_rx_clean(nic, &work_done, work_to_do);        tx_cleaned = e100_tx_clean(nic);        /* If no Rx and Tx cleanup work was done, exit polling mode. */        if((!tx_cleaned && (work_done == 0)) || !netif_running(netdev)) {                netif_rx_complete(netdev);                e100_enable_irq(nic);                return 0;        }        *budget -= work_done;        netdev->quota -= work_done;        return 1;}
目前,我们只关心rx,所以,e100_rx_clean函数就成了我们关注的对像,它用来从缓冲队列中接收全部数据(这或许是取名为clean的原因吧!):

[url=]view plain[/url][url=]print[/url][url=]?[/url]

[list=1][*]static
inline
void e100_rx_clean(struct nic *nic, unsigned int *work_done,  [*]        unsigned int work_to_do)  [*]{  [*]        struct rx *rx;  [*]        int restart_required = 0;  [*]        struct rx *rx_to_start = NULL;  [*]
[*]        /* are we already rnr? then pay attention!!! this ensures that [*]         * the state machine progression never allows a start with a  [*]         * partially cleaned list, avoiding a race between hardware [*]         * and rx_to_clean when in NAPI mode */
[*]        if(RU_SUSPENDED == nic->ru_running)  [*]                restart_required = 1;  [*]
[*]        /* 函数最重要的工作,就是遍历环形缓冲区,接收数据*/
[*]        for(rx = nic->rx_to_clean; rx->skb; rx = nic->rx_to_clean = rx->next) {  [*]                int err = e100_rx_indicate(nic, rx, work_done, work_to_do);  [*]                if(-EAGAIN == err) {  [*]                        /* hit quota so have more work to do, restart once [*]                         * cleanup is complete */
[*]                        restart_required = 0;  [*]                        break;  [*]                } else
if(-ENODATA == err)  [*]                        break; /* No more to clean */
[*]        }  [*]
[*]        /* save our starting point as the place we'll restart the receiver */
[*]        if(restart_required)  [*]                rx_to_start = nic->rx_to_clean;  [*]
[*]        /* Alloc new skbs to refill list */
[*]        for(rx = nic->rx_to_use; !rx->skb; rx = nic->rx_to_use = rx->next) {  [*]                if(unlikely(e100_rx_alloc_skb(nic, rx)))  [*]                        break; /* Better luck next time (see watchdog) */
[*]        }  [*]
[*]        if(restart_required) {  [*]                // ack the rnr?
[*]                writeb(stat_ack_rnr, &nic->csr->scb.stat_ack);  [*]                e100_start_receiver(nic, rx_to_start);  [*]                if(work_done)  [*]                        (*work_done)++;  [*]        }  [*]}  [/list]
static inline void e100_rx_clean(struct nic *nic, unsigned int *work_done,        unsigned int work_to_do){        struct rx *rx;        int restart_required = 0;        struct rx *rx_to_start = NULL;        /* are we already rnr? then pay attention!!! this ensures that         * the state machine progression never allows a start with a          * partially cleaned list, avoiding a race between hardware         * and rx_to_clean when in NAPI mode */        if(RU_SUSPENDED == nic->ru_running)                restart_required = 1;        /* 函数最重要的工作,就是遍历环形缓冲区,接收数据*/        for(rx = nic->rx_to_clean; rx->skb; rx = nic->rx_to_clean = rx->next) {                int err = e100_rx_indicate(nic, rx, work_done, work_to_do);                if(-EAGAIN == err) {                        /* hit quota so have more work to do, restart once                         * cleanup is complete */                        restart_required = 0;                        break;                } else if(-ENODATA == err)                        break; /* No more to clean */        }        /* save our starting point as the place we'll restart the receiver */        if(restart_required)                rx_to_start = nic->rx_to_clean;        /* Alloc new skbs to refill list */        for(rx = nic->rx_to_use; !rx->skb; rx = nic->rx_to_use = rx->next) {                if(unlikely(e100_rx_alloc_skb(nic, rx)))                        break; /* Better luck next time (see watchdog) */        }        if(restart_required) {                // ack the rnr?                writeb(stat_ack_rnr, &nic->csr->scb.stat_ack);                e100_start_receiver(nic, rx_to_start);                if(work_done)                        (*work_done)++;        }}

gunguymadman 发表于 2009-06-14 22:32

[url=]view plain[/url][url=]print[/url][url=]?[/url]

[list=1][*]static
inline
int e100_rx_indicate(struct nic *nic, struct rx *rx,  [*]        unsigned int *work_done, unsigned int work_to_do)  [*]{  [*]        struct sk_buff *skb = rx->skb;  [*]        struct rfd *rfd = (struct rfd *)skb->data;  [*]        u16 rfd_status, actual_size;  [*]
[*]        if(unlikely(work_done && *work_done >= work_to_do))  [*]                return -EAGAIN;  [*]
[*]        /* 读取数据之前,也就是取消DMA映射之前,需要先读取cb_complete 状态位, [*]        以确定数据是否真的准备好了,并且,rfd的actual_size中,也包含了真实的数据大小 [*]        pci_dma_sync_single_for_cpu函数前面已经介绍过,它让CPU在取消DMA映射之前,具备 [*]        访问DMA缓存的能力*/
[*]        pci_dma_sync_single_for_cpu(nic->pdev, rx->dma_addr,  [*]                sizeof(struct rfd), PCI_DMA_FROMDEVICE);  [*]        rfd_status = le16_to_cpu(rfd->status);  [*]
[*]        DPRINTK(RX_STATUS, DEBUG, "status=0x%04X\n", rfd_status);  [*]
[*]        /* If data isn't ready, nothing to indicate */
[*]        if(unlikely(!(rfd_status & cb_complete)))  [*]                return -ENODATA;  [*]
[*]        /* Get actual data size */
[*]        actual_size = le16_to_cpu(rfd->actual_size) & 0x3FFF;  [*]        if(unlikely(actual_size > RFD_BUF_LEN - sizeof(struct rfd)))  [*]                actual_size = RFD_BUF_LEN - sizeof(struct rfd);  [*]
[*]        /* 取消映射,因为通过DMA,网卡已经把数据放在了主内存中,这里一取消,也就意味着, [*]        CPU可以处理主内存中的数据了 */
[*]        pci_unmap_single(nic->pdev, rx->dma_addr,  [*]                RFD_BUF_LEN, PCI_DMA_FROMDEVICE);  [*]
[*]        /* this allows for a fast restart without re-enabling interrupts */
[*]        if(le16_to_cpu(rfd->command) & cb_el)  [*]                nic->ru_running = RU_SUSPENDED;  [*]
[*]        /*正确地设置data指针,因为最前面有一个sizeof(struct rfd)大小区域,跳过它*/
[*]        skb_reserve(skb, sizeof(struct rfd));  [*]        /*更新skb的tail和len指针,也是就更新接收到这么多数据的长度*/
[*]        skb_put(skb, actual_size);  [*]        /*设置协议位*/
[*]        skb->protocol = eth_type_trans(skb, nic->netdev);  [*]
[*]        if(unlikely(!(rfd_status & cb_ok))) {  [*]                /* Don't indicate if hardware indicates errors */
[*]                nic->net_stats.rx_dropped++;  [*]                dev_kfree_skb_any(skb);  [*]        } else
if(actual_size > nic->netdev->mtu + VLAN_ETH_HLEN) {  [*]                /* Don't indicate oversized frames */
[*]                nic->rx_over_length_errors++;  [*]                nic->net_stats.rx_dropped++;  [*]                dev_kfree_skb_any(skb);  [*]        } else {  [*]                /*网卡驱动要做的最后一步,就是统计接收计数器,设置接收时间戳,然后调用netif_receive_skb, [*]                把数据包交给上层协议栈,自己的光荣始命也就完成了*/
[*]                nic->net_stats.rx_packets++;  [*]                nic->net_stats.rx_bytes += actual_size;  [*]                nic->netdev->last_rx = jiffies;  [*]                netif_receive_skb(skb);  [*]                if(work_done)  [*]                        (*work_done)++;  [*]        }  [*]
[*]        rx->skb = NULL;  [*]
[*]        return 0;  [*]}  [/list]
static inline int e100_rx_indicate(struct nic *nic, struct rx *rx,        unsigned int *work_done, unsigned int work_to_do){        struct sk_buff *skb = rx->skb;        struct rfd *rfd = (struct rfd *)skb->data;        u16 rfd_status, actual_size;        if(unlikely(work_done && *work_done >= work_to_do))                return -EAGAIN;        /* 读取数据之前,也就是取消DMA映射之前,需要先读取cb_complete 状态位,        以确定数据是否真的准备好了,并且,rfd的actual_size中,也包含了真实的数据大小        pci_dma_sync_single_for_cpu函数前面已经介绍过,它让CPU在取消DMA映射之前,具备        访问DMA缓存的能力*/        pci_dma_sync_single_for_cpu(nic->pdev, rx->dma_addr,                sizeof(struct rfd), PCI_DMA_FROMDEVICE);        rfd_status = le16_to_cpu(rfd->status);        DPRINTK(RX_STATUS, DEBUG, "status=0x%04X\n", rfd_status);        /* If data isn't ready, nothing to indicate */        if(unlikely(!(rfd_status & cb_complete)))                return -ENODATA;        /* Get actual data size */        actual_size = le16_to_cpu(rfd->actual_size) & 0x3FFF;        if(unlikely(actual_size > RFD_BUF_LEN - sizeof(struct rfd)))                actual_size = RFD_BUF_LEN - sizeof(struct rfd);        /* 取消映射,因为通过DMA,网卡已经把数据放在了主内存中,这里一取消,也就意味着,        CPU可以处理主内存中的数据了 */        pci_unmap_single(nic->pdev, rx->dma_addr,                RFD_BUF_LEN, PCI_DMA_FROMDEVICE);        /* this allows for a fast restart without re-enabling interrupts */        if(le16_to_cpu(rfd->command) & cb_el)                nic->ru_running = RU_SUSPENDED;                /*正确地设置data指针,因为最前面有一个sizeof(struct rfd)大小区域,跳过它*/        skb_reserve(skb, sizeof(struct rfd));        /*更新skb的tail和len指针,也是就更新接收到这么多数据的长度*/        skb_put(skb, actual_size);        /*设置协议位*/        skb->protocol = eth_type_trans(skb, nic->netdev);        if(unlikely(!(rfd_status & cb_ok))) {                /* Don't indicate if hardware indicates errors */                nic->net_stats.rx_dropped++;                dev_kfree_skb_any(skb);        } else if(actual_size > nic->netdev->mtu + VLAN_ETH_HLEN) {                /* Don't indicate oversized frames */                nic->rx_over_length_errors++;                nic->net_stats.rx_dropped++;                dev_kfree_skb_any(skb);        } else {                /*网卡驱动要做的最后一步,就是统计接收计数器,设置接收时间戳,然后调用netif_receive_skb,                把数据包交给上层协议栈,自己的光荣始命也就完成了*/                nic->net_stats.rx_packets++;                nic->net_stats.rx_bytes += actual_size;                nic->netdev->last_rx = jiffies;                netif_receive_skb(skb);                if(work_done)                        (*work_done)++;        }        rx->skb = NULL;        return 0;}
网卡驱动执行到这里,数据接收的工作,也就处理完成了。但是,使用这一种方法的驱动,省去了网络栈中一个重要的内容,就是
“队列层”,让我们来看看,传统中断接收数据包模式下,使用netif_rx函数调用,又会发生什么。
阅读(1254) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2010-08-26 13:28:24

格式!!!