【Linux】-- 进程间通讯
目錄
進(jìn)程間通訊概念的引入
意義(手段)
思維構(gòu)建
進(jìn)程間通信方式
管道
站在用戶角度-淺度理解管道
匿名管道 pipe函數(shù)
站在文件描述符角度-深度理解管道
管道的特點(diǎn)總結(jié)
管道的拓展
單機(jī)版的負(fù)載均衡
匿名管道讀寫規(guī)則
命名管道
前言
原理
創(chuàng)建一個(gè)命名管道
用命名管道實(shí)現(xiàn)myServer&myClient通信
匿名管道與命名管道的區(qū)別
命名管道的打開規(guī)則
system V共享內(nèi)存
共享內(nèi)存數(shù)據(jù)結(jié)構(gòu)
共享內(nèi)存的創(chuàng)建
key概念引入
key概念解析
基于共享內(nèi)存理解信號(hào)量
總結(jié)
進(jìn)程間通訊概念的引入
意義(手段)
? ? ? ? 在沒有進(jìn)程間通訊之前,理論上都是單進(jìn)程的,那么也就無法使用并發(fā)能力,更無法實(shí)現(xiàn)多進(jìn)程協(xié)同(將一個(gè)事,分幾個(gè)進(jìn)程做)。而進(jìn)程間通訊,就是對(duì)于實(shí)現(xiàn)多進(jìn)程協(xié)同的手段。
- 數(shù)據(jù)傳輸:一個(gè)進(jìn)程需要將它的數(shù)據(jù)發(fā)送給另一個(gè)進(jìn)程
- 資源共享:多個(gè)進(jìn)程之間共享同樣的資源。
- 通知事件:一個(gè)進(jìn)程需要向另一個(gè)或一組進(jìn)程發(fā)送消息,通知它(它們)發(fā)生了某種事件(如進(jìn)程終止時(shí)要通知父進(jìn)程)。
- 進(jìn)程控制:有些進(jìn)程希望完全控制另一個(gè)進(jìn)程的執(zhí)行(如Debug進(jìn)程),此時(shí)控制進(jìn)程希望能夠攔截另一個(gè)進(jìn)程的所有陷入和異常,并能夠及時(shí)知道它的狀態(tài)改變。
思維構(gòu)建
? ? ? ? 進(jìn)程間通訊重點(diǎn),就在與如何讓不同的進(jìn)程資源的傳遞。而進(jìn)程是具有獨(dú)立性的,也就是說進(jìn)程相通訊會(huì)難度較大? --? 因?yàn)檫M(jìn)程間通訊的本質(zhì)是:先讓不同的進(jìn)程看見同一份資源。
融匯貫通的理解:
? ? ? ? 進(jìn)程的設(shè)計(jì)天然就是為了保證獨(dú)立性的(即,進(jìn)程之間無瓜葛),所以深入的說:所謂的同一份資源不能所屬于任何一個(gè)進(jìn)程,更強(qiáng)調(diào)共享,不屬于任何一個(gè)進(jìn)程。
進(jìn)程間通信方式
管道
- 匿名管道pipe
- 命名管道
System V IPC
- System V 消息隊(duì)列
- System V 共享內(nèi)存
- System V 信號(hào)量
POSIX IPC
- 消息隊(duì)列
- 共享內(nèi)存
- 信號(hào)量
- 互斥量
- 條件變量
- 讀寫鎖
管道
????????我們把從一個(gè)進(jìn)程連接到另一個(gè)進(jìn)程的數(shù)據(jù)流稱為一個(gè)“管道”。
????????當(dāng)在兩個(gè)命令之間設(shè)置管道 "|" 時(shí),管道符 "|" 左邊命令的輸出就變成了右邊命令的輸入。只要第一個(gè)命令向標(biāo)準(zhǔn)輸出寫入,而第二個(gè)令是從標(biāo)準(zhǔn)輸入讀取,那么這兩個(gè)命令就可以形成一個(gè)管道。大部分的 Linux 命令都可以用來形成管道。
命令:who | wc -l
用于查看當(dāng)前服務(wù)器下登陸的用戶人數(shù)。
補(bǔ)充:
????????Linux who命令:用于顯示系統(tǒng)中有哪些使用者正在上面,顯示的資料包含了使用者 ID、使用的終端機(jī)、從哪邊連上來的、上線時(shí)間、呆滯時(shí)間、CPU 使用量、動(dòng)作等等。使用權(quán)限:所有使用者都可使用。
????????Linux wc命令:用于計(jì)算字?jǐn)?shù)。在此處由于who中一個(gè)用戶為一行,所以此處用 -l?顯示行數(shù),即登錄用戶個(gè)數(shù)。
? ? ? ? 其中,運(yùn)行起來后who命令與wc命令就是兩個(gè)不同的進(jìn)程。who進(jìn)程作為數(shù)據(jù)提供方,通過標(biāo)準(zhǔn)輸入將數(shù)據(jù)寫入管道,wc進(jìn)程再通過標(biāo)準(zhǔn)輸入將數(shù)據(jù)從管道中讀取出,進(jìn)而再將數(shù)據(jù)進(jìn)行處理?"-l" ,后以標(biāo)準(zhǔn)輸出的方式將結(jié)果給用戶。
站在用戶角度-淺度理解管道
匿名管道 pipe函數(shù)
#include <unistd.h>
功能: ????????創(chuàng)建一無名管道。 原型: ????????int pipe(int?pipefd[2]); 參數(shù): ? ? ? ? 輸出型參數(shù),通過調(diào)用該參數(shù),得到被打開的文件fd。| pipefd[0] | 管道讀端文件描述符 |
| pipefd[1] | 管道寫端文件描述符 |
返回值:
????????成功時(shí),返回0。出現(xiàn)錯(cuò)誤時(shí),返回-1。
1. 父進(jìn)程創(chuàng)建管道?
2. 父進(jìn)程fork出子進(jìn)程
3. 父進(jìn)程關(guān)閉讀 / 寫,子進(jìn)程關(guān)閉寫 / 讀。(fork之后各自關(guān)掉不用的描述符)
Note:對(duì)于pipe函數(shù)創(chuàng)建的管道,只能夠進(jìn)行單向通信。(反之,會(huì)導(dǎo)致讀寫導(dǎo)致管道中數(shù)據(jù)污染、混亂)。我們需要對(duì)于父或子進(jìn)程中的fd參數(shù)中的,文件符號(hào)進(jìn)行關(guān)閉。
????????pipe函數(shù)的使用需要結(jié)合fork函數(shù)的父子進(jìn)程。
站在文件描述符角度-深度理解管道
#問:如何做到讓不同的進(jìn)程,看到同一份資源?
? ? ? ? 以fork讓子進(jìn)程繼承,能夠讓具有“血緣關(guān)系”的進(jìn)程進(jìn)行進(jìn)程間通訊。(管道:常用于父進(jìn)程進(jìn)程)
融匯貫通的理解:
? ? ? ? fork創(chuàng)建子進(jìn)程,等于系統(tǒng)中多了一個(gè)子進(jìn)程。而進(jìn)程 = 內(nèi)核數(shù)據(jù)結(jié)構(gòu) + 進(jìn)程代碼和數(shù)據(jù)。進(jìn)程相關(guān)內(nèi)核數(shù)據(jù)結(jié)構(gòu)來源于操作系統(tǒng),進(jìn)程代碼和數(shù)據(jù)一般來源于磁盤。
????????而由于為了進(jìn)程具有獨(dú)立性,所以創(chuàng)建子進(jìn)程的同時(shí),需要分配對(duì)應(yīng)的進(jìn)程相關(guān)內(nèi)核結(jié)構(gòu)。對(duì)于數(shù)據(jù),被寫入更改時(shí)操作系統(tǒng)采用寫時(shí)拷貝技術(shù),進(jìn)行對(duì)父子進(jìn)程數(shù)據(jù)的分離。
? ? ? ? 父進(jìn)程與子進(jìn)程擁有自身的fd_array[]存儲(chǔ)文件描述符fd,但是其中存儲(chǔ)的fd時(shí)相同的,而文件相關(guān)內(nèi)核數(shù)據(jù),并不屬于進(jìn)程數(shù)據(jù)結(jié)構(gòu),所以并不會(huì)單獨(dú)為子進(jìn)程創(chuàng)建。于是:父進(jìn)程與子進(jìn)程指向的是一個(gè)文件? ->? 這就讓不同的進(jìn)程看到了同一份資源。
? ? ? ? 管道本質(zhì)上就是一個(gè)文件。一個(gè)具有讀寫功能,并且無需放入磁盤的文件(通道是進(jìn)程進(jìn)行通訊的臨時(shí)內(nèi)存空間,無需將內(nèi)容放入磁盤中保留)。
(tty:標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯(cuò)誤)?
1. 父進(jìn)程創(chuàng)建管道?
?2. 父進(jìn)程fork出子進(jìn)程
?3. 父進(jìn)程關(guān)閉讀 / 寫,子進(jìn)程關(guān)閉寫 / 讀。(fork之后各自關(guān)掉不用的描述符)
代碼實(shí)現(xiàn)的關(guān)鍵:
管道的特點(diǎn)總結(jié)
1. 管道是用來進(jìn)程具有血緣關(guān)系的進(jìn)程進(jìn)行進(jìn)程間通訊。
2. 管道具有通過讓進(jìn)程間通訊,提供訪問控制。
? ? ? ? a、寫端快,讀端慢,寫滿了不能再寫了。
? ? ? ? b、寫端慢,讀端快,管道沒有數(shù)據(jù)的時(shí)候,讀需要等待。
補(bǔ)充:
? ? ? ? c、寫端關(guān)閉,讀端為0,標(biāo)識(shí)讀到了文件結(jié)尾。
? ? ? ? d、讀端關(guān)閉,寫端繼續(xù)寫,操作系統(tǒng)終止寫端進(jìn)程。
3. 管道提供的是面向流式的通信服務(wù) -- 面向字節(jié)流。
4. 管道是基于文件的,文件的生命周期是隨進(jìn)程的,所以管道的生命周期是隨進(jìn)程的。
5. 管道是單向通行的,就是半雙工通信的一種特殊情況。
數(shù)據(jù)的傳送方式可以分為三種:
? ? ? ? 單工通信(Half Duplex):是通訊傳輸?shù)囊粋€(gè)術(shù)語。一方固定為發(fā)送端,另一方固定為接收端。即:一方只能寫一方只能讀。
????????半雙工通信(Half Duplex):是通訊傳輸?shù)囊粋€(gè)術(shù)語。指數(shù)據(jù)傳輸指數(shù)據(jù)可以在一個(gè)信號(hào)載體的兩個(gè)方向上傳輸,但是不能同時(shí)傳輸。即:一段時(shí)間內(nèi),只能一方寫一方讀。
????????全雙工通信(Full Duplex):是通訊傳輸?shù)囊粋€(gè)術(shù)語。指通信允許數(shù)據(jù)在兩個(gè)方向上同時(shí)傳輸,它在能力上相當(dāng)于兩個(gè)單工通信方式的結(jié)合。即:一段時(shí)間內(nèi),每方能寫且讀。
管道的拓展
單機(jī)版的負(fù)載均衡
? ? ? ? 以循環(huán)fork函數(shù)開辟多個(gè)子進(jìn)程,并利用pipe函數(shù)。針對(duì)于每一個(gè)子進(jìn)程開辟一個(gè)管道,父進(jìn)程通過管道安排其中一個(gè)子進(jìn)程做某任務(wù)。
#pragma once#include <iostream> #include <unordered_map> #include <string> #include <functional>typedef std::function<void()> func;std::vector<func> callbacks; std::unordered_map<int, std::string> desc;void readMySQL() {std::cout << "sub process[" << getpid() << "]執(zhí)行訪問數(shù)據(jù)庫的任務(wù)" << std::endl; }void executeUlt() {std::cout << "sub process[" << getpid() << "]執(zhí)行url解析\n" << std::endl; }void cal() {std::cout << "sub process[" << getpid() << "] 執(zhí)行加密任務(wù)\n" << std::endl; }void save() {std::cout << "sub process[" << getpid() << "] 執(zhí)行數(shù)據(jù)持久化任務(wù)\n" << std::endl; }void load() {callbacks.push_back(readMySQL);desc.insert({callbacks.size(), "readMySQL: 執(zhí)行訪問數(shù)據(jù)庫的任務(wù)"});callbacks.push_back(executeUlt);desc.insert({callbacks.size(), "executeUlt: 進(jìn)行url解析"});callbacks.push_back(cal);desc.insert({callbacks.size(), "cal: 進(jìn)行加密計(jì)算"});callbacks.push_back(save);desc.insert({callbacks.size(), "save: 執(zhí)行數(shù)據(jù)持久化任務(wù)"}); }// 功能展示 void showHandler() {for(const auto &iter : desc)std::cout << iter.first << " -> " << iter.second << std::endl; }// 具有的功能數(shù) int handlerSize() {return callbacks.size(); } #include <iostream> #include <vector> #include <unistd.h> #include <cassert> #include <sys/wait.h> #include <sys/types.h> #include "Task.hpp"using namespace std;#define PROCESS_NUM 4int waitCommand(int waitfd, bool& quit) {//此處由于是父進(jìn)程寫入一個(gè)整數(shù) -- 用以子進(jìn)程執(zhí)行相關(guān)內(nèi)容//規(guī)定:子進(jìn)程讀取的數(shù)據(jù)必須是4字節(jié)uint32_t command = 0;ssize_t s = read(waitfd, &command, sizeof(command));if(s == 0){quit = 1;return -1;}assert(s == sizeof(uint32_t));return command; }void wakeUp(pid_t who, int fd, uint32_t command) {write(fd, &command, sizeof(command));cout << "main process call: " << who << "process," << " execute: " << desc[command] << ", through write fd: " << fd << endl; }int main() {load();// 存儲(chǔ):<子進(jìn)程id,父進(jìn)程對(duì)應(yīng)寫端符fd>vector<pair<pid_t, int>> slots;//1. 創(chuàng)建多個(gè)進(jìn)程for(int i = 0; i < PROCESS_NUM; ++i){//1.1 創(chuàng)建管道int pipefd[2] = {0};int n = pipe(pipefd);assert(n == 0);(void)n;//1.2 fork創(chuàng)建子進(jìn)程pid_t id = fork();assert(id != -1);(void)id;if(id == 0){// 子進(jìn)程 -- 關(guān)閉寫端close(pipefd[1]);while(true){// 用于判斷是否bool quit = 0;int command = waitCommand(pipefd[0], quit);if(quit){break;}if(command >= 1 && command <= handlerSize())callbacks[command - 1]();elsecout << "error, 非法操作" << endl;}exit(1);}//將父進(jìn)程讀端關(guān)閉close(pipefd[0]);slots.push_back(make_pair(id, pipefd[1]));}while(true){int select;int command;cout << "############################################" << endl;cout << "## 1. show funcitons 2.command ##" << endl;cout << "############################################" << endl;cout << "Please Select> ";cin >> select;if(select == 1)showHandler();else if(select == 2){cout << "Enter command" << endl;cin >> command;// 隨機(jī)挑選進(jìn)程int choice = rand() % PROCESS_NUM;//將任務(wù)指派給指定的進(jìn)程wakeUp(slots[choice].first, slots[choice].second, command);}elsecout << "輸入錯(cuò)誤,請(qǐng)重新輸入" << endl;}// 關(guān)閉父進(jìn)程寫端fd,所有的子進(jìn)程都會(huì)退出for(const auto &slot : slots)close(slot.second);// 回收所有的子進(jìn)程信息for(const auto &slot : slots)waitpid(slot.first, nullptr, 0);return 0; }匿名管道讀寫規(guī)則
- 當(dāng)沒有數(shù)據(jù)可讀時(shí)
- O_NONBLOCK disable:read調(diào)用阻塞,即進(jìn)程暫停執(zhí)行,一直等到有數(shù)據(jù)來到為止
- O_NONBLOCK enable:read調(diào)用返回-1,errno值為EAGAIN。
- 當(dāng)管道滿的時(shí)候
- O_NONBLOCK disable: write調(diào)用阻塞,直到有進(jìn)程讀走數(shù)據(jù)
- O_NONBLOCK enable:調(diào)用返回-1,errno值為EAGAIN
- 如果所有管道寫端對(duì)應(yīng)的文件描述符被關(guān)閉,則read返回0
- 如果所有管道讀端對(duì)應(yīng)的文件描述符被關(guān)閉,則write操作會(huì)產(chǎn)生信號(hào)SIGPIPE,進(jìn)而可能導(dǎo)致write進(jìn)程退出
- 當(dāng)要寫入的數(shù)據(jù)量不大于PIPE_BUF時(shí),linux將保證寫入的原子性。
- 當(dāng)要寫入的數(shù)據(jù)量大于PIPE_BUF時(shí),linux將不再保證寫入的原子性。
原子性:要么做,要么不做,沒有所謂的中間狀態(tài)。
POSIX.1-2001要求PIPE_BUF至少為512字節(jié)。(在Linux上,PIPE_BUF為4096字節(jié)。)
拓展:
? ? ? ? 討論原子性,需要在多執(zhí)行流下,數(shù)據(jù)出現(xiàn)并發(fā)訪問的時(shí)候,討論原子性才有意義。(此處不深入)
融會(huì)貫通的理解:
? ? ? ? 匿名管道就是一個(gè)文件,一個(gè)內(nèi)存級(jí)別的文件,并不會(huì)在磁盤上存儲(chǔ),并不會(huì)有自身的文件名。作為基礎(chǔ)間通訊的方式是:看見同一個(gè)文件 -- 通過父子進(jìn)程父子繼承的方式看見。
? ? ? ? 是一個(gè),只有通過具有 “血緣關(guān)系” 的進(jìn)程進(jìn)行使用,可以稱做:父子進(jìn)程通訊。
命名管道
前言
? ? ? ? 匿名管道只能使用于具有“親緣關(guān)系”的進(jìn)程之間通信,而對(duì)于毫無關(guān)系的兩個(gè)進(jìn)程無法使用匿名管道通訊,如果我們想在不相關(guān)的進(jìn)程之間交換數(shù)據(jù),可以使用FIFO文件來做這項(xiàng)工作,它經(jīng)常被稱為命名管道。命名管道是一種特殊類型的文件。
原理
? ? ? ? 當(dāng)兩個(gè)進(jìn)程需要同時(shí)帶開一個(gè)文件的時(shí)候,由于為了保證進(jìn)程的獨(dú)立性,所以兩個(gè)進(jìn)程會(huì)有各自的files_struct,而對(duì)于文件數(shù)據(jù),并不會(huì)為每一個(gè)進(jìn)程都備一份(是內(nèi)存的浪費(fèi)),此時(shí)A進(jìn)程的files_struct與B進(jìn)程的files_struct是不同的,但是其中的文件符fd指向的是由磁盤文件加載到內(nèi)存中的同一份數(shù)據(jù)空間。
? ? ? ? 命名管道就是如此,其原理與匿名管道很相識(shí)。命名管道在磁盤中,所以其有自己的文件名、屬性信息、路徑位置……,但是其沒有文件內(nèi)容。即,命名管道是內(nèi)存文件,其在磁盤中的本質(zhì)是命名管道在磁盤中的映像,且映像的大小永遠(yuǎn)為0。意義就是為了讓毫無關(guān)系的基進(jìn)程,皆能夠調(diào)用到命名管道。而管道中的數(shù)據(jù)是進(jìn)程通訊時(shí)的臨時(shí)數(shù)據(jù),無存儲(chǔ)的意義,所以命名管道在磁盤中為空。
創(chuàng)建一個(gè)命名管道
- 命名管道可以從命令行上創(chuàng)建:
命令:mkfifo fifo
創(chuàng)建一個(gè)名為fifo命名管道
此時(shí)文件類型不是常用 - 與 d ,而是 p ,此文件的類型為管道:
?? ? ? ? 此時(shí)會(huì)發(fā)現(xiàn)處于等待狀態(tài)。因?yàn)?/span>由于我們寫了,但是對(duì)方還沒有打開,于是處于阻塞狀態(tài)。
? ? ? ? 此時(shí) echo "hello name_pipe"(進(jìn)程A)就是寫入的進(jìn)程, cat(進(jìn)程B)就是讀取的進(jìn)程。這就是所謂的一個(gè)進(jìn)程向另一個(gè)進(jìn)程寫入消息的過程(通過管道寫入的方式)。
? ? ? ? 我們可以在命令行上使用循環(huán)的方式,往管道內(nèi)每隔1s寫入數(shù)據(jù)。即,進(jìn)程A原來應(yīng)向顯示器文件寫入的數(shù)據(jù),通過輸入重定向的方式,將數(shù)據(jù)寫入管道中,再將管道中數(shù)據(jù)通過輸出重定向,通過進(jìn)程B將數(shù)據(jù)寫入到顯示器文件中。如此,以毫無相關(guān)的進(jìn)程A與進(jìn)程B通過命名管道進(jìn)行數(shù)據(jù)傳輸 -?進(jìn)程間通信。
? ? ? ? 此時(shí)我們通過終止讀取進(jìn)程方,導(dǎo)致寫入端向管道寫入的數(shù)據(jù)無意義了(無讀取端),此時(shí)作為寫入端的進(jìn)程就應(yīng)該被操作系統(tǒng)殺掉。此時(shí)需要注意,echo是內(nèi)置命令,所以是bush本身自行執(zhí)行的命令,所以此時(shí)殺掉寫入端的進(jìn)程無疑就是殺掉bush。于是bush被操作系統(tǒng)殺死,云服務(wù)器即退出。
內(nèi)置命令:讓父進(jìn)程(myshell)自己執(zhí)行的命令,叫做內(nèi)置命令,內(nèi)建命令。
- 命名管道可以從程序里創(chuàng)建:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
參數(shù):
? ? pathname:創(chuàng)建的命名管道文件。
- 路徑的方式給出。(會(huì)在對(duì)應(yīng)路徑下創(chuàng)建)
- 文件名的方式給出。(默認(rèn)當(dāng)前路徑下創(chuàng)建)
????mode:創(chuàng)建命名管道文件的默認(rèn)權(quán)限。
- 我們創(chuàng)建的文件權(quán)限會(huì)被umask(文件默認(rèn)掩碼)進(jìn)行影響,umask的默認(rèn)值:0002,而實(shí)際創(chuàng)建出來文件的權(quán)限為:mode&(~umask)。于是導(dǎo)致我們創(chuàng)建的權(quán)限未隨我們的想法,如:0666 -> 0664。需要我們利用umask函數(shù)更改默認(rèn)。
- umask(0); //將默認(rèn)值設(shè)為 0000
返回值:????????
????????命名管道創(chuàng)建成功,返回0。
????????命名管道創(chuàng)建失敗,返回-1。
用命名管道實(shí)現(xiàn)myServer&myClient通信
? ? ? ? 利用命名管道,實(shí)現(xiàn)服務(wù)端myServer與客戶端myClient之間進(jìn)行通訊。將服務(wù)端myServer運(yùn)行起來并用mkfifo函數(shù)開辟一個(gè)命名管道。而客戶端myClient中利用open打開命名管道(命名管道本質(zhì)為文件),以write向管道中輸入數(shù)據(jù)。以此服務(wù)端myServer利用open打開命名管道,以read從管道中讀取數(shù)據(jù)。
comm.hpp
? ? ? ? 所展開的頭文件集合。
#ifndef _COMM_H_ #define _COMM_H_#include <iostream> #include <string> #include <cstring> #include <cstdlib> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/wait.h>std::string ipcPath = "./fifo.ipc";#endifLog.hpp
? ? ? ? 編程的日志:就是當(dāng)前程序運(yùn)行的狀態(tài)。
#ifndef _LOG_H_ #define _LOG_H_#include <iostream> #include <ctime>#define Debug 0 #define Notice 1 #define Warning 2 #define Error 3std::string msg[] = {"Debug","Notice","Warning","Error" }std::ostream &Log(std::string message, int level) {std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message; }#endifmyServer.cc?
細(xì)節(jié):
? ? ? ? mkfifo的第二個(gè)參數(shù)傳入權(quán)限0666之前需要以u(píng)mask(0),對(duì)于服務(wù)端因?yàn)橹恍枰诿艿乐凶x取數(shù)據(jù),所以以只讀的方式(O_RDONLY)open管道文件,后序以fork開辟子進(jìn)程,讓子進(jìn)程read讀取即可,同時(shí)也需要注意,C語言的字符串結(jié)尾必須是 '\0'(讀取大小:sizeof(buffer) - 1)。
? ? ? ? 由于我們讓子進(jìn)程執(zhí)行讀取工作,所以需要以waitpid等在子進(jìn)程(此處我們讓nums個(gè)子進(jìn)程進(jìn)行,所以waitpid的第一個(gè)參數(shù)為 -1 ,等待任意一個(gè)子進(jìn)程)。
? ? ? ? 由于open打開了管道類型的文件,所以需要以close(fd)關(guān)閉文件,由于mkfifo開辟了管道,所以需要以u(píng)nlink刪除管道文件。
#include "comm.hpp"// 管道文件創(chuàng)建權(quán)限(umask == 0) #define MODE 0x0666// 讀取數(shù)據(jù)大小 #define READ_SIZE 64// 從管道文件讀取數(shù)據(jù) static void getMessage(int fd) {char buffer[READ_SIZE];while(true){memset(buffer, '\0', sizeof(buffer));ssize_t s = read(fd, buffer, sizeof(buffer) - 1); // C語言字符串需要保證結(jié)尾為'\0'if(s > 0){std::cout <<"[" << getpid() << "] "<< "myClient say> " << buffer << std::endl;}else if(s == 0){// 寫端關(guān)閉 - 讀到文件結(jié)尾std::cerr <<"[" << getpid() << "] " << "read end of file, clien quit, server quit too!" << std::endl;}else{// 讀取錯(cuò)誤perror("read");exit(3);}} }int main() {//1. 創(chuàng)建管道文件umask(0);if(mkfifo(ipcPath.c_str(), MODE) < 0){perror("mkfifo");exit(1);}#ifdef DEBUGLog("創(chuàng)建管道文件成功", Debug) << " step 1 " << std::endl;#endif//2. 正常的文件操作int fd = open(ipcPath.c_str(), O_RDONLY);if(fd < 0){perror("open");exit(2);}#ifdef DEBUGLog("打開管道文件成功", Debug) << " step 2 " << std::endl;#endifint nums = 3;// 創(chuàng)建3個(gè)子進(jìn)程for(int i = 0; i < nums; ++i){pid_t id = fork();if(fd == 0){// 子進(jìn)程 - 讀取管道數(shù)據(jù)getMessage(fd);exit(1);}}// 父進(jìn)程 - 等待子進(jìn)程for(int i = 0; i < nums; i++){waitpid(-1, nullptr, 0);}// 4. 關(guān)閉管道文件close(fd);#ifdef DEBUGLog("關(guān)閉管道文件成功", Debug) << " step 3 " << std::endl;#endifunlink(ipcPath.c_str()); // 通信完畢,就刪除管道文件#ifdef DEBUGLog("刪除管道文件成功", Debug) << " step 4 " << std::endl;#endifreturn 0; }myClient.cc
細(xì)節(jié):
? ? ? ? 對(duì)于客戶端因?yàn)橹恍枰诿艿乐袑懭霐?shù)據(jù),所以以只寫的方式(O_WRONLY)open管道文件,后序write即可。
#include "comm.hpp"int main() {//1. 獲取管道文件 - 以寫的方式打開命名管道文件int fd = open(ipcPath.c_str(), O_WRONLY);if(fd < 0){perror("open");exit(1);}//2. ipc過程std::string buffer; //用戶級(jí)緩沖區(qū)while(true){std::cout << "Please Enter Message Line :> ";std::getline(std::cin, buffer);write(fd, buffer.c_str(), buffer.size());}//3. 通信完畢,關(guān)閉命名管道文件close(fd);return 0; }????????由于命名管道的創(chuàng)建是在服務(wù)端myServer中,所以需要先運(yùn)行myServer。
? ? ? ? 服務(wù)端myServer進(jìn)程運(yùn)行起來,我們就能看到創(chuàng)建的命名管道文件。此時(shí)服務(wù)端myServer處于阻塞狀態(tài)也是管道文件的特性(寫入端未開辟,讀取端需要等待寫入端開辟)。
? ? ? ? 可以通過 ps 命令查看進(jìn)程是否相關(guān):
? ? ? ? 從此可以看出myServer與myClient是毫無相關(guān)的進(jìn)程,即myServer的三個(gè)子進(jìn)程與myClient也是毫無相關(guān)的進(jìn)程。
匿名管道與命名管道的區(qū)別
- 匿名管道由pipe函數(shù)創(chuàng)建并打開。
- 命名管道由mkfififo函數(shù)創(chuàng)建,打開用open
- FIFO(命名管道)與pipe(匿名管道)之間唯一的區(qū)別在它們創(chuàng)建與打開的方式不同,一但這些工作完成之后,它們具有相同的語義。
命名管道的打開規(guī)則
- 如果當(dāng)前打開操作是為讀而打開FIFO時(shí)
- O_NONBLOCK disable:阻塞直到有相應(yīng)進(jìn)程為寫而打開該FIFO
- O_NONBLOCK enable:立刻返回成功
- 如果當(dāng)前打開操作是為寫而打開FIFO時(shí)
- O_NONBLOCK disable:阻塞直到有相應(yīng)進(jìn)程為讀而打開該FIFO
- O_NONBLOCK enable:立刻返回失敗,錯(cuò)誤碼為ENXIO
system V共享內(nèi)存
????????system V共享內(nèi)存是與管道不同的,管道基于操作系統(tǒng)已有的文件操作。文件部分,無論有沒有通訊的需求,這個(gè)文件都需要維護(hù),有沒有通訊都需要和指定進(jìn)程建立關(guān)聯(lián),通不通訊都會(huì)有。
????????而共享內(nèi)存是,不用來通訊,操作系統(tǒng)就不用進(jìn)行管理,只有需要使用時(shí),操作系統(tǒng)才提供 - 有通訊才會(huì)有,共享內(nèi)存。共享內(nèi)存是操作系統(tǒng)單獨(dú)設(shè)立的內(nèi)核模塊,專門為進(jìn)程間通訊設(shè)計(jì)?--? 這個(gè)內(nèi)核模塊就是system V。
? ? ? ? 即:前面的匿名管道、命名管道通訊是恰好使用文件方案可以實(shí)現(xiàn)。而共享內(nèi)存是操作系統(tǒng)專門為了通訊設(shè)計(jì)。
共享內(nèi)存的建立:
- 共享區(qū):共享內(nèi)存、內(nèi)存映射和共享庫保存位置。
共享內(nèi)存數(shù)據(jù)結(jié)構(gòu)
? ? ? ? 共享內(nèi)存的提供者,是操作系統(tǒng)。
? ? ? ? 大量的進(jìn)程進(jìn)行通訊 -> 共享內(nèi)存是大量的。所以,操作系統(tǒng)對(duì)于共享內(nèi)存需要進(jìn)行管理,需要管理 -> 先描述,再組織 -> 重新理解:共享內(nèi)存 = 共享內(nèi)存塊 + 對(duì)應(yīng)的共享內(nèi)存的內(nèi)核數(shù)據(jù)結(jié)構(gòu)。
共享內(nèi)存的數(shù)據(jù)結(jié)構(gòu)?shmid_ds 在?/usr/include/linux/shm.h 中定義:
(cat命令即可)
struct shmid_ds
{
????????struct ipc_perm?? ??? ?shm_perm;?? ?/* operation perms */
????????int?? ??? ??? ?shm_segsz;?? ?/* size of segment (bytes) */
????????__kernel_time_t?? ??? ?shm_atime;?? ?/* last attach time */
????????__kernel_time_t?? ??? ?shm_dtime;?? ?/* last detach time */
????????__kernel_time_t?? ??? ?shm_ctime;?? ?/* last change time */
????????__kernel_ipc_pid_t?? ?shm_cpid;?? ?/* pid of creator */
????????__kernel_ipc_pid_t?? ?shm_lpid;?? ?/* pid of last operator */
????????unsigned short?? ??? ?shm_nattch;?? ?/* no. of current attaches */
????????unsigned short ?? ??? ?shm_unused;?? ?/* compatibility */
????????void ?? ??? ??? ?*shm_unused2;?? ?/* ditto - used by DIPC */
????????void?? ??? ??? ?*shm_unused3;?? ?/* unused */
};
????????此處首先提一下key值(后面共享內(nèi)存的建立引入),其是在上面的共享內(nèi)存的第一個(gè)參數(shù)struct ipc_perm類型的shm_perm變量中的一個(gè)變量。
在?/usr/include/linux/ipc.h?中定義:
struct ipc_perm
{
????????__kernel_key_t?? ?key;
????????__kernel_uid_t?? ?uid;
????????__kernel_gid_t?? ?gid;
????????__kernel_uid_t?? ?cuid;
????????__kernel_gid_t?? ?cgid;
????????__kernel_mode_t?? ?mode;?
????????unsigned short?? ?seq;
};
共享內(nèi)存的創(chuàng)建
#include <sys/ipc.h> #include <sys/shm.h> // 用來創(chuàng)建共享內(nèi)存 int shmget(key_t key, size_t size, int shmflg);參數(shù):
????????key:這個(gè)共享內(nèi)存段名字。
????????size:共享內(nèi)存大小。
- 大小建議為4096的整數(shù)倍。(原因使用時(shí)講解)
????????shmflg:由九個(gè)權(quán)限標(biāo)志構(gòu)成,它們的用法和創(chuàng)建文件時(shí)使用的mode模式標(biāo)志是一樣的。
| IPC_CREAT | 創(chuàng)建共享內(nèi)存,如果底層已經(jīng)存在,獲取之,并且返回。如果底層不存在,創(chuàng)建之,并且返回。 |
| IPC_EXCL | 沒有意義 |
| IPC_CREAT | IPC_EXCL | 創(chuàng)建共享內(nèi)存,如果底層不存在,創(chuàng)建之,并且返回。如果底層存在,出錯(cuò)返回。 |
IPC_CREAT | IPC_EXCL意義:可以保證,放回成功一定是一個(gè)全新的共享內(nèi)存(shm)。
此外創(chuàng)建需要權(quán)限的初始化:
????????如:IPC_CREAT | IPC_EXCL | 0666
返回值:
????????成功返回一個(gè)非負(fù)整數(shù),即該共享內(nèi)存段的標(biāo)識(shí)碼(用戶層標(biāo)識(shí)符);失敗返回-1。
key概念引入
????????進(jìn)程間通訊,首先需要保證的看見同一份資源。
融會(huì)貫通的理解:
- 匿名管道:通過pipe函數(shù)開辟內(nèi)存級(jí)管道 -- 本質(zhì)是文件 -- 通過pipe函數(shù)的參數(shù)(文件符fd)--?看見同一份資源。
- 命名管道:通過mkfifo函數(shù)根據(jù)路徑開辟管道文件(可以從權(quán)限p看出)-- 本質(zhì)是開辟一個(gè)文件(可以從第二個(gè)參數(shù)需要初始化權(quán)限看出)-- 利用open、write、read、close文件級(jí)操作?--?看見同一份資源。
????????管道 -- 內(nèi)存級(jí)文件 -- 恰巧利用文件操作。前面已有所提system V共享內(nèi)存,是操作系統(tǒng)為進(jìn)程間通訊專門設(shè)計(jì)?,并無法利用類似于管道利用文件實(shí)現(xiàn)。于是便有了key。
key概念解析
? ? ? ? key其實(shí)就是一個(gè)整數(shù),是一個(gè)利用算法實(shí)現(xiàn)的整數(shù)。我們可以將key想象為一把鑰匙,而共享內(nèi)存為一把鎖。
? ? ? ? 更像是同心鎖和一對(duì)對(duì)情侶,情侶拿著同樣的鑰匙只可解一堆鎖中的一把鎖。
? ? ? ? 如同一把鑰匙會(huì)按照固定的形狀制造。其會(huì)使用同樣的算法規(guī)則形成一個(gè)唯一值key,同時(shí)再創(chuàng)建共享內(nèi)存時(shí),會(huì)將key值設(shè)置進(jìn)其中,此時(shí)兩個(gè)毫無關(guān)系的進(jìn)程,就可以通過key值用共享內(nèi)存進(jìn)行通訊(一方創(chuàng)建共享內(nèi)存,一方獲取共享內(nèi)存)。
制造唯一值key的算法:
#include <sys/types.h> #include <sys/ipc.h>key_t ftok(const char *pathname, int proj_id);????????其不進(jìn)行任何系統(tǒng)調(diào)用,其內(nèi)部是一套算法,該算法就是將兩個(gè)參數(shù)合起來,形成一個(gè)唯一值就可以,數(shù)值是幾不重要。(對(duì)于第一個(gè)參數(shù),ftok是拿帶文件的inode標(biāo)號(hào),所以路徑可以隨意寫,但必須保證具體訪問權(quán)限),proj_id(項(xiàng)目id),隨意寫即可,一般是0~255之間,可以隨便寫,因?yàn)槌似湟矔?huì)直接截?cái)唷?/p>
返回值:
? ? ? ? 成功后,返回生成的key_t值。失敗時(shí)返回-1。
note:
- 終究就是個(gè)簡易的算法,所以key值可能會(huì)產(chǎn)生沖突,于是可以對(duì)傳入ftok函數(shù)的參數(shù)進(jìn)行修改。
- 需要保證需要通訊的進(jìn)程使用的?pathname?與?proj_id 相同,如此才能保證生成的是同一個(gè)key值。
簡易的使用shmget函數(shù)結(jié)合ftok函數(shù):
????????其不進(jìn)行任何系統(tǒng)調(diào)用,其內(nèi)部是一套算法,該算法就是將兩個(gè)參數(shù)合起來,形成一個(gè)唯一值就可以,數(shù)值是幾不重要。(對(duì)于第一個(gè)參數(shù),ftok是拿帶文件的inode標(biāo)號(hào),路徑可以隨意寫,但必須保證具體訪問權(quán)限)
? ? ? ? 兩個(gè)進(jìn)程要通訊,就要保證兩個(gè)看見統(tǒng)一個(gè)共享內(nèi)存,本質(zhì)上:保證兩個(gè)看到同一個(gè)key。
? ? ? ? 與文件不同,文件是打開了,最后進(jìn)程退出,文件沒有進(jìn)程與其關(guān)聯(lián),文件就會(huì)自動(dòng)釋放。
? ? ? ? 操作系統(tǒng)為了維護(hù)共享內(nèi)存,就需要先描述,再組織。所以,共享內(nèi)存在內(nèi)核里,處理共享內(nèi)存的存儲(chǔ)內(nèi)存空間,也需要存儲(chǔ)對(duì)其描述信息的數(shù)據(jù)結(jié)構(gòu)。所以,為了設(shè)置或獲取其的屬性,就通過第三個(gè)參數(shù)。(當(dāng)只需要?jiǎng)h除的時(shí)候,第三個(gè)參數(shù)設(shè)為nullptr即可)
? ? ? ? 操作系統(tǒng)管理物理內(nèi)存的時(shí)候,頁得大小是以4KB為單位。也就是4096byte,如果我們用4097byte,就多這1byte,操作系統(tǒng)就會(huì)在底層,直接創(chuàng)建4096 * 2byte的空間,此時(shí)多余的4095byte并不會(huì)使用,就浪費(fèi)了。
? ? ? ? 此處,我們以4097byte申請(qǐng),操作系統(tǒng)開辟了4096 * 2byte,但是查詢下是4097byte,因?yàn)?#xff0c;操作系統(tǒng)分配了空間,但是并不代表對(duì)所有都有權(quán)利訪問,我們要的是4097byte,那操作系統(tǒng)只會(huì)給對(duì)應(yīng)的權(quán)限。所以建議配4096byte的整數(shù)倍。
????????prems:權(quán)限。此處為0 ,代表任何一個(gè)人,包括我們,都沒有權(quán)力讀寫共享內(nèi)存,此時(shí)創(chuàng)建共性內(nèi)存也就沒了意義。于是我們需要再加一個(gè)選項(xiàng),設(shè)置權(quán)限。
? ? ? ? nattch:n標(biāo)識(shí)個(gè)數(shù),attch表示關(guān)聯(lián)。表示有多少個(gè)進(jìn)程與該共享內(nèi)存關(guān)聯(lián)。
? ? ? ? 需要將指定的共享內(nèi)存,掛接到自己的進(jìn)程的地址空間。
參數(shù):
·? ? ? ? 范圍值,共享內(nèi)存的起始地址。
文件描述符,文件有其對(duì)應(yīng)的文件指針,可用戶從來不會(huì)用文件指針,用的全是文件描述符,它們都可以用來標(biāo)定一個(gè)文件。同樣的道理shmid與key,它們都可以用來標(biāo)定共享內(nèi)存的唯一性。(key:標(biāo)定共享內(nèi)存在系統(tǒng)級(jí)別上的唯一性。shmid:標(biāo)定共享內(nèi)存的用戶級(jí)別上的唯一性。)所以我們?cè)谟玫臅r(shí)候全部都是shmid。只要是指令編寫的時(shí)候,就是在用戶層次的,所以ipcs等用的是shmid。
????????system V IPC資源,生命周期隨內(nèi)核,與之相對(duì)的是生命周期隨進(jìn)程。即,操作系統(tǒng)會(huì)一直保存這個(gè)資源,除非用戶用手動(dòng)命令刪除,否則用代碼刪除。
????????共享內(nèi)存由操作系統(tǒng)提供,并對(duì)其進(jìn)行管理(先描述,再組織) -> 共享內(nèi)存 = 共享內(nèi)存塊 + 對(duì)應(yīng)的共享內(nèi)存的內(nèi)核數(shù)據(jù)結(jié)構(gòu)。
融會(huì)貫通的理解:
????????一個(gè)內(nèi)存為4G的地址空間,0~3G屬于用戶,3~4G屬于內(nèi)核。所謂的操作系統(tǒng)在進(jìn)行調(diào)度的時(shí)候,執(zhí)行系統(tǒng)調(diào)用接口、庫函數(shù)。本質(zhì)上都是要將代碼映射到地址空間當(dāng)中,所以我們的代碼無論是執(zhí)行動(dòng)態(tài)庫,還是執(zhí)行操作系統(tǒng)的代碼。都是在其地址空間中完成的。所以對(duì)于任何進(jìn)程,3~4G都是操作系統(tǒng)的代碼和數(shù)據(jù),所以無論進(jìn)程如何千變?nèi)f化,操作系統(tǒng)永遠(yuǎn)都能被找到。
? ? ? ? 堆棧之間的共享區(qū):是用戶空間,該空間拿到了,無需經(jīng)過系統(tǒng)調(diào)用便可直接訪問。 -- 共享內(nèi)存,是不用經(jīng)過系統(tǒng)調(diào)用,直接可以進(jìn)行訪問!雙方進(jìn)程如果要通訊,直接進(jìn)行內(nèi)存級(jí)的讀和寫即。
融會(huì)貫通的理解:
????????前面所說的匿名管道(pipe)、命名管道(fifo)。都需要通過read、write(IO系統(tǒng)調(diào)用)來進(jìn)行通訊。因?yàn)檫@兩個(gè)屬于文件,而文件是在內(nèi)核當(dāng)中的特定數(shù)據(jù)結(jié)構(gòu),所以其是操作系統(tǒng)維護(hù)的 -- 其是在3~4G的操作系統(tǒng)空間范圍中。(無權(quán)訪問,必須使用系統(tǒng)接口)
?共享內(nèi)存在被創(chuàng)建號(hào)之后,默認(rèn)被清成全0,所以打印字符是空串。
????????共享內(nèi)存就是天然的為了讓我們可以快速訪問的機(jī)制,所以其內(nèi)部沒有提供任何的控制策略。(共享內(nèi)存中有數(shù)據(jù)讀端讀,沒數(shù)據(jù)讀端也讀。甚至客戶端(寫入端)不在了,其也讀。)更直接的說:寫入端和讀取端根本不知道對(duì)方的存在。
? ? ? ? 缺乏控制策略 -- 會(huì)帶來并發(fā)的問題。
拓展:
并發(fā)的問題,如:
????????客戶端想讓一個(gè)進(jìn)程處理一個(gè)完整的數(shù)據(jù)內(nèi)容,然而客戶端在未完全寫入共享內(nèi)存時(shí),讀取方就將不完整的數(shù)據(jù)讀取并處理,此時(shí)處理結(jié)果為未定義。 --? 數(shù)據(jù)不一致問題
基于共享內(nèi)存理解信號(hào)量
根據(jù)前面的學(xué)習(xí):
- 匿名管道通過派生子進(jìn)程的方式看見同一份資源。
- 命名管道通過路徑的方式看見同一份資源。
- 共享內(nèi)存通過key值得方式看見同一份資源。
????????所以,為了讓進(jìn)程間通訊?-> 讓不同的進(jìn)程之間,看見同一份資源 -> 本質(zhì):讓不同的進(jìn)程看見同一份資源。
? ? ? ? 通過前面得到學(xué)習(xí)我們會(huì)發(fā)現(xiàn),如共享進(jìn)程,其并沒有訪問控制,即:獨(dú)斷讀取的時(shí)機(jī)是不確定的,這也就帶來了一些時(shí)序問題 ——?照成數(shù)據(jù)的不一致問題。
引入兩個(gè)概念:
? ? ? ? 所以,多個(gè)進(jìn)程(執(zhí)行流),互相運(yùn)行的時(shí)候互相干擾,主要是我們不加以保護(hù)的訪問了相同的資源(臨界資源),在非臨界區(qū)多個(gè)進(jìn)程(執(zhí)行流)互相是不干擾的。
? ? ? ? 而為了更好的進(jìn)行臨界資源的保護(hù),可以讓多個(gè)進(jìn)程(執(zhí)行流)在任何時(shí)刻,都只能有一個(gè)進(jìn)程進(jìn)入臨界區(qū) ——? 互斥?。
互斥的理解:
? ? ? ? 我們可以將,一個(gè)執(zhí)行流:人,臨界區(qū):電影院(一個(gè)位置的電影院)。
? ? ? ? 看電影一定要有位置(電影院中的唯一位子)。當(dāng)前一個(gè)人在其中看電影,那么其他人必須等待他看完才可進(jìn)入觀看。并且電影院中,此唯一的位置是并不屬于觀影人的,而是買票,只要買了票,即在你進(jìn)去看完電影之前,就擁有了這個(gè)位置。買票:就是對(duì)座位的?預(yù)定?機(jī)制。
? ? ? ? 同樣的道理,進(jìn)程想進(jìn)入臨界資源,訪問臨界資源,不能讓進(jìn)程直接去使用臨界資源(不能讓用戶直接去電影院內(nèi)部占資源),需要先申請(qǐng)票 ——? 信號(hào)量 。
? ? ? ? 信號(hào)量 的存在是等于一張票。"票"的意義是互斥,而互斥的本質(zhì)是串形化,互斥就是一個(gè)在跑另一個(gè)就不能跑,需要等待跑完才能跑。其必須串形的去執(zhí)行。但是一旦串形的去執(zhí)行,多并發(fā)的效率就差了。所以:
? ? ? ? 當(dāng)有一份公共資源,只要有多個(gè)執(zhí)行流訪問的是這個(gè)公共資源的不同區(qū)域,這個(gè)時(shí)候可以允許多個(gè)執(zhí)行流同時(shí)進(jìn)入臨界區(qū)。這個(gè)時(shí)候可以根據(jù)區(qū)域的數(shù)量(如同電影院座位的個(gè)數(shù) -> 允許觀影的人數(shù)),可以讓對(duì)應(yīng)的進(jìn)程個(gè)數(shù)并發(fā)的執(zhí)行自己臨界區(qū)的代碼(看電影的自行觀影)。
? ? ? ? 信號(hào)量本質(zhì)上:就是一個(gè)計(jì)數(shù)器,類似于int count = n(n張票)。
申請(qǐng)信號(hào)量:
????????只要申請(qǐng)信號(hào)量成功 ……只要申請(qǐng)成功,一定在臨界區(qū)中有一個(gè)資源對(duì)應(yīng)提供的。
????????換句話說:首先,我們要進(jìn)行訪問信號(hào)量計(jì)數(shù)器,要每一個(gè)線程訪問計(jì)數(shù)器,必須保證信號(hào)量本身的 --操作 以及 ++操作 是原子的。否者很難保護(hù)臨界資源。其次,信號(hào)量需要是公共的,能被所有進(jìn)程能看到的資源,叫做臨界資源 —— 而信號(hào)量計(jì)數(shù)器存在的意義就是保護(hù)臨界資源,但是其有又成了臨界資源,所以其必須保證自己是安全的,才能保證臨界資源的安全。
#:如果用一個(gè)整數(shù),表示信號(hào)量。假設(shè)讓多個(gè)進(jìn)程(整數(shù)n在共享內(nèi)存里),看見同一個(gè)全局變量,都可以進(jìn)行申請(qǐng)信號(hào)量?—— 不可以的。
CPU執(zhí)行指令的時(shí)候:
復(fù)習(xí):
? ? ? ? 執(zhí)行流在執(zhí)行的時(shí)候,在任何時(shí)刻都可能被切換。
切換的本質(zhì):CPU內(nèi)的寄存器是只有一份的,但是寄存器需要存儲(chǔ)的臨時(shí)數(shù)據(jù)(上下文)是多份的,分別對(duì)應(yīng)不同的進(jìn)程!
? ? ? ? 我們知道,每一個(gè)進(jìn)程的上下文是不一樣的,寄存器只有一份,那么根據(jù)并發(fā),為下一個(gè)進(jìn)程讓出位置。并且由于,上下文數(shù)據(jù)絕而對(duì)不可以被拋棄!
? ? ? ? 當(dāng)進(jìn)程A暫時(shí)被切下來的時(shí)候,需要進(jìn)程A順便帶走直接的上下文數(shù)據(jù)!帶走暫時(shí)保存數(shù)據(jù)的是為了下一次回來的時(shí)候,能夠恢復(fù)上去,以此繼續(xù)按照之前的邏輯繼續(xù)向后運(yùn)行,就如同沒有中斷過一樣。
? ? ? ? 由于寄存器只有一套,被所有的執(zhí)行流共享,但是寄存器里面的數(shù)據(jù),屬于一個(gè)執(zhí)行流(屬于該執(zhí)行流的上下文數(shù)據(jù))。所以對(duì)應(yīng)的執(zhí)行流需要將上下文數(shù)據(jù)進(jìn)行保護(hù),方便與上下文數(shù)據(jù)恢復(fù)(重新回到CPU,更具上下文數(shù)據(jù)繼續(xù)執(zhí)行)。
????????當(dāng)myClient執(zhí)行的時(shí)候,重點(diǎn)在于n--,到n++,因?yàn)闀r(shí)序的問題,會(huì)導(dǎo)致n有中間狀態(tài)。切換為myServer執(zhí)行的時(shí)候,中間狀態(tài)會(huì)導(dǎo)致數(shù)據(jù)不一致。
? ? ? ? 即,CPU執(zhí)行myClient中的寫入數(shù)據(jù)到共享內(nèi)存時(shí),就被替換了:
(CUP執(zhí)行到n的中間狀態(tài))
(myClient被切換為myServer)
(myServer信號(hào)量執(zhí)行完了,并將n寫回)
(myCilent帶著自己的上下文數(shù)據(jù),并將n寫回)
????????此時(shí)1 -> 2,凸顯了信號(hào)量操作必須是原子性的,只有原子性才不會(huì)怕因時(shí)序,導(dǎo)致的數(shù)據(jù)不一致問題。
總結(jié):
- 申請(qǐng)信號(hào)量 -> 計(jì)數(shù)器-- -> P操作 -> 必須是原子的
- 申請(qǐng)信號(hào)量 -> 計(jì)數(shù)器++?-> V操作 -> 必須是原子的
總結(jié)
????????所以,由于信號(hào)量的思想,也是讓我們看見同一份資源,所以其本質(zhì)與上面的管道、共享內(nèi)存沒有太大的區(qū)別。所以,信號(hào)量被納入進(jìn)程間通訊的范疇。
? ? ? ? 信號(hào)量是為了保證特定的臨界資源不被受侵害,保證臨界資源數(shù)據(jù)一致性。前面所講:信號(hào)量也是一個(gè)臨界資源,所以首先其需要保證自己的安全性 ——?提出信號(hào)量操作需是原子性的。?
? ? ? ? 而信號(hào)量理論的提出是由于:臨界區(qū)、臨界資源的?互斥 ,當(dāng)多個(gè)執(zhí)行流(進(jìn)程)才會(huì)真正的凸顯出來,所以此處由于是進(jìn)程間通訊 —— 需要提出信號(hào)量,但作用凸顯在多線程 —— 多線程再深入講解信號(hào)量。
總結(jié)
以上是生活随笔為你收集整理的【Linux】-- 进程间通讯的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 经典技术书籍大全
- 下一篇: Linux实战之KVM虚拟机安装爱快软路