内核版本:2.6.34
1. task_struct
task_struct 结构体即所说的进程描述符,包含了内核管理一个进程所需的全部信息。
位置:include/linux/sched.h
struct task_struct {
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
void *stack;
atomic_t usage;
unsigned int flags; /* per process flags, defined below */
unsigned int ptrace;
int lock_depth; /* BKL lock depth */
......
}
其中,*stack 是指向内核栈的指针。
2. stack
stack 即所说的进程内核栈,是一段大小为2个page(32位:PAGE_SIZE=4KB;64位:PAGE_SIZE=8KB)的内存空间。
位置:include/linux/sched.h
union thread_union {
struct thread_info thread_info;
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
从联合体thread_union的定义可以看出,thread_info和内核栈是共享内存空间的。换句话说,内核栈的开头就是一个thread_info结构。
3. thread_info
thread_info 即内核栈开头(注意这里“开头”指的是最低地址)的一段数据结构。
位置:arch/x86/include/asm/thread_info.h
struct thread_info {
struct task_struct *task; /* main task structure */
struct exec_domain *exec_domain; /* execution domain */
__u32 flags; /* low level flags */
__u32 status; /* thread synchronous flags */
__u32 cpu; /* current CPU */
int preempt_count; /* 0 => preemptable,
......
};
其中,*task 又指向了进程描述符stack_struct。
三者的关系见下图,可以大概猜测一下进程在创建的时候的一个流程:
-
首先给进程初始化一个task_struct(后面对进程的管理都仰仗这位老兄了);
-
分配一个大小为2*PAGE_SIZE的内存空间,并把首地址赋给task_struct->stack;
-
将stack头部的一段空间当作thread_info结构来使用,并把thread_info->task指向task_struct;
具体创建流程可以参考:浅谈Linux内核创建新进程的全过程
stack内核栈最高的8字节为预留,紧跟着的就是pt_regs,当进程从用户态陷入内核态时候,用户态的上下文信息保存在pt_regs数据结构。
4. 通过 task_struct 找内核栈
从上图可知,可以通过task_struct->stack找到内核栈,内核中已经定义好了接口。
位置:include/linux/sched.h
#define task_stack_page(task) ((task)->stack)
5. 通过 task_struct 找 thread_info
先找到内核栈,然后强转成thread_info就可以了。
位置:include/linux/sched.h
#define task_thread_info(task) ((struct thread_info *)(task)->stack)
6. 通过 task_struct 找 pt_regs
以X86_32为例,先找到内核栈,然后指针位移到最高地址,减掉8个字节的预留字节,即指向了pt_regs的末尾地址,强转成pt_regs结构,然后自减1,即指向了pt_regs的首地址。
而X86_64,使用到了task_struct中的thread_struct成员,后面再补充。
位置:arch/x86/include/asm/processor.h
// X86_32
#define task_pt_regs(task) \
({ \
struct pt_regs *__regs__; \
__regs__ = (struct pt_regs *)(KSTK_TOP(task_stack_page(task))-8); \
__regs__ - 1; \
})
// X86_64
#define task_pt_regs(tsk) ((struct pt_regs *)(tsk)->thread.sp0 - 1)
7. 通过内核栈找 task_struct
内核是通过current宏来获取当前进程的task_struct的。可以看出current先通过current_thread_info()获取到进程的thread_info,然后通过其task指针找到task_struct。
那么,current_thread_info()又是怎么找到当前进程的thread_info呢?这就与硬件体系结构相关了,后面再补充。
位置:include/asm-generic/current.h
#define get_current() (current_thread_info()->task)
#define current get_current()
参考
进程管理(五)–linux进程内核栈
linux 进程及调度基础知识
《Linux内核设计与实现(第三版)》