LINUX下的進程與線程

1.系統的啓動運行進程
  • 當用戶進程init開始運行,就開始扮演用戶進程的祖先角色,永遠不會被終止。
  • 所以:計算機上的所有進程都是有上下親屬關係的,他們組成一個龐大的家族樹。

※ 觀察linux下的進程間父子關係:

  • pstree
    以樹狀結構方式列出系統中正在運行的各進程間的父子關係。
  • ps ax -o pid,ppid,command
    在這裏插入圖片描述
2. 基本進程編程

在這裏插入圖片描述

(1)新建進程——fork
  • pid_t fork(void);
  • 一次克隆,兩個返回值
  • 兩個執行線路
//示例1:執行一次fork語句,父親睡眠2秒
[flora@localhost ~]$ vi fork_1.c
[flora@localhost ~]$ gcc -o myfork_1 fork_1.c
[flora@localhost ~]$ cat fork_1.c 
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void){
    pid_t pid;
    char *message;
    int x; 
    pid = fork(); 
        if (pid < 0)
         {   perror("fork failed");
             exit(1);    }
        if (pid == 0) 
         {   message = "This is the child\n";
             x = 0;  } 
        else 
         {   message = "This is the parent\n";              
             x = 10;
             sleep(2);     }   //注意
        printf("%s I'm %d, x=%d,my father is:%d\n",message,getpid(),x,getppid());
        return 0;
} 
[flora@localhost ~]$ ./myfork_1 
This is the child
 I'm 3928, x=0,my father is:3927
This is the parent
 I'm 3927, x=10,my father is:2725

上例運行結果:先輸出孩子語句,等待兩秒後輸出父親語句。

//示例2:執行一次fork語句,孩子睡眠2秒
[flora@localhost ~]$ vi fork_2.c
[flora@localhost ~]$ cat fork_2.c
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void){
    pid_t pid;
    char *message;
    int x; 
    pid = fork();
    if (pid < 0)
       {   perror("fork failed");
           exit(1);    }
    if (pid == 0) 
       {   message = "This is the child\n";             
           x = 0;   
           sleep(2);     }    //注意
    else 
       {   message = "This is the parent\n";
           x = 10;
                         }
      printf("%s I'm %d, x=%d,my father is:%d\n",message,getpid(),x,getppid());      return 0;
}
[flora@localhost ~]$ gcc -o myfork_2 fork_2.c 
[flora@localhost ~]$ ./myfork_2
This is the parent
 I'm 3168, x=10,my father is:3084
[flora@localhost ~]$ This is the child
 I'm 3169, x=0,my father is:1
ps
   PID TTY          TIME CMD
  2721 pts/0    00:00:00 bash
  2893 pts/0    00:00:00 bash
  3084 pts/0    00:00:00 bash
  3179 pts/0    00:00:00 ps

上例結果分析:先輸出父親語句,父親語句執行完後,bash獲得了cpu,把命令行輸出了一下,然後孩子睡兩秒後輸出語句。
注意:可以看到父親的父親是bash,而孩子的父親是1(因爲父親執行完後就結束了,孩子睡完再執行時,孩子的父親就是祖先進程了,由祖先進程回收進程)

注意:當循環語句中含fork語句時,循環n次,則有2^n個進程。

(2)執行新工作——exec
  • int execve()是最根本的系統調用
  • 需要路徑、命令行參數、和環境變量三種參數
    在這裏插入圖片描述

在這裏插入圖片描述

  • p:利用PATH環境變量查找可執行的文件;
  • l:希望接收以逗號分隔的形式傳遞參數列表,列表以NULL指針作爲結束標誌;
  • v:希望以字符串數組指針( NULL結尾)的形式傳遞命令行參數;
  • e:傳遞指定參數envp,允許改變子進程的環境,後綴沒有e時使用當前的程序環境

注意:只有execve是系統調用;其他函數調用最終都由execve完成工作。無論是哪個exec類函數,都是將要執行程序的路徑、命令行參數、和環境變量3個參數傳遞給execve

示例:

[flora@localhost ~]$ vi forkexec_1.c
[flora@localhost ~]$ cat forkexec_1.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void){
    pid_t a;
    a = fork();
    if (a < 0)  {
        perror("fork failed");
        exit(1);  
    }
    if (a == 0) {
        execlp ("ps" ,"ps",NULL);   
        execlp ("ls" ,"ls","-al","/etc/passwd ",NULL); 
        return 1;
    } 
    else    {
        wait(NULL);
        printf("father exit\n");
    }
    return 0;
}
[flora@localhost ~]$ gcc -o myforkexec_1 forkexec_1.c 
[flora@localhost ~]$ ./myforkexec_1 
   PID TTY          TIME CMD
  2721 pts/0    00:00:00 bash
  2893 pts/0    00:00:00 bash
  3084 pts/0    00:00:00 bash
  6591 pts/0    00:00:00 myforkexec_1
  6592 pts/0    00:00:00 ps
father exit

