操作系统—系统调用(实验)

文章目录

  • 系统调用
    • 1.实验目标
    • 2.实验过程记录
      • (1).理解系统调用接口
      • (2).阅读argraw、argint、argaddr和argstr
      • (3).理解系统调用的解耦合实现方式
      • (4).wait系统调用的非阻塞选项实现
      • (5).yield系统调用的实现
    • 3.存在的问题及解决方案
    • 实验小结

系统调用

1.实验目标

  阅读并了解xv6内核中关于系统调用的部分,基于实验手册的要求完成几个任务:完成wait系统调用的非阻塞选项、实现yield系统调用、阅读并理解系统调用的代码。

2.实验过程记录

(1).理解系统调用接口

操作内容:在VS Code中查看并理解user/usys.pl的代码

#!/usr/bin/perl -w
# Generate usys.S, the stubs for syscalls.
print "# generated by usys.pl - do not edit\n";

print "#include \"kernel/syscall.h\"\n";

sub entry {
    my $name = shift;
    print ".global $name\n";
    print "${name}:\n";
    print " li a7, SYS_${name}\n";
    print " ecall\n";
    print " ret\n";
}

  usys.pl是基于perl语言的代码文件,它的主要作用是为了所有当前内核的系统调用生成对应的汇编代码,简单来理解就是,它会根据传入系统调用的名字生成如下的一段系统调用对应的汇编代码:

.global $name
${name}:
    li a7, SYS_${name}
    ecall
    ret

  这段代码就很熟悉了,首先粘贴系统调用的name作为当前汇编代码生成文件的入口(通过.global指令指定入口),之后使用li(load immediate)将系统调用的调用号座位一个立即数加载到a7上,之后调用ecall进入操作系统内核,在执行结束之后再用ret返回。

(2).阅读argraw、argint、argaddr和argstr

操作内容:在VS Code中查看并理解argraw等函数的代码

static uint64 argraw(int n) {
  struct proc *p = myproc();
  switch (n) {
    case 0:
      return p->trapframe->a0;
    case 1:
      return p->trapframe->a1;
    case 2:
      return p->trapframe->a2;
    case 3:
      return p->trapframe->a3;
    case 4:
      return p->trapframe->a4;
    case 5:
      return p->trapframe->a5;
  }
  panic("argraw");
  return -1;
}

  实际上它的过程非常简单,调用系统调用时传入的参数会被存储在当前进程的trapframe当中,它最多可以接受6个参数(这一点和Linux内核是一致的,先前我在完成第一次选做作业的自定义系统调用时,在Linux内核当中看到的基于va_args实现的系统调用接口也最多只能接受6个参数)。

  在有了argraw这个函数之后,后续就可以通过argint、argaddr和argstr三个函数来获取系统调用的参数,分别是整数、地址和字符串,这撒个函数的实现相当简单,因为地址和整数本质上都是获取整数,因此可以直接通过argraw获取对应的内容,但是argstr的实现相对复杂一点,因为C语言的字符串实际上字符数组,作为参数传入的时候会退化成指针,所以实现获取字符串的过程可以首先通过argaddr获取地址,之后再通过实现的一个fetchstr函数获取字符串的内容并且存储到buf当中:

// Fetch the nth 32-bit system call argument.
int argint(int n, int *ip) {
  *ip = argraw(n);
  return 0;
}

// Retrieve an argument as a pointer.
// Doesn't check for legality, since
// copyin/copyout will do that.
int argaddr(int n, uint64 *ip) {
  *ip = argraw(n);
  return 0;
}

// Fetch the nth word-sized system call argument as a null-terminated string.
// Copies into buf, at most max.
// Returns string length if OK (including nul), -1 if error.
int argstr(int n, char *buf, int max) {
  uint64 addr;
  if (argaddr(n, &addr) < 0) return -1;
  return fetchstr(addr, buf, max);
}

  于是我在syscall.c当中又找到了fetchstr函数的实现细节:

// Fetch the nul-terminated string at addr from the current process.
// Returns length of string, not including nul, or -1 for error.
int fetchstr(uint64 addr, char *buf, int max) {
  struct proc *p = myproc();
  int err = copyinstr(p->pagetable, buf, addr, max);
  if (err < 0) return err;
  return strlen(buf);
}

  fetchstr的实现也并不复杂,不过因为处于内核态,它的实现不像我们会使用的类似strcpy之类的函数,首先获取到当前的进程控制块到p,然后从p的页表中拷贝出对应字节、对应地址的所有数据,在没有发生报错的时候则会正常返回字符串的长度。

(3).理解系统调用的解耦合实现方式

操作内容:在VS Code中查看并理解syscall.c的代码

