Linux系统调用------通过time系统调用理解系统调用的执行过程

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

一、重点知识
(1)用户栈与内核栈
内核栈:存在于内核空间,当进程在内核栈里运行时,CPU栈顶指针寄存器里面的内容是内核栈空间地址,使用内核栈。
用户栈:存在于用户空间,当进程在用户栈里运行时,CPU栈顶指针寄存器里面的内容是用户栈空间地址,使用用户栈。
(2)用户态与内核态
用户态:用户在非特权模式下,访问会资源会受到限制。
内核栈:用户在特权模式下,可随意访问资源
为什么要区分用户态与内核态?
因为在操作系统中有很多很重要的代码要保证安全性,不可被任意修改,区别用户态和内核态可访问的空间,保证了系统的安全,防止因不当操作而造成的系统崩溃。
在Linux中有0和3两种运行级别:0表示内核态,3表示用户态
(3)利用地址空间区别内核态和用户态
只有内核态可以访问0xc0000000以上的地址空间,0x00000000到0xbfffffff在两种状态下都可以访问
(4)内核态与用户态的切换
当进程因为硬件中断或者系统调用,而从用户态转变为内核态时,进程所使用的堆栈要从用户栈变为内核栈,此时进程进入内核态后,首先,在内核态上保存用户态堆栈上的地址,设置堆栈指针寄存器的内容为当前进程的内核栈地址,这样就完成了用户栈向内核栈的切换
当进程从内核态恢复到用户态时,将内核态保存的用户态的堆栈地址恢复到栈指针寄存器,这样就完成了内核态向用户态的切换
(5)系统调用
1.系统调用与API(应用程序编程接口)区别
API只是一个函数定义,而系统调用通过软中断向内核发出一个明确的请求。
不是每个API都对应一个特定的系统调用。首先,API可能直接提供用户态的服务(比如一些数学函数),其次,一个单独的API可能调用几个系统调用,不同的API可能调用了同一个系统调用。
2.系统调用的优点
系统调用为用户提供了调用与硬件设备等进行交互的接口,可以将用户从底层的硬件编程中解放出来,提高系统的安全性,是用户程序具有更好的可移植性。
3.系统调用时执行的操作
1)在进程的内核态堆栈中保存大多数寄存器的内容(即保存恢复进程到用户态执行所需要的上下文)
2)根据用户态传递的系统调用号,确定系统调用的服务例程
3)调用名为系统调用服务例程的相应的C函数来处理系统调用
4)从系统调用返回
4.系统调用的过程描述
这里写图片描述
简单描述系统调用的执行过程:在用户态下执行某个API函数,在API函数中有一个int 0x80的中断向量,触发后,由用户态进入内核态,保存现场,查找system_ call表找到相应的系统调用号,找到后执行相应的系统调用服务例程,执行过后返回,恢复现场,回到用户态。
这里写图片描述
可以发现:用户模式下的API函数xyz()中在执行时触发了中断向量int 0x80,随后进入了内核态,保存现场,执行相应的服务例程。
中断向量0x80与system_call连接起来,system _call包含了很多系统调用,通过系统号找到特定的服务例程。系统调用号将xyz与sys _xyz关联起来。
5.系统调用的参数传递(从用户态向内核态传递参数)规则:
(1)每个参数的残毒不能超过寄存器的长度
(2)在系统调用号(eax)外,参数的个数不能超过6个(ebx,ecx,edx,esi,edi,ebp),如果超过6个,将某一个寄存器作为一个指针指向一块内存,到达内核栈后,通过这块内存来传递参数
二、实验内容
1.简单的系统调用time,获得系统时间

//This program tests syscall call such as time
#include <stdio.h>
#include <time.h>
int main()
{
    time_t tt;         
    struct tm *t;
    tt = time(NULL);//time系统调用多的当前系统时间
    t = localtime(&tt);
    //将tt这个size_t的整数转换成tm结构体中的变量
    printf("time:%d/%d/%d ,%d:%d:%d\n",t->tm_year+1900,t->tm_mon,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec);
    return 0;
}

实验执行结果:gcc …… -m32,64位的系统按32位编译程序
这里写图片描述
2.利用内嵌汇编代码深层了解系统调用过程

#include <stdio.h>
#include <time.h>
//this program :use asm to call time
int main()
{
    time_t tt;
    struct tm *t;
    asm volatile(
                    "mov $0,%%ebx\n\t"
                    //ebx用来传递系统调用的第一个参数,这里传递的是NULL代表当前时间
                    "mov $0xd,%%eax\n\t"
                    //time系统调用的系统调用号是13(d),用eax来传递系统调用号
                    "int $0x80\n\t"
                    //触发系统调用
                    "mov %%eax,%0\n\t"
                    //将系统调用的返回值存储到%0(tt变量中)
                    :"=m" (tt)
                );
    t = localtime(&tt);
    printf("time:%d/%d/%d ,%d:%d:%d\n",t->tm_year+1900,t->tm_mon,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec);
    return 0;
}

实验执行结果
这里写图片描述
在这段内嵌汇编代码中,模仿了time系统调用的功能,在编写代码时我们用ebx来传递系统调用的第一个参数,用eax来传递系统调用号,通过int $0x80产生系统中断,执行系统调用号为13的系统调用,将返回的结果保存下来,这就完成了一次系统调用的模拟。


编写一个自己的嵌入式汇编代码实现的系统调用
我选择实现的系统调用是open,read,和write,
他们三个的用C语言的方法实现:
这里写图片描述
open的参数第一个为要打开的文件,第二个为打开的方式(一定要注意打开的方式,刚开始的时候我由于打开方式赋值错误,函数无法写入)
read共三个参数:要读的文件,读到哪里,读几个
wirte共三个参数:要写的文件,写入的内容来自哪里,写几个
执行效果:
这里写图片描述
根据上面time的改编
这里写图片描述
传入的第一个参数存入ebx,第二个参数传入ecx,第三个参数传入edx,open的系统调用号为0x05,在open中,共需要传入两个参数,所以在参数准备好后,通过int $0x80,进入system_call,查找0x05对应的服务例程,接着执行,同理read(调用号03)和wirte(调用号04)
这里写图片描述


三、实验总结
今天的实验简单的了解了系统调用的基本知识,包括内核态与用户态、内核栈与用户栈以及在系统调用过程中,如何从内核态过渡到用户态等。
形象化的记忆将系统调用可以看成三层皮:API函数,system_ call,sys _xxx()系统调用服务例程。
要清楚系统调用的执行过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值