Linux内核协议栈分析之网卡初始化——tcp/ip通信并不神秘(1)
写在代码前:
写技术类文章的一个痛苦之处在于——写简单了,看的人觉得没意思;写难了,又看不懂是什么意思。例如——《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/。
需要关注的包括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网卡的初始化核心函数栈就分析完成了。
初始化完成之后,如何收发数据呢?当一个数据包来了之后,会发生些什么呢?我们接下来进行分析。