Astroの光線のサムネイル。

pubDate: 2024-05-30

author: sakakibara

astro

公開学習

後退

コミュニティ

コンテキストスイッチ

スレッドの切替時にはコンテキストスイッチが発生する。
コンテキストスイッチとは、 CPUのレジスタに含まれる情報 (プログラムカウンタ、スタックポインタ、汎用レジスタ)を メモリの特定の場所 に保存し、後で復元すること(機構)である。

コンテキストスイッチは2つの手段に分かれる。

  1. コンテキストの保存
  1. コンテキストの復元

コントロールブロック

コントロールブロックとはOSがプロセスやスレッドの状態を管理するために使用するデータ構造である。 コントロールブロックは以下の2つに分けられる。

PCBに保存される内容としては

TCBに保存される内容としては

Terminal window
+------------------+
| プロセスID |
| プロセス状態 |
| プログラムカウンタ|
| レジスタ内容 |
| メモリ管理情報 |
| 入出力状態情報 |
| スケジューリング|
| アカウント情報 |
+------------------+
+------------------+
| スレッドID |
| スレッド状態 |
| プログラムカウンタ|
| レジスタ内容 |
| スタックポインタ|
| スケジューリング|
+------------------+

サブルーチンとコルーチン

サブルーチンと関数は同じ意味で、特定のタスクを実行するための自己完結型のコードブロックである。

対して、コルーチンは、サブルーチンと異なり複数のエントリポイントを持ち、実行状態を保持して再開することのできるプログラム構造である。 制御ポイントを移動さえるにはyieldを使う。

サブルーチンとコルーチンの違いとしては

コルーチンはどうやって関数の状態を保存しているのか? コルーチンが実行されると、その状態をコンテキストとしてメモリに保存する。

  1. ローカル変数と実行状態をメモリに保存する。
  1. スタックの管理
  1. Generatorとしての実装

スレッドの状態は(TCBなどにして)OSが管理し、コルーチンの状態はユーザーが管理する。 このため、コルーチンは軽量スレッドと言われる。

シグナル

killなどはプロセスに対してシグナルを送信する。 シグナルとはプロセスに対してOSが通知するためのメッセージ・メカニズムである。 シグナルはプロセスに何か特定の状況が起こったことを発生したことを通知するために使用される。 割り込み、エラー、プロセスの終了などが該当する。 プロセスはシグナルを受信した際にどのようにシグナルをハンドリングするか、どのような関数を定義して応答するかを決めることができる。 たとえば、プロセスの終了、無視、停止、コアダンプなどである。

スレッドのデータ共有

スレッドは

これを深く理解するために複数スレッドのプロセスのメモリ配置を見てみる。 プロセスのメモリ配置は以下のようになっている。

メモリアドレス 領域名 説明
0x00000000~0x3fffffff Kernel OSがプロセスを管理するための領域, TCBやPCBなどもここ。
0x40000000~0x7fffffff コード プログラムのコード
0x80000000~0xbfffffff ヒープ プログラム実行中に動的に確保されるメモリ. static変数もここ。
0xc0000000~0xfbffffff スタック 0xc00000~0xc0ffffはスレッド1用
0xc10000~0xc1ffffはスレッド2用
0xc20000~0xc2ffffはスレッド2用
0xc30000~0xfbffffffは新しいスレッド用
0xfc000000~0xffffffff IO ペリフェラルIO(メモリマップドIO)レジスタ

ここからすぐにわかることだが、スレッドは他のスレッドのスタック領域に(危ないが)アクセスできるし、IO領域にもアクセスできる。 もちろんスレッド毎に異なる値を持つ変数にアクセスすることができる。スレッド毎に異なる値をスレッドローカルな変数と呼ぶ。

なお、通常は共通のデータはヒープ領域に配置することになる。 これいがいもマナーが悪いがグローバル変数を使用する場合もある。

次はグローバル変数を使用する例である。

#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int varA; // GLOBAL variable
void *ThreadFunc(void *arg){
int n = (int)arg;
int varB;
varB = 4*n;
printf("ThreadFunc-%d-1: var-A=%d, varB=%d\n", n, varA, varB);
varA = 5*n;
printf("ThreadFunc-%d-2: var-A=%d, varB=%d\n", n, varA, varB);
sleep(2);
printf("ThreadFunc-%d-3: var-A=%d, varB=%d\n", n, varA, varB);
varB = 6*n;
printf("ThreadFunc-%d-4: var-A=%d, varB=%d\n", n, varA, varB);
return NULL;
}
int main(void){
pthread_t thread1, thread2;
int varB;
varA = 1; varB = 2;
printf("main-1: varA=%d, varB=%d\n", varA, varB);
pthread_create(&thread1, NULL, ThreadFunc, (void *)1);
sleep(1);
varB = 3;
printf("main-2: varA=%d, varB=%d\n", varA, varB);
pthread_create(&thread2, NULL, ThreadFunc, (void *)2);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
printf("main-3: varA=%d, varB=%d\n", varA, varB);
return 0;
}

次はヒープ領域に入れてグローバルで渡す例である。

#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
char *varA;
#define STRINGSIZE 32
void *ThreadFunc(void *arg){
int n = (int)arg;
snprintf(varA, STRINGSIZE, "Hello I'm No.%d", n);
printf("Thread-%d: let varA=%s \n", n, varA);
sleep(2);
printf("Thread-%d: After 2 secs. let varA=%s \n", n, varA);
return NULL;
}
int main(void){
pthread_t thread1, thread2;
int varB;
varA = (char *)malloc(STRINGSIZE);
strcpy(varA, "Good morning");
printf("main-1: varA=%s \n", varA);
pthread_create(&thread1, NULL, ThreadFunc, (void *)1);
sleep(1);
printf("main-2: varA=%s \n", varA);
pthread_create(&thread2, NULL, ThreadFunc, (void *)2);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
printf("main-3: varA=%s \n", varA);
return 0;
}

次はデータをポインタとして渡す方法である。

#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
typedef struct {
int id;
char *message;
} thread_data;
void *ThreadFunc(void *arg) {
thread_data *data = (thread_data *)arg;
// データを使用
printf("ThreadFunc: data %d\n", data->id);
data->id += 3;
}
int main() {
pthread_t thread;
thread_data data = {1, "Hello"};
pthread_create(&thread, NULL, ThreadFunc, (void *)&data);
sleep(3);
printf("main: data %d\n", data.id);
pthread_join(thread, NULL);
return 0;
}