都說這個主題不錯,連我本身都以爲有點過大了,不過我想我仍是得堅持下去,努力在有限的時間裏學習到Linux內核的奧祕,也但願你們多指點,讓 我更有進步。今天講的全是進程,這點在大二的時候就困惑了我,結果那個時候我就止步不前了,這裏主要講的是爲什麼引入進程、進程在Linux空間是如何實現 的,而且描述了全部與進程執行相關的數據結構,最後還會講到異常和中斷等異步執行流程,它們是如何和Linux內核進行交互的,下面我就來具體介紹一下進 程的奧妙。linux
首先咱們要明確一個概念,咱們說的程序是指由一組函數組成的可執行文件,而進程則是特定程序的個體化實例,進程是對硬件所提供資源進行操做的基本單位。在咱們繼續討論進程以前,得明白一個幾個命名習慣,一般說的「任務「和」進程「就是一回事。程序員
事實上,進程都有一個生命週期,進程從建立事後會經歷各類狀態後死亡,下面的例子幫助你們理解一下程序是如何實例化進程的。數組
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <fcnt1.h> 5 6 int main(int argc, char *argv[]) 7 { 8 int fd; 9 int pid; 10 11 pid = fork(); 12 if(pid == 0) 13 { 14 execle("/bin/ls", NULL); 15 exit(2); 16 } 17 18 if(waitpid(pid) <0 ) 19 printf("wait error\n"); 20 21 pid = fork(); 22 if(pid == 0) 23 { 24 fd = open("Chapter_2.txt",O_RDONLY); 25 close(fd); 26 } 27 28 if(waitpid(pid)<0) 29 printf("wait error\n"); 30 31 exit(0); 32 }
creat_process數據結構
一個進程包括了不少屬性,使進程彼此互不相同,在內核中,進程描述符是一個task_struct的結構體,用來保存進程的屬性和相關信息,內核使用循環 雙向鏈表task_list存放全部進程描述符,同時藉助全局變量current保存當前運行進程的task_struct。至於task_struct 的定義你們能夠參見include/Linux/sched.h這裏我講不了辣麼多,不過我得說明一下進程和線程的區別,進程由一個或者多個線程組成,每 個線程對應一個task_struct,其中包含一個惟一 的線程ID。線程做爲調度和分配的基本單位,而進程做爲擁有資源的基本單位;不只進程之間能夠併發執行,同一個進程的多個線程之間也能夠併發執行;進程是擁有資源的一個獨立單位,線程不擁有系統資源,但能夠訪問隸屬於進程的資源。併發
進程描述符(task_struct)某些字段含義,這裏 有太多的與進程相關的域,我羅列一些以下,,假設進程爲P。app
state:P進程狀態,用set_task_state和set_current_state宏更改之,或直接賦值。框架
thread_info:指向thread_info結構的指針。dom
run_list:假設P狀態爲TASK_RUNNING,優先級爲k,run_list將P鏈接到優先級爲k的可運行進程鏈表中。異步
tasks:將P鏈接到進程鏈表中。ide
ptrace_children:鏈表頭,鏈表中的全部元素是被調試器程序跟蹤的P的子進程。
ptrace_list:P被調試時,鏈表中的全部元素是被調試器程序跟蹤的P的子進程。
pid:P進程標識(PID)。
tgid:P所在的線程組的領頭進程的PID。
real_parent:P的真實的父進程的進程描述符指針。
parent:P的父進程的進程描述符指針,當被調試時就是調試器進程的描述符指針。
children:P的子進程鏈表。
sibling:將P鏈接到P的兄弟進程鏈表。
group_leader:P所在的線程組的領頭進程的描述符指針。
咱們瞭解到,任何進程都是由別的進程建立的,操做系統經過fork()、vfork()、clone()系統調用來完成進程的建立。進程建立的系統調用以下圖:
這三個系統最終都調用了do_fork()函數,do_fork()是內核函數,它完成與進程建立有關的大部分工做,下面 我來粗略介紹一下fork()、vfork()、clone()函數。
fork()函數返回兩次,一次是子進程,返回值爲0;一次是父進程,將返回子進程的PID,
和fork()函數相似,可是前者的父進程一直阻塞,直到子進程調用exit()或exec()後。
clone()函數接受一個指向函數的指針和該函數的參數,由do_fork()建立的子進程一誕生就調用這個庫函數。
三者 的惟一區別,在最終調用do_fork()函數設置的那些標誌不同,以下表。
fork() | vfork() | clone | |
SIGCHLD | X | X | |
CLONE_VFORK | X | ||
CLONE_VM | X |
do_fork()函數利用輔助函數copy_process()來建立進程描述符以及子進程執行所須要的全部其餘內核數據結構,在 Linux 內核中,供用戶建立進程的系統調用fork()函數的響應函數是 sys_fork()、sys_clone()、sys_vfork()。這三個函數都是經過調用內核函數 do_fork() 來實現的。下面就具體的 do_fork() 函數程序代碼進行分析(該代碼位於 kernel/fork.c 文件中)
1 int do_fork(unsigned long clone_flags,unsigned long stack_start, struct pt_regs *regs, 2 unsigned long stack_size) 3 { 4 int retval; 5 struct task_struct *p; 6 struct completion vfork; 7 8 retval = -EPERM ; 9 10 if ( clone_flags & CLONE_PID ) 11 { 12 if ( current->pid ) 13 goto fork_out; 14 } 15 16 reval = -ENOMEM ; 17 18 p = alloc_task_struct(); // 分配內存創建新進程的 task_struct 結構 19 if ( !p ) 20 goto fork_out; 21 22 *p = *current ; //將當前進程的 task_struct 結構的內容複製給新進程的 PCB結構 23 24 retval = -EAGAIN; 25 26 //下面代碼對父、子進程 task_struct 結構中不一樣值的數據成員進行賦值 27 28 if ( atomic_read ( &p->user->processes ) >= p->rlim[RLIMIT_NPROC].rlim_cur 29 && !capable( CAP_SYS_ADMIN ) && !capable( CAP_SYS_RESOURCE )) 30 goto bad_fork_free; 31 32 atomic_inc ( &p->user->__count); //count 計數器加 1 33 atomic_inc ( &p->user->processes); //進程數加 1 34 35 if ( nr_threads >= max_threads ) 36 goto bad_fork_cleanup_count ; 37 38 get_exec_domain( p->exec_domain ); 39 40 if ( p->binfmt && p->binfmt->module ) 41 __MOD_INC_USE_COUNT( p->binfmt->module ); //可執行文件 binfmt 結構共享計數 + 1 42 p->did_exec = 0 ; //進程未執行 43 p->swappable = 0 ; //進程不可換出 44 p->state = TASK_UNINTERRUPTIBLE ; //置進程狀態 45 copy_flags( clone_flags,p ); //拷貝進程標誌位 46 p->pid = get_pid( clone_flags ); //爲新進程分配進程標誌號 47 p->run_list.next = NULL ; 48 p->run_list.prev = NULL ; 49 p->run_list.cptr = NULL ; 50 51 init_waitqueue_head( &p->wait_childexit ); //初始化 wait_childexit 隊列 52 53 p->vfork_done = NULL ; 54 55 if ( clone_flags & CLONE_VFORK ) { 56 p->vfork_done = &vfork ; 57 init_completion(&vfork) ; 58 } 59 60 spin_lock_init( &p->alloc_lock ); 61 62 p->sigpending = 0 ; 63 64 init_sigpending( &p->pending ); 65 p->it_real_value = p->it_virt_value = p->it_prof_value = 0 ; //初始化時間數據成員 66 p->it_real_incr = p->it_virt_incr = p->it_prof_incr = 0 ; //初始化定時器結構 67 init_timer( &p->real_timer ); 68 p->real_timer.data = (unsigned long)p; 69 p->leader = 0 ; 70 p->tty_old_pgrp = 0 ; 71 p->times.tms_utime = p->times.tms_stime = 0 ; //初始化進程的各類運行時間 72 p->times.tms_cutime = p->times.tms_cstime = 0 ; 73 #ifdef CONFIG_SMP //初始化對稱處理器成員 74 { 75 int i; 76 p->cpus_runnable = ~0UL; 77 p->processor = current->processor ; 78 for( i = 0 ; i < smp_num_cpus ; i++ ) 79 p->per_cpu_utime[ i ] = p->per_cpu_stime[ i ] = 0; 80 spin_lock_init ( &p->sigmask_lock ); 81 } 82 83 #endif 84 p->lock_depth = -1 ; // 注意:這裏 -1 表明 no ,表示在上下文切換時,內核不上鎖 85 p->start_time = jiffies ; // 設置進程的起始時間 86 87 INIT_LIST_HEAD ( &p->local_pages ); 88 retval = -ENOMEM ; 89 90 if ( copy_files ( clone_flags , p )) //拷貝父進程的 files 指針,共享父進程已打開的文件 91 goto bad_fork_cleanup ; 92 93 if ( copy_fs ( clone_flags , p )) //拷貝父進程的 fs 指針,共享父進程文件系統 94 goto bad_fork_cleanup_files ; 95 96 if ( copy_sighand ( clone_flags , p )) //子進程共享父進程的信號處理函數指針 97 goto bad_fork_cleanup_fs ; 98 99 if ( copy_mm ( clone_flags , p )) 100 goto bad_fork_cleanup_mm ; //拷貝父進程的 mm 信息,共享存儲管理信息 101 102 retval = copy_thread( 0 , clone_flags , stack_start, stack_size , p regs ); 103 //初始化 TSS、LDT以及GDT項 104 105 if ( retval ) 106 goto bad_fork_cleanup_mm ; 107 108 p->semundo = NULL ; //初始化信號量成員 109 110 p->prent_exec_id = p-self_exec_id ; 111 112 p->swappable = 1 ; //進程佔用的內存頁面可換出 113 114 p->exit_signal = clone_flag & CSIGNAL ; 115 116 p->pdeatch_signal = 0 ; //注意:這裏是父進程消亡後發送的信號 117 118 p->counter = (current->counter + 1) >> 1 ;//進程動態優先級,這裏設置成父進程的一半,應注意的是,這裏是採用位操做來實現的。 119 120 current->counter >> =1; 121 122 if ( !current->counter ) 123 current->need_resched = 1 ; //置位從新調度標記,實際上從這個地方開始,分裂成了父子兩個進程。 124 125 retval = p->pid ; 126 127 p->tpid = retval ; 128 INIT_LIST_HEAD( &p->thread_group ); 129 130 write_lock_irq( &tasklist_lock ); 131 132 p->p_opptr = current->p_opptr ; 133 p->p_pptr = current->p_pptr ; 134 135 if ( !( clone_flags & (CLONE_PARENT | CLONE_THREAD ))) { 136 p->opptr = current ; 137 if ( !(p->ptrace & PT_PTRACED) ) 138 p->p_pptr = current ; 139 } 140 141 if ( clone_flags & CLONE_THREAD ){ 142 p->tpid = current->tpid ; 143 list_add ( &p->thread_group,¤t->thread_group ); 144 } 145 146 SET_LINKS(p); 147 148 hash_pid(p); 149 nr_threads++; 150 151 write_unlock_irq( &tasklist_lock ); 152 if ( p->ptrace & PT_PTRACED ) 153 send_sig( SIGSTOP , p ,1 ); 154 wake_up_process(p); //把新進程加入運行隊列,並啓動調度程序從新調度,使新進程得到運行機會 155 ++total_forks ; 156 if ( clone_flags & CLONE_VFRK ) 157 wait_for_completion(&vfork); 158 159 //如下是出錯處理部分 160 fork_out: 161 return retval; 162 bad_fork_cleanup_mm: 163 exit_mm(p); 164 bad_fork_cleanup_sighand: 165 exit_sighand(p); 166 bad_fork_cleanup_fs: 167 exit_fs(p); 168 bad_fork_cleanup_files: 169 exit_files(p); 170 171 bad_fork_cleanup: 172 put_exec_domain( p->exec_domain ); 173 174 if ( p->binfmt && p->binfmt->module ) 175 __MOD_DEC_USE_COUNT( p->binfmt->module ); 176 bad_fork_cleanup_count: 177 atomic_dec( &p->user->processes ); 178 free_uid ( p->user ); 179 bad_fork_free: 180 free_task_struct(p); 181 goto fork_out; 182 }
fork
Linux中的進程有7種狀態,進程的task_struct結構的state字段指明瞭該進程的狀態。下圖形象的形容了各個狀態之間的轉換,這裏很少加闡釋,你們看圖體會。
可運行狀態(TASK_RUNNING)
可中斷的等待(TASK_INTERRUPTIBLE)
不可中斷的等待(TASK_UNINTERRUPTIBLE)
暫停狀態(TASK_STOPPED)
跟蹤狀態(TASK_TRACED):進程被調試器暫停或監視。
僵死狀態(EXIT_ZOMBIE):進程被終止,但父進程未調用wait類系統調用。
僵死撤銷狀態(TASK_DEAD):父進程發起wait類系統調用,進程由系統刪除。
至於進程的終止,上文已經提到過了exit()函數,進程終止有三種方式:明確而自願的終止,隱含但也是自願終止,天然而然的運行終止,這些能夠 經過sys_exit()函數、do_exit()函數來實現,這裏很少說了,都很好懂的,到此,咱們應該對進程在生命週期中所經歷的各類狀態,完成狀態 轉換的大部分函數等等等有了瞭解了,有須要補充的或者不懂再借閱i些資料就應該可以對進程的相關知識有了很好的掌握了,但願你們可以理解,那麼個人任務也 算完成了一半了。
瞭解了以進程爲中心的狀態和轉換可是要真正完成進程的運行和終止,那麼內核的基本框架是必需要掌握的,如今咱們來介紹調度程序的基礎知識,調度程序的對象是一個稱爲運行隊列的結構,下圖說明了隊列中的優先權數組,其定義以及相關分析以下:
struct prio_array { int nr_active; //計數器,記錄優先權數組中的進程數 unsigned long bitmap[BITMAP_SIZE]; //bitmap是記錄數組中的優先權,實際長度取決於系統無符號長整型的大小 struct list_head queue[MAX_PRIO]; //queue存儲進程鏈表的數組,且每一個鏈表含有特定優先權的進程 };
最後講到的是異步執行流程,咱們說過,進程可以經過終端中斷一個狀態轉換到另外一個狀態,得到這種轉換的惟一途徑就包括異常和中斷在內的異步。(這 裏吐槽一下,其實這個時候我好累了,以爲好難寫,都怪大二時候基礎很差,如今一年過去了,大三狗寒假大晴天不出去逛,待在實驗室裏,不過這個時候符合主 題,腦殼瓜中斷了一下)
處理器產生的(Fault,Trap,Abort)異常
programmed exceptions(軟中斷):由程序員經過INT或INT3指令觸發,一般當作trap處理,用處:實現系統調用。
異常也叫作同步中斷,是發生在整個處理器硬件內部的事件。異常一般發生在指令執行以後。大多數現代 處理器容許程序員經過執行某些指令來產生一個異常。其中一個例子就是系統調用。
系統調用:
用戶態的程序調用的許多C庫例程,就是把代碼和一個或者多個系統調用捆綁在一塊兒造成一個單獨的函數。當用戶進程調用其中一個函數的時候,某個值 被放入適當的處理器寄存器中,併產生一個軟中斷irp(異常)。而後這個軟中斷調用內核入口點。系統調用可以在用戶空間和內核空間之間傳遞數據,由兩個內 核函數來完成這個任務:copy_to_user()和copy_from_user()。系統調用號和全部的參數都先被存入處理器的寄存器中,當x86 的異常處理程序處理軟中斷0x80時,它對系統調用表進行索引。
可屏蔽中斷:全部有I/O設備請求的中斷都是,被屏蔽的中斷會一直被CPU 忽略,直到屏蔽位被重置。
不可屏蔽中斷:很是危險的事件引發(如硬件失敗)
中斷對處理器的執行是異步的,就是說中斷可以早指令之間發生。通常要發生中斷,中斷控制器是必須的(x86用的是8259中斷處理器)。當中斷處 理器有有一個待處理的中斷時,它就觸發鏈接處處理器的相應INT線,而後處理器經過觸發線來確認這個信號,確認線鏈接到INTA線上。這時候,中斷處理器 就能夠把IRQ數據傳處處理器上了,這就是一箇中斷確認週期。具體的例子就很差列舉了,須要太大篇幅,也須要更多的知識才能去深入瞭解。
硬件設備控制器經過IRQ線向CPU發出中斷,能夠經過禁用某條IRQ線來屏蔽中斷。
被禁止的中斷不會丟失,激活IRQ後,中斷還會被髮到CPU
激活/禁止IRQ線 != 可屏蔽中斷的 全局屏蔽/非屏蔽
一天的時間,全在進程裏面,今天主要是解釋了爲什麼引入進程,簡單討論了用戶空間與內核空間的控制流,而且討論了進程在內核中是如何實現的,裏面涉及到隊列 的知識,本問沒有講到,就須要讀者本身去學習數據結構,總之Linux內核須要很好的數據結構知識,最後還粗略涵蓋了終端異常,總之,感受進程是個大骨 頭,講的很籠統,還須要大量時間去學習,而且分析Linux內核源代碼,總之,繼續加油~
版權全部,轉載請註明轉載地址: http://www.cnblogs.com/lihuidashen/
本文來自:Linux學習網