意識低い系会社員

意識低い系会社員の日常

【スポンサーリンク】

ふつうのLinuxプログラミング 第2版の内容memo(11章)

【スポンサーリンク】

 

こんにちは。

 

最近、ふつうのLinuxプログラミング 第2版を読んでいるので知識の定着のために学んだ内容を要約したメモを書きます。

このエントリは完全な個人のメモです。

 

お勉強のためにこの本を読んでいるので、内容を覚えるためにSummarizing(サマライジング)を行います。

 

10章の内容はこちら。

amistad06-a.hatenablog.com

 

amistad06-a.hatenablog.com

 

第11章 プロセスとハードウェア

この章で説明すること

この章ではコンピュータの基本的な構造、プロセスやメモリの概念、メモリ管理のAPI、ビルドの流れなどを説明します。

 

コンピュータの構造

コンピュータは主にUPU、メモリ、HDD、SSD、ディスプレイやマウスなどの周辺機器で構成されていて、それぞれバスで繋がっています。

CPUはメモリ上のバイトを読み書きすることができ、各種演算や条件分岐などができます。

 

機械語

CPUはメモリ上にあるプログラムに従って動作します。

プログラムは規則性のあるバイト列で構成されていて、その規則を機械語と言います。

機械語にはいくつか種類があり、CPUによって異なります。

x86系向けの言語や、ARMのCPU向けの言語が代表的です。

言語仕様に基づいたコンピュータの設計方式のことを一般的にコンピュータアーキテクチャと呼びます。

 

マルチタスクの仕組み

基本的にひとつのCPUはひとるのプロセスしか動かせません。

しかし、実際にはCPUはいくつものプロセスを同時に動かしています。

同時に複数のプロセスを動かすことをマルチタスクと言います。

マルチタスクは仮想CPUと仮想メモリで実現されます。

 

仮想CPU

仮想CPUは、短い時間ごとに実行するプロセスを切り替えることで実現できます。このとき区切られた短い時間のことをタイムスライスといいます。

プロセスによって割り当てられるタイムスライスは均等ではなく、プロセスの優先度によって異なります。

どのプロセスにどれだけタイムスライスを与えるかを決めるカーネルの機構をスケジューラまたはディスパッチャと呼びます。

 

仮想メモリ

各プロセスに対して一定の大きさのアドレス空間を割り当てます。

この時のアドレス空間の単位をページといいます。

ページの大きさは一般的に4KBまたは8KBです。

プロセスから見えるアドレス空間は全て0番地から始まり、論理アドレス空間と呼ばれます。

論理アドレス空間はページ単位で物理アドレス空間に紐づけられますが、論理アドレス空間は必ずしも物理アドレス空間に紐づけられているわけではありません。

プロセスが論理アドレス空間を参照するときには物理アドレス空間に紐づけられますが、参照されないときはSSDやHDDなどの記憶媒体にに退避されます。

記憶媒体とメモリ間のページの入れ替えをページングといいます。

これに対して、プロセス全体を切り替えることをスワッピングといいます。

論理アドレス空間は必ずしも物理アドレス空間に対応させなくてもいいので、論理アドレス空間は実際のメモリの要領よりも大きくできます。

たとえば64bitアーキテクチャのパソコンの場合、論理アドレス空間は約172臆GB(16エクサバイト)です。

172臆GBは64bitで表せる符号なし整数の最大値です。

 

メモリマップトファイル

ファイルをメモリとしてアクセスできるようにする仕組みです。

この仕組みがあると、メモリの操作をするのと同じ感覚でファイルの操作ができます。

仕組みは、論理アドレスと物理アドレスの対応を切っておき、アクセスされたタイミングでファイルをメモリに読み込み、メモリの操作が終わったタイミングでメモリの内容をファイルに書き出します。

 

共有メモリ

基本的に各プロセスに割り当てられるメモリは独立していますが、複数のプロセスから同じメモリを参照できる仕組みがあります。

それが共有メモリです。

仕組みは、1つの物理アドレスに2つ以上の論理アドレスを紐づけます。

 

アドレス空間の構造

アドレス空間は

・テキスト領域(機械語プログラムがある領域)

