首页 linux网卡驱动分析

linux网卡驱动分析

举报
开通vip

linux网卡驱动分析 学习内核---Linux 网卡驱动分析 学习应该是一个先把问题简单化,在把问题复杂化的过程。一开始就着手处理复杂的问题, 难免让人有心惊胆颤,捉襟见肘的感觉。读 Linux 网卡驱动也是一样。那长长的源码夹杂着 那些我们陌生的变量和符号,望而生畏便是理所当然的了。不要担心,事情总有解决的办法, 先把一些我们管不着的代码切割出去,留下必须的部分,把框架掌握了,哪其他的事情自然 就水到渠成了,这是笔者的心得。 一般在使用的 Linux 网卡驱动代码动辄 3000 行左右,这个代码量以及它所表达出来的 知识量...

linux网卡驱动分析
学习内核---Linux 网卡驱动分析 学习应该是一个先把问题简单化,在把问题复杂化的过程。一开始就着手处理复杂的问题, 难免让人有心惊胆颤,捉襟见肘的感觉。读 Linux 网卡驱动也是一样。那长长的源码夹杂着 那些我们陌生的变量和符号,望而生畏便是理所当然的了。不要担心,事情总有解决的办法, 先把一些我们管不着的代码切割出去,留下必须的部分,把框架掌握了,哪其他的事情自然 就水到渠成了,这是笔者的心得。 一般在使用的 Linux 网卡驱动代码动辄 3000 行左右,这个代码量以及它所表达出来的 知识量无疑是庞大的,我们有没有办法缩短一下这个代码量,使我们的学习变的简单些呢, 经过笔者的不懈努力,在仍然能够使网络设备正常工作的前提下,把它缩减到了 600 多行, 我们把暂时还用不上的功能先割出去。这样一来,事情就简单多了,真的就剩下一个框架了。 下面我们就来剖析这个可以执行的框架。 限于篇幅,以下分析用到的所有涉及到内核中的函数代码,我都不予列出,但给出在哪 个具体文件中,请读者自行查阅。 首先,我们来看看设备的初始化。当我们正确编译完我们的程序后,我们就需要把生成 的目标文件加载到内核中去,我们会先 ifconfig eth0 down 和 rmmod 8139too 来卸载正在使用 的网卡驱动,然后 insmod 8139too.o 把我们的驱动加载进去(其中 8139too.o 是我们编译生 成的目标文件)。就像 C 程序有主函数 main()一样,模块也有第一个执行的函数,即 module_init(rtl8139_init_module);在我们的程序中,rtl8139_init_module()在 insmod 之后首 先执行,它的代码如下: static int __init rtl8139_init_module (void) { return pci_module_init (&rtl8139_pci_driver); } 它直接调用了 pci_module_init(),这个函数代码在 Linux/drivers/net/eepro100.c 中,并 且把 rtl8139_pci_driver(这个结构是在我们的驱动代码里定义的,它是驱动程序和 PCI 设 备联系的纽带)的地址作为参数传给了它。 rtl8139_pci_driver 定义如下: static struct pci_driver rtl8139_pci_driver = { name: MODNAME, id_table: rtl8139_pci_tbl, probe: rtl8139_init_one, remove: rtl8139_remove_one, }; pci_module_init()在驱动代码里没有定义,你一定想到了,它是 Linux 内核提供给模 块是一个 标准 excel标准偏差excel标准偏差函数exl标准差函数国标检验抽样标准表免费下载红头文件格式标准下载 接口,那么这个接口都干了些什么,笔者跟踪了这个函数。里面调用了 pci_register_driver(),这个函数代码在 Linux/drivers/pci/pci.c 中, pci_register_driver 做了 三件事情。 ①是把带过来的参数 rtl8139_pci_driver 在内核中进行了注册,内核中有一个 PCI 设备 的大的链表,这里负责把这个 PCI 驱动挂到里面去。 ②是查看总线上所有 PCI 设备(网卡设备属于 PCI 设备的一种)的配置空间如果发现 标识信息与 rtl8139_pci_driver 中的 id_table 相同即 rtl8139_pci_tbl,而它的定义如下: static struct pci_device_id rtl8139_pci_tbl[] __devinitdata = { {0x10ec, 0x8129, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 1}, {PCI_ANY_ID, 0x8139, 0x10ec, 0x8139, 0, 0,0 }, {0,} }; , 那么就说明这个驱动程序就是用来驱动这个设备的,于是调用 rtl8139_pci_driver 中的 probe 函数即 rtl8139_init_one, 这个函数是在我们的驱动程序中定义了的,它是用来初始化 整个设备和做一些准备工作。这里需要注意一下 pci_device_id 是内核定义的用来辨别不同 PCI 设备的一个结构,例如在我们这里 0x10ec 代表的是 Realtek 公司,我们扫描 PCI 设备配 置空间如果发现有 Realtek 公司制造的设备时,两者就对上了。当然对上了公司号后还得看 其他的设备号什么的,都对上了才说明这个驱动是可以为这个设备服务的。 ③是把这个 rtl8139_pci_driver 结构挂在这个设备的数据结构(pci_dev)上,表示这个 设备从此就有了自己的驱动了。而驱动也找到了它服务的对象了。 PCI 是一个总线标准,PCI 总线上的设备就是 PCI 设备,这些设备有很多类型,当然也 包括网卡设备,每一个 PCI 设备在内核中抽象为一个数据结构 pci_dev,它描述了一个 PCI 设备的所有的特性,具体请查询相关文档,本文限于篇幅无法详细描述。但是有几个地方和 驱动程序的关系特别大,必须予以说明。PCI 设备都遵守 PCI 标准,这个部分所有的 PCI 设备都是一样的,每个 PCI 设备都有一段寄存器存储着配置空间,这一部分格式是一样的, 比如第一个寄存器总是生产商号码,如 Realtek 就是 10ec,而 Intel 则是另一个数字,这些都 是商家像标准组织申请的,是肯定不同的。我就可以通过配置空间来辨别其生产商,设备号, 不论你什么平台,x86 也好,ppc 也好,他们都是同一的标准格式。当然光有这些 PCI 配置 空间的统一格式还是不够的,比如说人类,都有鼻子和眼睛,但并不是所有人的鼻子和眼睛 都长的一样的。网卡设备是 PCI 设备必须遵守规则,在设备里集成了 PCI 配置空间,但它 是一个网卡就必须同时集成能控制网卡工作的寄存器。而寄存器的访问就成了一个问题。在 Linux 里面我们是把这些寄存器映射到主存虚拟空间上的,换句话说我们的 CPU 访存指令 就可以访问到这些处于外设中的控制寄存器。总结一下 PCI 设备主要包括两类空间,一个 是配置空间,它是操作系统或 BIOS 控制外设的统一格式的空间,CPU 指令不能访问,访问 这个空间要借助BIOS功能,事实上Linux的访问配置空间的函数是通过CPU指令驱使BIOS 来完成读写访问的。而另一类是普通的控制寄存器空间,这一部分映射完后 CPU 可以访问 来控制设备工作。 现在我们回到上面 pci_register_driver 的第二步,如果找到相关设备和我们的 pci_device_id 结构数组对上号了,说明我们找到服务对象了,则调用 rtl8139_init_one,它主要 做了七件事: ① 建立 net_device 结构,让它在内核中代表这个网络设备。但是读者可能会问,pci_dev 也是代表着这个设备,那么两者有什么区别呢,正如我们上面讨论的,网卡设备既要遵循 PCI 规范,也要担负起其作为网卡设备的职责,于是就分了两块,pci_dev 用来负责网卡的 PCI 规范,而这里要说的 net_device 则是负责网卡的网络设备这个职责。 dev = init_etherdev (NULL, sizeof (*tp)); if (dev == NULL) { printk ("unable to alloc new ethernet\n"); return -ENOMEM; } tp = dev->priv; init_etherdev 函数在 Linux/drivers/net/net_init.c 中,在这个函数中分配了 net_device 的内 存并进行了初步的初始化。这里值得注意的是 net_device 中的一个成员 priv,它代表着不同 网卡的私有数据,比如 Intel 的网卡和 Realtek 的网卡在内核中都是以 net_device 来代表。但 是他们是有区别的,比如 Intel 和 Realtek 实现同一功能的方法不一样,这些都是靠着 priv 来体现。所以这里把拿出来同 net_device 相提并论。分配内存时,net_device 中除了 priv 以 外的成员都是固定的,而 priv 的大小是可以任意的,所以分配时要把 priv 的大小传过去。 ②开启这个设备(其实是开启了设备的寄存器映射到内存的功能) rc = pci_enable_device (pdev); if (rc) goto err_out; pci_enable_device 也是一个内核开发出来的接口,代码在 drivers/pci/pci.c 中,笔者跟踪 发现这个函数主要就是把 PCI 配置空间的 Command 域的 0 位和 1 位置成了 1,从而达到了 开启设备的目的,因为 rtl8139 的官方 datasheet 中,说明了这两位的作用就是开启内存映射 和 I/O 映射,如果不开的话,那我们以上讨论的把控制寄存器空间映射到内存空间的这一功 能就被屏蔽了,这对我们是非常不利的,除此之外,pci_enable_device 还做了些中断开启工 作。 ③获得各项资源 mmio_start = pci_resource_start (pdev, 1); mmio_end = pci_resource_end (pdev, 1); mmio_flags = pci_resource_flags (pdev, 1); mmio_len = pci_resource_len (pdev, 1); 读者也许疑问我们的寄存器被映射到内存中的什么地方是什么时候有谁决定的呢。是这 样的,在硬件加电初始化时,BIOS 固件同统一检查了所有的 PCI 设备,并统一为他们分配 了一个和其他互不冲突的地址,让他们的驱动程序可以向这些地址映射他们的寄存器,这些 地址被 BIOS 写进了各个设备的配置空间,因为这个活动是一个 PCI 的标准的活动,所以自 然写到各个设备的配置空间里而不是他们风格各异的控制寄存器空间里。当然只有 BIOS 可 以访问配置空间。当操作系统初始化时,他为每个 PCI 设备分配了 pci_dev 结构,并且把 BIOS 获得的并写到了配置空间中的地址读出来写到了 pci_dev 中的 resource 字段中。这样 以后我们在读这些地址就不需要在访问配置空间了,直接跟 pci_dev 要就可以了,我们这里 的四个函数就是直接从 pci_dev 读出了相关数据,代码在 include/linux/pci.h 中。定义如下: #define pci_resource_start(dev,bar) ((dev)->resource[(bar)].start) #define pci_resource_end(dev,bar) ((dev)->resource[(bar)].end) 这里需要说明一下,每个 PCI 设备有 0-5 一共 6 个地址空间,我们通常只使用前两个, 这里我们把参数 1 传给了 bar 就是使用内存映射的地址空间。 ④把得到的地址进行映射 ioaddr = ioremap (mmio_start, mmio_len); if (ioaddr == NULL) { printk ("cannot remap MMIO, aborting\n"); rc = -EIO; goto err_out_free_res; } ioremap 是内核提供的用来映射外设寄存器到主存的函数,我们要映射的地址已经从 pci_dev 中读了出来(上一步),这样就水到渠成的成功映射了而不会和其他地址有冲突。映射完了 有什么效果呢,我举个例子,比如某个网卡有 100 个寄存器,他们都是连在一块的,位置 是固定的,加入每个寄存器占 4 个字节,那么一共 400 个字节的空间被映射到内存成功后, ioaddr 就是这段地址的开头(注意 ioaddr 是虚拟地址,而 mmio_start 是物理地址,它是 BIOS 得到的,肯定是物理地址,而保护模式下 CPU 不认物理地址,只认虚拟地址),ioaddr+0 就 是第一个寄存器的地址,ioaddr+4 就是第二个寄存器地址(每个寄存器占 4 个字节),以此 类推,我们就能够在内存中访问到所有的寄存器进而操控他们了。 ⑤重启网卡设备 重启网卡设备是初始化网卡设备的一个重要部分,它的原理就是向寄存器中写入命令就 可以了(注意这里写寄存器,而不是配置空间,因为跟 PCI 没有什么关系),代码如下: writeb ((readb(ioaddr+ChipCmd) & ChipCmdClear) | CmdReset,ioaddr+ChipCmd); 是我们看到第二参数 ioaddr+ChipCmd,ChipCmd 是一个位移,使地址刚好对应的就是 ChipCmd 哪个寄存器,读者可以查阅官方 datasheet 得到这个位移量,我们在程序中定义的 这个值为:ChipCmd = 0x37;与 datasheet是吻合的。我们把这个命令寄存器中相应位(RESET) 置 1 就可以完成操作。 ⑥获得 MAC 地址,并把它存储到 net_device 中。 for(i = 0; i < 6; i++) { /* Hardware Address */ dev->dev_addr[i] = readb(ioaddr+i); dev->broadcast[i] = 0xff; } 我们可以看到读的地址是 ioaddr+0 到 ioaddr+5,读者查看官方 datasheet 会发现寄存器 地址空间的开头 6 个字节正好存的是这个网卡设备的 MAC 地址,MAC 地址是网络中标识 网卡的物理地址,这个地址在今后的收发数据包时会用的上。 ⑦向 net_device 中登记一些主要的函数 dev->open = rtl8139_open; dev->hard_start_xmit = rtl8139_start_xmit; dev->stop = rtl8139_close; 由于 dev(net_device)代表着设备,把这些函数注册完后,rtl8139_open 就是用于打开 这个设备, rtl8139_start_xmit 就是当应用程序要通过这个设备往外面发数据时被调用,具 体的其实这个函数是在网络 协议 离婚协议模板下载合伙人协议 下载渠道分销协议免费下载敬业协议下载授课协议下载 层中调用的,这就涉及到 Linux 网络协议栈的内容,不再 我们讨论之列,我们只是负责实现它。rtl8139_close 用来关掉这个设备。 好了,到此我们把 rtl8139_init_one 函数介绍完了,初始化个设备完了之后呢,我们通 过 ifconfig eth0 up 命令来把我们的设备激活。这个命令直接导致了我们刚刚注册的 rtl8139_open 的调用。这个函数激活了设备。这个函数主要做了三件事。 ①注册这个设备的中断处理函数。当网卡发送数据完成或者接收到数据时,是用中断的 形式来告知的,比如有数据从网线传来,中断也通知了我们,那么必须要有一个处理这个中 断的函数来完成数据的接收。关于 Linux 的中断机制不是我们详细讲解的范畴,有兴趣的可 以参考《Linux 内核源代码情景分析》,但是有个非常重要的资源我们必须注意,那就是中 断号的分配,和内存地址映射一样,中断号也是 BIOS 在初始化阶段分配并写入设备的配置 空间的,然后 Linux 在建立 pci_dev 时从配置空间读出这个中断号然后写入 pci_dev 的 irq 成 员中,所以我们注册中断程序需要中断号就是直接从 pci_dev 里取就可以了。 retval = request_irq (dev->irq, rtl8139_interrupt, SA_SHIRQ, dev->name, dev); if (retval) { return retval; } 我们注册的中断处理函数是 rtl8139_interrupt,也就是说当网卡发生中断(如数据到达) 时,中断控制器 8259A 把中断号发给 CPU, CPU 根据这个中断号找到处理程序,这里就 是 rtl8139_interrupt,然后执行。rtl8139_interrupt 也是在我们的程序中定义好了的,这是驱 动程序的一个重要的义务,也是一个基本的功能。request_irq 的代码在 arch/i386/kernel/irq.c 中。 ②分配发送和接收的缓存空间 根据官方文档,发送一个数据包的过程是这样的:先从应用程序中把数据包拷贝到一段 连续的内存中(这段内存就是我们这里要分配的缓存),然后把这段内存的地址写进网卡的 数据发送地址寄存器(TSAD)中,这个寄存器的偏移量是 TxAddr0 = 0x20。在把这个数据包的 长度写进另一个寄存器(TSD)中,它的偏移量是 TxStatus0 = 0x10。然后就把这段内存的 数据发送到网卡内部的发送缓冲中(FIFO),最后由这个发送缓冲区把数据发送到网线上。 好了现在创建这么一个发送和接收缓冲内存的目的已经很显然了。 tp->tx_bufs = pci_alloc_consistent(tp->pci_dev, TX_BUF_TOT_LEN, &tp->tx_bufs_dma); tp->rx_ring = pci_alloc_consistent(tp->pci_dev, RX_BUF_TOT_LEN, &tp->rx_ring_dma); tp 是 net_device 的 priv 的指针,tx_bufs 是发送缓冲内存的首地址,rx_ring 是接收缓存 内存的首地址,他们都是虚拟地址,而最后一个参数 tx_bufs_dma 和 rx_ring_dma 均是这一 段内存的物理地址。为什么同一个事物,既用虚拟地址来表示它还要用物理地址呢,是这样 的, CPU 执行程序用到这个地址时,用虚拟地址,而网卡设备向这些内存中存取数据时用 的是物理地址(因为网卡相对 CPU 属于头脑比较简单型的)。 pci_alloc_consistent 的代码在 Linux/arch/i386/kernel/pci-dma.c 中。 ③发送和接收缓冲区初始化和网卡开始工作的操作 RTL8139 有 4 个发送描述符(包括 4 个发送缓冲区的基地址寄存器(TSAD0-TSAD3) 和 4 个发送状态寄存器(TSD0-TSD3)。也就是说我们分配的缓冲区要分成四个等分并把这四 个空间的地址都写到相关寄存器里去,下面这段代码完成了这个操作。 for (i = 0; i < NUM_TX_DESC; i++) ((struct rtl8139_private*)dev->priv)->tx_buf[i] = &((struct rtl8139_private*)dev->priv)->tx_bufs[i * TX_BUF_SIZE]; 上面这段代码负责把发送缓冲区虚拟空间进行了分割。 for (i = 0; i < NUM_TX_DESC; i++) { writel(tp->tx_bufs_dma+(tp->tx_buf[i]tp->tx_bufs),ioaddr+TxAddr0+(i*4)); readl(ioaddr+TxAddr0+(i * 4)); } 上面这段代码负责把发送缓冲区物理空间进行了分割,并把它写到了相关寄存器中,这 样在网卡开始工作后就能够迅速定位和找到这些内存并存取他们的数据。 writel(tp->rx_ring_dma,ioaddr+RxBuf); 上面这行代码是把接收缓冲区的物理地址写到了相关寄存器中,这样网卡接收到数据后 就能准确的把数据从网卡中搬运到这些内存空间中,等待 CPU 来领走他们。 writeb((readb(ioaddr+ChipCmd) & ChipCmdClear) | CmdRxEnb | CmdTxEnb,ioaddr+ChipCmd); 重新 RESET 设备后,我们要激活设备的发送和接收的功能,上面这行代码就是向相关 寄存器中写入相应值,激活了设备的这些功能。 writel ((TX_DMA_BURST << TxDMAShift),ioaddr+TxConfig); 上面这行代码是向网卡的 TxConfig (位移是 0x44)寄存器中写入 TX_DMA_BURST << TxDMAShift 这个值,翻译过来就是 6<<8,就是把第 8 到第 10 这三位置成 110,查阅管法 文档发现 6 就是 110 代表着一次 DMA 的数据量为 1024 字节。 另外在这个阶段设置了接收数据的模式,和开启中断等等,限于篇幅由读者自行研究。 下面进入数据收发阶段: 当一个网络应用程序要向网络发送数据时,它要利用 Linux 的网络协议栈来解决一系列 问题,找到网卡设备的代表 net_device,由这个结构来找到并控制这个网卡设备来完成数据 包的发送,具体是调用 net_device 的 hard_start_xmit 成员函数,这是一个函数指针,在我们 的驱动程序里它指向的是 rtl8139_start_xmit,正是由它来完成我们的发送工作的,下面我们 就来剖析这个函数。它一共做了四件事。 ①检查这个要发送的数据包的长度,如果它达不到以太网帧的长度,必须采取措施进行 填充。 if( skb->len < ETH_ZLEN ){//if data_len < 60 if( (skb->data + ETH_ZLEN) <= skb->end ){ memset( skb->data + skb->len, 0x20, (ETH_ZLEN - skb->len) ); skb->len = (skb->len >= ETH_ZLEN) ? skb->len : ETH_ZLEN;} else{ printk("%s:(skb->data+ETH_ZLEN) > skb->end\n",__FUNCTION__); } } skb->data 和 skb->end 就决定了这个包的内容,如果这个包本身总共的长度(skb->end- skb->data)都达不到要求,那么想填也没地方填,就出错返回了,否则的话就填上。 ②把包的数据拷贝到我们已经建立好的发送缓存中。 memcpy (tp->tx_buf[entry], skb->data, skb->len); 其中 skb->data 就是数据包数据的地址,而 tp->tx_buf[entry]就是我们的发送缓存地址, 这样就完成了拷贝,忘记了这些内容的回头看看前面的介绍。 ③光有了地址和数据还不行,我们要让网卡知道这个包的长度,才能保证数据不多不少 精确的从缓存中截取出来搬运到网卡中去,这是靠写发送状态寄存器(TSD)来完成的。 writel(tp->tx_flag | (skb->len >= ETH_ZLEN ? skb->len : ETH_ZLEN),ioaddr+TxStatus0+(entry * 4)); 我们把这个包的长度和一些控制信息一起写进了状态寄存器,使网卡的工作有了依据。 ④判断发送缓存是否已经满了,如果满了在发就覆盖数据了,要停发。 if ((tp->cur_tx - NUM_TX_DESC) == tp->dirty_tx) netif_stop_queue (dev); 谈完了发送,我们开始谈接收,当有数据从网线上过来时,网卡产生一个中断,调用的 中断服务程序是 rtl8139_interrupt,它主要做了三件事。 ①从网卡的中断状态寄存器中读出状态值进行分析,status = readw(ioaddr+IntrStatus); if ((status &(PCIErr | PCSTimeout | RxUnderrun | RxOverflow | RxFIFOOver | TxErr | TxOK | RxErr | RxOK)) == 0) goto out; 上面代码说明如果上面这 9 种情况均没有的表示没什么好处理的了,退出。 ② if (status & (RxOK | RxUnderrun | RxOverflow | RxFIFOOver))/* Rx interrupt */ rtl8139_rx_interrupt (dev, tp, ioaddr); 如果是以上 4 种情况,属于接收信号,调用 rtl8139_rx_interrupt 进行接收处理。 ③ if (status & (TxOK | TxErr)) { spin_lock (&tp->lock); rtl8139_tx_interrupt (dev, tp, ioaddr); spin_unlock (&tp->lock); } 如果是传输完成的信号,就调用 rtl8139_tx_interrupt 进行发送善后处理。 下面我们先来看看接收中断处理函数 rtl8139_rx_interrupt,在这个函数中主要做了下面 四件事 ①这个函数是一个大循环,循环条件是只要接收缓存不为空就还可以继续读取数据,循 环不会停止,读空了之后就跳出。 int ring_offset = cur_rx % RX_BUF_LEN; rx_status = le32_to_cpu (*(u32 *) (rx_ring + ring_offset)); rx_size = rx_status >> 16; 上面三行代码是计算出要接收的包的长度。 ②根据这个长度来分配包的数据结构 skb = dev_alloc_skb (pkt_size + 2); ③如果分配成功就把数据从接收缓存中拷贝到这个包中 eth_copy_and_sum (skb, &rx_ring[ring_offset + 4], pkt_size, 0); 这个函数在 include/linux/etherdevice.h 中,实质还是调用了 memcpy()。 static inline void eth_copy_and_sum(struct sk_buff*dest, unsigned char *src, int len, int base) { memcpy(dest->data, src, len); } 现在我们已经熟知,&rx_ring[ring_offset + 4]就是接收缓存,也是源地址,而 skb->data 就是包的数据地址,也是目的地址,一目了然。 ④把这个包送到 Linux 协议栈去进行下一步处理 skb->protocol = eth_type_trans (skb, dev); netif_rx (skb); 在 netif_rx()函数执行完后,这个包的数据就脱离了网卡驱动范畴,而进入了 Linux 网络协议栈里面,把这些数据包的以太网帧头,IP 头,TCP 头都脱下来,最后把数据送给 了应用程序,不过协议栈不再本文讨论范围内。netif_rx 函数在 net/core/dev.c,中。 而 rtl8139_remove_one 则基本是 rtl8139_init_one 的逆过程。 到此,本文已经将 Linux 驱动程序的框架勾勒了出来。
本文档为【linux网卡驱动分析】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_532630
暂无简介~
格式:pdf
大小:128KB
软件:PDF阅读器
页数:7
分类:互联网
上传时间:2012-04-11
浏览量:74