熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Oracle >> 正文

Linux線程實現機制分析

2013-11-13 15:53:12  來源: Oracle 

  作者楊沙洲  
  
  內容
  
  ·基礎知識線程和進程
  
  ·Linux 內核中的輕量進程實現
  
  ·LinuxThread的線程機制
  
  ·其他的線程實現機制
  
  ·參考資料
  
  ·關於作者
  
  自從多線程編程的概念出現在 Linux 中以來Linux 多線應用的發展總是與兩個問題脫不開干系兼容性效率本文從線程模型入手通過分析目前 Linux 平台上最流行的 LinuxThreads 線程庫的實現及其不足描述了 Linux 社區是如何看待和解決兼容性和效率這兩個問題的
  
  一基礎知識線程和進程
  
  按照教科書上的定義進程是資源管理的最小單位線程是程序執行的最小單位在操作系統設計上從進程演化出線程最主要的目的就是更好的支持SMP以及減小(進程/線程)上下文切換開銷
  
  無論按照怎樣的分法一個進程至少需要一個線程作為它的指令執行體進程管理著資源(比如cpu內存文件等等)而將線程分配到某個cpu上執行一個進程當然可以擁有多個線程此時如果進程運行在SMP機器上它就可以同時使用多個cpu來執行各個線程達到最大程度的並行以提高效率同時即使是在單cpu的機器上采用多線程模型來設計程序正如當年采用多進程模型代替單進程模型一樣使設計更簡潔功能更完備程序的執行效率也更高例如采用多個線程響應多個輸入而此時多線程模型所實現的功能實際上也可以用多進程模型來實現而與後者相比線程的上下文切換開銷就比進程要小多了從語義上來說同時響應多個輸入這樣的功能實際上就是共享了除cpu以外的所有資源的
  
  針對線程模型的兩大意義分別開發出了核心級線程和用戶級線程兩種線程模型分類的標准主要是線程的調度者在核內還是在核外前者更利於並發使用多處理器的資源而後者則更多考慮的是上下文切換開銷在目前的商用系統中通常都將兩者結合起來使用既提供核心線程以滿足smp系統的需要也支持用線程庫的方式在用戶態實現另一套線程機制此時一個核心線程同時成為多個用戶態線程的調度者正如很多技術一樣混合通常都能帶來更高的效率但同時也帶來更大的實現難度出於簡單的設計思路Linux從一開始就沒有實現混合模型的計劃但它在實現上采用了另一種思路的混合
  
  在線程機制的具體實現上可以在操作系統內核上實現線程也可以在核外實現後者顯然要求核內至少實現了進程而前者則一般要求在核內同時也支持進程核心級線程模型顯然要求前者的支持而用戶級線程模型則不一定基於後者實現這種差異正如前所述是兩種分類方式的標准不同帶來的
  
  當核內既支持進程也支持線程時就可以實現線程進程的多對多模型即一個進程的某個線程由核內調度而同時它也可以作為用戶級線程池的調度者選擇合適的用戶級線程在其空間中運行這就是前面提到的混合線程模型既可滿足多處理機系統的需要也可以最大限度的減小調度開銷絕大多數商業操作系統(如Digital UnixSolarisIrix)都采用的這種能夠完全實現POSIXc標准的線程模型在核外實現的線程又可以分為一對一多對一兩種模型前者用一個核心進程(也許是輕量進程)對應一個線程將線程調度等同於進程調度交給核心完成而後者則完全在核外實現多線程調度也在用戶態完成後者就是前面提到的單純的用戶級線程模型的實現方式顯然這種核外的線程調度器實際上只需要完成線程運行棧的切換調度開銷非常小但同時因為核心信號(無論是同步的還是異步的)都是以進程為單位的因而無法定位到線程所以這種實現方式不能用於多處理器系統而這個需求正變得越來越大因此在現實中純用戶級線程的實現除算法研究目的以外幾乎已經消失了
  
  Linux內核只提供了輕量進程的支持限制了更高效的線程模型的實現但Linux著重優化了進程的調度開銷一定程度上也彌補了這一缺陷目前最流行的線程機制LinuxThreads所采用的就是線程進程一對一模型調度交給核心而在用戶級實現一個包括信號處理在內的線程管理機制LinuxLinuxThreads的運行機制正是本文的描述重點
  
  二Linux 內核中的輕量進程實現
  
  最初的進程定義都包含程序資源及其執行三部分其中程序通常指代碼資源在操作系統層面上通常包括內存資源IO資源信號處理等部分而程序的執行通常理解為執行上下文包括對cpu的占用後來發展為線程在線程概念出現以前為了減小進程切換的開銷操作系統設計者逐漸修正進程的概念逐漸允許將進程所占有的資源從其主體剝離出來允許某些進程共享一部分資源例如文件信號數據內存甚至代碼這就發展出輕量進程的概念Linux內核在x版本就已經實現了輕量進程應用程序可以通過一個統一的clone()系統調用接口用不同的參數指定創建輕量進程還是普通進程在內核中clone()調用經過參數傳遞和解釋後會調用do_fork()這個核內函數同時也是fork()vfork()系統調用的最終實現
  
  
  int do_fork(unsigned long clone_flags unsigned long stack_start
  struct pt_regs *regs unsigned long stack_size)
  
  其中的clone_flags取自以下宏的
  
  
  #define CSIGNAL xff
  /* signal mask to be sent at exit */
  #define CLONE_VM x
  /* set if VM shared between processes */
  #define CLONE_FS    x
  /* set if fs info shared between processes */
  #define CLONE_FILES   x
  /* set if open files shared between processes */
  #define CLONE_SIGHAND x
  /* set if signal handlers and blocked signals shared */
  #define CLONE_PID x
  /* set if pid shared */
  #define CLONE_PTRACE x
  /* set if we want to let tracing continue on the child too */
  #define CLONE_VFORK x
  /* set if the parent wants the child to wake it up on mm_release */
  #define CLONE_PARENT x
  /* set if we want to have the same parent as the cloner */
  #define CLONE_THREAD x
  /* Same thread group? */
  #define CLONE_NEWNS x
  /* New namespace group? */
  #define CLONE_SIGNAL (CLONE_SIGHAND | CLONE_THREAD)
  
  在do_fork()中不同的clone_flags將導致不同的行為對於LinuxThreads它使用(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND)參數來調用clone()創建線程表示共享內存共享文件系統訪問計數共享文件描述符表以及共享信號處理方式本節就針對這幾個參數看看Linux內核是如何實現這些資源的共享的
  
  CLONE_VM
  
  do_fork()需要調用copy_mm()來設置task_struct中的mm和active_mm項這兩個mm_struct數據與進程所關聯的內存空間相對應如果do_fork()時指定了CLONE_VM開關copy_mm()將把新的task_struct中的mm和active_mm設置成與current的相同同時提高該mm_struct的使用者數目(mm_struct::mm_users)也就是說輕量級進程與父進程共享內存地址空間由下圖示意可以看出mm_struct在進程中的地位
   
  CLONE_FS
  
  task_struct中利用fs(struct fs_struct *)記錄了進程所在文件系統的根目錄和當前目錄信息do_fork()時調用copy_fs()復制了這個結構而對於輕量級進程則僅增加fs>count計數與父進程共享相同的fs_struct也就是說輕量級進程沒有獨立的文件系統相關的信息進程中任何一個線程改變當前目錄根目錄等信息都將直接影響到其他線程
  
  CLONE_FILES
  
  一個進程可能打開了一些文件在進程結構task_struct中利用files(struct files_struct *)來保存進程打開的文件結構(struct file)信息do_fork()中調用了copy_files()來處理這個進程屬性輕量級進程與父進程是共享該結構的copy_files()時僅增加files>count計數這一共享使得任何線程都能訪問進程所維護的打開文件對它們的操作會直接反映到進程中的其他線程
  
  CLONE_SIGHAND
  
  每一個Linux進程都可以自行定義對信號的處理方式在task_struct中的sig(struct signal_struct)中使用一個struct k_sigaction結構的數組來保存這個配置信息do_fork()中的copy_sighand()負責復制該信息輕量級進程不進行復制而僅僅增加signal_struct::count計數與父進程共享該結構也就是說子進程與父進程的信號處理方式完全相同而且可以相互更改
  
  do_fork()中所做的工作很多在此不詳細描述對於SMP系統所有的進程fork出來後都被分配到與父進程相同的cpu上一直到該進程被調度時才會進行cpu選擇
  
  盡管Linux支持輕量級進程但並不能說它就支持核心級線程因為Linux的線程進程實際上處於一個調度層次共享一個進程標識符空間這種限制使得不可能在Linux上實現完全意義上的POSIX線程機制因此眾多的Linux線程庫實現嘗
From:http://tw.wingwit.com/Article/program/Oracle/201311/17455.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.