linux內核學習-進程-進程控制

進程標識

每一個linux都有唯一一個進程標識,且爲非負數。進程的其他參數可以通過相應的函數獲得,函數聲明在頭文件“unistd.h”中。

函數聲明 功能
pid_t getpid(id) 獲得進程ID
pid_t getppid(id) 獲得父進程ID
pid_t getuid(id) 獲得進程實際用戶ID
pid_t getcuid(id) 獲得進程有效用戶ID
pid_t getgid(id) 獲得進程實際組ID
pid_t getegid(id) 獲得進程有效組ID

實際用戶:標識運行進程的用戶
有效用戶:程序以怎麼的身份運行程序
實際組:實際用戶所屬的組
有效組:有效用戶所屬的組

linux進程的結構

在這裏插入圖片描述
代碼段存放程序可執行代碼。
數據段存放程序全局變量、常量、靜態變量
堆:存放動態分配的內存變量
棧:存放函數調用

linux進程狀態

  1. 運行狀態
  2. 可中斷等待狀態
  3. 不可中斷等待狀態
  4. 僵死狀態
  5. 停止狀態

用ps命令可以查看進程的狀態。運行狀態R,可中斷等待狀態S,不可中斷等待狀態D,僵死狀態Z,停止狀態爲T。

在這裏插入圖片描述

進程的控制

用來對進程進行控制的主要系統調用如下
fork:用於創建一個新進程
exit:用於終止進程
exec:用於執行一個應用程序
wait:將父進程掛起,等待子進程終止
getpid:獲取當前進程的進程ID
nice:改變進程的優先級

進程的PID

PID_MAX=0x8000(可改),因此進程號的最大值爲0x7fff,即32767。其中0-299保留給daemon進程。可以通過/proc/sys/kernel/pid_max修改。
內核的PID號是會一直往上增長的,一般不會使用舊的PID號。但如果PID號達到最大值,內核就會重新開始找小的,沒有使用的PID號。

查看進程的詳細信息

查看進程詳細信息的常用三種方法有ps命令,top命令和cat /proc/pid/status。其中ps和top的顯示比較直觀,下面把cat /proc/pid/status的方法詳細說下,這裏是內核創建進程後存放進程信息最原始的地方。
1.先用ps命令找到你關心的進程的pid號,如我想看bash進程的pid號

ps -aux |grep bash
然後
cat /proc/pid/status  這裏的pid要用你 查到的pid號代替。如cat /proc/8311/status 

輸出如下

svauto@ubuntua:/proc/8311$ cat /proc/8311/status 
Name:	bash
State:	S (sleeping)
Tgid:	8311
Ngid:	0
Pid:	8311
PPid:	2632
TracerPid:	0
Uid:	1001	1001	1001	1001
Gid:	1001	1001	1001	1001
FDSize:	256
Groups:	27 1001 
NStgid:	8311
NSpid:	8311
NSpgid:	8311
NSsid:	8311
VmPeak:	   24908 kB
VmSize:	   24844 kB
VmLck:	       0 kB
VmPin:	       0 kB
VmHWM:	    6004 kB
VmRSS:	    5972 kB
VmData:	    2068 kB
VmStk:	     136 kB
VmExe:	     976 kB
VmLib:	    2308 kB
VmPTE:	      68 kB
VmPMD:	      12 kB
VmSwap:	       0 kB
HugetlbPages:	       0 kB
Threads:	1
SigQ:	0/15584
SigPnd:	0000000000000000
ShdPnd:	0000000000000000
SigBlk:	0000000000000000
SigIgn:	0000000000380004
SigCgt:	000000004b817efb
CapInh:	0000000000000000
CapPrm:	0000000000000000
CapEff:	0000000000000000
CapBnd:	0000003fffffffff
CapAmb:	0000000000000000
Seccomp:	0
Cpus_allowed:	ffffffff,ffffffff,ffffffff,ffffffff
Cpus_allowed_list:	0-127
Mems_allowed:	00000000,00000001
Mems_allowed_list:	0
voluntary_ctxt_switches:	2897
nonvoluntary_ctxt_switches:	520

各字段解釋

