深入理解進程:從底層原理到硬件系統實戰

深入理解進程:從底層原理到嵌入式實戰(3-4 萬字詳解)

前言:為什么硬件開發者必須吃透進程?

作為嵌入式開發者,你可能會說:“我平時用的 RTOS 里只有任務(Task),沒有進程啊!” 但如果你想在珠三角拿到 12k + 的嵌入式開發 offer,尤其是進入智能硬件或汽車電子領域,進程管理是繞不開的硬骨頭 ——

  • 智能硬件常需要 Linux 系統跑應用程序,多進程協作是基礎

  • 汽車電子的 ECU(電子控制單元)里,RTOS 的任務管理本質是簡化的進程管理

  • 面試時,進程相關知識點(如 IPC、調度算法)是大廠必考題

本文將從 “是什么 - 為什么 - 怎么做” 三個維度,用 3-4 萬字的篇幅徹底講透進程。包含 15 + 代碼示例、8 張思維導圖、10 + 實戰案例,保證刷過牛客 100 題的嵌入式開發者都能看懂。

一、進程的本質:從 “死代碼” 到 “活程序” 的蛻變

1.1 程序與進程的核心區別(附實例對比)

很多人搞不清 “程序” 和 “進程” 的區別,我們用一個嵌入式場景舉例:

程序(Program):你寫的led_blink.c編譯后生成的led_blink.elf文件,存儲在開發板的 Flash 里,這是靜態的—— 就像一本菜譜,躺在書架上不會自己做菜。

進程(Process):當你在 Linux 開發板上執行./led_blink,操作系統會把led_blink.elf加載到內存,分配 CPU 時間、GPIO 資源,讓代碼跑起來 —— 這是動態的,就像廚師按照菜譜實際做菜的過程。

用表格對比關鍵區別:

對比項 程序(Program) 進程(Process) 嵌入式場景舉例
存在形式 靜態文件(.elf/.bin) 動態執行過程 Flash 里的固件 vs 運行中的固件
資源占用 不占用 CPU / 內存(僅占磁盤) 占用 CPU、內存、I/O 資源 未運行的 APP vs 后臺運行的 WiFi 服務
生命周期 永久存在(除非刪除文件) 有創建、運行、終止的過程 下載固件 vs 啟動 / 關閉傳感器服務
獨立性 無(多個程序可共享文件) 獨立地址空間、獨立資源 多個任務共享 UART vs 進程獨占 SPI

實戰驗證:在 Linux 開發板上執行ls -l /bin/ls(查看程序)和ps -ef | grep ls(查看進程),前者顯示文件屬性,后者顯示運行狀態。

1.2 進程的 “三要素”:程序、數據、PCB

一個進程能跑起來,必須具備三個核心要素:

  1. 程序段(Code Segment):存放指令,比如while(1){toggle_led();delay(1000);}

  2. 數據段(Data Segment):存放變量,比如int led_state = 0;(全局變量)、棧上的局部變量

  3. 進程控制塊(PCB):操作系統管理進程的 “身份證”,記錄進程狀態、資源等信息

用思維導圖展示三者關系:

進程
程序段
數據段
PCB
機器指令
函數庫調用
全局變量
局部變量
常量
進程ID
狀態
CPU寄存器
內存指針
打開文件列表

嵌入式視角:在 STM32 的 FreeRTOS 中,任務控制塊(TCB)就是簡化的 PCB,包含任務棧指針、優先級、狀態等信息,對應的數據結構類似:

// FreeRTOS任務控制塊(簡化版)typedef struct tskTaskControlBlock {    StackType\_t \*pxTopOfStack;  // 棧頂指針(對應PCB的CPU上下文)    xListItem xStateListItem;   // 狀態鏈表項(對應PCB的狀態)    UBaseType\_t uxPriority;     // 優先級(對應PCB的調度信息)    // ... 其他資源信息} TCB\_t;

1.3 進程的 5 個核心特征(附反例說明)

進程有 5 個特征,缺一個都不能叫 “進程”:

  1. 動態性:能被創建、調度、終止(反例:ROM 里的固化程序,無法動態調度)

    舉例:在 Linux 中用./app &啟動進程,kill終止進程,體現動態性。

  2. 并發性:多個進程可同時存在(反例:單任務單片機程序,一次只能跑一個功能)

    舉例:開發板上同時運行溫度采集進程WiFi上傳進程

  3. 獨立性:擁有獨立地址空間(反例:線程,共享進程地址空間)

    舉例:一個進程崩潰(如段錯誤),不會影響其他進程。

  4. 異步性:進程按不可預知的速度推進(反例:實時任務,需嚴格按時間執行)

    舉例:兩個進程打印日志,輸出順序可能每次不同。

  5. 結構性:由程序段、數據段、PCB 組成(反例:裸機程序,沒有 PCB 管理)

    舉例:Linux 的/proc/[pid]/目錄下的文件,就是進程結構的體現。

面試陷阱:面試官可能問 “線程是否具備這些特征?”—— 線程沒有獨立性(共享地址空間),所以不是進程。

二、進程狀態:從 “就緒” 到 “運行” 的生死輪回

2.1 進程的 5 種基本狀態(附 Linux 實際驗證)

進程在生命周期中會經歷 5 種狀態,我們結合ps命令的實際輸出理解:

狀態名稱 英文標識 含義(大白話) Linux 中查看方式(ps aux)
創建態 NEW 剛被創建,還沒加入就緒隊列 一般看不到(持續時間極短)
就緒態 READY 萬事俱備,就等 CPU 時間片 R(Running 的縮寫,包含就緒)
運行態 RUNNING 正在 CPU 上執行 R
阻塞態 BLOCKED 等資源(如 I/O),主動放棄 CPU S(Sleeping)或 D(深度睡眠)
終止態 TERMINATED 已結束,等待回收 PCB Z(Zombie,僵尸進程)

實戰操作:在 Linux 開發板上執行:

\# 啟動一個會阻塞的進程(如ping一個不存在的IP)ping 192.168.1.254 &\# 查看狀態(會顯示S,阻塞在網絡I/O)ps aux | grep ping

你會看到ping進程狀態為S,表示它因等待網絡響應而阻塞。

2.2 狀態轉換的 6 種場景(附代碼觸發示例)

進程狀態不會憑空變化,每種轉換都有明確的觸發條件。我們用 “嵌入式傳感器采集” 場景舉例:

  1. 創建態 → 就緒態

    觸發:進程創建完成,資源分配完畢。

    代碼示例

\#include \<stdio.h>\#include \<unistd.h>int main() {&#x20;   pid\_t pid = fork();  // 創建子進程(進入創建態)&#x20;   if (pid == 0) {      // 子進程創建完成,進入就緒態&#x20;       printf("子進程就緒\n");&#x20;   }&#x20;   return 0;}
  1. 就緒態 → 運行態

    觸發:調度器選中該進程,分配 CPU。

    場景:就緒隊列中只有你的傳感器進程,調度器會立即讓它運行。

  2. 運行態 → 就緒態

    觸發:時間片用完,或被高優先級進程搶占。

    Linux 驗證

\# 啟動一個占用CPU的進程while true; do :; done &\# 再啟動一個高優先級進程(nice值更小)nice -n -5 ./high\_prio\_app &\# 查看第一個進程會變成就緒態(R,但實際未運行)ps -l
  1. 運行態 → 阻塞態

    觸發:進程請求 I/O(如讀取傳感器數據)。

    代碼示例

// 讀取I2C傳感器(會阻塞等待數據)int fd = open("/dev/i2c-1", O\_RDWR);char data\[10];read(fd, data, 10);  // 執行到此處,進程進入阻塞態
  1. 阻塞態 → 就緒態

    觸發:等待的資源到了(如傳感器數據讀取完成)。

    原理:I/O 完成后,硬件會產生中斷,內核處理中斷時將進程從阻塞隊列移到就緒隊列。

  2. 運行態 → 終止態

    觸發:進程執行完畢,或被 kill。

    代碼示例

// 正常終止int main() {&#x20;   printf("任務完成\n");&#x20;   return 0;  // 執行到此處,進程進入終止態}

狀態轉換思維導圖

分配完資源
調度器選中
時間片用完/被搶占
等I/O/信號量
資源就緒
執行完畢/被kill
被強制終止
被強制終止
創建態
就緒態
運行態
阻塞態
終止態

2.3 嵌入式 RTOS 中的狀態變種(以 FreeRTOS 為例)

RTOS 的任務狀態是進程狀態的簡化版,但更貼近硬件實際:

FreeRTOS 任務狀態 對應進程狀態 嵌入式場景舉例
就緒態(Ready) 就緒態 等待調度器分配 CPU 的傳感器任務
運行態(Running) 運行態 正在采集溫濕度的任務
阻塞態(Blocked) 阻塞態 調用 vTaskDelay () 的延時任務
掛起態(Suspended) 無對應 被 vTaskSuspend () 暫停的調試任務

關鍵區別:RTOS 沒有 “僵尸態”,任務刪除后資源立即回收(因為嵌入式系統資源有限,不允許浪費)。

代碼對比

// FreeRTOS任務狀態轉換示例void vSensorTask(void \*pvParameters) {&#x20;   while(1) {&#x20;       // 讀取傳感器(可能進入阻塞態)&#x20;       read\_sensor();&#x20;      &#x20;&#x20;       // 延時100ms(主動進入阻塞態)&#x20;       vTaskDelay(pdMS\_TO\_TICKS(100));  // 對應進程的阻塞態&#x20;   }}

三、進程控制塊(PCB):進程的 “身份證 + 檔案袋”

3.1 PCB 的作用:操作系統如何 “記住” 進程?

想象一個場景:你正在用開發板調試程序,突然被打斷去接電話,回來后能接著調試 —— 因為你 “記住” 了之前的狀態(斷點位置、變量值)。

操作系統管理進程也是同理,PCB 就是用來 “記住” 進程狀態的結構。沒有 PCB,操作系統就無法管理進程

具體來說,PCB 的作用有三個:

  1. 唯一標識:通過 PID 區分不同進程(就像身份證號)。

  2. 狀態記錄:記錄進程當前狀態(就緒 / 阻塞等),供調度器參考。

  3. 資源索引:保存進程占用的內存、文件、設備等資源的指針。

類比理解:PCB 就像醫院的病歷卡 —— 每個病人(進程)一張,記錄病情(狀態)、檢查結果(資源),醫生(操作系統)通過病歷卡了解病人情況。

3.2 Linux 內核中的 PCB:task_struct 結構體詳解

Linux 中的 PCB 是task_struct結構體(定義在linux/sched.h),包含 300 + 字段,我們挑嵌入式開發者必懂的 10 個字段詳解:

struct task\_struct {&#x20;   // 1. 進程標識&#x20;   pid\_t pid;                  // 進程ID(唯一標識)&#x20;   pid\_t tgid;                 // 線程組ID(多線程時用)&#x20;  &#x20;&#x20;   // 2. 狀態信息&#x20;   volatile long state;        // 進程狀態(TASK\_RUNNING等)&#x20;   unsigned int flags;         // 進程標志(如PF\_KTHREAD表示內核線程)&#x20;  &#x20;&#x20;   // 3. 調度信息&#x20;   int prio;                   // 動態優先級&#x20;   int static\_prio;            // 靜態優先級&#x20;   struct sched\_entity se;     // 調度實體(用于CFS調度器)&#x20;  &#x20;&#x20;   // 4. 內存信息&#x20;   struct mm\_struct \*mm;       // 內存描述符(用戶空間內存)&#x20;   struct mm\_struct \*active\_mm;// 活躍內存描述符(內核線程用)&#x20;  &#x20;&#x20;   // 5. 上下文信息(CPU寄存器)&#x20;   struct thread\_struct thread;// 存放寄存器值(切換時保存/恢復)&#x20;  &#x20;&#x20;   // 6. 父子關系&#x20;   struct task\_struct \*parent; // 父進程指針&#x20;   struct list\_head children;  // 子進程鏈表&#x20;  &#x20;&#x20;   // 7. 文件信息&#x20;   struct files\_struct \*files; // 打開的文件列表&#x20;  &#x20;&#x20;   // 8. 信號處理&#x20;   struct signal\_struct \*signal; // 信號描述符&#x20;   struct sighand\_struct \*sighand; // 信號處理函數&#x20;  &#x20;&#x20;   // 9. 時間信息&#x20;   cputime\_t utime;            // 用戶態CPU時間&#x20;   cputime\_t stime;            // 內核態CPU時間&#x20;  &#x20;&#x20;   // 10. 其他&#x20;   struct task\_struct \*real\_parent; // 實際父進程(被領養前)};

關鍵字段解析

  1. pid 與 tgid
  • 單進程:pid = tgid

  • 多線程:主線程 pid = tgid,子線程 pid 不同但 tgid 相同

  • 查看方式ps -L -p <pid> 可看到線程的 LWP(輕量級進程 ID,即 pid)

  1. state
  • TASK_RUNNING:運行 / 就緒態

  • TASK_INTERRUPTIBLE:可中斷阻塞(如等待鍵盤輸入)

  • TASK_UNINTERRUPTIBLE:不可中斷阻塞(如等待磁盤 I/O,ps顯示 D)

  • 注意ps命令中 R = 運行 / 就緒,S = 可中斷阻塞,D = 不可中斷阻塞

  1. mm 與 active_mm
  • 用戶進程:mm 指向自己的內存空間

  • 內核線程:mm=NULL,active_mm 指向借用的用戶內存

  • 嵌入式意義:內核線程不占用用戶內存,適合資源緊張的嵌入式系統

  1. thread_struct
  • 存放 CPU 寄存器值(如 ARM 的 sp、pc、lr 等)

  • 進程切換時,內核會保存當前 thread_struct,加載下一個進程的 thread_struct

  • 舉例:當進程因中斷切換時,pc(程序計數器)的值會被保存,恢復時從該地址繼續執行

3.3 PCB 的組織方式:進程鏈表與哈希表

操作系統需要快速找到某個進程的 PCB,Linux 用兩種數據結構組織:

  1. 雙向循環鏈表
  • 所有 PCB 通過task_structtasks字段鏈接成鏈表

  • 遍歷所有進程時使用(如ps aux命令)

  • 定義:struct list_head tasks;

  1. 哈希表
  • 通過 PID 快速查找 PCB(pid_hash數組)

