2.操作系统简介
Posted redreampt
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2.操作系统简介相关的知识,希望对你有一定的参考价值。
操作系统简介
如果你正在攻读本科操作系统课程,你应该已经知道计算机程序运行时的功能。如果没有,这本书(以及相应的课程)将会很难 - 所以你应该停止阅读本书,或者跑到最近的书店,然后快速阅读必要的背景材料(包括Patt/Patel[PP03],特别是Bryant/O’Hallaron[BOH10]是非常棒的书)。
那么程序运行会发生什么?
正在运行的程序做了一件非常简单的事情:它每秒执行数百万(今天,甚至数十亿)指令,处理器从内存中取出一条指令,对其进行解码(即确定这是哪条指令),并执行它(即完成它的工作,比如将两个数字加在一起,访问内存,检查条件,跳转到某个函数,等等。完成此指令后,处理器继续执行下一条指令,依此类推,直到程序最终完成)。
我们刚刚描述了冯诺依曼计算模型的基础知识。听起来很简单吧?但是在本课程中,我们将学习在程序运行的同时,还有很多其他的东西正在进行,其主要目标是使系统易于使用。
事实上,有一大堆软件负责使程序运行变得容易(甚至允许你看起来同时运行多个程序),允许程序共享内存,使程序与设备交互,以及其他类似这样的功能。
该软件主体称为操作系统(Operating system,OS),它确保系统以易于使用的方式正确有效地运行。
问题的关键:如何实现资源的虚拟化
我们将在本书中回答的一个很简单的核心问题:操作系统如何虚拟化资源? 这是我们问题的关键所在。 操作系统为什么这样做不是主要问题,因为答案是显而易见的:它使系统更易于使用。 因此我们关注如何实现:操作系统通过什么机制和策略来实现虚拟化? 操作系统如何有效地进行操作? 需要什么硬件支持? 我们将在诸如此类的阴影框中使用“问题的关键”,以此来解决我们在构建操作系统时试图解决的具体问题。 因此在关于特定主题的注释中,您可能会发现一个或多个突出显示问题。 当然,本章中的细节提供了解决方案,或者至少解决方案的基本。
操作系统执行此操作的主要方式是通过我们称为虚拟化(virtualization)的一般技术。也就是说,操作系统使用物理资源(例如处理器,内存或磁盘),并将其转换为更通用,功能强大且易于使用的虚拟形式。因此,我们有时将操作系统称为虚拟机(virtual machine)。
当然,为了让用户能够告诉操作系统做什么,从而利用虚拟机的功能(例如运行程序,分配内存,或访问文件),操作系统还提供了一些功能。您可以调用的接口(API)。事实上,典型的OS会提供几百个可供应用程序使用的系统调用(system calls)。由于操作系统提供这些调用来运行程序,访问内存和设备以及其他相关操作,我们有时也会说操作系统为应用程序提供了一个标准库(standard library)。
最后,因为虚拟化允许许多程序执行(通过共享CPU),以及许多程序同时(concurrently)访问自己的指令和数据(从而共享内存),以及许多程序访问设备(因此共享磁盘等),操作系统有时也称为资源管理者(resource manager)。每个CPU,内存和磁盘都是系统的资源;操作系统的角色是管理这些资源,有效或公平地执行,或者考虑到许多其他可能的目标。为了更好地理解操作系统的作用,让我们看看一些例子。
2.1 CPU虚拟化
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <assert.h>
#include "common.h"
int
main( int argc, char * argv[] )
{
if ( argc != 2 )
{
fprintf( stderr, "usage: cpu <string>
" );
exit( 1 );
}
char * str = argv[1];
while ( 1 )
{
Spin( 1 );
printf( "%s
", str );
}
}
Figure 2.1: Simple Example: Code That Loops and Prints (cpu.c)
图2.1 描绘了我们的第一个程序。 它没有做太多。 事实上,它所做的是调用Spin(),一个反复检查时间并一旦运行一秒钟就返回的函数。 然后,它打印出用户在命令行上输入的字符串,并一直重复。
假设我们将此文件保存为 cpu.c 并决定在具有单个处理器(我们有时称之为CPU)的系统上编译和运行它。 这是我们将要看到的:
prompt> gcc -o cpu cpu.c -Wall
prompt> ./cpu "A"
A
A
A
A
?C
prompt>
运行不太有趣 - 系统开始运行程序,该程序反复检查时间,直到一秒钟过去。 一旦第二次过去,代码将打印用户传入的输入字符串(在本例中为字母“A”),然后继续。 注意程序将永远运行; 只有按“Control-c”(基于UNIX的系统将终止在前台运行的程序)我们才能暂停程序。
现在,让我们做同样的事情,但这一次,让我们运行同一个程序的许多不同实例。 图2.2显示了这个稍微复杂的例子的结果。
prompt> ./cpu A & ; ./cpu B & ; ./cpu C & ; ./cpu D &
[1] 7353
[2] 7354
[3] 7355
[4] 7356
A
B
D
C
A
B
D
C
A
C
B
D
...
Figure 2.2: Running Many Programs At Once
好吧,现在事情变得更有趣了。即使我们只有一个处理器,但不知何故,所有这四个程序似乎都在同时运行!这种魔力是如何发生的?[^4]
事实证明,操作系统在硬件的帮助下负责这种错觉,即系统具有大量虚拟CPU的错觉。将单个CPU(或一小组CPU)转换为看似无限数量的CPU,从而允许许多程序看起来一次运行,这就是我们所谓的虚拟化CPU,这是本书第一部分的重点。
当然,要运行程序并停止它们,以及告诉操作系统运行哪些程序,需要使用一些接口(API)来将您的需求传达给操作系统。我们将在本书中讨论这些API;实际上,它们是大多数用户与操作系统交互的主要方式。
您可能还会注意到,同时运行多个程序的能力会引发各种新问题。例如,如果两个程序想要在特定时间运行,哪个应该运行?这个问题由操作系统的策略(policy)回答;策略在操作系统中的许多不同位置用于回答这些类型的问题,因此我们将在学习操作系统实现的基本机制(mechanisms)(例如一次运行多个程序的能力)时学习它们。这就是操作系统作为资源管理器的角色。
2.2 虚拟化内存
现在让我们考虑一下内存。 现代机器提供的物理内存模型非常简单。 内存只是一个字节数组;要读取内存,必须指定一个地址才能访问存储在那里的数据; 要写入(或更新)内存,还必须指定要写入给定地址的数据。
程序运行时始终访问内存。 一个程序在内存中保存它的数据结构,并在执行工作时在通过各种指令进行访问,例如加载、存储、或其他访问内存的显式指令。 不要忘记,程序的每个指令也都在内存中;因此,在每次取指令时访问内存。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include "common.h"
int
main( int argc, char * argv[] )
{
int * p = malloc( sizeof(int) ); /* a1 */
assert( p != NULL );
printf( "(%d) address pointed to by p: %p
",
getpid(), p ); /* a2 */
p = 0;
a3
while ( 1 )
{
Spin( 1 );
p = *p + 1;
printf( "(%d) p: %d
", getpid(), *p ); /* a4 */
}
return(0);
}
Figure 2.3: A Program that Accesses Memory (mem.c)
让我们看一下通过调用malloc()来分配一些内存的程序(图2.3)。 这个程序的输出可以在这里找到:
prompt> ./mem
(2134) address pointed to by p: 0x200000
(2134) p: 1
(2134) p: 2
(2134) p: 3
(2134) p: 4
(2134) p: 5
?C
该程序做了几件事。 首先,它分配一些内存(第a1行)。 然后,它打印出内存地址(a2),然后将数字0放入新分配的内存的第一个位置(a3)。 最后,它循环:延迟一秒并递增存储在p中保存的地址的值。 对于每个print语句,它还会打印出正在运行的程序的进程标识符(PID)。 该PID在每个运行过程中都是唯一的。
同样,结果不太有趣。 新分配的内存位于地址0x200000。 程序运行时,会慢慢更新值并打印出结果。
现在,我们再次运行同一程序的多个实例,看看会发生什么(图2.4)。 我们从示例中看到,每个正在运行的程序都在同一地址(0x200000)分配了内存,但每个程序似乎都在独立更新0x200000地址的值! 就好像每个正在运行的程序都有自己的私有内存,而不是与其他正在运行的程序共享相同的物理内存。
prompt> ./mem &; ./mem &
[1] 24113
[2] 24114
(24113) address pointed to by p: 0x200000
(24114) address pointed to by p: 0x200000
(24113) p: 1
(24114) p: 1
(24114) p: 2
(24113) p: 2
(24113) p: 3
(24114) p: 3
(24113) p: 4
(24114) p: 4
...
Figure 2.4: Running The Memory Program Multiple Times
实际上,这正是这里发生的事情,因为操作系统虚拟化了内存。 每个进程访问自己的私有虚拟地址空间(有时只称为其地址空间(address space)),操作系统以某种方式映射到机器的物理内存。 一个正在运行的程序中的内存引用不会影响其他进程(或OS本身)的地址空间;就运行程序而言,它拥有所有的物理内存。 然而,现实是物理内存是由操作系统管理的共享资源。 究竟如何实现所有这些也是本书第一部分关于虚拟化主题的内容。
2.3 并发
本书的另一个主题是并发性(concurrency)。 我们使用这个概念术语来指代在同一程序中同时(即并发)处理许多事情时出现并且必须解决的一系列问题。 并发问题首先出现在操作系统本身内; 正如您在上面的虚拟化示例中所看到的,操作系统同时处理许多事情,首先运行一个进程,然后运行另一个进程,等等。 事实证明,这样做会导致一些深刻而有趣的问题。
#include <stdio.h>
#include <stdlib.h>
#include "common.h"
volatile int counter = 0;
int loops;
void * worker( void * arg )
{
int i;
for ( i = 0; i < loops; i++ )
{
counter++;
}
return(NULL);
}
int
main( int argc, char * argv[] )
{
if ( argc != 2 )
{
fprintf( stderr, "usage: threads <value>
" );
exit( 1 );
}
loops = atoi( argv[1] );
pthread_t p1, p2;
printf( "Initial value : %d
", counter );
Pthread_create( &p1, NULL, worker, NULL );
Pthread_create( &p2, NULL, worker, NULL );
Pthread_join( p1, NULL );
Pthread_join( p2, NULL );
printf( "Final value : %d
", counter );
return(0);
}
Figure 2.5: A Multi-threaded Program (threads.c)
不幸的是,并发问题不再局限于操作系统本身。 实际上,现代多线程程序表现出同样的问题。 让我们演示一个多线程程序的例子(图2.5)。 虽然你现在可能还不完全理解这个例子(我们将在后面的章节中,在关于并发的一节中学到更多关于它的内容),但基本思想很简单。 主程序使用Pthread .create()创建两个线程。 您可以将线程视为在与其他函数相同的内存空间中运行的函数,其中一次激活多个函数。 在这个例子中,每个线程开始在一个名为worker()的例程中运行,在该例程中,它只是循环递增一个统计循环次数的计数器。
问题的关键:
如何构建正确的并发程序
当同一个内存空间中有许多并发执行的线程时,我们如何构建正确工作的程序? 操作系统需要哪些原语? 硬件应该提供哪些机制? 我们如何使用它们来解决并发问题?
下面是当我们运行此程序并将变量循环的输入值设置为1000时发生的事件的记录。循环的值确定两个工作程序中的每一个将在循环中递增共享计数器的次数。 当程序运行时,循环的值设置为1000,您期望计数器的最终值是什么?
prompt> gcc -o thread thread.c -Wall -pthread
prompt> ./thread 1000
Initial value : 0
Final value : 2000
正如您可能猜到的,当两个线程完成时,计数器的最终值为2000,因为每个线程将计数器递增1000次。 实际上,当循环的输入值设置为N时,我们期望程序的最终输出为2N。 但事实证明,生活并非如此简单。 让我们运行相同的程序,但循环的值更高,看看会发生什么:
prompt> ./thread 100000
Initial value : 0
Final value : 143012 // huh??
prompt> ./thread 100000
Initial value : 0
Final value : 137298 // what the??
在这次运行中,当我们给出100,000的输入值,而不是最终值为200,000时,我们首先获得143,012。 然后,当我们第二次运行程序时,我们不仅会再次获得错误的值,而且还会获得与上次不同的值。 事实上,如果你使用高值循环一遍又一遍地运行程序,你可能会发现有时你甚至得到了正确的答案! 那为什么会这样呢?
事实证明,这些奇怪和不寻常的结果的原因与指令的执行方式有关,一次一个。 不幸的是,上面程序的一个关键部分,共享计数器递增,需要三个指令:一个用于将计数器的值从存储器加载到寄存器中,一个用于递增它,一个用于将其存储回内存。 因为这三个指令不是原子地执行(一次全部执行),所以会发生奇怪的事情。 正是这个并发问题,我们将在本书的第二部分详细讨论。
[^6]The actual call should be to lower-case pthread create(); the upper-case version is
our own wrapper thatcalls pthread create()and makes sure thatthereturn code indicates
that the call succeeded. See the code for details.
2.4 持久性
该课程的第三个主题是持久性(persistence)。在系统内存中,数据很容易丢失,因为诸如DRAM的设备以易失的方式存储值;当断电或系统崩溃时,内存中的任何数据都将丢失。因此,我们需要硬件和软件来持久存储数据;这种存储对于任何系统都是至关重要的,因为用户非常关心他们的数据。硬件以某种输入/输出或I/O设备的形式出现;在现代系统中,硬盘驱动器是长期数据的通用存储库,尽管固态驱动器(SSD)也在这个领域处于领先地位。通常操作系统中管理磁盘的的软件称为文件系统,它负责将用户以可靠和有效的方式创建的任何文件存储在系统的磁盘上。与操作系统为CPU和内存提供的抽象不同,操作系统不会为每个应用程序创建专用的虚拟化磁盘。相反,假设用户通常希望共享文件中的信息。例如,在编写C程序时,首先使用编辑器(例如,Emacs)来创建和编辑C文件(emacs -nw main.c)。然后,使用编译器将源代码转换为可执行文件(例如,gcc -o main main.c)。最后,运行新的可执行文件(例如./main)。因此,您可以看到如何跨不同进程共享文件。首先,Emacs创建一个文件,作为编译器的输入;编译器使用该输入文件来创建新的可执行文件(在许多步骤中 ,在编译器课程中获取详情);最后,运行新的可执行文件。一个新的程序就这样生成了。
为了更好地理解这一点,让我们看看一些代码。图2.6显示了创建包含字符串“hello world”的文件(/ tmp / file)的代码。
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <fcntl.h>
#include <sys/types.h>
int
main( int argc, char * argv[] )
{
int fd = open( "/tmp/file", O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU );
assert( fd > -1 );
int rc = write( fd, "hello world
", 13 );
assert( rc == 13 );
close( fd );
return(0);
}
Figure 2.6: A Program That Does I/O (io.c)
问题关键
如何持久性存储数据
文件系统是OS中负责管理持久数据的一部分。 正确地实现这些需要哪些技术?需要哪些机制和策略才能高性能的实现? 面对硬件和软件故障,如何实现可靠性?
为完成此任务,程序会对操作系统进行三次调用。第一次调用open(),打开并创建文件;第二次调用write(),将一些数据写入文件;第三次调用close(),简单地关闭文件,从而表明程序不会再向其写入数据。这些系统调用(system call)被路由到操作系统的一部分,称为文件系统,然后处理请求并向用户返回某种错误代码。
您可能想知道操作系统为了实际写入磁盘而做了什么。我们会告诉你,但你必须承诺先闭上眼睛;这是令人不快的。文件系统必须完成相当多的工作:首先确定这些新数据将驻留在磁盘上的哪个位置,然后在文件系统维护的各种结构中跟踪它。这样做需要向底层存储设备发出I/O请求,以读取现有结构或更新(写入)它们。任何写过设备驱动程序的人都知道,让设备代表你做某事是一个错综复杂的过程。它需要深入了解低级设备接口及其确切语义。幸运的是,操作系统提供了一种通过系统调用访问设备的标准而简单的方法。因此,OS有时被视为标准库(standard library)。
当然,有关设备如何被访问的更多细节,以及文件系统如何在所述设备上持久地管理数据。出于性能原因,大多数文件系统首先将此类写入延迟一段时间,希望将它们批处理为更大的组。为了处理写入期间系统崩溃的问题,大多数文件系统都包含某种复杂的写入协议,例如日志记录(journaling)或写时复制(copy-on-write),仔细排序写入磁盘以确保在写入序列期间发生故障时,之后系统可以恢复到合理的状态。为了使不同的公共操作高效,文件系统采用许多不同的数据结构和访问方法,从简单列表到复杂的b树。如果所有这些都没有意义,那就好!我们将在本书的第三部分关于持久性的讨论中讨论所有这些,我们将讨论一般的设备和I/O,然后详细讨论磁盘,RAID和文件系统。
2.5 设计目标
所以现在你已经了解了操作系统实际上做了什么:它需要物理资源,例如CPU,内存或磁盘,并对它们进行虚拟化。它处理与并发相关的棘手且棘手的问题。它可以持久存储文件,从而使它们长期保持安全。鉴于我们希望建立这样一个系统,我们希望有一些目标,以帮助集中我们的设计和实施,并在必要时进行权衡;找到正确的权衡取舍是构建系统的关键。
最基本的目标之一是建立一些抽象,以使系统方便和易于使用。抽象是我们在计算机科学中所做的一切的基础。抽象使得编写一个大型程序成为可能,通过将它分成小的和可理解的部分,用C[^9]这样的高级语言编写这样的程序而不考虑汇编,在不考虑逻辑门的情况下在汇编中编写代码,并且在不考虑晶体管的情况下从栅极构建处理器。抽象是如此根本,有时我们会忘记它的重要性,但不会在这里;因此,在每一部分中,我们将讨论随着时间推移而形成的一些主要抽象,让您有机会思考操作系统的各个部分。
设计和实现操作系统的一个目标是提供高性能;另一种说法是我们的目标是最大限度地减少操作系统的开销。虚拟化和使系统易于使用是值得的,但不惜任何代价;因此,我们必须努力提供虚拟化和其他操作系统功能,而不会产生过多的开销。这些开销以多种形式出现:额外时间(更多指令)和额外空间(在内存或磁盘上)。如果可能的话,我们将寻求最小化一个或另一个或两者的解决方案。然而,完美并不总是可以实现的,我们将学会注意到(在适当的地方)容忍的东西。
另一个目标是在应用程序之间以及操作系统和应用程序之间提供保护。因为我们希望允许许多程序同时运行,所以我们希望确保一个程序的恶意或偶然的不良行为不会伤害他人;我们当然不希望应用程序能够损害操作系统本身(因为这会影响系统上运行的所有程序)。保护是操作系统的主要原则之一的核心,即操作系统的隔离;将进程彼此隔离是保护的关键,因此是操作系统必须做的大部分工作的基础。
操作系统也必须不间断运行;当它失败时,系统上运行的所有应用程序也会失败。由于这种依赖性,操作系统通常努力提供高度可靠性。随着操作系统变得越来越复杂(有时包含数百万行代码),构建可靠的操作系统是一项相当大的挑战 - 实际上,该领域正在进行的大部分研究(包括我们自己的一些工作[BS] +09,SS + 10])专注于这个确切的问题。
其他目标是有道理的:能源效率在我们日益增长的绿色世界中很重要; 对恶意应用程序的安全性(真正的保护扩展)至关重要,特别是在这些高度网络化的时代; 随着操作系统在越来越小的设备上运行,移动性变得越来越重要。 根据系统的使用方式,操作系统将有不同的目标,因此可能至少以稍微不同的方式实施。 但是,正如我们将看到的,我们将介绍如何构建操作系统的许多原则在一系列不同的设备上都很有用。
2.6 历史
在结束本介绍之前,让我们简要介绍操作系统的开发过程。 与人类构建的任何系统一样,随着时间的推移,操作系统中积累了好的想法,因为工程师在设计中学到了重要的东西。 在这里,我们讨论几个主要的发展。 对于更丰富的资料,请参阅Brinch Hansen的操作系统的优秀历史[BH00]。
早期操作系统:只是库
一开始,操作系统并没有做太多。基本上,它只是一组常用函数库;例如,代替让每个程序员编写低级I/O处理代码,“OS”将提供这样的API,从而使开发人员的生活更轻松。
通常,在这些旧的主机系统上,一次运行一个程序,由人工操作员控制。您认为现代操作系统将执行的大部分操作(例如,决定运行作业的顺序)是由此操作员执行的。如果你是一个聪明的开发人员,你会对这个操作员很好,这样他们就可以将你的工作转移到队列的前面。
这种计算模式称为批处理(batch),因为许多作业已经建立,然后由运营商“批量”运行。到目前为止,计算机没有以交互方式使用,因为成本:让用户坐在电脑前并使用它太昂贵了,因为大多数时候它只是闲置,每小时花费数十万美元[BH00].
库之上:保护
除了作为常用服务的简单库之外,操作系统在管理机器方面发挥了更重要的作用。一个重要方面是认识到代表操作系统运行的代码是特殊的;它控制了设备,因此应该相比正常的应用程序代码被区别对待。为什么是这样?好吧,想象一下,如果你允许任何应用程序从磁盘上的任何地方读取;隐私的概念就会消失,因为任何程序都可以读取任何文件。因此,将文件系统(管理文件)实现为库几乎没有意义。相反,需要其他东西。
因此,系统调用的想法是由Atlas计算系统[K + 61,L78]开创的。不是提供操作系统例程作为库(其中你需要使用过程调用去访问它们),而是增加一对特殊的硬件指令和硬件状态,以便将操作系统转换为更正式的受控过程。
系统调用和过程调用之间的关键区别在于系统调用将控制(即跳转)转移到OS中,同时提高硬件权限级别(hardware privilege level)。用户应用程序在所谓的用户模式(user model)下运行,这意味着硬件限制了应用程序可以执行的操作;例如,以用户模式运行的应用程序通常不能向磁盘发起I / O请求,访问任何物理内存页面或在网络上发送数据包。当启动系统调用时(通常通过称为陷阱(陷阱)的特殊硬件指令),硬件将控制转移到预先指定的陷阱处理程序(操作系统先前设置)并同时将特权级别提升到内核模式(kernel mode)。在内核模式下,操作系统可以完全访问系统的硬件,因此可以执行诸如启动I/O请求或为程序提供更多内存等操作。当OS完成对请求的服务时,它通过一个特殊的从陷阱返回指令将控制权传递给用户,该指令恢复到用户模式,同时将控制权传递回应用程序停止的位置。
多道程序时代
操作系统真正起飞的地方是大型机之后,即小型机(minicomputer)的计算时代。像Digital Equipment的PDP系列这样的经典机器使计算机的价格更加便宜;因此,相比每个大型组织中存在一个大型机,现在组织内的一小部分人可能拥有自己的计算机。毫不奇怪,这种成本下降的主要影响之一是开发人员活动的增加;更聪明的人掌握在计算机上,从而使计算机系统做更多有趣和美好的事情。
特别是,由于需要更好地利用机器资源,因此多道程序设计(multiprogramming)变得司空见惯。不再仅仅一个时间只运行一个程序,OS会加载多个作业到内存中并它们之间快速切换,从而提高CPU利用率。这种切换特别重要,因为I / O设备很慢;当I/O正在服务时在CPU上等待程序是浪费CPU时间。相反,为什么不切换到另一个工作并运行一段时间呢?
在存在I / O和中断的情况下支持多道程序设计和重叠的愿望迫使在多个方向上对操作系统的概念开发进行创新。内存保护等问题变得很重要,我们不希望一个程序能够访问另一个程序的内存。了解如何处理多道程序设计引入的并发问题也很关键,尽管存在中断,确保操作系统正常运行是一项巨大的挑战。我们将在本书后面研究这些问题和相关主题。当时的主要实际进步之一是引入了UNIX操作系统,主要得益于贝尔实验室的Ken Thompson(和Dennis Ritchie)(是的,电话公司)。 UNIX从不同的操作系统(特别是来自Multics [O72],以及一些来自TENEX [B + 72]和Berkeley Time-Sharing System [S + 68]等系统)获得了许多好想法,但使它们更简单,更容易使用。很快,这个团队向世界各地的人们发送了包含UNIX源代码的磁带,其中许多人随后参与并自行添加到系统中;有关更多详细信息,请参阅旁白(下一页)10。
现代
除了小型机之外,还出现了一种新型机器,更便宜,速度更快,适用于大众:我们今天称之为个人电脑或PC。在Apple的早期机器(例如Apple II)和IBM PC的带领下,这种新型机器将很快成为计算领域的主导力量,低成本的桌面电脑取代了每个工作组的共享小型机。
不幸的是,对于操作系统而言,PC最初代表了一个巨大的倒退,因为早期的系统忘记了(或者从未知道)在小型机时代学到的经验教训。例如,早期的操作系统,如DOS(来自微软的磁盘操作系统),并不认为内存保护很重要;因此,恶意(或者可能只是程序设计不佳)的应用程序可能会随意操作内存。 Mac OS的第一代(v9及更早版本)采用合作方式进行作业调度;因此,一个意外陷入无限循环的线程可以接管整个系统,迫使重启。在这一代系统中,缺少的操作系统功能的列表很长,太久了,无法在此进行全面讨论。
幸运的是,经过几年的痛苦,小型机操作系统的旧功能开始在桌面上找到它们的方式。例如,Mac OS X / macOS的核心是UNIX,包括人们对这种成熟系统所期望的所有功能。 Windows已经类似地采用了计算历史中的许多好想法,特别是从Windows NT开始,这是Microsoft OS技术的一次重大飞跃。即使在今天的手机上运行的操作系统(如Linux)也更像20世纪70年代的小型机,而不是20世纪80年代PC的运行(谢天谢地);很高兴看到在OS开发的全盛时期开发的好主意已经进入现代世界。更好的是,这些想法不断发展,提供更多功能,使现代系统更好地适用于用户和应用程序。
旁白:UNIX的重要性
在操作系统的历史中,很难夸大UNIX的重要性。受早期系统(特别是麻省理工学院著名的Multics系统)的影响,UNIX汇集了许多伟大的想法,并创建了简单而强大的系统。
最初的“贝尔实验室”UNIX的基础是构建小型强大程序的统一原则,这些程序可以连接在一起形成更大的工作流程。您键入命令的shell提供了诸如管道(pipe)之类的原语来启用此类元级别编程,因此将程序串联在一起以完成更大任务变得很容易。例如,要查找其中包含单词“foo”的文本文件的行,然后计算存在多少这样的行,您可以键入:grep foo file.txt | wc -l
,从而使用grep和wc (字数统计)程序来完成你的任务。
UNIX环境对程序员和开发人员都很友好,同时也为新的C编程语言提供了编译器。让程序员轻松编写自己的程序并分享它们,使UNIX非常受欢迎。这可能对作者提供了很多帮助,这对于任何提出过早期形式的开源软件的人来说都是免费的。
同样至关重要的是代码的可访问性和可读性。拥有一个用C编写的漂亮的小内核邀请其他人使用内核,添加新的和酷的功能。例如,由比尔·乔伊(Bill Joy)领导的伯克利(Berkeley)的一个企业集团做了一个精彩的发行版(Berkeley Systems Distribution,或BSD),它有一些先进的虚拟内存,文件系统和网络子系统。 Joy后来共同创立了Sun Microsystems。不幸的是,随着公司试图维护所有权并从中获利,UNIX的传播速度有所放缓,这是律师参与的一个
不幸(但很常见)的结果。许多公司都有自己的变体:Sun的SunOS,IBM的AIX,惠普的HPUX(a.k.a。“H-Pucks”)和SGI的IRIX。 AT&T /贝尔实验室和其他玩家之间的法律纠纷在U NIX上投下了一片乌云,许多人想知道它是否会存活下来,特别是在Windows被引入并占据了PC市场的大部分时......
盘白:Linux到来
对于UNIX来说幸运的是,一位名叫Linus Torvalds的年轻芬兰黑客决定编写他自己的UNIX版本,该版本大量借用原始系统背后的原则和思想,但不是来自代码库,因此避免了合法性问题。他得到了世界各地许多人的帮助,很快Linux诞生了(以及现代开源软件运动)。随着互联网时代的到来,大多数公司(如谷歌,亚马逊,Facebook和其他公司)选择运行Linux,因为它是免费的,可以随时修改以满足他们的需求;实际上,很难想象这些新公司的成功是否存在这样一个系统。随着智能手机成为面向用户的主导平台,由于许多相同的原因,Linux也在那里(通过android)找到了一个据点。史蒂夫·乔布斯将他基于UNIX的NeXTStep操作环境带到了苹果公司,从而使得UNIX在台式机上大受欢迎(尽管Apple技术的许多用户可能都不知道这一事实)。因此,UNIX继续存在,今天比以往任何时候都更加重要。计算神,如果你相信它们,应该感谢这个奇妙的结果。
2.7 总结
我们介绍了操作系统。今天的操作系统相对易于使用,今天你使用的几乎所有操作系统都受到将在本书中讨论的发展的影响。
由于时间限制,我们将不会在书中介绍操作系统的许多部分。例如,操作系统中有很多网络代码;我们留给您的网络课程了。同样,图形设备尤为重要,学习图形课程以扩展您在这方面的知识。最后,一些操作系统书籍谈论了很多安全性,我们将这样做,即操作系统必须在运行程序之间提供保护并使用户能够保护其文件,但我们不会深入研究安全课程中可能发现的更深层次的安全问题。但是,我们将讨论许多重要的主题,包括CPU和内存虚拟化的基础知识,并发性以及通过设备和文件系统的持久性。别担心!虽然有很多需要考虑的地方,但大部分内容都非常酷,而且学习完后,您将对计算机系统的真正运作方式有了新的认识。现在开始学习吧!
以上是关于2.操作系统简介的主要内容,如果未能解决你的问题,请参考以下文章
Android 逆向类加载器 ClassLoader ( 类加载器源码简介 | BaseDexClassLoader | DexClassLoader | PathClassLoader )(代码片段
C#-WebForm-★内置对象简介★Request-获取请求对象Response相应请求对象Session全局变量(私有)Cookie全局变量(私有)Application全局公共变量Vi(代码片段
newCacheThreadPool()newFixedThreadPool()newScheduledThreadPool()newSingleThreadExecutor()自定义线程池(代码片段