hyla 发表于 2004-1-15 09:53:12

Linux內核初始化(內核版本0.11)

在Linux引導程序(bootsect.s,setup.s,head.s)執行完後,會把系統的控制權交給init/main.c。main.c利用setup.s獲取的一些系統參數(存放在0x90000~0x90200)來設置系統的跟文件設備號以及一些內存全局變量(主要是主內存的開始位置、內存容量和作為高速緩衝的內存的末端地址)。然後開始硬件初始化,包括內存初始化(mem_init),陷阱門初始化(trao_init),塊設備初始化(blk_dev_init),字符設備初始化(chr_dev_init),tty初始化(tty_init),設置開機啟動時間(time_init),調度程序初始化(sched_init),緩衝管理初始化(buffer_init),硬盤初始化(hd_init),軟驅初始化(floppy_init)。所有初始化完成後,用sti()開中斷。然後CPU進入Ring3,也就是通過move_to_user_mode()切換到用戶態。然後fork()出一個子程序運行main.c中的init()子函數。

init()首先讀取硬盤參數(包括分區表),建立虛擬盤,安裝根文件系統設備。隨後又fork()出一個子進程,打開/etc/rc文件,並執行/bin/sh。若剛才建立的子進程終止,則再fork()一個子進程,再次執行bin/sh,如此循環往復......

------------------------------------------------------------------------------------------

內存初始化mem_init()主要是將內存劃分為4k大小的頁,以便為頁式內存管理做好準備,這個函數位於mm/memory.c, 399行

399 void mem_init(long start_mem, long end_mem)
400 {
401 int i;
402
403 HIGH_MEMORY = end_mem; // 设置内存最高端。
404 for (i=0 ; i<PAGING_PAGES ; i++) // 首先置所有页面为已占用(USED=100)状态,
405 mem_map = USED; // 即将页面映射数组全置成USED。
406 i = MAP_NR(start_mem); // 然后计算可使用起始内存的页面号。
407 end_mem -= start_mem; // 再计算可分页处理的内存块大小。
408 end_mem >>= 12; // 从而计算出可用于分页处理的页面数(2^12=4k)。
409 while (end_mem-->0) // 最后将这些可用页面对应的页面映射数组清零。
410 mem_map=0;
411 }

-------------------------------------------------------------------------------------------

陷阱門初始化trao_init()重設各種中斷的中斷向量(原來的中斷向量表已經被system覆蓋了,當然要想辦法補回來)。位於kernel/trap.c,181行

181 void trap_init(void)
182 {
183 int i;
184
185 set_trap_gate(0,&divide_error); // 设置除操作出错的中断向量值。以下雷同。
186 set_trap_gate(1,&debug);
187 set_trap_gate(2,&nmi);
188 set_system_gate(3,&int3); /* int3-5 can be called from all */
189 set_system_gate(4,&overflow);
190 set_system_gate(5,&bounds);
191 set_trap_gate(6,&invalid_op);
192 set_trap_gate(7,&device_not_available);
193 set_trap_gate(8,&double_fault);
194 set_trap_gate(9,&coprocessor_segment_overrun);
195 set_trap_gate(10,&invalid_TSS);
196 set_trap_gate(11,&segment_not_present);
197 set_trap_gate(12,&stack_segment);
198 set_trap_gate(13,&general_protection);
199 set_trap_gate(14,&page_fault);
200 set_trap_gate(15,&reserved);
201 set_trap_gate(16,&coprocessor_error);
// 下面将int17-48 的陷阱门先均设置为reserved,以后每个硬件初始化时会重新设置自己的陷阱门。
202 for (i=17;i<48;i++)
203 set_trap_gate(i,&reserved);
204 set_trap_gate(45,&irq13); // 设置协处理器的陷阱门。
205 outb_p(inb_p(0x21)&0xfb,0x21); // 允许主8259A 芯片的IRQ2 中断请求。
206 outb(inb_p(0xA1)&0xdf,0xA1); // 允许从8259A 芯片的IRQ13 中断请求。
207 set_trap_gate(39,&parallel_interrupt); // 设置并行口的陷阱门。
208 }

------------------------------------------------------------------------------------------------

塊設備初始化blk_dev_init()將所有的請求項設置為空閒(dev=-1)。位於kernel/blk_drv/ll_rw_blk.c,157行

157 void blk_dev_init(void)
158 {
159 int i;
160
161 for (i=0 ; i<NR_REQUEST ; i++) {//NR_REQUEST=32
162 request.dev = -1;
163 request.next = NULL;
164 }
165 }
-------------------------------------------------------------------------------------------

字符設備初始化chr_dev_init()為空,留待後續版本擴充:)。位於kernel/chr_drv/tty_io.c, 347行

