作者 | 看,未來
責編 | 王曉曼
出品 | CSDN博客
初見進程,先查一下戶口
1、進程環境
進程控制塊PCB:就是進程在作業系統中的「戶口」,具體實現是 task_struct數據結構:
1.狀態信息,例如這個進程處於可執行狀態,休眠,掛起等。
2.性質,由於unix有很多變種,進程有自己獨特的性質。
3.資源,資源的連結比如內存,還有資源的限制和權限等。
4.組織,例如按照家族關係建立起來的樹(父進程,子進程等)。
2、進程狀態
運行狀態R(TASK_RUNNING)
可中斷睡眠狀態S(TASK_INTERRUPTIBLE)
不可中斷睡眠狀態D(TASK_UNINTERRUPTIBLE)
暫停狀態T(TASK_STOPPED或TASK_TRACED)
僵死狀態Z(EXIT_ZOMBIE)
退出狀態X(EXIT_DEAD)
以上兩部分,了解即可
3、進程原語
(1)fork
#include <unistd.h>
pid_t fork(void);
功能:子進程複製父進程中的0~3G空間和PCB,但ID號不同。
Fork調用一次返回兩次
父進程中返回子進程ID (就是大於0的意思)
子進程返回0
讀時共享寫時複製,可保高效
與之相關函數:
#include<sys/types.h>
#include<unistd.h>
pid_t getpid(void); //獲取進程ID
pid_t getppid(void); //獲取父進程ID
#include <unistd.h>
#include <sys/types.h>
uid_t getuid(void);//返回實際用戶ID
uid_t geteuid(void);//返回有效用戶ID
進程的產生方式:
進程的產生有多種方式,但是追本溯源是相通的。
(1)複製父進程的系統環境(放心,只要是你開的進程,肯定有父進程)
(2)在內核中建立進程結構
(3)將結構插入到進程列表,便於維護
(4)分配資源給該進程
(5)複製父進程的內存映射消息
(6)管理文件描述符和連結點
(7)通知父進程
下面是一張進程列表的圖,命令:Pstree。
可以看到Init是所有進程的父進程,其他進程都是有Init進程直接或間接Fork出來的。
(2)Exec族為什麼需要Exec函數?
-
Fork子進程是為了執行新程序(Fork創建了子進程後,子進程和父進程同時被OS調度執行,因此子進程可以單獨的執行一個程序,這個程序宏觀上將會和父進程程序同時進行)。
-
可以直接在子進程的IF中寫入新程序打代碼。但這樣不夠靈活,因為我們只能把子進程程序的原始碼貼過來執行(必須知道原始碼,而且原始碼太長了也不好控制)使用Exec族函數運行新的可執行程序。Exec族函數可以直接把一個編譯好的可執行程序直接加載運行。
-
有了Exec族函數後,典型打父子進程程序是這樣的:子進程需要運行的程序被單獨編寫、單獨編譯連結成一個可執行程序(Hello)。主進程為父進程,Fork創建了子進程後在子進程中Exec來執行Hello,達到父子進程分別做不同程序同時(宏觀上)運行的效果。
代碼貼這兒,看這裡講解Exec族:
#include<unistd.h>
int execve(const char *path,char *const argv[],char *const envp[]);//這個是真正的系統調用
//以下的函數最後都是調用這個函數
int execl(const char *path,char *const argv,···);
int execlp(const char *file,char *const argv,···);
int execle(const char *path,char *const argv,···· ,char *const envp[]);
int execv(const char *path,char *const argv[]);
int execvp(const char *file,char *const argv,);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int main(void)
{
pid_t pid;
if((pid = fork) < 0)
{
perror("fork");
exit(1);
}
else if(pid == 0)
{
/* child */
execl("/bin/ls", "ls", "-l", "-a",);
}
else
{
printf("parent, child id = %d.\n",pid);
}
return 0;
}
(3)Wait/Waitpid
這裡幾個概念:
-
殭屍進程:子進程退出,父進程沒有及時回收,子進程成為殭屍進程
-
孤兒進程:父進程退出,而子進程沒有退出,子進程成為孤兒進程
-
Init進程:1號進程,負責收留孤兒進程,成為他們的父進程
有5種方式終止進程:
(1)main返回
(2)調用exit
(3)調用_exit
(4)調用abort(給自己發送異常終止信號)
(5)由一個信號終止
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int *status);
//這裡的status為一個整形指針,是該子進程的返回狀態。若該指針不為空,則可以通過該指針獲取子進程退出時的狀態。
pid_t waitpid(pid_t pid,int *status,int options);
// pid是進程號
/*
<-1 回收指定進程組內的任意子進程
-1 回收任意子進程
0 回收和當前waitpid調用一個組的所有子進程
>0 回收指定ID的子進程
*/
//options:
/*
WNOHANG:強制回收,不阻塞。
WUNTRANCED:一般用上面那個
*/
來個聯繫方式吧,進程間通信
常用的通信方式有:管道、消息隊列、共享內存、文件空間映射。1、管道:兩個進程間通信,最古老的通信方式了。2、消息隊列:在內核中建立一個鍊表,發送方按照一定標識將數據發送到內核中,內核將其放入鍊表。
接收方發送請求後,內核按照標識取出消息。
消息隊列是一種完全異步的通信方式。
3、共享內存:共享內存是將內存中的一段地址,在多個進程間共享。多個進程通過掛載在自己名下的地址直接對共享內存進行操作。4、文件空間映射:Mmap函數用來將文件或設備空間映射到內存中,可以通過對映射後的內存空間操作來獲得與操作文件一致的效果。進程間同步
進程間同步的方法主要有system信號量和進程間鎖。
家庭關係如何?(進程間關係)
1、進程組一個或多個進程組成的集合,進程組的組ID是一個正整數。
//獲取當前進程組組ID
pid_t getpgid(pid_t pid);
pid_t getpgrp(void);
幾個概念:
-
組長進程:進程ID號等於組ID。
-
組長進程可以創建一個進程組,創建該進程組中的進程。
- 只要進程中有一個進程存在,進程組就存在,與組長進程是否終止無關。
-
進程組生存期:進程組創建到最後一個進程離開(終止或轉移到另一個進程組)
//一個進程可以為自己或子進程設置進程組ID
int setpgid(pid_t pid,pid_t pgid);
//非root進程只能改變自己創建的子進程,或有權限操作的進程
2、會話
pid_t setsid(void);
- 調用進程不能是進程組組長,該進程變成新會話的首進程。
-
該進程成為一個新進程組的組長進程。
-
需要有Root權限(Ubunt不需要)
-
新會話丟棄原有控制終端,該會話沒有控制終端。
-
建立新會話時,先用Fork,然後父進程終止,子進程調用。
pid_t getsid(pid_t pid);
用於查看當前進程的會話ID。
注意點:組長進程不能成為新會話首進程,新會話首進程必定成為組長進程。
來串偽代碼:
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main
{
pid_t pid;
pid = fork;
if(pid == 0)
{
//列印:
getpid;//進程ID
getpgid(0);//組ID
getsid(0);//會話ID
sleep(10);
setsid;//子進程設為會話組長
//子進程非組長進程,故其成為新會話首進程,且成為組長進程。
//該進程ID即為會話進程ID
//再列印一遍
getpid;//進程ID
getpgid(0);//組ID
getsid(0);//會話ID
}
}
程序、進程與線程的區分
這個問題老師問過我,當時我沒答上來。1、進程是動態的,程序是靜態的。2、一個進程只能對應一個程序,而一個程序可以對應多個進程。
從作業系統方面來看,進程代表的是作業系統分配的內存、CPU時間片等資源的基本單位,是為正在運行的程序準備的運行環境。
版權聲明:本文為CSDN博主「看,未來」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本聲明。
原文連結:
https://blog.csdn.net/qq_43762191/article/details/106024446