轉載 linux 2.6進程與線程

linux 2.6進程與線程
 
 
1 >    線程和進程的差別
線程機制支持併發程序設計技術,在多處理器上能真正保證並行處理。而在linux實現線程很特別,linux把所有的線程都當作線程實現。
linux下線程看起來就像普通進程(只是該進程和其他進程共享資源,如地址空間)。上述機制與Microsoft windows或是Sun Solaris實現
差異很大。這些系統提供專門支持線程機制(輕量級進程)。

在現代操作系統中,進程支持多線程。進程是資源管理及分配的最小單元;而線程是程序執行的最小單元。一個進程的組成實體可以分爲兩大部分:線程集和資源集。進程中的線程是動態的對象,代表了進程指令的執行過程。資源,包括地址空間、打開的文件、用戶信息等等,由進程內的線程共享。線程有自己的私有數據:程序計數器,棧空間以及寄存器。

現實中有很多需要併發處理的任務,如數據庫的服務器端、網絡服務器、大容量計算等。

如果採用多進程的方法,則有如下問題:
      1> fork一個子進程的消耗是很大的,fork是一個昂貴的系統調用。
      2> 各個進程擁有自己獨立的地址空間,進程間的協作需要複雜的IPC技術,如消息傳遞和共享內存等。

線程推廣了進程的概念,使一個進程可以包含多個活動(或者說執行序列等等)。多線程的優點和缺點實際上是對立統一的。使用線程的優點在於:

      1> 改進程序的實時響應能力;
      2> 更有效的使用多處理器,真正的並行(parallelism);
      3> 改進程序結構,具備多個控制流;
      4> 通訊方便,由於共享進程的代碼和全局數據;
      5> 減少對系統資源的使用。對屬於同一個進程的線程之間進行調度切換時不需要調用系統調用,因此將減少額外的消耗,往往一個進程可以啓動上千個線程也沒有什麼問題。

缺點在於:
由於各線程共享進程的地址空間,因此可能會導致競爭,因此對某一塊有多個線程要訪問的數據需要一些同步技術。

 
2 >     線程的分類
2.1     內核線程
Linux內核可以看作一個服務進程(管理軟硬件資源,響應用戶進程的種種合理以及不合理的請求)。內核需要多個執行流並行,爲了防止可能的阻塞,多線程化是必要的。內核線程就是內核的分身,一個分身可以處理一件特定事情。Linux內核使用內核線程來將內核分成幾個功能模塊,像kswapd、kflushd等,這在處理異步事件如異步IO時特別有用。內核線程的使用是廉價的,唯一使用的資源就是內核棧和上下文切換時保存寄存器的空間。支持多線程的內核叫做多線程內核(Multi-Threads kernel )。內核線程的調度由內核負責,一個內核線程處於阻塞狀態時不影響其他的內核線程,因爲其是調度的基本單位。這與用戶線程是不一樣的。

2.2用戶線程
用戶線程在用戶空間中實現,內核並沒有直接對用戶線程進程調度,內核的調度對象和傳統進程一樣,還是進程本身,內核並不知道用戶線程的存在。

由於Linux內核沒有輕量級進程(線程)的概念,因此不能獨立的對用戶線程進行調度,而是由一個線程運行庫來組織線程的調度,其主要工作在於在各個線程的棧之間調度。如果一個進程中的某一個線程調用了一個阻塞的系統調用,整個進程就會被調度程序切換爲等待狀態,其他線程得不到運行的機會。因此linux使用了異步I/O機制。

 
3 >     內核線程
內核線程(thread)或叫守護進程(daemon),在操作系統中佔據相當大的比例,當Linux操作系統啓動以後,尤其是X window也啓動以後,你可以用”ps -ef”命令查看系統中的進程,這時會發現很多以”d”結尾的進程名,確切說名稱顯示裏面加 "[]"的,這些進程就是內核線程。系統的啓動是從硬件->內核->用戶態進程的,pid的分配是一個往前的循環的過程,所以隨系統啓動的內核線程的pid往往很小。

UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 09:42 ?        00:00:01 /sbin/init
root         2     0  0 09:42 ?        00:00:00 [kthreadd]
root         3     2  0 09:42 ?        00:00:00 [migration/0]
root         4     2  0 09:42 ?        00:00:00 [ksoftirqd/0]
root         5     2  0 09:42 ?        00:00:00 [watchdog/0]
root         6     2  0 09:42 ?        00:00:00 [events/0]
root         7     2  0 09:42 ?        00:00:00 [khelper]
root        40     2  0 09:42 ?        00:00:00 [kblockd/0]
root        64     2  0 09:42 ?        00:00:00 [kseriod]
root       110     2  0 09:42 ?        00:00:00 [pdflush]
root       111     2  0 09:42 ?        00:00:00 [pdflush]
root       112     2  0 09:42 ?        00:00:00 [kswapd0]
root       151     2  0 09:42 ?        00:00:00 [aio/0]
root      1327     2  0 09:42 ?        00:00:00 [ksuspend_usbd]
root      1330     2  0 09:42 ?        00:00:00 [khubd]
root      1362     2  0 09:42 ?        00:00:00 [ata/0]
root      1370     2  0 09:42 ?        00:00:00 [ata_aux]
root      1428     2  0 09:42 ?        00:00:00 [scsi_eh_0]
root      1430     2  0 09:42 ?        00:00:01 [scsi_eh_1]
root      2304     2  0 09:42 ?        00:00:00 [kjournald]
root      2507     1  0 09:42 ?        00:00:00 /sbin/udevd --daemon
root      2787     2  0 09:42 ?        00:00:00 [kgameportd]
root      3698     2  0 09:42 ?        00:00:00 [kapmd]
root      3872     2  0 09:42 ?        00:00:00 [kjournald]



3.1     內核線程
      events 處理內核事件 很多軟硬件事件(比如斷電,文件變更)被轉換爲events,並分發給對相應事件感興趣的線程進行響應

      ksoftirqd 處理軟中斷 硬件中斷處理往往需要關中斷,而這個時間不能太長,否則會丟失新的中斷。所以中斷處理的很大一部分工作移出,轉給任勞任怨的ksoftirqd在中斷之外進行處理。比如一個網絡包,從網卡里面取出這個過程可能需要關中斷,但是TCP/IP協議處理就不必關中斷了

      kblockd 管理磁盤塊讀寫

      kjournald Ext3文件系統的日誌管理 通常每個 _已mount_ 的 Ext3分區會有一個 kjournald看管,各分區的日誌是獨立

      pdflush dirty內存頁面的回寫 太多dirty的頁面意味着風險,比如故障時候的內容丟失,以及對突發的大量物理內存請求的響應(大量回寫會導致糟糕的響應時間)

      kswapd 內存回收 確保系統空閒物理內存的數量在一個合適的範圍

      aio 代替用戶進程管理io 用以支持用戶態的AIO
     


3.2     用戶進程

      crond 執行定時任務

      init 爲內核創建的第一個線程。引導用戶空間服務,管理孤兒線程,以及運行級別的轉換
     
      mingetty 等待用戶從tty登錄

      bash shell進程,一個命令行形式的系統接口;接受用戶的命令,並進行解釋、執

      sshd ssh登錄、文件傳輸、命令執行 等操作的服務進程

      klogd 從內核信息緩衝區獲取打印信息。內核在發現異常的時候,往往會輸出一些消息給用戶,這個對於故障處理很有用

      syslogd 系統日誌進程

      udevd 支持用戶態設備操作 (userspace device)
     
     
4>     創建線程 clone_flags   
最初的進程定義都包含程序、資源及其執行三部分,其中程序通常指代碼,資源在操作系統層面上通常包括內存資源、IO資源、信號處理等部分,而程序的執行通常理解爲執行上下文,包括對cpu的佔用,後來發展爲線程。在線程概念出現以前,爲了減小進程切換的開銷,操作系統設計者逐漸修正進程的概念,逐漸允許將進程所佔有的資源從其主體剝離出來,允許某些進程共享一部分資源,例如文件、信號,數據內存,甚至代碼。應用程序可以通過一個統一的clone()系統調用接口,用不同的參數指定創建輕量進程還是普通進程。在內核中,clone()調用經過參數傳遞和解釋後會調用do_fork(),這個核內函數同時也是fork():



