(转载)Linux内核网络设备操作部分阅读笔记

 
原文出处:http://tb.blog.csdn.net/TrackBack.aspx?PostId=589444


一、网络设备的初始化

网络设备初始化就是调用具有__init 前缀的net_dev_init函数完成的,网络设备初始化
包含两个部分(在linux内核2.4办源代码分析大全一书的第550页有详细说明),就是:


在系统初始化期间对系统已知的网络设备进行初始化过程,也就是,我们在编译内核时
选择编入内核的那部分网卡设备就会在这个时候逐个进行初始化工作。系统中已知的网
络设备都存储在一个全局表中,dev_base[],它将所有网络设备的net_devive结构连接
在一起。

int __init net_dev_init(void)

{

struct net_device *dev, **dp;

int i;



/*

这里如果是从系统初始化调用的则全局变量dev_boot_phase置1,如果是模块插
入则dev_boot_phase置0,到达这个地方以后就直接返回了。这就保证了系统调用设备初
始化函数一次。

*/

if (!dev_boot_phase)

return 0;

/*

在这里初始化数据包接收队列。

*/



for (i = 0; i < NR_CPUS; i++) {

struct softnet_data *queue;

/*

softnet_data数组内核为每一个CPU都维护一个接收数据包的队列,这
样不同的CPU之间就不需要进行互斥访问操作了。当然,如果只有一个CPU的话,这个队
列的维数就是1。

struct softnet_data{

int throttle; /*为 1 表示当前队列的数据包被禁止*/

int cng_level; /*表示当前处理器的数据包处理拥塞程度*/

int avg_blog; /*某个处理器的平均拥塞度*/

struct sk_buff_head input_pkt_queue;/*接收缓冲区的sk_buff队列*/

struct list_head poll_list; /*POLL设备队列头*/

struct net_device output_queue; /*网络设备发送队列的队列头*/

struct sk_buff completion_queue; /*完成发送的数据包等待释放的队列*/

struct net_device backlog_dev; /*表示当前参与POLL处理的网络设备*/

};

在这里我们初始化网络接收数据报队列的相关参数,初始化输入包队列
、完成队列等,其中应当注意的是咱这里注册了网卡的通用poll方法,该方法在网络软
中断处理当中负责从数据包队列当中取出待处理数据,并递交给上层协议进行处理。每
个网卡驱动程序可以自己实现poll函数,例如e1000就实现了poll方法,而一般的讲,是
不需要自己实现该函数的,因为系统已经提供了一个默认函数process_backlog。



*/

queue = &softnet_data[i];

skb_queue_head_init(&queue->input_pkt_queue);

queue->throttle = 0;

queue->cng_level = 0;

queue->avg_blog = 10; /* arbitrary non-zero */

queue->completion_queue = NULL;

INIT_LIST_HEAD(&queue->poll_list);

set_bit(__LINK_STATE_START, &queue->blog_dev.state);

queue->blog_dev.weight = weight_p;

queue->blog_dev.poll = process_backlog;

atomic_set(&queue->blog_dev.refcnt, 1);

}

/*

开始添加设备,这里从dev_base列表当中逐个取出net_device结构,如果该结构
存在,则对结构的一些参数进行初始化。最为关键的就是在这里调用每个设备的init函
数,如果该设备已经存在则init函数应该调用成功,如果调用失败,则表明可能该设备
并不存在,应当从dev_base列表中清除。

对于每个设备的dev->init方法对于不同类型的网络设备各不相同,对于以太网
卡来说,在driver/net/Space.c当中将其初始化为ethif_probe。

*/

dp = &dev_base;

