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. 并发问题
指令每次执行一条。
上面的程序核心是增加共享计数器,需要三条指令:
计时器的值从内存加载到寄存器
值递增
保存回内存
因为 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