風中低語:Linux 信號處理的藝術與實踐

文章目錄

  • 🌇前言
  • 🏙?正文
  • 1、信號的處理時機
    • 1.1、處理情況
    • 1.2、“合適” 的時機
  • 2、用戶態與內核態
    • 2.1、概念
    • 2.2、重談進程地址空間
    • 2.3、信號的處理過程
  • 3、信號的捕捉
    • 3.1、內核如何實現信號的捕捉?
    • 3.2、sigaction
  • 4、信號部分小結
  • ===== 補充 =====
  • 5、可重入函數
  • 6、volatile
  • 7、SIGCHLD 信號
  • 結語:在風的呢喃中前行

在這里插入圖片描述

🌇前言

在操作系統的長河中,Linux
進程如同并發世界中的浮萍,在調度器的波濤中起伏。而信號(Signal),正是內核賦予這些進程的一縷清風,或是一聲驚雷。它們悄然降臨,又或猛烈襲來,帶來命運的轉折。

本篇報告,將帶你領略 Linux 信號機制的詩意與科學,并以代碼為筆墨,勾勒出它的精妙運行。

🏙?正文

1、信號的處理時機

直奔主題,談談信號的 ·處理時機·

1.1、處理情況

普通情況

所謂的普通情況就是指 信號沒有被阻塞,直接產生,記錄未決信息后,再進行處理

在這種情況下,信號是不會被立即遞達的,也就無法立即處理,需要等待合適的時機

特殊情況

當信號被阻塞 后,信號 產生 時,記錄未決信息,此時信號被阻塞了,也不會進行處理

當阻塞解除后,信號會被立即遞達,此時信號會被立即處理

特殊情況 很好理解,就好比往氣球里吹氣,當氣球炸了,空氣會被立即釋放,因為空氣是被氣球 阻塞 的,當氣球炸了之后(阻塞 解除),空氣立馬往外跑,這不就是 立即遞達、立即處理 嗎?

通情況 就有點難搞了,它需要等待 “合適” 的時機,才能被 遞達,繼而被 處理

1.2、“合適” 的時機

信號的產生是 異步

也就是說,信號可能隨時產生,當信號產生時,**進程可能在處理更重要的事,**此時貿然處理信號顯然不夠明智

比如進程正在執行一個重要的 IO,突然一個終止信號發出,IO 立即終止,對進程、磁盤都不好

因此信號在 產生 后,需要等進程將 更重要 的事忙完后(合適的時機),才進行 處理

合適的時機:進程從 內核態 返回 用戶態 時,會在操作系統的指導下,對信號進行檢測及處理

至于處理動作,分為:默認動作、忽略、用戶自定義

搞清楚 “合適” 的時機 后,接下來需要學習 用戶態內核態 相關知識

2、用戶態與內核態

對于 用戶態、內核態 的理解及引出的 進程地址空間信號處理過程 相關知識是本文的重難點

2.1、概念

先來看看什么是 用戶態內核態

  • 用戶態:執行用戶所寫的代碼時,就屬于 用戶態

  • 內核態:執行操作系統的代碼時,就屬于 內核態

自己寫的代碼被執行很好理解,操作系統的代碼是什么?

  • 操作系統也是由大量代碼構成的
  • 在對進程進行調度、執行系統調用、異常、中斷、陷阱等,都需要借助操作系統之手
  • 此時執行的就是操作系統的代碼

也就是說,用戶態內核態 是兩種不同的狀態,必然存在相互轉換的情況

用戶態 切換為 內核態:

  • 當進程時間片到了之后,進行進程切換動作
  • 調用系統調用接口,比如 open、close、read、write 等
  • 產生異常、中斷、陷阱等

內核態 切換為 用戶態

  • 進程切換完畢后,運行相應的進程
  • 系統調用結束后
  • 異常、中斷、陷阱等處理完畢

信號的處理時機就是 內核態 切換為 用戶態,也就是 當把更重要的事做完后,進程才會在操作系統的指導下,對信號進行檢測、處理

下面來結合 進程地址空間 深入理解 操作系統的代碼狀態切換 的相關內容(拓展知識)

2.2、重談進程地址空間

首先簡單回顧下 進程地址空間 的相關知識:

  • 進程地址空間 是虛擬的,依靠 頁表+MMU機制 與真實的地址空間建立映射關系
  • 每個進程都有自己的 進程地址空間,不同 進程地址空間 中地址可能沖突,但實際上地址是獨立的
  • 進程地址空間 可以讓進程以統一的視角看待自己的代碼和數據
    在這里插入圖片描述
    不難發現,在 進程地址空間 中,存在 1 GB內核空間,每個進程都有,而這 1 GB 的空間中存儲的就是 操作系統相關代碼和數據,并且這塊區域采用 內核級頁表真實地址空間 進行映射

為什么要區分 用戶態內核態

  • 內核空間中存儲的可是操作系統的代碼和數據,權限非常高,絕不允許隨便一個進程對其造成影響
  • 區域的合理劃分也是為了更好的進行管理

在這里插入圖片描述
所謂的 執行操作系統的代碼及系統調用,就是在使用這 1 GB 的內核空間

進程間具有獨立性,比如存在用戶空間中的代碼和數據是不同的,難道多個進程需要存儲多份 操作系統的代碼和數據 嗎?

  • 當然不用,內核空間比較特殊,所有進程最終映射的都是同一塊區域,也就是說,進程只是將 操作系統代碼和數據 映射入自己的 進程地址空間 而已
  • 內核級頁表 不同于 用戶級頁表,專注于對 操作系統代碼和數據 進行映射,是很特殊的

當我們執行諸如 open 這類的 系統調用 時,會跑到 內核空間 中調用對應的函數

而 跑到內核空間 就是 用戶態 切換為 內核態 了(用戶空間切換至內核空間)

這個 跑到 是如何實現的呢?

  • 在 CPU 中,存在一個 CR3 寄存器,這個 寄存器 的作用就是用來表征當前處于 用戶態 還是 內核態

  • 當寄存器中的值為 3 時:表示正在執行用戶的代碼,也就是處于 用戶態

  • 當寄存器中的值為 0 時:表示正在執行操作系統的代碼,也就是處于 內核態

  • 通過一個 寄存器,表征當前所處的 狀態,修改其中的,就可以表示不同的 狀態,這是很聰明的做法

在這里插入圖片描述
重談 進程地址空間 后,得到以下結論

  • 所有進程的用戶空間 [0, 3] GB 是不一樣的,并且每個進程都要有自己的 用戶級頁表 進行不同的映射
  • 所有進程的內核空間 [3, 4] GB 是一樣的,每個進程都可以看到同一張內核級頁表,從而進行統一的映射,看到同一個 操作系統
  • 操作系統運行的本質其實就是在該進程的 內核空間內運行的(最終映射的都是同一塊區域)
  • 系統調用 的本質其實就是在調用庫中對應的方法后,通過內核空間中的地址進行跳轉調用

那么進程又是如何被調度的呢?

1. 操作系統的本質

  • 操作系統也是軟件啊,并且是一個死循環式等待指令的軟件
  • 存在一個硬件:操作系統時鐘硬件,每隔一段時間向操作系統發送時鐘中斷

2. 進程被調度,就意味著它的時間片到了,操作系統會通過時鐘中斷,檢測到是哪一個進程的時間片到了,然后通過系統調用函數 schedule() 保存進程的上下文數據,然后選擇合適的進程去運行

2.3、信號的處理過程

當在 內核態 完成某種任務后,需要切回 用戶態,此時就可以對信號進行 檢測處理

情況1:信號被阻塞,信號產生/未產生

信號都被阻塞了,也就不需要處理信號,此時不用管,直接切回 用戶態 就行了

下面的情況都是基于 信號未被阻塞信號已產生 的前提

情況2:當前信號的執行動作為 默認

大多數信號的默認執行動作都是 終止 進程,此時只需要把對應的進程干掉,然后切回 用戶態 就行了

在這里插入圖片描述

情況3:當前信號的執行動作為 忽略

當信號執行動作為 忽略 時,不做出任何動作,直接返回 用戶態

在這里插入圖片描述

情況4:當前信號的執行動作為 用戶自定義

這種情況就比較麻煩了,用戶自定義的動作位于 用戶態 中,也就是說,需要先切回 用戶態,把動作完成了,重新墜入 內核態,最后才能帶著進程的上下文相關數據,返回 用戶態

內核態 中,也可以直接執行 自定義動作,為什么還要切回 用戶態 執行自定義動作?

  • 因為在 內核態 可以訪問操作系統的代碼和數據,自定義動作 可能干出危害操作系統的事
  • 用戶態 中可以減少影響,并且可以做到溯源

為什么不在執行完 自定義動作 直接后返回進程?

  • 因為 自定義動作待返回的進程 屬于不同的堆棧,是無法返回的
  • 并且進程的上下文數據還在內核態中,所以需要先墜入內核態,才能正確返回用戶態

在這里插入圖片描述
==注意: 用戶自定義的動作,需要先切換至 用戶態中執行,執行結束后,還需要墜入 內核態

通過一張圖快速記錄信號的 處理 過程

在這里插入圖片描述

3、信號的捕捉

接下來談談 信號 是如何被 捕捉

3.1、內核如何實現信號的捕捉?

如果信號的執行動作為 用戶自定義動作,當信號 遞達 時調用 用戶自定義動作,這一動作稱為 信號捕捉

用戶自定義動作 是位于 用戶空間 中的

  • 內核態 中任務完成,準備返回 用戶態 時,檢測到信號 遞達,并且此時為 用戶自定義動作,需要先切入 用戶態 ,完成 用戶自定義動作 的執行;
  • 因為 用戶自定義動作待返回的函數 屬于不同的 堆棧 空間,它們之間也不存在 調用與被調用 的關系,是兩個 獨立的執行流,需要先墜入 內核態 (通過 sigreturn() 墜入),再返回 用戶態 (通過 sys_sigreturn() 返回)

在這里插入圖片描述

3.2、sigaction

sigaction 也可以 用戶自定義動作,比 signal 功能更豐富

在這里插入圖片描述

#include <signal.h>int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);struct sigaction 
{void     (*sa_handler)(int);	//自定義動作void     (*sa_sigaction)(int, siginfo_t *, void *);	//實時信號相關,不用管sigset_t   sa_mask;	//待屏蔽的信號集int        sa_flags;	//一些選項,一般設為 0void     (*sa_restorer)(void);	//實時信號相關,不用管
};

返回值:成功返回 0,失敗返回 -1 并將錯誤碼設置

參數1:待操作的信號

參數2sigaction 結構體,具體成員如上所示

參數3:保存修改前進程的 sigaction 結構體信息

這個函數的主要看點是 sigaction 結構體

其中部分字段不需要管,因為那些是與 實時信號 相關的,我們這里不討論

重點可以看看 sa_mask 字段

sa_mask:當信號在執行 用戶自定義動作 時,可以將部分信號進行屏蔽,直到 用戶自定義動作 執行完成

也就是說,我們可以提前設置一批 待阻塞屏蔽信號集,當執行 signum 中的 用戶自定義動作 時,這些 屏蔽信號集 中的 信號 將會被 屏蔽(避免干擾 用戶自定義動作 的執行),直到 用戶自定義動作 執行完成

可以簡單用一下 sigaction 函數

#include <iostream>
#include <cassert>
#include <cstring>
#include <signal.h>
#include <unistd.h>using namespace std;static void DisplayPending(const sigset_t pending)
{// 打印 pending 表cout << "當前進程的 pending 表為: ";int i = 1;while (i < 32){if (sigismember(&pending, i))cout << "1";elsecout << "0";i++;}cout << endl;
}static void handler(int signo)
{cout << signo << " 號信號確實遞達了" << endl;// 最終不退出進程int n = 10;while (n--){// 獲取進程的 未決信號集sigset_t pending;sigemptyset(&pending);int ret = sigpending(&pending);assert(ret == 0);(void)ret; // 欺騙編譯器,避免 release 模式中出錯DisplayPending(pending);sleep(1);}
}int main()
{cout << "當前進程: " << getpid() << endl;//使用 sigaction 函數struct sigaction act, oldact;//初始化結構體memset(&act, 0, sizeof(act));memset(&oldact, 0, sizeof(oldact));//初始化 自定義動作act.sa_handler = handler;//初始化 屏蔽信號集sigaddset(&act.sa_mask, 3);sigaddset(&act.sa_mask, 4);sigaddset(&act.sa_mask, 5);//給 2號 信號注冊自定義動作sigaction(2, &act, &oldact);// 死循環while (true);return 0;
}

在這里插入圖片描述

當 2 號信號的循環結束(10 秒),3、4、5 信號的 阻塞 狀態解除,立即被 遞達,進程就被干掉了

注意: 屏蔽信號集 ·sa_mask· 中已屏蔽的信號,在 ·用戶自定義動作· 執行完成后,會自動解除 ·阻塞· 狀態

4、信號部分小結

截至目前,信號 處理的所有過程已經全部學習完畢了

信號產生階段:有四種產生方式,包括 鍵盤鍵入、系統調用,軟件條件、硬件異常

信號保存階段:內核中存在三張表,blcok 表、pending 表以及 handler 表,信號在產生之后,存儲在 pending 表中

信號處理階段:信號在 內核態 切換回 用戶態 時,才會被處理
在這里插入圖片描述

===== 補充 =====

下面是一些補充知識

5、可重入函數

可以被重復進入的函數稱為 可重入函數

比如單鏈表頭插的場景中,節點 node1 還未完成插入時,node2 也進行了頭插,最終導致 節點 node2 丟失,造成 內存泄漏

在這里插入圖片描述

導致 內存泄漏 的罪魁禍首:對于 node1node2 來說,操作的 單鏈表 是同一個,同時進行并發訪問(重入)會出現問題的,因為此時的 單鏈表 是臨界資源

我們學過的函數中,90% 都是 不可重入

函數是否可重入是一個特性,而非缺點,需要正確看待

不可重入的條件:

  • 調用了內存管理相關函數
  • 調用了標準 I/O 庫函數,因為其中很多實現都以不可重入的方式使用數據結構

6、volatile

volatile 關鍵字可以避免 編譯器 的優化,保證內存的 可見性

比如在下面這個例子中

借助全局變量 flag 設計一個死循環的場景,在此之前將 2 號信號進行自定義動作捕捉,具體動作為:將 flag 改為 1,可以終止 main 函數中的循環體

#include <stdio.h>
#include <signal.h>int flag = 0;   // 一開始為假void handler(int signo)
{printf("%d號信號已經成功發出了\n", signo);flag = 1;
}int main()
{signal(2, handler);while(!flag);   // 故意不寫 while 的代碼塊 { }printf("進程已退出\n");return 0;
}

在這里插入圖片描述

初步結果符合預期,2 號信號發出后,循環結束,程序正常退出

這段代碼能符合我們預期般的正確運行是因為 當前編譯器默認的優化級別很低,沒有出現意外情況

通過指令查詢 gcc 優化級別的相關信息

man gcc
: /O1

在這里插入圖片描述
其中數字越大,優化級別越高,理論上編譯出來的程序性能會更好

事實真的如此嗎?

讓我們重新編譯上面的程序,并指定優化級別為 O1

gcc test2 test2.c -O1

編譯成功后,再次運行程序
在這里插入圖片描述
此時得到了不一樣的結果:2 號信號發出后,對于 flag 變量的修改似乎失效了

