進程生命周期管理:從創建到終止的完整邏輯

前言

在操作系統的世界里,進程就像一個個忙碌的 “工作單元”,從被創建到完成任務后終止,始終遵循著一套嚴謹的生命周期規則。理解進程的生命周期管理,是揭開操作系統多任務調度神秘面紗的關鍵 —— 而這其中,進程的創建、終止與等待機制,構成了整個生命周期的核心骨架。

本文將沿著 “創建→終止→等待” 的脈絡,系統解析進程管理的底層邏輯:從fork函數如何 “復制” 出一個新進程,到寫時拷貝技術如何優化內存效率;從進程正常退出與異常終止的不同場景,到退出碼背后的狀態傳遞邏輯;更會深入探討為何父進程必須等待子進程,以及waitwaitpid函數在阻塞 / 非阻塞模式下的實現細節。無論你是想搞懂 “父子進程為何能共享代碼卻互不干擾”,還是想理解 “如何安全回收子進程資源”,這些知識點都將為你構建起進程管理的完整知識體系。

目錄

1. 進程創建?

1.1 fork函數初始

1.2 fork函數返回值

1.3 寫時拷貝

1.4 fork常規用法

1.5 fork調用失敗原因

2. 進程終止

2.1 進程退出場景

2.2 進程常見退出方法

2.3 退出碼

3. 進程等待

3.1 進程等待必要性

3.2 進程等待函數

wait

waitpid

3.3 獲取子進程status

3.4 阻塞與非阻塞等待


1. 進程創建?

1.1 fork函數初始

在linux中fork函數是非常重要的函數,它從已存在進程中創建一個新進程。新進程為子進程,而原進程為父進程。

#include <unistd.h>
pid_t fork(void);
返回值:?進程中返回0,?進程返回?進程id,出錯返回-1
進程調用fork,當控制轉移到內核中的fork代碼后,內核做:
分配新的內存塊和內核數據結構給子進程
將父進程部分數據結構內容拷貝至子進程
添加子進程到系統進程列表當中
fork返回,開始調度器調度

當一個進程調用fork之后,就有兩個二進制代碼相同的進程。而且它們都運行到相同的地方。但每個進程都將可以開始它們自己的旅程,看如下程序。

int main()
{
pid_t pid;
printf("Before: pid is %d\n", getpid());
if ( (pid=fork()) == -1 )perror("fork()"),exit(1);
printf("After:pid is %d, fork return %d\n", getpid(), pid);
sleep(1);
return 0;
}

?

這里看到了三行輸出,一行before,兩行after。進程2466154先打印before消息,然后它有打印after。
另一個after消息有2466155打印的。注意到進程2466155沒有打印before,為什么呢?如下圖所示

?

所以,fork之前父進程獨立執行,fork之后,父子兩個執行流分別執行。注意,fork之后,誰先執行完全由調度器決定。

1.2 fork函數返回值

父進程返回子進程的pid,子進程返回0

1.3 寫時拷貝

通常,父子代碼共享,父子再不寫入時,數據也是共享的,當任意一方試圖寫入,便以寫時拷貝的方式各自一份副本。具體見下圖:
因為有寫時拷貝技術的存在,所以父子進程得以徹底分離!完成了進程獨立性的技術保證!
寫時拷貝,是一種延時申請技術,可以提高整機內存的使用率

寫時拷貝減少創建時間? 減少內存浪費?

1.4 fork常規用法

一個父進程希望復制自己,使父子進程同時執行不同的代碼段。例如,父進程等待客戶端請求,
生成子進程來處理請求。
一個進程要執行一個不同的程序。例如子進程從fork返回后,調用exec函數。

1.5 fork調用失敗原因

1. 系統中有太多的進程? 2. 實際用戶的進程數超過了限制
本質是內存不足了

2. 進程終止

進程終止的本質是釋放系統資源,就是釋放進程申請的相關內核數據結構和對應的數據和代碼。

2.1 進程退出場景

代碼運行完畢,結果正確

代碼運行完畢,結果不正確

代碼異常終止

在以前的語言代碼中,都有main函數,main函數的返回值,通常表明的程序的執行情況.

代碼運行完畢,結果正確,return 0;

