1.行程的概念
問1:什么是程式,什么是行程,有什么區別?
程式是靜態的概念,gcc xxx.c -o pro. 磁盤中生成pro檔案,叫做程式
行程是動態的概念,指的是程式的一次運行活動,通俗來說就是程式跑起來了,系統中就多了一個行程
程式:
(1)存放在磁盤上的指令和資料的有序集合(檔案)
(2)靜態的
行程:
(1)執行一個程式所分配資源的總稱
(2)行程是程式的一次執行程序
(3)動態的,包括創建、調度、執行和消亡
理解:cpu不能直接訪問磁盤,要先把程式中的內容(指令和資料)加載到記憶體中,這樣CPU才能去執行指令,才能獲取到資料,因此要執行程式,要分配記憶體空間,分配CPU資源,這些資源的總稱即為行程
一個程式執行的時候會創建多個行程
問2.什么是行程識別符號?
每個行程都有一個非負整數表示的唯一ID,叫做pid,類似身份證,可以和之前的檔案描述符fd一樣理解
Pid = 0; //交換行程(swapper)
作用:行程調度
Pid = 1;//init行程
作用:系統初始化
編程呼叫getpid函式獲取自身的行程識別符號,getppid獲取父行程的行程識別符號

通過系統資料段,可以使得作業系統有效的管理行程,系統資料段中主要包含:行程控制塊、CPU暫存器值、堆疊
1、行程控制塊(pcb)
作用:存放行程的屬性
有:
(1)行程標識PID:類似于人的身份證
(2)行程用戶:通過用戶檢查是否具有某種權限
(3)行程狀態、優先級:決定行程調度順序以及行程分配時間片的長短
(4)檔案描述符表:表明打開了哪些檔案
2、CPU暫存器值
每個行程會保存他用到暫存器的值
比如:pc暫存器(程式計數器)
用于存放行程下一條指令的地址
當行程的時間片用完,他就會讓出CPU,等到下次被調度執行的時候,行程不會重新開始執行,而是會從上一次停止的地方開始繼續往下執行,因此就需要從pc中取出下一條指令執行的地址,
3、堆疊
作業系統會用到行程堆疊,所有的區域變數,函式的引數等都會用到
2.行程型別
1.互動行程
在下shell啟動時,通過終端與用戶互動,用戶通過終端輸入,行程進行接受并處理,并將結果列印在終端上,是一種可以在前臺運行,也可以在后臺運行
前臺行程可以從終端輸入,也可以從終端輸出
后臺行程無法從終端讀取輸入,但可以往終端輸出
2.批處理行程
和終端無關,被提交到一個作業佇列中以便順序執行
批處理行程一般是管理員經常用到,開發人員很少用到
3.守護行程(重要)
和終端無關,一直在后臺運行
很多服務器都是以守護行程的形式在運行
系統關閉,守護行程才關閉,生命周期很長
3.行程狀態
(1)運行態,也分為正在運行的運行態和準備運行的準備態
(2)等待態,也成阻塞態,休眠態,指的是行程等待一個事件的發生或某種系統資源
當有資源的時候,作業系統會喚醒行程,行程又會回到運行態
也分為不可中斷和可中斷,通過會不會被信號打斷來進行區分
(3)停止態,行程被中止,收到信號后可繼續執行
最常用的是GDB去除錯一個程式的時候,設定斷點,運行到斷點時候會停下來,停下來的原因就是GDB向行程發了一個中止信號,
(4)死亡態,也叫僵尸態,已經終止的行程,但是pcb(放了行程回傳的值以及行程結束的一些資訊)沒有被釋放,其他資源釋放了
行程狀態圖如下:

4.查看行程資訊
1.ps
作用:查看系統行程快照
(1)ps -ef:查看所有行程的簡要資訊
(2)ps aux:還能顯示行程當前狀態
行程狀態:
R:運行態
S:等待態
Z:僵尸態
+:表示為前臺行程
兩種用法都可以配合管道進行過濾 |grep
2.top
作用:查看行程動態資訊,可以查看那些進行最占用資源,每個3s進行一次統計
3./proc
作用:查看行程詳細資訊,proc即目錄的意思
在該目錄下查看行程ID號,再進入行程對應目錄查看
5.行程相關命令-----修改優先級
1.nice
作用:按**用戶指定**的優先級運行行程
當使用yop命令時候可以看到有NI這一列,范圍為-20~19,默認為0,NI的數值越小代表優先級越高,
例:nice -n 2 ./test
其中:2就是指定優先級,普通用戶只能指定正整數,管理員用戶可以任意指定,
2.renice
作用:改變正在運行的行程的優先級
例:renice -n 2 xxx
其中,xxx為要修改行程的行程號,普通用戶只能降低優先級,管理員用戶可以降低,也可以提高,
3.jobs
作用:查看后臺行程(后臺作業)
例:./test &為運行一個后臺行程
得到[1] 29140
[1]為作業號,29140為行程號
再使用jobs命令就可以進行查看
4.bg
作用:將掛起的行程在后臺運行
5.fg
作用:將后臺運行的行程放到前臺運行
例:
fg 1:將一個后臺的行程放到前臺
ctrl+z:讓當前前臺行程在后臺掛起
bg 2:讓后臺作業在后臺運行起來
5.創建行程
使用fork函式創建一個行程
pid_t fork(void);
fork函式呼叫成功,回傳兩次
成功時父行程回傳子行程的ID號,子行程回傳0
回傳值為0:代表當前行程為子行程
回傳值為非負數:代表當前行程為父行程
回傳值為-1:呼叫失敗
fork創建一個子行程的一般目的:
(1)一個父行程希望復制自己,使父,子行程執行不同的代碼段,這在網路服務行程中是常見的------父行程等待客戶端的服務請求,在這種請求到達時,父行程呼叫fork,使子行程處理此請求,父行程繼續等待下一個請求到達,
(2)一個行程要執行一個不同的程式,這對shell是常見的情況,在這種情況下,子行程從fork回傳后立即呼叫exec
例子:
getpid():獲取當前行程,與pid進行比較
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t pid;
printf("pid = %d\n",getpid());
pid = fork();
if(pid>0){
printf("this is father pid,father pid is %d\n",getpid());
}else if(pid == 0){
printf("this is child pid,child pid is %d\n",getpid());
}else{
printf("fork error\n");
}
return 0;
}
運行結果:

父子行程:
(1)子行程繼承了父行程的內容
(2)父子行程有獨立的地址空間,互不影響
(3)若父行程先結束,則子行程會變成孤兒行程,被init行程收養,子行程變成后臺行程
(4)若子行程先結束,父行程沒有及時回收的話,子行程會變成僵尸行程
思考:
(1)子行程從何處開始運行?
答:從fork()后的下一條陳述句開始開始執行,注意:子行程并沒有執行fork(),因為如果子行程也執行了fork,最侄訓進去一個死回圈,不斷創建新的行程,
(2)父行程創建子行程后,父子行程誰先執行?
答:不確定,對于Linux來說,并沒有進行相應的規定,最終是看內核的調度,內核先調度誰,誰就先運行,
對上面兩個問題的總結即是第三個問題
(3)fork創建的時候發生了什么?
資料段,堆,堆疊拷貝
代碼段共享
父子行程誰先跑,由行程調度決定
驗證代碼:
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t pid;
int a = 10;
printf("pid = %d\n",getpid());
pid = fork();
if(pid>0){
printf("this is father pid,father pid is %d\n",getpid());
}else if(pid == 0){
printf("this is child pid,child pid is %d\n",getpid());
a = a + 10;
}else{
printf("fork error\n");
}
printf("a = %d\n",a);
return 0;
}
運行結果:

vfork函式也可以創建行程,與fork的區別:
1.vfork直接使用父行程存盤空間,不拷貝
2.vfork保證子行程先運行,當子行程呼叫exit退出后,父行程才執行
實體代碼:
#include<stdio.h>
#include <unistd.h>
#include<stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = vfork();
if(pid > 0){
while(1){
printf("cnt = %d\n",cnt);
printf("this is father pid,father pid = %d\n",getpid());
sleep(1);
}
}
else if(pid == 0){
while(1){
printf("this is child pid,child pid = %d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3){
exit(0);
}
}
}
return 0;
}
運行結果:
子行程線運行三次結束后,才運行父行程

