返回

Linux内核源码学习:PER_CPU变量,swapgs及栈切换(一)

后端

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内核的开发工具和调试技巧。
  • 最后要不断地实践和总结经验。