在看linux核心代码的时候看到/init/main.c 里面的do_initcalls函数
static void __init do_initcalls(void)
742 {
743 initcall_t *call;
744
745 for (call = __initcall_start; call < __initcall_end; call++)
746 do_one_initcall(*call);
747
748 /* Make sure there is no pending stuff from the initcall sequence */
749 flush_scheduled_work();
750 }
static void __init do_one_initcall( initcall_t fn)
697 {
698 int count = preempt_count();
699 ktime_t t0, t1, delta;
700 char msgbuf[64];
701 int result;
702
703 if ( initcall_debug) {
704 print_fn_descriptor_symbol( "calling %s\n", fn);
705 t0 = ktime_get();
706 }
707
708 result = fn();
709
710 if ( initcall_debug) {
711 t1 = ktime_get();
712 delta = ktime_sub( t1, t0);
713
714 print_fn_descriptor_symbol( "initcall %s", fn);
715 printk( " returned %d after %Ld msecs\n", result,
716 (unsigned long long) delta.tv64 >> 20);
717 }
718
719 msgbuf[0] = 0;
720
721 if ( result && result != - ENODEV && initcall_debug)
722 sprintf( msgbuf, "error code %d ", result);
723
724 if ( preempt_count() != count) {
725 strlcat( msgbuf, "preemption imbalance ", sizeof( msgbuf));
726 preempt_count() = count;
727 }
728 if ( irqs_disabled()) {
729 strlcat( msgbuf, "disabled interrupts ", sizeof( msgbuf));
730 local_irq_enable();
731 }
732 if ( msgbuf[0]) {
733 print_fn_descriptor_symbol( KERN_WARNING "initcall %s", fn);
734 printk( " returned with %s\n", msgbuf);
735 }
736 }
__initcall_start是在 arch目录中的相关CPU中的vmlinux.lds文件指定,如i386中位于arch/i386/vmlinux.lds中,至于在__initcall_start和__initcall_end之间的是由函数声明__init指定
2.
察看/arch/i386/vmlinux.lds,发现一段代码
__initcall_start = .;
.initcall.init : { *(.initcall.init) }
__initcall_end = .;
跟我找的东西相关
使用info ld,察看相关资料,(最终发现太麻烦,到网上找了一个ld.pdf).发现有这么一
段介绍关于c++地联结构造器的使用,和这段用法相同
其含义时,是让__initcall_start指向代码节.initcall.init的节首,而__initcall_end
指向.initcall.init的节尾。
那么第一段代码从程序逻辑上得到了解释。
3。因为do_initcalls所作的是系统中有关于选择的驱动部分的初始化工作,那么具体是这些
函数指针数据怎样放到了.initcall.init节。
想起来了,还没有使用grep哈哈,在
grep -rn .initcall.init *
发现在include/linux/init.h:83:有这样一个定义
#define __init_call __attribute__ ((unused,__section__ (".initcall.init")))
娃哈哈哈
终于让我发现了
然后又发现了
#define __initcall(fn) \
static initcall_t __initcall_##fn __init_call = fn
4。问题是什么是__attribute__??,查找man gcc,找到关于__attribute__的定义
`section ("section-name")'
Normally, the compiler places the code it generates in the `text'
section. Sometimes, however, you need additional sections, or you
need certain particular functions to appear in special sections.
The `section' attribute specifies that a function lives in a
particular section. For example, the declaration:
extern void foobar (void) __attribute__ ((section ("bar")));
puts the function `foobar' in the `bar' section.
Some file formats do not support arbitrary sections so the
`section' attribute is not available on all platforms. If you
need to map the entire contents of a module to a particular
section, consider using the facilities of the linker instead.
他的意思就是使它建造一个在.initcall.init节的指向初始函数的指针
5。问题是##是什么意思?
查阅gcc的man page得知,她是用在可变参数使用宏定义的时候的
在这里也就是建立一个变量名称为所指向的函数的名称,并且前面加上
__initcall_.
6.然后看看成果
在/include/linux/init.c中有发现
#define module_init(x) __initcall(x);
看看很多驱动中都有类似
module_init(usb_init);
module_exit(usb_exit);
的代码,哈哈,这下明白了。
分析kernel的initcall函数
Author: Dongas
Data: 08-07-15
先来看看这些initcall函数的声明:
/* include/linux/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)
#define __initcall(fn) device_initcall(fn)
#define __exitcall(fn) \
static exitcall_t __exitcall_##fn __exit_call = fn
#define console_initcall(fn) \
static initcall_t __initcall_##fn \
__attribute_used__ __attribute__((__section__(".con_initcall.init")))=fn
#define security_initcall(fn) \
static initcall_t __initcall_##fn \
__attribute_used__ __attribute__((__section__(".security_initcall.init"))) = fn
#define module_init(x) __initcall(x); ß从这里知道module_init的等级为6,相对靠后
#define module_exit(x) __exitcall(x);
可以发现这些*_initcall(fn)最终都是通过__define_initcall(level,fn)宏定义生成的。
__define_initcall宏定义如下:
#define __define_initcall(level,fn) \
static initcall_t __initcall_##fn __attribute_used__ \
__attribute__((__section__(".initcall" level ".init"))) = fn
这句话的意思为定义一个initcall_t型的初始化函数,函数存放在.initcall”level”.init section内。.initcall”level”.init section定义在vmlinux.lds内。
/* arch/arm/kernel/vmlinux.lds */
……
__initcall_start = .;
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
__initcall_end = .;
……
正好包括了上面init.h里定义的从core_initcall到late_initcall等7个level等级的.initcall”level”.init section. 因此通过不同的*_initcall声明的函数指针最终都会存放不同level等级的.initcall”level”.init section内。这些不同level的section按level等级高低依次存放。
下面我们再来看看,内核是什么时候调用存储在.initcall”level”.init section内的函数的。
内核是通过do_initcalls函数循环调用执行initcall.init section内的函数的,流程如下:
start_kernel -> rest_init -> kernel_thread -> init -> do_basic_setup -> do_initcalls
这里要分析两个函数: kernel_thread和do_initcalls,这两个函数都定义在init/main.c内
1) kernel_thread
1.static void noinline rest_init(void)
2. __releases(kernel_lock)
3.{
4. system_state = SYSTEM_BOOTING_SCHEDULER_OK;
5.
6. kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND);
7. numa_default_policy();
8. unlock_kernel();
9.
10. /*
11. * The boot idle thread must execute schedule()
12. * at least one to get things moving:
13. */
14. __preempt_enable_no_resched();
15. schedule();
16. preempt_disable();
17.
18. /* Call into cpu_idle with preempt disabled */
19. cpu_idle();
20.}
第6行通过kernel_thread创建一个内核线程执行init函数。(其实这里创建的即Linux的1号进程(init进程), 为linux中所有其他进程的父进程,有兴趣的可以自己查资料)
2) do_initcalls
1.static void __init do_initcalls(void)
2.{
3. initcall_t *call;
4. int count = preempt_count();
5.
6. for (call = __initcall_start; call
7. ……
8. result = (*call)();
9. ……
10. }
11.}
其中, initcall_t类型如下:
typedef int (*initcall_t)(void);
__initcall_start和__initcall_end定义在vmlinux.lds内,表示initcall section的起始和结束地址。
/* arch/arm/kernel/vmlinux.lds */
……
__initcall_start = .;
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
__initcall_end = .;
……
因此,上面6-10行代码的作用为按initcall level等级的顺序,依次循环调用预先存储在initcall section内的所有各个级别的初始化函数。这样,kernel的initcall函数的原理我们就搞清楚了。
最后要注意的是rest_init是在start_kernel函数内最后部分才被调用执行的,rest_init前包含了kernel一系列的初始化工作。另外,这些不同level等级的initcall.init section本身有一定的执行顺序,因此如果你的驱动依赖于特定的执行顺序的话需要考虑到这一点。
initcall机制原理及实践
说明:以下内容基于linux2.4.0
linux内核中关于网络初始化过程中do_basic_setup等的问题
linux网络初始化时,initmain.c中的start_kernel()函数中调用kernel_thread启动了init进程,该进程对应于initmain.c中的init函数。init函数中调用do_basic_setup()函数,这个函数又调用了netsocket.c中的sock_init()函数。
do_basic_setup()的功能:
总线初始化(比如pci_init())
网络初始化(初始化网络数据结构,sock_init();2.4.17中 sock_init()只是初始化了网络数据结构,真正的协议初始化是运行do_initcalls()时自动加载各个协议模块的)
创建事件管理核心线程(start_context_thread())
再加载任何模块(用do_initcalls()启动任何使用__initcall标识的函数(方便核心研发者添加启动函数))
#define module_init(x) __initcall(x);这句在linuxinit.h中,通过这么定义,使得系统初始化到调用initmain.c中的 do_initcalls时自动加载了模块x(进入x初始化)。而在af_inet.c中有module_init(inet_init);所以也包括初始化inet模块了。呵呵
在2.6.14.2中就比较容易理解了。看
static void __init do_basic_setup(void)
{
/* drivers will send hotplug events */
init_workqueues();
usermodehelper_init();
driver_init();
#ifdef CONFIG_SYSCTL
sysctl_init();
#endif
/* Networking initialization needs a process context */
sock_init();
do_initcalls();
}
主要根据代码看的,比较仓促,因为这不是我现在要做的主要工作,来不及周详整理了,先这样记下好了,有异议的朋友欢迎留言讨论,呵呵。
内核启动时,设备及驱动初始化的实现
Uboot完成系统的引导并将Linux内核拷贝到内存之后,bootm -> do_bootm_linux()跳转到kernel的起始位置;
压缩过的kernel入口在arch/arm/boot/compressed/head.S,它将调用函数decompress_kernel()<./arch/arm/boot/compressed/misc.c>解压,打印“Uncompressing Linux...”,调用gunzip(),打印"done, booting the kernel."
然后call_kernel,执行解压后的kernel,经linux/arch/arm/kernel/head.S调用start_kernel转入体系结构无关的通用C代码,在start_kernel()中完成了一系列系统初始化,设备及驱动的注册即在此时完成:
<./init/main.c>-------------------------
asmlinkage void __init start_kernel(void)
{
char * command_line;
extern struct kernel_param __start___param[], __stop___param[];
···········································································
printk(KERN_NOTICE "Kernel command line: %s\n", saved_command_line);
//打印内核命令行
parse_early_param();
parse_args("Booting kernel", command_line, __start___param,
__stop___param - __start___param,
&unknown_bootoption);
//解析由BOOT传递的启动参数
···········································································
/* Do the rest non-__init'ed, we're now alive */
rest_init();
}
start_kernel()中的函数rest_init()将创建第一个核心线程kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND),调用init()函数:
static int init(void * unused)-------------------
{
·······················
do_basic_setup();
······················
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) { //判断在启动时是否指定了init参数
//如果指定则执行用户init进程,成功将不会返回
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...\n", execute_command);
}
/* 如果没有指定init启动参数,则查找下面的目录init进程,成功将不会返回,否则打印出错信息 */
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
panic("No init found. Try passing init= option to kernel.");
}
继而调用函数do_basic_setup()(此时与体系结构相关的部分已经初始化完了,现在开始初始化设备了):
/*
* Ok, the machine is now initialized. None of the devices
* have been touched yet, but the CPU subsystem is up and
* running, and memory and process management works.
*
* Now we can finally start doing some real work..
*/
static void __init do_basic_setup(void)-----------------
{
/* drivers will send hotplug events */
init_workqueues();
usermodehelper_init();
driver_init(); //建立设备模型子系统
#ifdef CONFIG_SYSCTL
sysctl_init();
#endif
/* Networking initialization needs a process context */
sock_init();
do_initcalls(); //系统初始化(包括设备,文件系统,内核模块等)
}
<./drivers/base/init.c>-------------------------
/**
* driver_init - initialize driver model.
*
* Call the driver model init functions to initialize their
* subsystems. Called early from init/main.c.
*/
void __init driver_init(void)
{
/* These are the core pieces */
devices_init();
<./drivers/base/core.c>-------------
int __init devices_init(void)
{
return subsystem_register(&devices_subsys);
}
-----------------------
buses_init();
classes_init();
firmware_init();
/* These are also core pieces, but must come after the
* core core pieces.
*/
platform_bus_init();
system_bus_init();
cpu_dev_init();
memory_dev_init();
attribute_container_init();
}
---------------------------
extern initcall_t __initcall_start[], __initcall_end[];
static void __init do_initcalls(void)
{
initcall_t *call;
int count = preempt_count();
for (call = __initcall_start; call < __initcall_end; call++) {
··················
(*call)(); //调用一系列初始化函数
···················
}
---------------------------
__initcall_start和__initcall_end界定了存放初始化函数指针区域的起始地址,即从__initcall_start开始到__initcall_end结束的区域中存放了指向各个初始化函数的函数指针。 由 (*call)()完成各个部分的初始化工作,且便于扩充。具体实现如下:
<./arch/arm/kernel/vmlinux.lds.S>-----------------
__initcall_start = .;
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
__initcall_end = .;
<./include/linux/init.h>---------------------
#ifndef MODULE /* 如果驱动模块静态编译进内核 */
···············································
/* 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)
//此处初始化了设备
/*----eg:arch_initcall(at91sam9261_device_init)---
static int __init at91sam9261_device_init(void)
{
at91_add_device_udc();
at91_add_device_dm9000();
armebs3_add_input_buttons();
return platform_add_devices(at91sam9261_devices, ARRAY_SIZE(at91sam9261_devices));
}
------------------------*/
#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)
#define __initcall(fn) device_initcall(fn)
/**
* module_init() - driver initialization entry point
* @x: function to be run at kernel boot time or module insertion
*
* module_init() will either be called during do_initcalls (if
* builtin) or at module insertion time (if a module). There can only
* be one per module.
*/
#define module_init(x) __initcall(x);
//静态编译的驱动模块作为device_initcall在内核启动就被do_initcalls
/**
* module_exit() - driver exit entry point
* @x: function to be run when driver is removed
*
* module_exit() will wrap the driver clean-up code
* with cleanup_module() when used with rmmod when
* the driver is a module. If the driver is statically
* compiled into the kernel, module_exit() has no effect.
* There can only be one per module.
*/
#define module_exit(x) __exitcall(x);
#else /* MODULE 如果驱动模块动态加载入内核 */
···············································
/* Each module must use one module_init(), or one no_module_init */
#define module_init(initfn) \
static inline initcall_t __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));
//insmod 是通过系统调用sys_init_module(const char *name_user, struct module *mod_user)
//将动态驱动模块载入到内核空间
/* This is only required if you want to be unloadable. */
#define module_exit(exitfn) \
static inline exitcall_t __exittest(void) \
{ return exitfn; } \
void cleanup_module(void) __attribute__((alias(#exitfn)));
-----------------------------
arm linux启动流程之进入内核
还是从编译链接生成vmlinux的过程来看吧,由一大堆.o 文件链接而成,第一个就是
kernel\arch\arm\kernel\head-armv.o ,而且我们还看到了
lds链接文件kernel\arch\arm\vmlinux.lds,先把它分析一下
ENTRY(stext) //入口点是stext 应该就在head-armv.s中了
SECTIONS
{
. = 0xC0008000; //基址,是 内核开始的虚拟地址
.init : { /* Init code and data */
_stext = .;
__init_begin = .;
*(.text.init)
__ proc_info_begin = .;
*(.proc.info)
__proc_info_end = .;
__arch_info_begin = .;
*(.arch.info)
__arch_info_end = .;
__tagtable_begin = .;
*(.taglist)
__tagtable_end = .;
*(.data.init)
. = ALIGN(16);
__setup_start = .;
*(.setup.init)
__setup_end = .;
__initcall_start = .;
*(.initcall.init)
__initcall_end = .;
. = ALIGN(4096);
__init_end = .;
}
关于虚拟地址和物理地址的:使用MMU后, 系统就会使用虚拟地址,通过MMU来指向
实际物理地址而在这里我们的0xC0008000实际物理地址就是0x30008000,
具体关于MMU的介绍参考《 ARM体系 结构与编程》。
到head-armv.s找到 程序的入口
.section ".text.init",#alloc,#execinstr
.type stext, #function
ENTRY(stext)
mov r12, r0
mov r0, #F_BIT | I_BIT | MODE_SVC @ make sure svc mode
msr cpsr_c, r0 @ and all irqs disabled
bl __lookup_processor_type
teq r10, #0 @ invalid processor?
moveq r0, #'p' @ yes, error 'p'
beq __error
bl __lookup_architecture_type
teq r7, #0 @ invalid architecture?
moveq r0, #'a' @ yes, error 'a'
beq __error
bl __create_page_tables
adr lr, __ret @ return address
add pc, r10, #12 @ initialise processor
来看看上一句跳到哪里去了
去追寻r10的值,是在__lookup_processor_type子函数中赋的
__lookup_processor_type:
adr r5, 2f //r5 标号2的地址 基址是0x30008000
ldmia r5, {r7, r9, r10} //r7=__proc_info_end r9=__proc_info_begin
sub r5, r5, r10 //r10 标号2的链接地址 基址是0xc0008000
add r7, r7, r5 @ to our address space
add r10, r9, r5 //r10 变换为基址是0x30008000的__proc_info_begin
2: .long __proc_info_end
.long __proc_info_begin
.long 2b
这样r10中存放的是__proc_info_begin的地址,因为现在我们还没有打开MMU
所以还是需要把基址变换到0x30008000,接着我们就去找__proc_info_begin吧
注意到在上面的vmlinux.lds中有这个标号,下来链接的是.proc.info段,
在kernel\arch\arm\mm\proc-arm920.s的最后找到了这个段
.section ".proc.info", #alloc, #execinstr
.type __arm920_proc_info,#object
__arm920_proc_info:
.long 0x41009200
.long 0xff00fff0
.long 0x00000c1e @ mmuflags
b __arm920_setup
ok,这样我们就知道add pc, r10, #12跳到哪里去了,因为这个地址刚好放了条跳转语句
注意了b语句用的都是相对地址,所以不需要变换地址,反正是跳到__arm920_setup,而且
上一条语句是adr lr, __ret,设定了__arm920_setup的返回地址是__ret,所以执行完
__arm920_setup后回到head-armv.s的__ret标号继续执行.
__ret: ldr lr, __switch_data
mcr p15, 0, r0, c1, c0 //注意这里了,在这里打开了MMU
mov r0, r0
mov r0, r0
mov r0, r0
mov pc, lr //跳到__mmap_switched,这里已经用了虚拟地址了吧
// 这条 指令ldr lr, __switch_data加载的__mmap_switched地址就是虚拟地址啊
__switch_data: .long __mmap_switched
从__mmap_switched一路执行下来,就要调到 C语言代码中去了
b SYMBOL_NAME(start_kernel) //在kernel\init\main.c中
这个程序不是特别复杂,细心看看还是能大概看懂,我也不能去一一注释
这里有一个 流程图
到了C语言中就不是很难理解了
lock_kernel();
printk(linux_banner);
setup_arch(&command_line);
printk(" Kernel command line: %s\n", saved_command_line);
parse_options(command_line);
trap_init();
init_IRQ();
sched_init();
softirq_init();
time_init();
就是一大堆初始化工作,追着每个函数去看好了
start_kernel最后调用的一个函数
static void rest_init(void)
{
kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
unlock_kernel();
current->need_resched = 1;
cpu_idle();
}
用kernel_thread建立了一个init进程,执行的是main.c中的init函数
lock_kernel();
do_basic_setup();
在do_basic_setup中调用了do_initcalls函数
各种驱动都是在do_initcalls(void)中完成的
static void __init do_initcalls(void)
{
initcall_t *call;
call = &__initcall_start;
do {
(*call)();
call++;
} while (call
flush_scheduled_tasks();
}
__initcall_start也是在vmlinux.lds中赋值的,那就需要找到.initcall.ini这个段
在kernel\include\linux\init.h中可以找到
#define __init_call __attribute__ ((unused,__section__ (".initcall.init")))
typedef int (*initcall_t)(void);
#define __initcall(fn) \
static initcall_t __initcall_##fn __init_call = fn
仔细 研究下就发现这是把初始化函数的地址放到了.initcall.init段中
这样就可以不断调用驱动的初始化函数了
如果没有定义MODULE,那么#define module_init(x) __initcall(x);
所以如果要把驱动的编译进内核就很简单了吧
init的最后
if (execute_command)
execve(execute_command,argv_init,envp_init);
execute_command与ppcboot传的命令行参数是有关的哦,就是init=/linuxrc
这样就要去执行根 目录下的linuxrc脚本,这个脚本会去执行busybox
而busybox又去执行/etc/init.d/rcS脚本,这个脚本又去执行/usr/etc/rc.local
完了
__define_initcall 作用
前言
宏定义__define_initcall(level,fn)对于内核的初始化很重要,它指示
编译器在编译的时候,将一系列初始化函数的起始地址值按照一定的顺序
放在一个section中。在内核初始化阶段,do_initcalls() 将按顺序从该
section中以函数指针的形式取出这些函数的起始地址,来依次完成相应
的初始化。由于内核某些部分的初始化需要依赖于其他某些部分的初始化
的完成,因此这个顺序排列常常非常重要。
下面将从__define_initcall(level,fn) 宏定义的代码分析入手,依次
分析名称为initcall.init的section的结构,最后分析内核初始化函数
do_initcalls()是如何利用宏定义__define_initcall(level,fn)及其相
关的衍生的7个宏宏定义,来实现内核某些部分的顺序初始化的。
1、分析 __define_initcall(level,fn) 宏定义
1) 这个宏的定义位于inlclude\linux\init.h中:
#define __define_initcall(level,fn) \
static initcall_t __initcall_##fn \
__attribute__((__section__(".initcall" level ".init"))) \
= fn
其中 initcall_t 是一个函数指针类型:
typedef int (*initcall_t)(void);
而属性 __attribute__((__section__())) 则表示把对象放在一个这个
由括号中的名称所指代的section中。
所以这个宏定义的的含义是:1) 声明一个名称为__initcall_##fn的函数
指针(其中##表示替换连接,);2) 将这个函数指针初始化为fn;3) 编译
的时候需要把这个函数指针变量放置到名称为 ".initcall" level ".init"
的section中(比如level="1",代表这个section的名称是 ".initcall1.init")。
2) 举例:__define_initcall(6, pci_init)
上述宏调用的含义是:1) 声明一个函数指针__initcall_pic_init = pci_init;
且 2) 这个指针变量__initcall_pic_init 需要放置到名称为 .initcall6.init
的section中( 其实质就是将 这个函数pic_init的首地址放置到了这个
section中)。
3) 这个宏一般并不直接使用,而是被定义成下述其他更简单的7个衍生宏
这些衍生宏宏的定义也位于 inlclude\linux\Init.h 中:
#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)
因此通过宏 core_initcall() 来声明的函数指针,将放置到名称为
.initcall1.init的section中,而通过宏 postcore_initcall() 来
声明的函数指针,将放置到名称为.initcall2.init的section中,
依次类推。
4) 举例:device_initcall(pci_init)
解释同上 1-2)。
2、与初始化调用有关section--initcall.init被分成了7个子section
1) 它们依次是.initcall1.init、.initcall2.init、...、.initcall7.init
2) 按照先后顺序依次排列
3) 它们的定义在文件vmlinux.lds.S中
例如 对于i386+,在i386\kernel\vmlinux.lds.S中有:
__initcall_start = .;
.initcall.init : {
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
}
__initcall_end = .;
而在makefile 中有
LDFLAGS_vmlinux += -T arch/$(ARCH)/kernel/vmlinux.lds.s
4) 在这7个section总的开始位置被标识为__initcall_start,
而在结尾被标识为__initcall_end。
3、 内核初始化函数do_basic_setup(): do_initcalls() 将从.initcall.init
中,也就是这7个section中依次取出所有的函数指针,并调用这些
函数指针所指向的函数,来完成内核的一些相关的初始化。
这个函数的定义位于init\main.c中:
extern initcall_t __initcall_start, __initcall_end;
static void __init do_initcalls(void)
{
initcall_t *call;
....
for (call = &__initcall_start; call < &__initcall_end; call++)
{
....
(*call)();
....
}
....
}
这些函数指针指向的函数就是通过宏__define_initcall(level,fn)
赋值的函数fn,他们调用的顺序就是放置在这些section中的顺序,
这个顺序很重要, 这就是这个宏__define_initcall(level,fn)的作用。
注意到,这里__initcall_start 和 __initcall_end 就是section
initcall.init的头和尾。
4、 归纳之
1) __define_initcall(level,fn)的作用就是指示编译器把一些初始化函数
的指针(即:函数起始地址)按照顺序放置一个名为 .initcall.init 的
section中,这个section又被分成了7个子section,它们按顺序排列。
在内核初始化阶段,这些放置到这个section中的函数指针将供
do_initcalls() 按顺序依次调用,来完成相应初始化。
2) 函数指针放置到的子section由宏定义的level确定,对应level较小的
子section位于较前面。而位于同一个子section内的函数指针顺序不定,
将由编译器按照编译的顺序随机指定。
3) 因此,如果你希望某个初始化函数在内核初始化阶段就被调用,那么你
就应该使用宏__define_initcall(level,fn) 或 其7个衍生宏 把这个
函数fn的对应的指针放置到按照初始化的顺序放置到相关的 section 中。
同事,如果某个初始化函数fn_B需要依赖于另外一个初始化函数fn_A的
完成,那么你应该把fn_B放在比fn_A对应的level值较大的子section中,
这样,do_initcalls()将在fn_A之后调用fn_B。
就应该使用宏__define_initcall(level,fn) 或 其7个衍生宏来
把这个初始化函数fn的起始地址按照初始化的顺序放置到相关的
section 中。 内核初始化时的do_initcalls()将从这个section
中按顺序找到这些函数来执行。 如果你希望某个初始化函数在内核初始化阶段就被调用,那么你
就应该使用宏__define_initcall(level,fn) 或 其7个衍生宏来
把这个初始化函数fn的起始地址按照初始化的顺序放置到相关的
section 中。 内核初始化时的do_initcalls()将从这个section
中按顺序找到这些函数来执行。
我的驱动在I2C初始化之前就运行起来了,而这时I2C提供的API还处于不可用状态。查了很多资料,网上有人说所有使用module_init这个宏的驱动程序的起动顺序都是不确定的(我没有查到权威的资料)。
所有的__init函数在区段.initcall.init中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些__init函数指针,并在整个初始化完成后,释放整个init区段(包括.init.text,.initcall.init等)。
注意,这些函数在内核初始化过程中的调用顺序只和这里的函数指针的顺序有关,和1)中所述的这些函数本身在.init.text区段中的顺序无关。在 2.4内核中,这些函数指针的顺序也是和链接的顺序有关的,是不确定的。在2.6内核中,initcall.init区段又分成7个子区段,分别是
.initcall1.init
.initcall2.init
.initcall3.init
.initcall4.init
.initcall5.init
.initcall6.init
.initcall7.init
当需要把函数fn放到.initcall1.init区段时,只要声明
core_initcall(fn);
即可。
其他的各个区段的定义方法分别是:
core_initcall(fn) --->.initcall1.init
postcore_initcall(fn) --->.initcall2.init
arch_initcall(fn) --->.initcall3.init
subsys_initcall(fn) --->.initcall4.init
fs_initcall(fn) --->.initcall5.init
device_initcall(fn) --->.initcall6.init
late_initcall(fn) --->.initcall7.init
而与2.4兼容的initcall(fn)则等价于device_initcall(fn)。各个子区段之间的顺序是确定的,即先调用. initcall1.init中的函数指针,再调用.initcall2.init中的函数指针,等等。而在每个子区段中的函数指针的顺序是和链接顺序相关的,是不确定的。
在内核中,不同的init函数被放在不同的子区段中,因此也就决定了它们的调用顺序。这样也就解决了一些init函数之间必须保证一定的调用顺序的问题。按照include/linux/init.h文件所写的,我在驱动里偿试了这样两种方式:
__define_initcall("7", fn);
late_initcall(fn);
都可以把我的驱动调整到最后调用。实际上上面两个是一回事:
#define late_initcall(fn) __define_initcall("7", fn)
Linux-2.6.20的cs8900驱动分析(一)
一、初始化阶段
网络初始化被调用的路径为:
init->do_basic_setup->do_initcalls->net_olddevs_init->ethif_probe2->probe_list2->cs89x0_probe->cs89x0_probe1
真是不容易啊,终于进到cs89x0_probe1了,在这里开始探测和初始化cs8900了。下面就按照这个顺序来说明网络驱动第一阶段的工作。注意:这里的调用顺序是将cs8900驱动编入内核所产生的,如果将cs8900驱动选为模块,这个路径:init->do_basic_setup->do_initcalls->net_olddevs_init->ethif_probe2->probe_list2也会执行。
1.1 init函数
我们知道当start_kernel函数完成后就会启动init进程执行,在真正的应用程序init进程(如busybox的/sbin/init)之前,Linux还需要执行一些初始化操作。init的代码可以在\init\main.c中找到,它的代码如下:
static int init(void * unused)
{
lock_kernel();
…… //省略多cpu的初始化代码先
do_basic_setup(); //我们所关注的初始化函数
……
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace(); //挂接根文件系统
}
……
free_initmem(); //释放初始化代码的空间
unlock_kernel();
…… //这几段没看懂
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) //检查控制台console是否存在
printk(KERN_WARNING "Warning: unable to open an initial console.\n");
……//这几段没看懂
if (ramdisk_execute_command) { //运行ramdisk_execute_command指定的init用户进程
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}
……
if (execute_command) { //判断在启动时是否指定了init参数,如果指定,此值将赋给execute_command
run_init_process(execute_command); //开始执行用户init进程,如果成功将不会返回。
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...\n", execute_command);
}
//如果没有指定init启动参数,则查找下面的目录init进程,如果找到则不会返回
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
//如果上面的程序都出错,则打印下面的信息,如果内核找到init进程,则程序不会指向到此处
panic("No init found. Try passing init= option to kernel.");
}
1.2 do_basic_setup函数
在这里我们最关心的是do_basic_setup函数,顾名思义该函数的功能就是“做基本设置”,它的实现代码也在\init\main.c中。do_basic_setup()完成外设及其驱动程序的加载和初始化。该函数代码如下所示:
static void __init do_basic_setup(void)
{
/* drivers will send hotplug events */
init_workqueues(); //初始化工作队列
usermodehelper_init(); //初始化khelper内核线程,还没弄清楚
driver_init(); //初始化内核的设备管理架构需要的数据结构,很复杂,以后在谈这部分。
#ifdef CONFIG_SYSCTL
sysctl_init(); //没搞懂
#endif
do_initcalls(); //重点函数,初始化的主要工作就靠它了
}
1.3 do_ initcalls函数
do_initcalls函数将会调用内核中所有的初始化函数,它的代码同样在\init\main.c中。do_initcalls函数调用其他初始化函数相当简洁,它的关键代码如下所示:
initcall_t *call;
for (call = __initcall_start; call < __initcall_end; call ) {
……
result = (*call)();
……
}
简洁归简洁,但这段代码是什么意思呢?这说来就话长了,最重要的应该是先了解Linux处理初始化的大体思想,由于Linux有很多部分需要初始化,每个部分都有自己的初始化函数,如果按照常理一个一个的调用未免显得冗长,而且也不便于扩展。那么Linux是怎么处理的呢?首先,Linux将各个部分的初始化函数编译到一个块内存区中,当初始化完了以后释放这块内存区,这就是init函数中free_initmem所要做的事。然后,再在另外的内存区中存放一些函数指针,让每个指针指向一个初始化函数。然后在do_initcalls中依次根据这些指针调用初始化函数。
上面一段就是Linux实现初始化的大体思想,下面我们看看它最终是怎么实现的。首先要了解的是__define_initcall宏,该宏的定义在\ include\linux\init.h中,它的原型如下所示:
#define __define_initcall(level,fn,id) static initcall_t __initcall_##fn##id __attribute_used__ \
__attribute__((__section__(".initcall" level ".init"))) = fn
__define_initcall宏有三个参数,level表示初始化函数的级别,level值的大小觉得了调用顺序,level越小越先被调用,fn就是具体的初始化函数,id简单标识初始化函数,现在还没找到有什么用^_^。__define_initcall的功能为,首先声明一个initcall_t类型的函数指针__initcall_##fn##id,initcall_t的原型为:
typedef int (*initcall_t)(void);
该类型可简单理解为函数指针类型^_^。然后,让该函数指针指向fn。最后,通过编译器的编译参数将此指针放到指定的空间".initcall" level ".init"中,__attribute_used向编译器说明这段代码有用,即使在没用到的时候,编译器也不会警告。__attribute__的__section__参数表示该段代码放入什么内存区域中,也即指定编译到什么地方,编译参数更详细的地方可以查阅GCC文档,在gcc官方网站http://gcc.gnu.org/onlinedocs/中能找到各个版本的手册。这样说来还是比较抽象,下面举个例子来说明:
假如有初始化函数init_foolish函数,现在使用__define_initcall宏向内核加入该函数。假如调用方式如下:
__define_initcall("0",init_foolish,1);
那么,__define_initcall宏首先申请一个initcall_t类型的函数指针__initcall_init_foolish1(注意替换关系),且使该指针指向了init_foolish,函数指针__initcall_init_foolish1被放到.initcall.0.init内存区域中,这个标志在连接时会用到。
有了上面的基础知识,现在回到do_initcalls函数中,首先注意到是__initcall_start和__initcall_end,它们的作用就是界定了存放初始化函数指针区域的起始地址,也即从__initcall_start开始到__initcall_end结束的区域中存放了指向各个初始化函数的函数指针。换句话说,只要某段程序代码从__initcall_start开始依次调用函数指针,那么就可以完成各个部分的初始化工作,这显得十分优雅而且便于扩充,再看看do_initcalls,它何尝不是如此呢。这里还有一个有用的技巧就是__initcall_start和__initcall_end的原型是initcall_t型的数组,以后可以使用这种技巧^_^。
现在我们知道了do_initcalls函数的实现原理,那么到底它调用了多少初始化函数呢?我们怎样才能知道呢?根据上面的分析,我们知道所有的初始化函数的指针都放在__initcall_start和__initcall_end区域期间,而函数指针与它指向的函数之间又有固定的关系,如上面的例子,初始化函数名为init_foolish,指向它的函数指针就是__initcall_init_foolish1,即在此函数加上前缀__initcall_和一个数字后缀,反之,从函数指针也可推出初始化函数名。有了这两个信息,我们就可以很方便的找个初始化函数。怎么找呢??首先打开Linux完后产生的System.map文件,然后找到__initcall_start和__initcall_end字符串,你会发现它们之间有很多类似于__initcall_xxx1这样的符号,这些符号就是我们需要的函数指针了,这样就可推出初始化函数的名字。比如,我们这里需要的函数指针__initcall_net_olddevs_init6,按照上面的名字规则,很容易推出它所指向的初始化函数名字是net_olddevs_init。
得到了初始化函数的名字又怎么样呢?又不知道它在哪个文件里,不要着急!请打开你的浏览器登陆http://lxr.linux.no/ident网站,然后选择Linux版本和架构,然后可以搜索我们想要的信息。比如我输入net_olddevs_init,然后我就会得到该函数所在文件的相关信息。
1.4 net_olddevs_init函数
我们知道net_olddevs_init函数在do_initcalls函数中被调用并执行,那么它到底要做什么呢?看看实现代码就知道了,它的实现代码可以在\drivers\net\Space.c中找到。对于网络驱动部分的主要实现代码如下:
static int __init net_olddevs_init(void){
……
int num;
for (num = 0; num < 8; num)
ethif_probe2(num);
……
}
这段代码就不用讲解了吧,嘿嘿!就是调用了8次ethif_probe2,赶快去看看ethif_probe2长什么样子。
1.5 ethif_probe2函数
先看看该函数的实现代码,该代码也在\drivers\net\Space.c文件中。
static void __init ethif_probe2(int unit)
{
unsigned long base_addr = netdev_boot_base("eth", unit); // 由于ethif_probe2被net_olddevs_init调用了8次,
// 所以unit的值为0~7,也即在这里可以注册eth0~eth7八个网络设备
if (base_addr == 1)
return;
(void)( probe_list2(unit, m68k_probes, base_addr == 0) &&
probe_list2(unit, eisa_probes, base_addr == 0) &&
probe_list2(unit, mca_probes, base_addr == 0) &&
probe_list2(unit, isa_probes, base_addr == 0) &&
probe_list2(unit, parport_probes, base_addr == 0));
}
该函数首先调用netdev_boot_base所给的设备是否已经向内核注册,如果已注册netdev_boot_base返回1,随后推出ethif_probe2。如果设备没注册,则又调用函数probe_list2四次,每次传递的传输不同,注意到每次传递的第二个参数不同,这个参数也是相当重要的,这里拿isa_probes参数为例说明,因为这个参数与cs89x0_probe有关,isa_probes的定义也在\drivers\net\Space.c中,它的样子形如:
static struct devprobe2 isa_probes[] __initdata = {
……
#ifdef CONFIG_SEEQ8005
{seeq8005_probe, 0},
#endif
#ifdef CONFIG_CS89x0
{cs89x0_probe, 0},
#endif
#ifdef CONFIG_AT1700
{at1700_probe, 0},
#endif
{NULL, 0},
……
};
如果把cs8900的驱动选为非编译进内核,那么它的探测函数cs89x0_probe就不会存在于isa_probes数组中,所以在初始阶段就不能被调用。从上面的代码可以知道devprobe2类型至少包括两个域,至少一个域为函数指针,看看它的原型如下:
struct devprobe2 {
struct net_device *(*probe)(int unit); //函数指针,指向探测函数
int status; /* non-zero if autoprobe has failed */
};
下面看看probe_list2函数是怎么表演的。
1.6 ethif_probe2函数
对于ethif_probe2函数也没有什么需要说明的,它的主要任务是依次调用devprobe2类型的probe域指向的函数。他的实现代码同样在\drivers\net\Space.c中,它的关键代码如下:
static int __init probe_list2(int unit, struct devprobe2 *p, int autoprobe)
{
struct net_device *dev;
for (; p->probe; p ) {
……
dev = p->probe(unit);
……
}
……
}
1.7 cs89x0_probe函数
从该函数起,真正开始执行与cs8900驱动初始化程序,该函数在\drivers\net\cs89x0.c文件实现。下面依次解释该函数。
struct net_device * __init cs89x0_probe(int unit)
{
struct net_device *dev = alloc_etherdev(sizeof(struct net_local)); //该函数申请一个net_device+
//sizeof(struct net_local)的空间,net_local是cs8900驱动的私有数据空间。
unsigned *port;
int err = 0;
int irq;
int io;
if (!dev)
return ERR_PTR(-ENODEV);
sprintf(dev->name, "eth%d", unit); //初始化dev->name域
netdev_boot_setup_check(dev); //检查是否给定了启动参数,如果给定了启动参数,此函数将初始
//化dev的irq、base_addr、mem_start和mem_end域。
io = dev->base_addr; //io实际实质cs8900所占地址空间的起始地址,此地址为虚拟地址
irq = dev->irq;
if (net_debug)
printk("cs89x0:cs89x0_probe(0x%x)\n", io);
//下面根据io的值调用cs89x0_probe1函数
if (io > 0x1ff) {/* Check a single specified location. *///此段没搞懂,由于没给启动参数,这里也不会执行
err = cs89x0_probe1(dev, io, 0);
} else if (io != 0) { /* Don''''''''''''''''''''''''''''''''t probe at all. */
err = -ENXIO;
} else {
for (port = netcard_portlist; *port; port ) {// netcard_portlist为unsigned int型数组,在cs89x0.c文件中定
//义,里面列出了cs8900可能占用空间的起始地址,这些地址
//将在cs89x0_probe1函数中用于向内核申请。
if (cs89x0_probe1(dev, *port, 0) == 0) // cs89x0_probe1探测成功就返回0
break;
dev->irq = irq;
}
if (!*port)
err = -ENODEV;
}
if (err)
goto out;
return dev;
out:
free_netdev(dev); //表示探测失败,这里就释放dev的空间,随后打印些消息
printk(KERN_WARNING "cs89x0: no cs8900 or cs8920 detected. Be sure to disable PnP with SETUP\n");
return ERR_PTR(err);
}
从上面的程序清单可以看到该函数还没有真正的开始探测 cs8900 ,实质的探测工作是让 cs89x0_probe1 完成的。在解释 cs89x0_probe1 之前先提一下网络驱动程序中非常重要的一些函数。内核需要一个数据结构来管理或者描述每个网络驱动程序,这个数据类型就是 struct net_device ,该数据类型包括很多域,详细的解释可以参见《 Linux 设备驱动程序》一书中的描述,也可以参见源代码(在 \include\linux\netdevice.h 中,源码中也有详细的注解)。内核为了编程方便特地实现了函数 alloc_netdev 来完成对 net_device 的空间分配。那么 alloc_etherdev 函数主要针对以太网在 alloc_netdev 基础上封装的一个函数,它除了申请 net_device 空间外,还会初始化 net_device 的相关域。Linux下网络子系统的初始化
首先,我的最初出发点是从init进程开始的,如果你的系统的引导之类的感兴趣,那请在白云黄鹤的Linux版上查找相关资料或去其它地方查找.然后我的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[], __initcall_end[];你有仔细的查找过么?
恩,那就查找一番,原来,居然在[arch/i386/kernel/vmlinux.lds.S],如果这时你进去看了这个文件依然是雾水一头,那看来只能出杀手锏了:).(请参考网上的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框架初始化