?? main.c
字號:
/* * linux/init/main.c * * (C) 1991 Linus Torvalds */#define __LIBRARY__ // 定義該變量是為了包括定義在unistd.h 中的內嵌匯編代碼等信息。#include <unistd.h> // *.h 頭文件所在的默認目錄是include/,則在代碼中就不用明確指明位置。// 如果不是UNIX 的標準頭文件,則需要指明所在的目錄,并用雙引號括住。// 標準符號常數與類型文件。定義了各種符號常數和類型,并申明了各種函數。// 如果定義了__LIBRARY__,則還包括系統調用號和內嵌匯編代碼_syscall0()等。#include <time.h> // 時間類型頭文件。其中最主要定義了tm 結構和一些有關時間的函數原形。/* * we need this inline - forking from kernel space will result * in NO COPY ON WRITE (!!!), until an execve is executed. This * is no problem, but for the stack. This is handled by not letting * main() use the stack at all after fork(). Thus, no function * calls - which means inline code for fork too, as otherwise we * would use the stack upon exit from 'fork()'. * * Actually only pause and fork are needed inline, so that there * won't be any messing with the stack from main(), but we define * some others too. *//* * 我們需要下面這些內嵌語句 - 從內核空間創建進程(forking)將導致沒有寫時復制(COPY ON WRITE)!!! * 直到一個執行execve 調用。這對堆棧可能帶來問題。處理的方法是在fork()調用之后不讓main()使用 * 任何堆棧。因此就不能有函數調用 - 這意味著fork 也要使用內嵌的代碼,否則我們在從fork()退出 * 時就要使用堆棧了。 * 實際上只有pause 和fork 需要使用內嵌方式,以保證從main()中不會弄亂堆棧,但是我們同時還 * 定義了其它一些函數。 */static inline_syscall0 (int, fork) // 是unistd.h 中的內嵌宏代碼。以嵌入匯編的形式調用 // Linux 的系統調用中斷0x80。該中斷是所有系統調用的 // 入口。該條語句實際上是int fork()創建進程系統調用。 // syscall0 名稱中最后的0 表示無參數,1 表示1 個參數。 static inline _syscall0 (int, pause) // int pause()系統調用:暫停進程的執行,直到 // 收到一個信號。 static inline _syscall1 (int, setup, void *, BIOS) // int setup(void * BIOS)系統調用,僅用于 // linux 初始化(僅在這個程序中被調用)。 static inline _syscall0 (int, sync) // int sync()系統調用:更新文件系統。#include <linux/tty.h> // tty 頭文件,定義了有關tty_io,串行通信方面的參數、常數。#include <linux/sched.h> // 調度程序頭文件,定義了任務結構task_struct、第1 個初始任務 // 的數據。還有一些以宏的形式定義的有關描述符參數設置和獲取的 // 嵌入式匯編函數程序。#include <linux/head.h> // head 頭文件,定義了段描述符的簡單結構,和幾個選擇符常量。#include <asm/system.h> // 系統頭文件。以宏的形式定義了許多有關設置或修改 // 描述符/中斷門等的嵌入式匯編子程序。#include <asm/io.h> // io 頭文件。以宏的嵌入匯編程序形式定義對io 端口操作的函數。#include <stddef.h> // 標準定義頭文件。定義了NULL, offsetof(TYPE, MEMBER)。#include <stdarg.h> // 標準參數頭文件。以宏的形式定義變量參數列表。主要說明了-個 // 類型(va_list)和三個宏(va_start, va_arg 和va_end),vsprintf、 // vprintf、vfprintf。#include <unistd.h>#include <fcntl.h> // 文件控制頭文件。用于文件及其描述符的操作控制常數符號的定義。#include <sys/types.h> // 類型頭文件。定義了基本的系統數據類型。#include <linux/fs.h> // 文件系統頭文件。定義文件表結構(file,buffer_head,m_inode 等)。 static char printbuf[1024]; // 靜態字符串數組。 extern int vsprintf (); // 送格式化輸出到一字符串中(在kernel/vsprintf.c,92 行)。 extern void init (void); // 函數原形,初始化(在168 行)。 extern void blk_dev_init (void); // 塊設備初始化子程序(kernel/blk_drv/ll_rw_blk.c,157 行) extern void chr_dev_init (void); // 字符設備初始化(kernel/chr_drv/tty_io.c, 347 行) extern void hd_init (void); // 硬盤初始化程序(kernel/blk_drv/hd.c, 343 行) extern void floppy_init (void); // 軟驅初始化程序(kernel/blk_drv/floppy.c, 457 行) extern void mem_init (long start, long end); // 內存管理初始化(mm/memory.c, 399 行) extern long rd_init (long mem_start, int length); //虛擬盤初始化(kernel/blk_drv/ramdisk.c,52) extern long kernel_mktime (struct tm *tm); // 建立內核時間(秒)。 extern long startup_time; // 內核啟動時間(開機時間)(秒)。 /* * This is set up by the setup-routine at boot-time */ /* * 以下這些數據是由setup.s 程序在引導時間設置的(參見第2 章2.3.1 節中的表2.1)。 */#define EXT_MEM_K (*(unsigned short *)0x90002) // 1M 以后的擴展內存大小(KB)。#define DRIVE_INFO (*(struct drive_info *)0x90080) // 硬盤參數表基址。#define ORIG_ROOT_DEV (*(unsigned short *)0x901FC) // 根文件系統所在設備號。 /* * Yeah, yeah, it's ugly, but I cannot find how to do this correctly * and this seems to work. I anybody has more info on the real-time * clock I'd be interested. Most of this was trial and error, and some * bios-listing reading. Urghh. */ /* * 是啊,是啊,下面這段程序很差勁,但我不知道如何正確地實現,而且好象它還能運行。如果有 * 關于實時時鐘更多的資料,那我很感興趣。這些都是試探出來的,以及看了一些bios 程序,呵! */#define CMOS_READ(addr) ({ \ // 這段宏讀取CMOS 實時時鐘信息。outb_p (0x80 | addr, 0x70);\ // 0x70 是寫端口號,0x80|addr 是要讀取的CMOS 內存地址。 inb_p (0x71);\ // 0x71 是讀端口號。})#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10) // 將BCD 碼轉換成數字。 static void time_init (void) // 該子程序取CMOS 時鐘,并設置開機時間??startup_time(秒)。 { struct tm time; do { time.tm_sec = CMOS_READ (0); // 參見后面CMOS 內存列表。 time.tm_min = CMOS_READ (2); time.tm_hour = CMOS_READ (4); time.tm_mday = CMOS_READ (7); time.tm_mon = CMOS_READ (8); time.tm_year = CMOS_READ (9); } while (time.tm_sec != CMOS_READ (0)); BCD_TO_BIN (time.tm_sec); BCD_TO_BIN (time.tm_min); BCD_TO_BIN (time.tm_hour); BCD_TO_BIN (time.tm_mday); BCD_TO_BIN (time.tm_mon); BCD_TO_BIN (time.tm_year); time.tm_mon--; startup_time = kernel_mktime (&time); }static long memory_end = 0; // 機器具有的內存(字節數)。static long buffer_memory_end = 0; // 高速緩沖區末端地址。static long main_memory_start = 0; // 主內存(將用于分頁)開始的位置。struct drive_info{ char dummy[32];}drive_info; // 用于存放硬盤參數表信息。void main (void) /* This really IS void, no error here. */{ /* The startup routine assumes (well, ...) this */ /* 這里確實是void,并沒錯。在startup 程序(head.s)中就是這樣假設的。 */ // 參見head.s 程序第136 行開始的幾行代碼。 /* * Interrupts are still disabled. Do necessary setups, then * enable them */ /* * 此時中斷仍被禁止著,做完必要的設置后就將其開啟。 */ // 下面這段代碼用于保存: // 根設備號 ??ROOT_DEV; 高速緩存末端地址??buffer_memory_end; // 機器內存數??memory_end;主內存開始地址 ??main_memory_start; ROOT_DEV = ORIG_ROOT_DEV; drive_info = DRIVE_INFO; memory_end = (1 << 20) + (EXT_MEM_K << 10); // 內存大小=1Mb 字節+擴展內存(k)*1024 字節。 memory_end &= 0xfffff000; // 忽略不到4Kb(1 頁)的內存數。 if (memory_end > 16 * 1024 * 1024) // 如果內存超過16Mb,則按16Mb 計。 memory_end = 16 * 1024 * 1024; if (memory_end > 12 * 1024 * 1024) // 如果內存>12Mb,則設置緩沖區末端=4Mb buffer_memory_end = 4 * 1024 * 1024; else if (memory_end > 6 * 1024 * 1024) // 否則如果內存>6Mb,則設置緩沖區末端=2Mb buffer_memory_end = 2 * 1024 * 1024; else buffer_memory_end = 1 * 1024 * 1024; // 否則則設置緩沖區末端=1Mb main_memory_start = buffer_memory_end; // 主內存起始位置=緩沖區末端;#ifdef RAMDISK // 如果定義了虛擬盤,則主內存將減少。 main_memory_start += rd_init (main_memory_start, RAMDISK * 1024);#endif // 以下是內核進行所有方面的初始化工作。閱讀時最好跟著調用的程序深入進去看,實在看 // 不下去了,就先放一放,看下一個初始化調用 -- 這是經驗之談?。 mem_init (main_memory_start, memory_end); trap_init (); // 陷阱門(硬件中斷向量)初始化。(kernel/traps.c,181 行) blk_dev_init (); // 塊設備初始化。 (kernel/blk_dev/ll_rw_blk.c,157 行) chr_dev_init (); // 字符設備初始化。 (kernel/chr_dev/tty_io.c,347 行) tty_init (); // tty 初始化。 (kernel/chr_dev/tty_io.c,105 行) time_init (); // 設置開機啟動時間??startup_time(見76 行)。 sched_init (); // 調度程序初始化(加載了任務0 的tr, ldtr) (kernel/sched.c,385) buffer_init (buffer_memory_end); // 緩沖管理初始化,建內存鏈表等。(fs/buffer.c,348) hd_init (); // 硬盤初始化。 (kernel/blk_dev/hd.c,343 行) floppy_init (); // 軟驅初始化。 (kernel/blk_dev/floppy.c,457 行) sti (); // 所有初始化工作都做完了,開啟中斷。 // 下面過程通過在堆棧中設置的參數,利用中斷返回指令切換到任務0。 move_to_user_mode (); // 移到用戶模式。 (include/asm/system.h,第1 行) if (!fork ()) { /* we count on this going ok */ init (); } /* * NOTE!! For any other task 'pause()' would mean we have to get a * signal to awaken, but task0 is the sole exception (see 'schedule()') * as task 0 gets activated at every idle moment (when no other tasks * can run). For task0 'pause()' just means we go check if some other * task can run, and if not we return here. */ /* 注意!! 對于任何其它的任務,'pause()'將意味著我們必須等待收到一個信號才會返 * 回就緒運行態,但任務0(task0)是唯一的意外情況(參見'schedule()'),因為任務0 在 * 任何空閑時間里都會被激活(當沒有其它任務在運行時),因此對于任務0'pause()'僅意味著 * 我們返回來查看是否有其它任務可以運行,如果沒有的話我們就回到這里,一直循環執行'pause()'。 */ for (;;) pause ();}static int printf (const char *fmt, ...) // 產生格式化信息并輸出到標準輸出設備stdout(1),這里是指屏幕上顯示。參數'*fmt'指定輸出將 // 采用的格式,參見各種標準C 語言書籍。該子程序正好是vsprintf 如何使用的一個例子。 // 該程序使用vsprintf()將格式化的字符串放入printbuf 緩沖區,然后用write()將緩沖區的內容 // 輸出到標準設備(1--stdout)。{ va_list args; int i; va_start (args, fmt); write (1, printbuf, i = vsprintf (printbuf, fmt, args)); va_end (args); return i;}static char *argv_rc[] ={"/bin/sh", NULL}; // 調用執行程序時參數的字符串數組。static char *envp_rc[] ={"HOME=/", NULL}; // 調用執行程序時的環境字符串數組。static char *argv[] ={"-/bin/sh", NULL}; // 同上。static char *envp[] ={"HOME=/usr/root", NULL};void init (void){ int pid, i; // 讀取硬盤參數包括分區表信息并建立虛擬盤和安裝根文件系統設備。 // 該函數是在25 行上的宏定義的,對應函數是sys_setup(),在kernel/blk_drv/hd.c,71 行。 setup ((void *) &drive_info); (void) open ("/dev/tty0", O_RDWR, 0); // 用讀寫訪問方式打開設備“/dev/tty0”, // 這里對應終端控制臺。 // 返回的句柄號0 -- stdin 標準輸入設備。 (void) dup (0); // 復制句柄,產生句柄1 號 -- stdout 標準輸出設備。 (void) dup (0); // 復制句柄,產生句柄2 號 -- stderr 標準出錯輸出設備。 printf ("%d buffers = %d bytes buffer space\n\r", NR_BUFFERS, NR_BUFFERS * BLOCK_SIZE); // 打印緩沖區塊數和總字節數,每塊1024 字節。 printf ("Free mem: %d bytes\n\r", memory_end - main_memory_start); //空閑內存字節數。 // 下面fork()用于創建一個子進程(子任務)。對于被創建的子進程,fork()將返回0 值, // 對于原(父進程)將返回子進程的進程號。所以180-184 句是子進程執行的內容。該子進程 // 關閉了句柄0(stdin),以只讀方式打開/etc/rc 文件,并執行/bin/sh 程序,所帶參數和 // 環境變量分別由argv_rc 和envp_rc 數組給出。參見后面的描述。 if (!(pid = fork ())) { close (0); if (open ("/etc/rc", O_RDONLY, 0)) _exit (1); // 如果打開文件失敗,則退出(/lib/_exit.c,10)。 execve ("/bin/sh", argv_rc, envp_rc); // 裝入/bin/sh 程序并執行。 _exit (2); // 若execve()執行失敗則退出(出錯碼2,“文件或目錄不存在”)。 } // 下面是父進程執行的語句。wait()是等待子進程停止或終止,其返回值應是子進程的進程號(pid)。 // 這三句的作用是父進程等待子進程的結束。&i 是存放返回狀態信息的位置。如果wait()返回值不 // 等于子進程號,則繼續等待。 if (pid > 0) while (pid != wait (&i)) /* nothing */ ; // 如果執行到這里,說明剛創建的子進程的執行已停止或終止了。下面循環中首先再創建一個子進程, // 如果出錯,則顯示“初始化程序創建子進程失敗”的信息并繼續執行。對于所創建的子進程關閉所有 // 以前還遺留的句柄(stdin, stdout, stderr),新創建一個會話并設置進程組號,然后重新打開 // /dev/tty0 作為stdin,并復制成stdout 和stderr。再次執行系統解釋程序/bin/sh。但這次執行所 // 選用的參數和環境數組另選了一套(見上面165-167 行)。然后父進程再次運行wait()等待。如果 // 子進程又停止了執行,則在標準輸出上顯示出錯信息“子進程pid 停止了運行,返回碼是i”,然后 // 繼續重試下去…,形成“大”死循環。 while (1) { if ((pid = fork ()) < 0) { printf ("Fork failed in init\r\n"); continue; } if (!pid) { close (0); close (1); close (2); setsid (); (void) open ("/dev/tty0", O_RDWR, 0); (void) dup (0); (void) dup (0); _exit (execve ("/bin/sh", argv, envp)); } while (1) if (pid == wait (&i)) break; printf ("\n\rchild %d died with code %04x\n\r", pid, i); sync (); } _exit (0); /* NOTE! _exit, not exit() */}
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -