LDD一书中,网络驱动在linux-3.1中的修改运行

2450阅读 0评论2014-11-28 helianthus_lu
分类:LINUX

由于linux内核现在的更新速度非常之快,在LDD(linux内核驱动)一书中,很多代码都不能在现在的比较新的内核中直接运行了,都需要我们对其中的机制加以理解之后,重新修改代码才能运行。本文针对其中网络驱动snull进行修改,并使其在linux-3.1中运行。通过这个过程,笔者也对linux网络部分的理解也加深了一点点。

先看该驱动的初始化函数:int snullnet_init()
函数分析:
    1、为网络设备分配设备结构:net_device,该过由alloc_netdev实现;
    2、注册设备,由register_netdev实现;
  
alloc_netdev函数的第一个参数是网络设备的私有结构体,由于一个设备驱动程序可以被多个网络设备使用,所以不同的设备是用这个私有结构体来区分的;网络设备通过netdev_priv函数可以从网络接口的net_device结构得到该私有结构体的指针;下面我们看看LDD中的网络接口的私有结构体(struct snull_priv):

点击(此处)折叠或打开

  1. struct snull_priv {
  2.     struct net_device_stats stats;//网络统计信息,内核已经定义了该结构体
  3.     int status;                   //网络接口状态
  4.     struct snull_packet *ppool;   //缓冲池,驱动需要该结构时候,直接从这个队列中取出,用完后归还
  5.     struct snull_packet *rx_queue;//接收到的数据包的队列
  6.     int rx_int_enabled;           //接收中端是否使能
  7.     int tx_packetlen;             //要发送的数据包的长度
  8.     u8 *tx_packetdata;            //要发送的数据
  9.     struct sk_buff *skb;          //这个可以没有的,貌似我修改的程序中,木有用到
  10.     spinlock_t lock;              //保护该结构体并发访问的自旋锁
  11.     struct napi_struct *snull_napi;//使用napi时候,要用到的结构,在linux2.6.24中加入
  12. };
alloc_netdev的第三个参数是snull_init,指定了初始化alloc_netdev所分配空间的函数,下面我们九分析它。
void snull_init(struct net_device *dev)完成以下任务:
1、调用ether_setup;
        这里不得不说一下的是,ether_setup是内核为方便初始化以太网接口而定义的,其他的网络也有类似的函数,比如fddi_setup用于设置FDDI网络接口等等;其实如果你想加入一个以前不存在的接口类型的话,你可以定义自己的接口初始化函数;也可以用一个类似的函数初始化,然后再修改一些域就可以了
2、设置两个主要的操作函数集;        

  1. dev->netdev_ops = &snulldev_ops;//网络接口管理函数
  2. dev->header_ops = &snull_header_ops;//数据帧头部处理函数
从中我们可以看到:当我们定义一个网络接口的net_device结构体,通过初始化这两个域,可以把内核对接口的操作重新实现为我们定义的操作,这是不是面向对象编程中的“多态性”的思想呢?

既然提到了上面的两个操作集合,我们接下来就说说他们吧。
hdader_ops是header_ops结构的实例:
在本驱动中,我们定义了create、rebuild连个操作;
create操作在发送数据包之前被调用,用来组装帧头部;
rebuild在进行地址解析(如IPv4中的ARP协议)之后,重新组装帧头部;

netdev_ops是net_device_ops结构的实例;
该结构体定义了N多的操作,本驱动实现了其中的一部分下面一一对他们进行讲解:

snull_open设备UP时被调用,本驱动用它完成下面任务:
1、设置接口的硬件地址;
2、调用netif_start_queue函数,该调用等于告诉内核,该接口可以接收上层数据包了;
snull_release:设备进入DOWN状态时被调用,本驱动使用来实现:
1、调用netif_stop_queue函数,告诉内核,我以后不接收数据包了;
snull_chenge_mtu:这个没有歧义,不再说明;
snull_stats:获取设备的统计信息,本驱动直接返回私有结构中的net_device_ops内嵌结构的指针;
snull_tx:设备的发送函数,完成设备的数据发送工作;这个函数比较重要,但是根LDD中没有区别,可以参考上面的解释;

下面我们就主要看看设备接收数据的过程:
该驱动使用了两种方法来展示linux中处理网络接口数据接收问题的方法,由于该驱动模拟了接收中断,所以下面两种方法都是中断服务函数;
方法一:void snull_regular_interrupt(...),解析如下:
  1. pkt = priv->rx_queue;