-------------------------------------------------------------------------------------------

tty初始化tty_init()調用了串行初始化rs_init()和控制台終端初始化con_init()。位於kernel/chr_drv/tty_io.c, 105行

105 void tty_init(void)
106 {
107 rs_init(); // 初始化串行中断程序和串行接口1 和2。(serial.c, 37)
108 con_init(); // 初始化控制台终端。(console.c, 617)
109 }


//rs_init()位於kernel/chr_drv/serial.c, 37
37 void rs_init(void)
38 {
39 set_intr_gate(0x24,rs1_interrupt); // 设置串行口1 的中断门向量(硬件IRQ4 信号)。
40 set_intr_gate(0x23,rs2_interrupt); // 设置串行口2 的中断门向量(硬件IRQ3 信号)。
41 init(tty_table.read_q.data); // 初始化串行口1(.data 是端口号)。
42 init(tty_table.read_q.data); // 初始化串行口2。
43 outb(inb_p(0x21)&0xE7,0x21); // 允许主8259A 芯片的IRQ3,IRQ4 中断信号请求。
44 }

//con_int()位於kernel/chr_drv/console.c, 617
617 void con_init(void)
618 {
619 register unsigned char a;
620 char *display_desc = "????";
621 char *display_ptr;
622
623 video_num_columns = ORIG_VIDEO_COLS; // 显示器显示字符列数。
624 video_size_row = video_num_columns * 2; // 每行需使用字节数。
625 video_num_lines = ORIG_VIDEO_LINES; // 显示器显示字符行数。
626 video_page = ORIG_VIDEO_PAGE; // 当前显示页面。
627 video_erase_char = 0x0720; // 擦除字符(0x20 显示字符, 0x07 是属性)。
628
// 如果原始显示模式等于7,则表示是单色显示器。
629 if (ORIG_VIDEO_MODE == 7) /* Is this a monochrome display? */
630 {
631 video_mem_start = 0xb0000; // 设置单显映象内存起始地址。
632 video_port_reg = 0x3b4; // 设置单显索引寄存器端口。
633 video_port_val = 0x3b5; // 设置单显数据寄存器端口。
634 if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10)
635 {
636 video_type = VIDEO_TYPE_EGAM; // 设置显示类型(EGA 单色)。
637 video_mem_end = 0xb8000; // 设置显示内存末端地址。
638 display_desc = "EGAm"; // 设置显示描述字符串。
639 }
// 如果BX 寄存器的值等于0x10,则说明是单色显示卡MDA。则设置相应参数。
640 else
641 {
642 video_type = VIDEO_TYPE_MDA; // 设置显示类型(MDA 单色)。
643 video_mem_end = 0xb2000; // 设置显示内存末端地址。
644 display_desc = "*MDA"; // 设置显示描述字符串。
645 }
646 }
// 如果显示模式不为7,则为彩色模式。此时所用的显示内存起始地址为0xb800;显示控制索引寄存
// 器端口地址为0x3d4;数据寄存器端口地址为0x3d5。
647 else /* If not, it is color. */
648 {
649 video_mem_start = 0xb8000; // 显示内存起始地址。
650 video_port_reg = 0x3d4; // 设置彩色显示索引寄存器端口。
651 video_port_val = 0x3d5; // 设置彩色显示数据寄存器端口。
// 再判断显示卡类别。如果BX 不等于0x10,则说明是EGA 显示卡。
652 if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10)
653 {
654 video_type = VIDEO_TYPE_EGAC; // 设置显示类型(EGA 彩色)。
655 video_mem_end = 0xbc000; // 设置显示内存末端地址。
656 display_desc = "EGAc"; // 设置显示描述字符串。
657 }
658 else
659 {
660 video_type = VIDEO_TYPE_CGA; // 设置显示类型(CGA)。
661 video_mem_end = 0xba000; // 设置显示内存末端地址。
662 display_desc = "*CGA"; // 设置显示描述字符串。
663 }
664 }
665
666 /* Let the user known what kind of display driver we are using */
/* 让用户知道我们正在使用哪一类显示驱动程序*/
667
// 在屏幕的右上角显示显示描述字符串。采用的方法是直接将字符串写到显示内存的相应位置处。
// 首先将显示指针display_ptr 指到屏幕第一行右端差4 个字符处(每个字符需2 个字节,因此减8)。
668 display_ptr = ((char *)video_mem_start) + video_size_row - 8;
// 然后循环复制字符串中的字符,并且每复制一个字符都空开一个属性字节。
669 while (*display_desc)
670 {
671 *display_ptr++ = *display_desc++; // 复制字符。
672 display_ptr++; // 空开属性字节位置。
673 }
674
675 /* Initialize the variables used for scrolling (mostly EGA/VGA) */
/* 初始化用于滚屏的变量(主要用于EGA/VGA) */
676
677 origin = video_mem_start; // 滚屏起始显示内存地址。
678 scr_end = video_mem_start + video_num_lines * video_size_row; // 滚屏结束内存地址。
679 top = 0; // 最顶行号。
680 bottom = video_num_lines; // 最底行号。
681
682 gotoxy(ORIG_X,ORIG_Y); // 初始化光标位置x,y 和对应的内存位置pos。
683 set_trap_gate(0x21,&keyboard_interrupt); // 设置键盘中断陷阱门。
684 outb_p(inb_p(0x21)&0xfd,0x21); // 取消8259A 中对键盘中断的屏蔽,允许IRQ1。
685 a=inb_p(0x61); // 延迟读取键盘端口0x61(8255A 端口PB)。
686 outb_p(a|0x80,0x61); // 设置禁止键盘工作(位7 置位),
687 outb(a,0x61); // 再允许键盘工作,用以复位键盘操作。
688 }

