Linux進程在內核眼中是什麼樣子的?

本篇算是進程管理的的揭幕篇,簡單介紹一個進程在內核眼裏的來龍去脈,爲接下來的進程創建,進程調度,進程管理等篇章做好學習準備。

從程序到進程再到內核

啥是程序,啥是進程,一張圖可以給我們解釋:

程序轉換爲進程的過程不是本文重點,這裏不做詳解,詳情請看 《Linux 程序編譯過程的來龍去脈》。接下來我們轉換鏡頭,站在內核OS的視角看什麼是程序,什麼是進程。

ELF可執行文件送給內核後,OS是如何看待它的呢?換句話講,內核OS眼裏只有進程:

通過 top 命令我們可以看到 linux 的各種進程(即上右圖)。

內核通過 task_struct 描述進程

用命令 pstree 可以讓內核以樹形的結構把進程之間的關係列出來,如下圖:

這是進程在內核中的結構形式,那麼內核是如何來以樹形結構管理描述這些進程的呢?用來描述進程的數據結構,可以理解爲進程的屬性。比如進程的狀態、進程的標識(PID)等,都被封裝在了進程描述符這個數據結構中,一起來看下今天的主角—— task_struct 結構體。

struct task_struct {
volatile long state;  //說明了該進程是否可以執行,還是可中斷等信息 -1 unrunnable, 0 runnable, >0 stopped
unsigned long flags;  //Flage 是進程號,在調用fork()時給出
int sigpending;    //進程上是否有待處理的信號
mm_segment_t addr_limit; //進程地址空間,區分內核進程與普通進程在內存存放的位置不同
                        //0-0xBFFFFFFF for user-thead
                        //0-0xFFFFFFFF for kernel-thread
//調度標誌,表示該進程是否需要重新調度,若非0,則當從內核態返回到用戶態,會發生調度
volatile long need_resched;
int lock_depth;  //鎖深度
long nice;       //進程的基本時間片
//進程的調度策略,有三種,實時進程:SCHED_FIFO,SCHED_RR, 分時進程:SCHED_OTHER
unsigned long policy;
struct mm_struct *mm; //進程內存管理信息
int processor;
//若進程不在任何CPU上運行, cpus_runnable 的值是0,否則是1 這個值在運行隊列被鎖時更新
unsigned long cpus_runnable, cpus_allowed;
struct list_head run_list; //指向運行隊列的指針
unsigned long sleep_time;  //進程的睡眠時間
//用於將系統中所有的進程連成一個雙向循環鏈表, 其根是init_task
struct task_struct *next_task, *prev_task;
struct mm_struct *active_mm;
struct list_head local_pages;       //指向本地頁面      
unsigned int allocation_order, nr_local_pages;
struct linux_binfmt *binfmt;  //進程所運行的可執行文件的格式
int exit_code, exit_signal;
int pdeath_signal;     //父進程終止時向子進程發送的信號
unsigned long personality;
//Linux可以運行由其他UNIX操作系統生成的符合iBCS2標準的程序
int did_exec:1; 
pid_t pid;    //進程標識符,用來代表一個進程
pid_t pgrp;   //進程組標識,表示進程所屬的進程組
pid_t tty_old_pgrp;  //進程控制終端所在的組標識
pid_t session;  //進程的會話標識
pid_t tgid;
int leader;     //表示進程是否爲會話主管
struct task_struct *p_opptr,*p_pptr,*p_cptr,*p_ysptr,*p_osptr;
struct list_head thread_group;   //線程鏈表
struct task_struct *pidhash_next; //用於將進程鏈入HASH表
struct task_struct **pidhash_pprev;
wait_queue_head_t wait_chldexit;  //供wait4()使用
struct completion *vfork_done;  //供vfork() 使用
unsigned long rt_priority; //實時優先級,用它計算實時進程調度時的weight值
struct timer_list real_timer;   //指向實時定時器的指針
struct tms times;      //記錄進程消耗的時間
unsigned long start_time;  //進程創建的時間
//記錄進程在每個CPU上所消耗的用戶態時間和核心態時間
long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS]; 
int swappable:1; //表示進程的虛擬地址空間是否允許換出
int ngroups; //記錄進程在多少個用戶組中
gid_t groups[NGROUPS]; //記錄進程所在的組
//進程的權能,分別是有效位集合,繼承位集合,允許位集合
kernel_cap_t cap_effective, cap_inheritable, cap_permitted;
int keep_capabilities:1;
struct user_struct *user;
struct rlimit rlim[RLIM_NLIMITS];  //與進程相關的資源限制信息
unsigned short used_math;   //是否使用FPU
char comm[16];   //進程正在運行的可執行文件名
 //文件系統信息
