Linux进程内核栈

2023-11-01

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

1.内核栈数据结构

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

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

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

  struct task_struct {
      #ifdef CONFIG_THREAD_INFO_IN_TASK
      /*
       * For reasons of header soup (see current_thread_info()), this
       * must be the first element of task_struct.
       */
      struct thread_info      thread_info;
      #endif
      void * stack;
	  ...
  }

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

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

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

  • task_stack_page
static inline void *task_stack_page(const struct task_struct *task)
{
	return task->stack;
}
  • task_pt_regs
#define task_pt_regs(p) \                                                                                                                                                
    ((struct pt_regs *)(THREAD_START_SP + task_stack_page(p)) - 1)
  • pt_regs 
struct pt_regs {                                                                                                                                           
    unsigned long uregs[18];
};

#define ARM_cpsr    uregs[16]
#define ARM_pc      uregs[15]
#define ARM_lr      uregs[14]
#define ARM_sp      uregs[13]
#define ARM_ip      uregs[12]
#define ARM_fp      uregs[11]
#define ARM_r10     uregs[10]
#define ARM_r9      uregs[9]
#define ARM_r8      uregs[8]
#define ARM_r7      uregs[7]
#define ARM_r6      uregs[6]
#define ARM_r5      uregs[5]
#define ARM_r4      uregs[4]
#define ARM_r3      uregs[3]
#define ARM_r2      uregs[2]
#define ARM_r1      uregs[1]
#define ARM_r0      uregs[0]
#define ARM_ORIG_r0 uregs[17]

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

  • struct thread_info 
/*
 * low level task data that entry.S needs immediate access to.
 * __switch_to() assumes cpu_context follows immediately after cpu_domain.
 */
struct thread_info {
    unsigned long       flags;      /* low level flags */
    int         preempt_count;  /* 0 => preemptable, <0 => bug */
    mm_segment_t        addr_limit; /* address limit */
    struct task_struct  *task;      /* main task structure */
    __u32           cpu;        /* cpu */
    __u32           cpu_domain; /* cpu domain */
    struct cpu_context_save cpu_context;    /* cpu context */
    __u32           syscall;    /* syscall number */
    __u8            used_cp[16];    /* thread used copro */
    unsigned long       tp_value[2];    /* TLS registers */
#ifdef CONFIG_CRUNCH
    struct crunch_state crunchstate;
#endif
    union fp_state      fpstate __attribute__((aligned(8)));
    union vfp_state     vfpstate;
#ifdef CONFIG_ARM_THUMBEE
    unsigned long       thumbee_state;  /* ThumbEE Handler Base register */
#endif
    void            *regs_on_excp;  /* aee */
    int         cpu_excp;   /* aee */
};

struct cpu_context_save {
    __u32   r4;
    __u32   r5;
    __u32   r6;
    __u32   r7;
    __u32   r8;
    __u32   r9;
    __u32   sl;
    __u32   fp;
    __u32   sp;
    __u32   pc;
    __u32   extra[2];       /* Xscale 'acc' register, etc */
};

 3.内核态SP寄存器

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

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

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

