Linux Kernelの双方向リスト"list_head" 徹底解説

まずは双方向リストについて解説する。 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 Kernel "task_struct" 徹底解説

task_structについて

プロセスに関する情報はtask_struct構造体に格納される。 プロセスディスクリプタとも呼ばれる。

プロセスの情報は具体的には以下のようなものがあげれる。

  • プロセスの属性
  • CPU上で実行されているか
  • あるいは何かしらの事象を待って止められているか
  • どのファイルへのアクセス権が許可されているか

などなどプロセス状態を格納するメンバがそろっている。 それだけでなく、情報が格納されているほかの構造体へのポインタが入っているサメンバもあります。

  • thread_info :プロセスに対応する低水準(低レイヤー)な情報
  • mm_struct :メモリリージョンディスクリプタへのポインタ
  • tty_struct :プロセスに対応するtty(制御端末)
  • fs_struct :カレントディレクト
  • files_struct:ファイルディスクリプタへのポインタ
  • signal_struct:受信シグナル

linuxkernelでは以下のファイルに存在する。

linux-3.12.6/include/linux/sched.h

struct task_struct {
    volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
    ...
    int exit_state;
    int exit_code, exit_signal;
    ...
    struct task_struct __rcu *real_parent; /* real parent process */
    struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */
    ...
    struct list_head children;      /* list of my children */
    struct list_head sibling;       /* linkage in my parent's children list */
    struct task_struct *group_leader;       /* threadgroup leader */
    struct mm_struct       *mm;
    struct mm_struct       *active_mm;
    ...
    struct pid_link pids[PIDTYPE_MAX];
    ...
    const struct cred __rcu *cred;  /* effective (overridable) subjective task*/
    ...
    char comm[TASK_COMM_LEN]; /* executable name excluding path
    - access with [gs]et_task_comm (which lock
    it with task_lock())
    - initialized normally by setup_new_exec */
};

from https://www.coins.tsukuba.ac.jp/~yas/coins/os2-2013/2013-12-26/

参照:https://github.com/torvalds/linux/blob/692b7dc87ca6d55ab254f8259e6f970171dc9d01/include/linux/sched.h#L739

stateメンバについて

task_struct構造体のstateはその名が示す通り、プロセスの現在の状態を表します。

格納される値は以下の通りです。(多少編集有)

/* Used in tsk->state: */
#define TASK_RUNNING           0x00000000
#define TASK_INTERRUPTIBLE     0x00000001
#define TASK_UNINTERRUPTIBLE   0x00000002
#define TASK_STOPPED           0x00000004
#define TASK_TRACED            0x00000008
/* Used in tsk->exit_state: */
#define EXIT_DEAD          0x00000010
#define EXIT_ZOMBIE            0x00000020
#define EXIT_TRACE          (EXIT_ZOMBIE | EXIT_DEAD)
/* Used in tsk->state again: */
#define TASK_PARKED            0x00000040
#define TASK_DEAD          0x00000080
#define TASK_WAKEKILL      0x00000100
#define TASK_WAKING            0x00000200
#define TASK_NOLOAD            0x00000400
#define TASK_NEW           0x00000800
#define TASK_RTLOCK_WAIT   0x00001000
#define TASK_FREEZABLE     0x00002000
#define __TASK_FREEZABLE_UNSAFE        (0x00004000 * IS_ENABLED(CONFIG_LOCKDEP))
#define TASK_FROZEN            0x00008000
#define TASK_STATE_MAX         0x00010000

from https://github.com/torvalds/linux/blob/692b7dc87ca6d55ab254f8259e6f970171dc9d01/include/linux/sched.h#L86

  • TASK_STOPPPED:プロセスは停止中。SIGSTOP SIGTSTP SIGTTIN SIGTTOUを受け取るとこの状態になる。

  • TASK_TRACED:デバッガにより停止中。ptrace()システムコールなどによりほかのプログラムから監視されている場合は、どのシグナルを受信しても、この状態になります。

  • TASK_UNINTERRUPTIBLE:ある条件が成り立つのを一時停止して待ち合わせしている状態。次の条件でTASK_RUNNGINになる
    • ハードウェアの割り込み
    • プロセスが待っているシステム資源の解放
    • シグナルの受信

stateメンバの値は、以下のようにして単純に代入します。

p->state = TASK_RUNNNING

