1. 1. 操作系统概述
    1. 1.1. 操作系统的主要功能
    2. 1.2. 操作系统的分类
  2. 2. 进程管理
    1. 2.1. 进程概念
      1. 2.1.1. 进程与程序的区别
      2. 2.1.2. 进程控制块(PCB)
    2. 2.2. 进程状态
    3. 2.3. 进程调度
      1. 2.3.1. 调度算法
      2. 2.3.2. 示例:时间片轮转调度算法
    4. 2.4. 进程同步
      1. 2.4.1. 临界区问题
      2. 2.4.2. 同步机制
      3. 2.4.3. 示例:使用信号量解决生产者-消费者问题
    5. 2.5. 进程通信
      1. 2.5.1. 分类
      2. 2.5.2. 常见通信方式
    6. 2.6. 线程
      1. 2.6.1. 线程与进程的区别
      2. 2.6.2. 线程的分类
  3. 3. 内存管理
    1. 3.1. 内存管理概述
      1. 3.1.1. 内存管理的主要功能
    2. 3.2. 连续分配管理方式
      1. 3.2.1. 单一连续分配
      2. 3.2.2. 固定分区分配
      3. 3.2.3. 动态分区分配
    3. 3.3. 非连续分配管理方式
      1. 3.3.1. 分页存储管理
      2. 3.3.2. 分段存储管理
      3. 3.3.3. 段页式存储管理
    4. 3.4. 虚拟内存管理
      1. 3.4.1. 虚拟内存的特征
      2. 3.4.2. 虚拟内存的实现方式
      3. 3.4.3. 页面置换算法
      4. 3.4.4. 示例:请求分页存储管理的页表结构
  4. 4. 文件系统
    1. 4.1. 文件系统概述
      1. 4.1.1. 文件的概念
      2. 4.1.2. 文件的属性
      3. 4.1.3. 文件的操作
    2. 4.2. 文件的组织结构
      1. 4.2.1. 无结构文件(流式文件)
      2. 4.2.2. 有结构文件(记录式文件)
    3. 4.3. 文件的存储结构
      1. 4.3.1. 连续分配
      2. 4.3.2. 链接分配
      3. 4.3.3. 索引分配
    4. 4.4. 文件系统的实现
      1. 4.4.1. 文件控制块(FCB)
      2. 4.4.2. 目录结构
      3. 4.4.3. 存储空间管理
    5. 4.5. 文件系统的操作
      1. 4.5.1. 示例:使用系统调用操作文件
  5. 5. 设备管理
    1. 5.1. 设备管理概述
      1. 5.1.1. 设备的分类
      2. 5.1.2. 设备管理的主要功能
    2. 5.2. I/O 控制方式
    3. 5.3. 设备驱动程序
      1. 5.3.1. 设备驱动程序的主要功能
    4. 5.4. 设备独立性
    5. 5.5. 缓冲管理
      1. 5.5.1. 缓冲的类型
    6. 5.6. 磁盘管理
      1. 5.6.1. 磁盘调度算法
      2. 5.6.2. 示例:使用扫描算法(SCAN)进行磁盘调度
  6. 6. 用户接口
    1. 6.1. 命令接口
      1. 6.1.1. 联机命令接口
      2. 6.1.2. 脱机命令接口
    2. 6.2. 程序接口
      1. 6.2.1. 系统调用的分类
      2. 6.2.2. 系统调用的实现
  7. 7. 操作系统面试题
    1. 7.1. 1. 什么是操作系统?它的主要功能是什么?
    2. 7.2. 2. 进程和线程的区别是什么?
    3. 7.3. 3. 什么是死锁?产生死锁的必要条件是什么?
    4. 7.4. 4. 如何预防和避免死锁?
    5. 7.5. 5. 什么是虚拟内存?它的作用是什么?
    6. 7.6. 6. 分页和分段的区别是什么?
    7. 7.7. 7. 常见的进程调度算法有哪些?
    8. 7.8. 8. 常见的页面置换算法有哪些?
  8. 8. 参考资料
  9. 9. 总结

操作系统学习总结

操作系统概述

操作系统(Operating System,OS)是管理计算机硬件与软件资源的计算机程序,是计算机系统的核心与基石。它负责管理与配置内存、决定系统资源供需的优先次序、控制输入设备与输出设备、操作网络与管理文件系统等基本任务。

操作系统的主要功能

  • 进程管理:负责进程的创建、调度、同步和通信
  • 内存管理:负责内存的分配、回收和保护
  • 文件系统:负责文件的存储、组织和访问
  • 设备管理:负责设备的分配、控制和驱动
  • 用户接口:提供用户与操作系统交互的方式

操作系统的分类

  • 批处理操作系统:按批量处理作业,如早期的 IBM OS/360
  • 分时操作系统:允许多个用户同时使用计算机,如 UNIX、Linux
  • 实时操作系统:对时间有严格要求,如 VxWorks、RTOS
  • 网络操作系统:管理网络资源,如 Windows Server、Linux
  • 分布式操作系统:管理分布式系统资源,如 Amoeba、Mach

进程管理

进程概念

进程是程序的一次执行过程,是操作系统进行资源分配和调度的基本单位。

进程与程序的区别

  • 程序:静态的,是一组指令的集合
  • 进程:动态的,是程序的执行过程
  • 一个程序可以对应多个进程,一个进程可以包含多个程序

进程控制块(PCB)

进程控制块是操作系统用于管理进程的核心数据结构,包含进程的所有信息:

  • 进程标识符(PID)
  • 进程状态
  • 程序计数器(PC)
  • 寄存器集合
  • 内存管理信息
  • 文件描述符表
  • 进程优先级
  • 进程间通信信息

进程状态

进程在其生命周期中会经历不同的状态,典型的进程状态包括:

  1. 运行态(Running):进程正在 CPU 上执行
  2. 就绪态(Ready):进程已准备好执行,等待分配 CPU
  3. 阻塞态(Blocked):进程等待某事件发生(如 I/O 完成)
  4. 创建态(New):进程正在被创建
  5. 终止态(Terminated):进程执行完毕或被终止

进程调度

进程调度是操作系统按照一定的算法从就绪队列中选择一个进程分配 CPU。

调度算法

  • 先来先服务(FCFS):按照进程到达的先后顺序调度
  • 最短作业优先(SJF):选择估计运行时间最短的进程调度
  • 最短剩余时间优先(SRTF):SJF 的抢占式版本
  • 优先级调度:按照进程的优先级调度
  • 时间片轮转(RR):为每个进程分配一个时间片,时间片用完后切换到下一个进程
  • 多级反馈队列调度:结合了多种调度算法的优点

示例:时间片轮转调度算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include <stdio.h>
#include <stdlib.h>

typedef struct Process {
int pid; // 进程ID
int arrival_time; // 到达时间
int burst_time; // 运行时间
int remaining_time;// 剩余运行时间
int waiting_time; // 等待时间
int turnaround_time; // 周转时间
} Process;

void round_robin_scheduling(Process processes[], int n, int quantum) {
int current_time = 0;
int completed = 0;
int *queue = (int *)malloc(n * sizeof(int));
int front = 0, rear = 0;
int *visited = (int *)calloc(n, sizeof(int));

// 先将所有已到达的进程加入队列
for (int i = 0; i < n; i++) {
if (processes[i].arrival_time == 0) {
queue[rear++] = i;
visited[i] = 1;
}
}

while (completed < n) {
int current_process = queue[front++];

// 执行一个时间片
if (processes[current_process].remaining_time > quantum) {
current_time += quantum;
processes[current_process].remaining_time -= quantum;
} else {
current_time += processes[current_process].remaining_time;
processes[current_process].remaining_time = 0;
completed++;

// 计算等待时间和周转时间
processes[current_process].turnaround_time = current_time - processes[current_process].arrival_time;
processes[current_process].waiting_time = processes[current_process].turnaround_time - processes[current_process].burst_time;
}

// 将新到达的进程加入队列
for (int i = 0; i < n; i++) {
if (processes[i].arrival_time <= current_time && processes[i].remaining_time > 0 && !visited[i]) {
queue[rear++] = i;
visited[i] = 1;
}
}

// 如果当前进程还未完成,重新加入队列
if (processes[current_process].remaining_time > 0) {
queue[rear++] = current_process;
}
}

free(queue);
free(visited);
}

