系统调用与内嵌汇编语法
一、用户态、内核态
1.1 用户态、内核态的区别
在Liunx操作系统的体系框架之中分为用户态和内核态。内核态中的CPU执行级别是最高的,可以执行特权指令,访问任意的物理内存。而在用户态中代码可以掌控的范围受限。如图1-1所示。
1.2 为什么要划分用户态和内核态?
不管是什么系统,其稳定性一直是最重要的因素之一,而内核的稳定更是重中之中。如果用户态中的进程可以任意执行特权指令,很容易出现各种bug,非法访问其他进程的资源,容易引起系统的奔溃。对于用户而言,主要有这两方面的原因。
1.一方面是由于用户编写的代码健壮性不高,容易出错。
2. 第二个就是用户编写的代码不规范,与内核代码规则不一致,降低系统稳定性。
同时在这里也可以解决之前的学习中所遇到的问题:为什么不让用户拥有直接修改eip的权限,而是必须得通过堆栈的调用才能修改eip的值?我想答案已经在上面说明了。
1.3 特权级别的划分--以X86CPU为例
划分图如图1-2。一般而言系统中最底层的内核处于rank0级别,与底层硬件关系紧密的驱动程序处于rank1和rank2,而应用程序处于rank3之中。同时在Linux中,一般允许用户态访问低地址空间不允许访问高地址空间,而内核态可以访问任意空间因此其权限级别更高。以4GB内存为例,如图1-3。
!图1-2](https://img2018.cnblogs.com/blog/1800789/201910/1800789-20191011203832512-1004888517.png)
1.4 用户态如何进入内核态?
用户态进入内核态的主要方式就是中断,中断分为以下两种:
(1)硬中断:
硬中断可以理解为某个硬件有某个请求,此时硬中断可以直接中断CPU,让当前正在运行的进程处于暂停。此时CPU就需要调度一个进程来处理这个硬件请求。然后由于此时要操作的是硬件,只有进程处于内核态才有这个权限进行处理,所以此时这个就会将处理这个中断请求的内存调入内核空间,进行相应的处理,处理完成之后再返回用户态。然后处理完成之后,cpu有会去调度之前的程序了。
(2)软中断:
某个进程运行过程中出现运行不下去的状况。比如说由于计算机采用的虚拟存储技术,所以在运行过程可能会出现缺少某一页的情况,那么此时进程就需要把对应的页调度到内存当中进行运行,但是正常进程是处于用户空间中,它没有这个权限进行调度,所以发生了运行不下去的情况。此时就产生了软中断。此时就需要通过系统调用来进入内核空间处理中断,然后处理完成之后再返回用户空间。
在处理中断的时候需要利用堆栈保存现场,将用户态寄存器的上下文、栈顶位置、状态字保存,以及将内核态寄存器的值放入CPU中,CS:EIP的值则指向中断处理程序的入口。
二、系统调用概述
2.1系统调用的特性
- 把用户从底层的硬件编码中解放出来。由操作系统管理硬件,用户态进程不直接与硬件设备打交道。
- 极大提高系统安全性。如果用户态进程直接与硬件设备打交道,会产生安全隐患,甚至系统奔溃。
- 使用户程序具有可移植性,便于在不同系统间移植。
2.2 系统调用的必备武器--API
API (应用程序编程接口)是函数定义,libc函数库中包含了对系统调用的封装也就是API内部所使用的封装例程。一个API可能对应一个系统调用,可能内部可以由多个系统调用完成,也就是可能存在多个系统调用。也有可能一个系统调用的由多个API完成。
以xyz()API函数为例,如图1-4。应用程序调用系统调用也就是lib库中的封装程序,遇到0X80(syscall)则进入中断处理内核态系统调用处理入口,调用sys_xyz()函数随后的过程就是按原路返回的过程。
2.3 内核如何知道用哪个系统调用?
通常每个系统调用都有一个编号称为系统调用号,就可以将系统调用区分开来,同时将API中的具体函数和系统调用的内核函数关联起来。系统调用号是同通过eax寄存器进行传递。
同时参数传递主要是通过EBX、ECX、EDX、ESI、EDI、EBP这六个寄存器。
三、库函数API和C代码中嵌入汇编代码触发同一个系统调用
常用内嵌汇编语言修饰符含义。
以一个简单的输出目前时间的C程序为例,代码如下。
time.c
#include<stdio.h>
#include<time.h>
int main()
{
time_t tt;
struct tm *t;
tt=time(NULL);
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函数为tt=time(NULL),输出结果为如图。
同时将其time()函数转化为汇编代码。
#include<stdio.h>
#include<time.h>
int main(){
time_t tt;
struct tm *t;
asm volatile(
mov $0,%%ebx
"
"mov $0xd,%%eax
"
"int $0x80
"
"mov %%eax,%0
"
:"=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;
}
但是进行编译的时候出现错误
错误一:
发现是输入代码时末尾不注意添加了~,由于和自带的背景十分一致所以刚开始一直没发现。
错误二:
也不知道是啥原因,刚开始查到网上说要更新gcc但是实验过了没有用,但是确实没在代码中找到明显的错误于是重新输入一遍反而编译成功。
可以发现这两种代码输出的结果是一致的,