#Linux中的GCC編程# 進程相關的知識

基於C,進一步研究 linux內核函數,系統級別的函數

2017年8月

1. 知識瞭解

1.1 基本概念:

(1)程序 program
被存放在磁盤中的可執行文件。
(2)進程 process
是程序的執行實例。每個進程具有獨立的權限和職責,運行在各自的虛擬地址空間中。
進程之間不會相互影響,但可以進行通信。
(3)進程ID,process ID簡稱PID
非負整數,進程的數字標識符。
進程任務隊列

1.2 啓動例程

(1)“啓動例程”被編譯在main之前運行。
(2)收集命令行的參數,傳遞給main中的argc,argv。以及環境表envp。
(3)登記“終止函數”atexit()。

1.3 進程的終止

(1)正常終止

  • 從main函數返回 (return)
  • 調用exit(標準C庫函數)
  • 調用_exit或_Exit(系統調用)
  • 最後一個線程從“啓動例程”返回
  • 最後一個線程調用pthread_exit

注意:
return和exit(),會刷新標準IO緩存,會自動調用終止函數。
_exit()和_Exit(),不刷新,也不調用。

(2)異常終止

  • 調用abort
  • 接收一個信號並終止
  • 最後一個線程對“取消”請求 做出響應

(3)進程返回

  • 通常程序成功返回0,否則返回非0
  • 在shell中,可以產看進程返回值(echo $?)

(4)終止函數

#include <stdio.h>
int atexit(void (*function)(void));
向內核登記終止函數,成功返回0,否則-1
  • 每個啓動的進程都默認登記一個標準的終止函數
  • 終止函數在進程終止時釋放進程所佔用的一些資源
  • 登記多個終止函數,執行順序以棧的方式執行,先登記後執行。

1.4 示意圖

啓動示意圖

1.5 進程資源限制

linux中可用的資源resource如下:

RLIMIT_AS 			進程可用的存儲區大小
RLIMIT_CORE 		core文件最大字節數
RLIMIT_CPU 			CPU時間最大值
RLIMIT_DATA			數據段最大長度
RLIMIT_FSIZE		可創建文件的最大長度
RLIMIT_LOCKS		文件鎖的最大數
RLIMIT_MEMLOCK使用mlock能否在存儲器中鎖定的最長字節數
RLIMIT_NOFILE		能打開的最大文件數
RLIMIT_NPROC		每個用戶ID可擁有的最大子進程數
RLIMIT_RSS			最大駐內存集的字節長度
RLIMIT_STACK		棧的最大長度
  1. 頭文件
#include <sys/resource.h>

struct rlimit{
	rlim_t rlim_cur;/*軟件限制:當前限制*/
	rlim_t rlim_max;/*硬件限制:當前限制可以達到的最大值*/
}
  1. 函數
(1)獲取進程的資源限制,存放在rlptr指向 的結構體中。成功返回0,失敗非0。
int getrlimit(int resource , struct rlimit *rlptr);

(2)修改resource指定的資源限制,通過rlptr指向的結構體。成功返回0
int setlimit(int resource,const struct rlimie *rlptr);
  1. 配置文件
    (1)/etc/security/limits.conf
    (2)linux中,進程資源的初始化由0號進程建立,並被後續進程繼承。
  2. 資源限制的修改規則
    (1)硬件資源限制必須大於等於軟件限制。
    (2)任何一進程可以降低或者提升其軟件資源限制,但必須大於其軟件限制。普通用戶不可逆此操作。
    (3)超級用戶可以提高硬件限制。

2. 與進程有關的指令

2.1 PS指令

可以查看到:進程ID(PID),進程的用戶ID,進程狀態STAT,進程的command等等。

3. 進程常見狀態

3.1 運行狀態

系統當前的進程
就緒狀態進程
PS命令的stat列 == R

3.2 等待狀態

等待事件發生
等待系統資源
PS命令的stat列 == S

3.3 停止狀態

PS命令的stat列 == T

3.4 殭屍狀態

進程終止或結束
在進程表項中仍有記錄
PS命令的stat列 == Z

3.5 進程狀態的變換關係

進程狀態變換關係

