CMS源碼就可以做網(wǎng)站嗎網(wǎng)站建設(shè)公司 信科網(wǎng)絡(luò)
鶴壁市浩天電氣有限公司
2026/01/24 14:10:49
CMS源碼就可以做網(wǎng)站嗎,網(wǎng)站建設(shè)公司 信科網(wǎng)絡(luò),宣傳片拍攝公司排名,編程學校一學期多少錢進程創(chuàng)建1. fork 的本質(zhì)#xff1a;一次調(diào)用#xff0c;兩次返回這是 fork 最讓初學者困惑的地方。函數(shù)原型#xff1a;#include unistd.h
pid_t fork(void);現(xiàn)象#xff1a; 你在代碼里只寫了一行 fork()#xff0c;但程序運行后#xff0c;這一行代碼似乎“執(zhí)行…進程創(chuàng)建1.fork的本質(zhì)一次調(diào)用兩次返回這是fork最讓初學者困惑的地方。函數(shù)原型#include unistd.h pid_t fork(void);現(xiàn)象 你在代碼里只寫了一行fork()但程序運行后這一行代碼似乎“執(zhí)行”了兩次并且返回了兩個不同的值 。為什么會有兩次返回當你的程序執(zhí)行到fork()函數(shù)內(nèi)部時控制權(quán)轉(zhuǎn)移到了操作系統(tǒng)內(nèi)核。內(nèi)核做了一件驚天動地的事復制內(nèi)核以父進程為模板克隆了一個一模一樣的子進程。子進程也有自己的 PCB (task_struct)。子進程也有和父進程一樣的代碼、數(shù)據(jù)、文件描述符等。關(guān)鍵點子進程的程序計數(shù)器 (PC)記錄代碼執(zhí)行到哪一行了也和父進程一樣都指向fork()函數(shù)剛剛執(zhí)行完的位置 。分裂當內(nèi)核處理完復制工作準備從fork()函數(shù)返回時系統(tǒng)中已經(jīng)有了兩個正在運行的進程父進程和子進程。返回內(nèi)核讓父進程從fork返回帶回子進程的 PID。內(nèi)核讓子進程從fork返回帶回0。2. 深入理解返回值為什么是 0 和 PID設(shè)計這兩個不同的返回值是有深刻用意的 父進程返回子進程 PID父進程可能生了很多孩子調(diào)用多次fork。父進程必須拿到這個 ID才能在將來通過waitpid(pid)準確地找到并回收這個特定的子進程或者給它發(fā)信號。子進程返回 0子進程不需要fork告訴它父進程是誰因為子進程可以隨時調(diào)用getppid()獲取父進程 ID。更重要的是返回 0 是為了讓子進程知道“我就是那個被創(chuàng)造出來的新生命”。出錯返回 -1如果系統(tǒng)進程太多內(nèi)存不足或 PID 耗盡創(chuàng)建失敗只會在父進程返回 -1 。靈魂拷問同一個變量id怎么可能既等于 0 又大于 0看這段經(jīng)典代碼pid_t id fork(); if (id 0) { // 子進程邏輯 } else if (id 0) { // 父進程邏輯 }解釋 并非同一個變量同時有兩個值。而是在兩個獨立的進程空間里各有一個叫id的變量。父進程里的id變量被賦值為子進程 PID比如 1234。子進程里的id變量被賦值為 0。 它們只是名字相同但在物理內(nèi)存中是完全隔離的兩個變量 。3. 核心機制寫時拷貝 (Copy-On-Write, COW)這是 Linuxfork高效的秘訣。如果不理解這個你就無法理解為什么fork即使拷貝幾 GB 內(nèi)存的進程也極其迅速。誤區(qū)早期 Unix 的fork是“傻拷貝”父進程有 1GB 內(nèi)存fork就立馬申請 1GB 物理內(nèi)存把數(shù)據(jù)全部拷貝一份給子進程。這既浪費時間又浪費內(nèi)存。真相Linux 的惰性策略Linux 采用了寫時拷貝技術(shù) Fork 剛完成時只讀共享父子進程的頁表虛擬地址到物理地址的映射表是完全一樣的。它們指向同一塊物理內(nèi)存。關(guān)鍵動作內(nèi)核把這些共享的物理內(nèi)存頁標記為“只讀” (Read-Only)。當任意一方試圖寫入時觸發(fā)拷貝比如子進程執(zhí)行g(shù)_val 100;。CPU 發(fā)現(xiàn)正在往“只讀”頁面寫入數(shù)據(jù)觸發(fā)缺頁中斷 (Page Fault)。內(nèi)核捕獲這個中斷發(fā)現(xiàn)這是因為 COW 導致的。執(zhí)行拷貝內(nèi)核立刻申請一個新的物理內(nèi)存頁把原來的數(shù)據(jù)拷貝過來把新頁面的權(quán)限改為“可讀寫”然后讓子進程的頁表指向這個新頁面。父進程的頁表依然指向舊頁面權(quán)限也恢復為可讀寫。結(jié)論如果父子進程都只讀數(shù)據(jù)不修改物理內(nèi)存永遠共享零拷貝。只有被修改的那一頁數(shù)據(jù)才會被復制。這就是為什么fork極快且節(jié)省內(nèi)存 。4. 虛擬地址的“欺騙”// 偽代碼 int g_val 0; if (fork() 0) { g_val 100; printf(%d, %p
, g_val, g_val); // 輸出: 100, 地址: 0x601040 } else { sleep(1); printf(%d, %p
, g_val, g_val); // 輸出: 0, 地址: 0x601040 }現(xiàn)象父子進程打印的g_val地址竟然一模一樣0x601040但值卻不同。解釋0x601040是虛擬地址。父子進程擁有完全一樣的虛擬地址空間布局。由于發(fā)生了寫操作g_val 100觸發(fā)了寫時拷貝。在物理內(nèi)存層面父進程的0x601040映射到物理頁 A。子進程的0x601040映射到物理頁 B。用戶看到的只是虛擬地址這個“門牌號”卻不知道背后指向了不同的“房間” 。進程的退出1. 進程退出的三種場景在 Linux 看來進程退出只有三種情況 1代碼跑完結(jié)果正確return 0。代碼跑完結(jié)果不正確return非 0比如文件不存在、權(quán)限不足。代碼沒跑完異常終止程序崩潰了野指針、除零錯誤或者被信號kill -9殺死了。關(guān)鍵點只有前兩種情況正常退出進程的退出碼 (Exit Code)才有意義。如果進程是異常終止的被殺死的它的退出碼是無意義的我們需要關(guān)心的是它是被哪個信號殺死的。2. 退出碼 (Exit Code)概念main函數(shù)的返回值或者exit(n)中的n。規(guī)范0表示成功非0表示失敗不同的數(shù)字代表不同的錯誤原因。查看方式在 Shell 中運行完程序后立刻輸入echo $?可以查看上一個進程的退出碼 2。3. 核心考點exitvs_exit這是面試???。Linux 提供了兩個退出函數(shù)雖然結(jié)果都是進程結(jié)束但過程不同。_exit(int status)身份系統(tǒng)調(diào)用 (System Call)直接由內(nèi)核提供 3。行為冷酷無情。立刻關(guān)閉進程回收內(nèi)存不刷新緩沖區(qū)。如果你的printf內(nèi)容還在緩沖區(qū)里沒打印出來調(diào)用_exit后這些數(shù)據(jù)就丟了。exit(int status)身份庫函數(shù) (Library Function)由 C 標準庫提供 4。行為溫柔體貼。它在調(diào)用_exit之前會做很多收尾工作執(zhí)行用戶注冊的清理函數(shù)atexit。刷新緩沖區(qū)這是最大的區(qū)別把沒打印出來的printf數(shù)據(jù)強制刷到屏幕或文件中 5。最后才調(diào)用_exit。代碼驗證printf(hello); // 注意沒有
數(shù)據(jù)會暫存在緩沖區(qū) _exit(0); // 屏幕上什么都不會打印因為緩沖區(qū)直接被丟棄了 // exit(0); // 如果換成這個屏幕會打印 hello進程的等待子進程死了變成了僵尸 (Zombie)父進程必須負責回收它的資源PCB。1. 為什么要等待防僵尸解決內(nèi)存泄漏問題 6。獲知結(jié)果父進程需要知道子進程的任務(wù)完成得怎么樣是成功了還是被殺死了7。2. 等待的方法wait與waitpidA.wait(int* status)—— 簡單粗暴功能等待任意一個子進程退出。行為如果子進程沒退父進程就阻塞死等直到有子進程退出為止 8。B. waitpid(pid_t pid, int* status, int options) —— 精準控制 9這是更常用的函數(shù)因為它更靈活。pid參數(shù)pid 0等待指定的那個子進程比如 PID1234。pid -1等待任意子進程等同于wait。options參數(shù)0阻塞等待。和wait一樣子進程不完我不走。WNOHANG非阻塞等待。這是高并發(fā)程序的關(guān)鍵。父進程會問一下內(nèi)核“子進程結(jié)束了嗎”如果沒有結(jié)束waitpid立刻返回0父進程可以先去干別的事過會兒再來問輪詢。如果結(jié)束了返回子進程 PID。如果出錯了返回 -1。3. 深度解剖status位圖wait/waitpid的參數(shù)status是一個輸出型參數(shù)。它不僅僅是一個整數(shù)而是一個位圖。我們需要像看“驗尸報告”一樣解讀它。我們只關(guān)注低 16 位 10位區(qū)域含義提取宏高 8 位 (8-15)退出碼(正常退出才有意義)WEXITSTATUS(status)低 7 位 (0-6)終止信號(異常終止才有意義)status 0x7F第 7 位Core Dump 標志-判斷流程先看低 7 位是否收到信號如果低 7 位是 0說明是正常退出。此時再看高 8 位拿退出碼。如果低 7 位不是 0說明是異常退出被殺。此時高 8 位的退出碼是無效的不用看。代碼示例int status; pid_t ret waitpid(id, status, 0); if (ret 0) { if (WIFEXITED(status)) { // 宏判斷是否正常退出 (低7位是否為0) printf(正常退出退出碼: %d
, WEXITSTATUS(status)); } else { printf(異常退出被信號殺死: %d
, status 0x7F); } }進程的替換我們在fork之后子進程默認執(zhí)行的是和父進程一樣的代碼或者父進程代碼的副本。但通常我們創(chuàng)建子進程是為了讓它去執(zhí)行一個全新的程序比如你在 Shell 里輸入ls是希望運行/bin/ls這個程序而不是再跑一遍 Shell 的代碼。這時候就需要exec函數(shù)族出場了。1. 替換原理當進程調(diào)用exec系列函數(shù)時內(nèi)核會進行一場徹底的“大換血” 清空內(nèi)核會把當前進程的用戶空間完全清空代碼段、數(shù)據(jù)段、堆、棧統(tǒng)統(tǒng)不要了。加載內(nèi)核找到你指定的那個新程序比如磁盤上的ls可執(zhí)行文件把它的代碼和數(shù)據(jù)加載到內(nèi)存中。重置重置程序計數(shù)器 (PC)指向新程序的入口通常是_start-main。執(zhí)行進程開始執(zhí)行新程序的代碼。核心特征 (面試考點)PID 不變這就好比一個人“奪舍”了。軀殼PCB、PID、PPID還是原來那個但靈魂內(nèi)存里的代碼和數(shù)據(jù)已經(jīng)完全變成了另一個人 。不創(chuàng)建新進程exec只是用新程序覆蓋了舊程序沒有產(chǎn)生新的進程 ID。一次調(diào)用絕不返回exec函數(shù)一旦調(diào)用成功當前進程原本后續(xù)的代碼就直接灰飛煙滅了根本沒有機會執(zhí)行“return”。只有在調(diào)用失敗時比如找不到文件它才會返回 -1 。2.exec函數(shù)族Linux 提供了 6 個以exec開頭的庫函數(shù)它們功能一樣只是傳參方式不同。記住后綴的含義就能分清了 l (list)參數(shù)用列表一個個列出來最后必須以NULL結(jié)尾。v (vector)參數(shù)放進一個數(shù)組 (vector)里傳進去。p (path)自動在環(huán)境變量PATH里找程序不用寫全路徑比如寫ls就會自動找/bin/ls。e (env)不使用當前環(huán)境變量而是自己組裝一套環(huán)境變量傳給新程序。最常用的兩個execl/execlp(列表傳參) 適合參數(shù)已知且少的情況。// 執(zhí)行 ls -l -a // 這里的第一個 ls 是程序名第二個 ls 是 argv[0]占位但也得寫后面是參數(shù) execlp(ls, ls, -l, -a, NULL);execv/execvp(數(shù)組傳參) 適合參數(shù)動態(tài)生成的情況比如你自己寫的 Shell用戶輸入的參數(shù)個數(shù)不確定解析后放在數(shù)組里。char *const argv[] {ls, -l, -a, NULL}; execvp(ls, argv);注意只有execve是真正的系統(tǒng)調(diào)用 (System Call)其他 5 個都是 C 標準庫封裝的函數(shù)它們底層最終都會調(diào)用execve。