Linux内核堆栈调用实现分析

1 内核线程

内核为每个线程分配8K的栈空间, 在每个堆栈的顶部放着struct thread_info 结构体,用来保存线程相关信息.

其中有几个重要变量:

Preempt_count :

此变量分为四部分

0-7bit :当前进程是否能抢占的标志

8-15bit:softirq  使能标志

16-23bit :hardirq 使能标志

24bit:PREEMPT_ACTIVE标志位(原子上下文标志位??)

Task:  进程相关的结构,包含更加丰富的信息

Cpu_context :cpu 寄存器值,这个应该是当前进程被切换时,保留下来的线程执行场景.

struct thread_info {

unsignedlong     flags;     /* low level flags */

int        preempt_count;    /* 0 => preemptable, <0 => bug */

mm_segment_t      addr_limit;   /* address limit */

structtask_struct   *task;     /* main task structure */

__u32         cpu;       /*cpu */

structcpu_context_save  cpu_context;  /* cpu context */

……………………….

}

获取当前线程/进程

只需要获得当前sp指针,然后进行8k字节对齐即可找到thread_info结构

register unsignedlong sp asm ("sp");

return (structthread_info *)(sp & ~(THREAD_SIZE - 1));

内核提供的相关函数结构

static inline struct thread_info*current_thread_info(void)

以及current宏用于获取当前进程结构体.

c 语言中获取cpsr寄存器

static inline unsigned longarch_local_cpsr_save(void)

{

unsignedlong flags ;

asmvolatile( "   mrs %0, cpsr   @ arch_local_irq_save\n"

:: "r" (flags): "memory", "cc");

returnflags;

}

2 函数调用时stack frame

每一个进程都有自己的栈。考虑进程执行时发生函数调用的场景,母函数和子函数使用的是同一个栈,在通常的情况下,并不需要区分母函数和子函数分别使用了栈的哪个部分。但是,当需要在执行过程中对函数调用进行backtrace的时候,这一信息就很重要了。

简单的说,stack frame就是一个函数所使用的stack的一部分,所有函数的stack frame串起来就组成了一个完整的栈。stack frame的两个边界分别由FP和SP来限定。

通过FP指针就可以找出所有的backtrace过程

在程序执行过程中(通常是发生了某种意外情况而需要进行调试),通过SP和FP所限定的 stackframe,就可以得到母函数的SP和FP,从而得到母函数的stack frame(PC,LR,SP,FP会在函数调用的第一时间压栈),以此追溯,即可得到所有函数的调用顺序。

要内核支持FP指针必须打开CONFIG_FRAME_POINTER配置

3内核save_stack_trace分析

voidsave_stack_trace(struct stack_trace *trace)

{

save_stack_trace_tsk(current, trace);

}

接着分析:

voidsave_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace)

{

struct stack_trace_data data;

struct stackframe frame;

data.trace = trace;

data.skip = trace->skip;//设置需要忽视的调用级数,一般设置为0

if (tsk != current) {

#ifdefCONFIG_SMP

/*

* What guarantees do we have here that ‘tsk‘is not

* running on another CPU?  For now, ignore it as we

* can‘t guarantee we won‘t explode.

*/

//如果不是保存当前cpu上的当前进程,那么是很难确定tsk进程寄存器的值的,

也许时刻都在变化.

if (trace->nr_entries <trace->max_entries)

trace->entries[trace->nr_entries++]= ULONG_MAX;

return;

#else //在单cpu时,tsk进程已经被切换,可以追溯backtrace

data.no_sched_functions = 1;

frame.fp = thread_saved_fp(tsk);

frame.sp = thread_saved_sp(tsk);

frame.lr = 0;              /* recovered from the stack */

frame.pc = thread_saved_pc(tsk);

#endif

} else {

register unsigned long current_sp asm("sp");//通过sp获取当前堆栈指针

//__builtin_frame_address(0)返回当前函数的FP指针

//__builtin_return_address(0)返回当前函数的返回地址(LR)

data.no_sched_functions = 0;

frame.fp = (unsignedlong)__builtin_frame_address(0);

frame.sp = current_sp;

frame.lr = (unsignedlong)__builtin_return_address(0);

frame.pc = (unsignedlong)save_stack_trace_tsk;//通过函数名获取到当前pc

}

walk_stackframe(&frame, save_trace,&data);//进行堆栈遍历

if (trace->nr_entries <trace->max_entries)

trace->entries[trace->nr_entries++]= ULONG_MAX;

}

