轉自:http://blog.csdn.net/fool_duck/article/details/46917377linux
如下是基於 linux 0.11 內核的說明。sql
在init/main.c第138行,
在move_to_user_mode()以後,進程0經過fork()產生子進程,實際就是進程1(init進程)。數組
在main.c第23行:bash
static inline _syscall0(int,fork)
經過 _syscall0 調用 fork 。_syscall0 即不帶參數的系統調用:type name(void),_syscall0 的定義在unistd.h中第133行:數據結構
#define _syscall0(type,name) \ type name(void) \ { \ long __res; \ __asm__ volatile ("int $0x80" \ //調用0x80系統中斷 : "=a" (__res) \ : "0" (__NR_##name)); \ if (__res >= 0) \ return (type) __res; \ errno = -__res; \ return -1; \ }
在 kernel\sched.c 中的 sched_init 調用 system_call函數
void sched_init(void) { int i; struct desc_struct * p; if (sizeof(struct sigaction) != 16) panic("Struct sigaction MUST be 16 bytes"); set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss)); set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt)); p = gdt+2+FIRST_TSS_ENTRY; for(i=1;i<NR_TASKS;i++) { task[i] = NULL; p->a=p->b=0; p++; p->a=p->b=0; p++; } /* Clear NT, so that we won't have troubles with that later on */ __asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl"); ltr(0); lldt(0); outb_p(0x36,0x43); /* binary, mode 3, LSB/MSB, ch 0 */ outb_p(LATCH & 0xff , 0x40); /* LSB */ outb(LATCH >> 8 , 0x40); /* MSB */ set_intr_gate(0x20,&timer_interrupt); outb(inb_p(0x21)&~0x01,0x21); set_system_gate(0x80,&system_call); }
system_call 位於 kernel\system_call.s 中。在該文件第94行:ui
call _sys_call_table(,%eax,4)
調用地址爲:_sys_call_table + %eax * 4,此時 exa 的值爲2(根據__NR_fork的定義),因爲是32位機,指針佔4個字節。this
sys_call_table 的定義在 include\linux\sys.h 中第74行:spa
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 };
即調用的是 sys_fork 。.net
sys_fork 的定義是一段彙編代碼,位於 kernel\system_call.s 第208行:
_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 和 _copy_process 。
_find_empty_process 位於 kernel\fork.c 中第135行,其做用是找到一個空的進程號(對應於一個進程控制塊PCB),在linux 0.11版本中最多支持64個進程(全局數組task定義在sched.h中,數組大小爲64):
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; }
全局變量last_pid用來記錄上次使用的進程號,其定義在 kernel\fork.c 第22行:
long last_pid=0;
在find_empty_process中,不斷遞增last_pid,尋找第一個未被其它進程使用的進程號做爲新進程的進程號。若是遞增後的值超出正數表示範圍,則從新從1開始,並將其返回值存放在 %eax 中。若沒能找到可用進程號,則跳轉。若找到可用進程號則進行相關壓棧操做,而後調用_copy_process 開始複製進程內容。
_copy_process 的定義位於 kernel\fork.c 第63行:
/*
* Ok, this is the main fork-routine. It copies the system process * information (task[nr]) and sets up the necessary registers. It * also copies the data segment in it's entirety. */ 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; task[nr] = p; //將新任務結構指針放入任務數組中,其中nr 是由前面find_empty_process()返回的任務號 *p = *current; /* NOTE! this doesn't copy the supervisor stack */ p->state = TASK_UNINTERRUPTIBLE; // 將新進程的狀態先置爲不可中斷等待狀態 p->pid = last_pid; // fork 對父進程返回子進程ID 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; // fork 對子進程返回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]) 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)); set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt)); p->state = TASK_RUNNING; /* do this last, just in case */ return last_pid; }
進程控制塊中還保存有進程的任務狀態段數據結構tss,用於存儲處理器管理進程的全部信息。也就是說,在任務切換過程當中,首先將處理器中各寄存器的當前值被自動保存當前進程的tss中;而後,下一進程的tss被加載並從中提取出各個值送處處理器的寄存器中。因而可知,經過在tss中保存任務現場各寄存器狀態的完整映象,實現任務的切換。
struct tss_struct tss;
所以,一旦在task[]數組中找到空閒項和進程號,咱們就能夠爲該進程的進程控制塊結構申請一個頁面的內存。這個工做是在copy_process() 函數中完成的。
固然copy_process()函數的最主要的任務是爲子進程複製父進程信息,並設置子進程的任務狀態段,其中最關鍵的兩步是:
p->tss.eip = eip;
p->tss.eax = 0;