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

写在代码前:

写技术类文章的一个痛苦之处在于——写简单了,看的人觉得没意思;写难了,又看不懂是什么意思。例如——《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 line398
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、付费专栏及课程。

余额充值