Linux网络协议栈(三)——网络设备(2)

2.1、网络设备的注册与注销
注册网络设备发生在下列情形:
(1)加载网卡驱动程序
  网卡驱动程序如果被编译进内核,则它在启动时被初始化,在运行时被作为模块加载。无论初始化是否发生,所以由驱动程序控制的网卡都被注册。
(2)插入可热拔插网络设备
  当用户插入一块热拔插网卡,内核通知其对应的驱动程序以注册设备。(为了简单化,我们假定设备驱动程序已经被加载)。

两个主要的情形会导致设备注销:
(1)卸载网卡驱动程序
  这只适用与驱动程序作为模块被加载的情形,当然不适于编译进内核的情况。当管理员卸载网卡设备驱动程序时,所有相关网卡的驱动程序都被注销。
(2)移除热拔插网络设备
  当用户从正在运行且内核支持热拔插功能的系统中移除热拔插网卡时,网络设备被注销。

2.1.1、分配 net_device结构空间
在内核中,网络设备由结构net_device表示,在注册网络设备之前,必须为之分配内存空间,这一任务由net/core/dev.c中定义的alloc_netdev函数来完成:


//net/core/dev.c
/*
分配内存空间
 ** sizeof_priv:私有数据结构大小
 ** name:网络设备的名称
 ** setup:配置函数
*
*/
struct net_device *alloc_netdev(int sizeof_priv, const char *name,
        
void (*setup)(struct net_device *))
{
    
void *p;
    
struct net_device *dev;
    
int alloc_size;

    
/* ensure 32-byte alignment of both the device and private area */
    alloc_size 
= (sizeof(*dev) + NETDEV_ALIGN_CONST) & ~NETDEV_ALIGN_CONST;
    alloc_size 
+= sizeof_priv + NETDEV_ALIGN_CONST;

    p 
= kmalloc(alloc_size, GFP_KERNEL);//分配内存
    if (!p) {
        printk(KERN_ERR 
"alloc_dev: Unable to allocate device./n");
        
return NULL;
    }
    memset(p, 
0, alloc_size);

    dev 
= (struct net_device *)
        (((
long)p + NETDEV_ALIGN_CONST) & ~NETDEV_ALIGN_CONST);
    dev
->padded = (char *)dev - (char *)p;

    
if (sizeof_priv)
        dev
->priv = netdev_priv(dev);//设备私有数据块

    setup(dev);
//调用初始化函数
    strcpy(dev->name, name); //设置网络设备名称
    return dev;
}

内核也提供了一些封装alloc_netdev功能的函数,如下:

alloc_etherdev函数用于以太网设备,所以它创建以字符串eth后跟唯一数字形式的设备名;第二点,它指派ether_setup作为配置函数,对于所有以太网卡来说,配置函数均把net_device结构的部分域初始化为公用值。


//net/ethernet/eth.c
struct net_device *alloc_etherdev(int sizeof_priv)
{
    
return alloc_netdev(sizeof_priv, "eth%d", ether_setup);
}

//以太网配置函数
void ether_setup(struct net_device *dev)
{
    dev
->change_mtu        = eth_change_mtu;
    dev
->hard_header    = eth_header;
    dev
->rebuild_header     = eth_rebuild_header;
    dev
->set_mac_address     = eth_mac_addr;
    dev
->hard_header_cache    = eth_header_cache;
    dev
->header_cache_update= eth_header_cache_update;
    dev
->hard_header_parse    = eth_header_parse;

    dev
->type        = ARPHRD_ETHER; //设备类类型(==1)
    dev->hard_header_len     = ETH_HLEN;  //以字节为单位的帧头部长度,14个字节
    dev->mtu        = 1500/* eth_mtu */
    dev
->addr_len        = ETH_ALEN;//链路层地址的长度,6个字节
    dev->tx_queue_len    = 1000;    /* Ethernet wants good queues */    
    dev
->flags        = IFF_BROADCAST|IFF_MULTICAST; //支持广播和多播
    
    memset(dev
->broadcast,0xFF, ETH_ALEN);
}

2.1.2、注册网络设备
网络设备注册(a)与注销模型(b):

为了简单,来看看回环设备的注册:


//drivers/net/loopback.c
int __init loopback_init(void)
{
    
struct net_device_stats *stats; 

    
/* Can survive without statistics */
    stats 
= kmalloc(sizeof(struct net_device_stats), GFP_KERNEL);
    
if (stats) { //分配内存成功
        memset(stats, 0sizeof(struct net_device_stats));
        loopback_dev.priv 
= stats;  //私有数据块
        loopback_dev.get_stats = &get_stats;
    }
    
    
return register_netdev(&loopback_dev); //注册回环网络设备
};

