当前位置 : 主页 > 编程语言 > java >

【操作系统设计与实现】第一章

来源:互联网 收集:自由互联 发布时间:2022-06-23
程序员在读写磁盘数据时 没有操作系统:将预读取的磁盘块地址、每条磁道的扇区个数、物理介质的数据记录格式、扇区的空隙大小以及对已删除数据地址标识的处理方法等 13 个参数

程序员在读写磁盘数据时

  • 没有操作系统:将预读取的磁盘块地址、每条磁道的扇区个数、物理介质的数据记录格式、扇区的空隙大小以及对已删除数据地址标识的处理方法等 13 个参数 9 个字节数据放置到寄存器中,当读写操作完成时,控制器芯片将返回 23 个状态以及出错信息,封装在 7 个字节中,程序员还要关注电机状态。
  • 有操作系统时:磁盘包含了一组文件,每个文件都有一个文件名,打开文件进行操作,然后关闭文件。

所谓操作系统,一般指的是在内核态下运行的软件,它受到硬件的保护,不允许用户随便篡改。但是,在许多系统中,用户态程序和内核态程序并不是有明显的界限,有一些运行在用户态的程序,它们却为操作系统服务,用来实现一些特权功能,它们不运行在内核态,但是实现的功能很敏感,所以需要保护,同样,一些传统意义上属于操作系统的功能,也可以运行在用户空间中。MINIX 3 中,文件系统就是运行在用户态的一个 C 程序。

什么是操作系统

自上而下的解释 —— 一种扩展机

操作系统提供一台等价的计算机,使得程序员不用去面对复杂的硬件实现细节,而是使用这台等价的计算机所提供的接口(一种抽象、系统服务),来控制底层硬件。

自下而上的解释 —— 资源管理器

操作系统是一个复杂系统的管理者(处理器、存储器、定时器、磁盘、鼠标、网络连接、打印机),在相互竞争的程序之间,有序控制资源的分配。

资源管理

  • 时间上共享:宏观并行,微观串行。(决定分配给谁资源)
  • 空间是共享:内存划分成区域,并发运行各个程序。(公平性、内存保护)

操作系统发展史

第一代计算机(1945-1955):真空管、插接板

Babbage 、冯诺依曼、机械继电器

第二代计算机(1955 - 1965):晶体管、批处理系统

job、FORTRAN

第三代计算机(1965-1980):集成电路和多道程序

假脱机技术:将一个新作业从磁盘中读出,并装入刚刚空出来的内存分区去运行。

分时系统(CTSS)

MULTICS

第四代计算机(1980-):个人计算机

微处理器

BASIC

DOS(Disk Operating System)

网络操作系统、分布式操作系统

UNIX

MINIX3 —— 基于 POSIX 标准

Small is Beautiful

  • 1987,MINIX,8088、16 位、实模式、软盘
  • 1997,MINIX2,386、32 位、保护模式、硬盘
  • 2004,MINIX3,可用于PC机,也可用于嵌入式系统(简洁、模块化、可靠性),内核仅有不到 4000 行代码,

比 UNIX 出现时间晚,与 UNIX 完全兼容,mini-UNIX;模块化代码组织结构;UNIX 设计注重效率,MINIX 注重可读性;

操作系统概念

用户程序 --系统调用–> 操作系统

MINIX3 的系统调用:进程相关、文件系统相关

进程

一个进程就是一个正在执行的程序,每个进程都有自己的地址空间(可执行程序、程序的数据和栈),每个进程还拥有一组寄存器。

分时系统:每隔一定的周期,操作系统就会切换进程,进程的所有信息均放在进程表中。
对于被挂起的进程主要包括两部分内容:1. 进程的地址空间,2. 进程表项。

  • 进程可以创建子进程
  • 与进程有关的系统调用:请求更多的内存、等待一个子进程结束、加载并执行另一个程序;

文件

  • 目录:引入目录概念,进程和文件都可以被组织成树状结构,进程树一般不超过 3 层,文件数一般为四层、五层甚至更多;
  • 权限信息:MINIX 3 中的文件和目录通过一个 11 位的二进制码来保护,保护码包括三个 3 位的域;
  • 文件描述符:对一个文件进行读写操作之前,先要打开它,系统将返回一个整数,称为文件描述符;
  • mount:将光驱上的文件系统挂载到文件树上;
  • 设备文件:将 I/O 设备抽象为普通文件;
  • 块设备文件、字符设备文件:对磁盘和打印机的抽象;
  • 管道:一种用来连接两个进程的虚拟文件,进程间的通讯通过某个系统调用以文件的形式在管道中实现。

操作系统特性的例子 —— 命令解释器

操作系统是实现系统调用的代码,编辑器、编译器、汇编链接程序以及命令解释器都不是操作系统的组成部分。

尽管 shell 不是操作系统的一部分,但是大量使用了操作系统的特性,可以作为一个很好的范例,来学习各种系统调用的使用方法。

系统调用(编程接口,操作系统的外部特性)

  • 操作系统与应用程序之间的接口;
  • 由于发出一个系统调用的方式往往与具体的机器有关,并且通常用汇编语言来实现,所以为了能在 C 程序中使用系统调用,通常需要另外再构造一个函数库;
  • 任何单 CPU 计算机一次只能执行一条指令,如果一个进程正在用户态下运行一个程序,然后它需要一个系统服务,比如读取文件数据,那么它就必须执行一个陷阱或系统调用指令,把控制权交给操作系统。操作系统通过检查此次调用的参数,判断出该进程所需要的服务类型,然后去执行相应的服务功能并把控制权交还给用户进程,从系统调用后面的那条指令开始执行;
  • 发出一个系统调用类似于发出一个特殊的函数调用,二者区别在于,发出系统调用后,将进入内核或其他特权操作环境;
// C 程序中 read 系统调用的使用方法
count = read(fd, buffer, nbytes) // 第一个参数:指定所访问的文件,第二个参数:指定所用缓冲区,第三个参数:指定要读取的字节数
// 由于某些参数无效或磁盘访问等原因,使得系统调用无法完成,那么 count 会被置为 -1,同时把一个错误码放在全局变量 errno 中

MINIX3 总共有 53 条系统调用,这些系统调用所提供的服务很大程度上决定了操作系统应该提供的主要功能:

【操作系统设计与实现】第一章_文件描述符

【操作系统设计与实现】第一章_系统调用_02

进程管理的系统调用

fork 系统调用

  • 在 MINIX3 中,fork 是创建一个新进程的唯一途径;
  • 创建原进程的一个副本;
  • 复制原进程包括文件描述符、寄存器的值等内容;
  • 调用 fork 后,原进程和新进程各自执行,互不相干;
  • 父子进程代码段共享,由于二者数据空间相互独立,因此在 fork 后,变量值的变化不会引起冲突;
  • 可以通过 fork 返回的 PID 值来区分父子进程,如果是 0,则表明当前进程是子进程;

waitpid 系统调用

  • 为了等待子进程结束,父进程会执行一个 waitpid 系统调用,该调用可以将父进程阻塞,直至子进程结束,如果有多个子进程,只要任何一个子进程结束即可;
  • 第一个参数:-1, waitpid 可以等待某个特定的子进程;
  • 第二个参数:statloc 指向的是子进程的终止状态值所在的内存单元;
  • 第三个参数:设置不同的选项;

waitpid 系统调用

  • waitpid 取代了 wait 系统调用,为了兼容而保留了wait;

execve 系统调用

  • 当用户键入一条命令,shell 首先创建一个新进程,这个子进程通过 execve 系统调用执行用户命令;
  • 将第一个参数所指定的可执行文件来替换当前的内核镜像;
  • 实际上,真正的系统调用是 exec,但是有几个不同的库函数都调用了它,并且使用了不同的参数和稍微变化的函数名;

一个高度简化的 shell 框架:

#define TRUE 1
while(TRUE) // 无限循环
{
type_prompt(); // 在屏幕上显示提示符
read_command(command, parameters); // 在屏幕上显示提示符
if(fork() != 0) // 创建子进程
{
// parent code
waitpid(-1, &status, 0); // 等待子进程退出
}
else
{
// child code
execve(command, parameters, 0); // 执行命令,待执行的文件名、指向参数数组的指针和指向环境数组的指针
}
}// cp file1 file2 # argv[0]="cp", argv[1]="file1", argv[2]="file2", envp=字符串数组,用来将各种环境信息传递给程序,如终端类型,主目录名等

exit 系统调用

当一个进程完成任务后,可以用 exit 系统调用来结束运行,这个系统调用只有一个参数,也就是退出的状态值,这个值通过 waitpid 系统调用中的 statloc,返回给父进程,statloc 的低字节存放结束状态,0 表示正常退出,其它值表示不同的错误类型。statloc 的高字节包含子进程的退出状态。

​​n = waitpid(-1, &statloc, options);​​ 那么父进程将会被阻塞,直到它的某一个子进程运行结束。如果子进程退出时用 4 作为 exit 的参数值,则当父进程被唤醒时,n 中存放的是该子进程的 PID,而 statloc 的值为 0x0400。

brk、sbrk 系统调用

在 MINIX3 中,进程的内存空间被分为三个部分:代码段、数据段和栈段。数据段从下往上增长,栈从上往下增长,两者之间是空闲的地址空间。栈的增长随着程序的执行自动进行,数据段的扩展则需要通过 brk 系统调用来显式地完成,brk 有一个参数来指定数据段地结束地址,可以扩大数据段或者缩小数据段。

【操作系统设计与实现】第一章_文件描述符_03
出于程序员的方便考虑,系统还提供了一个库函数 sbrk 来改变数据段的大小,它只有一个参数,即数据段的增加量。工作原理:获取数据段的当前大小(由 brk 返回),然后根据用户给定的参数,计算出新的大小,再通过系统调用来申请所需要的空间。brk 和 sbrk 都不是 POSIX 标准的内容,程序员最好使用 malloc 库函数来申请动态内存空间。

getpid、setsid、getpgrp、ptrace 系统调用

getpid:返回调用进程的进程标识号 PID。在调用 fork 时,只有父进程能够获得子进程的 PID,如果子进程想要知道它自己的 PID,就必须使用 getpid。类似的系统调用包括:getpgrp 返回调用进程的组标识号,setsid 创建一个新的会话,并将进程组的 PID 设置为调用者的 PID,会话与 POSIX 的可选特性——作业控制有关,MINIX 3 不支持作业控制。ptrace 系统调用,主要用来对被调试的程序进行控制,通过它,调试器能够读写被控进程的地址空间,并进行其他方式的管理。

信号管理的系统调用

在多数情形下,进程间的通信是预先设计好的。但是有时可能需要处理不可预知的通信问题。在 MINIX3 中,用户可以通过按 CTRL-C 组合键做到这一点。信号还可以用来报告硬件捕获到的某些陷阱,如非法指令或浮点运算溢出等。超时也可以通过信号来实现。

对于一个尚未声明愿意接受信号的进程,

sigaction、sigreturn、signal、sigprocmask、sigpending、sigsuspend、kill、alarm 系统调用

如果此时它受到一个信号,那么该进程将会被杀死。为了避免这种结局,进程可以使用 sigaction 系统调用来声明它准备接受某种类型的信号,并提供两个参数:一个是信号处理程序的地址,另一个是内存单元,用于保存该信号的原先处理程序的地址。在执行完 sigaction 系统调用后,如果进程收到相关类型的信号,那么就会把进程的当前状态压入栈中,然后调用相应的信号处理程序。信号处理的时间可以任意长,也可以调用任何系统调用。当信号处理程序结束后,就调用 sigreturn 函数,返回到被此次信号所打断的指令,继续往下执行。sigaction 替换了原先的 signal 系统调用,但出于兼容性的考虑,在系统中仍然保留了 signal,只是把它变为了一个库函数。

在 MINIX3 中,信号可以被阻塞,被阻塞的信号一直被挂起,直到阻塞解除。在这段时间内,它不会被传递,但也不会丢失。sigprocmask 系统调用允许进程通过提交一幅位图的方式,来定义一组被阻塞的信号集合。与之相对应,进程也可以使用系统调用 sigpending 来查询当前因阻塞而挂起的信号集。最后,sigsuspend 系统调用允许进程去设置阻塞信号的位图并将其挂起。

除了使用函数来捕获信号之外,程序也可以使用常量 SIG_IGN 来忽略指定类型的信号,或者使用 SIG_DFL 来恢复默认的信号处理程序。默认的处理方式随信号而异,可以是撤销该进程,或者是忽略该信号。

使用 CTRL-C 产生 SIGINT 信号,使用 CTRL-\ 产生 SIGQUIT。

使用 kill 系统调用也可以向一个进程发送信号(前提是发送方和接收方具有相同的用户标识号 UID,无关进程之间不能发送信号)。

// 用来忽略 SIGINT、SIGQUIT 信号
sigaction(SIGINT,SIG_IGN,NULL);
sigaction(SIGQUIT,SIG_IGN,NULL);

假设一个后台进程已被启动,但随后发现它应被终止,此时 SIGINT 和 SIGQUIT 都已被禁用,解决方法就是 kill 程序,该程序将使用 kill 系统调用来发送一个信号,当后台进程收到信号 9 (SIGKILL)时,该进程将被撤销。

alarm 系统调用:当一个进程在运行一段时间间隔后,需要发生一次中断,以进行其它的一些处理,例如在不可靠的通信线路上重传一个丢失的数据包。alarm 的参数指定了一个时间间隔,以秒为单位,然后,一旦进程的运行时间超过了该时间间隔,就会收到一个 SIGALRM 信号。在任意时刻,一个进程只能设定一个警报时钟。

文件管理的系统调用

只讨论对单个文件进行操作的系统调用。

creat 系统调用的功能是创建一个新文件,参数包括文件的文件名和保护模式。
​​​fd = creat("abc", 0751); // 八进制数,文件所有者、同组用户和其他用户的访问权限​​ creat 系统调用在创建文件时,还以可写的方式将其打开。用户可以使用返回的文件描述符 fd,来对该文件进行写操作。如果对一个现有的文件进行 creat 操作,那么该文件将被清空,文件的长度变为 0。open 系统调用也可以创建新文件,为了保持兼容性,还是保留了下来。

设备文件的创建使用 mknod,而不是 creat。
​​​fd = mknod("/dev/ttyc2"m 020744, 0x0402); // /dev/ttyc2 文件,并将其模式代码设置为八进制数字 020744,高字节指定其主设备号4,低字节指定此设备号为2​​ 主设备号可以取任何值,但对于名为 /dev/ttyc2 的文件,此设备号应当为 2。

在读写一个文件之前,首先要使用 open 系统调用将其打开。open 的第一个参数指定文件的路径名,可使用绝对路径名或相对于当前工作目录的相对路径名,第二个参数指定打开方式。在打开一个文件后,就可以使用它返回的文件描述符 fd 对文件进行读写操作。

在使用完一个文件后,还要用 close 系统调用把它关闭,这样它的文件描述符就可空出来,给其它文件用。

多数程序对文件的读写操作都是顺序进行的,但有时也需要随机地去访问文件的任意部分。每一个文件都有一个指针来指明当前的访问位置。在顺序读写时,该指针通常指向下一个即将读写的字节。

我们可以使用 lseek 系统调用来直接修改文件指针的值,这样,随后的 read 或 write 操作就可以在文件的任意位置进行,设置可以超越文件的末尾。lseek 有三个参数:第一个是文件描述符,第二个是文件的位置,第三个指明该文件位置是相对于文件开头、当前位置还是文件末尾。lseek 的返回值是文件指针被修改之后的绝对位置。

对于每一个文件,MINIX3 记录如下信息:文件类型、文件大小、最后修改时间等。程序可以通过 stat 或 fstat 系统调用来获取这些信息,它们的区别仅在于 stat 是同过文件名来指定文件,而 fstat 则使用文件描述符。显然,fstat 比较适合于已打开的文件,尤其是像标准输入和标准输出这种文件名不可知的情形。

【操作系统设计与实现】第一章_系统调用_04
在操作文件描述符的时候,有时可以使用 dup 系统调用。假设一个程序需要关闭标准输出,并用一个普通文件来作为标准输出,然后调用一个函数向标准输出写入一些信息,最后再恢复原先的状态。为了实现这些功能,可以先关闭文件描述符,再打开一个新文件,这时该文件就成为标准输出,但如果这样的话,就无法恢复原先的标准输出。

​​fd = dup(1);​​​ 该操作将标准输出分配一个新的文件描述符 fd,并使之对应于标准输出文件。然后,就可以将标准输出关闭,并打开一个新文件,使用该文件来作为标准输出。当需要恢复原先的标准输出时,先关闭文件描述符,然后再执行 ​​n = dup(fd);​​​ 这时,最小的文件描述符被定向到 fd 所指的文件,最后将 fd 关闭就恢复了最初状态。​​dup2(fd, fd2);​​ 此处 fd 指向一个打开的文件,fd2 是一个未使用的文件描述符,当执行完这条语句后,fd2 将指向 fd 所指向的文件。

MINIX 3 中的进程间通信可以使用管道。​​cat file1 file2 | sort​​ shell 将创建一个管道并将第一个进程的标准输出信息写入到管道中。于是第二个进程的标准输入便可以从该管道中读取。pipe 系统调用创建一个管道并返回两个文件描述符,一个用于写,另一个用于读。​​pipe(&fd[0]);​​ fd 有两个整数组成的数组,fd[0] 存放读操作的文件描述符,fd[1]存放写操作的文件描述符。在本条语句之后会调用一个 fork 来创建一个子进程,然后父进程关掉用于读的文件描述符,子进程关掉用于写的文件描述符(或者相反),这样便可以做到一个进程向管道中写数据,另一个进程从管道中读数据。

【操作系统设计与实现】第一章_系统调用_05
ioctl 系统调用适用于所有设备文件。主要用途在于字符设备文件,尤其是终端。库函数 tcgetattr 和 tcsetattr 都使用 ioctl 来改变终端模式和各种属性。

终端模式:cooked 模式、raw 模式、cbreak 模式。cooked 模式是最常用的终端模式。在该模式下,字符的删除和终止能正常地工作。CTRL-S+CTRL-Q 组合键用来停止和恢复终端输出。CTRL-S 和 CTRL-D 产生一个中断信号。在 raw 模式下,上述功能都没有,每一个字符都是在未加处理的情况下直接发送给程序,而不是像 cooked 那样,要等到终端输入了一整行以后才发送给程序。cbreak 模式介于上述两者,用作编辑的删除键和终止键被禁用,CTRL-D 组合键也被禁用。但 CTRL-S、CTRL-Q、CTRL-C、CTRL-\ 组合键则仍然有效,单个字符不等到一行就送给程序。​​ioctl(fd, TCSETS,&termios);​​ 第一个参数指定一个文件,第二个参数指定操作的类型,第三个参数指定一个 POSIX 数据结构的地址。

access 系统调用可以检查文件访问操作是否具有相应的权限。

rename 系统调用可以改变一个文件的名字。

fcntl 系统调用用于对文件进行控制,最常用的是文件加锁,它可以对一个文件的一部分进行加锁和解锁,也可以用来检测文件的某个部分是否被上锁。

目录管理的系统调用

mkdir 和 rmdir 系统调用,分别用来创建和删除空目录。

link 系统调用允许同一个文件具有两个或多个名字。

link("/usr/jim/memo", "/usr/asr/note") # jim 目录下的文件memo将以文件名note出现在ast的目录下

目录实际上也是文件,只不过它的内容并非通常的数据,而是位于该目录下的每个文件的文件名与它的 i 节点号之间的对应关系。unlink 系统调用能够删除某个链接文件,另一个文件仍然存在。

mount 系统调用可以将两个文件系统合并成一个。 mount 命令,用户可以将各种可移动存储介质集成到一个统一的文件层次结构中,而不必关心文件存放在哪一台设备上。当一个文件系统不再需要时,可以用 umount 系统调用将其卸载。 sync 系统调用能够将缓冲区的某个数据块写到磁盘中。当 MINIX3 启动后,一个名为 update 的程序就会启动,作为后台进程运行,每隔 30s 就会执行一次 sync 操作,刷新缓冲区的内容。

chdir 和 chroot 是关于目录操作的系统调用,前者用来改变当前的工作目录,后者用来改变根目录。

保护的系统调用

在 MINIX3 中,每一个文件都有一个 11 位保护码,有 9 位来描述文件所有者、同组用户和其他用户的权限。chmod 系统调用可以改变文件的保护模式。保护模式码的另外两位,分别是 SETGID 和 SETUID。如果用户执行了一个标有 SETUID 位的程序,那么在这个进程运行期间,用户的有效 UID 被临时设置为文件所有者的 UID。这个特性常被用来使一般用户可以去执行一些通常只有超级用户才能执行的功能。 创建目录的系统调用使 mknod,通常只有超级才能使用,为了让普通用户也能用,解决办法是设置一个 mkdir 程序,在该程序中去调用 mknod,通过把 mkdir 程序的所有者设置为超级用户,同时将其保护模式设置为 04755,普通用户就可以具有以受限方式执行 mknod 的权利。

当进程执行一个标有 setuid 或 setgid 位的文件时,它便获得了一个有效 UID 或 GID,而这个有效 ID 不同于它的真实 ID,提供了 getuid 和 getgid 系统调用,前者返回真实 UID 和有效 UID,后者返回真实 GID 和有效 GID。

一般用户不能改变他们的 UID,除非执行了一个标有 SETUID 的程序。

在权限管理方面,umask 可以被普通用户执行,它可以设置一个内部掩码,用户文件创建时对指定的保护模式码进行掩蔽。

umask(022);
// creat mknod 指定的保护模式码都要与022的反码(0755)相与,所得到的结果才是最终的保护码

掩码可以被子进程继承,所以如果在登陆后shell执行了一次umask操作,那么此次登录期间的所有用户都将受到该掩码的约束,即它们所创建的文件都不能被其它用户修改。

对于一个所有者为 root 的程序A,如果它设置了 SETUID 位,那么它可以访问任何文件,因为它的有效 UID 为超级用户,如果此时对于普通用户来说,它本来没有权限去访问一个文件,但如果他执行了程序A,就有可能通过它来间接地访问该文件,因为他的有效 UID 已经是超级用户了,我们需要知道他的真实 UID 是否具有访问该文件的权限。使用 access 系统调用,access 的 mode 参数如果为 4,表示检查读权限,如果为 2,表示检查写权限,如果为 1,表示检查执行权限。还可以将值累加组合使用。当 mode 为 0 时,仅检查文件是否存在,同时检查从根目录直到该文件的所有目录是否允许搜索。

时间管理的系统调用

  • time:返回当前时间,以秒为单位,从 1970 年 1 月 1 日零时开始计时;
  • stime:设置当前的系统时间,仅超级用户;
  • utime:允许文件的所有者来修改存储在文件 i 结点中的时间;
  • times:返回进程的账户信息,进程总共执行了多少 CPU 时间,有多少是他自己用的,多少是操作系统代表它使用的(执行系统调用)…

操作系统结构

整体结构

一锅粥。整个操作系统是一组函数的集合,每个函数在需要的时候可以去调用任何其它的函数,需要保证每个函数都有一个定义完好的接口,包括入口参数和返回值,而且相互之间调用不受约束。

需要将一些独立的函数或文件进行编译,然后用链接程序把它们链接在一起成为一个单独的目标程序,所有函数都可见,信息没有隐藏。

大多数 CPU 有两种状态,即内核态和用户态。

