date: 2014-10-14 10:16linux
這四個要素是必要條件,缺了其中任何一條都不能稱其爲「進程」。
若是沒有第4條那就是「用戶線程」(借用父進程的用戶空間),更進一步,若是財產登記卡task_struct 結構中的mm字段爲NULL,那就是內核線程了(第二章看到的kswapd就是內核線程)。不要把這裏的線程與某些系統在用戶空間同一進程內實現的「線程」相混淆,那種線程顯然不擁有專用獨立的系統空間堆棧,也沒有對應的task_struct結構,也不做爲一個調度單位受內核調度。既然linux內核提供了對線程的支持,通常也就不必再在進程內部即用戶空間中自行實現線程。
另外一方面,進程與線程的區分也不是十分嚴格,通常在講到進程時經常也包括了線程。 Linux系統運行時的第一個進程(init進程)是在初始化階段捏造出來的,而此後的進程都是由一個也已存在的進程「細胞分裂」出來的(fork系統調用或者clone系統調用),從這個意義上講,init進程可稱爲全部進程的「祖宗」。數組
每一個進程都有一個task_struct結構與系統空間堆棧,這兩者缺一不可,而且具備緊密的聯繫,因此物理存儲空間也連在一塊兒。內核在爲每一個進程分配一個task_struct結構分配空間時,實際上分配了兩個連續的物理頁面(共8192字節)。這兩個頁面的底部用做進程的task_struct結構,結構之上的空間就用做系統空間堆棧,以下圖:安全
注意:系統空間堆棧不像用戶空間堆棧那樣能夠在運行時動態擴展,而是靜態地肯定了的。因此,在中斷服務程序、內核軟中斷服務程序以及其餘設備驅動程序的設計中,不能讓函數嵌套太深,也不能使用太多、太大的局部變量,防止系統空間堆棧被耗盡。 task_struct與進程系統空間的這種特殊安排,決定了內核中一些宏操做的定義。 首先是爲進程分配和釋放task_struct結構的操做:session
<include/asm/processor.h> #define THREAD_SIZE (2*PAGE_SIZE) #define alloc_task_struct() ((struct task_struct *) _get_free_pages(GFP_KERNEL,1)) #define free_task_struct(p) free_pages((unsigned long) (p), 1)
THREAD_SIZE被定義成兩個page的大小。
alloc_task_struct()分配了兩個連續頁面,並在兩個頁面的起始地址處,安放task_struct結構。注意,_get_free_pages中的第二個參數1表示分配2^1即兩個頁面。 其次,當進程在系統空間運行時,經常須要訪問當前進程的task_struct結構,爲此目的,內核定義了一個宏current。定義以下:數據結構
<include/asm/current.h> static inline struct task_struct * get_current(void) { struct task_struct *current; __asm__("andl %%esp,%0; ":"=r" (current) : "0" (~8191UL)); return current; } #define current get_current()
current最終調用get_current()函數,後者使用了嵌入式彙編代碼,相信你們如今讀來已經不是什麼難事了。這裏的輸入部是一個當即數(~8191UL)即8191取反(取反以後爲0xfffffe00),輸出部爲局部變量current,輸入部與輸出部共用同一個寄存器。指令部中,將進程系統空間堆棧的棧頂指針esp與0xfffffe000位與。由於task_struct的地址與8K(兩個頁面的大小)的邊界對齊,而進程系統空間堆棧棧頂指針esp在8K空間以內,0xfffffe00爲8K減1再求反,用做掩碼,esp與該掩碼位與便可獲得esp所在8K空間的起始地址,即task_struct結構的地址。 與此相似,在進入中斷和系統調用時所引用的宏操做GET_CURRENT,在<include/asm/hw_irq.h>中定義:app
#define GET_CURRENT \ "movl %esp, %ebx\n\t" \ "andl $-8192, %ebx\n\t"
task_struct結構定義在<include/linux/sched.h>文件中:dom
struct task_struct { volatile long state; unsigned long flags; int sigpending; mm_segment_t addr_limit; struct exec_domain *exec_domain; volatile long need_resched; unsigned long ptrace; int lock_depth; long counter; long nice; unsigned long policy; struct mm_struct *mm; int has_cpu, processor; unsigned long cpus_allowed; struct list_head run_list; unsigned long sleep_time; struct task_struct *next_task, *prev_task; struct mm_struct *active_mm; struct linux_binfmt *binfmt; int exit_code, exit_signal; int pdeath_signal; unsigned long personality; int dumpable:1; int did_exec:1; pid_t pid; pid_t pgrp; pid_t tty_old_pgrp; pid_t session; pid_t tgid; int leader; struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr, *p_osptr; struct list_head thread_group; struct task_struct *pidhash_next; struct task_struct **pidhash_pprev; wait_queue_head_t wait_chldexit; struct semaphore *vfork_sem; unsigned long rt_priority; unsigned long it_real_value, it_prof_value, it_virt_value; unsigned long it_real_incr, it_prof_incr, it_virt_incr; struct timer_list real_timer; struct tms times; unsigned long start_time; long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS]; unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap; int swappable:1; uid_t uid,euid,suid,fsuid; gid_t gid,egid,sgid,fsgid; int ngroups; gid_t groups[NGROUPS]; kernel_cap_t cap_effective, cap_inheritable, cap_permitted; int keep_capabilities:1; struct user_struct *user; struct rlimit rlim[RLIM_NLIMITS]; unsigned short used_math; char comm[16]; int link_count; struct tty_struct *tty; unsigned int locks; struct sem_undo *semundo; struct sem_queue *semsleeping; struct thread_struct thread; struct fs_struct *fs; struct files_struct *files; spinlock_t sigmask_lock; struct signal_struct *sig; sigset_t blocked; struct sigpending pending; unsigned long sas_ss_sp; size_t sas_ss_size; int (*notifier)(void *priv); void *notifier_data; sigset_t *notifier_mask; u32 parent_exec_id; u32 self_exec_id; spinlock_t alloc_lock; };
state: 表示進程的當前運行狀態,進程的全部可能狀態定義在同一個文件中:函數
#define TASK_RUNNING 0 #define TASK_INTERRUPTIBLE 1 #define TASK_UNINTERRUPTIBLE 2 #define TASK_ZOMBIE 4 #define TASK_STOPPED 8
TASK_RUNNING 狀態並非表示一個進程正在運行,或者是這個進程就是「當前進程」,而是表示這個進程可被調度執行而成爲當前進程,是進程表達了但願被調度運行的意願。當進程處於此狀態時,表示進程已經準備就緒或者能夠被執行了,內核會將該進程的task_struct結構經過其隊列頭run_list鏈入一個「可執行隊列」,至於最終調度運行誰,內核會根據調度策略從「可執行隊列」中挑選一個進程來調度運行。
TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE 都表示進程處於睡眠狀態。不過前者表示淺度睡眠,能夠被信號(也成爲軟中斷)喚醒;後者表示深度睡眠而不受信號的打擾。這裏的INTERRUPTIBLE或UNINTERRUPTIBLE與「中斷」毫無關係,而只是說睡眠可否因其餘時間而中斷。內核中提供了不一樣的函數讓一個進程進入不一樣程度的睡眠或將進程從睡眠中喚醒。具體來講,sleep_on和wake_up用於深度睡眠;而interruptible_sleep_on和interruptible_wake_up則用於淺度睡眠。 TASK_ZOMBIE 狀態表示進程已經去世(exit)而戶口(task_struct)還沒有註銷。 TASK_STOPPED 主要用於調試目的。進程接收到一個SIGSTOP信號後就將運行狀態改爲TASK_STOPPED而進入掛起狀態,而後在接收到一個SIGCONT信號時又恢復繼續運行。ui
flag: 與進程管理有關的信息,對應的標誌位在同一個文件中定義:this
/* * Per process flags */ #define PF_ALIGNWARN 0x00000001 /* Print alignment warning msgs */ /* Not implemented yet, only for 486*/ #define PF_STARTING 0x00000002 /* being created */ #define PF_EXITING 0x00000004 /* getting shut down */ #define PF_FORKNOEXEC 0x00000040 /* forked but didn't exec */ #define PF_SUPERPRIV 0x00000100 /* used super-user privileges */ #define PF_DUMPCORE 0x00000200 /* dumped core */ #define PF_SIGNALED 0x00000400 /* killed by a signal */ #define PF_MEMALLOC 0x00000800 /* Allocating memory */ #define PF_VFORK 0x00001000 /* Wake up parent in mm_release */ #define PF_USEDFPU 0x00100000 /* task used FPU this quantum (SMP) */
PF_MEMALLOC咱們已經在內存頁面分配時見過了,其餘的標誌位請參考註釋。
sigpending: 表示進程收到了信號但還沒有處理,與這個標誌相聯繫的是與信號隊列有關的sigqueue、sigqueue_tail、sig等指針以及sigmask_lock、signal、blocked等成分。
counter: 與調度有關。
need_resched: 與調度有關,表示CPU從系統空間返回到用戶空間前夕要進行一次調度。 上述字段反應了進程的動態特徵,下面這些字段則反應了進程的靜態特徵。
addr_limit: 虛存地址的空間上限。對進程而言就是其用戶空間的上限,因此爲0xbfffffff;對內核進程而言則是系統空間的上限爲0xffffffff。
personality: 看過APUE的都知道,Unix有許多不一樣的版本和變種,應用程序也就有各自的適用範圍。好比Unix SVR4的應用程序未必與爲linux開發的其餘軟件徹底兼容。因此每一個進程都有其與生俱來的「個性「。文件<include/linux/personality.h>定義了相關的常數。
exec_domain: 除了personality外,應用程序還有一些其餘版本間的差別,從而造成了不一樣的「執行域」。這個指針就指向描述本進程所屬執行域的數據結構。
binfmt: 應用程序的文件格式,如a.out、elf等。
exit_code: 退出碼,與之相關的exit_signal、pdeath_signal詳見《系統調用exit與wait4》。
pid: 進程號。
pgrp: 每一個進程除了有一個進程號之外,還屬於一個進程組。pgrp指向進程所屬的進程組ID。
session: 會話。當一個用戶登陸到系統後,就開始了一個會話,會話是一個或多個進程組的集合。
leader: 每一個會話都一個會話首進程(session leader)。
priority、rt_priority: 優先級別以及「實時」優先級別。
policy: 本進程的調度策略。
parent_exec_id、self_exec_id: 與會話(session)有關。
uid、euid、suid、fsuid、gid、egid、sgid、fsgid: 進程所屬的用戶ID、有效用戶ID、保存的設置用戶ID、用於文件系統訪問檢查的用戶ID,組ID、有效組ID、保存的設置組ID,用於文件系統訪問檢查的組ID。參考後面的內容(也能夠參考APUE第二版的第8章)。
cap_effective、cap_inheritable、cap_permitted: 通常進程都不能隨心所欲,而是各自被賦予了各類不一樣的權限。將權利細分,而不是籠統的取決於一個進程是不是「特權進程」,這也是linux安全機制的基礎之一(另外一個是文件訪問權限的設置)。<include/linux/capability.h>定義了許多這樣的權限。同時內核定義了一個內聯函數capable,用來檢查進程是否具備某種權限。
user: 指向一個user_struct結構,該數據結構表明着進程所屬的用戶。
rlim: 這是一個結構數組,代表進程對各類資源的使用數量所受的限制。咱們在第二章用戶堆棧的擴展中已經看到了對這種限制的應用(須要獲取用戶空間堆棧大小的限制等)。數據結構rlimit定義在<include/linux/resource.h>文件中:
struct rlimit { unsigned long rlim_cur; unsigned long rlim_max; };
rlim_cur 爲「soft limit」,是當前的限制,rlim_max 爲「hard limit」,表示最大限制。rlim_cur的取值不能比rlim_max大。
每個進程都不是孤立的存在於系統中,而老是根據不一樣的目的、關係和須要與其餘進程相聯繫。從內核的角度看,則是要根據不一樣的目的和性質將每一個進程歸入各類組織。
第一個組織就是進程的「家譜」。這是一種樹形組織,經過p_opptr、p_pptr、p_cptr、p_ysptr和p_osptr構成。其中p_opptr(original parent,生父)與p_pptr(養父)指向父進程,p_cptr(c表明child)指向最「年輕」(最近建立的)的子進程,而p_ysptr(youngest sibling)和p_osptr(oldest sibling)則分別指向其「弟弟」和「哥哥」。這種「家譜」組織示意以下:
根據p_cptr順藤摸瓜,一個進程能夠找到它全部的子進程。可是想要根據進程號pid找到對應的task_struct仍是很難,因而便有了第二個組織。
第二個組織就是按照進程號pid構成的哈希隊列。 給定pid,經過哈希計算就能夠獲得進程的task_struct結構在哈希隊列中的下標,進而找到進程對應的task_struct結構。哈希表pidhash定義在<kernel/fork.c>中:
struct task_struct *pidhash[PIDHASH_SZ];
而PIDHASH_SZ定義在<include/linux/sched.h>中:
#define PIDHASH_SZ (4096 >> 2)
可見,哈希表pidhash共有1024個元素,系統同時存在的進程數目不能超過1024個。同時,因爲一個指針佔4個字節,因此整個哈希表佔用4K即一個頁面的大小。
當內核須要遍歷每個進程時,這時就須要用到第三個組織。
第三個組織是將每一個進程鏈起來的線性隊列。 系統建立的第一個進程爲init_task,這個進程就是全部進程的總根,因此它就是這個線性隊列的起點。後續每建立一個進程,就經過其task_struct結構中的next_task和prev_task兩個指針鏈入這個隊列。
每一個進程必然同時身處這三個組織之中,直到進程消亡時才從這三個隊列中摘除。
在運行過程當中,一個進程隨時可能鏈入「可執行隊列」中接受系統調度。一個進程只有在「可執行隊列「中才有可能受到調度而投入運行。這就是第四個組織。進程經過task_struct結構中run_list鏈入可執行隊列。