Linuxで出てくる「x86」「AMD」「ARM」「RISC-V」とは何か?

linuxとCPUアーキテクチャ

Linuxが人気である理由は、多数の異なる CPU アーキテクチャ上で実行されることがおそらく理由の 1 つです。 Linux カーネルには、汎用コードとドライバーに加えて、それぞれのCPUアーキテクチャ固有のコンポーネントが含まれています。これにより異なる仕様のCPU上でもシステムを起動させることが可能なのです。

CPU

Linuxは複数のコンピューターアーキテクチャ上で動作します。 (ここでのコンピューターアーキテクチャはCPUファミリアと同じ意味です。) 現在使用しているLinuxがどのCPUアーキテクチャであるかを把握するにはdmidecodeコマンドを使用します。 もし出力結果が得られない場合には、lscpuコマンドを使用するのがよいでしょう。

[root@ac93e82b6845 myworking]# lscpu
Architecture:            x86_64
  CPU op-mode(s):        32-bit, 64-bit
  Address sizes:         46 bits physical, 48 bits virtual
  Byte Order:            Little Endian
CPU(s):                  20
  On-line CPU(s) list:   0-19
Vendor ID:               GenuineIntel
  Model name:            12th Gen Intel(R) Core(TM) i7-12700
    CPU family:          6
    Model:               151
    Thread(s) per core:  2
    Core(s) per socket:  10
    Socket(s):           1
    Stepping:            2
    BogoMIPS:            4224.00
    Flags:               fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse ss
                         e2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology tsc_reliable nonstop
                         _tsc cpuid pni pclmulqdq vmx ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline
                         _timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single ssbd ibrs
                         ibpb stibp ibrs_enhanced tpr_shadow vnmi ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi
                         2 erms invpcid rdseed adx smap clflushopt clwb sha_ni xsaveopt xsavec xgetbv1 xsaves avx_vnni u
                         mip waitpkg gfni vaes vpclmulqdq rdpid movdiri movdir64b fsrm serialize flush_l1d arch_capabili
                         ties
Virtualization features:
  Virtualization:        VT-x
  Hypervisor vendor:     Microsoft
  Virtualization type:   full
Caches (sum of all):
  L1d:                   480 KiB (10 instances)
  L1i:                   320 KiB (10 instances)
  L2:                    12.5 MiB (10 instances)
  L3:                    25 MiB (1 instance)
Vulnerabilities:
  Itlb multihit:         Not affected
  L1tf:                  Not affected
  Mds:                   Not affected
  Meltdown:              Not affected
  Mmio stale data:       Not affected
  Retbleed:              Mitigation; Enhanced IBRS
  Spec store bypass:     Mitigation; Speculative Store Bypass disabled via prctl and seccomp
  Spectre v1:            Mitigation; usercopy/swapgs barriers and __user pointer sanitization
  Spectre v2:            Mitigation; Enhanced IBRS, IBPB conditional, RSB filling, PBRSB-eIBRS SW sequence
  Srbds:                 Not affected
  Tsx async abort:       Not affected

lscpuコマンドの実行結果、

  • cpuファミリアはx86_64
  • CPU数は20
  • CPUのモデルはIntel 12世代

さらにコンパクトな情報はuanme -aで確認できます。

[root@ac93e82b6845 myworking]# uname -a
Linux ac93e82b6845 5.15.90.1-microsoft-standard-WSL2 #1 SMP Fri Jan 27 02:56:13 UTC 2023 x86_64 GNU/Linux

CPUファミリア一覧

x86 CPUファミリア

x86アーキテクチャIntelが開発した命令セットファミリでのちにAMDに提供されます。

  • Intel 64bit cpuカーネル内部ではx64またはx86_64と表記されています。
  • Intel-32bitカーネル内部ではx86と表記されます。
  • AMD84カーネル内部ではamd64と表記されます。

アウトオブオーダー実行への依存とエネルギー効率の悪さがデメリットとしてあげられますが、 x86はいま最も使用されているCPUファミリアです。

アウト・オブ・オーダー実行(アウト・オブ・オーダーじっこう、英: out-of-order execution)とは、高性能プロセッサにおいてクロックあたりの命令実行数(IPC値)を増やし性能を上げるための手法の1つで、機械語プログラム中の命令の並び順に依らず、データなどの依存関係から見て処理可能な命令について逐次開始・実行・完了させるものである。頭文字で'OoO'あるいは'O-o-O'とも書かれる。「順序を守らない実行」の意である。

逆に逐次実行はイン・オーダー実行と呼ばれる。

from https://ja.wikipedia.org/wiki/%E3%82%A2%E3%82%A6%E3%83%88%E3%83%BB%E3%82%AA%E3%83%96%E3%83%BB%E3%82%AA%E3%83%BC%E3%83%80%E3%83%BC%E5%AE%9F%E8%A1%8C

x86 CPUファミリアに対する命令はx86アセンブラとして定義されており、 x86アーキテクチャは8個の汎用レジスタ (GPR) と6個のセグメントレジスタ、1個のフラグレジスタ、1個の命令ポインタを持っている。

  • RAX/EAX/AX/AL/AH : 「アキュムレータレジスタ」と呼ばれる。算術演算操作の結果が格納される。
  • RCX/ECX/CX/CL/CH : 「カウンタレジスタ」と呼ばれる。シフトローテート命令とループ命令に使用される。
  • RDX/EDX/DX/DL/DH : 「データレジスタ」と呼ばれる。算術演算操作とI/O操作に使用される。
  • RBX/EBX/BX/BL/BH : 「ベースレジスタ」と呼ばれる。セグメントモードでのDS(後述)に格納されたデータを指し示すために使用される。
  • RSP/ESP/SP : 「スタックポインターレジスタ」と呼ばれる。スタックのトップを指し示すポインタ。
  • RBP/EBP/BP : 「スタックベースポインタレジスタ」と呼ばれる。スタックのベースを指し示すのに使用される。
  • RSI/ESI/SI : 「ソースレジスタ」と呼ばれる。ストリーム操作コマンド(たとえば MOV命令)でのソース(入力元)へのポインタとして使用される。
  • RDI/EDI/DI : 「デスティネーションレジスタ」と呼ばれる。ストリーム操作コマンドでの転送先(←英語でデスティネーションという. 出力先のようなもの)へのポインタとして使用される。