6.行程退出與回收
正常退出:
1.main函式呼叫return退出
2.行程呼叫exit(),屬于標準c庫
3.行程呼叫_exit()或者 _Exit(),屬于系統呼叫
補充:
1.行程包含多個執行緒,行程最后一個執行緒回傳
2.最后一個執行緒呼叫pthread_exit
例外退出:
1.呼叫abort
2.當行程收到某些信號時,如Ctrl+C
3.最后一個執行緒對取消請求做出回應
不管行程如何終止,最后都會執行內核中的同一段代碼,這段代碼為相應行程關閉所有打開描述符,釋放它所使用的存盤器等,
1.當子行程退出狀態不被收集,會變成僵死行程(僵尸行程),代碼與前面一樣
1 #include<stdio.h>
2 #include <unistd.h>
3 #include<stdlib.h>
4 int main()
5 {
6 pid_t pid;
7 int cnt = 0;
8 pid = vfork();
9 if(pid > 0){
10 while(1){
11 printf("cnt = %d\n",cnt);
12 printf("this is father pid,father pid = %d\n",getpid());
13 sleep(1);
14 }
15 }
16 else if(pid == 0){
17 while(1){
18 printf("this is child pid,child pid = %d\n",getpid());
19 sleep(1);
20 cnt++;
21 if(cnt == 3){
22 exit(0);
23 }
24 }
25 }
26 return 0;
27 }


s+:正在運行的狀態
z+:僵尸行程
2.父行程等待子行程退出,并收集子行程的退出狀態
正常退出時,會有退出碼,即exit中的引數為幾,根據子行程的退出碼來判斷子行程的完成情況

pid_t wait(int *status);
status:整形數指標
非空:子行程退出狀態放在它所指的地址中
空:不關心退出狀態
實體代碼:
1 #include<stdio.h>
2 #include <unistd.h>
3 #include <sys/wait.h>
4 #include<stdlib.h>
5 int main()
6 {
7 pid_t pid;
8 int status = 10;
9 int cnt = 0;
10 pid = vfork();
11 if(pid > 0){
12 wait(&status);
13 printf("child quit,child status = %d\n",WEXITSTATUS(status));
14 while(1){
15 printf("cnt = %d\n",cnt);
16 printf("this is father pid,father pid = %d\n",getpid());
17 sleep(1);
18 }
19 }
20 else if(pid == 0){
21 while(1){
22 printf("this is child pid,child pid = %d\n",getpid());
23 sleep(1);
24 cnt++;
25 if(cnt == 3){
26 exit(3);//退出碼為3
27 }
28 }
29 }
30 return 0;
31 }

(1)如果其所有子行程都還在運行,則阻塞
(2)如果一個子行程已經終止,正等待父行程獲取其終止狀態,則取得該子行程的終止狀態立即回傳
(3)如果它沒有任何子行程,則立即出錯回傳
引入waitpid,區別:
wait使地呼叫者阻塞,waitpid有一個選項可以使呼叫者不阻塞
pid_t waitpid(pid_t pid,int *status,int options);
pid == -1:等待任一子行程,與wait等效
pid > 0:等待其行程ID與pid相等的子行程 //用的較多
pid == 0:等待其組ID等于呼叫行程ID的任一子行程
pid < -1:等待其組ID等于pid絕對值的任一子行程

