linux解析任务堆栈方法,Linux系统堆栈讲解

Linux系统经过长时间的发展,很多用户都很了解Linux系统了,这里我发表一下Linux系统中共使用了四种堆栈个人理解,和大家讨论讨论。

一    系统引导初始化临时使用的堆栈

二    进入保护模式后提供内核程序始化使用的堆栈,该堆栈也是后来任务0使用的用户态堆栈

三    每个任务通过系统调用,执行内核程序时使用的堆栈,称之为任务的内核态堆栈,每个任务都有自己独立的内核态堆栈

四    任务在用户态执行的堆栈,位于任务(进程 )逻辑地址空间近末端处 使用多个栈或在不同情况下使用不同栈的主要原因

(一)由于从实模式进入保护模式,使得CPU对内存寻址访问方式发生了变化,因此需要重新设置堆栈区域

(二) 为了解决不同CPU特权级共享使用堆栈带来的保护问题,执行0级的内核代码和执行3级的用户代码需要使用不同的栈。当一个任务进入内核态运行时,就会使用其TSS段中给出的特权级0的堆栈指针tss.ss0.tss.esp0,即内核栈,原用户栈指针会保存在内核栈中,而当从内核态返回用户态时,就会恢复使用用户态的堆栈 以下分别说明。

开机初始化时(bootsect.s,setup.s) 当bootsect代码被ROM BIOS引导加载到物理内存0x7c00处时,并没有设置堆栈段,程序也没有使用堆栈,直到bootsect被移动到0x9000:0处时,才把堆栈段寄存器SS设置为0x9000,堆栈指针esp寄存器设置为0xff00,所以堆栈堆栈在0x9000:0xff00处(boot/bootsect.s L61,62)setup.s也使用这个堆栈

进入保护模式时候(head.s,L31) 此时堆栈段被设置为内核数据段(0x10),堆栈指针esp设置成指向user_stack数组(sched.c L67~72)的顶端,保留了1页内存作为堆栈使用

初始化时(main.c) 在执行move_to_user_mode()代码把控制权移交给任务0之前,系统一直使用上述堆栈,而在执行过move_to_user_mode()之后,main.c的代码被“切换”成任务0中执行。通过执行fork()系统调用,main.c中的init()将在任务1中执行,并使用任务1的堆栈,而main()本身则在被“切换”成为任务0后,仍热继续使用上述内核程序自己的堆栈作为任务0的用户态堆栈。

任务的堆栈 每个任务都有两个堆栈,分别用于用户态和内核态程序的执行,并且分别称为用户态堆栈和内核态堆栈。 除了处于不同CPU特权级中,这两个堆栈之间的主要区别在于任务的内核态堆栈很小,所保存的数据最多不能超过4096个字节,而任务的用户态堆栈却可以在用户的64MB空间中延伸

在用户态运行时 每个任务(除了任务0和任务1)有自己的64MB地址空间,当一个任务(进程)刚被创建时,它的用户态堆栈指针被设置在其地址空间的靠近末端部分,应用程序在用户态下运行时就一直使用这个堆栈,实际物理地址内存则由CPU分页机制确定。

在内核态运行时 每个任务有其自己的内核态堆栈,用于任务在内核代码中执行期间。其所在的线性地址中位置由该任务TSS段中ss0和esp0两个字段指定,任务内核态堆栈被设置在位于其任务数据结构所在页面的末端,即于任务的任务数据结构(task_struct)放在同一页面中,参见kernel/fork.c L93 p->tss.esp0 = PAGE_SIZE + (long)p; p->tss.ss0 = 0x10

*为什么从主存区申请得来的用于保存任务数据结构的一页内存也能被设置成内核数据段中的数据呢?就是说tss.ss0为什么可以是0x10? 用户内核态仍然属于内核数据空间,在head.s中设置内核代码段和数据段的描述符,段长度都设置成了16MB,这个长度值是Linux0.11内核所能支持的最大物理内存长度(head.s,110开始的注释),所以,内核代码可以寻址到整个物理内存范围中的任何位置,当然也包括主存区,每当任务执行内核程序而需要使用其内核栈时,CPU就会利用TSS结构把它的内核态堆栈设置成由tss.ss0和tss.esp0这两个值构成

任务0(空闲进程idle)和任务1(初始化进程init)的堆栈

任务0和任务1的代码段和数据段相同,限长都是640KB,但它们被映射到不同的线性地址空间,任务0的段基址从线性地址0开始,而任务1的段基址从64MB开始,但他们全部映射到物理地址0~640KB范围中,这个地址也就是内核代码和基本数据所存放的地方,在执行了move_to_user_mode()后,任务0和任务1的内核态堆栈分别位于各自任务数据结构所在页面的末端,而任务0的用户态堆栈就是前面进入保护模式后使用的堆栈,即user_stack[]数组的位置,由于任务1在创建时复制了任务0的用户堆栈,所以刚开始时任务0和任务1共享使用同一个用户堆栈空间,但是当任务1开始运行时,写时复制机制会为任务1另行分配主存区页面作为堆栈空间使用,只有到这个时候,任务1才开始使用自己独立的用户堆栈内存页面,因此任务0的堆栈需要在任务1实际开始使用之前保持干净,即任务0此时不能使用堆栈,以确保复制的堆栈页面中不含任务0的数据