void print_results(Process processes[], int n) {
printf("PID\t到达时间\t运行时间\t等待时间\t周转时间\n");
for (int i = 0; i < n; i++) {
printf("%d\t%d\t\t%d\t\t%d\t\t%d\n",
processes[i].pid,
processes[i].arrival_time,
processes[i].burst_time,
processes[i].waiting_time,
processes[i].turnaround_time);
}

// 计算平均等待时间和平均周转时间
float avg_waiting_time = 0, avg_turnaround_time = 0;
for (int i = 0; i < n; i++) {
avg_waiting_time += processes[i].waiting_time;
avg_turnaround_time += processes[i].turnaround_time;
}
avg_waiting_time /= n;
avg_turnaround_time /= n;

printf("\n平均等待时间: %.2f\n", avg_waiting_time);
printf("平均周转时间: %.2f\n", avg_turnaround_time);
}

int main() {
int n, quantum;
printf("输入进程数量: ");
scanf("%d", &n);

Process *processes = (Process *)malloc(n * sizeof(Process));

for (int i = 0; i < n; i++) {
printf("\n输入进程 %d 的到达时间和运行时间: ", i + 1);
scanf("%d %d", &processes[i].arrival_time, &processes[i].burst_time);
processes[i].pid = i + 1;
processes[i].remaining_time = processes[i].burst_time;
processes[i].waiting_time = 0;
processes[i].turnaround_time = 0;
}

printf("\n输入时间片大小: ");
scanf("%d", &quantum);

round_robin_scheduling(processes, n, quantum);
print_results(processes, n);

free(processes);
return 0;
}

进程同步

进程同步是指协调并发进程的执行,使它们按照一定的顺序或规则访问共享资源,避免数据不一致或竞态条件。

临界区问题

临界区是进程中访问共享资源的代码段。解决临界区问题需要满足以下条件:

  • 互斥:同一时间只能有一个进程进入临界区
  • 前进:如果没有进程在临界区,应允许一个等待的进程进入
  • 有限等待:进程在进入临界区前的等待时间是有限的

同步机制

  • 信号量(Semaphore):一种用于控制访问共享资源的计数器
    • 整型信号量
    • 记录型信号量
  • 互斥锁(Mutex):用于实现互斥的特殊信号量
  • 条件变量(Condition Variable):用于线程间的通信和同步
  • 管程(Monitor):将共享资源和操作封装起来的高级同步机制

示例:使用信号量解决生产者-消费者问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>

#define BUFFER_SIZE 5

int buffer[BUFFER_SIZE];
int in = 0, out = 0;

// 信号量
sem_t empty; // 空缓冲区数量
sem_t full; // 满缓冲区数量
sem_t mutex; // 互斥锁

void *producer(void *arg) {
int item;
for (int i = 0; i < 10; i++) {
item = rand() % 100; // 生产一个随机数

sem_wait(&empty); // 等待空缓冲区
sem_wait(&mutex); // 加锁

// 将 item 放入缓冲区
buffer[in] = item;
printf("生产者生产: %d, 放入位置: %d\n", item, in);
in = (in + 1) % BUFFER_SIZE;

sem_post(&mutex); // 解锁
sem_post(&full); // 增加满缓冲区数量

sleep(rand() % 2); // 模拟生产时间
}
return NULL;
}

void *consumer(void *arg) {
int item;
for (int i = 0; i < 10; i++) {
sem_wait(&full); // 等待满缓冲区
sem_wait(&mutex); // 加锁

// 从缓冲区取出 item
item = buffer[out];
printf("消费者消费: %d, 取出位置: %d\n", item, out);
out = (out + 1) % BUFFER_SIZE;

sem_post(&mutex); // 解锁
sem_post(&empty); // 增加空缓冲区数量

sleep(rand() % 3); // 模拟消费时间
}
return NULL;
}

int main() {
pthread_t producer_thread, consumer_thread;

// 初始化信号量
sem_init(&empty, 0, BUFFER_SIZE);
sem_init(&full, 0, 0);
sem_init(&mutex, 0, 1);

// 创建线程
pthread_create(&producer_thread, NULL, producer, NULL);
pthread_create(&consumer_thread, NULL, consumer, NULL);

// 等待线程结束
pthread_join(producer_thread, NULL);
pthread_join(consumer_thread, NULL);

// 销毁信号量
sem_destroy(&empty);
sem_destroy(&full);
sem_destroy(&mutex);

return 0;
}

进程通信

进程通信是指进程之间交换信息的机制。

分类

  • 低级通信机制:信号、管道
  • 高级通信机制:消息队列、共享内存、套接字

常见通信方式

  • 管道(Pipe):半双工通信,只能在父子进程或兄弟进程之间使用
  • 命名管道(FIFO):可以在不相关进程之间使用
  • 消息队列(Message Queue):存储消息的链表,支持消息的优先级
  • 共享内存(Shared Memory):进程共享同一块物理内存,是最快的通信方式
  • 信号量(Semaphore):主要用于同步,也可用于简单的通信
  • 套接字(Socket):用于网络通信,可以在不同主机上的进程之间通信

线程

线程是进程内的一个执行单元,是 CPU 调度的基本单位。

线程与进程的区别

  • 资源分配:进程是资源分配的基本单位,线程共享进程的资源
  • 调度:线程是 CPU 调度的基本单位
  • 并发性:一个进程内的多个线程可以并发执行
  • 系统开销:创建和切换线程的开销比进程小

线程的分类

  • 用户级线程:由用户程序管理,操作系统不可见
  • 内核级线程:由操作系统管理
  • 混合级线程:结合了用户级线程和内核级线程的优点

内存管理

内存管理概述

内存管理负责计算机内存的分配、回收和保护,确保多道程序能够高效、安全地运行。

内存管理的主要功能

  • 内存分配与回收
  • 内存保护
  • 地址映射
  • 内存扩充

连续分配管理方式

连续分配是指为进程分配连续的内存空间。

单一连续分配

  • 内存分为系统区和用户区
  • 一次只允许一个用户进程运行
  • 适用于单用户、单任务操作系统

固定分区分配

  • 将内存划分为固定大小的分区
  • 每个分区只能容纳一个进程
  • 存在内部碎片

动态分区分配

  • 根据进程的实际需要动态分配内存
  • 存在外部碎片
  • 分配算法:首次适应算法、最佳适应算法、最坏适应算法、邻近适应算法

非连续分配管理方式

非连续分配是指为进程分配不连续的内存空间。

分页存储管理

  • 将物理内存划分为大小相等的页框(Page Frame)
  • 将进程的逻辑地址空间划分为大小相等的页(Page)
  • 通过页表(Page Table)实现页到页框的映射

分段存储管理

  • 将进程的逻辑地址空间划分为若干个段(Segment)
  • 每个段对应一个逻辑单位(如函数、数组)
  • 通过段表(Segment Table)实现段到物理内存的映射

段页式存储管理

  • 结合了分页和分段的优点
  • 先将逻辑地址空间划分为段,再将每个段划分为页
  • 通过段表和页表实现地址映射

虚拟内存管理

虚拟内存是指操作系统为进程提供的一个比实际物理内存大得多的虚拟地址空间。

虚拟内存的特征

  • 多次性:进程可以分多次调入内存
  • 对换性:进程可以在内存和外存之间对换
  • 虚拟性:为进程提供大于实际物理内存的虚拟地址空间

虚拟内存的实现方式

  • 请求分页存储管理
  • 请求分段存储管理
  • 请求段页式存储管理

页面置换算法

  • 最佳置换算法(OPT):选择未来最长时间不使用的页面置换
  • 先进先出置换算法(FIFO):选择最早调入的页面置换
  • 最近最久未使用置换算法(LRU):选择最近最久未使用的页面置换
  • 时钟置换算法(Clock):FIFO 的改进版本
  • 最不常用置换算法(LFU):选择访问次数最少的页面置换

示例:请求分页存储管理的页表结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#include <stdio.h>
#include <stdlib.h>

#define PAGE_SIZE 4096 // 页面大小为4KB
#define PHYSICAL_MEMORY_SIZE 16384 // 物理内存大小为16KB
#define NUM_PAGES (PHYSICAL_MEMORY_SIZE / PAGE_SIZE) // 物理内存页数

// 页表项结构
typedef struct PageTableEntry {
int valid; // 有效位,1表示页面在内存中
int frame_number;// 页框号
int dirty; // 脏位,1表示页面被修改过
int reference; // 引用位,1表示页面最近被访问过
} PageTableEntry;

// 页表结构
typedef struct PageTable {
PageTableEntry *entries;
int num_entries;
} PageTable;

// 创建页表
PageTable* create_page_table(int num_entries) {
PageTable *page_table = (PageTable *)malloc(sizeof(PageTable));
page_table->num_entries = num_entries;
page_table->entries = (PageTableEntry *)calloc(num_entries, sizeof(PageTableEntry));
return page_table;
}

// 销毁页表
void destroy_page_table(PageTable *page_table) {
free(page_table->entries);
free(page_table);
}

// 地址转换:将逻辑地址转换为物理地址
int translate_address(PageTable *page_table, int logical_address) {
// 计算页号和页内偏移
int page_number = logical_address / PAGE_SIZE;
int offset = logical_address % PAGE_SIZE;

// 检查页号是否有效
if (page_number < 0 || page_number >= page_table->num_entries) {
printf("页号越界:%d\n", page_number);
return -1;
}

PageTableEntry *entry = &page_table->entries[page_number];

// 检查页面是否在内存中
if (!entry->valid) {
printf("页面故障:页号 %d 不在内存中\n", page_number);
// 这里应该触发页面置换算法
// 为了简化,我们假设页面已经被调入内存
entry->valid = 1;
entry->frame_number = page_number % NUM_PAGES; // 简单地分配一个页框
printf("页面 %d 已调入页框 %d\n", page_number, entry->frame_number);
}

// 更新引用位
entry->reference = 1;

// 计算物理地址
int physical_address = entry->frame_number * PAGE_SIZE + offset;
printf("逻辑地址: %d (页号: %d, 偏移: %d) -> 物理地址: %d\n",
logical_address, page_number, offset, physical_address);

return physical_address;
}

int main() {
// 创建一个包含10个页表项的页表
PageTable *page_table = create_page_table(10);

// 测试地址转换
int logical_addresses[] = {1000, 5000, 10000, 15000, 20000};
int num_addresses = sizeof(logical_addresses) / sizeof(logical_addresses[0]);

for (int i = 0; i < num_addresses; i++) {
int logical_address = logical_addresses[i];
int physical_address = translate_address(page_table, logical_address);
if (physical_address != -1) {
printf("地址转换成功\n");
} else {
printf("地址转换失败\n");
}
printf("\n");
}

// 销毁页表
destroy_page_table(page_table);

return 0;
}

文件系统

文件系统概述

文件系统是操作系统用于管理文件的一套机制,负责文件的存储、组织和访问。

文件的概念

文件是具有文件名的一组相关数据的集合。

文件的属性

  • 文件名
  • 文件类型
  • 文件大小
  • 文件位置
  • 文件保护
  • 文件时间(创建时间、修改时间、访问时间)

文件的操作

  • 创建文件(create)
  • 打开文件(open)
  • 读文件(read)
  • 写文件(write)
  • 关闭文件(close)
  • 删除文件(delete)
  • 重命名文件(rename)

文件的组织结构

文件的组织结构是指文件的逻辑结构,即用户看到的文件组织形式。

无结构文件(流式文件)

  • 文件是字节流的集合
  • 没有固定的记录结构
  • 适用于文本文件、二进制文件等

有结构文件(记录式文件)

  • 文件由若干个记录组成
  • 每个记录包含若干个字段
  • 适用于数据库文件等

文件的存储结构

