Linux内核分析:扒开系统调用的三层皮(上)

一般的CPU有不同的指令执行级别:

高级别:特权指令,可访问任意的物理地址。(内核态)
低级别:访问部分地址,代码的掌控范围会受到限制。

x86有四种不同的指令执行级别0~3,而Linux只使用了0级和3级来表示内核态和用户态。这是让操作系统变得更稳定的一种机制。

  • CS寄存器的最低两位表明了当前代码的特权级
  • 在Linux中,地址空间是一个显著的标志,部分地址只能在内核态下访问(此处指逻辑地址)
  • 中断处理是从用户态进入内核态的主要方式,系统调用是一种特殊的中断

从用户态切换到内核态:要保存当前用户态和内核态的寄存器上下文,如把当前的栈顶地址,状态字,cs:eip的值保存到内核堆栈,然后加载中断服务的入口到cs:eip里面,同时把ss:esp加载到CPU中

  • 保存现场就是进入中断服务程序,保存需要用到的寄存器的数据
  • 恢复现场是退出中断程序,恢复保存的寄存器的数据,发生在中断程序结束前的最后一句

系统调用

  • 意义: 把用户从底层的硬件编程中解放出来;极大的提高了系统的安全性;使用户程序具有可移植性
  • API和系统调用是不同的:API只是一个函数定义,系统调用通过软中断向内核发出一个明确的请求
  1. 当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数(在Linux中是通过执行int $0x80来执行系统调用的, 这条汇编指令产生向量为128的编程异常)
  2. 传参:内核实现了很多不同的系统调用, 进程必须指明需要哪个系统调用,这需要传递一个名为系统调用号的参数(使用eax寄存器)
  3. system_call是linux中所有系统调用的入口点,每个系统调用至少有一个参数,即由eax传递的系统调用号,eax的设置是libc库中的封装例程进行的,在进入中断程序之前赋值,因此用户一般不关心系统调用号,进入sys_call之后,立即将eax的值压入内核堆栈
  4. 系统调用的返回值使用eax存储,和普通函数一样
  5. 在系统调用号(eax)之外,传递的参数个数不能超过6个,可使用ebx,ecx,edx,esi,edi,ebp等寄存器,若参数超过6个,可将指向参数的指针赋给寄存器

实验部分:使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用

调用库函数API代码如下:

#include 
#include 
#include 

int main()
{
    struct utsname n;
    uname(&n);
    printf(" sysname:%s\n nodename:%s\n release:%s\n version:%s\n machine:%s\n ",\
                    n.sysname,\
                    n.nodename,\
                    n.release,\
                    n.version,\
                    n.machine);
    return 0;
}

实验结果:

Linux内核分析:扒开系统调用的三层皮(上)_第1张图片
调用库函数API

C代码中嵌入汇编代码:

#include 
#include 
#include 

int main()
{
    struct utsname n;
    int *s=&n;
    asm volatile(
            "mov %0,%%ebx\n\t"
            "mov $0x7A,%%eax\n\t"
            "int $0x80\n\t"
            :"=m"(s)
            );
    printf(" sysname:%s\n nodename:%s\n release:%s\n version:%s\n machine:%s\n ",\
                   n.sysname,\
           n.nodename,\
                   n.release,\
                   n.version,\
                   n.machine);
return 0;
}

实验结果:

Linux内核分析:扒开系统调用的三层皮(上)_第2张图片
嵌入汇编代码

本次实验采用的是uname系统调用,可以获取当前内核名称和其它信息。在调用库函数API时,传递的是结构n的地址,它指向了存放系统信息的缓冲区;在嵌入的汇编代码中,先将结构n的地址传给ebx,再将系统调用号122传递给eax,int $0x80系统通过中断处理,来进入内核态,进行系统调用,从而转到相应函数去执行。

总结:

  1. 系统调用可以通过调用对应的API进行,它是操作系统赋予程序员的对底层的硬件编程的接口
  2. 系统调用是通过中断处理的方式来从用户态进入内核态的,通过调用的API,执行int $0x80进行中断处理,进入内核态,实现系统调用,因此系统调用可以说是一种特殊的中断。
  3. 然后进入该中断向量对应的中断服务程序,也就是system_call,它是Linux中所有系统调用的入口点,因为每个系统调用被赋予了不同的系统调用号(由eax传递),然后根据不同的系统调用号来进入不同的系统调用服务程序。这样就完成了系统调用。
  4. 由此可见,系统调用的“三层皮”是指对应的API、中断服务程序、系统调用服务程序

如果有帮助到了您,记得打赏鼓励下作者哦....

Linux内核分析:扒开系统调用的三层皮(上)_第3张图片
收款码.png

你可能感兴趣的:(Linux内核分析:扒开系统调用的三层皮(上))