Linux网络驱动程序

来源:
导读 大家好,我是本期栏目编辑小友,现在为大家讲解Linux网络驱动程序问题。 1 驱动模块的装载和卸载。 如果网络设备(包括无线)是pci标准的,

大家好,我是本期栏目编辑小友,现在为大家讲解Linux网络驱动程序问题。

1.驱动模块的装载和卸载。

如果网络设备(包括无线)是pci标准的,首先向内核注册PCI设备(pci_register_driver),然后通过pci_driver数据结构中的探测函数指针指向的探测函数初始化PCI设备,同时注册初始化网络设备。

如果网络设备(包括无线)是PCMCIA标准的,首先向内核注册PCMCIA设备(register_pccard_driver),然后用driver_info_t数据结构中的attach函数指针指向的检测函数初始化PCMCIA设备,同时注册初始化网络设备。

staTIc int __init tg3_init(void)

{

//注册为PCI设备,先初始化。如果是其他ESIA或PCMCIA,请使用其他功能。

返回PCI _ module _ init(tg3 _ driver);

}

staTIc void _ _退出tg3 _清理(void)

{

PCI _ unregister _ driver(tg3 _ driver);//注销PCI设备。

}

module _ init(tg3 _ init);//加载驱动程序模块。

module _ exit(tg3 _ clean up);//卸载驱动程序模块。

声称为PCI设备:

staTIc struct PCI _ driver tg3 _ driver={ 0。名称=DRV _模块_名称。id_table=tg3_pci_tbl,//此驱动程序支持的网卡系列,厂商_id,设备_id。probe=tg3_init_one,//初始化网络设备的回调函数。remove=_ _ deve xit _ p(tg3 _ remove _ one),//注销网络设备的回调功能。suspend=tg3_suspend,//器件挂起功能。resume=tg3_resume //设备恢复功能。

};

2.初始化网络设备的PCI设备探测功能。

staTIc int _ _ devinit tg3 _ init _ one(struct PCI _ dev * pdev,const struct pci_device_id *ent)