ARM CPUファミリア

ARMはAcorn社から開発され、当初から消費電力を最小限に抑えることに重点を置いてます。 したがって、iphone,androidなどの多くの携帯機器にARMベースのチップが搭載されているようです。 そのほかにはRaspberry PiのようなIoT組み込みデバイスにも組み込まれており、AWSのデータセンターでもARMベースのCPUの採用が増えてきています。

高速かつ安価ですが、X86同様脆弱性があります。

RISC-Vファミリア

カルフォルニア大学バークレー校が開発したオープンなRISC規格のCPUです。 Nvidiaを含めた多くの実装が存在します。

比較的新規のCPUであるため広くは普及しておりません。

コマンドで目視する

procとcpu情報

今後、procという言葉がたくさん出てくる。 これはprocessの略であり、これが出てきたら、プロセスやCPUに関連したキーワードであると認識したほうが良い。

cpu情報を確認する

具体例:/proc/cpuinfo

まずはcpuの情報が書かれたファイルを見る。

less /proc/cpuinfo
  • processor

    • :CPUの単位。8コア存在するのであれば、0~7まである。
    • nprocコマンドと同じ
    • 例)
  • model name:CPUの名前。

  • flags:cpuが持つ機能。

    • 例)fpu : 小数点も含めた計算機能。
    • 例2)spectre_v1: 一昨年あたりにあったCPUの脆弱性に関する機能。

cpu情報を確認する手段2

lscpuコマンドでもcpuの情報を確認することが可能。

lscpu
  • サンプル実行結果
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
Address sizes: 40 bits physical, 48 bits virtual
CPU(s): 4
On-line CPU(s) list: 0-3
Thread(s) per core: 1
Core(s) per socket: 4
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 60
Model name: Intel Core Processor (Haswell, no TSX, IBRS)
Stepping: 1
  • CPUアーキテクチャは:x86_64
  • 利用可能なCPUは:4
  • CPUのモデル名はIntel Core Processor

nprocコマンド

CPUの数が得られる。(coreutilsに含まれる機能)

grep -c processor /proc/cpuinfoと同じ結果が得られる。

色々なスクリプトで使われている

CPUの使用状況を調べる

topコマンド

CPUに関する情報が出てくる。

システムが高負荷になるときにどのプログラムが動いているかどうかがわかる。

  • id:アイドリング。ここの値が高ければ高いほど、何もしていないCPUの数が多い。
  • us:ユーザー単位のCPU使用率

topを押した後、1を押してみる

CPUのプロセッサーごとの詳細画面に移る。

ループするプログラムを見てみる

#!/bin/sh

while true
do
  sleep 1
  date
done

後処理、loopコマンドの終了

killall loop

プロセッサーを指定してプログラムを実行する

tasksetを指定することで、cpuのプロセッサー番号を指定して動かすことができる

taskset -c 3 ./loop &

このプログラムを実行した後、topコマンドを実行する。

CPUの数を減らす

結論:システムドライバファイルを消せば良い。

  • sys/devices/system/cpu/cpu<プロセッサー名>
ll sys/devices/system/cpu/

cpuが大量に見えるはず。 今回はcpu7を選択

cat /sys/devices/system/cpu/cpu7/online

これを実行することで、1か0かが表示される。

  • 1:cpu7は有効
  • 0:cpu0は無効

逆に、このファイルに1や0を記述することで、CPUの数を増減させることができる

例)

  • echo 0 > /sys/devices/system/cpu/cpu7/online

注意!:cpu0はオフラインにできないようになっている。

Linuxのターミナルで使用できるショートカットキー一覧

そもそもターミナルとは?

テキストベースのLinuxユーザーインターフェースを提供するプログラムです。

現在はアプリとして使用され、xterm、rxvt、gnomeなどがあります。

ターミナルのショートカットキー

ターミナルは基本的な文字入力と出力に加えてエスケープシーケンスやエスケープコードをサポートし、 例えば Ctrl+Hでバックスペースと同じ意味になります。

  • Ctrl+u カーソル位置から行頭まで削除 (重要)
  • Ctrl+w カーソル位置から後側にある一単語分を削除 (重要)
  • Ctrl+a カーソル位置を行頭へ移動 (重要)
  • Ctrl+e カーソル位置を行末へ移動 (重要)

  • alt+b カーソル1つ戻る

  • alt+f カーソル一つ進む

ターミナルの情報を出力する

infocmp
infocmp

マルチターミナル管理

linuxmacユーザーの中には複数のターミナルを操作したいという方もいると思います。 その場合、マルチターミナルブレクスtmuxが使用可能です。

まずはtmuxをインストールしましょう。 使用しているパッケージマネージャーでtmuxをインストールしてください。

pacman -S tmux --noconfirm
yum install tmux
brew install tmux

tmuxの用語集