-------------------------------------------------------------------------------------------

設置開機啟動時間time_init()。位於init/main.c,76行

76 static void time_init(void) // 该子程序取CMOS 时钟,并设置开机时间 startup_time(秒)。
77 {
78 struct tm time;
79
80 do {
81 time.tm_sec = CMOS_READ(0); // CMOS參數請參見我的另外一篇筆記。
82 time.tm_min = CMOS_READ(2);
83 time.tm_hour = CMOS_READ(4);
84 time.tm_mday = CMOS_READ(7);
85 time.tm_mon = CMOS_READ(8);
86 time.tm_year = CMOS_READ(9);
87 } while (time.tm_sec != CMOS_READ(0));
88 BCD_TO_BIN(time.tm_sec);
89 BCD_TO_BIN(time.tm_min);
90 BCD_TO_BIN(time.tm_hour);
91 BCD_TO_BIN(time.tm_mday);
92 BCD_TO_BIN(time.tm_mon);
93 BCD_TO_BIN(time.tm_year);
94 time.tm_mon--;               //這裡為什麼--,我也沒弄明白:(
95 startup_time = kernel_mktime(&time);
96 }

------------------------------------------------------------------------------------------

調度程序初始化sched_init(),這一段看的我有點頭暈暈,只能以後再慢慢琢磨。先放這裡吧。位於kernel/sched.c,385行

385 void sched_init(void)
386 {
387 int i;
388 struct desc_struct * p; // 描述符表结构指针。
389
390 if (sizeof(struct sigaction) != 16) // sigaction 是存放有关信号状态的结构。
391 panic( "Struct sigaction MUST be 16 bytes");
// 设置初始任务(任务0)的任务状态段描述符和局部数据表描述符(include/asm/system.h,65)。
392 set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
393 set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));
// 清任务数组和描述符表项(注意i=1 开始,所以初始任务的描述符还在)。
394 p = gdt+2+FIRST_TSS_ENTRY;
395 for(i=1;i<NR_TASKS;i++) {
396 task = NULL;
397 p->a=p->b=0;
398 p++;
399 p->a=p->b=0;
400 p++;
401 }
402 /* Clear NT, so that we won't have troubles with that later on */
/* 清除标志寄存器中的位NT,这样以后就不会有麻烦*/
// NT 标志用于控制程序的递归调用(Nested Task)。当NT 置位时,那么当前中断任务执行
// iret 指令时就会引起任务切换。NT 指出TSS 中的back_link 字段是否有效。
403 __asm__( "pushfl ; andl $0xffffbfff,(%esp) ; popfl"); // 复位NT 标志。
404 ltr(0); // 将任务0 的TSS 加载到任务寄存器tr。
405 lldt(0); // 将局部描述符表加载到局部描述符表寄存器。
// 注意!!是将GDT 中相应LDT 描述符的选择符加载到ldtr。只明确加载这一次,以后新任务
// LDT 的加载,是CPU 根据TSS 中的LDT 项自动加载。
// 下面代码用于初始化8253 定时器。
406 outb_p(0x36,0x43); /* binary, mode 3, LSB/MSB, ch 0 */
407 outb_p(LATCH & 0xff , 0x40); /* LSB */ // 定时值低字节。
408 outb(LATCH >> 8 , 0x40); /* MSB */ // 定时值高字节。
// 设置时钟中断处理程序句柄(设置时钟中断门)。
409 set_intr_gate(0x20,&timer_interrupt);
// 修改中断控制器屏蔽码,允许时钟中断。
410 outb(inb_p(0x21)&~0x01,0x21);
// 设置系统调用中断门。
411 set_system_gate(0x80,&system_call);
412 }