long do_fork(unsigned long                   clone_flags,                                                                                                                    unsigned long stack_start,
struct pt_regs *regs,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)


其中的clone_flags取自以下宏的"或"值:

在 do_fork()中,不同的clone_flags將導致不同的行爲,對於LinuxThreads,它使用(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND)參數來調用clone()創建"線程",表示共享內存、共享文件系統訪問計數、共享文件描述符表,以及共享信號處理方式。本節就針對這幾個參數,看看Linux內核是如何實現這些資源的共享的。

1.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在進程中的地位:

2.CLONE_FS
task_struct 中利用fs(struct fs_struct *)記錄了進程所在文件系統的根目錄和當前目錄信息,do_fork()時調用copy_fs()複製了這個結構;而對於輕量級進程則僅增加 fs->count計數,與父進程共享相同的fs_struct。也就是說,輕量級進程沒有獨立的文件系統相關的信息,進程中任何一個線程改變當前目錄、根目錄等信息都將直接影響到其他線程。

3.CLONE_FILES
一個進程可能打開了一些文件,在進程結構 task_struct中利用files(struct files_struct *)來保存進程打開的文件結構(struct file)信息,do_fork()中調用了copy_files()來處理這個進程屬性;輕量級進程與父進程是共享該結構的,copy_files() 時僅增加files->count計數。這一共享使得任何線程都能訪問進程所維護的打開文件,對它們的操作會直接反映到進程中的其他線程。

4.CLONE_SIGHAND
每一個Linux進程都可以自行定義對信號的處理方式,在task_struct中的sig(struct signal_struct)中使用一個struct k_sigaction結構的數組來保存這個配置信息,do_fork()中的copy_sighand()負責複製該信息;輕量級進程不進行復制,而僅僅增加signal_struct::count計數,與父進程共享該結構。也就是說,子進程與父進程的信號處理方式完全相同,而且可以相互更改。


儘管Linux支持輕量級進程,但並不能說它就支持核心級線程,因爲Linux的"線程"和"進程"實際上處於一個調度層次,共享一個進程標識符空間,這種限制使得不可能在Linux上實現完全意義上的POSIX線程機制,因此衆多的Linux線程庫實現嘗試都只能儘可能實現POSIX的絕大部分語義,並在功能上儘可能逼近。
 

 

發表於: 2008-10-03,修改於: 2008-10-27 22:06,已瀏覽1315次,有評論1條 推薦 投訴
 
 


 
網友評論
 
網友: kylinux 時間:2008-10-07 10:45:12 IP地址:221.11.22.★
 
 
 
linux 內核創建線程還是kernel_thread(),最終會調用do_fork()

kernel_thread原形在/arch/kernel/process.c中.
(*fn)(void *)爲要執行的函數的指針,arg爲函數參數(可以爲NULL),
 flags爲do_fork產生線程時的標誌(上面談到了).
int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags)
{
    struct pt_regs regs;
    memset(&regs, 0, sizeof(regs));
    regs.ebx = (unsigned long) fn;    /* ebx指向函數地址 */
    regs.edx = (unsigned long) arg;    /* edx指向參數 */
    regs.xds = __USER_DS;
    regs.xes = __USER_DS;
    regs.orig_eax = -1;
    regs.eip = (unsigned long) kernel_thread_helper;
    regs.xcs = __KERNEL_CS;
    regs.eflags = X86_EFLAGS_IF | X86_EFLAGS_SF | X86_EFLAGS_PF | 0x2;
    /* 利用do_fork來產生一個新的線程,共享父進程地址空間,並且不允許調試子進程 */
    return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, &regs, 0, NULL, NULL);
}

 struct pt_regs regs;
結構解釋:在一個系統調用時,把寄存器的值保存在堆棧中。
具體作用分析下代碼,然後看看能不能解釋下
發佈了15 篇原創文章 · 獲贊 14 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章