while ((dev = *dp) != NULL) {

spin_lock_init(&dev->queue_lock);

spin_lock_init(&dev->xmit_lock);



dev->xmit_lock_owner = -1;

dev->iflink = -1;

dev_hold(dev);



/*

* Allocate name. If the init() fails

* the name will be reissued correctly.

*/

if (strchr(dev->name, '%'))

dev_alloc_name(dev, dev->name);



/*

* Check boot time settings for the device.

*/

netdev_boot_setup_check(dev);



if (dev->init && dev->init(dev)) {

/*

Init函数调用成功返回0,否则返回非0,这里返回非0,表明初
始化过程出现了问题,我们将它的deadbeaf标志置为,并在下面把这个设备从列表中删
除。如果init成功,则进一步进行初始化。

*/

dev->deadbeaf = 1;

dp = &dev->next;

} else {

dp = &dev->next;

dev->ifindex = dev_new_index();

if (dev->iflink == -1)

dev->iflink = dev->ifindex;

if (dev->rebuild_header == NULL)

dev->rebuild_header = default_rebuild_header;

dev_init_scheduler(dev);

set_bit(__LINK_STATE_PRESENT, &dev->state);

}

}



/*

在这里根据deadbeaf标志讲所有init调用失败的设备删除。

*/

dp = &dev_base;

while ((dev = *dp) != NULL) {

if (dev->deadbeaf) {

write_lock_bh(&dev_base_lock);

*dp = dev->next;

write_unlock_bh(&dev_base_lock);

dev_put(dev);

} else {

dp = &dev->next;

}

}

/*

到这里内核初始化过程已经完成。

*/

dev_boot_phase = 0;

/*

注册网络接收和发送软中断处理函数,发送处理函数是net_tx_action,接收处
理函数是net_rx_action。当系统调用软中断入口函数do_softirq时,就是调用net_rx_
action函数对接收数据包队列进行处理的,在这个函数当中又调用了相应网络设备的po
ll方法,以轮询的方式遍历数据包队列,将数据报向上层协议转发。

*/

open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);

open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);



dst_init();

dev_mcast_init();

/*

* Initialise network devices

*/

net_device_init();



return 0;





}

在这里我们列出了网络设备在内核初始化过程中的初始化函数调用关系。



上面对net_dev_init函数的分析已经知道,为每一个dev调用init函数,对于不同的设备
系统在drivers/net/Space.c当中已经分别进行了注册,对于以太网卡则将其init方法注
册为ethif_probe,它调用probe_list函数为每一个以太网卡调用已经注册好的probe方
法。对于基于pci设备的网卡驱动程序来说,应该为每一个网卡创建一个pci_driver结构
,该结构定义了该设备如何探测,如何从系统中删除等等基本的操作函数。每当实现一
个新的网卡驱动程序时,就可以将相应的处理函数注册到该结构当中,调用点就在prob
e_list函数中。

对于e1000网卡驱动来说,它的probe函数注册为e1000_probe,因此这也就是这个网卡最
早被调用的函数:

static int __devinit e1000_probe(struct pci_dev *pdev, const struct pci_devi
ce_id *ent)

