當調用fork的時候,因爲是系統調用,所以會調用_syscall0這個函數
#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; \
}
這裏是一段彙編代碼,其中 int $0x80 是通過系統軟中斷陷入到內核,然後跳轉到system_call裏面執行。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)
pushl %eax
movl current,%eax
cmpl $0,state(%eax) # state
jne reschedule
cmpl $0,counter(%eax) # counter
je reschedule
這段彙編代碼的意思是_syscall0 調用的時候把參數壓入堆棧,然後在sys_call_table找到對應的函數,然後調用對應的函數.這裏是調用了對應的sys_fork.
.align 2
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
在sys_fork裏面先調用了find_empty_process,
find_empty_process的目的是找到最大的不重複的進程PID,用在copy_process用,
實現是使用一個全局變量last_pid保存最大的PID,當需要產生一個進程的時候,先遍歷進程鏈表
task,找出最大的PID,然後跳轉到repeat,進行+1操作,此時lastID就是最大的了。
int find_empty_process(void)
{
int i;
repeat:
if ((++last_pid)<0) 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;
return -EAGAIN;
}
接下來把參數都壓入堆棧,然後調用copy_process這個函數,具體的實現:
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;
//獲得空閒的頁表,一頁爲4k
p = (struct task_struct *) get_free_page();
if (!p)
return -EAGAIN;
//nr爲find_empty_process返回來的可用的任務號,把新的進程結構賦值給新的任務
task[nr] = p;
// NOTE!: the following statement now work with gcc 4.3.2 now, and you
// must compile _THIS_ memcpy without no -O of gcc.#ifndef GCC4_3
*p = *current; /* NOTE! this doesn't copy the supervisor stack */
//先置爲不可中斷休眠,避免不小心切換進來
p->state = TASK_UNINTERRUPTIBLE;
//新子進程的pid
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;
//設置寄存器的內容
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;
p->tss.ldt = _LDT(nr);
p->tss.trace_bitmap = 0x80000000;
if (last_task_used_math == current)
__asm__("clts ; fnsave %0"::"m" (p->tss.i387));
//調用copy_mem()函數根據父進程的代碼端和數據段的地址加上偏移量(nr * 0x4000000)重 //新設置子進程代碼段和數據段,並複製頁表
if (copy_mem(nr,p)) {
task[nr] = NULL;
free_page((long) p);
return -EAGAIN;
}
//假如文件是打開的,那該文件的系統文件打開表增一,父子進程能共享這些文件打開表,另外pwd等等這些引用都要增一
for (i=0; i<NR_OPEN;i++)
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++;
/*gdt的前四個分別是null,內核代碼段,內核數據段,系統調用段,加上FIRST_TSS_ENTRY 就是跳過前面四個段,接下來就是任務0的TSS段和LDT段,任務1的TSS和LDT,以此類推,所以加上(nr<<1)就是指向了第nr個任務在GDT中的內容,第一句話設置了該任務的tss在GDT中的描述符,第二句設置了ldt在GDT的描述符,之後使任務變爲可運行,然後退出*/
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 */
//返回進程ID
return last_pid;
}