[Linux]学习笔记系列 -- [arm][process]


在这里插入图片描述

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 预取指令

  1. pld 是 ARM 汇编指令,用于预取数据到缓存中。它的作用是将指定地址的数据预取到 CPU 的 L1 缓存中,以提高后续访问的速度。
  2. pldw 是 ARM 汇编指令,用于预取数据到缓存中,并且它会将数据标记为“写入”。这意味着预取的数据可以在后续操作中被修改。
  3. __ALT_SMP_ASM 是一个宏,用于在单处理器和多处理器系统之间切换不同的汇编指令。它的作用是根据系统的配置选择适当的汇编指令。
  4. pld\t%a0 是 ARM 架构的预取指令,其中 pld 表示预取数据(Preload Data)。%a0 是一个占位符,表示将寄存器或内存地址替换为 ptr 的值。
  5. “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();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值