參數 解釋
Name 應用程序或命令的名字
State 任務的狀態,運行/睡眠/僵死/
SleepAVG 任務的平均等待時間(以nanosecond爲單位),交互式任務因爲休眠次數多、時間長,它們的 sleep_avg 也會相應地更大一些,所以計算出來的優先級也會相應高一些。
Tgid 線程組號
Pid 任務ID
Ppid 父進程ID
TracerPid 接收跟蹤該進程信息的進程的ID號
Uid Uid euid suid fsuid
Gid Gid egid sgid fsgid
FDSize 文件描述符的最大個數,file->fds
Groups
VmSize(KB) 任務虛擬地址空間的大小 (total_vm-reserved_vm),其中total_vm爲進程的地址空間的大小,reserved_vm:進程在預留或特殊的內存間的物理頁
VmLck(KB) 任務已經鎖住的物理內存的大小。鎖住的物理內存不能交換到硬盤 (locked_vm)
VmRSS(KB) 應用程序正在使用的物理內存的大小,就是用ps命令的參數rss的值 (rss)
VmData(KB) 程序數據段的大小(所佔虛擬內存的大小),存放初始化了的數據; (total_vm-shared_vm-stack_vm)
VmStk(KB) 任務在用戶態的棧的大小 (stack_vm)
VmExe(KB) 程序所擁有的可執行虛擬內存的大小,代碼段,不包括任務使用的庫 (end_code-start_code)
VmLib(KB) 被映像到任務的虛擬內存空間的庫的大小 (exec_lib)
VmPTE 該進程的所有頁表的大小,單位:kb
Threads 共享使用該信號描述符的任務的個數,在POSIX多線程序應用程序中,線程組中的所有線程使用同一個信號描述符。
SigQ 待處理信號的個數
SigPnd 屏蔽位,存儲了該線程的待處理信號
ShdPnd 屏蔽位,存儲了該線程組的待處理信號
SigBlk 存放被阻塞的信號
SigIgn 存放被忽略的信號
SigCgt 存放被俘獲到的信號
CapInh Inheritable 能被當前進程執行的程序的繼承的能力
CapPrm Permitted 進程能夠使用的能力,可以包含CapEff中沒有的能力,這些能力是被進程自己臨時放棄的,CapEff是CapPrm的一個子集,進程放棄沒有必要的能力有利於提高安全性
CapEff Effective 進程的有效能力

創建進程

創建進程可以由“操作系統”和“父進程”創建。操作系統創建的進程沒有繼承關係。父進程創建的子進程,可以繼承父進程的幾乎所有資源。

一、fork函數創建進程
需要包含頭文件

#include <unistd.h>

fork函數的示例代碼如下

#include "stdio.h"
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
	pid_t pid;
	
	printf("Process Creating study!\n");
	pid = fork();
	printf("point 1");
	switch(pid)
	{
	case 0:
		printf("Child process is running ,CurPid is %d, ParentId is %d\n",pid, getppid());
		break;
	case -1:
		printf("Process creating faild\n");
		break;
	default:
		printf("Parent process is running ,CurPid is %d, ParentId is %d\n",pid, getpid());
	}
	
	
	printf("exit\n");		
}

很多書會說fork在使用的時候,會返回兩次。但是我覺得這樣的描述是不妥的。其實fork並沒有返回兩次,看到有兩次是因爲fork會創建新的一個進程,且子進程的代碼和父進程的代碼是一樣的。並且進程的運行位置都是一樣的,有區別的是,子進程得到了0的返回值。父進程得到了子進程的PID號。

程序運行結果:
在這裏插入圖片描述
fork函數創建進程的特點
1.子進程和父進程是兩個獨立的程序。他們互不干擾的運行。
2.子進程有自己唯一的PID
3.父進程退出後,子進程繼承給祖先進程,一級一級的上升,直到繼承給init進程。
4.子進程共享父進程打開的文件描述符,但是父進程對文件描述符的修改,不會影響子進程
5.子進程不繼承父進程設置的警告
6.子進程的未決信號集被清空。