【操作系统设计与实现】第一章_系统调用_06

  • 用户读文件,调用 read 库函数,由其调用 read 系统调用,参数入栈,函数调用;
  • 进入read库函数,一般用汇编语言书写,主要功能就是把本次系统调用的编号放在操作系统要求的地方,执行 TRAP 指令;
  • 进入内核态,跳转到内核的某个固定位置开始执行,将检查此次系统调用的编号,并把它分派到相应的系统调用处理程序。通常做法是查询一张系统调用表,每个表项存放了相应的处理程序的起始地址,表项的下标即为系统调用的编号。当处理程序完成后,控制流可能回到了 read 库函数(如果是访问键盘,此时还有没任何字符被键入,系统就会把调用者阻塞,运行其它程序);
  • 进行函数返回,返回用户程序;
  • 清空栈中内容,从磁盘文件读入的数据已经保存在 buffer 中;
    • 一个主程序,用来调用被请求的服务例程;
    • 一组服务例程,用来实现相应的系统调用;
    • 一组工具函数,用来帮助服务例程的实现;

    分层结构

    把整个操作系统组织成一个层次结构,每一层软件都是在它的下层软件的基础上构造起来的。

    【操作系统设计与实现】第一章_文件描述符_07

  • 0 层,掩盖了 CPU 的使用细节,向上提供了基本的多道程序功能;
  • 1 层,负责存储管理,在 1 层之上,进程无需再考虑存储问题;
  • 2 层,负责处理进程与操作员控制台之间的通信,每个进程都可以认为它们有自己的控制台;
  • 3 层,进程不需要考虑物理设备的具体实现细节;
  • 4 层,无需考虑进程管理、存储管理等诸多环节;
  • 分层方案实际上只是在设计上提供了一些方便,系统的各个部分最终仍然被链接成一个完整且单一的目标程序。

    虚拟机

    系统的核心是一个虚拟机监控程序,它在裸机上运行并具备多道程序功能。它向上层提供了若干台虚拟机,这些虚拟机仅仅是裸机硬件的精确拷贝,包含有内核态/用户态、I/O功能、中断以及真实硬件所具有的所有内容。

    由于每台虚拟机都与裸机完全一样,所以在每台虚拟机上都可以运行任何一种操作系统。

    Java 虚拟机 —— JVM 虚拟机。Java 编译器将输入的源程序编译成 JVM 代码,然后就可以在一个 JVM 解释器上运行,生成的 JVM 代码可以在任何一台装有 JVM 解释器的计算机上执行。

    外核

    每个用户进程能够精确地获得真实计算机的副本,占用计算机的部分系统资源,一台虚拟机可能占用 0 个到 1023 个磁盘块,另一台虚拟机占用 1024 到 2047 个磁盘块…

    在内核态运行的最底层软件是一个称为外核的程序,其任务是为虚拟机分配资源并确保资源的使用不会发生冲突。其优点是省去了一个映射层。外核只需要记录每台虚拟机被分配了哪些资源。以较少的开销将多道程序与用户操作系统代码分离开,外核的工作仅仅是使各个虚拟机互不干扰。

    C/S结构

    从操作系统中去掉尽可能多的东西,只留下一个最小的内核。通常的方法是将大多数的操作系统功能由用户进程来实现。内核的全部工作就是处理客户与服务器之间的通信,所有的服务器都是以用户进程的形式运行,不是运行在内核态,不直接访问硬件,加入在文件服务器中发送错误,那么文件服务可能崩溃,但是整个系统不会。
    【操作系统设计与实现】第一章_子进程_08
    其适用于分布式系统。C/S 的另一个优点是它适用于分布式系统,如果一个客户通过发送消息与服务器通信,那么客户无需知道这条消息是在本地就地处理还是通过网络送给远地机器上的服务器。客户只需要发出请求,然后收到应答。

    有些操作系统功能靠用户空间的程序很难完成:

  • 设立运行于内核态的专用服务器进程,采用消息机制与其它进程通信;
  • 在内核中建立一套最小的机制,将策略留给用户空间中的服务器进程。
  • 操作系统的核心是一组系统调用,系统调用界定了操作系统能完成的功能。


    上一篇:【Docker 】第 1 章
    下一篇:没有了
    网友评论