分享

内核栈与用户栈 kernel thread

 WUCANADA 2013-07-15
Linux下每个用户空间进程(不是kernel thread)都有两个堆栈,一个内核栈,一个系统栈。
其中内核栈在创建进程或者线程(do_fork)是创建,在2.6内核中,他的内容如下:
union thread_union {
   struct thread_info thread_info;
   unsigned long stack[THREAD_SIZE/sizeof(long)];
};
由此可见,此内核栈的高端用于作为堆栈,底端用于存放thread_info(不是task_struct)。此堆栈用于存放进程在内核时的 call frames或者接收到中断时的现场状态(pt_regs),在有些体系结构下,也存放同步上下文切换时的状态(switch_stack)。内核栈不能动态增长。

用户栈在进程调用execve的时候(参见fs/binfmt_*.c文件中的load_binary函数,大概是这个函数)创建,对于用 clone创建的线程来说,它可以由用户来指定,用户栈和内核没有任何关系,它用于存放进程在用户空间时的call frames。用户堆栈可以动态增长。

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System VSunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

线程是独立调度和分派的基本单位。线程可以操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux Portable Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。

同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。

一个进程可以有很多线程,每条线程并行执行不同的任务。

每个进程都有自己的 3 G 用户空间,它们共享1GB的内核空间。当一个进程从用户空间进入内核空间时,它就不再有自己的进程空间了.
对内核线程的虚拟空间总结一下:
1、创建的时候:
父进程是用户进程,则mm和active_mm均共享父进程的,然后内核线程一般调用daemonize
父进程是内核线程,则mm和active_mm均为NULL
总之,内核线程的mm = NULL;进程调度的时候以此为依据判断是用户进程还是内核线程。
2、进程调度的时候
如果切换进来的是内核线程,则置active_mm为切换出去的进程的active_mm;
如果切换出去的是内核线程,则置active_mm为NULL。
linux在创建用户任务的时候,给每个任务都分配了一个kernel modestack。一个运行在用户态的任务如果被一个IRQ打断,中断处理要做一次堆栈切换。这时linux好像使用了任务的kernel modestack,也就是说linux系统中没有一个唯一的系统堆栈,而是每一个任务都有一个系统堆栈,中断处理的栈使用的就是被打断任务的系统堆栈。 内核线程也是进程,只不过没有自己的用户空间,但task_struct和内核堆栈还是得有的,要不怎么运行呢?
[1.内核在主动进行进程调度时,可以自己设置将要投入运行进程的 sp0为TSS段中的sp0,则该用户进程在进入内核后使用的是它自身的系统堆栈,但如果cpu运行在某一用户进程时,而为另一用户进程服务的外部中断发 生了,在进入内核后使用的是当前用户进程的系统堆栈,还是中断服务的另一用户进程的系统堆栈呢?
2操作系统映象是否拥有自己的堆栈空间?还是利用用户进程的系统堆栈?
回答: 1。外部中断不是为某个用户进程服务的,是为整个操作系统服务的,它始终用当前进程的核心堆栈。??????中断栈

从《深入Linux内核构架》中可以知道:内核在IA-32平台上,早期(2.6.36及之前)内核如 果配置了4K内核栈(CONFIG_4KSTACKS)(默认是8K),对于常规的内核工作以及IRQ处理例程共用这个栈来说似乎有点不够用,所有引入了 两个栈:硬件IRQ栈和软件IRQ栈。在这种情况下,当内核进入中断之后,检测自己所在的栈是内核栈还是中断栈。如果是中断栈(中断嵌套情况)就去执行中 断例程;如果是内核栈就切换到中断栈,同时复制当前内核栈中的部分thread_info数据到中断栈。
   但是2.6.36之后的内核就不再有4K内核栈的配置,对于IA-32统一使用8K内核栈,并总是使用两个独立的8K中断栈。这样的改变应该是由于计算机性能的提高、内存的扩大(4G内存已经很平常,16G、32G内存也已不新鲜)以及软件的复杂度提高(对栈的需求增加)。


跟踪了ARM构架的内核代码发现其中(arch/arm/kernel/irq.c)并没有和x86类似的栈转换设计。也就是说:ARM并没有独立的中断栈,中断共用当前进程的内核栈。

Linux 0.11 系统中共使用了四种堆栈。
一种是系统初始化时临时使用的堆栈;
一种是供内核程序自己使用的堆栈(内核堆栈),只有一个,位于系统地址空间固定的位置,也是后来任务0 的用户态堆栈;
另一种是每个任务通过系统调用,执行内核程序时使用的堆栈,我们称之为任务的内核态堆栈,每个任务都有自己独立的内核态堆栈;
最后一种是任务在用户态执行的堆栈,位于任务(进程)地址空间的末端。

也就是说,schedule()的时候发生上下文切换,何时调用schedule()?
1.程序员显示调用schedule().
2.内核提供need_reshed标志表明是否需要重新执行一次调度,每个进程都包含一个need_reshed标志(2.6中移入thread_info某个特殊变量的一位)。
1)当某个进程耗尽时间片时,scheduler_tick()会设置这个标志。
2)当一个优先级高的程序进入可执行状态的时候,try_to_wake_up()也会设置这个标志。 3)再返回用户空间以的时候,内核会检查need_reshed。 4)从中断返回的时候,内核会检查need_reshed

实时调度策略
Linux提供了两种实时调度策略:SCHED_FIFO和SCHED_RR。普通、非实时的调度策略是SCHED_NORMAL。
SCHED_FIFO级的进程会比任何SCHED_NORMAL级的进程都先得到调度,一旦一个SCHED_FIFO级进程处于可执行状态,就会一直执 行,直到它自己受阻塞或显式地释放处理器。它不基于时间片,可以一直执行下去,只有较高优先级的SCHED_FIFO或者SCHED_RR任务才能抢占 SCHED_FIFO任务。
SCHED_RR是带时间片的SCHED_FIFO,是一种实时轮流调度算法。
这两种实时算法实现的都是静态优先级,内核不为之计算动态优先级。
Linux的实时调度算法提供一种软实时的工作方式,内核调度进程,尽力使进程在它的限定时间到来前运行,但内核不保证总能满足这些进程的要求。



    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多