EXPORT_SYMBOL(loopback_dev);
//回环网络设备
struct net_device loopback_dev = {
    .name             
= "lo",
    .mtu            
= (16 * 1024+ 20 + 20 + 12,
    .hard_start_xmit    
= loopback_xmit,
    .hard_header        
= eth_header,
    .hard_header_cache    
= eth_header_cache,
    .header_cache_update    
= eth_header_cache_update,
    .hard_header_len    
= ETH_HLEN,    /* 14    */
    .addr_len        
= ETH_ALEN,    /* 6    */
    .tx_queue_len        
= 0,
    .type            
= ARPHRD_LOOPBACK,    /* 0x0001*/
    .rebuild_header        
= eth_rebuild_header,
    .flags            
= IFF_LOOPBACK,
    .features         
= NETIF_F_SG|NETIF_F_FRAGLIST
                  
|NETIF_F_NO_CSUM|NETIF_F_HIGHDMA
                  
|NETIF_F_LLTX,
    .ethtool_ops        
= &loopback_ethtool_ops,
};

注:在这里,设备驱动初始化函数loopback_init并没有调用alloc_netdev来分配内存,而是直接调用kmalloc,实际上alloc_netdev只是对kmalloc的封装而已。

2.2、网络设备的方法
2.2.1、打开与关闭设备
打开网络设备
一旦设备注册即可使用,但它必须在用户(或用户空间应用程序)使能后才可以收发数据。dev_open 处理设备使能的请求,它定义在net/core/dev.c,定义如下:


//打开设备
int dev_open(struct net_device *dev)
{
    
int ret = 0;

    
/*
     *    Is it already up?
     
*/

    
if (dev->flags & IFF_UP)
        
return 0;

    
/*
     *    Is it even present?
     
*/
    
if (!netif_device_present(dev))
        
return -ENODEV;

    
/*
     *    Call device private open method
     
*/
    set_bit(__LINK_STATE_START, 
&dev->state);
    
if (dev->open) { //如果网络设备驱动程序定义了open方法,则调用它
        ret = dev->open(dev);
        
if (ret)
            clear_bit(__LINK_STATE_START, 
&dev->state);
    }

     
/*
     *    If it went open OK then:
     
*/

    
if (!ret) {
        
/*
         *    Set the flags.
         
*/
        dev
->flags |= IFF_UP;//标记设备启动

        
/*
         *    Initialize multicasting status
         
*/
        dev_mc_upload(dev);

        
/*
         *    Wakeup transmit queue engine
         
*/
        dev_activate(dev);

        
/*
         *     and announce new interface.
         
*/
        notifier_call_chain(
&netdev_chain, NETDEV_UP, dev);
    }
    
return ret;
}

打开设备由以下几部组成:
(1)如果 dev->open 被定义则调用它。并非所有的驱动程序都初始化这个函数。
(2)设置 dev->state 的__LINK_STATE_START 标志位,标记设备打开并在运行。
(3)设置 dev->flags 的 IFF_UP 标志位标记设备启动。
(4)调用dev_activate所指函数初始化流量控制用的排队规则,并启动监视定时器。如
果用户没有配置流量控制,则指定缺省的先进先出(FIFO)队列。
(5)发送 NETDEV_UP 通知给 netdev_chain 通知链以通知对设备使能有兴趣的内核组件。

设备驱动的open方法
来看看3com网卡的驱动drivers/net/3c59x.c中的vortex_open,它是在vortex_init()中(即驱动程序初始化的过程中)赋给dev->open函数指针:


//打开网络设备,
static int
vortex_open(
struct net_device *dev)
{
  
//
vortex_up(dev);
}

static void
vortex_up(
struct net_device *dev)
{
//
//用于清除__LINK_STATE_XOFF标志,表示可以向网络适配器传达室递套接字缓冲区了
    netif_start_queue (dev);
}
//include/linux/netdevice.h
static inline void netif_start_queue(struct net_device *dev)
{
    clear_bit(__LINK_STATE_XOFF, 
&dev->state);
}


关闭网络设备


//关闭设备(net/core/dev.c)
int dev_close(struct net_device *dev)
{
    
if (!(dev->flags & IFF_UP))
        
return 0;

    
/*
     *    Tell people we are going down, so that they can
     *    prepare to death, when device is still operating.
     
*/
    notifier_call_chain(
&netdev_chain, NETDEV_GOING_DOWN, dev);

    dev_deactivate(dev);

    clear_bit(__LINK_STATE_START, 
&dev->state);

    
/* Synchronize to scheduled poll. We cannot touch poll list,
     * it can be even on different cpu. So just clear netif_running(),
     * and wait when poll really will happen. Actually, the best place
     * for this is inside dev->stop() after device stopped its irq
     * engine, but this requires more changes in devices. 
*/

    smp_mb__after_clear_bit(); 
/* Commit netif_running(). */
    
while (test_bit(__LINK_STATE_RX_SCHED, &dev->state)) {
        
/* No hurry. */
        current
->state = TASK_INTERRUPTIBLE;
        schedule_timeout(
1);
    }

    
/*
     *    Call the device specific close. This cannot fail.
     *    Only if device is UP
     *
     *    We allow it to be called even after a DETACH hot-plug
     *    event.
     
*/
    
if (dev->stop)
        dev
->stop(dev); //调用设备驱动的close方法

    
/*
     *    Device is now down.
     
*/

    dev
->flags &= ~IFF_UP;

    
/*
     * Tell people we are down
     
*/
    notifier_call_chain(
&netdev_chain, NETDEV_DOWN, dev);

    
return 0;
}

它由以下几步组成:
(1)发送 NETDEV_GOING_DOWN 通知到 netdev_chain 通知链以通知对设备禁止有兴趣的内核组件。
(2)调用 dev_deactivate 函数禁止出口队列规则,这样确保设备不再用于传输,并停止
不再需要的监控定时器。
(3)清除 dev->state 标志的__LINK_STATE_START 标志位,标记设备卸载。
(4)如果轮询动作被调度在读设备入队列数据包,则等待此动作完成。这是由于__LINK_STATE_START 标志位被清除,不再接受其它轮询在设备上调度,但在标志被清除前已有一个轮询正被调度。
(5)如果 dev->stop 指针不空则调用它,并非所有的设备驱动都初始化此函数指针。
(6)清除 dev->flags 的 IFF_UP 标志位标识设备关闭。
(7)发送 NETDEV_DOWN 通知给 netdev_chain 通知链,通知对设备禁止感兴趣的内核组件。

2.2.2、传输数据与接收数据
传输数据
网络子系统中,数据最后由链路层协议调用dev_queue_xmit(),位于net/core/dev.c,完成传输,而dev_queue_xmit又会调用具体网络适配器的驱动程序方法dev->hard_start_xmit(),从而驱动网络适配器最终完成数据传输,参见vortex_start_xmit()。

接收数据
当网络适配器接收一个数据帧时,就会触发一个中断,在中断处理程序(位于设备驱动)中,会分配sk_buff数据结构保存数据帧,最后会调用netif_rx(),将套接字缓冲区放入网络设备的输入队列。对于3c39x.c,其过程如下:
vortex_interrupt( )---> vortex_rx( )--->netif_rx( )。

来看看回环设备的发送与接收过程:

// derivers/net/loopback.c
/*
首先调用skb_orphan把skb孤立,使它跟发送socket和协议栈不再有任何联系,也即对本机来说,
**这个skb的数据内容已经发送出去了,而skb相当于已经被释放掉了。对于环回设备接口来说,
**数据的发送工作至此已经全部完成,接下来,只要把这个实际上还未被释放的skb传回给协议栈
**的接收函数即可。
*/
static   int  loopback_xmit( struct  sk_buff  * skb,  struct  net_device  * dev)
{
    
struct  net_device_stats  * lb_stats;
    
/ 相当于发送过程 // //
    skb_orphan(skb);

    
/ /以下相当于接收过程 /// /
    skb -> protocol = eth_type_trans(skb,dev);
    skb
-> dev = dev;
#ifndef LOOPBACK_MUST_CHECKSUM
    skb
-> ip_summed  =  CHECKSUM_UNNECESSARY;
#endif

    
if  (skb_shinfo(skb) -> tso_size) {
        BUG_ON(skb
-> protocol  !=  htons(ETH_P_IP));
        BUG_ON(skb
-> nh.iph -> protocol  !=  IPPROTO_TCP);

        emulate_large_send_offload(skb);
        
return   0 ;
    }

    dev
-> last_rx  =  jiffies;  // 接收到数据的时间
    
    
// 统计信息
    lb_stats  =   & per_cpu(loopback_stats, get_cpu());
    lb_stats
-> rx_bytes  +=  skb -> len;
    lb_stats
-> tx_bytes  +=  skb -> len;
    lb_stats
-> rx_packets ++ ;
    lb_stats
-> tx_packets ++ ;
    put_cpu();

    netif_rx(skb);

    
return ( 0 );
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值