快乐虾
http://blog.csdn.net/lights_joy/
lights@hb165.com
本文适用于
ADSP-BF561
uclinux-2008r1.5-RC3(smp patch)
Visual DSP++ 5.0(update 5)
欢迎转载,但请保留作者信息
内核的任务切换由schedule函数完成,此函数的结构大致为:
asmlinkage void schedule(void)
{
……………………
if (likely(prev != next)) {
next->timestamp = next->last_ran = now;
rq->nr_switches++;
rq->curr = next;
++*switch_count;
prepare_task_switch(rq, next);
prev = context_switch(rq, prev, next);
barrier();
/*
* this_rq must be evaluated again because prev may have moved
* CPUs since it called schedule(), thus the 'rq' on its stack
* frame will be invalid.
*/
finish_task_switch((struct rq *)this_rq(), prev);
} else
spin_unlock_irq(&rq->lock);
…………………….
}
在经过一系列的条件判断后,判断新旧两个任务是否一致,如果不一致则需要将旧任务切换出来,开始执行新任务。在这里prev和next即代表了新旧两个任务的指针。实际的上下文切换将由context_switch完成。
/*
* context_switch - switch to the new MM and the new
* thread's register state.
*/
static /*inline*/ struct task_struct *
context_switch(struct rq *rq, struct task_struct *prev,
struct task_struct *next)
{
struct mm_struct *mm = next->mm;
struct mm_struct *oldmm = prev->active_mm;
/*
* For paravirt, this is coupled with an exit in switch_to to
* combine the page table reload and the switch backend into
* one hypercall.
*/
arch_enter_lazy_cpu_mode();
if (!mm) {
next->active_mm = oldmm;
atomic_inc(&oldmm->mm_count);
enter_lazy_tlb(oldmm, next);
} else
switch_mm(oldmm, mm, next);
if (!prev->mm) {
prev->active_mm = NULL;
WARN_ON(rq->prev_mm);
rq->prev_mm = oldmm;
}
/*
* Since the runqueue lock will be released by the next
* task (which is an invalid locking op but in the case
* of the scheduler it's an obvious special-case), so we
* do an early lockdep release here:
*/
#ifndef __ARCH_WANT_UNLOCKED_CTXSW
spin_release(&rq->lock.dep_map, 1, _THIS_IP_);
#endif
/* Here we just switch the register state and the stack. */
//switch_to(prev, next, prev);
do { \
copy_from_pda(task_info, &task_thread_info(prev)->pda_info); \
copy_to_pda(task_info, &task_thread_info(next)->pda_info); \
(prev) = resume(prev, next); \
} while (0);
return prev;
}
这个函数先切换了mm_struct,然后将PDA,也就是scratch pad sram(4k)的内容保存到老任务的结构体中,再将新任务的PDA复制到scratch pad sram中。最后进行关键的上下文切换resume。
resume是一个定义在arch\blackfin\mach-common\entry.S中的函数:
ENTRY(_resume)
/*
* Beware - when entering resume, prev (the current task) is
* in r0, next (the new task) is in r1.
*/
p0 = r0;
p1 = r1;
[--sp] = rets;
[--sp] = fp;
[--sp] = (r7:4, p5:3);
/* save usp */
p2 = usp;
[p0+(TASK_THREAD+THREAD_USP)] = p2;
/* save current kernel stack pointer */
[p0+(TASK_THREAD+THREAD_KSP)] = sp;
/* save program counter */
r1.l = _new_old_task;
r1.h = _new_old_task;
[p0+(TASK_THREAD+THREAD_PC)] = r1;
/* restore the kernel stack pointer */
sp = [p1+(TASK_THREAD+THREAD_KSP)];
/* restore user stack pointer */
p0 = [p1+(TASK_THREAD+THREAD_USP)];
usp = p0;
/* restore pc */
p0 = [p1+(TASK_THREAD+THREAD_PC)];
jump (p0);
/*
* Following code actually lands up in a new (old) task.
*/
_new_old_task:
(r7:4, p5:3) = [sp++];
fp = [sp++];
rets = [sp++];
/*
* When we come out of resume, r0 carries "old" task, becuase we are
* in "new" task.
*/
rts;
ENDPROC(_resume)
这段代码的注释已经很清楚地说明了整个切换过程,比较有意思的是老线程的PC全部指向_new_old_task这个位置。这样,当此线程重新开始执行的时候,它仍然从_new_old_task这个地方开始执行。
那么用户程序是怎么开始执行的呢?答案在copy_thread。
每一个用户线程的创建,最后都将调用copy_thread这一内核函数:
int
copy_thread(int nr, unsigned long clone_flags,
unsigned long usp, unsigned long topstk,
struct task_struct *p, struct pt_regs *regs)
{
struct pt_regs *childregs;
childregs = (struct pt_regs *) (task_stack_page(p) + THREAD_SIZE) - 1;
*childregs = *regs;
childregs->r0 = 0;
p->thread.usp = usp;
p->thread.ksp = (unsigned long)childregs;
p->thread.pc = (unsigned long)ret_from_fork;
return 0;
}
注意在这里将PC指向了ret_from_fork,也就是说当这个线程首次被切换进来执行的时候,这个函数将被执行。此函数的定义在arch\blackfin\kernel\entry.S中。
ENTRY(_ret_from_fork)
SP += -12;
call _schedule_tail;
SP += 12;
r0 = [sp + PT_IPEND];
cc = bittst(r0,1);
if cc jump .Lin_kernel;
RESTORE_CONTEXT
rti;
.Lin_kernel:
bitclr(r0,1);
[sp + PT_IPEND] = r0;
/* do a 'fake' RTI by jumping to [RETI]
* to avoid clearing supervisor mode in child
*/
r0 = [sp + PT_PC];
[sp + PT_P0] = r0;
RESTORE_ALL_SYS
jump (p0);
ENDPROC(_ret_from_fork)
注意到这里的rti指令,表示要退出中断的执行。那么要退出什么中断呢?这就需要想想在什么情况下会进行schedule了。
1.1 在idle thread中执行切换
从内核启动开始一直执行下来的代码就是一个idle thread,它只在内核态中运行,在初始化完成后演变成一个死循环:
static void noinline __init_refok rest_init(void)
__releases(kernel_lock)
{
………………………….
/*
* The boot idle thread must execute schedule()
* at least one to get things moving:
*/
preempt_enable_no_resched();
schedule();
preempt_disable();
/* Call into cpu_idle with preempt disabled */
cpu_idle();
}
/*
* The idle thread. There's no useful work to be
* done, so just try to conserve power and have a
* low exit latency (ie sit in a loop waiting for
* somebody to say that they'd like to reschedule)
*/
void cpu_idle(void)
{
while (1) {
idle();
preempt_enable_no_resched();
schedule();
preempt_disable();
}
}
当第一次执行schedule时,此时dsp在中断15中运行,且没有加载用户程序,因此调度的只有kernel_init和kthreadd这两个内核线程,在kernel_init里面又创建其它的内核线程。在schedule函数中切换到其它内核线程之后,这些线程也在中断15的状态下运行。即便再切换到idle thread,仍然是中断15的状态。这种状态将一直持续到加载第一个用户程序退出到用户态或者被迫接受内核时钟中断并在更高一级的中断状态下运行。
1.2 在内核时钟中断时执行切换
这是调度最多的一个地方,内核时钟每4ms(CONFIG_HZ = 250)产生一个中断,在这里进行可能的调度。此时有可能有几种情况:
第一种情况是切换到刚加载的用户程序,此时在schedule里面将转而执行_ret_from_fork这一段代码,由于这段代码的后面使用了reti语句,因此DSP将退出时钟中断的执行,进入用户态。
uclinux第一个用户程序的加载(2009-4-23)
uclinux第一个内核线程的运行(2009-4-23)
uclinux内核线程的创建(2009-4-23)
从fork_init看uclinux内核的线程数量限制(2009-4-22)
uclinux内核的任务优先级及其load_weight(2009-4-22)
init_thread_union猜想(2009-1-17)
uclinux2.6(bf561)内核中的current_thread_info(2008/5/12)
分享到:
相关推荐
### uclinux内核中断处理_v0.3 #### 数据结构 **1.1 irq_desc** `irq_desc` 结构体是 uclinux 内核为每个中断定义的数据结构,用于描述中断特性及其处理方式。它与硬件无关,提供了一种抽象的方式来管理不同类型...
1. **内核裁剪**:ucLinux为了适应嵌入式设备资源有限的特性,进行了大量的内核裁剪,去除了许多非必要的服务和模块,如图形用户界面、网络协议栈等,只保留了最基本的系统服务。 2. **内存管理**:ucLinux的内存...
任务状态之间的转换是通过系统内核的调度算法来控制的。 2.2.2 任务调度:任务调度是决定哪个任务应该在何时运行的关键环节。在嵌入式实时操作系统中,任务调度器负责管理任务的优先级、上下文切换以及资源分配。...
UCLinux的中断流程始于内核启动阶段。在内核启动的第二个阶段`setup_arch`函数中,与体系结构相关的初始化工作被执行。对于UCLinux而言,这一步骤至关重要,因为它涉及到中断入口设置以及中断向量表的初始化。 1. *...
针对用户空间实现方案的不足,内核空间实现方案将PPPoE协议栈直接集成到uClinux内核中,提高了数据处理的速度和整体性能。 - **基本架构**:内核空间实现方案通过修改uClinux内核源代码,将PPPoE协议栈集成到内核中...
8. **系统调用**:如果需要调用操作系统的服务,例如延时、获取时间、任务切换等,需要通过系统调用来实现。 9. **调试与优化**:使用Keil的调试工具进行代码调试,观察任务调度、中断处理是否正常,以及性能瓶颈在...
它可以进行多任务的同时跟踪调试,实时检测任务启动,加载任务符号表,使开发者能够全面控制任务运行,并在内核态和任务态之间自由切换。此外,SldView还提供了内存管理、任务绑定、Timer查看、任务时间管理、任务上...
uCLinux内核移植涉及的主要任务包括配置内核以适应目标平台,编译内核生成可执行文件,并下载、运行和调试内核,确保其在目标硬件上正常工作。 异常处理程序返回时,需要恢复被保护的用户寄存器,设置程序状态...
抢占式调度允许高优先级的任务中断正在执行的任务,从而确保关键任务的及时响应,而非抢占式调度则保证任务的连续执行,减少上下文切换的开销。 文件系统支持对于嵌入式设备与PC机的数据交换至关重要。uClinux支持...
嵌入式系统软件设计通常分为两类:无操作系统(OS)的前后台系统和基于OS的多任务切换。前者简单直接,但无法实现复杂的任务调度;后者如RTX51-Tiny、嵌入式Linux、uCLINUX、uC/OS-II、eCos、Nucleus Plus和VxWorks...
当一个高优先级任务就绪时,RTOS会立即切换到该任务,确保关键任务的及时响应。开发者需合理分配任务优先级,避免优先级反转和优先级继承问题,以保持系统的稳定性和效率。 驱动程序开发是嵌入式系统的重要组成部分...
由于其抢占式的实时特性,uCOS-II在性能上接近商业嵌入式实时内核,而且它的任务切换响应时间快,主要取决于所使用的ARM CPU。uCOS-II还提供了一些扩展,如mC/FS文件系统、uC/GUI图形系统和uC/KA、uC/View调试工具,...
由于是抢占式内核,它能在紧迫的时间限制下切换任务,确保关键任务的及时响应。 在嵌入式操作系统领域,ucos2与众多其他系统如Linux、WinCE、VxWorks等并存。WinCE,即Windows Embedded Compact,是由Microsoft开发...
2. **OS_CPU_C.C**:这个C文件包含了与处理器相关的C语言函数,如空闲任务函数、任务切换函数等。 - **任务切换函数**:`OSSchedCtxSw()`用于在两个任务间进行上下文切换。 - **空闲任务函数**:`OSIntExit()`用于...
- **内核技术**:uClinux采用了特殊的内存管理技术,通过静态地址映射来绕过无MMU的问题。 - **移植方案**:针对不同的硬件平台,需要定制内核配置,调整驱动程序等。 - **应用程序开发**:提供了一套完整的工具...
6. **任务切换与寄存器**:任务切换时间与CPU需要保存和恢复的寄存器数量有关。更多的寄存器意味着更复杂的上下文切换。 7. **8位图立即数**:0x13000000可以是一个合法的8位图立即数,这取决于具体的编程环境和...
8. 判断题涉及了任务优先级的设定、ARM处理器在不同模式下的功能、μC/OS-II的操作系统特性、ARM指令的扩展、ARM与Thumb状态切换以及ARM处理器的架构等知识点。 9. 简答题部分可能涉及更深入的嵌入式系统设计、ARM...
学习OSKit可以帮助理解操作系统内核的工作原理,为uC/OS的移植提供理论基础,特别是如何实现任务调度和线程上下文切换。 再者,实时CORBA(Common Object Request Broker Architecture)结构构架是另一种与uC/OS...