zoukankan      html  css  js  c++  java
  • 系统调用的工作机制

    中断处理是从用户态进入内核态主要的方式

    当从用户态切换到内核态的时候,必须用户态的寄存器上下文保存起来,同时设置内核态的寄存器内容
    中断/int指令会在堆栈上保存一些寄存器的值
          如:用户态栈顶地址、当时的状态字、当时的 cs:eip 的值
    同时设置内核态的栈顶地址、内核态的状态字,中断处理程序的入口地址 cs:eip 的值(对于系统调用来讲,它是指向system_call函数)

    中断/int指令发生后第一件事就是保护现场

    保护现场就是进入中断程序保存需要用到的寄存器的数据

    当进入到中断处理程序后,一开始就执行SAVE_ALL,把其它的一些寄存器的值push到内核堆栈里面去

     
    SAVE_ALL

    中断处理结束前最后一件事是恢复现场

    恢复现场就是退出中断程序恢复寄存器的数据

    当中断处理程序结束之后,它会RESTORE_ALL,把保存的用户态的寄存器再pop出来到当前的CPU里面,最后iret,iret指令与中断信号(包括int指令)发生时CPU做的动作刚好相反

     
    RESTORE_ALL

    中断处理的完整过程

    interrupt(ex:int 0x80)
    save cs:eip/ss:esp/eflags(current) to kernel stack, then load cs:eip(entry of a specific ISR) and ss:esp(point to kernel stack)
    SAVE_ALL
    ....  // 内核代码,完成中断服务,发生进程调度
    RESTORE_ALL
    iret
    pop cs:eip/ss:esp/eflags from kernel stack

    SAVE_ALL....如果发生了进程调度,那么当前的状态都会暂时保存在系统里面,当下一次发生进程调度切换回当前进程的时候,就会接着把它执行完,RESTORE_ALL....

    以系统调用为例,看中断具体是怎么执行的

    系统调用通过软中断向内核发出一个明确的请求,是操作系统为用户态进程与硬件设备进行交互提供的一组接口

    封装例程 (wrapper routine),唯一目的就是发布系统调用,让程序员在写代码的时候不需要用汇编指令来触发一个系统调用,而是直接调用一个函数就可以触发一个系统调用

    应用编程接口(application program interface, API) 只是一个函数定义。一般每个系统调用对应一个封装例程,库再用这些封装例程定义出给用户的API。但并不是每个API都对应一个特定的系统调用,API可能直接提供用户态的服务,例如一些数学函数。一个单独的API可能调用几个系统调用,不同的API可能调用了同一个系统调用

     
    应用程序、封装例程、系统调用处理程序及系统调用服务例程之间的关系

    User Mode 用户态      Kernel Mode 内核态

    xyz()函数,是系统调用对应的API,这个应用程序编程接口里面封装了一个系统调用,这个系统调用会触发一个int 0x80的中断,0x80这个中断向量对应着system_call这个内核代码的起点,这个内核代码里面会有SAVE_ALL,然后执行到sys_xyz()中断服务程序,进入程序里面处理,在中断服务程序执行完之后会ret_from_sys_call,在return的过程中可能会发生进程调度(这是一个进程调度的时机),如果没有进程调度,就会iret,回到用户态接着执行

    Summary

    系统调用的三层皮:API、中断向量对应的system_call、中断服务程序sys_xyz

    当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数,在Linux中是通过执行int $0x80来执行系统调用的,这条汇编指令产生向量为128的编程异常。(Intel Pentium II中引入了sysenter指令(快速系统调用),2.6已经支持)

    内核实现了很多不同的系统调用,进程必须指明需要哪个系统调用,这需要传递一个名为系统调用号的参数,使用eax寄存器(系统调用号将xyz()和sys_xyz()关联起来了)

     

    系统调用的参数传递方法

    普通函数调用的时候,可以采用把参数压栈的方式传递参数。但是从用户态到内核态,怎么传递参数呢?

    system_call是linux中所有系统调用的入口点,每个系统调用至少有一个参数,即由eax传递的系统调用号

    一个应用程序调用fork()封装例程,那么在执行int $0x80之前就把eax寄存器的值置为2(即__NR_fork)
    这个寄存器的设置是libc库中的封装例程进行的,因此用户一般不关心系统调用号
    进入sys_call之后,立即将eax的值压入内核堆栈

    寄存器传递参数具有如下限制:
    1)每个参数的长度不能超过寄存器的长度,即32位
    2)在系统调用号(eax)之外,参数的个数不能超过6个(ebx, ecx,edx,esi,edi,ebp)
    超过6个怎么办?

    如果超过6个,就把某一个寄存器作为一个指针,指向一块内存,进入到内核态之后可以访问到所有的地址空间,通过内存来传递参数

    通过库函数API使用系统调用获取系统当前时间

     
    通过库函数API使用系统调用获取系统当前时间

    用汇编方式触发系统调用获取系统当前时间

     
    用汇编方式触发系统调用获取系统当前时间

    系统调用传递第一个参数使用ebx,这里是NULL
    使用eax传递系统调用号,这里time是13
    系统调用的返回值使用eax存储,和普通函数一样

    (完)



    作者:那只大象
    链接:https://www.jianshu.com/p/4c8a1242082a
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    python cx_Oracle install
    import uno 错误
    webkit report
    window bzr launchpad 安装配置
    如何让同一个字段在不同的view中显示不同的内容
    Trigger model Trigger expr_id in WorkFolow
    how to use a xml_id in field domain
    action 关联
    activity清除的所有方法
    listview加载性能优化ViewHolder
  • 原文地址:https://www.cnblogs.com/feng9exe/p/12521394.html
Copyright © 2011-2022 走看看