linux内核中task_struct与thread_info及stack三者的关系
在linux内核中进程以及线程(多线程也是通过一组轻量级进程实现的)都是通过task_struct结构体来描述的,我们称它为进程描述符。而thread_info则是一个与进程描述符相关的小数据结构,它同进程的内核态栈stack存放在一个单独为进程分配的内存区域。由于这个内存区域同时保存了thread_info和stack,所以使用了联合体来定义,相关数据结构如下(基于4.4.87版本内核):
thread_union联合体定义:
union thread_union { struct thread_info thread_info; unsigned long stack[THREAD_SIZE/sizeof(long)]; };
thread_info结构体定义:
struct thread_info { unsigned long flags; /* low level flags */ mm_segment_t addr_limit; /* address limit */ struct task_struct *task; /* main task structure */ int preempt_count; /* 0 => preemptable, <0 => bug */ int cpu; /* cpu */ };
task_struct的结构比较复杂,只列出部分成员变量,完整的可以在下面这个网站直接查看对应版本的内核代码
struct task_struct { volatile long state; void *stack; //... #ifdef CONFIG_SMP int on_cpu; int wake_cpu; #endif int on_rq; //... #ifdef CONFIG_SCHED_INFO struct sched_info sched_info; #endif //... pid_t pid; pid_t tgid; //... };
用一副图来表示:
这样设计的好处就是,得到stack,thread_info或task_struct任意一个数据结构的地址,就可以很快得到另外两个数据的地址。
我们可以通过crash工具在ubuntu系统上做个实验,来窥视一下某个进程的进程描述符
如果通过crash分析内核数据结构,可参考:
这里以进程systemd进程为例,其pid=1
crash> task 1 PID: 1 TASK: ffff88007c898000 CPU: 1 COMMAND: "systemd" struct task_struct { state = 1, stack = 0xffff88007c894000, usage = { counter = 2 }, 。。。
可以看到systemd进程的task_struct结构体指针task=0xffff88007c898000
通过task->stack这个结构体成员即可定位到进程的内核栈地址 stack=0xffff88007c894000
另外从之前的图可以看到,thread_info和stack处于同一地址空间,且thread_info在这段地址空间的最低地址处,而且这个地址空间是以THREAD_SIZE对齐的,所以只要将stack地址的最低N位变为0,即可得到thread_info的地址(2^N=THREAD_SIZE)
例如当THREAD_SZIE=8K时,systemd的thread_info地址就等于0xffff88007c894000&(~(0x1FFF)) = 0xffff88007c894000
crash> * thread_info 0xffff88007c894000 struct thread_info { task = 0xffff88007c898000, flags = 0, status = 0, cpu = 0, addr_limit = { seg = 140737488351232 }, sig_on_uaccess_error = 0, uaccess_err = 0 }
而通过thread_info->task这个成员变量,又能访问到进程的task_struct结构体,这样就形成了task_struct, thread_info,stack三者之间的关系网,知道其中任何一个,都可以快速的访问到另外两个,提高了数据存取的效率。