距離上一次利用高并發技術實作360度行車記錄儀功能已經過去半年了,開始寫一系列關于系統編程和網路編程內容進行總結,
溫故而知新,歡迎大家討論學習,
文章目錄
- 1 行程相關概念
- 1.1 程式和行程
- 1.2 并發
- 1.3 虛擬記憶體和虛擬地址空間
- 1.4 行程控制塊PCB
- 1.5 行程狀態
- 2 行程控制
- 2.1 fork/getpid/getppid
- 2.1.1 函式原型
- 2.1.2 案例一 列印父子行程id
- 2.1.3 案例二 順序列印5個行程
- 2.2 行程共享
- 2.2.1 讀時共享寫時復制原則
- 2.2.2 案例一:非共享變數資料
- 2.3 exec函式族
- 2.4 孤兒行程 僵尸行程
- 2.4.1 概念+實體
- 2.5 wait/waitpid
- 2.5.1 wait函式原型及作用
- 2.5.2 宏函式判斷終止原因
- 2.5.3 wait和宏函式配套使用實體
- 2.5.4 waitpid函式原型及作用
- 2.5.6 waitpid回收指定子行程
- 2.5.7 waitpid回收指定子行程(錯誤寫法)
- 2.5.8 waitpid 回收全部子行程
- 2.5.9 暴力回收子行程
- 總結
1 行程相關概念
一些比較基本的概念,如果學過作業系統,這些內容都很常見,可以適當跳過…
- 單道程式設計
- 多道程式設計
- 微觀串行、宏觀并行 時間片
- 同步 和 異步
- 并發和并行
相關術語參考鏈接(重)
1.1 程式和行程
“程式(Program)”是一個靜態的概念,一般對應于作業系統中的一個可執行檔案
執行中的程式叫做行程(Process),是一個動態的概念,現代的作業系統都可以同時啟動多個行程,比如:我們在用酷狗聽音樂,也可以使用eclipse寫代碼,也可以同時用瀏覽器查看網頁,
1.2 并發
并發是多個任務交替執行的,多個任務之間可能還是串行的,所有的并發處理都有排隊等候,喚醒和執行這三個步驟,所以并發是宏觀的觀念,在微觀上他們都是序列被處理的,只不過資源不會在某一個上被阻塞(一般是通過時間片輪轉),所以在宏觀上多個幾乎同時到達的請求同時在被處理,如果是同一時刻到達的請求也會根據優先級的不同,先后進入佇列排隊等候執行,并發針對的是多個請求,比如:一個CPU,一個web服務,同時涌入多個請求,CPU需要交替切換的執行多個請求,而不是一個請求執行完成之后再執行下一個請求,并發的實質是一個物理CPU(也可以是多個物理CPU)在若干個程式之間多路復用,并發性是對有限物理資源強制行使 多用戶共享以提高效率,
1.3 虛擬記憶體和虛擬地址空間
參考鏈接
虛擬記憶體:虛擬記憶體是一種邏輯上擴充物理記憶體的技術,基本思想是用軟、硬體技術把記憶體與外存這兩級存盤器當做一級存盤器來用,虛擬記憶體技術的實作利用了自動覆寫和交換技術,簡單的說就是將硬碟的一部分作為記憶體來使用,
虛擬地址空間:在32位的i386 CPU的地址總線的是32位的,也就是說可以尋找到4G的地址空間,我們的程式被CPU執行,就是0x000000000xFFFFFFFF這一段地址中,高1G的空間為內核空間,由作業系統呼叫,低3G的空間為用戶空間,由用戶使用,
CPU在尋址的時候,是按照虛擬地址來尋址,然后通過MMU(記憶體管理單元)將虛擬地址轉換為物理地址,因為只有程式的一部分加入到記憶體中,所以會出現所尋找的地址不在記憶體中的情況(CPU產生缺頁例外),如果在記憶體不足的情況下,就會通過頁面調度演算法來將記憶體中的頁面置換出來,然后將在外存中的頁面加入到記憶體中,使程式繼續正常運行,
原圖鏈接

1.4 行程控制塊PCB
我們知道,每個行程在內核中都有一個行程控制塊(PCB)來維護行程相關的資訊,Linux內核的行程控制塊是 task_struct 結構體,
/usr/src/linux-headers-3.16.0-30/include/linux/sched.h 檔案中可以查看 struct task_struct 結構體定義,其內部成員有很多,我們重點掌握以下部分即可:

補充
檔案id查看指令:
ps aux
1.5 行程狀態
參考鏈接
行程基本的狀態有 5 種,分別為新建態,就緒態,運行態,阻塞態與終止態,其中新建態為行程準備階段,常與就緒態結合來看,
引起行程狀態轉換的具體原因如下:
?
- NULL→新建態:執行一個程式,創建一個子行程,
? - 新建態→就緒態:當作業系統完成了行程創建的必要操作,并且當前系統的性能和虛擬記憶體的容量均允許,
? - 運行態→終止態:當一個行程到達了自然結束點,或是出現了無法克服的錯誤,或是被作業系統所終結,或是被其他有終止權的行程所終結,
? - 運行態→就緒態:運行時間片到;出現有更高優先權行程,
? - 運行態→等待態:等待使用資源;如等待外設傳輸;等待人工干預,
? - 就緒態→終止態:未在狀態轉換圖中顯示,但某些作業系統允許父行程終結子行程,
? - 等待態→終止態:未在狀態轉換圖中顯示,但某些作業系統允許父行程終結子行程,
? - 終止態→NULL:完成善后操作,
2 行程控制
2.1 fork/getpid/getppid
2.1.1 函式原型
pid_t fork(void);失敗回傳-1;成功回傳:① 父行程回傳子行程的 ID(非負) ②子行程回傳 0pid_t getpid(void);獲取當前行程 IDpid_t getppid(void);獲取當前行程的父行程 ID
補充:初學者常常有個問題,就是子行程的執行范圍:其實子行程只會繼續執行fork函式之后的部分…
2.1.2 案例一 列印父子行程id
fork getpid getppid的使用
# include<stdio.h>
# include<stdlib.h>
# include<unistd.h>
int main()
{
pid_t pid ;
pid = fork();
if(pid<0)
{
perror("fork error");
}
else if(pid==0)
{
printf("I am child pid =%d my father pid=%d\n",getpid(),getppid());
}
else
{
printf("I am father pid =%d my father pid =%d\n",getpid(),getppid());
}
return 0;
}

2.1.3 案例二 順序列印5個行程
通過命令列引數指定創建行程的個數,每個行程休眠 1S 列印自己是第幾個被創建的行程,
# include<stdio.h>
# include<stdlib.h>
# include<unistd.h>
int main()
{
pid_t pid;
int i;
for(i=0;i<5;i++)
{
pid =fork();
if(pid==0)
{
break;
}
}
if(5==i)//父行程 等待for結束自己臺跳出
{
sleep(5);
printf("I am father\n");
}
else//子行程 提前break跳出 列印
{
sleep(i);
printf("I am %d child \n",i+1);
}
return 0;
}

2.2 行程共享
2.2.1 讀時共享寫時復制原則
相同部分:全域變數、.data、.text、堆疊、堆、環境變數、用戶 ID、宿主目錄、行程工
作目錄、信號處理方式…
不同部分:1.行程 ID 2.fork 回傳值 3.父行程 ID 4.行程運行時間 5.鬧鐘(定時器) 6.未決信號集
值得注意的是:
父子行程間遵循讀時共享寫時復制的原則,
這樣設計,無論子行程執行父行程的邏輯還是執行自己的邏輯都能節省記憶體開銷,
而不是簡單的復制0-3G用戶空間內容,
2.2.2 案例一:非共享變數資料
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int main(int argc, char *argv[])
{
int a = 10;
pid_t pid = fork(); // 創建子行程
if (pid == -1) {
perror("fork error");
exit(1);
} else if (pid == 0) { // 子行程
a=a+10;
printf("---child is created\n");
printf("a=%d\n",a);
} else if (pid > 0) { // 父行程
printf("---parent process: my child is %d\n", pid);
printf("a=%d\n",a);
}
printf("===================end of file\n"); // 父子行程各自執行一次.
return 0;
}