あるいは、set_task_stateマクロや、set_current_state(現在実行中のプロセスの状態を設定する)マクロを利用することもあります。 →(以前はset_task_stateは次の階層の中に定義されていたようですが、今はないようです)

include/linux/sched.h
#define set_current_state(state_value)               \
   do {                          \
       current->task_state_change = _THIS_IP_;      \
       smp_store_mb(current->state, (state_value));      \
   } while (0)
#endif

#define set_current_state(state_value)          \
   smp_store_mb(current->state, (state_value))
#endif

プロセスのしきべつ

プロセスディスクリプタポインタ

プロセスとプロセスディスクリプタは必ず一対一対応しており、カーネルtask_struct構造体の32ビットのアドレスを使用してプロセスを識別します。このアドレスを、プロセスディスクリプタポインタと呼びます。これにより、独立にスケジューリング可能な各実行コンテキストにはそれぞれディスクリプタが割り当てられております。

PID

PIDはプロセスディスクリプタpidメンバに格納されています。新しく生成されるPIDはその直前に生成されたプロセスのPIDに1を足した値になります。

struct task_struct{
    ...
    /* PID/PID hash table linkage. */
    struct pid         *thread_pid;
    ...
}

from https://github.com/torvalds/linux/blob/692b7dc87ca6d55ab254f8259e6f970171dc9d01/include/linux/sched.h#LL998C1-L999C27

PIDのMAX

ちなみにPIDにもMAXの値は存在し、デフォルトではPID_MAX_DEFAULT-1です カーネルはPIDがこの上限に達したときに、使われていないPIDの再利用を行う必要があります。

/*
 * This controls the default maximum pid allocated to a process
 */
#define PID_MAX_DEFAULT (CONFIG_BASE_SMALL ? 0x1000 : 0x8000)

from https://github.com/torvalds/linux/blob/e660abd551f1172e428b4e4003de887176a8a1fd/include/linux/threads.h#L28

さらに、システム管理者は/proc/sys/kernel/pid_maxファイルにより小さな値を書き込むことにより、PIDの最大値を下げることができます。

PIDの再利用

PIDを再利用するときは、pidmap_arrayビットマップを使う必要があります。pidmap_arrayビットマップは、使用中のPIDと未使用のPIDを表しています。

static pidmap_t pidmap_array[PIDMAP_ENTRIES] =
     { [ 0 ... PIDMAP_ENTRIES-1 ] = { ATOMIC_INIT(BITS_PER_PAGE), NULL } };

TGIDによるグループ化

これまで、PIDは一意の値であることを期待されていると説明してきました。

しかし、UNIXプログラマは同じグループのスレッドが同じPIDを持っていることを期待しまsう。

例えば、あるPIDにシグナルを送ると、そのグループ内のすべてのスレッドに効果を及ぼすなど。

この仕組みを体現するため、Linuxはスレッドグループを使用しています。 スレッド間で共有する識別子として、グループの最初の軽量プロセスであるスレッドグループリーダのPIDを使用するのです。 この識別子は各プロセスディスクリプタtgidメンバに格納されています。 そして、getpidシステムコールは、カレントプロセスのpidではなく、tgidの値を返すのです。

static pid_t getpid(void)
{
    return bpf_get_current_pid_tgid();
}

参照 https://github.com/torvalds/linux/blob/e660abd551f1172e428b4e4003de887176a8a1fd/tools/perf/examples/bpf/augmented_raw_syscalls.c#LL351C1-L354C2

この仕組みのおかげで、すべてのマルチスレッドアプリケーションは同じ識別子を共有できるのです。

プロセスディスクリプタの処理

Linuxは各プロセスに一つのメモリ領域を割り当て、プロセスディスクリプタとリンクします。

この領域には二つの異なるデータ構造であるthread_info構造体とカーネルモードプロセッサスタックを割り当てます。

カーネルは8KBのメモリ領域を、先頭ページフレームが2の13乗の倍数のアドレス協会に位置合わせ下二つの連続するページフレームに置きます。 これにより、メモリのフラグメンテーションを防ぐことができます。

linux kernel

from https://doc.lagout.org/operating%20system%20/linux/Understanding%20Linux%20Kernel.pdf

この図を見ると

  • thread_info構造体はメモリ領域の先頭に格納し、
  • スタックは末尾から低位アドレス方向に延びていきます。

この状態をc言語であらわすと、以下のようなunionとして表現できますね。

union thread_union {
    struct thread_info thread_info;
    unsigned long stack[2048];
}