文件的存储结构是指文件在物理存储设备上的组织形式。

连续分配

  • 文件的所有块连续存储在物理磁盘上
  • 优点:顺序访问速度快
  • 缺点:存在外部碎片,文件扩展困难

链接分配

  • 文件的块分散存储在物理磁盘上,通过指针链接
  • 优点:没有外部碎片,文件扩展方便
  • 缺点:顺序访问速度慢,可靠性差

索引分配

  • 为每个文件创建一个索引块,记录文件所有块的位置
  • 优点:支持随机访问,文件扩展方便
  • 缺点:需要额外的存储空间存储索引块

文件系统的实现

文件系统的实现包括文件控制块(FCB)、目录结构和存储空间管理。

文件控制块(FCB)

文件控制块是操作系统用于管理文件的核心数据结构,包含文件的所有属性。

目录结构

  • 单级目录结构:所有文件都放在一个目录中
  • 两级目录结构:分为主目录和用户目录
  • 树形目录结构:目录组织成树形结构,支持路径名
  • 图形目录结构:树形目录结构的扩展,支持目录链接

存储空间管理

  • 空闲表法:记录空闲的磁盘块
  • 空闲链表法:将空闲磁盘块链接成链表
  • 位示图法:用位表示磁盘块的使用情况
  • 成组链接法:结合了空闲表法和空闲链表法的优点

文件系统的操作

示例:使用系统调用操作文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main() {
int fd;
char buffer[1024];
ssize_t bytes_read, bytes_written;

// 创建文件
fd = open("test.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}

// 写入文件
const char *text = "Hello, Operating System!\nThis is a test file.\n";
bytes_written = write(fd, text, strlen(text));
if (bytes_written == -1) {
perror("write");
close(fd);
exit(EXIT_FAILURE);
}
printf("写入了 %ld 字节\n", bytes_written);

// 关闭文件
close(fd);

// 打开文件读取
fd = open("test.txt", O_RDONLY);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}

// 读取文件
printf("\n文件内容:\n");
while ((bytes_read = read(fd, buffer, sizeof(buffer))) > 0) {
write(STDOUT_FILENO, buffer, bytes_read);
}
if (bytes_read == -1) {
perror("read");
close(fd);
exit(EXIT_FAILURE);
}

// 关闭文件
close(fd);

// 删除文件
if (unlink("test.txt") == -1) {
perror("unlink");
exit(EXIT_FAILURE);
}
printf("\n文件已删除\n");

return 0;
}

设备管理

设备管理概述

设备管理负责计算机系统中各种 I/O 设备的分配、控制和驱动,确保设备能够高效、可靠地工作。

设备的分类

  • 按传输速率:低速设备、中速设备、高速设备
  • 按信息交换单位:字符设备、块设备
  • 按设备共享属性:独占设备、共享设备、虚拟设备

设备管理的主要功能

  • 设备分配与回收
  • 设备控制
  • 缓冲管理
  • 设备独立性
  • 设备驱动程序管理

I/O 控制方式

I/O 控制方式是指 CPU 与 I/O 设备之间的信息交换方式。

  1. 程序直接控制方式:CPU 不断查询设备状态,直到 I/O 完成
  2. 中断驱动方式:设备完成 I/O 后向 CPU 发送中断信号
  3. DMA 方式:直接内存访问,设备与内存直接交换数据,CPU 只需初始化和完成时处理
  4. 通道控制方式:通过通道(一种专用的 I/O 处理器)控制 I/O 设备

设备驱动程序

设备驱动程序是操作系统与 I/O 设备之间的接口,负责将操作系统的请求转换为设备能够理解的命令。

设备驱动程序的主要功能

  • 初始化设备
  • 处理设备中断
  • 实现设备的各种操作(如读、写)
  • 管理设备的电源

设备独立性

设备独立性是指应用程序与实际使用的物理设备无关,提高了系统的可移植性和灵活性。

缓冲管理

缓冲是指在内存中为 I/O 设备分配的一块临时存储区域,用于缓解 CPU 与 I/O 设备之间速度不匹配的问题。

