📄 6.htm
字号:
692 /* To be exact, this node just hooks the initialization <br>
693 routines to the device structures. */ <br>
694 extern int slip_init_ctrl_dev(struct device *); <br>
695 static struct device slip_bootstrap = { <br>
696 "slip_proto", 0x0, 0x0, 0x0, 0x0, 0, 0, 0, 0, 0, NEXT_DEV, slip_init_ctr <br>
l_dev, }; <br>
697 #undef NEXT_DEV <br>
698 #define NEXT_DEV (&slip_bootstrap)/*将slip串起来*/ <br>
699 #endif /* SLIP */ <br>
…… ……/*将所有其它的设备串起来,省略*/ <br>
915 struct device *dev_base = &loopback_dev;/*dev_base的头*/ <br>
_____________________________________________________________________ <br>
这样就形成了图8中所示的dev_base的链表。 <br>
当系统转入内核后,start_kernel会创建一个init进程,这个init进程会通过系统调用 <br>
<br>
ys_setup进行所有尚未初始化的设备的初始化(内存,PCI等已经在此之前初始化过了)。 <br>
在sys_setup中调用device_setup,进而调用net_dev_init检测和初始化所有的网络设备。 <br>
net_dev_init内部调用所有dev->init函数指针,进行具体的物理设备的初始化工作。 <br>
整个调用流程如图9所示: <br>
5.2.3.3 网卡初始化函数分析 <br>
在内核启动初始化和模块驱动初始化两种方式中,驱动网卡的最终目的都是调用了网卡驱 <br>
动程序的init函数指针,也就是网卡的初始化函数。在这个函数里面主要要完成如下的任 <br>
务: <br>
1)检测该设备是否存在; <br>
2)自动检测该设备的I/O地址和中断号; <br>
3)填写该设备对应device结构所需要的大部分域段; <br>
4)在内核内存空间中申请需要的内存空间。 <br>
对ne.c驱动程序来说,初始化函数是ne_probe()函数。 <br>
_________________________________________________________________ne.c <br>
/*drivers/net/ne.c*/ <br>
180 __initfunc(int ne_probe(struct device *dev)) <br>
181 { <br>
182 int base_addr = dev ? dev->base_addr : 0; <br>
183 /*如果制定IO地址,就不自动检测*/ <br>
184 /* First check any supplied i/o locations. User knows best. <cough <br>
> */ <br>
> */ <br>
185 if (base_addr > 0x1ff) /* Check a single specified location. */ <br>
<br>
186 return ne_probe1(dev, base_addr);/*不自动检测*/ <br>
187 else if (base_addr != 0) /* Don't probe at all. */ <br>
188 return ENXIO;/*不检测,也不指定*/ <br>
189 <br>
190 #ifdef CONFIG_PCI <br>
191 /* Then look for any installed PCI clones */ <br>
192 if (probe_pci && pci_present() && (ne_probe_pci(dev) == 0)) <br>
193 return 0;/*检测ne2000 PCI,此处不分析*/ <br>
194 #endif <br>
195 <br>
196 #ifndef MODULE/*非模块化*/ <br>
197 /* Last resort. The semi-risky ISA auto-probe. *//*自动检测*/ <br>
198 for (base_addr = 0; netcard_portlist[base_addr] != 0; base_addr++) <br>
{ <br>
199 int ioaddr = netcard_portlist[base_addr]; <br>
200 if (check_region(ioaddr, NE_IO_EXTENT)) <br>
201 continue; <br>
202 if (ne_probe1(dev, ioaddr) == 0) <br>
203 return 0; <br>
204 }/*自动检测IO地址的原理是将portlist里面每一个都尝试一遍*/ <br>
205 #endif <br>
206 <br>
207 return ENODEV; <br>
208 } <br>
____________________________________________________________________ <br>
_________________________________________________________________ne.c <br>
246 __initfunc(static int ne_probe1(struct device *dev, int ioaddr)) <br>
…… <br>
/*检测并初始化8390芯片,*/ <br>
…… <br>
426 if (dev->irq < 2)/*中断号无效,需要自动检测*/ <br>
427 { <br>
428 autoirq_setup(0); <br>
429 outb_p(0x50, ioaddr + EN0_IMR); /* Enable one interrupt. */ <br>
430 outb_p(0x00, ioaddr + EN0_RCNTLO); <br>
431 outb_p(0x00, ioaddr + EN0_RCNTHI); <br>
432 outb_p(E8390_RREAD+E8390_START, ioaddr); /* Trigger it */ <br>
433 mdelay(10); /* wait 10ms for interrupt to propagate */ <br>
434 outb_p(0x00, ioaddr + EN0_IMR); /* Mask it again.*/ <br>
435 dev->irq = autoirq_report(0); <br>
436 if (ei_debug > 2) <br>
437 printk(" autoirq is %d\n", dev->irq); <br>
438 } else if (dev->irq == 2)/*设定2号中断可能就是9号中断*/ <br>
439 /* Fixup for users that don't know that IRQ 2 is really IRQ 9, <br>
<br>
440 or don't know which one to set. */ <br>
441 dev->irq = 9; <br>
……/*中断驱动程序为ei_interrupt,用来响应中断,接收数据*/ <br>
459 int irqval = request_irq(dev->irq, ei_interrupt, <br>
460 pci_irq_line ? SA_SHIRQ : 0, name, dev); <br>
……/*初始化dev结构,并且向系统登记IO地址*/ <br>
…… <br>
_____________________________________________________________________ <br>
至此,网卡驱动完成。 <br>
<br>
网卡驱动完成后,如果系统需要使用该网卡传输网络数据,那么使用dev->hard_start_ <br>
<br>
mit()函数指针发送;根据注册的中断处理程序ei_interrupt(),接收数据,并且将数据传 <br>
送到上层协议。在这一层网络数据传输的工作情况可以参看下面的原理图: <br>
另外,关于在网络驱动程序中的其它函数实现,如设备打开,设备关闭,数据包传输的 <br>
<br>
程等等分析,在这里因为篇幅的关系不进行介绍,可以参看我的《网络设备驱动程序分析 <br>
》一文。 <br>
5.3 在嵌入式系统中实现网络协议栈 <br>
在嵌入式系统实现网络协议栈有两种可能的途径,一种是利用现有的成果,复用Linux现成 <br>
的网络代码,以获得对TCP/IP以及其它网络协议栈的支持;另一种途径是通过对Linux网络 <br>
协议代码的分析,精简代码,将网络协议的代码作为微内核外的一部分为操作系统服务。 <br>
<br>
5.3.1 重用Linux网络协议栈代码 <br>
作为嵌入式操作系统,一般使用的关键技术是微内核技术,正如在2.2.3里介绍的那样,操 <br>
作系统的内核只是一些关键部分,而如网络部分、内存管理、文件系统等部分都可以作为 <br>
微内核外部在用户层次上的部分,需要的时候才和微内核通信,完成一定的功能。这样才 <br>
能使得嵌入式操作系统的内核能过做得很小,从而满足嵌入式系统本身的需要。 <br>
不过,在基于Linux的嵌入式系统的设计中,如果能够重用Linux的网络协议站代码,可见 <br>
减少很大程度的工作量和开发成本。而且Linux本身协议栈已经相当成熟,在代码效率、兼 <br>
容性和网络安全方面都很完善。 <br>
如果嵌入式系统对内核的体积要求不是太高,那么使用Linux的缩减内核就成为可能,并且 <br>
可以将网络部分代码内嵌重用。在这种情况下还可以重复使用Linux的网络设备驱动程序代 <br>
码和开发工具链,这样对嵌入式系统的开发是一个很大的便利。 <br>
5.3.2 重写网络协议栈 <br>
在自己的微内核的基础上编写自己需要的网络协议栈,可以根据需求,在实现功能的基础 <br>
上尽量减少代码量,减少占用的空间,提高执行效率。这是在大部分嵌入式系统进行定制 <br>
开发中需要完成的内容。开发难度大,周期长。 <br>
另外一个手段是利用市场上提供的网络协议芯片。这种芯片内植TCP/IP网络协议,直接使 <br>
用这种芯片可以减少开发工作量。 <br>
5.3.3 网络驱动程序的编写 <br>
如果是采取复用Linux网络协议栈的方法,那么,对于Linux环境下的大量网络设备驱动程 <br>
序代码都可以得到重用。在这种情况下,编写一个Linux下的网络驱动程序有如下几点要求 <br>
: <br>
1)网络设备的检测和初始化函数,供内核在启动驱动或者模块驱动的时候调用;这正是我 <br>
在 5.2.3中详细分析的部分。 <br>
2)网络设备的打开和关闭函数,用来在需要利用该网络接口传输数据时,获得该接口的函 <br>
数调用入口而用; <br>
3)提供数据传输函数,向硬件设备发送数据;如果是上一层协议需要发送数据,供dev_qu <br>
eue_xmit()函数调用; <br>
4)提供网络接口的中断服务程序,用来接收网络上到达的数据,并且将数据传递到上一层 <br>
协议。并且当数据处理完毕,也要触发中断,表示可以接收下一个数据。 <br>
<br>
如果不采用重用Linux网络协议栈的代码,那么需要进行的工作由两块,一块是对PC硬 <br>
<br>
的设备驱动程序的移植工作;另外一块是为自己的网络协议栈写设备驱动程序的代码。 <br>
<br>
5.4 小结 <br>
本章阐述了自己对Linux网络协议栈的整体分析和理解,并且就自己在毕业设计期间分析的 <br>
ne2000 compatible网卡驱动程序做出解释,说明在Linux下进行网络驱动程序的编写方法 <br>
。 <br>
同时,需要在嵌入式系统中实现网络协议栈,不管是否要重用Linux的网络部分的代码,分 <br>
析Linux的整个网络协议栈代码总是有用的。 <br>
<br>
-- <br>
紅酥手 黃滕酒 滿城春色尃m澚 <br>
|風惡 歡情薄 一懷愁緒 幾年離索 <br>
錯 錯 錯 ! <br>
春如舊 人空瘦 満I奂t 捧o綃透 <br>
桃花落 槌f亻w 山盟雖在 鍟\
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -