分类: LINUX
2008-05-16 17:35:42
内核从现在开始就进入了c语言部分,内核启动第二阶段从init/main.c的start_kernel()函数开始到函数结束。
这一阶段对整个系统内存、cache、信号、设备等进行初始化,最后产生新的内核线程init后,
调用cpu_idle()完成内核第二阶段。有很多书籍介绍这一部分的内容,我们这里仅仅讲述与xscale结构相关的部分。
首先我们看一下start_kernel开始部分的源代码
asmlinkage void __init start_kernel(void)
{
char * command_line;
extern char saved_command_line[];
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
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使用了asmlinkage进行修饰,该修饰符定义在kernel/include/linux/linkage.h中,如下所示:
#ifdef __cplusplus
#define CPP_ASMLINKAGE extern "C"
#else
#define CPP_ASMLINKAGE
#endif
#if defined __i386__
#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))
#elif defined __ia64__
#define asmlinkage CPP_ASMLINKAGE __attribute__((syscall_linkage))
#else
#define asmlinkage CPP_ASMLINKAGE
#endif
应为我们使用的是arm平台,所以这些定义没有意义,不过还是简单介绍一下regparm的意思,察看gcc手册,原文
介绍如下:
On the Intel 386, the regparm attribute causes the compiler to pass arguments
number one to number if they are of integral type in registers EAX, EDX,
and ECX instead of on the stack. Functions that take a variable number of
arguments will continue to be passed all of their arguments on the stack.
Beware that on some ELF systems this attribute is unsuitable for global functions
in shared libraries with lazy binding (which is the default). Lazy binding
will send the first call via resolving code in the loader, which might assume
EAX, EDX and ECX can be clobbered, as per the standard calling conventions.
Solaris 8 is affected by this. GNU systems with GLIBC 2.1 or higher,
and FreeBSD, are believed to be safe since the loaders there save all registers.
(Lazy binding can be disabled with the linker or the loader if desired, to avoid
the problem.)
在网上还看到一个比较好的英文说明:
The asmlinkage tag is one other thing that we should observe about this simple function.
This is a #define for some gcc magic that tells the compiler that the function should not
expect to find any of its arguments in registers (a common optimization),
but only on the CPU's stack. Recall our earlier assertion that system_call consumes its
first argument, the system call number, and allows up to four more arguments that are
passed along to the real system call. system_call achieves this feat simply by leaving
its other arguments (which were passed to it in registers) on the stack. All system calls
are marked with the asmlinkage tag, so they all look to the stack for arguments. Of course,
in sys_ni_syscall's case, this doesn't make any difference, because sys_ni_syscall doesn't
take any arguments, but it's an issue for most other system calls. And, because you'll be
seeing asmlinkage in front of many other functions, I thought you should know what it was about.
简单描述一下他的功能:
asmlinkage是个宏,使用它是为了保持参数在stack中。因为从汇编语言到C语言代码参数
的传递是通过stack的,它也可能从stack中得到一些不需要的参数。Asmlinkage将要
解析那些参数。regparm(0)表示不从寄存器传递参数。如果是__attribute__((regparm(3))),
那么调用函数的时候参数不是通过栈传递,而是直接放到寄存器里,被调用函数直接从寄存器取参数。
这一点可以从下面的定义可以看出:
#define fastcall __attribute__((regparm(3)))
这些都必须是在i386平台下才有意义。
说完asmlinkage,开始看源代码,第一个函数:lock_kernel(),
这是为了在SMP系统下设计的,它定义在kernel/include/linux/smp_lock.h,如果是SMP系统,则会
定义CONFIG_SMP,否则lock_kernel()将是空函数,如果定义CONFIG_SMP的话,则会包含kernel/include/
asm/smplock.h头文件,lock_kernel()就定一在该文件中,首先我们来看一下smp_lock.h文件:
#ifndef CONFIG_SMP
#define lock_kernel() do { } while(0)
#define unlock_kernel() do { } while(0)
#define release_kernel_lock(task, cpu) do { } while(0)
#define reacquire_kernel_lock(task) do { } while(0)
#define kernel_locked() 1
#else
#include
#endif /* CONFIG_SMP */
我们的平台是单cpu的(没有定义CONFIG_SMP),所以lock_kernel是空函数,不过仍然对它进行一下说明,
如果定义了CONFIG_SMP,则include kernel/include/asm-arm/smplock.h文件,看一下该文件:
static inline void lock_kernel(void)
{
if (!++current->lock_depth)
spin_lock(&kernel_flag);
}
static inline void unlock_kernel(void)
{
if (--current->lock_depth < 0)
spin_unlock(&kernel_flag);
}
找到两个比较好的说明如下
1
kernel_flag是一个内核大自旋锁,所有进程都通过这个大锁来实现向内核态的迁移。只有
获得这个大自旋锁的处理器可以进入内核,如中断处理程序等。在任何一对lock_kernel/
unlock_kernel函数里至多可以有一个程序占用CPU。 进程的lock_depth成员初始化为-1,
在kerenl/fork.c文件中设置。在它小于0时(恒为 -1),进程不拥有内核锁;当大于或等
于0时,进程得到内核锁。
2
kernel_flag,定义为自旋锁,因为很多核心操作(例如驱动中)需要保证当前仅由一个进程执行,
所以需要调用lock_kernel()/release_kernel()对核心锁进行操作,它在锁定/解锁kernel_flag的
同时还在task_struct::lock_depth上设置了标志,lock_depth小于0表示未加锁。当发生进程切换的时候,
不允许被切换走的进程握有kernel_flag锁,所以必须调用release_kernel_lock()强制释放,同时,
新进程投入运行时如果lock_depth>0,即表明该进程被切换走之前握有核心锁,
必须调用reacquire_kernel_lock()再次锁定;
代码printk(linux_banner)将linux的一些标语打印在内核启动的开始部分,需要说明的是虽然这是
在内核一开始运行时就打印了,但是它没有马上输出到控制台上,它只是将liunx_banner存储到printk
的内部缓冲中,因为这时printk的输出设备,一般都是串口还没有初始化,只有到输出设备初始化完毕
在缓冲中的数据才被输出,后面会看到在哪个位置linux_banner才真正输出到终端。linux_banner定义在
kernel/init/version.c中:
const char *linux_banner =
"Linux version " UTS_RELEASE " (" LINUX_COMPILE_BY "@"
LINUX_COMPILE_HOST ") (" LINUX_COMPILER ") " UTS_VERSION "\n";
这里面的字符串定义在文件kernel/include/linux/compile.h和kernel/include/linux/version.h中,
compile.h中的内容:
#define UTS_VERSION "#1 Thu, 01 Feb 2007 13:32:14 +0800"
#define LINUX_COMPILE_TIME "13:32:14"
#define LINUX_COMPILE_BY "taoyue"
#define LINUX_COMPILE_HOST "swlinux.cecwireless.com.cn"
#define LINUX_COMPILE_DOMAIN "cecwireless.com.cn"
#define LINUX_COMPILER "gcc version 3.2.1"
version.h中的内容:
#define UTS_RELEASE "2.4.19-rmk7-pxa2"
#define LINUX_VERSION_CODE 132115
#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
这两个文件都是在编译时候生成的,看一下kernel/Makefile文件:
include/linux/compile.h: $(CONFIGURATION) include/linux/version.h newversion
@echo -n \#`cat .version` > .ver1
@if [ -n "$(CONFIG_SMP)" ] ; then echo -n " SMP" >> .ver1; fi
@if [ -f .name ]; then echo -n \-`cat .name` >> .ver1; fi
@LANG=C echo ' '`date -R` >> .ver1
@echo \#define UTS_VERSION \"`cat .ver1 | $(uts_truncate)`\" > .ver
@LANG=C echo \#define LINUX_COMPILE_TIME \"`date +%T`\" >> .ver
@echo \#define LINUX_COMPILE_BY \"`whoami`\" >> .ver
@echo \#define LINUX_COMPILE_HOST \"`hostname | $(uts_truncate)`\" >> .ver
@([ -x /bin/dnsdomainname ] && /bin/dnsdomainname > .ver1) || \
([ -x /bin/domainname ] && /bin/domainname > .ver1) || \
echo > .ver1
@echo \#define LINUX_COMPILE_DOMAIN \"`cat .ver1 | $(uts_truncate)`\" >> .ver
@echo \#define LINUX_COMPILER \"`$(CC) $(CFLAGS) -v 2>&1 | tail -1`\" >> .ver
@mv -f .ver $@
@rm -f .ver1
include/linux/version.h: ./Makefile
@expr length "$(KERNELRELEASE)" \<= $(uts_len) > /dev/null || \
(echo KERNELRELEASE \"$(KERNELRELEASE)\" exceeds $(uts_len) characters >&2; false)
@echo \#define UTS_RELEASE \"$(KERNELRELEASE)\" > .ver
@echo \#define LINUX_VERSION_CODE `expr $(VERSION) \\* 65536 + $(PATCHLEVEL) \\* 256 + $(SUBLEVEL)` >> .ver
@echo '#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))' >>.ver
@mv -f .ver $@
可以修改的参数是:
VERSION = 2
PATCHLEVEL = 4
SUBLEVEL = 19
EXTRAVERSION = -rmk7-pxa2