缓冲的类型

  • 单缓冲:只有一个缓冲区
  • 双缓冲:有两个缓冲区,一个用于输入,一个用于输出
  • 循环缓冲:多个缓冲区组成一个循环队列
  • 缓冲池:由多个缓冲区组成的公用缓冲区域

磁盘管理

磁盘管理负责磁盘的调度、格式化和维护。

磁盘调度算法

  • 先来先服务(FCFS):按照请求到达的先后顺序处理
  • 最短寻道时间优先(SSTF):选择离当前磁头位置最近的请求处理
  • 扫描算法(SCAN):磁头在磁盘上双向移动,处理经过的所有请求
  • 循环扫描算法(C-SCAN):磁头只向一个方向移动,处理经过的所有请求
  • LOOK 算法:SCAN 算法的改进,磁头只移动到最远的请求位置
  • C-LOOK 算法:C-SCAN 算法的改进,磁头只移动到最远的请求位置

示例:使用扫描算法(SCAN)进行磁盘调度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include <stdio.h>
#include <stdlib.h>

// 比较函数,用于排序
int compare(const void *a, const void *b) {
return (*(int *)a - *(int *)b);
}

void scan_disk_scheduling(int requests[], int n, int current_position, int direction) {
int total_seek_time = 0;
int *left = (int *)malloc(n * sizeof(int));
int *right = (int *)malloc(n * sizeof(int));
int left_count = 0, right_count = 0;

// 将请求分为左边和右边
for (int i = 0; i < n; i++) {
if (requests[i] < current_position) {
left[left_count++] = requests[i];
} else {
right[right_count++] = requests[i];
}
}

// 排序
qsort(left, left_count, sizeof(int), compare);
qsort(right, right_count, sizeof(int), compare);

printf("磁盘调度顺序: %d", current_position);

// 向右移动
if (direction == 1) {
for (int i = 0; i < right_count; i++) {
total_seek_time += abs(right[i] - current_position);
current_position = right[i];
printf(" -> %d", current_position);
}
// 如果还有左边的请求,移动到最右端再向左移动
if (left_count > 0) {
total_seek_time += abs(199 - current_position); // 假设磁盘最大磁道号为199
current_position = 199;
printf(" -> %d", current_position);
for (int i = left_count - 1; i >= 0; i--) {
total_seek_time += abs(left[i] - current_position);
current_position = left[i];
printf(" -> %d", current_position);
}
}
}
// 向左移动
else {
for (int i = left_count - 1; i >= 0; i--) {
total_seek_time += abs(left[i] - current_position);
current_position = left[i];
printf(" -> %d", current_position);
}
// 如果还有右边的请求,移动到最左端再向右移动
if (right_count > 0) {
total_seek_time += abs(current_position - 0); // 假设磁盘最小磁道号为0
current_position = 0;
printf(" -> %d", current_position);
for (int i = 0; i < right_count; i++) {
total_seek_time += abs(right[i] - current_position);
current_position = right[i];
printf(" -> %d", current_position);
}
}
}

printf("\n总寻道时间: %d\n", total_seek_time);

free(left);
free(right);
}

int main() {
int n, current_position, direction;

printf("输入请求数量: ");
scanf("%d", &n);

int *requests = (int *)malloc(n * sizeof(int));

printf("输入请求的磁道号: ");
for (int i = 0; i < n; i++) {
scanf("%d", &requests[i]);
}

printf("输入当前磁头位置: ");
scanf("%d", &current_position);

printf("输入移动方向 (1: 向右, 0: 向左): ");
scanf("%d", &direction);

scan_disk_scheduling(requests, n, current_position, direction);

free(requests);
return 0;
}

用户接口

用户接口是用户与操作系统交互的桥梁,提供了用户使用操作系统功能的方式。

命令接口

命令接口是用户通过输入命令来操作计算机的接口。

联机命令接口

  • 交互式命令接口:用户输入一条命令,系统执行后返回结果
  • 命令行解释器:负责解释和执行用户输入的命令

脱机命令接口

  • 批处理命令接口:用户将命令写入批处理文件,系统批量执行