二、vfork函數創建進程
vfork的本質也是最終調用fork來進程,但是還是有細微的差別,如下
1.使用fork創建的父子兩個進程是完全獨立的。但是vfork創建的子進程共享父進程的地址空間。因此子進程改了數據,父進程是可以看見的。
2.fork創建的父子進程執行先後順序由內核進程調度機制決定。vfork創建的子進程需要調用exec或exit之後,父進程才能繼續執行。

fork和vfork創建進程的方法是一樣的。如下代碼所示

#include "stdio.h"
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

int globVar =5;

int main(void)
{
	pid_t pid;
	int var =1,i;
	
	printf("fork is diffirent with vfork!\n");
	printf("now globVar =%d,var=%d\r\n",globVar,var);
	
	pid = vfork();
	//pid = fork();  //解開註釋就能使用任意一個
	
	switch(pid)
	{
	case 0:
		i=2;
		while(i-->0)
		{
			printf("Child process is running \n");
			globVar++;
			var++;
			sleep(1);
		}
		printf("child globvar=%d,var=%d\r\n",globVar,var);
		break;
	case -1:
		printf("Process creating faild\n");
		break;
	default:
		i=2;
		while(i-->0)
		{
			printf("Parent process is running \n");
			globVar++;
			var++;
			sleep(2);
		}
		printf("parent gloVar =%d,var=%d\n",globVar,var);
		break;
	}
	
	exit(0);
		
}

這個程序,把下面代碼任意選擇一個編譯並運行。就可以看到vfork的地址空間父子進程是共享的。

pid = vfork();
	//pid = fork();  //解開註釋就能使用任意一個

如下vfork的執行結果,剛開始全局變量globVar=5,局部變量var=1.子進程對局部變量和全局變量都做了修改globVar=7,var=3.,然後父進程繼續做兩次+1操作。最終globVar=9,var=5.說明子進程的操作對父進程有影響。
在這裏插入圖片描述

以下是fork的結果,可以看出子進程和父進程的結果互不影響,各算各的,執行順序也是無法預計。
在這裏插入圖片描述

三、創建守護進程

守護進程就是在後臺運行的進程。用來做服務進程非常有用。創建步驟比較多。但都是套路,直接寫上去就行了。
守護進程一般在系統系統的時候自動啓動。因此都是修改啓動腳本(如/etc/rc.d)來完成啓動。
創建守護進程需要有如下特性:

  1. 進程後臺運行,fork一個子進程,然後父進程退出。
  2. 將進程變成會話組長,調用setsid。
  3. 禁止進程重新打開控制終端。可以用fork創建一個子進程,然後父進程退出
  4. 關閉不再需要的文件描述符。避免浪費資源或造成文件系統無法卸載。
  5. 更改當前目錄爲根目錄。
  6. 設置屏蔽字爲0,使用umask(0)。
  7. 處理SIGCHLD信號。這一步不是必須的。避免出現殭屍進程。

代碼如下

#include "stdio.h"
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <time.h>
#include <syslog.h>
#include <stdlib.h>

int init_daemon(void)
{
	int pid;
	int i;
	
	signal(SIGTTOU,SIG_IGN);
	signal(SIGTTIN,SIG_IGN);
	signal(SIGTSTP,SIG_IGN);
	signal(SIGHUP,SIG_IGN);
	
	
	pid = fork();
	
	if(pid>0)
	{
		exit(0);
	}
	else if(pid <0)
	{
		return -1;
	}
	
	setsid();
	
	pid = fork();
	if(pid >0)
	{
		exit(0);
	}
	else if(pid <0)
	{
		return -1;
	}
	
	for(i=0;i<NOFILE;close(i++));
	
	chdir("/");
	
	umask(0);
	
	return 0;	
}


int main()
{
	time_t now;
	init_daemon();
	syslog(LOG_USER|LOG_INFO,"守護程序\n");
	
	while(1)
	{
		sleep(8);
		time(&now);
		syslog(LOG_USER|LOG_INFO,"系統時間:\t%s\t\n",ctime(&now));
	}
	
}

編譯運行

gcc -o daemon daemon.c
./daemon
ps -aux |grep daemon
或者ps -ef

查看進程,有如下輸出

svauto   103299  0.0  0.0   4352    80 ?        S    14:56   0:00 ./daemon