2.3 exec函式族
在專案中也沒有用到,也不做重點記錄了,需要的時候補充,
fork 創建子行程后執行的是和父行程相同的程式(但有可能執行不同的代碼分支),子行程往往要呼叫一種 exec 函式以執行另一個程式,當行程呼叫一種 exec 函式時,該行程的用戶空間代碼和資料完全被新程式替換,從新程式的啟動例程開始執行,呼叫 exec 并不創建新行程,所以呼叫 exec 前后該行程的 id 并未改變,
將當前行程的.text、.data 替換為所要加載的程式的.text、.data,然后讓行程從新的.text第一條指令開始執行,但行程 ID 不變,換核不換殼,
int execl(const char *path, const char *arg, ...);int execlp(const char *file, const char *arg, ...);int execle(const char *path, const char *arg, ..., char *const envp[]);int execv(const char *path, char *const argv[])int execvp(const char *file, char *const argv[]);int execve(const char *path, char *const argv[], char *const envp[]);
2.4 孤兒行程 僵尸行程
2.4.1 概念+實體
孤兒行程: 父行程先于子行程結束,則子行程成為孤兒行程,子行程的父行程成為 init行程,稱為 init 行程領養孤兒行程,
ps ajx
# include<stdio.h>
# include<stdlib.h>
# include<unistd.h>
int main()
{
pid_t pid ;
pid = fork();
if(pid<0)
{
perror("fork error");
}
else if(pid==0)
{
while(1)
{
printf("I am child pid =%d my father pid=%d\n",getpid(),getppid());
sleep(1);
}
}
else
{
printf("I am father pid =%d \n",getpid());
sleep(4);
printf("I am died\n");
return 0;
}
return 0;
}

僵尸行程: 子行程終止,父行程尚未回收,子行程殘留資源(PCB)存放于內核中,變成僵尸(Zombie)行程,
- 此時殺死父行程,父行程轉變成init,init發現子行程是僵尸,自動回收,
2.5 wait/waitpid
一個行程在終止時會關閉所有檔案描述符,釋放在用戶空間分配的記憶體,但它的 PCB還保留著,內核在其中保存了一些資訊:如果是正常終止則保存著退出狀態,如果是例外終止則保存著導致該行程終止的信號是哪個,這個行程的父行程可以呼叫 wait 或 waitpid 獲取這些資訊,然后徹底清除掉這個行程,我們知道一個行程的退出狀態可以在 Shell 中用特殊變數$?查看,因為 Shell 是它的父行程,當它終止時 Shell 呼叫 wait 或 waitpid 得到它的退出狀態同時徹底清除掉這個行程,
2.5.1 wait函式原型及作用
作用:
- 阻塞等待子行程退出
- 回收子行程殘留資源
- 獲取子行程結束狀態(退出原因),
原型:
pid_t wait(int *status);- 成功:清理掉的子行程 ID;失敗:-1 (沒有子行程)
2.5.2 宏函式判斷終止原因
WIFEXITED(status)宏判斷為真 表示程式正常退出WEXITSTATUS(status)上一個宏判斷為真 則回傳狀態值WIFSIGNALED(status)宏判斷為真 表示程式例外退出WTERMSIG(status)上一個判斷為真,則回傳狀態值
2.5.3 wait和宏函式配套使用實體
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t pid,wpid;
int status;
pid =fork();
if(pid==0)
{
printf(" i am child,my id is%d\n",getpid());
printf("child die\n");
return 73;
}
else if(pid>0)
{
wpid=wait(NULL);//不關心怎么結束的
wpid = wait(&status);//等待子行程結束
if(wpid==-1)
{
perror("wait error");
exit(1);
}
if(WIFEXITED(status))//判斷 子行程正常退出判斷
{
printf("child exit with%d\n",WEXITSTATUS(status));
printf("------parent finish\n");
}
if(WIFSIGNALED(status))//判斷 子行程例外退出判斷
{
printf("child exit with%d\n",WTERMSIG(status));
}
}
else
{
perror("fork");
return 1;
}
}