さらに、thread_infoとtask_struct構造体(プロセスディスクリプタ)がtaskメンバとthread_infoメンバで互いにリンクしているのが分かると思います。

実際のlinuxkernelのthread_infoコードは以下の通りです。

struct thread_info {
    struct task_struct         *task;      /* main task structure */
    unsigned long             flags;      /* low level flags */
    __u32                       cpu;        /* current CPU */
    int                            preempt_count;  /* 0 => preemptable,<0 => BUG */
    struct thread_info         *real_thread;    /* Points to non-IRQ stack */
    unsigned long               aux_fp_regs[FP_SIZE]; 
    /* auxiliary fp_regs to save/restore them out-of-band */
};

from https://github.com/torvalds/linux/blob/e660abd551f1172e428b4e4003de887176a8a1fd/arch/um/include/asm/thread_info.h#LL19C1-L28C3

epsレジスタの動き

スタックは末尾から低位アドレス方向に延びると説明しましたが、espレジ須賀はCPUのスタックポイントであり、この値が小さければ小さいほど、スタックが使われていることを表します。

プロセスのカーネルスタックは、ユーザーモードからカーネルモードへ切り替わった直後は空です。 この状態ではespレジスタの値はスタックの末尾+1のアドレスを示しています。

Linuxファイルシステムの基本「シンボリックリンク」「iノード」

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の「dev」とか「sda」って何?

伝統的なデバイス

ブロックデバイスの伝統的な名前

  • HDD, SSD:/dev/sda, /dev/sdb, /dev/sdc...
  • NVMe SSD: dev/nvm0n1, dev/nvm0n2
  • VMの仮想デバイスdev/vda, dev/vdb, dev/vdc...

注意点

ブロックデバイスを一覧表示するコマンド

ブロックデバイスを一覧表示するコマンド:lsblk (ls blockの略)

実行例

ブロックデバイスの情報を得る

  • ブロックデバイスの情報を得る:/sys/block/<名前>/配下のファイルを参照
ll /sys/block/

ディレクトリ配下に大量のデバイスが見えるはず。これは各デバイスの情報を格納しているディレクトリである。

cat /sys/block/<デバイス名>/size
  • readonly
cat /sys/block/<デバイス名>/readonly
  • removable
cat /sys/block/<デバイス名>/size

1か0で確認できる。

バイス名の付け方

カーネルは起動時に、デバイスの認識順に a,b,c...とつけていく。

この時、何らかの事情でデバイス認識順が変わると、デバイス名が変わる。 (デバイスの故障など)

バイス名を固定する

Udevのデバイス名固定機能、persistent device nameを使用する。 Udevというサービスはデバイスを認識するごとに様々な別名を付けてくれる。

まずはuuidを確認

バイスのuuidを確認する。(世界で一意なid)

ls -l /dev/disk/by-uuid/

procって何?「Linux用語解説」

procfsについて

procfsとは

中身を見てみる

まずはecho $$を実行。

実行例: echo $$

実行結果:9667

すると、ターミナルのプロセスIDが手に入る。

その後、/proc内部にある、入手したプロセスIDと合致するディレクトリを見つける。

サンプル実行例

ll /proc/9667/

サンプル実行結果

合計 0
dr-xr-xr-x   9 minegishiminami minegishiminami 0  6月 14 17:49 ./
dr-xr-xr-x 381 root            root            0  6月 14 14:20 ../
-r--r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 arch_status
dr-xr-xr-x   2 minegishiminami minegishiminami 0  6月 14 17:56 attr/
-rw-r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 autogroup
-r--------   1 minegishiminami minegishiminami 0  6月 14 17:56 auxv
-r--r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 cgroup
--w-------   1 minegishiminami minegishiminami 0  6月 14 17:56 clear_refs
-r--r--r--   1 minegishiminami minegishiminami 0  6月 14 17:50 cmdline
-rw-r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 comm
-rw-r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 coredump_filter
-r--r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 cpuset
lrwxrwxrwx   1 minegishiminami minegishiminami 0  6月 14 17:56 cwd -> /home/minegishiminami/myworking/
-r--------   1 minegishiminami minegishiminami 0  6月 14 17:56 environ
lrwxrwxrwx   1 minegishiminami minegishiminami 0  6月 14 17:50 exe -> /bin/bash*
dr-x------   2 minegishiminami minegishiminami 0  6月 14 17:49 fd/
dr-x------   2 minegishiminami minegishiminami 0  6月 14 17:56 fdinfo/
-rw-r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 gid_map
-r--------   1 minegishiminami minegishiminami 0  6月 14 17:56 io
-r--r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 limits
-rw-r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 loginuid
dr-x------   2 minegishiminami minegishiminami 0  6月 14 17:56 map_files/
-r--r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 maps
-rw-------   1 minegishiminami minegishiminami 0  6月 14 17:56 mem
-r--r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 mountinfo
-r--r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 mounts
-r--------   1 minegishiminami minegishiminami 0  6月 14 17:56 mountstats
dr-xr-xr-x  59 minegishiminami minegishiminami 0  6月 14 17:56 net/
dr-x--x--x   2 minegishiminami minegishiminami 0  6月 14 17:56 ns/
-r--r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 numa_maps
-rw-r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 oom_adj
-r--r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 oom_score
-rw-r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 oom_score_adj
-r--------   1 minegishiminami minegishiminami 0  6月 14 17:56 pagemap
-r--------   1 minegishiminami minegishiminami 0  6月 14 17:56 patch_state
-r--------   1 minegishiminami minegishiminami 0  6月 14 17:56 personality
-rw-r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 projid_map
lrwxrwxrwx   1 minegishiminami minegishiminami 0  6月 14 17:56 root -> //
-rw-r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 sched
-r--r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 schedstat
-r--r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 sessionid
-rw-r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 setgroups
-r--r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 smaps
-r--r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 smaps_rollup
-r--------   1 minegishiminami minegishiminami 0  6月 14 17:56 stack
-r--r--r--   1 minegishiminami minegishiminami 0  6月 14 17:50 stat
-r--r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 statm
-r--r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 status
-r--------   1 minegishiminami minegishiminami 0  6月 14 17:56 syscall
dr-xr-xr-x   3 minegishiminami minegishiminami 0  6月 14 17:56 task/
-r--r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 timers
-rw-rw-rw-   1 minegishiminami minegishiminami 0  6月 14 17:56 timerslack_ns
-rw-r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 uid_map
-r--r--r--   1 minegishiminami minegishiminami 0  6月 14 17:56 wchan

現在進行形で開いているターミナルについての情報が大量に出てきた。

このproc配下には大量の情報がある。

コマンドを見てみる

  • /proc/<PID>/cmdlineにはそのプロセスで起動したコマンドが格納される

実行例:cat /proc/9667/cmdline

実行結果:bash

  • /proc/<PID>/statにはCPU使用時間などの統計情報

実行例:ll /proc/9667/stat

実行結果:-r--r--r-- 1 minegishiminami minegishiminami 0 6月 14 17:50 /proc/9667/stat

psコマンドの情報を見てみる

psコマンドの情報を確認する。

strace -o log.txt ps

すると、/proc/<PID>/...配下の情報を大量に抽出していることが分かる

