Linux内核协议栈分析之网卡初始化

本文以Linux1.0版本为例,详细分析了Linux网络协议栈中网卡的初始化过程,包括网卡设备注册、内存分配及中断设置等关键步骤。

Linux内核协议栈分析之网卡初始化——tcp/ip通信并不神秘(1)


id="cproIframe_u1507428" width="300" height="250" src="http://pos.baidu.com/acom?adn=0&at=103&aurl=&cad=1&ccd=24&cec=UTF-8&cfv=15&ch=0&col=zh-CN&conOP=0&cpa=1&dai=2&dis=0&ltr=http%3A%2F%2Fwww.baidu.com%2Fs%3Fie%3Dutf-8%26f%3D8%26rsv_bp%3D1%26ch%3D1%26tn%3D90294343_hao_pg%26wd%3Dinit_etherdev%26rsv_enter%3D0%26rsv_n%3D2%26rsv_sug3%3D1%26rsv_sug4%3D28%26inputT%3D1122%26bs%3Dlinux%25E7%25BD%2591%25E5%258D%25A1%25E9%25A9%25B1%25E5%258A%25A8%25E5%25BC%2580%25E5%258F%2591&ltu=http%3A%2F%2Fwww.xuebuyuan.com%2F1362487.html&lunum=6&n=83099053_cpr&pcs=1366x655&pis=10000x10000&ps=326x909&psr=1366x768&pss=1366x346&qn=1512bbdc2fefd90d&rad=&rsi0=300&rsi1=250&rsi5=4&rss0=%23FFFFFF&rss1=%23FFFFFF&rss2=%230080c0&rss3=%23444444&rss4=%23008000&rss5=&rss6=%23e10900&rss7=&scale=&skin=&td_id=1507428&tn=text_default_300_250&tpr=1414137173092&ts=1&xuanting=0&dtm=BAIDU_DUP2_SETJSONADSLOT&dc=2&di=u1507428" align="center,center" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" allowtransparency="true" style="margin: 0px; padding: 0px; border-width: 0px; background: transparent;">

写在代码前:

        写技术类文章的一个痛苦之处在于——写简单了,看的人觉得没意思;写难了,又看不懂是什么意思。例如——《redis源代码分析——事件机制》http://blog.youkuaiyun.com/freas_1990/article/details/8253906。所以,最简单的就是写一些对话了。不过写对话毕竟不是写技术类文章,姑且当做消遣来看吧。

 

 

        本文将会以Linux1.0版本,来简单分析Linux网络协议栈(TCP/IP)。之所以会选择1.0版本,是因为1.0版本比较简单,本着学习的目的够用就好,没必要搞一个3.x的内核包,在介绍tcp/ip的同时,还把SMP、/proc等等话题介绍一遍,搞得看得人蛋疼,写的人也蛋疼,疼到最后,大家都不清楚tcp/ip是啥了。

         在这里可以找到所有1.0版本的Linux内核包ftp://ftp.kernel.org/pub/linux/kernel/v1.0/

       需要关注的包括net、drivers这两个文件夹,net文件夹下包括了所有的tcp/ip实现核心文件。而drivers目录下的net目录存放了1994年流行的各种网卡的驱动。

介绍完毕。开始分析代码。

1、打开init目录下main.c文件。这个文件是Linux内核初始化的所在,如果要阅读Linux内核源代码。以这个文件为总纲就行了。

2、找到asmlinkage void start_kernel(void)//main.c line 351

3、大家关注下这几行代码

#ifdef CONFIG_INET   //main.c line 398
 memory_start = net_dev_init(memory_start,memory_end);
#endif

4、net_dev_init()这个函数定义在drivers/net/net_init.c line 64

5、

   net_dev_init //drivers/net/net_init.c line 64

            mem_start = lance_init(mem_start, mem_end); //drivers/net/lance.c line 222

                     mem_start = lance_probe1(ioaddr, mem_start); //drivers/net/lance.c line 239

                              dev = init_etherdev(0, sizeof(struct lance_private) + PKT_BUF_SZ*(RX_RING_SIZE + TX_RING_SIZE), &mem_start); //drivers/net/net_init.c line 95

init_etherdev是真正的网卡注册函数,这个函数执行完毕后,Linux就可以“正确操作”网卡,而网卡也已经作为一个dev对象嵌入到Linux内核。

 if (new_device) {
  /* Append the device to the device queue. */
  struct device **old_devp = &dev_base;
  while ((*old_devp)->next)
   old_devp = & (*old_devp)->next;
  (*old_devp)->next = dev;
  dev->next = 0;
 }

 

注意这几行代码,Linux把所有的网卡设备归为一类,所有的网卡通过一个链表链接起来。当输入ifconfig的时候,把所有的网卡遍历一遍,获取所有的dev对象的属性即可。

 

我们来详细分析下这几个核心函数。