4. 進程的調度

4.1 一般性步驟

(1)處理內核中的工作
(2)處理當前進程
(3)選擇進程(實時進程和普通進程)
(4)進程交換

4.2 task_struct中的調度信息

(1)策略

  • 輪流策略
  • 先進先出策略

(2)優先權

  • jiffies變量

(3)實時優先權

  • 實時進程之間

(4)計數器

5. 進程標識

進程有很多的標識:
當前進程ID,實際用戶ID,有效用戶ID,用戶組ID,父進程ID,進程組ID。
與此相關的函數:

  1. 頭文件
#include <unistd.h>
#include <sys/types.h>
  1. 獲取進程標識的函數
pid_t getpid(void);           //獲取當前進程的ID標識
pid_t getppid(void);         //獲取父進程的ID標識
pid_t getpgrp(void);        //獲取當前進程所在的進程組ID標識。
pid_t getpgid(pid_t pid);  //獲取指定ID的進程所在的進程組ID標識。

uid_t getuid(void);           //獲取當前進程的 實際用戶ID
uid_t geteuid(void);          //獲取當前進程的 有效用戶ID

gid_t getgid(void);           //獲取當前進程的用戶組ID

6. 進程的創建

本文的重點內容。

6.1 創建子進程的函數 fork

  1. fork函數
    fork創建的新進程被稱爲子進程。該函數被調用一次,會返回兩次。
    返回兩次的區別:
    (1)在父進程裏,返回的是 新子進程的進程ID。
    (2)在新子進程裏,返回的是0。因爲子進程的數據段、堆、棧都是重新創建的。
    (3)父和子進程的運行順序,根據系統調度自動決定。
    (4)子進程複製父進程的內存空間。

  2. vfork函數
    與fork類似,但是 子進程先行運行,且不復制父進程的內存空間。

  3. 子進程的繼承屬性

  • 用戶信息和權限
  • 目錄信息,信號信息,環境,資源限制
  • 共享存儲段,堆,棧和數據段,共享代碼段
  1. 子進程的特有屬性
  • 進程ID
  • 鎖信息
  • 運行時間
  • 未決信號
  1. 操作文件時內核結構變化
  • 子進程繼承文件描述表,不繼承但共享文件表項和i-node。
  • 創建一個子進程後,文件表項中的引用計數器加1變成2,當父進程作close操作後,計數器減1。子進程還是可以使用文件表項。只有當計數器爲0時,纔會釋放文件表項。

6.2 進程寄生 exec函數簇

  • exec函數用於執行另一個程序。新執行的程序會替換原進程的正文,數據,堆,棧。
  • exec並不是創建新進程,前後的進程ID並沒有改變。
  • 在fork創建一個子進程之後,可以在子進程中使用exec函數執行另一個程序。
  1. 頭文件
#include <unistd.h>

2.函數

//list 列出每個字符參數
int execl(const char *pathname,const char *arg0, ... /*(char*)0*/);
//argv 字符數組
int execv(const char *pathname,char * const argv[]);
//list 列出每個字符參數,環境表
int execle(const char *pathname,const char *arg0, ... /*(char*)0,char* const envp[]*/);
//argv 字符數組,環境表
int execve(const char *pathname,char * const argv[],char* const envp[]);
//
int execlp(const char *pathname,const char *arg0, ... /*(char*)0*/);
//
int execvp(const char *pathname,char * const argv[]);

上述所有的返回:出錯返回-1 ,成功不返回。

6.3 system函數

  • system函數,內部構件一個子進程,由子進程調用exec函數。
  1. 頭文件
#include <stdlib.h>

2.函數

簡化exec函數的使用,成功返回執行命令的狀態,錯誤返回-1
int system(const char * command);

7. 幾種特殊的進程

7.1 守護進程 daemon

  • 守護進程是生存期很長的一種進程。他們常常在系統引導裝入時啓動,在系統關閉時終止。
  • 所有守護進程都是以超級用戶(用戶ID爲0)的優先權運行。
  • 守護進程沒有控制終端。
  • 守護進程的父進程都是init進程。