代碼運行完畢,結果不正確,return !0;不同的非零值表示不同的出錯原因。

2.2 進程常見退出方法

正常終止:
1. 從main返回? ? ? ? ??2. 調用exit? ? ? ? ??3. _exit
異常退出:
ctrl + c,信號終止
#include <unistd.h>
void exit(int status);
main函數結束,表示進程結束,其他函數return,只表示自己函數調用完成,返回。

任何地方調用exit,表示進程結束。并返回給父進程,子進程的退出碼。?

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void func(){printf("I am func.\n");exit(1);
}
int main(){func();printf("main\n");return 0;
}    

exit(C)? vs _exit(系統)
進程如果exit退出的時候,exit(),進程退出的時候,會進行緩沖區的刷新。
進程如果exit退出的時候,_exit(),進程退出的時候,不會進行緩沖區的刷新。

int main(){printf("main");sleep(2);exit(1);
}

?exit變成_exit后

所以也可以得出我們之前談論的緩沖區一定不是操作系統內部的緩沖區。?

?前者是庫函數,后者是系統調用,exit最后會調用_exit,

2.3 退出碼

退出碼(退出狀態)可以告訴我們最后一次執行的命令的狀態。在命令結束以后,我們可以知道命令是成功完成的還是以錯誤結束的。其基本思想是,程序返回退出代碼 0 時表示執行成功,沒有問題。 代碼 1 或 0 以外的任何代碼都被視為不成功。
main函數的返回值是進程的退出碼。也能自己設置return值
代碼異常終止,退出碼無意義,進程一旦出現異常,一般是進程收到了信號。

?echo $?? 查看最近一個程序(進程)退出時的退出碼,進程的退出碼是寫到了task_struct內部的。

#include <stdio.h>
#include <stdlib.h>int main()
{// 注意:文件打開模式需要用雙引號括起來FILE *fp = fopen("Hello.txt", "r");// 檢查文件是否成功打開if (fp == NULL) {return 1;}// C語言中關閉文件使用fclose()函數,而不是C++的成員函數形式fclose(fp);return 0;
}

終端打印部分常見退出碼:

Linux Shell 中的主要退出碼:

return是一種更常見的退出進程方法。執行return n等同于執行exit(n),因為調用main的運行時函數會將main的返回值當做 exit的參數。

3. 進程等待

3.1 進程等待必要性

之前講過,子進程退出,父進程如果不管不顧,就可能造成‘僵尸進程’的問題,進而造成內存泄漏。
另外,進程一旦變成僵尸狀態,那就刀槍不入,“殺人不眨眼”的kill -9 也無能為力,因為誰也沒有辦法殺死一個已經死去的進程。
最后,父進程派給子進程的任務完成的如何,我們需要知道。如,子進程運行完成,結果對還是不對,或者是否正常退出。
父進程通過進程等待的方式,回收子進程資源,獲取子進程退出信息

3.2 進程等待函數

