Linux 0.11 内核学习之main.c

1.之所以选择这么低的版本学习,答案是简单,高版本的代码量太大,对于我这样的初学者来说,就是瞎子摸象不会有什么感觉。开始吧!

2首先你需要在一个地方下载源码:OldLinux

3.分析:

  1 /*
  2  *  linux/init/main.c
  3  *
  4  *  (C) 1991  Linus Torvalds
  5  */
  6 
  7 #define __LIBRARY__            //在unistd.h中,使用了#ifndef __LIBRARY__
  8 #include <unistd.h>            //包含unitsd.h
  9 #include <time.h>            // 结构体的定义,包含tm(结构体),时间函数定义
 10 
 11 /*
 12  * we need this inline - forking from kernel space will result
 13  * in NO COPY ON WRITE (!!!), until an execve is executed. This
 14  * is no problem, but for the stack. This is handled by not letting
 15  * main() use the stack at all after fork(). Thus, no function
 16  * calls - which means inline code for fork too, as otherwise we
 17  * would use the stack upon exit from 'fork()'.
 18  *
 19  * Actually only pause and fork are needed inline, so that there
 20  * won't be any messing with the stack from main(), but we define
 21  * some others too.
 22  */
 23  /*--翻译--*/  //这个地方与boot目录下的bootsect.s,setup.s,head.s有关
 24  /*
 25  * 我们需要下面这些内嵌语句 - 从内核空间创建进程(forking)将导致没有
 26  * 写时复制(COPY ON WRITE)!!! 直到一个执行execve 调用。这对堆栈可
 27  * 能带来问题。处理的方法是在fork()调用之后不让main()使用任何堆栈。
 28  * 因此就不能有函数调用 - 这意味着fork 也要使用内嵌的代码,否则我
 29  * 们在从fork()退出时就要使用堆栈了。实际上只有pause 和fork 需要使用
 30  * 内嵌方式,以保证从main()中不会弄乱堆栈,但是我们同时还定义了其它
 31  * 一些函数
 32  */
 33 static inline _syscall0(int,fork)
 34 static inline _syscall0(int,pause)
 35 static inline _syscall1(int,setup,void *,BIOS)
 36 static inline _syscall0(int,sync)        //系统调用
 37 
 38 #include <linux/tty.h>    //tty头文件,定义了有关tty_io,串行通信方面的参数,常数
 39 //所谓“串行通信”是指外设和计算机间使用一根数据信号线
 40 //数据在一根数据信号线上按位进行传输,每一位都占据一个固定的时间长度
 41 #include <linux/sched.h> //调度程序头文件,定义了任务结构和task_struct,第1一个初始任务的数据
 42 //还有一些以宏的形式定义的有关描述参数设置和获取的嵌入式汇编函数程序
 43 #include <linux/head.h>  //head头文件,定义了段描述的简单结构,和集合选择符常量
 44 #include <asm/system.h>  //系统头文件,以宏的形式定义了许多有关设置或修改
 45 //描述符/中断门等的嵌入式汇编子程序
 46 #include <asm/io.h>//io头文件,以宏的嵌入汇编程序形式定义对io端口操作的函数
 47 
 48 #include <stddef.h>//标准定义头文件,定义了NULL,offsetof(TYPE,MEMBER)
 49 #include <stdarg.h>//标准参数头文件,以宏的形式定义变量参数列表,主要说明一个类型(va_list)
 50 // 和三个宏(va_list,va_arg和va_end),vsprintf vprintf , vfprintf
 51 #include <unistd.h> 
 52 #include <fcntl.h> //文件控制头文件,用于文件及其描述符的操作控制常数符号的定义
 53 #include <sys/types.h>  //类型头文件,定义了基本的系统数据类型
 54 
 55 #include <linux/fs.h> //文件系统头文件,定义文件表结构(file,buffer_head,m_inode 等)
 56 
 57 static char printbuf[1024];
 58 
 59 extern int vsprintf();
 60 extern void init(void);
 61 extern void blk_dev_init(void); //块设备初始化
 62 extern void chr_dev_init(void); //字符设备初始化
 63 extern void hd_init(void);    //硬盘初始化程序
 64 extern void floppy_init(void); //软盘初始化程序
 65 extern void mem_init(long start, long end); //内存管理程序初始化
 66 extern long rd_init(long mem_start, int length); //虚拟盘初始化
 67 extern long kernel_mktime(struct tm * tm); //建立内核时间
 68 extern long startup_time; //内核启动时间(开机时间)(秒)
 69 
 70 /*
 71  * This is set up by the setup-routine at boot-time
 72  //一下数据是由setup.s程序在引导时间设置的
 73  */
 74 #define EXT_MEM_K (*(unsigned short *)0x90002) //1m以后的拓展内存大小
 75 #define DRIVE_INFO (*(struct drive_info *)0x90080) //硬盘参数表基地址
 76 #define ORIG_ROOT_DEV (*(unsigned short *)0x901FC) //根文件系统所在设备号
 77 
 78 /*
 79  * Yeah, yeah, it's ugly, but I cannot find how to do this correctly
 80  * and this seems to work. I anybody has more info on the real-time
 81  * clock I'd be interested. Most of this was trial and error, and some
 82  * bios-listing reading. Urghh.
 83  */
 84 
 85 #define CMOS_READ(addr) ({    //这段宏读取cmos实时时钟信息
 86 outb_p(0x80|addr,0x70);    //0x70是些端口号,0x80|addr是要读取CMOS内存地址
 87 inb_p(0x71);    //0x71是读端口号
 88 })
 89 
 90 #define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10) //将BSD码转换数字
 91 
 92 static void time_init(void) //读取cmos中的信息,初始化全局变量startup_time
 93 {
 94     struct tm time;
 95 
 96     do {
 97         time.tm_sec = CMOS_READ(0);
 98         time.tm_min = CMOS_READ(2);
 99         time.tm_hour = CMOS_READ(4);
100         time.tm_mday = CMOS_READ(7);
101         time.tm_mon = CMOS_READ(8);
102         time.tm_year = CMOS_READ(9);
103     } while (time.tm_sec != CMOS_READ(0));
104     BCD_TO_BIN(time.tm_sec);
105     BCD_TO_BIN(time.tm_min);
106     BCD_TO_BIN(time.tm_hour);
107     BCD_TO_BIN(time.tm_mday);
108     BCD_TO_BIN(time.tm_mon);
109     BCD_TO_BIN(time.tm_year);
110     time.tm_mon--;        //months since january - [0,11]
111     startup_time = kernel_mktime(&time);
112 }
113 
114 static long memory_end = 0; // 机器具有的内存(字节数)
115 static long buffer_memory_end = 0; //高速缓存末端地址
116 static long main_memory_start = 0; //主内存(将用于分页)开始的位置
117 
118 struct drive_info { char dummy[32]; } drive_info; //用于存放硬盘信息
119 
120 void main(void)        /* This really IS void, no error here. */
121 {            /* The startup routine assumes (well, ...) this */
122 //此时中断仍然是关着,在必要的设置完成之后
123 //打开中断
124 /*
125  * Interrupts are still disabled. Do necessary setups, then
126  * enable them
127  */
128   // 下面这段代码用于保存
129  // 根设备号 -- ROOT_DEV; 高速缓存末端地址 -- buffer_memory_end  
130  // 机器内存数 -- memory_end;主内存开始地址 -- main_memory_start
131      ROOT_DEV = ORIG_ROOT_DEV;
132      drive_info = DRIVE_INFO;
133     memory_end = (1<<20) + (EXT_MEM_K<<10); //内存大小=1Mb字节+扩展内存(k) * 1024字节
134     memory_end &= 0xfffff000;  //忽略不到4kb(1页)的内存数
135     if (memory_end > 16*1024*1024) //如果内存超过16Mb,则16Mb计
136         memory_end = 16*1024*1024;
137     if (memory_end > 12*1024*1024) //如果内存>12Mb,则设置缓冲末端4Mb
138         buffer_memory_end = 4*1024*1024;
139     else if (memory_end > 6*1024*1024) //否则如果内存 > 6Mb,则设置缓冲末端=2Mb
140         buffer_memory_end = 2*1024*1024;
141     else  //否则设置缓冲区末端=1Mb
142         buffer_memory_end = 1*1024*1024;
143     main_memory_start = buffer_memory_end; //主内存(用于分页使用)起始位置=缓冲区末端
144 #ifdef RAMDISK
145     main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
146 #endif
147     mem_init(main_memory_start,memory_end);
148     trap_init(); //陷阱门(硬件中断向量)初始化
149     blk_dev_init(); //块设备初始化
150     chr_dev_init(); //字符设备初始化
151     tty_init();//tty初始化
152     time_init();//设置开机启动时间,startup_time
153     sched_init(); //调度程序初始化
154     buffer_init(buffer_memory_end); //缓冲区初始化,建立内存链表
155     hd_init(); //硬盘初始化
156     floppy_init(); //软盘初始化
157     sti(); //设置完成,开启中断
158     move_to_user_mode(); //移到用户模式
159     if (!fork()) {        /* we count on this going ok */
160         init();
161     }
162 /*
163  *   NOTE!!   For any other task 'pause()' would mean we have to get a
164  * signal to awaken, but task0 is the sole exception (see 'schedule()')
165  * as task 0 gets activated at every idle moment (when no other tasks
166  * can run). For task0 'pause()' just means we go check if some other
167  * task can run, and if not we return here.
168  */
169  /*
170  * 注意!! 对于任何其它的任务,'pause()'将意味着我们必须等待收到一个信号才会返
171  * 回就绪运行态,但任务0(task0)是唯一的意外情况(参见'schedule()'),因为任务0 在
172  * 任何空闲时间里都会被激活(当没有其它任务在运行时),因此对于任务0'pause()'仅意味着
173  * 我们返回来查看是否有其它任务可以运行,如果没有的话我们就回到这里,一直循环执行'pause()'。
174  */  
175     for(;;) pause();
176 }
177 
178 static int printf(const char *fmt, ...)//使用变长参数,调用write系统调用
179 {
180     va_list args;
181     int i;
182 
183     va_start(args, fmt);
184     write(1,printbuf,i=vsprintf(printbuf, fmt, args));
185     va_end(args);
186     return i;
187 }
188 
189 static char * argv_rc[] = { "/bin/sh", NULL }; //调用执行程序时参数的字符串数组
190 static char * envp_rc[] = { "HOME=/", NULL };//调用执行程序时的环境字符串数组
191 
192 static char * argv[] = { "-/bin/sh",NULL };
193 static char * envp[] = { "HOME=/usr/root", NULL };
194 
195 void init(void)
196 {
197     int pid,i;
198 
199     setup((void *) &drive_info); //读取硬盘信息
200     (void) open("/dev/tty0",O_RDWR,0);//用读写访问方式打开设备"/dev/tty0"
201     (void) dup(0); //复制句柄,产生句柄1号--stdout标准输出设备
202     (void) dup(0); //复制句柄,产生句柄2号--stderr标准出错输出设备
203     //输出一些信息
204     printf("%d buffers = %d bytes buffer space

",NR_BUFFERS,
205         NR_BUFFERS*BLOCK_SIZE);
206     printf("Free mem: %d bytes

",memory_end-main_memory_start);
207     //下面的代码打开/etc/rc,然后执行/bin/sh,但是这里开辟了
208     //两个线程
209     if (!(pid=fork())) {
210         close(0);
211         if (open("/etc/rc",O_RDONLY,0))
212             _exit(1);
213         execve("/bin/sh",argv_rc,envp_rc);
214         _exit(2);
215     }
216     if (pid>0)
217         while (pid != wait(&i))
218             /* nothing */;
219 /*
220   * 如果执行到这里,说明刚创建的子进程的执行已停止或终止了。
221   * 下面循环中首先再创建一个子进程.如果出错,则显示“初始化
222   * 程序创建子进程失败”的信息并继续执行。对于所创建的子进
223   * 程关闭所有以前还遗留的句柄(stdin, stdout, stderr),新创
224   * 建一个会话并设置进程组号,然后重新打开/dev/tty0 作为stdin,
225   * 并复制成stdout 和stderr。再次执行系统解释程序/bin/sh。但
226   * 这次执行所选用的参数和环境数组另选了一套。然后父进程再次
227   * 运行wait()等待。如果子进程又停止了执行,则在标准输出上显
228   * 示出错信息“子进程pid 停止了运行,返回码是i”,然后继续重
229   * 试下去…,形成“大”死循环
230   *
231   */
232     while (1) {
233         if ((pid=fork())<0) {
234             printf("Fork failed in init
");
235             continue;
236         }
237         if (!pid) {
238             close(0);close(1);close(2);
239             setsid();
240             (void) open("/dev/tty0",O_RDWR,0);
241             (void) dup(0);
242             (void) dup(0);
243             _exit(execve("/bin/sh",argv,envp));
244         }
245         while (1)
246             if (pid == wait(&i))
247                 break;
248         printf("

child %d died with code %04x

",pid,i);
249         sync();
250     }
251     _exit(0);    /* NOTE! _exit, not exit() */
252 }
View Code

4.参考

参考一

参考二

参考三