购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.3 内核栈

在基于多核硬件的当代计算平台上,可以同时并行地运行应用程序。因此,在请求同一个进程时,可以同时启动多个进程的内核模式切换。为了能够处理这种情况,内核服务被设计为可重入的,允许多个进程介入并使用所需的服务。这就要求请求进程维护它自己的私有内核栈,来跟踪内核函数调用顺序,存储内核函数的本地数据,等等。

内核栈直接映射到物理内存,强制排列在物理上处于连续的区域中。默认情况下,内核栈对于x86-32和大多数其他32位系统(在内核构建期间可以配置4KB内核栈的选项)为8KB,在x86-64系统上为16KB。

当内核服务在当前进程上下文中被调用时,它们需要在进行任何相关操作之前验证进程的特权。要执行这类验证,内核服务必须能够访问当前进程的任务结构体并查看相关字段。同样,内核例程可能需要访问当前task structure,以修改各种资源结构体(如信号处理程序表),查找被挂起的信号、文件描述符表和内存描述符等。为了能够在运行时访问task structure,内核将当前task structure的地址加载到处理器寄存器(所选的寄存器与体系结构相关)中,并通过称为current的内核全局宏提供访问(在体系结构特定的内核头文件asm/current.h中定义):

/* arch/ia64/include/asm/current.h */
#ifndef _ASM_IA64_CURRENT_H
#define _ASM_IA64_CURRENT_H
/*
* Modified 1998-2000
*      David Mosberger-Tang <davidm@hpl.hp.com>, Hewlett-Packard Co
*/
#include <asm/intrinsics.h>
/*
* In kernel mode, thread pointer (r13) is used to point to the
  current task
* structure.
*/
#define current ((struct task_struct *) ia64_getreg(_IA64_REG_TP))
#endif /* _ASM_IA64_CURRENT_H */
/* arch/powerpc/include/asm/current.h */
#ifndef _ASM_POWERPC_CURRENT_H
#define _ASM_POWERPC_CURRENT_H
#ifdef __KERNEL__
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
struct task_struct;
#ifdef __powerpc64__
#include <linux/stddef.h>
#include <asm/paca.h>
static inline struct task_struct *get_current(void)
{
      struct task_struct *task;
 
      __asm__ __volatile__("ld %0,%1(13)"
      : "=r" (task)
      : "i" (offsetof(struct paca_struct, __current)));
      return task;
}
#define current get_current()
#else
/*
* We keep `current' in r2 for speed.
*/
register struct task_struct *current asm ("r2");
#endif
#endif /* __KERNEL__ */
#endif /* _ASM_POWERPC_CURRENT_H */

然而,在寄存器受限的体系结构中,只有很少的寄存器可用,预留一个寄存器来保存当前任务结构体的地址是不可行的。在这样的平台上,当前进程的task structure可以直接在其拥有的内核栈的顶部使用。通过屏蔽栈指针的最低有效位,这种方法在确定task structure位置方面具有很大的优势。

随着内核的演变,task structure增长并变得太大而无法容纳在内核栈中,而内核栈已经被限制在物理内存中(8KB)。因此,除了一些关键字段(如定义进程的CPU状态和其他底层处理器相关的信息),task structure已经被移出内核栈。然后将这些字段封装在一个新创建的结构体中,称为struct thread_info。这个结构体包含在内核栈的顶部,并提供一个指向当前task structure的指针,该指针可以被内核服务所使用。

下面的代码片段展示了x86体系结构(内核3.10)的struct thread_info:

/* linux-3.10/arch/x86/include/asm/thread_info.h */
struct thread_info {
 struct task_struct *task; /* main task structure */
 struct exec_domain *exec_domain; /* execution domain */
 __u32 flags; /* low level flags */
 __u32 status; /* thread synchronous flags */
 __u32 cpu; /* current CPU */
 int preempt_count; /* 0 => preemptable, <0 => BUG */
 mm_segment_t addr_limit;
 struct restart_block restart_block;
 void __user *sysenter_return;
 #ifdef CONFIG_X86_32
 unsigned long previous_esp; /* ESP of the previous stack in case of
 nested (IRQ) stacks */
 __u8 supervisor_stack[0];
 #endif
 unsigned int sig_on_uaccess_error:1;
 unsigned int uaccess_err:1; /* uaccess failed */
};

使用包含了进程相关信息的thread_info,除task structure之外,内核对当前进程结构体有多个视角:一个与体系结构无关的信息块struct task_struct和一个与体系结构相关的thread_info。图1-6描述了thread_info和task_struct。

图1-6

对于使用thread_info的体系结构,内核修改了当前宏的实现,以查看内核栈的顶部,从而获取对当前thread_info的引用,并通过它来获得当前的task structure。下面的代码片段所示为当前x86-64平台的实现。

#ifndef __ASM_GENERIC_CURRENT_H
#define __ASM_GENERIC_CURRENT_H
#include <linux/thread_info.h>
#define get_current() (current_thread_info()->task)
#define current get_current()
#endif /* __ASM_GENERIC_CURRENT_H */
/*
* how to get the current stack pointer in C
*/
register unsigned long current_stack_pointer asm ("sp");
/*
 * how to get the thread information struct from C
 */
static inline struct thread_info *current_thread_info(void)
__attribute_const__;
static inline struct thread_info *current_thread_info(void)
{
       return (struct thread_info *)
               (current_stack_pointer & ~(THREAD_SIZE - 1));
}

随着近段时间越来越多地使用PER_CPU变量,进程调度器进行了优化,它在PER_CPU区域中缓存了当前与进程相关的关键信息。这一更改使得可以通过查找内核栈来快速访问当前进程数据。下面的代码片段所示为current宏通过PER_CPU变量获取当前任务数据的实现。

#ifndef _ASM_X86_CURRENT_H
#define _ASM_X86_CURRENT_H
#include <linux/compiler.h>
#include <asm/percpu.h>
#ifndef __ASSEMBLY__
struct task_struct;
DECLARE_PER_CPU(struct task_struct *, current_task);
static __always_inline struct task_struct *get_current(void)
{
        return this_cpu_read_stable(current_task);
}
 
#define current get_current()
#endif /* __ASSEMBLY__ */
 
#endif /* _ASM_X86_CURRENT_H */

使用PER_CPU数据会导致thread_info中的信息逐渐减少。随着thread_info规模的缩小,内核开发者正在考虑通过将thread_info移动到task structure中,从而完全清除thread_info。由于这涉及对底层体系结构代码的修改,目前只在x86-64体系结构中实现了,而其他体系结构也计划跟随这项改动。以下代码片段所示为只有一个元素的thread_info结构体的当前状态。 IBNmltnXyCco7gvnUT8UOD1PxEtGbZzV9FIBDfEmUo/PN6Jon8EvhuR370zJpGKb

/* linux-4.9.10/arch/x86/include/asm/thread_info.h */
struct thread_info {
 unsigned long flags; /* low level flags */
};
点击中间区域
呼出菜单
上一章
目录
下一章
×