tmuxには三つのキーワードがあります。

  • セッション
    • 特定のタスクに特化した作業環境のこと。tmuxにおけるもっとも大きな単位
  • ウィンドウ
    • セッションに所属するものでブラウザにおけるタブみたいなもの。多くの場合は1セッションに1ウィンドウ
  • ペイン
    • 単一のシェルインスタンスのこと。ウィンドウの中に複数のペインを起動させることができ、縦や横に分割することも可能。

tmuxの起動

インストールができたらtmuxを入力しましょう。 新しいセッションが出来上がるはずです。

あるいは-sオプションで新しいセッションに名前を付けながら起動することもできます。

tmux new -s test

tmuxの操作

tmuxの操作をする場合、Ctrl + bに続けてコマンドを入力します。 これはプレフィックス、あるいはトリガーとも呼ばれ、tmuxのコマンドを入力するサインともいえます。

例えば、Ctrl + bsですべてのセッションを確認することができます。

覚えておくべきtmuxの操作

tmuxプレフィックス値は省略します。(ctrl + bが事前に入力されている前提です。)

  • % : ペインを左右に分割する。
  • " : ペインを上下に分割する
  • o : ペインを移動する。

推しの話

推しの話

この子です。

  • このペンギンは「Tux」(タックス)君です。
  • 技術者の方は嫌というほど見たことがあると思いますが「Linux」というOSのロゴになってます。
  • Linuxカーネルがバージョン2になる時に、ロゴのデザインコンテストが行われ、あのペンギンが選ばれたわけです。

from https://ja.wikipedia.org/wiki/%E3%82%BF%E3%83%83%E3%82%AF%E3%82%B9

少し話が変わって...

個人の趣味でLinux kernelを勉強しております。

ただ個人の範囲だとどうしてもモチベーションに限界があるので 一緒にLinuxのコミュニティを作ってくれる人探してます!

もしいたら声かけてほしいです!

Linuxワイワイ

https://disboard.org/ja/server/1102933966660583545

プロセスディスクリプタとは何か?【Linux kernelソース読解】

まずは双方向リストについて解説する。 Linuxkernel内部ではlist_headというデータ構造が用意されており、これが様々な場面で使用される。 このデータ構造はnextprevの二つのメンバを持ち、データ構造に前後の概念を導入したいときに使用される。

双方向リスト
双方向リスト

list_headの定義

list_headは以下のように定義されている。

struct list_head {
    struct list_head *next, *prev;
};

list_headの作成

list_headの作成にはLIST_HEADマクロを使用する。

#define LIST_HEAD_INIT(name) { &(name), &(name) }

#define LIST_HEAD(name) \
   struct list_head name = LIST_HEAD_INIT(name)

from https://github.com/torvalds/linux/blob/dad9774deaf1cf8e8f7483310dfb2690310193d2/scripts/kconfig/list.h#LL29C1-L32C46

このマクロを使用する際にはLIST_HEAD(list_name)と宣言することで新しいlist_headを作成することができる

list_headの操作

list_headを操作するためにいくつかの関数が用意されている。

  • list_add(n, p): pが指す要素の直後に、nが指す要素を挿入する
  • list_add_tail(n, p): pが指す要素の直前に、nが指す要素を挿入する
  • list_del(p): pが指す要素を削除する
  • list_for_each(p): リストの先頭(h)で指定したリストの各要素を操作する。

プロセスリスト

存在するすべてのプロセスディスクリプタをリンクするためにも、list_headは使われています。

struct task_struct {
    ...
    struct list_head       tasks;
    ...
}

from https://github.com/torvalds/linux/blob/dad9774deaf1cf8e8f7483310dfb2690310193d2/include/linux/sched.h#LL864C1-L866C26

それぞれのtask_struct構造体は上記の通り、list_headがたのtasksメンバを持ち、このtasksメンバのprevメンバがひとつ前のtask_struct要素を指し、nextメンバがひとつ後のtask_struct要素を指しています。

プロセスリストの先頭には常にtask_structがたのinit_taskが格納されており、init_taskはいわゆるプロセス0、つまりswapperのプロセスディスクリプタとなります。

プロセスの親子関係

  • プログラムから生成されたプロセスには親子関係があります。
  • プロセスが複数のプロセスを生成するとき、プロセス間には兄弟関係が構築されます。

こうした兄弟/親子関係を表すメンバが、プロセスディスクリプタには存在します。

プロセスを生成したプロセスディスクリプタ

プロセスPを生成したプロセスディスクリプタreal_parentメンバに格納されます。 (本物の)と名前が付く通り、このプロセスは削除される可能性もあります。 その場合はプロセス1を示すようになります。

struct task_struct {
    ...
    /* Real parent process: */
    struct task_struct __rcu   *real_parent;
    ...
}

プロセスの現在の親

プロセスPの"現在の"親プロセスを示します。この値は通常はreal_pparentと同じですが、ほかのプロセスがptrace()システムコールを使用してプロセスを監視するときなど、異なる番号を示すケースもあります。

struct task_struct {
    ...
    /* Recipient of SIGCHLD, wait4() reports: */
    struct task_struct __rcu   *parent;
    ...
}

プロセスのすべての子/親戚

プロセスの現在の子供、親戚のデータはchildren,siblingにそれぞれ格納されます。

struct task_struct {
    ...
    /*
    * Children/sibling form the list of natural children:
    */
    struct list_head       children;
    struct list_head       sibling;
    ...
}

pidhashテーブル

ハッシュ法について

ハッシュ法とは「目次の作成,分類分け」であり、配列内の要素へのアクセスを高速化するアルゴリズムです。 例えば、kill()システムコールを処理するときなどに、PIDから対応するプロセスディスクリプタを必要とする場合があります。この処理の高速化のために、ハッシュ法が使われています。

