Linux内核源码学习:PER_CPU变量,swapgs及栈切换(一)
2023-11-07 15:13:26
Linux内核源码学习:PER_CPU变量,swapgs及栈切换(一)
博客前言:Linux是一个具有悠久历史的计算机操作系统,拥有庞大而复杂的代码库。 学习Linux内核源码对于深入理解Linux操作系统的工作原理和实现机制有着重要意义。
1. PER_CPU变量简介
PER_CPU 变量是Linux内核中一种特殊类型的变量,它为每个CPU分配了一个独立的存储空间。这使得每个CPU都可以拥有自己的独立变量,而不会受到其他CPU的影响。PER_CPU变量通常用于存储与特定CPU相关的数据,例如当前CPU的运行状态、中断处理程序等。
2. PER_CPU变量的代码实现
PER_CPU变量的代码实现主要位于Linux内核的 arch/x86/include/asm/percpu.h 文件中。在这个文件中,定义了PER_CPU变量的结构体和相关的宏。
struct percpu_struct {
char *ptr;
unsigned long size;
};
PER_CPU变量的结构体percpu_struct包含了两个成员变量:ptr和size。ptr指向PER_CPU变量的存储空间,size表示PER_CPU变量的大小。
#define DEFINE_PER_CPU(type, name) \
struct percpu_struct name##_percpu __aligned(__alignof__(type))
#define DECLARE_PER_CPU(type, name) \
extern struct percpu_struct name##_percpu __aligned(__alignof__(type))
DEFINE_PER_CPU和DECLARE_PER_CPU宏用于定义和声明PER_CPU变量。这两个宏分别定义了一个PER_CPU变量的结构体和一个extern声明。
#define get_cpu_var(var) \
((typeof(var) *)((percpu_ptr_t)var + __this_cpu_offset(var)))
get_cpu_var宏用于获取PER_CPU变量的值。这个宏首先将PER_CPU变量的指针转换为一个指向其存储空间的指针,然后加上当前CPU的偏移量,最后返回PER_CPU变量的值。
3. PER_CPU变量的使用
PER_CPU变量通常用于存储与特定CPU相关的数据,例如当前CPU的运行状态、中断处理程序等。
static DEFINE_PER_CPU(struct cpu_rss_stat, cpu_rss_stat);
在这个例子中,定义了一个名为cpu_rss_stat的PER_CPU变量,它用于存储当前CPU的RSS统计信息。
void __this_cpu_write(struct cpu_rss_stat *p, unsigned long val)
{
p->rss_huge = val;
}
在这个例子中,使用__this_cpu_write宏将一个值写入cpu_rss_stat PER_CPU变量。__this_cpu_write宏将自动将值写入当前CPU的cpu_rss_stat PER_CPU变量。
4. 栈切换
栈切换是Linux内核中的一种重要操作,它允许内核在不同的任务之间切换执行。栈切换涉及到保存和恢复当前任务的寄存器值和堆栈指针。
static void switch_to(struct task_struct *next_task, struct task_struct *prev_task)
{
struct tss_struct *tss = &per_cpu(init_tss, get_cpu());
tss->esp0 = next_task->stack + THREAD_SIZE;
tss->ss0 = __KERNEL_DS;
tss->esp = next_task->stack;
tss->ss = __KERNEL_DS;
swapgs(next_task->mm->pgd, next_task);
__asm__ __volatile__(
"movl %0, %%esp\n"
"popal\n"
"pop %%gs\n"
"movq %1, %%fs\n"
"pop %%ds\n"
"pop %%es\n"
"pop %%fs\n"
"iretq"
: : "r" (next_task->stack), "r" (next_task->fs)
: "memory");
prev_task->thread.sp0 = tss->esp0;
prev_task->thread.sp = tss->esp;
}
在这个例子中,switch_to函数用于在两个任务之间切换执行。这个函数首先保存当前任务的寄存器值和堆栈指针,然后切换到下一个任务的寄存器值和堆栈指针,最后执行iretq指令,使下一个任务开始执行。
5. 经验和心得
在学习Linux内核源码的过程中,积累了以下经验和心得:
- 首先要对Linux内核的基础知识有充分的了解。
- 其次要熟悉Linux内核的代码结构和组织方式。
- 第三要掌握Linux内核的开发工具和调试技巧。
- 最后要不断地实践和总结经验。