{

struct net_device *netdev;

struct e1000_adapter *adapter;

static int cards_found = 0;

unsigned long mmio_start;

int mmio_len;

int pci_using_dac;

int i;

int err;

uint16_t eeprom_data;



if((err = pci_enable_device(pdev)))

return err;

/*

初始化网卡的DMA传输机制。

*/

if(!(err = pci_set_dma_mask(pdev, PCI_DMA_64BIT))) {

pci_using_dac = 1;

} else {

if((err = pci_set_dma_mask(pdev, PCI_DMA_32BIT))) {

E1000_ERR("No usable DMA configuration, aborting/n");

return err;

}

pci_using_dac = 0;

}



if((err = pci_request_regions(pdev, e1000_driver_name)))

return err;



pci_set_master(pdev);



/*

为这个设备分配一个net_device结构,并且调用ether_setup初始化该结构的部
分成员,特别是所有以太网设备的共同参数。

*/

netdev = alloc_etherdev(sizeof(struct e1000_adapter));

if(!netdev) {

err = -ENOMEM;

goto err_alloc_etherdev;

}



SET_MODULE_OWNER(netdev);



pci_set_drvdata(pdev, netdev);

adapter = netdev->priv;

adapter->netdev = netdev;

adapter->pdev = pdev;

adapter->hw.back = adapter;



mmio_start = pci_resource_start(pdev, BAR_0);

mmio_len = pci_resource_len(pdev, BAR_0);



adapter->hw.hw_addr = ioremap(mmio_start, mmio_len);

if(!adapter->hw.hw_addr) {

err = -EIO;

goto err_ioremap;

}



for(i = BAR_1; i <= BAR_5; i++) {

if(pci_resource_len(pdev, i) == 0)

continue;

if(pci_resource_flags(pdev, i) & IORESOURCE_IO) {

adapter->hw.io_base = pci_resource_start(pdev, i);

break;

}

}

/*

将e1000网卡驱动程序的各种处理函数注册到net_device结构的各个成员上,op
en方法用来打开一个网卡设备,hard_start_xmit方法用来进行网络数据传输,而poll方
法就是在软中断处理程序net_rx_action中被调用的poll方法。并且调用ether_setup初
始化该结构的部分成员,特别是所有以太网设备的共同参数。

*/

netdev->open = &e1000_open;

netdev->stop = &e1000_close;

netdev->hard_start_xmit = &e1000_xmit_frame;

netdev->get_stats = &e1000_get_stats;

netdev->set_multicast_list = &e1000_set_multi;

netdev->set_mac_address = &e1000_set_mac;

netdev->change_mtu = &e1000_change_mtu;

netdev->do_ioctl = &e1000_ioctl;

netdev->tx_timeout = &e1000_tx_timeout;

netdev->watchdog_timeo = 5 * HZ;

#ifdef CONFIG_E1000_NAPI

/*

如果在配置内核参数时选择了采用NAPI处理网络数据,则定义e1000自己的poll
处理函数(e1000_clean),否则就采用系统默认的处理函数process_backlog。以后会
对这两个函数的作用及不同点进行分析和比较。

*/

netdev->poll = &e1000_clean;

netdev->weight = 64;

#endif

netdev->vlan_rx_register = e1000_vlan_rx_register;

netdev->vlan_rx_add_vid = e1000_vlan_rx_add_vid;

netdev->vlan_rx_kill_vid = e1000_vlan_rx_kill_vid;



netdev->irq = pdev->irq;

netdev->mem_start = mmio_start;

netdev->mem_end = mmio_start + mmio_len;

netdev->base_addr = adapter->hw.io_base;



adapter->bd_number = cards_found;



/* setup the private structure */



if((err = e1000_sw_init(adapter)))

goto err_sw_init;



if(adapter->hw.mac_type >= e1000_82543) {

netdev->features = NETIF_F_SG |

NETIF_F_HW_CSUM |

NETIF_F_HW_VLAN_TX |

NETIF_F_HW_VLAN_RX |

NETIF_F_HW_VLAN_FILTER;

} else {

netdev->features = NETIF_F_SG;

}



#ifdef NETIF_F_TSO

if((adapter->hw.mac_type >= e1000_82544) &&

(adapter->hw.mac_type != e1000_82547))

netdev->features |= NETIF_F_TSO;

#endif



if(pci_using_dac)

netdev->features |= NETIF_F_HIGHDMA;



/* before reading the EEPROM, reset the controller to

* put the device in a known good starting state */



e1000_reset_hw(&adapter->hw);



/* make sure the EEPROM is good */



if(e1000_validate_eeprom_checksum(&adapter->hw) < 0) {

printk(KERN_ERR "The EEPROM Checksum Is Not Valid/n");

err = -EIO;

goto err_eeprom;

}



/* copy the MAC address out of the EEPROM */



e1000_read_mac_addr(&adapter->hw);

memcpy(netdev->dev_addr, adapter->hw.mac_addr, netdev->addr_len);



if(!is_valid_ether_addr(netdev->dev_addr)) {

err = -EIO;

goto err_eeprom;

}



e1000_read_part_num(&adapter->hw, &(adapter->part_num));



e1000_get_bus_info(&adapter->hw);



init_timer(&adapter->tx_fifo_stall_timer);

adapter->tx_fifo_stall_timer.function = &e1000_82547_tx_fifo_stall;

adapter->tx_fifo_stall_timer.data = (unsigned long) adapter;



init_timer(&adapter->watchdog_timer);

adapter->watchdog_timer.function = &e1000_watchdog;

adapter->watchdog_timer.data = (unsigned long) adapter;



init_timer(&adapter->phy_info_timer);

adapter->phy_info_timer.function = &e1000_update_phy_info;

adapter->phy_info_timer.data = (unsigned long) adapter;



INIT_TQUEUE(&adapter->tx_timeout_task,

(void (*)(void *))e1000_tx_timeout_task, netdev);



/*

将自己注册到dev_base数组当中,这个调用主要是为了模块启动准备的,因为如
果该网卡在net_dev_init已经初始化,则dev_base数组当中已经存在,在register_net
device函数中就会判断当前的net_device是否与dev_base中的某个相同,如果发现相同
的就立刻返回。对于模块方式来说,此时正是该设备初始化的好时机,首先调用dev->i
nit方法尝试对设备进行初始化工作,然后将自己添加到dev_base数组的最后,这样系统
就可以利用这个设备收发数据了。

register_netdev事实上是重复了net_dev_init的很多工作,设备驱动程序的模
块化启动顺序图示如下:











*/

register_netdev(netdev);



/* we're going to reset, so assume we have no link for now */



netif_carrier_off(netdev);

netif_stop_queue(netdev);



printk(KERN_INFO "%s: Intel(R) PRO/1000 Network Connection/n",

netdev->name);

e1000_check_options(adapter);

}