ハッシュ法では以下の二つの要素が出現します。

例えば、10で割ったときのあまりをハッシュ値とするというハッシュ関数があるとき、 どんなに膨大な集合でも、必ず10種類のどれかに分類分けできます。

この図の場合、22ハッシュ値2であるため、位置2の場所に22が格納されます。 これをすべての集合に対して処理を行い、ハッシュテーブルという目次を作成するのが目的です

from https://www.youtube.com/watch?v=bLw4HocrZiA

PIDハッシュテーブル一覧

実際、Linuxkernelではプロセスディスクリプタの特定のために、4つのハッシュテーブルが導入されました(PID_TYPE_MAXは最近導入?)

これら4つのテーブルを活用することにより、PID,TGID(スレッドグループID),PGID(プロセスグループID),SIDの探索を高速化します。

enum pid_type
{
    PIDTYPE_PID,
    PIDTYPE_TGID,
    PIDTYPE_PGID,
    PIDTYPE_SID,
    PIDTYPE_MAX,
};

/*
 * What is struct pid?
 *
 * A struct pid is the kernel's internal notion of a process identifier.
 * It refers to individual tasks, process groups, and sessions.  While
 * there are processes attached to it the struct pid lives in a hash
 * table, so it and then the processes that it refers to can be found
 * quickly from the numeric pid value.  The attached processes may be
 * quickly accessed by following pointers from struct pid.
 *
 * Storing pid_t values in the kernel and referring to them later has a
 * problem.  The process originally with that pid may have exited and the
 * pid allocator wrapped, and another process could have come along
 * and been assigned that pid.
 *
 * Referring to user space processes by holding a reference to struct
 * task_struct has a problem.  When the user space process exits
 * the now useless task_struct is still kept.  A task_struct plus a
 * stack consumes around 10K of low kernel memory.  More precisely
 * this is THREAD_SIZE + sizeof(struct task_struct).  By comparison
 * a struct pid is about 64 bytes.
 *
 * Holding a reference to struct pid solves both of these problems.
 * It is small so holding a reference does not consume a lot of
 * resources, and since a new struct pid is allocated when the numeric pid
 * value is reused (when pids wrap around) we don't mistakenly refer to new
 * processes.
 */

コメント和訳

※構造体pidとは何ですか? * struct pid は、プロセス識別子のカーネルの内部概念です。 ※個々のタスク、プロセスグループ、セッションを指します。その間 * プロセスが接続されており、構造体 pid はハッシュ内に存在します * テーブルなので、それとそれが参照するプロセスを見つけることができます * 数値の pid 値から迅速に取得します。付属のプロセスは次のとおりです。 * struct pid からのポインターをたどることですぐにアクセスできます。 * * pid_t 値をカーネルに保存し、後で参照するには、 * 問題。もともとその pid を持っていたプロセスが終了した可能性があり、 * pid アロケータがラップされており、別のプロセスが来る可能性があります * そしてそのpidが割り当てられました。 * * 構造体への参照を保持することでユーザー空間のプロセスを参照 * task_struct に問題があります。ユーザー空間プロセスの終了時 * 役に立たなくなった task_struct はまだ保持されています。 task_struct と * スタックは約 10K の低カーネル メモリを消費します。より正確に * これは THREAD_SIZE + sizeof(struct task_struct) です。比較すると ※ struct pidは約64バイトです。 * * struct pid への参照を保持すると、これらの問題の両方が解決されます。 ※小さいのでリファレンスを保持してもあまり消費しません * リソース、および数値 pid が割り当てられたときに新しい構造体 pid が割り当てられるため * 値は再利用されます (pid がラップアラウンドするとき) 誤って新しいものを参照することはありません

PIDハッシュテーブル操作

検索

find_task_by_pid_type(type, nr)

pidがnrであるプロセスを、type型のハッシュテーブルから検索します。 - みつかった場合はディスクリプタへのポインタを返し - 見つからなかった場合はnullを返します

/*
 * Must be called under rcu_read_lock().
 */
struct task_struct *find_task_by_pid_ns(pid_t nr, struct pid_namespace *ns)
{
    RCU_LOCKDEP_WARN(!rcu_read_lock_held(),
             "find_task_by_pid_ns() needs rcu_read_lock() protection");
    return pid_task(find_pid_ns(nr, ns), PIDTYPE_PID);
}

追加

attach_pid(task, type, nr)

type型のpidハッシュテーブルのPID番号がnrの場所に、taskが指すプロセスディスクリプタを挿入します。

/*
 * attach_pid() must be called with the tasklist_lock write-held.
 */
void attach_pid(struct task_struct *task, enum pid_type type)
{
    struct pid *pid = *task_pid_ptr(task, type);
    hlist_add_head_rcu(&task->pid_links[type], &pid->tasks[type]);
}

pidを外すときはdetach_pid(task, type)を使用します。

Linuxカーネルのプロセスについて知りたかったことすべて【Linuxカーネルを読む準備】

Linuxカーネルのプロセス

ユーザープロセスについて

ユーザープロセスとは文字通り、ユーザーの操作が元となり発生するプロセスのことです。

カーネルスレッド

ユーザープロセス以外にも、カーネルスレッドと呼ばれる特別なプロセスがあります。

  • システムの起動時に生成され、シャットダウンされるまで動作する
  • ユーザとのやり取りはないため、端末デバイスがいらない
  • カーネルアドレス空間にて、カーネルモードで動作する
    • ex) プロセスIDが1のinitプロセスなど

