文章目录

https://github.com/wdfk-prog/linux-study
process
arch/arm/include/asm/current.h
- 返回
__current
的值,__current
是一个全局变量,在arch/arm/kernel/process.c
中定义 __current
是一个指向当前进程的指针,在内核中,每个进程都有一个task_struct
结构体,该结构体包含了该进程的所有信息
//arch/arm/kernel/process.c
asmlinkage struct task_struct *__current;
EXPORT_SYMBOL(__current);
static __always_inline __attribute_const__ struct task_struct *get_current(void)
{
struct task_struct *cur;
#if __has_builtin(__builtin_thread_pointer) && defined(CONFIG_CURRENT_POINTER_IN_TPIDRURO)
#elif defined(CONFIG_CURRENT_POINTER_IN_TPIDRURO) //CPU_32v6K
|| defined(CONFIG_SMP) //SMP
#endif
#elif __LINUX_ARM_ARCH__>= 7 || \
!defined(CONFIG_ARM_HAS_GROUP_RELOCS) || \
(defined(MODULE) && defined(CONFIG_ARM_MODULE_PLTS))
cur = __current;
#else
asm(LOAD_SYM_ARMV6(%0, __current) : "=r"(cur));
#endif
return cur;
}
#define current get_current()
arch/arm/include/asm/processor.h
ARCH_HAS_PREFETCH 预取指令
pld
是 ARM 汇编指令,用于预取数据到缓存中。它的作用是将指定地址的数据预取到 CPU 的 L1 缓存中,以提高后续访问的速度。pldw
是 ARM 汇编指令,用于预取数据到缓存中,并且它会将数据标记为“写入”。这意味着预取的数据可以在后续操作中被修改。__ALT_SMP_ASM
是一个宏,用于在单处理器和多处理器系统之间切换不同的汇编指令。它的作用是根据系统的配置选择适当的汇编指令。pld\t%a0
是 ARM 架构的预取指令,其中pld
表示预取数据(Preload Data)。%a0
是一个占位符,表示将寄存器或内存地址替换为ptr
的值。- “p” (ptr) 告诉编译器将 ptr 作为一个指针操作数传递给汇编代码。编译器会根据需要将其转换为适当的格式。
/*
* Prefetching support - only ARMv5.
*/
#if __LINUX_ARM_ARCH__ >= 5
#define ARCH_HAS_PREFETCH
static inline void prefetch(const void *ptr)
{
__asm__ __volatile__(
"pld\t%a0"
:: "p" (ptr));
}
#if __LINUX_ARM_ARCH__ >= 7 && defined(CONFIG_SMP)
#define ARCH_HAS_PREFETCHW
static inline void prefetchw(const void *ptr)
{
__asm__ __volatile__(
".arch_extension mp\n"
__ALT_SMP_ASM(
"pldw\t%a0",
"pld\t%a0"
)
:: "p" (ptr));
}
#endif
#endif
- 为什么只有SMP才能支持这个函数
prefetchw
- 因为在多处理器系统中,缓存一致性是一个重要问题。pldw 指令不仅预取数据,还可以帮助处理器在写入数据时更高效地处理缓存一致性问题。
如果系统是单处理器(非 SMP),则没有必要使用 pldw 指令,因为缓存一致性问题在单核环境中并不存在。因此,在非 SMP 环境中,prefetchw 函数会降级为普通的 pld 指令(只读预取)。
task_pt_regs 获取指定任务(task_struct)的寄存器状态(pt_regs)
- 在 Linux 内核中,pt_regs 是一个结构体,保存了进程或线程的寄存器上下文信息,
- 例如程序计数器(PC)、堆栈指针(SP)等。这些信息通常用于调试、上下文切换或异常处理。
/* THREAD_START_SP:
表示线程堆栈的起始偏移量。它是一个常量,用于定位堆栈顶部。
task_stack_page(p):
获取获取任务 p 的堆栈起始地址。每个任务都有一个独立的内核堆栈,用于保存任务的运行时信息。
- 1:
从堆栈顶部向下偏移一个 pt_regs 结构的大小,得到寄存器状态的地址
*/
#define task_pt_regs(p) \
((struct pt_regs *)(THREAD_START_SP + task_stack_page(p)) - 1)
start_thread: 设置CPU寄存器以启动新线程
此宏的核心职责是接收新程序的入口点地址(pc
)和新程序的栈顶地址(sp
),并将它们以及其他一些必要的初始值,填入保存在内核中的进程上下文(struct pt_regs
)中。当execve
系统调用结束,内核执行返回用户空间的指令时,它会从这个pt_regs
结构中恢复所有寄存器。由于我们已经修改了pt_regs
,恢复后的CPU状态就完全是新程序的初始状态了。
/*
* start_thread: 一个用于启动新线程(在这里是新程序)执行的宏.
* 它通过直接修改内核栈上保存的寄存器上下文(pt_regs)来实现.
*
* @regs: 指向 pt_regs 结构体的指针, 它保存了从用户态进入内核态时的寄存器快照.
* @pc: 新程序的入口点地址 (Program Counter).
* @sp: 新程序的栈顶地址 (Stack Pointer).
*/
#define start_thread(regs,pc,sp) \
({ \
/*
* 定义三个临时变量, 用于在FDPIC模式下临时保存某些寄存器的值.
*/
unsigned long r7, r8, r9; \
\
/*
* IS_ENABLED(CONFIG_BINFMT_ELF_FDPIC) 是一个编译时检查.
* 如果内核配置了FDPIC支持...
*/
if (IS_ENABLED(CONFIG_BINFMT_ELF_FDPIC)) { \
/*
* ...那么就先保存 r7, r8, r9 寄存器的原始值.
* 这是因为这些寄存器可能被某些ABI约定用于特殊目的,
* 而下面的 memset 会清除它们, 所以需要先保存后恢复.
*/
r7 = regs->ARM_r7; \
r8 = regs->ARM_r8; \
r9 = regs->ARM_r9; \
} \
/*
* 将 pt_regs 结构体中的通用寄存器区域(uregs)清零.
* 这是一个安全措施, 确保新程序启动时不会继承任何旧的、可能包含敏感信息的寄存器值.
*/
memset(regs->uregs, 0, sizeof(regs->uregs)); \
/*
* 再次进行编译时和运行时检查.
*/
if (IS_ENABLED(CONFIG_BINFMT_ELF_FDPIC) && \
(current->personality & FDPIC_FUNCPTRS)) { \
/*
* 如果是FDPIC模式, 并且使用了函数指针描述符(一种FDPIC的实现方式),
* 恢复之前保存的 r7, r8, r9.
*/
regs->ARM_r7 = r7; \
regs->ARM_r8 = r8; \
regs->ARM_r9 = r9; \
/*
* FDPIC核心步骤之一: 将 r10 寄存器设置为指向新进程的私有数据段的起始地址.
* 这是实现数据隔离的关键.
*/
regs->ARM_r10 = current->mm->start_data; \
} else if (!IS_ENABLED(CONFIG_MMU)) \
/*
* 在非FDPIC的no-MMU系统上(虽然很少见), 也可能需要设置r10作为静态基地址.
*/
regs->ARM_r10 = current->mm->start_data; \
/*
* 根据进程的"个性化"标志, 设置正确的用户模式.
* ADDR_LIMIT_32BIT 通常表示现代的32位用户模式.
*/
if (current->personality & ADDR_LIMIT_32BIT) \
regs->ARM_cpsr = USR_MODE; \
else \
/* 否则, 使用旧的26位用户模式 (在现代ARM上已不使用). */
regs->ARM_cpsr = USR26_MODE; \
/*
* 检查CPU的硬件能力(elf_hwcap)是否支持Thumb指令集,
* 并且入口点地址(pc)的最低位是否为1.
*/
if (elf_hwcap & HWCAP_THUMB && pc & 1) \
/* 如果是, 就在CPSR(当前程序状态寄存器)中设置T位, 使CPU进入Thumb模式. */
regs->ARM_cpsr |= PSR_T_BIT; \
/*
* 设置CPSR中的字节序状态位, 确保与系统配置一致.
*/
regs->ARM_cpsr |= PSR_ENDSTATE; \
/*
* 设置程序计数器(PC). 使用 & ~1 将地址的最低位清零, 得到真正的指令地址.
*/
regs->ARM_pc = pc & ~1; /* pc */ \
/*
* 设置栈指针(SP).
*/
regs->ARM_sp = sp; /* sp */ \
})
arch/arm/include/asm/glue-proc.h
arm 的不同 CPU 处理器架构的宏定义
CONFIG_CPU_V7M
是一个配置选项,用于指定当前编译的内核是针对 ARMv7-M 架构的。CPU_NAME
是一个宏,用于指定当前 CPU 的名称。MULTI_CPU
是一个宏,用于指示是否支持多 CPU 架构。__glue
是一个宏,用于将两个宏连接起来,形成一个新的宏。
#ifdef CONFIG_CPU_V7M
# ifdef CPU_NAME
# undef MULTI_CPU
# define MULTI_CPU
# else
# define CPU_NAME cpu_v7m
# endif
#endif
#ifndef MULTI_CPU
#define cpu_proc_init __glue(CPU_NAME,_proc_init)
#define cpu_proc_fin __glue(CPU_NAME,_proc_fin)
#define cpu_reset __glue(CPU_NAME,_reset)
#define cpu_do_idle __glue(CPU_NAME,_do_idle)
#define cpu_dcache_clean_area __glue(CPU_NAME,_dcache_clean_area)
#define cpu_do_switch_mm __glue(CPU_NAME,_switch_mm)
#define cpu_set_pte_ext __glue(CPU_NAME,_set_pte_ext)
#define cpu_suspend_size __glue(CPU_NAME,_suspend_size)
#define cpu_do_suspend __glue(CPU_NAME,_do_suspend)
#define cpu_do_resume __glue(CPU_NAME,_do_resume)
#endif
arch/arm/kernel/process.c
show_regs 显示异常的堆栈信息
void __show_regs(struct pt_regs *regs)
{
unsigned long flags;
char buf[64];
#ifndef CONFIG_CPU_V7M
unsigned int domain;
#ifdef CONFIG_CPU_SW_DOMAIN_PAN
/*
* Get the domain register for the parent context. In user
* mode, we don't save the DACR, so lets use what it should
* be. For other modes, we place it after the pt_regs struct.
*/
if (user_mode(regs)) {
domain = DACR_UACCESS_ENABLE;
} else {
domain = to_svc_pt_regs(regs)->dacr;
}
#else
domain = get_domain();
#endif
#endif
show_regs_print_info(KERN_DEFAULT);
printk("PC is at %pS\n", (void *)instruction_pointer(regs));
printk("LR is at %pS\n", (void *)regs->ARM_lr);
printk("pc : [<%08lx>] lr : [<%08lx>] psr: %08lx\n",
regs->ARM_pc, regs->ARM_lr, regs->ARM_cpsr);
printk("sp : %08lx ip : %08lx fp : %08lx\n",
regs->ARM_sp, regs->ARM_ip, regs->ARM_fp);
printk("r10: %08lx r9 : %08lx r8 : %08lx\n",
regs->ARM_r10, regs->ARM_r9,
regs->ARM_r8);
printk("r7 : %08lx r6 : %08lx r5 : %08lx r4 : %08lx\n",
regs->ARM_r7, regs->ARM_r6,
regs->ARM_r5, regs->ARM_r4);
printk("r3 : %08lx r2 : %08lx r1 : %08lx r0 : %08lx\n",
regs->ARM_r3, regs->ARM_r2,
regs->ARM_r1, regs->ARM_r0);
flags = regs->ARM_cpsr;
buf[0] = flags & PSR_N_BIT ? 'N' : 'n';
buf[1] = flags & PSR_Z_BIT ? 'Z' : 'z';
buf[2] = flags & PSR_C_BIT ? 'C' : 'c';
buf[3] = flags & PSR_V_BIT ? 'V' : 'v';
buf[4] = '\0';
#ifndef CONFIG_CPU_V7M
{
const char *segment;
if ((domain & domain_mask(DOMAIN_USER)) ==
domain_val(DOMAIN_USER, DOMAIN_NOACCESS))
segment = "none";
else
segment = "user";
printk("Flags: %s IRQs o%s FIQs o%s Mode %s ISA %s Segment %s\n",
buf, interrupts_enabled(regs) ? "n" : "ff",
fast_interrupts_enabled(regs) ? "n" : "ff",
processor_modes[processor_mode(regs)],
isa_modes[isa_mode(regs)], segment);
}
#else
printk("xPSR: %08lx\n", regs->ARM_cpsr);
#endif
#ifdef CONFIG_CPU_CP15
{
unsigned int ctrl;
buf[0] = '\0';
#ifdef CONFIG_CPU_CP15_MMU
{
unsigned int transbase;
asm("mrc p15, 0, %0, c2, c0\n\t"
: "=r" (transbase));
snprintf(buf, sizeof(buf), " Table: %08x DAC: %08x",
transbase, domain);
}
#endif
asm("mrc p15, 0, %0, c1, c0\n" : "=r" (ctrl));
printk("Control: %08x%s\n", ctrl, buf);
}
#endif
}
void show_regs(struct pt_regs * regs)
{
__show_regs(regs);
dump_backtrace(regs, NULL, KERN_DEFAULT);
}
copy_thread 初始化新线程的上下文信息
- 对于一个新创建的任务,它的lr被内核精心设置为一个特殊的“蹦床”函数(Trampoline Function),通常是 ret_from_fork 或 ret_from_kthread
- 伪造的栈帧:内核会在新任务的内核栈上,手动构建一个假的 cpu_save 结构。这个结构看起来就好像这个新任务是在执行 __switch_to 时被换出的一样。
设置 lr:在伪造的栈帧中,原来应该存放返回地址 lr 的位置,被内核填入了 ret_from_fork 函数的地址。
设置 sp:新任务的 task_struct 中的 thread.sp(栈顶指针)会指向这个伪造的栈帧的末尾。
设置参数:其他寄存器(如r0)可能会被设置为新线程需要执行的函数的地址或其参数。 - SP寄存器 (Stack Pointer)
作用:SP寄存器的唯一作用就是指向当前程序栈的栈顶。它就像一个浮标,标记着堆栈这片内存区域中,当前“水位”在哪里。 - PC寄存器 (Program Counter)
作用:PC寄存器的唯一作用就是指向CPU下一条将要执行的指令的内存地址。它就像一个书签,告诉CPU接下来该读哪一页、哪一行代码。
int copy_thread(struct task_struct *p, const struct kernel_clone_args *args)
{
/* 从 kernel_clone_args 中提取克隆标志(flags)、堆栈起始地址(stack)和线程本地存储(tls) */
unsigned long clone_flags = args->flags;
unsigned long stack_start = args->stack;
unsigned long tls = args->tls;
struct thread_info *thread = task_thread_info(p);
struct pt_regs *childregs = task_pt_regs(p);
memset(&thread->cpu_context, 0, sizeof(struct cpu_context_save));
#ifdef CONFIG_CPU_USE_DOMAINS
/*
* Copy the initial value of the domain access control register
* from the current thread: thread->addr_limit will have been
* copied from the current thread via setup_thread_stack() in
* kernel/fork.c
*/
thread->cpu_domain = get_domain();
#endif
/* 如果未指定函数指针(args->fn),复制当前线程的寄存器状态到新线程。 */
if (likely(!args->fn)) {
*childregs = *current_pt_regs();
/* 设置返回值寄存器 ARM_r0 为 0,表示新线程的返回值 */
childregs->ARM_r0 = 0;
/* 如果提供了堆栈起始地址,更新堆栈指针(ARM_sp) */
if (stack_start)
childregs->ARM_sp = stack_start;
} else {
memset(childregs, 0, sizeof(struct pt_regs));
/* 设置 r4 和 r5 寄存器为函数参数和函数地址 */
thread->cpu_context.r4 = (unsigned long)args->fn_arg;
thread->cpu_context.r5 = (unsigned long)args->fn;
/* 设置 CPSR(程序状态寄存器)为 SVC_MODE,表示运行在超级用户模式 */
childregs->ARM_cpsr = SVC_MODE;
}
/* 设置程序计数器(pc)为 ret_from_fork,指向线程启动的入口点 */
thread->cpu_context.pc = (unsigned long)ret_from_fork;
/* 设置堆栈指针(sp)为新线程的寄存器状态 */
thread->cpu_context.sp = (unsigned long)childregs;
/* 清除硬件调试断点,确保新线程不会受到父线程的调试影响 */
clear_ptrace_hw_breakpoint(p);
if (clone_flags & CLONE_SETTLS)
thread->tp_value[0] = tls;
thread->tp_value[1] = get_tpuser();
thread_notify(THREAD_NOTIFY_COPY, thread);
return 0;
}
arch_cpu_idle_enter arch_cpu_idle_exit cpu空闲进入与退出
// include/linux/leds.h
static inline void ledtrig_cpu(enum cpu_led_event evt)
{
return;
}
void arch_cpu_idle_enter(void)
{
ledtrig_cpu(CPU_LED_IDLE_START);
#ifdef CONFIG_PL310_ERRATA_769419
wmb();
#endif
}
void arch_cpu_idle_exit(void)
{
ledtrig_cpu(CPU_LED_IDLE_END);
}
arch_cpu_idle cpu空闲处理函数
/*
* This is our default idle handler.
*/
void (*arm_pm_idle)(void);
/*
* Called from the core idle loop.
*/
void arch_cpu_idle(void)
{
if (arm_pm_idle)
arm_pm_idle();
else
cpu_do_idle();
}