static uint64 (*syscalls[])(void) = {
[SYS_fork] sys_fork,   [SYS_exit] sys_exit,     
[SYS_wait] sys_wait,     [SYS_pipe] sys_pipe,
[SYS_read] sys_read,   [SYS_kill] sys_kill,     
[SYS_exec] sys_exec,     [SYS_fstat] sys_fstat,
[SYS_chdir] sys_chdir, [SYS_dup] sys_dup,       
[SYS_getpid] sys_getpid, [SYS_sbrk] sys_sbrk,
[SYS_sleep] sys_sleep, [SYS_uptime] sys_uptime, 
[SYS_open] sys_open,     [SYS_write] sys_write,
[SYS_mknod] sys_mknod, [SYS_unlink] sys_unlink, 
[SYS_link] sys_link,     [SYS_mkdir] sys_mkdir,
    [SYS_close] sys_close, [SYS_rename] sys_rename,
};

void syscall(void) {
  int num;
  struct proc *p = myproc();

  num = p->trapframe->a7;
  if (num > 0 && num < NELEM(syscalls) && syscalls[num]) {
    p->trapframe->a0 = syscalls[num]();
  } else {
    printf("%d %s: unknown sys call %d\n", p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }
}

  这个过程也比较简单的,syscall.c当中将所有的系统调用存储在同一个指针数组当中,在具体调用某个系统调用的时候,syscall函数会通过trapframe当中的a7来访问刚刚存入的系统调用号,之后再直接通过下标访问就可以比较轻松地访问到对应的系统调用了,这里也可以看到,在根据系统调用号找到了对应的系统调用之后会执行这条指令:

    p->trapframe->a0 = syscalls[num]();

  也就是说,系统调用的执行结果会被存储在a0当中。

(4).wait系统调用的非阻塞选项实现

操作内容:参考实验手册要求,修改wait系统调用的实现系统,增加一个非阻塞选项参数flags
  为了增加sys_wait的非阻塞版本,我们首先需要在sys_wait中增加获取整型变量flags作为非阻塞标志的流程:

uint64 sys_wait(void) {
  uint64 p;
  if (argaddr(0, &p) < 0) return -1;
  int flags;
  if (argint(1, &flags) < 0) return -1;
  return wait(p, flags);
}

  这个过程是比较简单的,参考argraw、argint的实现可以知道,argint函数第一个参数传入的实际上是系统调用参数的位置,因为flags显然是在第二个位置上的参数,因此仿照上面获取地址p的方式,获取flags参数,之后与p一起传入wait函数即可,那么接下来就可以修改wait系统调用的函数声明了

int             wait(uint64, int);

  接下来就需要直接修改wait系统调用的实现本身了:

int wait(uint64 addr, int flags) {if (flags != 1) sleep(p, &p->lock);  // DOC: wait-sleep
    else {
        release(&p->lock);
        return -1;
    }
  }
}

  这里省略了wait函数的一部分,前面一部分的代码没有改动,具体的wait系统调用的实现已经再上一次的实验中分析过了,因此这里只需要改动最后一部分是否阻塞即可。
  当flags不为1的时候就阻塞等待,否则释放当前获取的自旋锁,然后返回-1,这样就可以完成非阻塞等待的全流程了,接下来尝试使用waittest完成对于wait系统调用的测试:
在这里插入图片描述
  可以看到,waittest在xv6内已经测试完毕,之后在内核仓库的目录下使用grade-lab-syscall进行测试:
在这里插入图片描述

(5).yield系统调用的实现

操作内容:参考实验手册要求,增加一个新的yield系统调用
  首先在kernel/syscall.h当中增加一个SYS_yield系统调用号,这里直接继承最后一个系统调用后的第一个系统调用号23:
在这里插入图片描述
  之后再kernel/syscall.c当中增加sys_yield的声明,并且在系统调用表syscalls当中增加刚刚添加的系统调用sys_yield:
在这里插入图片描述
  之后再sysproc.c当中添加sys_yield函数的定义:

uint64 sys_yield(void) {
    struct proc *p = myproc();
    uint64 pc = p->trapframe->epc;
    printf("start to yield, user pc %p\n", pc);
    yield();
    return 0;
}

  因为yield函数本身实现非常简单,所以不需要单独传参,为了满足实验手册的要求,在sys_yield当中获取了当前进程的PCB,从它的trapframe中获取epc即为进入trap时的用户pc,这里没有对PCB进行加锁,这是因为struct proc将trapframe划归为私有部分,访问时不需要持有锁。
然后需要再user.h和usys.pl当中添加代码
在这里插入图片描述
在这里插入图片描述
  最后,在Makefile的UPROGS增加_yieldtest的编译目标:
在这里插入图片描述
  之后编译运行,在xv6中执行yieldtest测试,发现效果是一致的:
在这里插入图片描述
  然后我尝试使用了grade-lab-syscall进行测试,发现有一个叫做time的点一直没有办法通过:
在这里插入图片描述
  于是我去MIT的课程网站上查看了一下实验要求,发现需要自行创建一个time.txt,然后写入做实验的时间:
在这里插入图片描述
  在创建了time.txt并且写入实验时间之后就100分通过了测试:
在这里插入图片描述

3.存在的问题及解决方案

问题:为什么在多CPU的情况下测试yield会出现乱码?

  首先我们来看看测试的时候会发生什么:
在这里插入图片描述
  可以发现,其实这一段输出信息混乱并不是完全没有规律,实际上应该是多个不同的需要打印出来的内容因为一些问题混杂在了一起,我猜测这个问题可能出在打印过程没有对缓冲区加锁,但是参考了printf.c当中对printf的实现,它实际上是加锁了的:

// Print to the console. only understands %d, %x, %p, %s.
void
#ifdef TEST
_printf(const char *filename, unsigned int line, char *fmt, ...)
#else
_printf(char *fmt, ...)
#endif
{
  …
  locking = pr.locking;
  if (locking) acquire(&pr.lock);#endif
  if (locking) release(&pr.lock);
}

void printfinit(void) {
  initlock(&pr.lock, "pr");
  pr.locking = 1;
}

  所以问题应该不在给输出过程加锁这件事上,最终我猜测是多核调度的过程中,不同核心上运行的进程的缓冲区不同步,而缓冲区到IO设备上这个过程可能是无法保证线程安全的,因此导致了输出也不同步的问题。

实验小结

  • 1、本次实验阅读了xv6内核中系统调用的实现细节,了解了系统调用的实现与调用是如何分离的。
  • 2、完成了对于wait系统调用的修改,完成了非阻塞式的wait系统调用,之后还增加了yield系统调用,最后通过grade-lab-syscall完成了这次修改/增加的两个系统调用的测试。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/569009.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于Python+Selenium+Pytest的Dockerfile如何写

使用 Dockerfile 部署 Python 应用程序与 Selenium 测试 在本文中&#xff0c;我们将介绍如何使用 Dockerfile 部署一个 Python 应用程序&#xff0c;同时利用 Selenium 进行自动化测试。我们将使用官方的 Python 运行时作为父镜像&#xff0c;并在其中安装所需的依赖项和工具…

Error creating bean with name ‘ribbonLoadBalancingHttpClient‘~

场景 利用Ribbon来实现负载均衡 报错提示 Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ribbonLoadBalancer defined in org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration: Unsatisfi…

Linux中的vi与vim:编辑器的王者之争与深度探索

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《Linux &#xff1a;从菜鸟到飞鸟的逆袭》&#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、前言 1、Linux的起源与发展 2、vi与vim的历史与发展 …

opencv android 使用笔记

目录 获取app路径&#xff1a; 下载&#xff1a;OpenCV-android-sdk cmakelist配置&#xff1a; 头文件路径&#xff1a; 编译报错&#xff1a;clang: error: linker command failed with exit code 1 (use -v to see invocation) 读取图片例子 保存mp4 获取app路径&am…

UE4网络图片加载库(带内存缓存和磁盘缓存)

UE4网络图片加载库,带内存缓存和磁盘缓存,支持自定义缓存大小,支持蓝图和C++代码调用 1、调用示例 2、对外暴露函数 3、源代码-网络模块 KeImageNet.h // Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreM…

链表操作III

看这篇文章之前&#xff0c;可以先看看链表操作I和链表操作II。而这篇文章主要是想说明两道关于链表环的问题。 环形链表 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则…

【六十】【算法分析与设计】用一道题目解决dfs深度优先遍历,dfs中节点信息,dfs递归函数模板进入前维护出去前回溯,唯一解的剪枝飞升返回值true

路径之谜 题目描述 小明冒充X星球的骑士,进入了一个奇怪的城堡。 城堡里边什么都没有,只有方形石头铺成的地面。 假设城堡地面是nn个方格。如下图所示。 按习俗,骑士要从西北角走到东南角。可以横向或纵向移动,但不能斜着音走,也不能跳跃。每走到一个新方格,就要向正北 方和正西…

【Linux】解决ubuntu20.04版本插入无线网卡没有wifi显示【无线网卡Realtek 8811cu】

ubuntu为Realtek 8811cu安装驱动&#xff0c;解决wifi连接问题 1、确认无线网卡的型号-Realtek 8810cu2、下载并配置驱动 一句话总结&#xff1a;先确定网卡的型号&#xff0c;然后根据网卡的型号区寻找对应的驱动下载&#xff0c;下载完成之后在ubuntu系统中进行编译&#xff…