Linuxカーネルのそのほかの役割

  • プロセスによるシステムコール
  • 例外信号の発生。(命令が無効な場合など、何かしら異常な状態の処理)
  • 周辺デバイスからCPUへの割り込み信号の発行。CPUの状態を注意するものや状態の変更などの事象を知らせるものであり、各割り込み信号あh、割り込みハンドラと呼ばれるカーネルプログラムによって処理されます。
  • カーネルスレッドの実行。

プロセスの切り替え

カーネルはプロセスの実行を停止する際、その時点でのプロセッサレジスタの内容をプロセスディスクリプタの中に退避します。 この機能により、すべてのUNIXカーネルは、再入可能(リエントラント)です。 これによりCPUは各プロセスを高速で切り替えながら処理を進めるので、実質的に同時に複数のプロセスをカーネルモードで実行することができます。

例えば

プロセスに代わってディスクからの読み取り要求を発行した後、カーネルはその仕事をディスクコントローラーに任せ、ほかのプロセスの実行を再開します。 デバイスが読み取りを完了すると、割り込みによってカーネルに通知します。

再入可能性(理エントラント)なシステムコールの実現方法

  • ローカル変数だけを変更し、グローバル変数を変更しないような関数を書くこと
  • カーネルグローバル変数に対してロック機能を使い、その関数を実行するプロセスがその時点で一つだけであることを保証します。

どんな時に複数のカーネル実行パスを切り替えるのか?

カーネル実行パスとは、システムコール、例外、割り込みを処理するためにカーネルによって実行される命令シーケンスのことです。

最も単純な場合、CPUは一つのカーネル実行パスを最初から最後まで逐次的に実行しますが、以下の事象が発生した場合はCPUは複数のカーネル実行パスを切り替えながら実行します。

  • A:ユーザーモードで実行中のプロセスによってシステムコールが発行されたが、カーネル実行パスではその要求を即座に完了できない場合。 この場合、新しい実行プロセスを選ぶためにスケジューラを起動します。
  • B:カーネル実行パスを実行中、CPUが例外を発生した場合。CPUは実行中のプロセスを一時停止し、対応する新たなプロセスを開始する。この処理が終了すると、最初の実行パスを再開できる。
  • C:割り込み許可されたカーネル実行パスを実行中に、ハードウェア割り込みが発生した場合。ハードウェアの処理を優先し、処理が完了すれば最初の処理に戻る。

参考書籍

メモリ内部の動き(プロセスアドレス空間

プロセスは基本的にはそれぞれの固有なアドレス空間において実行されます。

  • ユーザーモードプロセスは、固有のスタック、データ、コード領域を参照します。
  • カーネルモードプロセスは、カーネルのデータとコード領域を参照し、もう一つの固有スタックを使用します。

しかし、各プロセスが固有のアドレスう空間にアクセスしているかのように見えていても、固有のアドレス空間の一部が複数のプロセスによって共有されている場合もあります。

同時に複数のユーザーによって同じプログラム(エディタなど)が必要とされている場合、そのプログラムがメモリに読み込まれるのは一度だけです。

この種のアドレス空間の共有は、メモリの節約のためにカーネルが自動的に行います。

シグナル

UNIXシグナルは、プロセスに対する通知です。 シグナルは20種類ほど存在し、通常はSIGTERMのようなグローバル変数、マクロ名によって参照されます。

非同期と同期

シグナルは大きく分けて2つに大別できます。

  • 非同期通知

    ex) ユーザーは割り込みキーコード(Ctrl+C, Ctrl+Z)を入力することにより、割り込み信号SIGINTをフォアグラウンドプロセスに送ることができます。

  • 同期通知

    プロセスが無効なアドレスのメモリ位置にアクセスしようとした場合、カーネルはプロセスに対し、シグナルSIGSERGを送ります。

受信時の反応

一般的に、シグナル受信時のプロセスの対応は次のどちらかの選択肢が考えられます。

  • シグナルを無視する。(プログラムは通常は何もしないという選択をする)
  • 非同期的に特定の手続きをする(プログラムはシグナル受信時に事前に決めた処理をする)

プロセスがこの二つをどちらも指定していない場合。次の5つの対応が設定されます。

  • プロセスを終了する
  • 実行コンテキストとアドレス空間の内容をファイルに書き込み、プロセスを終了する(コアダンプともいう)
  • シグナルを無視する
  • プロセスを停止する
  • プロセスが停止していた場合、再開する

IPC資源(プロセス間通信)

IPCとは、プロセス間通信という意味である。

POSIXが定めるプロセス間通信は三つの種類があり、それぞれメリットデメリットがある。

  • メッセージキュー
  • メモリ共有
  • フォマ

メッセージキュー

いわゆるメッセージボックスのような動作を行う IPC です。 プロセス間で一意に識別できるメッセージキューに対してメッセージを書き込み、受け取りができます。

  • メッセージキューの作成
msgkey = ftok("msgq.key",'X');
  • メッセージキューのid取得
msqid = msgget(msgkey,IPC_CREAT|0666);
  • メッセージ受け取り
msgrcv(msqid,p,sizeof(p->mdata),0,IPC_NOWAIT)
  • メッセージの送信
msgsnd(msqid,p,sizeof(p->mdata),0);

from https://lightning-brains.blogspot.com/2019/08/unix-linux.html

サンプルコード(メッセージ受信)

#include <sys/types.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <unistd.h>
#define MSGBUFSIZ 256

