linux3.10 中0号任务的创建以及堆栈初始化

该博文转自:https://blog.csdn.net/wennuanddianbo/article/details/55107046

一、初识0号任务

    打开linux系统的命令行,敲下“ps -A”命令,就能看到系统中的所有任务:其中有一个pid号为0任务格外的显眼。
    我们知道linux中的task都是通过fork族函数由父进程创造出来,而这个0号进程却是个例外,它由人类捏造出来,没有parent。
    Linux中所有task都由一个task_struct结构来代表,一旦task有了task_struct,我们就能够看得见摸得着;一个新task的创建过程首先就是为其分配task_struct结构,使它的灵魂有所寄托。
    而这个0号进程(在linux中一般采用“任务”来指代一个进程,这里“进程”的说法和“任务”一个意思)的task_struct结构的对象通过静态全局变量的方式创建,这个对象实例叫init_task。它随内核代码一起编到内核镜像之中,内核镜像一旦编译完成0号进程的task_struct实例init_task也随之生成,即在内核中已经有存在。这个实例init_task的定义在init/init_task.c目录下:

struct task_struct init_task = INIT_TASK(init_task);  

    这个init_task就是0号进程task_struct结构对象实例,init_task中的各个成员通过INIT_TASK(init_task)这个宏来初始化,我们列举出一些我们关注的初始化成员值,没有列出的用省略号代替:

 #define INIT_TASK(tsk)    \  
    {                                    \  
        .state        = 0,                        \  
        .stack        = &init_thread_info,                \  
        .flags        = PF_KTHREAD,                    \  
         ......  
        .prio        = MAX_PRIO-20,                    \  
        .static_prio    = MAX_PRIO-20,                    \  
        .normal_prio    = MAX_PRIO-20,                    \  
        .policy        = SCHED_NORMAL,                    \  
        ......  
      
        .real_parent    = &tsk,                        \  
        .parent        = &tsk,                        \  
      
       ......   
       .comm = INIT_TASK_COMM, \   
       ......  
    }

    可以看出init_task的成员就是通过上面的宏展开后被一一赋予了给定的值:
  1. 内核堆栈
    一个任务内核堆栈指针由task_struct.stack来指定,init_task的stack指针也是静态分配的:

.stack        = &init_thread_info

  2. 任务标志
    Init_task是一个内核任务,其标志也初始设置为PF_KTHREAD。

.flags        = PF_KTHREAD

  3. 调度策略
    Init_task虽然是内核中的第一个任务,但它并不高特殊,不论是调度策略(SCHED_NORMAL)还是优先级(120),都是最普通的:

    .prio        = MAX_PRIO-20,                    \  
    .static_prio    = MAX_PRIO-20,                    \  
    .normal_prio    = MAX_PRIO-20,                    \  
    .policy        = SCHED_NORMAL,                    \

  4. init_task的父辈
    Init_task是系统中的第一个任务,所以它就是系统中的始祖:

    .real_parent    = &tsk,                        \  
    .parent        = &tsk,                        \  

  5. 任务名字
    Init_task的名字通过成员"comm"表示,其值就是"swapper"。

    #define INIT_TASK_COMM "swapper"  
    .comm        = INIT_TASK_COMM  

    一旦内核编译完成生成镜像,这个init_task就已经在其中占有自己的一席之地,内核已启动它便存在!

二、认识0号任务的堆栈

2.1 初识

     从上面我们已经看到0号任务的堆栈,准确的叫内核堆栈,已经初始化为init_thread_info:

.stack        = &init_thread_info

    这个init_trhead_info实际上是一个宏,其定义与架构相关,但是大部分架构的定义都一样(x86、arm、mips以及powerpc)如下:

#define init_thread_info    (init_thread_union.thread_info)

    看到没?这个init_thread_info只是一个幌子,实际上stack指向的是 .stack = &init_thread_union.thread_info;而这个init_thread_union是一个与架构无关的全局union,定义在init/init_task.c中:

    union thread_union init_thread_union __init_task_data =  
        { INIT_THREAD_INFO(init_task) };

    这个init_thread_union又是一个宏,我们再将他展开看看.....等等,思路似乎有些乱。我们先缕一缕:
     1) 前面已经确定了0号任务的内核堆栈指针init_task.stack指向的是&init_thread_union.thread_info;
     2) 一个task的内核堆栈指针指向一片连续的内存区域(即内核堆栈使用区域),这片堆栈区域的大小随架构的不同其size也有所差异,一般通过宏THREAD_SIZE来表示。
     3) 综合1)和2)可以知道0号进程内核堆栈指针与&init_thread_union.thread_info相等,即这片内核堆栈区域的底端实际放的就是init_thread_union.thread_info。而实际在一般情况下一个task的内核堆栈底部确实放着一个thread_info结构!
     4) 内核堆栈的作用就是用来保存数据用的,而0号任务的这块堆栈区域却是静态分配的;那这块区域是在哪里分配的呢??
    好,我们就再来深挖一下0号任务堆栈以及init_thread_union.thread_info的真面目。

