基于线程的用户栈与内核栈:
内核在创建每个线程时,都会创建两个栈,一个用户栈,存在于用户空间;一个内核栈,存在于内核空间。
当线程在用户空间运行时,cpu栈指针寄存器内容保存用户栈空间地址,使用用户栈;当线程在内核空间运行时,cpu栈指针寄存器保存内核栈空间地址,使用内核栈。
当线程因为中断或者系统调用而陷入内核态运行时,线程所使用的栈也要从用户栈切换到内核栈。
线程从用户态进入内核态后,需要先把用户态栈空间地址保存在内核栈中,然后设置栈指针寄存器内容为内核栈空间地址,这样就完成了用户栈向内核栈的转换;当线程从内核态恢复到用户态时,在内核态运行结束时,将保存在内核栈中的用户栈空间地址恢复到栈指针寄存器即可。这样就实现了内核栈和用户栈的互转。
线程每次从用户态陷入内核态时,该线程的内核栈都是空的。所以在进程陷入内核态的时候,直接把内核栈的栈顶地址给栈指针寄存器就可以了。
内核栈的实现:
内核栈在kernel-2.4和kernel-2.6里面的实现方式是不一样的。
在kernel-2.4内核里面,内核栈的实现是:
Union task_union {
Struct task_struct task;
Unsigned long stack[INIT_STACK_SIZE/sizeof(long)];
};
其中,INIT_STACK_SIZE的大小只能是8K。
内核为每个进程分配task_struct结构体的时候,实际上分配两个连续的物理页面,结构体底部用作task_struct结构体,结构体顶部用作内核栈。
注意:这个时候task_struct结构是在内核栈里面的,内核栈的实际能用大小大概有7K。
内核栈在kernel-2.6里面的实现是(kernel-2.6.32):
Union thread_union {
Struct thread_info thread_info;
Unsigned long stack[THREAD_SIZE/sizeof(long)];
};
其中THREAD_SIZE的大小可以是4K,也可以是8K,thread_info占52bytes。
当内核栈为8K时,Thread_info在这块内存的起始地址,内核栈从申请内存的最大地址开始。所以此时,kernel-2.6中的current宏是需要更改的。要通过thread_info结构体中的task_struct域来获得于thread_info相关联的task。
struct thread_info {
struct task_struct *task;
struct exec_domain *exec_domain;
__u32 flags;
__u32 status;
__u32 cpu;
… ..
};
注意:此时的task_struct结构体已经不在内核栈空间里面了。