・データ領域(グローバル変数や初期化済みstatic変数、文字リテラルがある領域)

・BSS領域(初期化が必要ないグローバル変数やstatic変数などの領域)

・ヒープ領域(mallocが管理する領域)

・スタック領域(関数の引数やローカル変数がある領域)

で構成されています。

以下のコマンドで実際に動いているプロセスのアドレス空間を見ることができます。

cat /proc/n/maps

 

malloc(3)

ヒープ領域を確保して、先頭アドレスを返します。

メモリ割り当てに失敗した場合nullを返します。

この関数で確保したメモリは必ずfree()で解放しなければなりません。

 

calloc(3)

ヒープ領域を確保して、先頭アドレスを返します。

確保したメモリは0で初期化されています。

メモリ割り当てに失敗した場合nullを返します。

この関数で確保したメモリは必ずfree()で解放しなければなりません。

 

realloc(3)

すでに確保したメモリのサイズを拡張または縮小します。

メモリの先頭アドレスが変わる可能性がありますが、その際はメモリの中身はコピーされます。

この関数で確保したメモリは必ずfree()で解放しなければなりません。

メモリ割り当てに失敗した場合nullを返します。

realloc()の戻り値を直接変数に代入すると、失敗したときにnullが代入されてしまうのでもともと確保していたメモリにアクセスすることができなくなります。

free()できない領域ができてしまうので必ず一旦別の変数に代入して戻り値判定を行ってください。

 

free(3)

ヒープ領域を解放します。

解放後はそのメモリにアクセスしてはいけません。

 

brk(2)

物理アドレスが割り当てられていないページに対して物理アドレスを紐づけるシステムコール。

mallocやreallocの中で呼ばれます。

割り当てるサイズが大きい場合はmmap(2)が呼ばれることもあります。

straceコマンドで物理アドレス割り当ての様子が見れます。

 

ビルドの流れ

C言語のプログラムのビルドは以下のような流れです。

・プリプロセス

・コンパイル

・アセンブル

・リンク

 

プリプロセス

#includeや#defineの処理を行います。

 

コンパイル

C言語からアセンブリ言語に変換します。

 

アセンブル

アセンブリ言語を機械語を含むオブジェクトファイルに変換します。

代表的なフォーマットは

・ELF

・COFF

・a.out

の3つです。

 

リンク

オブジェクトファイルから実行ファイルまたはライブラリを生成します。

リンクにはスタティックリンクとダイナミックリンクがあります。

 

スタティックリンク

必要な関数が実行ファイルの中に物理的に取り込まれます。

 

ダイナミックリンク

ビルドのタイミングでは参照する関数が存在するかどうかだけチェックします。

実行時にライブラリの中身を参照して実行します。

 

ダイナミックロード

全てのリンクをプログラム実行時に行う手法のことです。

 

演習問題

標準入力の末尾数行だけを出力するtailコマンドを書きなさい。表示する行数はコマンドラインオプションで受け取れるようにしなさい。

 

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

#define BUFSIZE 100

static void do_tail(int line_num){
    int  i, current_line = 0;
    char buf[line_num][BUFSIZE];

    while(fgets(buf[current_line % line_num], BUFSIZE, stdin)){
        current_line++;
    }

    if(current_line < line_num){
        // ファイルが指定された行以下の場合
        for(i = 0; i < current_line; i++){
            fprintf(stdout, "%s", buf[i]);
        }
    }else{
        // ファイルが指定された行以上の場合
        for(i = 0; i < line_num; i++){
            fprintf(stdout, "%s", buf[current_line++ % line_num]);
        }
    }

    return;
}

int main(int argc, char *argv[]){
    // コマンドライン引数で行数が指定されていない場合エラー
    if(argc != 2){
        // fprintf(stderr, "Usage: %s n\n", argv[0]);
        exit(1);
    }

    // fprintf(stdout, "main before call do_tail().\n");
    do_tail(atoi(argv[1]));
    exit(0);
}

 

第7章で作ったプログラムを元にしました。

amistad06-a.hatenablog.com

 

まとめ

11章の内容はこんな感じです。

12章はこちら。

amistad06-a.hatenablog.com

 

おわり。

 

【スポンサーリンク】