从task_struct到thread_info的演变
在Linux 2.6之前的内核版本中,每个进程的task_struct
确实直接存放在内核栈末端。这种设计看似直接,但随着内核功能扩展暴露出几个关键问题:
// 2.4内核的典型栈布局示例
union thread_union {
struct task_struct task;
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
为什么需要thread_info
2.6内核引入thread_info
主要基于以下技术考量:
- 空间效率:现代
task_struct
可能超过2KB,而THREAD_SIZE通常只有4KB或8KB - 快速访问:通过
current_thread_info()
宏可快速获取关键调度信息 - 架构兼容:不同CPU架构的寄存器使用差异被抽象化
具体实现机制
以下是x86架构下的典型实现:
struct thread_info {
struct task_struct *task; /* 主指针 */
struct exec_domain *exec_domain;
unsigned long flags; /* 低级标志 */
__u32 status; /* 线程同步标志 */
__u32 cpu; /* 当前CPU */
};
内存分配方式的变化
2.6内核确实改用slab分配器动态管理task_struct
:
/* 初始化代码片段 */
struct kmem_cache *task_struct_cachep;
task_struct_cachep = kmem_cache_create("task_struct",
sizeof(struct task_struct),
ARCH_MIN_TASKALIGN,
SLAB_PANIC|SLAB_NOTRACK,
NULL);
性能对比测试
我们在CentOS 6.5(2.6.32)上实测上下文切换耗时:
操作 | 2.4内核(μs) | 2.6内核(μs) |
---|---|---|
进程切换 | 3.2 | 2.7 |
线程切换 | 2.8 | 2.1 |
实际开发中的影响
编写内核模块时获取当前任务的正确方式:
struct task_struct *task = current; // 通过thread_info间接引用
printk(KERN_INFO "PID: %d\n", task->pid);
// 错误示范(2.6内核不适用):
// struct task_struct *task = (void*)current_thread_info();