• Linux进程内核栈


    进程创建的时候Linux内核会创建内核栈(arm手册也要求内核态有单独的栈),如应用进程在用户态通过系统调用陷入内核态的时候,上下文信息(如cpu寄存器)需要有个地方保存,如此,从内核态切换回用户态时候,能继续从系统调用之后的代码开始执行,这个保存的地方就是进程的内核栈,本文主要描述arm32下内核栈的生成过程和结构。

    1.内核栈数据结构

    正如进程在用户态执行函数跳转有一个栈,在内核态执行的时候同样有一个内核态的栈,分成两个栈也是处于安全的考虑,如果都使用用户态的栈,那么内核的数据可以被应用态访问不安全。我们不禁要问如下几个问题:

    • 内核栈大小/结构/创建过程
    • 怎么找到内核栈(哪些数据结构和API可以索引到)

    标识进程的核心数据结构task_struct中有一个void *stack成员指向进程内核栈:

    1. struct task_struct {
    2. #ifdef CONFIG_THREAD_INFO_IN_TASK
    3. /*
    4. * For reasons of header soup (see current_thread_info()), this
    5. * must be the first element of task_struct.
    6. */
    7. struct thread_info thread_info;
    8. #endif
    9. void * stack;
    10. ...
    11. }

    目前平台没有配置 CONFIG_THREAD_INFO_IN_TASK,所以thread_info放在了stack指向的内存中,thread_info中存储了体系结构相关的信息,arm32 内核栈大小8KB:

    1. //ARM架构 , 8K
    2. #define THREAD_SIZE_ORDER 1
    3. #define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER)
    4. #define THREAD_START_SP (THREAD_SIZE - 8)

     2.内核栈相关的API和数据结构

    • task_stack_page
    1. static inline void *task_stack_page(const struct task_struct *task)
    2. {
    3. return task->stack;
    4. }
    • task_pt_regs
    1. #define task_pt_regs(p) \
    2. ((struct pt_regs *)(THREAD_START_SP + task_stack_page(p)) - 1)
    • pt_regs 
    1. struct pt_regs {
    2. unsigned long uregs[18];
    3. };
    4. #define ARM_cpsr uregs[16]
    5. #define ARM_pc uregs[15]
    6. #define ARM_lr uregs[14]
    7. #define ARM_sp uregs[13]
    8. #define ARM_ip uregs[12]
    9. #define ARM_fp uregs[11]
    10. #define ARM_r10 uregs[10]
    11. #define ARM_r9 uregs[9]
    12. #define ARM_r8 uregs[8]
    13. #define ARM_r7 uregs[7]
    14. #define ARM_r6 uregs[6]
    15. #define ARM_r5 uregs[5]
    16. #define ARM_r4 uregs[4]
    17. #define ARM_r3 uregs[3]
    18. #define ARM_r2 uregs[2]
    19. #define ARM_r1 uregs[1]
    20. #define ARM_r0 uregs[0]
    21. #define ARM_ORIG_r0 uregs[17]

     进程从用户态陷入内核态时候,用户态的上下文信息保存在pt_regs数据结构中。

    • struct thread_info 
    1. /*
    2. * low level task data that entry.S needs immediate access to.
    3. * __switch_to() assumes cpu_context follows immediately after cpu_domain.
    4. */
    5. struct thread_info {
    6. unsigned long flags; /* low level flags */
    7. int preempt_count; /* 0 => preemptable, <0 => bug */
    8. mm_segment_t addr_limit; /* address limit */
    9. struct task_struct *task; /* main task structure */
    10. __u32 cpu; /* cpu */
    11. __u32 cpu_domain; /* cpu domain */
    12. struct cpu_context_save cpu_context; /* cpu context */
    13. __u32 syscall; /* syscall number */
    14. __u8 used_cp[16]; /* thread used copro */
    15. unsigned long tp_value[2]; /* TLS registers */
    16. #ifdef CONFIG_CRUNCH
    17. struct crunch_state crunchstate;
    18. #endif
    19. union fp_state fpstate __attribute__((aligned(8)));
    20. union vfp_state vfpstate;
    21. #ifdef CONFIG_ARM_THUMBEE
    22. unsigned long thumbee_state; /* ThumbEE Handler Base register */
    23. #endif
    24. void *regs_on_excp; /* aee */
    25. int cpu_excp; /* aee */
    26. };
    27. struct cpu_context_save {
    28. __u32 r4;
    29. __u32 r5;
    30. __u32 r6;
    31. __u32 r7;
    32. __u32 r8;
    33. __u32 r9;
    34. __u32 sl;
    35. __u32 fp;
    36. __u32 sp;
    37. __u32 pc;
    38. __u32 extra[2]; /* Xscale 'acc' register, etc */
    39. };

     3.内核态SP寄存器

    我们知道进程在内核态执行的时候,sp寄存器指向了内核栈,为什么内核的sp寄存器指向进程内核栈?这是什么时候设置的?

    答案:进程上下文切换的时候(switch_to汇编)

    首先进程创建的时候,在copy_thread会创建内核栈,并将内核栈地址保存在thread_info->cpu_context中,代码如下:

    1. //参数p时指新建进程的task_struct
    2. int
    3. copy_thread(unsigned long clone_flags, unsigned long stack_start,
    4. {
    5. struct thread_info *thread = task_thread_info(p);
    6. struct pt_regs *childregs = task_pt_regs(p);
    7. memset(&thread->cpu_context, 0, sizeof(struct cpu_context_save));
    8. #ifdef CONFIG_CPU_USE_DOMAINS
    9. /*
    10. * Copy the initial value of the domain access control register
    11. * from the current thread: thread->addr_limit will have been
    12. * copied from the current thread via setup_thread_stack() in
    13. * kernel/fork.c
    14. */
    15. thread->cpu_domain = get_domain();
    16. #endif
    17. if (likely(!(p->flags & PF_KTHREAD))) {
    18. *childregs = *current_pt_regs();
    19. childregs->ARM_r0 = 0;
    20. if (stack_start)
    21. childregs->ARM_sp = stack_start;
    22. } else {
    23. memset(childregs, 0, sizeof(struct pt_regs));
    24. thread->cpu_context.r4 = stk_sz;
    25. thread->cpu_context.r5 = stack_start;
    26. childregs->ARM_cpsr = SVC_MODE;
    27. }
    28. thread->cpu_context.pc = (unsigned long)ret_from_fork;
    29. thread->cpu_context.sp = (unsigned long)childregs;
    30. clear_ptrace_hw_breakpoint(p);
    31. if (clone_flags & CLONE_SETTLS)
    32. thread->tp_value[0] = childregs->ARM_r3;
    33. thread->tp_value[1] = get_tpuser();
    34. thread_notify(THREAD_NOTIFY_COPY, thread);
    35. return 0;
    36. }

    thread->cpu_context.pc = (unsigned long) ret_from_fork设置新建进程的执行入口时ret_from_frok函数。

    thread->cpu_context.sp = (unsigned long)childregs;thread_info成员cpu_context的sp成员指向了内核栈的pt_regs数据结构,pt_regs保存了用户态的通用寄存器。

    上下文切换switch_to函数会将thread->cpu_context.sp设置到cpu的寄存器中,那么其中的sp就设置了内核态的sp寄存器中:

    1. /*
    2. * Register switch for ARMv3 and ARMv4 processors
    3. * r0 = previous task_struct, r1 = previous thread_info, r2 = next thread_info
    4. * previous and next are guaranteed not to be the same.
    5. */
    6. ENTRY(__switch_to)
    7. UNWIND(.fnstart )
    8. UNWIND(.cantunwind )
    9. add ip, r1, #TI_CPU_SAVE @ip指向被换出进程的thread_info->cpu_context
    10. ARM( stmia ip!, {r4 - sl, fp, sp, lr} ) @ Store most regs on stack,即保存到cpu_context中
    11. THUMB( stmia ip!, {r4 - sl, fp} ) @ Store most regs on stack
    12. THUMB( str sp, [ip], #4 )
    13. THUMB( str lr, [ip], #4 )
    14. ldr r4, [r2, #TI_TP_VALUE]
    15. ldr r5, [r2, #TI_TP_VALUE + 4]
    16. #ifdef CONFIG_CPU_USE_DOMAINS
    17. mrc p15, 0, r6, c3, c0, 0 @ Get domain register
    18. str r6, [r1, #TI_CPU_DOMAIN] @ Save old domain register
    19. ldr r6, [r2, #TI_CPU_DOMAIN]
    20. #endif
    21. switch_tls r1, r4, r5, r3, r7
    22. #if defined(CONFIG_CC_STACKPROTECTOR) && !defined(CONFIG_SMP)
    23. ldr r7, [r2, #TI_TASK]
    24. ldr r8, =__stack_chk_guard
    25. .if (TSK_STACK_CANARY > IMM12_MASK)
    26. add r7, r7, #TSK_STACK_CANARY & ~IMM12_MASK
    27. .endif
    28. ldr r7, [r7, #TSK_STACK_CANARY & IMM12_MASK]
    29. #endif
    30. #ifdef CONFIG_CPU_USE_DOMAINS
    31. mcr p15, 0, r6, c3, c0, 0 @ Set domain register
    32. #endif
    33. mov r5, r0
    34. add r4, r2, #TI_CPU_SAVE @r4指向换入进程的cpu_context
    35. ldr r0, =thread_notify_head
    36. mov r1, #THREAD_NOTIFY_SWITCH
    37. bl atomic_notifier_call_chain
    38. #if defined(CONFIG_CC_STACKPROTECTOR) && !defined(CONFIG_SMP)
    39. str r7, [r8]
    40. #endif
    41. THUMB( mov ip, r4 )
    42. mov r0, r5
    43. ARM( ldmia r4, {r4 - sl, fp, sp, pc} ) @ Load all regs saved previously,即将cpu_context中值加载到cpu寄存器中
    44. THUMB( ldmia ip!, {r4 - sl, fp} ) @ Load all regs saved previously
    45. THUMB( ldr sp, [ip], #4 )
    46. THUMB( ldr pc, [ip] )
    47. UNWIND(.fnend )
    48. ENDPROC(__switch_to)

    ARM(   ldmia   r4, {r4 - sl, fp, sp, pc}  )会将进程thread_info->cpu_context中的值加载到cpu寄存器执行,上面分析我们知道进程创建的时候,thread->cpu_context.sp = (unsigned long)childregs,这样childregs值会加载到cpu sp寄存器,即内核态下sp指向了内核栈(更具体的说是内核栈中的pt_regs)

  • 相关阅读:
    Middleware ❀ Kafka功能与使用详解
    React 入门:实战案例 TodoList 组件列表动态初始化
    【精品资源】2024最新毕业设计题目(950+)java毕设、信息管理系统、python毕设、大数据分析毕设、机器学习毕设.
    【MySQL基础】数据库系统之关系型数据库与非关系型数据库
    C语言——如何写出好的代码?
    RESIDUAL INTERPOLATION FOR COLOR IMAGE DEMOSAICKING
    Python 机器学习入门之逻辑回归
    软件测试入门+面试点
    Nginx Mysql负载均衡 初步配置及验证 笔记
    TikTok国际版 使用特网科技Bluestacks模拟器安装方法
  • 原文地址:https://blog.csdn.net/GetNextWindow/article/details/126903271