//参数p时指新建进程的task_struct
int
copy_thread(unsigned long clone_flags, unsigned long stack_start,
{       
    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

    if (likely(!(p->flags & PF_KTHREAD))) {
        *childregs = *current_pt_regs();
        childregs->ARM_r0 = 0;
        if (stack_start)
            childregs->ARM_sp = stack_start;
    } else {
        memset(childregs, 0, sizeof(struct pt_regs));
        thread->cpu_context.r4 = stk_sz;
        thread->cpu_context.r5 = stack_start;
        childregs->ARM_cpsr = SVC_MODE;
    }   
    thread->cpu_context.pc = (unsigned long)ret_from_fork;
    thread->cpu_context.sp = (unsigned long)childregs;
    
    clear_ptrace_hw_breakpoint(p);
    
    if (clone_flags & CLONE_SETTLS)
        thread->tp_value[0] = childregs->ARM_r3;
    thread->tp_value[1] = get_tpuser();
    
    thread_notify(THREAD_NOTIFY_COPY, thread);
    
    return 0;
} 

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寄存器中:

/*
 * Register switch for ARMv3 and ARMv4 processors
 * r0 = previous task_struct, r1 = previous thread_info, r2 = next thread_info
 * previous and next are guaranteed not to be the same.
 */
ENTRY(__switch_to)
 UNWIND(.fnstart    )
 UNWIND(.cantunwind )
    add ip, r1, #TI_CPU_SAVE    @ip指向被换出进程的thread_info->cpu_context
 ARM(   stmia   ip!, {r4 - sl, fp, sp, lr} )    @ Store most regs on stack,即保存到cpu_context中
 THUMB( stmia   ip!, {r4 - sl, fp}     )    @ Store most regs on stack
 THUMB( str sp, [ip], #4           )
 THUMB( str lr, [ip], #4           )
    ldr r4, [r2, #TI_TP_VALUE]
    ldr r5, [r2, #TI_TP_VALUE + 4]
#ifdef CONFIG_CPU_USE_DOMAINS
    mrc p15, 0, r6, c3, c0, 0       @ Get domain register
    str r6, [r1, #TI_CPU_DOMAIN]    @ Save old domain register
    ldr r6, [r2, #TI_CPU_DOMAIN]
#endif
    switch_tls r1, r4, r5, r3, r7
#if defined(CONFIG_CC_STACKPROTECTOR) && !defined(CONFIG_SMP)
    ldr r7, [r2, #TI_TASK]
    ldr r8, =__stack_chk_guard
    .if (TSK_STACK_CANARY > IMM12_MASK)
    add r7, r7, #TSK_STACK_CANARY & ~IMM12_MASK
    .endif
    ldr r7, [r7, #TSK_STACK_CANARY & IMM12_MASK]
#endif
#ifdef CONFIG_CPU_USE_DOMAINS
    mcr p15, 0, r6, c3, c0, 0       @ Set domain register
#endif
    mov r5, r0
    add r4, r2, #TI_CPU_SAVE        @r4指向换入进程的cpu_context
    ldr r0, =thread_notify_head
    mov r1, #THREAD_NOTIFY_SWITCH
    bl  atomic_notifier_call_chain
#if defined(CONFIG_CC_STACKPROTECTOR) && !defined(CONFIG_SMP)
    str r7, [r8]
#endif
 THUMB( mov ip, r4             )
    mov r0, r5
 ARM(   ldmia   r4, {r4 - sl, fp, sp, pc}  )    @ Load all regs saved previously,即将cpu_context中值加载到cpu寄存器中
 THUMB( ldmia   ip!, {r4 - sl, fp}     )    @ Load all regs saved previously
 THUMB( ldr sp, [ip], #4           )
 THUMB( ldr pc, [ip]           )
 UNWIND(.fnend      )
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)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux进程内核栈 的相关文章

  • Linux 中 m 标志和 o 标志将存储在哪里

    我想知道最近收到的路由器通告的 m 标志和 o 标志的值 从内核源代码中我知道存储了 m 标志和 o 标志 Remember the managed otherconf flags from most recently received R
  • 适用于 Linux 的轻量级 IDE [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • docker 非 root 绑定安装权限,WITH --userns-remap

    all 尝试让绑定安装权限正常工作 我的目标是在容器中绑定安装卷 以便 a 容器不以 root 用户身份运行入口点 二 docker daemon 配置了 userns remap 这样容器 主机上没有 root c 我可以绑定挂载和读 写
  • 我们真的应该使用 Chef 来管理 sudoers 文件吗?

    这是我的问题 我担心如果 Chef 破坏了 sudoers 文件中的某些内容 可能是 Chef 用户错误地使用了说明书 那么服务器将完全无法访问 我讨厌我们完全失去客户的生产服务器 因为我们弄乱了 sudoers 文件并且无法再通过 ssh
  • python获取上传/下载速度

    我想在我的计算机上监控上传和下载速度 一个名为 conky 的程序已经在 conky conf 中执行了以下操作 Connection quality alignr wireless link qual perc wlan0 downspe
  • 通过 Visual Studio 2017 使用远程调试时 Linux 控制台输出在哪里?

    我的Visual Studio 2017 VS2017 成功连接Linux系统 代码如下 include
  • tcpdump 是否受 iptables 过滤影响?

    如果我的开发机器有iptables规则到FORWARD一些数据包 这些数据包是否被 tcpdump 捕获 我有这个问题 因为我知道存在其他链称为INPUT如果数据包路由到 它会过滤发往应用程序的数据包FORWARD链 它会到达吗tcpdum
  • Linux 上的 Pervasive ODBC 错误 [01000][unixODBC][驱动程序管理器]无法打开 lib '/usr/local/psql/lib/odbcci.so':找不到文件

    我正在尝试让 Pervasive v10 客户端 ODBC 在 Centos 6 上运行 据我所知 没有 64 位 ODBC 客户端 因此我必须使用 32 位客户端 我终于成功安装了它 但尝试使用时出现以下错误 isql v mydsn 0
  • 从 ttyUSB0 写入和读取,无法得到响应

    我对 Linux tty 不太有经验 我的环境是带有丰富 USB 串行的 Raspbian 什么有效 stty F dev ttyUSB0 38400 cu l dev ttyUSB0 s 38400 cu to dev ttyUSB0作品
  • 尽管 if 语句,Visual Studio 仍尝试包含 Linux 标头

    我正在尝试创建一个强大的头文件 无需更改即可在 Windows 和 Linux 上进行编译 为此 我的包含内容中有一个 if 语句 如下所示 if defined WINDOWS include
  • 使用包管理器时如何管理 Perl 模块?

    A 最近的问题 https stackoverflow com questions 397817 unable to find perl modules in intrepid ibex ubuntu这让我开始思考 在我尝试过的大多数 Li
  • 从 Xlib 转换为 xcb

    我目前正在将我的一个应用程序从 Xlib 移植到 libxcb 但在查找有关我有时使用的 XInput2 扩展的信息时遇到了一些麻烦 libxcb 中有 XInput2 实现吗 如果是的话 在哪里可以找到文档 目前我在使用此功能时遇到问题
  • 如何在 Linux 中使用 C 语言使用共享内存

    我的一个项目有点问题 我一直在试图找到一个有据可查的使用共享内存的例子fork 但没有成功 基本上情况是 当用户启动程序时 我需要在共享内存中存储两个值 当前路径这是一个char and a 文件名这也是char 根据命令参数 启动一个新进
  • Intel 上的 gcc 中的 _mm_pause 用法

    我参考过这个网页 https software intel com en us articles benefitting power and performance sleep loops https software intel com
  • Linux 为一组进程保留一个处理器(动态)

    有没有办法将处理器排除在正常调度之外 也就是说 使用sched setaffinity我可以指示线程应该在哪个处理器上运行 但我正在寻找相反的情况 也就是说 我想从正常调度中排除给定的处理器 以便只有已明确调度的进程才能在那里运行 我还知道
  • Apache 访问 Linux 中的 NTFS 链接文件夹

    在 Debian jessie 中使用 Apache2 PHP 当我想在 Apache 的文档文件夹 var www 中创建一个新的小节时 我只需创建一个指向我的 php 文件所在的外部文件夹的链接 然后只需更改该文件夹的所有者和权限文件夹
  • 这种文件锁定方法可以接受吗?

    我们有 10 个 Linux 机器 每周必须运行 100 个不同的任务 这些计算机主要在我们晚上在家时执行这些任务 我的一位同事正在开发一个项目 通过使用 Python 自动启动任务来优化运行时间 他的程序将读取任务列表 抓取一个打开的任务
  • cdc_acm:无法设置 dtr/rts - 无法与 USB cdc 设备通信

    我试图使用 pic24fj128gb206 枚举 usb cdc 设备 设备似乎已正确枚举 但是当我将设备连接到 Linux PC 时 我从内核收到以下警告消息 cdc acm 1 8 1 6 7 1 0 failed to set dtr
  • 复制目录内容

    我想将目录 tmp1 的内容复制到另一个目录 tmp2 tmp1 可能包含文件和其他目录 我想使用C C 复制tmp1的内容 包括模式 如果 tmp1 包含目录树 我想递归复制它们 最简单的解决方案是什么 我找到了一个解决方案来打开目录并读
  • 尽管我已在 python ctypes 中设置了信号处理程序,但并未调用它

    我尝试过使用 sigaction 和 ctypes 设置信号处理程序 我知道它可以与python中的信号模块一起使用 但我想尝试学习 当我向该进程发送 SIGTERM 时 但它没有调用我设置的处理程序 只打印 终止 为什么它不调用处理程序

随机推荐

  • vue项目中该如何引入自定义字体?超简单几步教你实现

    在这里斗胆说一句教你实现 其实是我的一种实现方法 觉得十分简单 很好理解 所以在这里分享给大家了 具体操作 1 在assets中创建一个文件夹 比如叫fonts 或者是其他乱七八糟的名字都随便 都可以 2 将字体文件引入这个fonts文件夹
  • Python的os.walk()方法详细讲解

    http www cnblogs com herbert archive 2013 01 07 2848892 html 写的特别清楚的一篇 http alanland iteye com blog 612459 我们可以看到 返回的是一个
  • jdk的环境搭建

    1 鼠标右键单击 此电脑 左键单击 属性 2 点击 高级系统设置 3 选择 环境变量 4 这里需要配置三个环境变量1 java home 2 classpash 3 path 其中1和2是系统中没有的需要新建 不区分大小写 1 java h
  • (机器学习实战)第四章

    都是在python3下面的 def loadDataSet postingList my dog has flea problems help please maybe not take him to dog park stupid my
  • linux常见文件夹名称及作用

    在Linux系统中 有许多常用的目录 每个目录都有其特定的作用和用途 以下是一些常见的Linux文件夹及其作用的示例 命令 公共 程序 bin 存放系统命令 二进制文件 如ls cp和mkdir等 这些命令可以在系统启动时使用 sbin 与
  • spark源码分析之shufflemanager

    1 shufflemanager的实现类 sortshufflemanager Spark 0 8及以前 Hash Based Shuffle 在Shuffle Write过程按照Hash的方式重组Partition的数据 不进行排序 每个
  • Java 使用esayExcel进行导出、导入包含多个sheet页面

  • Win10 CubeMX 安装java环境,安装不上去的问题解决

    问题描述 运行STM32CubeMX的时候 如果JAVA环境被破坏 会有如下问题 会自动弹出以下安装地址 Download Java for Windows 然后 在下载的文件 选择安装 点击安装后 就没有然后了 网上有建议 更改下面安装文
  • [ C语言 ]三子棋 代码实现

    引言 三子棋是一种简单而又有趣的棋类游戏 它可以帮助我们提高逻辑思维和决策能力 在本文中 我们将使用C语言来实现一个简单的三子棋游戏 并介绍一些基本的算法和技巧 一 游戏规则 1 游戏开始时 棋盘是空的 由两位玩家交替进行操作 2 玩家使用
  • 通过python写脚本简单爆破web页面登陆

    GET传参 import requests url payload username admin password admin submit 登陆 r requests get url params payload result r con
  • Jackson框架

    Jackson框架 一 Jackson简介 Jackson可以轻松的将Java对象转换成json对象和xml文档 同样也可以将json xml转换成Java对象 相比json lib框架 Jackson所依赖的jar包较少 简单易用并且性能
  • 金蝶EAS-BOS二开详细过程

    我们在做金蝶的项目时 经常会要求更改其项目本身的代码 但是它的代码都被封装在jar包中 我们应该怎么做呢 将要二开的单据实体或者facade 复制到我们的本地项目中 选中你要修改的具体实体或者facade 右键点击复制 重命名 复制到我们的
  • 配置Hadoop集群+WordCount案例

    配置Hadoop集群 配置环境变量 etc profile export HADOOP HOME bigData hadoop 2 8 0 export PATH P A T H PATH PATH HADOOP
  • cannot read property ‘line‘ of undefined

    环境 vite vue3 ts 这个问题的点还挺不明显的 翻了翻代码修改记录 发现是漏了结尾的 lt style gt 标签 做好本地代码管理真的很重要
  • gradle 两种更新方法

    第一种 Android studio更新 第一步 在你所在项目文件夹下 你项目根目录gradlewrappergradle wrapper properties 替换 distributionUrl https services gradl
  • 【算法/剑指Offer】地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。

    题目描述 地上有一个m行和n列的方格 一个机器人从坐标0 0的格子开始移动 每一次只能向左 右 上 下四个方向移动一格 但是不能进入行坐标和列坐标的数位之和大于k的格子 例如 当k为18时 机器人能够进入方格 35 37 因为3 5 3 7
  • java螺旋数组

    1 程序设计题 对于一个 n 行 m 列的表格 我们可以使用螺旋的方式给表格依次填上正整数 我们称填好的表格为一个螺旋矩阵 例如 一个 4 行 5 列的螺旋矩阵如下 1 2 3 4 5 14 15 16 17 6 13 20 19 18 7
  • jquery 等待3秒钟执行函数

    setTimeout function div2 hide 3000
  • CF1249B2 Books Exchange (hard version) 题解

    题目大意 共 q q q 组询问 对于每一组询问有长度为 n n n 的序列 p p
  • Linux进程内核栈

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