您的位置:首页 > 运维架构

[翻译] Operating System-There Easy Pieces 2.操作系统介绍

2019-07-04 07:58 387 查看

《 Operating System-There Easy Pieces 》这本书是一本非常好的操作系统教材,里面讲述了进程管理、内存管理、并发和文件系统的原理、历史。浅显易懂,是学习操作系统不可多得的好书。但是目前只有英文版,其实英文版词汇量也不算多,我这英语水平看着都不会太吃力。

但还是想翻译成中文,以便以后复习方便。可我最近发现已经有中文翻译版本的书出版在 卖了,而我目前只翻译了前10章。一下子没有了动力。

把书买了回来,先看看。前10章自己翻译(其实基本是谷歌翻译)的内容就在这里贴出来吧。

 

 

2.操作系统介绍

如果您正在攻读本科操作系统课程,那么您应该已经知道计算机程序运行时发生了什么。如果没有,这本书(和相应的课程)将很难。

 - 所以你应该停止阅读这本书,或者跑到最近的书店,去学习消化必要的背景资料(Patt / Patel [PP03],特别是Bryant / O'Hallaron [BOH10]是非常好的书) 。

那么程序运行会发生什么?

好吧,正在运行的程序做了一件非常简单的事情:它执行指令。每秒数百万(有时甚至数十亿)次,处理器从存储器中取出指令,对其进行解码(即,确定这是哪条指令),并执行它(即,让该指令完成该做的事,比如两个数字相加,访问内存,检查条件,跳转到一个函数,等等)。完成此指令后,处理器继续执行下一条指令,依此类推直到程序最终完成。

因此,我们刚刚描述了冯诺依曼计算模型的基础知识。听起来很简单吧?但是在本课程中,我们将学习在程序运行的同时,还有很多其他的东西正在进行,其主要目标是使系统易于使用。

事实上,有一大堆软件负责使程序运行变得容易(甚至允许你看似同时运行多个程序),允许程序共享内存,使程序与设备交互,以及其他有趣的类似的东西。该软件主体称为操作系统(OS),因为它负责确保系统以易于使用的方式正确有效地运行。

操作系统执行此操作的主要方式是通过我们称为虚拟化的通用技术。也就是说,操作系统采用物理资源(例如处理器,内存或磁盘)并将其转换为更通用,功能强大且易于使用的虚拟形式。因此,我们有时将操作系统称为虚拟机。

当然,为了让用户告诉操作系统做什么从而利用虚拟机的功能(例如运行程序,分配内存或访问文件),操作系统还提供了一些接口(您可以调用的API)。事实上,典型的OS会导出几百个可供应用程序使用的系统调用。由于操作系统提供这些调用来运行程序,访问内存和设备以及其他相关操作,我们有时也会说操作系统为应用程序提供了标准库。

最后,因为虚拟化允许许多程序运行(从而共享了CPU),并且许多程序同时访问自己的指令和数据(从而共享内存),以及许多访问设备的程序(因此共享磁盘等等),OS有时被称为资源管理器。每个CPU,内存和磁盘都是系统的资源;因此,操作系统的角色是管理这些资源,有效或公平地执行,或者考虑到许多其他可能的目标。为了更好地理解操作系统的作用,让我们看看一些例子。

1   #include <stdio.h>

2   #include <stdlib.h>

3   #include <sys/time.h>

4   #include <assert.h>

5   #include "common.h"

6

7   int

8   main(int argc, char *argv[])

9   {

10        if (argc != 2) {

11             fprintf(stderr, "usage: cpu <string>\n");

12             exit(1);

13        }

14        char *str = argv[1];

15        while (1) {

16             Spin(1);

17             printf("%s\n", str);

18        }

19        return 0;

20  }

Figure 2.1: Simple Example: Code That Loops and Prints

 

 

2.1 虚拟CPU

图2.1描绘了我们的第一个程序。它没有做太多事情。实际上,它所做的只是调用Spin(),这是一个重复检查时间的函数,一旦运行一秒就会返回。 然后,它打印出用户在命令行上传入的字符串,并永远重复。 假设我们将此文件保存为cpu.c并决定在具有单个处理器的系统上编译和运行它。 以下是我们将看到的内容:

 

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

C

A

B

D

C

A

C

B

D

...

Figure 2.2: Running Many Programs At Once

好吧,现在事情变得更有趣了。即使我们只有一个处理器,但不知何故,所有这四个程序似乎都在同时运行!这种魔力是如何发生的?事实证明,操作系统在硬件的帮助下负责处理这种情况,即让系统看起来具有大量虚拟CPU的错觉。将单个CPU(或一小组CPU)转换为看似无限数量的CPU,从而允许许多程序看起来像是同同时运行的。这就是我们所说的虚拟化CPU,这是本书第一部分的重点。

当然,想要要运行程序、停止程序以及告诉操作系统运行哪些程序,这都需要使用一些接口(API)来将您的需求传达给操作系统。我们将在本书中讨论这些API。实际上,它们是大多数用户与操作系统交互的主要方式。

您可能还注意到,一次运行多个程序的能力会引发各种新问题。例如,如果两个程序想要在特定时间运行,哪个应该运行?这个问题取决于操作系统的策略。这种策略在操作系统中的很多地方都用到了,都用于处理这类问题。因此我们将在学习操作系统实现的基本机制(例如一次运行多个程序的能力)时研究它们。所以说,操作系统是作为资源管理器的角色。

 

1     #include <unistd.h>    

2     #include <stdio.h>      

3     #include <stdlib.h>     

4     #include "common.h" 

5           

6     int  

7     main(int argc, char *argv[]) 

8     {     

9            int *p = malloc(sizeof(int));   // a1

10           assert(p != NULL);

11           printf("(%d) address of p: %08x\n",      

12           getpid(), (unsigned) p); // a2

13           *p = 0;    // a3

14           while (1) {

15                  Spin(1);

16                  *p = *p + 1;

17                  printf("(%d) p: %d\n", getpid(), *p); // a4

18           }

19           return 0;

20    }

Figure 2.3: A Program that Accesses Memory

 

2.2 虚拟内存

现在让我们考虑一下内存。 现代机器呈现的物理内存模型非常简单。 内存只是一个字节数组; 要读取内存,必须指定一个地址才能访问存储在那里的数据; 要写入(或更新)存储器,还必须指定要写入给定地址的数据。

程序运行时始终访问内存。 程序将所有数据结构保存在内存中,并通过各种指令访问它们,例如加载和存储或其他在执行工作时访问内存的显式指令。 不要忘记,程序的每个指令也都在内存中; 因此,在每次取指令时访问存储器。

让我们看一下通过调用malloc()来分配一些内存的程序(图2.3)。 这个程序的输出可以在这里找到:

prompt> ./mem

(2134) memory address of p: 00200000

(2134) p: 1

(2134) p: 2

(2134) p: 3

(2134) p: 4

(2134) p: 5

ˆC

该程序做了几件事。 首先,它分配一些内存(第a1行)。 然后,它打印出存储器的地址(a2),然后将数字0放入新分配的存储器的第一个插槽(a3)。 最后,它循环,延迟一秒并递增存储在p中保存的地址的值。 对于每个print语句,它还会打印出正在运行的程序的进程标识符(PID)。

  该PID在每个运行过程中都是唯一的。

prompt> ./mem &; ./mem & [1] 24113

[2] 24114

(24113)   memory address of p: 00200000

(24114)   memory address of p: 00200000

(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

同样,这第一个结果并不太有趣。新分配的内存位于地址00200000.程序运行时,会慢慢更新值并打印出结果。

现在,我们再次运行同一程序的多个实例,看看会发生什么(图2.4)。我们从示例中看到,运行程序已经在同一地址(00200000)分配了内存,但是如果每个正在运行的程序都有自己的私有内存,而不是与其他正在运行的程序共享相同的物理内存。

实际上,这正是这里发生的事情,因为操作系统对内存进行虚拟化。每个进程都访问自己的私有虚拟地址空间(有时也称为地址空间),操作系统以某种方式将其映射到计算机的物理内存中。一个正在运行的程序中的内存引用不会影响其他进程(或OS本身)的地址空间;就运行程序而言,它本身就具有物理内存。然而,现实是物理内存是由操作系统管理的共享资源。究竟如何实现所有这些也是本书第一部分关于虚拟化主题的主题。

 

2.3 并发

本书的另一个主题是并发性。 我们使用这个概念术语来指代在同一程序中同时处理许多事情(即同时)时出现并且必须解决的一系列问题。 并发问题首先出现在操作系统本身; 正如您在上面的虚拟化示例中所看到的,操作系统同时处理许多事情,首先运行一个进程,然后运行另一个进程,等等。 事实证明,这样做会导致一些深刻而有趣的问题。

1     #include <stdio.h>

2     #include <stdlib.h>

3     #include "common.h"

4

5     volatile int counter = 0;

6     int loops;

7

8     void *worker(void *arg) {

9            int i;

10           for (i = 0; i < loops; i++) {

11                  counter++;

12           }

13           return NULL;

14    }

15

16    int

17    main(int argc, char *argv[])

18    {

19           if (argc != 2) {

20                  fprintf(stderr, "usage: threads <value>\n");

21                  exit(1);

22           }

23           loops = atoi(argv[1]);

24           pthread_t p1, p2;

25           printf("Initial value : %d\n", counter);

26

27           Pthread_create(&p1, NULL, worker, NULL);

28           Pthread_create(&p2, NULL, worker, NULL);

29           Pthread_join(p1, NULL);

30           Pthread_join(p2, NULL);

31           printf("Final value  : %d\n", counter);

32           return 0;

33    }

Figure 2.5: A Multi-threaded Program

不幸的是,并发问题不再局限于操作系统本身。实际上,现代多线程程序表现出相同的问题。让我们用一个多线程程序的例子来演示(图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。 然后,当我们第二次运行程序时,我们不仅会再次获得错误的值,而且还会获得与上次不同的值。 事实上,如果你使用高值循环一遍又一遍地运行程序,你可能会发现有时你甚至得到了正确的答案! 那为什么会这样呢?

事实证明,这些奇怪和不寻常的结果的原因与指令的执行方式有关,这是一次一个。 不幸的是,上面程序的一个关键部分,共享计数器递增,需要三个指令:一个用于将计数器的值从存储器加载到寄存器中,一个用于递增它,一个用于将其存储回存储器。 因为这三个指令不是原子地执行(一次全部执行),所以会发生奇怪的事情。 正是这个并发问题,我们将在本书的第二部分详细讨论。

1     #include <stdio.h>

2     #include <unistd.h>

3     #include <assert.h>

4     #include <fcntl.h>

5     #include <sys/types.h>

6

7     int

8     main(int argc, char *argv[])

9     {

10           int fd = open("/tmp/file", O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU);

11           assert(fd > -1);

12           int rc = write(fd, "hello world\n", 13);

13           assert(rc == 13);

14           close(fd);

15           return 0;

16    }

Figure 2.6: A Program That Does I/O

 

2.4 持久性

该课程的第三个主题是持久性。在系统存储器中,数据很容易丢失,因为诸如DRAM的设备以一种可能的方式存储值;当电源消失或系统崩溃时,存储器中的任何数据都将丢失。因此,我们需要硬件和软件能够持久存储数据;因此,这种存储对于任何系统都是至关重要的,因为用户非常关心他们的数据。

硬件以某种输入/输出或I / O设备的形式出现;在现代系统中,硬盘驱动器是长期信息的常用存储库,尽管固态驱动器(SSD)也在这个领域处于领先地位。

通常管理磁盘的操作系统中的软件称为文件系统;因此,它负责将用户以可靠和有效的方式创建的任何文件存储在系统的磁盘上。

与操作系统为CPU和内存提供的抽象不同,操作系统不会为每个应用程序创建专用的虚拟化磁盘。相反,假设用户经常想要共享文件中的信息。例如,在编写C程序时,您可能首先使用编辑器(例如,Emacs7)来创建和编辑C文件(emacs -nw main.c)。完成后,您可以使用编译器将源代码转换为可执行文件(例如,gcc -o main main.c)。完成后,您可以运行新的可执行文件(例如./main)。因此,您可以看到如何跨不同进程共享文件。首先,Emacs创建一个文件,作为编译器的输入,编译器使用该输入文件来创建新的可执行文件(这里其实有许多步骤,可以编译器课程来获取详细信息);最后,然后运行新的可执行文件。因此一个新的程序诞生了!

为了更好地理解这一点,让我们看看一些代码。图2.6显示了创建包含字符串“hello world”的文件(/ tmp / file)的代码。

问题的关键:

如何持续存储数据

文件系统是负责管理持久数据的OS的一部分。 确保数据正确存储需要哪些技术? 高性能需要哪些机制和策略才能实现? 面对硬件和软件故障,如何实现可靠性?

为完成此任务,程序会对操作系统进行三次调用。第一个是对open()的调用,打开文件并创建它;第二个,write(),将一些数据写入文件;第三个,close(),简单地关闭文件,从而表明程序不会再向其写入数据。这些系统调用被集成到操作系统中,称为文件系统,然后处理请求并向用户返回某种错误代码。

您可能想知道操作系统为了实际写入磁盘而执行的操作。我们会告诉你,但你必须承诺先闭上眼睛;这是令人不快的。文件系统必须完成相当多的工作:首先确定这些新数据将驻留在磁盘上的哪个位置,然后在文件系统维护的各种结构中跟踪它。这样做需要向底层存储设备发出I / O请求,以读取现有结构或更新(写入)它们。任何编写过设备驱动程序的人都知道,让设备代表你做一些事情是一个非常复杂和详细的过程。它需要深入了解低级设备接口及其精确的语义。幸运的是,操作系统提供了一种通过系统调用访问设备的标准而简单的方法。因此,OS有时被视为标准库。

当然,还有更多关于如何访问设备的细节,以及文件系统如何在所述设备上持久地管理数据。出于性能原因,大多数文件系统首先将此类写入延迟一段时间,希望将它们批处理为更大的组。为了处理写入期间系统崩溃的问题,大多数文件系统都包含某种复杂的写入协议,例如日志记录或写入时复制,仔细排序写入磁盘以确保在写入序列期间发生故障时,之后系统可以恢复到合理的状态。为了使不同的通用操作高效,文件系统采用许多不同的数据结构和访问方法,从简单列表到复杂的网络。如果所有这些都没有意义,那就好!我们将在本书的第三部分关于持久性的讨论中讨论所有这些,我们将讨论设备和I / O,然后详细讨论磁盘,RAID和文件系统。

 

2.5 设计目标

所以现在你已经了解了操作系统实际上做了什么:它需要物理资源,例如CPU,内存或磁盘,并对它们进行虚拟化。它处理与并发相关的棘手问题。它可以持久存储文件,从而使它们长期安全。鉴于我们希望建立这样一个系统,我们希望有一些目标,以帮助集中我们的设计和实施,并在必要时进行权衡;找到正确的权衡取舍是构建系统的关键。

最基本的目标之一是建立一些抽象,以使系统方便和易于使用。抽象是我们在计算机科学中所做的一切的基础。抽象可以通过将大型程序划分为小型和可理解的部分来编写大型程序,以类似于C这样的高级语言编写这样的程序,而不考虑逻辑门的情况。在汇编中编写代码而不用不考虑晶体管的情况下从栅极构建处理器。抽象是如此基础,有时我们会忘记它的重要性,但我们这里不会。因此,在每一部分中,我们将讨论随着时间推移而形成的一些主要抽象,让您有机会思考操作系统的各个部分。

设计和实现操作系统的一个目标是提供高性能。另一种说法是我们的​​目标是最大限度地减少操作系统的开销。虚拟化使系统易于使用是值得的,但并非不惜任何代价;因此,我们必须努力提供虚拟化和其他操作系统功能,而不会产生过多的开销。这些开销以多种形式出现:额外时间(更多指令)和额外空间(在内存或磁盘上)。如果可能的话,我们将寻求两者之间最小开销的解决方案。然而,完美并不总是可以实现的,我们将学会适当(在适当的地方)容忍一些东西。

另一个目标是在应用程序之间,以及操作系统和应用程序之间提供保护。因为我们希望允许许多程序同时运行,所以我们希望确保一个程序的恶意行为,或偶然的不良行为不会伤害其他进程。我们当然不希望应用程序能够损害操作系统本身(因为这会影响系统上运行的所有程序)。保护是操作系统的主要原则之一的核心,即操作系统的隔离,将进程彼此隔离是保护的关键,这也是操作系统必须做一大基础工作。

操作系统也必须不间断运行;当它失败时,系统上运行的所有应用程序也会失败。由于这种依赖性,操作系统通常努力提供高度可靠性。随着操作系统变得越来越复杂(有时包含数百万行代码),构建可靠的操作系统是一个巨大的挑战,但同时也是非常有必要的。

事实上,该领域正在进行的大部分研究(包括我们自己的一些工作[BS + 09,SS + 10])都关注这个确切的问题。

其他目标是有道理的:能源效率在我们日益绿色的世界中很重要; 对恶意应用程序的安全性(真正的保护扩展)至关重要,特别是在这些高度网络化的时代; 随着操作系统在越来越小的设备上运行,移动性变得越来越重要。 根据系统的使用方式,操作系统将有不同的目标,因此可能至少以略微不同的方式实施。 但是,正如我们将要看到的,我们将介绍如何构建操作系统的许多原则在各种不同的设备中都很有用。

 

2.6 一些历史

在结束本介绍之前,让我们简要介绍操作系统的开发过程。与人类构建的任何系统一样,随着时间的推移,操作系统中积累了好的想法,因为工程师在设计中学到了重要的东西。在这里,我们讨论几个主要的发展。对于更丰富的治疗,请参阅Brinch Hansen的操作系统的优秀历史[BH00]。

 

早期操作系统:只是库

一开始,操作系统并没有做太多。基本上,它只是一组常用函数库。例如,代替让系统的每个程序员编写低级I / O处理代码,“OS”将提供这样的API,从而使开发人员的生活更轻松。

通常,在这些旧的主机系统上,一个程序一次运行,由人工操作员控制。您认为现代操作系统将执行的大部分操作(例如,决定运行作业的顺序)是由此操作员执行的。如果你是一个聪明的开发人员,你会对这个操作员很好,这样他们就可以将你的工作转移到队列的前面。

这种计算模式称为批处理,因为设置了许多作业,然后由操作员以“批处理”方式运行。到目前为止,计算机并没有以交互方式使用,因为成本:让用户坐在电脑前并使用它的成本太高,因为大多数时候它只是闲置,每小时花费数十万美元[BH00]。

 

超越库:保护

作为一个简单的常用服务库,运营系统在管理机器方面发挥了更重要的作用。其中一个重要方面是,认识到代表操作系统运行的代码是特殊的。它控制了设备,因此应该与正常的应用程序代码区别对待。为什么是这样?好吧,想象一下,如果你允许任何应用程序从磁盘上的任何地方读取,隐私的概念就会消失。因为任何程序都可以读取任何文件。因此,将文件系统(管理文件)实现为库几乎没有意义。相反,需要其他东西。

因此,系统调用的想法是由Atlas计算系统[K + 61,L78]开创的。这里的想法是添加一对特殊的硬件指令和硬件状态,以使转换到操作系统更加正式,受控制,而不是提供操作系统例程作为库(您只需要进行过程调用来访问它们)处理。

系统调用和过程调用之间的关键区别在于,系统调用将控制(即跳转)转移到OS中,同时提高硬件权限级别。用户应用程序在所谓的用户模式下运行,这意味着硬件限制了应用程序可以执行的操作。例如,以用户模式运行的应用程序通常不能发起对磁盘的I / O请求,访问任何物理内存页面或在网络上发送数据包。当启动系统调用时(通常通过称为陷阱的特殊硬件指令),硬件将控制转移到预先指定的陷阱处理程序(操作系统先前设置)并且同时将特权级别提升到内核模式。在内核模式下,操作系统可以完全访问系统的硬件,因此可以执行诸如启动I / O请求或为程序提供更多可用内存等操作。当OS完成对请求的服务时,它通过一个特殊的从陷阱返回指令将控制权传递给用户,该指令恢复到用户模式,同时将控制权传递回应用程序停止的位置。

 

多进程时代

操作系统真正起飞的时代是超出大型机的计算时代,即小型机的计算时代。像Digital Equipment的PDP系列这样的经典机器使计算机的价格更加便宜。因此,现在每个大型组织中没有一个大型机,现在组织内的一小部分人可能拥有自己的计算机。毫不奇怪,这种成本下降的主要影响之一是开发人员活动的增加;更聪明的人掌握在计算机上,从而使计算机系统做更多有趣和美好的事情。

特别是,由于需要更好地利用机器资源,因此多进程的设计变得司空见惯。操作系统不是一次只运行一个作业,而是将大量作业加载到内存中并在它们之间快速切换,从而提高CPU的使用率。这种切换特别重要,因为I / O设备很慢,在对I / O进行服务时对CPU进行程序等待是浪费CPU时间。相反,为什么不切换到另一个工作并运行一段时间呢?

在存在I / O和中断的情况下,支持多进程设计和重叠的愿望迫使操作系统的概念开发沿着多个方向进行创新。内存保护等问题变得很重要。我们不希望一个程序能够访问另一个程序的内存。了解如何处理多进程设计引入的并发问题也很关键。尽管存在中断,但确保操作系统正常运行依然是一项巨大的挑战。我们将在本书后面研究这些问题和相关主题。

当时最主要的进步之一是UNIX操作系统的引入,这主要得益于贝尔实验室的Ken Thompson(和Dennis Ritchie)(是的,那个电话公司)。 UNIX从不同的操作系统(特别是来自Multics [O72],以及一些来自TENEX [B + 72]和伯克利时间共享系统[S + 68]等系统)获得了灵感,但使它们更简单易用。很快,这个团队就向世界各地的人们发送了包含UNIX源代码的磁盘,其中更多的人参与了系统的开发。有关更多详细信息,请参阅旁白(下一页)10。

 

现代时代

除了小型机之外,还出现了一种新型机器,更便宜,速度更快,适用于大众:我们今天称之为个人电脑或PC。在Apple的早期机器(例如Apple II)和IBM PC的带领下,这种新型机器将很快成为计算领域的主导力量,因为它们以每台台式机的低成本启用一台机器,而不是每个工作组的共享小型机。

不幸的是,对于操作系统而言,PC最初代表了一个巨大的飞跃,因为早期的系统忘记了(或者从未知道)在小型机时代学到的经验教训。例如,早期的操作系统,如DOS(微软的磁盘操作系统),并不认为内存保护很重要;因此,恶意(或者可能只是程序设计不佳)的应用程序可能会在整个存储器中崩溃。第一代Mac OS(v9及更早版本)采用了合作方式进行作业调度。因此,一个意外陷入无限循环的线程可以接管整个系统,迫使重启。在这一代系统中缺少的OS功能的痛苦列表很长,对于这里的完整讨论来说太长了。

幸运的是,经过几年的苦难,微型计算机操作系统的旧功能开始进入桌面。 例如,Mac OS X的核心是UNIX,包括人们对这种成熟系统所期望的所有功能。 Windows同样采用了许多计算历史中的伟大创意,特别是从Windows NT开始,这是微软操作系统技术的一次重大飞跃。 即使是今天的手机也运行着操作系统(比如Linux),这些操作系统更像是20世纪70年代的小型机运行,而不是20世纪80年代的PC运行(谢天谢地); 很高兴看到在OS开发的全盛时期开发的好点子已经进入了现代世界。 更好的是,这些想法继续发展,提供更多功能,使现代系统更好地为用户和应用程序。

ASIDE:UNIX的重要性

在操作系统的历史中很难说明UNIX的重要性。受早期系统(特别是麻省理工学院著名的Multics系统)的影响,UNIX汇集了许多伟大的想法,并使系统既简单又强大。

原始“贝尔实验室”的基础UNIX是构建小型强大程序的基本原则,这些程序可以连接在一起形成更大的工作流程。您键入命令的shell提供了诸如管道之类的原语来启用此类元级编程,因此将程序串联起来以完成更大任务变得很容易。例如,要查找其中包含单词“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 Microsystems的SunOS,来自IBM的AIX,来自HP的HPUX(来自“H-Pucks”)和来自SGI的IRIX。 AT&T /贝尔实验室和其他玩家之间的法律纠纷在UNIX上投下了一片黑暗的云,许多人想知道它是否能够存活下来,特别是在Windows被引入并占据了PC市场的大部分时......

 

ASIDE:然后是CAME LINUX

幸运的是,对于UNIX来说,一位名叫Linus Torvalds的年轻芬兰黑客决定编写他自己的UNIX版本,该版本大量借用原始系统背后的原则和思想,但不是来自代码库,从而避免了合法性问题。他获得了世界各地许多其他人的帮助,很快Linux诞生了(以及现代开源软件运动)。

随着互联网时代的到来,大多数公司(如谷歌,亚马逊,Facebook和其他公司)选择运行Linux,因为它是免费的,可以随时修改以满足他们的需求;事实上,很难想象这些新公司的成功是否存在这样一个系统。随着智能手机成为一个占主导地位的面向用户的平台,由于许多相同的原因,Linux也在那里找到了一个据点(通过Android)。史蒂夫·乔布斯将他基于UNIX的NeXTStep操作环境带到了苹果公司,从而使UNIX在台式机上流行(尽管Apple技术的许多用户可能都不知道这一事实)。因此,UNIX比以往任何时候都更加重要。计算神,如果你相信它们,应该感谢这个奇妙的结果。

 

2.7 总结

本章我们介绍了操作系统。今天的操作系统使系统相对易于使用,而您今天使用的几乎所有操作系统都受到我们将在本书中讨论的发展的影响。

不幸的是,由于时间限制,我们将不会在书中介绍操作系统的许多部分。例如,操作系统中有很多网络代码。我们留给您网络工作班了解更多相关信息。同样,图形设备尤为重要;学习图形课程以扩展您在这方面的知识。最后,一些操作系统书籍谈论了很多安全性;我们将这样做,因为操作系统必须在运行程序之间提供保护并使用户能够保护他们的文件,但我们不会深入探讨安全课程中可能发现的更深层次的安全问题。

但是,我们将介绍许多重要主题,包括CPU和内存虚拟化的基础知识,并发性以及通过设备和文件系统的持久性。别担心!虽然有很多方面可以覆盖,但大部分内容都非常酷,而且在路的尽头,您将对计算机系统的真正工作方式有了新的认识。现在开始工作!

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