int main(int argc, char** argv) {
    int msqid;
    key_t msgkey;
    struct msgbuf{
      long mtype;
      char mdata[MSGBUFSIZ];
    };
    struct msgbuf msgdata,*p;
    p = &msgdata;
    // メッセージキューの作成
    msgkey = ftok("msgq.key",'X');
    // メッセージキューの情報取得
    msqid = msgget(msgkey,IPC_CREAT|0666);
    // "Quit"メッセージを受け取るまでループさせる
    while(1) {
        // メッセージ受け取り
        if(msgrcv(msqid,p,sizeof(p->mdata),0,IPC_NOWAIT)<=0) {
          printf("No message\n");
          sleep(3);
          continue;
        }
        printf("message received from %ld\n%s\n",p->mtype,p->mdata);
        if(strncmp((char*)p->mdata, "Quit", (size_t)3)==0)
            break;
    }
    // Remove Message Queue
    msgctl(msqid, IPC_RMID, 0);
}

from https://software.fujitsu.com/jp/manual/manualfiles/M060041/B1WN7071/02Z200/mqdml07/mqdml163.htm

サンプルコード(メッセージ送信)

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#define MSGBUFSIZ 256

int main(int argc, char** argv) {
    int msqid;
    key_t msgkey;
    struct msgbuf{
        long mtype;
        char mdata[MSGBUFSIZ];
    };
    struct msgbuf msgdata,*p;
    // メッセージキューの作成
    msgkey = ftok(argv[1], 'X');
    // メッセージキューの取得 (Only get existing message queue.)
    msqid = msgget(msgkey, 0666);
    printf("QID = %d\n", msqid);
    if(msqid<0 -1="" fflush="" fgets="" nter="" p-="" p="&msgdata;" printf="" return="" stdin="">mdata,MSGBUFSIZ,stdin);
    p->mtype = getpid();
    // メッセージ送信
    msgsnd(msqid,p,sizeof(p->mdata),0);
}

メモリ共有

データをやり取りしたり共有する際に最も高速な方法。 プロセスはshmget()システムコールを発行することで、要求された大きさの新しい共有メモリの作成から始めます。 IPC資源の識別子を獲得した後は、プロセスはshmat()システムコールを呼び出します。 このシステムコールは、プロセスアドレス空間内の新しい領域の先頭アドレスを返します。 最後に、アドレス空間から共有メモリを開放したい場合、ぷろっすはshmdt()システムコールを呼び出します。

ちなみに、shmshare memoryの略

フォマ

英語の「semaphore」の本来の語義は「視覚による通信・信号」全般を指し、腕木通信や、それから派生した鉄道の腕木信号(や自動車の方向指示器)、手旗信号などが含まれる。

複数プロセスの同時アクセスを制御する機構を持っていますが、整数型のデータしか扱えないというのが難点。主な用途としては共有メモリの相互排他に使われます。

図書館での例

ある図書室に学習室が10部屋あり、それぞれの学習室は一度に1人の学生が使用するとする。争奪戦が始まらないよう、学生は受付カウンターで学習室の使用を申し込むことになっている。学習室を使用し終えたら、学生は受付所に立ち寄ってその学習室が空いたことを知らせなければならない。全ての学習室が埋まっている場合、学生は受付所で学習室が空くのを待つ。

受付カウンターの図書係はどの学習室が空いているかは把握しておらず、単に空いている部屋数のみを知っている。学生が申し込んできたとき、図書係は空き部屋数から1を引く。学生が学習室の利用を終えたとき、図書係は空き部屋数に1を加える。学習室の使用許可が与えられたとき、その部屋は必要なだけ使い続けることができる。学習室は事前に予約することはできない。

このシナリオでは、受付カウンターがセマフォ、学習室が資源、学生が実行単位に相当する。また、セマフォの初期値は10になっている。学生が学習室の使用を申し込んで許可されたとき、セマフォ値から1が引かれて9になる。次の学生のときには8、さらに次は7となる。1を引くとセマフォ値が負になるという状態で学生が申し込んだ場合、その学生は待たされる[2]。複数人の学生が待っているとき、彼らは待ち行列を形成するか、いずれかの学生が学習室の使用を終えて受付カウンターにやってきたときに空いた部屋を割り当てる。割り当て方式には、ラウンドロビン・スケジューリング方式等がある(セマフォが正しく制御されるよう実装されていれば、割り当て方式は妥当なものであればよい)。

親子プロセス

プロセスの作成と終了

新しいプロセスの生成と終了のために、それぞれfork(),_exec()システムコールを使用し、新しいプログラムを読み込むためにはexec()系のシステムコールを呼び出します。 プロセスは読み込まれたプログラムを含む、まったく新しいアドレス空間を使用して実行を再開します。

  • fork()を呼び出したプロセスを親プロセス、呼び出されたプロセスを子プロセスと呼びます。
  • _exit()システムコールはプロセスを終了させ、資源を解消し、親プロセスにSIGCHLDシグナルを送ります。
  • wait4()システムコールは自分の子プロセスの一つが終了するまで待つことができます。

ゾンビプロセス

ゾンビプロセスとは親プロセスが子プロセスをほっといてしまい、いつまで経っても終了できない子プロセスのことです。 そして厳密にいえば、親プロセスがfork()を実行した後はゾンビの状態であり、wait4()システムコールを行うまでゾンビのままです。wait4()を実行した後はゾンビプロセスの状態を解消することができます。ゾンビプロセスとは、親プロセスが子プロセスの状態を管理できていない状態といえます。

Unixファイル管理関連概要説明【Linuxカーネルを読む準備】

Unixファイル管理関連

ファイルの種類

Unixで使用するファイルはいずれかの種類になる。

