http://www.qiusuo365.com/bbs/forumdisplay.php?fid=104&page=4我們基于RH9 內(nèi)核從兩部分來分析Linux系統(tǒng)動(dòng)態(tài)運(yùn)行過程
一: 系統(tǒng)初始化開始,Linux進(jìn)入保護(hù)模式,初始內(nèi)存系統(tǒng)、中斷系統(tǒng)、文件系
統(tǒng)等,直到創(chuàng)建第一個(gè)用戶進(jìn)程。
二: 用戶進(jìn)程通過系統(tǒng)調(diào)用主動(dòng)進(jìn)入內(nèi)核,CPU 接受中斷請(qǐng)求被動(dòng)執(zhí)行各種中斷服務(wù)。
第一部分 系統(tǒng)初始化
進(jìn)入保護(hù)模式 Arch/i386/boot/Setup.s
gdt:
.fill GDT_ENTRY_KERNEL_CS,8,0 #空12×(8個(gè)字節(jié)=2個(gè)雙字),用0 填充。
.word 0xFFFF # 4Gb - (0x100000*0x1000 = 4Gb)
.word 0 # base address = 0
.word 0x9A00 # code read/exec
.word 0x00CF # granularity = 4096, 386
# (+5th nibble of limit)
.word 0xFFFF # 4Gb - (0x100000*0x1000 = 4Gb)
.word 0 # base address = 0
.word 0x9200 # data read/write
.word 0x00CF # granularity = 4096, 386
gdt_48:
.word 0x8000 # gdt limit=2048,
# 256 GDT entries
我們進(jìn)入保護(hù)模式之后選擇子首先應(yīng)該是0x60 = 1100000b, 右移3 位為1100 =
0xC = 12 ,這就是code在GDT里的位置。當(dāng)然這只是第一次進(jìn)入保護(hù)模式,以后還會(huì)調(diào)整。
進(jìn)入分頁模式 arch/i386/kernel/head.S
.org 0x1000
ENTRY(swapper_pg_dir)
.long 0x00102007 #基地址為0x00102000,就是下面pg0,0x00100000是內(nèi)核加載的地方,1M的位置
.long 0x00103007
.fill BOOT_USER_PGD_PTRS-2,4,0 #768-2 (long) 0
/* default: 766 entries */
.long 0x00102007
.long 0x00103007
/* default: 254 entries */
.fill BOOT_KERNEL_PGD_PTRS-2,4,0 #256-2(long) 0
# 這個(gè)說明了內(nèi)核是1G,用戶就3G。
.org 0x2000
ENTRY(pg0)
.org 0x3000
ENTRY(pg1)
說明映射了兩個(gè)頁面,一個(gè)頁面代表4M, 每個(gè)pg0 里的一項(xiàng)代表4K。具體pg0數(shù)據(jù)是用程序填充的 參考L82。L101通過短跳轉(zhuǎn)進(jìn)入分頁(但eip 還不是),L104通過絕對(duì)地址完全進(jìn)入分頁。比如L105的地址是0xC0100058 =1100 00000001 0000 0000 0000 0101 1000B,高10 位為1100 0000 00 = 0x300 =768。從swapper_pg_dir里找768項(xiàng)就是0x00102007,檢查之后基地址為0x00102000;就是pg0,再看0xC0100058 中間10位01 0000 0000B = 0x100 = 256,再找pg0的256項(xiàng)應(yīng)該為00100***,那么最后的物理地址為0x00100058,所以內(nèi)核里的虛擬地址換物理地址很簡單,就是0xC0100058 -0xC0000000 = 0x00100058 就是物理地址?,F(xiàn)在系統(tǒng)只是映射了8M的內(nèi)存空間,之后還會(huì)調(diào)整。
內(nèi)存管理
物理內(nèi)存管理
第一次探測
Arch/i386/boot/setup.S L281
在這里使用了三種方法,通過調(diào)用0x15 中斷:
E820H 得到memory map。
E801H 得到大小。
88H 0-64M.
第二次探測
start_kernel() -> setup_arch()
這里主要是對(duì)物理內(nèi)存的初始化,建立zone區(qū)。并且初始化page。
start_kernel() -> setup_arch() -> setup_memory_region()
把bios 里的memory map 拷貝到 e820 這個(gè)全局變量里。
會(huì)在屏幕上顯示
BIOS-provided physical RAM map:
BIOS-e820: 0000000000000000 – 000000000009FC00 (usable)
BIOS-e820: 0000000000100000-0000000002000000 (usable)
BIOS-e820 說的是通過e820 來讀取成功的數(shù)據(jù),第一個(gè)代表起始地址,后面接
著的是大小。usable 說明內(nèi)存可用。還有 reserved, ACPI data, ACPI NVS 等。
第二條說明起始地址為0x100000 = 1M,大小是0x2000000 = 32M 內(nèi)存。
start_kernel() -> setup_arch() -> setup_memory ()
start_kernel() -> setup_arch() -> setup_memory ()->find_max_pfn()
通過e820計(jì)算出最大可用頁面。
e820.map[1].addr = 0x100000 e820.map[1].size = 0x1f00000,那么start =
PFN_UP(0x100000) = (0x100000+0x1000(4096 = 1>>12)-1)>>12 =
0x100FFF>>12=0x100, end = PFN_DOWN(0x2000000) = 0x2000000 >> 12 =
0x2000 = 8192,那么max_pfn = 0x2000 就是最大的頁面
start_kernel() -> setup_arch() -> setup_memory ()->find_max_low_pfn()
主要是高端內(nèi)存的限制。
max_low_pfn = max_pfn 不能大于896M, PFN_DOWN((-0xC0000000 –
0x8000000(128 setup_arch() -> setup_memory ()-> init_bootmem()
引導(dǎo)內(nèi)存分配只使用在引導(dǎo)過程中,為內(nèi)核提供保留分配頁,并且建立內(nèi)存的位圖。
比如_end 為0xc02e5f18,start_pfn = PFN_UP(__pa(&end)) = 0x2e6,所以
start_pfn 指的是內(nèi)核結(jié)束的下一個(gè)頁面((_pa(&_end) >> 12 =0x2e5 ,那么
init_bootmen(start_pfn,max_low_pfn) = init_bootmen(0x2e6,0x2000)
start_kernel() -> setup_arch() -> setup_memory ()->
init_bootmem()->init_bootmem_core()
init_bootmen(0x2e6,0x2000) = init_bootmem_core(&contig_page_data, 0x2e6, 0,
0x2000),mapsize就是建立內(nèi)存位圖大小需要多少字節(jié) 1024=(8192-0+7)/8,
1個(gè)位代表著4K的一個(gè)頁面。
bdata->node_bootmem_map = phys_to_virt(mapstart node_boot_start = (start node_low_pfn = end = 8192
把0xc02e6000 開始0xff 填充1024 個(gè)字節(jié) 0xc02e6400,這樣就代表著所有內(nèi)
存不可用。
start_kernel() -> setup_arch() -> setup_memory ()-> register_bootmem_low_pages
e()
根據(jù)e820和內(nèi)存位圖來標(biāo)識(shí)位圖那些內(nèi)存可用。
start_kernel() -> setup_arch() -> setup_memory ()-> register_bootmem_low_pages
e()->free_bootmem()->free_bootmem_core()
第一次調(diào)用 free_bootmem(PFN_PHYS(curr_pfn), PFN_PHYS(size)) =
free_bootmem(PFN_PHYS( 0), PFN_PHYS(159))= free_bootmem(0, 0x9F000))
free_bootmem_core(contig_page_data.bdata, 0, 0x9f000)
eidx代表這塊內(nèi)存共占用多少頁面
eidx = (addr + size - bdata->node_boot_start)/PAGE_SIZE = (0+0x9f000-0)/0x1000=0x9f
end = (addr + size)/PAGE_SIZE=(0+0x9f000)/0x1000=0x9f
start = (addr + PAGE_SIZE-1) / PAGE_SIZE = (0+0x1000-1)/0x1000=0
sidx = start - (bdata->node_boot_start/PAGE_SIZE)=0-(0/0x1000)=0
每一次循環(huán)清1 位,第一次循環(huán)0x9f次清到0x9f /8= 0x13,所以第一次清到
了0xc02e6013的第7位159%8 = 7,所以0xc02e6013的數(shù)據(jù)應(yīng)該是0x10000000,
第二次free_bootmem_core(contig_page_data.bdata, 0x100000, 0x 1F00000)
eidx = (addr + size - bdata->node_boot_start)/PAGE_SIZE = 0x2000
end = (addr + size)/PAGE_SIZE = 0x2000
start = (addr + PAGE_SIZE-1) / PAGE_SIZE = 0x100
sidx = start - (bdata->node_boot_start/PAGE_SIZE) = 0x100
這次從0xc02e6020 ( 0x100/8 ) 開始清0x2000-0x100 次, 清到
0xc02e6400(0xc02e6020+0x1f00/8)
通過free_bootmem的操作,我們已經(jīng)把內(nèi)存的位圖標(biāo)識(shí)出來。
start_kernel() -> setup_arch() -> setup_memory ()->reserve_bootmem()
保留內(nèi)存,說明這部分不能用于動(dòng)態(tài)分配。
reserve_bootmem(HIGH_MEMORY, (PFN_PHYS(start_pfn) +
bootmap_size + PAGE_SIZE-1) - (HIGH_MEMORY)) =
reserve_bootmem(0x100000, (PFN_PHYS(2e6) +1024 + 4096-1) - (0x100000))=
reserve_bootmem(0x100000, 1E73FF)
保留內(nèi)核開始1M 到建立的內(nèi)存位圖的結(jié)束地方,因?yàn)槲粓D是接著內(nèi)核的下
一個(gè)頁面存放的,所以一起保留,對(duì)于+ PAGE_SIZE-1 操作就是位圖的結(jié)束
位置也與內(nèi)存邊界對(duì)齊。重新改寫內(nèi)存位圖之后對(duì)于我們的情況是
0xc02e6020(1M)到0xc02e605C 都改成了1(保留)0xc02e605D還是0
reserve_bootmem(0,PAGE_SIZE)= reserve_bootmem(0,4096)
保留物理內(nèi)存的第0 頁內(nèi)存。0x0-0x4096
start_kernel() -> setup_arch() -> paging_init()
當(dāng)我們上面的物理內(nèi)存都完成時(shí),我們就要對(duì)所有內(nèi)存進(jìn)行管理,需要重新
建立頁面映射。
start_kernel() -> setup_arch() -> paging_init()->pagetable_init()
我們根據(jù)已有的內(nèi)存信息,重新修改頁面表
end = (unsigned long)__va(max_low_pfn*PAGE_SIZE) = 0xC2000000
pgd_base = swapper_pg_dir = 0xC0101000
i = __pgd_offset(PAGE_OFFSET) = (0xC0000000>>22)&(1024-1)=768
pgd = pgd_base + i= 0xC0101C00
pgd就是內(nèi)核模式的第一個(gè)頁目錄
第一個(gè)for循環(huán)就是說從768開始到1024結(jié)束,也就是swapper_pg_dir結(jié)束,
這部分都是內(nèi)核的頁目錄,也就是我們要修改的頁目錄。
第二個(gè)for循環(huán)用于中間頁表,對(duì)我們2 層i386 體系無效。
alloc_bootmem_low_pages(PAGE_SIZE) = __alloc_bootmem(0x1000,0x1000,0)
->__alloc_bootmem_core(pgdat->bdata, 0x1000,0x1000,0)
eidx = bdata->node_low_pfn - (bdata->node_boot_start >> PAGE_SHIFT) =
8192-(0>>12) = 8192 代表著這個(gè)區(qū)域可用的頁面,實(shí)際上就是整個(gè)內(nèi)存。
我們以前保留過第0 頁面的內(nèi)存,所以我們分配內(nèi)存就是從第一頁面開始,
重新寫內(nèi)存位圖將已分配的頁面做保留標(biāo)記。這次運(yùn)行后我們分配了一個(gè)頁
面(0x4096),開始地址為0xc0001000,與此同時(shí),0xc02e6000 = 0x3 = 0011。
第三個(gè)for循環(huán)用于寫頁表,寫滿這個(gè)我們新分配的頁表。
第一次循環(huán)的時(shí)候pte = 0xc0001000;vaddr = 0xc0000000;
*pte = mk_pte_phys(__pa(vaddr), PAGE_KERNEL) =
mk_pte_phys(0, MAKE_GLOBAL(_PAGE_PRESENT | _PAGE_RW |
_PAGE_DIRTY | _PAGE_ACCESSED) =
mk_pte_phys(0, MAKE_GLOBAL(0x001 | 0x002 | 0x040 | 0x020) =
mk_pte_phys(0, ( MAKE_GLOBAL(0x001 | 0x002 | 0x040 | 0x020) = 0x63)) =
mk_pte_phys(0, pgprot_t __ret = __pgprot(0x63)) = mk_pte_phys(0, __ret) =
__mk_pte((0) >> 12, __ret) = __pte(((0) setup_arch() -> paging_init()->zone_sizes_init()
根據(jù)內(nèi)存位圖開始建立分區(qū)。
max_dma = virt_to_phys((char*) MAX_DMA_ADDRESS) >> PAGE_SHIFT =
virt_to_phys(0xC1000000) >> 12 = 0x1000
對(duì)于我們32M內(nèi)存來說,zones_size = { 4096, 4096, 0 }
start_kernel() -> setup_arch() -> paging_init()->zone_sizes_init()->free_area_init()
建立分區(qū)數(shù)據(jù)結(jié)構(gòu):標(biāo)記所有頁面保留,標(biāo)記所有內(nèi)存隊(duì)列空,清除內(nèi)存位
圖。(需要詳細(xì))
map_size = (totalpages + 1)*sizeof(struct page) = 0xE000 根據(jù)page結(jié)構(gòu)看
8192 個(gè)頁面需要多大空間。
lmem_map =alloc_bootmem_node(pgdat, 0xE000) =
__alloc_bootmem_node(pgdat, 0xE000, 0x10, 0x1000000) = 0xc1000000
lmem_map = (struct page *)(PAGE_OFFSET +
MAP_ALIGN((unsigned long)lmem_map - PAGE_OFFSET))
= 0xC0000000+MAP_ALIGN(0x1000000) = 0xC0000000+
((((0x1000000) % sizeof(mem_map_t)) == 0) ? (0x1000000) : ((0x1000000) +
sizeof(mem_map_t) - ((0x1000000) % sizeof(mem_map_t))))=
0xC0000000+((8==0)?0x1000000: 0x1000038-8) = 0xC1000030
因?yàn)閏ache需要對(duì)齊
接下來循環(huán)初始化每個(gè)區(qū)
zone->zone_mem_map = mem_map + offset; 這個(gè)區(qū)的mem_map 結(jié)構(gòu)位置
zone->zone_start_paddr = zone_start_paddr; 這個(gè)區(qū)的開始虛擬地址。
…
for (i = 0; i free_area
.free_list);
…
}
這是伙伴分配系統(tǒng)的初始化
這就說明了每個(gè)區(qū)有每個(gè)區(qū)自己的管理系統(tǒng)。
http://www-900.ibm.com/developerWorks/cn/linux/l-numa/index.shtml
start_kernel() -> setup_arch() -> paging_init()->zone_sizes_init()->free_area_init()
->build_zonelists()
start_kernel() -> setup_arch() -> dmi_scan_machine()
dmi_iterate()
isa_memcpy_fromio(buf, fp, 15)= memcpy_fromio(buf,__ISA_IO_base +
0xF0000,15) = __memcpy(buf,__io_virt(0xC00F0000),(15))
buf = 0xC00F0000 里的數(shù)據(jù),循環(huán)一直到fp = 0xFFFFF為止,每次fp 加0x10
當(dāng)這些執(zhí)行完的時(shí)候,內(nèi)核的基礎(chǔ)也就大部分已經(jīng)建立 ,我們在繼續(xù)看第
三部分的初始化。
第三次探測
start_kernel() ->mem_init()
start_kernel() ->mem_init()->free_pages_init()
start_kernel() ->mem_init()->free_pages_init()->free_all_bootmem_core()
struct page *page = pgdat->node_mem_map; 這就是第一個(gè)page.
idx = bdata->node_low_pfn - (bdata->node_boot_start >> PAGE_SHIFT) =
8192-0>>12 = 8192
第一個(gè)循環(huán)就是根據(jù)我們以前建立的內(nèi)存位圖來初始化Page。
start_kernel() ->mem_init()->free_pages_init()->free_all_bootmem_core()->
__free_page()->__free_pages()->__free_pages_ok()
釋放內(nèi)存頁(需要詳細(xì))
第二個(gè)循環(huán)就是釋放內(nèi)存位圖本身的內(nèi)存。
執(zhí)行完后以后的內(nèi)存操作就可以使用Page 了,第三階段的內(nèi)存初始化完畢,還
有一部分零散的內(nèi)存操作會(huì)在別的部分討論,至此,物理內(nèi)存管理結(jié)束。
虛擬內(nèi)存管理
中斷系統(tǒng)
描述
X86 微機(jī)通過外接兩片級(jí)連的可編程中斷控制器8259A,接收更多的外部中
斷。每個(gè)8259A控制器可以管理8 條中斷線,兩個(gè)可以控制15 條中斷。
由于Intel 公司保留0-31 號(hào)中斷向量用來處理異常事件,所以硬件中斷必須
設(shè)在31以后,Linux則在實(shí)模式下初始化時(shí)把硬件中斷設(shè)在0x20-0x2F。還有一
個(gè)是0x80是系統(tǒng)調(diào)用。
中斷門與陷阱門區(qū)別:通過中斷門門進(jìn)入中斷服務(wù)程序時(shí)CPU 會(huì)自動(dòng)將中斷關(guān)
閉,而通過陷阱門進(jìn)入則不會(huì)。
原理
Linux的中斷實(shí)現(xiàn)原理:考慮中斷處理的效率,Linux的中斷處理程序分為兩
個(gè)部分:上半部和下半部。上半部的功能是“登記中斷”,具體的中斷實(shí)現(xiàn)會(huì)在
下半部。之所以上半部要快,因?yàn)樯习氩渴峭耆帘沃袛嗟摹O掳氩坑幸韵聨追N
實(shí)現(xiàn)方式:bottom half, task queue(沒看), tasklet, softicq,在我們分析的這個(gè)版
本中,我認(rèn)為bottom half以及softicq 都是基于tasklet實(shí)現(xiàn)的,以后會(huì)具體介紹。
第一次中斷設(shè)置arch/i386/kernel/head.S
# L325
ignore_int 這是默認(rèn)的中斷服務(wù)程序,實(shí)際上什么也不做。
L359
SYMBOL_NAME(idt_descr):
.word IDT_ENTRIES*8-1 # idt contains 256 entries
.long SYMBOL_NAME(idt_table) #idt_table定義在Arch/i386/Kernel/Traps.c
L245
lidt idt_descr
這樣如果有中斷、異常發(fā)生就會(huì)去ignore_int空轉(zhuǎn)一次。
第二次中斷設(shè)置
start_kernel() -> trap_init ()
主要是對(duì)一些系統(tǒng)保留的中斷向量初始化
struct desc_struct idt_table[256] Arch/i386/kernel/Traps.c L67
extern irq_desc_t irq_desc [NR_IRQS]; include/linux/irq.h L67
idt_table = 0xC028B000
陷阱門 只允許在系統(tǒng)級(jí)下調(diào)用
set_trap_gate(0,÷_error)=_set_gate(idt_table+0,15,0, 0xC0109060);
0xC028B000 = 0xC0108F0000609060
中斷門 只允許在系統(tǒng)級(jí)下調(diào)用
set_intr_gate(2,&nmi)=_set_gate(idt_table+2,14,0, 0xC0109108);
0xC028B010 = 0xC0108E0000609108
系統(tǒng)門 可以用戶級(jí)調(diào)用,否則無法提供系統(tǒng)調(diào)用了??吹谌齻€(gè)參數(shù)為3
set_system_gate(3,&int3) =_set_gate(idt_table+3,15,3, 0xC0109138);
0xC028B018 = 0xC010EF0000609138
start_kernel() -> trap_init () -> cpu_init()
初始化CPU,并且重新裝載GDT 與IDT。
我們先說一下ccurrent 宏。通過這個(gè)宏可以得到當(dāng)前運(yùn)行進(jìn)程的struct
task_struct 。
cpu_gdt_table Arch/i386/Kernel/head.S L423
然后通過兩行匯編重新裝載GDT與IDT。
start_kernel() -> init_IRQ()
主要是對(duì)外設(shè)的中斷初始化
init_ISA_irqs() 中斷請(qǐng)求隊(duì)列的初始化
init_ISA_irqs()->init_8259A() 可編程中斷控制器8259A的初始化
繼續(xù)初始化中斷向量,從0x20(32)開始16個(gè),因?yàn)橹笆潜A舻摹?br>void (*interrupt[NR_IRQS])(void) = {
IRQLIST_16(0x0),
… }
#define IRQLIST_16(x) \
IRQ(x,0), IRQ(x,1), IRQ(x,2), IRQ(x,3), \
IRQ(x,4), IRQ(x,5), IRQ(x,6), IRQ(x,7), \
IRQ(x,8), IRQ(x,9), IRQ(x,a), IRQ(x,b), \
IRQ(x,c), IRQ(x,d), IRQ(x,e), IRQ(x,f)
#define IRQ(x,y) \
IRQ##x##y##_interrupt
#
define NR_IRQS 224
255 -31 = 224 所以interrupt 定義的Intel保留之后的中斷
對(duì)于IRQ##x##y##_interrupt函數(shù)的定義在 include/asm-i386/hw_irq.h中
#define BUILD_IRQ(nr) \
asmlinkage void IRQ_NAME(nr); \
__asm__( \
"\n"__ALIGN_STR"\n" \
SYMBOL_NAME_STR(IRQ) #nr "_interrupt:\n\t" \
"pushl $"#nr"-256\n\t" \
"jmp common_interrupt");
經(jīng)過處理之后得到:
asmlinkage void IRQ0x01_interrupt(); \
__asm__( \
"\n"__ALIGN_STR"\n" \
IRQ0x01_interrupt:\n\t" \
"pushl $0x01-256\n\t" \ #這就是每個(gè)中斷的最大區(qū)別
"jmp common_interrupt");
#define BUILD_COMMON_IRQ() \
asmlinkage void call_do_IRQ(void); \
__asm__( \
"\n" __ALIGN_STR"\n" \
"common_interrupt:\n\t" \
SAVE_ALL \
"pushl $ret_from_intr\n\t" \
SYMBOL_NAME_STR(call_do_IRQ)":\n\t" \
"jmp "SYMBOL_NAME_STR(do_IRQ));
*/
中斷發(fā)生時(shí)都會(huì)運(yùn)行do_IRQ 這個(gè)公共的中斷處理函數(shù)。返回時(shí)就會(huì)返回到
ret_from_intr繼續(xù)運(yùn)行后再返回。
idt_table里存放了具體的中斷服務(wù)的內(nèi)存地址,CPU產(chǎn)生中斷會(huì)通過idt_table
去運(yùn)行具體的中斷服務(wù)程序。除了系統(tǒng)保留的中斷是運(yùn)行各自不同的中斷服
務(wù),其它都會(huì)運(yùn)行到do_IRQ 這個(gè)公共中斷服務(wù)程序里來。如果有兩個(gè)設(shè)備
使用了同一個(gè)中斷的話,我們就需要有一種機(jī)制來識(shí)別具體是那個(gè)設(shè)備發(fā)生
了中斷。
中斷服務(wù)隊(duì)列的初始化
init_IRQ() 初始化完成后,并沒有具體的中斷服務(wù)程序,真正的中斷服務(wù)程
序要到具體設(shè)備的初始化程序?qū)⑵渲袛喾?wù)程序通過request_irq()向系統(tǒng)“登
記”,掛入某個(gè)中斷請(qǐng)求隊(duì)列以后才會(huì)發(fā)生。
request_iqrt()->setup_irq()
將具體的中斷服務(wù)鏈入相應(yīng)的中斷請(qǐng)求隊(duì)列。
下面這個(gè)圖就是有中斷服務(wù)鏈入后的數(shù)據(jù)結(jié)構(gòu)的簡圖。
中斷服務(wù)的響應(yīng)(上半部)
do_IRQ() Arch/i386/kernel/irq.c L563
desc->handler->ack(irq);
中斷處理器(如i8259A)在將中斷請(qǐng)求“上報(bào)”給CPU以后,希望CPU
給一個(gè)確認(rèn)(ACK),表示CPU在處理了。
IRQ_INPROGRESS 主要是為多處理器設(shè)置的,IRQ_PENDING 說明再
次有同樣的中斷發(fā)生,可能是SMP系統(tǒng)結(jié)構(gòu)中,一個(gè)CPU正在中斷服務(wù),
而另一個(gè)又進(jìn)入do_IRQ(),這時(shí)IRQ_INPROGRESS標(biāo)志為1,所以返回了,
也可能是在單處理器中CPU 已經(jīng)在中斷服務(wù)程序中,但是因?yàn)槟撤N原因又
將中斷開啟了,而且在同一個(gè)中斷通道中再次產(chǎn)生了中斷,也會(huì)因?yàn)?br>IRQ_INPROGRESS標(biāo)志為1 返回
handle_IRQ_event()是for 循環(huán)里的主題,進(jìn)入for 循環(huán)時(shí)desc->status的
IRQ_PENDING標(biāo)志為0,如果執(zhí)行完handle_IRQ_event()后,發(fā)現(xiàn)desc->status
的IRQ_PENDING 標(biāo)志為1 就說明發(fā)生了上述情況。那就在來循環(huán),直到
IRQ_PENDING標(biāo)志為0為止。
desc->handler->end(irq);
if (softirq_pending(cpu))
do_softirq();
這就是下半部的了,我們在下半部里再分析。
do_IRQ()->handle_IRQ_event()
irq_enter(cpu, irq);說明那個(gè)CPU在那個(gè)中斷服務(wù)程序里。
irq_exit(cpu, irq);與上面相反。
循環(huán)執(zhí)行這個(gè)中斷通道的所有中斷程序,因?yàn)長inux也不知道是那個(gè)設(shè)備產(chǎn)
生的中斷。(如果中斷是共用的話)到了自己的中斷服務(wù)程序里自己檢查。
action是struct irqaction,看上面的圖。所以struct irqaction里的handler一般是
在關(guān)中斷里執(zhí)行的(可以自己開中斷),所以要快速處理,否則中斷太久會(huì)
丟失別的中斷。
第三次中斷設(shè)置(軟中斷)
下半部
原始bottom half
原理:
static void (*bh_base[32])(void); Arch/i386/Kernel/Softirq.c L274
可以用來指向一個(gè)具體的bh 函數(shù)。同時(shí)又設(shè)置兩個(gè)32 位bh_active(中斷請(qǐng)
求寄存器)和bh_mask(中斷屏蔽寄存器)。
使用方式
void init_bh(int nr, void (*routine)(void)) L311
void remove_bh(int nr) L317
static inline void mark_bh(int nr) Include/linux/interrupt.h L228
將bh_active 的nr 位置一,如果bh_mask 中的相應(yīng)位也是1,那么系統(tǒng)在執(zhí)
行完do_IRQ()后,以及每次系統(tǒng)調(diào)用結(jié)束后都會(huì)在函數(shù)do_buttom_half()中
執(zhí)行相應(yīng)的bh函數(shù)。
原始bottom half機(jī)制有很大局限性,主要是個(gè)數(shù)限制在32 個(gè)以內(nèi),所以在
2.4內(nèi)核里,bottom half使用的是tasklet 機(jī)制,只是在接口上保持向下兼容。
task queue
include/linux/tqueue.h L38
struct tq_struct {
struct list_head list; /* linked list of active bh's */
unsigned long sync; /* must be initialized to zero */
void (*routine)(void *); /* function to call */
void *data; /* argument to function */
};
typedef struct list_head task_queue; L64
使用方式
#define DECLARE_TASK_QUEUE(q) LIST_HEAD(q) L66
static inline int queue_task(struct tq_struct *bh_pointer, task_queue *bh_list)
L100 – L119
static inline void run_task_queue(task_queue *list)
tasklet
主要是考慮是為了更好的支持SMP,提高SMP 的利用率;不同的tasklet可
以同時(shí)運(yùn)行在不同的CPU上,同時(shí)也是Linux推薦使用的方式,我們將重點(diǎn)
解釋
struct tasklet_struct Include/Linux/Interrupt.h L104
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
struct tasklet_struct bh_task_vec[32]; L275
使用方式(這中方式是我們普通使用的方式)
void my_tasklet_func(unsigned long);
DECLARE_TASKLET(my_tasklet, my_tasklet_func, data) L113
static inline void tasklet_schedule(&my_tasklet) L158
另外的一些調(diào)用接口
DECLARE_TASKLET_DISABLED(name, func, data) L116
static inline void tasklet_disable(struct tasklet_struct *t) L179
static inline void tasklet_enable(struct tasklet_struct *t) L186
extern void tasklet_kill(struct tasklet_struct *t); L198
extern void tasklet_init(struct tasklet_struct *t, L199
我們看一下原始bottom half就怎么用tasklet實(shí)現(xiàn)的以及于我們普通的tasklet
的區(qū)別。
init_bh(TIMER_BH, timer_bh); 初始化,
mark_bh(TIMER_BH) = tasklet_hi_schedule(bh_task_vec+ TIMER_BH)
提出對(duì)timer_bh的執(zhí)行請(qǐng)求。
可以看到,bh_task_vec就是實(shí)現(xiàn)bh的tasklet_struct,看下面的softirq_init()
mark_bh()->tasklet_hi_schedule()
將bh 的tasklet_struct 掛到當(dāng)前CPU 的軟中斷執(zhí)行隊(duì)列,并通過
__cpu_raise_softirq()正式發(fā)出軟中斷請(qǐng)求。這樣子后bh_action就會(huì)在將來的
某個(gè)適合的時(shí)間在do_softirq()得以執(zhí)行。而bh_action 的主要功能就是實(shí)現(xiàn)
了原始bottom half,既在任意時(shí)間最多只有一個(gè)CPU在執(zhí)行bh函數(shù)。但是
tasklet_struct本身是支持SMP 結(jié)構(gòu)的。
tasklet_hi_schedule 與tasklet_schedule都是將tasklet_struct鏈入到CPU隊(duì)列,
不同的是__cpu_raise_softirq() 正式發(fā)出軟中斷請(qǐng)求不一樣,
tasklet_hi_schedule 使用的是HI_SOFTIRQ,而tasklet_schedule 使用的是
TASKLET_SOFTIRQ。而這兩種都是在softirq_init里我們初始化的,這樣我
們就建立起來了softicq。
softicq
這是底層機(jī)制,很少直接使用,softicq 基于tasklet實(shí)現(xiàn)更復(fù)雜更龐大的軟中
斷子系統(tǒng)。
struct softirq_action Include/Linux/Interrupt.h L69
{
void (*action)(struct softirq_action *);
void *data;
};
extern struct tasklet_head tasklet_vec[NR_CPUS]; L131
start_kernel() -> softirq_init ()
對(duì)用于hb 的32 個(gè)用于bh的tasklet_struct結(jié)構(gòu)調(diào)用tasklet_init()以后,它們
的函數(shù)指針func全都指向bh_action()。bh_action就是tasklet實(shí)現(xiàn)bh的機(jī)制了
open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
這是我們普通的tasklet_struct要用到的軟中斷。
open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
這是tasklet_struct實(shí)現(xiàn)的bh調(diào)用。
軟中斷的執(zhí)行
do_softirq()
軟中斷服務(wù)不允許在一個(gè)硬中斷服務(wù)程序內(nèi)部執(zhí)行,也不允許在一個(gè)軟
中斷服務(wù)程序內(nèi)部執(zhí)行,所以通過in_interrupt()加以檢查。
h->action 就是串行化執(zhí)行軟中斷,當(dāng)bh 的tasklet_struct 鏈入的時(shí)候,就能
在這里執(zhí)行,在bh里重新鎖了所有CPU,導(dǎo)致一個(gè)時(shí)間只有一個(gè)CPU可以
執(zhí)行bh 函數(shù),但是do_softirq 是可以在多CPU 上同時(shí)執(zhí)行的。而每個(gè)
tasklet_struct在一個(gè)時(shí)間上是不會(huì)出現(xiàn)在兩個(gè)CPU上的。
只有當(dāng)Linux初始化完成開啟中斷后,中斷系統(tǒng)才可以開始工作。(開始心跳J)
文件系統(tǒng)
Linux使用了VFS(虛擬文件系統(tǒng))對(duì)上提供統(tǒng)一的接口來提供服務(wù)。對(duì)下使用
了“總線”來讀取各種文件系統(tǒng)及設(shè)備。如圖所示:
一個(gè)塊設(shè)備的文件及節(jié)點(diǎn)在引導(dǎo)之初是不可訪問的,只有將它“安裝”在文
件系統(tǒng)的某個(gè)節(jié)點(diǎn)上才可訪問。最初只有一個(gè)“根”節(jié)點(diǎn)“/”。
VFS(虛擬文件系統(tǒng))
在講解文件系統(tǒng)時(shí),有很多重要的數(shù)據(jù)結(jié)構(gòu)需要弄清楚,我們根據(jù)需要一個(gè)
一個(gè)的介紹數(shù)據(jù)結(jié)構(gòu)之間的關(guān)系。
下圖是VFS 體系結(jié)構(gòu),對(duì)于具體的文件系統(tǒng)都要融入到這個(gè)體系結(jié)構(gòu)里來。
從下圖可以看到:每一個(gè)轉(zhuǎn)載點(diǎn)都有一個(gè)struct vfsmount結(jié)構(gòu)。而每一個(gè)節(jié)點(diǎn)都
有struct dentry 和 struct inode。而這兩個(gè)結(jié)構(gòu)側(cè)重面有些不同。 struct dentry主
要是VFS使用,而struct inode主要是由各個(gè)具體的文件系統(tǒng)自己解釋。通過struct
inode,使各個(gè)具體的文件系統(tǒng)融入到VSF體系里來。
在講解根文件系統(tǒng)的安裝之前要先說以下系統(tǒng)堆棧。
我們回過頭看
lss stack_start,%esp esp = 0xC0272000 /Arch/i386/Kernel/Head.S
L107
這是在進(jìn)入分頁后執(zhí)行的第一條語句,它的意義是建立內(nèi)核堆棧。
ENTRY(stack_start) L317
.long SYMBOL_NAME(init_task_union)+8192
.long __KERNEL_DS
堆棧大小是8K(8192)。并且將init_task_union 寫到堆里,這是為系統(tǒng)的進(jìn)
程準(zhǔn)備環(huán)境。(可以把內(nèi)核也看成是一個(gè)進(jìn)程,以后可以看到,不管是內(nèi)核本身
還是內(nèi)核創(chuàng)建的進(jìn)程都是用的這一個(gè)堆棧)堆棧底部為0xC0270000,并且
init_task_union就初始化在0xC0270000處。
union task_union init_task_union Arch/i386/Kernel/Init_task.c L22
__attribute__((__section__(".data.init_task"))) =
{ INIT_TASK(init_task_union.task) };
union task_union { include/linux/Sched.h L684
task_t task;
unsigned long stack[INIT_TASK_SIZE/sizeof(long)];
};
typedef struct task_struct task_t; L159
說明task_union既可以是task的結(jié)構(gòu),也可以是stack數(shù)組。
#define INIT_TASK(tsk) Include/linux/Sched.h L636
這個(gè)宏是初始化一個(gè)task_struct結(jié)構(gòu)。這個(gè)結(jié)構(gòu)是一個(gè)進(jìn)程的結(jié)構(gòu),也就是說,
內(nèi)核編譯時(shí),init_task_union 就已經(jīng)寫了數(shù)據(jù),這些數(shù)據(jù)都是固定的。而我們關(guān)
心的是task結(jié)構(gòu)里的fs這一項(xiàng)。&fs = 0xc0270640, fs=0xC0254FE0
fs = static struct fs_struct init_fs = INIT_FS; Arch/i386/Kernel/Init_task.c L9
我們看到,fs這項(xiàng)在編譯是寫的是&init_fs的地址。而init_fs是通過INIT_FS宏
填充好的。所以我們得到了一個(gè)如下的圖,這個(gè)圖是在內(nèi)核編譯是就確定了的。
可以看到,task_struct代表的是一個(gè)進(jìn)程,fs_struct代表著該進(jìn)程的部分環(huán)境。
根文件系統(tǒng)的安裝過程
第一次安裝:
register_filesystem () fs/Suer.c L99
任何一種文件系統(tǒng)都需要通過調(diào)用該函數(shù)進(jìn)行注冊,系統(tǒng)才能識(shí)別。
這個(gè)函數(shù)很簡單,就是遍歷一次注冊的文件系統(tǒng)鏈表,如果沒有注冊的
話就不將其加入到鏈表中。file_systems=0xC02BA3E0 就是鏈表的頭,也就
是root_fs_type的位置。
start_kernel () ->vfs_caches_init ()->mnt_init()->init_rootfs()
注冊了rootfs_fs_type這種文件系統(tǒng)。
static DECLARE_FSTYPE(rootfs_fs_type, "rootfs", ramfs_read_super,
FS_NOMOUNT|FS_LITTER); fs/ramfs/Inode.c L321
DECLARE_FSTYPE是個(gè)宏,定義一個(gè)file_system_type的結(jié)構(gòu),展開為
struct file_system_type rootfs_fs_type = {
name: "rootfs",
read_super: ramfs_read_super,
fs_flags: FS_NOMOUNT|FS_LITTER,
owner: NULL,
}
start_kernel () ->vfs_caches_init ()->mnt_init()->init_mount_tree ()
do_kern_mount()
對(duì)文件系統(tǒng)的預(yù)安裝。
通過alloc_vfsmnt 初始化一個(gè)struct vfsmount, mnt = 0xC10EE104,通過flags
來判斷應(yīng)該走那條線路,
do_kern_mount()->get_fs_type(“rootfs”)
可以看到首先通過文件系統(tǒng)的名字遍歷文件系統(tǒng)的鏈表得到文件系統(tǒng)的
file_system_type的結(jié)構(gòu),fs=0xC025AB0C,try_inc_mod_count是對(duì)文件系統(tǒng)
的使用進(jìn)行計(jì)數(shù)。request_module 這一行的意義是如果沒有找到文件系統(tǒng)則
通過加載的模塊方式來看看有沒有該文件系統(tǒng)。我們這里的情況是有該文件
系統(tǒng),所以就返回了該文件系統(tǒng)的指針。
do_kern_mount()->get_sb_nodev()
get_sb_nodev(0xC025AB0C,0,“rootfs”,0)
do_kern_mount()->get_sb_nodev()->get_anon_super()
get_anon_super(0xC025AB0C,0,0)分配一個(gè)到超級(jí)塊,并讓這個(gè)超
級(jí)塊與file_system_type建立關(guān)聯(lián)。
通過該文件系統(tǒng)所提供的read_super 函數(shù)指針讀取該文件系統(tǒng)的超級(jí)塊
的具體數(shù)據(jù)(主要是生成struct inode與struct dentry讓其與超級(jí)塊建立關(guān)聯(lián),
稍侯我們在介紹這兩個(gè)結(jié)構(gòu))。實(shí)際上我們看到rootfs是一個(gè)ramfs系統(tǒng)。超
級(jí)塊的讀取是ramfs_read_super,與ramfs文件系統(tǒng)是一樣的。這是一個(gè)內(nèi)存
文件系統(tǒng)。
當(dāng)get_sb_nodev成功返回后,帶回來了一個(gè)初始化好的struct super_block。
在將struct vfsmount與struct super_block關(guān)聯(lián)后返回,這樣就又帶回了一個(gè)
初始化好的struct vfsmount。當(dāng)do_kern_mount()返回時(shí),具體的文件系統(tǒng)那
邊就已經(jīng)準(zhǔn)備就緒。
再次初始化一個(gè)struct namespace,并且將vsfmount 掛在namespace 下,將
namespace掛到當(dāng)前進(jìn)程的mnt_list 里,說明已經(jīng)安裝了,最后將namespace
的根設(shè)成當(dāng)前進(jìn)程的根。
首先通過do_kern_mount 預(yù)安裝rootfs這個(gè)文件系統(tǒng)得到它得掛接點(diǎn),初始化后,
通過最后兩個(gè)set_fs_…來把當(dāng)前得fs 指向了該文件系統(tǒng)。實(shí)際上也就是說,系
統(tǒng)第一個(gè)“安裝”的文件系統(tǒng)是rootfs,這個(gè)時(shí)候又多了幾個(gè)數(shù)據(jù)結(jié)構(gòu),它們之
間的關(guān)系如圖所示:
struct task_struct
init_task_union
fs
namespace
struct fs_struct
struct namespace
namespace
root
struct vfsmount
mnt(0xC10EE104)
mnt_sb
mnt_root
mnt_devname
="root_fs"
mnt_mountpoint
mnt_parent
file_systems struct file_system_type
rootfs
name :"rootfs"
super_block
struct super_block
rootfs_super_block
s_root
s_type
struct dentry
dentry(0xC10E9094)
d_sb
d_inode
INIT_FS
root
pwd
rootmnt
pwdmnt
0xC02BA3E0
0xC025AB0C
0xC10EC000
super_blocks
0xC02590A0
struct inode
inode(0xC10EA044)
i_sb
第一次初始化內(nèi)存結(jié)構(gòu)
RAMFS 文件系統(tǒng)
http://www.linuxforum.net/forum/showflat.php?Cat=&Board=linuxK&Number=
99919&page=164&view=collapsed&sb=5&o=all
ext2 文件系統(tǒng)
http://www-900.ibm.com/developerworks/cn/linux/filesystem/ext2/#1
我們回顧一下ROOTFS 是怎么裝載上去的。
開始時(shí)init_task_union里是只指向INIT_FS的,可是INIT_FS 里沒什么東西,
注冊完rootfs文件系統(tǒng)之后,試圖裝載rootfs文件系統(tǒng),那么首先要生成一個(gè)裝
載點(diǎn)struct vfsmount,并且要讀取文件系統(tǒng)生成超級(jí)塊struct super_block,在
ROOTFS 讀取超級(jí)塊的過程中會(huì)在生成struct dentry,這個(gè)是對(duì)于VFS文件系統(tǒng)
用的(VFS 看到一個(gè)節(jié)點(diǎn),不用管它是在什么文件系統(tǒng)中,對(duì)各種文件系統(tǒng)統(tǒng)一
接口)。而struct inode則是真正的節(jié)點(diǎn)(每種文件系統(tǒng)實(shí)現(xiàn)自己對(duì)節(jié)點(diǎn)的具體操
作)。因?yàn)檫@個(gè)節(jié)點(diǎn)是用于掛接用的,所以要通過struct vfsmount 來標(biāo)識(shí)這個(gè)節(jié)
點(diǎn)同時(shí)也是個(gè)掛接點(diǎn)。這樣它們就建立了聯(lián)系,也就是上圖表示。
為什么根系統(tǒng)的安裝會(huì)在系統(tǒng)初始化的盡頭呢?J
通過上面的鏈接我們對(duì)Ext2 文件系統(tǒng)應(yīng)該有了一些了解,我們想要知道的就是
Ext2 的那種物理布局情況是如何融入到VFS 體系結(jié)構(gòu)的。
第二次安裝:
init () -> prepare_namespace()
這里就是真正的根文件系統(tǒng)的安裝。首先要判斷做為根的是什么設(shè)備,并且
在root_fs文件系統(tǒng)里創(chuàng)建目錄以及創(chuàng)建節(jié)點(diǎn)和設(shè)備。然后通過mount_root()以只
讀方式裝載根文件系統(tǒng)。在mount_root()里可以看出,根文件系統(tǒng)是掛接到了
root_fs文件系統(tǒng)的/ root下,最后在mount_block_root通過sys_chdir將當(dāng)前目錄
設(shè)置到了/root。出來后卸載/dev 目錄,最后在prepare_namespace 的結(jié)束處通過
sys_mount重新完成了根目錄的掛接(以移動(dòng)方式)。
在調(diào)用mount_root()之前如圖所示:
init () -> prepare_namespace()->sys_mkdir()
系統(tǒng)調(diào)用,創(chuàng)建目錄。
這個(gè)地方創(chuàng)建的是 root_fs的目錄,實(shí)際上就是一個(gè)VFS的節(jié)點(diǎn),因?yàn)閞oot_fs
是內(nèi)存文件系統(tǒng)。首先通過path_lookup-> path_init 得到目錄,nd->dentry =
0xC10E0904, nd->mnt=0xC10EE104,這兩個(gè)數(shù)據(jù)看上面的圖。path_lookup->
path_walk 里跳轉(zhuǎn)到last_component:然后再跳轉(zhuǎn)到lookup_parent:設(shè)置值后跳到
return_base:返回。lookup_create(),它的作用是查找緩沖,如果沒有就創(chuàng)建一個(gè)新
的。lookup_hash()->cached_lookup 在hash 里找,因?yàn)槲覀兪切聞?chuàng)建的,一定不
存在,還有就是root_fs不提供d_hash的操作。所以dentry=0,這是我們就要自己
創(chuàng)建一個(gè)新的。new=0xC10E9430,創(chuàng)建了struct dentry還不行,還要?jiǎng)?chuàng)建與之對(duì)
應(yīng)的struct inode,通過lookup 跳轉(zhuǎn)到了ramfs_lookup 函數(shù)里,fs/ramfs/inode.c L55
可以看到在這個(gè)函數(shù)里永遠(yuǎn)返回NULL,并且通過d_add 函數(shù)把dentry->inode
也置成空。這樣一來在lookup_hash的dentry=new了,但是現(xiàn)在并沒有inode,回
到lookup_create 里,就會(huì)跳轉(zhuǎn)到enoent,返回失敗。在回到sys_mkdir,發(fā)現(xiàn)失
敗后會(huì)調(diào)用vfs_mkdir 來創(chuàng)建。May_create 檢查有沒有權(quán)限,然后通過具體的文
件系統(tǒng)的mkdir 來創(chuàng)建節(jié)點(diǎn)。這里的mkdir 是ramfs_mkdir.里面很簡單,只是生
成一個(gè)inode結(jié)構(gòu),并且與dentry掛鉤。
普通文件系統(tǒng)的裝載
假設(shè)我們已經(jīng)啟動(dòng)Linux到了shell里。
執(zhí)行mount –t iso9600 /dev/cdrom /mnt
sys_mount ()
它也是mount 的系統(tǒng)調(diào)用
sys_mount (dev_name=0x8104f78 "/dev/hda",
dir_name=0x8106f98 "/mnt",
type=0xbfffff88 "iso9660",
flags=3236757504,
data=0x8104f68)
可以看到,這是從用戶空間進(jìn)來的。所以首先把數(shù)據(jù)拷貝到系統(tǒng)空間。
sys_mount ()->do_mount ()
這也是mount 的主體函數(shù),
do_mount (dev_name=0xc1be8000 "/dev/hda",
dir_name=0xc10a9000 "/mnt",
type_page=0xc1be9000 "iso9660",
flags=3236757504,
data_page=0xc1be7000)
經(jīng)過一些判斷之后就要進(jìn)入path_lookup
sys_mount ()->do_mount ()->path_lookup()->path_init()
可以看到,這個(gè)函數(shù)的主要目的就是根據(jù)name 來確定目錄,如果是’/’,那
初始化的目錄就是根,否則就是當(dāng)前目錄,對(duì)于fs_struct 里rootmnt 就是根
的掛接點(diǎn),root就是掛接點(diǎn)的目錄,而pwdmnt 與pwd 則是當(dāng)前目錄的掛接
點(diǎn)以及當(dāng)前目錄。current 代表了當(dāng)前進(jìn)程的task_struct,每一個(gè)進(jìn)程都有一
個(gè)task_struct結(jié)構(gòu),在進(jìn)程部分我們在繼續(xù)討論。通過path_init之后,struct
nameidata *nd 則設(shè)置好了初始目錄(這個(gè)結(jié)構(gòu)只是暫時(shí)使用的)。
sys_mount ()->do_mount ()->path_lookup()->path_walk()->link_path_walk()
因?yàn)槌跏寄夸浺呀?jīng)設(shè)置好,直接到‘/’子后的字符。這是如果*name為空說
明我們完成任務(wù),返回。link_count 用于防止函數(shù)嵌套。
第一個(gè)for循環(huán)主要處理路徑問題,在我們現(xiàn)在的環(huán)境里,*name=“mnt”
所以this.name = “mnt”, this.len = 3,并且通過if (!c) 到了 last_component處。
因?yàn)槲覀冎皇且淮文夸?,如果有多層目錄的話往下有幾種情況,比如在界面上我
們打的是 “/mnt/”或“/mnt/aaa/” 這樣就走 last_with_slashes,說明走的是目錄。
另外就是要處理 “.”與“..”的情況了,我們都知道“.”與“..”代表的意義。
所以“..”就是向上走了一層繼續(xù),“.”的情況就是不發(fā)生變化,繼續(xù),這就相
當(dāng)于在界面上打“mnt/../mnt”或“mnt/./../mnt”,如果不是“.”與“..”,那說明
是正常的目錄,就要繼續(xù)往下走。比如我們在界面上鍵入的是/mnt/cdrom,那么就
通過cached_lookup 在cache 里查mnt 這個(gè)節(jié)點(diǎn)有沒有,如果沒有就通過
real_lookup 從文件系統(tǒng)里讀出來并建立struct dentry。對(duì)讀出來的struct dentry所
指向的目錄進(jìn)行判斷,是不是安裝點(diǎn),是不是符號(hào)鏈接,詳細(xì)的操作過程在具體
的文件系統(tǒng)里討論,我們現(xiàn)在先看VFS層。
如果是目錄就會(huì)跳轉(zhuǎn)到last_with_slashes,標(biāo)記上是目錄便繼續(xù)到
last_component,如果標(biāo)記中有LOOKUP_PARENT,說明找的是根,便跳
lookup_parent處理,緊接著還是判斷“.”與“..”的情況,假如我們在界面上寫
的是“/mnt/..”或“/mnt/.”,這種情況就是在這里調(diào)整跳到return_reval處理???br>以看出一個(gè)目錄或文件都是找的struct dentry,那么這個(gè)結(jié)構(gòu)也就是整個(gè)VFS的
中心結(jié)構(gòu)。我們通過path_init 與path_walk確定了我們要安裝的目錄在系統(tǒng)的所
在。通過do_add_mount就把文件系統(tǒng)安裝到了該目錄。
補(bǔ)充
在文件系統(tǒng)里一個(gè)很重要的數(shù)據(jù)結(jié)構(gòu)是file_oprations
進(jìn)程管理及調(diào)度
說明:
在Linux 的實(shí)現(xiàn)中,一個(gè)進(jìn)程或一個(gè)線程都擁有一個(gè)task_struct。都擁有專
用的系統(tǒng)堆棧空間,他們的區(qū)別只是在于線程沒有用戶空間。
下面我們繼續(xù)看在Linux初始化過程中創(chuàng)建的第一個(gè)內(nèi)核線程
rest_init-> init_idle()
首先初始化idle,也就是CPU無事做的時(shí)候運(yùn)行的函數(shù)。
init_idle(0xC0270000, 0);
首先可以看到idle_rq= cpu_rq(0)=runqueues=0xC028BA00, runqueues 是
一個(gè)以CPU 個(gè)數(shù)為下標(biāo)的數(shù)組。代表是每個(gè)CPU 的運(yùn)行隊(duì)列。然后使用
set_tsk_need_resched 設(shè)置current->need_resched = 1.
rest_init-> kernel_thread()
kernel_thread( 0xC010502C , arg=0x0,flags=3584)
rest_init-> kernel_thread()->arch_kernel_thread()
自己做一個(gè)struct pt_regs,為什么要做一個(gè)?往下看
rest_init-> kernel_thread()->arch_kernel_thread()->do_fork()
do_fork(clone_flags=8392448, stack_start=0, regs=0xc0271f58,
stack_size=0,parent_tidptr=0x0, child_tidptr=0x0)
返回0xC10D0000,也就是新的task_struct
rest_init-> kernel_thread()->arch_kernel_thread()->do_fork()->copy_process()
dup_task_struct 拷貝一個(gè)task_struct,還根據(jù)參數(shù)決定是否拷貝深層的數(shù)據(jù)結(jié)
構(gòu),比如:copy_files、copy_fs、等。最后設(shè)置需要調(diào)度。
拷貝出來的線程開始并沒有運(yùn)行,要等到調(diào)度的時(shí)候才能運(yùn)行。這個(gè)時(shí)候系
統(tǒng)有了一個(gè)“進(jìn)程”和一個(gè)線程了
rest_init-> cpu_idle()
CPU 的“初始化”都結(jié)束了,CPU 進(jìn)入idle,其實(shí)一進(jìn)入idle 就是開始了進(jìn)
程調(diào)度??梢钥吹絚urrent->need_resched(init_idle())這個(gè)是1.所以不會(huì)在while里
循環(huán)。
rest_init-> cpu_idle()->schedule()
這是整個(gè)系統(tǒng)的進(jìn)程調(diào)度算法(核心),不但這里有調(diào)用,很多地方都有調(diào)
用。
首先我們看一下runqueues 這個(gè)結(jié)構(gòu)的說明,雖然這是2.4 的內(nèi)核,但是調(diào)
度算法卻使用的是2.6 的。應(yīng)該是RH自己修改的吧,我也不清楚,猜的。J
http://www-900.ibm.com/developerWorks/cn/linux/kernel/l-kn26sch/index.shtml
http://www.linuxfans.org/nuke/modules.php?name=News&file=article&op=view&sid
=2368
prev = current; 當(dāng)前進(jìn)程,我們這里現(xiàn)在是0xC0270000
rq = this_rq(); 當(dāng)前運(yùn)行schedule 的CPU,我們這里現(xiàn)在只有一個(gè)CPU,
所以是runqueues,0xC028BA00
prev->last_run = jiffies; 刷新當(dāng)前進(jìn)程的運(yùn)行時(shí)間
spin_lock_irq(&rq->lock); 對(duì)當(dāng)前的CPU 的運(yùn)行隊(duì)列加鎖。并禁用當(dāng)前
CPU中斷。這里我們的進(jìn)程的運(yùn)行狀態(tài)是TASK_RUNNING(init_idle()L1916),
判斷nr_running,如果當(dāng)前CPU上沒有進(jìn)程要運(yùn)行的話,next=rq->idle.
判斷nr_active,本進(jìn)程組中的進(jìn)程數(shù)如果為0,就把a(bǔ)ctive 與expired對(duì)掉。
我們看一下我們這里的rn_running與nr_active是多少?;剡^頭去看
rest_init-> init_idle()->deactivate_task()
deactivate_task(0xC0270000, 0xC028BA00),看上面的數(shù)據(jù)
那么我們這里nr_running = ?,rq->active->nr_active = ?,再回頭看
start_kernel()->sched_init()
我們看到runqueues 是一個(gè)全局變量。開始的時(shí)候里面是空。這里就是對(duì)它
的初始化
首先通過循環(huán)按支持的CPU數(shù)來初始化runqueues,然將內(nèi)核看做是一個(gè)進(jìn)
程idle來初始化。
start_kernel()->sched_init()->wake_up_forked_process()
首先調(diào)整sleep_avg 等,因?yàn)榈?一次進(jìn)來current->array 是空。所以要走
__activate_task。
start_kernel()->sched_init()->wake_up_forked_process()->activate_task()
執(zhí)行完這里current->array=runqueues->active(0xC028BA24);
并且runqueues->active->nr_active = 1,runqueues->active->nr_running=1;
回到deactivate_task
現(xiàn)在又把nr_nr_running--,nr_active—都變成了0,。并且把a(bǔ)rray也清了,那
么這個(gè)時(shí)候又恢復(fù)成原來的樣子的。
在繼續(xù)往下看
rest_init-> kernel_thread()->arch_kernel_thread()->do_fork()
再次調(diào)用wake_up_forked_process,這樣相當(dāng)于activate_task() 沒有執(zhí)行。J
那么我們這里nr_running=1,rq->active->nr_active=1
回到調(diào)度
繼續(xù)向下sched_find_first_bit,它用于快速定位就緒隊(duì)列中高優(yōu)先級(jí)任務(wù)
next = 0xC10D0000,這就是我們新創(chuàng)建的線程的task_struct 的起始地址,接
著使用clear_tsk_need_resched 清掉need_resched
判斷prev 與next 是不是一個(gè)進(jìn)程,是的話可以看到,什么也沒有做。不是
的話我們就要開始進(jìn)行進(jìn)程切換了。
context_switch 主要就處理進(jìn)程的用戶空間。每個(gè)進(jìn)程都有一個(gè)struct
mm_struct 代表著自己的用戶空間,但是線程卻沒有用戶空間,怎么辦?只好暫
時(shí)借用一下上一個(gè)進(jìn)程的用戶空間。(會(huì)有問題嗎?沒有,因?yàn)楝F(xiàn)在是在內(nèi)核空
間,內(nèi)核訪問用戶空間都是一樣的,就算是有問題,那也是返回到用戶空間也會(huì)
出現(xiàn)問題。再加上線程并不使用用戶空間,所以沒有關(guān)系)
switch_to這個(gè)就是進(jìn)程切換的關(guān)鍵所在。(主要是切換進(jìn)程堆棧,已經(jīng)說過,
不管是進(jìn)程或是線程,都有自己的堆棧)
prev->thread 里的部分?jǐn)?shù)據(jù)
struct thread_struct {
…
unsigned long esp0 = 0
unsigned long eip = 0
unsigned long esp = 0
unsigned long fs = 0
unsigned long gs = 0
…
};
next->thread
struct thread_struct {
…
unsigned long esp0 = C10D2000
unsigned long eip = C0108F60
unsigned long esp = C10D1FC4
unsigned long fs = 0
unsigned long gs = 0
…
};
我們回頭看一下我們的線程是什么時(shí)候把thread 這個(gè)結(jié)構(gòu)初始化的。
rest_init-> kernel_thread()->arch_kernel_thread()->)->do_fork()->copy_process()
->copy_thread()
childregs = ((struct pt_regs *)(THREAD_SIZE+(unsigned long) p))- 1
= ((struct pt_regs *)(8192+0xC10D0000))- 1 = 0xC10D4000 – sizeof( pt_regs)
= 0xC10D4000 – 60 = 0xC10D1FC4
在這里實(shí)際上是打造一個(gè)堆棧的環(huán)境,這個(gè)堆棧就是我們生成的線程用的堆
棧。那么這個(gè)線程是怎么使用這個(gè)堆棧的呢?那就要看調(diào)度了。
那么我們運(yùn)行完這個(gè)函數(shù),里面初始化成什么樣子呢?
Childregs數(shù)據(jù):
ebx = 0xC010502C
eip=0xc01071E4
xes=xds=0x68
xcs=0x60,
orig_eax=0xFFFFFFFF,其它為0,其實(shí)childregs 大部分是我們開始在
arch_kernel_thread自己做的那個(gè)regs。自己可以對(duì)照一下。
(struct task_struct*)(0xC10D0000) ->thread 的數(shù)據(jù)
esp = 0xC10D1FC4
esp0 = 0xC10D2000
eip = 0xC0108F60 ret_form_fork 的地址(怎么會(huì)是它,為什么不是
kernel_thread_helper,回頭看copy_thread, p->thread.eip = (unsigned long)
ret_from_fork)
這就完成了next->thread的初始化,現(xiàn)在回過頭來看調(diào)度
switch_to 反匯編代碼
0xc01160c5 : mov %esi,%eax
0xc01160c7 : mov %ecx,%edx
0xc01160c9 : push %esi
0xc01160ca : push %edi
0xc01160cb : push %ebp
0xc01160cc : mov %esp,0x350(%esi)
0xc01160d2 : mov 0x350(%ecx),%esp
0xc01160d8 : movl $0xc01160ed,0x34c(%esi)
0xc01160e2 : pushl 0x34c(%ecx)
0xc01160e8 : jmp 0xc0107654
0xc01160ed : pop %ebp
0xc01160ee : pop %edi
0xc01160ef : pop %esi
執(zhí)行switch_to 之前寄存器狀態(tài) 當(dāng)eip = 0xC01160CC
eax 0xC028BA00 0xC0270000
ecx 0xC10D0000
edx 0x0 0xC010D000
ebx 0xC0255700
esp 0xC0271FB4 0xC0271FA8
ebp 0xC0271FC4
esi 0xC0270000
edi 0xC028BA00
eip 0xC01160C5 0xC01160CC
eflags 0x46
cs 0x60
ss 0x68
ds 0x68
es 0x68
fs 0x0
gs 0x0
當(dāng)0xC01160CC執(zhí)行完后,那么當(dāng)前進(jìn)程的ESP 就放到了prev->thread.esp 里,也
就是說0xC0270000 ->thread.esp = 0xC0271FA8
當(dāng)0xC01160D2執(zhí)行完后ESP = 0xC10D1FC4,這樣就切換了堆棧,也就是說,
現(xiàn)在切換了進(jìn)程。但是,僅僅切換了堆棧進(jìn)程還不能向下運(yùn)行。還要做一些工作。
當(dāng)0xC01160E2 執(zhí)行完后prev->thread.eip = 0xC01160ED, next->thread.eip =
0xC0108F60 壓入了堆棧。這個(gè)地址就是返回地址了(如果碰到ret 的話)對(duì)于我
們線程來說,通過0xC01160E8 這行執(zhí)行__switch_to 后就返回到0xC0108F60 去
了,因?yàn)槲覀冞@里是新創(chuàng)建的線程。
如果不是線程是進(jìn)程呢,那下面那幾句pop 就很有用了,這部分到了第二部分再
講。
rest_init-> cpu_idle()->schedule()->context_switch()->__switch_to()
arch/i386/kernel/process.c L710
tss->esp0 = next->esp0 = 0xC10D2000
主要是處理TSS,當(dāng)這個(gè)函數(shù)返回時(shí),返回到ret_form_fork = 0xC0108F60,最
后要運(yùn)行到kernel_thread_helper = 0xC01071E4這里。通過call 0xC010502C,就
到init 里去了。
kernel_thread_helper arch/i386/kernel/process.c L494
這是一個(gè)宏。做的事情很簡單,首先是壓參數(shù),接著調(diào)用init 函數(shù)。執(zhí)行完
返回來的時(shí)候,再調(diào)用了do_exit,注消了進(jìn)程。可以看到在Linux 的線程實(shí)現(xiàn)
實(shí)際上就是使用一個(gè)進(jìn)程來執(zhí)行函數(shù),接著注銷進(jìn)程。這種進(jìn)程的開銷還是很小
的。回頭看copy_process
網(wǎng)絡(luò)系統(tǒng)
Linux的網(wǎng)絡(luò)系統(tǒng)如圖所示
用戶模式
內(nèi)核模式
套接字文件系統(tǒng)
TCP UDP
IP
PPP 以太網(wǎng)... ...
ARP
init()->do_basic_setup()-> sock_init()
主要是注冊sockfs這個(gè)文件系統(tǒng),也就實(shí)現(xiàn)了sock的初始化。
現(xiàn)在我們看PF_INET 這種地址家族的注冊
#define module_init(x) __initcall(x) include/linux/init.h L112
init()->do_basic_setup()->do_initcalls()->inet_init() net/ipv4/af_inet.c L1102
init()->do_basic_setup()->do_initcalls()->inet_init()->sock_register()
注冊地址家族,這里注冊的是inet_family地址家族
在RH9 這個(gè)版本中最多支持32 種地址家族
注冊完P(guān)F_INET 家族之后就要注冊PF_INET家族的協(xié)議
inet_protocol_base很有趣,使用了C語言技巧
我們看inet_protocol_base的定義
#define IPPROTO_PREVIOUS &udp_protocol
static struct inet_protocol icmp_protocol = {
handler: icmp_rcv,
next: IPPROTO_PREVIOUS,
protocol: IPPROTO_ICMP,
name: "ICMP"
};
#undef IPPROTO_PREVIOUS
#define IPPROTO_PREVIOUS &icmp_protocol
struct inet_protocol *inet_protocol_base = IPPROTO_PREVIOUS; L100
所以它指向的是 icmp_protocol,但是我們再在protocol.c 里向上看,在定義
icmp_protocol 時(shí), IPPROTO_PREVIOUS 指向的是udp_protocol , 所以
icmp_protocol里的next = udp_protocol,同理,向上推,實(shí)際上這是在編譯時(shí)就
完成了一個(gè)鏈表。最后IPPROTO_PREVIOU 指的就是一個(gè)頭?,F(xiàn)在明白之后就
知道循環(huán)是怎么回事了。
通過inet_add_protocol把協(xié)議添加到inet_protos表中
接著各種注冊信息。
整體來說,網(wǎng)絡(luò)的初始化比較簡單。我們會(huì)在第二部分從用戶角度上來分析網(wǎng)絡(luò)
具體的流程。
待續(xù)…
后記
楊玲