OSの4.xの課題

OSの講義の4.xの問題を解く

これが正解だぜ(どや)みたいなノリでは書いてないです、ただの平均的な技術力を持っているB2が解いているだけ

課題の取り組みをブログで書いてもいいという許可をもらった上で記事を書いてます

最新の課題内容であることや、答えが正しいかどうかは保証しません

このブログの情報で読者に何らかの不都合が生じても著者は責任は取らないです


4.xについて

4.2から4.5はkono先生からメールで各自ランダムな数字を与えられるので、その数字に該当する教科書の問題(Exercisesにある)を解くといったもの

こんな風にメールで問題が与えられる、一人一人が違う問題を与えられる

該当する番号の問題がなかったら好きなの解いていい(って質問チャンネルで書いてた)、ただ簡単すぎるのを選ぶのは...ね?

Your OS assignments for Paperback are
Chapter 11 ex 11.15
Chapter 6 ex 6.17
Chapter 12 ex 12.5
Chapter 21 ex 21.15

で、この講義ページの通りに、4.2から4.5までの課題としてこれらの問題を解く


4.1 シミュレータ

このページの問題を解く

level 1 task list を作る

コード

use std::collections::VecDeque;

// Define the `Task` struct, representing a task with a name and the number of clock cycles it consumes.
struct Task {
    name: String,
    clock_cycles: u32,
}

fn main() {
    // Create a queue for tasks. VecDeque allows efficient popping and pushing operations.
    let mut task_queue: VecDeque<Task> = VecDeque::new();
}

level 2 task list をファイルから読み込む、あるいは乱数で生成する

コード

use rand::Rng;
use std::collections::VecDeque; // Add this to use the random number generator

// Define the `Task` struct, representing a task with a name and the number of clock cycles it consumes.
struct Task {
    name: String,
    clock_cycles: u32,
}

fn main() {
    // Create a queue for tasks. VecDeque allows efficient popping and pushing operations.
    let mut task_queue: VecDeque<Task> = VecDeque::new();

    // Generate tasks with random clock cycles and add them to the queue
    for i in 0..10 {
        let task = Task {
            name: format!("Task_{}", i),
            clock_cycles: rand::thread_rng().gen_range(1..101), // Random number between 1 and 100
        };
        task_queue.push_back(task);
    }

    // Print each task in the queue
    for task in task_queue {
        println!("{}: {} clock cycles", task.name, task.clock_cycles);
    }
}

実行結果

$ cargo run .
    Finished dev [unoptimized + debuginfo] target(s) in 0.04s
     Running `target/debug/rust-scheduling-simulator .`
Task_0: 46 clock cycles
Task_1: 45 clock cycles
Task_2: 12 clock cycles
Task_3: 61 clock cycles
Task_4: 18 clock cycles
Task_5: 42 clock cycles
Task_6: 54 clock cycles
Task_7: 42 clock cycles
Task_8: 81 clock cycles
Task_9: 51 clock cycles

level 3 FIFO スケジューラシミュレータを書く

コード

use rand::Rng;
use std::collections::VecDeque;
use std::thread;
use std::time::Duration;

// Define the `Task` struct, representing a task with a name and the number of clock cycles it consumes.
struct Task {
    name: String,
    clock_cycles: u32,
}

// Define the `task_exec` function, which processes tasks in FIFO order
fn task_exec(mut task_queue: VecDeque<Task>) {
    while let Some(task) = task_queue.pop_front() {
        // Simulate clock cycle consumption with sleep
        thread::sleep(Duration::from_millis(task.clock_cycles.into()));
    }
}

fn main() {
    // Create a queue for tasks. VecDeque allows efficient popping and pushing operations.
    let mut task_queue: VecDeque<Task> = VecDeque::new();

    // Generate tasks with random clock cycles and add them to the queue
    for i in 0..10 {
        let task = Task {
            name: format!("Task_{}", i),
            clock_cycles: rand::thread_rng().gen_range(1..101), // Random number between 1 and 100
        };
        task_queue.push_back(task);
    }

    task_exec(task_queue);
}

level 4 結果を表示する

コード

use rand::Rng;
use std::collections::VecDeque;
use std::thread;
use std::time::Duration;

// Define the `Task` struct, representing a task with a name and the number of clock cycles it consumes.
struct Task {
    name: String,
    clock_cycles: u32,
}

// Define the `task_exec` function, which processes tasks in FIFO order
fn task_exec(mut task_queue: VecDeque<Task>) {
    while let Some(task) = task_queue.pop_front() {
        println!(
            "Executing {}: {} clock cycles",
            task.name, task.clock_cycles
        );
        // Simulate clock cycle consumption with sleep
        thread::sleep(Duration::from_millis(task.clock_cycles.into()));
    }
}

fn main() {
    // Create a queue for tasks. VecDeque allows efficient popping and pushing operations.
    let mut task_queue: VecDeque<Task> = VecDeque::new();

    // Generate tasks with random clock cycles and add them to the queue
    for i in 0..10 {
        let task = Task {
            name: format!("Task_{}", i),
            clock_cycles: rand::thread_rng().gen_range(1..101), // Random number between 1 and 100
        };
        task_queue.push_back(task);
    }

    task_exec(task_queue);
}

実行結果

$ cargo run .
    Finished dev [unoptimized + debuginfo] target(s) in 0.04s
     Running `target/debug/rust-scheduling-simulator .`
Executing Task_0: 9 clock cycles
Executing Task_1: 37 clock cycles
Executing Task_2: 67 clock cycles
Executing Task_3: 47 clock cycles
Executing Task_4: 32 clock cycles
Executing Task_5: 89 clock cycles
Executing Task_6: 73 clock cycles
Executing Task_7: 74 clock cycles
Executing Task_8: 77 clock cycles
Executing Task_9: 83 clock cycles

