(學習總結29)Linux 進程概念和進程狀態

Linux 進程概念

  • 馮·諾依曼體系結構
    • 軟件運行與存儲分級
    • 數據流動的理論過程
  • 操作系統
    • 操作系統(Operator System) 概念
    • 操作系統的功能與作用
    • 系統調用和庫函數概念
  • 進程概念
    • 描述進程 - PCB
    • task_struct
    • 查看進程
    • 通過系統調用獲取進程標示符 PID
    • 通過系統調用 fork 函數創建進程
      • 簡單使用
      • 區分父子進程操作
      • 父子進程的寫時拷貝
  • 進程狀態
    • 具體的 Linux 內核解釋
      • 運行、阻塞 和 掛起狀態
      • 進程如何被轉移
    • Linux 進程狀態
    • 僵尸進程與其危害
      • 僵尸進程危害
    • 孤兒進程

以下代碼環境為 Linux Ubuntu 22.04.5 gcc C語言

馮·諾依曼體系結構

我們生活中的計算機大部分都遵守馮·諾依曼體系,如筆記本、服務器等等。

在這里插入圖片描述

而計算機都是由一個個的硬件組件組成的:

  • 輸入設備:包括鍵盤、鼠標、掃描儀、寫板等。
  • 存儲器:內存。
  • 中央處理器(CPU):含有運算器和控制器等。
  • 輸出設備:顯示器、打印機等。

如果再精確一點說明,則有:

  • 這里的存儲器確切是指內存。
  • 不考慮緩存情況,這里的 CPU 只能對內存進行讀寫,不能訪問外設(輸入或輸出設備)。
  • 外部設備(輸入或輸出設備) 要輸入或者輸出數據,也只能寫入內存或者從內存中讀取。
  • 可以肯定的是,所有設備都只能直接和內存打交道。

軟件運行與存儲分級

軟件在運行之前,會先存儲在磁盤或其它外部存儲設備中。當需要運行軟件時,會將軟件的程序加載到內存當中,然后由 CPU 獲取來執行程序,處理程序邏輯,最后由顯示器等輸出設備顯示結果。

考慮到 CPU 的處理速度非常快,這個體系之下還會細分很多的存儲設備,目的就是為了盡可能不拖慢 CPU 的速度。

在這里插入圖片描述

數據流動的理論過程

當我們使用這個體系結構的計算機進行信息交流,就一定會通過 輸入設備 -> 載入內存 + CPU運算 -> 輸出設備 這個步驟:

在這里插入圖片描述

操作系統

操作系統(Operator System) 概念

任何計算機系統都包含一個基本的程序集合,稱為操作系統(OS)。

更廣泛上的操作系統包括:

  • 內核(包括:進程管理、內存管理、文件管理、驅動管理等)
  • 其他程序(例如函數庫,shell程序 等等)

在這里插入圖片描述

操作系統的功能與作用

在整個計算機軟硬件架構中,操作系統的功能定位是一款搞 " 管理 " 的軟件,它的作用是讓應用程序正常執行,具體為:

  • 對下,與硬件交互和管理所有的軟硬件資源。
  • 對上,為用戶程序(應用程序)提供一個良好的執行環境。

在這里插入圖片描述
在上圖中可以看到:

  • 軟硬件體系結構為層狀結構,各層也設計成 高內聚低耦合,方便各個部分自己更新迭代。

  • 訪問操作系統就必須使用系統提供的系統調用接口。

  • 若用戶程序訪問硬件,則一定會貫穿整個軟硬件體系結構!

那操作系統如何 " 管理 " 呢?

  1. 先描述被管理對象:使用 struct 結構體(使用的是 C語言)構建被管理對象的數據。

  2. 再組織被管理對象:使用高效的數據結構組織被管理對象。

總結起來就是一句話:先描述,再組織

系統調用和庫函數概念

在開發角度,操作系統對外會表現為一個整體,但是會暴露自己的部分接口,供上層開發使用,這部分由操作系統提供的接口,叫做系統調用

系統調用在使用上,功能比較基礎,對用戶的要求相對也比較高,所以有的開發者對部分系統調用進行適度封裝,形成庫。有了庫,就利于更上層用戶或者開發者進行二次開發。

進程概念

從課本概念出發:程序的一個執行實例,正在執行的程序等。

從內核觀點出發:擔當分配系統資源(CPU時間,內存)的實體。

