中國最好的網(wǎng)站器域名統(tǒng)一幫忙推廣的平臺
總言
??進程間通信:簡述進程間通信,介紹一些通信方式,管道通信(匿名、名命)、共享內(nèi)存等。
文章目錄
- 總言
- 1、進程間通信簡述
- 2、管道
- 2.1、簡介
- 2.2、匿名管道
- 2.2.1、匿名管道的原理
- 2.2.2、編碼理解:用fork來共享管道
- 2.2.2.1、準備工作一:創(chuàng)建一個管道pipe
- 2.2.2.2、準備工作二:創(chuàng)建子進程,對父子進程構建單向通信的信道
- 2.2.2.3、正式工作:父子進程間通信
- 2.2.2.4、結束工作:收尾處理(附整體代碼)
- 2.2.3、總結說明(匿名管道特點、讀寫原則)
- 2.2.4、擴展應用:進程池的模擬實現(xiàn)
- 2.2.4.1、processpool.c
- 2.2.4.2、Task.hpp
- 2.3、名命管道
- 2.3.1、是什么
- 2.3.2、相關使用演示
- 2.3.2.1、comm.hpp
- 2.3.2.2、log.hpp
- 2.3.2.3、server.cxx
- 2.3.2.4、client.cxx
- 2.3.2.5、server端加入子進程(多個讀端演示)
- 3、system V 共享內(nèi)存
- 3.1、基礎理論
- 3.2、實操環(huán)節(jié)
- 3.2.1、相關函數(shù)、指令匯總(通訊前后的工作)
- 3.2.1.1、shmget
- 3.2.1.2、ftok
- 3.2.1.3、ipcs、ipcrm
- 3.2.1.4、shmat、shmdt
- 3.2.1.5、shmclt
- 3.2.2、使用演示
- 3.2.2.1、基本說明
- 3.2.2.2、comm.hpp
- 3.2.2.3、log.hpp
- 3.2.2.4、shmserver.cxx
- 3.2.2.5、shmclient.cxx
- 3.3、總結補充
- 4、system V 信號量
- 4.1、理解層面
??
??
??
1、進程間通信簡述
??1)、為什么要進行進程間通信?
??以下為一些舉例:
??數(shù)據(jù)傳輸:一個進程需要將它的數(shù)據(jù)發(fā)送給另一個進程。
??資源共享:多個進程之間共享同樣的資源。
??通知事件:一個進程需要向另一個或一組進程發(fā)送消息,通知它(它們)發(fā)生了某種事件(如進程終止時要通知父進程)。
??進程控制:有些進程希望完全控制另一個進程的執(zhí)行(如Debug進程),此時控制進程希望能夠攔截另一個進程的所有陷入和異常,并能夠及時知道它的狀態(tài)改變。
??
??
??
??2)、要達成上述進程間通信,需要面臨哪些問題,為什么?
??說明:進程具有獨立性,要實現(xiàn)進程間通信,則需要讓通信雙方進程能夠看到同一份"資源",此處強調(diào)"共享"這一特性。
??
??
??
??3)、進程間通信發(fā)展簡述
??
??
??
??
2、管道
2.1、簡介
??1)、什么是管道
??管道是Unix中最古老的進程間通信的形式,我們把從一個進程連接到另一個進程的一個數(shù)據(jù)流稱為一個“管道”。
??
??特點:單向通信、傳送的都是資源(數(shù)據(jù)是計算機里最重要的資源)
??
??說明:由于進程具有獨立性,若讓進程來提供管道,其它進程是看不到該管道存在的。因此,計算機里管道的實現(xiàn),由操作系統(tǒng)提供,由進程來使用。
??
??
??
2.2、匿名管道
2.2.1、匿名管道的原理
??1)、問題引入:當進程打開一個文件后,創(chuàng)建一個子進程會發(fā)生什么?
??回答:對進程相關數(shù)據(jù)結構(*file
、files_struct
、file* fd_array
等),子進程會進行寫時拷貝;對被打開文件相關的內(nèi)核數(shù)據(jù)結構(文件信息所生成的文件結構file
),父子進程指向相同。由此誕生了共享文件,即管道。
??
??
??
??
??2)、如何實現(xiàn)一個管道?
??實際上,管道的底層原理就是通過文件實現(xiàn)的。相關步驟說明:
??1、分別以讀寫方式打開同一個文件
??2、fork()創(chuàng)建子進程(此時父子進程雖然彼此獨立,但它們共享同一個文件)
??3、雙方進程各自關閉自己不需要的文件描述符(假設父進程寫入、子進程讀取,則父進程關閉讀端,子進程關閉寫端。)
??
??PS:進程間通信屬于內(nèi)存級別的通信,大多以臨時數(shù)據(jù)為主,不需要寫入磁盤,雖然存在,但一般情況下沒必要,且影響傳送效率。
??
??
??
??
??
2.2.2、編碼理解:用fork來共享管道
2.2.2.1、準備工作一:創(chuàng)建一個管道pipe
??1)、相關函數(shù)介紹
??這里我們使用pipe
函數(shù),其作用是創(chuàng)建一個匿名管道。頭文件為<unistd.h>
。(man 2 pipe
查看具體信息。)
NAMEpipe, pipe2 - create pipeSYNOPSIS#include <unistd.h>int pipe(int pipefd[2]);
??
??說明:該函數(shù)用于獲取兩個輸出型參數(shù)(這里返回文件描述符數(shù)組)。pipefd
類似于基礎IO中學習的文件描述標識符,這里pipefd[0]
表示管道的讀端,pipefd[1]
表示管道的寫端。注意參數(shù)的返回類型是int
。
pipe() creates a pipe, a unidirectional data channel that can be used for interprocess communica‐tion. The array pipefd is used to return two file descriptors referring to the ends of the pipe.pipefd[0] refers to the read end of the pipe. pipefd[1] refers to the write end of the pipe. Datawritten to the write end of the pipe is buffered by the kernel until it is read from the read end ofthe pipe. For further details, see pipe(7).
??
??返回值:int
類型,若成功,則返回0
,若失敗則返回-1
。
RETURN VALUEOn success, zero is returned. On error, -1 is returned, and errno is set appropriately.
??
??
??2)、相關使用如下
//step1:創(chuàng)建一個管道int pipefd[2] = {0};//文件描述符數(shù)組,用于接受pipe讀端、寫端int ret = pipe(pipefd);assert(ret != -1);//用于檢測是否成功創(chuàng)建管道(void)ret;
??說明:
??1、pipe(pipefd)
:這里我們并未給出管道名稱,所以說pipe創(chuàng)建的是一個匿名管道。
??2、(void)ret
:assert只在debug模式下有效,為了避免release模式下,ret變量因無效(未使用)而報警,此處設置(void)ret
以證其使用性。
??
??
??
??3)、驗證:pipefd表示的含義
??
//驗證:pipefd[2]具體含義cout << "pipefd[0]" << pipefd[0] << endl;cout << "pipefd[1]" << pipefd[1] << endl;
mypipe.out:mypipe.ccg++ -o $@ $^ -std=c++11 -DDEBUG //debug模式
??
??
??
??
??
2.2.2.2、準備工作二:創(chuàng)建子進程,對父子進程構建單向通信的信道
??說明:上述步驟,我們只是創(chuàng)建出管道,未曾實際使用它。此處我們以fork的形式創(chuàng)建父子進程,讓其使用管道進行通信。根據(jù)之前描述的如何實現(xiàn)一個管道,相關示意圖如下:
??
??相關代碼如下:
//step2:創(chuàng)建子進程,對父子進程構建單向通信的信道pid_t id = fork();assert(id != -1);//fork失敗if(id = 1)//子進程:此處設子進程讀取{close(pipefd[1]);//對子進程,保留讀端,關閉寫端//相關讀取操作//……exit(0);}//父進程:此處設父進程寫入close(pipefd[0]);//對父進程,保留寫端,關閉讀端//相關寫入操作//……
??
??man 2 fork
:
SYNOPSIS#include <unistd.h>pid_t fork(void);RETURN VALUEOn success, the PID of the child process is returned in the parent, and 0 is returned in the child.On failure, -1 is returned in the parent, no child process is created, and errno is set appropri‐ately.
??
??
2.2.2.3、正式工作:父子進程間通信
??說明:在上述準備工作中,所創(chuàng)建的匿名管道能夠讓父子進程單向通信,只用在讀端、寫端分別編寫相關代碼操作即可。以下為一個演示案例(該部分內(nèi)容不固定,僅做演示理解):
??對子進程,讀取管道中的數(shù)據(jù):使用read
函數(shù)讀取管道文件,并將讀取到的數(shù)據(jù)存儲在re_buffer
中。
if(0 == id){close(pipefd[1]);//子進程保留讀端//……相關讀取操作char re_buffer[1024*8]={0};//子進程緩沖區(qū):用于接收讀端讀取到的數(shù)據(jù)while(true){//從管道中讀取數(shù)據(jù)存儲入緩沖區(qū)ssize_t re = read(pipefd[0], re_buffer, sizeof(re_buffer)-1);if(re > 0){re_buffer[re]= 0;cout << "【childpid-" << getpid() << ", get a message 】:" << re_buffer << endl;}else if(re == 0)//end of file,讀取到0,文件結尾。{cout << "child: end of file, quit." << endl;break;//退出循環(huán)}}exit(0);//終止子進程}
??
??man 2 read
:
??
??
??對父進程,寫入數(shù)據(jù):這里設置寫10次結束。
//父進程close(pipefd[0]);//父進程保留寫端//……相關寫入操作char wr_buffer[1024*8]={0};//寫端緩沖區(qū),用于存儲發(fā)送的數(shù)據(jù)string message = "父進程寫入數(shù)據(jù)";//提示文字1int count=0;//提示文字2,用于記錄寫入次數(shù)while(true){//向wr_buffer中寫入相關信息snprintf(wr_buffer,sizeof(wr_buffer),"fatherid-%d, %s, 第%d寫入.",getpid(),message.c_str(),1+count++);//將寫端緩沖區(qū)數(shù)據(jù)寫入管道文件中write(pipefd[1],wr_buffer,strlen(wr_buffer));sleep(1);//便于觀察輸出結果而設//結束寫端if(10 == count){cout<<"father quit!\n"<<endl;break;}}
??
??
??
2.2.2.4、結束工作:收尾處理(附整體代碼)
??結束后,需要將通信管道關閉。
//step3:執(zhí)行完畢close(pipefd[1]);pid_t ret2 = waitpid(id, nullptr, 0);printf("wait:%d, childpid:%d, count:%d\n",ret2,id,count);assert(ret2 > 0);(void)ret2;
??演示結果:
??
??
??
??整體代碼如下:
#include<iostream>
#include<unistd.h>
#include<assert.h>
#include<string>
#include<cstring>
#include<cstdio>
#include<sys/types.h>
#include<sys/wait.h>using namespace std;//演示以fork父子進程的方式創(chuàng)建匿名管道
int main()
{//step1:創(chuàng)建一個管道int pipefd[2] = {0};//文件描述符數(shù)組,用于接受pipe讀端、寫端int ret = pipe(pipefd);assert(ret != -1);//用于檢測是否成功創(chuàng)建管道(void)ret;//assert只在debug模式下有效,為了避免release模式下ret變量未被使用而報警設置。//驗證:pipefd[2]具體含義#ifdef DEBUGcout << "pipefd[0]" << pipefd[0] << endl;cout << "pipefd[1]" << pipefd[1] << endl;cout<<endl;#endif//step2:創(chuàng)建子進程,對父子進程構建單向通信的信道pid_t id = fork();assert(id != -1);//fork失敗if(0 == id)//子進程:此處設子進程讀取{close(pipefd[1]);//對子進程,保留讀端,關閉寫端//……相關讀取操作char re_buffer[1024*8]={0};//子進程緩沖區(qū):用于接收讀端讀取到的數(shù)據(jù)while(true){//從管道中讀取數(shù)據(jù)存儲入緩沖區(qū)ssize_t re = read(pipefd[0], re_buffer, sizeof(re_buffer)-1);if(re > 0){re_buffer[re]= 0;cout << "【childpid-" << getpid() << ", get a message 】:" << re_buffer << endl;}else if(re == 0)//end of file{cout << "child: end of file, quit." << endl;break;//退出循環(huán)}}exit(0);//終止子進程}//父進程:此處設父進程寫入close(pipefd[0]);//對父進程,保留寫端,關閉讀端//……相關寫入操作char wr_buffer[1024*8]={0};//寫端緩沖區(qū),用于存儲發(fā)送的數(shù)據(jù)string message = "父進程寫入數(shù)據(jù)";//提示文字1int count=0;//提示文字2while(true){//向wr_buffer中寫入相關信息,并將其顯示snprintf(wr_buffer,sizeof(wr_buffer),"fatherid-%d, %s, 第%d寫入.",getpid(),message.c_str(),1+count++);//將寫端緩沖區(qū)數(shù)據(jù)傳入管道write(pipefd[1],wr_buffer,strlen(wr_buffer));//結束寫端sleep(1);if(10 == count){cout<<"father quit!\n"<<endl;break;}}//step3:執(zhí)行完畢close(pipefd[1]);pid_t ret2 = waitpid(id, nullptr, 0);printf("wait:%d, childpid:%d, count:%d\n",ret2,id,count);assert(ret2 > 0);(void)ret2;return 0;
}
??
??
??
2.2.3、總結說明(匿名管道特點、讀寫原則)
??1)、管道特點
??1、匿名管道只能用于具有親緣關系的進程之間進行通信。通常運用于父子進程(一個管道由一個進程創(chuàng)建,然后該進程調(diào)用fork,此后父、子進程之間就可應用該管道。假如不斷在fork,那么就能實現(xiàn)父子、兄弟之間共用同一管道)。
??2、管道通過讓進程間協(xié)同,提供了訪問控制。(根據(jù)之前fork的學習,創(chuàng)建子進程后,父子進程執(zhí)行是沒有先后順序的,即缺乏訪問控制)。
??3、管道提供流式服務。(即面向字節(jié)流,后續(xù)需要通過訂制協(xié)議來進行區(qū)分)。
??4、管道是基于文件的,文件的生命周期隨進程,故管道的生命周期是隨進程的。(一般而言,進程退出,管道釋放)。
??5、管道是半雙工的,數(shù)據(jù)只能向一個方向流動。(需要雙方通信時,需要建立起兩個管道。)
??
??
??2)、管道讀寫原則
??兩種訪問方式和兩種退出方式:
??1、若寫端快,讀端慢,管道寫滿了就不再寫入;
??2、若寫端慢,讀端快,管道內(nèi)沒有數(shù)據(jù)時,讀端必須等待;
??3、若寫端關閉,讀端最終會讀取到0,標志到了文件結尾。
??4、若讀端關閉,OS會終止寫進行。
??
??
??
??
2.2.4、擴展應用:進程池的模擬實現(xiàn)
??上述匿名管道,可運用于創(chuàng)建進程池。如下圖,當有多個子進程時,父子之間建立起管道通信。當父進程發(fā)送指令(command code),可派遣任務讓隨機一個子進程執(zhí)行。
??
??
??PS:該部分相關代碼如下,可結合思維導圖理解。相關演示結果如下:
??
??
2.2.4.1、processpool.c
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include"task.hpp"#define PROCESS_NUM 5 //子進程數(shù)目
using namespace std;//子進程使用:用于等待接受父進程傳遞的任務編號指令,以便后續(xù)執(zhí)行
int waitCommand(int pipefd, bool& quit)
{//讀取:從對應的pipefd中讀取到相關的指令,將其返回。uint32_t command = 0;int ret = read(pipefd, &command, sizeof(command));if(ret==0)//讀取到文件末尾{quit=true;return -1;}assert(ret==sizeof(uint32_t));//用于檢測讀取到的指令是否合法return command;
}//父進程使用:用于委派任務給子進程
void sentAndWakeup(pid_t id, int pipefd, uint32_t command)
{//寫入:將對應的command傳遞到子進程對應的通信管道中write(pipefd,&command,sizeof(command));cout<< "【father process-" << getpid() <<"】: call child process-" << id << ", execute task-" << desc[command] << ", child pipefd-" << pipefd <<"\n" <<endl;
}int main()
{//1、準備工作Load();//加載任務vector<pair<pid_t,int>> slots;//用于記錄創(chuàng)建出的子進程,以及該進程對應的通信管道//2、創(chuàng)建子進程,建立單向通信管道for(int i = 0; i < PROCESS_NUM; ++i){//建立管道int pipefd[2];int n = pipe(pipefd);assert(n == 0);(void)n;//創(chuàng)建子進程pid_t id = fork();assert(id!=-1);if(0==id)//對子進程{close(pipefd[1]);//3、子進程開始工作while(true){// 等待父進程命令bool quit = false;int command = waitCommand(pipefd[0], quit);if(quit)//在管道中讀取到文件尾break;// 執(zhí)行對應的命令:此處對command做了檢測if(command >= 0 && command <handlerSize()){calltasks[command];}else{cout<<"command error: "<< command << endl;}}exit(1);//子進程結束,退出}//2、對父進程close(pipefd[0]);//對管道:關閉讀端slots.push_back({id,pipefd[1]});//對進程:記錄當前創(chuàng)建的子進程id及其通信管道fd}//3、父進程開始工作:派遣任務srand((unsigned long)time(nullptr)*getpid());//隨機數(shù)方式的負載均衡,這里為了盡量隨機可做一些處理while(true){int command = rand() % handlerSize();// 選擇一個任務:獲取任務編號int choosechild = rand() % slots.size();// 選擇一個子進程:獲取到子進程id,通信管道fdsentAndWakeup(slots[choosechild].first, slots[choosechild].second, command); // 將任務派遣給子進程:根據(jù)選擇的進程choosechild,將command任務編號傳入該進程對應的通信管道。sleep(1);}//4、父進程結束工作:收尾處理for(const auto &it : slots)//關閉所有通信管道{close(it.second);}for(const auto &it : slots)//回收所有的子進程信息{waitpid(it.first,nullptr,0);}return 0;
}
??
??
??
??
??
2.2.4.2、Task.hpp
#pragma once
#include<iostream>
#include<vector>
#include<unordered_map>
#include<functional>
#include<unistd.h>using func = std::function<void()>;
std::vector<func> calltasks;//任務表:存儲各函數(shù)接口std::unordered_map<int,std::string> desc;//任務編號及其文字描述//任務:(各函數(shù)實現(xiàn))
void readMySQL()
{std::cout << "【child process-" << getpid() << " 】 :執(zhí)行訪問數(shù)據(jù)庫的任務\n" << std::endl;
}void execuleUrl()
{std::cout << "【child process-" << getpid() << " 】 :執(zhí)行url解析\n" << std::endl;
}void cal()
{std::cout << "【child process-" << getpid() << " 】 :執(zhí)行加密任務\n" << std::endl;
}void save()
{std::cout << "【child process-" << getpid() << " 】 執(zhí)行數(shù)據(jù)持久化任務\n" << std::endl;
}//任務匯總
void Load()
{desc.insert({calltasks.size(),"readMySQL:讀取數(shù)據(jù)庫"});calltasks.push_back(readMySQL);desc.insert({calltasks.size(),"execuleUrl:進行url解析"});calltasks.push_back(execuleUrl);desc.insert({calltasks.size(),"cal:進行加密"});calltasks.push_back(cal);desc.insert({calltasks.size(),"saveL:進行數(shù)據(jù)持久化"});calltasks.push_back(save);}void showHandler()//用于展示目前所具有的任務信息及其編號
{for(auto &it: desc){std::cout << it.first << "\t" << it.second << std::endl;}
}int handlerSize()//用于獲取函數(shù)數(shù)組的大小,即任務總數(shù)
{return calltasks.size();
}
??
??
??
??
??
2.3、名命管道
2.3.1、是什么
??1)、問題引入:對于非血緣關系的進程,如何讓二者看到同一份資源?
??
??回答:在磁盤上建立名命管道文件(FIFO文件),可以被雙方進程打開,但不會將內(nèi)存數(shù)據(jù)刷新到磁盤上。(PS:命名管道是一種特殊類型的文件,其在磁盤中大小為0)。
??
??
??
??2)、如何創(chuàng)建一份名命管道文件?
??相關函數(shù)指令:mkfifo
,(man 3 mkfifo
可在Linux中查看)。
??
函數(shù)介紹
??頭文件和對應函數(shù)聲明:
NAMEmkfifo - make a FIFO special file (a named pipe)SYNOPSIS#include <sys/types.h>#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);
??具體介紹:
DESCRIPTIONmkfifo() makes a FIFO special file with name pathname. mode specifies the FIFO's permissions.It is modified by the process's umask in the usual way: the permissions of the created file are(mode & ~umask).A FIFO special file is similar to a pipe, except that it is created in a different way. Insteadof being an anonymous communications channel, a FIFO special file is entered into the file sys‐tem by calling mkfifo().Once you have created a FIFO special file in this way, any process can open it for reading orwriting, in the same way as an ordinary file. However, it has to be open at both ends simulta‐neously before you can proceed to do any input or output operations on it. Opening a FIFO forreading normally blocks until some other process opens the same FIFO for writing, and viceversa. See fifo(7) for nonblocking handling of FIFO special files.
??返回值:
RETURN VALUEOn success mkfifo() returns 0. In the case of an error, -1 is returned (in which case, errno isset appropriately).
??
??
??
使用演示
??mkfifo named_pipe
:mkfifo可在命令行上使用。
??
[wj@VM-4-3-centos T0917]$ mkfifo named_pipe
[wj@VM-4-3-centos T0917]$ ll
total 0
prw-rw-r-- 1 wj wj 0 Sep 16 20:48 named_pipe //可以看到其在磁盤上大小為0
[wj@VM-4-3-centos T0917]$ echo "hello" > named_pipe [wj@VM-4-3-centos T0917]$ cat < named_pipe
hello
??
??以腳本演示:while :; do echo "hello pipe"; sleep 1 ; done > named_pipe
??
??
??
??
??
2.3.2、相關使用演示
??整體實現(xiàn)框架如下:
??演示結果:
??
??
2.3.2.1、comm.hpp
#ifndef _COMM_H_
#define _COMM_H_#include<iostream>
#include<string>
#include<cstring>
#include<assert.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include "log.hpp"using namespace std;#define MODE 0666 //mkfifo中第二參數(shù)mode
#define SIZE 1024 //讀寫端緩沖區(qū)大小//命名管道路徑、名稱:此處創(chuàng)建在當前路徑下
string ipcPath="./fifo.ipc";#endif
??
??
??
??
2.3.2.2、log.hpp
#ifndef _LOG_H
#define _LOG_H#include<iostream>
#include<ctime>#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3const std::string msg[]={"Debug","Notice","Warning","Error"
};std::ostream &Log(std::string masseage, int label)
{std::cout << "[" << (unsigned long)time(nullptr) << "] :" << msg[label]<< ", "<< masseage << std::endl;return std::cout;
}#endif
??
??
??
??
2.3.2.3、server.cxx
#include "comm.hpp"static void getMessage(int fd)
{char buffer[SIZE]={0};//讀端緩沖區(qū)while(true){//void * memset ( void * ptr, int value, size_t num );memset(buffer,'\0',sizeof(buffer));//為了每次接受信息前,清屏處理上此遺留的無效數(shù)據(jù)int rd = read(fd, buffer, sizeof(buffer) - 1);if (rd > 0) // 讀取到數(shù)據(jù){cout << "masseage from client:> " << buffer << endl;}else if (rd == 0) // 讀取到文件尾end of file{cout << "end of the file, quit!" << endl;break;}else{perror("read"); // errorbreak;}}
}int main()//server:讀取端,用于接受client發(fā)送的通信
{//1、創(chuàng)建管道文件int ret = mkfifo(ipcPath.c_str(), MODE);assert(0 == ret);(void)ret;Log("server:創(chuàng)建管道文件",Debug) << endl;//2、正常文件操作:接受通信、顯示通信//2.1、打開名命管道int fd = open(ipcPath.c_str(), O_RDONLY);if(fd < 0){perror("open");exit(2);//處理assert,上述mkfifo也可以用perror檢測}Log("server:打開名命管道",Debug)<< endl;//2.2、進行通信getMessage(fd);//2.3、通信結束,關閉文件close(fd);Log("server:通信結束,關閉文件",Debug)<< endl;//3、刪除管道文件unlink(ipcPath.c_str());Log("server:刪除管道文件",Debug)<< endl;return 0;
}
??
??
??
??
2.3.2.4、client.cxx
#include "comm.hpp"int main()//client:寫入端,用于發(fā)送通信到管道中
{//1、獲取管道文件int fd = open(ipcPath.c_str(), O_WRONLY);if(fd < 0){perror("open");exit(1);}Log("client: 獲取管道文件",Debug)<< endl;//2、ipc通信過程string buffer;while(true){cout<< "client sent massage:> ";std::getline(std::cin, buffer);//獲取數(shù)據(jù)到緩沖區(qū)write(fd,buffer.c_str(),buffer.size());//將緩沖區(qū)數(shù)據(jù)寫入通信管道中}//3、關閉管道文件close(fd);Log("client: 關閉管道文件",Debug)<< endl;return 0;
}
??
??
??
2.3.2.5、server端加入子進程(多個讀端演示)
??相關演示結果:
??
??只需要在server端做出小改動就行:
static void getMessage(int fd)
{char buffer[SIZE]={0};//讀端緩沖區(qū)while(true){//void * memset ( void * ptr, int value, size_t num );memset(buffer,'\0',sizeof(buffer));//為了每次接受信息前,清屏處理上此遺留的無效數(shù)據(jù)int rd = read(fd, buffer, sizeof(buffer) - 1);if (rd > 0) // 讀取到數(shù)據(jù){cout <<"child: " << getpid() <<" | masseage from client:> " << buffer << endl;}else if (rd == 0) // 讀取到文件尾end of file{cout << "end of the file, quit!" << endl;break;}else{perror("read"); // errorbreak;}}
}
// //2.2、進行通信// getMessage(fd);//2.2、寫法二:創(chuàng)建子進程,進行多項讀端通信int nums=3;//進程數(shù)目for(int i = 0; i < nums; ++i){int id = fork();assert(id >= 0);if(id == 0)//子進程:執(zhí)行讀取任務{getMessage(fd);exit(1);//子進程結束,退出進程}}for(int i = 0; i < nums; ++i)//父進程:等待子進程結束{waitpid(-1,nullptr,0);}
??
??
??
??
??
??
??
3、system V 共享內(nèi)存
3.1、基礎理論
??1)、共享內(nèi)存的原理簡述
??問題1、共享內(nèi)存是誰提供的?
??回答:操作系統(tǒng)。共享內(nèi)存是OS專門為了進程通信而設定的,進程只是使用它,而非擁有者。
??
??
??問題2、操作系統(tǒng)要不要管理共享內(nèi)存?
??回答:需要。實際上共享內(nèi)存=共享內(nèi)存塊+對應共享內(nèi)存的內(nèi)核數(shù)據(jù)結構。(PS:對于管道文件,OS也要管理,只不過其包含在文件管理中,不需要單獨拎出處理)。
??
??
??
3.2、實操環(huán)節(jié)
3.2.1、相關函數(shù)、指令匯總(通訊前后的工作)
3.2.1.1、shmget
??1)、基礎介紹
??shmget
功能:用來創(chuàng)建共享內(nèi)存。查看方式:man shmget
。
NAMEshmget - allocates a System V shared memory segmentSYNOPSIS#include <sys/ipc.h>#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);
??參數(shù)介紹:
??key
:key值用于保證通信雙方看到的都是同一個共享內(nèi)存。通信雙方(client&server)能夠匹配同一個key值,則能看到同一個共享內(nèi)存。其在OS中具有唯一性,其數(shù)值本身不重要。
??使用方法
:使用同樣的算法規(guī)則,使得通信雙方獲取同一個key值(唯一)。比如:ftok
函數(shù)(下述介紹)。
??
??size
:共享內(nèi)存大小。
??size = 4096/4097
:共享內(nèi)存的大小最好是頁(PAGE,4096,4kb)的整數(shù)倍。(PS:4097會創(chuàng)建兩個頁,但OS實際只給4097個bit)
??
??
??shmflg
:由九個權限標志構成,它們的用法和創(chuàng)建文件時使用的mode
模式標志是一樣的。
The value shmflg is composed of:IPC_CREAT to create a new segment. If this flag is not used, then shmget() will find the segment asso‐ciated with key and check to see if the user has permission to access the segment.IPC_EXCL used with IPC_CREAT to ensure failure if the segment already exists.
??
??返回值: 若創(chuàng)建成功,shmget的返回值為共享內(nèi)存的用戶層標識符,類似fd文件標識符。
RETURN VALUEOn success, a valid shared memory identifier is returned. On errir, -1 is returned, and errno is set toindicate the error.
??相關使用舉例見下述ipcs、ipcrm
小節(jié)。
??
??
??
??
3.2.1.2、ftok
??1)、基礎介紹
??功能:用于創(chuàng)建共享內(nèi)存時,形成相同的key值。查看方式,man ftok
。
NAMEftok - convert a pathname and a project identifier to a System V IPC keySYNOPSIS#include <sys/types.h>#include <sys/ipc.h>key_t ftok(const char *pathname, int proj_id);
??
DESCRIPTIONThe ftok() function uses the identity of the file named by the given pathname (which must refer to anexisting, accessible file) and the least significant 8 bits of proj_id (which must be nonzero) to generatea key_t type System V IPC key, suitable for use with msgget(2), semget(2), or shmget(2).The resulting value is the same for all pathnames that name the same file, when the same value of proj_idis used. The value returned should be different when the (simultaneously existing) files or the projectIDs differ.
??
??返回值:
RETURN VALUEOn success, the generated key_t value is returned. On failure -1 is returned, with errno indicating theerror as for the stat(2) system call.
??
??
??2)、使用演示
??
??相關代碼:
[wj@VM-4-3-centos shm]$ ls
comm.hpp log.hpp makefile shmclient.cxx shmserver.cxx
[wj@VM-4-3-centos shm]$ make
g++ -o shmclient.out shmclient.cxx -std=c++11
g++ -o shmserver.out shmserver.cxx -std=c++11[wj@VM-4-3-centos shm]$ ls
comm.hpp log.hpp makefile shmclient.cxx shmclient.out shmserver.cxx shmserver.out[wj@VM-4-3-centos shm]$ ./shmserver.out
[1694951640] :Debug, server:ftok successserver key: 1140916349[wj@VM-4-3-centos shm]$ ./shmclient.out
[1694951646] :Debug, client:ftok successclinet key: 1140916349
[wj@VM-4-3-centos shm]$
??
??
??
??
3.2.1.3、ipcs、ipcrm
??1)、基礎介紹
??ipcs
:
NAMEipcs - provide information on IPC facilitiesDESCRIPTIONipcs provides information on the inter-process communication facilities for which the calling process hasread access.
??ipcs -m
:可用于查看共享內(nèi)存。
-m, --shmemsWrite information about active shared memory segments.
??
??
??ipcrm
:
NAMEipcrm - remove a message queue, semaphore set or shared memory idSYNOPSISipcrm [options]
??ipcrm -m shmid
:刪除共享內(nèi)存(注意此處需要填shmid值)
OPTIONS-M, --shmem-key shmkeyremoves the shared memorysegment created with shmkey after the last detach is performed.-m, --shmem-id shmidremoves the shared memory segment identified by shmid after the last detach is performed.
??
??
??
??2)、使用演示
??int shmget(key_t key, size_t size, int shmflg);
shmlg帶上權限時:
??使用ipcs
查看時各項含義簡介:
??若使用shmget,參數(shù)shmlg中不加權限,結果如下:
??
??
??
??
3.2.1.4、shmat、shmdt
??1)、基礎介紹
??man shmat
、man shmdt
,可查看相關信息:
??shmat
將共享內(nèi)存段連接到進程地址空間;shmdt
:將共享內(nèi)存段與當前進程脫離。
NAMEshmat, shmdt - System V shared memory operationsSYNOPSIS#include <sys/types.h>#include <sys/shm.h>void *shmat(int shmid, const void *shmaddr, int shmflg);int shmdt(const void *shmaddr);
??相關參數(shù):
?? shmid
:共享內(nèi)存標識
?? shmaddr
:指定連接的虛擬地址(一般設置為nullptr即可,由操作系統(tǒng)自行配置)
?? shmflg
:它的兩個可能取值是SHM_RND和SHM_RDONLY
??
??返回值: 對shmat,成功返回一個指針,指向共享內(nèi)存第一個節(jié);失敗返回-1。對shmdt,成功返回0,失敗返回-1。
RETURN VALUEOn success shmat() returns the address of the attached shared memory segment; on error (void *) -1 isreturned, and errno is set to indicate the cause of the error.On success shmdt() returns 0; on error -1 is returned, and errno is set to indicate the cause of theerror.
??相關使用演示見下述shmclt.
??
??
??
??
??
3.2.1.5、shmclt
??1)、基礎介紹
??man shmclt
,可查看相關信息:
NAMEshmctl - System V shared memory controlSYNOPSIS#include <sys/ipc.h>#include <sys/shm.h>int shmctl(int shmid, int cmd, struct shmid_ds *buf);DESCRIPTIONshmctl() performs the control operation specified by cmd on the System V shared memory segment whose iden‐tifier is given in shmid.The buf argument is a pointer to a shmid_ds structure,
??相關參數(shù):
??cmd
:該參數(shù)可針對shmid對應的共享內(nèi)存進行各項選項操作(下述演示中只使用到IPC_RMID
:即便是有進程和當下的shm掛接,依舊刪除共享內(nèi)存。)
??buf
:為共享內(nèi)存屬性而準備,若不使用可設置為空nullptr
。若想自行設置或做其它處理,可使用到該參數(shù)。
??
??返回值: 成功返回0;失敗返回-1。
Other operations return 0 on success.On error, -1 is returned, and errno is set appropriately.
??
??2)、使用演示
??用于觀察的腳本:while :; do ipcs -m; sleep 1; done
??
??
??
??
3.2.2、使用演示
3.2.2.1、基本說明
??此部分整體介紹框架如下:
??
??須知:
??1、共享空間創(chuàng)建在虛擬地址中的共享區(qū),該部分屬于用戶空間,不用經(jīng)過系統(tǒng)調(diào)用就可以直接訪問。即:通訊進程雙方可以直接進行內(nèi)核級別的讀寫。
??2、若創(chuàng)建共享內(nèi)存后,寫端沒有寫入任何消息數(shù)據(jù),不妨礙讀端讀取數(shù)據(jù)(讀取為空,共享內(nèi)存被創(chuàng)建后默認清除所有數(shù)據(jù))。
??
??使用時注意事項:
??1、使用共享內(nèi)存,通信雙方只要一方直接向共享內(nèi)存中寫入數(shù)據(jù),另一方就可以立馬看到對方寫入的數(shù)據(jù)。
??2、共享內(nèi)存缺乏訪問控制!會帶來并發(fā)問題。(如:寫端數(shù)據(jù)信息尚未完全錄入,讀端就把數(shù)據(jù)讀取出去。由于數(shù)據(jù)的不完整性,導致該數(shù)據(jù)無效或錯誤。)
??
??
??PS:實際進程間通信部分可以根據(jù)自己的需求設置,這里主要演示使用共享內(nèi)存通訊時如何讓其一定程度上得到訪問控制。以下為整體代碼展示。
??
??
??
3.2.2.2、comm.hpp
#pragma once#include<iostream>
#include<string>
#include<cstring>
#include<assert.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/stat.h>
#include<fcntl.h>
#include "log.hpp"using namespace std;#define SHM_SIZE 4096 //shmget參數(shù):定義shm的大小,最好是頁(PAGE,4096,4kb)的整數(shù)倍
#define PROJ_ID 0x44//ftok參數(shù):值可隨意,the least significant 8 bits of proj_id (which must be nonzero)
#define PATH_NAME "/home/wj"//路徑正確即可。/
//用于讓共享內(nèi)存達到訪問控制的一些設置:借助管道實現(xiàn)#define FIFO_NAME "./fifo" //將名命管道創(chuàng)建在當前目錄下,名稱為fifo#define READ O_RDONLY //以讀的方式打開管道文件
#define WRITE O_WRONLY //以寫的方式打開管道文件//類:用于讓進程運行、關閉時,自動創(chuàng)建、銷毀管道文件
class Init
{
public:Init()//默認構造{umask(0);int fifoid = mkfifo(FIFO_NAME,0666);assert(fifoid != -1);(void)fifoid;Log("FIFO pipe created!", Notice);}~Init()//默認析構{unlink(FIFO_NAME);Log("remove FIFO pipe!", Notice);}
};//函數(shù):主要目的是為讀寫兩端以不同方式打開管道文件
int OpenFIFO(std::string pathname, int flags)
{int fd = open(pathname.c_str(),flags);assert(fd > 0);(void)fd;return fd;
}//函數(shù):等待。讓讀端處于等待中,直到從管道文件中接受到指令。
void Wait(int fd)//此處fd是管道文件
{uint32_t command = 0;//指令:對于輸入內(nèi)容不重要,只是為了控制ssize_t n = read(fd, &command, sizeof(uint32_t));assert(n == sizeof(uint32_t));(void)n;
}//函數(shù):喚醒。當寫端數(shù)據(jù)輸入完成后,輸入指令喚醒讀端。
void Signal(int fd)//此處fd是管道文件
{uint32_t command = 0;ssize_t n = write(fd, &command, sizeof(uint32_t));assert(n == sizeof(uint32_t));(void)n;
}//函數(shù):關閉管道文件.
void closeFIFO(int fd)
{close(fd);
}
/
??
??
??
??
3.2.2.3、log.hpp
#ifndef _LOG_H
#define _LOG_H#include<iostream>
#include<ctime>#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3const std::string msg[]={"Debug","Notice","Warning","Error"
};std::ostream &Log(std::string masseage, int label)
{std::cout << "[" << (unsigned long)time(nullptr) << "] :" << msg[label]<< ", "<< masseage << std::endl;return std::cout;
}#endif
??
??
??
??
3.2.2.4、shmserver.cxx
#include "comm.hpp"int main()
{// 1、創(chuàng)建公共的key值key_t k = ftok(PATH_NAME, PROJ_ID);if (k < 0){Log("client:ftok failure!", Error)<< " clinet key: " << k << endl;exit(1);}Log("client:ftok success", Debug) << " clinet key: " << k << endl;//2、獲取共享內(nèi)存int shmid = shmget(k, SHM_SIZE, IPC_CREAT);//若創(chuàng)建的共享內(nèi)存在底層存在,則獲取并返回if( shmid < 0){Log("client: get shm failure!", Error) << " the shmid:" << shmid << endl;exit(2);}Log("client: get shm success", Debug) << " the shmid:" << shmid << endl;//3、建立映射(關聯(lián))char* shmaddr = (char*)shmat(shmid, nullptr, 0);Log("client: attach shm success", Debug) << " the shmaddr: "<< shmaddr << endl;//4、進行通信:寫端,輸入數(shù)據(jù)//4.1、以寫的方式打開管道文件int fd = OpenFIFO(FIFO_NAME,WRITE);//4.2、通訊:將數(shù)據(jù)寫入共享內(nèi)存while(true){int n = read(0, shmaddr, SHM_SIZE-1);if( n > 0){shmaddr[n-1] = 0;//鍵盤讀取數(shù)據(jù)輸入共享內(nèi)存,最后一位為\n//4.3、寫端:向名命管道中發(fā)送指令(內(nèi)容隨機),告知讀端數(shù)據(jù)輸入完畢Signal(fd);Log("寫端:數(shù)據(jù)輸入完成:>", Notice);if(strcmp(shmaddr,"quit")==0) break; //用于結束寫端}}//4.3、完成通訊,關閉名命管道(見下述)//5、取消映射(去關聯(lián))int ret = shmdt(shmaddr);assert( ret != -1);Log("client: detach shm success", Debug) << " the ret: " << ret << endl;//6、server中做了處理,此處不用刪除共享內(nèi)存closeFIFO(fd);//關閉名命管道return 0;
}
??
??
??
??
3.2.2.5、shmclient.cxx
#include "comm.hpp"int main()
{// 1、創(chuàng)建公共的key值key_t k = ftok(PATH_NAME, PROJ_ID);if (k < 0){Log("client:ftok failure!", Error)<< " clinet key: " << k << endl;exit(1);}Log("client:ftok success", Debug) << " clinet key: " << k << endl;//2、獲取共享內(nèi)存int shmid = shmget(k, SHM_SIZE, IPC_CREAT);//若創(chuàng)建的共享內(nèi)存在底層存在,則獲取并返回if( shmid < 0){Log("client: get shm failure!", Error) << " the shmid:" << shmid << endl;exit(2);}Log("client: get shm success", Debug) << " the shmid:" << shmid << endl;//3、建立映射(關聯(lián))char* shmaddr = (char*)shmat(shmid, nullptr, 0);Log("client: attach shm success", Debug) << " the shmaddr: "<< shmaddr << endl;//4、進行通信:寫端,輸入數(shù)據(jù)//4.1、以寫的方式打開管道文件int fd = OpenFIFO(FIFO_NAME,WRITE);//4.2、通訊:將數(shù)據(jù)寫入共享內(nèi)存while(true){int n = read(0, shmaddr, SHM_SIZE-1);if( n > 0){shmaddr[n-1] = 0;//鍵盤讀取數(shù)據(jù)輸入共享內(nèi)存,最后一位為\n//4.3、寫端:向名命管道中發(fā)送指令(內(nèi)容隨機),告知讀端數(shù)據(jù)輸入完畢Signal(fd);Log("寫端:數(shù)據(jù)輸入完成:>", Notice);if(strcmp(shmaddr,"quit")==0) break; //用于結束寫端}}//4.3、完成通訊,關閉名命管道(見下述)//5、取消映射(去關聯(lián))int ret = shmdt(shmaddr);assert( ret != -1);Log("client: detach shm success", Debug) << " the ret: " << ret << endl;//6、server中做了處理,此處不用刪除共享內(nèi)存closeFIFO(fd);//關閉名命管道return 0;
}
??
??
??
??
3.3、總結補充
??
??1、我們把多個進程(執(zhí)行流)看到的同一份公共資源,稱之為臨界資源。
??2、進程中,用于訪問臨界資源的代碼稱之為臨界區(qū)。
??
??根據(jù)上述共享內(nèi)存學習,當多個執(zhí)行流不加保護地訪問相同的臨界資源時,會存在運行時互相干擾的情況,帶來時序問題。
??
??3、為了更好的進行臨界區(qū)的保護,可以讓多個執(zhí)行流在任何時刻,都只有一個進程進入臨界區(qū)。將這種操作稱之為互斥。
??
??4、原子性。
??
??
??
??
4、system V 信號量
4.1、理解層面
??
??為了合理保護和使用臨界資源,當進程想要訪問臨界資源時,需要先申請信號量。信號量本質(zhì)是一種計數(shù)器,當進程申請信號量成功,計數(shù)器 -1,臨界資源內(nèi)部會給進程預留下空間。進程執(zhí)行自己的臨界區(qū)代碼,訪問臨界資源,結束后釋放信號量,進而計數(shù)器+1。
??
??如果某一項操作只有一行匯編,該操作就是原子的。
?? 信號量計數(shù)器是對臨界資源的預定機制,要保證其本身也是原子的。那么申請信號量時,計數(shù)器--
(P操作),釋放信號量,計數(shù)器++
(V操作),二者都必須是原子的。
??
??
??問題:能否讓多個進程看到同一個全局變量,將其作為信號量?
??回答:不可以。根據(jù)之前所學,CPU在執(zhí)行進程時有調(diào)度,因執(zhí)行流及其上下文數(shù)據(jù)的切換,此時會造成該臨時變量具有時序問題。
??
??
??
??
??
??
??
??
??