Chinaunix首页 | 论坛 | 博客
  • 博客访问: 243525
  • 博文数量: 33
  • 博客积分: 2511
  • 博客等级: 少校
  • 技术积分: 391
  • 用 户 组: 普通用户
  • 注册时间: 2008-06-06 09:24
文章分类
文章存档

2011年(3)

2010年(9)

2009年(3)

2008年(18)

我的朋友

分类: LINUX

2008-06-06 15:18:09

本文档的Copyleft归popy所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,
严禁用于任何商业用途。
gtalk: mypopy at gmail.com
来源:barrypopy.cublog.cn
 

前几天去水木,恰巧碰到有人问网络子系统的初始化问题,这其实是个有意思的问题,
读网络代码基本上第一件事情就是这,于是写下这篇文档,算做自己的回顾之举.

首先,我的最初出发点是从init进程开始的,如果你的系统的引导之类的感兴趣,那请
在版上查找相关资料或去其它地方查找.然后我的Code也是基于单CPU的机器,SMP相关的话
请参考其它的资料.分析的代码是2.6.18+

一.init进程与网络子系统的第一个函数.
init进程相关的知识相信是总所周之,这就不提了(init进程是所有进程之父,使用ps
-aux可以查看init的进程号为1)
init进程的入口函数即为init()函数,定义于init/main.c中。
1.init()函数调用do_basic_setup()函数完成最后的初始化工作。
2.调用free_initmem()把内核中标记有__init、__initfunc、__initdata的程序代码和数
据所占据的内存全部释放。
3.调用open("/dev/console", O_RDWR, 0)打开控制台设备。同时调用复制标准输出设备
和标准出错设备的描述符。
4.调用execve()执行init程序,该程序完成各种脚本的执行。

好了,我们的故事就此开始:),来看看网络子系统是如何登上历史的舞台,继续自己的辉煌
之旅.

前面说道了do_basic_setup(),我们的故事,其实真真是从它开始的,不过开头似乎有些隐
晦,但只要认真看来,紧抓蛛丝马迹,自然抽丝剥茧不再话下:)

do_basic_setup()[init/main.c]
|--------do_initcalls()[init/main.c]

我们贴出do_initcalls()的全部代码,以备分析:

__setup("initcall_debug", initcall_debug_setup);

struct task_struct *child_reaper = &init_task;

extern initcall_t __initcall_start[], __initcall_end[];

static void __init do_initcalls(void)
{
.........

for (call = __initcall_start; call < __initcall_end; call++) {
char *msg = NULL;
char msgbuf[40];
int result;

if (initcall_debug) {
printk("Calling initcall 0x%p", *call);
print_fn_descriptor_symbol(": %s()",
(unsigned long) *call);
printk("\n");
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{1}:需要注意的代码
result = (*call)();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

if (result && result != -ENODEV && initcall_debug) {
sprintf(msgbuf, "error code %d", result);
msg = msgbuf;
}
if (preempt_count() != count) {
msg = "preemption imbalance";
preempt_count() = count;
}
if (irqs_disabled()) {
msg = "disabled interrupts";
local_irq_enable();
}
if (msg) {
printk(KERN_WARNING "initcall at 0x%p", *call);
print_fn_descriptor_symbol(": %s()",
(unsigned long) *call);
printk(": returned with %s\n", msg);
}
}

/* Make sure there is no pending stuff from the initcall sequence*/
flush_scheduled_work();

........
}

上面的Code有一个很是奇怪的extern initcall_t __initcall_start[], __initcall_end
[];生命,仿佛initcall_t只是个一般的数据类型而已,但真的是这样吗?

grep一下:
typedef (*initcall_t)(void);[include/init.h]
原来如此,不就一函数指针而已么?犯的着吗?但是,后面的变量__initcall_start[], __in
itcall_end[];你有仔细的查找过么?

恩,那就查找一番,原来,居然在[arch/i386/kernel/vmlinux.lds.S],如果这时你进去看了
这个文件依然是雾水一头,那看来只能出杀手锏了:).(请参考TmacD在版上贴出的Using LD
的中文译文)

#######################################################################
关键时刻,除了的确是语法上的东西,还是考虑GNU的哪些工具:),还有就是,万千万千不要忘
了,就算Kernel,它也是个software,自然,少不了:
预处理->编译->链接->运行,在这方面来说,与其它程序并无二置
#######################################################################

好了,我们继续,查看[include/init.h]的代码,
/* initcalls are now grouped by functionality into separate
* subsections. Ordering inside the subsections is determined
* by link order.
* For backwards compatibility, initcall() puts the call in
* the device init subsection.
*/

#define __define_initcall(level,fn) \
static initcall_t __initcall_##fn __attribute_used__ \
__attribute__((__section__(".initcall" level ".init"))) = fn

#define core_initcall(fn) __define_initcall("1",fn)
#define postcore_initcall(fn) __define_initcall("2",fn)
#define arch_initcall(fn) __define_initcall("3",fn)
#define subsys_initcall(fn) __define_initcall("4",fn)
#define fs_initcall(fn) __define_initcall("5",fn)
#define device_initcall(fn) __define_initcall("6",fn)
#define late_initcall(fn) __define_initcall("7",fn)
结合上面的ld脚本,基本可以开始猜测了,原来do_initcalls()中的:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{1}:需要注意的代码
result = (*call)();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

是按照声明为core_initcall()->late_initcall()这样的顺序来调用这些函数啊(看到声明
对应的1,2,3...,7可以猜测啊)

你或许又开始发问了,这和网络子系统初始化,有关系么?
怎么没有,请参考[net/socket.c]
static int __init sock_init(void)
{
/*
* Initialize sock SLAB cache.
*/

sk_init();

/*
* Initialize skbuff SLAB cache
*/
skb_init();

/*
* Initialize the protocols module.
*/

init_inodecache();
register_filesystem(&sock_fs_type);
sock_mnt = kern_mount(&sock_fs_type);

/* The real protocol initialization is performed in later
* initcalls.
*/

#ifdef CONFIG_NETFILTER
netfilter_init();
#endif

return 0;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{2}:和我们上面的叙述是不是可以联系上了:),说了这久,原来只是为了说这一点啊:)
core_initcall(sock_init); /* early initcall */
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

因此啊,我可以断定sock_init()[net/socket.c]是网络子系统初始化的第一个函数,不信,
可以在net目录里面"grep core_initcall * -rn",看声明为core_initcall函数有几个,然
后采用排除法:)

恩,可以喝口水了,是否有拨云见日的感觉:),可打铁得趁热,继续吧:)

二. 网络子系统得初始化
在上面得分析中,既然都见到了第一个与网络子系统相关得函数,后面自然要好过一些了.


在上面得Code中可以看到几点:
sock_init()[net/socket.c]
|--------sk_init()[net/core/sock.c]
|--------skb_init()[net/core/skbuff.c]
|--------init_inodecache() |#|
|--------register_filesystem() |#|
|--------kern_mount() |#|
|--------netfilter_init()[net/netfilter/core.c]

简单说明一下这几个函数:
1.函数sk_init()
这个函数初始化slab cache给struct sock类型的对象使用。
if (num_physpages <= 4096) { /* 4096=16MB 在x86 */
sysctl_wmem_max = 32767; /* 32767=32Kb-1 */
sysctl_rmem_max = 32767;
sysctl_wmem_default = 32767;
sysctl_rmem_default = 32767;
} else if (num_physpages >= 131072) { /* 131072=512MB 在x86 */
sysctl_wmem_max = 131071; /* 131071=128Kb-1 */
sysctl_rmem_max = 131071;
}
这设置了读或写的buffer最大值,如果物理页数目(num_physpages)在16MB~512MB之间,
默认分配为(net/core/sock.c):
/* Run time adjustable parameters. */
__u32 sysctl_wmem_max = SK_WMEM_MAX;
__u32 sysctl_rmem_max = SK_RMEM_MAX;
__u32 sysctl_wmem_default = SK_WMEM_MAX;
__u32 sysctl_rmem_default = SK_RMEM_MAX;
其中,SK_WMEM_MAX和SK_RMEM_MAX均在skbuff.h中设置为65535b(64Kb-1)

2.函数skb_init()
这个函数创建struct sk_buff的对象的cache(参考sk_init()很明显)

3.注册sock文件系统(详细内容参考文件系统相关文档).

4.Netfilter框架初始化

好了,基本上就这样了,休息,休息,休息一下:)


BTW:这是刚接触Linux IP协议栈时候的一篇文章,张贴于BYHH,其实现在看来,只是链接器的使用和ELF文件格式的问题,
可当时对此很是疑惑,直到不断查阅资料,才明白各种原有,其实现在的工作已经不用深入到协议栈去了,
但回顾看来,对其他人可能有一些帮助。同时,欢迎讨论。
阅读(1524) | 评论(0) | 转发(0) |
0

上一篇:没有了

下一篇:介绍一个开源的SIP协议库PJSIP

给主人留下些什么吧!~~