kernel最开头是一段汇编,本文讲的最开头的kernel的C代码的函数展开。
init task底加魔术字函数的展开
也就是/init/main.c文件start_kernel主函数中的第一个实际执行函数set_task_stack_end_magic。(这个函数的主要作用是给kernel自身主进程设置内核栈底魔术字)

这个函数涉及了底层的寄存器相关操作,这是C语言无法靠自己生成的,需要结合汇编语言生成并且需要针对不同的架构进行适配,所以在main主文件中没有这个函数的实例。
这个函数的定义转到/kernel/fork.c文件中:
void set_task_stack_end_magic(struct task_struct *tsk)
{
unsigned long *stackend;
stackend = end_of_stack(tsk);
*stackend = STACK_END_MAGIC; /* for overflow detection */
}
static inline unsigned long *end_of_stack(struct task_struct *p)
{
#ifdef CONFIG_STACK_GROWSUP
return (unsigned long *)((unsigned long)task_thread_info(p) + THREAD_SIZE) - 1;
#else
return (unsigned long *)(task_thread_info(p) + 1); // 这里是生效的代码
#endif
}
static inline struct thread_info *task_thread_info(struct task_struct *task)
{
return &task->thread_info;
}这里的init task是由/init/init_task.c文件专门定义的一个数据结构。end_of_stack直接返回task结构里的init_stack指针,这个指针定义在/include/linux/sched.h中被声明:

然后在/include/asm-generic/vmlinux.lds.h中被实例化:

这里的链接器代码含义是在内核镜像中静态分配并初始化0号进程的内核栈和任务结构体内存区域。
. = ALIGN(align)含义是进行内存地址对齐到align
__start_init_task = .;含义是定义一个__start_init_task变量,值为这块内存区域的起始地址
init_thread_union = .;含义是定义一个可以被调用的名为init_thread_union的变量,值为这块内存区域的起始地址,对,就是跟上面那个变量保存的是同一个值
init_stack = .;也是一样的,含义都是这个INIT_TASK_DATA段的内存起始地址作为值保留在这个变量中
KEEP(*(.data..init_task)),这个是init_task变量描述的关键。在/init/init_task.c的init_task变量的描述中,会加入一个__init_task_data的宏展开,解析为/include/linux/init_task.h中的#define __init_task_data __section(".data..init_task"),正是因为这段解析,会将这个init_task变量放入一个专门的段名为.data..init_task的段,进而在链接的时候就可以被链接器找到了。这是kernel编译过程中C语言进行静态变量栈底、栈顶修饰时,由链接器找段再给内存栈底顶地址变量回给静态变量结构的一种非常经典好用的操作
KEEP(*(.data..init_thread_info))跟上面是一样的作用,如果kernel是以init_thread_info的方式做INIT TASK的话(取决于一些结构于config的设置),也一样可以用于链接器找初始化变量的内存位置
. = __start_init_task + THREAD_SIZE;将当前计数器挪到初始任务栈栈底
__end_init_task = .;定义__end_init_task变量的值为初始任务栈栈底地址

所以,到此结合
这与init_task本身的结构体是遥相呼应的:
struct task_struct init_task
#ifdef CONFIG_ARCH_TASK_STRUCT_ON_STACK
__init_task_data
#endif
__aligned(L1_CACHE_BYTES)
= {
#ifdef CONFIG_THREAD_INFO_IN_TASK
.thread_info = INIT_THREAD_INFO(init_task),
.stack_refcount = REFCOUNT_INIT(1),
#endif
.state = 0,
.stack = init_stack,
......
}也就是说,这个所谓的init task,是由init_task.c文件给出携带特殊section段定义,然后由gcc编译后根据vmlinux.lds链接到特定INIT_TASK_DATA内存片段,并在链接器中给定片段头和尾的内存地址值到__start_init_task、__end_init_task变量中,最后由init_task.c文件中有关这两个变量的赋值引用,最终实现代码在进入内存时,init_task里的init_task变量就已经保存了自己在内存中地址的功能。
最后,start_kernel函数要做的只是简单的在这个init task任务给定的内存片段段尾加上一个魔术字的标识就OK了。