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

使用库函数APIC代码中嵌入汇编代码两种方式使用同一个系统调用

符钰婧 原创作品转载请注明出处 

 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”

 此实验使用了122号系统调用uname来获取当前UNIX系统的名称、版本和主机等信息。

一、实验过程

1、使用库函数API进行调用的代码【namel.c】如下:

#include <sys/utsname.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    struct utsname testbuff;
    int fb=0;

    fb=uname(&testbuff);
    if(fb<0)
    {
        perror("uname");
        return 0;
    }

    else

     {
        printf("sysname:%s  nodename:%s  release:%s  version:%s  machine:%s  ",testbuff.sysname,testbuff.nodename,testbuff.release,testbuff.version,testbuff.machine);

#if _UTSNAME_DOMAIN_LENGTH - 0
# ifdef __USE_GNU
    printf(" domainame:%s  ",testbuff.domainname);
# else
    printf(" __domainame:%s  ",testbuff.__domainname);
# endif
#endif
    }
    return 0;
}

编译后输出结果为:

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

2、然后将部分代码修改之后变为使用汇编方式触发系统调用,部分代码【namel-asm.c】如下:

 int fb;

          asm volatile(

               "mov $0,%%ebx "

               "mov $0x7A,%%eax "

               "int $0x80 "

               "mov %%eax,%0 "

               : "=m" (testbuff)

          );

    fb=uname(&testbuff);

l 下面来分析汇编代码调用系统调用的工作过程。

第一个move是先将ebx清零,即令ebx为NULL;

第二个move是将0x7A(uname是122号系统调用)放到eax中(eax是传递系统调用号的),即用eax传递参数;

执行int $0x80指令开始进行系统调用,返回值用eax存储;

然后将eax放到%0(即变量testbuff)中。

编译后输出结果为:

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

二、总结

从视频和实验的学习中大概了解了系统调用的工作机制,尽管明白的肯定不够透彻,也足够帮助我消化这周的学习内容。

1、中断处理是从用户态进入内核态主要的方式,系统调用是一种特殊的中断。

中断处理的完整过程(由中断信号或者int指令完成):

将cs:eip的值,堆栈段寄存器当前的栈顶(ss:eip)和当前的标志寄存器(eflags)保存到内核堆栈中;同时将当前中断服务例程的入口加载到cs:eip中,当前堆栈段和eip也加载到CPU中。

执行完以上以上步骤之后,当前CPU在执行下一条指令时,就已经开始执行整个中断处理程序的入口了。

此时已经开始操作内核态的堆栈了。

若完成中断服务之后不发生进程调度,则继续执行指令(RESTORE_ALL和iret),然后返回到原来的状态;

若发生进程调度,那么当前发生的状态都会暂时的保存在系统中,当下一次发生调度再次回到当前进程时就继续执行指令RESTORE_ALL和iret。

2系统调用的工作机制:

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

用户态中xyz()函数就是系统调用对应的API;

这个API中封装了一个系统调用,这个系统调用会触发int 0x80的一个中断;

0x80这个中断向量就对应着system_call(内核代码的入口起点);

内核代码中可能会执行到对应的中断服务程序sys_xyz();

在中断服务程序执行完之后,可能会执行ret指令,此时可能会发生进程调度;

如果没有发生进程调度,就执行iret,返回到用户态接着执行其他指令。

总结:系统调用的三层皮:xyz(API)、system_call(中断向量)和sys_xyz(服务程序)。