MIT 6.s081操作系统实验 Lab2: system calls

2023-10-22 17:14

本文主要是介绍MIT 6.s081操作系统实验 Lab2: system calls,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 1 System call tracing
    • 1.1 主要思路
    • 1.2 系统调用流程
  • 2 Sysinfo
    • 2.1 kernel/kalloc.c 此文件用于实现分配物理空间的函数
      • 2.1.1 结构体定义
      • 2.1.2 空闲链表初始化
      • 2.1.3 内存的分配和释放
      • 2.1.4 获取空闲内存字节数
    • 2.2 kernel/proc.c 此文件用于进程管理
    • 2.3 sys_Sysinfo
      • 2.3.1读取用户传递的参数
      • 2.3.2 数据复制到用户态
      • sys_sysinfo具体实现
  • 3 总结

实验链接

1 System call tracing

写一个系统调用追踪器,作用是对于指定的系统调用号,对进程号,系统调用名称,返回值进行打印。

1.1 主要思路

  1. 每个系统调用都需要经过syscall函数,此函数从进程的trapframe取得将要进行调用的系统调用号num,因此思路就是在进行系统调用后,若num是否在进程指定的调用号中,则将以上信息进行打印。
  2. 完成trace系统调用:在1.中需要知道进程要追踪的指定系统调用号是什么,因此trace系统调用就是来指定系统调用号的,因此对于每个进程,为进程结构体中新增属性mask指定系统调用号集合,并且还需要在fork出子进程时,让子进程继承此属性。
    //kernel/proc.h
    struct proc {int pid;                     // Process ID....// mask that expresses which syscalls will be traced.int mask;
    };
    //kernel/proc.c
    int
    fork(void){.....np->mask = p->mask;
    }
    // kernel/sysproc.c
    uint64
    sys_trace(void){int mask;if(argint(0, &mask) < 0)return -1;myproc()->mask = mask;return 0;
    }
    

1.2 系统调用流程

根据Lab的介绍,系统调用的流程如下:

  • 系统调用函数需要被用户程序所感知,因此在/user/user.h中添加系统调用的方法int trace(int);,以供给用户调用
  • user/usys.pl脚本类似其他系统调用添加entry("trace");
    //user/usys.pl文件
    #!/usr/bin/perl -w
    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";
    }
    entry("fork");
    ...
    entry("trace");
    
  • Makefile会执行此脚本,生成user/usys.S
    //Makefile文件
    $U/usys.S : $U/usys.plperl $U/usys.pl > $U/usys.S
    
  • user/usys.S汇编代码中,用户调用trace,实际上会通过ecall跳转到真正的系统调用,具体的实现很简单,就是为1个a7寄存器赋值一个在头文件define好的系统调用号。
    # generated by usys.pl - do not edit
    #include "kernel/syscall.h"
    .....
    .global trace
    trace:li a7, SYS_traceecallret
    
  • 看到user/usys.S中,include了"kernel/syscall.h"的头文件,此头文件只有一些define值,因此以上汇编句,其实等价于li a7, 22,即a7 = 22,进行了一下寄存器赋值
    // System call numbers
    ...
    #define SYS_close  21
    #define SYS_trace  22
    
  • 22实际上是在kernel/syscall.c中的一个系统调用函数数组的下标,在系统调用时,进程取到寄存器a7的值作为系统调用函数数组的下标,至此终于真的调用到了这个函数,当然,最后我们还需要在kernel/sysproc.c中实现对应的函数。
    //kernel/syscall.c
    ........
    extern uint64 sys_trace(void);
    extern uint64 sys_sysinfo(void);
    static uint64 (*syscalls[])(void) = {
    .........
    [SYS_trace]   sys_trace,
    [SYS_sysinfo] sys_sysinfo,
    };
    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](); //※} ......if((p->mask >> num) & 1)printf("%d: syscall %s -> %d\n", p->pid, sys_call_name[num], p->trapframe->a0);
    }
    

2 Sysinfo

新增一个系统调用Sysinfo,传入的参数为以下结构体的指针,内核会将结构体填满:freemem表示空闲内存的字节数,nproc表示状态为UNUSED的进程数。

struct sysinfo {uint64 freemem;   // amount of free memory (bytes)uint64 nproc;     // number of process
};

