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結束再次喚醒父進程,輸出對應語句,最後再輸出自己的語句,而孫進程則由祖先進程回收。