7.2 孤兒進程

  • 父進程結束,子進程就成了孤兒進程。由1號進程(init進程)領養。

7.3 殭屍進程

  • 子進程結束,但是沒有完全釋放內存(在內核中的task_struct沒有釋放)。該進程就成了殭屍進程。
  • 當殭屍進程的父進程結束,由1號進程(init進程)領養,最終回收。
  • 如何避免殭屍進程:
    (1)讓殭屍進程的父進程來回收。父進程每隔一段時間來查詢子進程是否結束並回收。調用wait()或者waitpid(),通知內核釋放殭屍進程。
    (2)採用信號SIGCHLD,通知處理。並在信號處理程序中調用wait()。
    (3)讓殭屍進程成爲孤兒進程,由init進程來回收。
  1. 頭文件
#include <sys/types.h>
#include <sys/wait.h>
  1. 函數
(1)等待子進程退出並回收。防止殭屍進程。成功返回子進程ID,出錯返回-1。
pid_t wait(int *status);

(2)wait函數的非阻塞版本。成功返回子進程ID,出錯返回-1。
pid_t waitpid(pid_t pid , int * status ,int options);

	1)pid參數。
			pid==-1   //等待任一子進程,功能和wait等效。
			pid== 0   //等待 同進程組ID的任一子進程。
			pid >  0   //等待指定的子進程
			pid < -1   //等待 組ID等於(-pid)的任一子進程。
	2)status參數。爲空時,等待回收【任意狀態】結束的子進程。不爲空,則等待【指定狀態】結束的子進程。
         檢查wait和waitpid函數返回終止狀態的宏
				WIFEXITED/WEXITSTATUS(status)    //若爲正常終止子進程返回的狀態,則爲真。
				WIFSIGNALED/WTERMSIG(status)    //若爲異常終止子進程返回的狀態,則爲真。(接到一個不能捕捉的信號)
				WIFSTOPED/WSTOPSIG(status)     //若爲當前暫停子進程返回的狀態,則爲真。
	3)options參數。
				WNOHANG    //若由pid指定的子進程沒有退出,則立即返回。waitpid不阻塞,返回值爲0。
				WUNTRACED    //若某實現支持 作業控制,則有pid指定的任一子進程狀態已暫停,其狀態自暫停以來還沒有報告過,則返回其狀態。

  1. wait和waitpid函數的區別
    (1)在一個子進程終止前,wait使其調用者阻塞。waitpid 不會阻塞。
    (2)wait等待所有的子進程;waitpid等待指定的子進程。

8. 進程組

  • 一個或者多個進程的集合
  • 可以接收同一終端的各種信號。同組共信號。
  • 唯一的進程組ID
  • 組內所有的進程結束,進程組纔會消亡。
  • kill命令 發送信號給進程組。

8.1 獲取進程組號

前文已經給出:

#include <unistd.h>
pid_t getpgrp(void);        //獲取當前進程所在的進程組ID標識。
pid_t getpgid(pid_t pid);  //獲取指定ID的進程所在的進程組ID標識。

8.2 組長進程

  • 進程組的創建從第一個進程(組長進程)加入開始。組長進程的ID作爲組ID。
  • 組長進程可以創建進程組以及該組中的進程。
  1. 頭文件
#include <unistd.h>
  1. 函數
(1) 將進程加入到指定的進程組中。成功返回0,錯誤返回-1。
int setpgid(pid_t pid,pid_t pgid);

(2) pid參數爲指定的進程號,pgid爲進程組。

8.3 前臺進程組

  • 在linux中,自動接收終端信號的組,成爲前臺進程組。
  • 在終端中,CTRL+c 之類的信號,首先被前臺進程組接收。
  • shell啓動的若干個進程組,默認父進程所在的組爲前臺進程組。
  1. 頭文件
#include <unistd.h>
  1. 函數
(1)獲取前臺進程組ID。成功返回 前臺進程組ID,錯誤返回-1。
pid_t tcgetpgrp(int fd); 

(2)使用pgrpid,設置前臺進程組ID
int tcsetpgrp(int fd,pid_t pgrpid);

(3)fd必須引用該會話的控制終端。0表示當前正在使用的終端。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章