作業系統

計算機概論[3]

講師:溫室蔡

發展脈絡

早期的電腦一次只能執行一個程式

每個程式都要手動載入電腦中

隨著電腦越來越快

載入程式比執行程式還要花時間

於是需要一個「管理程式的程式」

批次處理

最早的作業系統非常簡單

它讓使用者可以一次放入多個程式

然後作業系統會從第一個程式開始執行

執行完畢後再換下一個

這就是「批次處理」

(Batch processing)

周邊設備

早期電腦的周邊設備可能是讀卡機、

打字機/印表機(相當於螢幕)等

但這些裝置型號、規格不一

為了讓程式設計師少考慮這些

作業系統就要負責硬體抽象化

驅動程式

作業系統透過專門設計的驅動程式與硬體溝通

作業系統

驅動程式

應用程式

應用程式

應用程式

應用程式

驅動程式

驅動程式

硬碟

螢幕

鍵盤

多工處理

作業系統的

三種多工的比較

Asynchronous execution

Threading

Multi-processing

import asyncio
import threading
import multiprocessing

非同步處理(async)

創建不會阻塞的函式

在開始一項耗時的工作後

就跳出去做別的事

常見於發送大量網路請求

多執行緒(threading)

同一個程式裡面有多條線在跑

執行緒之間共享記憶體

多處理程序(multiprocessing

一台電腦上開好幾個 process

每個 process 有自己的記憶體

由作業系統管理所有的 process

小測驗

一個四核心的 CPU

可以同時執行幾個程式?

A. 一個

B. 四個

C. 無限多個

小測驗

一個四核心的 CPU

可以同時執行幾個程式?

A. 一個

B. 四個

C. 無限多個

「同時」的假象

一個核心一次就是只能做一件事

看起來像同時是因為

核心在不同的 process 間高速切換

核心

A

OS

B

OS

A

OS

B

OS

上下文切換

程式在執行時會使用到 CPU 的暫存器

但是暫存器就只有那幾個

所以在切換 process 前

作業系統要保存目前 process 的狀態

並復原下一個 process 的狀態

稱為「上下文切換」(context switch)

記憶體管理

每一個 process

理想的狀況中

都會有自己的記憶體

這些記憶體是連續的

但實務上

可能會非常零碎

Process A

Process B

Process C

記憶體管理

每一個 process

理想的狀況中

都會有自己的記憶體

這些記憶體是連續的

但實務上

可能會非常零碎

Process A

Process B

Process C

Process A

Process B

Process A

記憶體管理

Process A

Process B

Process C

Process A

Process B

Process A

虛擬記憶體

Process A

Process B

Process C

Process A

Process B

Process A

Process A

Process A

Process A

Process A

虛擬記憶體

Process A

Process B

Process C

Process A

Process B

Process A

Process A

作業系統可以創造

「虛擬記憶體」

讓 process 認為

自己有連續的記憶體

資源管理

每個 process 的記憶體是獨立的

所以通常不會有寫入同一塊記憶體的問題

但是對於像硬碟這種外部設備的存取

就有可能出現資源爭奪的情況

Process A

Process B

資源 1

互斥鎖

最簡單的解法就是引入一個鎖

Process A

Process B

資源 1

把資源給了一個 process 之後就鎖住

不讓其他 process 取用直到資源釋放

稱為互斥鎖(mutual exclusion,mutex)

死結

並且都在等待對方釋出剩下的資源

Process A

Process B

資源 1

有可能雙方都僅獲得部份資源

資源 2

需要

需要

給了

給了

哲學家用餐問題

有五個哲學家(process)

跟五支叉子(資源)

哲學家要左右手各拿一支叉子

才可以吃義大利麵(執行)

哲學家用餐問題

模擬用程式碼

from threading import Semaphore, Thread
import time
from random import random

class Philosopher:
    def __init__(self, speed, phil_id, left_fork, right_fork):
        self.thread = Thread(target=self.routine)
        self.speed = speed
        self.phil_id = phil_id
        self.left_fork = left_fork
        self.right_fork = right_fork

    def think(self):
        print(f'Philosopher {self.phil_id} is thinking.')
        time.sleep(random() / self.speed)

    def eat(self):
        print(f'Philosopher {self.phil_id} is eating.')
        time.sleep(random() / self.speed)

    def pick_up(self, fork_side):
        if fork_side == 'left':
            self.left_fork.acquire()
        else:
            self.right_fork.acquire()
        print(f'Philosopher {self.phil_id} picked up the fork on his {fork_side}.')

    def put_down(self, fork_side):
        if fork_side == 'left':
            self.left_fork.release()
        else:
            self.right_fork.release()
        print(f'Philosopher {self.phil_id} put down the fork on his {fork_side}.')

    def dine(self):
        self.thread.start()

    def routine(self):
        while True:
            self.think()
            self.pick_up('left')
            self.pick_up('right')
            self.eat()
            self.put_down('left')
            self.put_down('right')

def main():
    N = 5
    SPEED = 10000
    forks = [Semaphore() for _ in range(N)]
    phils = [Philosopher(SPEED, i, forks[i], forks[(i+1) % N]) for i in range(N)]
    for phil in phils:
        phil.dine()

if __name__ == '__main__':
    main()

最直覺的寫法

所有人都拿起左邊的叉子

while True:
    think()
    pick_up(left)
    pick_up(right)
    eat()
    put_down(left)
    put_down(right)

沒人吃得了義大利麵

解法

規定拿不到右邊的叉子

while True:
    think()
    pick_up_both()
    eat()
    put_down(left)
    put_down(right)

就把左邊的叉子放下

死結與作業系統

現代作業系統有成千上萬個互斥鎖

它們都有機會變成死結

如果死結太嚴重,那就叫當機

此時最有效的解法叫做重開機

本日課程結束

Operating System

By Justin Tsai

Operating System

  • 176