2.5.4 waitpid函式原型及作用
作用:
作用同 wait,但可指定 pid 行程清理,可以不阻塞,
原型:
pid_t waitpid(pid_t pid, int *status, in options);成功:回傳清理掉的子行程 ID;失
敗:-1(無子行程)
引數pid
-
0 回收指定 ID 的子行程
- -1 回收任意子行程(相當于 wait)
- 0 回收和當前呼叫 waitpid 一個組的所有子行程
- < -1 回收指定行程組內的任意子行程
引數三
- 使用WNOHANG,子行程正在運行,直接回傳0,設定阻塞或者非阻塞
注意:
- 一次 wait 或 waitpid 呼叫只能清理一個子行程,清理多個子行程應使用回圈,
2.5.6 waitpid回收指定子行程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
int main(int argc, char *argv[])
{
int i;
pid_t pid, wpid, tmpid;
for (i = 0; i < 5; i++) {
pid = fork();
if (pid == 0) { // 回圈期間, 子行程不 fork
break;
}
if (i == 2) {
tmpid = pid; //子行程pid
printf("--------pid = %d\n", tmpid);
}
}
if (5 == i) { // 父行程, 從 運算式 2 跳出
// sleep(5);
//wait(NULL); // 一次wait/waitpid函式呼叫,只能回收一個子行程.
//wpid = waitpid(-1, NULL, WNOHANG); //回收任意子行程,沒有結束的子行程,父行程直接回傳0
//wpid = waitpid(tmpid, NULL, 0); //指定一個行程回收, 阻塞等待
printf("i am parent , before waitpid, pid = %d\n", tmpid);
//wpid = waitpid(tmpid, NULL, WNOHANG); //指定一個行程回收, 不阻塞
wpid = waitpid(tmpid, NULL, 0); //指定一個行程回收, 阻塞回收
if (wpid == -1) {
perror("waitpid error");
exit(1);
}
printf("I'm parent, wait a child finish : %d \n", wpid);
} else { // 子行程, 從 break 跳出
sleep(i);
printf("I'm %dth child, pid= %d\n", i+1, getpid());
}
return 0;
}

2.5.7 waitpid回收指定子行程(錯誤寫法)
這段代碼從結果上可以看出做不到回收指定的行程,兩段代碼的區別在于子行程id的存放的方式,
在這段代碼中,我們在子行程中做了pid = getpid();父子行程資源不共享導致的是父行程不知道這個pid的值,
而在上一段代碼中,我們直接通過pid = fork();tmpid = pid;這樣的方式在父行程中成功保存子行程的行程號,
//指定回收一個子行程錯誤示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
int main(int argc, char *argv[])
{
int i;
pid_t pid, wpid;
for (i = 0; i < 5; i++) {
if (fork() == 0) { // 回圈期間, 子行程不 fork
if (i == 2) {
pid = getpid();
printf("------pid = %d\n", pid);
}
break;
}
}
if (5 == i) { // 父行程, 從 運算式 2 跳出
sleep(5);
printf("------in parent , before waitpid, pid= %d\n", pid);
wpid = waitpid(pid, NULL, 0); //指定一個行程回收
if (wpid == -1) {
perror("waitpid error");
exit(1);
}
printf("I'm parent, wait a child finish : %d \n", wpid);
} else { // 子行程, 從 break 跳出
sleep(i);
printf("I'm %dth child, pid= %d\n", i+1, getpid());
}
return 0;
}

2.5.8 waitpid 回收全部子行程
// 回收多個子行程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
int main(int argc, char *argv[])
{
int i;
pid_t pid, wpid;
for (i = 0; i < 5; i++) {
pid = fork();
if (pid == 0) { // 回圈期間, 子行程不 fork
break;
}
}
if (5 == i) { // 父行程, 從 運算式 2 跳出
while((wpid=waitpid(-1,NULL,0))>0)
{
printf("I'm parent,wait a child finish:%d\n",wpid);
}
} else { // 子行程, 從 break 跳出
sleep(i);
printf("I'm %dth child, pid= %d\n", i+1, getpid());
}
return 0;
}
2.5.9 暴力回收子行程
kill -9 父行程號
總結
如有錯誤歡迎指出…

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/276626.html
標籤:其他
