current宏及Linux进程栈的底层实现
1. current宏的实现
#ifndef __ASSEMBLY__struct task_struct;//用于在编译时候声明一个perCPU变量,该变量被放在一个特殊的段中,原型为DECLARE_PER_CPU(type,name),主要作用是为处理器创建一个type类型,名为name的变量。DECLARE_PER_CPU(struct task_struct *, current_task);static __always_inline struct task_struct *get_current(void){return percpu_read_stable(current_task);}#define current get_current()
#endif /* __ASSEMBLY__ */
- 继续跟踪
percpu_read_stable()个宏函数:位\linux-2.6.32.63\arch\x86\include\asm\percpu.h查得定义:
#define percpu_read_stable(var) percpu_from_op("mov", per_cpu__##var, "p" (&per_cpu__##var))
- 继续跟进
percpu_from_op()这个函数:
/*
percpu_from_op宏中根据不同的sizeof(var)选择不同的分支,执行不同的流程,因为这里是x86体系,所以sizeof(current_task)的值为4
在每个分支中使用了一条的內联汇编代码,其中__percpu_arg(1)为%%fs:%P1(X86)或者%%gs:%P1(X86_64),将上述代码整理后,current获取代码如下:
1. x86: asm(movl "%%fs:%P1","%0" : "=r" (pfo_ret__) :"p" (&(var))
2. x86_64: asm(movl "%%gs:%P1","%0" : "=r" (pfo_ret__) :"p" (&(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__; \
})
-
将fs(或者gs)段中P1偏移处的值传送给pfo_ret__变量。
-
继续跟进per_cpu__kernel_stack的定义。(linux-2.6.32.63\arch\x86\kernel\cpu\common.c)
/*
The following four percpu variables are hot. Align current_task to
cacheline size such that all four fall in the same cacheline.
*/
DEFINE_PER_CPU(struct task_struct *, current_task) ____cacheline_aligned = &init_task;
EXPORT_PER_CPU_SYMBOL(current_task);DEFINE_PER_CPU(unsigned long, kernel_stack) = (unsigned long)&init_thread_union - KERNEL_STACK_OFFSET + THREAD_SIZE;
EXPORT_PER_CPU_SYMBOL(kernel_stack);DEFINE_PER_CPU(char *, irq_stack_ptr) = init_per_cpu_var(irq_stack_union.irq_stack) + IRQ_STACK_SIZE - 64;DEFINE_PER_CPU(unsigned int, irq_count) = -1;
- 继续看进程内核栈初始化的关键代码:
DEFINE_PER_CPU(unsigned long, kernel_stack) = (unsigned long)&init_thread_union - KERNEL_STACK_OFFSET + THREAD_SIZE;
//linux-2.6.32.63\arch\x86\kernel\init_task.c
/** Initial task structure.** All other task structs will be allocated on slabs in fork.c*/
struct task_struct init_task = INIT_TASK(init_task);
EXPORT_SYMBOL(init_task);/** 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..*/
union thread_union init_thread_union __init_task_data =
{ INIT_THREAD_INFO(init_task)
};
- \linux-2.6.32.63\include\linux\init_task.h
/** 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) \
}
- 继续跟进和进程信息密切相关的数据结构:
- 进入\linux-2.6.32.63\include\linux\sched.h
/*
THREAD_SIZE在32位平台上一般定义为4K,所以stack的大小其实就是4KB,这就是初始任务在核心里所拥有的所有空间,除去thread_info和KERNEL_STACK_OFFSET占用的空间后,就是任务在核心里实际拥有堆栈的大小。KERNEL_STACK_OFFSET定义为5*8,由于是unsigned long,所以堆栈底部以上还有5*8*4B=200B的空间用来存放程序运行时相关的环境参数
*/
union thread_union
{struct thread_info thread_info;unsigned long stack[THREAD_SIZE/sizeof(long)];
};
- 小结:
- 在linux中,整个内核栈是所有进程公用的,每个进程会像切蛋糕一样从中切去一份指定大小(THREAD_SIZE)的内存区域;
- 每个进程都在当前内核栈中分配一段内存区域( thread_union),这段内核栈内存被分为两个部分:
- 低地址部分保存的: thread_info 。
- 剩余的高地址部分保存当前进程的内核栈stack。
- thread_info当中就保存着当前进程的信息,所以我们可以从本质上理解,current宏其实并不神秘,它就是在做一个内存栈上的取地址操作。
- 拓展链接:
- http://www.pagefault.info/?p=36
- http://www.cnblogs.com/justinzhang/archive/2011/07/18/2109923.html
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
