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

时间:2021-10-05 02:18:03

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


<iframe id="cproIframe_u1507428" width="300" height="250" src="http://pos.baidu.com/acom?adn=0&amp;at=103&amp;aurl=&amp;cad=1&amp;ccd=24&amp;cec=UTF-8&amp;cfv=15&amp;ch=0&amp;col=zh-CN&amp;conOP=0&amp;cpa=1&amp;dai=2&amp;dis=0&amp;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&amp;ltu=http%3A%2F%2Fwww.xuebuyuan.com%2F1362487.html&amp;lunum=6&amp;n=83099053_cpr&amp;pcs=1366x655&amp;pis=10000x10000&amp;ps=326x909&amp;psr=1366x768&amp;pss=1366x346&amp;qn=1512bbdc2fefd90d&amp;rad=&amp;rsi0=300&amp;rsi1=250&amp;rsi5=4&amp;rss0=%23FFFFFF&amp;rss1=%23FFFFFF&amp;rss2=%230080c0&amp;rss3=%23444444&amp;rss4=%23008000&amp;rss5=&amp;rss6=%23e10900&amp;rss7=&amp;scale=&amp;skin=&amp;td_id=1507428&amp;tn=text_default_300_250&amp;tpr=1414137173092&amp;ts=1&amp;xuanting=0&amp;dtm=BAIDU_DUP2_SETJSONADSLOT&amp;dc=2&amp;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;"></iframe>

写在代码前:

        写技术类文章的一个痛苦之处在于——写简单了,看的人觉得没意思;写难了,又看不懂是什么意思。例如——《redis源代码分析——事件机制》http://blog.csdn.net/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/

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

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

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

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

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

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

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网卡的初始化核心函数栈就分析完成了。

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