深入理解系统调用

一、实验要求

  1. 找一个系统调用,系统调用号为学号最后2位相同的系统调用;
  2. 通过汇编指令触发该系统调用;
  3. 通过gdb跟踪该系统调用的内核处理过程;
  4. 重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化。

二、实验目的

  1. 理解Linux操作系统调用;
  2. 了解系统调用过程中内核堆栈状态的变化过程。

三、实验环境

  实验楼环境:https://www.shiyanlou.com/courses/195/learning

四、实验过程

  (一)查找所需要的编号以及环境测试

    进入根目录下,通过  find . -name 'syscall_64.tbl'  查找系统调用表,我的学号尾号为79,因此查表之后,选择的系统调用为 getcwd深入理解系统调用_第1张图片 

getcwd系统调用介绍:

每个进程都有两个目录相关属性:根目录和当前目录,分别用于解释绝对路径和相对路径getcwd()会返回当前工作目录的绝对路径,如果当前目录不属于当前进程的根目录(例如:该进程使用chroot设置了一个新的文件系统根目录,但是没有将当前目录的根目录替换成新的)。

更新实验menu得环境,使用新版本

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

(二)编写汇编触发系统调用

    首先判断C语言能否使用该系统调用

#include 
int main()
{
    char buf[80];
    getcwd(buf, sizeof(buf));
    printf("current working directory : %s\n", buf);
}

  修改menuOS中test代码。添加关于getcwd得系统调用,此处使用上述代码代替,后期换成汇编代码执行。   

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

将C语言中的调用函数部分改成汇编执行

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

  搭建好了MenuOS后进行调试跟踪

       (三) gdb跟踪内核处理过程

   首先启动muneOS ,启动后会暂停继续执行,然后使用命令行进入gdb系统调试。

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

 设置系统系统调用得阻塞点。

 启动成功。执行我们在MenuOS中编写得getcwd命令,可以发现他会调用getcwd系统调用,因此会被gdb中断,开始查看调用流程。

重新编译。主要命令如下:

qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s 

#开启新的terminal
gdb 
target remote:1234
#设置断点
b sys_getcwd
c

 

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

执行该命令后,就会发现系统已经被阻塞。可以进行调试跟踪。可以看到是从3212行开始调用

 

 

 

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

 

 由于实验环境的代码可读性较差,因此招了一份源码如下所示。

源码分析如下所示。

系统调用的源码如下所示:

YSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)
{
    int error;
    struct path pwd, root;//一个存放当前目录,一个存放根目录
    char *page = __getname();

    if (!page)
        return -ENOMEM;

    rcu_read_lock(); //加锁,防止读取时当前路径被修改
    get_fs_root_and_pwd_rcu(current->fs, &root, &pwd);
    //获取根目录和当前目录项。get_fs_root_and_pwd_rcu函数就是根据fs结构来获取&root和&pwd

    error = -ENOENT;
    if (!d_unlinked(pwd.dentry)) {
        unsigned long len;
        char *cwd = page + PATH_MAX;
        int buflen = PATH_MAX;

        prepend(&cwd, &buflen, "\0", 1);
        error = prepend_path(&pwd, &root, &cwd, &buflen);
        rcu_read_unlock();//读取完成,释放上边加的锁

        if (error < 0)
            goto out;

        /* Unreachable from current root */
        if (error > 0) {
            error = prepend_unreachable(&cwd, &buflen);
            if (error)
                goto out;
        }

        error = -ERANGE;
        len = PATH_MAX + page - cwd;
        if (len <= size) {
            error = len;
            if (copy_to_user(buf, cwd, len))
                error = -EFAULT;
        }
    } else {
        rcu_read_unlock();
    }

out:
    __putname(page);
    return error;
}

 

此外还对getcwd方法的C语言源码进行了阅读

char *
__getcwd (char *buf, size_t size)
{
  char *path;
  char *result;

  //确定缓冲区的长度
  size_t alloc_size = size;
  if (size == 0)
    {
      if (buf != NULL)
    {
      __set_errno (EINVAL);
      return NULL;
    }

      alloc_size = MAX (PATH_MAX, __getpagesize ());
    }
  //申请缓冲区
  if (buf == NULL)
    {
      path = malloc (alloc_size);
      if (path == NULL)
        return NULL;
    }
  else
    path = buf;

  int retval;
  //调用系统调用获取当前工作目录
  retval = INLINE_SYSCALL (getcwd, 2, path, alloc_size);
  //获取成功
  if (retval >= 0)
    {
      //重新设置缓冲区
      if (buf == NULL && size == 0)
          buf = realloc (path, (size_t) retval);

      if (buf == NULL)
        buf = path;

      return buf;
    }

   //如果出错了,且出错原因是文件路径太长,则执行generic_getcwd获取当前文件路径。 generic_getcwd是内部函数,源码不可见。
  if (errno == ENAMETOOLONG)
    {

      if (buf == NULL && size == 0)
      {
          free (path);
          path = NULL;
      }


      result = generic_getcwd (path, size);


      if (result == NULL && buf == NULL && size != 0)
           free (path);


      return result;
    }

  assert (errno != ERANGE || buf != NULL || size != 0);

  if (buf == NULL)
    free (path);

  return NULL;
}
weak_alias (__getcwd, getcwd)

 

四、实验总结(分析系统调用的工作机制)

  系统调用主要分为以下五步,指令触发系统调用(这里是syscall),保存现场,中断处理,恢复现场,中断返回,第一步在用户态,剩下四步在内核态。从系统调用的整个过程来看,

首先用户态程序执行某些代码,发生syscall,触发系统调用;然后系统进入内核态,完成内核初始化后,调用entry_SYSCALL_64 ()。完成现场的保存,将关键寄存器压栈,包括程序计数器,内存地址等相关信息,方便系统调用完成后继续执行。

然后从CPU内部的MSR寄存器来查找系统调⽤处理⼊⼝,更改CPU的指令指针(eip/rip)到系统调⽤处理⼊⼝ ,调用do_syscall_64()。do_syscall_64()函数根据系统调用号,调用相关的函数,比如本例中的getcwd()。调用结束后,保存现场和恢复现场时的CPU存器也通过CPU内部的存储器快速保存和恢复 。系统调用返回,回到用户态程序

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