2.2 深挖

    既然0号task的堆栈指针指向init_thread_union.thread_info的地址,那就拨开云雾见天日,研究一下我们深挖的重点:0号任务init_task.stack指针指向的大小THREAD_SIZE的区域究竟在哪里分配。

2.2.1 init_thread_union的真面目

    首先,init_task.stack是与init_thread_union这个变量关联,我们先来关注它。

    union thread_union init_thread_union __init_task_data =  
        { INIT_THREAD_INFO(init_task) };  

     这个thred_union联合体定义如下:

    union thread_union {  
        struct thread_info thread_info;                /* init_task.stack 指向这个成员 */  
        unsigned long stack[THREAD_SIZE/sizeof(long)]; /* stack[]将thread_union扩张为THREAD_SIZE大小 */  
    };  

    即这个静态全局变量 init_thread_union实际上已经静态分配了THREAD_SIZE的大小,由于它是一个联合体,所以我们引用init_thread_union.thread_info就可以够着这个THREAD_SIZE的内存区域,我们的init_task.stack也就在这里着落。
    一般情况下全局变量都放在内核镜像的数据段,但是这里可以的init_thread_union通过__init_task_data属性使其init_thread_union最终链接到镜像中的__init_task_data这个段中,即init_task的内核堆栈THREAD_SIZE大小的区域最终放在这个__init_task_data属性段中。

 2.2.2 init_task内核堆栈的实际位置

    既然我们知道0号任务内核堆栈是在__init_task_data属性段中,那我们就来研究一下:

#define __init_task_data __attribute__((__section__(".data..init_task")))

    即init_thread_union最终是链接到.data..init_task这个数据段中,即init_task的堆栈指向.data..init_task这个位置。
    而.data..init_task这个段的位置是在vmlinux.lds.S文件中决定的,不同的架构其位置不一样:
      在arm架构中如下所示:

    .data : AT(__data_loc) {  
            _data = .;        /* address in memory */  
            _sdata = .;  
            INIT_TASK_DATA(THREAD_SIZE)    /* .data..init_task段 */  
            NOSAVE_DATA  

 

    宏INIT_TASK_DATA的定义在include/asm-generic/vmlinux.lds.h中,它是与架构无关的:

    #define INIT_TASK_DATA(align)                        \  
        . = ALIGN(align);                        \  
        *(.data..init_task)

    这里的align=THREAD_SIZE,即.data..init_task段是THREAD_SIZE大小对齐的,对,这就是我们想要的结果,完全吻合。
    从上面可以看出.data..init_task实际上是在内核镜像中.data靠前的地方定义的一个大小为THREAD_SIZE的大小数据块。
    THREAD_SIZE的大小随架构的不同其值不同,arm架构一般是8k(也就是2个page),这样一来,一旦内核编译完成,系统中的0号进程也就真的“凭空”创造出来了!

三、内核启动初期sp初始化

    从上面已经了解到内核中静态创建了一个0号进程init_task,其堆栈init_task.stack指向.data..init_task段中一个大小为THREAD_SIZE大小的区域,在这个栈的底端放着全局变量init_thread_info。
    需要指出一点,在内核完成初始化之前,内核会使用这个栈来进行工作---这是通过将系统中的堆栈指针sp指向init_task的堆栈来实现的。
    这个过程的实现具体的架构相关,机器上电后会在bios或者uboot或者其他固件中执行,最终跳转到内核入口执行,这个入口点在做完一些重要的初始化工作后就会跳转到start_kernel入口开始初始化内核。而堆栈指针sp的初始化就是在内核入口点的初期、跳转到start_kernel之前进行。
    arm架构中会先将 init_thread_union+THREAD_START_SP放到__mmap_switched_data标号处后面16字节的位置,然后通过ld指令加载到sp寄存器中。

    __mmap_switched:  
        adr    r3, __mmap_switched_data       /* 将__mmap_switched_data标号对应地址赋给r3 */  
      
        ldmia    r3!, {r4, r5, r6, r7}        /* 把r3指向的内容赋值给r4,r5,r6,r7,同时把r3指向__mmap_switched_data + 4*sizeof(long) */  
        cmp    r4, r5                @ Copy data segment if needed  
        ......  
      
      
    /* 将 init_thread_union+THREAD_START_SP 装入sp寄存器 */  
      
     ARM(    ldmia    r3, {r4, r5, r6, r7, sp})    
        ........  
        b    start_kernel  
    .....................  
    __mmap_switched_data:  
        .long    __data_loc            @ r4  
        .long    _sdata                @ r5  
        .long    __bss_start            @ r6  
        .long    _end                @ r7  
        /* 执行ldmia  r3!, {r4, r5, r6, r7}后r3指向这里 */  
        .long    processor_id            @ r4   
        .long    __machine_arch_type        @ r5  
        .long    __atags_pointer            @ r6  
    #ifdef CONFIG_CPU_CP15  
        .long    cr_alignment            @ r7  
    #else  
        .long    0                @ r7  
    #endif  
         /* 这里存放值 init_thread_union+THREAD_START_SP */  
        .long    init_thread_union + THREAD_START_SP @ sp  

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值