抽象思想解读Linux进程描述符
來源 |?嵌入式客棧
責編 | Carol
頭圖 | CSDN 下載自視覺中國
內核是怎么工作的?
首先要理解進程管理,進程調度,本文開始閱讀進程管理部分,首先從進程的抽象描述開始。抽象是軟件工程的靈魂,而對于Linux操作系統而言,更是將抽象思想體現的淋漓盡致。
本文從抽象建模的角度來對Linux進程描述符進行個人解讀,同時也參考了內核文檔,一些網絡信息。
注:代碼基于linux-5.4.31,是一個最新的長期支持穩定版本。
本文為作者個人整理,文中可能有一些解讀不到位的地方,真誠請各位擅長此領域的大佬在評論區多多交流指出或討論,感謝!
進程的基本概念
進程 or 線程 or 任務?
進程:進程是一個正在運行的程序實例,由可執行的目標代碼組成,通常從某些硬媒介(如磁盤,閃存等)讀取并加載到內存中。但是,從內核的角度來看,涉及很多相關的工作內容。操作系統存儲和管理有關任何當前正在運行的程序的其他信息:地址空間,內存映射,用于讀/寫操作的打開文件,進程狀態,線程等。
進程是正在執行的計算機程序的實例。它包含程序代碼及其當前活動。取決于操作系統(OS),進程可能由同時執行指令的多個執行線程組成。基于進程的多任務處理使您可以在使用文本編輯器的同時運行Java編譯器。在單個CPU中采用多個進程時,使用了各種內存上下文之間的上下文切換。每個過程都有其自己的變量的完整集合。
但是,在Linux中,如果不討論線程(有時稱為輕量級進程),進程的抽象是不完整的。根據定義,線程是流程中的執行上下文或執行流;因此,每個進程至少包含一個線程。包含多個執行線程的進程被稱為多線程進程。一個進程中有多個線程可以進行當前編程,并且在多處理器系統上可以實現真正的并行性。
線程:則是某一進程中一路單獨運行的程序,也就是說,線程存在于進程之中。一個進程由一個或多個線程構成,各線程共享相同的代碼和全局數據,但各有其自己的堆棧。
由于堆棧是每個線程一個,所以局部變量對每一線程來說是私有的。由于所有線程共享同樣的代碼和全局數據,它們比進程更緊密,比單獨的進程間更趨向于相互作用,線程間的相互作用更容易些,因為它們本身就有某些供通信用的共享內存:進程的全局數據。
線程是CPU利用率的基本單位,由程序計數器,堆棧和一組寄存器組成。執行線程是由計算機程序的分支分解為兩個或多個同時運行的任務而產生的。線程和進程的實現因一個操作系統而異,但在大多數情況下,線程包含在進程內部。
多個線程可以存在于同一進程中并共享資源(例如內存),而不同進程則不共享這些資源。同一進程中的線程示例是自動拼寫檢查和寫入時自動保存文件。線程基本上是在相同內存上下文中運行的進程。線程在執行時可能共享相同的數據。線程圖,即單線程與多線程
任務:是最抽象的,是一個一般性的術語,指由軟件完成的一個活動。一個任務既可以是一個進程,也可以是一個線程。簡而言之,它指的是一系列共同達到某一目的的操作。與線程非常相似,不同之處在于它們通常不直接與OS交互。
像線程池一樣,任務不會創建自己的OS線程。一個任務內部可能有一個線程,也可能沒有。例如,讀取數據并將數據放入內存中。這個任務可以作為一個進程來實現,也可以作為一個線程(或作為一個中斷任務)來實現。在RTOS中,一般會將調度的基本單元稱為任務,比如freeRTOS,ucos,embOS等,在RTOS中沒有進程的概念。
進程間通訊機制:
管道(Pipe)及有名管道(named pipe):管道可用于具有親緣關系進程間的通信,有名管道克服了管道沒有名字的限制,因此,除具有管道所具有的功能外,它還允許無親緣關系進程間的通信;
信號(Signal):信號是比較復雜的通信方式,用于通知接受進程有某種事件發生,除了用于進程間通信外,進程還可以發送信號給進程本身;linux除了支持Unix早期信號語義函數sigal外,還支持語義符合Posix.1標準的信號函數 sigaction(實際上,該函數是基于BSD的,BSD為了實現可靠信號機制,又能夠統一對外接口,用sigaction函數重新實現了signal 函數);
報文(Message)隊列(消息隊列):消息隊列是消息的鏈接表,包括Posix消息隊列system V消息隊列。有足夠權限的進程可以向隊列中添加消息,被賦予讀權限的進程則可以讀走隊列中的消息。消息隊列克服了信號承載信息量少,管道只能承載無格式字節流以及緩沖區大小受限等缺點。
共享內存:使得多個進程可以訪問同一塊內存空間,是最快的可用IPC形式。是針對其他通信機制運行效率較低而設計的。往往與其它通信機制,如信號量結合使用,來達到進程間的同步及互斥。
信號量(semaphore):主要作為進程間以及同一進程不同線程之間的同步手段。
套接字(Socket):更為一般的進程間通信機制,可用于不同機器之間的進程間通信。起初是由Unix系統的BSD分支開發出來的,但現在一般可以移植到其它類Unix系統上:Linux和System V的變種都支持套接字。
線程間的同步機制:為啥線程間沒有討論通訊機制?因為同一進程內的線程共享進程的資源。那么資源共享,則需要處理資源共享時的同步問題。
互斥鎖(mutex):通過鎖機制實現線程間的同步。同一時刻只允許一個線程執行一個關鍵部分的代碼。這部分代碼常稱為臨界區。哪些可能是臨界區呢?簡言之,多個線程可能競爭訪問的資源。以下一些函數是互斥鎖的API函數。
全局條件變量(condition variable): 創建一些全局條件變量進行互斥訪問控制。以下是其操作的基本接口函數:
int?pthread_cond_init(pthread_cond_t?*cond,pthread_condattr_t?*cond_attr);?? int?pthread_cond_wait(pthread_cond_t?*cond,pthread_mutex_t?*mutex); int?pthread_cond_timewait(pthread_cond_t?*cond,pthread_mutex?*mutex,const?timespec?*abstime); int?pthread_cond_destroy(pthread_cond_t?*cond); int?pthread_cond_signal(pthread_cond_t?*cond); int?pthread_cond_broadcast(pthread_cond_t?*cond);信號量(semaphore):如同進程一樣,線程也可以通過信號量來實現通信,其基本操作接口API:
int?sem_init?(sem_t?*sem?,?int?pshared,?unsigned?int?value); int?sem_wait(sem_t?*sem); int?sem_post(sem_t?*sem); int?sem_destroy(sem_t?*sem);進程在內核中如何描述?
Linux中進程描述在./include/linux/sched.h中定義:
struct?task_struct?{ #ifdef?CONFIG_THREAD_INFO_IN_TASK/*?必須是首個元素??*/struct?thread_info??thread_info; #endif/*?-1?unrunnable,?0?runnable,?>0?stopped:?*/volatile?long???state;/*?前面是與調度密切相關的信息添加在這之前?*/randomized_struct_fields_startvoid????*stack;refcount_t???usage;/*?Per?task?flags?(PF_*),?defined?further?below:?*/unsigned?int???flags;unsigned?int???ptrace;......... };該結構非常大,集總抽象了進程的所有信息,包括進程ID,狀態,父進程,子進程,同級,處理器寄存器,打開的文件,地址空間等。系統使用循環雙向鏈接列表進行存儲 所有過程描述符。
像這樣的大型結構肯定會占用大量內存空間。為每個進程提供較小的內核堆棧大小(可以使用編譯時選項進行配置,但默認情況下限制為一頁,即對于32位體系結構嚴格為4KB(一個頁),對于64位體系結構嚴格為8KB(兩個頁) –內核堆棧不具備增長或收縮),以這種浪費的方式使用資源并不是很方便。因此,決定在堆棧中放置一個更簡單的結構,并帶有指向實際task_struct的指針,從而引申出thread_info。
抽象建模思想看進程描述符
進程首先是操作系統對底層進行抽象而提供面向應用接口的一種抽象,而進程描述符則將底層資源、進程本身的調度從以下幾個大的方面進行高級別的抽象封裝:
應用程序信息抽象
操作系統資源抽象
調度接口抽象
內存管理抽象
賬戶信息抽象
......
通過預讀進程描述符,個人將進程描述相關信息大致分為以下幾個大類抽象:
調度相關抽象
涉及thread_info、優先級、棧、上下文切換、調度相關鏈表等關鍵數據。
CPU相關抽象
涉及SMP多核處理抽象、CPUSET子系統相關、當前CPU等相關數據抽象。
保護機制抽象
內存管理抽象
緩存相關抽象
參考閱讀:https://www.cnblogs.com/20135228guoyao/p/5334985.html
https://blog.csdn.net/wang_xya/article/details/35234429
信號通信抽象
接口相關抽象
調試跟蹤抽象
安全機制抽象
資源管理抽象
雜項信息抽象
最后附上些整理搜集到數據域的一些較詳細的介紹。
thread_info
該字段保存特定于處理器的狀態信息,并且是進程描述符的關鍵元素。具體定義在./arch/xxx/include/asm/thread_info.h中。
entry.S需要立即訪問此結構的低級任務數據應完全適合一個緩存行,此結構共享主管堆棧頁面
如果更改此結構的內容,則還必須更改匯編代碼。
因為thread_info包含了當前進程的指針,存儲在棧底或棧頂,取決于不同體系架構棧的增長方向,利用thread_info可以快速的訪問當前進程的信息,而不必依次遍歷。
ARM32的定義:
struct?thread_info?{unsigned?long?flags;??/*?low?level?flags?*/int???????preempt_count;?/*?0?=>?preemptable,?<0?=>?bug?*/mm_segment_t?addr_limit;?/*?address?limit?*/struct?task_struct?*task;??/*?main?task?structure?*/__u32???cpu;??/*?cpu?*/__u32???cpu_domain;?/*?cpu?domain?*/ #ifdef?CONFIG_STACKPROTECTOR_PER_TASKunsigned?long??stack_canary; #endifstruct?cpu_context_save?cpu_context;?/*?cpu?context?*/__u32???syscall;?/*?syscall?number?*/__u8???used_cp[16];?/*?thread?used?copro?*/unsigned?long??tp_value[2];?/*?TLS?registers?*/ #ifdef?CONFIG_CRUNCHstruct?crunch_state?crunchstate; #endifunion?fp_state??fpstate?__attribute__((aligned(8)));union?vfp_state??vfpstate; #ifdef?CONFIG_ARM_THUMBEEunsigned?long??thumbee_state;?/*?ThumbEE?Handler?Base?register?*/ #endif };從書上和網上看到都是前面這樣描述的,但是對于ARM64的卻沒有當前進程指針,這是為何呢?沒弄明白,有誰知道告訴下我唄。
struct?thread_info?{unsigned?long?flags;??/*?low?level?flags?*/mm_segment_t????addr_limit;?/*?address?limit?*/ #ifdef?CONFIG_ARM64_SW_TTBR0_PANu64???ttbr0;?????/*?saved?TTBR0_EL1?*/ #endifunion?{u64??preempt_count;????/*?0?=>?preemptible,?<0?=>?bug?*/struct?{ #ifdef?CONFIG_CPU_BIG_ENDIANu32?need_resched;u32?count; #elseu32?count;u32?need_resched; #endif}?preempt;}; };利用如下的幾種方式,可以獲取thread_info信息:
static inline struct thread_info *current_thread_info(void)
#define GET_THREAD_INFO(reg)
...
SLUB 分配器
thread_info實現了進程存儲對描述符的引用以及如何訪問它們。但是,如果task_struct不是在內核堆棧內部,則task_struct到底位于內存中的什么位置?為此,Linux提供了一種特殊的內存管理機制,稱為SLUB層。SLUB動態生成task_struct,并把thread_info存在棧底或棧頂。
volatile long state
進程狀態,可取的進程狀態:
TASK_RUNNING: ?可執行態
TASK_INTERRUPTIBLE:可中斷
TASK_UNINTERRUPTIBLE:不可中斷
__TASK_STOPPED:停止態
__TASK_TRACED:被其他進程跟蹤的進程
為何用volatile修飾。由于內核經常需要從不同位置更改進程的狀態,例如,如果在單個CPU硬件上同時將兩個進程設置為RUNNABLE。熟悉單片機編程的朋友一定知道,當在中斷函數中需要修改以及在中斷外部也會被修改的變量,就會使用到volatile修飾變量。
randomized_struct_fields_start
這是gcc的一個插件(插件來自于Grsecurity),其作用就是這之后的變量不會按照聲明順序存儲在內存中,而會按照一定的隨機順序存放,這樣做是基于安全考慮,比如應用程序的進程描述符被劫持,如果按順序存放,則容易篡改其內容。
今日福利
遇見大咖
由 CSDN 全新專為技術人打造的高端對話欄目《大咖來了》來啦!
CSDN 創始人&董事長、極客幫創投創始合伙人蔣濤攜手京東集團技術副總裁、IEEE Fellow、京東人工智能研究院常務副院長、深度學習及語音和語言實驗室負責人何曉冬,來也科技 CTO 胡一川,共話中國 AI 應用元年來了,開發者及企業的路徑及發展方向!
戳鏈接或點擊閱讀原文,直達報名:https://t.csdnimg.cn/uZfQ
總結
以上是生活随笔為你收集整理的抽象思想解读Linux进程描述符的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 为什么嫁人就选程序员!
- 下一篇: 【惊天】京东任命周伯文担任京东云与AI事