Linux操作系統之線程:線程概念

目錄

前言:

一、進程與線程

二、線程初體驗

三、分頁式存儲管理初談

總結:


前言:

大家好啊,今天我們就要開始翻閱我們linux操作系統的另外一座大山:線程了。

對于線程,大體結構上我們是劃分為兩部分,一部分是線程的概念與控制,另外一部分是線程的同步與互斥的相關內容。

本篇文章我將會為大家介紹線程的一些基礎知識,加上對我們之前所學內容與線程之間的聯系。

一、進程與線程

我們之前學過進程。

當時我們說:進程是一個執行起來的程序,進程=內核數據結構+代碼與數據。

而什么是線程呢?

線程是一個執行流,執行粒度比進程更細,是進程內部的一個執行分支。

也就是說,一個進程可以包含多個線程,而這些線程共享進程的資源(如內存空間,我們后面會解釋),但各自擁有獨立的執行上下文(如棧、寄存器)

我們之前所說的進程,只有一個執行流,這種“單線程進程”其實是多線程模型的一種特例,如今更常見的是多線程程序,以提高并發性和資源利用率。

我們現在要更新一下對于線程進程的概念。

對于進程來說,進程是分配系統資源的基本實體。

對于線程來說,線程是OS調度的基本單位。


進程都需要被管理起來,那么比進程執行粒度更細的線程呢?

自然也要被管理起來,提到管理,就不得不說出那六個字:先描述,再組織!!

那我們就應該類似管理進程一樣,專門弄出一個類似于PCB的結構來管理線程?

那我們的操作系統未免也太復雜了吧。

所以,linux的設計者也考慮到了這一點,于是linux的設計者就決定,我們可不可以讓PCB(task_struct)近似的拿去管理線程呢?

所以線程,實際上也是通過PCB來進行管理的,沒錯,你沒有聽錯,線程,也是通過PCB來進行管理的。

一個程序是一個進程,但是這個進程不一定只有一個PCB,我們從來沒說過一個進程只能有一個PCB。所以有著多個PCB的進程,這多出來的,就是一個一個的執行流,就是一個一個的線程。線程也是task_struct描述起來的。

我們之前的模型,都是單線程進程,這唯一一個PCB,代表著這個進程的主執行流。

該進程唯一的?task_struct(PCB)即代表其主執行流,二者是等價的。此時?“進程”=“線程”,因為只有一個執行流,無需區分概念。


而我們之前講進程的PCB的時候說過,PCB中包含很多數據結構,包括頁表,mm_struct,vm_area_struct list。那現在的多線程進程中,我們有多個PCB,這里面的每一個PCB都有著這些結構嗎?

當然,要不然為什么他們都是由PCB管理起來的呢?

那他們的數據也是一樣的嗎?

是,也不是。

一個進程中可以含多個PCB,我們就以主執行流的PCB數據為準,其他執行流(線程)的PCB,里面的mm_struct,vm_area_struct list這些結構數據,其實是共享的,他們之間共享相同的地址空間和資源,但是他們的寄存器和用戶棧空間是每個線程各自獨立的:

struct task_struct {pid_t pid;    // 線程ID(內核視角)pid_t tgid;   // 線程組ID(用戶視角的進程ID)struct mm_struct *mm; // 指向共享的內存描述符// 每個線程有獨立的:struct thread_struct thread;  // 寄存器狀態void *stack;                 // 內核棧
};

我們可以理解為每一個線程存儲的大部分數據都是一樣的,在執行代碼時,由于我們的執行上下文不同,各自維護獨立的執行狀態,所以我們可以并發的執行不同的代碼。


所以我們今天就有了更清楚的概念:一個PCB(task_struct)<= 進程

我們也不在區分執行流到底是線程還是進程,轉而把linux執行流統一稱為:輕量級進程(LWP)?

linux系統中沒有真正意義上的“線程”,只有?“共享資源的輕量級進程”。

值得提的是,Windows是真的有線程的專屬數據結構,所以它的內核代碼極其復雜。


二、線程初體驗

?我們接下來寫一下簡單的測試代碼,讓大家體驗一下線程的概念:

在linux中,我們一般用pthread_create函數來創建一個線程。

值得注意的是,這個函數并不是一個系統調用,而是我們glibc封裝的一個函數。

