Linux進程控制

      網友投稿 733 2025-03-31

      @TOC

      零、前言

      前篇我們講解學習了關于進程的概念知識,本章主要講解關于進程的控制,深入學習進程

      一、進程創建

      1、fork函數

      概念:

      linux中fork函數從已存在進程中創建一個新進程(子進程),而原進程為父進程

      fork函數原型:

      pid_t fork(void);

      注意:

      使用fork()函數需要包含頭文件;pid_t類型需要包含頭文件

      fork成功后對子進程返回0,對父進程返回子進程id,fork出錯返回-1

      內核視角看待fork:

      進程調用fork,內核分配新的內存塊和內核數據結構給子進程

      將父進程部分數據結構內容拷貝至子進程(例如PCB進程控制塊,進程地址空間,頁表等)

      添加子進程到系統進程列表當中,當fork返回后開始調度器調度進程

      示圖:

      fork后執行問題:

      當一個進程調用fork之后,父子進程共享同一份代碼,也就是說整個代碼父子進程都可以看到,但是此時父子進程的執行位置都是相同的,也就是說fork返回后子進程也是往fork之后的代碼執行(并非再從頭執行)

      示例:

      #include #include #include int main() { printf("Before fork: pid is %d\n", getpid()); pid_t pid=fork(); if (pid== -1 )//fork錯誤 { perror("fork fail"); exit(1); } printf("After fork:pid is %d, fork return %d\n", getpid(), pid); sleep(1); return 0; }

      結果:

      示圖:

      2、fork返回值

      返回值:

      fork成功對子進程返回0,對父進程返回子進程的pid

      寫時拷貝

      概念:

      fork成功之后父子代碼共享,當父子不寫入數據時,數據也是共享的,當任意一方試圖寫入,便以寫時拷貝的方式各自一份副本

      為什么數據要進行寫時拷貝:

      進程具有獨立性,多進程運行,需要獨享各種資源,多進程運行期間互不干擾,不能讓子進程的修改影響到父進程

      為什么不在創建子進程的時候就進行數據的拷貝:

      子進程不一定會使用父進程的所有數據,并且在子進程不對數據進行寫入的情況下,沒有必要對數據進行拷貝,我們應該按需分配,在需要修改數據的時候再分配(延時分配),這樣可以高效的使用內存空間,提高fork效率,以及fork的成功率

      代碼會不會進行寫時拷貝:

      90%的情況下是不會的,但這并不代表代碼不能進行寫時拷貝,例如在進行進程替換的時候,則需要進行代碼的寫時拷貝

      示圖:

      fork函數為什么要給子進程返回0,給父進程返回子進程的PID:

      一個父進程可以創建多個子進程,而一個子進程只能有一個父進程。因此,對于子進程來說,父進程是不需要被標識的;而對于父進程來說,子進程是需要被標識的,因為父進程創建子進程的目的是讓其執行任務的,父進程只有知道了子進程的PID才能很好的對該子進程進行深入操作

      為什么fork存在“兩個”返回值:

      父進程創建子進程時,子進程以父進程為模板構建進程,代碼數據父子共享,返回時也是父子進程進行修改數據時,由頁表發現該數據是父子進程共享的,所以系統會找到另一個物理空間進行拷貝數據,拷貝數據后再修改數據,達到數據各有一份互不干擾的目的,保證進程的獨立性

      3、fork用法

      我們創建子進程并不是為了父進程執行一樣的代碼,而是為了使父子進程同時執行不同的代碼段

      例如:父進程等待客戶端請求,生成子進程來處理請求

      用法1:fork返回后分流執行不同代碼

      示例:

      #include #include #include #include int main() { pid_t id=fork();//創建子進程 if(id==0) { //child int cnt=0; while(1) { printf("I am child: pid:%d ppid:%d\n",getpid(),getppid()); sleep(1); if(cnt==8) break; cnt++; } exit(1);//終止進程 } else if(id>0) { //father int cnt=0; while(1) { printf("I am father: pid:%d ppid:%d\n",getpid(),getppid()); sleep(1); if(cnt==8) break; cnt++; } } return 0; }

      結果:

      用法2:fork返回后調用exec函數替換進程

      注:在下文有著重講解

      4、fork失敗

      fork本質就是向系統要資源,當某個資源不夠時則會發生fork失敗

      失敗原因:

      1.系統中有太多的進程

      2.實際用戶的進程數超過了限制

      二、進程終止

      1、退出碼

      概念:

      其實main函數是間接性被操作系統所調用的,當main函數調用結束后就應該給操作系統返回相應的退出信息,而這個所謂的退出信息就是以退出碼的形式作為main函數的返回值返回

      我們一般以0表示代碼成功執行完畢,以非0表示代碼執行過程中出現錯誤,一般來說我們寫的代碼都不太規范,沒有根據執行結果返回相應的退出碼

      注:退出碼可以人為定義,也可以使用系統的錯誤碼表

      示圖:系統錯誤碼表

      退出碼查看:

      使用指令 echo $?

      示例:

      注:如果main沒有return,則echo $?查看的是最近函數的退出碼,一般來說都是0

      2、退出方法

      進程退出場景:

      代碼運行完畢,結果正確,退出碼為0

      代碼運行完畢,結果不正確,邏輯存在問題,退出碼為非0

      代碼異常終止,層序崩潰,退出碼沒有意義

      進程常見退出方法:

      1) 調用_exit函數

      _exit函數原型:

      #include void _exit(int status);

      注意:

      status 定義了進程的終止狀態,父進程通過wait來獲取該值

      雖然status是int,但是僅有低8位可以被父進程所用

      注:_exit(-1)時,在終端執行$?發現返回值是255

      示圖:

      2)調用exit函數

      exit函數原型:

      #include void exit(int status);

      exit與_exit的區別:

      _exit僅僅是退出進程

      exit在退出進程前,先執行用戶通過 atexit或on_exit定義的清理函數,關閉所有打開的流,所有的緩存數據均寫入(刷新緩沖區),最后調用_exit

      示圖:

      示例:

      3)main函數return

      return是一種更常見的退出進程方法,執行return n等同于執行exit(n),因為調用main的運行時函數會將main的返回值當做exit的參數

      示圖:

      注意:

      只有是在main函數的的return才算是終止進程,其他函數return只是結束函數,因為系統調用的是main函數,main函數返回才是進程的返回

      調用main函數運行結束后,main函數的return返回值當做exit的參數來調用exit函數,而使用exit函數退出進程前,exit函數會先執行用戶定義的清理函數、沖刷緩沖,關閉流等操作,然后再調用_exit函數終止進程

      4)異常退出

      向進程發生信號

      如在進程運行過程中向進程發生kill -9信號使得進程異常退出,或是使用Ctrl+C迫使進程退出

      代碼運行異常

      如代碼當中存在野指針問題等bug問題使得進程運行時異常退出

      3、理解終止

      以OS角度理解:核心思想-歸還資源

      釋放曾經為管理進程所維護的數據結構資源,并非銷毀釋放數據結構對象,而是將狀態設置為無效并保存起來,下一次需要就直接使用不用申請,相當于建立對應的數據結構“內存池”

      釋放程序數據和代碼占用的空間,并非清空數據和代碼,而是將對應內存區域設置為無效,要再次使用時直接覆蓋數據和代碼就行了

      取消曾經該進程在進程隊列里的鏈接關系(避免”野指針“)

      三、進程等待

      進程等待必要性:

      當子進程退出,并不是完全退出,子進程的PCB任然存在,父進程如果不等待回收,就可能造成‘僵尸進程’的問題,進而造成內存泄漏

      注:進程一旦變成僵尸狀態,并不能被父進程給kill掉,因為子進程已經死去,只能父進程等待回收

      子進程的PCB保留著退出前任務執行的信息,而通過回收子進程我們可以知道子進程運行完成,結果對還是不對,或者是否正常退出

      注:非必須,依執行的程序和需求而定

      盡量使父進程晚于子進程退出,可以規范化進行資源的回收

      注:所以一般來說,當我們fork之后,就需要父進程等待回收子進程

      1、等待方法

      wait方法:

      wait函數原型:

      #include #include pid_t wait(int*status);

      注意:

      wait函數作用的等待任意子進程

      返回值:成功返回被等待進程pid,失敗返回-1

      參數:輸出型參數,獲取子進程退出狀態,不關心則可以設置成為NULL

      waitpid方法:

      waitpid函數原型:

      #include #include pid_ t waitpid(pid_t pid, int *status, int options);

      注意:

      返回值:

      當正常返回的時候waitpid返回收集到的子進程的進程ID

      如果設置了選項options為WNOHANG,而調用中waitpid發現沒有已退出的子進程可收集,則返回0;如果調用中出錯,則返回-1,這時errno會被設置成相應的值以指示錯誤所在

      參數pid:

      Pid=-1,等待任一個子進程,與wait等效

      Pid>0,等待其進程ID與pid相等的子進程

      參數status:

      參數status是一個輸出型參數,需要我們傳入一個整形變量的地址,以此獲取子進程退出的信息

      使用對應的宏可以查看我們需要的退出信息:WIFEXITED(status): 若為正常終止子進程返回的狀態,則為真(查看進程是否是正常退出);WEXITSTATUS(status): 若WIFEXITED非零,提取子進程退出碼(查看進程的退出碼)

      參數options:

      設置為0:表示默認的阻塞式等待子進程退出,即子進程沒退出就不返回,一直等待到子進程退出回收子進程

      設置為WNOHANG:若pid指定的子進程沒有結束,則waitpid()函數返回0,不予以等待;若正常結束,則返回該子進程的ID

      示例1:阻塞等待

      #include #include #include #include #include int main() { pid_t id=fork(); if(id==0) { printf("I am child process: pid:%d ppid:%d\n",getpid(),getppid()); sleep(5); // int* p=12345; // *p=100;//野指針 exit(123); } printf("father wait...\n"); int status=0; //pid_t ret=wait(&status);//等待特定任意子進程 pid_t ret=waitpid(id,&status,0);//阻塞等待特定子進程 if(ret>0&&WIFEXITED(status))//等待成功并子進程退出正常 { printf("wait success: wait for id:%d status code:%d\n",ret,WEXITSTATUS(status)); } else if(ret>0)//等待成功但是子進程退出異常 { printf("exit error! status codedump:%d sign:%d\n",(status>>7)&1,status&0x7F); } return 0; }

      結果:

      示例2:非阻塞等待

      #include #include #include #include int main() { pid_t pid; pid = fork(); if(pid < 0){//fork失敗 printf("%s fork error\n",__FUNCTION__); return 1; }else if( pid == 0 ){ //child執行 printf("child is run, pid is : %d\n",getpid()); sleep(5); exit(1); } else{ //father執行 int status = 0; pid_t ret = 0; do{ ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待 if( ret == 0 ){//等待失敗則繼續等待 printf("child is running\n"); //TODO...等待執行其他任務,待會再等待 } sleep(1); }while(ret == 0); //等待成功打印對應信息 if( WIFEXITED(status) && ret == pid ){ //退出正常輸出退出碼 printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status)); }else{ //退出異常 printf("wait child failed, return.\n"); return 1; } } return 0; }

      結果:

      總結:

      如果子進程已經退出,調用wait/waitpid時,wait/waitpid會立即返回,并且釋放資源,獲得子進程退出信息

      如果在任意時刻調用wait/waitpid,子進程存在且正常運行,則進程可能阻塞

      如果不存在該子進程,則立即出錯返回

      示圖:

      2、獲取status

      概念:

      wait和waitpid,都有一個status參數,該參數是一個輸出型參數,由操作系統進行將退出信息填充

      如果傳遞NULL,表示不關心子進程的退出狀態信息;如果傳遞變量地址,操作系統會根據該參數將子進程的退出信息反饋給父進程

      使用對應的宏可以方便查看我們需要的退出信息:WIFEXITED(status): 若為正常終止子進程返回的狀態,則為真(查看進程是否是正常退出);WEXITSTATUS(status): 若WIFEXITED非零,提取子進程退出碼(查看進程的退出碼)

      注:status不能簡單的當作整形來看待,可以當作位圖來看待(只有status的低16比特位有有效信息)

      示圖:

      注意:

      如果是正常退出,我們可以進一步獲取子進程退出的退出碼(退出狀態),通過退出碼判斷進程執行的結果如何,是對還是錯

      如果是異常退出,那么退出碼變沒有意義(執行任務已經失敗),只需要考慮低7位的信息查看是怎樣的異常

      示例:

      #include #include #include #include #include int main() { pid_t pid; if ( (pid=fork()) == -1 ) perror("fork"),exit(1); if ( pid == 0 ){ sleep(20); exit(10); } else { int st; int ret = wait(&st); if ( ret > 0 && ( st & 0X7F ) == 0 ){ // 正常退出 printf("child exit code:%d\n", (st>>8)&0XFF); } else if( ret > 0 ) { // 異常退出 printf("sig code : %d\n", st&0X7F ); } } }

      結果:

      3、理解等待

      以OS的視角理解:

      父進程創建子進程,并調用系統接口wait/waitpid進行等待

      系統會將當前進程放進等待隊列,并將進程的狀態設置為非R

      當到一定程度時,系統會喚醒進程,進程由等待隊列轉為運行隊列,同時狀態變為R

      四、進程替換

      1、替換原理

      用fork創建子進程后執行的是和父進程相同的程序(但有可能執行不同的代碼分支)

      如果想執行不同程序,子進程可以調用一種exec函數以執行另一個程序

      當進程調用一種exec函數時,該進程的用戶空間代碼和數據完全被新程序替換,從新程序的啟動例程開始執行

      注:調用exec并不創建新進程,只是將進程的代碼和數據寫時拷貝成新程序的代碼和數據(達到替換的效果),所以調用exec前后該進程的id并未改變

      示圖:

      2、替換方法

      exec系列函數原型:

      #include ` 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,所以exec函數只有出錯的返回值而沒有成功的返回值

      命名理解:

      l(list) : 表示參數采用列表的形式傳入如何使用程序或者命令 v(vector) : 參數用數組 p(path) : 有p自動搜索環境變量PATH e(env) : 表示自己維護環境變量

      示圖:

      具體使用介紹:

      //子進程替換程序為ls命令 execl("/user/bin/ls","ls","-i","-a","-l",NULL); //注:l表示列表形式,即以可變參數的形式使用程序,最后一個參數需要傳入NULL,表示參數傳入結束 execlp("ls","ls","-i","-a","-l",NULL); //注:對于ls這樣的系統命令,其路徑被儲存在PATH環境變量里,execlp函數會自動到PATH里通過各路徑去尋找ls命令;如果系統程序指令,則要么拷貝程序到PATH里的某個路徑下,或者添加程序路徑到PATH變量里 //注:對于這里兩個ls其實并不沖突,第一個表示程序的名稱,第二個表示如何通過參數列表使用程序(使用時需要帶上名稱) char* const MY_Env[]={ "MYENV=hello linux",NULL } execle("./mycmd","mycmd",NULL,MY_Env); //注:對于不是當前環境變量,需要自己組裝,或者將添加到當前環境變量里 char* const MY_acgv[]={ "ls", ,"-l" ,"-a" ,"-i" ,NULL } execv("/user/bin/ls",MY_acgv); //注:v表示數組的形式傳入參數列表 execvp("ls",MY_acgv); execve("/user/bin/ls",MY_acgv,env);

      示例:替換程序為mycmd

      test_exec.c: #include #include #include #include #include int main() { pid_t id=fork(); if(id==0) { //child printf("I am child :pid:%d ppid:%d\n",getpid(),getppid()); execl("./mycmd","mycmd",NULL); exit(1); } printf("I am father:pid:%d ppid:%d\n",getpid(),getppid()); int status=0; pid_t ret=waitpid(id,&status,0); if(ret>0&&WIFEXITED(status)) { printf("wait for id:%d eixt code:%d\n",id,WEXITSTATUS(status)); } else if(ret>0) { printf("eixt error sign:%d codedump:%d\n",status&0x7F,(status>>7)&1); } return 0; } mycmd.c: #include #include int main() { for(int i =0; i < 10; i ++) printf("cmd: %d\n", i); printf("MYENV: %s", getenv("MYENV")); return 0; } Makefile: .PHONY:all all: exec_cmd mycmd exec_cmd:exec_cmd.c gcc -o $@ $^ mycmd:mycmd.c gcc -o $@ $^ -std=c99 .PHONY:clean clean: rm -f exec_cmd mycmd

      結果:

      注:本質上只有execve是真正的系統調用,其它五個函數最終都調用execve(在系統調用上的一個封裝),所以execve在man手冊 第2節,其它函數在man手冊第3節

      示圖:

      注:對于軟件或者程序執行,要預先將存在磁盤里的軟件或者程序加載到CPU上,而我們也可以將exec系列函數看作是一種特殊的加載器

      五、實現簡易shell

      Linux進程控制

      shell視角執行:

      shell讀取新的一行輸入,建立一個新的進程,在這個進程中運行程序并等待這個進程結束,再進行新的輸入讀取

      注意:

      對于shell來說作為命令行解釋器,執行命令需要將執行結果給用戶看到,這時候就需要子進程執行,讓子進程的結果返回,即父進程等待回收子進程

      但是對于一些內建命令則需要shell自己執行,例如執行cd …返回上層目錄,我們希望的并不是子進程返回上層目錄,所以需要shell自己執行

      具體流程:

      獲取命令行

      解析命令行

      建立一個子進程(fork)

      替換子進程(execvp)

      父進程等待子進程退出(wait)

      示圖:

      注:根據這些思路,和我們前面的學的技術,就可以自己來實現一個shell了

      實現代碼:

      #include #include #include #include #include #include #define MAX 128 #define SIZE 32 char cmd_line[MAX];//保存獲取輸入 char* cmd_parse[SIZE];//命令選項 int main() { //不斷執行 while(1) { memset(cmd_line,0,sizeof(cmd_line));//清空數據 printf("[zgj@myhost my_mini_shell]$ ");//顯示詞條 fflush(stdout);//強制刷新 if(fgets(cmd_line,sizeof(cmd_line)-1,stdin))//獲取數據 { //獲取成功,設置結束符 cmd_line[strlen(cmd_line)-1]='\0';//注意這里的下標需要減一,因為最后一個接收到的是回車符\n //切分命令選項 int index=0; cmd_parse[index]=strtok(cmd_line," "); while(cmd_parse[index]!=NULL) { index++; cmd_parse[index]=strtok(NULL," "); } //分析指令 if(strcmp(cmd_parse[0],"cd")==0) { if(chdir(cmd_parse[1])==0) continue; } else//非內置命令,子進程執行 { pid_t id=fork(); if(id==0) { //child execvp(cmd_parse[0],cmd_parse); exit(1); } //father int status=0; pid_t ret=waitpid(-1,&status,0); if(ret>0&&WIFEXITED(status)) { printf("exit code:%d\n",WEXITSTATUS(status)); } } } } return 0; }

      效果:

      Linux 任務調度

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:WPS表格如何使用數據有效性限制輸入指定內容(wps表格限定了可以輸入單元格的數值)
      下一篇:WPS表2012采用多條件求和公式進行評分統計 提高效率(wps表格計算總分和平均分)
      相關文章
      性色av极品无码专区亚洲 | 久久亚洲精品人成综合网| 亚洲?v无码国产在丝袜线观看| 99久久国产亚洲综合精品| 亚洲精品自拍视频| 亚洲欧洲日产国码久在线观看| 亚洲成A人片在线观看WWW| 亚洲国产AV无码专区亚洲AV| 国产国拍亚洲精品mv在线观看| 国产国拍亚洲精品mv在线观看| 亚洲五月综合缴情在线观看| 伊人久久大香线蕉亚洲五月天| 久久国产成人精品国产成人亚洲| 亚洲中文字幕成人在线| 亚洲伊人久久精品影院| 国产V亚洲V天堂A无码| 亚洲精选在线观看| 亚洲欧洲日产国码www| 亚洲精品国产情侣av在线| 亚洲精品资源在线| 亚洲伊人久久精品| 亚洲真人无码永久在线观看| 亚洲日本天堂在线| 在线观看免费亚洲| 国产亚洲精品自在线观看| 亚洲码国产精品高潮在线| 亚洲第一AV网站| 久久精品亚洲中文字幕无码网站| 亚洲色四在线视频观看| 亚洲第一网站免费视频| 亚洲AV成人噜噜无码网站| 亚洲人成网站在线播放2019| 国产精品亚洲专区无码不卡| 久久亚洲中文字幕精品一区| 亚洲av无码不卡一区二区三区| 亚洲视频在线观看地址| 国产.亚洲.欧洲在线| 亚洲av无码专区在线电影天堂| 亚洲国产一级在线观看| 国外亚洲成AV人片在线观看| 久久久久亚洲精品美女|