转载:http://blog.chinaunix.net/uid-22548820-id-2125152.html
之所以将thread_info结构称之为小型的进程描述符,是因为在这个结构中并没有直接包含与进程相关的字段,而是通过task字段指向具体某个进程描述符。通常这块内存区域的大小是8KB,也就是两个页的大小(有时候也使用一个页来存储,即4KB)。一个进程的内核栈和thread_info结构之间的逻辑关系如下图所示:
从上图可知,内核栈是从该内存区域的顶层向下(从高地址到低地址)增长的,而thread_info结构则是从该区域的开始处向上(从低地址到高地址)增长。内核栈的栈顶地址存储在esp寄存器中。所以,当进程从用户态切换到内核态后,esp寄存器指向这个区域的末端。
因为一个页大小是4K,一个页的起始地址都是4K的整数倍,即后12位都为0,取得esp内核栈栈顶的地址,将其后12位取0,就可以得到上述内存区域的起始地址0x015fa000,该地址即是thread_info的地址,通过thread_info又可以得到task_struct的地址进而得到进程pid。
从代码的角度来看,内核栈和thread_info结构是被定义在一个联合体当中的:
其中,THREAD_SIZE的值取8192时,stack数组的大小为2048;THREAD_SIZE的值取4096时,stack数组的大小为1024。现在我们应该思考,为何要将内核栈和thread_info(其实也就相当于task_struct,只不过使用thread_info结构更节省空间)紧密的放在一起?最主要的原因就是内核可以很容易的通过esp寄存器的值获得当前正在运行进程的thread_info结构的地址,进而获得当前进程描述符的地址。
这条内联汇编语句会屏蔽掉esp寄存器中的内核栈顶地址的低13位(或12位,当THREAD_SIZE为4096时)。此时ti所指的地址就是这片内存区域的起始地址,也就刚好是thread_info结构的地址。但是,thread_info结构的地址并不会对我们直接有用。我们通常可以轻松的通过current宏获得当前进程的task_struct结构,这个宏是如何实现的?
通过上述源码可以发现,current宏返回的是thread_info结构task字段。而task正好指向与thread_info结构关联的那个进程描述符。得到current后,我们就可以获得当前正在运行进程的描述符中任何一个字段了,比如我们通常所做的:current->pid。