十天學Linux內核之次日---進程

都說這個主題不錯,連我本身都以爲有點過大了,不過我想我仍是得堅持下去,努力在有限的時間裏學習到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()函數

fork()函數返回兩次,一次是子進程,返回值爲0;一次是父進程,將返回子進程的PID,

vfork()函數

和fork()函數相似,可是前者的父進程一直阻塞,直到子進程調用exit()或exec()後。

clone()函數

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,&current->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結構

  • 硬件設備控制器經過IRQ線向CPU發出中斷,能夠經過禁用某條IRQ線來屏蔽中斷。

  • 被禁止的中斷不會丟失,激活IRQ後,中斷還會被髮到CPU 

  • 激活/禁止IRQ線 != 可屏蔽中斷的 全局屏蔽/非屏蔽

小結

一天的時間,全在進程裏面,今天主要是解釋了爲什麼引入進程,簡單討論了用戶空間與內核空間的控制流,而且討論了進程在內核中是如何實現的,裏面涉及到隊列 的知識,本問沒有講到,就須要讀者本身去學習數據結構,總之Linux內核須要很好的數據結構知識,最後還粗略涵蓋了終端異常,總之,感受進程是個大骨 頭,講的很籠統,還須要大量時間去學習,而且分析Linux內核源代碼,總之,繼續加油~

版權全部,轉載請註明轉載地址: http://www.cnblogs.com/lihuidashen/

相關文章
相關標籤/搜索