leevis.com
leevis.com copied to clipboard
Linux-0.11 fork系统调用分析
概述
fork
系统调用产生一个子进程,返回2次。父进程返回子进程的进程ID,子进程则返回0.
出错则返回-1.
例如:nginx调用fork创建worker进程。
switch (pid) {
case -1:
return NGX_INVALID_PID;
case 0:
proc(cycle, data); // worker进程执行函数
break;
default: // 父进程
break;
}
...
fork内核代码分析
int find_empty_process(void)
{
int i;
repeat:
if ((++last_pid)<0) last_pid=1; // 32位整形 最大2**31,超过最大值从1重新开始。
for(i=0 ; i<NR_TASKS ; i++)
if (task[i] && task[i]->pid == last_pid) goto repeat; // 找运行进程最大的一个值作为新进程的pid。
for(i=1 ; i<NR_TASKS ; i++) // 进程数组中,找一个空位,返回空位的下标。
if (!task[i])
return i;
return -EAGAIN; // 没有找到空槽,则返回一个负数。
}
_sys_fork:
call _find_empty_process # 任务数组中找一个空位,eax是空位下标,如果没找到返回负数。
testl %eax,%eax # 查看 eax内容是 0, <0, >0
js 1f # <0 则跳到1处。 也就是没有找到空槽则返回
push %gs
pushl %esi
pushl %edi
pushl %ebp
pushl %eax # eax内容是调用 find_empty_process 的返回值,也就是找到的空槽下标
call _copy_process # 复制进程结构
addl $20,%esp # 恢复堆栈
1: ret # 此时 eax已经是调用 copy_process的返回值了,也就是sys_fork的返回值。
#
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;
p = (struct task_struct *) get_free_page(); // 找一页空闲物理内存
if (!p)
return -EAGAIN; // 如果没有空闲内存则返回负数。最终被赋给了errno。
task[nr] = p;
*p = *current; /* NOTE! this doesn't copy the supervisor stack */
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;
p->tss.esp0 = PAGE_SIZE + (long) p; // 申请了一页(4k)物理内存,低地址保存了task_struct,剩余的都是内核态栈
p->tss.ss0 = 0x10; // 内核数据段选择子 0x10=0b0001 0000。 index=0b00010 TI=0b0 RPL=0b00 GDT表第3项。 对于linux0.11内核,内核数据段,段限长足够长可以寻址整个物理内存,包括主内存。
p->tss.eip = eip;
p->tss.eflags = eflags;
p->tss.eax = 0; // eax做为函数调用的返回值,当新进程被调度执行时,
// 该值会被cpu加载到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);
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++)
if (f=p->filp[I]) // 继承父进程的打开文件数加1
f->f_count++;
if (current->pwd) // 进程工作目录,i节点引用数加1
current->pwd->i_count++;
if (current->root) // 进程根目录,i节点引用数加1
current->root->i_count++;
if (current->executable) // 执行文件i节点引用数加1
current->executable->i_count++;
set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
p->state = TASK_RUNNING; /* do this last, just in case */
return last_pid; // 返回子进程的id
}
总结
用户进程调用fork
后,通过int80中断调用到内核的sys_fork
函数。
该函数首先获取一个任务空槽,然后申请一页物理内存,保存子进程任务结构体,复制父进程结构体,把子进程状态改为不可中断的等待状态,防止被提前调度。然后对子进程结构进行修改。修改完后将新的子进程的状态改为就绪,等待调度。