進程是程序執行時的一個實例,它是描述程序已經執行到何種程度的數據結構的彙集。
父子進程有相同的代碼,共享正文頁,但是它們有着獨立的數據拷貝(堆和棧),因此子進程對一個內存單元的修改對於父進程是不可見的(反之亦然)。但是現在的unix系統,它們支持多線程應用程序-----擁有很多相對獨立執行流的用戶程序共享應用程序的大部分數據結構。現在絕大部分應用程序都是用pthread(POSIX thread)庫的標準函數庫編寫的。
linux採用輕量級進程對多線程應用程序提供更好的支持。兩個輕量級進程基本上可以共享一些資源,如地址空間/打開文件等等。只要其中一個修改共享資源,另一個就可以立即查看這種修改,當然它們訪問共享資源時必須同步它們自己。實現多線程應用程序的一個簡單方式就是把輕量級進程與每個線程關聯起來,這樣線程之間就可以通過簡單的共享同一地址空間、同一打開文件等來訪問相同的數據結構集;同時每個線程都可以由內核獨立調度,以便一個睡眠的時候另一個仍然可以時運行的。POSIX兼容的多線程應用程序由支持“線程組”的內核,在linux中,一個線程組就是實現了多線程應用程序的一組輕量級進程。對於像getpid(),kill()和_exit()這樣的一些系統調用,它像一個組織,起整體的作用。
進程描述符都是task_struct類型結構,它的字段包含了與一個進程相關的所有信息,在include\linux\sched.h文件中定義。
- //進程描述符task_struct
- struct task_struct {
- /* * offsets of these are hardcoded elsewhere - touch with care
- */ volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ //-1 不能運行 0 運行 >0 停止
- unsigned long flags; /* per process flags, defined below *///進程標誌,在下面定義
- int sigpending; //進程上是否有待處理的信號
- mm_segment_t addr_limit; /* thread address space:進程地址空間
- 0-0xBFFFFFFF for user-thead
- 0-0xFFFFFFFF for kernel-thread
- */
- volatile long need_resched; //調度標誌,表示該進程是否需要重新調度,若非0,則當從內核態返回到用戶態,會發生調度
- int lock_depth; /* Lock depth *///鎖深度
- /* * offset 32 begins here on 32-bit platforms. We keep
- * all fields in a single cacheline that are needed for
- * the goodness() loop in schedule().
- */ long counter; //進程可運行的時間量
- long nice; //進程的基本時間片
- unsigned long policy; //進程的調度策略,有三種,實時進程:SCHED_FIFO,SCHED_RR;分時進程:SCHED_OTHER;
- struct mm_struct *mm; //進程內存管理信息
- int processor;
- /* * cpus_runnable is ~0 if the process is not running on any
- * CPU. It's (1 << cpu) if it's running on a CPU. This mask
- * is updated under the runqueue lock.
- * * To determine whether a process might run on a CPU, this
- * mask is AND-ed with cpus_allowed.
- * 若進程不在任何CPU上運行,cpus_runnable 的值是0,否則是1。這個值在運行 *隊列被鎖時更新;*/
- unsigned long cpus_runnable, cpus_allowed;
- /* * (only the 'next' pointer fits into the cacheline, but
- * that's just fine.)
- */
- struct list_head run_list; //指向運行隊列的指針
- unsigned long sleep_time; //進程的睡眠時間
- struct task_struct *next_task, *prev_task; //用於將系統中所有的進程連成一個雙向循環鏈表,其根是init_task.
- struct mm_struct *active_mm;
- struct list_head local_pages; //指向本地頁面
- unsigned int allocation_order, nr_local_pages;
- /* task state */
- struct linux_binfmt *binfmt; //進程所運行的可執行文件的格式
- int exit_code, exit_signal;
- int pdeath_signal; /* The signal sent when the parent dies *///父進程終止是向子進程發送的信號
- /* ??? */
- unsigned long personality; //Linux可以運行由其他UNIX操作系統生成的符合iBCS2標準的程序
- int did_exec:1; //按POSIX要求設計的布爾量,區分進程正在執行從父進程中繼承的代碼,還是執行由execve裝入的新程序代碼
- pid_t pid; //進程標識符,用來代表一個進程
- pid_t pgrp; //進程組標識,表示進程所屬的進程組
- pid_t tty_old_pgrp; //進程控制終端所在的組標識
- pid_t session; //進程的會話標識
- pid_t tgid;
- /* boolean value for session group leader */
- int leader; //標誌,表示進程是否爲會話主管
- /*
- * pointers to (original) parent process, youngest child, younger sibling,
- * older sibling, respectively. (p->father can be replaced with
- * p->p_pptr->pid)
- *///指針指向(原始的)父進程,孩子進程,比自己年輕的兄弟進程,比自己年長的兄弟進程
- //(p->father能被p->p_pptr->pid代替)
- struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr, *p_osptr;
- struct list_head thread_group; //線程鏈表
- /* PID hash table linkage. *///進程散列表指針
- struct task_struct *pidhash_next; //用於將進程鏈入HASH表pidhash
- struct task_struct **pidhash_pprev;
- wait_queue_head_t wait_chldexit; /* for wait4() *///wait4()使用
- struct completion *vfork_done; /* for vfork() */// vfork() 使用
- unsigned long rt_priority; //實時優先級,用它計算實時進程調度時的weight值
- //it_real_value,it_real_incr用於REAL定時器,單位爲jiffies。系統根據it_real_value //設置定時器的第一個終止時間。在定時器到期時,向進程發送SIGALRM信號,同時根據it_real_incr重置終止時間
- //it_prof_value,it_prof_incr用於Profile定時器,單位爲jiffies。當進程運行時,不管在何種狀態下,每個tick都使
- //it_prof_value值減一,當減到0時,向進程發送信號SIGPROF,並根據it_prof_incr重置時間
- //it_virt_value,it_virt_value用於Virtual定時器,單位爲jiffies。當進程運行時,不管在何種狀態下,每個tick都使
- //it_virt_value值減一,當減到0時,向進程發送信號SIGVTALRM,根據it_virt_incr重置初值。
- //Real定時器根據系統時間實時更新,不管進程是否在運行
- //Virtual定時器只在進程運行時,根據進程在用戶態消耗的時間更新
- //Profile定時器在進程運行時,根據進程消耗的時(不管在用戶態還是內核態)更新
- unsigned long it_real_value, it_prof_value, it_virt_value;
- unsigned long it_real_incr, it_prof_incr, it_virt_value;
- struct timer_list real_timer;//指向實時定時器的指針
- struct tms times; //記錄進程消耗的時間,
- unsigned long start_time;//進程創建的時間
- long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS]; //記錄進程在每個CPU上所消耗的用戶態時間和核心態時間
- /* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */
- //內存缺頁和交換信息:
- //min_flt, maj_flt累計進程的次缺頁數(Copy on Write頁和匿名頁)和主缺頁數(從映射文件或交換設備讀入的頁面數);
- //nswap記錄進程累計換出的頁面數,即寫到交換設備上的頁面數。
- //cmin_flt, cmaj_flt, cnswap記錄本進程爲祖先的所有子孫進程的累計次缺頁數,主缺頁數和換出頁面數。在父進程
- //回收終止的子進程時,父進程會將子進程的這些信息累計到自己結構的這些域中
- unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;
- int swappable:1; //表示進程的虛擬地址空間是否允許換出
- /* process credentials *////進程認證信息
- //uid,gid爲運行該進程的用戶的用戶標識符和組標識符,通常是進程創建者的uid,gid //euid,egid爲有效uid,gid
- //fsuid,fsgid爲文件系統uid,gid,這兩個ID號通常與有效uid,gid相等,在檢查對於文件系統的訪問權限時使用他們。
- //suid,sgid爲備份uid,gid
- 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;
- /* limits */
- struct rlimit rlim[RLIM_NLIMITS]; //與進程相關的資源限制信息
- unsigned short used_math; //是否使用FPU
- char comm[16]; //進程正在運行的可執行文件名
- /* file system info *///文件系統信息
- int link_count, total_link_count;
- struct tty_struct *tty; /* NULL if no tty 進程所在的控制終端,如果不需要控制終端,則該指針爲空*/
- unsigned int locks; /* How many file locks are being held */
- /* ipc stuff *///進程間通信信息
- struct sem_undo *semundo; //進程在信號燈上的所有undo操作
- struct sem_queue *semsleeping; //當進程因爲信號燈操作而掛起時,他在該隊列中記錄等待的操作
- /* CPU-specific state of this task *///進程的CPU狀態,切換時,要保存到停止進程的
- task_struct中
- struct thread_struct thread;
- /* filesystem information文件系統信息*/
- struct fs_struct *fs;
- /* open file information *///打開文件信息
- struct files_struct *files;
- /* signal handlers *///信號處理函數
- spinlock_t sigmask_lock; /* Protects signal and blocked */
- 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;
- /* Thread group tracking */
- u32 parent_exec_id;
- u32 self_exec_id;
- /* Protection of (de-)allocation: mm, files, fs, tty */
- spinlock_t alloc_lock;
- /* journalling filesystem info */
- void *journal_info;
- };
在當前的linux版本中這些狀態時互斥的,只能設置一種狀態。
可運行狀態(TASK—RUNNING),進程要麼在cpu上執行,要麼準備執行;
可中斷的等待狀態(TASK—INTERRUPTIBLE),進程在等待某個條件,一旦該條件成立可立即變爲TASK—RUNNING;
不可中斷的等待狀態(TASK—UNINTERRUPTIBLE),即使等待的條件爲真,該睡眠進程也不能改變它的狀態,如設備驅動程序正在探測相應的硬件設備時,在設備探測完成前,設備驅動程序不可被中斷,否則設備會處於不可預知的狀態;
暫停狀態(TASK—STOPPED),進程接受到SIGSTOP,SIGTSTP,SIGTTIN或SIGTTOU;
跟蹤狀態(TASK—TRACED),進程的執行由debugger程序暫停,當一個進程被另一個進程監控(如被另一個進程執行ptrace);
當進程的執行被終止時,進程的狀態會變爲以下兩種狀態中的一種,它可以存放在進程描述符的state字段中,也可以存放在exit—state字段中。
僵死狀態(TASK—ZOMBIE),進程的執行被終止,但是父進程還沒有發佈wait4()或者waitpid()系統調用來返回有關死亡進程的信息。此時內核不能丟棄包含在死進程描述符中的數據,因爲父進程可能還需要它。
僵死撤銷狀態(EXIT—DEAD),父進程發出wait4()或waitpid()系統調用。可以防止其他執行線程在同一個進程上執行wait類系統調用。
state字段的值通常用一個簡單的賦值語句設置,如:p->state=TASK_—RUNNING,內核也可以用宏設置,可以確保編譯程序不把賦值語句和其他指令混合,防止災難性的後果。
標示一個進程:
一般使用32位進程描述符地址標示一個進程,進程描述符指針指向這些地址,內核對進程的大部分引用都是通過進程描述符指針進行的。
在類unix系統中,允許用戶使用一個叫做進程標識符processID的數來標識進程,PID存放在進程描述符的pid字段中,且pid編號循環使用,內核通過一個pidmap—array位圖來表示當前已分配的pid號和閒置的pid號。一個頁框包含32768位,在32位體系結構中只需一頁,在64位體系結構中,pid的上限可以擴展到4194303,此時需要爲pid位圖增加多個頁.系統管理員可以通過往/proc/sys/kernel/pid—max這個文件中寫入值來決定pid的上限。
一個多線程應用程序中的所有線程必須由相同的pid,即同一組中的線程由共通的pid。爲此linux引入線程組的表示,一個線程組中的所有線程使用和該線程組的領頭線程相同的pid,也就是該組中第一個輕量級進程的pid,它被存入進程描述符的tgid字段中,getpid()系統調用返回當前進程的tgid而不是pid,因此一個多線程應用程序的所有線程共享相同的pid。絕大多數進程都屬於一個線程組,包含單一的成員;線程組的領頭線程其tgid的值與pid的值相同,而getpid()系統調用對這類進程所起的作用和一般進程是一樣的。
進程描述符處理:
內核要同時處理很多進程,並把進程描述符放在動態內存中。對每個進程,linux都是把兩個不同的數據結構緊湊的放在一個單獨爲進程分配的存儲區域中:1內核態的進程堆棧;2緊挨進程描述符的小數據結構thread—info,叫做線程描述符,這塊存儲區域大小通常位8192字節(2個頁框),通常這塊存儲區連續,且第一個頁框的起始地址是2的13次方的倍數。線程描述符駐留在該內存區的開始,而棧從末端向下增長。
esp寄存器是cpu棧指針,用來存放棧頂單元的地址。從用戶態剛切換爲內核態以後,進程的內核棧總是空的,esp寄存器指向這個棧的頂端,一旦寫入數據,esp值就減小。由於thread_info結構是52字節,因此內核棧能擴展到8196-52=8144字節。
c語言使用下列的聯合結構標示一個進程的線程描述符和內核棧
union thread_union{
struct thread_info thread_info;
unsigned long stack[2048];// 對4k的棧數組下標示1024
};
標示當前進程:
thread_info結構與內核態對戰之間緊密結合的好處是:內核很容易從esp寄存器的值獲得當前在cpu上正在運行進程的thread_info結構的地址。如果thread_union結構長度是8k,則內核屏蔽esp的低13位有效數字就可以獲得thread_info結構的基地址。
進程常用的是進程描述符的地址而不是thread_info的地址,爲此內核調用current宏,該宏本質上等價於current_thread_info->task,它產生如下彙編語言指令:
movl $0xffffe000,%ecx
andl %esp,%ecx
movl %ecx,p
因爲task字段在thread_info結構中的偏移量爲0,執行這三條指令後,p就是在cpu上運行進程的描述符指針。
對每個硬件處理器,僅通過檢查棧就可以獲得當前正確的進程。
進程鏈表:
進程鏈表把所有進程的描述符鏈接起來,它的本質是一個雙向鏈表。每個task_struct結構都包含一個list_head結構的tasks字段,這個類型的prev和next字段分別指向前面和後面的task_struct元素。進程鏈表的頭是init_task描述符,它是所謂的0進程(process 0)或者swapper進程的進程描述符。
有一個宏for_each_process,它的功能是掃描整個進程鏈表,
#define for_eack_process
for(p=&init_task;(p=list_entry(p->tasks.next,struct task_struct,tasks))!=&init_task)
選擇進程運行:
實質是選擇一個處於TASK_RUNNING狀態的進程,linux 2.6開始建立多個可運行進程鏈表,每種進程優先權對應一個鏈表。每個task_struct描述符包含一個list_head類型的字段run_list,進程描述符的prio字段存放進程的動態優先權。