Chapter 2 Introduction to Operating Systems

第 2 章 操作系统介绍

有一类软件负责让程序运行变得容易,允许同时运行多个程序,允许程序共享内存,让程序能够与设备交互...这些软件叫做操作系统(Operating Systems, OS)

1.1. 通用技术 1:虚拟化(vier/tualization)

操作系统 (virtual machine) 将物理资源转换为更通用、更强大、更易于使用的虚拟形式。

操作系统提供 API 来调用:

  • 几百个 system call

  • 提供 standard library

1.2. 通用技术 2:资源管理

操作系统 (resource manager) 将 资源 (resource) 进行 管理 (manage)

1. 虚拟化 CPU

1.1. cpu.c

#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>\n");
    exit(1);
  }

  char *str = argv[1];
  while (1)
  {
    Spin(1);
    printf("%s\n", str);
  }
}

同一个程序的多个实例运行

root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter2# ./cpu "A" & ./cpu "B" & ./cpu "C" & ./cpu "D" &
[1] 1235
[2] 1236
[3] 1237
[4] 1238
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter2# B
D
A
C
D
B
A
C
B
D
A
C
^C

操作系统负责提供这种假象,尽管我们只有一个处理器,但四个程序似乎同时运行!

OS 负责提供这种 illusion,即系统拥有非常多虚拟 CPU(virtualizaing the CPU)

OS 有一些既定的 policy 来决定相关事项...

1.2. 如何运行 & 停止

运行

./cpu "A" & ./cpu "B" & ./cpu "C" & ./cpu "D" &

停止

kill %1	# 其中   %1   表示第一个后台作业。
kill %2
kill %3
kill %4

2. 虚拟化内存

内存的 read:需要指定 address

而 write 和 update 则还需要指定 data

2.1. mem.c

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include "common.h"

int main(int argc, char *argv[])
{
  int *p = malloc(sizeof(int)); // 分配内存
  assert(p != NULL);
  printf("(%d) memory address of p: %08x\n", getpid(), (unsigned int)p); // 打印出内存的地址
  *p = 0;                                                                // 数字 0 放入新分配的内存的第一个空位中

  while (1)
  {
    Spin(1);                                // 等待 1 秒
    *p = *p + 1;                            // 数字 递增 放入内存的第一个空位中
    printf("(%d) *p = %d\n", getpid(), *p); // 打印出值 和 PID, PID 对于每个运行进程是唯一的
  }

  return 0;
}

PID 对于 每个运行进程是唯一的

使用 kill PID 来停止程序!在 Ctrl + C 失效的时候。

2.2. 虚拟化内存(virtualizing memory)

每个进程访问自己的私有虚拟地址空间(virtual address space)/(address space)

所有这些是如何完成的?请继续学下去!

3. 并发

concurrency

并发原本来自于操作系统中,如何同时处理很多事情?

后续拓展到现代多线程(multi-threaded)程序...

-Wall 是 GCC(GNU Compiler Collection)编译器的一个常用选项,它用于启用所有警告信息。这些警告信息可以帮助你发现代码中可能存在的问题,从而提高代码质量和可维护性。

3.1. thread.c

代码有小改动:

加入 pthread.h 头文件

pthread 的相关函数 改为小写开头

编译指令记得加上 -lpthread

#include <stdio.h>
#include <stdlib.h>
#include <pthread.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>\n");
    exit(1);
  }
  loops = atoi(argv[1]);
  pthread_t p1, p2;
  printf("Initial value: %d\n", 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\n", counter);
  return 0;
}

基本思想

  • 主程序使用 pthread_create() 创建了两个线程

  • 两个线程分别在 worker 函数中运行。

  • 线程同时共享一个计时器 counter,各自在循环中增加其次数...

  • 按道理来说最终的次数应该是输入次数的两倍

root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter2# gcc -o thread thread.c -Wall -lpthread
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter2# ./thread 1000
Initial value: 0
Final value: 2000
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter2# ./thread 10000
Initial value: 0
Final value: 20000
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osTEP/chapter2# ./thread 100000
Initial value: 0
Final value: 151388

在 loop 的值较小时,输出正确,但是值越大...就出现了偏差,甚至每次运行结果都不一样了

3.2. 并发问题

指令每次执行一条。

上面的程序核心是增加共享计数器,需要三条指令:

  1. 计时器的值从内存加载到寄存器

  2. 值递增

  3. 保存回内存

因为 3 条指令不是 atomically 原子执行的...所以会发生奇怪的事情...后续会继续介绍

原子方式:所有指令一次性执行

4. 持久性

系统内存中,数据容易丢失。如果断电或崩溃,内存中所有数据都丢失,所以需要硬件和软件来 persistently 存储数据。

易失的方式:volatile 一种存储数值的方式

涉及:

  • Input/Output,IO

  • hard drive

  • Solid-State Drive, SSD

  • file system and file

4.1. io.c

#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!\n", 13);
  assert(rc == 13);
  close(fd);
  return 0;
}

操作系统发出三个调用:

  • open() 调用,打开文件并创建它

  • write() 调用,将一些文件写入文件

  • close() 调用,简单地关闭文件,表明程序不会再向他写入更多的数据

这些 system call 被转到 file system 的操作系统部分,然后系统处理这些请求!

并向用户返回相应的错误代码。

4.2. 关键问题:如何持久地存储数据

文件系统是操作系统的一部分,负责管理持久的数据。

  • 持久性需要哪些技术来实现?

  • 需要哪些机制和策略才能高性能实现?

  • 面对硬件和软件故障,可靠性如何实现?

5. 设计目标

操作系统:

  • manage resource

  • virtualize

  • concurrency

  • persistently file

5.1. 基本目标

建立 abstraction,让系统方便和易于使用。

5.2. 设计实现的目标

提高性能(performance)即 最小化操作系统的开销(minimize the overhead)

开销:

  • 额外时间 - 更多指令

  • 额外空间 - 内存或磁盘

5.3. APP 之间 && OS 和 APP 之间 的目标

提供保护 protection

我们希望许多程序同时进行,所以要保证一个程序的恶意或偶然的不良行为不会损害其他程序。

protection 的核心是 isolation

5.4. 不间断运行

reliability,一旦 OS 大厦崩塌,那么系统上运行的所有 APP 也会失效...

5.5. 其他目标

能源效率

energy-efficiency

安全性

security

移动性

mobility

6. 简单的历史

简单介绍开发历史,属于课外阅读材料

确实应当如此,而不是像国内的课程一般,历史...来源....名词等等甚至单开一章...

6.1. 早期的操作系统:只是一些库

由专门的 operator 开管理程序。

batch 批处理

6.2. 超越库:保护

系统概念 system call 的概念诞生了

系通调用 和 过程调用 procedure call

6.3. 多道程序时代

大主机计算时代之后,小型机(minicomputer)时代 OS 兴起。

multiprogramming 变得很普遍。

  • memory protection 内存保护

  • concurrency 并发问题

成为新的挑战

6.4. 摩登时代

今天 Personal Computer 个人计算机普及之后

开源的操作系统为用户和应用程序提供更好功能,让现代系统更加完善

UNIX 的 重要性

其环境对程序员和开发人员很友好,为 C 语言提供编译器。一开始开源时非常受欢迎,open-source software 的早期形式,author 向所有人发布 免费副本

后期公司维护权和利润,于是公司们纷纷开创出 UNIX 变种供使用

Linux 的开发

芬兰黑客决定编写他自己的 UNIX 版本,召集世界黑客,不久 Linux 就诞生了

现在大多数公司都选择使用 Linux 因为其免费。

Last updated