上例結果分析:因爲父進程有wait語句,相當於阻塞態,所以cpu會先給孩子進程,執行第一條execlp語句,但是當執行完這條語句後,已經覆蓋掉了孩子進程中的代碼,所以不會執行後續的指令了,當孩子進程執行完後,系統會觸發剛纔的父親進程,父親進程被喚醒,輸出它的語句。

注意:

  • exec函數執行成功會裝入新代碼,原來的代碼在其進程空間中已不存在。
  • 只有exec調用失敗返回-1,其後的代碼纔有可能得到執行。

拓展:若修改子進程代碼:在開始時加入輸出語句

if (a == 0) {
        printf("I am child\n");
        execlp ("ps" ,"ps",NULL);   
        execlp ("ls" ,"ls","-al","/etc/passwd ",NULL); 
        return 1;
    } 

則結果如下圖:
在這裏插入圖片描述

(3)進程的消亡——exit
  • void exit(int status);
  • 需要路徑、命令行參數、和環境變量三種參數

注意:程序執行結束或調用exit後並不是馬上消失,而是變爲僵死狀態——放棄了幾乎所有內存空間,不再被調度,但保留有PCB信息供wait收集。

示例:注意要想看子進程的僵死態,必須讓父進程睡眠一段時間,否則父進程結束後,就只能由祖先進程回收子進程,就看不到僵死態了

[flora@localhost ~]$ vi fork_1.c
[flora@localhost ~]$ cat fork_1.c
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void){
    pid_t pid;
    char *message;
    int x; 
    pid = fork();
        if (pid < 0)
         {   perror("fork failed");
             exit(1);    }
        if (pid == 0) 
         {   message = "This is the child\n";
             x = 0;  } 
        else 
         {   message = "This is the parent\n";              
             x = 10;
             sleep(20);     }
        printf("%s I'm %d, x=%d,my father is:%d\n",message,getpid(),x,getppid());
        return 0;
} 
[flora@localhost ~]$ gcc fork_1.c
[flora@localhost ~]$ ./a.out&    //注意:&使進程在後臺執行
[1] 7202
[flora@localhost ~]$ This is the child
 I'm 7203, x=0,my father is:7202
ps -x|grep a.out    //此時父親在睡眠,此契機下,用ps命令觀察子進程死亡前狀態
  7202 pts/0    S      0:00 ./a.out            //睡眠態
  7203 pts/0    Z      0:00 [a.out] <defunct>  //僵死態
  7205 pts/0    R+     0:00 grep --color=auto a.out
[flora@localhost ~]$ This is the parent   //父親睡眠後返回,打印其輸出
 I'm 7202, x=10,my father is:3084
 
 [1]+  完成                  ./a.out
(4)進程間的等待——wait
  • pid_t wait(int *status)
  • 使調用它的進程等待,進入阻塞狀態
  • 子進程死亡會喚醒阻塞的父進程(但注意:一個孩子只能喚醒一次父親,如果有多個孩子的話,則需要加多個wait語句)

示例:
在這裏插入圖片描述

[flora@localhost ~]$ vi forkwait_cp.c
[flora@localhost ~]$ cat forkwait_cp.c
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main(){
    pid_t pc1,pc2,pw1,pw2;
    pc1=fork();
    pc2=fork();
    if (pc1>0&&pc2>0) {     //*父進程
        pw1=wait(NULL);
        printf("***Catch a dead child process with pid: %d\n",pw1);
        pw2=wait(NULL);
        printf("***Catch a dead child process with pid: %d\n",pw2);
        printf("***I'M %d,THE MAIN PROCESS LEAVE!\n",getpid()); \
    }
    if (pc1==0&&pc2>0) {        //*子進程1
        printf("I'M the first child PID:%d,my father is:%d\n",getpid(),getppid());
        sleep(10);               
    }
    if (pc1>0&&pc2==0)          //*子進程2
        printf("I'M the 2nd child PID:%d,my father is:%d,i don't sleep.\n",getpid(),getppid());
    if (pc1==0&&pc2==0)         //*孫進程
        printf("I'M grandson PID:%d,my father is:%d,no one is waiting for – me.\n",getpid(),getppid());
    
    exit(0);    
}   
[flora@localhost ~]$ gcc forkwait_cp.c -o myforkwait_cp
[flora@localhost ~]$ ./myforkwait_cp
I'M the 2nd child PID:7852,my father is:7850,i don't sleep.
***Catch a dead child process with pid: 7852
I'M the first child PID:7851,my father is:7850
I'M grandson PID:7853,my father is:7851,no one is waiting for – me.
***Catch a dead child process with pid: 7851
***I'M 7850,THE MAIN PROCESS LEAVE!

上例結果分析:
可以看出由於父進程有wait語句,所以cpu會先給子進程,而子進程1有睡眠時間,因此子進程2會先輸出執行,結束後會喚醒一次父進程,輸出對應的語句;然後由於父進程還有一個wait語句,所以子進程1和孫進程會先後執行輸出對應的語句,停頓10s後,子進程1結束再次喚醒父進程,輸出對應語句,最後再輸出自己的語句,而孫進程則由祖先進程回收。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章