更具體的說進程:進程 = 內核數據結構元素 + 進程的代碼和數據

描述進程 - PCB

基本概念

  • 進程信息被放在一個叫做進程控制塊的數據結構中,可以理解為進程屬性的集合。

  • 概念上稱之為 PCB(process control block),在 Linux 操作系統下的 PCB 是: task_struct

task_struct 為 PCB 的一種

  • 在 Linux 中描述進程的結構體叫做 task_struct

  • task_struct 是 Linux 內核的一種數據結構,它會被裝載到 RAM(內存) 里并且包含著進程的屬性信息。

在這里插入圖片描述

task_struct

內容分類

  • 標示符:描述本進程的唯一標示符,用來區別其它進程。
  • 狀態:任務狀態,退出代碼,退出信號等。
  • 優先級:相對于其它進程的優先級。
  • 程序計數器:程序中即將被執行的下一條指令的地址。
  • 內存指針:包括程序代碼和進程相關數據的指針,還有和其它進程共享的內存塊的指針。
  • 上下文數據:進程執行時處理器的寄存器中的數據。
  • I/O 狀態信息:包括顯示的 I/O 請求,分配給進程的 I/O 設備和被進程使用的文件列表。
  • 記賬信息:可能包括處理器時間總和,使用的時鐘數總和,時間限制,記賬號等。
  • 其它信息…

組織進程

可以在 Linux 的內核源代碼里找到它,所有運行在系統里的進程都以 task_struct 鏈表的形式存在內核里。

查看進程

  1. 進程的信息可以通過 /proc 系統文件夾查看。如:要獲取 PID 為 1 的進程信息,則需要查看 /proc/1 這個文件夾。
    在這里插入圖片描述
    在這里插入圖片描述

  2. 大多數進程信息同樣可以使用 topps 這些用戶級工具命令來獲取。

另外可以注意到 OS 會給每個登錄用戶分配一個 bash 進程。

通過系統調用獲取進程標示符 PID

通過查看 man 手冊可以知道在代碼層面進程的 PID 如何獲取:
在這里插入圖片描述
我們可以用代碼測試看看:

#include <stdio.h>                                                                                                                                                       
#include <unistd.h>int main()
{printf("當前進程的 PID 為 %d\n", getpid());printf("當前進程父進程的 PID 為 %d\n", getppid());return 0;
}   

在這里插入圖片描述

通過系統調用 fork 函數創建進程

簡單使用

通過查看 man 手冊可以知道在代碼層面創建進程的 fork 函數信息:
在這里插入圖片描述
我們可以測試下面的代碼,父進程進入 fork 函數時,會創建子進程,最后兩者一起從 fork 函數出來執行 PID 的打印:

#include <stdio.h>
#include <unistd.h>int main()
{printf("父進程 PID 為 %d\n", getpid());fork();									// 父進程進入創建子進程,fork() 調用完后兩者同時出來   	printf("進程 PID 為 %d\n", getpid());  	// 父子都獨自打印自己的 PID                                                                                                                          return 0;
}

在這里插入圖片描述

區分父子進程操作

這也就意味著,當父進程進入 fork 函數創建子進程時,兩者代碼一樣,執行代碼命令一樣,在上面的代碼中 fork 函數后的執行操作就是一樣的。

為了區分父子進程,如果是子進程 fork 的返回值規定為 0,如果是父進程則返回大于 0 的數,小于 0 說明創建子進程失敗。

接下來測試返回值是否是這樣規定的:

#include <stdio.h>
#include <unistd.h>int main()
{int ret = fork();           // ret 同時接收 fork 返回的兩個值if (ret < 0)    {   perror("fork");         // 小于 0 表示調用失敗return 0;}   else if (ret == 0)			// 0 為子進程{   int child = 2;while (child--){   printf("我是子進程,我的 PID 為 %d\n", getpid());printf("我的父進程 PID 為 %d\n", getppid());};  }   else						// 大于 0 為父進程{   sleep(3);int parent = 3;while (parent--){   printf("我是父進程,我的 PID 為 %d\n", getpid());}                                                                                                                                                                    }   return 0;
}

在這里插入圖片描述
可以看到,通過分支語句可以讓兩個進程共享代碼的情況下去執行不同的代碼。

這意味著:

  1. fork 函數有兩個返回值,父進程有一個,子進程有一個

  2. 父子進程代碼共享,數據各自開辟(寫時拷貝節省空間)

  3. fork 調用之后通常需要使用 if 分支語句進行分流

