zoukankan      html  css  js  c++  java
  • Linux内核分析——扒开系统调用的三层皮(上)

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

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

    1、用户通过库函数与系统调用联系起来。

    2、在高执行级别下,代码可以执行特权指令,访问任意的物理地址,这种CPU执行级别就对应着内核态。而在相应的低级别执行状态下代码的掌控范围受到限制。只能在对应级别允许的范围内活动。

    3、intel x86 CPU有四种不同的执行级别0-3。Linux只取两种,0级是内核态,3级是用户态。

    4、如何区分用户态与内核态?

         cs寄存器的最低两位表明了当前代码的特权级

         CPU每条指令的读取都是通过cs:eip这两个寄存器:cs是代码段选择寄存器,eip是偏移量寄存器

         上述判断由硬件完成

         一般来说在Linux中,地址空间是一个显著的标志:0xc0000000以上的空间只能在内核态下访问,0x00000000-0xbfffffff的地址空间在两种状态下都可以访问(这里所说的地址空间是逻辑地址而不是物理地址)。

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

    6、寄存器上下文

         从用户态切换到内核态时,必须保存用户态的寄存器上下文到内核堆栈中,同时会把当前内核态的一些信息加载,例如cs:eip指向中断处理程序入口。

         如:用户态栈顶地址、当时的状态字、当时的cs:eip的值

    7、中断发生后的第一件事就是保存现场 - SAVE_ALL

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

    二、系统调用概述

    1. 系统调用的意义:

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

       (1)把用户从底层的硬件编程中解放出来。

       (2)极大地提高了系统的安全性

       (3)使用户程序具有可移植性

    2、API(应用编程接口)

        与系统调用区别:

       (1)API只是一个函数定义

       (2)系统调用通过软中断向内核发出一个明确的请求。

       (3)API可直接提供用户态服务;一个API调用几个系统调用;不同API可调用同一个系统调用。

    3、Libc库

       (1)定义的一些API引用了封装例程(唯一目的就是发布系统调用)

       (2)一般每个系统调用对应一个封装例程。

       (3)库再用这些封装例程定义出给用户的API;

    4、返回值

       (1)大多封装例程返回一个整数,其值依赖于相应的系统调用;

       (2)-1表示内核不能满足进程的请求;

       (3)Libc定义的errorno变量包含特定出错码;

    5、系统调用的三层皮

       (1)1API(xyz)

       (2)中断向量(system_call)

       (3)中断服务程序(sys_xyz) 

     

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

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

       (3)系统调用号将xyz与sys_xyz关联起来。调用号在eax寄存器中。

    系统调用的参数传递:

       (1)函数调用——压栈

       (2)用户态到内核态——寄存器传递。

               每个参数长度不能超过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;

    }

    四、总结

       系统调用(System Call)是操作系统为在用户态运行的进程与硬件设备(如CPU、磁盘、打印机等)进行交互提供的一组接口。当用户进程需要发生系统调用时,CPU 通过软中断切换到内核态开始执行内核系统调用函数。在Linux 下三种发生系统调用的方法:

       1、通过 glibc 提供的库函数

            glibc 是 Linux 下使用的开源的标准 C 库,它是 GNU 发布的 libc 库,即运行时库。glibc 为程序员提供丰富的 API(Application Programming Interface),除了例如字符串处理、数学运算等用户态服务之外,最重要的是封装了操作系统提供的系统服务,即系统调用的封装。

       2、使用 syscall 直接调用

        如果 glibc 没有封装某个内核提供的系统调用时,就没办法通过上面的方法来调用该系统调用。此时我们可以利用 glibc 提供的syscall 函数直接调用。

       3、通过int指令陷入

        如果我们知道系统调用的整个过程的话,应该就能知道用户态程序通过软中断指令int 0x80 来陷入内核态(在Intel Pentium II 又引入了sysenter指令),参数的传递是通过寄存器,eax 传递的是系统调用号,ebx、ecx、edx、esi和edi 来依次传递最多五个参数,当系统调用返回时,返回值存放在 eax 中。

       

     

  • 相关阅读:
    usb2.0 规范学习笔记
    Linux开机启动程序详解[转]
    linux 系统运行级别及修改[转]
    linux下开发板网络速度测试记录
    tcp 和 udp 缓冲区的默认大小及设置【转】
    linux 环境变量的设置【转】
    1014. Waiting in Line (30)
    构建乘积数组
    数组中重复的数字
    把字符串转换成整数
  • 原文地址:https://www.cnblogs.com/20135235my/p/5291918.html
Copyright © 2011-2022 走看看