/* * 2011年4月8日22:49:50 * 作者:张超 * email:uestczhangchao@gmail.com * Linux2.6.32.25中如何获得当前进程的指针? */ //Thread_info.h //申明per_cpu_kernel_stack变量,它在其它位置定义,见下文内容. 1: DECLARE_PER_CPU(unsigned long, kernel_stack); 2: 3: static inline struct thread_info *current_thread_info(void) 4: 5: { 6: 7: struct thread_info *ti;//存放thread_info的指针 8: 9: ti = (void *)(percpu_read_stable(kernel_stack) + 10: 11: KERNEL_STACK_OFFSET - THREAD_SIZE); 12: 13: return ti; 14: 15: } |
//取得相应当前cpu上thread_info所在的地址,返回值是整数
//Percpu.h 1: #define percpu_read_stable(var) percpu_from_op("mov", per_cpu__##var, \ 2: 3: "p" (&per_cpu__##var)) |
//下层实际的操作
#define percpu_from_op(op, var, constraint) \ ({ \ typeof(var) ret__; \ switch (sizeof(var)) { \ case 1: \ asm(op "b "__percpu_arg(1)",%0" \ : "=q" (ret__) \ : constraint); \ break; \ case 2: \ asm(op "w "__percpu_arg(1)",%0" \ : "=r" (ret__) \ : constraint); \ break; \ case 4: \ asm(op "l "__percpu_arg(1)",%0" \ : "=r" (ret__) \ : constraint); \ break; \ case 8: \ asm(op "q "__percpu_arg(1)",%0" \ : "=r" (ret__) \ : constraint); \ break; \ default: __bad_percpu_size(); \ } \ ret__; \ }) |
per_cpu__kernel_stack是在下面的代码中定义的:
DEFINE_PER_CPU(unsigned long, kernel_stack) = (unsigned long)&init_thread_union - KERNEL_STACK_OFFSET + THREAD_SIZE; extern union thread_union init_thread_union; union thread_union { struct thread_info thread_info; unsigned long stack[THREAD_SIZE /sizeof(long)]; }; thread_union是一个联合体,里面定义了thread_info结构和堆栈结构,THREAD_SIZE在32位平台上一般定义为4K,所以stack的大小其实就是4KB,这就是初始任务在核心里所拥有的所有空间,除去thread_info和KERNEL_STACK_OFFSET占用的空间后,就是任务在核心里实际拥有堆栈的大小。KERNEL_STACK_OFFSET定义为5*8,由于是unsigned long,所以堆栈底部以上还有5*8*4B=200B的空间用来存放程序运行时相关的环境参数。 //Init_task.c 23 /* * Initial thread structure. * * We need to make sure that this is THREAD_SIZE aligned due to the * way process stacks are handled. This is done by having a special * "init_task" linker map entry.. */ //Init_task.c struct task_struct init_task = INIT_TASK(init_task); union thread_union init_thread_union __init_task_data = { INIT_THREAD_INFO(init_task) }; //Sched.h thread_union union thread_union { struct thread_info thread_info; unsigned long stack[THREAD_SIZE/sizeof(long)]; }; |
/* * INIT_TASK is used to set up the first task table, touch at * your own risk!. Base=0, limit=0x1fffff (=2MB) */ #define INIT_TASK(tsk) \ { \ .state = 0, \ .stack = &init_thread_info, \ .usage = ATOMIC_INIT(2), \ .flags = PF_KTHREAD, \ .lock_depth = -1, \ .prio = MAX_PRIO-20, \ .static_prio = MAX_PRIO-20, \ .normal_prio = MAX_PRIO-20, \ .policy = SCHED_NORMAL, \ .cpus_allowed = CPU_MASK_ALL, \ .mm = NULL, \ .active_mm = &init_mm, \ .se = { \ .group_node = LIST_HEAD_INIT(tsk.se.group_node), \ }, \ .rt = { \ .run_list = LIST_HEAD_INIT(tsk.rt.run_list), \ .time_slice = HZ, \ .nr_cpus_allowed = NR_CPUS, \ }, \ .tasks = LIST_HEAD_INIT(tsk.tasks), \ .pushable_tasks = PLIST_NODE_INIT(tsk.pushable_tasks, MAX_PRIO), \ .ptraced = LIST_HEAD_INIT(tsk.ptraced), \ .ptrace_entry = LIST_HEAD_INIT(tsk.ptrace_entry), \ .real_parent = &tsk, \ .parent = &tsk, \ .children = LIST_HEAD_INIT(tsk.children), \ .sibling = LIST_HEAD_INIT(tsk.sibling), \ .group_leader = &tsk, \ .real_cred = &init_cred, \ .cred = &init_cred, \ .cred_guard_mutex = \ __MUTEX_INITIALIZER(tsk.cred_guard_mutex), \ .comm = "swapper", \ .thread = INIT_THREAD, \ .fs = &init_fs, \ .files = &init_files, \ .signal = &init_signals, \ .sighand = &init_sighand, \ .nsproxy = &init_nsproxy, \ .pending = { \ .list = LIST_HEAD_INIT(tsk.pending.list), \ .signal = {{0}}}, \ .blocked = {{0}}, \ .alloc_lock = __SPIN_LOCK_UNLOCKED(tsk.alloc_lock), \ .journal_info = NULL, \ .cpu_timers = INIT_CPU_TIMERS(tsk.cpu_timers), \ .fs_excl = ATOMIC_INIT(0), \ .pi_lock = __SPIN_LOCK_UNLOCKED(tsk.pi_lock), \ .timer_slack_ns = 50000, /* 50 usec default slack */ \ .pids = { \ [PIDTYPE_PID] = INIT_PID_LINK(PIDTYPE_PID), \ [PIDTYPE_PGID] = INIT_PID_LINK(PIDTYPE_PGID), \ [PIDTYPE_SID] = INIT_PID_LINK(PIDTYPE_SID), \ }, \ .dirties = INIT_PROP_LOCAL_SINGLE(dirties), \ INIT_IDS \ INIT_PERF_EVENTS(tsk) \ INIT_TRACE_IRQFLAGS \ INIT_LOCKDEP \ INIT_FTRACE_GRAPH \ INIT_TRACE_RECURSION \ INIT_TASK_RCU_PREEMPT(tsk) \ } |
初始化thread_info,传入参数是任务结构体
#define INIT_THREAD_INFO(tsk) \
{ \
.task = &tsk, \
.exec_domain = &default_exec_domain, \
.flags = 0, \
.cpu = 0, \
.preempt_count = INIT_PREEMPT_COUNT, \
.addr_limit = KERNEL_DS, \
.restart_block = { \
.fn = do_no_restart_syscall, \
}, \
}
|
//宏定义展开过后的结果 1: struct thread_info * ti ; 2: 3: ti = ( void * ) ( ( { typeof ( per_cpu__kernel_stack ) ret__ ; 4: 5: switch ( sizeof ( per_cpu__kernel_stack ) ) { 6: 7: case 1 : __asm ( "movb %%fs:%P1,%0" : "=q" ( ret__ ) : "p" ( & per_cpu__kernel_stack ) ) ; break ; 8: 9: case 2 : __asm ( "movw %%fs:%P1,%0" : "=r" ( ret__ ) : "p" ( & per_cpu__kernel_stack ) ) ; break ; 10: 11: case 4 : __asm ( "movl %%fs:%P1,%0" : "=r" ( ret__ ) : "p" ( & per_cpu__kernel_stack ) ) ; break ; 12: 13: case 8 : __asm ( "movq %%fs:%P1,%0" : "=r" ( ret__ ) : "p" ( & per_cpu__kernel_stack ) ) ; break ; 14: 15: default : __bad_percpu_size ( ) ; } ret__ ; } ) + KERNEL_STACK_OFFSET - THREAD_SIZE ) ; |
总结:该版本内核和以前版本的内核在获得当前任务指针的方法上有很大的不同,以前是通过如下汇编指令实现的:movl $-8192,%eax; andl %esp, %eax来实现的,执行完这两条指令后,eax中存放的就是thread_info的地址。而现在的代码中再也找不到这样的指令了,以前SMP共享数据很多依靠自旋锁来达到目的,自旋锁是一种忙等锁,在一定程度上造成了进行自旋的CPU上CPU利用率的下降,随着内核的不断发展,更多的使用每个CPU数据(per_cpu_data)的概念,在每一个处理器中各自维护一个以前在多个CPU之间进行共享的数据,如当前运行的任务结构体,以前就是在多个CPU之间共享的。