<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一文看懂 | fork 系統(tǒng)調(diào)用

          共 10803字,需瀏覽 22分鐘

           ·

          2021-10-22 01:54

          前言

          Unix標(biāo)準(zhǔn)的復(fù)制進程的系統(tǒng)調(diào)用時fork(即分叉),但是Linux,BSD等操作系統(tǒng)并不止實現(xiàn)這一個,確切的說linux實現(xiàn)了三個,fork,vfork,clone(確切說vfork創(chuàng)造出來的是輕量級進程,也叫線程,是共享資源的進程)

          系統(tǒng)調(diào)用描述
          forkfork創(chuàng)造的子進程是父進程的完整副本,復(fù)制了父親進程的資源,包括內(nèi)存的內(nèi)容task_struct內(nèi)容
          vforkvfork創(chuàng)建的子進程與父進程共享數(shù)據(jù)段,而且由vfork()創(chuàng)建的子進程將先于父進程運行
          cloneLinux上創(chuàng)建線程一般使用的是pthread庫 實際上linux也給我們提供了創(chuàng)建線程的系統(tǒng)調(diào)用,就是clone

          fork, vfork和clone的系統(tǒng)調(diào)用的入口地址分別是sys_fork, sys_vfork和sys_clone, 而他們的定義是依賴于體系結(jié)構(gòu)的, 因為在用戶空間和內(nèi)核空間之間傳遞參數(shù)的方法因體系結(jié)構(gòu)而異

          系統(tǒng)調(diào)用的參數(shù)傳遞

          系統(tǒng)調(diào)用的實現(xiàn)與C庫不同, 普通C函數(shù)通過將參數(shù)的值壓入到進程的棧中進行參數(shù)的傳遞。由于系統(tǒng)調(diào)用是通過中斷進程從用戶態(tài)到內(nèi)核態(tài)的一種特殊的函數(shù)調(diào)用,沒有用戶態(tài)或者內(nèi)核態(tài)的堆??梢员挥脕碓谡{(diào)用函數(shù)和被調(diào)函數(shù)之間進行參數(shù)傳遞。系統(tǒng)調(diào)用通過CPU的寄存器來進行參數(shù)傳遞。在進行系統(tǒng)調(diào)用之前,系統(tǒng)調(diào)用的參數(shù)被寫入CPU的寄存器,而在實際調(diào)用系統(tǒng)服務(wù)例程之前,內(nèi)核將CPU寄存器的內(nèi)容拷貝到內(nèi)核堆棧中,實現(xiàn)參數(shù)的傳遞。

          因此不同的體系結(jié)構(gòu)可能采用不同的方式或者不同的寄存器來傳遞參數(shù),而上面函數(shù)的任務(wù)就是從處理器的寄存器中提取用戶空間提供的信息, 并調(diào)用體系結(jié)構(gòu)無關(guān)的?_do_fork(或者早期的do_fork)函數(shù), 負(fù)責(zé)進程的復(fù)制

          即不同的體系結(jié)構(gòu)可能需要采用不同的方式或者寄存器來存儲函數(shù)調(diào)用的參數(shù), 因此linux在設(shè)計系統(tǒng)調(diào)用的時候, 將其劃分成體系結(jié)構(gòu)相關(guān)的層次和體系結(jié)構(gòu)無關(guān)的層次, 前者復(fù)雜提取出依賴與體系結(jié)構(gòu)的特定的參數(shù),后者則依據(jù)參數(shù)的設(shè)置執(zhí)行特定的真正操作。

          fork, vfork, clone系統(tǒng)調(diào)用的實現(xiàn)

          關(guān)于do_fork和_do_frok

          linux2.5.32以后, 添加了TLS(Thread Local Storage)機制, clone的標(biāo)識CLONE_SETTLS接受一個參數(shù)來設(shè)置線程的本地存儲區(qū)。sys_clone也因此增加了一個int參數(shù)來傳入相應(yīng)的點tls_val。sys_clone通過do_fork來調(diào)用copy_process完成進程的復(fù)制,它調(diào)用特定的copy_thread和copy_thread把相應(yīng)的系統(tǒng)調(diào)用參數(shù)從pt_regs寄存器列表中提取出來,但是會導(dǎo)致意外的情況。

          only one code path into copy_thread can pass the CLONE_SETTLS flag, and that code path comes from sys_clone with its architecture-specific argument-passing order.

          前面我們說了, 在實現(xiàn)函數(shù)調(diào)用的時候,我iosys_clone等將特定體系結(jié)構(gòu)的參數(shù)從寄存器中提取出來, 然后到達do_fork這步的時候已經(jīng)應(yīng)該是體系結(jié)構(gòu)無關(guān)了, 但是我們sys_clone需要設(shè)置的CLONE_SETTLS的tls仍然是個依賴與體系結(jié)構(gòu)的參數(shù), 這里就會出現(xiàn)問題。

          因此linux-4.2之后選擇引入一個新的CONFIG_HAVE_COPY_THREAD_TLS,和一個新的COPY_THREAD_TLS接受TLS參數(shù)為 額外的長整型(系統(tǒng)調(diào)用參數(shù)大?。┑臓幷?。改變sys_clone的TLS參數(shù)unsigned long,并傳遞到copy_thread_tls。

          /*?http://lxr.free-electrons.com/source/include/linux/sched.h?v=4.5#L2646??*/
          extern?long?_do_fork(unsigned?long,?unsigned?long,?unsigned?long,?int?__user?*,?int?__user?*,?unsigned?long);
          extern?long?do_fork(unsigned?long,?unsigned?long,?unsigned?long,?int?__user?*,?int?__user?*);


          /*?linux2.5.32以后,?添加了TLS(Thread?Local?Storage)機制,?
          ?在最新的linux-4.2中添加了對CLONE_SETTLS?的支持?
          ????底層的_do_fork實現(xiàn)了對其的支持,?
          ????dansh*/

          #ifndef?CONFIG_HAVE_COPY_THREAD_TLS
          /*?For?compatibility?with?architectures?that?call?do_fork?directly?rather?than
          ?*?using?the?syscall?entry?points?below.?*/

          long?do_fork(unsigned?long?clone_flags,
          ??????????????unsigned?long?stack_start,
          ??????????????unsigned?long?stack_size,
          ??????????????int?__user?*parent_tidptr,
          ??????????????int?__user?*child_tidptr)
          {
          ????????return?_do_fork(clone_flags,?stack_start,?stack_size,
          ????????????????????????parent_tidptr,?child_tidptr,?0);
          }
          #endif

          我們會發(fā)現(xiàn),新版本的系統(tǒng)中clone的TLS設(shè)置標(biāo)識會通過TLS參數(shù)傳遞, 因此_do_fork替代了老版本的do_fork。

          老版本的do_fork只有在如下情況才會定義

          • 只有當(dāng)系統(tǒng)不支持通過TLS參數(shù)通過參數(shù)傳遞而是使用pt_regs寄存器列表傳遞時

          • 未定義CONFIG_HAVE_COPY_THREAD_TLS宏

          參數(shù)描述
          clone_flags與clone()參數(shù)flags相同, 用來控制進程復(fù)制過的一些屬性信息, 描述你需要從父進程繼承那些資源。該標(biāo)志位的4個字節(jié)分為兩部分。最低的一個字節(jié)為子進程結(jié)束時發(fā)送給父進程的信號代碼,通常為SIGCHLD;剩余的三個字節(jié)則是各種clone標(biāo)志的組合(本文所涉及的標(biāo)志含義詳見下表),也就是若干個標(biāo)志之間的或運算。通過clone標(biāo)志可以有選擇的對父進程的資源進行復(fù)制;
          stack_start與clone()參數(shù)stack_start相同, 子進程用戶態(tài)堆棧的地址
          regs是一個指向了寄存器集合的指針, 其中以原始形式, 保存了調(diào)用的參數(shù), 該參數(shù)使用的數(shù)據(jù)類型是特定體系結(jié)構(gòu)的struct pt_regs,其中按照系統(tǒng)調(diào)用執(zhí)行時寄存器在內(nèi)核棧上的存儲順序, 保存了所有的寄存器, 即指向內(nèi)核態(tài)堆棧通用寄存器值的指針,通用寄存器的值是在從用戶態(tài)切換到內(nèi)核態(tài)時被保存到內(nèi)核態(tài)堆棧中的(指向pt_regs結(jié)構(gòu)體的指針。當(dāng)系統(tǒng)發(fā)生系統(tǒng)調(diào)用,即用戶進程從用戶態(tài)切換到內(nèi)核態(tài)時,該結(jié)構(gòu)體保存通用寄存器中的值,并被存放于內(nèi)核態(tài)的堆棧中)
          stack_size用戶狀態(tài)下棧的大小, 該參數(shù)通常是不必要的, 總被設(shè)置為0
          parent_tidptr與clone的ptid參數(shù)相同, 父進程在用戶態(tài)下pid的地址,該參數(shù)在CLONE_PARENT_SETTID標(biāo)志被設(shè)定時有意義
          child_tidptr與clone的ctid參數(shù)相同, 子進程在用戶太下pid的地址,該參數(shù)在CLONE_CHILD_SETTID標(biāo)志被設(shè)定時有意義

          其中clone_flags如下表所示

          CLONE_FLAGS

          sys_fork的實現(xiàn)

          不同體系結(jié)構(gòu)下的fork實現(xiàn)sys_fork主要是通過標(biāo)志集合區(qū)分, 在大多數(shù)體系結(jié)構(gòu)上, 典型的fork實現(xiàn)方式與如下

          早期實現(xiàn)

          架構(gòu)實現(xiàn)
          armarch/arm/kernel/sys_arm.c, line 239
          i386arch/i386/kernel/process.c, line 710
          x86_64arch/x86_64/kernel/process.c, line 706
          asmlinkage?long?sys_fork(struct?pt_regs?regs)
          {
          ????return?do_fork(SIGCHLD,?regs.rsp,?®s,?0);
          }

          新版本

          http://lxr.free-electrons.com/source/kernel/fork.c?v=4.5#L1785

          #ifdef?__ARCH_WANT_SYS_FORK
          SYSCALL_DEFINE0(fork)
          {
          #ifdef?CONFIG_MMU
          ????return?_do_fork(SIGCHLD,?0,?0,?NULL,?NULL,?0);
          #else
          ????/*?can?not?support?in?nommu?mode?*/
          ????return?-EINVAL;
          #endif
          }
          #endif

          我們可以看到唯一使用的標(biāo)志是SIGCHLD。這意味著在子進程終止后將發(fā)送信號SIGCHLD信號通知父進程,

          由于寫時復(fù)制(COW)技術(shù), 最初父子進程的棧地址相同, 但是如果操作棧地址閉并寫入數(shù)據(jù), 則COW機制會為每個進程分別創(chuàng)建一個新的棧副本

          如果do_fork成功, 則新建進程的pid作為系統(tǒng)調(diào)用的結(jié)果返回, 否則返回錯誤碼

          sys_vfork的實現(xiàn)

          早期實現(xiàn)

          架構(gòu)實現(xiàn)
          armarch/arm/kernel/sys_arm.c, line 254
          i386arch/i386/kernel/process.c, line 737
          x86_64arch/x86_64/kernel/process.c, line 728
          asmlinkage?long?sys_vfork(struct?pt_regs?regs)
          {
          ????return?do_fork(CLONE_VFORK?|?CLONE_VM?|?SIGCHLD,?regs.rsp,?®s,?0);
          }

          新版本

          http://lxr.free-electrons.com/source/kernel/fork.c?v=4.5#L1797

          #ifdef?__ARCH_WANT_SYS_VFORK
          SYSCALL_DEFINE0(vfork)
          {
          ????return?_do_fork(CLONE_VFORK?|?CLONE_VM?|?SIGCHLD,?0,
          ????????????????????0,?NULL,?NULL,?0);
          }
          #endif

          可以看到sys_vfork的實現(xiàn)與sys_fork只是略微不同, 前者使用了額外的標(biāo)志CLONE_VFORK | CLONE_VM

          sys_clone的實現(xiàn)

          早期實現(xiàn)

          架構(gòu)實現(xiàn)
          armarch/arm/kernel/sys_arm.c, line 247
          i386arch/i386/kernel/process.c, line 715
          x86_64arch/x86_64/kernel/process.c, line 711

          sys_clone的實現(xiàn)方式與上述系統(tǒng)調(diào)用類似, 但實際差別在于do_fork如下調(diào)用

          casmlinkage?int?sys_clone(struct?pt_regs?regs)
          {
          ????/*?注釋中是i385下增加的代碼,?其他體系結(jié)構(gòu)無此定義
          ????unsigned?long?clone_flags;
          ????unsigned?long?newsp;

          ????clone_flags?=?regs.ebx;
          ????newsp?=?regs.ecx;*/

          ????if?(!newsp)
          ????????newsp?=?regs.esp;
          ????return?do_fork(clone_flags,?newsp,?®s,?0);
          }

          新版本

          http://lxr.free-electrons.com/source/kernel/fork.c?v=4.5#L1805

          #ifdef?__ARCH_WANT_SYS_CLONE
          #ifdef?CONFIG_CLONE_BACKWARDS
          SYSCALL_DEFINE5(clone,?unsigned?long,?clone_flags,?unsigned?long,?newsp,
          ?????????????????int?__user?*,?parent_tidptr,
          ?????????????????unsigned?long,?tls,
          ?????????????????int?__user?*,?child_tidptr)
          #elif?defined(CONFIG_CLONE_BACKWARDS2)
          SYSCALL_DEFINE5(clone,?unsigned?long,?newsp,?unsigned?long,?clone_flags,
          ?????????????????int?__user?*,?parent_tidptr,
          ?????????????????int?__user?*,?child_tidptr,
          ?????????????????unsigned?long,?tls)
          #elif?defined(CONFIG_CLONE_BACKWARDS3)
          SYSCALL_DEFINE6(clone,?unsigned?long,?clone_flags,?unsigned?long,?newsp,
          ????????????????int,?stack_size,
          ????????????????int?__user?*,?parent_tidptr,
          ????????????????int?__user?*,?child_tidptr,
          ????????????????unsigned?long,?tls)
          #else
          SYSCALL_DEFINE5(clone,?unsigned?long,?clone_flags,?unsigned?long,?newsp,
          ?????????????????int?__user?*,?parent_tidptr,
          ?????????????????int?__user?*,?child_tidptr,
          ?????????????????unsigned?long,?tls)
          #endif
          {
          ????????return?_do_fork(clone_flags,?newsp,?0,?parent_tidptr,?child_tidptr,?tls);
          }
          #endif

          我們可以看到sys_clone的標(biāo)識不再是硬編碼的, 而是通過各個寄存器參數(shù)傳遞到系統(tǒng)調(diào)用, 因而我們需要提取這些參數(shù)。

          另外,clone也不再復(fù)制進程的棧, 而是可以指定新的棧地址, 在生成線程時, 可能需要這樣做, 線程可能與父進程共享地址空間, 但是線程自身的??赡茉诹硗庖粋€地址空間

          另外還指令了用戶空間的兩個指針(parent_tidptr和child_tidptr), 用于與線程庫通信

          創(chuàng)建子進程的流程

          _do_fork的流程

          _do_fork和do_fork在進程的復(fù)制的時候并沒有太大的區(qū)別, 他們就只是在進程tls復(fù)制的過程中實現(xiàn)有細微差別

          所有進程復(fù)制(創(chuàng)建)的fork機制最終都調(diào)用了kernel/fork.c中的_do_fork(一個體系結(jié)構(gòu)無關(guān)的函數(shù)),

          其定義在 http://lxr.free-electrons.com/source/kernel/fork.c?v=4.2#L1679

          _do_fork以調(diào)用copy_process開始, 后者執(zhí)行生成新的進程的實際工作, 并根據(jù)指定的標(biāo)志復(fù)制父進程的數(shù)據(jù)。在子進程生成后, 內(nèi)核必須執(zhí)行下列收尾操作:

          1. 調(diào)用 copy_process 為子進程復(fù)制出一份進程信息

          2. 如果是 vfork(設(shè)置了CLONE_VFORK和ptrace標(biāo)志)初始化完成處理信息

          3. 調(diào)用 wake_up_new_task 將子進程加入調(diào)度器,為之分配 CPU

          4. 如果是 vfork,父進程等待子進程完成 exec 替換自己的地址空間

          我們從<深入linux'內(nèi)核架構(gòu)>中找到了早期的流程圖,基本一致可以作為參考

          do_fork
          long?_do_fork(unsigned?long?clone_flags,
          ??????unsigned?long?stack_start,
          ??????unsigned?long?stack_size,
          ??????int?__user?*parent_tidptr,
          ??????int?__user?*child_tidptr,
          ??????unsigned?long?tls)
          {
          ????struct?task_struct?*p;
          ????int?trace?=?0;
          ????long?nr;
          ??
          ????/*
          ?????*?Determine?whether?and?which?event?to?report?to?ptracer.??When
          ?????*?called?from?kernel_thread?or?CLONE_UNTRACED?is?explicitly
          ?????*?requested,?no?event?is?reported;?otherwise,?report?if?the?event
          ?????*?for?the?type?of?forking?is?enabled.
          ?????*/

          ????if?(!(clone_flags?&?CLONE_UNTRACED))?{
          ????if?(clone_flags?&?CLONE_VFORK)
          ????????trace?=?PTRACE_EVENT_VFORK;
          ????else?if?((clone_flags?&?CSIGNAL)?!=?SIGCHLD)
          ????????trace?=?PTRACE_EVENT_CLONE;
          ????else
          ????????trace?=?PTRACE_EVENT_FORK;
          ??
          ????if?(likely(!ptrace_event_enabled(current,?trace)))
          ????????trace?=?0;
          ????}
          ???/*??復(fù)制進程描述符,copy_process()的返回值是一個?task_struct?指針??*/
          ????p?=?copy_process(clone_flags,?stack_start,?stack_size,
          ?????????child_tidptr,?NULL,?trace,?tls);
          ????/*
          ?????*?Do?this?prior?waking?up?the?new?thread?-?the?thread?pointer
          ?????*?might?get?invalid?after?that?point,?if?the?thread?exits?quickly.
          ?????*/

          ????if?(!IS_ERR(p))?{
          ????struct?completion?vfork;
          ????struct?pid?*pid;
          ??
          ????trace_sched_process_fork(current,?p);
          ???/*??得到新創(chuàng)建的進程的pid信息??*/
          ????pid?=?get_task_pid(p,?PIDTYPE_PID);
          ????nr?=?pid_vnr(pid);
          ??
          ????if?(clone_flags?&?CLONE_PARENT_SETTID)
          ????????put_user(nr,?parent_tidptr);
          ???
          ????/*??如果調(diào)用的?vfork()方法,初始化?vfork?完成處理信息?*/
          ????if?(clone_flags?&?CLONE_VFORK)?{
          ????????p->vfork_done?=?&vfork;
          ????????init_completion(&vfork);
          ????????get_task_struct(p);
          ????}
          ?/*??將子進程加入到調(diào)度器中,為其分配?CPU,準(zhǔn)備執(zhí)行??*/
          ????wake_up_new_task(p);
          ??
          ????/*?forking?complete?and?child?started?to?run,?tell?ptracer?*/
          ????if?(unlikely(trace))
          ????????ptrace_event_pid(trace,?pid);
          ???
          ????/*??如果是?vfork,將父進程加入至等待隊列,等待子進程完成??*/
          ????if?(clone_flags?&?CLONE_VFORK)?{
          ????????if?(!wait_for_vfork_done(p,?&vfork))
          ????????ptrace_event_pid(PTRACE_EVENT_VFORK_DONE,?pid);
          ????}
          ??
          ????put_pid(pid);
          ????}?else?{
          ????nr?=?PTR_ERR(p);
          ????}
          ????return?nr;
          }

          copy_process流程

          http://lxr.free-electrons.com/source/kernel/fork.c?v=4.5#L1237

          1. 調(diào)用 dup_task_struct 復(fù)制當(dāng)前的 task_struct

          2. 檢查進程數(shù)是否超過限制

          3. 初始化自旋鎖、掛起信號、CPU 定時器等

          4. 調(diào)用 sched_fork 初始化進程數(shù)據(jù)結(jié)構(gòu),并把進程狀態(tài)設(shè)置為 TASK_RUNNING

          5. 復(fù)制所有進程信息,包括文件系統(tǒng)、信號處理函數(shù)、信號、內(nèi)存管理等

          6. 調(diào)用 copy_thread_tls 初始化子進程內(nèi)核棧

          7. 為新進程分配并設(shè)置新的 pid

          我們從<深入linux'內(nèi)核架構(gòu)>中找到了早期的流程圖,基本一致可以作為參考

          do_fork
          /*
          ?*?This?creates?a?new?process?as?a?copy?of?the?old?one,
          ?*?but?does?not?actually?start?it?yet.
          ?*
          ?*?It?copies?the?registers,?and?all?the?appropriate
          ?*?parts?of?the?process?environment?(as?per?the?clone
          ?*?flags).?The?actual?kick-off?is?left?to?the?caller.
          ?*/

          static?struct?task_struct?*copy_process(unsigned?long?clone_flags,
          ????????????????????unsigned?long?stack_start,
          ????????????????????unsigned?long?stack_size,
          ????????????????????int?__user?*child_tidptr,
          ????????????????????struct?pid?*pid,
          ????????????????????int?trace,
          ????????????????????unsigned?long?tls)
          {
          ????int?retval;
          ????struct?task_struct?*p;

          ????retval?=?security_task_create(clone_flags);
          ????if?(retval)
          ????????goto?fork_out;
          ?//??復(fù)制當(dāng)前的?task_struct
          ????retval?=?-ENOMEM;
          ????p?=?dup_task_struct(current);
          ????if?(!p)
          ????????goto?fork_out;

          ????ftrace_graph_init_task(p);

          ????//初始化互斥變量
          ????rt_mutex_init_task(p);

          #ifdef?CONFIG_PROVE_LOCKING
          ????DEBUG_LOCKS_WARN_ON(!p->hardirqs_enabled);
          ????DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled);
          #endif

          ?//檢查進程數(shù)是否超過限制,由操作系統(tǒng)定義
          ????retval?=?-EAGAIN;
          ????if?(atomic_read(&p->real_cred->user->processes)?>=
          ????????????task_rlimit(p,?RLIMIT_NPROC))?{
          ????????if?(p->real_cred->user?!=?INIT_USER?&&
          ????????????!capable(CAP_SYS_RESOURCE)?&&?!capable(CAP_SYS_ADMIN))
          ????????????goto?bad_fork_free;
          ????}
          ????current->flags?&=?~PF_NPROC_EXCEEDED;

          ????retval?=?copy_creds(p,?clone_flags);
          ????if?(retval?0)
          ????????goto?bad_fork_free;

          ????/*
          ?????*?If?multiple?threads?are?within?copy_process(),?then?this?check
          ?????*?triggers?too?late.?This?doesn't?hurt,?the?check?is?only?there
          ?????*?to?stop?root?fork?bombs.
          ?????*/

          ?//檢查進程數(shù)是否超過?max_threads?由內(nèi)存大小決定
          ????retval?=?-EAGAIN;
          ????if?(nr_threads?>=?max_threads)
          ????????goto?bad_fork_cleanup_count;

          ????delayacct_tsk_init(p);??/*?Must?remain?after?dup_task_struct()?*/
          ????p->flags?&=?~(PF_SUPERPRIV?|?PF_WQ_WORKER);
          ????p->flags?|=?PF_FORKNOEXEC;
          ????INIT_LIST_HEAD(&p->children);
          ????INIT_LIST_HEAD(&p->sibling);
          ????rcu_copy_process(p);
          ????p->vfork_done?=?NULL;

          ????//??初始化自旋鎖
          ????spin_lock_init(&p->alloc_lock);
          ?//??初始化掛起信號
          ????init_sigpending(&p->pending);

          ????//??初始化?CPU?定時器
          ????posix_cpu_timers_init(p);
          ?//??......

          ????/*?Perform?scheduler?related?setup.?Assign?this?task?to?a?CPU.?
          ?????初始化進程數(shù)據(jù)結(jié)構(gòu),并把進程狀態(tài)設(shè)置為?TASK_RUNNING
          ????*/

          ????retval?=?sched_fork(clone_flags,?p);
          ????if?(retval)
          ????????goto?bad_fork_cleanup_policy;
          ?retval?=?perf_event_init_task(p);

          ????/*?復(fù)制所有進程信息,包括文件系統(tǒng)、信號處理函數(shù)、信號、內(nèi)存管理等???*/
          ????if?(retval)
          ????????goto?bad_fork_cleanup_policy;
          ????retval?=?audit_alloc(p);
          ????if?(retval)
          ????????goto?bad_fork_cleanup_perf;
          ????/*?copy?all?the?process?information?*/
          ????shm_init_task(p);
          ????retval?=?copy_semundo(clone_flags,?p);
          ????if?(retval)
          ????????goto?bad_fork_cleanup_audit;
          ????retval?=?copy_files(clone_flags,?p);
          ????if?(retval)
          ????????goto?bad_fork_cleanup_semundo;
          ????retval?=?copy_fs(clone_flags,?p);
          ????if?(retval)
          ????????goto?bad_fork_cleanup_files;
          ????retval?=?copy_sighand(clone_flags,?p);
          ????if?(retval)
          ????????goto?bad_fork_cleanup_fs;
          ????retval?=?copy_signal(clone_flags,?p);
          ????if?(retval)
          ????????goto?bad_fork_cleanup_sighand;
          ????retval?=?copy_mm(clone_flags,?p);
          ????if?(retval)
          ????????goto?bad_fork_cleanup_signal;
          ????retval?=?copy_namespaces(clone_flags,?p);
          ????if?(retval)
          ????????goto?bad_fork_cleanup_mm;
          ????retval?=?copy_io(clone_flags,?p);
          ????if?(retval)
          ????????goto?bad_fork_cleanup_namespaces;
          ????/*????初始化子進程內(nèi)核棧
          ?????linux-4.2新增處理TLS
          ????????之前版本是?retval?=?copy_thread(clone_flags,?stack_start,?stack_size,?p);
          ????????*/

          ????retval?=?copy_thread_tls(clone_flags,?stack_start,?stack_size,?p,?tls);
          ????if?(retval)
          ????????goto?bad_fork_cleanup_io;

          ?/*??為新進程分配新的pid??*/
          ????if?(pid?!=?&init_struct_pid)?{
          ????????pid?=?alloc_pid(p->nsproxy->pid_ns_for_children);
          ????????if?(IS_ERR(pid))?{
          ????????????retval?=?PTR_ERR(pid);
          ????????????goto?bad_fork_cleanup_io;
          ????????}
          ????}

          ?/*??設(shè)置子進程的pid??*/
          ????/*?ok,?now?we?should?be?set?up..?*/
          ????p->pid?=?pid_nr(pid);
          ????if?(clone_flags?&?CLONE_THREAD)?{
          ????????p->exit_signal?=?-1;
          ????????p->group_leader?=?current->group_leader;
          ????????p->tgid?=?current->tgid;
          ????}?else?{
          ????????if?(clone_flags?&?CLONE_PARENT)
          ????????????p->exit_signal?=?current->group_leader->exit_signal;
          ????????else
          ????????????p->exit_signal?=?(clone_flags?&?CSIGNAL);
          ????????p->group_leader?=?p;
          ????????p->tgid?=?p->pid;
          ????}

          ????p->nr_dirtied?=?0;
          ????p->nr_dirtied_pause?=?128?>>?(PAGE_SHIFT?-?10);
          ????p->dirty_paused_when?=?0;

          ????p->pdeath_signal?=?0;
          ????INIT_LIST_HEAD(&p->thread_group);
          ????p->task_works?=?NULL;

          ????/*
          ?????*?Make?it?visible?to?the?rest?of?the?system,?but?dont?wake?it?up?yet.
          ?????*?Need?tasklist?lock?for?parent?etc?handling!
          ?????*/

          ????write_lock_irq(&tasklist_lock);

          ?/*??調(diào)用fork的進程為其父進程??*/
          ????/*?CLONE_PARENT?re-uses?the?old?parent?*/
          ????if?(clone_flags?&?(CLONE_PARENT|CLONE_THREAD))?{
          ????????p->real_parent?=?current->real_parent;
          ????????p->parent_exec_id?=?current->parent_exec_id;
          ????}?else?{
          ????????p->real_parent?=?current;
          ????????p->parent_exec_id?=?current->self_exec_id;
          ????}

          ????spin_lock(¤t->sighand->siglock);

          ????//?......

          ????return?p;
          }

          dup_task_struct 流程

          http://lxr.free-electrons.com/source/kernel/fork.c?v=4.5#L334

          static?struct?task_struct?*dup_task_struct(struct?task_struct?*orig)
          {
          ?struct?task_struct?*tsk;
          ?struct?thread_info?*ti;
          ?int?node?=?tsk_fork_get_node(orig);
          ?int?err;

          ?//分配一個?task_struct?節(jié)點
          ?tsk?=?alloc_task_struct_node(node);
          ?if?(!tsk)
          ??return?NULL;

          ?//分配一個?thread_info?節(jié)點,包含進程的內(nèi)核棧,ti?為棧底
          ?ti?=?alloc_thread_info_node(tsk,?node);
          ?if?(!ti)
          ??goto?free_tsk;

          ?//將棧底的值賦給新節(jié)點的棧
          ?tsk->stack?=?ti;

          ?//……

          ?return?tsk;

          }
          1. 調(diào)用alloc_task_struct_node分配一個 task_struct 節(jié)點

          2. 調(diào)用alloc_thread_info_node分配一個 thread_info 節(jié)點,其實是分配了一個thread_union聯(lián)合體,將棧底返回給 ti

          union?thread_union?{
          ???struct?thread_info?thread_info;
          ??unsigned?long?stack[THREAD_SIZE/sizeof(long)];
          };
          • 最后將棧底的值 ti 賦值給新節(jié)點的棧

          • 最終執(zhí)行完dup_task_struct之后,子進程除了tsk->stack指針不同之外,全部都一樣!

          sched_fork 流程

          int?sched_fork(unsigned?long?clone_flags,?struct?task_struct?*p)
          {
          ?unsigned?long?flags;
          ?int?cpu?=?get_cpu();

          ?__sched_fork(clone_flags,?p);

          ?//??將子進程狀態(tài)設(shè)置為?TASK_RUNNING
          ?p->state?=?TASK_RUNNING;

          ?//??……

          ?//??為子進程分配?CPU
          ?set_task_cpu(p,?cpu);

          ?put_cpu();
          ?return?0;
          }

          我們可以看到sched_fork大致完成了兩項重要工作,

          • 一是將子進程狀態(tài)設(shè)置為 TASK_RUNNING,

          • 二是為其分配 CPU

          copy_thread和copy_thread_tls流程

          我們可以看到linux-4.2之后增加了copy_thread_tls函數(shù)和CONFIG_HAVE_COPY_THREAD_TLS宏

          但是如果未定義CONFIG_HAVE_COPY_THREAD_TLS宏默認(rèn)則使用copy_thread同時將定義copy_thread_tls為copy_thread

          #ifdef?CONFIG_HAVE_COPY_THREAD_TLS
          extern?int?copy_thread_tls(unsigned?long,?unsigned?long,?unsigned?long,
          ????????????struct?task_struct?*,?unsigned?long)
          ;
          #else
          extern?int?copy_thread(unsigned?long,?unsigned?long,?unsigned?long,
          ????????????struct?task_struct?*)
          ;

          /*?Architectures?that?haven't?opted?into?copy_thread_tls?get?the?tls?argument
          ?*?via?pt_regs,?so?ignore?the?tls?argument?passed?via?C.?*/

          static?inline?int?copy_thread_tls(
          ????????unsigned?long?clone_flags,?unsigned?long?sp,?unsigned?long?arg,
          ????????struct?task_struct?*p,?unsigned?long?tls)
          {
          ????return?copy_thread(clone_flags,?sp,?arg,?p);
          }
          #endif
          內(nèi)核實現(xiàn)
          4.5arch/x86/kernel/process_32.c, line 132
          4.5arch/x86/kernel/process_64.c, line 155

          下面我們來看32位架構(gòu)的copy_thread_tls函數(shù),他與原來的copy_thread變動并不大, 只是多了后面TLS的設(shè)置信息

          int?copy_thread_tls(unsigned?long?clone_flags,?unsigned?long?sp,
          ????unsigned?long?arg,?struct?task_struct?*p,?unsigned?long?tls)
          {
          ????struct?pt_regs?*childregs?=?task_pt_regs(p);
          ????struct?task_struct?*tsk;
          ????int?err;
          ?/*??獲取寄存器的信息??*/
          ????p->thread.sp?=?(unsigned?long)?childregs;
          ????p->thread.sp0?=?(unsigned?long)?(childregs+1);
          ????memset(p->thread.ptrace_bps,?0,?sizeof(p->thread.ptrace_bps));

          ????if?(unlikely(p->flags?&?PF_KTHREAD))?{
          ????????/*?kernel?thread
          ?????????內(nèi)核線程的設(shè)置??*/

          ????????memset(childregs,?0,?sizeof(struct?pt_regs));
          ????????p->thread.ip?=?(unsigned?long)?ret_from_kernel_thread;
          ????????task_user_gs(p)?=?__KERNEL_STACK_CANARY;
          ????????childregs->ds?=?__USER_DS;
          ????????childregs->es?=?__USER_DS;
          ????????childregs->fs?=?__KERNEL_PERCPU;
          ????????childregs->bx?=?sp;?????/*?function?*/
          ????????childregs->bp?=?arg;
          ????????childregs->orig_ax?=?-1;
          ????????childregs->cs?=?__KERNEL_CS?|?get_kernel_rpl();
          ????????childregs->flags?=?X86_EFLAGS_IF?|?X86_EFLAGS_FIXED;
          ????????p->thread.io_bitmap_ptr?=?NULL;
          ????????return?0;
          ????}
          ????/*??將當(dāng)前寄存器信息復(fù)制給子進程??*/
          ????*childregs?=?*current_pt_regs();
          ????/*??子進程?eax?置?0,因此fork?在子進程返回0??*/
          ????childregs->ax?=?0;
          ????if?(sp)
          ????????childregs->sp?=?sp;
          ?/*??子進程ip?設(shè)置為ret_from_fork,因此子進程從ret_from_fork開始執(zhí)行??*/
          ????p->thread.ip?=?(unsigned?long)?ret_from_fork;
          ????task_user_gs(p)?=?get_user_gs(current_pt_regs());

          ????p->thread.io_bitmap_ptr?=?NULL;
          ????tsk?=?current;
          ????err?=?-ENOMEM;

          ????if?(unlikely(test_tsk_thread_flag(tsk,?TIF_IO_BITMAP)))?{
          ????????p->thread.io_bitmap_ptr?=?kmemdup(tsk->thread.io_bitmap_ptr,
          ????????????????????????IO_BITMAP_BYTES,?GFP_KERNEL);
          ????????if?(!p->thread.io_bitmap_ptr)?{
          ????????????p->thread.io_bitmap_max?=?0;
          ????????????return?-ENOMEM;
          ????????}
          ????????set_tsk_thread_flag(p,?TIF_IO_BITMAP);
          ????}

          ????err?=?0;

          ????/*
          ?????*?Set?a?new?TLS?for?the?child?thread?
          ?????*?為進程設(shè)置一個新的TLS
          ?????*/

          ????if?(clone_flags?&?CLONE_SETTLS)
          ????????err?=?do_set_thread_area(p,?-1,
          ????????????(struct?user_desc?__user?*)tls,?0);

          ????if?(err?&&?p->thread.io_bitmap_ptr)?{
          ????????kfree(p->thread.io_bitmap_ptr);
          ????????p->thread.io_bitmap_max?=?0;
          ????}
          ????return?err;
          }

          copy_thread 這段代碼為我們解釋了兩個相當(dāng)重要的問題!

          • 一是,為什么 fork 在子進程中返回0,原因是childregs->ax = 0;這段代碼將子進程的 eax 賦值為0
          • 二是,p->thread.ip = (unsigned long) ret_from_fork;將子進程的 ip 設(shè)置為 ret_form_fork 的首地址,因此子進程是從 ret_from_fork 開始執(zhí)行的

          總結(jié)

          fork, vfork和clone的系統(tǒng)調(diào)用的入口地址分別是sys_fork, sys_vfork和sys_clone, 而他們的定義是依賴于體系結(jié)構(gòu)的, 而他們最終都調(diào)用了_do_fork(linux-4.2之前的內(nèi)核中是do_fork),在_do_fork中通過copy_process復(fù)制進程的信息,調(diào)用wake_up_new_task將子進程加入調(diào)度器中

          1. dup_task_struct中為其分配了新的堆棧

          2. 調(diào)用了sched_fork,將其置為TASK_RUNNING

          3. copy_thread(_tls)中將父進程的寄存器上下文復(fù)制給子進程,保證了父子進程的堆棧信息是一致的,

          4. 將ret_from_fork的地址設(shè)置為eip寄存器的值

          5. 為新進程分配并設(shè)置新的pid

          6. 最終子進程從ret_from_fork開始執(zhí)行

          進程的創(chuàng)建到執(zhí)行過程如下圖所示

          進程的狀態(tài)

          轉(zhuǎn)自:

          https://blog.csdn.net/gatieme/article/details/51569932

          瀏覽 80
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天视频国产 | 国产日韩一区二区三免费高清 | 五月天婷婷啪啪 | 久久婷婷网站 | 俺来也俺也去精品一区 |