オペレーティングシステムの概要
こんにちは、Habr! 私の考えでは、OSTEPという興味深い文献の一連の翻訳記事に注目したいと思います。 この記事では、unixに似たオペレーティングシステムの動作、つまり、プロセス、さまざまなスケジューラ、メモリ、および現代のOSを構成する他の同様のコンポーネントの動作について詳しく説明します。 ここで見ることができるすべての資料のオリジナル。 翻訳は専門的ではない(非常に自由に)行われましたが、一般的な意味を保持したいと思います。
このテーマの実験室の仕事はここで見つけることができます:
- オリジナル: pages.cs.wisc.edu/~remzi/OSTEP/Homework/homework.html
- オリジナル: github.com/remzi-arpacidusseau/ostep-code
- 私の個人的な適応: github.com/bykvaadm/OS/tree/master/ostep
- パート3:スケジューラーの概要
その他の部品:
そして、あなたは電報で私のチャンネルを見ることができます=)
プログラム作業
プログラムが動作するとどうなりますか? プログラムを実行すると、簡単なことが1つ実行されます。命令を実行します。 1秒ごとに、プロセッサによってRAMから数百万、場合によっては数十億もの命令が抽出され、次にそれらがデコード(たとえば、これらの命令がどのタイプに属するかを認識)して実行されます。 これには、メモリへのアクセス、条件の確認、関数への切り替えなど、2つの数字を追加できます。 1つの命令が完了すると、プロセッサは別の命令を実行します。 そして、命令ごとに、プログラムが完了するまで実行されます。
この例は当然、単純化されていると考えられています。実際、プロセッサを高速化するために、最新のハードウェアでは順番をずらして命令を実行し、可能な結果を計算し、同時に命令に従うことができます。
フォンノイマンコンピューティングモデル
私たちが説明する作業の簡略化された形式は、計算のフォンノイマンモデルに似ています。 フォンノイマンはコンピューターシステムの先駆者の一人であり、ゲーム理論の著者の一人でもあります。 プログラムの実行中に、他の多くのイベントが発生し、他の多くのプロセスとサードパーティロジックが動作します。その主な目的は、システムの起動、操作、およびメンテナンスを簡素化することです。
プログラムを簡単に実行できるようにする(または複数のプログラムを同時に実行できるようにする)ソフトウェアのセットがあり、プログラムが同じメモリを共有し、異なるデバイスと対話できるようにします。 このようなソフトウェア(ソフトウェア)のセットは、基本的にオペレーティングシステムと呼ばれ、そのタスクには、システムが正しく効率的に動作することの監視、およびこのシステムの管理の容易さの確認が含まれます。
オペレーティングシステム
OSの略であるオペレーティングシステムは、コンピューターリソースを管理し、コンピューターとのユーザーインタラクションを整理するように設計された相互接続されたプログラムの複合体です 。
OSは、主に最も重要な手法である仮想化手法によってその効果を達成します。 OSは物理リソース(プロセッサ、メモリ、ディスクなど)と対話し、それをより一般的で強力で使いやすい形式に変換します。 したがって、一般的な理解のために、オペレーティングシステムと仮想マシンを非常に大まかに比較できます。
ユーザーがオペレーティングシステムに指示を与え、仮想マシンの機能(プログラムの起動、メモリの割り当て、ファイルへのアクセスなど)を使用できるようにするために、オペレーティングシステムはAPI (アプリケーションプログラミングインターフェイス)と呼ばれるインターフェイスを提供します。電話をかけることができます。 典型的なオペレーティングシステムでは、何百ものシステムコールを実行できます。
そして最後に、仮想化により多くのプログラムが動作し(CPUを共有)、同時に命令とデータにアクセス(メモリを共有)し、ディスクにアクセス(I / Oデバイスを共有)できるため)、オペレーティングシステムはリソースマネージャーとも呼ばれます。 各プロセッサ、ディスク、およびメモリはシステムのリソースであるため、オペレーティングシステムの役割の1つはこれらのリソースを管理するタスクになり、このオペレーティングシステムが設計されているタスクに応じて、効率的、正直、または逆にそれを実行します。
CPU仮想化
次のプログラムを検討してください。
(https://www.youtube.com/watch?v=zDwT5fUcki4)

特別なアクションを実行することはありません。実際、 spin ()関数を呼び出すだけです。そのタスクは、サイクル時間と1秒経過後に戻ることです。 したがって、ユーザーが引数として渡した文字列を無限に繰り返します。
このプログラムを実行し、文字「A」を引数として渡します。 結果はあまり興味深いものではありません。システムは単にシンボル「A」を表示するプログラムを実行するだけです。
次に、同じプログラムの多くのインスタンスが実行されているが、明確にするために異なる文字を表示するときにオプションを試してみましょう。 この場合、結果はわずかに異なります。 プロセッサが1つあるという事実にもかかわらず、プログラムは同時に実行されます。 これはどのように起こりますか? しかし、ハードウェア機能の助けがなければ、オペレーティングシステムは幻想を作り出すことがわかります。 システムに複数の仮想プロセッサがあり、1つの物理プロセッサを理論的には無限の数に変えて、プログラムが同時に実行されているように見えるという錯覚。 このような錯覚はCPU仮想化と呼ばれます 。
このような写真には多くの疑問が投げかけられます。たとえば、複数のプログラムを同時に起動したい場合、どれを起動しますか? OSの「ポリシー」がこの質問に責任を負います。 ポリシーはOSの多くの場所で使用され、同様の質問に答えます。また、OSが実装する基本的なメカニズムでもあります。 したがって、リソースマネージャーとしてのOSの役割。
メモリ仮想化
それでは、メモリを見てみましょう。 現代のシステムの物理メモリモデルは、バイトの配列として表されます 。 メモリから読み取るには、セルにアクセスするためにセルのアドレスを指定する必要があります。 データを記録または更新するには、データとデータを書き込むセルのアドレスも指定する必要があります。
プログラム操作中は、メモリアクセスが常に発生します。 プログラムは、そのデータ構造全体をメモリに保存し、さまざまな命令に従ってアクセスします。 一方、命令もメモリに保存されるため、次の命令へのすべてのリクエストに対してもアクセスされます。
malloc()を呼び出す
malloc()呼び出し(https://youtu.be/jnlKRnoT1m0)を使用してメモリ領域を割り当てる次のプログラムを検討してください。

プログラムはいくつかのことを行います。 まず、一定量のメモリを割り当て(7行目)、選択したセルのアドレスを表示し(9行目)、割り当てられたメモリの最初のスロットにゼロを書き込みます。 さらに、プログラムは、変数「p」のアドレスでメモリに記録された値をインクリメントするサイクルに入ります。 また、自身のプロセス識別子も表示します。 プロセスIDは、実行中のプロセスごとに一意です 。 いくつかのコピーを起動すると、興味深い結果に出くわします。最初のケースでは、何もせずにいくつかのコピーを開始するだけであれば、アドレスは異なります。 しかし、これは私たちの理論に該当しません! 確かに、最新のディストリビューションにはデフォルトのランダム化機能が含まれているためです。 オフにすると、期待どおりの結果が得られます。同時に実行されている2つのプログラムのメモリアドレスは一致します。

その結果、2つの独立したプログラムが独自のプライベートアドレス空間で機能し、オペレーティングシステムによって物理メモリに表示されることがわかりました 。 したがって、1つのプログラム内でメモリアドレスを使用しても、他のプログラムには影響しません。また、各プログラムは、独自の物理メモリを所有しているように見えます。 ただし、実際には、物理メモリはオペレーティングシステムによって管理される共有リソースです。
コヒーレンス
オペレーティングシステム内の別の重要なトピックは、 一貫性です。 この用語は、同じプログラム内で同時に多くのことを処理するときに発生する可能性のあるシステムの問題に関して使用されます。 オペレーティングシステム自体にも一貫性の問題が発生します。 メモリとプロセッサの仮想化に関する以前の例では、OSが同時に多くのことを管理していることに気付きました。最初のプロセスを開始し、次に2番目のプロセスを開始します。 結局のところ、この動作はいくつかの問題につながる可能性があります。 たとえば、最新のマルチスレッドプログラムではこのような問題が発生します。
次のプログラムを検討してください。

main関数のプログラムは、 Pthread_create()呼び出しを使用して2つのスレッドを作成します。 この例では、スレッドは他の関数の隣にある同じメモリ空間で実行される関数と考えることができ、同時に起動される関数の数は明らかに複数です。 この例では、各スレッドがworker()関数を開始および実行し、 これにより変数が単純にインクリメントされます。
引数1000でこのプログラムを実行します。ご想像のとおり、各スレッドは変数を1000回インクリメントするため、結果は2000になります。 ただし、すべてがそれほど単純ではありません。 繰り返し回数を1桁増やしてプログラムを実行してみましょう。

100000などの数値を入力すると、出力に200000が表示されますが、100000を数回実行すると、正しい答えが表示されるだけでなく、異なる間違った答えが表示されます。 答えは、数値を増やすには3つの操作が必要であるという事実にあります。つまり、メモリから数値を抽出し、増分してから数値を書き戻します。 これらの命令はすべてアトミックに実装されているわけではないため(すべて同時に)、このような奇妙なことが起こる可能性があります。 この問題は、 競合状態プログラミング- 競合状態と呼ばれます 。 未知の瞬間に未知の力があなたのオペレーションのパフォーマンスに影響を与える場合があります。