進程(Process)是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操做系統結構的基礎。在早期面向進程設計的計算機結構中,進程是程序的基本執行實體;在當代面向線程設計的計算機結構中,進程是線程的容器。程序是指令、數據及其組織形式的描述,進程是程序的實體。css
動態性:進程的實質是程序在多道程序系統中的一次執行過程,進程是動態產生,動態消亡的。併發性:任何進程均可以同其餘進程一塊兒併發執行獨立性:進程是一個能獨立運行的基本單位,同時也是系統分配資源和調度的獨立單位;異步性:因爲進程間的相互制約,使進程具備執行的間斷性,即進程按各自獨立的、不可預知的速度向前推動結構特徵:進程由程序、數據和進程控制塊三部分組成。多個不一樣的進程能夠包含相同的程序:一個程序在不一樣的數據集裏就構成不一樣的進程,能獲得不一樣的結果;可是執行過程當中,程序不能發生改變。
Linux是一套無償使用和自由傳播的類Unix操做系統,是一個基於POSIX和UNIX的多用戶、多任務、支持多線程和多CPU的操做系統。它能運行主要的UNIX工具軟件、應用程序和網絡協議。它支持32位和64位硬件。Linux繼承了Unix以網絡爲核心的設計思想,是一個性能穩定的多用戶網絡操做系統。html
task_struct
結構體來描述一個進程的全部信息,結構體被定義在 include/linux/sched.h
中。進程狀態(State)
進程調度信息
標識符
進程通訊有關信息
進程連接信息
時間和定時器信息
文件系統信息
虛擬內存信息
頁面管理信息
對稱多處理機(SMP)信息
和處理器相關的環境(上下文)信息
其它
struct task_struct { volatile long state; //進程狀態 void *stack; //內存指針 atomic_t usage; unsigned int flags; //進程標號(進程名字) unsigned int ptrace; int lock_depth; //BLK 鎖深度 #ifdef CONFIG_SMP #ifdef __ARCH_WANT_UNLOCKED_CTXSW //配置多核多線程 int oncpu; #endif #endif int prio, static_prio, normal_prio; //進程的優先級 unsigned int rt_priority; //實時進程的優先級 const struct sched_class *sched_class; //調度器的指針 struct sched_entity se; //調度器 實例化的對象 struct sched_rt_entity rt; //實時 調度器的一個對象 #ifdef CONFIG_PREEMPT_NOTIFIERS //配置搶佔通知器 /* struct preempt_notifier列表 */ struct hlist_head preempt_notifiers; #endif /*fpu_count 裏面內容是若是一個浮點運算器被使用,它記錄着連續的上下文切換的次數,若是fpu_Count超過一個 臨界值,不怎麼工做的FPU會火力全開以致於當fpu_count超過 256次後才變得閒置下來,爲了解決這個問題,FPU 僅僅使用一段時間 */ unsigned char fpu_counter; //定義 fpu_count #ifdef CONFIG_BLK_DEV_IO_TRACE //配置 BLK 鎖開發版的輸入輸出跟蹤器 unsigned int btrace_seq; #endif unsigned int policy; cpumask_t cpus_allowed; #ifdef CONFIG_TREE_PREEMPT_RCU //配置搶佔樹,搶佔的結構體的讀寫機制,即RCU機制。 int rcu_read_lock_nesting; char rcu_read_unlock_special; struct rcu_node *rcu_blocked_node; struct list_head rcu_node_entry; #endif /* #ifdef CONFIG_TREE_PREEMPT_RCU */ #if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT) struct sched_info sched_info; //調度器的狀態 #endif struct list_head tasks; struct plist_node pushable_tasks; struct mm_struct *mm, *active_mm; //虛擬地址空間的結構體 //進程退出時getpid 就獲取status就是它。 int exit_state; //task 狀態 ,正常退出狀態 int exit_code, exit_signal; //退出信號 int pdeath_signal; //當成爲孤兒進程時發送信號 unsigned int personality; //代表進程的狀態 unsigned did_exec:1; unsigned in_execve:1; //第一個表已經調過了exec族函數,已經發生了進程的程序替換 第二個表明該進程正在調用execve函數 第三個 正在等待i/o設備 第四個 表示當fork生成子進程時,是否恢復了進程的默認優先級 unsigned in_iowait:1; /* 在分叉時恢復默認優先級/策略*/ unsigned sched_reset_on_fork:1; pid_t pid; pid_t tgid; #ifdef CONFIG_CC_STACKPROTECTOR //配置堆棧保護措施 unsigned long stack_canary; //canary值 保護編譯器 防止堆棧溢出 致使的返回地址被填充 #endif struct task_struct *real_parent; struct task_struct *parent; struct list_head children; //子節點和兄弟節點的定義 struct list_head sibling; struct task_struct *group_leader; //線程組的頭結點 struct list_head ptraced; //跟蹤器的頭結點,跟蹤器 跟蹤 進程的邏輯流,即PC指令流 struct list_head ptrace_entry; struct pid_link pids[PIDTYPE_MAX]; //定義 PID_LINK 結構體用它經過PID在哈希散列表中查找相應的task_struct struct list_head thread_group; //用來保存線程組的PID struct completion *vfork_done; int __user *set_child_tid; //指向用戶創造創立的線程的TID號 int __user *clear_child_tid; //指向被清除的線程的TID號 cputime_t utime, stime, utimescaled, stimescaled; cputime_t gtime; cputime_t prev_utime, prev_stime; unsigned long nvcsw, nivcsw; //上下文切換的次數 struct timespec start_time; struct timespec real_start_time; unsigned long min_flt, maj_flt; struct task_cputime cputime_expires; struct list_head cpu_timers[3]; const struct cred *real_cred; const struct cred *cred; struct mutex cred_guard_mutex; struct cred *replacement_session_keyring; char comm[TASK_COMM_LEN]; //文件系統信息 int link_count, total_link_count; #ifdef CONFIG_SYSVIPC //配置進程的通訊機制 struct sysv_sem sysvsem; #endif #ifdef CONFIG_DETECT_HUNG_TASK unsigned long last_switch_count; #endif struct thread_struct thread; //CPU特殊狀態的測試,線程結構體 struct fs_struct *fs; //fs 指向一個文件系統信息結構體,該結構體有文件系統的信息 //指向記錄打開文件信息的 結構體 struct files_struct *files; //命名空間的定義 struct nsproxy *nsproxy; //配置進程的信號處理 struct signal_struct *signal; //如下是普通訊號部分 struct sighand_struct *sighand; //這個指向 handler表 sigset_t blocked, real_blocked; //這個表示進程的屏蔽字 sigset_t saved_sigmask; struct sigpending pending; //pending表 unsigned long sas_ss_sp; // 如下是實時信號部分 size_t sas_ss_size; int (*notifier)(void *priv); void *notifier_data; sigset_t *notifier_mask; struct audit_context *audit_context; #ifdef CONFIG_AUDITSYSCALL // 配置系統調用 uid_t loginuid; unsigned int sessionid; #endif seccomp_t seccomp; #ifdef CONFIG_UTRACE struct utrace *utrace; unsigned long utrace_flags; #endif u32 parent_exec_id; u32 self_exec_id; /* 配置器保護措施配置 */ spinlock_t alloc_lock; #ifdef CONFIG_GENERIC_HARDIRQS struct irqaction *irqaction; #endif spinlock_t pi_lock; #ifdef CONFIG_RT_MUTEXES // 互斥的配置 struct plist_head pi_waiters; struct rt_mutex_waiter *pi_blocked_on; #endif #ifdef CONFIG_LOCKDEP // 死鎖模塊的配置 # define MAX_LOCK_DEPTH 48UL u64 curr_chain_key; int lockdep_depth; unsigned int lockdep_recursion; struct held_lock held_locks[MAX_LOCK_DEPTH]; gfp_t lockdep_reclaim_gfp; #endif // 文件系統的日誌信息 void *journal_info; struct bio *bio_list, **bio_tail; //VM 虛擬機的狀態 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; struct task_io_accounting ioac; #ifdef CONFIG_CPUSETS nodemask_t mems_allowed; //定義一個結構體 標誌 內存是否容許訪問 保護配置器的鎖的 #ifndef __GENKSYMS__ unsigned short cpuset_mem_spread_rotor; unsigned short cpuset_slab_spread_rotor; int mems_allowed_change_disable; #else int cpuset_mem_spread_rotor; int cpuset_slab_spread_rotor; #endif #endif #ifdef CONFIG_CGROUPS // 配置控制組信息 struct css_set *cgroups; struct list_head cg_list; #endif #endif };
#include <unistd.h> pid_t fork(void);
#include <unistd.h> pid_t vfork(void);
異常終止(3種):node
進程狀態反映進程執行過程的變化。這些狀態隨着進程的執行和外界條件的變化而轉換。在三態模型中,進程狀態分爲三個基本狀態,即運行態,就緒態,阻塞態。在五態模型中,進程分爲新建態、終止態,運行態,就緒態,阻塞態。linux
內核表示算法 |
含義ubuntu |
TASK_RUNNINGwindows |
可運行服務器 |
TASK_INTERRUPTIBLE網絡 |
可中斷的等待狀態session |
TASK_UNINTERRUPTIBLE |
不可中斷的等待狀態 |
TASK_ZOMBIE |
僵死 |
TASK_STOPPED |
暫停 |
TASK_SWAPPING |
換入/換出 |
不管是在批處理系統仍是分時系統中,用戶進程數通常都多於處理機數、這將致使它們互相爭奪處理機。另外,系統進程也一樣須要使用處理機。這就要求進程調度程序按必定的策略,動態地把處理機分配給處於就緒隊列中的某一個進程,以使之執行。
Linux一開始的調度器是複雜度爲O(n)的始調度算法, 這個算法的缺點是遍歷的時間太長,當內核中有不少任務時,調度器自己就會耗費很多時間,因此,從linux2.5開始引入赫赫有名的O(1)調度器。然而,隨着時代的迅速發展,O(1)調度器又被另外一個更優秀的調度器取代了,它就是CFS調度器(Completely Fair Scheduler) 。這是在2.6內核中引入的,具體爲2.6.23,即今後版本開始,內核使用CFS做爲它的默認調度器。
CFS 背後的主要想法是維護爲任務提供處理器時間方面的平衡(公平性)。這意味着應給進程分配至關數量的處理器。分給某個任務的時間失去平衡時(意味着一個或多個任務相對於其餘任務而言未被給予至關數量的時間),應給失去平衡的任務分配時間,讓其執行。
要實現平衡,CFS 在叫作虛擬運行時的地方維持提供給某個任務的時間量。任務的虛擬運行時越小, 意味着任務被容許訪問服務器的時間越短 — 其對處理器的需求越高。CFS 還包含睡眠公平概念以便確保那些目前沒有運行的 任務(例如,等待 I/O)在其最終須要時得到至關份額的處理器。
可是與以前的 Linux 調度器不一樣,它沒有將任務維護在運行隊列中,CFS 維護了一個以時間爲順序的紅黑樹(以下圖)。 紅黑樹是一個樹,具備不少有趣、有用的屬性。首先,它是自平衡的,這意味着樹上沒有路徑比任何其餘路徑長兩倍以上。 第二,樹上的運行按 O(log n) 時間發生(其中 n 是樹中節點的數量)。這意味着能夠快速高效地插入或刪除任務。
任務存儲在以時間爲順序的紅黑樹中(由 sched_entity
對象表示),對處理器需求最多的任務 (最低虛擬運行時)存儲在樹的左側,處理器需求最少的任務(最高虛擬運行時)存儲在樹的右側。 爲了公平,調度器而後選取紅黑樹最左端的節點調度爲下一個以便保持公平性。任務經過將其運行時間添加到虛擬運行時, 說明其佔用 CPU 的時間,而後若是可運行,再插回到樹中。這樣,樹左側的任務就被給予時間運行了,樹的內容從右側遷移到左側以保持公平。 所以,每一個可運行的任務都會追趕其餘任務以維持整個可運行任務集合的執行平衡。
task_struct任務結構和紅黑樹的結構層次
樹的根經過 rb_root
元素經過 cfs_rq
結構(在 ./kernel/sched.c 中)引用。紅黑樹的葉子不包含信息,可是內部節點表明一個或多個可運行的任務。紅黑樹的每一個節點都由 rb_node
表示,它只包含子引用和父對象的顏色。 rb_node
包含在 sched_entity
結構中,該結構包含 rb_node
引用、負載權重以及各類統計數據。最重要的是,sched_entity
包含 vruntime
(64 位字段),它表示任務運行的時間量,並做爲紅黑樹的索引。 最後,task_struct
位於頂端,它完整地描述任務幷包含 sched_entity
結構。
就 CFS 部分而言,調度函數很是簡單。 在 ./kernel/sched.c 中,有通用 schedule()
函數,它會先搶佔當前運行任務(除非它經過 yield()
代碼先搶佔本身)。注意 CFS 沒有真正的時間切片概念用於搶佔,由於搶佔時間是可變的。 當前運行任務(如今被搶佔的任務)經過對 put_prev_task
調用(經過調度類)返回到紅黑樹。 當schedule
函數開始肯定下一個要調度的任務時,它會調用 pick_next_task
函數。此函數也是通用的(在 ./kernel/sched.c 中),但它會經過調度器類調用 CFS 調度器。 CFS 中的 pick_next_task
函數能夠在 ./kernel/sched_fair.c(稱爲 pick_next_task_fair()
)中找到。 此函數只是從紅黑樹中獲取最左端的任務並返回相關 sched_entity
。經過此引用,一個簡單的 task_of()
調用肯定返回的 task_struct
引用。通用調度器最後爲此任務提供處理器。
徹底公平運行隊列:描述運行在同一個cpu上的處於TASK_RUNNING狀態的普通進程的各類運行信息
struct cfs_rq { struct load_weight load; //運行隊列總的進程權重 unsigned int nr_running, h_nr_running; //進程的個數 u64 exec_clock; //運行的時鐘 u64 min_vruntime; //該cpu運行隊列的vruntime推動值, 通常是紅黑樹中最小的vruntime值 struct rb_root tasks_timeline; //紅黑樹的根結點 struct rb_node *rb_leftmost; //指向vruntime值最小的結點 //當前運行進程, 下一個將要調度的進程, 立刻要搶佔的進程, struct sched_entity *curr, *next, *last, *skip; struct rq *rq; //系統中有普通進程的運行隊列, 實時進程的運行隊列, 這些隊列都包含在rq運行隊列中 ... };
調度實體:記錄一個進程的運行狀態信息
struct sched_entity { struct load_weight load; //進程的權重 struct rb_node run_node; //運行隊列中的紅黑樹結點 struct list_head group_node; //與組調度有關 unsigned int on_rq; //進程如今是否處於TASK_RUNNING狀態 u64 exec_start; //一個調度tick的開始時間 u64 sum_exec_runtime; //進程從出生開始, 已經運行的實際時間 u64 vruntime; //虛擬運行時間 u64 prev_sum_exec_runtime; //本次調度以前, 進程已經運行的實際時間 struct sched_entity *parent; //組調度中的父進程 struct cfs_rq *cfs_rq; //進程此時在哪一個運行隊列中 };
建立進程,設置新進程的vruntime值,task_fork_fair()函數部分代碼:
static void task_fork_fair(struct task_struct *p) { struct cfs_rq *cfs_rq; struct sched_entity *se = &p->se, *curr; int this_cpu = smp_processor_id(); struct rq *rq = this_rq(); unsigned long flags; raw_spin_lock_irqsave(&rq->lock, flags); update_rq_clock(rq); cfs_rq = task_cfs_rq(current); curr = cfs_rq->curr; rcu_read_lock(); __set_task_cpu(p, this_cpu); //設置新進程在哪一個cpu上運行 rcu_read_unlock(); update_curr(cfs_rq); //更新當前進程的vruntime值 if (curr) se->vruntime = curr->vruntime; //先以父進程的vruntime爲基礎 place_entity(cfs_rq, se, 1); //設置新進程的vruntime值, 1表示是新進程 if (sysctl_sched_child_runs_first && curr && entity_before(curr, se)) { //sysctl_sched_child_runs_first值表示是否設置了讓子進程先運行 swap(curr->vruntime, se->vruntime); //當子進程的vruntime值大於父進程的vruntime時, 交換兩個進程的vruntime值 resched_task(rq->curr); //設置從新調度標誌TIF_NEED_RESCHED } se->vruntime -= cfs_rq->min_vruntime; //防止新進程運行時是在其餘cpu上運行的, 這樣在加入另外一個cfs_rq時再加上另外一個cfs_rq隊列的min_vruntime值便可(具體能夠看enqueue_entity函數) raw_spin_unlock_irqrestore(&rq->lock, flags); }
進程的主動調度函數是schedule():
asmlinkage void __sched schedule(void) { struct task_struct *prev, *next; unsigned long *switch_count; struct rq *rq; int cpu; need_resched: preempt_disable(); //在這裏面被搶佔可能出現問題,先禁止它! cpu = smp_processor_id(); rq = cpu_rq(cpu); rcu_qsctr_inc(cpu); prev = rq->curr; switch_count = &prev->nivcsw; release_kernel_lock(prev); need_resched_nonpreemptible: spin_lock_irq(&rq->lock); update_rq_clock(rq); clear_tsk_need_resched(prev); //清除須要調度的位 if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) { if (unlikely(signal_pending_state(prev->state, prev))) prev->state = TASK_RUNNING; else deactivate_task(rq, prev, 1); //出隊, 此處主要是把prev->on_rq賦值爲0, 由於當前進程原本就沒在紅黑樹中. on_rq爲0後, 後面的put_prev_task函數就不會把當前進程加入紅黑樹了 switch_count = &prev->nvcsw; } if (unlikely(!rq->nr_running)) idle_balance(cpu, rq); prev->sched_class->put_prev_task(rq, prev); //把當前進程加入紅黑樹中 next = pick_next_task(rq, prev); //從紅黑樹中挑選出下一個要運行的進程, 並將其設置爲當前進程 if (likely(prev != next)) { sched_info_switch(prev, next); rq->nr_switches++; rq->curr = next; ++*switch_count; //完成進程切換 context_switch(rq, prev, next); cpu = smp_processor_id(); rq = cpu_rq(cpu); } else spin_unlock_irq(&rq->lock); if (unlikely(reacquire_kernel_lock(current) < 0)) goto need_resched_nonpreemptible; preempt_enable_no_resched(); //這裏新進程也可能有TIF_NEED_RESCHED標誌,若是新進程也須要調度則再調度一次 if (unlikely(test_thread_flag(TIF_NEED_RESCHED))) goto need_resched; }
進程是操做系統的核心之一,對於 Linux 技術而言,唯一不變的就是永恆的變化,不斷追求更優更有效率的算法,讓計算機給人們提供更好的服務。咱們從進程模型中學習到的理論知識與算法的更新模式等,都應與現實結合,好好實踐。
https://baike.baidu.com/item/%E8%BF%9B%E7%A8%8B/382503?fr=aladdin
https://baike.baidu.com/item/linux/27050
https://www.cnblogs.com/hanxiaoyu/p/5549212.html
https://baike.baidu.com/item/%E8%BF%9B%E7%A8%8B%E8%B0%83%E5%BA%A6
https://www.ibm.com/developerworks/cn/linux/l-completely-fair-scheduler/index.html?ca=drs-cn-0125