前言:????????
? ? ? ? 上文我們講到了匿名管道【Linux系統】匿名管道以及進程池的簡單實現-CSDN博客
? ? ? ? 本文我們來講一講命名管道與共享內存
命名管道
????????上面我們講到,匿名管道只能用于有血緣關系(尤其父子)的進程進行通信!但如果我們想讓沒有關系的進程進行通信,該怎么辦呢?命名管道就是答案!
? ? ? ? 進程間通信的本質是讓不同的進程看到同一份資源!命名管道也是一樣!
1.命名管道原理
? ? ? ? 1.命名管道與匿名管道一樣,本質上都是文件!
? ? ? ? 2.命名管道不同與匿名管道,命名管道是有名字、有路徑的!
? ? ? ? 3.如下圖,創建命名管道只會返回一個fd。并且當多個進程打開同一個文件時,系統并不會將其加載多次。
? ? ? ? 4.命名管道同匿名管道一樣,其緩沖區不會刷新到磁盤中!
? ? ? ? 5.如何保證多個進程打開的是同一個命名管道?路徑!路徑是唯一的!
2.命名管道的特性
命名管道的特性與匿名管道基本一樣!唯一區別就是:命名管道可用于不相關進程間的通信!
5種特性:
命名管道,可用于不相關的進程通信 |
命名管道文件,自帶同步機制:包含5種通信情況! |
命名管道的面向字節流的 |
命名管道是單向通信的!(屬于半雙工的特殊情況。半雙工:任何時候一個發,一個收。全雙工:任何時候,可以同時收發) |
命名管道的生命周期是由管道文件是否被刪除決定的! |
5種通信情況:
只要有一方沒有打開管道文件,另一方就會阻塞在open處!直到都打開了管道文件,才會向下繼續執行! |
寫慢,讀快:讀端阻塞,等待寫端 |
寫快,讀慢:管道緩沖區寫滿了,就要阻塞等待讀端 |
寫關閉,讀繼續:一直讀取,知道讀到完,返回0,表示讀取到文件末尾 |
寫繼續,讀關閉:無意義操作!OS會自動殺掉寫端進程(通過信號:13 SIGPIPE殺掉) |
3.命名管道的接口
指令方面
//創建命名管道
mkfifo 管道名//刪除命名管道
rm 管道名
unlink 管道名
yc@hyc-alicloud:~$ mkfifo t1hyc@hyc-alicloud:~$ ls -l
total 8
prw-rw-r-- 1 hyc hyc 0 Aug 22 12:01 t1hyc@hyc-alicloud:~$ unlink t1
hyc@hyc-alicloud:~$ ls -l
total 8
? ? ? ? 我們可以看到,創建的管道文件第一個字母為:p!這代表管道文件!
代碼方面
創建命名管道:
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);pathname:FIFO 文件路徑(如 /tmp/myfifo),進程通過該路徑訪問管道。
mode:文件權限(如 0666 表示讀寫權限,需結合進程的 umask 計算實際權限)。
返回值:成功返回 0,失敗返回-1
打開命名管道:
#include <fcntl.h>
int open(const char *pathname, int flags);flags:打開模式,需指定 O_RDONLY(只讀,讀端)或 O_WRONLY(只寫,寫端)
返回值:成功返回fd,失敗返回-1
刪除命名管道:
#include <unistd.h>
int unlink(const char *pathname);FIFO文件被刪除后,已打開的進程仍可繼續使用對應資源,直到所有進程關閉文件描述符后,資源才徹底釋放
4.利用命名管道實現通信
? ? ? ? 值得一提的,命名管道的同步機制是:只要有一方沒有打開管道文件,另一方就會阻塞在open處!直到都打開了管道文件,才會向下繼續執行!
//comm.hpp#pragma once
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <iostream>
using namespace std;// 目標:實現client與service的通信#define EXIT(m) \do \{ \perror(m); \exit(EXIT_FAILURE); \} while (0)class NameFifo
{
public:NameFifo(string path, string name): _path(path), _name(name){_fd = -1;_PATH = _path + "/" + _name;}// 創建管道void Create(){int n = mkfifo(_PATH.c_str(), 0666);if (n < 0){EXIT("mkfifo");}cout << "命名管道創建成功!\n";}// 打開管道void OpenForRead(){_fd = open(_PATH.c_str(), O_RDONLY);if (_fd < 0){EXIT("open");}cout << "讀端打開成功!\n";}void OpenForWrite(){_fd = open(_PATH.c_str(), O_WRONLY);if (_fd < 0){EXIT("open");}cout << "寫端打開成功!\n";}// 讀取數據void Read(){char buffer[1024];int n = read(_fd, buffer, sizeof(buffer) - 1);buffer[n] = 0;printf("接收到數據:%s\n", buffer);}// 寫數據void Write(){string msg;cout << "請輸入內容:\n";cin >> msg;write(_fd, msg.c_str(), msg.size());}// 關閉管道void Close(){close(_fd);unlink(_PATH.c_str());cout << "管道:" << _fd << "關閉并刪除!\n";}private:string _path;string _name;string _PATH;int _fd;
};//service.cc#include "comm.hpp"int main()
{NameFifo nf(".", "myfifo");nf.Create();nf.OpenForWrite();nf.Write();nf.Close();
}//client.cc#include "comm.hpp"int main()
{//service已經創建了管道,這里不用再創建了!NameFifo nf(".", "myfifo");nf.OpenForRead();nf.Read();nf.Close();
}
system V共享內存
system V共享內存也是進程間通信的一重要方式!
1.system V
? ? ? ? system V是Linux系統中的一種標準。它規定了系統調用接口的設計,共享內存正是滿足了這一標準。
2.共享內存的原理
? ? ? ? 如圖,顧名思義共享內存就是將相同的內存空間,通過頁表映射到不同的進程中去,達到不同進程訪問同一個數據的效果(既IPC)
????????1.想要完成上面的操作系統通過操作系統提供的系統調用來實現!
? ? ? ? 2.取消內存與進程之間的映射關系,OS會自動的釋放共享內存
? ? ? ? 3.一個操作系統必然存在多個共享內存供給多個進程使用,所以OS一定會去管理共享內存,至于如何管理,我們后面說!
3.共享內存接口
創建or獲取共享內存:
#include <sys/ipc.h>
#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);key:共享內存的唯一標識(通過 ftok 函數生成)
size:共享內存段的大小(字節),創建新段時必須指定,獲取已有段時可設為 0shmflg:標志位(獲取)IPC_CREAT:若不存在則創建新段,若存在則打開這個共享內存,并返回(創建)IPC_EXCL:與 IPC_CREAT 配合使用(單獨使用沒有意義),若指定要創建的共享內存已經存在則返回錯誤,否則創建(想要給出權限:如0666)成功:返回共享內存段標識符(shmid,非負整數);
失敗:返回 -1,并設置 errno
#include <sys/types.h>
#include <sys/ipc.h>key_t ftok(const char *pathname, int proj_id);pathname:指向一個已存在的文件路徑的字符串,ftok 會使用該文件的 inode 編號 和 設備編號 作為生成鍵值proj_id:一個 8 位的非 0 整數用于區分同一文件對應的不同 IPC 對象,范圍為1~255成功:返回一個 key_t 類型的鍵值(非負整數)
失敗:返回 -1,并設置 errno 表示錯誤原因
讓物理內存地址與虛擬地址進行映射:
void *shmat(int shmid, const void *shmaddr, int shmflg);shmid:shmget返回的共享內存 ID
shmaddr:指定映射到進程地址空間的起始地址。通常設為NULL,由內核自動分配
shmflg:映射選項,如SHM_RDONLY(只讀映射,默認是讀寫)返回值:成功返回映射后的內存起始地址(void*),失敗返回(void*)-1(設置errno)
解除映射關系:
int shmdt(const void *shmaddr);參數:shmaddr為shmat返回的共享內存起始地址
返回值:成功返回0,失敗返回-1(設置errno)
刪除or查詢狀態:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);shmid:共享內存 IDcmd:控制命令,常用:
IPC_RMID:標記共享內存段為待刪除(所有進程分離后實際刪除)
IPC_STAT:獲取共享內存屬性,存儲到buf指向的struct shmid_ds結構中
IPC_SET:修改共享內存屬性(需進程有足夠權限)
buf:指向struct shmid_ds的指針(用于IPC_STAT/IPC_SET),IPC_RMID時可設為NULL返回值:成功返回0,失敗返回-1(設置errno)
4.共享內存特性
在上面的接口中,我們會發現共享內存中存在兩個標示符:唯一標識符key、內存段標識符shmid |
理解:
|
如何保證不同的進程訪問的是同一個內存呢?那當然是key了!只要對ftok傳入相同的函數,就可以得到相同的key,從而找到相同的內存段! |
共享內存的生命周期是隨內核的!如果不顯示的刪除,那么就算進程退出了,共享內存仍然存在! |
共享內存大小:必須是4KB(4096)的整數! |
同步機制:
不同于管道,共享內存本身是沒有同步機制的! |
共享內存屬于用戶空間,用戶可以直接訪問! 那其優點就是:速度快,映射之后可以直接看到資源,并可以直接讀取!沒有限制的 |
但缺點就是,沒有同步機制,這會導致數據不被保護! 比如:寫數據寫到一半,就被讀取走了!(管道調用系統調用,會被內核保護起來。而共享內存是沒有內核保護的) |
5.利用共享內存實現進程間通信
//Shm.hpp#pragma once
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string>
#include <iostream>
using namespace std;// 目標:利用共享內存,實現service和client的通信#define SIZE 4096
#define gmode 0666
#define EXIT(m) \{ \perror(m); \exit(EXIT_FAILURE); \}class Shm
{
public:Shm(string &pathname, int &projid){_key = ftok(pathname.c_str(), projid);}// 創建共享內存void Creat(){umask(0);_shmid = shmget(_key, SIZE, IPC_CREAT | IPC_EXCL | gmode);if (_shmid < 0){EXIT("shmget");}cout << "創建共享內存成功!\n";}// 獲取共享內存void Get(){_shmid = shmget(_key, SIZE, IPC_CREAT);if (_shmid < 0){EXIT("shmget");}cout << "獲取共享內存成功!\n";}// 映射共享內存至虛擬空間void Attach(){_start_mem = shmat(_shmid, NULL, 0);if ((long long)_start_mem < 0){EXIT("shmat");}cout << "映射成功!\n";}void Destroy(){UnAttach();int n = shmctl(_shmid, IPC_RMID, NULL);if (n < 0){EXIT("shmctl");}cout << "刪除共享內存成功!\n";}// 獲取開始地址void *Start(){return _start_mem;}private:// 解除映射void UnAttach(){int n = shmdt(_start_mem);if (n < 0){EXIT("shmdt");}cout << "解除映射成功!\n";}key_t _key;int _shmid;void *_start_mem;
};//service.cc#include "Shm.hpp"
#include <unistd.h>int main()
{string pathname = ".";int projid = 0x66;Shm shm(pathname, projid);shm.Creat();shm.Attach();// 寫入數據char *arr = (char *)shm.Start();for (int i = 'a'; i <= 'z'; i++){arr[i - 'a'] = i;sleep(1);}shm.Destroy();
}//client.cc#include "Shm.hpp"
#include <unistd.h>int main()
{string pathname = ".";int projid = 0x66;Shm shm(pathname, projid);shm.Get();shm.Attach();// 讀取數據while (1){printf("%s\n", (char *)shm.Start());sleep(1);}shm.Destroy();
}