進程標識
每一個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進程狀態
- 運行狀態
- 可中斷等待狀態
- 不可中斷等待狀態
- 僵死狀態
- 停止狀態
用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)來完成啓動。
創建守護進程需要有如下特性:
- 進程後臺運行,fork一個子進程,然後父進程退出。
- 將進程變成會話組長,調用setsid。
- 禁止進程重新打開控制終端。可以用fork創建一個子進程,然後父進程退出
- 關閉不再需要的文件描述符。避免浪費資源或造成文件系統無法卸載。
- 更改當前目錄爲根目錄。
- 設置屏蔽字爲0,使用umask(0)。
- 處理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 | 設置優先級 |
遇到的錯誤總結:
如下圖錯誤,程序語法是沒有問題的,但是在最後鏈接的時候,因爲找不到函數,就會出現鏈接錯誤,如下圖,這時候需要增加頭文件。