其中save_trace主要保存当前pc到数组中.

staticint save_trace(struct stackframe *frame, void *d)

{

struct stack_trace_data *data = d;

struct stack_trace *trace = data->trace;

unsigned long addr = frame->pc;

if (data->no_sched_functions &&in_sched_functions(addr))

return 0;

if (data->skip) {//如果有设置skip,则会跳过当前调用

data->skip--;

return 0;

}

trace->entries[trace->nr_entries++] =addr;//保存当前pc

return trace->nr_entries >=trace->max_entries;

}

接着分析walk_stackframe函数

void notrace walk_stackframe(struct stackframe *frame,

int (*fn)(struct stackframe *, void *), void *data)

{

while (1) {

int ret;

if (fn(frame, data))//调用save_trace保存当前pc

break;

ret = unwind_frame(frame);//把FP指针移到上一个函数

if (ret < 0)

break;

}

}

intnotrace unwind_frame(struct stackframe *frame)

{

unsigned long high, low;

unsigned long fp = frame->fp;

/* only go to a higher address on the stack */

low = frame->sp;//当前堆栈末端

high = ALIGN(low, THREAD_SIZE);//当前堆栈顶端

/* check current frame pointer is within bounds*/

if (fp < low + 12 || fp > high - 4)

return -EINVAL;

/* restore the registers from the stack frame*/

由上面的堆栈图可知,这几个指针在堆栈上的存放顺序为

Pc,lr,sp,fp

frame->fp = *(unsigned long *)(fp - 12);

frame->sp = *(unsigned long *)(fp - 8);

frame->pc = *(unsigned long *)(fp - 4);

return 0;

}

3.1 获取stack实例

voidaee_get_traces(char *msg)

{

structstack_trace trace;

inti;

intoffset;

if(trace_entry_ptr == NULL)

return;

memset(trace_entry_ptr,0, MAX_STACK_TRACE_DEPTH * 4);

trace.entries= trace_entry_ptr;

/*savebacktraces */

trace.nr_entries= 0;

trace.max_entries= 32;//32级调用

trace.skip= 0;

save_stack_trace_tsk(current,&trace);

for(i = 0; i < trace.nr_entries; i++) {//current

offset= strlen(msg);

//根据pc,通过%pf , %pf就可以打印出函数名

snprintf(msg+ offset, KERNEL_REPORT_LENGTH - offset, "[<%p>]%pS\n",

(void *)trace.entries[i], (void*)trace.entries[i]);

}

}

时间: 2024-12-17 11:28:34

Linux内核堆栈调用实现分析的相关文章

Linux内核抢占实现机制分析【转】

Linux内核抢占实现机制分析 转自:http://blog.chinaunix.net/uid-24227137-id-3050754.html [摘要]本文详解了Linux内核抢占实现机制.首先介绍了内核抢占和用户抢占的概念和区别,接着分析了不可抢占内核的特点及实时系统中实现内核抢占的必要性.然后分析了禁止内核抢占的情况和内核抢占的时机,最后介绍了实现抢占内核所做的改动以及何时需要重新调度. [关键字]内核抢占,用户抢占,中断, 实时性,自旋锁,抢占时机,调度时机,schedule,pree

Linux内核抢占实现机制分析