wait

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* status);
返回值:
成功返回被等待進程pid,失敗返回-1。
參數:
輸出型參數,獲取?進程退出狀態,不關?則可以設置成為NULL

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>int main()
{pid_t id = fork();if (id < 0) {perror("fork");  // 錯誤處理:打印fork失敗原因return 1;}else if (id == 0) {  // 子進程邏輯int cnt = 3;while (cnt--) {// 增加換行符刷新緩沖區,避免輸出混亂printf("I am child, pid : %d\n", getpid());sleep(1);  // 子進程每次打印后休眠1秒,方便觀察}// 子進程退出前顯式說明printf("Child process exit\n");exit(0);  // 子進程正常退出}// 父進程邏輯pid_t ret = wait(NULL);  // 回收子進程資源,不關心退出狀態if (ret > 0) {  // 等待成功的判斷(ret為回收的子進程PID)printf("wait success, child pid=%d\n", ret);} else {perror("wait");  // 處理wait可能的錯誤return 1;}sleep(2);return 0;
}

?

如果等待子進程,子進程沒有退出,父進程會阻塞在wait處。?

當子進程結束若等待個幾秒觀察僵尸的情況。

觀察圖片明顯解決了僵尸的問題

waitpid

pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
當正常返回的時候waitpid返回收集到的?進程的進程ID;
如果設置了選項WNOHANG,?調?中waitpid發現沒有已退出的?進程可收集,則返回0;
如果調?中出錯,則返回-1,這時errno會被設置成相應的值以指?錯誤所在;
參數:
pid:
Pid=-1,等待任?個?進程。與wait等效。
Pid>0.等待其進程ID與pid相等的?進程。
status: 輸出型參數
WIFEXITED(status): 若為正常終??進程返回的狀態,則為真。(查看進程是
否是正常退出)
WEXITSTATUS(status): 若WIFEXITED?零,提取?進程退出碼。(查看進程的
退出碼)
options:默認為0,表?阻塞等待
WNOHANG: 若pid指定的?進程沒有結束,則waitpid()函數返回0,不予以等
待。若正常結束,則返回該?進程的ID。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>int main()
{pid_t id = fork();if (id < 0) {perror("fork");  // 錯誤處理:打印fork失敗原因return 1;}else if (id == 0) {  // 子進程邏輯int cnt = 3;while (cnt--) {// 增加換行符刷新緩沖區,避免輸出混亂printf("I am child, pid : %d\n", getpid());sleep(1);  // 子進程每次打印后休眠1秒,方便觀察}// 子進程退出前顯式說明printf("Child process exit\n");exit(0);  // 子進程正常退出}// 父進程邏輯pid_t ret = wait(id,NULL,0);  // 回收子進程資源,不關心退出狀態if (ret > 0) {  // 等待成功的判斷(ret為回收的子進程PID)printf("wait success, child pid=%d\n", ret);} else {perror("wait");  // 處理wait可能的錯誤return 1;}sleep(2);return 0;
}

?

如果子進程已經退出,調用wait/waitpid時,wait/waitpid會立即返回,并且釋放資源,獲得子進程退出信息。
如果在任意時刻調用wait/waitpid,子進程存在且正常運行,則進程可能阻塞。
如果不存在該子進程,則立即出錯返回。

3.3 獲取子進程status

wait和waitpid,都有一個status參數,該參數是?個輸出型參數,由操作系統填充。
如果傳遞NULL,表示不關心子進程的退出狀態信息。
否則,操作系統會根據該參數,將子進程的退出信息反饋給父進程。
status不能簡單的當作整形來看待,可以當作位圖來看待,具體細節如下圖(只研究status低16
?特位):

?

如果代碼沒有異常,低7個比特位為0,一旦低7個比特位!=0,異常退出的,退出碼無意義。?

退出異常,低7位保存異常時對應的信號編號

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>int main() {pid_t id = fork();if (id < 0) {  // 增加fork失敗的錯誤處理perror("fork failed");return 1;} else if (id == 0) {  // 子進程邏輯int cnt = 3;while (cnt--) {printf("我是子進程,pid:%d, ppid:%d\n", getpid(), getppid());sleep(1);}exit(10);  // 子進程退出,返回狀態碼10}// 父進程邏輯int status = 0;// 等待指定子進程(id),阻塞式等待(WNOHANG=0)pid_t rid = waitpid(id, &status, 0);if (rid > 0) {// 正確解析退出狀態:先判斷是否正常退出if (WIFEXITED(status)) {  // 宏判斷是否正常退出printf("wait success, 回收的子進程pid:%d, 退出碼:%d\n", rid, WEXITSTATUS(status));  // 宏獲取退出碼} else if (WIFSIGNALED(status)) {  // 宏判斷是否被信號終止printf("子進程被信號終止,信號編號:%d\n", WTERMSIG(status));}} else {printf("wait failed: %d:%s\n", errno, strerror(errno));}return 0;
}

#include <errno.h>
#include <string.h>
#include <unistd.h>
int main(){pid_t id=fork();if(id==0){int cnt=3;while(cnt--){printf("我是子進程,pid:%d,ppid:%d\n",getpid(),getppid());sleep(1);}exit(10);}int status=0;pid_t rid=waitpid(id,&status,0);                                                               if(rid>0){printf("wait success,rid:%d,exit code:%d,exit signal :%d\n",rid,(status>>8)&0xFF,status&0x7F);}else{printf("wait failed:%d:%s\n",errno,strerror(errno));}return 0;}

代碼中有除0操作 ,退出異常:

?子進程的退出信息只能放在task_struct.

3.4 阻塞與非阻塞等待

張三在寢室樓下打電話找李四吃飯,李四在復習,說等一會,過了一會張三又打了一次電話。李四說還有一點,于是張三隔一段時間就給李四打電話直到李四下樓。

張三是父進程,李四是子進程,打電話就是一次調用,以上就是非阻塞輪詢。

輪詢就是通過循環完成的。

第二次張三同樣找李四吃飯,李四同樣在復習,這次打電話張三叫李四不要掛電話,就一直開著,知道李四下樓,就打一次電話。

這就是阻塞調用。?

非阻塞調用,pid_t waitpid,返回值大于0,等待結束;等于0,調用結束,但是子進程沒有退出;小于0,等待失敗。

非阻塞調用可以讓等待方做做自己的事。

阻塞等待只有大于和小于。

非阻塞調用示例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <vector>  // C++容器,需用g++編譯
#include <errno.h>// 函數指針類型定義
typedef void(*handler)();
// 全局函數回調列表(C++標準庫容器,需加std::)
std::vector<handler> func;void Load(){printf("登錄!\n");
}
void Exit(){printf("退出!\n");
}// 初始化回調函數列表(只需要初始化一次)
void work(){// 避免重復添加(如果已經有函數則不再添加)if(func.empty()){func.push_back(Load);func.push_back(Exit);}
}// 執行所有回調函數
void handle(){if(func.empty()){work();  // 若未初始化則先初始化}for(auto e : func){  // auto是C++11特性,需用g++編譯e();}
}int main(){pid_t id = fork();if(id < 0){printf("fork error! errno:%d\n", errno);return 1;}else if(id == 0){  // 子進程邏輯printf("我是子進程, pid:%d\n", getpid());sleep(3);  // 子進程休眠3秒后退出exit(1);   // 退出碼為1}else{  // 父進程邏輯int status = 0;// 非阻塞等待:WNOHANG表示若子進程未結束則立即返回0pid_t ret = waitpid(id, &status, WNOHANG);// 循環等待子進程結束(每次檢查都需要重新調用waitpid)while(ret == 0){printf("child is running!\n");handle();  // 執行回調函數(登錄、退出)sleep(1);  // 休眠1秒再檢查,避免CPU空轉ret = waitpid(id, &status, WNOHANG);  // 重新獲取子進程狀態printf("本輪調用結束!\n");}// 檢查等待結果(ret應為子進程PID,即id)if(ret == id){// 判斷子進程是否正常退出if(WIFEXITED(status)){printf("wait child success, child return code is :%d.\n",WEXITSTATUS(status));}} else {printf("wait child failed, ret:%d, errno:%d\n", ret, errno);return 1;}}return 0;
}

結束語

進程的創建、終止與等待,看似是三個獨立的操作,實則是操作系統 “資源管理” 與 “程序協作” 理念的集中體現:fork通過寫時拷貝實現高效的進程復制,既保證了進程獨立性,又避免了不必要的內存浪費;進程終止機制通過退出碼傳遞狀態,讓程序的結束有跡可循;而wait/waitpid函數則解決了 “僵尸進程” 的資源泄漏問題,確保系統資源的有序回收。

理解這些機制,不僅能幫助我們寫出更健壯的多進程程序(如避免僵尸進程、正確處理子進程狀態),更能讓我們體會到操作系統設計的精妙 —— 每一個函數接口的背后,都是對 “效率” 與 “安全” 的平衡,每一種機制的實現,都服務于 “讓多任務協作更可靠” 的終極目標。

進程生命周期的故事遠不止于此,它與進程調度、信號處理等機制緊密相連,共同構成了操作系統的核心能力。希望本文能成為你探索進程管理的起點,讓你在編寫或調試多進程程序時,多一份對底層邏輯的清晰認知,少一份面對 “僵尸進程”“孤兒進程” 時的困惑。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/917978.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/917978.shtml
英文地址,請注明出處:http://en.pswp.cn/news/917978.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【顯示器】背光板的結構和工作原理

背光板是LCD&#xff08;液晶顯示器&#xff09;中的一個重要組件&#xff0c;它負責提供屏幕所需的光源。下面我們詳細解釋背光板的結構和工作原理。背光板的基本結構一個典型的背光板由以下幾個主要部分組成&#xff1a;LED燈條&#xff1a;通常使用白色LED作為光源。導光板&…

hadoop HDFS 重置詳細步驟

有時候我們需要對hdfs重置&#xff0c;步驟如下&#xff1a; 1、停止服務 2. 清除日志節點ssh dmp-hdfs-ns1 rm -rf /disk1/dfs/jn/meta/*ssh dmp-hdfs-ns2 rm -rf /disk1/dfs/jn/meta/*ssh dmp-hdfs-dt1 rm -rf /disk1/dfs/jn/meta/*ssh dmp-hdfs-dt2 rm -rf /disk1/dfs/jn/me…

前端性能優化:從請求到資源的精細調控

在用戶體驗為王的時代&#xff0c;前端性能直接決定產品的留存率。本文聚焦 “減少不必要的傳輸與加載損耗”&#xff0c;從 合并HTTP請求、啟用壓縮、減少Cookie、資源加載順序 四個維度&#xff0c;拆解優化思路與落地方法。 一、合并HTTP請求&#xff1a;突破瀏覽器并發瓶頸…

【NLP輿情分析】基于python微博輿情分析可視化系統(flask+pandas+echarts) 視頻教程 - 微博輿情數據可視化分析-熱詞情感趨勢柱狀圖

大家好&#xff0c;我是java1234_小鋒老師&#xff0c;最近寫了一套【NLP輿情分析】基于python微博輿情分析可視化系統(flaskpandasecharts)視頻教程&#xff0c;持續更新中&#xff0c;計劃月底更新完&#xff0c;感謝支持。今天講解微博輿情數據可視化分析-熱詞情感趨勢柱狀圖…

腳本統計MongoDB集合結構信息

場景&#xff1a; 當想統計mongodb集合的結構是什么數據類型時。 1.利用variety.js解析 https://github.com/variety/variety 2.腳本 #!/bin/bash#userxxx #passwxxx host1xx.1x.1x.150 port27010 dbhgrtabs$(echo "show collections"|mongo ${host}:${port}/${db}|g…

訂單簿流動性分析與機器學習在大單匹配中的應用

一、訂單簿流動性的基本概念 1.1 訂單簿的結構與組成 在金融市場中&#xff0c;訂單簿&#xff08;Order Book&#xff09;是買賣雙方提交的限價訂單的集合&#xff0c;通常以價格優先、時間優先的原則進行排序。訂單簿由多個層級的價格檔位組成&#xff0c;每個檔位包含若干限…

CSS :is () 與 :where ():簡化復雜選擇器的 “語法糖”

在 CSS 編寫中&#xff0c;你是否遇到過這樣的場景&#xff1a;需要給多個不同父元素下的子元素設置相同樣式&#xff0c;結果寫出一長串重復的選擇器&#xff1f;比如給header、main、footer中的p標簽設置相同的顏色&#xff0c;傳統寫法可能是header p, main p, footer p { c…

vue打包號的文件如何快速查找文件打包后的位置

解析“explorer yz-front-dist”&#xff1a;前端開發者的實用命令小知識 在前端開發的日常工作中&#xff0c;我們經常會接觸到各種命令行操作&#xff0c;其中“explorer yz-front-dist”是一個看似簡單卻暗藏實用價值的命令。對于剛接觸開發的新手來說&#xff0c;理解它的含…

Go語言數據類型深度解析:位、字節與進制

Go語言數據類型深度解析&#xff1a;位、字節與進制 在計算機編程中&#xff0c;數據類型是構建一切的基礎。理解不同數據類型的特性、內存占用以及在不同場景下的應用&#xff0c;對于編寫高效、可靠的代碼至關重要。 本文將深入探討Go語言中的數據類型系統&#xff0c;重點講…

計算機視覺(opencv)——圖像本質、數字矩陣、RGB + 基本操作(實戰一)

OpenCV 入門教程&#xff1a; OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一個開源的計算機視覺庫&#xff0c;廣泛應用于圖像處理、視頻分析、機器學習等領域。 在 Python 中&#xff0c;cv2 是 OpenCV 的主要接口模塊。本文將帶你一步步掌握 cv2…

【數據庫】使用Sql Server創建索引優化查詢速度,一般2萬多數據后,通過非索引時間字段排序查詢出現超時情況

大家好&#xff0c;我是全棧小5&#xff0c;歡迎來到《小5講堂》。 這是《Sql Server》系列文章&#xff0c;每篇文章將以博主理解的角度展開講解。 溫馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不對之處望指正&#xff01; 目錄前言SQL 創建索引…

MyBatis聯合查詢

文章目錄數據庫設計MyBatis 配置MyBatis 映射文件Mapper 接口總結數據庫設計 建表 SQL CREATE TABLE user (id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(50) NOT NULL );CREATE TABLE order (id INT PRIMARY KEY AUTO_INCREMENT,user_id INT NOT NULL,order_no VARCHAR(…

項目中使用的設計模式

項目中使用的設計模式請列舉幾個項目中常用的設計模式什么是設計模式&#xff0c;在項目中使用了那些設計模式動態代理模式JDK動態代理CGLIB動態代理單例模式懶漢式&#xff08;非線程安全&#xff09;餓漢式懶漢式&#xff08;線程安全&#xff09;工廠模式觀察者模式裝飾器模…

實戰教程:從“對象文件為空“到倉庫重生——修復 Git 倉庫損壞全記錄

文章目錄實戰教程&#xff1a;從"對象文件為空"到倉庫重生——修復 Git 倉庫損壞全記錄案發現場&#xff1a;一個嚴重損壞的倉庫修復之旅&#xff1a;四步讓倉庫重獲新生準備工作&#xff1a;創建安全備份第 1 步&#xff1a;清理戰場——刪除所有空對象第 2 步&…

ansible 操作家族(ansible_os_family)信息

1. 操作系統系列 &#xff08;ansible_os_family&#xff09;ansible web -m setup -a filteransible_os_family2. 操作系統家族為 RedHat 時執行任務--- - hosts: websrvsremote_user: roottasks:- name: Install package on RedHat systemsyum:name: httpdstate: presentwhen…

一文學會c++繼承 組合

文章目錄繼承簡介定義訪問限定符和繼承方式?基類派生類賦值轉換繼承的作用域派生類的默認成員函數繼承與友元繼承與靜態成員?復雜的菱形繼承虛擬繼承組合繼承簡介 繼承是面向對象程序設計代碼復用的重要手段&#xff0c;使得程序員可以在保持原類的基礎上擴展&#xff0c;新…

.Net下載共享文件夾中的文件

由于IIS站點權限等問題&#xff0c;總是沒找到處理辦法&#xff0c;所以改用外掛的winform的方式來下載共享文件&#xff08;也可以改為使用windows服務的方式&#xff09;。 前提需要先在資源管理器中登錄到共享文件夾&#xff0c;確保系統能訪問。 服務端代碼 (.NET后端) usi…

目標檢測數據集 - 眼睛瞳孔檢測數據集下載「包含COCO、YOLO兩種格式」

數據集介紹&#xff1a;眼睛瞳孔檢測數據集&#xff0c;真實采集高質量人臉眼部圖片數據&#xff0c;適用于人臉定位、人臉疾病如白內障等疾病的視覺檢測。數據標注標簽包括 eyepupil 瞳孔一 個缺陷類別&#xff1b;適用實際項目應用&#xff1a;眼睛瞳孔檢測項目&#xff0c;以…

Keil MDK-ARM V5.42a 完整安裝教程

文章目錄一、安裝前期準備二、Keil MDK-ARM 主程序安裝三、器件支持包&#xff08;Pack&#xff09;安裝四、許可證激活五、安裝驗證Keil MDK&#xff08;Microcontroller Development Kit&#xff09;是針對 Arm Cortex-M 系列微控制器的專業開發環境&#xff0c;集成了 μVis…

WPF中引用其他元素各種方法

在WPF中&#xff0c;引用其他元素的方式有多種&#xff0c;每種方式適用于不同場景&#xff0c;各有優缺點。除了x:Reference&#xff0c;常用的還有以下幾種&#xff1a; 一、ElementName 綁定&#xff08;最常用的XAML綁定方式&#xff09; 通過元素的x:Name屬性引用同一作用…