深入理解系统调用

本人学号最后两位为30,根据 syscall_32.tbl 中的相应映射,选择 utime 系统调用展开分析。

 

深入理解系统调用_第1张图片

 

 

 

utime 系统调用的功能为“更改一个文件的访问和修改时间”,实际使用的效果如下:

深入理解系统调用_第2张图片

可以看到,在 03:11 创建的 file1 和 file2 文件,未经过 utime 系统调用的处理,最后修改时间、最后访问时间、更改状态时间均为 03:11。

经过 ./utime file1 的命令后,可以看到 file1 的最后修改时间和最后访问时间未变,但是更改状态时间则更改为程序运行时的时间,程序成功运行。

以下为通过汇编指令触发的具体的代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main (int argc, char **argv)
{
 int i, fd;
 struct stat statbuf;
 struct utimbuf timebuf;
 
 for(i = 1; i < argc; i++)
 {
 if(stat(argv[i], &statbuf) < 0)
 {
 printf ("%s: stat error\n", argv[i]);
 continue;
 }
 if((fd = open(argv[i], O_RDWR | O_TRUNC)) < 0)
 {
 printf ("%s: open error\n", argv[i]);
 continue;
 }
 close(fd);
 timebuf.actime = statbuf.st_atime;
 timebuf.modtime = statbuf.st_mtime;

 int res; // 存储函数运算结果
 asm volatile(
    "movl %1, %%ebx\n\t"  // 将第一个参数 argv[i] 放入 ebx 寄存器
    "movl %2, %%ecx\n\t"  // 将第二个参数 &timebuf 放入 ecx 寄存器
    "movl $30, %%eax\n\t" // utime 的系统调用号为30,将其放入 eax 寄存器
    "int $0x80\n\t" // 触发系统中断
    "movl %%eax, %0\n\t" // 将函数处理结果返回给 res 变量中
    :"=m"(res)
    :"b"(argv[i]),"c"(&timebuf)
 );
//  if(utime(argv[i], &timebuf) < 0)
if(res < 0)
 {
 printf ("%s: utime error\n", argv[i]);
 continue;
 }
 }
 exit(0);
}

 

通过gdb跟踪该系统调用的内核处理过程如下:

(1) 启动qemu,并进入gdb调试状态

深入理解系统调用_第3张图片

深入理解系统调用_第4张图片

(2) 在指定的系统调用处打断点,并在qemu中运行写好的程序

进入 utime 的系统调用 syscall 处的入口,接下来单步调试,看具体过程

(3) 保存现场

深入理解系统调用_第5张图片

将 utime 系统调用相关参数存入寄存器,加载运行,最后恢复现场

深入理解系统调用_第6张图片

直至系统调用结束,无其他步骤执行。

 

总结:

即便是最简单的程序,也难免要用到诸如输入、输出以及退出等操作,而要进行这些操作则需要调用操作系统所提供的服务,也就是系统调用。

在 Linux 平台下有两种方式来使用系统调用:利用封装后的 C 库(libc)或者通过汇编直接调用。

在32位环境中(也是本文的实验环境),

Linux 下的系统调用是通过中断(int 0x80)来实现的。在执行 int 80 指令时,寄存器 eax 中存放的是系统调用的功能号,而传给系统调用的参数则必须按顺序放到寄存器 ebx,ecx,edx,esi,edi 中,当系统调用完成之后,返回值可以在寄存器 eax 中获得所有的系统调用功能号都可以在文件 /usr/include/bits/syscall.h 中找到,为了便于使用,它们是用 SYS_ 这样的宏来定义的,如 SYS_write、SYS_exit 等。

你可能感兴趣的:(深入理解系统调用)