Sailor_forever  [email protected] 转载请注明 http://blog.csdn.net/sailor_8318/archive/2008/09/03/2870184.aspx [摘要]本文详解了Linux内核抢占实现机制.首先介绍了内核抢占和用户抢占的概念和区别,接着分析了不可抢占内核的特点及实时系统中实现内核抢占的必要性.然后分析了禁止内核抢占的情况和内核抢占的时机,最后介绍了实现抢占内核所做的改动以及何时需要重新调度. [关键字]内核抢占,用户抢占,中断, 

(转)Linux内核基数树应用分析

Linux内核基数树应用分析 ——lvyilong316 基数树(Radix tree)可看做是以二进制位串为关键字的trie树,是一种多叉树结构,同时又类似多层索引表,每个中间节点包含指向多个节点的指针数组,叶子节点包含指向实际对象的指针(由于对象不具备树节点结构,因此将其父节点看做叶子节点). 图1是一个基数树样例,该基数树的分叉为4(2^2),树高为4,树的每个叶子结点用来快速定位8位文件内偏移,可以定位4x4x4x4=256(叶子节点的个数)页,如:图中虚线对应的两个叶子结点的路径组成值

Linux内核--网络栈实现分析(七)--数据包的传递过程(下)

本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7545855 更多请查看专栏,地址http://blog.csdn.net/column/details/linux-kernel-net.html 作者:闫明 注:标题中的”(上)“,”(下)“表示分析过程基于数据包的传递方向:”(上)“表示分析是从底层向上分析.”(下)“表示分析是从上向下分析. 在博文Linux内核--网络栈

Linux内核--网络栈实现分析(十一)--驱动程序层(下)

本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7555870 更多请查看专栏,地址http://blog.csdn.net/column/details/linux-kernel-net.html 作者:闫明 注:标题中的”(上)“,”(下)“表示分析过程基于数据包的传递方向:”(上)“表示分析是从底层向上分析.”(下)“表示分析是从上向下分析. 在博文Linux内核--网络栈

Linux内核态抢占机制分析(转)

Linux内核态抢占机制分析  http://blog.sina.com.cn/s/blog_502c8cc401012pxj.html 摘 要]本文首先介绍非抢占式内核(Non-Preemptive Kernel)和可抢占式内核(Preemptive Kernel)的区别.接着分析Linux下有两种抢占:用户态抢占(User Preemption).内核态抢占(Kernel Preemption).然后分析了在内核态下:如何判断能否抢占内核(什么是可抢占的条件):何时触发重新调度(何时设置可抢

linux内核网络协议栈架构分析,全流程分析-干货

https://download.csdn.net/download/wuhuacai/10157233 https://blog.csdn.net/zxorange321/article/details/75676063 LINUX内核协议栈分析 目  录 1      说明...4 2      TCP协议...4 2.1       分层...4 2.2       TCP/IP的分层...5 2.3       互联网的地址...6 2.4       封装...7 2.5       

Linux内核--网络栈实现分析(一)--网络栈初始化

本文分析基于内核Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7488828 更多请看专栏,地址http://blog.csdn.net/column/details/linux-kernel-net.html 作者:闫明 以后的系列博文将深入分析Linux内核的网络栈实现原理,这里看到曹桂平博士的分析后,也决定选择Linux内核1.2.13版本进行分析. 原因如下: 1.功能和网络栈层次

Linux内核的idle进程分析

1. idle是什么 简单的说idle是一个进程,其pid号为 0.其前身是系统创建的第一个进程.也是唯一一个没有通过fork()产生的进程. 在smp系统中,每一个处理器单元有独立的一个执行队列,而每一个执行队列上又有一个idle进程,即有多少处理器单元.就有多少idle进程. 系统的空暇时间,事实上就是指idle进程的"执行时间".既然是idle是进程.那我们来看看idle是怎样被创建,又详细做了哪些事情? 2. idle的创建 我们知道系统是从BIOS加电自检,载入MBR中的引导