int link_count, total_link_count;
//NULL if no tty 進程所在的控制終端,如果不需要控制終端,則該指針爲空
struct tty_struct *tty;
unsigned int locks;
//進程間通信信息
struct sem_undo *semundo;  //進程在信號燈上的所有undo操作
struct sem_queue *semsleeping; //當進程因爲信號燈操作而掛起時,他在該隊列中記錄等待的操作
//進程的CPU狀態,切換時,要保存到停止進程的task_struct中
struct thread_struct thread;
  //文件系統信息
struct fs_struct *fs;
  //打開文件信息
struct files_struct *files;
  //信號處理函數
spinlock_t sigmask_lock;
struct signal_struct *sig; //信號處理函數
sigset_t blocked;  //進程當前要阻塞的信號,每個信號對應一位
struct sigpending pending;  //進程上是否有待處理的信號
......
};

內核就是通過list_head鏈表把各個進程關係以樹形結構管理起來的。

task_struct 結構體內容太多,這裏只列出部分成員變量,感興趣的讀者可以去源碼 include/linux/sched.h頭文件查看。

task_struct 中的主要信息分類:

1. 標示符:描述本進程的唯一標識符,用來區別其他進程。

2. 狀態:任務狀態,退出代碼,退出信號等 

3. 優先級:相對於其他進程的優先級 

4. 程序計數器:程序中即將被執行的下一條指令的地址 

5. 內存指針:包括程序代碼和進程相關數據的指針,還有和其他進程共享的內存塊的指針 

6. 上下文數據:進程執行時處理器的寄存器中的數據 

7. I/O狀態信息:包括顯示的I/O請求,分配的進程I/O設備和進程使用的文件列表 

8. 記賬信息:可能包括處理器時間總和,使用的時鐘總和,時間限制,記帳號等

這些信息每類都可以單獨開個章節去講解,這裏先簡單描述下任務狀態的轉換,以後篇章再深入介紹各個分類。

任務狀態轉換

上面可以看到變量定義後面的註釋,它說明變量內容<0是不運行的,=0是運行狀態,>0是停止狀態。

下面我們介紹幾個常用的取值:


任務狀態在不同情況下的狀態轉換如下:

圖來源於https://www.lagou.com/lgeduarticle/96239.html

內核如何存放 task_struct 

我們知道一個進程所佔的棧空間有用戶棧和內核棧,用戶棧的分佈方式見之前的文章《C語言在ARM中函數調用時,棧是如何變化的?》。那麼內核棧是如何存放進程描述符的呢?

內核棧對於應用程序是不可見的,因爲它位於內核空間中。在應用程序執行過程中,如果發生異常、中斷或系統調用的話,應用程序會被暫停,系統進入內核態,轉去執行異常響應等代碼,這個時候所使用的棧就是內核棧。

爲了節省空間,linux把內核棧和緊挨着task_struct的thread_info放在一起,如上所示,thread_info中存放了進程/線程(內核不大區分進程與線程)的一些數據,其中包括指向task_struct結構的指針。數組stack即內核棧,stack佔據8K/4K(依配置不同)空間。

union thread_union {
#ifndef CONFIG_THREAD_INFO_IN_TASK
  struct thread_info thread_info;
#endif
  unsigned long stack[THREAD_SIZE/sizeof(long)];
};

最後

到這裏應該已經瞭解了一個程序如何轉換爲進程,內核如何描述進程,又如何存儲進程,當然還有很多關於進程的描述沒有介紹,比如進程的調度,優先級,內存管理等等,這些會在以後的文章裏單獨分開詳細介紹。但這些所有的信息都存儲在今天的主角里——task_struct。

續篇敬請期待......


添加極客助手微信,加入技術交流羣

長按,掃碼,關注公衆號

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章