• LINUX内核分析第四周——扒开系统调用的三层皮


    LINUX内核分析第四周——扒开系统调用的三层皮

    李雪琦 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

    一、用户态、内核态和中断处理过程

    1. 用户态和内核态

    CPU指令执行级别:

    • 执行特权指令,访问任意的物理地址——内核态。

    • 低级别:代码只能在级别允许的特定范围内活动——用户态。在日常操作下,执行系统调用的方式是通过库函数,库函数封装系统调用,为用户提供接口以便直接使用。

    • Intel x86 CPU有四种不同的执行级别0~3,Linux只用其中的0和3来表示内核态和用户态。

    • 区分内核态和用户态:CPU每条指令的读取都是通过cs:eip,cs寄存器最低两位表明了当前代码的特权级。

    • 内核态下可访问所有地址空间。

    • 0xc0000000(逻辑地址)以上的空间只能在内核态下访问。

    • 0x00000000 ~ 0xbfffffff 内核态和用户态均可访问。

    • 用户态转换为内核态的主要方式:中断。

    2. 中断处理

    • 用户态到内核态的切换:必须保存用户态的寄存器上下文,包括用户态栈顶地址、当时的状态字、cs:eip的值,以及内核态的栈顶地址、当时的状态字、中断处理程序入口。
    • 中断发生后的第一件事:保存现场(SAVE_ALL:保存需要用到的寄存器数据)。
    • 中断处理结束前的最后一件事:恢复现场(RESTORE_ALL:退出中断程序,恢复保存寄存器的数据)。

    二、系统调用概述

    1. 系统调用的意义:

    操作系统为用户态进程与硬件设备进行交互提供了一组接口,就是系统调用。

    • 远离底层硬件编程
    • 安全性
    • 可移植性

    2. API - 应用编程接口

    与系统调用区别:

    • API只是一个函数定义。
    • 系统调用是通过软中断向内核发出一个明确的请求。
    • 一般每个系统调用对应一个封装例程,库再用这些封装例程定义出用户的API,方便用户使用。也就是说,API与系统调用不是一一对应的。

    API可以:

    • 直接提供用户态服务
    • 一个单独的API可能调用几个系统调用
    • 不同的API可能调用了同一个系统调用

    返回值:

    • 大部分封装例程返回一个整数
    • -1表示失败,不能满足请求
    • errno 特定出错码

    3.所谓“扒开系统调用的三层皮”

    • API(xyz)
    • 中断向量(system_call)
    • 中断服务程序(sys_xyz)

    1.系统调用的服务例程中,中断向量0x80与system_call绑定起来。(Linux中可以通过执行int $128来执行系统调用。)

    2.system_call是linux中所有系统调用的入口点,每个系统调用至少有一个参数,即系统调用号。

    3.系统调用号将xyz与sys_xyz关联起来。调用号在eax中。
    系统调用的参数传递:

    • 函数调用——压栈
    • 用户态到内核态——寄存器传递。

    每个参数长度不能超过32位,个数不能超过6个。

    超过的话,使某个寄存器中存储指针,指向内存,内存中存储参数。

    三、使用库函数API和C代码中嵌入汇编代码触发同一个系统调用

    1.使用库函数API获取系统当前时间

    使用time(),代码如下:

    #include<stdio.h>
    #include<time.h>
    int  main() 
    {
    time_t tt;  
    struct tm *t;//构造一个结构体,方便读取
    tt = time(NULL);//time系统调用
    t = localtime(&tt);  
    printf("time:%d:%d:%d:%d:%d:%d
    ", t->tm_year+1900, t->tm_mon, t->tm_mday,  t->tm_hour, t->tm_min, t->tm_sec);
    return 0; 
    } 
    

    2.使用C代码中嵌入汇编代码触发系统调用获取系统当前时间

    代码如下:

    #include<stdio.h>
    #include<time.h>
    int  main() 
    {
    time_t tt;  
    struct tm *t;
    asm volatile(  
    "mov $0,%%ebx
    	"  # 把ebx清零,相当于传参数
    "mov $0xd,%%eax
    	"# 把0xd放入eax中,即系统调用号13,指time 
    "int $0x80
    	"  
    "mov %%eax,%0
    	"  # 返回值是在eax中,%0指tt,把返回值放到tt中去。
    : "=m" (tt)   
    );  
    t = localtime(&tt);  
    printf("time:%d:%d:%d:%d:%d:%d
    ", t->tm_year+1900, t->tm_mon, t->tm_mday,  t->tm_hour, t->tm_min, t->tm_sec);
    return 0; 
    } 
    

    四、使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用

    在这里我选择的是第7号系统调用,waitpid。

    1.使用库函数API:

    2.嵌入汇编:

    3.运行结果:

    五、总结

    • 即便是最简单的程序,在进行输入输出等操作时也会需要调用操作系统所提供的服务,也就是系统调用。
    • Linux下的系统调用是通过中断(int 0x80)来实现的。
    • 在执行int 80指令时,寄存器 eax 中存放的是系统调用的功能号,而传给系统调用的参数则必须按顺序放到寄存器 ebx,ecx,edx,esi,edi 中,当系统调用完成之后,返回值可以在寄存器 eax 中获得。
    • Linux 采用的是 C 语言的调用模式,这就意味着所有参数必须以相反的顺序进栈,即最后一个参数先入栈,而第一个参数则最后入栈。
  • 相关阅读:
    UWP中实现大爆炸效果(二)
    UWP中实现大爆炸效果(一)
    c# 【电影搜索引擎】采集电影站源码
    安利一个聚合搜索导航站,及怎么样设置成默认的搜索引擎
    女朋友经常问影视剧, 答不上来怎么办?
    宝塔linux面板, 服务器日志分析与流量统计这款插件的mysql版优化。
    苹果cms自动采集,重复执行遇到“上次执行时间: --跳过”的解决办法
    苹果cms, 后台设置保存不了的解决办法
    解决 C:WINDOWSsystem32inetsrv ewrite.dll 未能加载。返回的数据为错误.
    img error 图片加载失败的最佳方案
  • 原文地址:https://www.cnblogs.com/lxq20135309/p/5296439.html
Copyright © 2020-2023  润新知