1. 环境
Kernel Version: 2.6.11
Architecture: i386
英文名称 | 中文名称 | 数据结构 |
---|---|---|
Process Descriptor | 进程描述符 | struct task |
- | 线程描述符 | struct thread_info |
- | 进程内核态堆栈 | unsigned long stack |
- | - | union thread_union |
函数名 | 作用 |
---|---|
CURRENT | 根据ESP得到struct task地址 |
current_thread_info() | 根据ESP得到struct thread_info地址 |
为了文章的名词的可读和唯一性,使用数据结构作为名词。
2. 概要
在ULK3中,英文原文:
Processes are dynamic entities whose lifetimes range from a few milliseconds to months. . Thus, kernel must be able to handle many processes at the same time, and process descriptors are stored in dynamic memory rather than rather than in the memory area permanently assigned to the kernel. For each process, Linux packs two different data structures in a single per-process memory area: a small data structure linked to the process descriptor, namely the thread_info structure, and the Kernel Mode process stack.
因为进程是动态分配的,所以内核(kernel)必须能够同时处理全部进程;因为进程是动态分配,则进程描述符(process descriptor)也应该动态存储。
对于每个进程,内核会为每个进程把两个数据结构封装在一起: thread_info和stack。
- thread_info是和process descriptor相关联。
- stack是对应进程内核态的堆栈。
主要讲:
- thread_info和stack
- 第0个进程的堆栈
弄清楚两件事:
4. 进程相关的堆栈知识
5. 内核idle进程的堆栈
6. 内核idle进程的创建
3. union thread_union
3.1 定义
在include/linux/sched.h中,thread_union定义如下:
806 union thread_union {
807 struct thread_info thread_info;
808 unsigned long stack[THREAD_SIZE/sizeof(long)];
809 };
用联合体thread_union,大小是TREAD_SIZE个字节;(0-51)个字节用作thread_info和 (52-8191)个字节用于stack,在使用stack时候,应该不能踩踏到thread_info的成员值。
thread_union中的struct thread_info定义在文件include/asm-i386/thread_info中:
28 struct thread_info {
29 struct task_struct *task; /* main task structure */
30 struct exec_domain *exec_domain; /* execution domain */
31 unsigned long flags; /* low level flags */
32 unsigned long status; /* thread-synchronous flags */
33 __u32 cpu; /* current CPU */
34 __s32 preempt_count; /* 0 => preemptable, <0 => BUG */
35
36
37 mm_segment_t addr_limit; /* thread address space:
38 0-0xBFFFFFFF for user-thead
39 0-0xFFFFFFFF for kernel-thread
40 */
41 struct restart_block restart_block;
42
43 unsigned long previous_esp; /* ESP of the previous stack in case
44 of nested (IRQ) stacks
45 */
46 __u8 supervisor_stack[0];
47 };
3.2 示意图
union thread_union和struct task之间的关系示意图如下:
Figure 1 Storing the thread_info struct and the process kernel stack in two page frames
说明:
1)esp指向内核堆栈的栈顶,堆栈生长的方向是从高地址到低地址。
2)进程间切换,esp赋值,后续研究。
3)研究某进程的esp值得到 struct thread_info地址和从某进程esp得到struct task地址?
3.3 current_thread_info
current_thread_info()是根据ESP得到struct thread_info地址。
从图1中,可以看出,只要把esp的低12位和0与上,就能得到structure thread_info地址,是由函数current_thread_info()函数完成的,等价于汇编语言:
movl $0xffffe000,%ecx /* or 0xfffff000 for 4KB stacks */
andl %esp,%ecx
movl %ecx,p
2.4 CURRENT
CURRENT是根据ESP得到struct task地址。
先看下thread_info结构体,在include/asm-i386/thread_info.h中
28 struct thread_info {
29 struct task_struct *task; /* main task structure */
30 struct exec_domain *exec_domain; /* execution domain */
31 unsigned long flags; /* low level flags */
32 unsigned long status; /* thread-synchronous flags */
33 __u32 cpu; /* current CPU */
34 __s32 preempt_count; /* 0 => preemptable, <0 => BUG */
35
36
37 mm_segment_t addr_limit; /* thread address space:
38 0-0xBFFFFFFF for user-thead
39 0-0xFFFFFFFF for kernel-thread
40 */
41 struct restart_block restart_block;
42
43 unsigned long previous_esp; /* ESP of the previous stack in case
44 of nested (IRQ) stacks
45 */
46 __u8 supervisor_stack[0];
47 };
可以看出和Process Descriptor相关联的task,在thread_info结构中的偏移量是0;通过current_thread_info()得到thread_info地址,然后在这个地址存储的值就是tasck成员的值,这样就能得到struct task地址。
内核使用CURRENT宏,等价于下面汇编:
movl $0xffffe000,%ecx /* or 0xfffff000 for 4KB stacks */
andl %esp,%ecx
movl (%ecx),p
说明:ecx是thread_info地址,ecx指向地址的存储的值也就是(%ecx)就是struct task地址。
3.内核0号进程
0号进程是静态分配,主要涉及到两方面:
1) 进程描述符分配
2) 内核堆栈分配
3) 进程描述符和内核堆栈关联
4) 把内核空间堆栈描述加载到内存
5) 把ESP指向对应的栈空间
6) 跳转到进程的代码段
3.1 进程描述符分配
第0号进程描述符变量init_task,在文件arch/i386/kernel/init_task.c中:
37 struct task_struct init_task = INIT_TASK(init_task);
38
39 EXPORT_SYMBOL(init_task);
宏INIT_TASK的定义在文件include/linux/init_task.h中:
69 #define INIT_TASK(tsk) \
70 { \
71 .state = 0, \
72 .thread_info = &init_thread_info, \
73 .usage = ATOMIC_INIT(2), \
74 .flags = 0, \
75 .lock_depth = -1, \
76 .prio = MAX_PRIO-20, \
77 .static_prio = MAX_PRIO-20, \
78 .policy = SCHED_NORMAL, \
79 .cpus_allowed = CPU_MASK_ALL, \
80 .mm = NULL, \
.........................................................................
101 .comm = "swapper", \
从comm字段看出,进程0叫swapper。
从thread_info字段看出,进程的内核空间堆栈指向init_thread_info。
3.2 内核空间堆栈创建
内核空间堆栈变量名叫init_thread_info,在文件include/asm-i386/thread_info.h定义。
83 #define init_thread_info (init_thread_union.thread_info)
变量init_thread_info是init_thread_union联合体成员threa_info。
init_thread_union变量的section “.data.init_task”。
28 union thread_union init_thread_union
29 __attribute__((__section__(".data.init_task"))) =
30 { INIT_THREAD_INFO(init_task) };
objdump -h vmlinux
可以看出这个段大小是0x2000,是8K,也就是union thread_union大小是8K。
在System.map中可以看出:
这个.data.init_task在arch/i386/kernel/vmlinux.lds.S中
53 _edata = .; /* End of data section */
54
55 . = ALIGN(THREAD_SIZE); /* init_task */
56 .data.init_task : { *(.data.init_task) }
57
init_thread_union变量被编译进.data.init_task的section,大小是THTEAD_SIZE=8192=8K。
3.3 关联
在thread_info中
70 #define INIT_THREAD_INFO(tsk) \
71 { \
72 .task = &tsk, \
73 .exec_domain = &default_exec_domain, \
74 .flags = 0, \
75 .cpu = 0, \
76 .preempt_count = 1, \
77 .addr_limit = KERNEL_DS, \
78 .restart_block = { \
79 .fn = do_no_restart_syscall, \
80 }, \
81 }
第72行可以,看出struct thread_info中的task指向进程描述符。
宏INIT_TASK的定义在文件include/linux/init_task.h中:
69 #define INIT_TASK(tsk) \
70 { \
71 .state = 0, \
72 .thread_info = &init_thread_info, \
101 .comm = "swapper", \
可以看出,进程描述符struct task的thread_info字段指向init_thread_info字段。
实现了进程堆栈描述符和进程描述符的关联。
3.4 内核堆栈加载与跳转
.data.init_task的section已经被编译进vmlinux中.
在arch/i386/kernel/head.S
425 ENTRY(stack_start)
426 .long init_thread_union+THREAD_SIZE
427 .long __BOOT_DS
193 /* Set up the stack pointer */
194 lss stack_start,%esp
326 #endif /* CONFIG_SMP */
327 call start_kernel
stack_start是init_thread_union+THREAD_SIZE,然后赋值给esp,如下面示意图,栈是空的,ESP处于栈顶位置。
执行进程代码段
在arch/i386/kernel/head.S中
326 #endif /* CONFIG_SMP */
327 call start_kernel
0号进程,就是从head.S的汇编代码到start_kernel。