进程栈和内核栈

1.进程的堆栈

       内核在创建进程的时候,在创建task_struct的同时,会为进程创建相应的堆栈。每个进程会有两个栈,一个用户栈,存在于用户空间,一个内核栈,存在于内核空间。当进程在用户空间运行时,cpu堆栈指针寄存器里面的内容是用户堆栈地址,使用用户栈;当进程在内核空间时,cpu堆栈指针寄存器里面的内容是内核栈空间地址,使用内核栈。

2.进程用户栈和内核栈的切换(堆栈指针寄存器切换

      当进程因为中断或者系统调用而陷入内核态之行时,进程所使用的堆栈也要从用户栈转到内核栈。

      进程陷入内核态后,先把用户态堆栈的地址保存在内核栈之中,然后设置堆栈指针寄存器的内容为内核栈的地址,这样就完成了用户栈向内核栈的转换;当进程从内核态恢复到用户态之行时,在内核态之行的最后将保存在内核栈里面的用户栈的地址恢复到堆栈指针寄存器即可。这样就实现了内核栈和用户栈的互转。

      那么,我们知道从内核转到用户态时用户栈的地址是在陷入内核的时候保存在内核栈里面的,但是在陷入内核的时候,我们是如何知道内核栈的地址的呢?

     关键在进程从用户态转到内核态的时候,进程的内核栈总是空的。这是因为,当进程在用户态运行时,使用的是用户栈,当进程陷入到内核态时,内核栈保存进程在内核态运行的相关信息,但是一旦进程返回到用户态后,内核栈中保存的信息无效,会全部恢复,因此每次进程从用户态陷入内核的时候得到的内核栈都是空的。所以在进程陷入内核的时候,直接把内核栈的栈顶地址给堆栈指针寄存器就可以了。(这里因为内核栈的栈顶地址是已经知道的

3.内核栈的实现

 每个进程在创建的时候都会得到一个内核栈空间,内核栈和进程的对应关系是通过2个结构体中的指针成员来完成的:
(1)struct task_struct
    在学习Linux进程管理肯定要学的结构体,在内核中代表了一个进程,其中记录的进程的所有状态信息,定义在Sched.h (include\linux)。
    其中有一个成员:void *stack;就是指向下面的内核栈结构体的“栈底”。
    在系统运行的时候,宏current获得的就是当前进程的struct task_struct结构体。

(2)内核栈结构体union thread_union

  1. union thread_union {
  2.     struct thread_info thread_info;
  3.     unsigned long stack[THREAD_SIZE/sizeof(long)];
  4. };

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

  1. /*
  2.  * low level task data that entry.S needs immediate access to.
  3.  * __switch_to() assumes cpu_context follows immediately after cpu_domain.
  4.  */
  5. struct thread_info {
  6.     unsigned long        flags;        /* low level flags */
  7.     int            preempt_count;    /* 0 => preemptable, <=> bug */
  8.     mm_segment_t        addr_limit;    /* address limit */
  9.     struct task_struct    *task;        /* main task structure */
  10.     struct exec_domain    *exec_domain;    /* execution domain */
  11.     __u32            cpu;        /* cpu */
  12.     __u32            cpu_domain;    /* cpu domain */
  13.     struct cpu_context_save    cpu_context;    /* cpu context */
  14.     __u32            syscall;    /* syscall number */
  15.     __u8            used_cp[16];    /* thread used copro */
  16.     unsigned long        tp_value;
  17.     struct crunch_state    crunchstate;
  18.     union fp_state        fpstate __attribute__((aligned(8)));
  19.     union vfp_state        vfpstate;
  20. #ifdef CONFIG_ARM_THUMBEE
  21.     unsigned long        thumbee_state;    /* ThumbEE Handler Base register */
  22. #endif
  23.     struct restart_block    restart_block;
  24. };

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

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

内核栈---struct thread_info----struct task_struct三者的关系入下图:



 内核栈的产生

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

fork族的系统调用--->do_fork--->copy_process--->dup_task_struct
在dup_task_struct函数中:

  1. static struct task_struct *dup_task_struct(struct task_struct *orig)
  2. {
  3.     struct task_struct *tsk;
  4.     struct thread_info *ti;
  5.     unsigned long *stackend;

  6.     int err;

  7.     prepare_to_copy(orig);

  8.     tsk = alloc_task_struct();
  9.     if (!tsk)
  10.         return NULL;

  11.     ti = alloc_thread_info(tsk);
  12.     if (!ti) {
  13.         free_task_struct(tsk);
  14.         return NULL;
  15.     }

  16.      err = arch_dup_task_struct(tsk, orig);
  17.     if (err)
  18.         goto out;

  19.     tsk->stack = ti;

  20.     err = prop_local_init_single(&tsk->dirties);
  21.     if (err)
  22.         goto out;

  23.     setup_thread_stack(tsk, orig);
  24. ......

其中alloc_task_struct使用内核的slab分配器去为所要创建的进程分配struct task_struct的空间
alloc_thread_info使用内核的伙伴系统去为所要创建的进程分配内核栈(union thread_union )空间

注意:
后面的tsk->stack = ti;语句,这就是关联了struct task_struct和内核栈
而在setup_thread_stack(tsk, orig);中,关联了内核栈和struct task_struct:

  1. static inline void setup_thread_stack(struct task_struct *p, struct task_struct *org)
  2. {
  3.     *task_thread_info(p) = *task_thread_info(org);
  4.     task_thread_info(p)->task = p;
  5. }

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

  1. #define THREAD_SIZE_ORDER    1
  2. #define THREAD_SIZE     8192
  3. #define THREAD_START_SP     (THREAD_SIZE - 8)

所以ARM的内核栈是8KB

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

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值