这一句从设备的接收队列中取出一个数据包,
然后,驱动调用了snull_rx函数,这个函数利用要传递的数据,调用dev_alloc_skb函数来初始化sk_buff结构,初始化sk_buff时期,最值得关注的一个细节是:

  1. skb->protocol = eth_type_trans(skb, dev);
这一句设置了要传递给上层的协议类型,当然,该类型是eth_type_trans通过skb结构体和设备类型判断的,大家可以仔细看看这个函数的执行过程;
最终,函数调用了netif_rx(skb)函数,这个函数由内核提供;至此,驱动完成了数据向上层传递的任务,其他工作由内核完成;
建议大家空闲的时候,跟踪一下netif_rx这个函数,以便更好的理解linux网络数据的接收;
方法二:void snull_napi_interrupt(...),解析如下:

  1.         snull_rx_ints(dev, 0);//禁止接收中断
  2.         napi_enable(priv->snull_napi);
  3.         napi_schedule(priv->snull_napi);
这三句就是网络驱动napi接口的主要工作了:禁止接收中断、打开napi轮循、调度该接口的napi;
这个主要涉及到网络的napi功能;该功能简单来说就是在发生网络数据接收中断时,关闭接口的中断,通过轮循的方法处理以后到来的网络数据,直到所有的数据处理完毕(这个不准确,但是更容易理解);数据处理完毕再打开接收中断;napi接口以后有空再讲,现在暂且不提;

至此,一个具备基本功能的网络驱动程序就已经勾画出来了,自己在这里记录一下,以便以后复习;下面粘贴完整的源文件:snullnet.c
--->文件名:snullnet.c