这样你就学会Linux系统中使用了四种堆栈知识了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
如果工程很大,头文件很多,而有几个头文件又经常要用的,那么: 1、把这些头文件全部写到一个头文件中,比如:preh.h 2、写一个preh.c,里面的包含库文件,只要一句话#include"preh.h" 3、对于preh.c,在project settings 里面设置creat precompilesd headers ,对于其他.c文件,设置use precompiled header file 。 预编译头文件:就是把一个工程中的那一部分代码,预先编译好放在一个文件里(通常是以.pch为扩展名的),这个文件就成为预编译头文件。这些预先编译好的代码可以是任何的C/C++代码,甚至是inline的函数,但必须是稳定的在工程开发的过程中不会被经常改变。 编译器是以文件为单位编译的,一个文件经过修改后,会重新编译整个文件,当然在这个文件里包含的所有头文件中的东西都要重新处理一遍 预编译头的作用: 根据上文介绍,预编译头文件的作用当然就是提高便宜速度了,有了它你没有必要每次 都编译那些不需要经常改变的代码。编译性能当然就提高了。 预编译头的使用: 要使用预编译头,我们必须指定一个头文件,这个头文件包含我们不会经常改变的 代码和其他的头文件,然后我们用这个头文件来生成一个预编译头文件(.pch文件) 想必大家都知道 StdAfx.h这个文件。很多人都认为这是VC提供的一个“系统级别”的 ,编译器带的一个头文件。其实不是的,这个文件可以是任何名字的。我们来考察一个 典型的由AppWizard生成的MFC Dialog Based 程序的预编译头文件。(因为AppWizard 会为我们指定好如何使用预编译头文件,默认的是StdAfx.h,这是VC起的名字)。我们 会发现这个头文件里包含了以下的头文件: #include // MFC core and standard components #include // MFC extensions #include // MFC Automation classes #include // MFC support for Internet Explorer 4 Common Controls #include 这些正是使用MFC的必须包含的头文件,当然我们不太可能在我们的工程中修改这些头文 件的,所以说他们是稳定的。 那么我们如何指定它来生成预编译头文件。我们知道一个头文件是不能编译的。所以我 们还需要一个cpp文件来生成.pch 文件。这个文件默认的就是StdAfx.cpp。在这个文件 里只有一句代码就是:#include “Stdafx.h”。原因是理所当然的,我们仅仅是要它能 够编译而已?D?D?D也就是说,要的只是它的.cpp的扩展名。 我们可以用/Yc编译开关来指 定StdAfx.cpp来生成一个.pch文件,通过/Fp编译开关来指定生成的pch文件的名字。打 开project ->Setting->C/C++ 对话框。把Category指向Precompiled Header。在左边的 树形视图里选择整个工程  Project Options(右下角的那个白的地方)可以看到 /Fp “debug/PCH.pch”,这就是指 定生成的.pch文件的名字,默认的通常是 .pch(我的示例工程名就是PCH)。 然后,在左边的树形视图里选择StdAfx.cpp.//这时只能选一个cpp文件! 这时原来的Project Option变成了 Source File Option(原来是工程,现在是一个文件 ,当然变了)。在这里我们可以看到 /Yc开关,/Yc的作用就是指定这个文件来创建一个 Pch文件。/Yc后面的文件名是那个包含了稳定代码的头文件,一个工程里只能有一个文 件的可以有YC开关。VC就根据这个选项把 StdAfx.cpp编译成一个Obj文件和一个PCH文件 。 然后我们再选择一个其它的文件来看看,//其他cpp文件 在这里,Precomplier 选择了 Use ⋯⋯⋯一项,头文件是我们指定创建PCH 文件的stda fx.h 文件。事实上,这里是使用工程里的设置,(如图1)/Yu”stdafx.h”。 这样,我们就设置好了预编译头文件。也就是说,我们可以使用预编译头功能了。以 下是注意事项: 1):如果使用了/Yu,就是说使用了预编译,我们在每个.cpp文件的最开头,我强调一遍 是最开头,包含 你指定产生pch文件的.h文件(默认是stdafx.h)不然就会有问题。如 果你没有包含这个文件,就告诉你Unexpected file end. 如果你不是在最开头包含的, 你自己试以下就知道了,绝对有很惊人的效果⋯.. fatal error C1010: unexp
Linux内核中,每个进程都有自己的内核堆栈,用于保存当前进程的执行状态、寄存器、局部变量等信息。当内核执行系统调用、中断处理、异常处理等操作时,会使用内核堆栈来保存相关信息。 以下是Linux内核堆栈解析方法: 1. 确定内核堆栈地址:在Linux中,内核堆栈通常位于进程控制块(PCB)中的内核指针(kernel stack pointer)所指向的地址。可以通过查看进程的PCB结构体中的kernel_stack成员来获取内核堆栈地址。 2. 获取堆栈帧指针:堆栈帧指针(frame pointer)是指向当前堆栈帧底部的指针,用于确定当前堆栈帧的大小和位置。可以通过读取当前CPU寄存器ebp(x86架构)或者r29(ARM架构)中的值来获取堆栈帧指针。 3. 解析堆栈帧:在堆栈帧中,局部变量、函数参数、返回值等信息都保存在中。可以通过指针运算和类型转换等方法来访问这些信息。需要注意的是,由于内核堆栈是内核态的,因此在解析过程中需要特别小心,防止出现悬垂指针、越界访问等问题。 4. 调试工具:除了手动解析堆栈外,还可以使用调试工具来辅助解析。例如,可以使用gdb调试器的bt命令来打印当前进程的堆栈回溯信息,或者使用系统调用ptrace来跟踪进程的堆栈信息。 总之,在Linux内核中解析堆栈需要一定的经验和技能,需要特别小心,以避免出现问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值