
操作系统导论读书笔记
文章平均质量分 53
Operating Systems Tree Easy Pieces
CCSUZB
吾生也有涯,而知也无涯
展开
-
第 33 章 基于事件的并发(进阶)
33.1 基本想法:事件循环在深入细节之前,我们先看一个典型的基于事件的服务器。这种应用都是基于一个简单的结构,称为事件循环(event loop)。while (1) { events = getEvents(); for (e in events) processEvent(e);}主循环等待某些事件发生(通过 getEvents()调用),然后依次处理这些发生的事件。处理事件的代码叫作事件处理程序(event handler)。重要的是,处理程序在处理一个事件时,它是系统中发生的唯一活原创 2022-04-09 13:58:17 · 349 阅读 · 0 评论 -
第32章 常见并发问题
32.1 有哪些类型的缺陷非死锁的缺陷死锁缺陷32.2 非死锁缺陷违反原子性缺陷: Thread 1:: if (thd->proc_info) { ... fputs(thd->proc_info, ...); ... } Thread 2:: thd->proc_info = NULL;这个例子中,两个线程都要访问 thd 结构中的成员 proc_info。第一个线程检查 proc_info非空,然后打印出值;第二个线程设置其为空。显然,当第一个线程检查原创 2022-03-28 22:54:05 · 458 阅读 · 0 评论 -
第31章 信号量
31.1 信号量的定义信号量是有一个整数值的对象,可以用两个函数来操作它。在 POSIX 标准中,是sem_wait()和 sem_post()1。#include <semaphore.h>sem_t s;sem_init(&s, 0, 1);其中申明了一个信号量 s,通过第三个参数,将它的值初始化为 1。sem_init()的第二个参数,在我们看到的所有例子中都设置为 0,表示信号量是在同一进程的多个线程共享的。31.2 二值信号量(锁)信号量的第一种用法是我们已经熟原创 2022-03-26 14:34:28 · 1547 阅读 · 0 评论 -
第30章 条件变量
,在很多情况下,线程需要检查某一条件(condition)满足之后,才会继续运行。例如,父线程需要检查子线程是否执行完毕 [这常被称为 join()]。这种等待如何实现呢?我们可以尝试用一个共享变量,如图 30.2 所示。这种解决方案一般能工作,但是效率低下,因为主线程会自旋检查,浪费 CPU 时间。我们希望有某种方式让父线程休眠,直到等待的条件满足(即子线程完成执行)。 volatile int done = 0; void *child(void *arg) { printf("child\原创 2022-03-20 14:19:56 · 1049 阅读 · 0 评论 -
第29章基于锁的并发数据结构
29.1 并发计数器如下为一个非并发的计数器: typedef struct counter_t { int value; } counter_t; void init(counter_t *c) { c->value = 0; } void increment(counter_t *c) { c->value++; } void decrement(counter_t *c) { c->value--; } int get(counter_t *原创 2022-03-16 22:40:19 · 557 阅读 · 0 评论 -
第28章 锁
程序员在源代码中加锁,放在临界区周围,保证临界区能够像单条原子指令一样执行。28.1锁的基本思想 lock_t mutex; // some globally-allocated lock 'mutex' lock(&mutex); balance = balance + 1; unlock(&mutex);锁就是一个变量,因此我们需要声明一个某种类型的锁变量(lock variable,如上面的mutex),才能使用。这个锁变量(简称锁)保存了锁在某一时刻的状态。它要么是可用原创 2022-03-09 22:17:33 · 550 阅读 · 0 评论 -
第27章 插叙: 线程API
27.1 线程创建#include <assert.h>#include <stdio.h>#include <pthread.h>typedef struct { int a; int b;} myarg_t;void *mythread(void *arg) { myarg_t *args = (myarg_t *) arg; printf("%d %d\n", args->a, args->b);原创 2022-03-07 22:33:34 · 224 阅读 · 0 评论 -
第26章 并发:介绍
本章将介绍为单个运行进程提供的新抽象:线程(thread)。经典观点是一个程序只有一个执行点(一个程序计数器,用来存放要执行的指令),但多线程(multi-threaded)程序会有多个执行点(多个程序计数器,每个都用于取指令和执行) 。换一个角度来看,每个线程类似于独立的进程,只有一点区别:它们共享地址空间,从而能够访问相同的数据。26.1 实例:线程创建#include <stdio.h>#include <stdlib.h>#include <pthread.h原创 2022-03-03 23:06:17 · 279 阅读 · 0 评论 -
第19章 分页:快速地址转换(TLB)
使用分页作为核心机制来实现虚拟内存,可能会带来较高的性能开销。因为要使用分页,就要将内存地址空间切分成大量固定大小的单元(页),并且需要记录这些单元的地址映射信息原创 2022-03-02 22:21:47 · 593 阅读 · 0 评论 -
第18章 分页:介绍
18.1 一个简单例子为了记录地址空间的每个虚拟页放在物理内存中的位置,操作系统通常为每个进程保存一个数据结构,称为页表(page table)。页表的主要作用是为地址空间的每个虚拟页面保存地址转换(address translation),从而让我们知道每个页在物理内存中的位置为了转换(translate)该过程生成的虚拟地址,我们必须首先将它分成两个组件:虚拟页面号(virtual page number,VPN)和页内的偏移量(offset)18.3 列表中究竟有什么页表就是一种数据结构,用原创 2022-01-02 16:24:41 · 312 阅读 · 0 评论 -
第 17 章 空闲空间管理
以本章需要解决的问题是:要满足变长的分配请求,应该如何管理空闲空间?什么策略可以让碎片最小化?不同方法的时间和空间开销如何?17.1 假设在堆上管理空闲空间的数据结构通常称为空闲列表(free list)。该结构包含了管理内存区域中所有空闲块的引用。17.3 基本策略最优匹配:首先遍历整个空闲列表,找到和请求大小一样或更大的空闲块,然后返回这组候选者中最小的一块。最差匹配:,它尝试找最大的空闲块,分割并满足用户需求后,将剩余的块(很大)加入空闲列表首次匹配:找到第一个足够大的块,将请求的原创 2021-12-26 22:30:35 · 444 阅读 · 0 评论 -
第16章分段
如果我们将整个地址空间放入物理内存,那么栈和堆之间的空间并没有被进程使用,却依然占用了实际的物理内存。16.1 分段:泛化的基址/界限分段的机制使得操作系统能够将不同的段放到不同的物理内存区域,从而避免了虚拟地址空间中的未使用部分占用物理内存16.2 我们引用哪个段硬件在地址转换时使用段寄存器。它如何知道段内的偏移量,以及地址引用了哪个段?那么在我们的例子中,如果前两位是 00,硬件就知道这是属于代码段的地址,因此使用代码段的基址和界限来重定位到正确的物理地址。如果前两位是 01,则是堆地址,对原创 2021-12-26 21:33:14 · 211 阅读 · 0 评论 -
第15章 机制:地址转换
地址转换(address translation)。它可以看成是受限直接执行这种一般方法的补充。利用地址转换,硬件对每次内存访问进行处理(即指令获取、数据读取或写入),将指令中的虚拟(virtual)地址转换为数据实际存储的物理(physical)地址15.3 动态(基于硬件)重定位每个 CPU 需要两个硬件寄存器:基址(base)寄存器和界限(bound)寄存器,有时称为限制(limit)寄存器将虚拟地址转换为物理地址,这正是所谓的地址转换(address translation)技术。转换示例原创 2021-12-25 19:33:04 · 696 阅读 · 0 评论 -
第14章 插叙 :内存操作 API
14.1 内存类型第一种称为栈内存,它的申请和释放操作是编译器来隐式管理的第二种类型的内存,即所谓的堆(heap)内存,其中所有的申请和释放操作都由程序员显式地完成14.4 常见错误忘记分配内存char *src = "hello";char *dst; // oops! unallocatedstrcpy(dst, src); // segfault and die没有分配足够的内存char *src = "hello";char *dst = (char *) mal原创 2021-12-22 20:43:22 · 280 阅读 · 0 评论 -
第13章 抽象:地址空间
13.1 早期系统操作系统曾经是一组函数(实际上是一个库),在内存中(在本例中,从物理地址 0 开始),然后有一个正在运行的程序(进程),目前在物理内存中(在本例中,从物理地址 64KB 开始),并使用剩余的内存。13.2 多道程序和时分共享多道程序(multiprogramming)系统时代开启[DV66],其中多个进程在给定时间准备运行,比如当有一个进程在等待 I/O 操作的时候,操作系统会切换这些进程,这样增加了 CPU 的有效利用率(utilization)。实现时分共享的方法,是让原创 2021-12-20 22:51:19 · 297 阅读 · 0 评论 -
第9章 调度: 比例份额
原创 2021-03-07 21:34:03 · 140 阅读 · 0 评论 -
第8章 调度: 多级反馈队列
8.1 多级反馈队列(MLFQ):基本规则6 6原创 2021-03-02 22:44:53 · 278 阅读 · 0 评论 -
第七章 进程调度: 介绍
7.3 先进先出(FIFO):Example_1: 工作平均周转时间为(100+110+120)/3=120Example_2: 工作平均周转时间为(10+20+30)/3=207.4 最短任务优先(SJF)原创 2020-12-20 11:20:31 · 264 阅读 · 1 评论 -
第6章 机制: 受限直接执行
6.2 问题1:受限制的操作用户模式下运行的代码会受到限制,例如在用户模式下运行时候不能发出I/O请求;内核模式下运行的代码可以做它喜欢的事,包括特权操作;但是用户如果想从用户模式转到内核模式应该怎么做? 这时候就要靠特殊的陷阱(trap)指令,该指令同时跳入内核并将特权级别提升到内核模式;...原创 2020-12-18 21:51:08 · 137 阅读 · 0 评论 -
第5章 插叙:进程API
5.1 fork系统调用p1.c代码:#include <stdio.h>#include <stdlib.h>#include <unistd.h>int main(int argc, char *argv[]){ printf("hello world (pid:%d)\n", (int) getpid()); int rc = fork(); if (rc < 0 ) { fprintf(stderr, "原创 2020-12-14 21:56:51 · 418 阅读 · 0 评论 -
第4章 抽象:进程
4.3 进程创建操作系统运行第一件事情就是将代码和所有静态数据加载到内存中,加载到进程的地址空间中4.4 进程状态运行: 进程正在处理器上运行,意味着它正在执行指令就绪:进程已经准备好运行阻塞:一个进程执行了某种操作,知道发生其他事件才会准备运行...原创 2020-12-13 14:52:26 · 108 阅读 · 0 评论 -
第2章:操作系统介绍
1. 虚拟化CPU#include <stdio.h>#include <stdlib.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) { printf("%s原创 2020-12-13 13:54:05 · 215 阅读 · 0 评论