1.
在看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
一、initcall机制原理
在linux初始化的过程中,内核采用了一种initcall的机制,它利用gcc的扩展功能以及ld的连接控制脚本实现了在内核初始化的过程中通过简单的循环就实现了相关驱动的初始化。核心代码的/init/main.c里面有do_initcalls函数如下:
static void __init do_initcalls(void)
{
initcall_t *call;
call = &__initcall_start;
do {
(*call)();
call++;
} while (call < &__initcall_end);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_tasks();
}
其中__initcall_start和__initcall_end在源码中并无定义,只是在include\linux\init.h中申明为外部变量:
extern initcall_t __initcall_start, __initcall_end;
i386平台下,连接控制脚本为vmlinux.lds, 这2各变量的定义是在/arch/i386/vmlinux.lds中,代码如下:
__initcall_start = .;
.initcall.init : { *(.initcall.init) }
__initcall_end = .;
其含义是指示连接程序让__initcall_start指向代码节.initcall.init的节首,而__initcall_end指向.initcall.init的节尾。
则在内核中,只要把需要初始化调用的函数的指针放在__initcall_start和__initcall_end之间的节内,函数就会在内核初始化时被调用。
类似的内核引用的外部变量还有:…
二、实践:
实际过程分如下几步:
1、编写主程序doinitcall.c:代码如下并有详细注释:
#include <stdio.h>
typedef int (*initcall_t)(void); /*定义函数指针*/
extern initcall_t __initcall_start, __initcall_end; /*申明外部变量,在ld的脚本文件中定义*/
#define __initcall(fn) \
static initcall_t __initcall_##fn __init_call = fn
#define __init_call
__attribute__ ((unused,__section__ ("function_ptrs")))
#define module_init(x) __initcall(x);
/*上述宏定义名为"__initcall_函数名"的函数指针,且将函数指针放在function_ptrs节
这个函数指针,指向fn(fn函数则放在code_segment节中)*/
#define __init __attribute__ ((__section__ ("code_segment")))
static int __init /*函数放在code_segment节*/
my_init1 (void)
{
printf ("my_init () #1\n");
return 0;
}
static int __init
my_init2 (void)
{
printf ("my_init () #2\n");
return 0;
}
module_init (my_init1);/*定义要被调用的函数指针并放到指定的节中*/
module_init (my_init2);
void
do_initcalls (void)
{
initcall_t *call_p; 定义函数指针变量
call_p = &__initcall_start;/*获取节首址*/
do {
fprintf (stderr, "call_p: %p\n", call_p);
(*call_p)();
++call_p;/*32位机器上,函数指针占4bytes,增加一次就是指针便宜4bytes*/
} while (call_p < &__initcall_end);
}
int
main (void)
{
fprintf (stderr, "in main()\n");
do_initcalls (); /*调用*/
return 0;
}
2、导出默认的连接控制脚本文件:保存为linker.lds
通过命令gcc -Wl,--verbose可以获得默认的连接控制脚本, 即选择 "=======..."之间的文本,保存为linker.lds文件
3、在linker.lds文件中增加本例需要控制的语句:
将下面
/*定义__initcall_start符号为当前位置,即.代表当前位置*/
__initcall_start = .;
function_ptrs : { *(function_ptrs) }
__initcall_end = .;
/*上述3行代码代表function_ptrs节位于__initcall_start和__initcall_end之间*/
code_segment : { *(code_segment) }
这段代码copy到linker.lds文件的
__bss_start = .;
语句之前。
完整的linker.lds文件如下:
/* Script for -z combreloc: combine and sort reloc sections */
OUTPUT_FORMAT("elf32-i386", "elf32-i386",
"elf32-i386")
OUTPUT_ARCH(i386)
ENTRY(_start)
SEARCH_DIR("/usr/i386-redhat-linux/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/local/lib"); SEARCH_DIR("/lib");
/* Do we need any of these for elf?
__DYNAMIC = 0; */
SECTIONS
{
/* Read-only sections, merged into text segment: */
. = 0x08048000 + SIZEOF_HEADERS;
.interp
: { *(.interp) }
.hash
: { *(.hash) }
.dynsym
: { *(.dynsym) }
.dynstr
: { *(.dynstr) }
.gnu.version
: { *(.gnu.version) }
.gnu.version_d : { *(.gnu.version_d) }
.gnu.version_r : { *(.gnu.version_r) }
.rel.dyn
:
{
*(.rel.init)
*(.rel.text .rel.text.* .rel.gnu.linkonce.t.*)
*(.rel.fini)
*(.rel.rodata .rel.rodata.* .rel.gnu.linkonce.r.*)
*(.rel.data .rel.data.* .rel.gnu.linkonce.d.*)
*(.rel.tdata .rel.tdata.* .rel.gnu.linkonce.td.*)
*(.rel.tbss .rel.tbss.* .rel.gnu.linkonce.tb.*)
*(.rel.ctors)
*(.rel.dtors)
*(.rel.got)
*(.rel.bss .rel.bss.* .rel.gnu.linkonce.b.*)
}
.rela.dyn
:
{
*(.rela.init)
*(.rela.text .rela.text.* .rela.gnu.linkonce.t.*)
*(.rela.fini)
*(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*)
*(.rela.data .rela.data.* .rela.gnu.linkonce.d.*)
*(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*)
*(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*)
*(.rela.ctors)
*(.rela.dtors)
*(.rela.got)
*(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*)
}
.rel.plt
: { *(.rel.plt) }
.rela.plt
: { *(.rela.plt) }
.init
:
{
KEEP (*(.init))
} =0x90909090
.plt
: { *(.plt) }
.text
:
{
*(.text .stub .text.* .gnu.linkonce.t.*)
/* .gnu.warning sections are handled specially by elf32.em. */
*(.gnu.warning)
} =0x90909090
.fini
:
{
KEEP (*(.fini))
} =0x90909090
PROVIDE (__etext = .);
PROVIDE (_etext = .);
PROVIDE (etext = .);
.rodata
: { *(.rodata .rodata.* .gnu.linkonce.r.*) }
.rodata1
: { *(.rodata1) }
.eh_frame_hdr : { *(.eh_frame_hdr) }
.eh_frame
: ONLY_IF_RO { KEEP (*(.eh_frame)) }
.gcc_except_table
: ONLY_IF_RO { *(.gcc_except_table) }
/* Adjust the address for the data segment. We want to adjust up to
the same address within the page on the next page up. */
. = ALIGN (0x1000) - ((0x1000 - .) & (0x1000 - 1)); . = DATA_SEGMENT_ALIGN (0x1000, 0x1000);
/* Ensure the __preinit_array_start label is properly aligned. We
could instead move the label definition inside the section, but
the linker would then create the section even if it turns out to
be empty, which isn't pretty. */
. = ALIGN(32 / 8);
PROVIDE (__preinit_array_start = .);
.preinit_array
: { *(.preinit_array) }
PROVIDE (__preinit_array_end = .);
PROVIDE (__init_array_start = .);
.init_array
: { *(.init_array) }
PROVIDE (__init_array_end = .);
PROVIDE (__fini_array_start = .);
.fini_array
: { *(.fini_array) }
PROVIDE (__fini_array_end = .);
.data
:
{
*(.data .data.* .gnu.linkonce.d.*)
SORT(CONSTRUCTORS)
}
.data1
: { *(.data1) }
.tdata
: { *(.tdata .tdata.* .gnu.linkonce.td.*) }
.tbss
: { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
.eh_frame
: ONLY_IF_RW { KEEP (*(.eh_frame)) }
.gcc_except_table
: ONLY_IF_RW { *(.gcc_except_table) }
.dynamic
: { *(.dynamic) }
.ctors
:
{
/* gcc uses crtbegin.o to find the start of
the constructors, so we make sure it is
first. Because this is a wildcard, it
doesn't matter if the user does not
actually link against crtbegin.o; the
linker won't look for a file to match a
wildcard. The wildcard also means that it
doesn't matter which directory crtbegin.o
is in. */
KEEP (*crtbegin*.o(.ctors))
/* We don't want to include the .ctor section from
from the crtend.o file until after the sorted ctors.
The .ctor section from the crtend file contains the
end of ctors marker and it must be last */
KEEP (*(EXCLUDE_FILE (*crtend*.o ) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
}
.dtors
:
{
KEEP (*crtbegin*.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend*.o ) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
}
.jcr
: { KEEP (*(.jcr)) }
.got
: { *(.got.plt) *(.got) }
_edata = .;
PROVIDE (edata = .);
/*定义__initcall_start符号为当前位置,即.代表当前位置*/
__initcall_start = .;
function_ptrs
: { *(function_ptrs) }
__initcall_end = .;
/*上述3行代码代表function_ptrs节位于__initcall_start和__initcall_end之间*/
code_segment
: { *(code_segment) }
__bss_start = .;
.bss
:
{
*(.dynbss)
*(.bss .bss.* .gnu.linkonce.b.*)
*(COMMON)
/* Align here to ensure that the .bss section occupies space up to
_end. Align after .bss to ensure correct alignment even if the
.bss section disappears because there are no input sections. */
. = ALIGN(32 / 8);
}
. = ALIGN(32 / 8);
_end = .;
PROVIDE (end = .);
. = DATA_SEGMENT_END (.);
/* Stabs debugging sections. */
.stab
0 : { *(.stab) }
.stabstr
0 : { *(.stabstr) }
.stab.excl
0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index
0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment
0 : { *(.comment) }
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section so we begin them at 0. */
/* DWARF 1 */
.debug
0 : { *(.debug) }
.line
0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info
0 : { *(.debug_info .gnu.linkonce.wi.*) }
.debug_abbrev
0 : { *(.debug_abbrev) }
.debug_line
0 : { *(.debug_line) }
.debug_frame
0 : { *(.debug_frame) }
.debug_str
0 : { *(.debug_str) }
.debug_loc
0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
/DISCARD/ : { *(.note.GNU-stack) }
}
4、编译程序
命令:
gcc -Tlinker.lds -o doinitcall doinitcall.c
其中:
-T选项告诉ld要用的连接控制脚本文件,做为链接程序的依据。格式如下:
-T commandfile 或
--script=commandfile
5、执行程序
可以看到如下结果:
[zhouys@Cluster1 zhouys]$ ./doinitcall
in main()
call_p: 0x804961c
my_init () #1
call_p: 0x8049620
my_init () #2
三、参考资料:
1、
Understanding The Linux Kernel Initcall Mechanism
2
、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启动流程之进入内核
[email protected]
还是从编译链接生成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
完了
_