---------------------------------------------------------------------------------------

緩衝管理初始化buffer_init()。位於fs/buffer.c,348行

348 void buffer_init(long buffer_end)
349 {
350 struct buffer_head * h = start_buffer;
351 void * b;
352 int i;
353
// 如果缓冲区高端等于1Mb,则由于从640KB-1MB 被显示内存和BIOS 占用,因此实际可用缓冲区内存
// 高端应该是640KB。否则内存高端一定大于1MB。
354 if (buffer_end == 1<<20)
355 b = (void *) (640*1024);
356 else
357 b = (void *) buffer_end;
358 while ( (b -= BLOCK_SIZE) >= ((void *) (h+1)) ) {
359 h->b_dev = 0; // 使用该缓冲区的设备号。
360 h->b_dirt = 0; // 脏标志,也即缓冲区修改标志。
361 h->b_count = 0; // 该缓冲区引用计数。
362 h->b_lock = 0; // 缓冲区锁定标志。
363 h->b_uptodate = 0; // 缓冲区更新标志(或称数据有效标志)。
364 h->b_wait = NULL; // 指向等待该缓冲区解锁的进程。
365 h->b_next = NULL; // 指向具有相同hash 值的下一个缓冲头。
366 h->b_prev = NULL; // 指向具有相同hash 值的前一个缓冲头。
367 h->b_data = (char *) b; // 指向对应缓冲区数据块(1024 字节)。
368 h->b_prev_free = h-1; // 指向链表中前一项。
369 h->b_next_free = h+1; // 指向链表中下一项。
370 h++; // h 指向下一新缓冲头位置。
371 NR_BUFFERS++; // 缓冲区块数累加。
372 if (b == (void *) 0x100000) // 如果地址b 递减到等于1MB,则跳过384KB,
373 b = (void *) 0xA0000; // 让b 指向地址0xA0000(640KB)处。
374 }
375 h--; // 让h 指向最后一个有效缓冲头。
376 free_list = start_buffer; // 让空闲链表头指向头一个缓冲区头。
377 free_list->b_prev_free = h; // 链表头的b_prev_free 指向前一项(即最后一项)。
378 h->b_next_free = free_list; // h 的下一项指针指向第一项,形成一个环链。
// 初始化hash 表(哈希表、散列表),置表中所有的指针为NULL。
379 for (i=0;i<NR_HASH;i++)
380 hash_table=NULL;
381 }

---------------------------------------------------------------------------------------

硬盤初始化hd_init()。位於kernel/blk_dev/hd.c,343 行
J710
343 void hd_init(void)
344 {
345 blk_dev.request_fn = DEVICE_REQUEST; // do_hd_request()。
346 set_intr_gate(0x2E,&hd_interrupt); // 设置硬盘中断门向量int 0x2E(46)。
// hd_interrupt 在(kernel/system_call.s,221)。
347 outb_p(inb_p(0x21)&0xfb,0x21); // 复位接联的主8259A int2 的屏蔽位,允许从片
// 发出中断请求信号。
348 outb(inb_p(0xA1)&0xbf,0xA1); // 复位硬盘的中断请求屏蔽位(在从片上),允许
// 硬盘控制器发送中断请求信号。
349 }

--------------------------------------------------------------------------------------

軟驅初始化floppy_init()。位於kernel/blk_dev/floppy.c,457 行

457 void floppy_init(void)
458 {
459 blk_dev.request_fn = DEVICE_REQUEST; // = do_fd_request()。
460 set_trap_gate(0x26,&floppy_interrupt); //设置软盘中断门int 0x26(38)。
461 outb(inb_p(0x21)&~0x40,0x21); // 复位软盘的中断请求屏蔽位,允许
// 软盘控制器发送中断请求信号。
462 }

-------------------------------------------------------------------------------------

hyla 发表于 2004-1-15 10:05:41

後面有幾段init還不是太明白,畢竟剛開始看,從一大段代碼中抽一兩個函數要想完全看懂是有一定難度的:)。我現在是想先弄明白大致的結構,所以先都列出來放在後面。弄明白了引導和初始化過程,接下來應該是分模塊看了,比如內存管理MM,文件系統FS等。0.11的內核比較小,很多代碼也不是特別複雜,比較容易找出內核的結構,這也是我選擇它的原因。了解了基本原理和結構後再來看新內核,應該就是些技術細節問題了,這個可要發時間慢慢磨。

初次進入Linux內核世界,希望沒有摸錯方向,請各位有經驗的大俠指教!

DexterK 发表于 2004-3-31 22:54:31

能加你进我的msn嘛?
好帖子
页: [1]
查看完整版本: Linux內核初始化(內核版本0.11)