level 5 sfj / round robin などを書く

「Abstract factory pattern を使って、scheduling algorithm を切り替える」という指示もある

コード

use rand::Rng;
use std::cmp::Reverse;
use std::collections::BinaryHeap;
use std::collections::VecDeque;
use std::thread;
use std::time::Duration;

// Define the `Task` struct, representing a task with a name and the number of clock cycles it consumes.
#[derive(Eq, PartialEq, Clone)]
struct Task {
    name: String,
    clock_cycles: u32,
}

// Implement Ord and PartialOrd to use Task in a BinaryHeap (for SJF scheduling).
impl Ord for Task {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.clock_cycles.cmp(&other.clock_cycles)
    }
}

impl PartialOrd for Task {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

// Define a trait for scheduling algorithms
trait Scheduler {
    fn push(&mut self, task: Task);
    fn pop(&mut self) -> Option<Task>;
}

// FIFO Scheduler
struct FIFOScheduler(VecDeque<Task>);

impl Scheduler for FIFOScheduler {
    fn push(&mut self, task: Task) {
        self.0.push_back(task);
    }

    fn pop(&mut self) -> Option<Task> {
        self.0.pop_front()
    }
}

// SJF Scheduler
struct SJFScheduler(BinaryHeap<Reverse<Task>>);

impl Scheduler for SJFScheduler {
    fn push(&mut self, task: Task) {
        self.0.push(Reverse(task));
    }

    fn pop(&mut self) -> Option<Task> {
        self.0.pop().map(|reverse| reverse.0)
    }
}

// Round Robin Scheduler
struct RRScheduler {
    queue: VecDeque<Task>,
    quantum: u32,
}

impl Scheduler for RRScheduler {
    fn push(&mut self, task: Task) {
        self.queue.push_back(task);
    }

    fn pop(&mut self) -> Option<Task> {
        if let Some(mut task) = self.queue.pop_front() {
            if task.clock_cycles > self.quantum {
                task.clock_cycles -= self.quantum;
                self.queue.push_back(task.clone());
                Some(Task {
                    name: task.name,
                    clock_cycles: self.quantum,
                })
            } else {
                Some(task)
            }
        } else {
            None
        }
    }
}

fn task_exec<S: Scheduler>(mut scheduler: S) {
    while let Some(task) = scheduler.pop() {
        println!(
            "Executing {}: {} clock cycles",
            task.name, task.clock_cycles
        );
        // Simulate clock cycle consumption with sleep
        thread::sleep(Duration::from_millis(task.clock_cycles.into()));
    }
}

fn main() {
    // Create tasks. VecDeque allows efficient popping and pushing operations.
    let tasks: Vec<Task> = (0..10)
        .map(|i| {
            Task {
                name: format!("Task_{}", i),
                clock_cycles: rand::thread_rng().gen_range(1..101), // Random number between 1 and 100
            }
        })
        .collect();

    println!("FIFO Scheduler:");
    let fifo_scheduler = FIFOScheduler(VecDeque::from(tasks.clone()));
    task_exec(fifo_scheduler);

    println!("\nSJF Scheduler:");
    let sjf_scheduler = SJFScheduler(tasks.iter().cloned().map(Reverse).collect());
    task_exec(sjf_scheduler);

    println!("\nRound Robin Scheduler:");
    let rr_scheduler = RRScheduler {
        queue: VecDeque::from(tasks),
        quantum: 20,
    };
    task_exec(rr_scheduler);
}

実行結果

$ cargo run .
   Compiling rust-scheduling-simulator v0.1.0 (/Users/yoshisaur/workspace/lectures/operating-system/uryukyu-lecture-OS/4.x/4.1/rust-scheduling-simulator)
    Finished dev [unoptimized + debuginfo] target(s) in 0.46s
     Running `target/debug/rust-scheduling-simulator .`
FIFO Scheduler:
Executing Task_0: 44 clock cycles
Executing Task_1: 36 clock cycles
Executing Task_2: 38 clock cycles
Executing Task_3: 84 clock cycles
Executing Task_4: 7 clock cycles
Executing Task_5: 40 clock cycles
Executing Task_6: 1 clock cycles
Executing Task_7: 73 clock cycles
Executing Task_8: 21 clock cycles
Executing Task_9: 61 clock cycles

SJF Scheduler:
Executing Task_6: 1 clock cycles
Executing Task_4: 7 clock cycles
Executing Task_8: 21 clock cycles
Executing Task_1: 36 clock cycles
Executing Task_2: 38 clock cycles
Executing Task_5: 40 clock cycles
Executing Task_0: 44 clock cycles
Executing Task_9: 61 clock cycles
Executing Task_7: 73 clock cycles
Executing Task_3: 84 clock cycles

Round Robin Scheduler:
Executing Task_0: 20 clock cycles
Executing Task_1: 20 clock cycles
Executing Task_2: 20 clock cycles
Executing Task_3: 20 clock cycles
Executing Task_4: 7 clock cycles
Executing Task_5: 20 clock cycles
Executing Task_6: 1 clock cycles
Executing Task_7: 20 clock cycles
Executing Task_8: 20 clock cycles
Executing Task_9: 20 clock cycles
Executing Task_0: 20 clock cycles
Executing Task_1: 16 clock cycles
Executing Task_2: 18 clock cycles
Executing Task_3: 20 clock cycles
Executing Task_5: 20 clock cycles
Executing Task_7: 20 clock cycles
Executing Task_8: 1 clock cycles
Executing Task_9: 20 clock cycles
Executing Task_0: 4 clock cycles
Executing Task_3: 20 clock cycles
Executing Task_7: 20 clock cycles
Executing Task_9: 20 clock cycles
Executing Task_3: 20 clock cycles
Executing Task_7: 13 clock cycles
Executing Task_9: 1 clock cycles
Executing Task_3: 4 clock cycles

level 7 Gannt Chart を書く、複数のタスクのスケジューリングを表す時間を横軸としたグラフを描く

コード

実行結果

出力したグラフはここにある。

level 8 Periodical task

Ratemonotonic なスケジューラを書く

コード

実行結果

RMS Scheduler:
Executing Task_2: 49 clock cycles, period: 113
Executing Task_0: 53 clock cycles, period: 131
Executing Task_7: 2 clock cycles, period: 144
Executing Task_4: 65 clock cycles, period: 145
Executing Task_8: 65 clock cycles, period: 149
Executing Task_5: 41 clock cycles, period: 167
Executing Task_3: 47 clock cycles, period: 177
Executing Task_9: 83 clock cycles, period: 178
Executing Task_6: 57 clock cycles, period: 190
Executing Task_1: 84 clock cycles, period: 200

level 9 scheduling rust thread、thread 自体を schedule する方法を考案し、rust に実装せよ

前提として、スレッドのスケジューリングはOSの役割でRustの標準APIではそのスケジューリングを操作することはできない。Mutex, Condvarを用いてスレッドを間接的にスケジュールすることはできる。

コード

実行結果

$ cargo run .
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/rust-scheduling-simulator-level9 .`
FIFO Scheduler:
Executing Task_1: 71 clock cycles
Executing Task_2: 70 clock cycles
Executing Task_3: 34 clock cycles
Executing Task_4: 58 clock cycles
Executing Task_5: 67 clock cycles

SJF Scheduler:
Executing Task_3: 34 clock cycles
Executing Task_4: 58 clock cycles
Executing Task_5: 67 clock cycles
Executing Task_2: 70 clock cycles
Executing Task_1: 71 clock cycles

Round Robin Scheduler:
Executing Task_1: 20 clock cycles
Executing Task_2: 20 clock cycles
Executing Task_3: 20 clock cycles
Executing Task_4: 20 clock cycles
Executing Task_5: 20 clock cycles
Executing Task_1: 20 clock cycles
Executing Task_2: 20 clock cycles
Executing Task_3: 14 clock cycles
Executing Task_4: 20 clock cycles
Executing Task_5: 20 clock cycles
Executing Task_1: 20 clock cycles
Executing Task_2: 20 clock cycles
Executing Task_4: 18 clock cycles
Executing Task_5: 20 clock cycles
Executing Task_1: 11 clock cycles
Executing Task_2: 10 clock cycles
Executing Task_5: 7 clock cycles

4.2 教科書の問題 Chapter 7 ex 7.7

メールでは11.15と書かれていたがChapter 11に問題がなかった

7章(Interprocess Communication)から問題を選ぶ

7.7が面白そう

教科書の7.7の問題を解く、問題文は以下の通り、

    In this example the character names bear similarity with the Gauls. Gauls have a chief evaluator of art. His name is Evalutrix and his job is to evaluate every piece of art which is brought in the Gaul land. He has advisors whose names are Moodix, Chancix, and Gamblix. When a piece of art is brought up to Evalutrix he contacts his advisors indicating the likely base price. The three advisors use the following systems to indicate their evaluations.  


    a. Chancix always tosses a coin. If it is heads he increases the evaluation by 5 per cent. If it is tails, he reduces the evaluation by 5 per cent.

    b. Gamblix shuffles a pack of cards and draws a card from it. The face value of the card determines the percentage change he would suggest. If the card is a one from the black suit, he increases the evaluation. If it is from the red suit, then he decreases the evaluation.

    c. Moodix always takes a look at the real-time clock on his system. He adds up the digits in hours, and minutes and subtracts 15 from it. Whatever the result is, that percentage change in value he communicates to Evalutrix.


Evalutrix averages all the values including his own base price and declares the result as the value of the art. Simulate Evalutrix as the parent process, and his advisors as child processes with the base price being given as the inline argument.

和訳

この例では、登場人物名がガリア人のと似ている。ガリア人には芸術品の最高評価者が存在し、その名をEvalutrixという。彼には、Moodix、Chancix、Gamblixという名のアドバイサーがいる。美術品がEvalutrixに持ち込まれると、彼はアドバイサー3人に連絡して、その芸術品の基本価格を提示する。3人のアドバイサーは、以下のような方法で評価をする。

a. Chancixはコインを投げて、表が出れば5%上げる、裏が出れば5%下げる。

b. Gamblixはカードのパックをシャッフルして、そこからカードを1枚引き黒のスートならば評価を上げて、赤のスートならば評価を下げる。カードの番号が提示する変化の割合(%)を決定する。

c. Moodixは、時計を見る。時計の「時」と「分」の数字を足し合わせて、15を引く。その値の変化の割合(%)をEvalutrixに伝える。

Evalutrixは、自分の基本価格とa, b, cの提示した値の平均をとって、その結果を芸術品の価値として、宣言する。

Evalutrixを親プロセスと、アドバイサー達を基本価格をインライン引数とする子プロセスとしてシミュレートしなさい。

解く

システムコール(fork)を使いプロセスを生成して、パイプ(pipe)によるプロセス間通信をする

上記の操作をC言語で実装してEvalutrixを親プロセス、アドバイサー達を子プロセスとして扱い、芸術品の価値を決定するスクリプトを作成する

この問題では「単一の親プロセス」と「複数の子プロセス」とのプロセス間通信をするC言語スクリプトを書くことが求められている


答え(日本語)

Cのコードを書いた


#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define READ  (0)
#define WRITE (1)

#define CHANCIX (0)
#define GAMBLIX (1)
#define MOODIX (2)

#define HEADS (1)
#define TAILS (0)

#define BLACK_SUIT (1)
#define RED_SUIT (0)


int calculate_chancix_value(int basic_price) {

    srand((unsigned int)time(NULL));
    int flipped_coin = rand() % 2;
    int chancix_value = basic_price;

    if (flipped_coin == HEADS) {
        chancix_value += basic_price * 0.05;
    
    } else if (flipped_coin == TAILS) {
        chancix_value -= basic_price * 0.05;
    
    }

    printf("The price Chancix evaluates: %d\n", chancix_value);

    return chancix_value;


}

int calculate_gamblix_value(int basic_price) {

    srand((unsigned int)time(NULL));
    int card_suit = rand() % 2;
    int card_num = rand() % 13 + 1;
    int gamblix_value = basic_price;

    if (card_suit == BLACK_SUIT) {
        gamblix_value += basic_price * card_num / 100.0;
    
    } else if (card_suit == RED_SUIT) {
        gamblix_value -= basic_price * card_num / 100.0;
    
    }

    printf("The price Gamblix evaluates: %d\n", gamblix_value);

    return gamblix_value;


}

int calculate_moodix_value(int basic_price) {

    time_t seconds;
    struct tm *timeStruct;
    seconds = time(NULL);
    timeStruct = localtime(&seconds);

    int hours = timeStruct->tm_hour;
    int minutes = timeStruct->tm_min;
    int moodix_value = basic_price;

    // add the hours and minutes in Japan time, subtract by 15, and devide by 100
    moodix_value += basic_price * ((hours + 9) % 24 + minutes - 15) / 100.0;

    printf("The price Moodix evaluates: %d\n", moodix_value);

    return moodix_value;


}


int main(int argc, char *argv[]) {

    const int CHILD_PROCESS_NUM = 3;

    pid_t process_ids[CHILD_PROCESS_NUM];

    char pipe_input[256], pipe_output[256];

    int parent2child_pipes[CHILD_PROCESS_NUM][2];
    int child2parent_pipes[CHILD_PROCESS_NUM][2];

    int p_i, close_i;

    int parent2child_pipe_status, child2parent_pipe_status;

    int basic_price, evaluated_price, price_sum, price_average;

    // prompt user input to set the base price
    printf("The basic price is ");
    fgets(pipe_input, 256, stdin);
    printf("The price Evalutrix indicates: %s", pipe_input);

    price_sum = atoi(pipe_input);


    for (p_i = 0; p_i < CHILD_PROCESS_NUM; ++p_i) {

       parent2child_pipe_status = pipe(parent2child_pipes[p_i]);
       child2parent_pipe_status = pipe(child2parent_pipes[p_i]);

       // if either a parent2child pipe or a child2parent pipe fails to open
       if (parent2child_pipe_status < 0 || child2parent_pipe_status < 0) {

           // close all proccesses' exisiting pipes
           for (close_i = 0; close_i < p_i; ++close_i) {

               close(parent2child_pipes[close_i][READ]);
               close(parent2child_pipes[close_i][WRITE]);
               close(child2parent_pipes[close_i][READ]);
               close(child2parent_pipes[close_i][WRITE]);

           
           }

           // if a child2parent pipe fails to open
           if (child2parent_pipe_status < 0) {

               // close the most recently opened parent2child_pipe
               close(parent2child_pipes[p_i][READ]);
               close(parent2child_pipes[p_i][WRITE]);

       
           }

           return 1;

       
       }

    
    }


    for (p_i = 0; p_i < CHILD_PROCESS_NUM; ++p_i) {

        process_ids[p_i] = fork();

        // if a child process fails to be generated
    if (process_ids[p_i] < 0) {

            // close all processes' pipes
        for (close_i = 0; close_i < CHILD_PROCESS_NUM; ++close_i) {

                close(parent2child_pipes[close_i][READ]);
                close(parent2child_pipes[close_i][WRITE]);
                close(child2parent_pipes[close_i][READ]);
                close(child2parent_pipes[close_i][WRITE]);

            
        }

            return 1;

        // if the current process is a child process
        
    } else if (process_ids[p_i] == 0) {

            close(parent2child_pipes[p_i][WRITE]);
            close(child2parent_pipes[p_i][READ]);

            read(parent2child_pipes[p_i][READ], pipe_input, 256);
            basic_price = atoi(pipe_input);

            evaluated_price = basic_price;

        if (p_i == CHANCIX) {
               // do what Chancix does
               evaluated_price = calculate_chancix_value(basic_price);
            
        } else if (p_i == GAMBLIX) {
               // do what Gamlix does
               evaluated_price = calculate_gamblix_value(basic_price);
            
        } else if (p_i == MOODIX) {
               // do what Moodix does
               evaluated_price = calculate_moodix_value(basic_price);
            
        }

            sprintf(pipe_input, "%d", evaluated_price);

            write(child2parent_pipes[p_i][WRITE], pipe_input, strlen(pipe_input) + 1);

            close(parent2child_pipes[p_i][READ]);
            close(child2parent_pipes[p_i][WRITE]);

            exit(0);

        // if the current process is the parent process
    
    } else if (process_ids[p_i] > 0) {

            close(parent2child_pipes[p_i][READ]);
            close(child2parent_pipes[p_i][WRITE]);

            write(parent2child_pipes[p_i][WRITE], pipe_input, strlen(pipe_input) + 1);

            read(child2parent_pipes[p_i][READ], pipe_output, 256);

            price_sum += atoi(pipe_output);

            close(parent2child_pipes[p_i][WRITE]);
            close(child2parent_pipes[p_i][READ]);

    
    }

    
    }


    price_average = price_sum / (CHILD_PROCESS_NUM + 1);
    printf("The price to declare is %d\n", price_average);
    

}

パイプについて

  • プロセス同士でデータをやり取りするためにはパイプを作成する必要がある
  • 1本のパイプで一方向(親->子、子->親のいずれか)のデータの送信が可能になる
    • つまり、今回の問題のように双方向でデータのやりとりを行うためにはプロセス間で2本のパイプ(親->子、子->親の両方)が必要になる
  • コードの104行目から135行目までがパイプを作成する部分である
    • 途中でパイプの作成に失敗したら、今まで作成したパイプを閉じる必要がある
  • 親->子のパイプを作成したときに、パイプの読み出し用と書き込み用の2つのディスクリプタが作られる
    • 親プロセスでは親->子のパイプに対して書き込むだけで読み込みはしないので、191行目のように親プロセスの処理の記述の前に読み込み部分を閉じさせている
    • 子プロセスでは親->子のパイプに対して読み込むだけで書き込みはしないので、160行目のように子プロセスの処理の記述の前に書き込み部分を閉じさせている
  • 子->親のパイプを作成したときに、パイプの読み出し用と書き込み用の2つのディスクリプタが作られる
    • 子プロセスでは子->親のパイプに対して書き込むだけで読み込みはしないので、161行目のように子プロセスの処理の記述の前に読み込み部分を閉じさせている
    • 親プロセスでは子->親のパイプに対して読み込むだけで書き込みはしないので、192行目のように親プロセスの処理の記述の前に書き込み部分を閉じさせている
  • 以下が説明したプロセス間通信の図である
    • Pは親プロセス、Cは子プロセスを示す
    • 矢印は一方向のパイプを示す
    • RはREADのディスクリプタ、WはWRITEのディスクリプタを示す
      • 赤の斜線が引かれているものは処理の始めに閉じさせる
      • 青の丸で囲われているものは処理の最後に閉じさせる

プロセス間通信の図


フォークについて

  • パイプを作成したあとはプロセスをフォークして親プロセスと子プロセスに分ける
  • プロセスのidを使ってif文の分岐で親プロセスと子プロセスの処理を記述する
    • 子プロセスの処理の最後に186行目のようにexit(0)を加えて子プロセスを正常に終了させる必要がある

シュミレートする

実行結果(4:01a.m.に実行)

The basic price is 10000
The price Evalutrix indicates: 10000
The price Chancix evaluates: 9500
The price Gamblix evaluates: 9700
The price Moodix evaluates: 9000
The price to declare is 9550

実行結果(4:02a.m.に実行)

The basic price is 10000
The price Evalutrix indicates: 10000
The price Chancix evaluates: 10500
The price Gamblix evaluates: 11000
The price Moodix evaluates: 9100
The price to declare is 10150

実行結果(4:03a.m.に実行)

The basic price is 10000
The price Evalutrix indicates: 10000
The price Chancix evaluates: 10500
The price Gamblix evaluates: 10500
The price Moodix evaluates: 9200
The price to declare is 10050

すべての値が想定する範囲内で値が変動していることを確認した


4.2の感想

  • めっちゃ面白い
  • C言語楽しい
  • プロセス間通信って面白い!

4.3 教科書の問題 Chapter 6 ex 6.8

メールでは6.17と書いてたが問題が存在しなかったので、6章の中の面白そうな問題を選んだ

教科書の6.8の問題を解く、問題文は以下の通り、

Dining philosophers problem:

    This is a very famous and often quoted problem in OS research. It reflects considerable degree of concurrency in operation. It also requires synchronisation of actions. We will assume that the philosophers are all Japanese and love eating the Japanese rice (Gohan in Japanese). Five philosophers sit around a circular table. Each philosopher spends his life alternatively thinking and eating. In the centre of the table is a bowl of rice and there is a plate in front of each philosopher. Clearly, philosophers would need two chopsticks to have a helping of rice. The philosophers have five chopsticks which are placed between their plates. So each plate has a chopstick to its right and to its left. The philosophers only use the chopsticks to their immediate right and left.

    How would the philosophers need to schedule their thinking and eating?

(Hint: Think of each chopstick as a semaphore.)

和訳

dining philosophers問題:

    OSの研究において非常に有名でよく引用される問題である。哲学者はみんな日本のご飯を食べるのが大好きな日本人で、全員で5人が円卓を囲んでいると想定する。哲学者はそれぞれ全員、考えることと食べることを交互に繰り返して暮らしている。テーブルの中心にはご飯が盛られたボウルがあり、哲学者一人一人の前に皿が置かれている。ご飯を食べるには箸が2本(1膳)必要である。哲学者達は合わせて5本の箸を持ち、それぞれの皿の間に置いている。つまり、皿の右側と左側に1本の箸があるということを意味する。哲学者はすぐ右と左の箸しか使えない。

    哲学者たちは、どのように思考と食事のスケジュールを立てるべきか?

答え(日本語)

ナイーブな実装のデッドロック

  • まずはDining Philosophers 問題を愚直に実装しデッドロックさせる
    • 全員、左の箸を取ってから右の箸を取って思考と食事をする
    • 全員が左の箸を持ったまま右の箸を待ち続ける状態になればデッドロック
use rand::Rng;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

const PHILOSOPHERS_COUNT: usize = 5;

fn philosopher(
    id: usize,
    left_chopstick: Arc<Mutex<()>>,
    right_chopstick: Arc<Mutex<()>>,
    right_chopstick_counter: Arc<Mutex<usize>>,
) {
    let mut rng = rand::thread_rng();
    loop {
        println!("Philosopher {} is thinking", id);
        {
            let _left_guard = left_chopstick.lock().unwrap();
            println!("Philosopher {} picked up the left chopstick.", id);

            {
                let mut counter = right_chopstick_counter.lock().unwrap();
                *counter += 1;
            }

            println!(
                "Philosopher {} is trying to pick up the right chopstick.",
                id
            );
            let _right_guard = right_chopstick.lock().unwrap();
            println!("Philosopher {} picked up the right chopstick.", id);

            {
                let mut counter = right_chopstick_counter.lock().unwrap();
                *counter -= 1;
            }

            let eat_duration = rng.gen_range(0..10000);
            println!("Philosopher {} is eating.", id);
            thread::sleep(Duration::from_millis(eat_duration));

            println!("Philosopher {} dropped the right chopstick.", id);
            println!("Philosopher {} dropped the left chopstick.", id);
        }
    }
}

fn deadlock_detector(counter: Arc<Mutex<usize>>) {
    loop {
        thread::sleep(Duration::from_millis(10));
        let c = {
            let count = counter.lock().unwrap();
            *count
        };
        if c == PHILOSOPHERS_COUNT {
            println!("DEADLOCK DETECTED!");
            break;
        }
    }
}

fn main() {
    let chopsticks: Vec<_> = (0..PHILOSOPHERS_COUNT)
        .map(|_| Arc::new(Mutex::new(())))
        .collect();
    let right_chopstick_counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for i in 0..PHILOSOPHERS_COUNT {
        let left_chopstick = chopsticks[i].clone();
        let right_chopstick = chopsticks[(i + 1) % PHILOSOPHERS_COUNT].clone();
        let counter_clone = right_chopstick_counter.clone();

        handles.push(thread::spawn(move || {
            philosopher(i, left_chopstick, right_chopstick, counter_clone)
        }));
    }

    let detector_handle = thread::spawn({
        let counter_clone = right_chopstick_counter.clone();
        move || deadlock_detector(counter_clone)
    });

    for handle in handles {
        handle.join().unwrap();
    }

    detector_handle.join().unwrap();
}

実行時のログ

$ cargo run
   Compiling dining-philosophers-fail v0.1.0 (/Users/yoshisaur/workspace/lectures/operating-system/uryukyu-lecture-OS/4.x/4.3/dining-philo
sophers-fail)
    Finished dev [unoptimized + debuginfo] target(s) in 0.42s
     Running `target/debug/dining-philosophers-fail`
Philosopher 4 is thinking
Philosopher 4 picked up the left chopstick.
Philosopher 4 is trying to pick up the right chopstick.
Philosopher 4 picked up the right chopstick.
Philosopher 2 is thinking
Philosopher 2 picked up the left chopstick.
Philosopher 2 is trying to pick up the right chopstick.
Philosopher 3 is thinking
Philosopher 1 is thinking
Philosopher 2 picked up the right chopstick.
Philosopher 1 picked up the left chopstick.
Philosopher 1 is trying to pick up the right chopstick.
Philosopher 0 is thinking
Philosopher 2 is eating.
Philosopher 4 is eating.
Philosopher 2 dropped the right chopstick.
Philosopher 2 dropped the left chopstick.
Philosopher 2 is thinking
Philosopher 2 picked up the left chopstick.
Philosopher 2 is trying to pick up the right chopstick.
Philosopher 2 picked up the right chopstick.
Philosopher 2 is eating.
Philosopher 4 dropped the right chopstick.
Philosopher 4 dropped the left chopstick.
Philosopher 4 is thinking
Philosopher 4 picked up the left chopstick.
Philosopher 4 is trying to pick up the right chopstick.
Philosopher 4 picked up the right chopstick.
Philosopher 4 is eating.
Philosopher 2 dropped the right chopstick.
Philosopher 2 dropped the left chopstick.
Philosopher 2 is thinking
Philosopher 2 picked up the left chopstick.
Philosopher 3 picked up the left chopstick.
Philosopher 3 is trying to pick up the right chopstick.
Philosopher 2 is trying to pick up the right chopstick.
Philosopher 4 dropped the right chopstick.
Philosopher 4 dropped the left chopstick.
Philosopher 4 is thinking
Philosopher 4 picked up the left chopstick.
Philosopher 0 picked up the left chopstick.
Philosopher 0 is trying to pick up the right chopstick.
Philosopher 4 is trying to pick up the right chopstick.
DEADLOCK DETECTED!

最後の哲学者だけ箸を取る順番を変える実装

  • デッドロックにならないためには、すべての哲学者が同時に右の箸を取り上げようとしないようにすることが重要
    • 最後の哲学者だけが逆の順序(左の箸を先に取り上げる)で箸を取り上げるようにすれば、すべての哲学者が同時に右の箸を取り上げようとしなくなるので、デッドロックにならない
use rand::Rng;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

const PHILOSOPHERS_COUNT: usize = 5;

fn philosopher(id: usize, left_chopstick: Arc<Mutex<()>>, right_chopstick: Arc<Mutex<()>>) {
    let mut rng = rand::thread_rng();
    loop {
        println!("Philosopher {} is thinking", id);

        if id == PHILOSOPHERS_COUNT - 1 {
            let _left_guard = left_chopstick.lock().unwrap();
            println!("Philosopher {} picked up the left chopstick.", id);

            let _right_guard = right_chopstick.lock().unwrap();
            println!("Philosopher {} picked up the right chopstick.", id);
        } else {
            let _right_guard = right_chopstick.lock().unwrap();
            println!("Philosopher {} picked up the right chopstick.", id);

            let _left_guard = left_chopstick.lock().unwrap();
            println!("Philosopher {} picked up the left chopstick.", id);
        }

        let eat_duration = rng.gen_range(0..10000);
        println!("Philosopher {} is eating.", id);
        thread::sleep(Duration::from_millis(eat_duration));

        println!("Philosopher {} dropped the right chopstick.", id);
        println!("Philosopher {} dropped the left chopstick.", id);
    }
}

fn main() {
    let chopsticks: Vec<_> = (0..PHILOSOPHERS_COUNT)
        .map(|_| Arc::new(Mutex::new(())))
        .collect();
    let mut handles = vec![];

    for i in 0..PHILOSOPHERS_COUNT {
        let left_chopstick = chopsticks[i].clone();
        let right_chopstick = chopsticks[(i + 1) % PHILOSOPHERS_COUNT].clone();

        handles.push(thread::spawn(move || {
            philosopher(i, left_chopstick, right_chopstick)
        }));
    }

    for handle in handles {
        handle.join().unwrap();
    }
}

実行時ログ

Philosopher 0 is thinking
Philosopher 0 picked up the right chopstick.
Philosopher 0 picked up the left chopstick.
Philosopher 3 is thinking
Philosopher 3 picked up the right chopstick.
Philosopher 3 picked up the left chopstick.
Philosopher 4 is thinking
Philosopher 1 is thinking
Philosopher 1 picked up the right chopstick.
Philosopher 1 picked up the left chopstick.
Philosopher 2 is thinking
Philosopher 2 picked up the right chopstick.
Philosopher 2 picked up the left chopstick.
Philosopher 4 picked up the left chopstick.
Philosopher 4 picked up the right chopstick.
Philosopher 3 is eating.
Philosopher 1 is eating.
Philosopher 2 is eating.
Philosopher 4 is eating.
Philosopher 0 is eating.
Philosopher 4 dropped the right chopstick.
Philosopher 4 dropped the left chopstick.
Philosopher 4 is thinking
Philosopher 4 picked up the left chopstick.
Philosopher 4 picked up the right chopstick.
Philosopher 4 is eating.
Philosopher 0 dropped the right chopstick.
Philosopher 0 dropped the left chopstick.
Philosopher 0 is thinking
Philosopher 0 picked up the right chopstick.
Philosopher 0 picked up the left chopstick.
Philosopher 0 is eating.
Philosopher 2 dropped the right chopstick.
Philosopher 2 dropped the left chopstick.
Philosopher 2 is thinking
Philosopher 2 picked up the right chopstick.
Philosopher 2 picked up the left chopstick.
Philosopher 2 is eating.
Philosopher 4 dropped the right chopstick.
Philosopher 4 dropped the left chopstick.
Philosopher 4 is thinking
Philosopher 4 picked up the left chopstick.
Philosopher 4 picked up the right chopstick.
Philosopher 4 is eating.
Philosopher 3 dropped the right chopstick.
Philosopher 3 dropped the left chopstick.
Philosopher 3 is thinking
Philosopher 3 picked up the right chopstick.
Philosopher 3 picked up the left chopstick.
Philosopher 3 is eating.
Philosopher 1 dropped the right chopstick.
Philosopher 1 dropped the left chopstick.
Philosopher 1 is thinking
Philosopher 1 picked up the right chopstick.
Philosopher 1 picked up the left chopstick.
Philosopher 1 is eating.
Philosopher 1 dropped the right chopstick.
Philosopher 1 dropped the left chopstick.
Philosopher 1 is thinking
Philosopher 1 picked up the right chopstick.
Philosopher 1 picked up the left chopstick.
Philosopher 1 is eating.
Philosopher 0 dropped the right chopstick.
Philosopher 0 dropped the left chopstick.
Philosopher 0 is thinking
Philosopher 0 picked up the right chopstick.
Philosopher 0 picked up the left chopstick.
Philosopher 0 is eating.
Philosopher 3 dropped the right chopstick.
Philosopher 3 dropped the left chopstick.
Philosopher 3 is thinking
Philosopher 3 picked up the right chopstick.
Philosopher 3 picked up the left chopstick.
Philosopher 3 is eating.
...(以後、永遠と哲学者が全員が思考と食事を繰り返す)

ソースコードこちら

4.3の感想

  • 意外と思考実験系の問題がOSの教科書に多いなという感じがした

4.4 教科書の問題 Chapter 12 ex 12.5

教科書の12.5の問題を解く、問題文は以下の通り

What is a basic tool? Give three examples of tools you use often.

和訳

basic toolとは何か? あなたがよく使うツールの例を3つ挙げよ。

答え(日本語)

basic toolとは、特に教科書の12章ではファイル/ディレクトリ操作、編集をするためのシェル、コマンドやエディタのことを指す

僕がよく使うツールは、zsh, nvim, tmuxである

  • zshは補完機能や拡張性に優れたコマンドシェルである
  • nvimはvimをforkしたオープンソースのエディタ
  • tmuxは端末を多重化するソフトウェア
    • ペインの移動をvim-likeにした
    • コピーモードの操作をvim-likeにした

各種の設定があるdotfiles


4.4の感想

  • zsh, nvim, tmuxいいよね

4.5 教科書の問題 Chapter 9 ex 9.13

メールでは21.15と書かれていたがChapter 21に問題がなかった

9章(Real-Time Operating Systems and Microkernels)から問題を選ぶ

教科書の9.13の問題(実は#4.2でといた教科書の7.7と繋がっている)を解く、問題文は以下の通り、

    Gauls whom we came across in the exercises on interprocess communication are back again here. GetAFix, the druid who makes the magic potion, uses herb concentrates for his magic potion. However in the preparation of the potion,4 some enzymes are used as catalysts. The concentration of enzymes affects the rate of reaction. Additionally, the volume of the herb concentrate and its temperature need to be regulated. Note that the total time for brewing is also critical. What kind of schedule would you propose to GetAFix? Will any one of the three scheduling methods, namely rate-monotonic, earliest deadline first or earliest laxity first, by itself be adequate?

和訳

    プロセス間通信の演習で出会ったガリア人が、ここでも登場する。魔法薬を作るドルイドである、GetAFixはハーブの濃縮液を使っている。しかし、魔法薬の調合には触媒としていくつかの酵素が使われている。酵素の濃度は、反応速度に影響を与える。さらに、ハーブの濃縮液の量や温度も調節する必要がある。また、醸造にかかる総時間も重要である。GetAFixにどのようなスケジュールを提案したら良いだろうか。rate-monotonic、earliest deadline first、earliest laxity firstの3つのスケジューリングを行う方法のうち、どれか1つだけでも十分になるだろうか?

スケジューリングを行う方法をまとめる

rate-monotonic

  • 特徴
    • 周期の短いタスクは優先度が高くなる
    • 固定優先度割り当て
    • preemptive

以下のタスクのスケジューリングを考える

Task Capacity(タスク実行時間) Period(周期)
T1 3 20
T2 2 5
T3 2 10

Capacityはタスクの実行時間のことである

Periodはタスクの周期のことである

Periodが一番短い順にタスクの優先度が高くなる、一番短いT2に注目する

T2のCapacityは2なのでタスクの実行に2の時間がかかる

T2のCapacity

T2のPeriodは5なので5の倍数の時間にタスクが開始される

T2のPeriod

次に2番目にPeriodが短いT3に注目する

T3が10の倍数の時間にタスクが開始されるはずだが、T2のほうが優先される、T2が終わった後にT3のタスクが実行される

T3

T1に注目する

T1が20の倍数の時間にタスクが開始されるはずだが、T2,T3のほうが優先される、T2,T3が終わった後にT1のタスクが実行される

T1

T1の実行中にT2のPeriodの倍数の時間に到達したため、Periodが短いタスクであるT2が優先して実行され、長い方のT1が中断される、このようにタイマや割り込みなどで自動的にタスクが入れ替わるスケジューリングをPreemptiveという

図の目盛りの最大値が20である理由はそれぞれのタスクのPeriod、20, 5, 10の最小公倍数であるから

上記の例では、与えられた3つのタスクをスケジューリングすることに成功している


スケジュール可能である条件

nをタスクの数、Tを{T1, T2, ..., Tn}のタスクの集合と考えて、CiをTiのCapacity、PiをTiのPeriodと定義して

Σ[i=1→n]Ci/Pi

という式を作る

これがn(21/n-1)以下である、つまり

Σ[i=1→n]Ci/Pi <= n(2^(1/n)-1)

であるならば、タスクすべてを実行期限(デッドライン)内に完了することができる、つまりスケジュール可能である


earliest deadline first

  • 特徴
    • 最も実行期限が近いタスクを優先して実行する
    • 動的に優先度が割り当てられる
    • non-preemptive

スケジュール可能である条件

nをタスクの数、Tを{T1, T2, ..., Tn}のタスクの集合と考えて、CiをTiのCapacity、PiをTiのPeriodと定義して

Σ[i=1→n]Ci/Pi <= 1ならばスケジュール可能である


earliest laxity first

  • 教科書にはearliest laxity firstが載っているページがこの問題ページにしか存在しない、調べてもLeast Laxity Firstしかでない

Least Laxity Firstが教科書の指すearliest laxity firstであるとして問題を解く

まず、laxity timeとはそのタスクを可能な限り速く実行するとしたときの実行期限までの余裕の時間である

  • 特徴
    • laxity timeが最小なタスクを優先して選択して実行する
    • 動的に優先度を割り当てられる
    • preemptive

スケジュール可能である条件

earliest deadline firstと同じで、nをタスクの数、Tを{T1, T2, ..., Tn}のタスクの集合と考えて、CiをTiのCapacity、PiをTiのPeriodと定義して

Σ[i=1→n]Ci/Pi <= 1ならばスケジュール可能である


答え(日本語)

提示されたrate-monotonic, earliest deadline first, earliest laxity firstの3つのスケジューリング手法の特徴を分類する

  • 優先度の割り当て
    • 静的
      • rate-monotonic
    • 動的
      • earliest deadline first
      • earliest laxity first
  • タスクの入れ替わり
    • preemptive
      • rate-monotonic
      • earliest laxity first
    • non-preemptive
      • earliest deadline first

問題文の「酵素の濃度は、反応速度に影響を与える」という記述の解釈によって、答え方が変わる

酵素自体の濃度か、または、調合に使った1種類の酵素と全種類の酵素の割合のことを指しているのか

もし、酵素自体の濃度が反応速度に影響を与えるのであれば、タスクの切り替わりに関してpreemptive、non-preemptiveどちらでも構わない、その中でも静的に優先度が割り当てられているrate-monotonicはタスクの管理がしやすいので、rate-monotonicが最適と思われる

もし、調合に使った1種類の酵素と全種類の酵素の割合が影響を与えるのであれば、non-preemptiveなearliest deadline firstの一択となる


4.5の感想

  • 結構難しかった
  • スケジューリング手法について勉強できて楽しかった