Linux C編程第十章 進(jìn)程及進(jìn)程控制

      網(wǎng)友投稿 952 2025-04-04

      一、整體大綱


      二、基礎(chǔ)知識(shí)

      1. 進(jìn)程相關(guān)概念

      1)程序和進(jìn)程

      程序,是指編譯好的二進(jìn)制文件,在磁盤(pán)上,不占用系統(tǒng)資源(cpu、內(nèi)存、打開(kāi)的文件、設(shè)備、鎖....)

      進(jìn)程,是一個(gè)抽象的概念,與操作系統(tǒng)原理聯(lián)系緊密。進(jìn)程是活躍(運(yùn)行起來(lái)的)的程序,占用系統(tǒng)資源。在內(nèi)存中執(zhí)行。(程序運(yùn)行起來(lái),產(chǎn)生一個(gè)進(jìn)程)。

      程序 → 劇本(紙) 進(jìn)程 → 戲(舞臺(tái)、演員、燈光、道具...),同一個(gè)劇本可以在多個(gè)舞臺(tái)同時(shí)上演。同樣,同一個(gè)程序也可以加載為不同的進(jìn)程(彼此之間互不影響)

      如:同時(shí)開(kāi)兩個(gè)終端。各自都有一個(gè)bash但彼此ID不同。

      2)并發(fā)

      并發(fā),在操作系統(tǒng)中,一個(gè)時(shí)間段中有多個(gè)進(jìn)程都處于已啟動(dòng)運(yùn)行到運(yùn)行完畢之間的狀態(tài)。但,任一個(gè)時(shí)刻點(diǎn)上仍只有一個(gè)進(jìn)程在運(yùn)行。理論依據(jù):時(shí)鐘中斷

      例如,當(dāng)下,我們使用計(jì)算機(jī)時(shí)可以邊聽(tīng)音樂(lè)邊聊天邊上網(wǎng)。 若籠統(tǒng)的將他們均看做一個(gè)進(jìn)程的話(huà),為什么可以同時(shí)運(yùn)行呢,因?yàn)椴l(fā)。

      3)單道程序設(shè)計(jì)

      所有進(jìn)程一個(gè)一個(gè)排對(duì)執(zhí)行。若A阻塞,B只能等待,即使CPU處于空閑狀態(tài)。而在人機(jī)交互時(shí)阻塞的出現(xiàn)時(shí)必然的。所有這種模型在系統(tǒng)資源利用上及其不合理,在計(jì)算機(jī)發(fā)展歷史上存在不久,大部分便被淘汰了。

      4)多道程序設(shè)計(jì)

      在計(jì)算機(jī)內(nèi)存中同時(shí)存放幾道相互獨(dú)立的程序,它們?cè)诠芾沓绦蚩刂浦拢嗷ゴ┎宓倪\(yùn)行。多道程序設(shè)計(jì)必須有硬件基礎(chǔ)作為保證。

      時(shí)鐘中斷即為多道程序設(shè)計(jì)模型的理論基礎(chǔ)。 并發(fā)時(shí),任意進(jìn)程在執(zhí)行期間都不希望放棄cpu。因此系統(tǒng)需要一種強(qiáng)制讓進(jìn)程讓出cpu資源的手段。時(shí)鐘中斷有硬件基礎(chǔ)作為保障,對(duì)進(jìn)程而言不可抗拒。 操作系統(tǒng)中的中斷處理函數(shù),來(lái)負(fù)責(zé)調(diào)度程序執(zhí)行。

      在多道程序設(shè)計(jì)模型中,多個(gè)進(jìn)程輪流使用CPU (分時(shí)復(fù)用CPU資源)。而當(dāng)下常見(jiàn)CPU為納秒級(jí),1秒可以執(zhí)行大約10億條指令。由于人眼的反應(yīng)速度是毫秒級(jí),所以看似同時(shí)在運(yùn)行。

      1s = 1000ms, 1ms = 1000us, 1us = 1000ns ???1000000000

      實(shí)質(zhì)上,并發(fā)是宏觀(guān)并行,微觀(guān)串行!推動(dòng)了計(jì)算機(jī)蓬勃發(fā)展,將人類(lèi)引入了多媒體時(shí)代。

      5)CPU和MMU

      我們?cè)诔绦蛑杏玫降乃械膬?nèi)存都是虛擬內(nèi)存,但是虛擬內(nèi)存在計(jì)算機(jī)中是不實(shí)際存在的,我們存儲(chǔ)的數(shù)據(jù)都是存儲(chǔ)在物理內(nèi)存中。

      內(nèi)存管理單元MMU

      6)進(jìn)程控制塊PCB

      我們知道,每個(gè)進(jìn)程在內(nèi)核中都有一個(gè)進(jìn)程控制塊(PCB)來(lái)維護(hù)進(jìn)程相關(guān)的信息,Linux內(nèi)核的進(jìn)程控制塊是task_struct結(jié)構(gòu)體。補(bǔ)充:打開(kāi)文件的信息也是一個(gè)結(jié)構(gòu)體

      /usr/src/kernels/3.10.0-957.10.1.el7.x86_64/include/linux/sched.h(Centos7)文件中可以查看struct task_struct 結(jié)構(gòu)體定義。其內(nèi)部成員有很多,我們重點(diǎn)掌握以下部分即可:

      * 進(jìn)程id。系統(tǒng)中每個(gè)進(jìn)程有唯一的id,在C語(yǔ)言中用pid_t類(lèi)型表示,其實(shí)就是一個(gè)非負(fù)整數(shù)。 * 進(jìn)程的狀態(tài),有初始化、就緒、運(yùn)行、掛起、停止等狀態(tài)。 * 進(jìn)程切換時(shí)需要保存和恢復(fù)的一些CPU寄存器的值。 * 描述虛擬地址空間的信息。 * 描述控制終端的信息。 * 當(dāng)前工作目錄(Current Working Directory)。 * umask掩碼。 * 文件描述符表,包含很多指向已經(jīng)打開(kāi)的文件的file結(jié)構(gòu)體的指針的一個(gè)數(shù)組。 (注:pcb中有一根指針,指針存儲(chǔ)的是文件描述符表的首地址) * 和信號(hào)相關(guān)的信息。 * 用戶(hù)id和組id。 * 會(huì)話(huà)(Session)和進(jìn)程組。 * 進(jìn)程可以使用的資源上限(Resource Limit)。

      ulimit -a 查看當(dāng)前Linux下的資源上線(xiàn)

      用戶(hù)空間 映射到物理內(nèi)存是獨(dú)立的

      7)進(jìn)程狀態(tài)

      進(jìn)程基本的狀態(tài)有5種。分別為初始態(tài),就緒態(tài),運(yùn)行態(tài),掛起態(tài)與終止態(tài)。其中初始態(tài)為進(jìn)程準(zhǔn)備階段,常與就緒態(tài)結(jié)合來(lái)看。

      2. 環(huán)境變量

      (1)定義:

      環(huán)境變量,是指在操作系統(tǒng)中用來(lái)指定操作系統(tǒng)運(yùn)行環(huán)境的一些參數(shù)。通常具備以下特征:

      ① 字符串(本質(zhì)) ② 有統(tǒng)一的格式:名=值[:值] ③ 值用來(lái)描述進(jìn)程環(huán)境信息。

      存儲(chǔ)形式:與命令行參數(shù)類(lèi)似。char *[]數(shù)組,數(shù)組名environ,內(nèi)部存儲(chǔ)字符串,NULL作為哨兵結(jié)尾。

      使用形式:與命令行參數(shù)類(lèi)似。

      加載位置:與命令行參數(shù)類(lèi)似。位于用戶(hù)區(qū),高于stack的起始位置。

      引入環(huán)境變量表:須聲明環(huán)境變量。extern char ** environ;

      (2)常見(jiàn)環(huán)境變量:

      按照慣例,環(huán)境變量字符串都是name=value這樣的形式,大多數(shù)name由大寫(xiě)字母加下劃線(xiàn)組成,一般把name的部分叫做環(huán)境變量,value的部分則是環(huán)境變量的值。環(huán)境變量定義了進(jìn)程的運(yùn)行? ? ? ? 環(huán)境,一些比較重要的環(huán)境變量的含義如下:

      PATH

      可執(zhí)行文件的搜索路徑。ls命令也是一個(gè)程序,執(zhí)行它不需要提供完整的路徑名/bin/ls,然而通常我們執(zhí)行當(dāng)前目錄下的程序a.out卻需要提供完整的路徑名./a.out,這是因?yàn)镻ATH環(huán)境變量的值里面包含了ls命令所在的目錄/bin,卻不包含a.out所在的目錄。PATH環(huán)境變量的值可以包含多個(gè)目錄,用:號(hào)隔開(kāi)。在Shell中用echo命令可以查看這個(gè)環(huán)境變量的值:

      $ echo $PATH

      SHELL

      當(dāng)前Shell,它的值通常是/bin/bash。

      TERM

      當(dāng)前終端類(lèi)型,在圖形界面終端下它的值通常是xterm,終端類(lèi)型決定了一些程序的輸出顯示方式,比如圖形界面終端可以顯示漢字,而字符終端一般不行。

      LANG

      語(yǔ)言和locale,決定了字符編碼以及時(shí)間、貨幣等信息的顯示格式。

      【Linux C編程】第十章 進(jìn)程及進(jìn)程控制

      HOME

      當(dāng)前用戶(hù)主目錄的路徑,很多程序需要在主目錄下保存配置文件,使得每個(gè)用戶(hù)在運(yùn)行該程序時(shí)都有自己的一套配置。

      (3)相關(guān)函數(shù)

      1)getenv

      函數(shù)作用:獲取當(dāng)前進(jìn)程環(huán)境變量

      頭文件

      #include

      函數(shù)原型

      char *getenv(const char *name);

      參數(shù)說(shuō)明:

      name環(huán)境變量名

      返回值

      成功:指向環(huán)境變值得指針

      失敗:返回NULL

      示例:

      獲取當(dāng)前進(jìn)程家目錄

      1 #include 2 #include 3 4 int main() 5 { 6 printf("homepath is [%s]\n", getenv("HOME")); 7 8 return 0; 9 }

      2)setenv

      函數(shù)作用:設(shè)置環(huán)境變量。

      頭文件:

      #include

      函數(shù)原型:

      int setenv(const char *name, const char *value, int overwrite);

      參數(shù)說(shuō)明:

      name 環(huán)境變量名

      value 要設(shè)置的環(huán)境變量值

      overwrite取值: 1:覆蓋原環(huán)境變量。0:不覆蓋

      返回值:

      成功:0;失敗:-1

      3)unsetenv

      函數(shù)作用:刪除環(huán)境變量name的定義

      頭文件:

      #include

      函數(shù)原型:

      int unsetenv(const char *name);

      參數(shù)說(shuō)明:

      name 環(huán)境變量名

      返回值

      成功:0;

      失敗:-1

      注意:name不存在仍返回0(成功),當(dāng)name命名為"ABC="時(shí)則會(huì)出錯(cuò)。因?yàn)椤?”是構(gòu)成環(huán)境變量中的一個(gè)組成部分。

      setenv 和 unsetenv 后兩個(gè)不常用。

      3. 進(jìn)程控制

      (1)進(jìn)程相關(guān)函數(shù)

      1)fork

      函數(shù)作用:創(chuàng)建子進(jìn)程

      頭文件

      #include

      函數(shù)原型

      pid_t fork(void);

      返回值

      成功:兩次返回,父進(jìn)程返回子進(jìn)程的id,子進(jìn)程返回0

      失敗:返回-1給父進(jìn)程,設(shè)置errno

      示例:

      fork示例

      1 #include 2 #include 3 #include 4 5 int main() 6 { 7 printf("Begin ..."); 8 pid_t pid = fork(); 9 printf("End ...\n"); 10 11 return 0; 12 }

      執(zhí)行結(jié)果分析:

      執(zhí)行結(jié)果: begin...end... begin...end... 結(jié)果分析: 為何會(huì)打印兩次begin? 這是由于printf("Begin ...");執(zhí)行之后并不會(huì)打印到屏幕,而是在緩沖區(qū),因此fork之后子進(jìn)程在執(zhí)行printf("End ...\n");遇到\n則全部打印出來(lái)。如果修改為printf("Begin …\n");(在遇到\n時(shí)會(huì)將緩沖區(qū)內(nèi)容打印到屏幕。)則子進(jìn)程不會(huì)打印begin…

      2)getpid與getppid

      函數(shù)作用:獲取進(jìn)程id

      頭文件

      #include #include

      函數(shù)原型

      pid_t getpid(void); 獲得當(dāng)前進(jìn)程id pid_t getppid(void); 獲得當(dāng)前進(jìn)程的父進(jìn)程id

      返回值:

      getpid 獲得當(dāng)前進(jìn)程的pid,getppid獲取當(dāng)前進(jìn)程父進(jìn)程的pid。

      示例1:

      getpid與getppid示例

      1 #include 2 #include 3 #include 4 5 int main() 6 { 7 printf("Begin ...\n"); 8 pid_t pid = fork(); 9 if (pid < 0) 10 { 11 perror("fork err:"); 12 return -1; 13 } 14 if (pid == 0) 15 { 16 printf("I am son, pid = %d, ppid = %d\n", getpid(), getppid()); 17 while(1) 18 { 19 printf("I am a child.\n"); 20 sleep(1); 21 } 22 } 23 else if (pid > 0) 24 { 25 printf("I am father, self = %d, ppid = %d\n", getpid(), getppid()); 26 //要是不加sleep則,父進(jìn)程比子進(jìn)程退出早,子進(jìn)程成為孤兒進(jìn)程 27 while(1) 28 { 29 sleep(1); 30 } 31 } 32 printf("End ...\n"); 33 34 return 0; 35 }

      補(bǔ)充:

      (1)查看進(jìn)程信息:

      init進(jìn)程是所有進(jìn)程的祖先。

      ps命令:

      ps aux

      ps ajx ---可以追溯進(jìn)程之間的血緣關(guān)系

      kill命令:

      給進(jìn)程發(fā)送一個(gè)信號(hào)

      SIGKILL 9信號(hào)

      kill -SIGKILL pid 殺死進(jìn)程

      (2)循環(huán)創(chuàng)建n個(gè)子進(jìn)程

      一次fork函數(shù)調(diào)用可以創(chuàng)建一個(gè)子進(jìn)程。那么創(chuàng)建N個(gè)子進(jìn)程應(yīng)該怎樣實(shí)現(xiàn)呢?

      簡(jiǎn)單想,for(i = 0; i < n; i++) { fork() } 即可。但這樣創(chuàng)建的是N個(gè)子進(jìn)程嗎?

      示例2:創(chuàng)建多個(gè)線(xiàn)程?

      bug版本(創(chuàng)建2個(gè)線(xiàn)程)

      1 #include 2 #include 3 #include 4 5 int main() 6 { 7 int n = 2; 8 int i = 0; 9 pid_t pid = 0; 10 for(i = 0; i < 2; i++) //父進(jìn)程循環(huán)結(jié)束 11 { 12 pid = fork(); 13 if (pid == 0) 14 { 15 //son 16 printf("I am child,pid=%d,ppid=%d,i=%d\n", getpid(), getppid(), i); 17 //break; //子進(jìn)程退出循環(huán)的接口 18 } 19 else if (pid > 0) 20 { 21 //father 22 printf("I am father,pid=%d,ppid=%d, i=%d\n", getpid(), getppid(), i); 23 } 24 } 25 while(1) 26 { 27 sleep(1); 28 } 29 30 return 0; 31 }

      執(zhí)行結(jié)果:

      總共產(chǎn)生4個(gè)進(jìn)程,但是本來(lái)想產(chǎn)生2個(gè),因此將代碼中的break打開(kāi),在fork進(jìn)程之后,將子進(jìn)程退出。

      結(jié)果分析:

      從上圖我們可以很清晰的看到,當(dāng)n為3時(shí)候,循環(huán)創(chuàng)建了(2^n)-1個(gè)子進(jìn)程,而不是N的子進(jìn)程。需要在循環(huán)的過(guò)程,保證子進(jìn)程不再執(zhí)行fork ,因此當(dāng)(fork() == 0)時(shí),子進(jìn)程應(yīng)該立即break;才正確。

      如何修改成預(yù)期創(chuàng)建兩個(gè)線(xiàn)程?

      將代碼中的break解注釋?zhuān)?dāng)為子線(xiàn)程的時(shí)候直接退出。

      重點(diǎn):通過(guò)該練習(xí)掌握框架:循環(huán)創(chuàng)建n個(gè)子進(jìn)程,使用循環(huán)因子i對(duì)創(chuàng)建的子進(jìn)程加以區(qū)分。

      示例3:進(jìn)程先創(chuàng)建先退出?

      進(jìn)程先創(chuàng)建先退出

      1 #include 2 #include 3 #include 4 5 int main() 6 { 7 int n = 5; 8 int i = 0; 9 pid_t pid = 0; 10 for(i = 0; i < n; i++) //父進(jìn)程循環(huán)結(jié)束 11 { 12 pid = fork(); 13 if (pid == 0) 14 { 15 //son 16 printf("I am child,pid=%d,ppid=%d,i=%d\n", getpid(), getppid(), i); 17 break; //子進(jìn)程退出循環(huán)的接口 18 } 19 else if (pid > 0) 20 { 21 //father 22 printf("I am father,pid=%d,ppid=%d, i=%d\n", getpid(), getppid(), i); 23 } 24 } 25 26 sleep(i); 27 if (i < n) 28 { 29 printf("I am child, will exit, pid=%d, ppid=%d\n", getpid(), getppid()); 30 } 31 else 32 { 33 printf("I am parent, will exit, pid=%d, ppid=%d\n", getpid(), getppid()); 34 } 35 36 return 0; 37 }

      3)getuid

      uid_t getuid(void); --> 獲取當(dāng)前進(jìn)程實(shí)際用戶(hù)ID

      uid_t geteuid(void); -->?獲取當(dāng)前進(jìn)程有效用戶(hù)ID

      4)getgid

      gid_t getgid(void); -->?獲取當(dāng)前進(jìn)程使用用戶(hù)組ID

      gid_t getegid(void); -->?獲取當(dāng)前進(jìn)程有效用戶(hù)組ID

      (2)進(jìn)程共享

      父子進(jìn)程之間在fork后。有哪些相同,那些相異之處呢?

      剛fork之后:

      父子相同處(0-3G的用戶(hù)區(qū)及3-4G的內(nèi)核區(qū)大部分): 全局變量、.data、.text、棧、堆、環(huán)境變量、用戶(hù)ID、宿主目錄、進(jìn)程工作目錄、信號(hào)處理方式...

      父子不同處(3-4G中的內(nèi)核區(qū)的PCB區(qū)): 1.進(jìn)程ID ??2.fork返回值 ??3.父進(jìn)程ID ???4.進(jìn)程運(yùn)行時(shí)間 ???5.鬧鐘(定時(shí)器)(定時(shí)器是以進(jìn)程為單位進(jìn)行分配,每個(gè)進(jìn)程有且僅有一個(gè)) 6.未決信號(hào)集。

      似乎,子進(jìn)程復(fù)制了父進(jìn)程0-3G用戶(hù)空間內(nèi)容,以及父進(jìn)程的PCB,但pid不同。真的每fork一個(gè)子進(jìn)程都要將父進(jìn)程的0-3G地址空間完全拷貝一份,然后在映射至物理內(nèi)存嗎?

      當(dāng)然不是!父子進(jìn)程間遵循讀時(shí)共享寫(xiě)時(shí)復(fù)制的原則(針對(duì)的是物理地址)。這樣設(shè)計(jì),無(wú)論子進(jìn)程執(zhí)行父進(jìn)程的邏輯還是執(zhí)行自己的邏輯都能節(jié)省內(nèi)存開(kāi)銷(xiāo)。

      練習(xí):編寫(xiě)程序測(cè)試,父子進(jìn)程是否共享全局變?

      驗(yàn)證父子進(jìn)程不共享全局變量

      1 #include 2 #include 3 4 int var = 100; 5 6 int main() 7 { 8 pid_t pid = fork(); 9 if (pid == 0) 10 { 11 //son 12 printf("var = %d, child, pid = %d, ppid = %d\n", var, getpid(), getppid()); 13 var = 1000; 14 printf("var = %d, child, pid = %d, ppid = %d\n", var, getpid(), getppid()); 15 sleep(3); //等待父進(jìn)程修改var完成 16 printf("var = %d, child, pid = %d, ppid = %d\n", var, getpid(), getppid()); 17 } 18 else if (pid > 0) 19 { 20 sleep(1); //保證子進(jìn)程能夠修改var的值成功 21 printf("var = %d, parent, pid = %d, ppid = %d\n", var, getpid(), getppid()); 22 var = 2000; 23 printf("var = %d, parent, pid = %d, ppid = %d\n", var, getpid(), getppid()); 24 25 } 26 27 return 0; 28 }

      結(jié)論:父子進(jìn)程不共享全局變量。

      父子進(jìn)程共享:1. 文件描述符(打開(kāi)文件的結(jié)構(gòu)體) ?2. mmap建立的映射區(qū) (進(jìn)程間通信詳解)

      特別的,fork之后父進(jìn)程先執(zhí)行還是子進(jìn)程先執(zhí)行不確定。取決于內(nèi)核所使用的調(diào)度算法,即隨機(jī)爭(zhēng)奪。

      如上圖:如果有一個(gè)全局變量 i = 5,當(dāng)fork出子進(jìn)程之后,此時(shí)父子進(jìn)程指向同一片物理內(nèi)存,父子進(jìn)程讀到的 i = 5,但是當(dāng)子進(jìn)程或者父進(jìn)程去修改全局變量(i = 10),則此時(shí)系統(tǒng)會(huì)開(kāi)辟一片新內(nèi)存,則父子進(jìn)程的 i 就不是同一個(gè)值。這樣做為了減少系統(tǒng)開(kāi)銷(xiāo),也就是讀時(shí)共享,寫(xiě)時(shí)復(fù)制。

      (3)gdb調(diào)試

      使用gdb調(diào)試的時(shí)候,gdb只能跟蹤一個(gè)進(jìn)程。可以在fork函數(shù)調(diào)用之前,通過(guò)指令設(shè)置gdb調(diào)試工具跟蹤父進(jìn)程或者是跟蹤子進(jìn)程。默認(rèn)跟蹤父進(jìn)程。

      set follow-fork-mode child 命令設(shè)置gdb在fork之后跟蹤子進(jìn)程。 set follow-fork-mode parent 設(shè)置跟蹤父進(jìn)程。

      注意,一定要在fork函數(shù)調(diào)用之前設(shè)置才有效。

      4. exec函數(shù)族

      fork創(chuàng)建子進(jìn)程后執(zhí)行的是和父進(jìn)程相同的程序(但有可能執(zhí)行不同的代碼分支),子進(jìn)程往往要調(diào)用一種exec函數(shù)以執(zhí)行另一個(gè)程序。當(dāng)進(jìn)程調(diào)用一種exec函數(shù)時(shí),該進(jìn)程的用戶(hù)空間代碼和數(shù)據(jù)完全被新程序替換,從新程序的啟動(dòng)例程開(kāi)始執(zhí)行。調(diào)用exec并不創(chuàng)建新進(jìn)程,所以調(diào)用exec前后該進(jìn)程的id并未改變。

      將當(dāng)前進(jìn)程的.text、.data替換為所要加載的程序的.text、.data,然后讓進(jìn)程從新的.text第一條指令開(kāi)始執(zhí)行,但進(jìn)程ID不變,換核不換殼。也就是調(diào)用完exec函數(shù)中的命令之后,原來(lái)函數(shù)后面的代碼就不會(huì)執(zhí)行。

      其實(shí)有六種以exec開(kāi)頭的函數(shù),統(tǒng)稱(chēng)exec函數(shù):

      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[]);

      1)execlp

      函數(shù)作用:

      頭文件

      #include

      函數(shù)原型

      int execlp(const char *file, const char *arg, ...);

      參數(shù)說(shuō)明:

      file 需要加載的程序的名字

      arg 一般是程序名

      ... 參數(shù)名,可變參數(shù)

      返回值:

      成功:無(wú)返回

      失敗:-1

      注意:p是PATH的縮寫(xiě),?execlp 加載一個(gè)進(jìn)程,借助PATH環(huán)境變量 (不用寫(xiě)該命令的絕對(duì)路徑,會(huì)到當(dāng)前進(jìn)程的環(huán)境變量中去找),當(dāng)PATH中所有目錄搜索后沒(méi)有參數(shù)1則出錯(cuò)返回。

      該函數(shù)通常用來(lái)調(diào)用系統(tǒng)程序。如:ls、date、cp、cat等命令。

      比如:execlp("ls", "ls", "-l", "-F", NULL);? 使用程序名在PATH中搜索。

      注意:int execlp(const char *file, const char *arg, ...);? --->??int execlp(const char *file, const char *argv[]); 因此 arg 就相當(dāng)于第一個(gè)參數(shù)(argv[0])。

      execlp示例

      1 #include 2 #include 3 4 int main() 5 { 6 //int execlp(const char *file, const char *arg, ...); 7 //注意:參數(shù)是從arg[0]開(kāi)始,因此第二個(gè)參數(shù)也為ls,占位 8 execlp("ls", "ls", "-l", "--color=auto", NULL); 9 //不需要判斷返回值 10 perror("exec err:"); 11 12 return 0; 13 }

      2)execl函數(shù)

      其中 l 是 list 的縮寫(xiě),基本同execlp函數(shù),只是該函數(shù)在加載程序式,需要寫(xiě)絕對(duì)路徑。

      比如:execl("/bin/ls", "ls", "-l", "-F", NULL); ???使用參數(shù)1給出的絕對(duì)路徑搜索。

      execl示例

      1 #include 2 #include 3 4 int main() 5 { 6 //int execlp(const char *file, const char *arg, ...); 7 //注意:參數(shù)是從arg[0]開(kāi)始,因此第二個(gè)參數(shù)也為ls,占位 8 execl("/bin/ls", "ls", "-l", "--color=auto", NULL); 9 //不需要判斷返回值 10 perror("exec err:"); 11 //下面代碼不會(huì)執(zhí) 12 printf("hello\n"); 13 14 return 0; 15 }

      3)execvp函數(shù)

      加載一個(gè)進(jìn)程,使用自定義環(huán)境變量env

      int execvp(const char *file, const char *argv[]);

      變參形式: 1)... 2)argv[] ?(main函數(shù)也是變參函數(shù),形式上等同于 int main(int argc, char *argv0, ...))

      變參終止條件:1)NULL結(jié)尾 2)固參指定

      execvp與execlp參數(shù)形式不同,原理一致。

      4)exec函數(shù)族一般規(guī)律

      exec函數(shù)一旦調(diào)用成功即執(zhí)行新的程序,不返回。只有失敗才返回,錯(cuò)誤值-1。所以通常我們直接在exec函數(shù)調(diào)用后直接調(diào)用perror()和exit(),無(wú)需if判斷。

      l (list) 命令行參數(shù)列表

      p (path) 搜素file時(shí)使用path變量

      v (vector) 使用命令行參數(shù)數(shù)組

      e (environment) 使用環(huán)境變量數(shù)組,不使用進(jìn)程原有的環(huán)境變量,設(shè)置新加載程序運(yùn)行的環(huán)境變量

      事實(shí)上,只有execve是真正的系統(tǒng)調(diào)用,其它五個(gè)函數(shù)最終都調(diào)用execve,所以execve在man手冊(cè)第2節(jié),其它函數(shù)在man手冊(cè)第3節(jié)。這些函數(shù)之間的關(guān)系如下圖所示。

      5. 回收子進(jìn)程

      孤兒進(jìn)程:父進(jìn)程先于子進(jìn)程結(jié)束,則子進(jìn)程成為孤兒進(jìn)程,子進(jìn)程的父進(jìn)程成為init進(jìn)程,稱(chēng)為init進(jìn)程領(lǐng)養(yǎng)孤兒進(jìn)程。

      僵尸進(jìn)程:子進(jìn)程終止,父進(jìn)程尚未回收,子進(jìn)程殘留資源(PCB)存放于內(nèi)核中,變成僵尸(Zombie)進(jìn)程。

      特別注意,僵尸進(jìn)程是不能使用kill命令清除掉的。因?yàn)閗ill命令只是用來(lái)終止進(jìn)程的,而僵尸進(jìn)程已經(jīng)終止。思考!用什么辦法可清除掉僵尸進(jìn)程呢?

      方一:wait函數(shù)。

      方二:殺死他的父進(jìn)程使其變成孤兒進(jìn)程,進(jìn)而被系統(tǒng)處理。

      示例:

      孤兒進(jìn)程

      1 #include 2 #include 3 4 int main() 5 { 6 pid_t pid = fork(); 7 if (pid == 0) 8 { 9 while(1) 10 { 11 printf("I am child, pid = %d, ppid = %d\n", getpid(), getppid()); 12 sleep(1); 13 } 14 } 15 else if (pid > 0) 16 { 17 printf("I am parent, pid = %d, ppid = %d\n", getpid(), getppid()); 18 sleep(5); 19 printf("I am parent, I will die!\n"); 20 } 21 22 return 0; 23 }

      僵尸進(jìn)程

      1 #include 2 #include 3 4 int main() 5 { 6 pid_t pid = fork(); 7 if (pid == 0) 8 { 9 printf("I am child, pid = %d, ppid = %d\n", getpid(), getppid()); 10 sleep(2); 11 printf("I am child, I will die!\n"); 12 } 13 else if (pid > 0) 14 { 15 while(1) 16 { 17 printf("I am father, very happy, pid = %d\n", getpid()); 18 sleep(1); 19 } 20 } 21 22 return 0; 23 }

      (1)相關(guān)函數(shù)

      1)wait

      一個(gè)進(jìn)程在終止時(shí)會(huì)關(guān)閉所有文件描述符,釋放在用戶(hù)空間分配的內(nèi)存,但它的PCB還保留著,內(nèi)核在其中保存了一些信息:如果是正常終止則保存著退出狀態(tài),如果是異常終止則保存著導(dǎo)致該進(jìn)程終止的信號(hào)是哪個(gè)。這個(gè)進(jìn)程的父進(jìn)程可以調(diào)用wait或waitpid獲取這些信息,然后徹底清除掉這個(gè)進(jìn)程。我們知道一個(gè)進(jìn)程的退出狀態(tài)可以在Shell中用特殊變量$?查看,因?yàn)镾hell是它的父進(jìn)程,當(dāng)它終止時(shí)Shell調(diào)用wait或waitpid得到它的退出狀態(tài)同時(shí)徹底清除掉這個(gè)進(jìn)程。

      父進(jìn)程調(diào)用wait函數(shù)可以回收子進(jìn)程終止信息。該函數(shù)有三個(gè)功能:

      阻塞等待子進(jìn)程退出

      回收子進(jìn)程殘留資源

      獲取子進(jìn)程結(jié)束狀態(tài)(退出原因)

      當(dāng)進(jìn)程終止時(shí),操作系統(tǒng)的隱式回收機(jī)制會(huì):1.關(guān)閉所有文件描述符 2. 釋放用戶(hù)空間分配的內(nèi)存。內(nèi)核的PCB仍存在。其中保存該進(jìn)程的退出狀態(tài)。(正常終止→退出值;異常終止→終止信號(hào))

      可使用wait函數(shù)傳出參數(shù)status來(lái)保存進(jìn)程的退出狀態(tài)(status只是一個(gè)整型變量,不能很精確的描述出狀態(tài)),因此需要借助宏函數(shù)來(lái)進(jìn)一步判斷進(jìn)程終止的具體原因。宏函數(shù)可分為如下三組:

      WIFEXITED(status) 為非0 → 進(jìn)程正常結(jié)束

      WEXITSTATUS(status) 如上宏為真,使用此宏 → 獲取進(jìn)程退出狀態(tài) (exit的參數(shù))

      WIFSIGNALED(status) 為非0 → 進(jìn)程異常終止

      WTERMSIG(status) 如上宏為真,使用此宏 → 取得使進(jìn)程終止的那個(gè)信號(hào)的編號(hào)。

      WIFSTOPPED(status) 為非0 → 進(jìn)程處于暫停狀態(tài)

      WSTOPSIG(status) 如上宏為真,使用此宏 → 取得使進(jìn)程暫停的那個(gè)信號(hào)的編號(hào)。

      WIFCONTINUED(status) 為真 → 進(jìn)程暫停后已經(jīng)繼續(xù)運(yùn)行

      wait 函數(shù)作用:1)阻塞等待 2)回收子進(jìn)程資源 3)查看死亡原因

      頭文件

      #include #include

      函數(shù)原型

      pid_t wait(int *status);

      參數(shù)說(shuō)明:

      status傳出參數(shù),用來(lái)獲取子進(jìn)程退出的狀態(tài)。

      返回值:

      成功:返回終止的子進(jìn)程pid

      失敗 返回-1,設(shè)置errno

      子進(jìn)程的死亡原因:

      正常死亡 WIFEXITED,如果WIFEXITED為真,使用WEXITSTATUS得到退出狀態(tài)。

      非正常死亡WIFSIGNALED,如果WIFSIGNALED為真,使用WTERMSIG得到信號(hào)。

      wait回收子進(jìn)程

      1 #include 2 #include 3 #include 4 #include 5 6 int main() 7 { 8 pid_t pid = fork(); 9 if (pid == 0) 10 { 11 printf("I am child, will die!\n"); 12 sleep(2); 13 } 14 else if (pid > 0) 15 { 16 printf("I am parent, wait for child die!\n"); 17 //會(huì)阻塞 18 pid_t wpid = wait(NULL); 19 printf("wait ok, wpid = %d, pid = %d\n", wpid, pid); 20 while(1) 21 { 22 sleep(1); 23 } 24 } 25 26 return 0; 27 }

      wait查看子進(jìn)程死亡原因

      1 #include 2 #include 3 #include 4 #include 5 #include 6 7 int main() 8 { 9 pid_t pid = fork(); 10 if (pid == 0) 11 { 12 printf("I am child, pid = %d, will die!\n", getpid()); 13 sleep(2); 14 //return 101; 15 //exit(102); 16 while(1) 17 { 18 printf("I am child, pid = %d, who can kill me!\n", getpid()); 19 sleep(1); 20 } 21 exit(102); 22 } 23 else if (pid > 0) 24 { 25 printf("I am parent, wait for child die!\n"); 26 int status; 27 //會(huì)阻塞 28 pid_t wpid = wait(&status); 29 printf("wait ok, wpid = %d, pid = %d\n", wpid, pid); 30 if (WIFEXITED(status)) 31 { 32 printf("child exit with %d\n", WEXITSTATUS(status)); 33 } 34 else if (WIFSIGNALED(status)) 35 { 36 printf("child killed by %d\n", WTERMSIG(status)); 37 } 38 while(1) 39 { 40 sleep(1); 41 } 42 } 43 44 return 0; 45 }

      2)waitpid

      作用同wait,但可指定pid進(jìn)程清理,可以不阻塞。

      頭文件

      #include #include

      函數(shù)原型

      pid_t waitpid(pid_t pid, int *status, int options);

      參數(shù)說(shuō)明:

      pid:

      ? < -1 組id

      ? -1 回收任意

      ? 0 回收和調(diào)用進(jìn)程組id相同組內(nèi)的子進(jìn)程

      ? >0 回收指定的pid

      options

      0與wait形同,也會(huì)阻塞

      WNOHANG 如果當(dāng)前沒(méi)有子進(jìn)程退出的,會(huì)立即返回。

      返回值:

      如果設(shè)置了WNOHANG,那么如果沒(méi)有子進(jìn)程退出,返回0。

      如果有子進(jìn)程退出,返回退出子進(jìn)程的pid

      失敗: 返回-1(沒(méi)有子進(jìn)程),設(shè)置errno

      注意:一次wait或waitpid調(diào)用只能清理一個(gè)子進(jìn)程,清理多個(gè)子進(jìn)程應(yīng)使用循環(huán)。

      waitpid回收子進(jìn)程

      1 #include 2 #include 3 #include 4 #include 5 6 int main() 7 { 8 pid_t pid = fork(); 9 if (pid == 0) 10 { 11 printf("I am child, pid = %d\n", getpid()); 12 sleep(2); 13 } 14 else if (pid > 0) 15 { 16 printf("I am parent, pid = %d\n", getpid()); 17 //不會(huì)阻塞 18 int ret; 19 20 while((ret = waitpid(-1, NULL, WNOHANG)) == 0 ) 21 { 22 sleep(1); 23 } 24 printf("ret = %d\n", ret); 25 ret = waitpid(-1, NULL, WNOHANG); 26 /* 27 if (ret < 0) 28 { 29 perror("wait err:"); 30 }*/ 31 while(1) 32 { 33 sleep(1); 34 } 35 } 36 37 return 0; 38 }

      wait回收多個(gè)子進(jìn)程

      1 #include 2 #include 3 #include 4 #include 5 6 int main() 7 { 8 int n = 5; 9 int i = 0; 10 pid_t pid; 11 for(i=0; i < 5; i++) 12 { 13 pid = fork(); 14 if (pid == 0) 15 { 16 printf("I am child, pid = %d\n", getpid()); 17 break; 18 } 19 } 20 sleep(i); //子進(jìn)程睡 21 if (i == 5) //父進(jìn)程回收子進(jìn)程 22 { 23 for (i = 0; i < 5; i++) 24 { 25 pid_t wpid = wait(NULL); 26 printf("wpid = %d\n", wpid); 27 } 28 while(1) 29 { 30 sleep(1); 31 } 32 } 33 34 return 0; 35 }

      waitpid回收多個(gè)子進(jìn)程

      1 #include 2 #include 3 #include 4 #include 5 6 int main() 7 { 8 int n = 5; 9 int i = 0; 10 pid_t pid; 11 for(i=0; i < 5; i++) 12 { 13 pid = fork(); 14 if (pid == 0) 15 { 16 printf("I am child, pid = %d\n", getpid()); 17 break; 18 } 19 } 20 21 if (i == 5) //父進(jìn)程回收子進(jìn)程 22 { 23 printf("I am parent!\n"); 24 while(1) 25 { 26 pid_t wpid = waitpid(-1, NULL, WNOHANG); 27 if (wpid == -1) 28 { 29 break; 30 } 31 else if (wpid > 0) 32 { 33 printf("waitpid wpid = %d\n", wpid); 34 } 35 } 36 while(1) 37 { 38 sleep(1); 39 } 40 } 41 if (i < 5) 42 { 43 //sleep(i); 44 printf("I am child, i = %d, pid = %d\n", i, getpid()); 45 } 46 47 return 0; 48 49 }

      練習(xí):

      1. 父進(jìn)程fork 3 個(gè)子進(jìn)程,三個(gè)子進(jìn)程一個(gè)調(diào)用ps命令, 一個(gè)調(diào)用自定義程序1(正常),一個(gè)調(diào)用自定義程序2(會(huì)出段錯(cuò)誤)。父進(jìn)程使用waitpid對(duì)其子進(jìn)程進(jìn)行回收?

      2. 驗(yàn)證子進(jìn)程是否共享文件描述符,子進(jìn)程負(fù)責(zé)寫(xiě)入數(shù)據(jù),父進(jìn)程負(fù)責(zé)讀數(shù)據(jù)?

      父子進(jìn)程共享文件描述符

      1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 9 10 int main(int argc, char *argv[]) 11 { 12 if (argc != 2) 13 { 14 printf("./a.out filename\n"); 15 return -1; 16 } 17 int fd = open(argv[1], O_RDWR|O_CREAT); 18 if (fd < 0) 19 { 20 perror("open err"); 21 exit(1); 22 } 23 pid_t pid = fork(); 24 if (pid == 0) 25 { 26 write(fd, "hello\n", 6); 27 } 28 else if (pid > 0) 29 { 30 sleep(1); 31 write(fd, "world\n", 6); 32 wait(NULL); 33 } 34 35 return 0; 36 }

      結(jié)論:父子進(jìn)程共享文件描述符。

      Linux 任務(wù)調(diào)度

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶(hù)投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶(hù)投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:云圖說(shuō)】 第187期 初識(shí)華為云SAP系統(tǒng)磁盤(pán) 【云圖說(shuō)】第57期 華為云SAP解決方案,讓業(yè)務(wù)運(yùn)營(yíng)更高效 【云圖說(shuō)】第65期 SAP與華為云的“聯(lián)姻之路”
      下一篇:excel表格打印半截字怎么辦
      相關(guān)文章
      亚洲福利视频导航| 亚洲精品无码成人片在线观看| 亚洲一区二区三区无码影院| 最新亚洲人成无码网站| 国产偷国产偷亚洲清高APP| 亚洲熟女乱色一区二区三区| 国产亚洲精aa在线看| 国产精品亚洲精品| 最新国产精品亚洲| 亚洲人成欧美中文字幕| 亚洲精品成a人在线观看☆| 亚洲第一成年免费网站| 久久亚洲精品高潮综合色a片| 久久亚洲精品无码av| 国产成人亚洲综合a∨| 亚洲高清成人一区二区三区| 亚洲国产精品自在拍在线播放| 亚洲国产日韩在线观频| 亚洲一区二区高清| 亚洲精品狼友在线播放| 久久精品国产亚洲AV麻豆不卡| 亚洲国产精品一区二区久久| 亚洲精品在线播放| 91丁香亚洲综合社区| 亚洲国产精品无码久久久秋霞1| 国产精品无码亚洲精品2021| 亚洲精品无码久久久| 亚洲精品tv久久久久久久久 | 亚洲av无码乱码国产精品| 亚洲高清在线视频| 亚洲国产中文在线二区三区免| 国内精品久久久久影院亚洲| 亚洲av无码偷拍在线观看| 亚洲精品视频久久久| 中文字幕亚洲乱码熟女一区二区| 亚洲国产a∨无码中文777| 亚洲韩国在线一卡二卡| 国产精品高清视亚洲精品| 精品亚洲视频在线| 国产亚洲精品福利在线无卡一| 午夜影视日本亚洲欧洲精品一区|