from https://qiita.com/angel_p_57/items/1faafa275525469788b4#%E3%83%91%E3%82%A4%E3%83%97fifo%E3%82%B9%E3%83%9A%E3%82%B7%E3%83%A3%E3%83%AB https://qiita.com/angel_p_57/items/1faafa275525469788b4#%E3%82%BD%E3%82%B1%E3%83%83%E3%83%88 https://qiita.com/akym03/items/aadef9638f78e222de22#%E6%A8%99%E6%BA%96%E3%82%B9%E3%83%88%E3%83%AA%E3%83%BC%E3%83%A0

ハードリンク

ハードリンクは(ほぼ)Unix系のOSにしかない機能です。 ハードリンクを使うことで、ファイルに対して別名を付けることができます。

ハードリンクの特性は以下の通りです。

  • ファイルの実態に対して直接リンクするイメージ
  • 一つの実態のファイルに対して、異なるディレクトリ、同じディレクトリ内に複数のリンクを持つことができる。
  • リンク元のファイルのアクセスする方法と同じ方法でアクセスする
  • リンク元ファイルを編集すればリンク先のファイルも変更される
  • すべてのリンクがなくなるまで実態は消えないリンク元だけを削除してもディスク上のデータは残り続ける。)

リンクを作成するには、lnコマンドを使用します。

ln <origin hard link> <new hard link>

from https://www.infraexpert.com/infra/linux32.html

ただし、以下の点は制限があります。

  • ディレクトリのハードリンクを作成することはできません。循環グラフとなるのを防ぐため。
  • 同じファイルシステム内に含まれているファイルに対してのみ生成することができる。

シンボリックリンク

windowsのショートカットと同じ。 リンク元ファイルを参照し、ディスク上の実態にアクセスするわけではない。 したがって、オリジナルのハードリンクを削除してしまえばシンボリックリンクも無効になる。

シンボリックリンクができたのはハードリンクのデメリットをカバーするためだ。 つまり、

リンクを作成するコマンドは、ln -sのオプションを使用する。

ln <origin hard link> <new hard link>

iノード

UNIXではファイルの内容とファイルのメタ情報を明確に区別しています。 ファイルを取り扱うのにファイルシステムが必要とするすべての情報はiノードと呼ばれるデータ構造に含まれています。 各ファイルごとにiノードが存在し、ファイルシステムを特定するために使用されます。

iノードに含まれる情報はPOSIXが定めてます。

  • ファイルの種類
  • ハードリンクの数
  • ファイルのバイト長
  • バイス番号
  • ファイルを識別するiノード番号。ファイルシステムにて使用
  • ユーザーID
  • ユーザーグループID
  • タイムスタンプ
    • iノード変更時刻
    • 最終アクセス時刻
    • 最終変更時刻
  • アクセス権とファイルのモード

ファイルのiノード情報を調べるにはstatコマンドが有用だ。

stat -l program

実行結果例

  File: "program"
  Size: 4096            Blocks: 8          IO Block: 4096   Directory
Device: 303h/771d       Inode: 6062517     Links: 2
Access: (0775/drwxrwxr-x)  Uid: ( 500/noriyu-k)   Gid: ( 500/noriyu-k)
Access: Mon Feb 18 00:40:35 2002
Modify: Mon Feb 18 00:35:23 2002
Change: Mon Feb 18 02:52:58 2002

アクセス権とファイルのモード

ファイルには

  • ファイルの所有者
  • ファイルのグループ
  • それ以外のユーザー

のそれぞれに対して

  • 読み込み
  • 書き込み
  • 実行

の三つを定義できる。

加えて、ファイルのモードを定義できる三つのフラグが存在する。 通常、ファイルを実行中のプロセスは、プロセス所有者のUIDで実行される。 このプロセスのUIDを変更できる。

  • suid:実行時にsuidフラグが設定されていると、プロセスはファイル所有者のUIDで実行される。
  • sgid:実行時にsgidフラグが設定されていると、プロセスはファイルのグループIDで実行される。

さらに特殊なモードとして、stickyモードが存在する。

  • sticky:実行が終了した後も、メモリ内にプログラムを残しておくようにカーネルへ要求する。 具体例は、/tmpなど。

「詳細 LINUXカーネル」を読む前に読む用語集【Linuxカーネルを読む準備】

カーネル

すべてのPCにはオペレーティングシステムが存在します。その中でも中心となるプログラムがカーネルであり、 これはすべてのアプリケーションの基礎となるプログラムであり、システム起動時にRAMに読み込まれます。

反対に、カーネルではないシステムとは、コンピューターが動作するということに関しては必須ではないシステムということです。

  • カーネル...:コンピューターが動くために必須なプログラム群
  • カーネルではない...:必須ではないプログラム

カーネルは次の二つの目的を果たします。

  • ハードウェアプラットフォームの構成要素である低水準のプログラマブルなハードウェアを制御する
  • ユーザーが使用するアプリの実行環境を提供する

ユーザーアプリは直接ハードウェアを触れない

実は、OSの中にはハードウェアを直接操作することがすべてのカーネル以外のプログラムに許可しているものもあります。

ex) MS-DOS

反対に、UNIX系のオペレーティングシステムでは、ユーザーが実行するアプリケーションが直接ハードウェアを触ることを禁止しているのです。

その代わり、ユーザープログラムがハードウェアにアクセスしたいときは、オペレーティングシステムに要求を発行することで使用を許可されているのです。 この要求はカーネルが監視しており、カーネルが許可を発行した場合、カーネルはそのユーザープログラムに代わって対象のハードウェアを操作します。

近年のハードウェアには機能が備わっています。カーネルはこのハードウェアに備わった機能を利用することでユーザープログラムによる操作を許可しているのです。 逆に、カーネルはハードウェアの機能に依存しているといえるでしょう。

