---
title: みかん本輪読会_13
tags: 
author: [こた](https://www.docswell.com/user/kota)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/4EZ161ML73.jpg?width=480
description: みかん本輪読会_13 by こた
published: August 17, 25
canonical: https://www.docswell.com/s/kota/5374WY-mikan-13
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/4EZ161ML73.jpg)

みかん本輪読会
13章
マルチタスク(1)


# Page. 2

![Page Image](https://bcdn.docswell.com/page/Y76L2LNMJV.jpg)

マルチタスクとは？
p.310,311
- 複数のプロセスを「同時に」動作させる
- 同時に動作してるように見せかけることもある
- 並列処理
→ 1つのプロセスを分割しそれぞれを別のプロセッサで動作させる
- 並行処理
→ 複数のプロセスを1つのプロセッサ上で同時に動作させる処理


# Page. 3

![Page Image](https://bcdn.docswell.com/page/G751215QE4.jpg)

マルチタスクの例
p.310
- 音楽を再生しつつ、ブラウザで調べ物をしながら、エディタ
を開いてプログラミングをする
- USB機器やタイマからのイベントを1つずつ処理しつつ、
マウスの移動、キー入力、カーソルの点滅
→ どちらも同じマルチタスクだが性質が違う
前者は自己完結型、後者は一極集中型
特に後者では処理に時間がかかると他の処理が遅れる


# Page. 4

![Page Image](https://bcdn.docswell.com/page/9J2141NW7R.jpg)

コンテキスト
- 実行に必要なデータや変数をまとめたもの
例) OS上で動くC言語のアプリ
- 実行バイナリ
- コマンドライン引数
- 環境変数
- スタックメモリ
- 各レジスタの値
p.310~311


# Page. 5

![Page Image](https://bcdn.docswell.com/page/DEYZMZP97M.jpg)

自己完結型と一極集中型の違い
p.311
- 音楽を再生しつつ、ブラウザで調べ物をしながら、エ
ディタを開いてプログラミングをする
- USB機器やタイマからのイベントを1つずつ処理しつ
つ、マウスの移動、キー入力、カーソルの点滅
→ 前者はアプリケーションごとにコンテキストを持つ
後者は1つのコンテキストが複数のタスクをこなす
→ 別々のコンテキストを持つタスクを同時実行したい


# Page. 6

![Page Image](https://bcdn.docswell.com/page/VJN3W35DJ8.jpg)

コンテキストの切り替えに挑戦
p.311
- コンテキストの切り替えのことをコンテキストスイッチと
呼ぶ
- コンテキストの1つはメイン関数を、もう1つはTaskB()関
数を実行するようなコンテキストとする
- 2つのコンテキストを交互に切り替えて実行する


# Page. 7

![Page Image](https://bcdn.docswell.com/page/YE99X9Y8E3.jpg)

詳説コンテキストスイッチ
p.311
- CPUはメイン関数を実行中
- RIPレジスタは現在実行中の命令の次の命令を指す
→ メイン関数内のどこか
- RSPレジスタはスタックの先頭を指す
→ kernel_main_stackのどこか
- 汎用レジスタ(RAXなど)を使って次々にメイン関数内の
命令が実行されていく


# Page. 8

![Page Image](https://bcdn.docswell.com/page/GE89296Z7D.jpg)



# Page. 9

![Page Image](https://bcdn.docswell.com/page/LELW2W91JR.jpg)

詳説コンテキストスイッチ
p.312
- TaskB()のコンテキストに切り替える処理が必要
- RIPをTaskB()の先頭アドレスに書き換える
- TaskB()の2つの引数をRDI、RSIに設定
→ これだけだとTaskB()にスイッチした後、メイン関数に
帰ってこれない
- PIRの値の保存やRSPの値の復元など必要だが後述


# Page. 10

![Page Image](https://bcdn.docswell.com/page/4JM989Z5EW.jpg)

コンテキストスイッチの本質
p.312
- CPUはあくまでもレジスタの値を参照して機械語を1つ
ずつ実行しているだけ
→ RIPが指すメモリを読みRIPを更新、機械語を実行
- 単純なのでレジスタの値をうまく変更すればコンテキス
トを切り替えられる
→ これこそがコンテキストスイッチの本質


# Page. 11

![Page Image](https://bcdn.docswell.com/page/PJRG5G6ZE9.jpg)

コンテキストスイッチの構造
p.313
- リスト13.2はコンテキストを保存するための構造体
- コンテキストを切り替えるときに値の保存と復帰が必要
リスト 13.2
なレジスタをすべて含んでいる
- メイン関数内で本来保存
されるべきレジスタ値が
変更されるかもしれない
→ 呼び出し前後で値の保存


# Page. 12

![Page Image](https://bcdn.docswell.com/page/PEXXKXM1EX.jpg)

TaskB 関数
p.313
- TaskB関数は一旦起動されると無限ループに入る
- 普通の無限ループと違いSwitchContext()を呼ぶ
- SwitchContext()はコンテキストを切り替える関数
- task_b_ctx → task_a_ctxに切り替える
- レジスタを操作して、コンテキストを切り替える
- しかし、C++ではTaskB()を実行中にメイン関数に実行
が移るのは想定していないため処理が必要


# Page. 13

![Page Image](https://bcdn.docswell.com/page/3EKW5WGMED.jpg)

TaskB コンテキスト
p.314
- TaskBのコンテキスト用に適当なサイズのスタック
リスト 13.5
- コンテキスト領域全体を0で初期化
- 設定した値はSwitchCo
ntext()の中で利用される
- task_b_ctx.ripにはTask
Bの先頭アドレスが入る


# Page. 14

![Page Image](https://bcdn.docswell.com/page/L731K1N2J5.jpg)

コンテキストスイッチの実装
p.316~317
- リスト13.7はSwitchContext()のソースコード
- この関数の主目的はコンテキストの保存と復帰
- 現在実行中のコンテキストを第2引数(RSI)が指すメモ
リ領域に保存し、第1引数が指すメモリ領域からCPU
のレジスタ郡を復帰する
- CR3、セグメントレジスタ、RBP、RBX、R12~R15を除
く汎用レジスタは保存と復帰が不要


# Page. 15

![Page Image](https://bcdn.docswell.com/page/87DX3XW6EG.jpg)

SwitchContext() の構造
p.318~319
- コンテキスト構造体に保存した値は次にそのコンテキス
トを復帰するときにレジスタに書き戻される
- メイン関数はcall命令によってSwitchContext()を呼び
出す
- call命令はスタックに戻り先アドレスを積んでから対象
の関数にジャンプする命令
- RSPが指すスタックにはcall命令の次の命令のアドレス
が記録されている


# Page. 16

![Page Image](https://bcdn.docswell.com/page/VJPP4PXZJ8.jpg)

コンテキストスイッチの自動化
p.321
- 2つのタスクがSwitchContext()を互いに呼び合うことで
切り替えを行うことを協調的マルチタスク
- しかしSwitchContext()が呼び出されないと破綻
- すべてのアプリにSwitchContext()があれば良いが…
- プリエンプティブマルチタスクを実現する
- CPUの割り込み処理(通常はタイマ)を使って強制的にタ
スクを切り替える


# Page. 17

![Page Image](https://bcdn.docswell.com/page/2EV2X2LMEQ.jpg)

プリエンプティブマルチタスクへの切り替え
- TaskB()とメイン関数からSwitchContext()を削除
- 即座に切り替える必要がなくなったのでhlt命令に
- タイマは20ms周期で1秒間に50回の切り替え
- リスト13.13はタイマによる割り込みハンドラの実装
- SwitchTask()で次に切り替えるべきタスクを決定
- 現在実行中のタスクをcurrent_task変数に格納
- taskaとtaskbでcurrent_taskに残っていない方を実行


# Page. 18

![Page Image](https://bcdn.docswell.com/page/57GRVRXXEL.jpg)

タスクを増やす
p.326~327
- 今の状態でタスクを増やそうと思うとtask_c_ctxのよ
うに増やさないとダメ
- これでは不便なので好きなだけ増やせるように
- 1つのタスクを表すのにはスタック領域とコンテキスト
構造体で足りそう → Taskクラスを作る
- タスクは固有のIDとスタック領域、コンテキスト構造
体を持つ


# Page. 19

![Page Image](https://bcdn.docswell.com/page/4EQV6V85JP.jpg)

タスクを増やす
p.328
- タスク用のコンテキスト構造に初期値を設定する関数
InitContext()を実装(リスト13.18)
- この関数で設定した値はこのタスクが最初に
SwithContext()によって起動されたときに読み取り
- 複数タスクを管理するTaskManagerクラスの実装
- SwitchTask()メソッドのよって現在実行中のタスクとそ
の次のタスクを取得し、次のタスクが持つコンテキスト
へと実行を切り替える


