进程的内核栈是什么?浅谈Linux的进程内核栈linux 内核栈




进程的内核栈是什么?浅谈Linux的进程内核栈linux 内核栈

2022-07-20 20:23:48 网络知识 官方管理员

在重游《LDD3》的时候,又发现了一个当年被我忽略的一句话:

内核具有非常小的栈,它可能只和一个4096字节大小的页那样小

针对这句话,我简单地学习了一下进程的内核栈

什么是进程的内核栈?

在每一个进程的生命周期中,必然会通过到系统调用陷入内核。在执行系统调用陷入内核之后,这些内核代码所使用的栈并不是原先用户空间中的栈,而是一个内核空间的栈,这个称作进程的内核栈。

比如,有一个简单的字符驱动实现了open方法。在这个驱动挂载后,应用程序对那个驱动所对应的设备节点执行open操作,这个应用程序的open其实就通过glib库调用了Linux的open系统调用,执行系统调用陷入内核后,处理器转换为了特权模式(具体的转换机制因构架而异,对于ARM来说普通模式和用户模式的的栈针(SP)是不同的寄存器),此时使用的栈指针就是内核栈指针,他指向内核为每个进程分配的内核栈空间。

内核栈的作用

我个人的理解是:在陷入内核后,系统调用中也是存在函数调用和自动变量,这些都需要栈支持。用户空间的栈显然不安全,需要内核栈的支持。此外,内核栈同时用于保存一些系统调用前的应用层信息(如用户空间栈指针、系统调用参数)。

内核栈与进程结构体的关联

每个进程在创建的时候都会得到一个内核栈空间,内核栈和进程的对应关系是通过2个结构体中的指针成员来完成的:

(1)structtask_struct

在学习Linux进程管理肯定要学的结构体,在内核中代表了一个进程,其中记录的进程的所有状态信息,定义在Sched.h(include\linux)。

其中有一个成员:void*stack;就是指向下面的内核栈结构体的栈底。

在系统运行的时候,宏current获得的就是当前进程的structtask_struct结构体。

(2)内核栈结构体unionthread_union

unionthread_union{

structthread_infothread_info;

unsignedlongstack[THREAD_SIZE/sizeof(long)];

};

其中structthread_info是记录部分进程信息的结构体,其中包括了进程上下文信息:

/*

*lowleveltaskdatathatentry.Sneedsimmediateaccessto.

*__switch_to()assumescpu_contextfollowsimmediatelyaftercpu_domain.

*/

structthread_info{

unsignedlongflags;/*lowlevelflags*/

intpreempt_count;/*0=>preemptable,<0=>bug*/

mm_segment_taddr_limit;/*addresslimit*/

structtask_struct*task;/*maintaskstructure*/

structexec_domain*exec_domain;/*executiondomain*/

__u32cpu;/*cpu*/

__u32cpu_domain;/*cpudomain*/

structcpu_context_savecpu_context;/*cpucontext*/

__u32syscall;/*syscallnumber*/

__u8used_cp[16];/*threadusedcopro*/

unsignedlongtp_value;

structcrunch_statecrunchstate;

unionfp_statefpstate__attribute__((aligned(8)));

unionvfp_statevfpstate;

#ifdefCONFIG_ARM_THUMBEE

unsignedlongthumbee_state;/*ThumbEEHandlerBaseregister*/

#endif

structrestart_blockrestart_block;

};

关键是其中的task成员,指向的是所创建的进程的structtask_struct结构体

而其中的stack成员就是内核栈。从这里可以看出内核栈空间和thread_info是共用一块空间的。如果内核栈溢出,thread_info就会被摧毁,系统崩溃了~~~

内核栈---structthread_info----structtask_struct三者的关系入下图:

Linux,进程内核栈

内核栈的产生

在进程被创建的时候,fork族的系统调用中会分别为内核栈和structtask_struct分配空间,调用过程是:

fork族的系统调用--->do_fork--->copy_process--->dup_task_struct

在dup_task_struct函数中:

staticstructtask_struct*dup_task_struct(structtask_struct*orig)

{

structtask_struct*tsk;

structthread_info*ti;

unsignedlong*stackend;

interr;

prepare_to_copy(orig);

tsk=alloc_task_struct();

if(!tsk)

returnNULL;

ti=alloc_thread_info(tsk);

if(!ti){

free_task_struct(tsk);

returnNULL;

}

err=arch_dup_task_struct(tsk,orig);

if(err)

gotoout;

tsk->stack=ti;

err=prop_local_init_single(&tsk->dirties);

if(err)

gotoout;

setup_thread_stack(tsk,orig);

......

其中alloc_task_struct使用内核的slab分配器去为所要创建的进程分配structtask_struct的空间

而alloc_thread_info使用内核的伙伴系统去为所要创建的进程分配内核栈(unionthread_union)空间

注意:

后面的tsk->stack=ti;语句,这就是关联了structtask_struct和内核栈

而在setup_thread_stack(tsk,orig);中,关联了内核栈和structtask_struct:

staticinlinevoidsetup_thread_stack(structtask_struct*p,structtask_struct*org)

{

*task_thread_info(p)=*task_thread_info(org);

task_thread_info(p)->task=p;

}

内核栈的大小

由于是每一个进程都分配一个内核栈空间,所以不可能分配很大。这个大小是构架相关的,一般以页为单位。其实也就是上面我们看到的THREAD_SIZE,这个值一般为4K或者8K。对于ARM构架,这个定义在Thread_info.h(arch\arm\include\asm),

#defineTHREAD_SIZE_ORDER1

#defineTHREAD_SIZE8192

#defineTHREAD_START_SP(THREAD_SIZE-8)

所以ARM的内核栈是8KB

在(内核)驱动编程时需要注意的问题:

由于栈空间的限制,在编写的驱动(特别是被系统调用使用的底层函数)中要注意避免对栈空间消耗较大的代码,比如递归算法、局部自动变量定义的大小等等


发表评论:

最近发表
网站分类
标签列表