【linux V0.11】init/main.c
文章目录
- void main(void)
- move_to_user_mode
- _syscall
- fork()
- void init(void)
- 其他
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 DEVdrive_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,则限制最大可用内存为 16MBmemory_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;elsebuffer_memory_end = 1*1024*1024;main_memory_start = buffer_memory_end; //主内存从缓冲区之后开始分配
#ifdef RAMDISK //如果定义了虚拟盘,则初始化虚拟盘。此时主内存将减少main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
#endifmem_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,%dsmovw %ax, %esmovw %ax,%fsmovw %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 nameif (__res >= 0)return (type) __res; //如果返回值>=0,则直接返回该值errno = -__res; //否则置出错号,并返回-1return -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,%eaxjs 1fpush %gspushl %esipushl %edipushl %ebppushl %eaxcall _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