將優化級別設為更高是一樣的結果,如果設為 O0 則會符合預期般的運行,說明我們當前的編譯器默認的優化級別是 O0

查看編譯器的版本

gcc --version

在這里插入圖片描述
那么我們這段代碼哪個地方被優化了呢?

答案是 while 循環判斷
首先要明白:

  • 對于程序中的數據,需要先被 loadCPU 中的 寄存器 中
  • 判斷語句所需要的數據(比如 flag),在進行判斷時,是從 寄存器 中拿取并判斷
  • 根據判斷的結果,判斷代碼的下一步該如何執行(通過 PC 指針指向具體的代碼執行語句)
  • 所以程序在優化級別為 O0 或更低時,是這樣執行的:
    在這里插入圖片描述

7、SIGCHLD 信號

進程控制 學習時期,我們明白了一個事實:父進程必須等待子進程退出并回收,并為其 “收尸”,避免變成 “僵尸進程” 占用系統資源、造成內存泄漏

那么 父進程是如何知道子進程退出了呢?

在之前的場景中,父進程要么就是設置為 阻塞式專心等待,要么就是 設置為 WNOHANG 非阻塞式等待,這兩種方法都需要 父進程 主動去檢測 子進程 的狀態

如今學習了 進程信號 相關知識后,可以思考一下:子進程真的是安安靜靜的退出的嗎?

  • 答案當然不是,子進程在退出后,會給父進程發送 SIGCHLD 信號
  • 可以通過 SIGCHLD 信號 通知 父進程子進程 要退出了,這樣可以解放 父進程,不必再去 主動檢測 ,而是 子進程 要退出的時候才通知其來 “收尸”

在這里插入圖片描述
SIGCHLD 信號比較特殊,默認動作 SIG_DEF 是 什么都不做

