文章目錄
初見進程,先查一下戶口
①進程環境
別吃驚我爲什麼能有個圈圈的①,專用符號嘿嘿。
進程控制塊PCB:就是進程在操作系統中的“戶口”,具體實現是 task_struct數據結構:
1.狀態信息,例如這個進程處於可執行狀態,休眠,掛起等。
2.性質,由於unix有很多變種,進程有自己獨特的性質。
3.資源,資源的鏈接比如內存,還有資源的限制和權限等。
4.組織,例如按照家族關係建立起來的樹(父進程,子進程等)。
②進程狀態
運行狀態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出來的。
3.2、exec族
爲什麼需要exec函數?
fork子進程是爲了執行新程序(fork創建了子進程後,子進程和父進程同時被OS調度執行,因此子進程可以單獨的執行一個程序,這個程序宏觀上將會和父進程程序同時進行)
可以直接在子進程的if中寫入新程序打代碼。但這樣不夠靈活,因爲我們只能把子進程程序的源代碼貼過來執行(必須知道源代碼,而且源代碼太長了也不好控制)
使用exec族函數運行新的可執行程序。exec族函數可以直接把一個編譯好的可執行程序直接加載運行。
有了exec族函數後,典型打父子進程程序是這樣的:子進程需要運行的程序被單獨編寫、單獨編譯鏈接成一個可執行程序(hello)。主進程爲父進程,fork創建了子進程後在子進程中exec來執行hello,達到父子進程分別做不同程序同時(宏觀上)運行的效果。
在我的印象中,我有一篇博客專門講解exec族,就那麼一找,還真有: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",NULL);
}
else
{
printf("parent, child id = %d.\n",pid);
}
return 0;
}
3.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:一般用上面那個
*/
來個聯繫方式吧,進程間通信
常用的通信方式有:管道、消息隊列、共享內存、文件空間映射。
管道:兩個進程間通信,最古老的通信方式了。
消息隊列:在內核中建立一個鏈表,發送方按照一定標識將數據發送到內核中,內核將其放入鏈表。
()接收方發送請求後,內核按照標識取出消息。
()消息隊列是一種完全異步的通信方式。
共享內存:共享內存是將內存中的一段地址,在多個進程間共享。多個進程通過掛載在自己名下的地址直接對共享內存進行操作。
文件空間映射:mmap函數用來將文件或設備空間映射到內存中,可以通過對映射後的內存空間操作來獲得與操作文件一致的效果。
這塊如果要展開的話,篇幅會很長,很長,所以我做了一個目錄表:
想要進程的聯繫方式?點這裏
進程間同步
進程間同步的方法主要有system信號量和進程間鎖,信號量我會在後面的文章再行整理,進程間鎖嘛,
進程間鎖
家庭關係如何?(進程間關係)
①進程組
一個或多個進程組成的集合,進程組的組ID是一個正整數。
//獲取當前進程組組ID
pid_t getpgid(pid_t pid);
pid_t getpgrp(void);
幾個概念:
組長進程:進程ID號等於組ID。
組長進程可以創建一個進程組,創建該進程組中的進程。
只要進程中有一個進程存在,進程組就存在,與組長進程是否終止無關。
進程組生存期:進程組創建到最後一個進程離開(終止或轉移到另一個進程組)
//一個進程可以爲自己或子進程設置進程組ID
int setpgid(pid_t pid,pid_t pgid);
//非root進程只能改變自己創建的子進程,或有權限操作的進程
②會話
pid_t setsid(void);
1、調用進程不能是進程組組長,該進程變成新會話的首進程。
2、該進程成爲一個新進程組的組長進程。
3、需要有root權限(ubunt不需要)
4、新會話丟棄原有控制終端,該會話沒有控制終端。
5、建立新會話時,先用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時間片等資源的基本單位,是爲正在運行的程序準備的運行環境。