totofugaのブログ

ネットワークとかc言語、perlの話。

linuxカーネルのpid管理(取得周り)

カーネルのpid管理と取得周りを調べてみた

PID名前空間

Linuxではpid_namespace(詳しくはman 7参照)に対応しているため、 1つのプロセスに対して複数のプロセスIDを複数持てるようになっている。

pid_namespaceは階層構造になっていて、 子のプロセスIDは同一プロセスでもそれぞれの名前空間で別のプロセスidとなる f:id:totofuga:20170624091642p:plain

親で作成されたのプロセスIDは子からは見えないようになっている f:id:totofuga:20170624091957p:plain

したがってプロセスIDは作成された階層の深度分プロセスIDを持つようになっている

カーネルでの表現

カーネル内部ではPIDはpidとupidという構造で表される

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

struct upid {                                                             
    int nr;  // 実際のID
    struct pid_namespace *ns; // 属しているネームスペース
    struct hlist_node pid_chain;                                          
};

struct pid                                 
{                                         
    atomic_t count;                        
    unsigned int level;  // このプロセスが作成された時のname_spaceの深度
    struct hlist_head tasks[PIDTYPE_MAX];  // pid構造体のプロセス、プロセスグループ、セッションに属するタスク
    struct rcu_head rcu;                   
    struct upid numbers[1]; // 可変長でlevel分だけupidを持つ
};

pid構造体のIDはネームスペース毎にネームスペース毎に持つため、所属している深度分のupidを持つようになっていて、upidに実際の番号を持っている。

pid->tasksメンバ

tasksメンバはそのpidのプロセス、プロセスグループ、セッションに属するtask_structの一覧を管理する f:id:totofuga:20170624102843p:plain

pid構造体に属するPIDTYPE_PIDに結びつくプロセスは1つしかないので、 1つまでしか入らないことが保証されている。

IDからpidへの変換処理

IDからpidへの変換処理はよく行われるので、 pid_hashというIDとネームスペースのポインタをキーにしたハッシュを使い処理を高速化している

関数としてはネームスペースを渡すバージョンと、現在実行中のタスクのネームスペースを使うものの二種類がある。

extern struct pid *find_pid_ns(int nr, struct pid_namespace *ns);
extern struct pid *find_vpid(int nr);

pidからidへの変換処理

ネームスペースのlevelからupidを探し出してnrを返す関数

こちらもネームスペースを渡すバージョンと、現在実行中のタスクのネームスペースを使うものの二種類がある。

pid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns);
pid_t pid_vnr(struct pid *pid);                            

現在実行中のプロセスのネームスペース取得方法

// 呼び出し元
task_active_pid_ns(current->group_leader); // currentはスレッドに対応するのでプロセスのtask_structで呼び出し

struct pid_namespace *task_active_pid_ns(struct task_struct *tsk)  
{
    return ns_of_pid(task_pid(tsk)); // task_pidでpid構造体を取得してpid構造体からネームスペースを取得する
}                              

static inline struct pid *task_pid(struct task_struct *task)  
{
    return task->pids[PIDTYPE_PID].pid; // カレントプロセスのPID構造体を取得
}

static inline struct pid_namespace *ns_of_pid(struct pid *pid) 
{                                                              
    struct pid_namespace *ns = NULL;
    if (pid)                                                   
        ns = pid->numbers[pid->level].ns; // カレントプロセスの最大深度 = カレントプロセスが作成された名前空間となる。                      
    return ns;                                                 
}