zoukankan      html  css  js  c++  java
  • RTEMS 进程切换分析

    RTEMS 进程切换分析(基于i386体系)

    在支持多任务操作系统中,进程切换是不可避免的,以使进程能在单个CPU上并发执行。进程的调度涉及到的东西较多,例如调度的时机、调度的策略等等,在这里我们只讨论RTEMS任务调度中进程切换的细节,通过分析以明白操作系统如何做到使一个CPU的使用权如何从一个任务上切换到另一个任务。

    下面假设两个任务TASK1和TASK2,当前正在执行的任务executing = TASK1,需要切换到的任务 heir = TASK2,下面为进程调度进行上下文切换的代码(最精简的一个函数,除去多核的配置、其他一些扩展函数、可配置的浮点上下文保存恢复等代码):










    10 
    11 
    12 
    13 
    14 
    15 
    16 
    17 
    18 
    19 
    20 
    21 
    22 
    23 
    24 
    25 
    26 
    27 
    28 
    29 
    30 
    31 
    32 
    33 
    34 
    35 
    36 
    37 
    38 
    39 
    40 
    41
     
    void _Thread_Dispatch( void ) 

      Thread_Control   *executing; 
      Thread_Control   *heir; 
      ISR_Level         level; 

      
    /* 
       *  Now determine if we need to perform a dispatch on the current CPU. 
       */
     
      executing   = _Thread_Executing; 
      _ISR_Disable( level ); 
      
    while ( _Thread_Dispatch_necessary == true ) { 
        heir = _Thread_Heir; 
        _Thread_Dispatch_necessary = 
    false
        _Thread_Executing = heir; 

        
    /* 
         *  When the heir and executing are the same, then we are being 
         *  requested to do the post switch dispatching.  This is normally 
         *  done to dispatch signals. 
         */
     
        
    if ( heir == executing ) 
          
    goto post_switch; 

        
    /* 
         *  Since heir and executing are not the same, we need to do a real 
         *  context switch. 
         */
     

        _ISR_Enable( level ); 

        _Context_Switch( &executing->Registers, &heir->Registers ); 

        executing = _Thread_Executing; 

        _ISR_Disable( level ); 
      } 

    post_switch: 
      _ISR_Enable( level ); 
    }

    此函数执行之前,有两个全局变量_Thread_Executing 和 _Heir_Executing 分别指向 Task1 和 Task2 的进程控制块TCB,下面对此函数进行分析:

    首先:切换之后全局变量 _Thread_Executing 应该指向 Task2(第15行);

    其次,如果切换之前和切换之后是同一个任务,就无需进行上下文切换(第22行);

    若不同,则必须进行上下文切换,使CPU的控制权转到Task2上。所谓的上下文切换,就是保存Task1进程执行的上下文(主要是一些重要的寄存器),并且恢复Task2进程被切换出去之前执行的上下文。

    进程控制块TCB中有个字段Context_Control  Registers 是用来保存/恢复上下文的,该结构体在i386体系下定义为:

    typedef struct 

        uint32_t    eflags;   /* extended flags register                   */ 
        void       *esp;      /* extended stack pointer register           */ 
        void       *ebp;      /* extended base pointer register            */ 
        uint32_t    ebx;      /* extended bx register                      */ 
        uint32_t    esi;      /* extended source index register            */ 
        uint32_t    edi;      /* extended destination index flags register */ 
    } Context_Control;

    也即意味着两个进程进行切换时,需要保存的寄存器有EFLAGS、ESP、EBP、EBX、ESI、EDI等,第32行_Context_Switch 函数完成:切换之前将这些寄存器的值保存在需要切换的进程(此处为Task1)TCB的Registers中,切换时这些寄存器的值从即将切换的进程(此处为Task2)TCB的Registers中恢复。这是一个与体系结构相关的操作,需要使用汇编去完成,我们看看_Context_Switch( &executing->Registers, &heir->Registers ) 的汇编实现:










    10 
    11 
    12 
    13 
    14 
    15 
    16 
    17 
    18 
    19 
    20 
    21 
    22 
    23 
    24 
    25 
    26 
    27 
    28 
    29 
    30 
    31 
    32 
    33 
    34 
    35 
    36 
    37 
    38 
    39 
    40 
    41 
    42 
    43 
    44 
    45 
    46 
    47
     
    /* 
    * Format of i386 Register structure 
    */ 

    .set REG_EFLAGS,  0 
    .set REG_ESP,     REG_EFLAGS + 
    4 
    .set REG_EBP,     REG_ESP + 
    4 
    .set REG_EBX,     REG_EBP + 
    4 
    .set REG_ESI,     REG_EBX + 
    4 
    .set REG_EDI,     REG_ESI + 
    4 
    .set SIZE_REGS,   REG_EDI + 
    4 

            BEGIN_CODE 

    /* 
    *  void _CPU_Context_switch( run_context, heir_context ) 

    *  
    This routine performs a normal non-FP context. 
    */ 

            .p2align  
    1 
            
    PUBLIC (_CPU_Context_switch) 

    .set RUNCONTEXT_ARG,   
    4                   /* save context argument */ 
    .set HEIRCONTEXT_ARG,  
    8                   /* restore context argument */ 

    SYM (_CPU_Context_switch): 
            movl      RUNCONTEXT_ARG(
    esp),eax  /* eax = running threads context */ 
            
    pushf                              /* push eflags */ 
            popl      REG_EFLAGS(
    eax)          /* save eflags */ 
            movl      
    esp,REG_ESP(eax)         /* save stack pointer */ 
            movl      
    ebp,REG_EBP(eax)         /* save base pointer */ 
            movl      
    ebx,REG_EBX(eax)         /* save ebx */ 
            movl      
    esi,REG_ESI(eax)         /* save source register */ 
            movl      
    edi,REG_EDI(eax)         /* save destination register */ 

            movl      HEIRCONTEXT_ARG(
    esp),eax /* eax = heir threads context */ 

    restore: 
            pushl     REG_EFLAGS(
    eax)          /* push eflags */ 
            
    popf                               /* restore eflags */ 
            movl      REG_ESP(
    eax),esp         /* restore stack pointer */ 
            movl      REG_EBP(
    eax),ebp         /* restore base pointer */ 
            movl      REG_EBX(
    eax),ebx         /* restore ebx */ 
            movl      REG_ESI(
    eax),esi         /* restore source register */ 
            movl      REG_EDI(
    eax),edi         /* restore destination register */ 
            
    ret

    在进入此汇编代码之前,Task1 栈为:

    image

    解释一下,C语言通过堆栈传参惯例,参数从右到左依次压栈,然后将下一条程序指针压栈,因此在进入_CPU_Context_Switch函数之后Task1栈如上图所示。函数体完成的功能:

    第一步:将Task1上下文保存在Task1->Registers结构体中(第28~35行)。

    28:将Task1->Register的指针(esp+4)保存在eax寄存器中;

    29:将eflags寄存器压栈(pushl);

    30:再出栈,存放在(&Task1->Register)->eflags中;

    31~35:分别将esp、ebp、ebx、esi、edi寄存器的值保存在Task1->Register结构的相关字段中。

    到此为止,完成了保存Task1进程的上下文,Task1切换出去之前栈还是如上图所示。

    同理可以想象Task2被其他进程切换走之后一定也具有类似的栈结构,Task2->Registers中保存的是切换出去之前的它的上下文。

    第二步:从Task2->Register结构中恢复Task2的上下文(第37~46行)。

    37:将Task2->Registers的指针(esp+8)保存在eax寄存器中;

    40:将Task2->Registers.eflags(即Task2切换出去之前保存的eflags寄存器)压栈;

    41:出栈popf,此时eflags寄存器恢复为Task2上下文的eflags;

    42:恢复esp,此步最为关键,因为此步之后esp寄存器将由Task1栈顶转移到Task2栈顶,此后操作将在Task2的栈上进行;

    43~46:依次恢复esp、ebp、ebx、esi、edi寄存器。

    至此,完成了恢复Task2进程的上下文。

    第三步:从_CPU_Context_Switch 函数返回。

    47:ret操作,弹出栈顶到eip中,注意esp此时已经指向的是Task2的栈,但是上面我们说过Task2栈与Task1栈切换之前的结构是类似的,因此Task2栈顶保存的仍然是_Context_Switch 下一条语句的代码。

    从汇编函数中返回之后,又回到_Thread_Dispatch 函数体中,只不过与之前不同的是,此时处理器运行在Task2的上下文环境中。


    还有一个问题需要我们去探索:如果Task2是第一次被调度执行,即Task2之前没有被切换出去,不曾执行到_Thread_Dispatch 中的 _Context_Switch 切换出去,也就是Task2栈并不是我们上面讨论的那样,同样我们也不希望Task2第一次被调度执行的第一条代码不是_Context_Switch 的下一条语句,因为Task2栈上并不存在_Thread_Dispatch函数的栈帧,如果这样,肯定会出现不可预期的错误。

    所以,我们在创建任务时,就需要先初始化好Task2的栈,使得其第一次调度时,执行的是我们需要它需要执行的代码。

    在rtems_task_start –> _Thread_Start –> _Thread_Load_environment –> _Context_initialize 函数会在任务加入就绪队列的时候进行上下文初始化。函数调用如下:








    8
     
    _Context_Initialize( 
      &the_thread->Registers,                   //任务上下文结构指针                  
      the_thread->Start.Initial_stack.area,     //指向新建的一个任务栈区域的基址 
      the_thread->Start.Initial_stack.size,     //任务栈的大小 
      the_thread->Start.isr_level,              //中断级别 
      _Thread_Handler,                          //任务执行时第一次要执行的代码 
      is_fp 
    );

    我们看看这个函数实现的汇编代码:










    10 
    11 
    12 
    13 
    14 
    15
     
    #define _CPU_Context_Initialize( _the_context, _stack_base, _size, \ 
                                       _isr, _entry_point, _is_fp ) \ 
      do { \ 
        uint32_t   _stack; \ 
        \ 
        
    if ( (_isr) ) (_the_context)->eflags = CPU_EFLAGS_INTERRUPTS_OFF; \ 
        else          (_the_context)->eflags = CPU_EFLAGS_INTERRUPTS_ON; \ 
        \ 
        _stack  = ((uint32_t)(_stack_base)) + (_size)
    ; \ 
        _stack &= ~ (CPU_STACK_ALIGNMENT - 1); \ 
        _stack -= 2*sizeof(proc_ptr*);  \ 
        *((proc_ptr *)(_stack)) = (_entry_point); \ 
        (_the_context)->ebp     = (void *) 0; \ 
        (_the_context)->esp     = (void *) _stack; \ 
      } while (0)

    第6&7行通过参数isr来决定应初始化elfags寄存器的值;

    第9&10行根据栈基址以及栈的大小计算出初始时栈顶的位置(需要对齐);

    第11行将栈顶的位置往下移两个指针大小;

    第12行将程序入口指针_entry_point写入栈顶;

    第13&14行分别初始化ebp、esp寄存器。

    初始任务栈的示意图如下:

    image

    至此,任务初始上下文初始化完成,主要初始化了eflags、esp、ebp等寄存器,保存在TCB的registers结构体中。

    当一个从未执行的任务第一次被调度执行时,回到上下文切换函数_Context_Switch中,保存和恢复和上面一样,只不过在第三步ret操作时,会返回栈顶位置的值当作程序指针,和上面区别的是:这种情形eip不是跳到_Context_Switch的下一句,而是我们初始化保存在栈顶的值_entry_point。返回之后,就会执行所_entry_point指向的函数_Thread_Handle,在这个函数会执行到我们创建的任务体中。

    结束语:上面讨论的是基于i386体系下RTEMS任务切换上下文的过程,虽然是基于特定的体系结构特定的操作系统,但对于任一个多任务的操作系统任务切换都大相径庭,无非就是保存上下文、恢复上下文,而若移植操作系统,这是需要针对特定平台进行改写的一段代码。

    Creative Commons License

    本博客的内容如果没有标注转载字样,均属个人原创!欢迎学习交流,如果觉得有价值,欢迎转载,转载请注明出处,谢谢!

    邮箱:haifenglinying#yahoo.cn (#->@)

    个人主页:www.hazirguo.com

     
     

    AgileEAS.NET 5.0中输入字典功能

    Posted on 2013-05-07 10:41 EvilYang 阅读(518) 评论(1编辑 收藏 

    我们在写”查询界面”过程中经常通过字典表的选择来进行数据检索,比如通过员工字典表选择了其中一个员工,然后在商品类别字典表中选择了一类商品类型,最后点击查询,查询本员工所经手的产品列表。 常规的做法,你可能通过数据访问得到一个实体列表或者DataTable,绑定到了一个DropDownList上,同时指定value和Text属性。那么有的开发人员为了增加用户体验,可能将这个数据源绑定到了其他的控件上,可以相应TextChanged事件。 那么,开发人员可能又要制作一个“库存查询”的功能,同样要使用员工字典表和商品类型字典表的检索,开发人员不得不将这些相同的代码复制到“库存查询”界面。 比较严重的问题是,很多界面,需要很多这样重复的字典表检索控件,那么有没有比较高效而且省事的解决方案呢? AgileEAS.NET5.0提供了一个 “输入字典”功能,我们一起来看看是如何使用的!

    1.利用EAS.OrmDesigner.exe创建字典表

    image

    本文最后提供sql语句和代码的下载

    将生成的实体文件拖到EAS.Demo.DAL程序集中,修改实体文件的命名空间并重新生成EAS.Demo.DAL程序集

    2.打开EAS.WinClient.Start.exe,找到“输入字典”工具

    image

    在桌面上找不到"输入字典"工具的话,说明你没有将本图标添加到桌面上,“右键”-“添加”,在如下对话框中将“输入工具”勾选上即可

    image

    在输入字典界面点击“添加”,在添加界面我们进行相应的设置

    image

    在“筛选条件”选项卡中勾选检索使用的字段

    image

    ok,我们的一个输入字典就添加完毕,接下来我们看如何使用这个输入字典

    3.使用输入字典

    首先在UI程序集中新建一个界面,然后放一个TextBox文本框,用来加载我们的输入字典,也就是此文本框要跟我们上一步所制作的“输入字典"要关联起来,此时需要我们在vs工具箱上"右键"-"选择项"-"选择EAS.Data.Controls.dll-"将TextBoxAutoComplete组件加入到工具栏中,然后我们拖这个组件到我们新建的界面上

    此时我们的TextBox文本框已经多了一个属性:只需要把之前做的”输入字典“生成的GUID输入进来即可

    image

    运行界面,就可以看到效果了

    image

    如此一来,所有用到本字典表的界面都可以简单配置一次就可以了,省去了多次重复写代码的过程

    最后将demo.sln解决方案提供下载,后续的内容也将继续在此解决方案下添加。

    下载EAS.Demo.sln解决方案 
    注意:本解决方案中的bin\dotnet目录是空的,你需要将下载到的AgileEAS.NET5.0中的此目录覆盖,才能成功运行

     
     
    分类: AgileEAS.NET
  • 相关阅读:
    关于冲动 CQ
    关于在虚拟机上使用boost库 CQ
    新项目奖金分配方案 CQ
    关于桌子 CQ
    linux 添加本地yum源
    linux samba 服务器 简单配置
    linux 修改密码
    linux ntp 服务器和用户端
    linux 修改ip 地址
    Union 和 Union All 的区别
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3065602.html
Copyright © 2011-2022 走看看