设为首页 加入收藏

TOP

MIT6.S081学习笔记--lec 1(一)
2023-07-23 13:25:04 】 浏览:43
Tags:MIT6.S081 习笔记 --lec

引言

操作系统的目标

  • abstract H/W 抽象化硬件
  • multiplex 多路复用
  • isolation 隔离性
  • sharing 共享(进程通信,数据共享)
  • security / access control 安全性/权限控制
  • performance 性能/内核开销
  • range of applications 多应用场景

操作系统概览

操作系统应该提供的功能:1. 多进程支持 2. 进程间隔离 3. 受控制的进程间通信

  • xv6:一种在本课程中使用的类UNIX的教学操作系统,运行在RISC-V指令集处理器上,本课程中将使用QEMU模拟器代替

  • kernel(内核):为运行的程序提供服务的一种特殊程序。每个运行着的程序叫做进程,每个进程的内存中存储指令、数据和堆栈。一个计算机可以拥有多个进程,但是只能有一个内核

    每当进程需要调用内核时,它会触发一个system call(系统调用),system call进入内核执行相应的服务然后返回。

操作系统的组织结构如图1所示

OS Organization

内核提供的一系列系统调用就是用户程序可见的操作系统接口,xv6 内核提供了 Unix 传统系统调用的一部分,它们是:

image-20230717090404859

进程和内存

每个进程拥有自己的用户空间内存以及内核空间状态,当进程不再执行时xv6将存储和这些进程相关的CPU寄存器直到下一次运行这些进程。kernel将每一个进程用一个PID(process identifier)指代。在进程执行中,常常会使用forkexec系统调用来创建新的进程。如下面代码所示:

fork and wait

  • fork:形式:int fork()。其作用是让一个进程生成另外一个和这个进程的内存内容相同的子进程。在父进程中,fork的返回值是这个子进程的PID,在子进程中,返回值是0
  • exit:形式:int exit(int status)。让调用它的进程停止执行并且将内存等占用的资源全部释放。需要一个整数形式的状态参数,0代表以正常状态退出,1代表以非正常状态退出
  • wait:形式:int wait(int *status)。等待子进程退出,返回子进程PID,子进程的退出状态存储到int *status这个地址中。如果调用者没有子进程,wait将返回-1
  • pipe:形式:int pipe(int p[])。创建一个管道,将读/写文件描述符放在p[0]和p[1]中
  • sbrk:形式:char *sbrk(int n)。将进程的内存增加n字节。返回新内存的起始位置。
int pid = fork();
if (pid > 0) {
    printf("parent: child=%d\n", pid);
    pid = wait((int *) 0);
    printf("child %d is done\n", pid);
} else if (pid == 0) {
    printf("child: exiting\n");
    exit(0);
} else {
    printf("fork error\n");
}

前两行输出可能是

parent: child=1234
child: exiting

也可能是

child: exiting
parent: child=1234

这是因为在fork了之后,父进程和子进程将同时开始判断PID的值,在父进程中,PID为1234,而在子进程中,PID为0。看哪个进程先判断好PID的值,以上输出顺序才会被决定。

最后一行输出为

parent: child 1234 is done

子进程在判断完pid == 0之后将exit,父进程发现子进程exit之后,wait执行完毕,打印输出。

尽管fork了之后子进程和父进程有相同的内存内容,但是内存地址和寄存器是不一样的,也就是说在一个进程中改变变量并不会影响另一个进程。

fork and exec

exec:形式:int exec(char *file, char *argv[])。加载一个文件,获取执行它的参数,执行。如果执行错误返回-1,执行成功则不会返回,而是开始从文件入口位置开始执行命令。文件必须是ELF格式。

xv6 shell使用以上四个system call来为用户执行程序。在shell进程的main中主循环先通过getcmd来从用户获取命令,然后调用fork来运行一个和当前shell进程完全相同的子进程。父进程调用wait等待子进程exec执行完(在runcmd中调用exec

/* sh.c */
int
main(void)
{
  static char buf[100];
  int fd;

  // Ensure that three file descriptors are open.
  while((fd = open("console", O_RDWR)) >= 0){
    if(fd >= 3){
      close(fd);
      break;
    }
  }

  // Read and run input commands.
  while(getcmd(buf, sizeof(buf)) >= 0){
    if(buf[0] == 'c' && buf[1] == 'd' && buf[2] == ' '){
      // Chdir must be called by the parent, not the child.
      buf[strlen(buf)-1] = 0;  // chop \n
      if(chdir(buf+3) < 0)
        fprintf(2, "cannot cd %s\n", buf+3);
      continue;
    }
    if(fork1() == 0)
      runcmd(parsecmd(buf));
    // parent wait the child exit
    wait(0);
  }
  exit(0);
}

你可能会想,既然forkexec总是一起使用,为什么不合并成一个呢?实际上我们可以在fork 之后,对子进程进行一些设置,比如输入/输出重定向,然后再执行exec,注意exec并不会改变子进程的file table。

当我们不需要进行额外的设置时,fork 复制内存,exec替换内存,这意味着内存的浪费,有什么办法可以优化这种情况么?答案是肯定的,之后的4.6节我们会讲到 COW (copy-on-write)机制。

I/O 和文件描述符

  • file descriptor:文件描述符,用来表示一个被内核管理的、可以被进程读/写的对象的一个整数,表现形式类似于字节流,通过打开文件、目录、设备等方式获得。一个文件被打开得越早,文件描述符就越小。

    每个进程都拥有自己独立的文件描述符列表,其中0是标准输入,1是标准输出,2是标准错误。shell将保证总是有3个文件描述符是可用的

    while((fd = open("console", O_RDWR)) >= 0)
    {    
        if(fd >= 3)
        {        
            close(fd);        
            break;    
        } 
    }
    
  • readwrite:形式int write(int fd, char *buf, int n)int read(int fd, char *bf, int n)。从/向文件描述符fd读/写n字节bf的内容,返回值是成功读取/写入的字节数。每个文件描述符有一个offset,read会从这个offset开始读取内容,

首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇shell脚本-Nginx访问日志分析 下一篇跟运维学 Linux - 02

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目