Linux2.4內(nèi)核說明文檔(進(jìn)程與中斷管理篇) 作者:wantin6 時(shí)間:2006-08-08 23:34 |
[list=]
本文檔是《Linux2.4 內(nèi)核說明文檔》中的第二部分。以下是整個(gè)文檔大致目錄:
1,啟動(dòng) ([url=http://bbs./forum/viewtopic.php?t=557946]http://bbs./forum/viewtopic.php?t=557946[/url] )
2,進(jìn)程和中斷管理
3,虛擬文件系統(tǒng)
4,Linux 頁緩沖
5,IPC機(jī)制
本篇文檔的目錄為:
2.1. Tack結(jié)構(gòu)和進(jìn)程表
2.2. 創(chuàng)建和中止任務(wù)與內(nèi)核線程
2.3. 調(diào)度程序
2.4. Linux執(zhí)行鏈表
2.5. 等待隊(duì)列
2.6. 內(nèi)核時(shí)鐘
2.7. Bottom Halves
2.8. 任務(wù)隊(duì)列
2.9. I386體系中系統(tǒng)調(diào)用實(shí)現(xiàn)
2.10. 原子操作
2.11. 旋轉(zhuǎn)鎖、讀寫旋轉(zhuǎn)鎖和Big-Reader旋轉(zhuǎn)鎖
2.12. 信號(hào)燈和讀寫信號(hào)燈
2.13. 裝載模塊的內(nèi)核支持
一下是正文:
2. 進(jìn)程和中斷管理
2.1. Tack結(jié)構(gòu)和進(jìn)程表
linux下的每個(gè)進(jìn)程都是動(dòng)態(tài)分配一個(gè)task_struct結(jié)構(gòu),整個(gè)系統(tǒng)可以創(chuàng)建的最大進(jìn)程數(shù)僅由當(dāng)前可用物理內(nèi)存總數(shù)限制,并且等于(見kernel/fork.c:fork_init()函數(shù)):
/*
* The default maximum number of threads is set to a safe
* value: the thread structures can take up at most half
* of memory.
*/
max_threads = mempages / (THREAD_SIZE/PAGE_SIZE) / 2;
這個(gè)式子在IA32體系結(jié)構(gòu)上主要意味著最大數(shù)為物理內(nèi)存頁數(shù)/4,例如:在一個(gè)512M內(nèi)存機(jī)器上你可以創(chuàng)建32K線程。這對(duì)于舊版本(2.2或者更老)內(nèi)核的4K限制是一個(gè)可觀的改進(jìn),而且這可以在運(yùn)行時(shí)使用系統(tǒng)調(diào)用sysctl(2)修改KERN_MAX_THREADS,或者簡(jiǎn)單使用procfs系統(tǒng)接口來調(diào)整。
# cat /proc/sys/kernel/threads.max
32764
# echo 100000 >; /proc/sys/kernel/threads.max
# cat /proc/sys/kernel/threads.max
100000
# gdb .q vmlinux /proc/kcore
Core was generated by `BOOT_IMAGE=240ac18 ro root=306 video=matrox:vesa:0x118'.
#0 0x0 in ?? ()
(gdb) p max_threads
$1 = 100000
Linux系統(tǒng)上進(jìn)程的關(guān)聯(lián)表現(xiàn)為一個(gè)以下兩個(gè)方式鏈接的task_struct結(jié)構(gòu)的集合:
1) 以pid為鍵值的hash表。
2) 通過p->;next_task和p->;prev_task指針連接的雙向鏈表。
這個(gè)hash表名為pidhash,并在include/linux/sched.h中定義:
/* PID hashing. (shouldnt this be dynamic?) */
#define PIDHASH_SZ (4096 >;>; 2)
extern struct task_struct *pidhash[PIDHASH_SZ];
#define pid_hashfn(x) ((((x) >;>; ^ (x)) & (PIDHASH_SZ . 1))
所有的任務(wù)以他們的pid為鍵值存放到hash表中,并假定均勻地從(0 to PID_MAX-1)分布。這個(gè)hash表用來通過指定的pid快速的找到task結(jié)構(gòu),搜索函數(shù)find_task_pid()定義在include/linux/sched.h中:
static inline struct task_struct *find_task_by_pid(int pid)
{
struct task_struct *p, **htable = &pidhash[pid_hashfn(pid)];
for(p = *htable; p && p.>;pid != pid; p = p.>;pidhash_next)
;
return p;
}
在每個(gè)hash鏈上的所有任務(wù)都通過p->;pidhash_next和p->;pidhash_pprev連接起來,這在hash_pid函數(shù)和unhash_pid函數(shù)將指定進(jìn)程插入hash表或者移出hash表時(shí)使用。所有的操作都受到tasklist_lock寫同步鎖保護(hù)。
而雙向鏈表則為系統(tǒng)遍歷所有的任務(wù)提供了方便,這個(gè)操作由定義在include/linux/sched.h中的for_each_tack()宏來實(shí)現(xiàn)。
#define for_each_task(p) \
for (p = &init_task ; (p = p.>;next_task) != &init_task ; )
for_each_task()函數(shù)的使用者必須使用tasklist_lock讀同步鎖。注意for_each_task()函數(shù)采用init_task標(biāo)識(shí)鏈表的起點(diǎn),這樣才是安全的,因?yàn)榭杖蝿?wù)(pid = 0) 是不在鏈表里的。進(jìn)程hash表和進(jìn)程鏈表的修改操作,特別是fork函數(shù),exit函數(shù)和ptrace函數(shù),必須調(diào)用tasklist_lock寫同步鎖。更有趣的是,所有的寫操作還必須屏蔽當(dāng)前CPU的中斷,這個(gè)原因是顯而易見的:send_sigio函數(shù)遍歷了進(jìn)程表,這樣需要調(diào)用tasklist_lock讀同步鎖,并且該函數(shù)是kill_fasync函數(shù)在中斷環(huán)境下調(diào)用的。
現(xiàn)在我們已經(jīng)知道task_struct結(jié)構(gòu)是怎樣鏈接到一起的,現(xiàn)在讓我們分析一下task_struct結(jié)構(gòu)的成員。這些成員是UNIX系統(tǒng)的proc結(jié)構(gòu)和user結(jié)構(gòu)松散組合到一起的。
其他UNIX版本總是將進(jìn)程狀態(tài)信息作為單獨(dú)一部分常駐內(nèi)存,其他部分則作為進(jìn)程運(yùn)行時(shí)所需信息,如此簡(jiǎn)陋的設(shè)計(jì)僅僅因?yàn)閮?nèi)存時(shí)非常寶貴的資源。現(xiàn)代操作系統(tǒng)(如Linux或者FreeBSD)并不做如此區(qū)分,而是在內(nèi)核常駐內(nèi)存的數(shù)據(jù)結(jié)構(gòu)中維護(hù)進(jìn)程狀態(tài)。
include/linux/sched.h中定義了task_struct結(jié)構(gòu),并且通常大寫為1680字節(jié),狀態(tài)宏也定義同一個(gè)頭文件中。
volatile long state; /* .1 unrunnable, 0 runnable, >;0 stopped */
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define TASK_ZOMBIE 4
#define TASK_STOPPED 8
#define TASK_EXCLUSIVE 32
為什么TASK_EXCLUSIVE宏定義為32而不是16呢?這是由于16被TASK_SWAPPING使用了,并且后來在移出TASK_SWAPPING時(shí)沒有把TASK_EXCLUSIVE的值上調(diào)。
可變量p->;state的定義意味著它自身可以被中斷處理者異步修改。
1) TASK_RUNNING:含義是假定任務(wù)已經(jīng)處于運(yùn)行隊(duì)列中。至于不是已經(jīng)處于運(yùn)行隊(duì)列的原因是由于將一個(gè)任務(wù)標(biāo)識(shí)為TASK_RUNNING和將該任務(wù)移動(dòng)到運(yùn)行隊(duì)列不是一個(gè)原子操作。從運(yùn)行隊(duì)列角度考慮,操作時(shí)需要保持runqueue_lock讀同步鎖。如果這樣操作,你將發(fā)現(xiàn)在運(yùn)行隊(duì)列的每個(gè)任務(wù)都處于TASK_RUNNING狀態(tài)。然后,反過來卻不一定。同樣地,驅(qū)動(dòng)程序可以標(biāo)識(shí)他們自身狀態(tài)為TASK_INTERRUPTIBLE,然后調(diào)用schedule()函數(shù),這個(gè)函數(shù)將從運(yùn)行隊(duì)列移出它自己(除非當(dāng)時(shí)有一個(gè)導(dǎo)致它滯留在運(yùn)行隊(duì)列的未處理信號(hào))。
2) TASK_INTERRUPTIBLE:含義是任務(wù)處于休眠狀態(tài)但可以通過一個(gè)信號(hào)或者休眠中止時(shí)鐘喚醒。
3) TASK_UNINTERRUPTIBLE:含義類似于TASK_INTERRUPTIBL,但任務(wù)不能被喚醒。
4) TASK_ZOMBIE:含義是任務(wù)已經(jīng)被中止但它的狀態(tài)還沒被父進(jìn)程獲取。
5) TASK_STOPPED:含義是由于任務(wù)控制信號(hào)或者ptrace系統(tǒng)調(diào)用,任務(wù)已經(jīng)被停止。
6) TASK_EXCLUSIVE:含義是這不是一個(gè)單獨(dú)狀態(tài),但能夠與TASK_INTERRUPTIBLE或者TASK_UNINTERRUPTIBLE狀態(tài)并存(OR操作)。這意味著當(dāng)任務(wù)與其他在等待隊(duì)列休眠時(shí),它可以單獨(dú)被喚醒而不需要喚醒整個(gè)等待隊(duì)列的任務(wù)。
任務(wù)標(biāo)記包含了關(guān)于非互相排斥的進(jìn)程狀態(tài)信息。
unsigned long flags; /* per process flags, defined below */
/*
* Per process flags
*/
#define PF_ALIGNWARN 0x00000001 /* Print alignment warning msgs */
/* Not implemented yet, only for 486*/
#define PF_STARTING 0x00000002 /* being created */
#define PF_EXITING 0x00000004 /* getting shut down */
#define PF_FORKNOEXEC 0x00000040 /* forked but didn't exec */
#define PF_SUPERPRIV 0x00000100 /* used super.user privileges */
#define PF_DUMPCORE 0x00000200 /* dumped core */
#define PF_SIGNALED 0x00000400 /* killed by a signal */
#define PF_MEMALLOC 0x00000800 /* Allocating memory */
#define PF_VFORK 0x00001000 /* Wake up parent in mm_release */
#define PF_USEDFPU 0x00100000 /* task used FPU this quantum (SMP) */
p->;has_cpu, p->;processor, p->;counter, p->;priority, p->;policy and p->;rt_priority字段和調(diào)度程序關(guān)聯(lián),并將在后面描述。
p->;mm 和p->;active_mm字段分別指向mm_struct結(jié)構(gòu)描述的進(jìn)程地址空間和有效地址空間(如果這個(gè)進(jìn)程不是內(nèi)核進(jìn)程的話)。這使得當(dāng)任務(wù)被調(diào)度離開時(shí)TLB能夠在地址空間自由切換。所以,如果當(dāng)前正在執(zhí)行內(nèi)核任務(wù)(沒有p->;mm),則它的next->;active_mm將被設(shè)置為已經(jīng)被調(diào)度離開的任務(wù)的prev->;active_mm,如果pre-mm != NULL,則這個(gè)地址將和prev->;mm相同。如果CLONE_VM標(biāo)識(shí)傳遞到了clone系統(tǒng)調(diào)用或者依靠vfork系統(tǒng)調(diào)用,則地址空間可以在任務(wù)之間共享。
p->;exec_domain和p->;personality字段與任務(wù)的特性相關(guān),也就是為了模仿UNIX特性的唯一系統(tǒng)調(diào)用。
p->;fs字段包含了文件系統(tǒng)信息,在linux下有三個(gè)方面的含義:
1) root目錄實(shí)體結(jié)構(gòu)和掛載點(diǎn);
2) 預(yù)備的root目錄實(shí)體和掛載點(diǎn);
3) 當(dāng)前工作目錄實(shí)體和掛載點(diǎn);
這個(gè)結(jié)構(gòu)同樣包含了一個(gè)引用計(jì)數(shù),因?yàn)楫?dāng)進(jìn)行帶CLONE_FS標(biāo)識(shí)的clone系統(tǒng)調(diào)用時(shí),它是共享的。
p->;files字段包含了文件句柄表,這在進(jìn)行帶CLONE_FILES標(biāo)識(shí)clone系統(tǒng)調(diào)用時(shí)也是多任務(wù)共享的。
p->;sig字段包含了信號(hào)處理函數(shù)入口,以CLONE_SIGHAND參數(shù)執(zhí)行clone操作后頁可以在進(jìn)程間共享。
__________________________________
雪,是我的印記;
風(fēng),是我的侍從.
請(qǐng),別問我是誰;
我,其實(shí)不是誰.
我只是飄搖的飛雪,陽光下融在風(fēng)里的孤雕.
2.2. 創(chuàng)建和中止任務(wù)與內(nèi)核線程
不同的操作系統(tǒng)書籍,從一個(gè)“正在執(zhí)行的程序的實(shí)例”到“由clone或者fork系統(tǒng)調(diào)用產(chǎn)生的任務(wù)”等不同方式定義了“進(jìn)程”。在linux下,共有三種類型程序:
空線程;
內(nèi)核線程;
用戶任務(wù);
空線程在為第一個(gè)CPU引導(dǎo)時(shí)創(chuàng)建,然后依靠定義在arch/i386/kernel/smpboot.c的fork_by_hand()函數(shù)手工為每個(gè)CPU創(chuàng)建這個(gè)線程。所有的空線程共享一個(gè)init_task結(jié)構(gòu),但都擁有各自的存放在CPU隊(duì)列里的init_tss表示的TSS結(jié)構(gòu)。他們以CLONE_PID方式clone,PID都為零,其他任務(wù)都不能共享這個(gè)PID。
內(nèi)核模式下,kernel_thread函數(shù)調(diào)用clone系統(tǒng)調(diào)用創(chuàng)建了內(nèi)核線程。內(nèi)核線程通常沒有用戶地址空間,也就是p->;mm=NULL,因?yàn)樗麄兠鞔_通過daemonize()函數(shù)執(zhí)行exit_mm()函數(shù)。內(nèi)核線程通??梢灾苯硬僮鲀?nèi)核地址空間,并被分配低范圍的pid號(hào)。當(dāng)在處理器模式下運(yùn)行時(shí)意味著內(nèi)核線程將享用所有的I/O特權(quán)并不能被調(diào)用程序預(yù)清空。
用戶任務(wù)通過clone或者fork系統(tǒng)調(diào)用創(chuàng)建,他們都在內(nèi)部調(diào)用了kernel/fork.c的do_fork()函數(shù)。
讓我們分析一下當(dāng)用戶進(jìn)程調(diào)用fork系統(tǒng)調(diào)用時(shí)發(fā)生了什么。雖然fork操作在傳遞用戶堆棧和寄存器時(shí)依賴于體系架構(gòu),但在下面真實(shí)執(zhí)行這個(gè)操作的do_fork()函數(shù)確實(shí)簡(jiǎn)潔的,并位于kernel/fork.c文件。
以下步驟將被執(zhí)行:
1) 本地變量被設(shè)置為-ENOMEM,當(dāng)fork創(chuàng)建一個(gè)新任務(wù)結(jié)構(gòu)失敗時(shí)將作為錯(cuò)誤代碼返回。
2) .如果CLONE_PID標(biāo)識(shí)被設(shè)置,則返回-EPERM錯(cuò)誤,除非調(diào)用者是空線程。普通用戶線程clone時(shí)不能傳遞CLONE_PID標(biāo)識(shí)并期待操作成功。SIFCHLDclone標(biāo)識(shí),對(duì)于fork來說,它被認(rèn)為是不相關(guān)的,僅在sys_clone調(diào)用do_fork時(shí)才被認(rèn)為是相關(guān)的。
3) 初始化current->;vfork_sem。它將在sys_vfork函數(shù)為了休眠父進(jìn)程直到子進(jìn)程執(zhí)行mm_release函數(shù)時(shí)使用,就像執(zhí)行其他程序或者中止其他程序一樣。
4) 通過alloc_task_struct()宏分配一個(gè)新的任務(wù)結(jié)構(gòu)。在x86系統(tǒng)上,它僅是一個(gè)GFP_KERNEL優(yōu)先級(jí)的gfp。這酒是為何fork系統(tǒng)調(diào)用可能休眠的第一個(gè)原因。如果分配失敗,返回-ENOMEM錯(cuò)誤。
5) 通過結(jié)構(gòu)拷貝*p = *current,將所有當(dāng)前進(jìn)程結(jié)構(gòu)的數(shù)據(jù)都拷貝到新進(jìn)程,或許這個(gè)操作應(yīng)該被memcpy替換。然后,所有不能被子進(jìn)程修改的字段將被設(shè)置為正確的值。
6) 大范圍的內(nèi)核鎖被采用以防止其他部分執(zhí)行本段代碼。
7) 如果父進(jìn)程擁有用戶資源則校驗(yàn)是否超出了RLIMIT_NPROC限制。如果是這樣,則返回-EAGAIN錯(cuò)誤;如果沒有,則通過指定的uid將計(jì)數(shù)器p->;user->;count進(jìn)程數(shù)刷新。
如果系統(tǒng)所有的任務(wù)數(shù)目超過了最大線程數(shù),返回-EAGAIN錯(cuò)誤。
9) 如果進(jìn)程是依賴預(yù)模塊執(zhí)行的,則增加依賴模塊的引用計(jì)數(shù)。
10) 如果進(jìn)程是依賴預(yù)模塊二進(jìn)制格式的,也增加依賴模塊的引用計(jì)數(shù)。
11) 子進(jìn)程被標(biāo)識(shí)為“沒有被執(zhí)行”(p->;did_exec=0)。
12) 子進(jìn)程被標(biāo)識(shí)為'not.swappable' (p->;swappable = 0)。
13) 子進(jìn)程被置為TASK_UNINTERRUPTIBLE狀態(tài),即p->;state = TASK_UNINTERRUPTIBLE。
14) 依照clone_flags的數(shù)值設(shè)置子進(jìn)程的p->;flags,如果是簡(jiǎn)單fork,p->;flags= PF_FORKNOEXEC。
15) 通過快速算法kernel/fork.c的get_pid()函數(shù)設(shè)置子進(jìn)程號(hào)p->;pid。
16) 初始化子進(jìn)程其他任務(wù)結(jié)構(gòu)。最后子進(jìn)程結(jié)構(gòu)被插入到pidhash表中,并且被喚醒。
這樣任務(wù)就被創(chuàng)建了。停止任務(wù)有很多方式。
1) 通過exit系統(tǒng)調(diào)用;
2) 收到一個(gè)中止信號(hào);
3) 被確定異常強(qiáng)制中止;
4) 以func == 1參數(shù)調(diào)用bdflush。
系統(tǒng)調(diào)用的實(shí)現(xiàn)函數(shù)都有sys_前綴,當(dāng)他們通常僅與參數(shù)檢測(cè)或者以細(xì)節(jié)方式傳遞信息,真正的操作是由do_**函數(shù)完成的。所以sys_exit()函數(shù)調(diào)用了do_exit來完成操作。盡管如此,內(nèi)核其他部分有時(shí)也通過調(diào)用sys_exit實(shí)現(xiàn)堆do_exit的調(diào)用。
do_exit函數(shù)定義在kernel/exit.c中,按照以下幾個(gè)步驟執(zhí)行:
獲取內(nèi)核全局鎖;
在最后調(diào)用一直循環(huán)的schedule()函數(shù);
設(shè)置任務(wù)狀態(tài)為TASK_ZOMBIE;
以current->;pdeath_signa信號(hào)通知所有的子進(jìn)程;
以等同于SIGCHLD的信號(hào)current.>;exit_signal通知父進(jìn)程;
釋放fork函數(shù)分配的資源,關(guān)閉已經(jīng)打開的文件;
在采用少量FPU切換的體系中,不管硬件設(shè)備要求什么都向FPU的所有者傳遞一個(gè)“none”;
|
|