main
功能
前面介绍了head.s将执行权交给main.c,内核初始化完成,现在开始进入C代码的分析了。Init主要是取得前面setup.s设置的根文件设备号和一下内存全局变量,初始化陷阱门,块设备,字符设备,tty,磁盘,开启中断等,并从内核模式切换到用户模式。通过fork创建一个子程序,从进程0切换到进程1继续运行指定,进程0通过无限循环进行等待。
void main(void)
{
ROOT_DEV = ORIG_ROOT_DEV;
drive_info = DRIVE_INFO;
memory_end = (1<<20) + (EXT_MEM_K<<10);
memory_end &= 0xfffff000;
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;
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
tty_init();
!开机时间
time_init();
!调度
sched_init();
buffer_init(buffer_memory_end);
hd_init();
floppy_init();
sti();
!切换到用户模式
move_to_user_mode();
if (!fork()) {
!fork 子程序继续执行
init();
}
!父进程等待
for(;;) pause();
}
move_to_user_mode
这段代码的作用是切换到用户模式
主要由iret指令完成,当iret返回时,程序将数据出栈给ss,esp,eflags,cs,eip,之后就可以使进程进入用户态。
#define move_to_user_mode() \
__asm__ ("movl %%esp,%%eax\n\t" \ !堆栈指针复制给eax
"pushl $0x17\n\t" \ !压入SS段选择符
"pushl %%eax\n\t" \ !压入堆栈指针
"pushfl\n\t" \ !压入eflag
"pushl $0x0f\n\t" \ !压入CS段选择符
"pushl $1f\n\t" \ !压入下面名为1的label指针
"iret\n" \ !通过iret跳转到下面代码
"1:\tmovl $0x17,%%eax\n\t" \ !执行任务0
"movw %%ax,%%ds\n\t" \ !初始化段寄存器指向本局部表的数据段
"movw %%ax,%%es\n\t" \
"movw %%ax,%%fs\n\t" \
"movw %%ax,%%gs" \
:::"ax")
fork
在内核中,每个进程都使用一个不同的大于零的正整数来标识,称为进程标识号 pid(Porcess ID)。一个进
程可以通过 fork()调用创建一个或多个子进程。
system_call
从system_call.s中可以找到fork的实现
首先通过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
!调用copy_process
call _copy_process
!堆栈平衡
addl $20,%esp
1: ret
copy_mem
这个函数负责将老进程的mem数据向新进程中复制
int copy_mem(int nr,struct task_struct * p)
{
unsigned long old_data_base,new_data_base,data_limit;
unsigned long old_code_base,new_code_base,code_limit;
code_limit=get_limit(0x0f);
data_limit=get_limit(0x17);
!老进程的代码段 base
old_code_base = get_base(current->ldt[1]);
!老进程的数据段 base
old_data_base = get_base(current->ldt[2]);
if (old_data_base != old_code_base)
panic("We don't support separate I&D");
if (data_limit < code_limit)
panic("Bad data_limit");
new_data_base = new_code_base = nr * 0x4000000;
!设置新进程代码入口地址
p->start_code = new_code_base;
!设置新进程的idt
set_base(p->ldt[1],new_code_base);
set_base(p->ldt[2],new_data_base);
//新进程直接简单粗暴地复制了父进程的数据,但是代码并未复用
if (copy_page_tables(old_data_base,new_data_base,data_limit)) {
free_page_tables(new_data_base,data_limit);
return -ENOMEM;
}
return 0;
}
copy_process
copy_process的作用主要是构造task_struct结构体,将从find_empty_process找到的任务号作为索引放入task数组中,并作为新的pid。
int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
long ebx,long ecx,long edx,
long fs,long es,long ds,
long eip,long cs,long eflags,long esp,long ss)
{
struct task_struct *p;
int i;
struct file *f;
!分配task_struct的结构体内存
p = (struct task_struct *) get_free_page();
if (!p)
return -EAGAIN;
task[nr] = p;
*p = *current;
p->state = TASK_UNINTERRUPTIBLE;
p->pid = last_pid;
!取当前pid作为新进程的父pid
p->father = current->pid;
p->counter = p->priority;
!信号置0
p->signal = 0;
p->alarm = 0;
p->leader = 0;
p->utime = p->stime = 0;
p->cutime = p->cstime = 0;
p->start_time = jiffies;
p->tss.back_link = 0;
!复制寄存器
p->tss.esp0 = PAGE_SIZE + (long) p;
p->tss.ss0 = 0x10;
p->tss.eip = eip;
p->tss.eflags = eflags;
p->tss.eax = 0;
p->tss.ecx = ecx;
p->tss.edx = edx;
p->tss.ebx = ebx;
p->tss.esp = esp;
p->tss.ebp = ebp;
p->tss.esi = esi;
p->tss.edi = edi;
p->tss.es = es & 0xffff;
p->tss.cs = cs & 0xffff;
p->tss.ss = ss & 0xffff;
p->tss.ds = ds & 0xffff;
p->tss.fs = fs & 0xffff;
p->tss.gs = gs & 0xffff;
!新任务的ldt
p->tss.ldt = _LDT(nr);
p->tss.trace_bitmap = 0x80000000;
if (last_task_used_math == current)
__asm__("clts ; fnsave %0"::"m" (p->tss.i387));
!复制新任务的内存
if (copy_mem(nr,p)) {
task[nr] = NULL;
free_page((long) p);
return -EAGAIN;
}
!将父进程打开的文件引用次数+1
for (i=0; i<NR_OPEN;i++)
if (f=p->filp[i])
f->f_count++;
!pwd引用次数+1
if (current->pwd)
current->pwd->i_count++;
!root引用次数+1
if (current->root)
current->root->i_count++;
if (current->executable)
current->executable->i_count++;
!设置新任务的TSS 和LDT 描述符项
set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
!新任务设置为TASK_RUNNING
p->state = TASK_RUNNING; /* do this last, just in case */
return last_pid;
}
init
流程
init的任务主要是读取硬盘参数包括分区表信息并建立虚拟盘和安装根文件系统设备,fork进程打开etc/rc文件,执行里面的命令。在这个子进程中运行终端初始程序agetty,agetty的作用主要是打印"login:",并从输入设备获取用户名提供给login程序,login判断输入的用户名正确才会去调用/bin/sh。
代码
void init(void)
{
int pid,i;
!装载驱动信息
setup((void *) &drive_info);
!打开tty
(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);
!fork一个子进程
if (!(pid=fork())) {
close(0);
!子进程中打开rc文件并执行
if (open("/etc/rc",O_RDONLY,0))
_exit(1);
execve("/bin/sh",argv_rc,envp_rc);
_exit(2);
}
!如果不是0号进程
if (pid>0)
!父进程等待
while (pid != wait(&i))
/* nothing */;
while (1) {
if ((pid=fork())<0) {
printf("Fork failed in init\r\n");
continue;
}
!fork成功
if (!pid) {
!关闭ioe句柄
close(0);close(1);close(2);
!重新设置id
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() */
}
总结
到这里linux的启动基本上就完成了,已经可以运行了,到这里还有一个问题,linux是如何对进程进行调度运行的呢。下一篇就分析下进程相关的代码。
关注我的技术公众号 不定期分析各种技术文章