他有四個參數,第一個參數是一個指針,函數成功返回后,會將新線程的 ID 寫入該地址。所以我們在使用這個函數前,一般會創建一個pthread_t類型的id。

第二個參數指定線程的屬性(如棧大小、調度策略等),如果是NULL,則使用默認屬性。

第三個參數是一個函數指針,代表值這個線程將要執行的方法,而第四個參數表示傳遞給線程函數的參數。

#include <pthread.h>
#include <iostream>
#include <unistd.h>void* func(void *argv)
{while(true){std::cout<<"I am func pthread,my pid :"<<getpid()<<std::endl;   sleep(1);}
}
int main()
{pthread_t tid;int i = 100;pthread_create(&tid, nullptr, func, (void *)i);while (true){std::cout << "I am main pthread,my pid :" << getpid() << std::endl;sleep(1);}return 0;
}

在編譯這個代碼時應該注意,我們的編譯指令應該是?g++ test.cc -o test -lpthread?,因為我們需要需要鏈接?pthread 庫。

可以看見,如果只有一個執行流,是不能同時執行兩個while循環的。?

?我們再次運行代碼,通過新的bash輸入以上兩個命令可以查看進程運行信息。

可以看見,操作系統中叫做test的進程只有這一個,我們可以使用ps -aL來查看線程信息。

這里就多出來一個叫做LWP的東西。這個就是表示輕量級進程。

我們LWP與PID相同的,就是主執行流,如果光看PID的話,我們是區分不出來兩個執行流的。所以,我們可以通過LWP來區分執行流唯一性,我們的OS真實調度時,也是用的LWP而不是PID。


三、分頁式存儲管理初談

如果在沒有虛擬內存和分頁機制的情況下,每一個用戶程序在物理內存上所對應的空間必須是連續的,如下圖:

因為每一個程序的代碼、數據長度都是不一樣的,按照這樣的映射方式,物理內存將會被分割成各種離散的、大小不同的塊。結果一段時間運行后,有些程序會退出,那么他們占據的物理內存空間就會被回收,導致這些物理內存都是以很多碎片的形式存在的。

我們希望操作系統提供給用戶的空間必須是連續的,但是物理內存又不連續,該怎么辦呢?

所以此時虛擬內存與分頁就出現了。

?我們這張圖中牽涉到了頁框的概念,那么什么是頁框呢?

同學們,還記得我們學過的物理內存管理嗎?
我們當時說過塊的概念,我們可以把一定數量的扇區,劃分為一個塊。一個塊的數據大小為4kb,也就是八個扇區。(ext2下)

我們說:塊是文件系統管理數據的最小單位,一個塊的大小是4kb。

這里的塊是文件系統讀寫磁盤的最小邏輯單位,類似的,我們的一個頁框的大小也是固定為4kb,他是操作系統管理物理內存的最小單位。

我們把物理內存按照一個固定的長度的頁框進行分割,有時候叫做物理頁。每一個頁框包含一個物理頁(page)。一個頁的大小等于頁框的大小,32位大多支持4kb,64為8kb。

我們需要區分頁與頁框:

頁是一個數據塊,可以存放在任何頁框或者磁盤中,而頁框是一個存儲區域!

有了這種機制,CPU便并非是直接訪問物理內存地址,而是通過虛擬地址空間來間接訪問物理內存地址。所謂的虛擬空間,是操作系統為每一個正在執行的進程分配的一個邏輯地址。操作系統將虛擬地址空間與物理內存地址之間建立映射關系,也就是頁表,這張表上記錄了每一頁和頁框的映射關系。

總結一下,其思想就是將虛擬內存下的邏輯地址空間分為若干頁,將物理內存空間劃分為若干頁框,通過頁表就能把連續的虛擬內存,映射到若干個不同的物理內存頁,就解決了碎片問題。


但是假設?個可用的物理內存有 4GB 的空間。按照?個頁框的大小?4KB 進行劃分4GB 的空間就是 4GB/4KB = 1048576 頁框。有這么多的物理頁,操作系統肯定是要將其管理起來的,操作系統需要知道哪些頁正在被使用,哪些頁空閑等等。
內核用?struct page 結構表示系統中的每個物理頁,出于節省內存的考慮, struct page 中是用來大量的聯合體union。
/* include/linux/mm_types.h */
struct page
{/* 原?標志,有些情況下會異步更新 */unsigned long flags;union{struct{/* 換出?列表,例如由zone->lru_lock保護的active_list */struct list_head lru;/* 如果最低為為0,則指向inode* address_space,或為NULL* 如果?映射為匿名內存,最低為置位* ?且該指針指向anon_vma對象*/struct address_space *mapping;/* 在映射內的偏移量 */pgoff_t index;/** 由映射私有,不透明數據* 如果設置了PagePrivate,通常?于buffer_heads* 如果設置了PageSwapCache,則?于swp_entry_t如果設置了PG_buddy,則?于表?伙伴系統中的階*/unsigned long private;};struct{ /* slab, slob and slub */union{struct list_head slab_list; /* uses lru */struct{ /* Partial pages */struct page *next;
#ifdef CONFIG_64BITint pages;    /* Nr of pages left */int pobjects; /* Approximate count */
#elseshort int pages;short int pobjects;
#endif};};struct kmem_cache *slab_cache; /* not slob *//* Double-word boundary */void *freelist; /* first free object */union{void *s_mem;            /* slab: first object */unsigned long counters; /* SLUB */struct{                        /* SLUB */unsigned inuse : 16; /* ?于SLUB分配器:對象的數? */unsigned objects : 15;unsigned frozen : 1;};};};...};union{/* 內存管理?系統中映射的?表項計數,?于表??是否已經映射,還?于限制逆向映射搜索*/atomic_t _mapcount;unsigned int page_type;unsigned int active; /* SLAB */int units;           /* SLOB */};...
#if defined(WANT_PAGE_VIRTUAL)/* 內核虛擬地址(如果沒有映射則為NULL,即?端內存) */void *virtual;
#endif /* WANT_PAGE_VIRTUAL */...
}

    系統啟動時,內核會根據檢測到的物理內存大小,為每個物理頁框(page frame)分配一個對應的 struct page 結構體。

    在 Linux 內核中, struct page ?是用于管理物理內存頁(頁框)的核心數據結構,每個物理頁都對應一個這樣的結構體。該結構體包含幾個關鍵字段:

    1. flags:這是一個多功能的標志位字段,用于記錄頁的各種狀態。每位代表一種獨立的狀態,可同時表示32種不同的狀態(定義在<linux/page-flags.h>中)。其中重要的標志位包括:

      • PG_locked:表示頁是否被鎖定在內存中

      • PG_uptodate:表示頁數據已從塊設備正確讀取

    2. _mapcount:這個計數器記錄有多少個頁表項指向該物理頁,即頁的引用計數。當值為-1時,表示內核不再引用該頁,可以被重新分配使用。

    3. virtual:存儲頁的虛擬地址。對于常規內存,這就是頁在虛擬地址空間中的映射地址;而對于高端內存(不永久映射到內核地址空間的部分),此字段為NULL,需要時再動態映射。

    值得注意的是, struct page 描述的是物理頁而非虛擬頁。以典型的4KB頁大小和4GB物理內存為例,系統需要管理約1百萬個物理頁(4GB/4KB=1M)。假設每個 struct page 占用40字節,則總內存開銷約為40MB(1M*40B),僅占系統總內存的1%,這個管理開銷是相當合理的。


    我們之所以扯到這里,主要是想幫助大家理解虛擬地址空間與線程之間的關系,同一進程的所有線程共享同一個 mm_struct(內存描述符),因此它們看到的是完全相同的虛擬地址空間.

    我們可以把struct page當做數組來理解,Linux 內核通過一個名為?mem_map?的全局數組(元素類型為?struct page)管理所有物理頁。而當作數組,就有了下標,我們就可以快速轉化為物理地址,一個頁框是4kb,所以物理地址就等于=下標*4kb


    總結:

    由于時間原因,我們今天就講到這里。

    但是我們的分頁式存儲管理還是沒有講完,我們還沒用深刻理解頁表的分級存儲。

    所以明天我們將會講解頁表的分級存儲,之后會繼續講解線程的概念!

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

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

    相關文章

    windows利用wsl安裝qemu

    首先需要安裝wsl,然后在swl中啟動一個子系統。這里我啟動一個ubuntu22.04。 接下來的操作全部為在子系統中的操作。 檢查虛擬化 在開始安裝之前,讓我們檢查一下你的機器是否支持虛擬化。 要做到這一點,請使用以下命令: sean@DESKTOP-PPNPJJ3:~$ LC_ALL=C lscpu | grep …

    如何使用 OpenCV 打開指定攝像頭

    在計算機視覺應用中&#xff0c;經常需要從特定的攝像頭設備獲取視頻流。例如&#xff0c;在多攝像頭環境中&#xff0c;當使用 OpenCV 的 cv::VideoCapture 類打開攝像頭時&#xff0c;如果不指定攝像頭的 ID&#xff0c;可能會隨機打開系統中的某個攝像頭&#xff0c;或者按照…

    JAVA面試寶典 -《分布式ID生成器:Snowflake優化變種》

    &#x1f680; 分布式ID生成器&#xff1a;Snowflake優化變種 一場訂單高峰&#xff0c;一次鏈路追蹤&#xff0c;一條消息投遞…你是否想過&#xff0c;它們背后都依賴著一個“低調卻關鍵”的存在——唯一ID。本文將帶你深入理解分布式ID生成器的核心原理與工程實踐&#xff0…

    蘋果的機器學習框架將支持英偉達的CUDA平臺

    蘋果專為Apple Silicon設計的MLX機器學習框架即將迎來CUDA后端支持&#xff0c;這意義重大。原因如下。 這項開發工作由GitHub開發者zcbenz主導&#xff08;據AppleInsider報道&#xff09;&#xff0c;他于數月前開始構建CUDA支持的原型。此后他將項目拆分為多個模塊&#xff…

    golang語法-----變量、常量

    變量1、聲明與初始化&#xff08;1&#xff09;標準聲明 (先聲明&#xff0c;后賦值)var age int // 聲明一個 int 類型的變量 age&#xff0c;此時 age 的值是 0 fmt.Println(age) // 輸出: 0age 30 // 給 age 賦值 fmt.Println(age) // 輸出: 30//int 的零…

    Jenkins+Docker(docker-compose、Dockerfile)+Gitee實現自動化部署

    項目目錄結構 project-root/ ├── pom.xml ├── docker │ ├── copy.sh │ ├── file │ │ ├── jar │ │ │ └── 存放執行copy.sh以后jar包的位置 │ │ └── Dockerfile │ └── docker-compose.yml ├── docker-only-test │ ├─…

    TASK01【datawhale組隊學習】地瓜機器人具身智能概述

    https://github.com/datawhalechina/ai-hardware-robotics 參考資料地址 具身智能&#xff08;Embodied AI&#xff09; 具身智能 智能的大腦 行動的身體。 比例&#xff08;Proportional&#xff09;、積分&#xff08;Integral&#xff09;、微分&#xff08;Derivative&a…

    uni-app 配置華為離線推送流程

    1、首先需要創建一個華為開發者賬號&#xff0c;我這個是個人開發賬號 申請開發者賬號 2、去AppGallery Connect登陸我們剛剛創建好的賬號&#xff0c;點擊頁面的APP進入到如下3 AppGallery Connect ????? ?3、在AppGallery Connect 網站中創建一個 Android應用、點擊…

    當下主流攝像頭及其核心參數詳解

    &#x1f4d6; 推薦閱讀&#xff1a;《Yocto項目實戰教程:高效定制嵌入式Linux系統》 &#x1f3a5; 更多學習視頻請關注 B 站&#xff1a;嵌入式Jerry 當下主流攝像頭及其核心參數詳解 一、攝像頭發展概述 攝像頭作為現代智能設備&#xff08;如手機、安防、車載、工業等&am…

    下載了docker但是VirtualBox突然啟動不了了

    今天下docker后發現 eNSP 路由器&#xff0c;防火墻啟動不了了去virtualbox檢查的時候發現無法啟動&#xff1a;報錯&#xff1a;不能為虛擬電腦 AR_Base 打開一個新任務.Raw-mode is unavailable courtesy of Hyper-V. (VERR_SUPDRV_NO_RAW_MODE_HYPER_V_ROOT).返回代碼: E_F…

    C++11之lambda表達式與包裝器

    lambda與包裝器lambda語法捕捉列表lambda的應用lambda的原理包裝器functionbindlambda語法 lambda 表達式本質是?個匿名函數對象&#xff0c;跟普通函數不同的是他可以定義在函數內部。 lambda 表達式語法使?層??沒有類型&#xff0c;所以我們?般是?auto或者模板參數定義…

    有痛呻吟!!!

    XiTuJueJin:YYDS 分盤 有些平臺吃相太難看&#xff0c;同樣的文章&#xff0c;我還先選擇現在這里發布&#xff0c;TMD. 莫名其妙將我的文章設置為僅VIP可見&#xff0c;還是今天才發現&#xff0c;之前只是將一兩篇設置為僅VIP可見&#xff0c;今天突然發現這種標識的都自動…

    2025年7-9月高含金量數學建模競賽清單

    2025年7-9月高含金量數學建模競賽 ——“高教社杯”國賽 & “華為杯”研賽作為過來人&#xff0c;真心覺得參加數學建模比賽是我本科階段做的最值的事之一。 它鍛煉的那種把實際問題轉化成模型求解的思維&#xff0c;對做研究、寫論文甚至以后工作都幫助很大。我當時就是靠…

    SpringBoot為什么使用new RuntimeException() 來獲取調用棧?

    為什么不直接使用 Thread.currentThread().getStackTrace()&#xff1f;這確實看起來有點“奇怪”或者“繞”&#xff0c;但其實這是 Java 中一種非常常見、巧妙且合法的技巧&#xff0c;用于在運行時動態獲取當前代碼的調用棧信息。Spring 選擇用 new RuntimeException().getS…

    小白成長之路-haproxy負載均衡

    文章目錄一、概述1、HAProxy簡介2、HAProxy特點和優點&#xff1a;3、HAProxy保持會話的三種解決方法4、HAProxy的balance 8種負載均衡算法1&#xff09;RR&#xff08;Round Robin&#xff09;2&#xff09;LC&#xff08;Least Connections&#xff09;3&#xff09;SH&#…

    Kafka 與 RocketMQ 消息確認機制對比分析

    目錄 生產者消息確認機制 Kafka 生產者 ACK 機制 RocketMQ 生產者確認機制 消費者消息確認機制 Kafka 消費者確認機制 RocketMQ 消費者確認機制 核心差異對比 選型建議 消息確認機制是分布式消息中間件的核心功能之一&#xff0c;它直接關系到消息傳遞的可靠性和系統性能…

    C/C++---rdbuf()函數

    在C中&#xff0c;rdbuf() 是I/O流庫中的一個核心成員函數&#xff0c;主要用于訪問和操作流對象的緩沖區。這個函數在底層數據處理、流重定向以及自定義流操作等場景中應用廣泛。下面將從多個方面詳細解析 rdbuf() 函數。 基本概念與函數原型 rdbuf() 是 std::basic_ios 類的成…

    【LLM】從零到一構建一個小型LLM--MiniGPT

    從零到一構建一個小型LLM (Small Language Model)暫時起名為MiniGPT。這個模型將專注于因果語言建模 (Causal Language Modeling)&#xff0c;這是許多現代LLM&#xff08;如GPT系列&#xff09;的核心預訓練任務。模型設計&#xff1a; 我們設計的模型是一個僅包含解碼器 (Dec…

    網絡安全威脅下的企業困境與破局技術實踐

    前言&#xff1a;網絡安全威脅下的企業困境 在數字化轉型的浪潮中&#xff0c;企業對信息技術的依賴程度日益加深&#xff0c;但隨之而來的網絡安全威脅也愈發嚴峻。據統計&#xff0c;全球每年因網絡安全事件造成的經濟損失高達數萬億美元&#xff0c;其中中小企業更是成為了網…

    [RAG system] 信息檢索器 | BM25 Vector | Pickle格式 | HybridRetriever重排序

    第六章&#xff1a;信息檢索器 在上一章中&#xff0c;我們成功完成了知識庫攝入流程。這是巨大的進步~ 我們精心準備了文檔"塊"&#xff08;類似獨立的索引卡&#xff09;&#xff0c;并將其存儲在兩套智能歸檔系統中&#xff1a;向量數據庫&#xff08;用于基于含…