一、進程相關概念
1.什么是進程
程序:靜態的,編譯好的可執行文件,存放在磁盤中的指令和數據的集合
進程:動態的,是程序的一次執行過程,是獨立的可調度的任務
2.進程的特點
(1)對32位系統,系統會為每個進程分配0~4G的虛擬空間,其中,0~3G(用戶空間)是每個進程獨有的,3~4G(內核空間)是所有進程共有
? ??
? 進程間通信:通過內核空間
(2)? CPU調度進程時會給進程分配時間片(幾毫秒~十幾毫秒),當時間片用完后,cpu再進行其他進程的調度,實現進程的輪轉,從而實現多任務的操作。(沒有外界干預的情況下怎么調度進程是CPU隨機分配的 )
(3)進程控制塊task_struct(了解)
●?進程控制塊pcb:包含描述進程的相關信息
●?進程標識PID:唯一的標識一個進程
????????????????主要進程標識:
????????????????進程號(PID: Process Identity Number)
????????????????父進程號:(Parent Process ID: PPID)
●?進程用戶
●?進程狀態、優先級
●?文件描述符(記錄當前進程打開的文件)
3.進程段
Linux中的進程大致包含三個段:
數據段:存放的是全局變量、常數以及動態數據分配的數據空間(如malloc函數取得的空間)等。
正文段:存放的是程序中的代碼
堆棧段:存放的是函數的返回地址、函數的參數以及程序中的局部變量 (類比內存的棧區)
4.進程分類
?交互進程:該類進程是由shell控制和運行的。交互進程既可以在前臺運行,也可以在后臺運行。?該類進程經常與用戶進行交互,需要等待用戶的輸入,當接收到用戶的輸入后,該類?進程會立刻響應,典型的交互式進程有:shell命令進程、文本編輯器等
批處理進程:該類進程不屬于某個終端,它被提交到一個隊列中以便順序執行。
?5.進程狀態
? ? ? ? ? ? ? ? ? ? ??
6. 進程狀態切換
? ? ? ? ? ?
?7.進程相關命令
?補充:根據進程的優先級進行調度,優先級高的進程先執行。
兩種類型:
1.? 非剝奪式(非搶占式)優先級調度算法。當一個進程正在處理上運行時,即使有某個更為重要或緊迫的進程進入就緒隊列,仍然讓正在進行的進程繼續運行,直到由于其自身原因而主動讓出處理機(任務完成或等待事件),才把處理機分配給更為重要或緊迫的進程。
2.? 剝奪式(搶占式)優先級調度算法。當一個進程正在處理機上運行時,若有某個更為重要或緊迫的進程進入就緒隊列,則立即暫停正在運行的進程,將處理機分配給更重要或緊迫的進程。
二、進程函數接口
1.創建進程 fork()
#include <sys/types.h>#include <unistd.h>/*
功能:創建子進程參數:無返回值: 成功:父進程-->返回子進程進程號子進程-->返回0失敗:父進程-->返回-1,并設置errno子進程并未創建*/
pid_t fork(void);
fork函數特點:1)子進程幾乎拷貝了父進程的全部內容。包括代碼、數據、系統數據段中的pc值、棧中的數據、父進程中打開的文件等;但它們的PID、PPID是不同的。
2)父子進程有獨立的地址空間,互不影響;當在相應的進程中改變全局變量、靜態變量,都互不影響。
3)若父進程先結束,子進程成為孤兒進程,被init進程收養,子進程變成后臺進程。
4)若子進程先結束,父進程如果沒有及時回收資源,子進程變成僵尸進程(要避免僵尸進程產生)
2.回收資源wait()
#include <sys/types.h>#include <sys/wait.h>/*
功能:回收進程資源(是一個阻塞函數)參數:子進程的退出狀態,不接受子進程狀態的話就將參數設為NULL返回值:成功 ---> 返回子進程的進程號失敗--->返回-1*/pid_t wait(int *wstatus);
#include <sys/types.h>
#include <sys/wait.h>/*
功能:回收進程資源(是一個阻塞函數)參數: pid:>0 指定某一個子進程=-1 任意子進程=0 等待其組ID等于調用進程的組ID的任一子進程<-1 等待其組ID等于pid的絕對值的任一子進程status:子進程退出狀態options:0 阻塞 WNOHANG 非阻塞返回值: 成功:當options的值設置成 0時:返回結束的子進程的進程號當options的值設置成WNOHANG時若此時子進程已經結束,返回結束的子進程的進程號若此時子進程已經結束,返回 0失敗:返回-1*/pid_t waitpid(pid_t pid, int *wstatus, int options);
wait(NULL)? ?<==>? wait(-1,NULL,0)
3.結束進程exit()
參數:status是一個整型的參數,可以利用這個參數傳遞進程結束時的狀態。通常0表示正常結束;
其他的數值表示出現了錯誤,進程非正常結束#include <stdlib.h>
void exit(int status);
功能:結束進程,刷新緩存#include <unistd.h>
void _exit(int status);
功能:結束進程,不刷新緩存
?常用:exit(0)
4.獲取進程號getpid()
#include <sys/types.h>#include <unistd.h>pid_t getpid(void);
功能:獲取當前進程的進程號
pid_t getppid(void);
功能:獲取當前進程的父進程號
在父進程中獲取子進程的PID? :通過fork()的返回值?
????????
三、進程間通信 (IPC)
?1.通信方式介紹:
- 早期的進程間通信:無名管道(pipe) 有名管道(fifo)信號(signal)
- system V IPC:? ?共享內存 (share memory) 消息隊列(message queue) 信號集(semaphore set)
- BSD: 套接字(socket)
2.無名管道
?????????????? ? ? ?
特點:
(1)? 只能用于具有親緣關系的進程之間的通信
(2)? 半雙工的通信模式,具有固定的讀端fd[0]和寫端fd[1]。
(3)? 無名管道可以看成是一種特殊的文件(實際是找不到這個文件的),對于它的讀寫可以使用文件IO如read、write函數進行操作。
(4)? 管道是基于文件描述符的通信方式。當一個管道建立時,它會創建兩個文件描述符 fd[0]和fd[1]。其中fd[0]固定用于讀管道,而fd[1]固定用于寫管道。
函數接口:?
int pipe(int fd[2])
功能:創建無名管道
參數:文件描述符 fd[0]:讀端 fd[1]:寫端
返回值:成功 0失敗 -1
3.有名管道
特點:
1)? 有名管道可以使互不相關的兩個進程互相通信。
2)? 有名管道可以通過路徑名來指出,并且在文件系統中可見,但內容存放在內存中。但是讀寫數據不會存在文件中,而是在管道中,也就是內核空間中。
3)? 進程通過文件IO來操作有名管道
4)? 不支持如lseek() 操作
5)? 有名管道遵循先進先出規則
?函數接口:
/*
功能:創健有名管道
參數:filename:有名管道文件名mode:權限
返回值:成功:0失敗:-1,并設置errno號*/int mkfifo(const char *filename,mode_t mode);
4.信號
?kill -l? :查看系統中的信號
kill -num PID :給某個進程發信號
4.1概念:
- 信號是軟件層面上對中斷機制的一種模擬,是一種異步通信模式。
- 信號可以直接進行用戶空間進程和內核進程之間的交互,內核進程也可以利用它來通知用戶空間進程發生了哪些系統事件。
- 如果該進程當前并未處于執行態,則該信號就由內核保存起來,直到該進程恢復執行再傳遞給它;如果一個信號被進程設置為阻塞,則該信號的傳遞被延遲,直到其阻塞被取消時才被傳遞給進程。
4.2信號的響應方式
- 忽略信號 :對信號不做任何處理,但是有兩個信號不能忽略:即SIGKILL及SIGSTOP。
- 捕捉信號:定義信號處理函數,當信號發生時,執行相應的處理函數
- 執行缺省操作:Linux對每種信號都規定了默認操作
4.3信號種類
4.4函數接口
?(1)信號的發送kill()和掛起 pause()
#include <sys/types.h>
#include <signal.h>
/*
功能:信號發送
參數:pid:指定進程sig:要發送的信號
返回值:成功 0 失敗 -1*/int kill(pid_t pid, int sig);
#include <signal.h>
/*
功能:進程向自己發送信號
參數:sig:信號
返回值:成功 0 失敗 -1*/int raise(int sig);
? ? ? ?注意:? ?? raise( sig )? <==> kill ( getpid() , sig )
#include <unistd.h>/*功能:用于將調用進程掛起(類似于死循環且不占用CPU資源),直到收到被捕獲處理的信號為止。*/
int pause(void);
?注意:若程序中有使用signal的捕捉信號自定義函數處理時,pause接收到被捕獲處理的信號以后會結束掛起
?(2)定時器alarm()
#include <unistd.h>/*
功能:在進程中設置一個定時器。當定時器指定的時間到了時,它就向進程發送SIGALARM信號。
參數:seconds:定時時間,單位為秒
返回值:如果調用此alarm()前,進程中已經設置了鬧鐘時間,則返回上一個鬧鐘時間的剩余時間,否則返回0。*/
unsigned int alarm(unsigned int seconds);
注意:
1)一個進程只能有一個鬧鐘時間。如果在調用alarm時已設置過鬧鐘時間,則之前的鬧鐘時間被新值所代替。
2)系統默認對SIGALRM(鬧鐘到點后內核發送的信號)信號的響應: 如果不對SIGALRM信號進行捕捉或采取措施,默認情況下,鬧鐘響鈴時刻會退出進程(程序非正常結束,要注意刷新問題!!)。
3)常用操作:取消定時器alarm(0),返回舊鬧鐘余下秒數。
4)alarm是非阻塞函數
?(3)信號處理函數signal()
#include <signal.h>/*
功能:信號處理函數參數:signum:要處理的信號handler:信號處理方式忽略信號 (SIG_IGN)執行默認操作 (SIG_DFL)捕捉信號并執行自定義操作 (自定義函數名)返回值:成功:設置之前的信號處理方式失敗:-1*/sighandler_t signal(int signum, sighandler_t handler);
補充:
sighandler_t? 是一個 函數指針 的重命名 (該指針指向的函數是一個 無返回值,形參為一個整型的類型函數)?typedef void (*sighandler_t)(int);??
5. 共享內存
5.1特點
1)共享內存是一種最為高效的進程間通信方式,進程可以直接讀寫內存,而不需要任何數據的拷貝。
2)為了在多個進程間交換信息,內核專門留出了一塊內存區,可以由需要訪問的進程
將其映射到自己的私有地址空間。進程就可以直接讀寫這一內存區而不需要進行數據的拷貝,從而大大提高的效率。
3)? 由于多個進程共享一段內存,因此也需要依靠某種同步機制,如互斥鎖和信號量等
?
5.2步驟
(1)? 創建key值
(2)? 創建或打開共享內存
(3)? 映射共享內存到用戶空間
(4)? 撤銷映射
(5)? 刪除共享內存
?5.3函數接口
(1)創建key值
使用pathname提供的文件的inode號和字符的ASCII碼值通過某種計算方法得到key值
#include <sys/types.h>
#include <sys/ipc.h>
/*
功能:創建出來的具有唯一映射關系的一個key值,幫助操作系統用來標識一塊共享內存
參數:Pathname:已經存在的可訪問文件的名字Proj_id:一個字符(因為只用低8位,一個字符正好是8位)返回值:成功:key值失敗:-1*/key_t ftok(const char *pathname, int proj_id);
(2)? 創建或打開共享內存
#include <sys/ipc.h>
#include <sys/shm.h>/*
功能:創建或打開共享內存
參數:key 鍵值size 共享內存的大小shmflg IPC_CREAT|IPC_EXCL|0777
返回值:成功 返回共享內存號 shmid出錯或共享內存已存在 -1*/int shmget(key_t key, size_t size, int shmflg);
?注意:shmget()?出錯或共享內存已存在都返回?-1,因此,要在容錯判斷中在加一次判斷,使用系統定義的file exist錯誤號進行判斷
(3)? 映射共享內存到用戶空間
#include <sys/types.h>
#include <sys/shm.h>/*
功能:映射共享內存,即把指定的共享內存映射到進程的地址空間用于訪問
參數:shmid 共享內存的id號shmaddr 一般為NULL,表示由系統自動完成映射如果不為NULL,那么有用戶指定shmflg:SHM_RDONLY就是對該共享內存只進行讀操作0 可讀可寫
返回值:成功:完成映射后的地址,出錯:-1(地址)*/void *shmat(int shmid,const void *shmaddr,int shmflg);
最常使用形式:(強制轉換)shmat(shmid,NULL,0);
容錯判斷:因為shmat()返回的是任意類型的指針,要注意強轉
(4)? 撤銷映射
#include <sys/types.h>
#include <sys/shm.h>
/*
功能:取消映射
參數:要取消的地址
返回值:成功0 失敗的-1*/int shmdt(const void *shmaddr);
(5)? 刪除共享內存
使用的是shmctl()函數的刪除功能
#include <sys/ipc.h>
#include <sys/shm.h>
/*功能:(刪除共享內存),對共享內存進行各種操作
參數:shmid 共享內存的id號cmd IPC_STAT 獲得shmid屬性信息,存放在第三參數IPC_SET 設置shmid屬性信息,要設置的屬性放在第三參數IPC_RMID:刪除共享內存,此時第三個參數為NULL即可buf shmid所指向的共享內存的地址,空間被釋放以后地址就賦值為null
返回:成功0 失敗-1*/int shmctl(int shmid,int cmd,struct shmid_ds *buf);
即使用??shmctl(shmid,IPC_RMID,NULL);???
5.4 命令
ipcs -m? --->查看系統中的共享內存
ipcrm -m shmid? --->刪除共享內存
注意:可能不能直接刪除仍有進程使用的共享內存
(可通過ps -ef查看有哪些進程,kill掉相關進程后再執行操作)
6.信號燈集
6.1特點
信號燈(semaphore),也叫信號量,信號燈集是一個信號燈的集合。它是不同進程間或一個給定進程內部不同線程間同步的機制;
而Posix信號燈指的是單個計數信號燈:無名信號燈、有名信號燈。(咱們學的是無名信號燈)
System V的信號燈是一個或者多個信號燈的一個集合。其中的每一個都是單獨的計數信號燈。
通過信號燈集實現共享內存的同步操作
6.2 步驟
(1)? 創建key值: ftok
(2)? 創建或打開信號燈集: semget
(3)? 初始化信號燈: semctl
(4)? PV操作: semop
(5)? 刪除信號燈集: semctl
6.3 命令
ipcs -s
ipcrm -s
6.4 函數接口
int semget(key_t key, int nsems, int semflg);
功能:創建/打開信號燈
參數:key:ftok產生的key值nsems:信號燈集中包含的信號燈數目semflg:信號燈集的訪問權限,通常為IPC_CREAT|IPC_EXCL|0666
返回值:成功:信號燈集ID失敗:-1int semctl ( int semid, int semnum, int cmd…/*union semun arg*/);
功能:信號燈集合的控制(初始化/刪除)
參數:semid:信號燈集IDsemnum: 要操作的集合中的信號燈編號,信號燈編號從0開始cmd: GETVAL:獲取信號燈的值,返回值是獲得值SETVAL:設置信號燈的值,需要用到第四個參數:共用體IPC_RMID:從系統中刪除信號燈集合
返回值:成功 0失敗 -1
用法:
1. 初始化信號燈集:
需要自定義共用體
union semun{int val;
} mysemun;
mysemun.val = 10;
semctl(semid, 0, SETVAL, mysemun);2. 獲取信號燈值:函數semctl(semid, 0, GETVAL)的返回值
3. 刪除信號燈集:semctl(semid, 0, IPC_RMID);int semop ( int semid, struct sembuf *opsptr, size_t nops);
功能:對信號燈集合中的信號量進行PV操作
參數:semid:信號燈集IDopsptr:操作方式nops: 要操作的信號燈的個數 1個
返回值:成功 :0失敗:-1
struct sembuf {short sem_num; // 要操作的信號燈的編號short sem_op; // 0 : 等待,直到信號燈的值變成0// 1 : 釋放資源,V操作// -1 : 申請資源,P操作 short sem_flg; // 0(阻塞),IPC_NOWAIT, SEM_UNDO
};用法:
申請資源 P操作:mysembuf.sem_num = 0;mysembuf.sem_op = -1;mysembuf.sem_flg = 0;semop(semid, &mysembuf, 1);
釋放資源 V操作:mysembuf.sem_num = 0;mysembuf.sem_op = 1;mysembuf.sem_flg = 0;semop(semid, &mysembuf, 1);
7.消息隊列
7.1特點
消息隊列是IPC對象(活動在內核級別的一種進程間通信的工具)的一種一個消息隊列由一個標識符 (即隊列號?--> msgid)來標識
消息隊列就是一個消息的列表。用戶可以在消息隊列中添加消息、讀取消息等
消息隊列可以按照類型(自己設一個值作為類型)來發送/接收消息
7.2 步驟
(1)? 創建key值: ftok()
(2)? 創建或打開消息隊列: msgget()
(3)? 添加消息: 按照類型把消息添加到已經打開的消息隊列末尾msgsnd()
(4)? 讀取消息: 可以按照類型把消息從列表中取走 msgrcv()
(5)? 刪除消息隊列msgctl()
7.3 操作命令
ipcs -q: 查看消息隊列
ipcrm -q msgid: 刪除消息隊列
7.4 函數接口
(1)? 創建key值: ftok()? ?同前面的共享內存中的ftok函數
(2)? 創建或打開消息隊列: msgget()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>/*
功能:創建或打開一個消息隊列
參數: key值flag:創建消息隊列的權限IPC_CREAT|IPC_EXCL|0666
返回值:成功:msgid失敗:-1*/int msgget(key_t key, int flag);
注意:有時候可能會創建失敗或者migid==0,因此創建完后都要用?ipcs -q 命令查看,若是則要用 ipcrm -q msgid 命令刪除已建隊列然后重新進行創建。
(3)? 添加消息: 按照類型把消息添加到已經打開的消息隊列末尾msgsnd()
常用的消息結構:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?制作消息
? ? ? ? ? ? ? ? ? ? ? ?
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>/*
功能:添加消息
參數:msqid:消息隊列的IDmsgp:指向消息的指針。常用消息結構msgbuf如下:struct msgbuf{long mtype; //消息類型char mtext[N]}; //消息正文size:發送的消息正文的字節數flag:IPC_NOWAIT消息沒有發送完成函數也會立即返回 0:直到發送完成函數才返回 (阻塞等待)
返回值:成功:0失敗:-1*/int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
(4)? 讀取消息: 可以按照類型把消息從列表中取走 msgrcv()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>/*
功能:讀取消息
參數:msgid:消息隊列的IDmsgp:存放讀取消息的空間size:接受的消息正文的字節數(sizeof(msgp)-sizeof(long))msgtype:0:接收消息隊列中第一個消息。大于0:接收消息隊列中第一個類型為msgtyp的消息.小于0:接收消息隊列中類型值不小于msgtyp的絕對值且類型值又最小的消息。flag:0:若無消息函數會一直阻塞IPC_NOWAIT:若沒有消息,進程會立即返回ENOMSG返回值:成功:接收到的消息的長度失敗:-1*/ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
(5)? 刪除消息隊列msgctl()
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>/*
功能:對消息隊列的操作,刪除消息隊列
參數:msqid:消息隊列的隊列IDcmd:IPC_STAT:讀取消息隊列的屬性,并將其保存在buf指向的緩沖區中。IPC_SET:設置消息隊列的屬性。這個值取自buf參數。IPC_RMID:從系統中刪除消息隊列。buf:消息隊列緩沖區
返回值:成功:0失敗:-1
*/int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
用法:msgctl(msgid, IPC_RMID, NULL)