qq_31073871的博客

路过的朋友,如果发现技术性错误,欢迎留言指正,共同进步

linux用户态、内核态的切换,SWI指令的理解

内核态的代码可以直接操作寄存器,把自己切向用户态,但是用户态想切换进入内核态,就需要费一番周折了

参考本博客的另一篇文章《STM32/ARM术语:处理者模式/线程模式、特权级/用户级

总体思路是这样的:

异常服务函数和中断服务函数都是运行在内核态的,在普通模式下,用户如果想干一些特权功能,通过修改寄存器直接进入内核态是不可能的,只能通过异常服务函数来做(一般是通过软中断异常),只要程序员们提前约定好协议,把想做的事情放到异常服务函数中就行了。

对于用户态的各种各样的特权操作代码(也即,各种系统调用system_call的代码),都放到异常服务函数中,在服务函数中做一个约定好的类似switch case的分支操作,用户就能选择执行这些system_call了,可能有些system_call还需要参数,如何选择system_call,又如何给system_call传参呢?有两种方法:

方法1:通过SWI xxx指令的操作数xxx来选择system_call,通过Rn或栈来传递实参。常见的SWI指令,高8位是操作码SWI,低24位是操作数xxx;

方法2:约定某个Rn例如R0来选择system_call,通过其他Rn或栈来传递实参,这样SWI后面的操作数就不起作用了;

下面是方法1的例子:

;--------------这段代码一般位于启动文件中,是厂家做好的-------------------  
; 异常和中断向量表开始  
; 0x00: 复位Reset异常  
         b       Reset  
; 0x04: NMI_Handler异常  
         b       NMI_Handler  
  
; 0x08: 软件中断异常,跳往软件中断处理函数HandleSWI  
    b         HandleSWI ;跳转到SWI异常服务函数中去  
  
… …  
;设置堆、栈的起始地址、大小等,弱定义(WEAK)各种中断/异常服务函数等···略···我们常见的STM32的启动文件例如startup_stm32f10x_hd.s中可以阅读详细代码  
;-------------------------------------------------------------------------------------------------    
  
  
;异常服务函数是弱定义的,因此我们可以重自定义自己的异常服务函数
HandleSWI
    STMFD     SP!, {R0-R12,  LR}            ; 保存现场
    LDR R4, [LR, #-4]                       ; LR - 4 为指令" swi xxx" 的地址,xxx为低24位,我们根据xxx的值来选择不同的系统调用
    BIC   R4, R4, #0xFF000000               ; 取得ARM指令swi xxx中xxx的值,放到R4中
    
    ;下面这段switch代码应该用汇编来写,不过用C语言能更清楚的显示出原理。
    ;c语言转为汇编,可参考搜索以下关键字自行学习:汇编调用C语言函数。难点可能在于汇编代码要符合编译器的规范,
    ;例如:第几个实参应放入第几个通用寄存器、从第几个实参开始,要入栈、返回值的传递方法等
    ;当然,如果你自定义的系统调用不是用C而是直接用汇编子程序来写,那么不必考虑C语言的编译规范了,直接用B、Bx、call等指令跳转就是了
    switch(R4)
    {
        case 0: system_call_read(R0);break;
        case 1: system_call_write(R0,R1);break;
        case 2: system_call_peek();break;
        case 3: system_call_3(R0);break;
        case 4: system_call_4(R1);break;
        case 5: system_call_5();break;
        default:
    }    
    
    LDMIA     SP!, {R0-R12, PC}^             ;恢复现场 中断返回, ^表示将spsr的值复制到cpsr

;-------------------------------------------------------------------------------

void system_call_read(int fn)
{
   //按照编译器的编译规范,R0的内容就是第一个形参的内容,读fn就可以读到R0的内容
   //如果在发生系统调用前,汇编代码传的第一个实参参不是用的R0,那么该函数就不能用纯C语言来写了,只能用汇编或者在本函数内嵌入汇编,来取得实参    
}

//···其余系统调用的代码··略··
    
关于

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