程序接口

程序接口是应用程序与操作系统之间的接口,通过系统调用(System Call)实现。

系统调用的分类

  • 进程控制:创建进程、终止进程、进程通信等
  • 文件操作:创建文件、打开文件、读文件、写文件等
  • 设备操作:请求设备、释放设备、读写设备等
  • 信息维护:获取系统时间、设置系统时间等
  • 通信:创建管道、创建套接字等

系统调用的实现

  • 用户程序通过陷入指令(Trap)进入内核态
  • 内核执行相应的系统调用处理函数
  • 处理完成后返回用户态,将结果返回给用户程序

操作系统面试题

1. 什么是操作系统?它的主要功能是什么?

操作系统是管理计算机硬件与软件资源的计算机程序,是计算机系统的核心与基石。它的主要功能包括进程管理、内存管理、文件系统、设备管理和用户接口。

2. 进程和线程的区别是什么?

  • 资源分配:进程是资源分配的基本单位,线程共享进程的资源
  • 调度:线程是 CPU 调度的基本单位
  • 并发性:一个进程内的多个线程可以并发执行
  • 系统开销:创建和切换线程的开销比进程小

3. 什么是死锁?产生死锁的必要条件是什么?

死锁是指多个进程因竞争资源而造成的一种僵局,若无外力作用,这些进程都将无法继续执行。产生死锁的必要条件包括:

  • 互斥条件:资源不能被共享
  • 请求和保持条件:进程已获得的资源在未使用完之前不能被剥夺
  • 不剥夺条件:进程已获得的资源在未使用完之前不能被剥夺
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

4. 如何预防和避免死锁?

  • 预防死锁:破坏产生死锁的四个必要条件之一
  • 避免死锁:在资源分配时进行安全性检查,确保系统处于安全状态
  • 检测和解除死锁:允许死锁发生,然后检测并解除

5. 什么是虚拟内存?它的作用是什么?

虚拟内存是指操作系统为进程提供的一个比实际物理内存大得多的虚拟地址空间。它的作用是:

  • 扩大地址空间
  • 内存保护
  • 内存共享
  • 内存分配优化

6. 分页和分段的区别是什么?

  • 分页:将物理内存划分为大小相等的页框,将进程的逻辑地址空间划分为大小相等的页
  • 分段:将进程的逻辑地址空间划分为若干个段,每个段对应一个逻辑单位
  • 目的:分页主要是为了提高内存利用率,分段主要是为了满足用户的需求
  • 大小:页的大小固定,段的大小不固定
  • 地址空间:分页的地址空间是一维的,分段的地址空间是二维的

7. 常见的进程调度算法有哪些?

  • 先来先服务(FCFS)
  • 最短作业优先(SJF)
  • 最短剩余时间优先(SRTF)
  • 优先级调度
  • 时间片轮转(RR)
  • 多级反馈队列调度

8. 常见的页面置换算法有哪些?

  • 最佳置换算法(OPT)
  • 先进先出置换算法(FIFO)
  • 最近最久未使用置换算法(LRU)
  • 时钟置换算法(Clock)
  • 最不常用置换算法(LFU)

参考资料

  1. 《操作系统概念》(第九版),Abraham Silberschatz 等著
  2. 《现代操作系统》(第四版),Andrew S. Tanenbaum 著
  3. 《深入理解计算机系统》(第三版),Randal E. Bryant 等著
  4. 《Linux 内核设计与实现》(第三版),Robert Love 著

总结

操作系统是计算机系统的核心软件,负责管理和协调计算机的所有资源。本文从操作系统概述入手,详细介绍了进程管理、内存管理、文件系统、设备管理和用户接口等核心内容,并通过示例代码演示了一些重要的概念和算法。同时,本文还解答了一些常见的操作系统面试题,帮助读者更好地理解和应用操作系统知识。

学习操作系统不仅可以帮助我们理解计算机系统的工作原理,还可以提高我们的程序设计能力和系统调优能力。希望本文能够为读者提供一个全面、系统的操作系统学习总结,为进一步深入学习操作系统打下坚实的基础。