查看系統日誌。在/var/log目錄下的sudo tail syslog。結果如下圖。
在這裏插入圖片描述

四、程序退出

1.正常退出。可用方法

  • 在main中return
  • 調用exit
  • 調用_exit

2.異常退出。可用方法

  • 調用about函數
  • 進程收到某個信號,而該信號終止進程

五、執行新進程

執行新程序的函數
在這裏插入圖片描述
這幾個函數的區別與環境變量和傳參方法有關。

函數 傳參
execve 通過路勁方式調用,argv,envp對應新程序的argv ,envp
execv 通過路勁方式調用,argv對應新程序的argv
execl 和execv類似,不過argv參數數量不確定
execle 和execl類似,但是要指定環境變量
execvp 和execv類似,但可以在環境變量中找可執行程序
execlp 類似execle , 但可以在環境變量中找可執行程序

exec函數族除了execve是系統調用,其他都是庫函數。

exec函數返回錯誤表
在這裏插入圖片描述
實例代碼
程序1,用來被調用

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc,char *argv[],char **environ)
{
	int i;
	
	printf("i am a process image!\n");
	printf("My pid = %d, parentpid = %d\n",getpid(),getppid());
	printf("uid = %d,gid  = %d\n",getuid(),getgid());
	
	for(i= 0 ;i<argc;i++)
	{
		printf("argv[%d]:%s\n",i,argv[i]);
	}
}

程序2:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc,char *argv[],char ** environ)
{
	pid_t pid;
	int stat_val;
	
	printf("Exec example !\n");
	pid = fork();
	switch(pid)
	{
	case -1:
		perror("process creation failed\n");
		exit(1);
	case 0:
		printf("Child process is running\n");
		printf("My pid =%d,parentpid=%d\n",getpid(),getppid());
		execve("processimage",argv,environ);
		printf("process never go to here!\n");
		exit(0);
	default:
		printf("Parent process is running !\n");	
		break;
	
	}
	
	wait(&stat_val);
	exit(0);
}

運行程序
在這裏插入圖片描述
做個測試,如果把execveDemo刪除,運行程序就會報錯,如下
在這裏插入圖片描述
六、等待進程結束wait 和waitpid

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main()
{
	pid_t pid;
	char *msg;
	int k;
	int exit_code;
	
	printf("Study how to get exit code\n");
	pid =fork();
	switch(pid)
	{
	case 0:
		msg = "Child process is running";
		k = 5;
		exit_code = 37;
		break;
	case 1:
		perror("process create faild\n");
		exit(1);
	default:
		exit_code= 0;
		break;
		
	}
	
	//父進程會執行這段代碼,
	if(pid!=0)
	{
		int stat_val;
		pid_t child_pid;
		
		child_pid = wait(&stat_val);
		
		printf("Child process has exited,pid = %d\n",child_pid);
		if(WIFEXITED(stat_val))
			printf("child exited with code %d\n",WEXITSTATUS(stat_val));
		else 
			printf("child exited abnormallly\n");
			
	}
	else
	{//子進程暫停5秒,在這個時候可以運行ps -aux查看父進程狀態
		while(k-->0)
		{
			puts(msg);
			sleep(1);
		}
		
	}
	
	exit(exit_code);
}

運行程序輸出
在這裏插入圖片描述
可以看出父進程在執行wait後,就處於等待狀態,知道子進程退出後,父進程再繼續執行。

如果子進程比父進程提前結束,子進程就會成爲殭屍進程,佔用着資源,無法釋放。所以在子進程結束之前,需要讓父進程調用wait。殭屍進程只有父進程結束了,才能釋放資源。

八、常見的進程的其他操作

函數 功能
getpid 獲取進程ID
setuid 設置實際用戶id
setgid 設置有效用戶ID
nice 改變進程優先級
getpriority 獲取優先級
setpriority 設置優先級

遇到的錯誤總結:

如下圖錯誤,程序語法是沒有問題的,但是在最後鏈接的時候,因爲找不到函數,就會出現鏈接錯誤,如下圖,這時候需要增加頭文件。
在這裏插入圖片描述

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