提示1:sysinfo需要将数据复制到用户态,需要使用copy_out函数,可参考sys_fstat() (kernel/sysfile.c) filestat() (kernel/file.c)函数。
提示2:在kernel/kalloc.c中新增统计空闲内存的函数,在kernel/proc.c中新增统计空闲进程的函数。

2.1 kernel/kalloc.c 此文件用于实现分配物理空间的函数

主要是定义了一个空闲链表用来管空闲内存,包括初始化,内存回收,内存分配,以下是具体函数功能介绍,非常非常简单。

2.1.1 结构体定义

首先是定义了1个run的极简链表,用来串起空闲内存,其次是kmem结构体,用来放1个锁,还有空闲内存链表的表头。

struct run {struct run *next;
};struct {struct spinlock lock;struct run *freelist;
} kmem;

2.1.2 空闲链表初始化

  • kinit 此函数用于空闲链表初始化
    先使用initlock初始化锁,然后使用freerange指定初始化的内存范围是从endPHYSTOP

    extern char end[]; // first address after kernel.// defined by kernel.ld.
    extern char end[]; // first address after kernel.// defined by kernel.ld.
    void
    kinit()
    {initlock(&kmem.lock, "kmem");freerange(end, (void*)PHYSTOP);
    }
    
  • freerange 此函数用于将连续长度的内存空间释放掉
    释放的方法是使用kfree逐页(一页PGSIZE为4096B)将首地址插入到空闲链表中

    void
    freerange(void *pa_start, void *pa_end)
    {char *p;p = (char*)PGROUNDUP((uint64)pa_start);for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE)kfree(p);
    }
    

2.1.3 内存的分配和释放

  • kfree 此函数将1个物理页的首地址存放到空闲链表中
    实现上:
    • 先判断地址是否越界
    • 不越界则先将该页填满数据垃圾,避免数据泄露
    • 进行隐式链接:直接在该地址创建1个空闲链表节点,采用头插的方法将该节点作为新的空闲链表节点。这种链接方法为隐式链接,即直接在空闲的地址创建节点,这样的好处是空闲节点不用额外占据空间,可节约空间;坏处是隐式链接的稳定性较差,若某个链接断开或错漏,则整个链表失效。注意在链接前需要获得锁,在链接后释放锁,因为空闲链表是临界资源,同一时间只能由1个进程访问。
    // Free the page of physical memory pointed at by v,
    // which normally should have been returned by a
    // call to kalloc().  (The exception is when
    // initializing the allocator; see kinit above.)
    void
    kfree(void *pa)
    {struct run *r;if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)panic("kfree");// Fill with junk to catch dangling refs.memset(pa, 1, PGSIZE);r = (struct run*)pa;acquire(&kmem.lock);r->next = kmem.freelist;kmem.freelist = r;release(&kmem.lock);
    }
    
  • kalloc 此函数将空闲链表中的一页分配出去
    // Allocate one 4096-byte page of physical memory.
    // Returns a pointer that the kernel can use.
    // Returns 0 if the memory cannot be allocated.
    void *
    kalloc(void)
    {struct run *r;acquire(&kmem.lock);r = kmem.freelist;if(r)kmem.freelist = r->next;release(&kmem.lock);if(r)memset((char*)r, 5, PGSIZE); // fill with junkreturn (void*)r;
    }
    

2.1.4 获取空闲内存字节数

  • 自实现get_freemem,用于返回当前内存空闲字节数
    根据以上分析,显然内存空闲字节数就是空闲链表的节点数量乘上每个物理页的大小,注意在访问空闲链表的时候需要加锁,访问结束后释放锁,因为可能出现以下场景:
    • 进程A调用sysinfo,并获取了kmem.freelist
    • 进程B调用malloc,并触发了kalloc,若未加锁,则kalloc将链表头部的节点kmem.freelist分配给进程B
    • 进程A尝试通过run链表的next指针尝试去获取下一个节点,但kmem.freelist已经给进程B在使用,此地址不再存放之前的next地址,而是进程B的私有数据,不应该再在此时访问,这样会导致数据泄露,破坏内存的安全性,并且next在此语义下仍是一个地址,可能会出现访问越界等情况。
    uint64 get_freemem(){uint64 freemem = 0;acquire(&kmem.lock);struct run *p= kmem.freelist;while(p){freemem += PGSIZE;p = p->next;}release(&kmem.lock);return freemem;
    }
    