したがって、

ユーザープログラム→カーネル→ハードウェア機能→ハードウェア

というアクセスが成り立ちます。

なぜここまで冗長に行う必要があるかといえば、ハードウェアはユーザーのプログラムが下手にアクセスするとそれだけで壊れるぐらいもろいのです。 最悪ハードウェアが二度と扱えなくなる可能性すらあります。

ユーザーモードカーネルモード:ハードウェアは二つの状態を用意してくれる

ハードウェアはCPUに少なくとも二つの実行モードを用意しています。 具体的にはユーザーモードカーネルモードの二つです。

マルチユーザーシステム

二人以上のユーザーが様々なアプリケーションを並列して独立した実行が可能なコンピューターのことです。 一般に以下のような機構が必要になります。

  • ユーザーIDを確認する機構
  • 同じシステムで動作する別プログラムを保護する機構
  • ほかのユーザーの処理を見えないようにする機能
  • 各ユーザーの資源の使用量を制限するアカウンティング機構

しかし、マルチユーザーシステムを使用するということは、一つのアプリケーションから別のアプリケーションへの切り替えは当然、それぞれの処理を遅くし、ユーザー側から見た応答時間に影響を与えます。 また、すべてのOSがこの機能を実装しているわけではなく、MicrosftのWindows98はシングルユーザーのOSです。

from https://windows-core.com/windows_history/history-5.php

Windows95/98 系でもユーザーを作ることはできましたが、フォルダごとにアクセスを制限することは出来ず、親の使っているパソコンのブラウザの閲覧履歴やダウンロードしたフォルダの中身が子供にぜんぶ見られるといった悲喜劇もあったとか。結局、業務用の WindowsNT 系のパソコンを導入することはあまりなく、親も子も一人一台のパソコンを購入し、各自で勝手に管理して使う、という形に落ち着いたようで、XP で両系の統合が実現しても、それは変わらなかったようです。同様に、一般的な事業所でも、現在のように一人一台のコンピュータを使うならば、むしろシングルユーザーで使ってもらうほうが扱いやすいということなのでは。

ユーザーとグループ

  • マルチユーザーシステムでは、マシン上に各ユーザーの領域があります。

  • 個人用のユーザー領域は、その所有者だけが参照できることを保証する必要があります。

  • ユーザはすべて、ユーザーIDでかんりします。(UIDとも言う)
  • 各ユーザーは一つ以上のユーザグループのメンバとなっており、グループはGIDと呼ばれる番号で管理されます。

ファイルのアクセス権限

ファイルのアクセス制限は三つのグループで分けられます。

つまり

  • ファイルの作成者は読み書きの権限を持つ
  • ファイルが所属するグループは読み取りのみの権限を持つ
  • そのほかのユーザーはアクセスできない

という制限が可能なのです。

特殊なユーザ

すべてのUNIX系OSにはrootユーザーや、superuserあるいは、supervisorなどと呼ばれる特殊なユーザーが存在します。 rootユーザーはそのシステムではなんでもできます。

アドレス空間

メモリの中の住所のこと

情報処理において、アドレス空間 (アドレスくうかん、英: address space) とは、メモリアドレスが意味を成すコンテキストを定義したもの。あるいは、一連のメモリアドレスによってアクセス可能なメモリ空間を意味する。

メモリアドレスはコンピュータのメモリ内の物理的位置を識別するものであり、住所とある意味で類似している。アドレスはデータが格納されている位置を指すが、それはちょうど人間の住所がその人の居住地を指すのと同じである。人間の住所とのアナロジーで言えば、「アドレス空間」とは、町や市や国といったある範囲の地域に対応すると考えることができる。2つのアドレスが数値的に同じでも、それぞれ異なるアドレス空間内のアドレスであれば、異なる位置を指していると言える。これは2つの市に「××町○丁目△-□」という住所が存在したとき、それらが別の場所を指すのと同じことである。

from https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%89%E3%83%AC%E3%82%B9%E7%A9%BA%E9%96%93

スケジューラ

CPUの数は常に限られています。 その中で、どのプロセスを実行していくかの選択を、オペレーティングシステムのスケジューラという機能が行っています。

プリエンプト可能

プリエンプション(英:preemption)とは、複数のタスクを同時に実行するマルチタスク(マルチプログラミング)に対応したOSの制御機能のひとつで、実行状態のタスクを一旦停止して、実行可能状態に戻すこと(他のタスクを実行状態にする)ことをいいます。

from https://medium-company.com/%E3%83%97%E3%83%AA%E3%82%A8%E3%83%B3%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3/

プロセス

UNIXはプリエンプト可能なプロセスを持つマルチプロセッシングOSです。 ユーザーがログインしていない、アプリも動作していないときでも、様々なシステムプロセスが周辺デバイスを監視しています。

いくつかのプロセスはユーザーのログインを待ってシステム端末を見張ってます。 こうしたプロセスはユーザーがログインすると、新しいプロセスを起動し、 コマンドの入力を受け付けるシェルを実行します。 グラフィカルな画面が動作する場合は、通常あるプロセスがウィンドウマネージャーを実行し、それとはまた別のプロセスによって、ウィンドウが起動します。

プロセス実行コンテキスト

プロセスを実行するために必要となるひとまとまりの情報。

POSIXとは

POSIXとは、主にUNIX系OSに共通する機能などについて、プログラムからの呼び出し方法などの標準を定めた規格。 IEEEが定めている。

要は、Linuxってこうあるべきだよねというのを定められた企画のこと。