2020-2021-1 20209320 《Linux内核原理与分析》第五周作业

系统调用的三层皮

  • API:第一层是指Libc中定义的API,这些API封装了系统调用,使用int 0x80触发一个系统调用中断;当然,并非所有的API都使用了系统调用,如完成数学加减运算的API就没有使用系统调用;也有可能某个API使用了多个系统调用;这一层存在的价值就是为应用程序员提供易于使用的API来调用系统调用;
  • system_call:运行于内核态。system_call是所有系统调用在内核的入口点,在其中的开始处保护用户态程序执行上下文,结束处恢复用户态程序执行上下文,在中间根据传入的系统调用号对应的中断服务程序;
  • sys_xyz 系统调用封装例程:执行具体的系统调用操作,完成用户的系统调用请求;每个系统调用都对应一个封装例程;

系统调用执行流程

实验一

1.1 使用库函数API time()来获取系统当前时间

代码实现

#include<stdio.h>
#include<time.h>
int main()
{
    time_t tt;           //t是一个int型的数值,记录当前系统的时间,
    struct tm *t;        //定义一个 struct tm 类型的指针变量 t
    tt = time(NULL);     //使用库函数 API time() 来获取 tt
    t = localtime(&tt);  //调用 localtime() 把tt变成 struct 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;
}

实验结果

1.2 用汇编方式触发系统调用获取系统当前时间

代码实现

#include<stdio.h>
#include<time.h>
int main()
{
 time_t tt;
 struct tm *t;
 asm volatile
 (
    "mov $0,%%ebx\n\t"//把EBX寄存器清0
	"mov $0xd,%%eax\n\t"//eax用于传递系统调用号
	"int $0x80\n\t"//触发系统调用陷入内核执行13好系统调用的内核处理函数
	"mov %%eax,%0\n\t"//系统调用的返回值通过eax返回
	:"=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;
 }

实验结果

实验二

2.1 含两个参数的C语言调用remane系统调用

代码实现

#include<stdio.h>
int main()
{
    int ret;
    char *oldname = "hello.c";
    char *newname = "new1hello.c";
    ret = rename(oldname,newname);
    if(ret == 0
        printf("Renamed successfully\n");
    else 
        printf("Unable to rename the file\n");
    return 0;
}

实现结果

2.2 含两个参数的汇编语言调用remane系统调用

代码实现

#include<stdio.h>
int main()
{
    int ret;
    char *oldname = "hello.c";
    char *newname = "newhello.c";
    asm volatile(
        "movl %2,%%ecx\n\t"      //将newname存入ecx寄存器
        "movl %1,%%ebx\n\t"      //将oldname存入ebx寄存器
        "movl $0x26,%%eax\n\t"   //把系统调用号38存入EAX寄存器中
        "int $0x80"              //执行系统调用陷入内核态,执行38号系统调用的内核处理函数
        :"=a"(ret)
        :"b"(oldname),"c"(newname)
    );
    if(ret == 0)
        printf("Renamed successfully\n");
    else 
        printf("Unable to rename the file\n");
return 0;
}

实现结果

2.3 通用的触发系统调用的库函数syscall

实现代码

#include<stdio.h>
#include<sys/syscall.h>
int main()
{
    int ret;
    char oldname[]="hello.c";
    char newname[]="xxxhello.c";
    ret=syscall(38,oldname,newname);
    if (ret==0)
        printf("Renamed successfully\n");
    else
        printf("Unable to renam the file\n");
    return 0;
 }

实验结果

总结

Linux 下的系统调用是通过中断(int 0x80)来实现的。在执行 int 80 指令时,寄存器 eax 中存放的是系统调用的功能号,而传给系统调用的参数则必须按顺序放到寄存器 ebx,ecx,edx,esi,edi 中,当系统调用完成之后,返回值可以在寄存器 eax 中获得。
所有的系统调用功能号都可以在文件 /usr/include/bits/syscall.h 中找到,为了便于使用,它们是用 SYS_ 这样的宏来定义的,如 SYS_write、SYS_exit 等。例如,经常用到的 write 函数是如下定义的:
ssize_t write(int fd, const void *buf, size_t count);
该函数的功能最终是通过 SYS_write 这一系统调用来实现的。根据上面的约定,参数 fb、buf 和 count 分别存在寄存器 ebx、ecx 和 edx 中,而系统调用号 SYS_write 则放在寄存器 eax 中,当 int 0x80 指令执行完毕后,返回值可以从寄存器 eax 中获得。
或许你已经发现,在进行系统调用时至多只有 5 个寄存器能够用来保存参数,难道所有系统调用的参数个数都不超过 5 吗?当然不是,例如 mmap 函数就有 6 个参数,这些参数最后都需要传递给系统调用 SYS_mmap:
void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset);
当一个系统调用所需的参数个数大于 5 时,执行int 0x80 指令时仍需将系统调用功能号保存在寄存器 eax 中,所不同的只是全部参数应该依次放在一块连续的内存区域里,同时在寄存器 ebx 中保存指向该内存区域的指针。系统调用完成之后,返回值仍将保存在寄存器 eax 中。
由于只是需要一块连续的内存区域来保存系统调用的参数,因此完全可以像普通的函数调用一样使用栈(stack)来传递系统调用所需的参数。但要注意一点,Linux 采用的是 C 语言的调用模式,这就意味着所有参数必须以相反的顺序进栈,即最后一个参数先入栈,而第一个参数则最后入栈。如果采用栈来传递系统调用所需的参数,在执行int 0x80 指令时还应该将栈指针的当前值复制到寄存器 ebx中。

posted @ 2020-11-07 16:49  20209320dyy  阅读(107)  评论(0编辑  收藏  举报