点击(此处)折叠或打开

  1. #include <linux/module.h>
  2. #include <linux/init.h>
  3. #include <linux/moduleparam.h>
  4. #include <linux/netdevice.h>
  5. #include <linux/etherdevice.h>
  6. #include <linux/kernel.h>
  7. #include <linux/interrupt.h>
  8. #include <linux/spinlock.h>

  9. #include <linux/sched.h>
  10. #include <linux/slab.h>
  11. #include <linux/errno.h>
  12. #include <linux/types.h>
  13. #include <linux/in.h>
  14. #include <linux/ip.h>
  15. #include <linux/tcp.h>
  16. #include <linux/skbuff.h>
  17. #include <asm/checksum.h>

  18. #define SNULL_RX_INTR    1UL
  19. #define SNULL_TX_INTR    2UL
  20. #define SNULL_TIMEOUT    5

  21. MODULE_AUTHOR("xishuai");
  22. MODULE_LICENSE("GPLv3");

  23. int pool_size = 8;
  24. module_param(pool_size, int, 0);
  25. int use_napi = 0;
  26. module_param(use_napi, int, 0);

  27. struct snull_packet {
  28.     struct snull_packet *next;
  29.     struct net_device *dev;
  30.     int datalen;
  31.     u8 data[ETH_DATA_LEN];
  32. };

  33. struct snull_priv {
  34.     struct net_device_stats stats;
  35.     int status;
  36.     struct snull_packet *ppool;
  37.     struct snull_packet *rx_queue;
  38.     int rx_int_enabled;
  39.     int tx_packetlen;
  40.     u8 *tx_packetdata;
  41.     struct sk_buff *skb;
  42.     spinlock_t lock;
  43.     struct napi_struct *snull_napi;
  44. };

  45. static struct net_device *snull_dev[2];

  46. static void(*snull_interrupt)(int, void*, struct pt_regs *);

  47. static int snull_open(struct net_device *dev)
  48. {
  49.     printk(KERN_ALERT "snull_open\n");

  50.     memcpy(dev->dev_addr, "\0SNUL0", ETH_ALEN);
  51.     if (dev == snull_dev[1]) {
  52.         dev->dev_addr[ETH_ALEN -1]++;
  53.     }
  54.     netif_start_queue(dev);//允许上层调用ndo_start_xmit函数
  55.     return 0;
  56. }

  57. static int snull_release(struct net_device *dev)
  58. {
  59.     printk("snullnet:snull_release\n");
  60.     netif_stop_queue(dev);//禁止上层调用本设备的发送函数
  61.     return 0;
  62. }

  63. static int snull_config(struct net_device *dev, struct ifmap *map)
  64. {
  65.     printk("snullnet:snull_config\n");
  66.     if(dev->flags & IFF_UP) return -EBUSY;

  67.     if(map->base_addr != dev->base_addr) {
  68.         printk(KERN_WARNING "snull: Can't change I/O address\n");
  69.         return -EOPNOTSUPP;
  70.     }
  71.     
  72.     if(map->irq != dev->irq) {
  73.         dev->irq = map->irq;
  74.     }

  75.     /*忽略其他域的改变*/
  76.     return 0;
  77. }

  78. static void snull_enqueue_buf(struct net_device *dev, struct snull_packet *pkt)
  79. {
  80.     unsigned long flags;
  81.     struct snull_priv *priv = netdev_priv(dev);

  82.     printk("snullnet:snull_enqueue_buf\n");
  83.     spin_lock_irqsave(&priv->lock, flags);
  84.     pkt->next = priv->rx_queue;
  85.     priv->rx_queue = pkt;
  86.     spin_unlock_irqrestore(&priv->lock, flags);
  87. }

  88. static struct snull_packet *snull_get_tx_buffer(struct net_device *dev)
  89. {
  90.     struct snull_priv *priv = netdev_priv(dev);
  91.     unsigned long flags;
  92.     struct snull_packet *pkt;

  93.     printk("snullnet:snull_get_tx_buffer\n");
  94.     spin_lock_irqsave(&priv->lock, flags);
  95.     pkt = priv->ppool;
  96.     priv->ppool = pkt->next;
  97.     if (priv->ppool == NULL) {
  98.         printk(KERN_INFO "pool empty\n");
  99.         netif_stop_queue(dev);
  100.     }
  101.     spin_unlock_irqrestore(&priv->lock, flags);
  102.     return pkt;
  103. }

  104. //发送上层数据,本函数吃力底层硬件细节
  105. static void snull_hw_tx(char *buf, int len, struct net_device *dev)
  106. {
  107.     struct iphdr *ih;//
  108.     struct net_device *dest;
  109.     struct snull_priv *priv;
  110.     u32 *saddr, *daddr;
  111.     struct snull_packet *tx_buffer;

  112.     ih = (struct iphdr *)(buf + sizeof(struct ethhdr));
  113.     saddr = &ih->saddr;
  114.     daddr = &ih->daddr;
  115.     
  116.     //驱动中修改了IP层的头部,这在正常的驱动中是不应该的
  117.     ((u8 *)saddr)[2] ^= 1;
  118.     ((u8 *)daddr)[2] ^= 1;

  119.     ih->check = 0;    //要重新计算校验和
  120.     ih->check = ip_fast_csum((unsigned char *)ih, ih->ihl);

  121.     //准备发送
  122.     dest = snull_dev[dev == snull_dev[0] ? 1 : 0];
  123.     priv = netdev_priv(dest);
  124.     tx_buffer = snull_get_tx_buffer(dev);
  125.     tx_buffer->datalen = len;
  126.     memcpy(tx_buffer->data, buf, len);
  127.     snull_enqueue_buf(dest, tx_buffer);
  128.     if (priv->rx_int_enabled) {
  129.         priv->status |= SNULL_RX_INTR;
  130.         snull_interrupt(0, dest, NULL);
  131.     }

  132.     priv = netdev_priv(dev);
  133.     priv->tx_packetlen = len;
  134.     priv->tx_packetdata = buf;
  135.     priv->status |= SNULL_TX_INTR;
  136.     snull_interrupt(0, dev, NULL);//原来的代码里面有个模拟丢包的代码段
  137. }

  138. static int snull_tx(struct sk_buff *skb, struct net_device *dev)
  139. {
  140.     int len;
  141.     char *data, shortpkt[ETH_ZLEN];
  142.     struct snull_priv *priv = netdev_priv(dev);

  143.     printk("snullnet:snull_tx\n");
  144.     data = skb->data;
  145.     len = skb->len;
  146.     if (len < ETH_ZLEN) {
  147.         memset(shortpkt, 0, ETH_ZLEN);
  148.         memcpy(shortpkt, skb->data, skb->len);
  149.         len = ETH_ZLEN;
  150.         data = shortpkt;
  151.     }
  152.     dev->trans_start = jiffies;
  153.     priv->skb = skb;
  154.     snull_hw_tx(data, len, dev);//实际的发送过程,设备相关

  155.     return 0;
  156. }

  157. static void snull_tx_timeout (struct net_device *dev)
  158. {
  159.     struct snull_priv *priv = netdev_priv(dev);

  160.     //模拟传输中断
  161.     priv->status = SNULL_TX_INTR;
  162.     snull_interrupt(0, dev, NULL);
  163.     priv->stats.tx_errors++;
  164.     netif_wake_queue(dev);
  165.     return;
  166. }

  167. static int snull_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
  168. {
  169.     printk("snullnet:snull_ioctl\n");
  170.     return 0;
  171. }

  172. static struct net_device_stats *snull_stats(struct net_device *dev)
  173. {
  174.     struct snull_priv *priv = netdev_priv(dev);
  175.     return &priv->stats;
  176. }

  177. static int snull_create_header(struct sk_buff *skb, struct net_device *dev,
  178.                             unsigned short type, const void *daddr,
  179.                             const void *saddr, unsigned len)
  180. {
  181.     struct ethhdr *eth = (struct ethhdr *)skb_push(skb, ETH_HLEN);
  182.     
  183.     printk("snullnet:snull_create_header\n");
  184.     eth->h_proto = htons(type);
  185.     memcpy(eth->h_source, saddr ? saddr : dev->dev_addr, dev->addr_len);
  186.     memcpy(eth->h_dest, daddr ? daddr : dev->dev_addr, dev->addr_len);
  187.     eth->h_dest[ETH_ALEN - 1] ^= 1;
  188.     return (dev->hard_header_len);
  189. }

  190. static int snull_rebuild_header(struct sk_buff *skb)
  191. {
  192.     struct ethhdr *eth = (struct ethhdr *)skb->data;
  193.     struct net_device *dev = skb->dev;

  194.     printk("snullnet:snull_rebuild_header\n");
  195.     memcpy(eth->h_source, dev->dev_addr, dev->addr_len);
  196.     memcpy(eth->h_dest, dev->dev_addr, dev->addr_len);
  197.     eth->h_dest[ETH_ALEN - 1] ^= 1;
  198.     return 0;
  199. }

  200. static int snull_change_mtu(struct net_device *dev, int new_mtu)
  201. {
  202.     unsigned long flags;
  203.     struct snull_priv *priv = netdev_priv(dev);
  204.     spinlock_t *lock = &priv->lock;

  205.     printk("snullnet:snull_change_mtu\n");
  206.     if((new_mtu < 68) || (new_mtu > 1500)) return -EINVAL;

  207.     spin_lock_irqsave(lock, flags);
  208.     dev->mtu = new_mtu;
  209.     spin_unlock_irqrestore(lock, flags);
  210.     return 0;
  211. }

  212. static struct header_ops snull_header_ops = {
  213.     .create     = snull_create_header,
  214.     .rebuild    = snull_rebuild_header,
  215. };

  216. static struct net_device_ops snulldev_ops = {
  217.     .ndo_open                 = snull_open,
  218.     .ndo_stop                 = snull_release,
  219.     .ndo_set_config         = snull_config,
  220.     .ndo_start_xmit         = snull_tx,
  221.     .ndo_do_ioctl            = snull_ioctl,
  222.     .ndo_get_stats            = snull_stats,
  223.     .ndo_change_mtu            = snull_change_mtu,
  224.     .ndo_tx_timeout            = snull_tx_timeout,
  225. };

  226. static void snull_rx_ints(struct net_device *dev, int enable)
  227. {
  228.     struct snull_priv *priv     = netdev_priv(dev);
  229.     priv->rx_int_enabled         = enable;
  230. }

  231. static void snull_setup_pool(struct net_device *dev)
  232. {
  233.     
  234.     struct snull_priv *priv = netdev_priv(dev);
  235.     int i;
  236.     struct snull_packet *pkt;

  237.     printk("snullnet:snull_setup_pool\n");
  238.     priv->ppool = NULL;
  239.     for(i = 0; i < pool_size; i++) {
  240.         pkt = kmalloc(sizeof (struct snull_packet), GFP_KERNEL);
  241.         if(NULL == pkt) {
  242.             printk(KERN_NOTICE "Ran out of memory allocating packet pool\n");
  243.             return;
  244.         }
  245.         pkt->dev = dev;
  246.         pkt->next = priv->ppool;
  247.         priv->ppool = pkt;
  248.     }
  249. }

  250. static void snull_release_buffer(struct snull_packet *pkt)
  251. {
  252.     unsigned long flags;
  253.     struct snull_priv *priv = netdev_priv(pkt->dev);

  254.     spin_lock_irqsave(&priv->lock, flags);
  255.     pkt->next = priv->ppool;//牛逼,还整个结构提复用池
  256.     priv->ppool = pkt;
  257.     spin_unlock_irqrestore(&priv->lock, flags);
  258.     if (netif_queue_stopped(pkt->dev) && pkt->next == NULL) {
  259.         netif_wake_queue(pkt->dev);
  260.     }
  261. }

  262. static int snull_poll(struct napi_struct *napi, int work_limit)
  263. {
  264.     int nworked = 0;
  265.     struct snull_priv *priv = netdev_priv(napi->dev);
  266.     struct sk_buff *skb;
  267.     struct snull_packet *pkt;
  268.     unsigned long flags;

  269.     printk("snullnet:work_limit = %d\n", work_limit);

  270.     while(nworked < work_limit && priv->rx_queue) {
  271.         spin_lock_irqsave(&priv->lock, flags);
  272.         pkt = priv->rx_queue;
  273.         priv->rx_queue = priv->rx_queue->next;
  274.         spin_unlock_irqrestore(&priv->lock, flags);

  275.         skb = dev_alloc_skb(pkt->datalen + 2);
  276.         if (!skb) {
  277.             //没有分配到内存
  278.             priv->stats.rx_dropped++;
  279.             snull_release_buffer(pkt);
  280.             continue;
  281.         }
  282.         skb_reserve(skb, 2);
  283.         memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);
  284.         skb->dev = napi->dev;
  285.         skb->protocol = eth_type_trans(skb, napi->dev);
  286.         //skb->ip_summed = CHECKSUM_UNNECTSSARY;
  287.         netif_receive_skb(skb);

  288.         nworked++;
  289.         priv->stats.rx_packets++;
  290.         priv->stats.rx_bytes += pkt->datalen;
  291.         snull_release_buffer(pkt);
  292.     }

  293.     //该次的poll过程结束
  294.     if (! priv->rx_queue) {
  295.         //已经处理完所有数据包
  296.         napi_complete(napi);
  297.         snull_rx_ints(napi->dev, 1);
  298.         //return 0;
  299.     }
  300.     
  301.     return nworked;
  302. }

  303. static void snull_init(struct net_device *dev)
  304. {
  305.     struct snull_priv *priv;

  306.     printk("snullnet:snull_init\n");
  307.     priv = netdev_priv(dev);
  308.     ether_setup(dev);
  309.     dev->netdev_ops = &snulldev_ops;
  310.     dev->header_ops = &snull_header_ops;
  311.     if (use_napi) {
  312.         priv->snull_napi = kmalloc(sizeof (struct napi_struct), GFP_KERNEL);
  313.         netif_napi_add(dev, priv->snull_napi, snull_poll, 16);
  314.     }

  315.     dev->flags                 |= IFF_NOARP;
  316.     //dev->features             |=
  317.     //dev->hard_header_cache    =

  318.     memset(priv, 0, sizeof(struct snull_priv));
  319.     spin_lock_init(&priv->lock);
  320.     snull_rx_ints(dev, 1);
  321.     snull_setup_pool(dev);
  322. }



  323. static void snull_rx(struct net_device *dev, struct snull_packet *pkt)
  324. {
  325.     struct sk_buff *skb;
  326.     struct snull_priv *priv = netdev_priv(dev);

  327.     printk("snullnet:snull_rx\n");
  328.     skb = dev_alloc_skb(pkt->datalen + 2);
  329.     if(!skb) {
  330.         priv->stats.rx_dropped++;
  331.         goto out;
  332.     }

  333.     skb_reserve(skb, 2);
  334.     memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);

  335.     skb->dev = dev;
  336.     skb->protocol = eth_type_trans(skb, dev);
  337.     skb->ip_summed = CHECKSUM_UNNECESSARY;
  338.     priv->stats.rx_packets++;
  339.     priv->stats.rx_bytes += pkt->datalen;
  340.     netif_rx(skb);
  341. out:
  342.     return;
  343. }

  344. static void snull_napi_interrupt(int irq, void *dev_id, struct pt_regs *regs)
  345. {
  346.     int statusword;
  347.     struct snull_priv *priv;
  348.     struct net_device *dev = (struct net_device *)dev_id;
  349.     
  350.     printk("snull_napi_interrupt\n");

  351.     if (!dev) return;

  352.     priv = netdev_priv(dev);
  353.     spin_lock(&priv->lock);

  354.     statusword = priv->status;
  355.     priv->status = 0;                                                                                return;
  356.     if (statusword & SNULL_RX_INTR) {
  357.         snull_rx_ints(dev, 0);//禁止接收中断
  358.         napi_enable(priv->snull_napi);
  359.         napi_schedule(priv->snull_napi);
  360.     }
  361.     if (statusword & SNULL_TX_INTR) {
  362.         priv->stats.tx_packets++;
  363.         priv->stats.tx_bytes += priv->tx_packetlen;
  364.         kfree_skb(priv->skb);
  365.     }

  366.     spin_unlock(&priv->lock);
  367.     return ;
  368. }

  369. static void snull_regular_interrupt(int irq, \
  370.                                     void *dev_id, struct pt_regs *regs) {
  371.     int statusword;
  372.     struct snull_priv *priv;
  373.     struct snull_packet *pkt = NULL;
  374.     struct net_device *dev = (struct net_device *)dev_id;
  375.     if (!dev) return;

  376.     priv = netdev_priv(dev);
  377.     spin_lock(&priv->lock);

  378.     statusword = priv->status;
  379.     priv->status = 0;
  380.     if (statusword & SNULL_RX_INTR) {
  381.         pkt = priv->rx_queue;
  382.         if (pkt) {
  383.             priv->rx_queue = pkt->next;
  384.             snull_rx(dev, pkt);
  385.         }
  386.     }
  387.     if (statusword & SNULL_TX_INTR) {
  388.         priv->stats.tx_packets++;
  389.         priv->stats.tx_bytes += priv->tx_packetlen;
  390.         dev_kfree_skb(priv->skb);
  391.     }
  392.     
  393.     spin_unlock(&priv->lock);
  394.     if (pkt) snull_release_buffer(pkt);
  395.     return;
  396. }

  397. static void snull_free_pool(struct net_device *dev)
  398. {
  399.     int i;
  400.     struct snull_priv *priv = netdev_priv(dev);
  401.     struct snull_packet *pkt;

  402.     for(i = 0; i < pool_size; i++) {
  403.         pkt = priv->ppool;
  404.         priv->ppool = pkt->next;
  405.         kfree(pkt);
  406.     }
  407. }

  408. static void snullnet_clean(void)
  409. {
  410.     int i;
  411.     struct snull_priv *priv;

  412.     printk(KERN_ALERT "snullnet exiting");
  413.     for(i = 0;i < 2; i++) {
  414.         if(snull_dev[i]) {
  415.             unregister_netdev(snull_dev[i]);
  416.             snull_free_pool(snull_dev[i]);
  417.             priv = netdev_priv(snull_dev[i]);
  418.             kfree(priv->snull_napi);
  419.             free_netdev(snull_dev[i]);    
  420.         }
  421.     }
  422. }

  423. static int __init snullnet_init(void)
  424. {
  425.     int i, ret = 0;
  426.     printk(KERN_ALERT "snullnet initing");
  427.     
  428.     snull_interrupt = use_napi ? snull_napi_interrupt : \
  429.                                 snull_regular_interrupt;

  430.     for(i = 0; i < 2; i++) {//分配设备
  431.         snull_dev[i] = alloc_netdev(sizeof(struct snull_priv), "sn%d",
  432.                             snull_init);
  433.     }
  434.     if(!snull_dev[0] || !snull_dev[1]) {
  435.         ret = -ENOMEM;
  436.         goto out;
  437.     }
  438.     
  439.     for(i = 0; i < 2; i++) {//注册设备
  440.         int ret = 0;
  441.         if((ret = register_netdev(snull_dev[i]))) {
  442.             printk(KERN_ALERT "snull: error %i registering device \
  443.                     \"%s\"\n", ret, snull_dev[i]->name);
  444.             goto out;
  445.         }
  446.     }
  447.     return 0;

  448. out:
  449.     snullnet_clean();
  450.     return ret;
  451. }
  452. module_init(snullnet_init);
  453. module_exit(snullnet_clean);


上一篇:ubuntu创建root用户,以root登陆
下一篇:超经典程序设计类书籍大盘点