继main函数开启中断和将进程0从0特权级翻转至3特权级后 main函数就调用fork()函数创建子进程
/main.c
static inline _syscall0(int,fork)//内联函数
static inline _syscall0(int,pause)
static inline _syscall1(int,setup,void *,BIOS)
static inline _syscall0(int,sync)
void main(void)
{
...
if (!fork()) { /* we count on this going ok */
init();
}
for(;;) pause();
}
/unistd.h
#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 __NR_write 4
#define __NR_open 5
#define __NR_close 6
...
volatile void exit(int status);
volatile void _exit(int status);
int fcntl(int fildes, int cmd, ...);
int fork(void);
int getpid(void);
int getuid(void);
int geteuid(void);
...
#define _syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name)); \
if (__res >= 0) \
return (type) __res; \
errno = -__res; \
return -1; \
}
...
/sys.h
extern int sys_setup();
extern int sys_exit();
extern int sys_fork();
...
//sys_fork位于sys_call_table的第三项
fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,
sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,
sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,
sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,
sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,
sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,
sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,
sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,
sys_setreuid,sys_setregid };
...
所以经过宏定义的代替 扩展可以得到fork函数的代码
int fork(void)
{
long __res;
__asm__ volatile ("int $0x80"
: "=a" (__res) //将__res赋给eax
: "0" (__NR_fork)); //将2赋值给eax
if (__res >= 0) //int 0x80中断返回就执行这句
return (type) __res;
errno = -__res;
return -1;
}
这段嵌入的汇编代码 执行步骤主要是
1.先执行”0” (__NR_fork); 将__NR_fork的值即2 赋值给eax 这个2也就是sys_fork在sys_call_table的偏移量。
2.执行int 0x80产生软中断cpu从3特权级的进程跳转到0特权级内核代码执行。中断会使硬件自动将SS,ESP,EFLAGS,CS,EIP寄存器的数值压入内核栈
3.压栈后跳转到system_call.s中的_system_call处执行
_system_call:
cmpl $nr_system_calls-1,%eax
ja bad_sys_call
push %ds
push %es
push %fs
pushl %edx
pushl %ecx # push %ebx,%ecx,%edx as parameters
pushl %ebx # to the system call
movl $0x10,%edx # set up ds,es to kernel space
mov %dx,%ds
mov %dx,%es
movl $0x17,%edx # fs points to local data space
mov %dx,%fs
call _sys_call_table(,%eax,4) //eax是2 即调用sys_fork
pushl %eax
movl _current,%eax
cmpl $0,state(%eax) # state
jne reschedule
cmpl $0,counter(%eax) # counter
je reschedule
ret_from_sys_call:
movl _current,%eax # task[0] cannot have signals
cmpl _task,%eax
je 3f
cmpw $0x0f,CS(%esp) # was old code segment supervisor ?
jne 3f
cmpw $0x17,OLDSS(%esp) # was stack segment = 0x17 ?
jne 3f
movl signal(%eax),%ebx
movl blocked(%eax),%ecx
notl %ecx
andl %ebx,%ecx
bsfl %ecx,%ecx
je 3f
btrl %ecx,%ebx
movl %ebx,signal(%eax)
incl %ecx
pushl %ecx
call _do_signal
popl %eax
3: popl %eax
popl %ebx
popl %ecx
popl %edx
pop %fs
pop %es
pop %ds
iret
调用sys_fork执行
_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
//在task[64]中确定进程1的位置
int find_empty_process(void)
{
int i;
repeat:
if ((++last_pid)<0) last_pid=1; //last_pid溢出就置为1
for(i=0 ; i<NR_TASKS ; i++)
if (task[i] && task[i]->pid == last_pid) goto repeat;
for(i=1 ; i<NR_TASKS ; i++)
if (!task[i])
return i; //返回第一个空闲的i
return -EAGAIN;
}
接下来是调用copy_process为子进程创建task_struct 页表之类具体分析代码
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)
{//函数参数是前面int 0x80 system_call,sys_fork压栈的寄存器数据
struct task_struct *p;
int i;
struct file *f;
p = (struct task_struct *) get_free_page();//申请一个空闲页面
if (!p)
return -EAGAIN;
task[nr] = p;
*p = *current; /* NOTE! this doesn't copy the supervisor stack */ //将父进程的task_struct复制给子进程 current是当前进程的指针 指向task_struct
p->state = TASK_UNINTERRUPTIBLE; //子进程设置为不可中断状态
p->pid = last_pid; //开始子进程的个性化设置
p->father = current->pid;
p->counter = p->priority;
p->signal = 0;
p->alarm = 0;
p->leader = 0; /* process leadership doesn't inherit */
p->utime = p->stime = 0;
p->cutime = p->cstime = 0;
p->start_time = jiffies;
p->tss.back_link = 0; //设置子进程的tss
p->tss.esp0 = PAGE_SIZE + (long) p; //esp0是内核栈指针
p->tss.ss0 = 0x10; //0x10可以看做 10000 0特权级 gdt 数据段
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;
p->tss.ldt = _LDT(nr); 挂载子进程的ldt
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;
}
for (i=0; i<NR_OPEN;i++)//将父进程相关文件属性的引用计数加1 表明父子进程共享文件
if (f=p->filp[i])
f->f_count++;
if (current->pwd)
current->pwd->i_count++;
if (current->root)
current->root->i_count++;
if (current->executable)
current->executable->i_count++;
set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss)); //设置GDT中与子进程相关的项 就将子进程的tss和ldt挂接在gdt中
set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
p->state = TASK_RUNNING; /* do this last, just in case */// 设置子进程为就绪态
return last_pid;
}
unsigned long get_free_page(void) //遍历mem_map[],找到主内存中第一个空闲页面
{
register unsigned long __res asm("ax");
__asm__("std ; repne ; scasb\n\t"
"jne 1f\n\t"
"movb $1,1(%%edi)\n\t"
"sall $12,%%ecx\n\t"
"addl %2,%%ecx\n\t"
"movl %%ecx,%%edx\n\t"
"movl $1024,%%ecx\n\t"
"leal 4092(%%edx),%%edi\n\t"
"rep ; stosl\n\t"
"movl %%edx,%%eax\n"
"1:"
:"=a" (__res)
:"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),
"D" (mem_map+PAGING_PAGES-1)
:"di","cx","dx");
return __res;
}
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); //0x0f 即1111,代码段,LDT,3特权级
data_limit=get_limit(0x17);//0x17 即10111,数据段,LDT,3特权级
old_code_base = get_base(current->ldt[1]); //父进程的代码段数据段基址
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;//子进程的代码基址 64MB
p->start_code = new_code_base;
set_base(p->ldt[1],new_code_base);//设置子进程的代码段基址
set_base(p->ldt[2],new_data_base); //设置子进程的数据段基址
//为进程1创建第一个页表 复制进程0的页表 设置进程一的页目录项(通过页目录项就能找到页表 )
if (copy_page_tables(old_data_base,new_data_base,data_limit)) {
free_page_tables(new_data_base,data_limit);
return -ENOMEM;
}
return 0;
}
int copy_page_tables(unsigned long from,unsigned long to,long size)
{
unsigned long * from_page_table;
unsigned long * to_page_table;
unsigned long this_page;
unsigned long * from_dir, * to_dir;
unsigned long nr;
//一个页表的管理范围为4MB 二进制是22个1 所以两边必须为0 即4MB的整数倍 来符合分页的要求
if ((from&0x3fffff) || (to&0x3fffff))
panic("copy_page_tables called with wrong alignment");
from_dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
to_dir = (unsigned long *) ((to>>20) & 0xffc);
size = ((unsigned) (size+0x3fffff)) >> 22;
for( ; size-->0 ; from_dir++,to_dir++) {
if (1 & *to_dir)
panic("copy_page_tables: already exist");
if (!(1 & *from_dir))
continue;
/* from_dir是页目录项的地址 高20位的页表的地址 */
from_page_table = (unsigned long *) (0xfffff000 & *from_dir);
if (!(to_page_table = (unsigned long *) get_free_page()))
return -1; /* Out of memory, see freeing */
*to_dir = ((unsigned long) to_page_table) | 7;
nr = (from==0)?0xA0:1024;
for ( ; nr-- > 0 ; from_page_table++,to_page_table++) { //复制父进程的页表
this_page = *from_page_table;
if (!(1 & this_page))
continue;
this_page &= ~2; //设置页表项属性 2是010 ~2是101 代表用户 只读 存在
*to_page_table = this_page;
if (this_page > LOW_MEM) { //1MB以下的内核区不参加用户分页管理
*from_page_table = this_page;
this_page -= LOW_MEM;
this_page >>= 12;
mem_map[this_page]++; //增加引用计数
}
}
}
invalidate();
return 0;
}
进程1创建完毕后就返回执行
_sys_fork:
call _find_empty_process
testl %eax,%eax
js 1f
push %gs
pushl %esi
pushl %edi
pushl %ebp
pushl %eax
call _copy_process
addl $20,%esp
1: ret
执行完copy_process就esp清20字节的栈 也就是前面压入的参数值 这里eax的值是copy_process返回的last_pid 即进程1的进程号
_system_call:
cmpl $nr_system_calls-1,%eax
ja bad_sys_call
push %ds
push %es
push %fs
pushl %edx
pushl %ecx # push %ebx,%ecx,%edx as parameters
pushl %ebx # to the system call
movl $0x10,%edx # set up ds,es to kernel space
mov %dx,%ds
mov %dx,%es
movl $0x17,%edx # fs points to local data space
mov %dx,%fs
call _sys_call_table(,%eax,4)
pushl %eax
movl _current,%eax #当前进程是进程0
cmpl $0,state(%eax) # state
jne reschedule #如果进程0不是就绪态 就进程调度
cmpl $0,counter(%eax) # counter
je reschedule #如果当前进程是进程0 就跳到后面的3:执行
call _sys_call_table(,%eax,4)执行完后将eax压栈 然后
3: popl %eax
popl %ebx
popl %ecx
popl %edx
pop %fs
pop %es
pop %ds
iret
3:这里主要是将压栈的各个寄存器数值还原
最后
int fork(void)
{
long __res;
__asm__ volatile ("int $0x80"
: "=a" (__res) //将__res赋给eax
: "0" (__NR_fork)); //将2赋值给eax
if (__res >= 0) //iret后 执行这里 __res就是eax 值是1
return (type) __res; //返回1
errno = -__res;
return -1;
}
void main(void)
{
...
if (!fork()) { /* we count on this going ok */ //返回1 所以这里不会执行init()
init();
for(;;) pause(); //所以从这里开始执行
...
}
这里就创建玩了进程 接下来是进程调度 下节再分析