2.2 kernel/proc.c 此文件用于进程管理

因为这个文件的体量较为庞大,后续还有和进程管理更强相关的如Lab thread: Multithreading实现多线程,Lab lock: Parallelism/locking实现并行和进程锁,因此暂不做详细解析。
目前任务是统计空闲进程的数量。
采用的是最简单的遍历思路,在文件中定义了进程结构体数组struct proc proc[NPROC];直接遍历此进程数组,获取进程状态stateUNUSED的数量。参考已有代码中遍历进程数组的某个函数procdump:利用for(p = proc; p < &proc[NPROC]; p++)遍历进程数组,利用p->state判断进程状态。

//Get the num of processes that are not in UNUSED state
int 
not_UNUSED_process_num(void){int count = 0;for(int i = 0; i < NPROC; i ++)count += (proc[i].state != UNUSED);return count;
}

2.3 sys_Sysinfo

提示1:sysinfo需要将数据复制到用户态,因此需要使用copy_out函数,可参考sys_fstat() (kernel/sysfile.c) filestat() (kernel/file.c)函数。sys_fstat() (kernel/sysfile.c) 函数如下:

// kernel/sysfile.c
uint64
sys_fstat(void)
{struct file *f;uint64 st; // user pointer to struct statif(argfd(0, 0, &f) < 0 || argaddr(1, &st) < 0)return -1;return filestat(f, st);
}

主要完成的是2个工作:

2.3.1读取用户传递的参数

此系统调用有2个参数,参数1为struct file *类型,参数2为uint64类型,但参数不是直接传递的,而是需要通过argxx去读取trapframe寄存器,因为在用户调用参数前,会将参数按顺序存放到trapframea0a6寄存器。

例如static int argfd(int n, int *pfd, struct file **pf)用于读取用户传进来的fd,并从进程的打开文件列表ofile中,根据fd索引到其指向的文件结构体file

// Fetch the nth word-sized system call argument as a file descriptor
// and return both the descriptor and the corresponding struct file.
static int
argfd(int n, int *pfd, struct file **pf)
{int fd;struct file *f;if(argint(n, &fd) < 0)return -1;if(fd < 0 || fd >= NOFILE || (f=myproc()->ofile[fd]) == 0)return -1;if(pfd)*pfd = fd;if(pf)*pf = f;return 0;
}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;
}

内核获得用户态函数参数调用流程

2.3.2 数据复制到用户态

参考filestat() (kernel/file.c),此处使用copyout函数将数据从内核态复制到用户态。copyout函数原型如下,它从src开始,复制len字节到虚拟地址dstva中。
int copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)

// Get metadata about file f.
// addr is a user virtual address, pointing to a struct stat.
int
filestat(struct file *f, uint64 addr)
{struct proc *p = myproc();struct stat st;if(f->type == FD_INODE || f->type == FD_DEVICE){ilock(f->ip);stati(f->ip, &st);iunlock(f->ip);if(copyout(p->pagetable, addr, (char *)&st, sizeof(st)) < 0)return -1;return 0;}return -1;
}

sys_sysinfo具体实现

  1. 读取用户传递参数:用户传递进来的是1个sysinfo结构体的指针,因此先使用argaddr获取此指针
  2. 获取需要的系统信息
  3. 通过copyout将获取的信息传递到用户态。
uint64
sys_sysinfo(void)
{uint64 p;if(argaddr(0, &p) < 0)return -1;struct sysinfo sp;sp.nproc = not_UNUSED_process_num();sp.freemem = get_freemem();if(copyout(myproc()->pagetable, p, (char *)(&sp), sizeof(sp)) < 0)return -1;return 0;
}

3 总结

通过这个实验,我了解了:

  • 系统调用的流程,但还不了解具体是如何从用户态陷入内核态(TODO)
  • xv6使用隐式链表数据结构来进行空闲内存的分配和回收,同时加锁防止多个进程使用此临界资源
  • 进程管理是是1个进程结构体数组维护,可通过遍历获取每个进程的状态
  • 内核态要将数据返回给用户态,因为用户态有自己的进程空间,使用的虚拟地址无法直接在内核态解引用,所以在内核态需要实现copyout函数,利用用户态进程的页表来进行虚拟地址到物理地址的寻址,再直接将数据拷贝到用户态进程的虚拟地址映射的物理地址上。