二、网络设备的打开和关闭操作

每一个pci设备的驱动程序在注册自己的时候都提供了open方法和close方法用来对设备
进行打开和关闭操作。

设备的open操作在net/core/dev.c中的dev_open函数中调用。究竟dev_open是由谁调用
的我们并不关心。

网卡设备是否处于打开状态由dev->flags标志控制,如果该标志置为IFF_UP,则表明设
备已经打开。



三、网络数据的接收过程

网络数据的接收过程是根据硬件的中断来实现的,每个网卡驱动程序提供硬件中断处理
函数,系统收到某个硬件发来的中断信号以后,调用do_IRQ函数。该函数根据中断信号
中带的中断号判断是那一个硬件产生的中断,然后查找相应的设备的中断处理入口函数
,调用handle_IRQ_event,在这个函数中调用了网卡的中断处理函数。以e1000为例,则
是e1000_intr:

static irqreturn_t e1000_intr(int irq, void *data, struct pt_regs *regs)

{

struct net_device *netdev = data;

struct e1000_adapter *adapter = netdev->priv;

uint32_t icr = E1000_READ_REG(&adapter->hw, ICR);

#ifndef CONFIG_E1000_NAPI

unsigned int i;

#endif

/*

首先判断是否是该网卡的中断号。

*/

if(!icr)

return IRQ_NONE; /* Not our interrupt */



if(icr & (E1000_ICR_RXSEQ | E1000_ICR_LSC)) {

adapter->hw.get_link_status = 1;

mod_timer(&adapter->watchdog_timer, jiffies);

}

/*

如果采用网卡的NAPI方法,则在这里转入轮询的处理过程。

*/

#ifdef CONFIG_E1000_NAPI

if(netif_rx_schedule_prep(netdev)) {



/* Disable interrupts and register for poll. The flush

of the posted write is intentionally left out.

*/



atomic_inc(&adapter->irq_sem);

E1000_WRITE_REG(&adapter->hw, IMC, ~0);

__netif_rx_schedule(netdev);

}

#else

/*

否则,采用正常的中断处理机制,这里设置一个变量E1000_MAX_INTR可能表示一次中断
处理,网卡能够处理的网络数据包的个数。因为从效率的角度上讲,中断一次能够处理
的数据包越多越好,但是中断处理函数需要屏蔽所有的中断,因此,过长的处理时间会
导致系统不能同时相应其他的中断请求。

*/

for(i = 0; i < E1000_MAX_INTR; i++)

if(!e1000_clean_rx_irq(adapter) &

!e1000_clean_tx_irq(adapter))

break;

#endif



return IRQ_HANDLED;

}



