亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 操作系統 > 正文

關于fork( )函數父子進程返回值的問題

2024-06-28 13:20:25
字體:
來源:轉載
供稿:網友
關于fork( )函數父子進程返回值的問題

fork()是linux的系統調用函數sys_fork()的提供給用戶的接口函數,fork()函數會實現對中斷int 0x80的調用過程并把調用結果返回給用戶程序。

fork()的函數定義是在init/main.c中(這一點我感到奇怪,因為大多數系統調用的接口函數都會單獨封裝成一個.c文件,然后在里面進行嵌入匯編展開執行int 0x80中斷從而執行相應的系統調用,如/lib/close.c中:

1 #define __LIBRARY__2 #include <unistd.h>3 4 _syscall1(int,close,int,fd)

但fork()函數確實在mai.c中進行嵌入匯編展開定義的,呃,可能是我目前還沒有完全理解這一部分,但這就是我目前的認識)

以下是init/main.c中fork()函數的嵌入匯編定義:

1 static inline _syscall0(int,fork)

其中_syscall0()是include/linux/sys/unistd.h中的內嵌宏代碼,它以嵌入匯編的形式調用linux的系統調用中斷int 0x80。

 1 #define _syscall0(type,name) / 2 type name(void) / 3 { / 4 long __res; / 5 __asm__ volatile ("int $0x80" / 6     : "=a" (__res) / 7     : "0" (__NR_##name)); / 8 if (__res >= 0) / 9     return (type) __res; /10 errno = -__res; /11 return -1; /12 }

對其進行宏展開,即得到fork()函數的代碼:

 1 int fork(void)  2 {  3     long __res;  4     __asm__ volatile ("int $0x80"  5             : "=a" (__res)        //eax保存的是int 0x80中斷調用的返回值 6             : "0" (__NR_##fork));    //同時eax也是int 0x80中斷調用的系統調用功能號 7     if (__res >= 0)  8             return (type) __res;        //返回int 0x80的返回值作為fork()函數的返回值 9     errno = -__res; 10     return -1; 11 }

理解這個函數的關鍵,就在于理解系統調用中斷int 0x80。

在main.c進行初始化時,設置好了int 0x80的系統調用中斷門。

 1 void main(void) 2 { 3         ...... 4     sched_init();        //在sched_init()中設置了系統調用中斷int 0x80的中斷門 5         ...... 6     move_to_user_mode(); 7     if (!fork()) {        /* we count on this going ok */ 8         init(); 9     }10     for(;;) pause();11 }

sched_init()定義在kernel/sched.c中:

1 void sched_init(void)2 {3         ......4     set_system_gate(0x80,&system_call);    //在IDT中設置系統調用中斷int 0x80的描述符5 }

其中,set_system_gate(0x80,&system_call)的宏定義在文件include/asm/system.h中:

 1 #define _set_gate(gate_addr,type,dpl,addr) / 2 __asm__ ("movw %%dx,%%ax/n/t" / 3     "movw %0,%%dx/n/t" / 4     "movl %%eax,%1/n/t" / 5     "movl %%edx,%2" / 6     : / 7     : "i" ((short) (0x8000+(dpl<<13)+(type<<8))), / 8     "o" (*((char *) (gate_addr))), / 9     "o" (*(4+(char *) (gate_addr))), /10     "d" ((char *) (addr)),"a" (0x00080000))11 ......12 #define set_system_gate(n,addr) /13     _set_gate(&idt[n],15,3,addr)

其作用也就是填寫IDT中int 0x80的中斷描述符,中斷描述符的格式如下:

中斷門描述符

輸入參數:%0: i(立即數) = 0x8000(0b1000,0000,0000,0000)

          | (dpl<<13)(0b0110,0000,0000,0000)

          | (type<<8)(0b0000,1111,0000,0000)

          = 0b1110,1111,0000,0000

     ?。ㄟ@里我有個疑問,按照編號%0指的是輸出寄存器,雖然這里并沒有用到輸出寄存器,但%0是否依然指的是輸出寄存器?)

      o(內存變量) =%2: (高四位) *(4+&idt[0x80])

             %1: (低四位)*(&idt[0x80])

      %3: d(edx,32位)=&system_callcall

      %4: a(eax,32位)=0x0008,0000(0b0000,0000,0000,1000,0000,0000,0000,0000)

匯編語句的執行過程:

1 movw %%dx,%%ax    //ax=dx,即eax的低兩個字節等于edx的低兩個字節,也就是&system_call的低兩個字節2 movw %0,%%dx        //dx=i(0b1110,1111,0000,0000),即edx的低兩個字節等于i3 movl %%eax,%1        //*(&idt[0x80]) = 0000,0000,0000,1000  &system_call(低兩個字節)4 movl %%edx,%2        //*(4+&idt[0x80]) = &system_call(高兩個字節) 1110,1111,0000,0000

這樣,IDT表中的int 0x80的中斷門描述符就填寫好了,如下所示:

填充后的中斷門描述符

(這里我還存在一個疑問,就是int 0x80中斷門描述符的高四位中的第8位填充的是1,但表中要求是寫0)

int 0x80的中斷調用是一個有趣的過程:

首先應用程序通過庫函數fork()調用系統調用sys_fork(),由于應用程序運行在特權級3,是不能訪問內核代碼的中斷處理函數system_call()以及system_call()要進一步調用的具體系統調用函數sys_fork(),所以在int 0x80初始化在填寫IDT表中int 0x80的描述符時,將其DPL置為3,這樣應用程序得以進入內核,而在跳轉到中斷處理函數system_call()時,將對應的選擇符置為0000,0000,0000,1000,即cs=0b0000,0000,0000,1000,表示訪問特權級為0、使用全局描述符表GDT中的第2個段描述符項(內核代碼段),即訪問的基地址是內核代碼段,偏移地址是system_call()的代碼,使其又變為以最高特權級0訪問system_call()函數,這樣就完成了從應用程序到內核代碼段的轉移。

并且在執行int 0x80中斷時,會發生堆棧的切換,即從用戶棧切換到用戶的內核堆棧。具體過程是:

處理器從當前執行任務的TSS段中得到中斷處理過程使用的用戶內核堆棧的段選擇符和棧指針(例如tss.ss0、tss.esp0)。然后將應用程序的用戶棧的段選擇符和棧指針壓棧,接著將EFLAGS、CS、Eip的值也壓棧,而此刻EIP的值就是fork()函數中嵌入匯編int 0x80后的下一條指令的地址,即指令:

 5             : "=a" (__res)
 1 int fork(void)  2 {  3     long __res;  4     __asm__ volatile ("int $0x80"  5             : "=a" (__res)        //eax保存的是int 0x80中斷調用的返回值 6             : "0" (__NR_##fork));    //同時eax也是int 0x80中斷調用的系統調用功能號 7     if (__res >= 0)  8             return (type) __res;        //返回int 0x80的返回值作為fork()函數的返回值 9     errno = -__res; 10     return -1; 11 }這一點對于理解為什么fork()函數返回時子進程的返回值是0非常關鍵。接下來我們要關注下system_call的執行過程。system_call函數在kernel/system_call.s中:
 1 nr_system_calls = 72 2  3 bad_sys_call: 4     movl $-1,%eax 5     iret 6  7 reschedule: 8     pushl $ret_from_sys_call 9     jmp schedule10 11 system_call:12     cmpl $nr_system_calls-1,%eax    #檢查eax中的功能號是否有效(在給定的范圍內)13     ja bad_sys_call    #跳轉到bad_sys_call,即eax=-1,中斷返回14     push %ds15     push %es16     push %fs17     pushl %edx18     pushl %ecx        # 將edx,ecx,ebx壓棧,作為system_call的參數19     pushl %ebx20     movl $0x10,%edx        # ds,es用于內核數據段21     mov %dx,%ds22     mov %dx,%es23     movl $0x17,%edx        # fs用于用戶數據段24     mov %dx,%fs25     call sys_call_table(,%eax,4)    # 跳轉到對應的系統調用函數中,此處是sys_fork()26     pushl %eax    # 把系統調用的返回值入棧27     movl current,%eax    # 取當前任務數據結構地址->eax28     cmpl $0,state(%eax)        # 判斷當前任務的狀態29     jne reschedule    # 不在就緒狀態(state != 0)就去執行調度程序schedule()30     cmpl $0,counter(%eax)        # 判斷當前任務時間片是否已用完31     je reschedule    # 時間片已用完(counter = 0)也去執行調度程序schedule()32 ret_from_sys_call:    # 執行完調度程序schedule()返回或沒有執行調度程序直接到該處33     movl current,%eax        # task[0] cannot have signals34     cmpl task,%eax    # 判斷當前任務是否是初始任務task035     je 3f    # 如果是不必對其進行信號量方面的處理,直接退出中斷36     cmpw $0x0f,CS(%esp)        # 判斷調用程序是否是用戶任務37     jne 3f    # 如果不是,直接退出中斷38     cmpw $0x17,OLDSS(%esp)        # 判斷是否為用戶代碼段的選擇符39     jne 3f    # 如果不是,則說明是某個中斷服務程序跳轉到這里,直接退出中斷40     movl signal(%eax),%ebx    # 處理當前用戶任務中的信號41     movl blocked(%eax),%ecx42     notl %ecx43     andl %ebx,%ecx44     bsfl %ecx,%ecx45     je 3f46     btrl %ecx,%ebx47     movl %ebx,signal(%eax)48     incl %ecx49     pushl %ecx50     call do_signal51     popl %eax52 3:    popl %eax    # eax含有系統調用的返回值53     popl %ebx54     popl %ecx55     popl %edx56     pop %fs57     pop %es58     pop %ds59     iret    # 中斷返回

這里說明了系統調用int 0x80的中斷處理過程。每次執行完對應的系統調用,操作系統都會檢查本次調用進程的狀態。如果由于上面的系統調用操作或其他情況而使進程的狀態從執行態變成了其他狀態,或者由于進程的時間片已經用完,則調用進程調度函數schedule()。schedule()也是個有趣的函數,schedule()會從就緒隊列中選擇一個就緒進程,將此就緒進程與當前進程執行狀態切換,而跳轉到新的進程中去(即選中的就緒進程),只有當schedule()執行進程切換,再次切換回當前進程時,此次的中斷調用int 0x80才會繼續返回執行,進行信號處理,并中斷返回。對于schedule()函數的理解,也是理解為什么fork()函數父子進程返回值不同的關鍵點。

接下來看一下系統調用sys_fork(),它定義在kernel/system_call.s中:

 1 sys_fork: 2     call find_empty_PRocess    # 調用find_empty_process() 3     testl %eax,%eax    # 在eax中返回進程號pid。若返回負數則退出 4     js 1f 5     push %gs 6     pushl %esi 7     pushl %edi 8     pushl %ebp 9     pushl %eax10     call copy_process    # 調用c函數 copy_process()11     addl $20,%esp    # 丟棄這里所有壓棧內容12 1:    ret

其中,find_empty_process()和copy_process()在kernel/fork.c中定義:

 1 int copy_mem(int nr,struct task_struct * p) 2 { 3     unsigned long old_data_base,new_data_base,data_limit; 4     unsigned long old_code_base,new_code_base,code_limit; 5  6     code_limit=get_limit(0x0f); 7     data_limit=get_limit(0x17); 8     old_code_base = get_base(current->ldt[1]); 9     old_data_base = get_base(current->ldt[2]);10     if (old_data_base != old_code_base)11         panic("We don't support separate I&D");12     if (data_limit < code_limit)13         panic("Bad data_limit");14     new_data_base = new_code_base = nr * 0x4000000;15     p->start_code = new_code_base;16     set_base(p->ldt[1],new_code_base);17     set_base(p->ldt[2],new_data_base);18     if (copy_page_tables(old_data_base,new_data_base,data_limit)) {    //復制當前進程(父進程)的頁目錄表項和頁表項作為子進程的頁目錄表項和頁表項,則子進程共享父進程的內存頁面19         printk("free_page_tables: from copy_mem/n");20         free_page_tables(new_data_base,data_limit);21         return -ENOMEM;22     }23     return 0;24 }25 26 /*27  *  Ok, this is the main fork-routine. It copies the system process28  * information (task[nr]) and sets up the necessary registers. It29  * also copies the data segment in it's entirety.30  */31 int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,32         long ebx,long ecx,long edx,33         long fs,long es,long ds,34         long eip,long cs,long eflags,long esp,long ss)    //該函數的參數是進入系統調用中斷處理過程(system_call.s)開始,直到sys_fork()和調用copy_process()前時逐步壓入棧的各寄存器的值,所以新建子進程的狀態會保持為父進程即將進入中斷過程前的狀態35 {36     struct task_struct *p;37     int i;38     struct file *f;39 40     p = (struct task_struct *) get_free_page();41     if (!p)42         return -EAGAIN;43     task[nr] = p;44     *p = *current;    /* NOTE! this doesn't copy the supervisor stack */45     p->state = TASK_UNINTERRUPTIBLE;    //先將新進程的狀態置為不可中斷等待狀態,以防止內核調度其執行46     p->pid = last_pid;    //新進程號,由find_empty_process()得到47     p->father = current->pid;48     p->counter = p->priority;49     p->signal = 0;50     p->alarm = 0;51     p->leader = 0;        /* process leadership doesn't inherit */52     p->utime = p->stime = 0;53     p->cutime = p->cstime = 0;54     p->start_time = jiffies;55     p->tss.back_link = 0;56     p->tss.esp0 = PAGE_SIZE + (long) p;    //任務內核棧指針指向系統給任務結構p分配的1頁新內存的頂端57     p->tss.ss0 = 0x10;    //內核態棧的段選擇符(與內核數據段相同)58     p->tss.eip = eip;59     p->tss.eflags = eflags;60     p->tss.eax = 0;    //這是當fork()返回時新進程會返回0的原因所在61     p->tss.ecx = ecx;62     p->tss.edx = edx;63     p->tss.ebx = ebx;64     p->tss.esp = esp;65     p->tss.ebp = ebp;66     p->tss.esi = esi;67     p->tss.edi = edi;68     p->tss.es = es & 0xffff;69     p->tss.cs = cs & 0xffff;70     p->tss.ss = ss & 0xffff;71     p->tss.ds = ds & 0xffff;72     p->tss.fs = fs & 0xffff;73     p->tss.gs = gs & 0xffff;74     p->tss.ldt = _LDT(nr);75     p->tss.trace_bitmap = 0x80000000;76     if (last_task_used_math == current)77         __asm__("clts ; fnsave %0"::"m" (p->tss.i387));78     if (copy_mem(nr,p)) {79         task[nr] = NULL;80         free_page((long) p);81         return -EAGAIN;82     }83     for (i=0; i<NR_OPEN;i++)84         if ((f=p->filp[i]))85             f->f_count++;86     if (current->pwd)87         current->pwd->i_count++;88     if (current->root)89         current->root->i_count++;90     if (current->executable)91         current->executable->i_count++;92         //在GDT表中設置新任務TSS段和LDT段描述符項93     set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));94     set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));95     p->state = TASK_RUNNING;    /* do this last, just in case */96         //最后才將新任務置成就緒態,以防萬一97     return last_pid;    //最后返回新進程的pid98 }

雖然操作系統為新進程在GDT表中設置了它的TSS段和LDT段的描述符項,也為其在線性地址空間設置了它的頁目錄項和頁表項,但由于其頁目錄項和頁表項是復制父進程的,所以內核并不會立刻為新進程分配代碼和數據內存頁。新進程將與父進程共同使用父進程已有的代碼和數據內存頁面。只有當以后執行過程中如果其中有一個進程以寫方式訪問內存時被訪問的內存頁面才會在寫操作前被復制到新申請的內存頁面中。而此后父進程和子進程就各有擁有其獨立的頁面。

這里我們可以看到,對于父進程來說,當它使用接口函數fork()引發系統調用,到進入系統調用中斷int 0x80執行相應的系統調用中斷處理過程(system_call.s)以及調用對應的系統調用函數(sys_fork()),再到可能被schedule()函數調度讓出CPU使用權,到最后重新得到CPU使用權從int 0x80中斷返回,父進程的返回值就是新建子進程的pid。而子進程當被schedule()函數調度獲得CPU的使用權后,它會繼續執行int 0x80下面的那條指令,即:

5             : "=a" (__res)又由于已經將子進程TSS中的eax置為0,所以當子進程被切換入運行態時,將會把子進程TSS段的各寄存器的值作為CPU此時各寄存器的值,然后執行標號5的指令,將eax=0作為中斷調用的返回值返回到fork()函數結尾處,所以對于子進程來說,它的返回值是0。

好累,第一次寫博客,終于完成了,用了將近一天。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产精品免费一区豆花| 国产成人精品久久二区二区| www.久久草.com| 久久久噜噜噜久久久| 青青在线视频一区二区三区| 亚洲一区二区免费在线| 国产999精品| 91国内产香蕉| 日韩一区在线视频| 91av在线播放视频| 欧美日韩在线影院| 夜夜嗨av色综合久久久综合网| 成人午夜激情网| 第一福利永久视频精品| 亚洲综合在线播放| 成人免费网站在线观看| 国产精品久久久久久久久久新婚| 成人欧美一区二区三区黑人孕妇| 国产精品视频1区| 亚洲美女喷白浆| 久久久精品2019中文字幕神马| 日本成人精品在线| 国产日韩欧美日韩| 97视频在线观看成人| 亚洲精品国产综合区久久久久久久| 欧美中文在线观看国产| 日韩激情在线视频| 国产精品羞羞答答| 久久久国产精彩视频美女艺术照福利| 日本三级韩国三级久久| 国产亚洲精品91在线| 亚洲自拍另类欧美丝袜| 亚洲剧情一区二区| 影音先锋日韩有码| 欧美日韩免费网站| 国产精品入口日韩视频大尺度| 亚洲欧美日韩精品| 欧美性视频精品| 欧美激情xxxx| 国产色婷婷国产综合在线理论片a| 综合网中文字幕| 色爱精品视频一区| 北条麻妃一区二区在线观看| 欧美电影免费看| 亚洲第一网中文字幕| 国产精品视频免费在线| 久久久91精品| 2018国产精品视频| 中文字幕亚洲天堂| 欧美亚洲免费电影| 久久夜精品香蕉| 91精品视频免费观看| 日韩精品免费在线视频观看| 午夜精品久久久久久久99热| 久久久久国产精品一区| 欧美国产精品va在线观看| 国产精品扒开腿做爽爽爽男男| 亚洲缚视频在线观看| 国语自产精品视频在线看抢先版图片| 国产a级全部精品| 成人欧美一区二区三区黑人孕妇| 日韩成人在线网站| 亚洲第一综合天堂另类专| 亚洲一区二区三区视频| 欧美孕妇孕交黑巨大网站| 国产精品国语对白| 国产深夜精品福利| 欧美中文在线观看| 国产精品久久久久久中文字| 亚洲精品成人久久电影| 亚洲精品黄网在线观看| 亚洲精品久久久久久久久| 亚洲图片欧美日产| 亚洲直播在线一区| 欧美麻豆久久久久久中文| 国产成人精品电影| 色香阁99久久精品久久久| 国产成人午夜视频网址| 亚洲电影在线观看| 国产精品一区二区电影| 久久久久久国产精品美女| 亚洲三级av在线| 欧美性一区二区三区| 美日韩精品视频免费看| 在线播放亚洲激情| 91豆花精品一区| 91在线免费看网站| www.99久久热国产日韩欧美.com| 亚洲性无码av在线| 久久精品视频va| 亚洲电影免费观看高清完整版| 欧美激情中文字幕在线| 精品欧美一区二区三区| 亚洲第一区中文字幕| 久久久噜噜噜久久中文字免| 国自在线精品视频| 亚洲一级黄色av| 97热在线精品视频在线观看| 亚洲 日韩 国产第一| 国产精品爱啪在线线免费观看| 久久91精品国产91久久久| 日韩成人在线视频网站| 日本欧美爱爱爱| 亚洲免费视频一区二区| 日韩在线视频导航| 久久精品99国产精品酒店日本| 亚洲国产第一页| 国产成人福利夜色影视| 久久精品国产久精国产一老狼| 欧洲亚洲免费视频| 91精品视频专区| 欧美国产高跟鞋裸体秀xxxhd| 精品高清一区二区三区| 国产精品久久久久影院日本| 91九色在线视频| 日韩国产精品视频| 少妇激情综合网| 欧美裸体男粗大视频在线观看| 成人在线视频网站| 亚洲电影av在线| 夜夜狂射影院欧美极品| 91亚洲国产成人久久精品网站| 岛国视频午夜一区免费在线观看| 成人黄色av播放免费| 伊人成人开心激情综合网| 日韩免费电影在线观看| 精品国产91乱高清在线观看| 欧洲亚洲免费视频| 国产精品99久久久久久久久| 久久精品这里热有精品| 国产午夜精品一区理论片飘花| 亚洲色图综合久久| 在线观看日韩欧美| 国产精品久久久久久久av大片| 午夜精品视频在线| 国产精品久久久久9999| 国产一区二区三区在线观看网站| 亚洲精品美女久久久| 国产精品99久久久久久久久| 精品香蕉在线观看视频一| 欧美夜福利tv在线| 热99久久精品| 亚洲图片制服诱惑| 色综合五月天导航| 亚洲欧美制服中文字幕| 韩国视频理论视频久久| 久久中文字幕国产| 久久久www成人免费精品| 97不卡在线视频| 久久久久亚洲精品成人网小说| 91久久精品日日躁夜夜躁国产| 国产成人福利夜色影视| 日韩高清电影好看的电视剧电影| 欧美精品在线极品| 国产成人免费av电影| 在线观看不卡av| 欧美专区在线视频| 欧美激情一级欧美精品| 亚洲欧美另类在线观看| 欧美亚洲国产另类| 在线播放国产精品| 国产一区二区三区视频免费| 国产99久久久欧美黑人| 777777777亚洲妇女|