《神佑釋放》主機版走私商重新整理點整理
程序的基本概念
程序由程式段、資料段和PCB(Process Control Block)三部分組成。
程序是系統進行資源分配和排程的一個最小單位。
程序的三種基本狀態
-
就緒態
就緒佇列
-
執行態
正在執行的程序數量與CPU核數有關
-
阻塞態
執行態的程序因為一些事件無法繼續執行而轉變為阻塞態
程序三種狀態間的互相轉換
就緒態的程序只需要等待OS給它分配處理機資源就可以執行;執行態的程序如果因為時間片用完,將回到就緒態,如果因為其他事件無法繼續執行,將回到阻塞態;阻塞態的程序當事件處理後,回到就緒態。
掛起態
與因為其他事件而被迫停止的阻塞態程序不同,掛起態的程序由使用者主動讓程序停止。
一般用於程序的檢查以及一些不重要的程序的掛起以節省系統資源。
被掛起的程序被啟用後將回歸其之前的狀態。
建立態和終止態
建立態:PCB已經被建立,但是還沒有為其分配資源。
終止態:等待OS進行善後處理,將PCB清零並歸還空間。
PCB
PCB是每個程序用於控制的資料結構。當OS要排程某程序執行時,要從該程序的PCB中查出其現行狀態和優先順序;排程某程序執行時,要從該程序的PCB的資訊中恢復現場。
由於PCB的被訪問頻率很高,所以需要常駐記憶體,所有PCB通過若干個連結串列連結起來,放在專門的PCB區內。
PCB資訊
-
程序識別符號
-
處理機狀態
儲存處理機執行時所需要的暫存器狀態、指令計數器、程式狀態字、使用者棧指標。
-
程序排程資訊
程序執行狀態、程序優先順序、其它排程相關資訊、事件(程序進入阻塞狀態所等待的事件)
-
程序控制資訊
程式和資料的地址、程序同步和通訊機制、資源清單、連結指標。
程序控制
程序控制一般由核心中的原語來實現,原語具有原子性。
程序的建立
作業系統的所有程序都由一個初始程序(init)建立而來,每個程序也可以繼續建立程序。
子程序可以繼承父程序所擁有的資源,當子程序被撤銷時,應將其從父程序所獲得的資源歸還給父程序。
建立程序步驟:
- 申請一個唯一的程序識別符號,並申請一個空白PCB
- 為新程序分配資源
- 初始化PCB
- 將新程序插入就緒佇列
程序的終止
- 根據識別符號從PCB集合中檢索出該程序的PCB,讀出該程序的狀態
- 若處於執行狀態,則立即停止執行,並置排程標誌為真,指示該程序被終止後應重新進行排程
- 若該程序還有子孫程序,還應將其所有子孫程序予以終止
- 將被終止程序的所有全部資源,或歸還給父程序,或歸還給系統
- 將PCB從佇列中移除
程序同步
訊號量機制
-
記錄型訊號量:對於一次wait操作,訊號量減1, 進行一次signal操作,訊號量加1。當訊號量小於等於0時,程序必須阻塞以等待訊號量。
-
AND型訊號量:
如果需要同時共享多個訊號量,用上面的方法可能導致死鎖。
AND型訊號量的思想是:只要有一種訊號量的數量不足以分配給某個程序,則完全不予以分配。
-
訊號量集:
將同一個訊號量擴成n個,可以一次消耗n個訊號量
管程機制
管程:代表共享資源的資料結構,以及對該共享資料結構操作的一組過程。
管程對於每個需要使用共享變數的程序,都將其放在阻塞佇列中。
條件變數
當一個程序呼叫了管程,在管程中被阻塞或掛起;如果該程序不釋放管程,將導致其他程序無法進入管程。
因此,管程設定了多種條件變數,每個程序訪問管程時設定一個條件變數,並有兩種操作:
- x.wait:正在呼叫管程的程序因x條件需要被阻塞或掛起,則呼叫x.wait將自己插入到x條件的等待佇列上,並釋放管程,直到x條件發生變化
- x.signal:正在呼叫管程的程序發現x條件發生了變化,則呼叫x.signal,重新啟動一個因為x條件而阻塞或掛起的程序。
程序通訊
Unix環境中可用的程序間通訊方式主要包括:
- 管道
- 協同程序
- FIFO
- IPC
- 訊息佇列
- 訊號量
- 共享儲存
執行緒
執行緒是排程和分派的基本單位。
執行緒具有併發性。
執行緒的實現方式
-
核心支援執行緒
執行緒的建立、撤銷和切換等都是在核心中進行,還為每個執行緒設定了一個執行緒控制塊。
優點:
- 多處理器系統可以同時排程同一程序中多個執行緒並行執行
- 具有很小的資料結構和堆疊,執行緒的切換比較快
核心支援執行緒可以直接被排程
-
使用者級執行緒
全程由使用者程序實現,核心不知道執行緒的存在,不管是執行緒切換還是執行緒通訊都由使用者程式實現。
任一時刻,每個程序只能有一個執行緒被執行,因為使用者級執行緒的排程單位依然是程序。
-
輕量級程序LWP(Light Weight Process)
輕量級程序同樣屬於程序的一種,具有單獨的程序識別符號pid, 只是其具有最小的執行上下文和排程程式所需的統計資訊,且多與其他LWP共享資源,因此非常輕量級。
通常可以設定輕量級程序池來實現使用者執行緒與核心的通訊。
Linux中的程序與程序控制
在Linux中PCB對應的資料結構為task_struct
.
在Linux2.6其定義為:
struct task_struct {
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
void *stack;
atomic_t usage;
unsigned int flags; /* per process flags, defined below */
unsigned int ptrace;
int lock_depth; /* BKL lock depth */
#ifdef CONFIG_SMP
#ifdef __ARCH_WANT_UNLOCKED_CTXSW
int oncpu;
#endif
#endif
int prio, static_prio, normal_prio;
struct list_head run_list;
const struct sched_class *sched_class;
struct sched_entity se;
#ifdef CONFIG_PREEMPT_NOTIFIERS
/* list of struct preempt_notifier: */
struct hlist_head preempt_notifiers;
#endif
unsigned short ioprio;
unsigned char fpu_counter;
s8 oomkilladj; /* OOM kill score adjustment (bit shift). */
#ifdef CONFIG_BLK_DEV_IO_TRACE
unsigned int btrace_seq;
#endif
unsigned int policy;
cpumask_t cpus_allowed;
unsigned int time_slice;
#if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT)
struct sched_info sched_info;
#endif
struct list_head tasks;
/*
* ptrace_list/ptrace_children forms the list of my children
* that were stolen by a ptracer.
*/
struct list_head ptrace_children;
struct list_head ptrace_list;
struct mm_struct *mm, *active_mm;
/* task state */
struct linux_binfmt *binfmt;
int exit_state;
int exit_code, exit_signal;
int pdeath_signal; /* The signal sent when the parent dies */
/* ??? */
unsigned int personality;
unsigned did_exec:1;
pid_t pid;
pid_t tgid;
#ifdef CONFIG_CC_STACKPROTECTOR
/* Canary value for the -fstack-protector gcc feature */
unsigned long stack_canary;
#endif
struct task_struct *real_parent; /* real parent process (when being debugged) */
struct task_struct *parent; /* parent process */
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 */
/* PID/PID hash table linkage. */
struct pid_link pids[PIDTYPE_MAX];
struct list_head thread_group;
struct completion *vfork_done; /* for vfork() */
int __user *set_child_tid; /* CLONE_CHILD_SETTID */
int __user *clear_child_tid; /* CLONE_CHILD_CLEARTID */
unsigned int rt_priority;
cputime_t utime, stime, utimescaled, stimescaled;
cputime_t gtime;
cputime_t prev_utime, prev_stime;
unsigned long nvcsw, nivcsw; /* context switch counts */
struct timespec start_time; /* monotonic time */
struct timespec real_start_time; /* boot based time */
unsigned long min_flt, maj_flt;
cputime_t it_prof_expires, it_virt_expires;
unsigned long long it_sched_expires;
struct list_head cpu_timers[3];
/* process credentials */
uid_t uid,euid,suid,fsuid;
gid_t gid,egid,sgid,fsgid;
struct group_info *group_info;
kernel_cap_t cap_effective, cap_inheritable, cap_permitted;
unsigned keep_capabilities:1;
struct user_struct *user;
#ifdef CONFIG_KEYS
struct key *request_key_auth; /* assumed request_key authority */
struct key *thread_keyring; /* keyring private to this thread */
unsigned char jit_keyring; /* default keyring to attach requested keys to */
#endif
char comm[TASK_COMM_LEN];
int link_count, total_link_count;
#ifdef CONFIG_SYSVIPC
/* ipc stuff */
struct sysv_sem sysvsem;
#endif
/* CPU-specific state of this task */
struct thread_struct thread;
/* filesystem information */
struct fs_struct *fs;
/* open file information */
struct files_struct *files;
/* namespaces */
struct nsproxy *nsproxy;
/* signal handlers */
struct signal_struct *signal;
struct sighand_struct *sighand;
sigset_t blocked, real_blocked;
sigset_t saved_sigmask; /* To be restored with TIF_RESTORE_SIGMASK */
struct sigpending pending;
unsigned long sas_ss_sp;
size_t sas_ss_size;
int (*notifier)(void *priv);
void *notifier_data;
sigset_t *notifier_mask;
#ifdef CONFIG_SECURITY
void *security;
#endif
struct audit_context *audit_context;
seccomp_t seccomp;
/* Thread group tracking */
u32 parent_exec_id;
u32 self_exec_id;
/* Protection of (de-)allocation: mm, files, fs, tty, keyrings */
spinlock_t alloc_lock;
/* Protection of the PI data structures: */
spinlock_t pi_lock;
#ifdef CONFIG_RT_MUTEXES
/* PI waiters blocked on a rt_mutex held by this task */
struct plist_head pi_waiters;
/* Deadlock detection and priority inheritance handling */
struct rt_mutex_waiter *pi_blocked_on;
#endif
#ifdef CONFIG_DEBUG_MUTEXES
/* mutex deadlock detection */
struct mutex_waiter *blocked_on;
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
unsigned int irq_events;
int hardirqs_enabled;
unsigned long hardirq_enable_ip;
unsigned int hardirq_enable_event;
unsigned long hardirq_disable_ip;
unsigned int hardirq_disable_event;
int softirqs_enabled;
unsigned long softirq_disable_ip;
unsigned int softirq_disable_event;
unsigned long softirq_enable_ip;
unsigned int softirq_enable_event;
int hardirq_context;
int softirq_context;
#endif
#ifdef CONFIG_LOCKDEP
# define MAX_LOCK_DEPTH 30UL
u64 curr_chain_key;
int lockdep_depth;
struct held_lock held_locks[MAX_LOCK_DEPTH];
unsigned int lockdep_recursion;
#endif
/* journalling filesystem info */
void *journal_info;
/* stacked block device info */
struct bio *bio_list, **bio_tail;
/* VM state */
struct reclaim_state *reclaim_state;
struct backing_dev_info *backing_dev_info;
struct io_context *io_context;
unsigned long ptrace_message;
siginfo_t *last_siginfo; /* For ptrace use. */
#ifdef CONFIG_TASK_XACCT
/* i/o counters(bytes read/written, #syscalls */
u64 rchar, wchar, syscr, syscw;
#endif
struct task_io_accounting ioac;
#if defined(CONFIG_TASK_XACCT)
u64 acct_rss_mem1; /* accumulated rss usage */
u64 acct_vm_mem1; /* accumulated virtual memory usage */
cputime_t acct_stimexpd;/* stime since last update */
#endif
#ifdef CONFIG_NUMA
struct mempolicy *mempolicy;
short il_next;
#endif
#ifdef CONFIG_CPUSETS
nodemask_t mems_allowed;
int cpuset_mems_generation;
int cpuset_mem_spread_rotor;
#endif
#ifdef CONFIG_CGROUPS
/* Control Group info protected by css_set_lock */
struct css_set *cgroups;
/* cg_list protected by css_set_lock and tsk->alloc_lock */
struct list_head cg_list;
#endif
#ifdef CONFIG_FUTEX
struct robust_list_head __user *robust_list;
#ifdef CONFIG_COMPAT
struct compat_robust_list_head __user *compat_robust_list;
#endif
struct list_head pi_state_list;
struct futex_pi_state *pi_state_cache;
#endif
atomic_t fs_excl; /* holding fs exclusive resources */
struct rcu_head rcu;
/*
* cache last used pipe for splice
*/
struct pipe_inode_info *splice_pipe;
#ifdef CONFIG_TASK_DELAY_ACCT
struct task_delay_info *delays;
#endif
#ifdef CONFIG_FAULT_INJECTION
int make_it_fail;
#endif
struct prop_local_single dirties;
};
state欄位描述了程序所處的狀態:
-
可執行 TASK_RUNNING
程序要麼在CPU上執行,要麼準備執行
-
可中斷的等待狀態 TASK_INTERRUPTIBLE
程序被掛起,直到某個條件變為真,產生一個硬體中斷
-
不可中斷的等待狀態 TASK_UNINTERRUPTIBLE
把訊號傳遞到掛起程序不能改變它的狀態
-
暫停狀態 TASK_STOPPED
程序的執行被掛起
-
追蹤狀態 TASK_TRACED
用於debugger程式追蹤程式執行
程序描述符處理
為了核心能夠迅速找到當前程序的pid,Linux把兩個不同的資料結構緊湊地存放在一個單獨為程序分配的儲存區域內:一個是核心態的程序堆疊,另一個是緊挨程序描述符的執行緒描述符thread_info
. 這兩個資料結構放在兩個連續的頁框中,執行緒描述符位於低地址端,而核心態的程序堆疊從高地址端開始向低地址端增長,並採用了額外的棧來防止溢位。CPU內有專門的暫存器用於存放棧頂單元的地址。
由於兩個頁框在選址時總是選擇 \(2^{13}\) 倍數的地址,所以執行緒描述符的開始地址很容易得到,而執行緒描述符和程序描述符內部都有指標互相關聯,所以核心很容易就得到了當前程序的程序描述符指標。
程序連結串列
為了能夠方便地處理所有程序,核心定義了 list_head
資料結構,其可以作為一個雙向連結串列的節點將所有程序描述符串聯起來。
同時,為了能夠迅速找出下一個執行的程序,Linux又建立了多個可執行程序連結串列,每個程序連結串列的程序優先順序相同(類Unix系統總共有40個優先順序,從-20到19優先順序逐漸降低)。對於多處理器系統,每個CPU都要維護多個可執行程序連結串列。
程序間的關係
程序描述符有多個欄位用於表示程序間的關係:
- real_parent: 指向建立了P程序的描述符,如果P程序不再存在,就指向init程序
- parent:指向P的當前父程序,通常與real_parent一致
- children:子程序連結串列的頭部,連結串列中所有的元素都是P程序建立的子程序
- sibling:指向兄弟程序連結串列中的下一個元素,這些兄弟程序的父程序都是P
等待佇列
等待佇列代表一組睡眠的程序,當某一條件為真時,由核心喚醒。一般根據不同的等待時間分為不同的等待佇列。
另一方面,如果程序是因為等待互斥資源而進入等待佇列,則由核心選擇性地喚醒,而非互斥程序總是在事件發生時喚醒。
程序切換
為了控制程序的執行,核心必須有能力掛起正在執行的程序,並恢復之前被掛起的程序,這種行為被稱為程序切換、任務切換或上下文切換。
硬體上下文(hardware context):程序恢復之前必須裝入暫存器的一組資料。
程序切換隻發生在核心態,在執行程序切換之前,使用者態程序使用的上下文都儲存在核心態堆疊上。
thread欄位
每個程序描述符包含一個型別為 thread_struct
的 thread欄位,只要程序被切換出去,核心就將其硬體上下文儲存在這個結構中。這個結構中的欄位涉及大部分CPU暫存器。
執行程序切換
每個程序切換由兩步組成:
- 切換頁全域性目錄以安裝一個新的地址空間
- 切換核心態堆疊和硬體上下文
switch_to macro
程序切換的第二步由switch_to巨集組成,其通常有三個引數:prev, next和last.
prev表示換出程序,next表示換進程序,last則是一個輸出引數。因為換出程序的執行流被停止後,需要依靠另一個程序(通常不同於換進程序)才能啟用。即相當於將換出程序安排在last對應的程序之後執行。
建立程序
傳統說法的Unix子程序將會複製父程序的所有資源將會導致效率非常低,因為大多數情況下子程序根本不需要父程序所有的資源。
現代Unix通過下列三種機制解決這個問題:
- 寫時複製技術:子程序與父程序共享物理頁,只有兩者之一試圖寫入時,才會複製一個物理頁給另一個程序
- 輕量級程序允許父子程序共享程序在核心的很多資料結構,這也是Unix對於執行緒的實現方法
vfork()
系統呼叫建立的程序能共享父程序的記憶體地址空間,父程序的執行將被阻塞,直到子程序退出