unsigned long net_dev_init (unsigned long mem_start, unsigned long mem_end)
{

#ifdef NET_MAJOR_NUM
 if (register_chrdev(NET_MAJOR_NUM, "network",&netcard_fops))
  printk("WARNING: Unable to get major %d for the network devices.\n",
      NET_MAJOR_NUM);
#endif

#if defined(CONFIG_LANCE)   /* Note this is _not_ CONFIG_AT1500. */
 mem_start = lance_init(mem_start, mem_end);
#endif

 return mem_start;
}

 

早期的网卡被划分成字符型设备(现在一般把网卡设备与字符设备、块设备并列),所以调用了register_chrdev。不过,这不是重点。

重点在mem_start = lance_init(mem_start, mem_end);这一行。lance_init()函数以两个内存地址作为参数,是为网卡分配地址空间,在mem_start,mem_end这中间的内存是给网卡用的。

unsigned long lance_init(unsigned long mem_start, unsigned long mem_end)
{
    int *port, ports[] = {0x300, 0x320, 0x340, 0x360, 0};

    for (port = &ports[0]; *port; port++) {
 int ioaddr = *port;

 if (   check_region(ioaddr, LANCE_TOTAL_SIZE) == 0
     && inb(ioaddr + 14) == 0x57
     && inb(ioaddr + 15) == 0x57) {
     mem_start = lance_probe1(ioaddr, mem_start);
 }
    }

    return mem_start;
}

 

lance_init()这个函数做了2件事:

     1、给网卡分配IO端口。(这种IO模式早已经被淘汰了)

     2、把获得的地址传递给lance_probe1。

所以,lance_init()其实是个包裹函数。

unsigned long lance_probe1(short ioaddr, unsigned long mem_start)
{
    struct device *dev;
......

    return mem_start;
}

这个函数看起来比较复杂,总结起来,做了3件事:

1、继续进行网卡的IO设置。

2、调用了init_etherdev//这个函数是网卡设备注册的核心。

3、

 if (dev->irq < 2) {

     autoirq_setup(0);

     /* Trigger an initialization just for the interrupt. */
     outw(0x0041, ioaddr+LANCE_DATA);

     dev->irq = autoirq_report(1);
     if (dev->irq)
  printk(", probed IRQ %d, fixed at DMA %d.\n",
         dev->irq, dev->dma);
     else {
  printk(", failed to detect IRQ line.\n");
  return mem_start;
     }
 } else...

    dev->hard_start_xmit = &lance_start_xmit;

 

第3部分是和中断相关的。一个网卡必须要把中断设置好,不然数据来了不知道接收、数据要发出去不知道怎么发送。

这里的中断还采用了早期的BH(下半部)方式,在当时,是比较牛逼的技术了。不过现在看起来已经很过时了。

struct device *init_etherdev(struct device *dev, int sizeof_private,
        unsigned long *mem_startp)
{
 int i;
 int new_device = 0;

 if (dev == NULL) {
  int alloc_size = sizeof(struct device) + sizeof("eth%d ")
   + sizeof_private;
  if (mem_startp && *mem_startp ) {
   dev = (struct device *)*mem_startp;
   *mem_startp += alloc_size;
  } else
   dev = (struct device *)kmalloc(alloc_size, GFP_KERNEL);
  memset(dev, 0, sizeof(alloc_size));
  dev->name = (char *)(dev + 1);
  if (sizeof_private)
   dev->priv = dev->name + sizeof("eth%d ");
  new_device = 1;
 }

 if (dev->name  &&  dev->name[0] == '\0')
  sprintf(dev->name, "eth%d", next_ethdev_number++);

 for (i = 0; i < DEV_NUMBUFFS; i++)
  dev->buffs[i] = NULL;
 
 dev->hard_header = eth_header;
 dev->add_arp  = eth_add_arp;
 dev->queue_xmit  = dev_queue_xmit;
 dev->rebuild_header = eth_rebuild_header;
 dev->type_trans  = eth_type_trans;
 
 dev->type   = ARPHRD_ETHER;
 dev->hard_header_len = ETH_HLEN;
 dev->mtu   = 1500; /* eth_mtu */
 dev->addr_len  = ETH_ALEN;
 for (i = 0; i < ETH_ALEN; i++) {
  dev->broadcast[i]=0xff;
 }
 
 /* New-style flags. */
 dev->flags   = IFF_BROADCAST;
 dev->family   = AF_INET;
 dev->pa_addr  = 0;
 dev->pa_brdaddr  = 0;
 dev->pa_mask  = 0;
 dev->pa_alen  = sizeof(unsigned long);
 
 if (new_device) {
  /* Append the device to the device queue. */
  struct device **old_devp = &dev_base;
  while ((*old_devp)->next)
   old_devp = & (*old_devp)->next;
  (*old_devp)->next = dev;
  dev->next = 0;
 }
 return dev;
}

 

在这里,网卡设备终于获取到它应得的内存:

  if (mem_startp && *mem_startp ) {
   dev = (struct device *)*mem_startp;
   *mem_startp += alloc_size;
  } else
   dev = (struct device *)kmalloc(alloc_size, GFP_KERNEL);

 

至此,Linux网卡的初始化核心函数栈就分析完成了。

初始化完成之后,如何收发数据呢?当一个数据包来了之后,会发生些什么呢?我们接下来进行分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值