`
kongweile
  • 浏览: 517363 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

linux内核栈与用户栈

 
阅读更多

最近linux内核的中断部分,总是被书里的栈弄晕,一会儿内核栈,一会儿用户栈的……很是崩溃,在网上google了一下 找了一篇不错的文章拿来分享。
 

5.8 Linux 系统中堆栈的使用方法

本节内容概要描述了Linux内核从开机引导到系统正常运行过程中对堆栈的使用方式。这部分内容的说明与内核代码关系比较密切,可以先跳过。在开始阅读相应代码时再回来仔细研究
Linux 0.12系统中共使用了4种堆栈。第1种是系统引导初始化时临时使用的堆栈;第2种是进入保护模式之后提供内核程序初始化使用的堆栈,位于内核代码地址空间固定位置处。该堆栈也是后来任务0使用的用户态堆栈;第3种是每个任务通过系统调用,执行内核程序时使用的堆栈,我们称之为任务的内核态堆栈。每个任务都有自己独立的内核态堆栈;第4种是任务在用户态执行的堆栈,位于任务(进程)逻辑地址空间近末端处。
使用多个栈或在不同情况下使用不同栈的主要原因有两个。首先是由于从实模式进入保护模式,使得CPU对内存寻址访问方式发生了变化,因此需要重新调整设置栈区域。另外,为了解决不同CPU特权级共享使用堆栈带来的保护问题,执行0级的内核代码和执行3级的用户代码需要使用不同的栈。当一个任务进入内核态运行时,就会使用其TSS段中给出的特权级0的堆栈指针tss.ss0、tss.esp0,即内核栈。原用户栈指针会被保存在内核栈中。而当从内核态返回用户态时,就会恢复使用用户态的堆栈。下面分别对它们进行说明。

5.8.1  初始化阶段

(1)开机初始化时(bootsect.S,setup.s)
当bootsect代码被ROM BIOS引导加载到物理内存0x7c00处时,并没有设置堆栈段,当然程序也没有使用堆栈。直到bootsect被移动到0x9000:0处时,才把堆栈段寄存器SS设置为0x9000,堆栈指针esp寄存器设置为0xff00,即堆栈顶端在0x9000:0xff00处,参见boot/bootsect.s第61、62行。setup.s程序中也沿用了bootsect中设置的堆栈段。这就是系统初始化时临时使用的堆栈。
(2)进入保护模式时(head.s)
head.s程序起,系统开始正式在保护模式下运行。此时堆栈段被设置为内核数据段(0x10),堆栈指针esp设置成指向user_stack数组的顶端(参见head.s,第31行),保留了1页内存(4KB)作为堆栈使用。user_stack数组定义在sched.c的6772行,共含有1024个长字。它在物理内存中的位置示意图可参见图5-23。此时该堆栈是内核程序自己使用的堆栈。其中给出的地址是大约值,它们与编译时的实际设置参数有关。这些地址位置是从编译内核时生成的system.map文件中查到的。
5-23  刚进入保护模式时内核使用的堆栈示意图
(3)初始化时(main.c)
在init/main.c程序中,在执行move_to_user_mode()代码把控制权移交给任务0之前,系统一直使用上述堆栈。而在执行过move_to_user_mode()之后,main.c的代码被“切换”成任务0中执行。通过执行fork()系统调用,main.c中的init()将在任务1中执行,并使用任务1的堆栈。而main()本身则在被“切换”成为任务0后,仍然继续使用上述内核程序自己的堆栈作为任务0的用户态堆栈。关于任务0所使用堆栈的详细描述见后面说明。

5.8.2  任务的堆栈

每个任务都有两个堆栈,分别用于用户态和内核态程序的执行,并且分别称为用户态堆栈和内核态堆栈。除了处于不同CPU特权级中,这两个堆栈之间的主要区别在于任务的内核态堆栈很小,所保存的数据量最多不能超过4096 – 任务数据结构块个字节,大约为3KB。而任务的用户态堆栈却可以在用户的64MB空间内延伸。
(1)在用户态运行时
每个任务(除了任务0和任务1)有自己的64MB地址空间。当一个任务(进程)刚被创建时,它的用户态堆栈指针被设置在其地址空间的靠近末端(64MB顶端)部分。实际上末端部分还要包括执行程序的参数和环境变量,然后才是用户堆栈空间,如图5-24所示。应用程序在用户态下运行时就一直使用这个堆栈。堆栈实际使用的物理内存则由CPU分页机制确定。由于Linux实现了写时复制功能(Copy on Write),因此在进程被创建后,若该进程及其父进程都没有使用堆栈,则两者共享同一堆栈对应的物理内存页面。只有当其中一个进程执行堆栈写操作(如push操作)时内核内存管理程序才会为写操作进程分配新的内存页面。而进程0和进程1的用户堆栈比较特殊,见后面说明。
5-24  逻辑空间中的用户态堆栈
(2)在内核态运行时
每个任务都有自己的内核态堆栈,用于任务在内核代码中执行期间。其所在线性地址中的位置由该任务TSS段中ss0和esp0两个字段指定。ss0是任务内核态堆栈的段选择符,esp0是堆栈栈底指针。因此每当任务从用户代码转移进入内核代码中执行时,任务的内核态栈总是空的。任务内核态堆栈被设置在位于其任务数据结构所在页面的末端,即与任务的任务数据结构(task_struct)放在同一页面内。这是在建立新任务时,fork()程序在任务tss段的内核级堆栈字段(tss.esp0和tss.ss0)中设置的,参见kernel/fork.c,92行:
    p->tss.esp0 = PAGE_SIZE + (long)p;
    p->tss.ss0 = 0x10;
其中,p是新任务的任务数据结构指针,tss是任务状态段结构。内核为新任务申请内存用作保存其task_struct结构数据,而tss结构(段)是task_struct中的一个字段。该任务的内核堆栈段值tss.ss0也被设置成为0x10(即内核数据段选择符),而tss.esp0则指向保存task_struct结构页面的末端。如图5-25所示。实际上tss.esp0被设置成指向该页面(外)上一字节处(图中堆栈底处)。这是因为Intel CPU执行堆栈操作时是先递减堆栈指针esp值,然后在esp指针处保存入栈内容。
5-25  进程的内核态堆栈示意图
为什么从主内存区申请得来的用于保存任务数据结构的一页内存也能被设置成内核数据段中的数据呢,即tss.ss0为什么能被设置成0x10呢?这是因为用户内核态栈仍然属于内核数据空间。我们可以从内核代码段的长度范围来说明。在head.s程序的末端,分别设置了内核代码段和数据段的描述符,段长度都被设置成了16MB。这个长度值是Linux 0.12内核所能支持的最大物理内存长度(参见head.s,110行开始的注释)。因此,内核代码可以寻址到整个物理内存范围中的任何位置,当然也包括主内存区。每当任务执行内核程序而需要使用其内核栈时,CPU就会利用TSS结构把它的内核态堆栈设置成由tss.ss0和tss.esp0这两个值构成。在任务切换时,老任务的内核栈指针esp0不会被保存。对CPU来讲,这两个值是只读的。因此每当一个任务进入内核态执行时,其内核态堆栈总是空的。
(3)任务0和任务1的堆栈
任务0(空闲进程idle和任务1(初始化进程init的堆栈比较特殊,需要特别予以说明。任务0和任务1的代码段和数据段相同,限长也都是640KB,但它们被映射到不同的线性地址范围中。任务0的段基地址从线性地址0开始,而任务1的段基地址从64MB开始。但是它们全都映射到物理地址0~640KB范围中。这个地址范围也就是内核代码和基本数据所存放的地方。在执行了move_to_user_mode()之后,任务0和任务1的内核态堆栈分别位于各自任务数据结构所在页面的末端,而任务0的用户态堆栈就是前面进入保护模式后所使用的堆栈,即sched.c的user_stack[]数组的位置。由于任务1在创建时复制了任务0的用户堆栈,因此刚开始时任务0和任务1共享使用同一个用户堆栈空间。但是当任务1开始运行时,由于任务1映射到user_stack[]处的页表项被设置成只读,使得任务1在执行堆栈操作时将会引起写页面异常,从而内核会使用写时复制机制(关于写时复制技术的说明请参见第13为任务1另行分配主内存区页面作为堆栈空间使用。只有到此时,任务1才开始使用自己独立的用户堆栈内存页面。因此任务0的堆栈需要在任务1实际开始使用之前保持“干净”,即任务0此时不能使用堆栈,以确保复制的堆栈页面中不含有任务0的数据。
任务0的内核态堆栈是在其人工设置的初始化任务数据结构中指定的,而它的用户态堆栈是在执行move_to_user_mode()时,在模拟iret返回之前的堆栈中设置的,参见图5-22所示。我们知道,当进行特权级会发生变化的控制权转移时,目的代码会使用新特权级的堆栈,而原特权级代码堆栈指针将保留在新堆栈中。因此这里先把任务0用户堆栈指针压入当前处于特权级0的堆栈中,同时把代码指针也压入堆栈,然后执行IRET指令即可实现把控制权从特权级0的代码转移到特权级3的任务0代码中。在这个人工设置内容的堆栈中,原esp值被设置成仍然是user_stack中原来的位置值,而原ss段选择符被设置成0x17,即设置成用户态局部表LDT中的数据段选择符。然后把任务0代码段选择符0x0f压入堆栈作为栈中原CS段的选择符,把下一条指令的指针作为原EIP压入堆栈。这样,通过执行IRET指令即可“返回”到任务0的代码中继续执行了。

5.8.3  任务内核态堆栈与用户态堆栈之间的切换

Linux 0.12系统中,所有中断服务程序都属于内核代码。如果一个中断产生时任务正在用户代码中执行,那么该中断就会引起CPU特权级从3级到0级的变化,此时CPU就会进行用户态堆栈到内核态堆栈的切换操作。CPU会从当前任务的任务状态段TSS中取得新堆栈的段选择符和偏移值。因为中断服务程序在内核中,属于0级特权级代码,所以48位的内核态堆栈指针会从TSS的ss0和esp0字段中获得。在定位了新堆栈(内核态堆栈)之后,CPU就会首先把原用户态堆栈指针ss和esp压入内核态堆栈,随后把标志寄存器eflags的内容和返回位置cs、eip压入内核态堆栈。
内核的系统调用是一个软件中断,因此任务调用系统调用时就会进入内核并执行内核中的中断服务代码。此时内核代码就会使用该任务的内核态堆栈进行操作。同样,当进入内核程序时,由于特权级别发生了改变(从用户态转到内核态),用户态堆栈的堆栈段和堆栈指针以及eflags会被保存在任务的内核态堆栈中。而在执行iret退出内核程序返回到用户程序时,将恢复用户态的堆栈和eflags。这个过程如图5-26所示。
5-26  内核态和用户态堆栈的切换
如果一个任务正在内核态中运行,那么若CPU响应中断就不再需要进行堆栈切换操作,因为此时该任务运行的内核代码已经在使用内核态堆栈,并且不涉及优先级别的变化,所以CPU仅把eflags和中断返回指针cs、eip压入当前内核态堆栈,然后执行中断服务过程。
分享到:
评论

相关推荐

    Linux 中的各种栈:进程栈 线程栈 内核栈 中断栈

    内核栈通常比用户栈小得多,因为在内核模式下运行的代码应当尽量简洁高效。 中断栈是在发生硬件中断时使用的栈。中断处理通常需要在非常短的时间内完成,因此需要一个专用的栈空间来保存当前执行的上下文,以保证...

    Linux内核网络栈源代码情景分析_12390610.pdf

    Linux内核网络栈源代码情景分析

    Linux内核协议栈的详解完整版

    详解Linux协议栈的数据流向,SOCKET的操作流程,unicast multicast等等的区别。

    Linux内核设计与实现(第三版中文高清带目录)_linux_linux内核_

    《Linux内核设计与实现》是理解Linux操作系统内核架构和技术细节的重要参考资料,特别是第三版中文高清版,为读者提供了全面且深入的内核解析。这本书由Robert Love编写,是学习Linux内核不可或缺的经典之作。以下将...

    深入理解Linux内核 + Linux内核设计与实现 英文版

    《深入理解Linux内核》和《Linux内核设计与实现》是两本经典的Linux内核研究书籍,旨在帮助读者从宏观到微观全面了解Linux操作系统的核心机制。这两本书结合阅读,可以为初学者提供一个系统而深入的学习路径。 ...

    《linux内核网络栈源代码情景分析》.(曹桂平).[PDF].&ckook;.pdf.zip

    《Linux内核网络栈源代码情景分析》是曹桂平撰写的一本深入解析Linux内核网络处理机制的著作。这本书详细介绍了Linux操作系统如何处理网络数据包,从硬件接口到高层协议栈的每一个环节,帮助读者理解Linux网络内核的...

    linux内核协议栈源码解析(2.6.18内核)

    Linux内核协议栈是操作系统核心的一部分,负责处理所有与网络相关的任务,包括数据包的接收、处理和发送。在2.6.18这个版本中,协议栈已经相当成熟,支持多种网络协议,如TCP/IP(传输控制协议/互联网协议),这是...

    Linux内核的栈使用,问题的定位

    Linux 内核栈使用与问题定位 Linux 内核栈使用是 Linux 内核或驱动开发人员常见的功能之一。栈回溯功能可以快速定位到在内核哪个函数崩溃,大概在函数什么位置,大大简化了问题排查过程。基于 MIPS、ARM 架构 Linux...

    linux内核协议栈分析

    Linux内核协议栈是Linux操作系统中负责处理网络数据包传输的核心组件。它按照TCP/IP协议模型的分层设计,将网络通信划分为链路层、网络层、运输层和应用层四个层次,并为每层提供了相应的协议实现与数据结构。 在...

    Linux内核设计与实现-第三版

    《Linux内核设计与实现》第三版是一本深入探讨Linux操作系统核心的书籍,它基于Linux 2.6.34内核版本,全面覆盖了Linux内核的设计原理和实现机制。本书适合具有一定Linux操作系统知识背景的读者,尤其是对系统编程、...

    linux内核设计与实现 第三版_linux内核设计_

    《Linux内核设计与实现》第三版是一本深入解析Linux内核的重要著作,它详尽地阐述了Linux操作系统的核心设计理念和实现机制。本书是Linux爱好者、系统管理员、软件开发人员以及对操作系统有深入兴趣的读者不可或缺的...

    Linux内核802.11无线网络协议栈的设计与实现.pdf

    "Linux内核802.11无线网络协议栈的设计与实现" Linux内核802.11无线网络协议栈是指在Linux操作系统中实现IEEE 802.11无线局域网协议栈的设计和实现。该协议栈的主要目标是实现IEEE 802.11系列标准中的媒体访问子层...

    linux内核设计与实现3

    《Linux内核设计与实现》第三版是一本深入解析Linux操作系统内核的权威书籍,对于想要深入了解Linux系统底层工作原理的初学者来说,是不可多得的参考资料。这本书全面覆盖了Linux内核的设计哲学、核心架构以及关键...

    Linux 内核IPSec(xfrm)协议栈源码分析

    本文档深入剖析Linux内核中IPSec协议栈的源码,以理解数据包如何经过IPSec处理,以及其在内核中的路由查询和收发过程。 2. 相关数据结构类型 2.1 XFRM_STATE(SA) SA(Security Association)是IPSec的核心概念,...

    Linux内核完全注释:基于0.11内核(V5.0)_0.11内核_linux_linux内核完全注释_Linux内核注释_

    8. 安全与权限:Linux内核的安全模型基于用户ID和组ID,以及文件权限和访问控制列表(ACL)。注释会讨论这些机制以及如何保护系统免受恶意攻击。 9. 调试工具:书中还会介绍一些内核调试工具,如kgdb、sysfs、...

    Linux 内核设计与实现

    1. **系统调用**:Linux内核通过系统调用接口与用户空间交互。系统调用是用户程序请求操作系统服务的唯一途径,包括创建新进程、打开文件、读写磁盘等。内核通过中断处理机制来执行这些操作,确保了用户程序与内核的...

    Linux内核设计与实现(第3版) -- 中文+英文

    《Linux内核设计与实现》是一本深入了解Linux操作系统内核的经典著作,特别是在其第三版中,作者们提供了关于Linux内核的最新发展和深入解析。这本书对于那些希望深入理解操作系统原理,尤其是对Linux内核感兴趣的...

    Linux内核设计与实现(第三版中文高清带目录).pdf.zip

    4. **网络协议栈**:Linux内核支持完整的TCP/IP协议栈,包括网络接口层、网络层、传输层和应用层。读者将了解到网络数据包的处理流程,socket接口的使用,以及网络设备驱动的编写。 5. **设备驱动**:设备驱动程序...

    Linux内核设计与实现 Linux Kernel Development 第三版

    7. **网络栈**:分析了Linux内核中的网络协议栈架构,包括TCP/IP协议栈的实现细节。 8. **调试与优化**:提供了多种调试工具和技术,帮助开发者定位和解决内核中的问题,同时介绍了一些性能优化的策略。 #### 书籍...

Global site tag (gtag.js) - Google Analytics