进程描述符: struct_task,定义于<linux/sched.h>,进程的task_struct存放在进程内核栈的尾端.
把task_struct结构与内核栈放在一起的好处:内核中大部分处理进程的代码都是通过进程描述符进行的。 因而current宏查找到当前进程的描述符的速度就尤为重要。对于寄存器不富裕的体系结构来说,通过预先分配和重复使用task_struct可以避免动态分配和释放所带来的资源消耗,UNIX的一个特点就是创建进程迅速。
在Intel系统中,栈起始于末端(栈底),并朝栈底的方向增长。从用户态刚切换到内核态以后,进程的内核栈总是空的,因此,esp寄存器直接指向这个内存区的顶端。从用户态切换到内核态后,esp寄存器包含的地址为0x018fc00。进程描述符存放在从0x015fa00开始的地址。只要把数据写进栈中,esp的值就递减。
在/include/linux/sched.h中定义了如下一个联合结构:
union task_union {
struct task_struct task;
unsigned long stack[2408];
};
从这个结构可以看出,内核栈占8kb的内存区。实际上,进程的task_struct结构所占的内存是由内核动态分配的,更确切地说,内核根本不给task_struct分配内存,而仅仅给内核栈分配8K的内存,并把其中的一部分给task_struct使用。
task_struct结构大约占1K字节左右,其具体数字与内核版本有关,因为不同的版本其域稍有不同。因此,内核栈的大小不能超过7K,否则,内核栈会覆盖
task_struct结构,从而导致内核崩溃。
kernel利用current里寻找task_struct的位置,假设栈的大小为8k(13个二进制位),我们可以将进程栈的地址的后13位屏蔽掉,这样得到的刚好就是进程栈的起
始地址,而thread_info刚好就是位于进程栈的底部,所以进程栈的起始地址就是struct thread_info的地址,得到了thread_info的地址,我们就很容易找到
task_struct的地址了。
汇编实现过程为
movl %-9162 ,%eax
andl %esp ,%eax
寄存器esp存放进程栈的当前地址,eax最后存放的就是进程栈的起始地址。current使用current_thread_info来实现这个过程。
linux-3.14.12 内核分析:
#define get_current() (current_thread_info()->task)
#define current get_current()
/* how to get the thread information struct from c */
static inline struct thread_info *current_thread_info(void)
{
return (struct thread_info *)
(current_stack_pointer & ~(THREAD_SIZE - 1));
}
#define current_stack_pointer ({ \
unsigned long sp; \
asm("mov %%esp,%0" : "=g" (sp)); \
sp; \
})
arch/x86/include/asm/page_32_types.h:
#define THREAD_SIZE_ORDER 1
#define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER)
arch/x86/include/asm/page_types.h:
#define PAGE_SHIFT 12
#define PAGE_SIZE (_AC(1,UL) << PAGE_SHIFT)
include/linux/sched.h:
union thread_union {
struct thread_info thread_info;
unsigned long stack[THREAD_SIZE/sizeof(long)];
};