3.孤兒行程
父行程如果不等待子行程退出,在子行程之前就結束了自己的“生命”,此時子行程就叫做孤兒行程
linux避免系統村存在過多的孤兒行程,init行程(行程ID為1,是系統的初始化行程)收留孤兒行程,變成孤兒行程的父行程
7.exec函式族
(1)行程呼叫exec函式族執行某個程式
(2)行程當前內容被指定的程式替換
(3)實作父子行程執行不同的程式
實作:父行程創建子行程,子行程呼叫exec函式族,從而去執行指定的程式,而父行程不會受到影響
這在shell中是很常見的,shell相當于一個父行程,shell創建的子行程會去呼叫執行用戶指定的程式,
博文推薦:exec族函式用法
exec實體代碼:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4
5 //函式原型:int execl(const char *path, const char *arg, ...);
6
7 int main(void)
8 {
9 printf("before execl\n");
10
11 char *argv[] = {"ps",NULL,NULL};
12 // if(execvp("ps",argv) == -1)
13 if(execv("/bin/ps",argv) == -1)
14 {
15 printf("execl failed!\n");
16
17
18 perror("why?");
19
20 }
21 printf("after execl\n");
22 return 0;
23 }
8.system函式
int system(const char* command);
system()函式的回傳值:
成功:回傳行程的狀態值
當sh不能執行時:回傳127
失敗:回傳-1
實體代碼:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
if(execv("ps") == -1) //直接ps
{
printf("execl failed!\n");
perror("why?");
}
printf("after execl\n");
return 0;
}
與exec不同的是最后還是會回傳到源程式當中,執行后面的代碼
推薦博文:Linux system函式詳解
9.popen函式
相比于system的好處:popen可以獲取運行的輸出結果
FILE *popen(const char *command,const char *type);
引數說明:
command: 是一個指向以 NULL 結束的 shell 命令字串的指標,這行命令將被傳到 bin/sh 并使用 -c 標志,shell 將執行這個命令,
type: 只能是讀或者寫中的一種,得到的回傳值(標準 I/O 流)也具有和 type 相應的只讀或只寫型別,如果 type 是 “r” 則檔案指標連接到 command 的標準輸出;如果 type 是 “w” 則檔案指標連接到 command 的標準輸入,
回傳值:
如果呼叫成功,則回傳一個讀或者打開檔案的指標,如果失敗,回傳NULL,具體錯誤要根據errno判斷
int pclose (FILE* stream)
引數說明:
stream:popen回傳的檔案指標
回傳值:
如果呼叫失敗,回傳 -1
popen執行某個程式,例如popen(“ps”,“r”);會把這個結果重定位到管道中,將資料往管道里面扔,管道的另外一邊呼叫fread來把資料讀到緩沖區
實體代碼:
#include <stdio.h>
#include <string.h>
int main(void)
{
FILE *fp;
char buf[10240] = {0};
fp = popen("ps","r");
fread(buf, 10240, 1, fp);
printf("%s\n",buf);
pclose(fp);
return 0;
}
博文推薦:linux下popen使用心得
10.守護行程
1.守護行程特點
1.1相關介紹:
(1)守護行程是Linux下三種行程型別之一
Linux作業系統包括三種不同型別的行程,每種行程都有自己的特點和屬性,
互動行程是由一個Shell啟動的行程,互動行程既可以在前臺運行,也可以在后臺運行,批處理行程和終端沒有聯系,是一個行程式列,監控行程(也稱系統守護行程)是Linux系統啟動時運行的行程,并常駐后臺,例如,httpd是著名的Apache服務器的監控行程,
(2)通常在系統啟動時運行,系統關閉時結束
(3)守護行程在Linux中大量使用,很多服務程式都是以守護行程形式運行
1.2特點
(1)始終在后臺運行
(2)獨立于任何終端(與互動行程的最大區別)
(3)周期性的執行某種任務或等待處理特定事件
2.會話,控制終端
(1)Linux以會話、行程組的方式管理行程
(2)每個行程屬于一個行程組
(3)會話是一個或多個行程的集合
會話中運行的第一個行程是shell,因此shell也叫作會話的首進行
(4)一個會話最多為只能打開一個控制終端
(5)終端關閉時,所有相關的進行都會結束
3.守護行程創建(5步)
(1)創建子行程,父行程退出
if(fork() > 0){
exit(0);
}
子行程變成孤兒行程
子行程在后臺運行
(2)子行程創建新會話
if(setsid() < 0){
exit(-1);
}
子行程變成新的會話組長
子行程脫離原來的終端
(3)更改當前作業目錄
chdir('/');
chdir("/tmp");
引數為:指定修改的目錄
守護行程一直在后臺運行,其作業目錄不能被卸載
重設當前目錄為cwd
(4)重設檔案權限掩碼
if(umask(0) < 0){
exit(-1);
}
檔案權限掩碼設定為0
只影響當前行程
(5)關閉打開的檔案描述符
int i;
for(i=0;i<getdtablesize();i++){
close(i);
}
getdtablesize回傳當前行程打開檔案的最大個數
關閉所有從父行程繼承的打開檔案
已經脫離終端,stdin/stdout/stderr無法再使用
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/275093.html
標籤:其他
上一篇:沒有對比就沒有傷害 Linux- RPM與yum軟體包安裝
下一篇:Docker學習系列