首先通過程序證明一下子進程會發出 SIGCHLD 信號

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>void handler(int signo)
{printf("進程 %d 捕捉到了 %d 號信號\n", getpid(), signo);
}int main()
{signal(SIGCHLD, handler);pid_t id = fork();if(id == 0){int n = 5;while(n)printf("子進程剩余生存時間: %d秒 [pid: %d  ppid: %d]\n", n--, getpid(), getppid());// 子進程退出exit(-1);}waitpid(id, NULL, 0);return 0;
}

通過自定義捕捉,打印相關信息

在這里插入圖片描述
因此可以證明 SIGCHLD 是被子進程真實發出的,當然,我們可以自定義捕捉動作為 回收子進程,讓父進程不再主動檢測子進程的狀態,可以自己忙自己的事

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>pid_t id;   // 將子進程的id設為全局變量,方便對比void handler(int signo)
{printf("進程 %d 捕捉到了 %d 號信號\n", getpid(), signo);// 這里的 -1 表示父進程等待時,只要是已經退出了的子進程,都可以進行回收pid_t ret = waitpid(-1, NULL, 0);if(ret > 0)printf("父進程: %d 已經成功回收了 %d 號進程,之前的子進程是 %d\n", getpid(), ret, id);
}int main()
{signal(SIGCHLD, handler);id = fork();if(id == 0){int n = 5;while(n){printf("子進程剩余生存時間: %d秒 [pid: %d  ppid: %d]\n", n--, getpid(), getppid());sleep(1);}// 子進程退出exit(-1);}// 父進程很忙的話,可以去做自己的事while(1){// TODOprintf("父進程正在忙...\n");sleep(1);}return 0;
}

父進程和子進程各忙各的,子進程退出后會發信號通知父進程,并且能做到正確回收

在這里插入圖片描述
那么這種方法就一定對嗎?

  • 答案是不一定,在只有一個子進程的場景中,這個代碼沒問題,但如果是涉及多個子進程回收時,這個代碼就有問題了

  • 根本原因:SIGCHLD 也是一個信號啊,它可能也會在 block 表和 pending 表中被置為 1,當多個子進程同時向父進程發出信號時,父進程只能先回收最快發出信號的子進程,并將隨后發出信號的子進程 SIGCHLD 信號保存在 blcok 表中,除此之外,其他的子進程信號就丟失了,父進程處理完這兩個信號后,就認為沒有信號需要處理了,這就造成了內存泄漏

  • 解決方案:自定義捕捉函數中,采取 while 循環式回收,有很多進程都需要回收沒問題,排好隊一個個來就好了,這樣就可以確保多個子進程同時發出SIGCHLD信號時,可以做到一一回收

  • 細節:多個子進程運行時,可能有的退了,有的沒退,這會導致退了的子進程發出信號后,觸發自定義捕捉函數中的循環等待機制,回收完已經退出了的子進程后,會阻塞式的等待還沒有退出的子進程,如果子進程一直不退,就會一直被阻塞,所以我們需要把進程回收設為 WNOHANG 非阻塞式等待

正確的代碼長這樣:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>void handler(int signo)
{printf("進程 %d 捕捉到了 %d 號信號\n", getpid(), signo);// 這里的 -1 表示父進程等待時,只要是已經退出了的子進程,都可以進行回收while (1){pid_t ret = waitpid(-1, NULL, WNOHANG);if (ret > 0)printf("父進程: %d 已經成功回收了 %d 號進程\n", getpid(), ret);elsebreak;}printf("子進程回收成功\n");
}int main()
{signal(SIGCHLD, handler);// 創建10個子進程int n = 10;while (n--){pid_t id = fork();if (id == 0){int n = 5;while (n){printf("子進程剩余生存時間: %d秒 [pid: %d  ppid: %d]\n", n--, getpid(), getppid());sleep(1);}// 子進程退出exit(-1);}}// 父進程很忙的話,可以去做自己的事while (1){// TODOprintf("父進程正在忙...\n");sleep(1);}return 0;
}

在這里插入圖片描述
其實還有一種更加優雅的子進程回收方案

由于 UNIX 歷史原因,要想子進程不變成 僵尸進程,可以把 SIGCHLD 的處理動作設為 SIG_IGN 忽略,這里的忽略是個特例,只是父進程不對其進行處理,但只要設置之后,子進程在退出時,由 操作系統 對其負責,自動清理資源并進行回收,不會產生 僵尸進程

也就是說,直接在父進程中使用 signal(SIGCHLD, SIG_IGN) 就可以優雅的解決 子進程回收問題,父進程既不用等待,也不需要對信號做出處理

原理在設置 SIGCHLD信號的處理動作為忽略后,父進程的 PCB 中有關僵尸進程處理的標記位會被修改,子進程繼承父進程的特性,子進程在退出時,操作系統檢測到此標記位發生了改變,會直接把該子進程進行釋放

SIGCHLD 的默認處理動作是忽略(什么都不做),而忽略動作是讓操作系統幫忙回收,父進程不必關心

注意: 這種情況很特殊,只能保證在 Linux 系統中有效,其他類UNIX系統中可能沒啥用

結語:在風的呢喃中前行

信號在 Linux
世界中,是一場場悄然到來的變奏。它讓進程更加靈動、系統更加優雅。而每一個信號處理函數,都是程序在面對突發時的一種從容回應。

如同在風中舞蹈的詩人,程序員在信號處理的細節中,寫下的不僅是代碼,更是與系統對話的詩行。

本篇關于信號處理的介紹就暫告段落啦,希望能對大家的學習產生幫助,歡迎各位佬前來支持斧正!!!

在這里插入圖片描述

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

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

相關文章

ASP.NET Core SignalR - 部分客戶端消息發送

文章目錄 前言一、消息發送的核心概念1.客戶端標識2.消息接收范圍 二、向特定用戶發送消息管理員向指定用戶發送私信&#xff0c;或用戶之間一對一聊天。 三、向組發送消息聊天室、工作群組、通知訂閱等。 四、廣播消息系統公告、實時統計數據更新等。 五、向角色發送消息向管理…

前后端交互過程中—各類文件/圖片的上傳、下載、顯示轉換

前后端交互過程中—各類文件/圖片的上傳、下載、顯示轉換 圖片上傳下載常用函數&#xff1a;new Blob()**blobParts&#xff1a;&#xff08;必傳&#xff09;****options&#xff1a;&#xff08;可選&#xff09;**blob的常見的MIME類型&#xff1a; URL.createObjectURL()替…

校園二手交易平臺(微信小程序版)

文章目錄 1. 項目概述2. 項目功能思維導圖3. 技術架構1. 前端技術棧2. 后端技術棧 4. 核心模塊實現5. 總結6. 項目實現效果截圖7. 關于作者其它項目視頻教程介紹 1. 項目概述 校園二手交易平臺微信小程序旨在為在校學生提供一個便捷的二手物品交易渠道&#xff0c;包含用戶模塊…

Linux簡單的操作

ls ls 查看當前目錄 ll 查看詳細內容 ls -a 查看所有的內容 ls --help 查看方法文檔 pwd pwd 查看當前路徑 cd cd 轉路徑 cd .. 轉上一級路徑 cd 名 轉換路徑 …

【芯片設計- RTL 數字邏輯設計入門 4.2 -- 組合邏輯賦值 + 時序邏輯狀態保持】

文章目錄 Overview原語句分析變量含義假設(根據命名推測)狀態更新邏輯詳解狀態轉移邏輯舉個實際例子小結Overview 本文將詳細介紹 verilog rtl 中 assign reg_halt_mode_nx = halt_taken | (reg_halt_mode & ~halt_return);的作用,以及這里為何要使用 reg_halt_mode,…

【單片機期末】匯編試卷

一、選擇題 DPTR是16位的&#xff0c;所以尋址范圍是64KB R1是8位的&#xff0c;只能尋址256 訪問內部ROM只能用MOVC指令 一個指令周期是時鐘周期的1/12 12個時鐘周期是一個機器周期 單指令周期是指一個機器周期 T 1 / f 12MHz ~ 1us 13位計數16位計數8位自動重裝載雙8位計數器…

校驗枚舉類類型的入參合法性的統一方案

文章目錄 背景解決實踐定義枚舉類 InEnum注解定義驗證邏輯 InEnumValidator 實際使用 背景 業務要做電商平臺做入參, 在電商平臺被抽離成枚舉類的情況下 &#xff0c;要怎么驗證輸入的參數是正確的呢? 解決 Constraint 實現自定義驗證邏輯 Constraint 注解用于標注其他注解&am…

Unity-NavMesh詳解-其一

今天我們來詳細地探究一下Unity的NavMesh這一性能強大的組件&#xff1a; NavMesh基本使用 NavMesh簡單地說本質上是一個自動尋路的AI組件&#xff0c;我們首先來學習基本的使用。 畫面中我已經添加好了地面&#xff0c;目標&#xff0c;障礙物以及玩家四個要素。 注意我們要…

vue的created和mounted區別

在Vue.js中&#xff0c;created和mounted的核心區別在于調用時機和DOM可訪問性?&#xff1a;created鉤子在組件實例創建后、DOM掛載前調用&#xff0c;適用于數據初始化&#xff1b;mounted鉤子在DOM掛載后調用&#xff0c;支持DOM操作。?? ?調用時機與核心能力對比? ?…

MySQL 8.0 OCP 英文題庫解析(十四)

Oracle 為慶祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免費考取原價245美元的MySQL OCP 認證。 從今天開始&#xff0c;將英文題庫免費公布出來&#xff0c;并進行解析&#xff0c;幫助大家在一個月之內輕松通過OCP認證。 本期公布試題121~130 試題1…

【HarmonyOS 5】拍攝美化開發實踐介紹以及詳細案例

以下是 HarmonyOS 5 拍攝美化功能的簡潔介紹&#xff0c;整合核心能力與技術亮點&#xff1a; 一、AI 影像創新 ?AI 魔法移圖? 系統級圖像分層技術實現人物/物體自由拖拽、縮放與復制&#xff0c;突破傳統構圖限制。自動分離主體與背景&#xff0c;一鍵生成錯位創意照&…

【Java多線程從青銅到王者】懶漢模式的優化(九)

懶漢模式的問題 我們看上述的代碼&#xff0c;當第一次調用getIntance的時候&#xff0c;intance為null&#xff0c;就會進入if里面&#xff0c;創建出實例&#xff0c;當不是第一次調用的時候&#xff0c;此時的intandce不是null&#xff0c;不進入循環&#xff0c;直接return…

SCI期刊查重參考文獻會被查重嗎?

查重的時候&#xff0c;參考文獻不會被查重。 不管中文還是英文查重系統里一般都有排除參考文獻的設置。 比如英文查重系統iThenticate 的排除文獻的設置如下&#xff1a; 在iThenticate在線報告界面的右下角點擊“漏斗”圖標&#xff08;Filter&#xff09;&#xff0c; ?…

OpenLayers 獲取地圖狀態

注&#xff1a;當前使用的是 ol 5.3.0 版本&#xff0c;天地圖使用的key請到天地圖官網申請&#xff0c;并替換為自己的key 地圖狀態信息包括中心點、當前縮放級別、比例尺以及當前鼠標移動位置信息等&#xff0c;在WebGIS開發中&#xff0c;地圖狀態可以方便快捷的向用戶展示基…

JxBrowser 8.8.0 版本發布啦!

一次調用即可下載文件精準清除瀏覽數據右鍵點擊位置檢測獲取元素在視口中的位置 &#x1f517; 點擊此處了解更多詳情。 &#x1f193; 獲取 30 天免費試用。

React 中的TypeScript開發范式

在 TypeScript 中使用 React 可以提高代碼的可維護性、可讀性和可靠性。TypeScript 提供了靜態類型檢查和豐富的類型系統&#xff0c;這些功能在 React 開發中非常有用。下面詳細介紹如何在 React 項目中使用 TypeScript&#xff0c;并結合泛型和 infer 來定義類型。 1. 項目初…

72道Nginx高頻題整理(附答案背誦版)

1. 簡述什么是Nginx &#xff1f; Nginx 是一個開源的高性能HTTP和反向代理服務器&#xff0c;也能夠用作IMAP/POP3/SMTP代理服務器。它最初由Igor Sysoev為俄羅斯的一個大型網站Rambler開發&#xff0c;并在2004年首次公開發布。Nginx被設計用來解決C10k問題&#xff0c;即同…

AI時代,數據分析師如何成為不可替代的個體

在數據爆炸的 AI 時代&#xff0c;AI工具正以驚人的速度重塑數據分析行業&#xff0c;數據分析師的工作方式正在經歷一場前所未有的變革。數據分析師又該如何破局&#xff0c;讓自己不被AI取代呢&#xff1f; 一、AI工具對重復性工作的徹底解構 如以往我們需要花幾天寫一份數…

DockerHub與私有鏡像倉庫在容器化中的應用與管理

哈嘍&#xff0c;大家好&#xff0c;我是左手python&#xff01; Docker Hub的應用與管理 Docker Hub的基本概念與使用方法 Docker Hub是Docker官方提供的一個公共鏡像倉庫&#xff0c;用戶可以在其中找到各種操作系統、軟件和應用的鏡像。開發者可以通過Docker Hub輕松獲取所…

Kafka入門-Broker以及文件存儲機制

Kafka Broker Broker實際上就是kafka實例&#xff0c;每一個節點都是獨立的Kafka服務器。 Zookeeper中存儲的Kafka信息 節點的服役以及退役 服役 首先要重新建立一臺全新的服務器105&#xff0c;并且在服務器中安裝JDK、Zookeeper、以及Kafka。配置好基礎的信息之后&#x…