Linux-0.11 入口函数main.c详解
模块简介
main.c大部分代码主要是对内核进行初始化,而main.c开始,就都是c语言编写的内核了。
函数详解
time_init
static void time_init(void)
该函数读取CMOS时钟信息作为系统的开机时间。
struct tm time;
do {
time.tm_sec = CMOS_READ(0);//当前的秒数
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);//调用kernel_mktime构建时间,详情参考Linux-0.11 kernel目录mktime.c详解
main
void main(void)
在head.s中会跳转main函数中进行执行。
if (memory_end > 16*1024*1024)
memory_end = 16*1024*1024;
if (memory_end > 12*1024*1024)
buffer_memory_end = 4*1024*1024;
else if (memory_end > 6*1024*1024)
buffer_memory_end = 2*1024*1024;
else
buffer_memory_end = 1*1024*1024;
接下来就是对各个模块进行初始化。其内容在具体的模块都有讲解,这里不再赘述。在这最后,会重新打开中断。
mem_init(main_memory_start,memory_end);
trap_init();
blk_dev_init();
chr_dev_init();
tty_init();
time_init();
sched_init();
buffer_init(buffer_memory_end);
hd_init();
floppy_init();
sti();
接下来,将切换到用户态去执行进程0。
move_to_user_mode();
if (!fork()) { /* we count on this going ok */
init();
}
在分析move_to_user_mode,我们回顾一下sched_init函数中和进程0相关的部分。
在sched_init函数中设置了进程0的ldt和tss。其中ldt部分的定义如下:
//ldt
{ \
{0,0}, \
{0x9f,0xc0fa00}, \
{0x9f,0xc0f200}, \
}, \
其中代码段为{0x0000009f,0x00c0fa00}, 数据段为{0x0000009f,0x00c0f200}
接下来我们依次分析。
首先是代码段,{0x0000009f,0x00c0fa00}, 翻译成二进制,注意是按照小端序列,如下所示:
63-48:00000000 11000000
47-32:11111010 00000000
31-16:00000000 00000000
15-00:00000000 10011111
按照段描述符的定义,代码段的分析结果如下
:
段基址:00000000 00000000 00000000 00000000
段限长:0x009f * 4k
接下来是数据段,{0x0000009f,0x00c0f200}, 翻译成二进制,注意是按照小端序列,如下所示:
63-48:00000000 11000000
47-32:11110010 00000000
31-16:00000000 00000000
15-00:00000000 10011111
按照段描述符的定义,数据段的分析结果如下
段基址:00000000 00000000 00000000 00000000
段限长:0x009f * 4k
由此可以看出,进程0的代码段和数据段都映射到线性地址为0处。
数据段和代码段唯一不同的是TYPE字段不同,代码段是1010,代表是代码段,其可读可执行。数据段是0010,代表数据段,且可读可写。
因此sched_init为这里切换进程0到用户态执行打下了铺垫。
下面我们就来看move_to_user_mode, 其定义如下:
#define move_to_user_mode() \
__asm__ ("movl %%esp,%%eax\n\t" \
"pushl $0x17\n\t" \
"pushl %%eax\n\t" \
"pushfl\n\t" \
"pushl $0x0f\n\t" \
"pushl $1f\n\t" \
"iret\n" \
"1:\tmovl $0x17,%%eax\n\t" \
"movw %%ax,%%ds\n\t" \
"movw %%ax,%%es\n\t" \
"movw %%ax,%%fs\n\t" \
"movw %%ax,%%gs" \
:::"ax")
看到其中使用了iret指令,因此这里其实是模拟了一个中断的压栈情况。其效果就是在iret返回时,使得ss=0x17,cs=0x0f
接着通过movw指令,将ds、es、fs、gs都设置为0x17。
通过这一番操作,这些寄存器中的DPL都是3了,也就是用户态了。因此,可以这样总结,move_to_user_mode实际就是将cs/ds/es/fs/gs/ss 都设置为用户态的值。
在这最后,进程0开始fork出进程1, 而自己则进入pause,因此进程0又被称为idle进程。
if (!fork()) { /* we count on this going ok */
init();
}
for(;;) pause();
init
void init(void)
init进程是系统中真正的第一个进程。
int pid,i;
setup((void *) &drive_info);
(void) open("/dev/tty0",O_RDWR,0);
(void) dup(0);
(void) dup(0);
printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS,
NR_BUFFERS*BLOCK_SIZE);
printf("Free mem: %d bytes\n\r",memory_end-main_memory_start);
执行shell程序,加载/etc/rc
if (!(pid=fork())) {
close(0);
if (open("/etc/rc",O_RDONLY,0))
_exit(1);
execve("/bin/sh",argv_rc,envp_rc);
_exit(2);
}
if (pid>0)
while (pid != wait(&i))
/* nothing */;
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() */