这篇关于MIT 6.s081操作系统实验 Lab2: system calls的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/262644

相关文章

Linux操作系统 初识

在认识操作系统之前,我们首先来了解一下计算机的发展: 计算机的发展 世界上第一台计算机名叫埃尼阿克,诞生在1945年2月14日,用于军事用途。 后来因为计算机的优势和潜力巨大,计算机开始飞速发展,并产生了一个当时一直有效的定律:摩尔定律--当价格不变时,集成电路上可容纳的元器件的数目,约每隔18-24个月便会增加一倍,性能也将提升一倍。 那么相应的,计算机就会变得越来越快,越来越小型化。

STM32(十一):ADC数模转换器实验

AD单通道: 1.RCC开启GPIO和ADC时钟。配置ADCCLK分频器。 2.配置GPIO,把GPIO配置成模拟输入的模式。 3.配置多路开关,把左面通道接入到右面规则组列表里。 4.配置ADC转换器, 包括AD转换器和AD数据寄存器。单次转换,连续转换;扫描、非扫描;有几个通道,触发源是什么,数据对齐是左对齐还是右对齐。 5.ADC_CMD 开启ADC。 void RCC_AD

Partical System

创建"粒子系统物体"(点击菜单GameObject -> Create Other -> Particle System) 添加"粒子系统组件"(点击Component -> Effects  ->Particle System) 粒子系统检视面板  点击粒子系统检视面板的右上角的"+"来增加新的模块。(Show All Modules:显示全部) 初始化模块: •

小技巧绕过Sina Visitor System(新浪访客系统)

0x00 前言 一直以来,爬虫与反爬虫技术都时刻进行着博弈,而新浪微博作为一个数据大户更是在反爬虫上不遗余力。常规手段如验证码、封IP等等相信很多人都见识过…… 当然确实有需要的话可以通过新浪开放平台提供的API进行数据采集,但是普通开发者的权限比较低,限制也比较多。所以如果只是做一些简单的功能还是爬虫比较方便~ 应该是今年的早些时候,新浪引入了一个Sina Visitor Syst

HNU-2023电路与电子学-实验3

写在前面: 一、实验目的 1.了解简易模型机的内部结构和工作原理。 2.分析模型机的功能,设计 8 重 3-1 多路复用器。 3.分析模型机的功能,设计 8 重 2-1 多路复用器。 4.分析模型机的工作原理,设计模型机控制信号产生逻辑。 二、实验内容 1.用 VERILOG 语言设计模型机的 8 重 3-1 多路复用器; 2.用 VERILOG 语言设计模型机的 8 重 2-1 多

System.getProperties().

Java.version Java 运行时环境版本 java.vendor Java 运行时环境供应商 java.vendor.url Java 供应商的 URL java.home Java 安装目录 java.vm.specification.version Java 虚拟机规范版本 java.vm.specification.vendor

12C 新特性,MOVE DATAFILE 在线移动 包括system, 附带改名 NID ,cdb_data_files视图坏了

ALTER DATABASE MOVE DATAFILE  可以改名 可以move file,全部一个命令。 resue 可以重用,keep好像不生效!!! system照移动不误-------- SQL> select file_name, status, online_status from dba_data_files where tablespace_name='SYSTEM'

android6/7 system打包脚本

1.android5打包system就是网站上常见的制作ROM必备的解包打包system脚本 指令如下:mkuserimg.sh -s out/target/product/$TARGET_PRODUCT/system out/target/product/$TARGET_PRODUCT/obj/PACKAGING/systemimage_intermediates/system.img

android打包解包boot.img,system.img

原帖地址:http://www.52pojie.cn/thread-488025-1-1.html 转载Mark一下,日后研究 最近工作需要对boot.img,system.img进行破解。顺便将心得分享一下。 我的工作环境是在linux下的。所以工具都是针对linux的。 boot.img破解相关工具: 1、split_boot    perl脚本 2、boot_i

MTK Android P/Q system/vendor/super快速打包

一、Android 新版本默认开启了动态分区,把system vendor  product等分区打包成一个super分区。这对于我们使用替换分区的方法来排查问题不是很方便,直接替换一个super也不知道到底是哪个部分导致的。所以我们需要自己制作super.img来缩小范围。下面讲讲如何快速生成system、vendor、super,以及vbmeta(校验image,不匹配可能会导致不开机) 二