{

//初始化设备,使I/O和内存可用,并唤醒设备。

PCI _ enable _ device(pdev);

//申请内存空间,配置网卡的I/O和内存资源。

pci_request_regions(pdev,DRV _ MODULE _ NAME);

PCI _ set _ master(pdev);

//设置DMA属性。

pci_set_dma_mask(pdev,(u64)0x ffffffffffffffff);

//网卡I/o,内存资源起始地址。

tg3reg _ base=PCI _ resource _ start(pdev,0);

//网卡I/o和内存资源的大小。

tg3reg _ len=PCI _ resource _ len(pdev,0);

//分配和设置网络设备。

dev=alloc _ ether dev(sizeof(* TP));

//声明为内核设备模块。

SET _ MODULE _ OWNER(dev);

//初始化私有结构中的每个成员值。

TP=dev-priv;

TP-pdev=pdev;

TP-dev=dev;

……

//锁的初始化。

spin _ lock _ init(TP-lock);

//将I/O、内存地址映射到私有域中的寄存器结构。

tp-regs=(无符号长)ioremap(tg3reg_base,tg3reg _ len);

dev-IRQ=pdev-IRQ;

//网络设备回调函数赋值。

dev-open=tg3 _ open;

dev-stop=tg3 _ close;

dev-get _ stats=tg3 _ get _ stats;

dev-set_multicast_list=tg3_s

et_rx_mode;

  dev->set_mac_aDDRess = tg3_set_mac_addr;

  dev->do_ioctl = tg3_ioctl;

  dev->tx_timeout = tg3_tx_timeout;

  dev->hard_start_xmit= tg3_start_xmit;

  //网卡的MAC地址赋值dev->addr

  tg3_get_device_address(tp);

  //注册网络设备

  register_netdev(dev);

  //把网络设备指针地址放入PCI设备中的设备指针中

  pci_set_drvdata(pdev, dev);

  }

  3.注销网络设备

  static void __devexit tg3_remove_one(struct pci_dev *pdev)

  {

  struct net_device *dev = pci_get_drvdata(pdev);

  //注销网络设备

  unregister_netdev(dev);

  //取消地址映射

  iounmap((void *) ((struct tg3 *)(dev->priv))->regs);

  //释放网络设备

  kfree(dev);

  //释放PCI资源

  pci_release_regions(pdev);

  //停用PCI设备

  pci_disable_device(pdev);

  //PCI设备中的设备指针赋空

  pci_set_drvdata(pdev, NULL);

  }

  4.打开网络设备

  static int tg3_open(struct net_device *dev)

  {

  //分配一个中断

  request_irq(dev->irq, tg3_interrupt, SA_SHIRQ, dev->name, dev);

  /* int request_irq(unsigned int irq,

  void (*handler)(int irq, void *dev_id, struct pt_regs *regs),

  unsigned long irqflags,

  const char * devname,

  void *dev_id);

  irq是要申请的硬件中断号。在Intel平台,范围0--15。handler是向系统登记的中断处理函数。这是一个回调函数,中断发生时,系统调用这个函数,传入的参数包括硬件中断号,device id,寄存器值。dev_id就是下面的request_irq时传递给系统的参数dev_id。irqflags是中断处理的一些属性。比较重要的有SA_INTERRUPT,标明中断处理程序是快速处理程序(设置SA_INTERRUPT)还是慢速处理程序(不设置SA_INTERRUPT)。快速处理程序被调用时屏蔽所有中断。慢速处理程序不屏蔽。还有一个SA_SHIRQ属性,设置了以后运行多个设备共享中断。dev_id在中断共享时会用到。一般设置为这个设备的device结构本身或者NULL。中断处理程序可以用dev_id找到相应的控制这个中断的设备,或者用rq2dev_map找到中断对应的设备。*/

  //初始化硬件

  tg3_init_hw(tp);

  //初始化收包和发包的缓冲区

  tg3_init_rings(tp);

  //初始化定时器

  init_timer(&tp->timer);

  tp->timer.expires = jiffies + tp->timer_offset;

  tp->timer.data = (unsigned long) tp;

  tp->timer.function = tg3_timer; //超时回调函数

  add_timer(&tp->timer);

  //允许网卡开始传输包

  netif_start_queue(dev);

  }

  5.关闭网络设备

  static int tg3_close(struct net_device *dev)

  {

  //停止网卡传输包

  netif_stop_queue(dev);

  netif_carrier_off(tp->dev);

  //去除定时器

  del_timer_sync(&tp->timer);

  //释放收包和发包的缓冲区

  tg3_free_rings(tp);

  //释放中断

  free_irq(dev->irq, dev);

  }

  [NextPage]

  6.硬件处理数据包发送

  static int tg3_start_xmit(struct sk_buff *skb, struct net_device *dev)

  {

  len = (skb->len - skb->data_len);

  //以DMA方式向网卡物理设备传输包。如果是wireless的话,需要根据802.11协议及硬件的规范从新填充

  //硬件帧头,然后提交给硬件发送。

  mapping = pci_map_single(tp->pdev, skb->data, len, PCI_DMA_TODEVICE);

  tp->tx_buffers[entry].skb = skb;

  pci_unmap_addr_set(&tp->tx_buffers[entry], mapping, mapping);

  //硬件发送

  tg3_set_txd(tp, entry, mapping, len, base_flags, mss_and_is_end);

  //记录发包开始时间

  dev->trans_start = jiffies;

  }

  7.中断处理收包,发包

  static void tg3_interrupt(int irq, void *dev_id, struct pt_regs *regs)

  {

  //如果要收包

  tg3_rx(tp);

  //如果要发包

  tg3_tx(tp);

  }

  8.发包

  static void tg3_tx(struct tg3 *tp)

  {

  struct tx_ring_info *ri = &tp->tx_buffers[sw_idx];

  struct sk_buff *skb = ri->skb;

  //以DMA方式向网卡传输包完毕

  pci_unmap_single(tp->pdev, pci_unmap_addr(ri, mapping),

  (skb->len - skb->data_len), PCI_DMA_TODEVICE);

  ri->skb = NULL;

  dev_kfree_skb_irq(skb);

  }

  9.收包

  static int tg3_rx(struct tg3 *tp, int budget)

  {

  struct sk_buff *copy_skb;

  //分配一个包

  copy_skb = dev_alloc_skb(len + 2);

  copy_skb->dev = tp->dev;

  //修改包头空间

  skb_reserve(copy_skb, 2);

  //加入数据到包中

  skb_put(copy_skb, len);

  //以DMA方式从网卡传输回数据

  pci_dma_sync_single(tp->pdev, dma_addr, len, PCI_DMA_FROMDEVICE);

  memcpy(copy_skb->data, skb->data, len);

  skb = copy_skb;

  //解析包的协议

  skb->protocol = eth_type_trans(skb, tp->dev);

  //把包送到协议层

  netif_rx(skb);

  //记录收包时间

  tp->dev->last_rx = jiffies;

  }

  10.读取包的网卡收发包的状态,统计数据

  static struct net_device_stats *tg3_get_stats(struct net_device *dev)

  {

  //从硬件相关的寄存器读取数据,累加

  //stats->rx_packets, stats->tx_packets, stats->rx_bytes, stats->tx_bytes等

  }

  11.用户的ioctl命令系统调用

  static int tg3_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)

  {

  struct mii_ioctl_data *data = (struct mii_ioctl_data *)&ifr->ifr_data;

  switch(cmd) {

  //ethtool程序命令的调用

  case SIO*HTOOL:

  return tg3_ethtool_ioctl(dev, (void *) ifr->ifr_data);

  //mii程序命令的调用

  case SIOCGMIIREG: {

  err = tg3_readphy(tp, data->reg_num & 0x1f, &mii_regval)

  data->val_out = mii_regval;

  return err;

  }

  ……

  }

  }

  12.PCI设备的挂起和恢复函数

  static int tg3_suspend(struct pci_dev *pdev, u32 state)

  {

  //停用网卡的中断寄存器

  tg3_disable_ints(tp);

  //停止网卡收发包

  netif_device_detach(dev);

  //停止网卡某些硬件,fireware的一些功能

  tg3_halt(tp);

  //设置网卡的电源状态

  tg3_set_power_state(tp, state);

  }

  static int tg3_resume(struct pci_dev *pdev)

  {

  //恢复网卡电源

  tg3_set_power_state(tp, 0);

  //允许网卡收发包

  netif_device_attach(dev);

  //初始化收发包的缓冲区

  tg3_init_rings(tp);

  //初始化网卡硬件

  tg3_init_hw(tp);

  //打开网卡中断寄存器

  tg3_enable_ints(tp);

  }

  13.参数设置

  在驱动程序里还提供一些方法供系统对设备的参数进行设置和读取信息。一般只有超级用户(root)权限才能对设备参数进行设置。设置方法有:

  tg3_set_mac_addr (dev->set_mac_address)

  当用户调用ioctl类型为SIOCSIFHWADDR时是要设置这个设备的mac地址。一般对mac地址的设置没有太大意义的。

  dev->set_config()

  当用户调用ioctl时类型为SIOCSIFMAP时,系统会调用驱动程序的set_config方法

  用户会传递一个ifmap结构包含需要的I/O、中断等参数。

  总结:

  所有的Linux网络驱动程序遵循通用的接口。设计时采用的是面向对象的方法。一个设备就是一个对象(net_device 结构),它内部有自己的数据和方法。一个网络设备最基本的方法有初始化,发送和接收。

  Linux网络驱动程序的体系结构可以划分为四层:

  网络协议接口,网络设备接口,设备驱动功能,网络设备和网络媒介层

  网络驱动程序,最主要的工作就是完成设备驱动功能层。在Linux中所有网络设备都抽象为一个接口,这个接口提供了对所有网络设备的操作集合。由数据结构struct net_device来表示网络设备在内核中的运行情况,即网络设备接口。它既包括纯软件网络设备接口,如环路(Loopback),也包括硬件网络设备接口,如以太网卡。而由以dev_base为头指针的设备链表来集体管理所有网络设备,该设备链表中的每个元素代表一个网络设备接口。数据结构net_device中有很多供系统访问和协议层调用的设备方法,包括初始化,打开和关闭网络设备的open和stop函数,处理数据包发送的hard_start_xmit函数,以及中断处理函数等。

  网络设备在Linux里做专门的处理。Linux的网络系统主要是基于BSD unix的socket机制。在系统和驱动程序之间定义有专门的数据结构(sk_buff)进行数据的传递。系统里支持对发送数据和接收数据的缓存,提供流量控制机制,提供对多协议的支持。

  1.驱动模块的加载和卸载

  如果网络设备(包括wireless)是PCI规范的,则先是向内核注册该PCI设备(pci_register_driver),然后由pci_driver数据结构中的probe函数指针所指向的侦测函数来初始化该PCI设备,并且同时注册和初始化该网络设备。

  如果网络设备(包括wireless)是PCMCIA规范的,则先是向内核注册该PCMCIA设备(register_pccard_driver),然后driver_info_t数据结构中的attach函数指针所指向的侦测函数来初始化该PCMCIA设备,并且同时注册和初始化该网络设备。

  staTIc int __init tg3_init(void)

  {

  //先注册成PCI设备,并初始化,如果是其他的ESIA,PCMCIA,用其他函数

  return pci_module_init(&tg3_driver);

  }

  staTIc void __exit tg3_cleanup(void)

  {

  pci_unregister_driver(&tg3_driver);//注销PCI设备

  }

  module_init(tg3_init); //驱动模块的加载

  module_exit(tg3_cleanup); //驱动模块的卸载

  申明为PCI设备:

  staTIc struct pci_driver tg3_driver = {

  .name = DRV_MODULE_NAME,

  .id_table = tg3_pci_tbl, //此驱动所支持的网卡系列,vendor_id, device_id

  .probe = tg3_init_one, //初始化网络设备的回调函数

  .remove = __devexit_p(tg3_remove_one), //注销网络设备的回调函数

  .suspend = tg3_suspend, //设备挂起函数

  .resume = tg3_resume //设备恢复函数

  };

  2.PCI设备探测函数probe,初始化网络设备

  staTIc int __devinit tg3_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)

  {

  //初始化设备,使I/O,memory可用,唤醒设备

  pci_enable_device(pdev);

  //申请内存空间,配置网卡的I/O,memory资源

  pci_request_regions(pdev, DRV_MODULE_NAME);

  pci_set_master(pdev);

  //设置DMA属性

  pci_set_dma_mask(pdev, (u64) 0xffffffffffffffff);

  //网卡 I/O,memory资源的启始地址

  tg3reg_base = pci_resource_start(pdev, 0);

  //网卡I/O,memory资源的大小

  tg3reg_len = pci_resource_len(pdev, 0);

  //分配并设置网络设备

  dev = alloc_etherdev(sizeof(*tp));

  //申明为内核设备模块

  SET_MODULE_OWNER(dev);

  //初始化私有结构中的各成员值

  tp = dev->priv;

  tp->pdev = pdev;

  tp->dev = dev;

  ……

  //锁的初始化

  spin_lock_init(&tp->lock);

  //映射I/O,memory地址到私有域中的寄存器结构

  tp->regs = (unsigned long) ioremap(tg3reg_base, tg3reg_len);

  dev->irq = pdev->irq;

  //网络设备回调函数赋值

  dev->open = tg3_open;

  dev->stop = tg3_close;

  dev->get_stats = tg3_get_stats;

  dev->set_multicast_list = tg3_set_rx_mode;

  dev->set_mac_aDDRess = tg3_set_mac_addr;

  dev->do_ioctl = tg3_ioctl;

  dev->tx_timeout = tg3_tx_timeout;

  dev->hard_start_xmit= tg3_start_xmit;

  //网卡的MAC地址赋值dev->addr

  tg3_get_device_address(tp);

  //注册网络设备

  register_netdev(dev);

  //把网络设备指针地址放入PCI设备中的设备指针中

  pci_set_drvdata(pdev, dev);

  }

  3.注销网络设备

  static void __devexit tg3_remove_one(struct pci_dev *pdev)

  {

  struct net_device *dev = pci_get_drvdata(pdev);

  //注销网络设备

  unregister_netdev(dev);

  //取消地址映射

  iounmap((void *) ((struct tg3 *)(dev->priv))->regs);

  //释放网络设备

  kfree(dev);

  //释放PCI资源

  pci_release_regions(pdev);

  //停用PCI设备

  pci_disable_device(pdev);

  //PCI设备中的设备指针赋空

  pci_set_drvdata(pdev, NULL);

  }

  4.打开网络设备

  static int tg3_open(struct net_device *dev)

  {

  //分配一个中断

  request_irq(dev->irq, tg3_interrupt, SA_SHIRQ, dev->name, dev);

  /* int request_irq(unsigned int irq,

  void (*handler)(int irq, void *dev_id, struct pt_regs *regs),

  unsigned long irqflags,

  const char * devname,

  void *dev_id);

  irq是要申请的硬件中断号。在Intel平台,范围0--15。handler是向系统登记的中断处理函数。这是一个回调函数,中断发生时,系统调用这个函数,传入的参数包括硬件中断号,device id,寄存器值。dev_id就是下面的request_irq时传递给系统的参数dev_id。irqflags是中断处理的一些属性。比较重要的有SA_INTERRUPT,标明中断处理程序是快速处理程序(设置SA_INTERRUPT)还是慢速处理程序(不设置SA_INTERRUPT)。快速处理程序被调用时屏蔽所有中断。慢速处理程序不屏蔽。还有一个SA_SHIRQ属性,设置了以后运行多个设备共享中断。dev_id在中断共享时会用到。一般设置为这个设备的device结构本身或者NULL。中断处理程序可以用dev_id找到相应的控制这个中断的设备,或者用rq2dev_map找到中断对应的设备。*/

  //初始化硬件

  tg3_init_hw(tp);

  //初始化收包和发包的缓冲区

  tg3_init_rings(tp);

  //初始化定时器

  init_timer(&tp->timer);

  tp->timer.expires = jiffies + tp->timer_offset;

  tp->timer.data = (unsigned long) tp;

  tp->timer.function = tg3_timer; //超时回调函数

  add_timer(&tp->timer);

  //允许网卡开始传输包

  netif_start_queue(dev);

  }

  5.关闭网络设备

  static int tg3_close(struct net_device *dev)

  {

  //停止网卡传输包

  netif_stop_queue(dev);

  netif_carrier_off(tp->dev);

  //去除定时器

  del_timer_sync(&tp->timer);

  //释放收包和发包的缓冲区

  tg3_free_rings(tp);

  //释放中断

  free_irq(dev->irq, dev);

  }

  [NextPage]

  6.硬件处理数据包发送

  static int tg3_start_xmit(struct sk_buff *skb, struct net_device *dev)

  {

  len = (skb->len - skb->data_len);

  //以DMA方式向网卡物理设备传输包。如果是wireless的话,需要根据802.11协议及硬件的规范从新填充

  //硬件帧头,然后提交给硬件发送。

  mapping = pci_map_single(tp->pdev, skb->data, len, PCI_DMA_TODEVICE);

  tp->tx_buffers[entry].skb = skb;

  pci_unmap_addr_set(&tp->tx_buffers[entry], mapping, mapping);

  //硬件发送

  tg3_set_txd(tp, entry, mapping, len, base_flags, mss_and_is_end);

  //记录发包开始时间

  dev->trans_start = jiffies;

  }

  7.中断处理收包,发包

  static void tg3_interrupt(int irq, void *dev_id, struct pt_regs *regs)

  {

  //如果要收包

  tg3_rx(tp);

  //如果要发包

  tg3_tx(tp);

  }

  8.发包

  static void tg3_tx(struct tg3 *tp)

  {

  struct tx_ring_info *ri = &tp->tx_buffers[sw_idx];

  struct sk_buff *skb = ri->skb;

  //以DMA方式向网卡传输包完毕

  pci_unmap_single(tp->pdev, pci_unmap_addr(ri, mapping),

  (skb->len - skb->data_len), PCI_DMA_TODEVICE);

  ri->skb = NULL;

  dev_kfree_skb_irq(skb);

  }

  9.收包

  static int tg3_rx(struct tg3 *tp, int budget)

  {

  struct sk_buff *copy_skb;

  //分配一个包

  copy_skb = dev_alloc_skb(len + 2);

  copy_skb->dev = tp->dev;

  //修改包头空间

  skb_reserve(copy_skb, 2);

  //加入数据到包中

  skb_put(copy_skb, len);

  //以DMA方式从网卡传输回数据

  pci_dma_sync_single(tp->pdev, dma_addr, len, PCI_DMA_FROMDEVICE);

  memcpy(copy_skb->data, skb->data, len);

  skb = copy_skb;

  //解析包的协议

  skb->protocol = eth_type_trans(skb, tp->dev);

  //把包送到协议层

  netif_rx(skb);

  //记录收包时间

  tp->dev->last_rx = jiffies;

  }

  10.读取包的网卡收发包的状态,统计数据

  static struct net_device_stats *tg3_get_stats(struct net_device *dev)

  {

  //从硬件相关的寄存器读取数据,累加

  //stats->rx_packets, stats->tx_packets, stats->rx_bytes, stats->tx_bytes等

  }

  11.用户的ioctl命令系统调用

  static int tg3_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)

  {

  struct mii_ioctl_data *data = (struct mii_ioctl_data *)&ifr->ifr_data;

  switch(cmd) {

  //ethtool程序命令的调用

  case SIO*HTOOL:

  return tg3_ethtool_ioctl(dev, (void *) ifr->ifr_data);

  //mii程序命令的调用

  case SIOCGMIIREG: {

  err = tg3_readphy(tp, data->reg_num & 0x1f, &mii_regval)

  data->val_out = mii_regval;

  return err;

  }

  ……

  }

  }

  12.PCI设备的挂起和恢复函数

  static int tg3_suspend(struct pci_dev *pdev, u32 state)

  {

  //停用网卡的中断寄存器

  tg3_disable_ints(tp);

  //停止网卡收发包

  netif_device_detach(dev);

  //停止网卡某些硬件,fireware的一些功能

  tg3_halt(tp);

  //设置网卡的电源状态

  tg3_set_power_state(tp, state);

  }

  static int tg3_resume(struct pci_dev *pdev)

  {

  //恢复网卡电源

  tg3_set_power_state(tp, 0);

  //允许网卡收发包

  netif_device_attach(dev);

  //初始化收发包的缓冲区

  tg3_init_rings(tp);

  //初始化网卡硬件

  tg3_init_hw(tp);

  //打开网卡中断寄存器

  tg3_enable_ints(tp);

  }

  13.参数设置

  在驱动程序里还提供一些方法供系统对设备的参数进行设置和读取信息。一般只有超级用户(root)权限才能对设备参数进行设置。设置方法有:

  tg3_set_mac_addr (dev->set_mac_address)

  当用户调用ioctl类型为SIOCSIFHWADDR时是要设置这个设备的mac地址。一般对mac地址的设置没有太大意义的。

  dev->set_config()

  当用户调用ioctl时类型为SIOCSIFMAP时,系统会调用驱动程序的set_config方法

  用户会传递一个ifmap结构包含需要的I/O、中断等参数。

  总结:

  所有的Linux网络驱动程序遵循通用的接口。设计时采用的是面向对象的方法。一个设备就是一个对象(net_device 结构),它内部有自己的数据和方法。一个网络设备最基本的方法有初始化,发送和接收。

  Linux网络驱动程序的体系结构可以划分为四层:

  网络协议接口,网络设备接口,设备驱动功能,网络设备和网络媒介层

  网络驱动程序,最主要的工作就是完成设备驱动功能层。在Linux中所有网络设备都抽象为一个接口,这个接口提供了对所有网络设备的操作集合。由数据结构struct net_device来表示网络设备在内核中的运行情况,即网络设备接口。它既包括纯软件网络设备接口,如环路(Loopback),也包括硬件网络设备接口,如以太网卡。而由以dev_base为头指针的设备链表来集体管理所有网络设备,该设备链表中的每个元素代表一个网络设备接口。数据结构net_device中有很多供系统访问和协议层调用的设备方法,包括初始化,打开和关闭网络设备的open和stop函数,处理数据包发送的hard_start_xmit函数,以及中断处理函数等。

  网络设备在Linux里做专门的处理。Linux的网络系统主要是基于BSD unix的socket机制。在系统和驱动程序之间定义有专门的数据结构(sk_buff)进行数据的传递。系统里支持对发送数据和接收数据的缓存,提供流量控制机制,提供对多协议的支持。

技术专区 实现linux系统内存的分配 读写 释放功能 CAN总线成为车电系统的总线标准 一种基于CAN总线控制的电动汽车整车电子系统设计方案 嵌入式系统USB接口的虚拟示波器设计 是距离还是速度,让设计RS485系统陷入困境

标签:

版权声明:转载此文是出于传递更多信息之目的。若有来源标注错误或侵犯了您的合法权益,请作者持权属证明与本网联系,我们将及时更正、删除,谢谢您的支持与理解。