void main(void)
#define EXT_MEM_K(∗(unsignedshort∗)0x90002) //1MB以后的扩展内存大小(KB)。
#define DRIVE_NFO(∗(structdrive info∗)0x90080) //硬盘参数表基址。
#define ORIG_ROOT_DEV(∗(unsignedshort∗)0x901FC) //根文件系统所在设备号。
struct drive_info { char dummy[32]; } drive_info;//用于存放硬盘参数表信息
void main(void)
{
ROOT_DEV = ORIG_ROOT_DEV; //根设备号 →ROOT DEV
drive_info = DRIVE_INFO;
memory_end = (1<<20) + (EXT_MEM_K<<10); //内存大小=1MB+扩展内存(KB)∗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) //根据总内存大小决定保留多少空间作为缓冲区
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;
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();
blk_dev_init();
chr_dev_init();
tty_init();
time_init();
sched_init();
buffer_init(buffer_memory_end);
hd_init();
floppy_init();
sti(); //开中断
move_to_user_mode(); //切换到用户模式
if (!fork()) {
init(); //创建第一个用户态进程 init,它是所有后续进程的祖先进程。
}
//task0(空闲进程),是唯一一个可以在没有收到信号的情况下被唤醒的任务。
for(;;) pause(); //进入 idle 循环 pause()
}
move_to_user_mode
内核在初始化结束时“切换”到初始任务0。
模拟中断调用返回过程,即利用指令iret
运行初始任务0。
首先设置堆栈,模拟刚 进入中断调用过程时(具有特权层切换的) 堆栈的内容布置情况。
movl %esp,%eax //将当前堆栈指针 esp 保存到 eax,这是用户态堆栈的地址(即内核栈当前的位置)
pushl $0x17 //首先将Task0堆栈段选择符(SS)入栈
pushl %eax //将 eax(即用户栈指针)压入栈中
pushfl //将当前标志寄存器压栈,保存状态
pushl $0x0f //将Task0代码段选择符(cs)入栈。
pushl $1f //将下面标号1的偏移地址(eip)入栈。
iret
1: movl $0x17,%eax //设置用户态的段寄存器,都设置为 0x17,即用户数据段选择符
movw %ax,%ds
movw %ax, %es
movw %ax,%fs
movw %ax,%gs
_syscall
这些宏的作用是让用户程序通过 int $0x80 指令触发中断,进入内核态,从而调用相应的内核函数。
_syscall0处理无参数,_syscall1处理一个参数,以此类推。
用户程序不能直接访问硬件或执行特权指令 ,必须通过“系统调用”来请求内核完成任务。
//内核实现的系统调用符号常数,用作系统调用函数表中的索引值
#define __NR_setup 0 /* used only by init, to get system going */
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
...
#define _syscall0(type,name)
type name(void)
{
long __res;
__asm__ volatile ("int $0x80" //调用系统中断0x80
: "=a" (__res) //返回值==>eax( res)。
: "0" (__NR_##name)); //输入为系统中断调用号 NR name
if (__res >= 0)
return (type) __res; //如果返回值>=0,则直接返回该值
errno = -__res; //否则置出错号,并返回-1
return -1;
}
当用户态执行 int $0x80
后,CPU 会跳转到内核中的中断处理入口(_system_call)。Linux 内核根据sys_call_table和传入的系统调用号(索引)找到对应的处理函数。
fork()
static inline _syscall0(int,fork)
首先调用C函数find_empty_process(),取得一个进程号pid。若返回负数则说明目前任务数组已
#满。然后调用copy_process()复制进程。
_sys_fork:
call _find_empty_process //查找空进程槽
testl %eax,%eax
js 1f
push %gs
pushl %esi
pushl %edi
pushl %ebp
pushl %eax
call _copy_process //调用copy_process()复制进程
addl $20,%esp
1: ret
具体内容以后再看。
void init(void)
init()函数运行在任务0创建的子进程(任务1)中。它首先对第一个要执行的程序(shell)的环境进行初始化,然后加载该程序并执行之。
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;
//sys_setup,读取硬盘参数包括分区表信息并建立虚拟盘和安装根文件系统设备。
setup((void *) &drive_info);
//用读写访问方式打开设备“/dev/tty0”,这里对应终端控制台。返回的句柄0号———stdin标准输入设备
(void) open("/dev/tty0",O_RDWR,0);
(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值,对于原(父进程)将返回子进程的进程号。
if (!(pid=fork())) {
//子进程执行的内容
close(0);//该子进程关闭了句柄0(stdin)
if (open("/etc/rc",O_RDONLY,0))//以只读方式打开/etc/rc文件
_exit(1);
execve("/bin/sh",argv_rc,envp_rc);//执行/bin/sh程序
_exit(2);
}
//wait()是等待子进程停止或终止,其返回值应是子进程的进程号(pid)。
//父进程等待子进程的结束。
//&i是存放返回状态信息的位置。如果wait()返回值不等于子进程号,则继续等待。
if (pid>0)
while (pid != wait(&i));
//如果执行到这里,说明刚创建的子进程的执行已停止或终止了。
while (1) {
//先再创建一个子进程,如果出错,打印信息后继续fork()。
if ((pid=fork())<0) {
printf("Fork failed in init\r\n");
continue;
}
if (!pid) {
close(0);close(1);close(2);//关闭所有以前还遗留的句柄(stdin,stdout,stderr)
//新创建一个会话并设置进程组号,然后重新打开/dev/tty0作为stdin,并复制成stdout和stderr。
setsid();
(void) open("/dev/tty0",O_RDWR,0);
(void) dup(0);
(void) dup(0);
_exit(execve("/bin/sh",argv,envp));//再次执行系统解释程序,但是参数环境与上次不一样
}
//父进程再次运行wait()等待
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() */
}
其他
main.c还有关于时间的初始化一些内容,ez