1. 程式人生 > 實用技巧 >Linux作業系統--程序/執行緒(2)

Linux作業系統--程序/執行緒(2)

前言

在本系列的上一篇博文裡,我已經介紹了程序/執行緒的基本含義以及一些相關資料結構,現在我們來看看Linux中程序的管理。

程序連結串列

Linux核心定義了一個list_head結構,資料結構定義

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

欄位next 和 prev 分別表示通用雙向連結串列向前和向後的指標元素!list_head欄位的指標中存放的是另一個list_head欄位的元素,而不是本身的資料結構地址。如圖

在我們上一篇部落格介紹到的程序描述符(task_struct)也有這個結構體,稱為程序連結串列

。程序連結串列是一個雙向迴圈連結串列,它把所有程序的描述符連結起來。每個task_struct結構都包含一個list_head型別的欄位tasks,這個結構的prev和next分別指向前面和後面的task_struct元素。

這個連結串列是一個迴圈的雙向連結串列,開始的時候只有init_task這一個程序,它是核心的第一個程序,它的初始化是通過靜態分配記憶體,"手動"(其它的程序初始化都是通過動態分配記憶體初始化的)進行的,每新建一個程序,就通過SET_LINKS巨集將該程序的task_struct結構加入到這條雙向連結串列中,不過要注意的是如果一個程序新建一個執行緒(不包括主執行緒),也就是輕量級程序,它是不會加入該連結串列的。通過巨集for_each_process可以從init_task開始遍歷所有的程序。

#define for_each_task(p)
for (p = &init_task ; (p = p->next_task) != &init_task ; )

可執行佇列(runqueue)

當核心尋找一個新程序在CPU上執行時,必須只考慮可執行程序(即處在TASK_RUNNING狀態的程序)。把可執行狀態的程序組成一個雙向迴圈連結串列,也叫可執行佇列(runqueue)。
在task_struct結構中定義了兩個指標。

struct task_struct *next_run, *prev_run;

由正在執行或是可以執行的,其程序狀態均為TASK_RUNNING的程序所組成的一個雙向迴圈連結串列,即run_queue就緒佇列。該連結串列的前後向指標用next_run和prev_run,連結串列的頭和尾都是init_task(即0號
程序)。
但是,為了實現在固定的時間內選出“最佳”的可執行程式,核心將可執行程序的優先順序劃分為0-139,併為此建立了140個可執行程序連結串列,用以組織處於TASK_RUNNING狀態的程序,每個程序優先權對應一個不同的連結串列


linux核心定義了一個prio_array_t型別的結構體來管理這140個連結串列。每個可執行的程序都在這140個連結串列中的一個,通過程序描述符結構中的run_list來實現,它也是一個list_head型別。enqueue_task是把程序描述符插入到某個可執行連結串列中,dequeue_task則從某個可執行連結串列中刪除該程序描述符。TASK_RUNNING狀態的prio_array_t型別的結構體是runqueue結構的arrays[1]成員。

pidhash連結串列

為了通過pid找到程序的描述符,如果直接遍歷程序間互聯的連結串列來查詢程序id為pid的程序描述符顯然是低效的,所以為了更為高效的查詢,linux核心使用了4個hash散列表來加快查詢,之所以使用4個散列表,是為了能根據不同的pid型別來查詢程序描述符,它們分別是程序的pid,執行緒組領頭程序的pid,程序組領頭程序的pid,會話領頭程序的pid。每個型別的散列表中是通過巨集pid_hashfn(x)來進行雜湊值的計算的。每個程序都可能同時處於這是個散列表中,所以在程序描述符中有一個型別為pid結構的pids成員,通過它可以將程序加入散列表中,pid結構中包含解決雜湊衝突的pid_chain成員,它是hlist_node型別的,還有一個是將相同pid鏈起來的pid_list,它是list_head型別。

struct pid_link {
    int nr;  // pid的數值
    struct hlist_node pid_chain;
    struct list_head pid_list;
}

struct task_struct {
    …
    struct pid_link pids[4];
    …
}

Linux 程序安全上下文 struct cred

   95  *
   96  * The parts of the context break down into two categories:
   97  *
   98  *  (1) The objective context of a task.  These parts are used when some other
   99  *      task is attempting to affect this one.
  100  *
  101  *  (2) The subjective context.  These details are used when the task is acting
  102  *      upon another object, be that a file, a task, a key or whatever.
  103  *
  104  * Note that some members of this structure belong to both categories - the
  105  * LSM security pointer for instance.
  106  *
  107  * A task has two security pointers.  task->real_cred points to the objective
  108  * context that defines that task's actual details.  The objective part of this
  109  * context is used whenever that task is acted upon.
  110  *
  111  * task->cred points to the subjective context that defines the details of how
  112  * that task is going to act upon another object.  This may be overridden
  113  * temporarily to point to another security context, but normally points to the
  114  * same context as task->real_cred.
  115  */
  116 struct cred {
  117         atomic_t        usage;
  118 #ifdef CONFIG_DEBUG_CREDENTIALS
  119         atomic_t        subscribers;    /* number of processes subscribed */
  120         void            *put_addr;
  121         unsigned        magic;
  122 #define CRED_MAGIC      0x43736564
  123 #define CRED_MAGIC_DEAD 0x44656144
  124 #endif
  125         uid_t           uid;            /* real UID of the task */
  126         gid_t           gid;            /* real GID of the task */
  127         uid_t           suid;           /* saved UID of the task */
  128         gid_t           sgid;           /* saved GID of the task */
  129         uid_t           euid;           /* effective UID of the task */
  130         gid_t           egid;           /* effective GID of the task */
  131         uid_t           fsuid;          /* UID for VFS ops */
  132         gid_t           fsgid;          /* GID for VFS ops */
  133         unsigned        securebits;     /* SUID-less security management */
  134         kernel_cap_t    cap_inheritable; /* caps our children can inherit */
  135         kernel_cap_t    cap_permitted;  /* caps we're permitted */
  136         kernel_cap_t    cap_effective;  /* caps we can actually use */
  137         kernel_cap_t    cap_bset;       /* capability bounding set */
  138 #ifdef CONFIG_KEYS
  139         unsigned char   jit_keyring;    /* default keyring to attach requested
  140                                          * keys to */
  141         struct key      *thread_keyring; /* keyring private to this thread */
  142         struct key      *request_key_auth; /* assumed request_key authority */
  143         struct thread_group_cred *tgcred; /* thread-group shared credentials */
  144 #endif
  145 #ifdef CONFIG_SECURITY
  146         void            *security;      /* subjective LSM security */
  147 #endif
  148         struct user_struct *user;       /* real user ID subscription */
  149         struct user_namespace *user_ns; /* cached user->user_ns */
  150         struct group_info *group_info;  /* supplementary groups for euid/fsgid */
  151         struct rcu_head rcu;            /* RCU deletion hook */
  152 };

正如uid,euid的關係一樣,task_struct也有兩種身份cred

struct task_struct{
 ...
 /* process credentials */
 const struct cred __rcu *real_cred; /* objective and real subjective task credentials (COW) */
 const struct cred __rcu *cred;  /* effective (overridable) subjective task credentials (COW) */
 ...
 }

各種id

uid 和 gid 都是成雙成對的,這裡就拿uid說明其作用:

  • uid 是建立程序的使用者的id,不是建立可執行程式的使用者id
  • euid 是程序執行過程中實時的ID(或者說動態獲取的ID)
  • suid 是儲存的euid切換之前的id,用於euid切換回來