  • 時間復雜度 O (1),比遍歷鏈表快

  • 嵌入式優化:嵌入式 Linux 可能精簡哈希表大小,減少內存占用

圖示

graph LRsubgraph 進程鏈表A[PCB1(pid=1)] <--> B[PCB2(pid=2)]B <--> C[PCB3(pid=3)]C <--> Aendsubgraph 哈希表(pid_hash)D[哈希桶0] --> AE[哈希桶1] --> BF[哈希桶2] --> Cend

實戰查看:在 Linux 內核源碼中,init_task是第一個進程(swapper)的 PCB,所有進程都從它衍生:

// 內核啟動時創建的第一個進程struct task\_struct init\_task = INIT\_TASK(init\_task);

四、進程創建:從 fork () 到 exec () 的完整流程

4.1 進程創建的 4 個步驟(附內核源碼分析)

創建進程就像開分店:總店(父進程)復制一套經營模式(代碼),準備新店面(資源),招聘員工(分配 PID),最后開業(加入就緒隊列)。

具體步驟:

  1. 分配 PID
  • pidmap位圖中找一個未使用的 PID

  • 代碼邏輯(簡化):

static int alloc\_pid(struct pid\_namespace \*ns) {&#x20;   // 遍歷pidmap,找第一個0位&#x20;   for (i = 0; i < PIDMAP\_ENTRIES; i++) {&#x20;       if (pidmap\[i].page) {&#x20;           // 找到空閑PID&#x20;           return pid;&#x20;       }&#x20;   }}
  1. 復制 PCB
  • 調用dup_task_struct()復制父進程的task_struct

  • 關鍵操作:分配新的內核棧(alloc_thread_info

  • 注意:默認不復制用戶內存(用寫時復制技術)

  1. 初始化新 PCB
  • 修改 PID、狀態等信息(設為 TASK_RUNNING)

  • 清空父進程特有的信息(如信號處理、計時器)

  • 代碼片段:

p->pid = alloc\_pid(p->nsproxy->pid\_ns);p->state = TASK\_RUNNING;p->parent = current;  // current是當前進程(父進程)
  1. 加入進程隊列
  • 將新 PCB 加入進程鏈表(list_add(&p->tasks, &init_task.tasks)

  • 加入對應優先級的就緒隊列

  • 通知調度器有新進程就緒

4.2 fork () 系統調用:從 “一分為二” 到 “寫時復制”

fork()是創建進程的 “瑞士軍刀”,我們從用法、原理、優化三個層面解析。

4.2.1 fork () 的基本用法(附嵌入式場景示例)

函數原型

\#include \<unistd.h>pid\_t fork(void);  // 返回值:父進程得到子進程PID,子進程得到0,失敗返回-1

嵌入式場景示例:開發板上同時采集溫濕度和光照數據:

\#include \<stdio.h>\#include \<unistd.h>\#include \<sys/wait.h>// 采集溫度(子進程)void collect\_temperature() {&#x20;   while(1) {&#x20;       printf("溫度: 25℃\n");&#x20;       sleep(2); // 模擬2秒采集一次&#x20;   }}// 采集光照(父進程)void collect\_light() {&#x20;   while(1) {&#x20;       printf("光照: 500lux\n");&#x20;       sleep(3); // 模擬3秒采集一次&#x20;   }}int main() {&#x20;   pid\_t pid = fork();&#x20;   if (pid < 0) {&#x20;       perror("fork failed");&#x20;       return 1;&#x20;   } else if (pid == 0) {&#x20;       // 子進程:采集溫度&#x20;       collect\_temperature();&#x20;   } else {&#x20;       // 父進程:采集光照&#x20;       collect\_light();&#x20;       // 等待子進程(實際中不會在循環里等)&#x20;       wait(NULL);&#x20;   }&#x20;   return 0;}

運行結果:溫度和光照數據交替打印,實現并行采集。

4.2.2 fork () 的 “寫時復制”(COW)優化

早期的fork()會完整復制父進程的內存,效率極低(比如父進程有 1GB 內存,復制就要 1GB 空間)。現代操作系統用 “寫時復制” 優化:

  • 原理:父子進程共享同一塊物理內存,只有當任一進程修改內存時,才復制被修改的部分(頁)

  • 好處:創建進程快(不用復制內存),節省內存(未修改的頁共享)

圖示

graph TDA[父進程內存] -->|fork()| B[共享物理頁]B --> C[父進程修改頁1]C --> D[復制頁1,父進程用新頁1]B --> E[子進程未修改]E --> F[子進程仍用共享頁]

驗證 COW:在 Linux 上用fork()創建子進程后,立即查看內存使用(top命令),會發現父子進程共享大部分內存。

4.2.3 vfork () 與 fork () 的區別(嵌入式必知)

嵌入式系統資源有限,vfork()fork()更輕量,區別如下:

對比項 fork() vfork()
內存共享 寫時復制 完全共享(包括棧)
執行順序 父子進程執行順序不確定 子進程先執行,父進程阻塞到子進程 exit ()
用途 通用進程創建 子進程立即調用 exec () 的場景
風險 高(子進程修改內存會影響父進程)

嵌入式使用場景:在內存只有 64MB 的嵌入式設備上,用vfork()+execve()啟動新程序,比fork()節省內存。

代碼示例

\#include \<stdio.h>\#include \<unistd.h>\#include \<sys/stat.h>\#include \<sys/wait.h>int main() {&#x20;   pid\_t pid = vfork();&#x20;   if (pid == 0) {&#x20;       // 子進程必須調用exec系列函數或exit&#x20;       execl("/bin/ls", "ls", "-l", NULL);&#x20;       \_exit(0); // 如果exec失敗,必須exit&#x20;   } else {&#x20;       // 父進程在子進程exit或exec后才執行&#x20;       printf("子進程已執行\n");&#x20;       wait(NULL);&#x20;   }&#x20;   return 0;}

4.3 exec 系列函數:進程 “改頭換面”

fork()創建的子進程與父進程執行相同代碼,exec系列函數能讓子進程執行新程序(“換代碼”)。

常用 exec 函數

函數名 功能 示例
execl() 命令行參數列表傳參 execl(“/bin/ls”, “ls”, “-l”, NULL)
execv() 命令行參數數組傳參 char *argv[] = {“ls”, “-l”, NULL}; execv(“/bin/ls”, argv)
execlp() 從 PATH 找程序,不用寫全路徑 execlp(“ls”, “ls”, “-l”, NULL)
execvp() 結合 execv () 和 execlp () 的特點 char *argv[] = {“ls”, “-l”, NULL}; execvp(“ls”, argv)

嵌入式場景:父進程監控傳感器,子進程執行不同的處理程序:

\#include \<stdio.h>\#include \<unistd.h>\#include \<sys/wait.h>int main() {&#x20;   pid\_t pid = fork();&#x20;   if (pid == 0) {&#x20;       // 子進程:執行溫度處理程序&#x20;       execl("./temperature\_handler", "temperature\_handler", "25", NULL);&#x20;       // 如果exec失敗才會執行下面的代碼&#x20;       perror("exec failed");&#x20;       \_exit(1);&#x20;   } else {&#x20;       // 父進程:繼續監控&#x20;       printf("監控中...\n");&#x20;       wait(NULL);&#x20;   }&#x20;   return 0;}

注意exec成功后,子進程的代碼、數據會被新程序替換,但 PID 不變(還是原來的子進程)。

五、進程終止與資源回收:避免 “僵尸” 橫行

5.1 進程終止的 3 種方式(附代碼)

進程終止就像 “死亡”,有自然死亡、意外死亡、被殺死三種方式:

  1. 正常終止(自然死亡)
\#include \<stdio.h>\#include \<stdlib.h> // exit()\#include \<unistd.h> // \_exit()int main() {&#x20;   printf("正常終止"); // 沒有換行符&#x20;   exit(0); // 會刷新緩沖區,輸出"正常終止"&#x20;   // \_exit(0); // 不刷新緩沖區,可能不輸出}
  • main()返回(return 0;

  • 調用exit()(會刷新緩沖區)

  • 調用_exit()(不刷新緩沖區,嵌入式常用)

  1. 異常終止(意外死亡)
int main() {&#x20;   int a = 1 / 0; // 會產生SIGFPE信號,異常終止&#x20;   return 0;}
  • 除以零、非法內存訪問(段錯誤)

  • 收到致命信號(如 SIGSEGV、SIGFPE)

  1. 被其他進程殺死(他殺)
\#include \<signal.h>\#include \<stdio.h>int main() {&#x20;   pid\_t pid = fork();&#x20;   if (pid == 0) {&#x20;       while(1) sleep(1); // 子進程死循環&#x20;   } else {&#x20;       sleep(2);&#x20;       kill(pid, SIGKILL); // 父進程殺死子進程&#x20;   }&#x20;   return 0;}
  • 其他進程調用kill()發送信號

  • kill命令(如kill -9 <pid>

5.2 僵尸進程:是什么、為什么、怎么辦

5.2.1 僵尸進程的產生(附復現代碼)

定義:子進程終止后,PCB 未被回收,變成僵尸進程(Zombie)。

產生原因:父進程未調用wait()waitpid()回收子進程資源。

復現代碼

\#include \<stdio.h>\#include \<unistd.h>int main() {&#x20;   pid\_t pid = fork();&#x20;   if (pid == 0) {&#x20;       // 子進程:立即終止&#x20;       printf("子進程終止\n");&#x20;       \_exit(0);&#x20;   } else {&#x20;       // 父進程:不調用wait(),進入死循環&#x20;       while(1) sleep(1);&#x20;   }&#x20;   return 0;}

查看僵尸進程

\# 編譯運行上述程序后ps aux | grep defunct  # defunct表示僵尸進程

會看到子進程狀態為Z+(Zombie)。

5.2.2 僵尸進程的危害與解決方法

危害

  • 占用 PID(系統 PID 有限,如 32768 個),僵尸太多會導致無法創建新進程

  • 占用 PCB 內存(每個 PCB 約 1KB,10 萬個僵尸就占 100MB)

解決方法

  1. 父進程主動回收:調用wait()waitpid()
\#include \<stdio.h>\#include \<unistd.h>\#include \<sys/wait.h>int main() {&#x20;   pid\_t pid = fork();&#x20;   if (pid == 0) {&#x20;       \_exit(0);&#x20;   } else {&#x20;       int status;&#x20;       waitpid(pid, \&status, 0); // 等待子進程終止&#x20;       // 可以通過status獲取子進程退出狀態&#x20;       if (WIFEXITED(status)) {&#x20;           printf("子進程正常退出,返回值:%d\n", WEXITSTATUS(status));&#x20;       }&#x20;   }&#x20;   return 0;}
  1. 父進程忽略 SIGCHLD 信號
\#include \<signal.h>signal(SIGCHLD, SIG\_IGN); // 告訴內核:子進程終止后自動回收
  1. 雙重 fork ():讓 init 進程領養孫子進程
// 父進程 -> 子進程A -> 子進程B// 子進程A創建B后立即退出,B成為孤兒進程被init領養,init會回收B

5.3 孤兒進程:被 “福利院”(init)領養

定義:父進程先于子進程終止,子進程被 init 進程(PID=1)領養。

特點

  • 無害(init 會負責回收)

  • 狀態為S(就緒 / 阻塞),不是僵尸

復現代碼

\#include \<stdio.h>\#include \<unistd.h>int main() {&#x20;   pid\_t pid = fork();&#x20;   if (pid == 0) {&#x20;       // 子進程:等待父進程死亡&#x20;       sleep(2);&#x20;       // 父進程已死,打印新的父進程PID(應為1)&#x20;       printf("子進程的新父進程PID:%d\n", getppid());&#x20;   } else {&#x20;       // 父進程:立即退出&#x20;       \_exit(0);&#x20;   }&#x20;   return 0;}

運行結果:子進程的新父進程 PID 為 1(init 進程)。

六、進程間通信(IPC):讓進程 “說話”

進程是獨立的,但需要協作(如傳感器進程將數據傳給上傳進程),這就需要 IPC。

6.1 管道(Pipe):最簡單的 “傳話筒”

管道是最古老的 IPC 方式,像一根 “管子”,數據從一端進,另一端出。

6.1.1 匿名管道(父子進程專用)

特點

  • 半雙工(數據單向流動)

  • 只能用于有親緣關系的進程(父子、兄弟)

  • 基于文件描述符(讀端 fd [0],寫端 fd [1])

代碼示例:父進程給子進程發送傳感器數據

\#include \<stdio.h>\#include \<unistd.h>\#include \<string.h>int main() {&#x20;   int fd\[2];&#x20;   // 創建管道&#x20;   if (pipe(fd) == -1) {&#x20;       perror("pipe failed");&#x20;       return 1;&#x20;   }&#x20;   pid\_t pid = fork();&#x20;   if (pid == 0) {&#x20;       // 子進程:讀數據&#x20;       close(fd\[1]); // 關閉寫端(只需要讀)&#x20;       char buf\[100];&#x20;       read(fd\[0], buf, sizeof(buf));&#x20;       printf("子進程收到:%s\n", buf);&#x20;       close(fd\[0]);&#x20;   } else {&#x20;       // 父進程:寫數據&#x20;       close(fd\[0]); // 關閉讀端(只需要寫)&#x20;       char \*data = "溫度:25℃";&#x20;       write(fd\[1], data, strlen(data));&#x20;       close(fd\[1]);&#x20;   }&#x20;   return 0;}

注意

  • 管道有緩沖(默認 64KB),滿了會阻塞寫操作

  • 讀端關閉后寫操作會產生 SIGPIPE 信號(默認終止進程)

6.1.2 命名管道(FIFO):任意進程通信

特點

  • 有文件名(在文件系統中可見,如/tmp/myfifo

  • 可用于任意進程(無親緣關系)

  • 用法類似匿名管道,但需要先創建

創建 FIFO

mkfifo /tmp/sensor\_fifo  # 命令行創建

或代碼創建:

\#include \<sys/stat.h>mkfifo("/tmp/sensor\_fifo", 0666); // 0666是權限

通信示例

寫進程(傳感器采集):

\#include \<stdio.h>\#include \<unistd.h>\#include \<fcntl.h>\#include \<string.h>int main() {&#x20;   int fd = open("/tmp/sensor\_fifo", O\_WRONLY);&#x20;   char \*data = "光照:500lux";&#x20;   write(fd, data, strlen(data));&#x20;   close(fd);&#x20;   return 0;}

讀進程(數據處理):

\#include \<stdio.h>\#include \<unistd.h>\#include \<fcntl.h>int main() {&#x20;   int fd = open("/tmp/sensor\_fifo", O\_RDONLY);&#x20;   char buf\[100];&#x20;   read(fd, buf, sizeof(buf));&#x20;   printf("收到:%s\n", buf);&#x20;   close(fd);&#x20;   return 0;}

嵌入式應用:在嵌入式 Linux 中,多個進程(如采集、處理、顯示)可通過 FIFO 傳遞數據,無需考慮進程關系。

6.2 信號(Signal):進程間的 “緊急電報”

信號是異步通知機制,像 “發電報” 一樣簡單粗暴,適合傳遞簡單指令(如終止、暫停)。

6.2.1 常見信號及默認行為
信號編號 名稱 含義 默認行為 嵌入式場景舉例
2 SIGINT 中斷(Ctrl+C) 終止進程 手動停止調試中的程序
9 SIGKILL 殺死進程 終止進程 強制結束無響應的進程
11 SIGSEGV 段錯誤(非法內存訪問) 終止 + CoreDump 程序 bug 導致內存越界
17 SIGCHLD 子進程終止 忽略 父進程回收子進程
19 SIGSTOP 暫停進程 暫停進程 調試時暫停程序執行

查看所有信號kill -l

6.2.2 發送信號:kill () 函數與 kill 命令

用 kill 命令發送信號

kill -9 1234  # 給PID=1234的進程發SIGKILLkill -SIGSTOP 1234  # 暫停進程

用 kill () 函數發送信號

\#include \<signal.h>\#include \<stdio.h>\#include \<unistd.h>int main() {&#x20;   pid\_t pid = fork();&#x20;   if (pid == 0) {&#x20;       while(1) {&#x20;           printf("運行中...\n");&#x20;           sleep(1);&#x20;       }&#x20;   } else {&#x20;       sleep(2);&#x20;       kill(pid, SIGSTOP); // 暫停子進程&#x20;       sleep(2);&#x20;       kill(pid, SIGCONT); // 繼續子進程&#x20;       sleep(2);&#x20;       kill(pid, SIGKILL); // 殺死子進程&#x20;   }&#x20;   return 0;}
6.2.3 捕獲信號:自定義信號處理函數

進程可以自定義信號的處理方式(除 SIGKILL 和 SIGSTOP,這兩個信號不能被捕獲)。

代碼示例:捕獲 SIGINT,實現優雅退出

\#include \<stdio.h>\#include \<signal.h>\#include \<unistd.h>// 信號處理函數void sigint\_handler(int signo) {&#x20;   if (signo == SIGINT) {&#x20;       printf("\n收到中斷信號,正在保存數據...\n");&#x20;       // 保存傳感器數據等清理工作&#x20;       sleep(1);&#x20;       printf("數據保存完成,退出\n");&#x20;       \_exit(0);&#x20;   }}int main() {&#x20;   // 注冊信號處理函數&#x20;   if (signal(SIGINT, sigint\_handler) == SIG\_ERR) {&#x20;       perror("signal failed");&#x20;       return 1;&#x20;   }&#x20;  &#x20;&#x20;   // 模擬傳感器采集&#x20;   while(1) {&#x20;       printf("采集數據中...\n");&#x20;       sleep(1);&#x20;   }&#x20;   return 0;}

運行:按 Ctrl+C 時,進程會先保存數據再退出,而不是立即終止。

6.3 共享內存:最快的 IPC(嵌入式首選)

共享內存是效率最高的 IPC 方式 —— 數據直接在內存中共享,無需拷貝。

6.3.1 共享內存的使用步驟
  1. 創建 / 打開共享內存shmget()

  2. 映射到進程地址空間shmat()

  3. 讀寫共享內存:直接操作指針

  4. 解除映射shmdt()

  5. 刪除共享內存shmctl()

代碼示例

寫進程(傳感器):

\#include \<stdio.h>\#include \<sys/ipc.h>\#include \<sys/shm.h>\#include \<string.h>\#define SHM\_SIZE 1024  // 共享內存大小\#define SHM\_KEY 0x1234 // 共享內存鍵值(唯一標識)int main() {&#x20;   // 1. 創建共享內存&#x20;   int shmid = shmget(SHM\_KEY, SHM\_SIZE, IPC\_CREAT | 0666);&#x20;   if (shmid == -1) {&#x20;       perror("shmget failed");&#x20;       return 1;&#x20;   }&#x20;   // 2. 映射到地址空間&#x20;   char \*shmaddr = shmat(shmid, NULL, 0);&#x20;   if (shmaddr == (void\*)-1) {&#x20;       perror("shmat failed");&#x20;       return 1;&#x20;   }&#x20;   // 3. 寫數據&#x20;   strcpy(shmaddr, "溫度:25℃ 濕度:60%");&#x20;   printf("寫入共享內存: %s\n", shmaddr);&#x20;   // 等待讀進程讀取&#x20;   sleep(5);&#x20;   // 4. 解除映射&#x20;   shmdt(shmaddr);&#x20;   // 5. 刪除共享內存(通常由一個進程負責)&#x20;   shmctl(shmid, IPC\_RMID, NULL);&#x20;   return 0;}

讀進程(數據處理):

\#include \<stdio.h>\#include \<sys/ipc.h>\#include \<sys/shm.h>\#define SHM\_SIZE 1024\#define SHM\_KEY 0x1234int main() {&#x20;   // 1. 獲取共享內存(已由寫進程創建)&#x20;   int shmid = shmget(SHM\_KEY, SHM\_SIZE, 0666);&#x20;   if (shmid == -1) {&#x20;       perror("shmget failed");&#x20;       return 1;&#x20;   }&#x20;   // 2. 映射到地址空間&#x20;   char \*shmaddr = shmat(shmid, NULL, 0);&#x20;   if (shmaddr == (void\*)-1) {&#x20;       perror("shmat failed");&#x20;       return 1;&#x20;   }&#x20;   // 3. 讀數據&#x20;   printf("從共享內存讀取: %s\n", shmaddr);&#x20;   // 4. 解除映射&#x20;   shmdt(shmaddr);&#x20;   return 0;}
6.3.2 共享內存的同步問題(必知)

共享內存不提供同步機制,多進程同時讀寫會導致數據錯亂(如兩個進程同時寫同一位置)。

解決方法:用信號量(Semaphore)同步。

示例:用信號量保護共享內存讀寫:

// 初始化信號量(確保先于共享內存操作)sem\_t \*sem = sem\_open("/sensor\_sem", O\_CREAT, 0666, 1);// 寫共享內存前加鎖sem\_wait(sem);// 寫操作...sem\_post(sem);// 讀共享內存前加鎖sem\_wait(sem);// 讀操作...sem\_post(sem);

嵌入式注意:嵌入式 Linux 可能需要開啟CONFIG_SYSVIPC配置才能使用共享內存。

6.4 信號量(Semaphore):進程同步的 “紅綠燈”

信號量像 “紅綠燈”,控制進程何時可以訪問共享資源(如共享內存、硬件設備)。

6.4.1 信號量的基本概念
  • 計數信號量:值可以是任意非負數,用于控制資源數量(如 3 個串口設備)

  • 二元信號量(互斥鎖):值只能是 0 或 1,用于互斥訪問(如同一時間只能一個進程用 SPI 總線)

P 操作(等待)sem_wait()—— 信號量減 1,若值 < 0 則阻塞

V 操作(釋放)sem_post()—— 信號量加 1,喚醒阻塞進程

6.4.2 System V 信號量與 POSIX 信號量

Linux 有兩種信號量接口,嵌入式常用 POSIX 信號量(更簡單):

POSIX 信號量示例(互斥訪問 SPI)

\#include \<semaphore.h>\#include \<stdio.h>\#include \<unistd.h>\#include \<pthread.h>sem\_t sem; // 全局信號量// 模擬SPI操作void spi\_operation(int id) {&#x20;   sem\_wait(\&sem); // P操作:獲取鎖&#x20;   printf("進程%d開始使用SPI\n", id);&#x20;   sleep(2); // 模擬SPI操作&#x20;   printf("進程%d結束使用SPI\n", id);&#x20;   sem\_post(\&sem); // V操作:釋放鎖}int main() {&#x20;   // 初始化信號量(1表示互斥鎖)&#x20;   sem\_init(\&sem, 0, 1); // 第二個參數0表示線程間共享&#x20;   pid\_t pid = fork();&#x20;   if (pid == 0) {&#x20;       spi\_operation(2); // 子進程&#x20;   } else {&#x20;       spi\_operation(1); // 父進程&#x20;   }&#x20;   // 銷毀信號量&#x20;   sem\_destroy(\&sem);&#x20;   return 0;}

運行結果:兩個進程不會同時使用 SPI,體現互斥效果。

6.4.3 信號量解決生產者 - 消費者問題

場景:傳感器(生產者)采集數據到緩沖區,處理程序(消費者)從緩沖區取數據。

代碼示例

\#include \<semaphore.h>\#include \<stdio.h>\#include \<unistd.h>\#include \<pthread.h>\#define BUFFER\_SIZE 5int buffer\[BUFFER\_SIZE];int in = 0, out = 0;sem\_t empty; // 空緩沖區數量sem\_t full;  // 滿緩沖區數量sem\_t mutex; // 互斥鎖// 生產者(傳感器)void \*producer(void \*arg) {&#x20;   for (int i = 0; i < 10; i++) {&#x20;       int data = i; // 模擬傳感器數據&#x20;       sem\_wait(\&empty); // 等空緩沖區&#x20;       sem\_wait(\&mutex);&#x20;       buffer\[in] = data;&#x20;       printf("生產: %d, 位置: %d\n", data, in);&#x20;       in = (in + 1) % BUFFER\_SIZE;&#x20;       sem\_post(\&mutex);&#x20;       sem\_post(\&full); // 滿緩沖區+1&#x20;       sleep(1); // 模擬采集間隔&#x20;   }&#x20;   return NULL;}// 消費者(數據處理)void \*consumer(void \*arg) {&#x20;   for (int i = 0; i < 10; i++) {&#x20;       sem\_wait(\&full); // 等滿緩沖區&#x20;       sem\_wait(\&mutex);&#x20;       int data = buffer\[out];&#x20;       printf("消費: %d, 位置: %d\n", data, out);&#x20;       out = (out + 1) % BUFFER\_SIZE;&#x20;       sem\_post(\&mutex);&#x20;       sem\_post(\&empty); // 空緩沖區+1&#x20;       sleep(2); // 模擬處理時間&#x20;   }&#x20;   return NULL;}int main() {&#x20;   // 初始化信號量&#x20;   sem\_init(\&empty, 0, BUFFER\_SIZE); // 初始有5個空緩沖區&#x20;   sem\_init(\&full, 0, 0);            // 初始0個滿緩沖區&#x20;   sem\_init(\&mutex, 0, 1);           // 互斥鎖&#x20;   pthread\_t prod\_tid, cons\_tid;&#x20;   pthread\_create(\&prod\_tid, NULL, producer, NULL);&#x20;   pthread\_create(\&cons\_tid, NULL, consumer, NULL);&#x20;   pthread\_join(prod\_tid, NULL);&#x20;   pthread\_join(cons\_tid, NULL);&#x20;   // 清理&#x20;   sem\_destroy(\&empty);&#x20;   sem\_destroy(\&full);&#x20;   sem\_destroy(\&mutex);&#x20;   return 0;}

運行結果:生產者和消費者交替操作緩沖區,不會出現數據混亂。

七、進程調度:誰先 “上車” 誰說了算

7.1 進程調度的基本概念(嵌入式視角)

進程調度就是 “決定哪個進程先使用 CPU”,像公交車調度 —— 誰先上車、誰后上車,需要規則。

為什么需要調度

  • CPU 是稀缺資源(通常只有 1-4 核)

  • 多個進程需要 “公平” 使用 CPU

  • 不同進程有不同需求(如實時進程需要立即響應)

嵌入式調度 vs 通用 OS 調度

  • 嵌入式:強調實時性(如傳感器數據必須 10ms 內處理)

  • 通用 OS:強調公平性和交互性(如桌面系統)

7.2 Linux 的 CFS 調度器(完全公平調度)

Linux 采用 CFS(Completely Fair Scheduler)調度器,核心思想是 “讓每個進程獲得公平的 CPU 時間”。

7.2.1 CFS 的基本原理
  • 虛擬運行時間:進程實際運行時間按優先級加權后的時間

  • 紅黑樹:所有就緒進程按虛擬運行時間排序,每次選虛擬運行時間最小的進程

舉例

  • 高優先級進程的虛擬時間流逝慢(如實際運行 1ms,虛擬時間 + 0.5ms)

  • 低優先級進程的虛擬時間流逝快(如實際運行 1ms,虛擬時間 + 2ms)

  • 這樣高優先級進程能獲得更多實際 CPU 時間

7.2.2 進程優先級與 nice 值

Linux 用 nice 值表示進程優先級:

  • 范圍:-20(最高優先級)~ 19(最低優先級)

  • 默認值:0

  • 調整優先級:nice -n <值> 命令renice <值> -p <pid>

查看進程 nice 值ps -l(NI 列)

嵌入式應用:在嵌入式系統中,可將實時任務的 nice 值設為 - 20,確保優先執行。

7.3 實時調度策略(嵌入式必備)

嵌入式系統常需要實時調度(如汽車的剎車控制必須立即響應),Linux 提供兩種實時調度策略:

  1. SCHED_FIFO
  • 先進先出,一旦獲得 CPU 就一直運行,直到主動放棄或被更高優先級進程搶占

  • 適合短時間運行的實時任務(如傳感器數據處理)

  1. SCHED_RR
  • 時間片輪轉,相同優先級的進程輪流執行

  • 適合需要定期執行的任務(如 10ms 一次的電機控制)

設置實時調度策略

\#include \<stdio.h>\#include \<sched.h>int main() {&#x20;   struct sched\_param param;&#x20;   param.sched\_priority = 50; // 優先級(1-99,值越大優先級越高)&#x20;   // 設置SCHED\_FIFO調度策略&#x20;   if (sched\_setscheduler(0, SCHED\_FIFO, \&param) == -1) {&#x20;       perror("sched\_setscheduler failed");&#x20;       return 1;&#x20;   }&#x20;   // 實時任務...&#x20;   return 0;}

注意:需要 root 權限才能設置實時優先級,嵌入式系統中通常會開啟相關配置。

7.4 嵌入式 RTOS 的調度器(以 FreeRTOS 為例)

FreeRTOS 的調度器比 Linux 簡單,適合資源有限的嵌入式系統:

  • 搶占式調度:高優先級任務可立即搶占低優先級任務

  • 時間片調度:相同優先級任務輪流執行(可配置)

代碼示例

// 高優先級任務(傳感器數據處理)void vHighPriorityTask(void \*pvParameters) {&#x20;   while(1) {&#x20;       // 處理數據(必須快速完成)&#x20;       vTaskDelay(pdMS\_TO\_TICKS(10));&#x20;   }}// 低優先級任務(日志打印)void vLowPriorityTask(void \*pvParameters) {&#x20;   while(1) {&#x20;       // 打印日志(可延遲)&#x20;       vTaskDelay(pdMS\_TO\_TICKS(100));&#x20;   }}int main() {&#x20;   // 創建任務,高優先級任務優先執行&#x20;   xTaskCreate(vHighPriorityTask, "HighTask", 128, NULL, 2, NULL);&#x20;   xTaskCreate(vLowPriorityTask, "LowTask", 128, NULL, 1, NULL);&#x20;   vTaskStartScheduler(); // 啟動調度器&#x20;   return 0;}

關鍵區別:FreeRTOS 的任務切換開銷小(約幾微秒),適合微控制器(如 STM32),而 Linux 調度切換開銷大(約幾十微秒)。

八、實戰:用進程知識解決嵌入式實際問題

8.1 案例 1:嵌入式設備的多進程架構設計

以 “智能溫濕度傳感器” 為例,設計多進程架構:

進程名稱 功能 優先級 IPC 方式
采集進程 讀取溫濕度傳感器 共享內存
處理進程 數據校準、轉換 共享內存 + 信號量
上傳進程 WiFi 上傳數據 管道
日志進程 記錄系統日志 最低 命名管道

優勢

  • 模塊化(一個進程出問題不影響其他進程)

  • 可獨立升級(如只更新上傳進程支持新協議)

  • 方便調試(可單獨重啟某個進程)

8.2 案例 2:解決傳感器數據丟失問題

問題:傳感器數據采集快(10ms 一次),但上傳慢(100ms 一次),導致數據丟失。

分析:采集進程和上傳進程速度不匹配,沒有緩沖機制。

解決方案:用共享內存 + 信號量實現環形緩沖區:

  1. 采集進程:將數據寫入環形緩沖區,信號量計數 + 1

  2. 上傳進程:從緩沖區讀數據,信號量計數 - 1

  3. 緩沖區滿時,采集進程可選擇覆蓋舊數據或等待

核心代碼:參考 6.4.3 的生產者 - 消費者模型,將緩沖區改為環形。

8.3 案例 3:調試進程相關問題的工具

工具 用途 嵌入式場景示例
ps 查看進程狀態 檢查是否有僵尸進程(Z 狀態)
top/htop 實時查看進程 CPU / 內存使用 發現 CPU 占用 100% 的異常進程
pstree 查看進程樹(父子關系) 找到某個進程的父進程
strace 跟蹤進程系統調用 調試進程為何無法打開設備文件
gdb attach 調試運行中的進程 在不重啟的情況下調試上傳進程

調試示例:用strace查看進程為何無法讀取傳感器:

strace -f ./sensor\_collect  # -f跟蹤子進程

會輸出所有系統調用,若看到open("/dev/i2c-1", O_RDWR) = -1 ENOENT,說明設備文件不存在。

九、總結:進程知識體系與面試重點

9.1 進程知識體系思維導圖

進程
基本概念
狀態
PCB
創建與終止
IPC
調度
程序vs進程
進程特征
5種狀態及轉換
task_struct結構
組織方式
fork/exec
僵尸/孤兒進程
管道/FIFO
信號
共享內存
信號量
CFS調度器
實時調度

9.2 面試高頻問題與答案要點

  1. 進程與線程的區別?
  • 進程:資源分配單位,有獨立地址空間

  • 線程:調度單位,共享進程資源

  • 開銷:進程創建 / 切換開銷大,線程小

  1. 僵尸進程產生原因及解決方法?
  • 原因:子進程終止后父進程未回收 PCB

  • 解決:wait ()/waitpid ()、忽略 SIGCHLD、雙重 fork ()

  1. 什么是寫時復制?為什么用它?
  • 原理:fork () 后父子進程共享內存,修改時才復制

  • 好處:加快進程創建速度,節省內存

  1. 進程間通信方式及優缺點?
  • 管道:簡單,僅限親緣進程

  • 共享內存:最快,需同步

  • 信號量:用于同步,不傳遞數據

  • 信號:異步,適合簡單通知

  1. 實時調度與普通調度的區別?
  • 實時:優先保證響應時間(如 SCHED_FIFO)

  • 普通:優先保證公平性(如 CFS)

9.3 下一步學習建議

  1. 動手實踐:用本文代碼在開發板上實際運行,觀察進程行為

  2. 閱讀源碼:看 FreeRTOS 的任務調度器源碼(理解簡化版進程管理)

  3. 項目實戰:實現一個多進程的嵌入式應用(如智能家居網關)

  4. 深入內核:學習 Linux 內核進程調度和 IPC 的實現細節

掌握進程知識,不僅能通過面試,更能設計出穩定、高效的嵌入式系統。記住:最好的學習方法是 “用起來”—— 在實際項目中遇到問題、解決問題,才能真正理解進程的精髓。

(注:文檔部分內容可能由 AI 生成)

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

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

相關文章

Elasticsearch 簡化指南:GCP Google Compute Engine

作者&#xff1a;來自 Elastic Eduard Martin 系列內容的一部分&#xff1a;開始使用 Elasticsearch&#xff1a;GCP 想獲得 Elastic 認證&#xff1f;看看下一期 Elasticsearch Engineer 培訓什么時候開始&#xff01; Elasticsearch 擁有豐富的新功能&#xff0c;幫助你根據…

STM32的定時器輸入捕獲-超聲波測距案例

STM32的定時器輸入捕獲-超聲波測距案例 gitee代碼輸入捕獲硬件電路案例說明主函數代碼 gitee代碼 https://gitee.com/xiaolixi/l-stm32/tree/master/STM32F103C8T6/2-1tem-ld-timer-input-pluse 輸入捕獲硬件電路 超聲波測距案例說明 使用超聲波測距傳感器使用tim1的輸入捕獲…

[特殊字符] Spring Boot 常用注解全解析:20 個高頻注解 + 使用場景實例

一文掌握 Spring Boot 中最常用的 20 個注解&#xff0c;涵蓋開發、配置、Web、數據庫、測試等場景&#xff0c;配合示例講解&#xff0c;一站式掌握&#xff01;&#x1f4cc; 一、核心配置類注解 1. SpringBootApplication 作用&#xff1a;標記為 Spring Boot 應用的入口類&…

【工具變量】地級市城市包容性綠色增長數據(2011-2023年)

城市包容性綠色增長是指在推動城市經濟增長的過程中&#xff0c;兼顧環境可持續性、社會公平和包容性發展的理念與實踐。它強調在實現綠色轉型和低碳發展的同時&#xff0c;保障社會各群體&#xff0c;特別是弱勢群體的利益與參與權利&#xff0c;確保增長成果能夠公平共享 本…

深入理解React Hooks:從使用到原理

4. 源碼解析類:《深入理解React Hooks:從使用到原理》 # 深入理解React Hooks:從使用到原理?? **背景**: - Hooks解決了Class組件的哪些問題? - 為什么不能在循環/條件中調用Hooks??? **核心原理**:### 1. Hooks鏈表 React內部維護一個單向鏈表:fiber.memoizedSta…

【云原生】Docker 部署 Elasticsearch 9 操作詳解

目錄 一、前言 二、Elasticsearch 9 新特性介紹 2.1 基于 Lucene 10 重大升級 2.2 Better Binary Quantization(BBQ) 2.3 Elastic Distributions of OpenTelemetry(EDOT) 2.4 LLM 可觀測性 2.5 攻擊發現與自動導入 2.6 ES|QL 增強 2.7 語義檢索 三、基于Docker部署…

uview-ui使用u-search搜索框

1、效果圖 2、帶地址搜索框&#xff0c;在微信小程序線上需要開啟地圖定位接口&#xff0c;若沒有權限則顯示不了城市名&#xff0c;注意事項參考uniapp相關地圖 API調用-CSDN博客 <template><view><u-sticky offset-top"-1"><u-search v-mode…

Elasticsearch+Logstash+Kibana部署

目錄 一、實驗準備 1.下載安裝 2.下載java 2.同步主機系統時間 二、部署 1.部署elasticsearch 修改 /etc/elasticsearch/elasticsearch.yml 配置文件 修改 /etc/hosts/ 文件 啟動elasticsearch 查看是否啟動進程netstat -antptu | grep java 2.部署logstash 進入/et…

TEngine學習

關于靜態類中的靜態變量賦值&#xff1a; public static class ActorEventDefine{public static readonly int ScoreChange RuntimeId.ToRuntimeId("ActorEventDefine.ScoreChange");public static readonly int GameOver RuntimeId.ToRuntimeId("ActorEventD…

獵板:在 5G 與 AI 時代,印制線路板如何滿足高性能需求

5G 與 AI 技術的深度融合&#xff0c;推動電子設備向高速傳輸、高算力、高集成方向發展&#xff0c;印制線路板&#xff08;PCB&#xff09;作為核心載體&#xff0c;其性能直接決定終端設備的運行效率與可靠性。獵板 PCB 聚焦 5G 通信的高頻需求與 AI 算力的密集需求&#xff…

教你如何借助AI精讀文獻

目錄1. 原文2. 對文獻有一個快速的理解3. 專業術語解讀4. 解答疑問5. 借助AI翻譯摘要和引言部分5.1 **摘要 (Abstract)**5.2 **引言 (Introduction)**6. 介紹論文中的“Stack-Propagation”7. 查閱論文里的參考文獻&#xff0c;看看他是如何在Introduction中引述研究進展文獻&a…

FastAdmin框架超級管理員密碼重置與常規admin安全機制解析-卓伊凡|大東家

FastAdmin框架超級管理員密碼重置與常規admin安全機制解析-卓伊凡|大東家我們可以看到admin賬戶是不允許直接修改的&#xff0c;這也是目前fastadmin 框架不允許的&#xff0c;那么如何處理一、FastAdmin超級管理員密碼重置方法當FastAdmin的超級管理員密碼忘記或需要重置時&am…

我做的基礎服務項目,是如何實現 API 安全與限流的(短信、郵件、文件上傳、釘釘通知)

我做的基礎服務項目&#xff0c;是如何實現 API 安全與限流的&#xff08;短信、郵件、文件上傳、釘釘通知&#xff09;一、背景 最近我做了一個基礎服務項目&#xff0c;主要對外提供短信、郵件、文件上傳和釘釘通知等基礎功能。這些接口是多個業務系統都要調用的&#xff0c;…

(Python)類和類的方法(基礎教程介紹)(Python基礎教程)

源代碼&#xff1a;class Students:stats"大學"def __init__(self,name,age,sex,credit):self.namenameself.ageageself.sexsexself.creditcreditdef tell(self):return f"{self.name}說&#xff1a;你好"class Teachers(Students):stats"教師"d…

網絡智能體研究綜述

網絡智能體研究綜述1.什么是網絡智能體1.1.核心特征1.2.分類方式1.2.1.按功能定位1.2.2. 按網絡結構1.2.3.按應用場景1.3.典型應用場景1.4.技術基礎1.5.發展趨勢與挑戰1.5.1.發展趨勢1.5.2.核心挑戰2.網絡智能體盤點3.阿里的WebSailor3.1.WebSailor的主要功能和技術特點3.2.技術…

git 介紹與使用教程

Git 是一個 分布式版本控制系統&#xff0c;每個開發者都有一個完整的本地倉庫&#xff08;包含完整歷史記錄&#xff09;&#xff0c;而遠程倉庫&#xff08;如 GitHub、GitLab、Gitee&#xff09;是團隊共享的中央倉庫。它們的關系如下&#xff1a;本地倉庫&#xff08;Local…

[AI風堇]基于ChatGPT3.5+科大訊飛錄音轉文字API+GPT-SOVITS的模擬情感實時語音對話項目

[AI風堇]最近利用工作日的晚間和周末時間&#xff0c;我完成了一個有趣的Python編程小項目。這個項目的靈感來源于上個月在B站看到的"科技怪咖"UP主分享的一個視頻&#xff0c;視頻中展示了一個名為"DataMagic"的自動化數據處理工具&#xff0c;能夠智能分…

物聯網-規則引擎的定義

構建物聯網系統中的規則引擎是一個系統性的工程&#xff0c;它需要處理來自海量設備的實時數據流&#xff0c;并根據預定義的邏輯觸發動作。以下是構建一個高效、可靠、可擴展的物聯網規則引擎的關鍵步驟和考慮因素&#xff1a; 核心目標 實時性&#xff1a; 快速處理設備事件并…

SIMATIC WinCC Unified 使用 KPI 優化流程

大家好&#xff0c;我是東哥說-MES基本知識 33.1 KPI組態簡介現有工廠結構表示在面向對象的組態中定義標準化 KPI 概念的起點。 可通過在工廠視圖中用作實例的工廠對象類型來映射工廠的各組件。在“性能指 標”(Performance indicators) 全局編輯器中&#xff0c;可定義全局操作…

機器學習-多重線性回歸和邏輯回歸

目錄 1. 多重線性回歸 1.1 多元線性回歸 1.2 向量化&#xff08;矢量化&#xff09; 1.3 多元線性回歸的梯度下降算法 1.4 正規方程 2. 特征縮放 2.1 特征縮放 2.2 檢查梯度下降是否收斂 2.3 學習率的選擇 2.4 特征工程 2.5 多項式回歸 3. 邏輯回歸 3.1 Motivatio…