父子進程的寫時拷貝

另外我們注意到 ret 變量接收 fork 函數返回值,竟然出現不一樣的 if 語句跳轉。事實上,如果修改父子任何一方的數據,OS 會將被修改數據在底層拷貝一份,讓目標進程修改這個拷貝,這種做法叫寫時拷貝

#include <stdio.h>
#include <unistd.h>int main()
{int a = 10; int b = 20; int ret = fork();if (ret < 0){   perror("fork");return 0;}   else if (ret == 0){   b = 10; printf("我是子進程,我的 PID 為 %d\n", getpid());printf("我的 ret 變量的值為 %d\n", ret);                                                                                                                             printf("子進程的 a == %d, &a == %p\n", a, &a);printf("子進程的 b == %d, &b == %p\n", b, &b);}   else{   sleep(2);   // 父進程等待 2 秒,讓子進程先修改。                                                                                                                     printf("我是父進程,我的 PID 為 %d\n", getpid());printf("我的 ret 變量的值為 %d\n", ret);printf("父進程的 a == %d, &a == %p\n", a, &a);printf("父進程的 b == %d, &b == %p\n", b, &b);}   return 0;
}

在這里插入圖片描述

可以看到,兩個進程的 b 地址一樣,子進程修改后,父進程的 b 變量竟然沒有變!

這說明進程變量的地址不是物理地址,而是虛擬的地址!雖然兩者地址一樣,但是底層的物理地址一定不一樣。

這可以說明接收 fork 函數的 ret 變量在被修改時,兩個進程的 ret 已經不一樣了(寫時拷貝執行),if 判斷的值自然不一樣。

也就是說,進程具有獨立性,在大部分運行情況不受其它進程影響。

但為什么 fork 函數返回的值大于 0 為父進程,等于 0 為子進程呢?

因為大于 0 的值實際是子進程的 PID,一個父進程可以有多個子進程,其 PID 拿給父進程用于管理(上述子進程的 PID 由父進程的 ret 保管)。而子進程可以使用 getpid() 函數拿到自己的 PID,使用 getppid() 函數拿到父進程的 PID。其 ret 拿取沒有必要,則規定 0 為子進程。

進程狀態

在操作系統的概念上說,進程狀態有:創建、就緒、運行、阻塞、掛起、結束等狀態。
在這里插入圖片描述

具體的 Linux 內核解釋

運行、阻塞 和 掛起狀態

在具體的操作系統也就是 Linux 中,每個 CPU 有一個調度隊列 runqueue,只要在 runqueue 調度隊列中的進程就算在運行中(也就是包含就緒和運行狀態)。
在這里插入圖片描述

當進程進入阻塞狀態,通常是在等待某種設備或資源就緒,如:C語言的 scanf 函數會等待用戶在鍵盤輸入內容加回車后再繼續向下執行,若用戶不輸入,則一直處于阻塞狀態。

在阻塞狀態時,進程會脫離調度隊列被分配到其它等待隊列:
在這里插入圖片描述

如果鍵盤輸入數據后,OS 會第一時間知道并將獲取數據的進程加入調度隊列,讓該進程進入運行狀態。

那什么是掛起狀態呢?當內存空間不夠時,為保證 OS 本身正常運行,OS 不得不將部分沒有使用的進程的代碼和數據部分,臨時的放入磁盤交換分區。此時在內存中的進程只有 task_struct,代碼和數據卻放在了磁盤,這就叫做掛起狀態。
在這里插入圖片描述
如果輪到當前的進程運行、獲取數據,OS 又會將對應進程的代碼和數據歸還給進程。

進程如何被轉移

上述中我們注意到一個問題,當進程從 runqueue 分配到 wait queue 時,進程是如何被轉移的?

事實上,Linux 進程的 PCB(tast_struct) 使用的是特殊的雙鏈表結構 struct list_task,這個結構只包含 next 和 prev,用于指向后一個節點(task_struct)和前一個節點。在一個 task_struct 中包含多個 struct list_task 就可以在不同隊列轉移。

當然,這個結構本身不能獲取整個 task_struct 結構的信息,但可以使用 C語言的 offsetof 找到其相對于 task_struct 的偏移值,通過 地址移動 + 強制類型轉換,即可在不同的隊列中獲取 task_struct 完整的結構。

在這里插入圖片描述

這意味著進程被操作時,刪除與插入到其它隊列的時間復雜度都是 O(1) 級別,極大的提高了效率。

Linux 進程狀態

Linux 對進程的狀態具體規定了以下幾種(在 Linux 內核里,進程也叫做任務 task)。以下的狀態是在 Linux 內核源代碼里定義的,可能不適用于其它 OS:

  1. R(running)運行狀態:并不意味著進程一定在運行中,只表明進程要么是在運行中要么在運行隊列(調度隊列)里。

  2. S(sleeping)睡眠狀態:意味著進程在等待事件完成(這里的睡眠有時候也叫做 可中斷睡眠(interruptible sleep) )。

  3. D(Disk sleep)磁盤休眠狀態:有時候也叫不可中斷睡眠狀態(uninterruptible sleep) ,在這個狀態的進程通常會等待 I/O 的結束。

  4. T(stopped)停止狀態:可以通過發送 SIGSTOP 信號讓進程停止下來,再發送 SIGCONT 信號讓進程繼續運行。

  5. t(tracing stop)跟蹤停止狀態:當進程被調試如 gdb 調試代碼程序,在斷點處停止時的狀態。

  6. X(dead)死亡狀態:這個狀態只是一個返回狀態,不能在任務列表里看到這個狀態。

  7. Z(zombie)僵尸狀態:為了獲取進程退出信息的臨時狀態。

我們著重查看僵尸狀態,讓子進程只打印自己的 PID 就退出,而父進程則執行死循環:

#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>int main()
{int ret = fork();if (ret < 0){   perror("fork");return 0;}   else if (ret == 0){   printf("我是子進程,我的 PID 為: %d\n", getpid()); // 子進程執行后退出,但父進程沒有回收,會一直保持僵尸狀態}   else {   printf("我是父進程,我的 PID 為: %d\n", getpid()); // 父進程執行死循環while (true){                                                                                                                                                                    ;   }   }   return 0;
}

在這里插入圖片描述
可以看到,子進程狀態處于 Z 也就是僵尸狀態,而父進程使用 Ctrl + Z 快捷鍵正處于暫停狀態,grep 命令也是一個進程,ps 命令查看正好將其打印出來,它此時處于睡眠狀態,S++ 號表示是前臺進程,沒有則表示后臺進程。

另外,D 狀態下的進程是無法被 OS 殺掉的,這是為保證重要的數據正常處理,不受 OS 在內存不足時的干擾。但這意味著除非它自己的任務完成,然后自己結束,想要手動結束就只能讓電腦關機或斷電了。

僵尸進程與其危害

上述的僵尸狀態(zombie)是一個比較特殊的狀態。當進程退出并且父進程沒有讀取到子進程退出的返回代碼時就會產生僵尸進程。

僵尸進程會以終止狀態保持在進程表中,并且會一直在等待父進程讀取退出狀態代碼。

僵尸進程危害

進程的退出狀態必須被維持下去,因為它要告訴與它聯系的進程(父進程)自己任務處理的情況。可父進程一直不讀取,那子進程就會一直處于 Z 狀態。

維護退出狀態本身就是要用數據維護(此時進程的代碼和數據已經沒有了),也屬于進程基本信息,所以保存在 task_struct 中。換句話說,Z 狀態一直不退出,task_struct 就一直要維護,這就導致內存被浪費,也就是內存泄漏。

如果一個父進程創建很多子進程,但不讀取,自己也不退出,就會造成大量的內存資源浪費。

如何讓父進程讀取子進程狀態?使用 wait() 系統調用(有機會再剖析解釋)。

孤兒進程

父進程如果提前退出,而子進程后退出,進入 Z 之后,子進程就稱之為 " 孤兒進程 "

孤兒進程會被 1 號 init 或 systemd 進程領養,此時由 init / systemd 進程處理子進程,防止內存泄漏:

#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>int main()
{int ret = fork();if (ret < 0){   perror("fork");return 0;}   else if (ret == 0){   printf("我是子進程,我的 PID 為: %d\n", getpid());  // 子進程死循環                                                                                                  while (true){   ;   }   }   else {   printf("我是父進程,我的 PID 為: %d\n", getpid()); // 父進程打印退出}   return 0;
}

在這里插入圖片描述
可以看到子進程被 1 號進程 " 領養 ",子進程退出變為僵尸狀態,而 1 號進程會定期調用 wait() 回收所有孤兒進程的狀態信息,確保其不會長期滯留為僵尸進程。

當然,這種方法只是兜底策略,如果在父進程長期運行的環境下(如服務器),不注意回收子進程的內存泄漏風險依然存在。

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

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

相關文章

MySQL密碼修改的全部方式一篇詳解

本文將詳細介紹多種修改MySQL密碼的方式。 本文目錄 一、alter user 語句操作步驟 二、set password操作步驟 三、直接修改 mysql.user表操作步驟 一、alter user 語句 當你以 root 用戶或者擁有足夠權限的用戶登錄 MySQL 時&#xff0c;可以使用 ALTER USER 語句來修改密碼。…

Wi-Fi NAN 架構(Wi-Fi Aware Specification v4.0,第2章:2.3~2.6)

1. NAN 數據通信架構 1.1 單播支持 要在兩個NAN設備之間啟動單播數據通信&#xff0c;服務需發起一個NAN數據路徑&#xff08;NDP&#xff0c;NAN Data Path&#xff09;請求。這對NAN設備之間會建立一個NAN設備鏈路&#xff08;NDL&#xff0c;NAN Device Link&#xff09;&…

Lineageos 22.1(Android 15)實現負一屏

一、前言 方案是參考的這位大佬的&#xff0c;大家可以去付費訂閱支持一波。我大概理一下Android15的修改。 大佬的方案代碼 二、Android15適配調整 1.bp調整&#xff0c;加入aidl引入&#xff0c;這樣make之后就可以索引代碼了 filegroup {name: "launcher-src"…

Java 大視界 -- Java 大數據在智能醫療遠程會診與專家協作中的技術支持(146)

&#x1f496;親愛的朋友們&#xff0c;熱烈歡迎來到 青云交的博客&#xff01;能與諸位在此相逢&#xff0c;我倍感榮幸。在這飛速更迭的時代&#xff0c;我們都渴望一方心靈凈土&#xff0c;而 我的博客 正是這樣溫暖的所在。這里為你呈上趣味與實用兼具的知識&#xff0c;也…

Sqlite3數據庫

工具庫的使用&#xff1a;程序編寫時#include <庫名.h>即可調用庫中的函數 編譯時鏈接工具庫&#xff1b; 注意&#xff1a;數據庫中不區分字母大小寫&#xff1b; SQLite 中的事務是數據庫操作中非常重要的一個概念&#xff0c;它用于確保數據庫操作的完整性和一致性。…

虛擬路由與單頁應用(SPA):詳解

在單頁應用&#xff08;SPA&#xff0c;Single Page Application&#xff09;中&#xff0c;虛擬路由&#xff08;也稱為前端路由&#xff09;是一種關鍵的技術&#xff0c;用于管理頁面導航和狀態變化&#xff0c;而無需重新加載整個頁面。為了幫助你更好地理解這一概念&#…

練習:運動計劃

需求&#xff1a;鍵盤錄入星期數&#xff0c;顯示今天的減肥活動。 周一&#xff1a;跑步&#xff1b; 周二&#xff1a;游泳&#xff1b; 周三&#xff1a;慢走&#xff1b; 周四&#xff1a;騎動感單車&#xff1b; 周五&#xff1a;拳擊&#xff1b; 周六&#xff1a;…

通過webrtc+canvas+css實現簡單的電腦濾鏡拍照效果

這里我們用的是webrtc中的MediaDevices.getUserMedia()的瀏覽器api進行的效果實現&#xff0c;MediaDevices.getUserMedia() 會提示用戶給予使用媒體輸入的許可&#xff0c;媒體輸入會產生一個MediaStream&#xff0c;里面包含了請求的媒體類型的軌道。此流可以包含一個視頻軌道…

《TCP/IP網絡編程》學習筆記 | Chapter 20:Windows 中的線程同步

《TCP/IP網絡編程》學習筆記 | Chapter 20&#xff1a;Windows 中的線程同步 《TCP/IP網絡編程》學習筆記 | Chapter 20&#xff1a;Windows 中的線程同步用戶模式和內核模式用戶模式同步內核模式同步 基于 CRITICAL_SECTION 的同步內核模式的同步方法基于互斥量對象的同步基于…

VBA-Excel

VBA 一、數據類型與變量 常用數據類型&#xff1a; Byte&#xff1a;字節型&#xff0c;0~255。Integer&#xff1a;整數型&#xff0c;用于存儲整數值&#xff0c;范圍 -32768 到 32767。Long&#xff1a;長整型&#xff0c;可存儲更大范圍的整數&#xff0c;范圍 -214748364…

kotlin 內聯函數 inline

高階函數實現的原理&#xff1a;函數類型其實是生成了一個對象 。 inline翻譯成中文的意思就是內聯&#xff0c;在kotlin里面inline被用來修飾函數&#xff0c;表明當前函數在編譯時是以內嵌的形式進行編譯的&#xff0c;從而減少了一層函數調用棧&#xff1a; inline fun fun…

PairRE: Knowledge Graph Embeddings via Paired Relation Vectors(論文筆記)

CCF等級&#xff1a;A 發布時間&#xff1a;2020年11月 25年3月24日交 目錄 一、簡介 二、原理 1.整體 2.關系模式 3.優化模型 三、實驗性能 四、結論和未來工作 一、簡介 將RotatE進行生級&#xff0c;RotatE只對頭實體h進行計算&#xff0c;PairRE對頭尾實體都進行…

從報錯到成功:Mermaid 流程圖語法避坑指南?

&#x1f680; 從報錯到成功&#xff1a;Mermaid 流程圖語法避坑指南 &#x1f680; &#x1f6a8; 問題背景 在開發文檔或技術博客中&#xff0c;我們經常使用 Mermaid 流程圖 來可視化代碼邏輯。但最近我在嘗試繪制一個 Java Stream 轉換流程圖時&#xff0c;遭遇了以下報錯…

深入解析 Redis 實現分布式鎖的最佳實踐

前言 在分布式系統中&#xff0c;多個進程或線程可能會同時訪問同一個共享資源&#xff0c;這就可能導致數據不一致的問題。為了保證數據的一致性&#xff0c;我們通常需要使用分布式鎖。Redis 作為高性能的內存數據庫&#xff0c;提供了一種簡單高效的方式來實現分布式鎖。本…

2025年03月10日人慧前端面試(外包滴滴)

目錄 普通函數和箭頭函數的區別loader 和 plugin 的區別webpack 怎么實現分包&#xff0c;為什么要分包webpack 的構建流程變量提升react 開發中遇到過什么問題什么是閉包vue 開發中遇到過什么問題vue中的 dep 和 watcher 的依賴收集是什么階段什么是原型鏈react setState 是同…

Android10 系統截屏功能異常的處理

客戶反饋的問題&#xff0c;設備上使用狀態欄中“長截屏”功能&#xff0c;截屏失敗且出現系統卡死問題。 在此記錄該問題的處理 一現象&#xff1a; 設備A10上使用系統“長截屏”功能&#xff0c;出現截屏失敗&#xff0c;系統死機。 二復現問題并分析 使用設備操作該功能&…

openvela新時代的國產開源RTOS系統

openvela 簡介 openvela 操作系統專為 AIoT 領域量身定制&#xff0c;以輕量化、標準兼容、安全性和高度可擴展性為核心特點。openvela 以其卓越的技術優勢&#xff0c;已成為眾多物聯網設備和 AI 硬件的技術首選&#xff0c;涵蓋了智能手表、運動手環、智能音箱、耳機、智能家…

ENSP學習day9

ACL訪問控制列表實驗 ACL&#xff08;Access Control List&#xff0c;訪問控制列表&#xff09;是一種用于控制用戶或系統對資源&#xff08;如文件、文件夾、網絡等&#xff09;訪問權限的機制。通過ACL&#xff0c;系統管理員可以定義哪些用戶或系統可以訪問特定資源&#x…

JVM的組成--運行時數據區

JVM的組成 1、類加載器&#xff08;ClassLoader&#xff09; 類加載器負責將字節碼文件從文件系統中加載到JVM中&#xff0c;分為&#xff1a;加載、鏈接&#xff08;驗證、準備、解析&#xff09;、和初始化三個階段 2、運行時數據區 運行時數據區包括&#xff1a;程序計數…

RAG(Retrieval-Augmented Generation)基建之PDF解析的“魔法”與“陷阱”

嘿&#xff0c;親愛的算法工程師們&#xff01;今天咱們聊一聊PDF解析的那些事兒&#xff0c;簡直就像是在玩一場“信息捉迷藏”游戲&#xff01;PDF文檔就像是個調皮的小精靈&#xff0c;表面上看起來規規矩矩&#xff0c;但當你想要從它那里提取信息時&#xff0c;它就開始跟…