事实上,在网卡的中断模式下,e1000_clean_rx_irq是从硬件接收数据的关键函数,同
时,该函数还可以处理轮询情况。

e1000_clean_rx_irq(struct e1000_adapter *adapter)

{

i = rx_ring->next_to_clean;

rx_desc = E1000_RX_DESC(*rx_ring, i);



while(rx_desc->status & E1000_RXD_STAT_DD) {

buffer_info = &rx_ring->buffer_info[i];



#ifdef CONFIG_E1000_NAPI

if(*work_done >= work_to_do)

break;



(*work_done)++;

#endif



cleaned = TRUE;

/*

当前的PCI网卡驱动程序基本上都是采用DMA传输的方式接收数据包,一
次硬件中断的产生表明一批数据已经通过设备的DMA通道从硬件层传递到了DMA缓冲区当
中,这个DMA缓冲区是在设备创建的时候申请的。数据已经拷贝到DMA区以后,会产生一
个中断,通知中断处理函数将这些数据取走。这里调用pci_unmap_single就是将缓冲区
映射解除,以便开始处理数据。

*/

pci_unmap_single(pdev,

buffer_info->dma,

buffer_info->length,

PCI_DMA_FROMDEVICE);



skb = buffer_info->skb;

length = le16_to_cpu(rx_desc->length);

/*

这里忽略了错误处理。

*/

/* 接收到一个正确的数据包。*/

skb_put(skb, length - ETHERNET_FCS_SIZE);



/* 校验和 */

e1000_rx_checksum(adapter, rx_desc, skb);



skb->protocol = eth_type_trans(skb, netdev);

/*

采用NAPI方法接收数据包。

*/

#ifdef CONFIG_E1000_NAPI

if(adapter->vlgrp && (rx_desc->status & E1000_RXD_STAT_VP)) {

vlan_hwaccel_receive_skb(skb, adapter->vlgrp,

le16_to_cpu(rx_desc->special &

E1000_RXD_SPC_VLAN_MASK));

} else {

netif_receive_skb(skb);

}

#else /* CONFIG_E1000_NAPI */

/*

采用正常的中断方式处理数据包。实际上就是调用netif_rx将接收到的skb放到接收缓冲
区列表当中,然后就返回了。接收缓冲区队列中的数据由随后执行的软中断处理函数进
一步处理。

*/

if(adapter->vlgrp && (rx_desc->status & E1000_RXD_STAT_VP)) {

vlan_hwaccel_rx(skb, adapter->vlgrp,

le16_to_cpu(rx_desc->special &

E1000_RXD_SPC_VLAN_MASK));

} else {

netif_rx(skb);

}

#endif /* CONFIG_E1000_NAPI */

netdev->last_rx = jiffies;



rx_desc->status = 0;

buffer_info->skb = NULL;



if(++i == rx_ring->count) i = 0;



rx_desc = E1000_RX_DESC(*rx_ring, i);

}



rx_ring->next_to_clean = i;



e1000_alloc_rx_buffers(adapter);



return cleaned;

}

网卡的硬件中断处理函数主要的工作就是将DMA传输过来的数据包(skbuff)插入相应的
接收缓冲区队列中,然后调用netif_rx函数将sk_buff插入接收缓冲区队列中就返回了。
而在e1000的网卡中断处理程序当中用到了比较新的技术NAPI,这种技术是采用轮询的机
制替换中断模式,加快网络数据的处理速度,详细的内容见《NAPI技术在Linux网络驱动
上的应用》。这里我暂时只分析传统的中断机制。



四、网络处理的软中断机制分析

详见《网络处理的软中断机制分析》。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值