3D 文件格式的江湖纷争

自从上世纪 60 年代计算机辅助设计(Computer Aided Design, CAD)发明已来,3D 图形产业繁荣发展,逐步覆盖工业制造、影视游戏、VR/AR 、3D 打印等各个领域。如果说 3D 模型是构成 XR 应用场景的基础组件,那么 3D 文件格式就是构建 XR 世界沟通语言。而伴随各种 3D 建模软件…

C++链表操作入门

数据结构基础&#xff1a;链表操作入门 数据结构基础&#xff1a;链表操作入门链表的基本概念链表的基本操作输出链表插入节点删除节点查找值 完整的链表操作示例结语 数据结构基础&#xff1a;链表操作入门 在计算机科学中&#xff0c;数据结构是组织和存储数据的方式&#x…

H264 编码标准常见术语解释

H264 编码标准 H.264编码标准&#xff0c;也被称作MPEG-4 AVC&#xff08;Advanced Video Coding&#xff09;&#xff0c;是一种被广泛使用的数字视频压缩标准&#xff0c;由国际电信联盟&#xff08;ITU-T&#xff09;和国际标准化组织&#xff08;ISO&#xff09;共同开发。…

【蓝牙协议栈】【BLE】低功耗蓝牙工作流程(角色\广播\扫描\连接等专业名词介绍)

1. 精讲蓝牙协议栈&#xff08;Bluetooth Stack&#xff09;&#xff1a;SPP/A2DP/AVRCP/HFP/PBAP/IAP2/HID/MAP/OPP/PAN/GATTC/GATTS/HOGP等协议理论 2. 欢迎大家关注和订阅&#xff0c;【精讲蓝牙协议栈】和【Android Bluetooth Stack】专栏会持续更新中.....敬请期待&#x…

谷歌搜索seo排名怎么做上去?

谷歌算法纵使千变万化&#xff0c;用户体验&#xff08;UX&#xff09;也始终是核心&#xff0c;用户体验包含很多&#xff0c;但核心就是让访问你网站的人觉得你的网站看着顺眼&#xff0c;同时轻松找到他们需要的信息或服务&#xff0c;这意味着你的网站得易于导航&#xff0…

命名空间:namespace

对于无名命名空间 &#xff1a;但是不能再次定义相同名称的变量 在同一文件中

Stable Diffusion WebUI 使用 LoRA 调整风格——详细教程

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里&#xff0c;订阅后可阅读专栏内所有文章。 大家好&#xff0c;我是水滴~~ 本教程旨在深入探讨 LoRA 模型的奥秘&#xff0c;涵盖其基本概念、独特作用以及实操指南。我们将从下载和使用LoRA的步…

Laravel 6 - 第十五章 验证器

​ 文章目录 Laravel 6 - 第一章 简介 Laravel 6 - 第二章 项目搭建 Laravel 6 - 第三章 文件夹结构 Laravel 6 - 第四章 生命周期 Laravel 6 - 第五章 控制反转和依赖注入 Laravel 6 - 第六章 服务容器 Laravel 6 - 第七章 服务提供者 Laravel 6 - 第八章 门面 Laravel 6 - …

微信小程序实时日志使用,setFilterMsg用法

实时日志 背景 为帮助小程序开发者快捷地排查小程序漏洞、定位问题&#xff0c;我们推出了实时日志功能。开发者可通过提供的接口打印日志&#xff0c;日志汇聚并实时上报到小程序后台。开发者可从We分析“性能质量->实时日志->小程序日志”进入小程序端日志查询页面&am…

【八股】计算机网络篇

网络模型 应用层【HTTP&#x1f449;报文/消息】 传输层【TCP或UDP&#x1f449;段&#x1f449;MSS】网络层【IP、寻址和路由&#x1f449;MTU】 ①IP&#xff08;Internet Protocol&#xff0c;网际协议&#xff09;主要作用是定义数据包的格式、对数据包进行路由和寻址&…

【Linux-14】进程地址空间&虚拟空间&页表——原理&知识点详解

前言 大家好吖&#xff0c;欢迎来到 YY 滴 系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过Linux的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的《Lin…

STM32的GPIO输入和输出函数详解

系列文章目录 STM32单片机系列专栏 C语言术语和结构总结专栏 文章目录 1. GPIO模式 2. GPIO输出 2.1 RCC 2.2 GPIO 3. 代码示例 3.1 RCC时钟 3.2 GPIO初始化 3.3 GPIO输出函数 3.4 推挽输出和开漏输出 4. GPIO输入 4.1 输入模式 4.2 数据读取函数 5. C语言语法 1…
最新文章