openat(AT_FDCWD, "/proc/4/stat", O_RDONLY) = 6
read(6, "4 (rcu_par_gp) I 2 0 0 0 -1 6923"..., 2048) = 155
close(6)                                = 0
openat(AT_FDCWD, "/proc/4/status", O_RDONLY) = 6
read(6, "Name:\trcu_par_gp\nUmask:\t0000\nSta"..., 2048) = 938
close(6)                                = 0
stat("/proc/6", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
openat(AT_FDCWD, "/proc/6/stat", O_RDONLY) = 6
read(6, "6 (kworker/0:0H-kblockd) I 2 0 0"..., 2048) = 165
close(6)                                = 0
openat(AT_FDCWD, "/proc/6/status", O_RDONLY) = 6
read(6, "Name:\tkworker/0:0H-kblockd\nUmask"..., 2048) = 946
close(6)                                = 0
stat("/proc/8", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
openat(AT_FDCWD, "/proc/8/stat", O_RDONLY) = 6
read(6, "8 (kworker/u16:0-i915) I 2 0 0 0"..., 2048) = 168
close(6)                                = 0
openat(AT_FDCWD, "/proc/8/status", O_RDONLY) = 6
read(6, "Name:\tkworker/u16:0-i915\nUmask:\t"..., 2048) = 953
close(6)                                = 0
stat("/proc/9", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
openat(AT_FDCWD, "/proc/9/stat", O_RDONLY) = 6
read(6, "9 (mm_percpu_wq) I 2 0 0 0 -1 69"..., 2048) = 157

Linuxの「cgroups」って何?

cgroups概要

cgroupはリソースごとにコントローラーが存在し、コントローラーはcgroupfsという特別なファイルシステムを介して使います。 このファイルシステムは、ストレージデバイス上に存在するわけではなく、メモリ上にだけ存在します。

Ubuntu20.04では /sys/fs/cgroup ディレクトリ以下に、各コントローラーに対応するcgroupファイルシステムがマウントされています。

cgroupsは、Linuxカーネルの一部として提供されており、コマンドラインツール(通常は「cgroup-tools」パッケージに含まれています)やAPIを介して操作することができます。

cgroupsはファイルとして扱う

cgroupfsという名前の仮想ファイルシステムディレクトリーを作成することにより、システム上の cgroup 階層を管理できます。

デフォルトのパスは/sys/fs/cgroup/です。

from https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/9/html/managing_monitoring_and_updating_the_kernel/assembly_using-cgroupfs-to-manually-manage-cgroups_managing-monitoring-and-updating-the-kernel

新規プロセスを作ってみる

次のように、cgroup配下にフォルダーを作成することで新規プロセスを作成することができます。

mkdir /sys/fs/cgroup/Example/

/sys/fs/cgroup/Example/ ディレクトリーには、memory および pids コントローラー用のコントローラー固有のファイルも含まれます。

これらのファイルは、linux kernelに含まれる、cgroupsによって自動的に作成されたものです。

cat /sys/fs/cgroup/cgroup.controllers

実際にlinux環境下で上記のコマンドを打つとプロセスが見えると思います。

cgroup
cgroup

ブラウザを開いてcgroupsを見てみる。

1.まずはブラウザを開き、ターミナルでtopコマンドを入力します。

top

cgroup process確認
cgroup process確認

この時に開いたブラウザのPIDをtopで確認しましょう。 今回だと、PID:2331ですね。

2.PIDをメモしたらqを押してtopコマンドを終了します。

3./proc配下にあるプロセスを確認しょう。

cat /proc/2331/cgroup

プロセスが属する cgroup を表示するには、cat proc/<PID>/cgroup コマンドを実行します。

4./sys/fs/cgroup配下を調べる

先ほど調べた結果を、/sys/fs/cgroup配下で調べましょう。

プロセスに関する情報が記述されてますね!

Linuxでソースコードをバイナリ化する「xxdコマンドの使い方」

コンピューターは1と0で動く...とは?

プログラミングをするとき、人間から見て半端な数字が多い。

  • 8bit
  • 16GB, 32GB
  • 32bitcpu, 64bitcpu

→切りのいい数字じゃダメなんか?

これらの数字はすべて2のn乗。

理論

素子のon/offがたくさんある。

データは何らかの物理デバイスのon/offの塊であらわされる。

  • メモリ、SSD:素子内部の電荷
  • HDD;磁性体の向き
  • DVD:ディスクの溝の深さ

これらのデバイスに刻まれた指標をもとにon/offが決まる。

素子の数で表現できる数は変わる

そして、素子が多ければ多いほど、表現できる数は増える。

素子一つだけだと、表現できる状態は2つ

  • off,onの二通。

素子二つになると、表現できる状態は4つ

  • off-off, off-on, on-off, on-onの4通り。

これらのon/offが1,0に対応し、その理論に基づいて2進法がコンピューターで利用されている。

ascii code

Dec Hex Sym    Dec Hex Char  Dec Hex Char  Dec Hex Char
-----------    ------------  ------------  ------------
0   0  NUL     32  20        64  40  @     96  60  `
1   1  SOH     33  21  !     65  41  A     97  61  a
2   2  STX     34  22  "     66  42  B     98  62  b
3   3  ETX     35  23  #     67  43  C     99  63  c
4   4  EOT     36  24  $     68  44  D    100  64  d
5   5  ENQ     37  25  %     69  45  E    101  65  e
6   6  ACK     38  26  &     70  46  F    102  66  f
7   7  BEL     39  27  '     71  47  G    103  67  g
8   8   BS     40  28  (     72  48  H    104  68  h
9   9  TAB     41  29  )     73  49  I    105  69  i
10   A   LF     42  2A  *     74  4A  J    106  6A  j
11   B   VT     43  2B  +     75  4B  K    107  6B  k
12   C   FF     44  2C  ,     76  4C  L    108  6C  l
13   D   CR     45  2D  -     77  4D  M    109  6D  m
14   E   SO     46  2E  .     78  4E  N    110  6E  n
15   F   SI     47  2F  /     79  4F  O    111  6F  o
16  10  DLE     48  30  0     80  50  P    112  70  p
17  11  DC1     49  31  1     81  51  Q    113  71  q
18  12  DC2     50  32  2     82  52  R    114  72  r
19  13  DC3     51  33  3     83  53  S    115  73  s
20  14  DC4     52  34  4     84  54  T    116  74  t
21  15  NAK     53  35  5     85  55  U    117  75  u
22  16  SYN     54  36  6     86  56  V    118  76  v
23  17  ETB     55  37  7     87  57  W    119  77  w
24  18  CAN     56  38  8     88  58  X    120  78  x
25  19   EM     57  39  9     89  59  Y    121  79  y
26  1A  SUB     58  3A  :     90  5A  Z    122  7A  z
27  1B  ESC     59  3B  ;     91  5B  [    123  7B  {
28  1C   FS     60  3C  <     92  5C  \    124  7C  |
29  1D   GS     61  3D  =     93  5D  ]    125  7D  }
30  1E   RS     62  3E  >     94  5E  ^    126  7E  ~
31  1F   US     63  3F  ?     95  5F  _    127  7F 


Name   Char  Dec   Hex  Description           
NUL     ^@   00    00   null
SOH     ^A   01    01   start of heading
STX     ^B   02    02   start of text
ETX     ^C   03    03   end of text
EOT     ^D   04    04   end of transmission
ENQ     ^E   05    05   enquiry
ACK     ^F   06    06   acknowledge
BEL     ^G   07    07   bell
BS      ^H   08    08   backspace
TAB     ^I   09    09   horizontal tab
LF, NL  ^J   10    0A   line feed, new line
VT      ^K   11    0B   vertical tab
FF, NP  ^L   12    0C   form feed, new page
CR      ^M   13    0D   carriage return
SO      ^N   14    0E   shift out
SI      ^O   15    0F   shift in
DLE     ^P   16    10   data link escape
DC1     ^Q   17    11   device control 1
DC2     ^R   18    12   device control 2
DC3     ^S   19    13   device control 3
DC4     ^T   20    14   device control 4
NAK     ^U   21    15   negative acknowledge
SYN     ^V   22    16   synchronous idle
ETB     ^W   23    17   end of trans. block
CAN     ^X   24    18   cancel
EM      ^Y   25    19   end of medium
SUB     ^Z   26    1A   substitute
ESC     ^[   27    1B   escape
FS      ^\   28    1C   file separator
GS      ^]   29    1D   group separator
RS      ^^   30    1E   record separator
US      ^_   31    1F   unit separator
SPACE        32    20   space
DEL          127   7F   delete

https://www.ieee.li/computer/ascii.htm

実態

数字をバイナリ形式で見てみる

  • xxdプログラムを使ってみる。 これは、ファイルの中身を2進数や16進数表記で見ることができる。

  • numプログラムを使う

package main

import (
    "os",
    "bytes",
    "encoding/binary"
)
func main(){
    buf := new(bytes.Buffer)
    var num uint8
    num = 0
    err := binary.Write(buf, binary.LittleEndian, num)
    if err != nil {
        panic("faild to write a number")
    }
    os.Stdout.Write(buf.Bytes())
}
package main

import (
    "os",
    "bytes",
    "encoding/binary"
)

func main() {
    buf := new(bytes.Buffer)
    var num uint8
    num = 0
    err := binary.Write(buf, binary.LittleEndian, num)
    if err != nil {
        panic("faild to write a number")
    }
    os.Stdout.Write(buf.Bytes())
}

このプログラムはいかのリンクから写経したものです。

https://www.youtube.com/watch?v=8qg2b8ZZm_c

このコードをbuild & 実行し、出力結果をdataというファイルに格納する。

go build num.go
./num > data

dataファイルをxxdコマンドで確認する。 xxdの-bオプションはファイルの中身をbinary形式で見ることができる。

xxd -b data

サンプル実行結果

00000000: 00

文字列をxxdコマンドで見てみる

まずはdataaが入力されているかどうかを確認する。

echo a > data
cat data

次に、xxdコマンドで文字列データをバイナリ形式で見てみる

xxd data

00000000: 610a

これをascii表で確認してみます。 すると、61は16進数でaに対応し、意図した文字がバイナリとして保存されていることが分かりました。

Dec Hex Char
97  61  a

また、0aが16進数でどれに該当するか確認してみると、改行コードであることが分かると思います。