名前付きパイプと SystemV 共有メモリ
名前付きパイプ
1. 名前付きパイプとは何ですか?
名前付きパイプは、FIFO (先入れ先出し) とも呼ばれる特別なタイプのファイルです。パイプを使用するのと同じようにプロセス間の通信が可能ですが、 ファイル システム内に存在する特殊なファイルであるという点が異なります。
名前付きパイプを使用すると、あるプロセスがパイプにデータを書き込み、別のプロセスがパイプから同じデータを読み取ることができます。これにより、プロセスがこの特別なファイルにアクセスできる限り、プロセスが同じコンピュータ上にあるかどうかに関係なく、プロセス間の通信が可能になります。
コマンドmkfifo
を使用して、Linux で名前付きパイプを作成できます。この特別なファイルは、ファイル システム内の他のファイルと同じように見えますが、プロセス間でデータを受け渡すように設計されています。
名前付きパイプを使用する場合、プロセス間通信を示す 2 つの単純なスクリプトを作成できます。次の例では、1 つのスクリプトが名前付きパイプにメッセージを書き込み、別のスクリプトがパイプからメッセージを読み取ります。
ステップ:
-
名前付きパイプを作成する
mkfifo my_pipe
-
スクリプト 1: 名前付きパイプへのデータの書き込み 名前付きパイプにメッセージを書き込むスクリプト
writer.sh
を作成します。#!/bin/bash PIPE=my_pipe echo "Sending message to the pipe..." echo "Hello from the writer script" > $PIPE
-
スクリプト 2: 名前付きパイプからのデータの読み取り 名前付きパイプからメッセージを読み取る別のスクリプトを作成します
reader.sh
。#!/bin/bash PIPE=my_pipe echo "Reading message from the pipe..." message=$(cat $PIPE) echo "Message received: $message"
-
スクリプトを実行する
- ターミナルで実行
./reader.sh
します。 - 別の端末で実行
./writer.sh
します。
- ターミナルで実行
こうすると、書き込みスクリプトwriter.sh
が名前付きパイプにメッセージを書き込み、読み取りスクリプトreader.sh
が名前付きパイプから読み取ることがわかります。というメッセージが表示されます。このプロセスは、名前付きパイプを介した 2 つのスクリプト間の単純な通信を示します。
Writer.sh を実行してパイプに書き込み、読み取りがない場合は最初にブロックします。
Reader.shを実行してパイプを読み込み、スクリプトコマンドを実行して送信された情報を出力します。
名前付きパイプに関しては、詳しく調べる価値のあるポイントがいくつかあります。
- ファイル システムに存在します:
- 名前付きパイプはファイル システム内にファイルとして存在し、パス名を持っています。これにより、複数のプロセスがこのパス名を通じて同じパイプにアクセスできるようになり、プロセス間通信が可能になります。他のプロセスはこのファイルを開いて、ファイルにデータを書き込んだり、ファイルからデータを読み取ることができます。
- 持久性:
- 名前付きパイプはファイル システムに残り、通信プロセスが終了した後もパイプ ファイルは残ります。明示的に削除しない限り、ファイル システムに残ります。これにより、長期にわたる無関係なプロセス間通信に適しています。
- 读写操作:
- 匿名パイプと同様に、名前付きパイプも先入れ先出し (FIFO) チャネルです。 1 つのプロセスがパイプにデータを書き込むことができ、別のプロセスがパイプから同じデータを読み取ることができます。この読み取りおよび書き込み操作はブロッキングです。つまり、使用可能なデータがない場合、読み取り操作はデータが使用可能になるまで待機します。
- 権限とアクセス制御:
- 他のファイルと同様、名前付きパイプはファイル システムのアクセス許可によって制御されます。これは、アクセス許可ビット マスク (
mode
パラメーター) を使用して、パイプ上で読み取り、書き込み、および操作を実行できるユーザーを設定できることを意味します。
- 他のファイルと同様、名前付きパイプはファイル システムのアクセス許可によって制御されます。これは、アクセス許可ビット マスク (
- 多用途性:
- 名前付きパイプはファイル システム内に存在し、そのパス名があるため、異なるプログラム間でデータを共有するのに役立ちます。これらのプロセスが同じパス名にアクセスできる限り、無関係なプロセスでも使用できます。
名前付きパイプはプログラムから作成することもできます。関連する関数は次のとおりです。
int mkfifo(const char *filename, mode_t mode);
filename
引数は、作成される名前付きパイプのパス名です。mode
パラメータは、ファイルのアクセス許可を設定するために使用されるモードです。これは、ファイルへのアクセス権を決定するためにchmod
コマンドで使用される許可ビットマスクに似ています。
この関数は、指定されたパスに名前付きパイプを作成します。戻り値は、作成が成功した場合は 0、失敗した場合は -1 です。適切なエラー コードが設定されます。errno
名前付きパイプと匿名パイプは、プロセス間通信の 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 3
const std::string msg[] = {
"Debug",
"Notice",
"Warning",
"Error"
};
std::ostream &Log(std::string message, int level)
{
std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;
return std::cout;
}
#endif
このヘッダー ファイルは、単純なログ機能Log
とログ レベル定数を定義します。
#ifndef _LOG_H_
と#define _LOG_H_
は、ヘッダー ファイルが繰り返し含まれることを避けるための保護マクロです。#include
必要な標準ライブラリのヘッダー ファイルを部分的にインポートします。Debug
、Notice
、Warning
、Error
はログ レベルの定数であり、さまざまなログ タイプを表します。msg[]
ログタイプの文字列を格納する配列です。Log()
この関数はメッセージ文字列とログ レベルを受け入れ、メッセージを標準出力に出力し、タイムスタンプ、ログ レベル、およびメッセージの内容を表示します。
通信.hpp
#ifndef _COMM_H_
#define _COMM_H_
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "Log.hpp"
using namespace std;
#define MODE 0666
#define SIZE 128
string ipcPath = "./fifo.ipc";
#endif
このファイルには、いくつかの定数とグローバル変数、および必要なヘッダー ファイルが含まれています。
#ifndef _COMM_H_
と#define _COMM_H_
はヘッダー ファイルの保護マクロです。#include
セクションでは、必要な標準ライブラリ ヘッダーと、Log.hpp
などのその他のカスタム ヘッダーについて説明します。using namespace std;
これは、C++ 標準ライブラリの関数とオブジェクトの使用を容易にするためです。MODE
とSIZE
はパイプとバッファのサイズの定数です。ipcPath
名前付きパイプへのパスです。
サーバー.cpp
#include "comm.hpp"
#include <sys/wait.h>
static void getMessage(int fd)
{
char buffer[SIZE];
while (true)
{
memset(buffer, '\0', sizeof(buffer));
ssize_t s = read(fd, buffer, sizeof(buffer) - 1);
if (s > 0)
{
cout <<"[" << getpid() << "] "<< "client say> " << buffer << endl;
}
else if (s == 0)
{
// end of file
cerr <<"[" << getpid() << "] " << "read end of file, clien quit, server quit too!" << endl;
break;
}
else
{
// read error
perror("read");
break;
}
}
}
int main()
{
// 1. 创建管道文件
if (mkfifo(ipcPath.c_str(), MODE) < 0)
{
perror("mkfifo");
exit(1);
}
Log("创建管道文件成功", Debug) << " step 1" << endl;
// 2. 正常的文件操作
int fd = open(ipcPath.c_str(), O_RDONLY);
if (fd < 0)
{
perror("open");
exit(2);
}
Log("打开管道文件成功", Debug) << " step 2" << endl;
int nums = 3;
for (int i = 0; i < nums; i++)
{
pid_t id = fork();
if (id == 0)
{
// 3. 编写正常的通信代码了
getMessage(fd);
exit(1);
}
}
for(int i = 0; i < nums; i++)
{
waitpid(-1, nullptr, 0);
}
// 4. 关闭文件
close(fd);
Log("关闭管道文件成功", Debug) << " step 3" << endl;
unlink(ipcPath.c_str()); // 通信完毕,就删除文件
Log("删除管道文件成功", Debug) << " step 4" << endl;
return 0;
}
このファイルには、名前付きパイプからメッセージを読み取るサーバー側のコードが含まれています。
- 在
main()
函数中:mkfifo()
を使用して名前付きパイプを作成します。open(ipcPath.c_str(), O_RDONLY)
読み取り操作のために名前付きパイプを開きます。fork()
を使用して複数の子プロセスを作成します。各子プロセスはgetMessage()
を呼び出してパイプからメッセージを読み取り、コンソールに表示します。- すべての子プロセスの実行が完了するまで待ってから、ファイル記述子を閉じます。
- 名前付きパイプ ファイルを削除するには
unlink()
を使用します。
getMessage()
関数:
- 継続的にループしてパイプ内のデータを読み取り、コンソールにデータを表示します。
client.cpp
#include "comm.hpp"
int main()
{
// 1. 获取管道文件
int fd = open(ipcPath.c_str(), O_WRONLY);
if(fd < 0)
{
perror("open");
exit(1);
}
// 2. ipc过程
string buffer;
while(true)
{
cout << "Please Enter Message Line :> ";
std::getline(std::cin, buffer);
write(fd, buffer.c_str(), buffer.size());
}
// 3. 关闭
close(fd);
return 0;
}
client.cpp
このファイルには、名前付きパイプにメッセージを書き込むクライアント コードが含まれています。
- 在
main()
函数中:open(ipcPath.c_str(), O_WRONLY)
書き込み用に名前付きパイプを開きます。std::getline()
を使用してユーザーが入力したメッセージを取得し、write()
を使用してメッセージをパイプに書き込みます。close(fd)
ファイル記述子を閉じます。
メイクファイルのコンパイル
.PHONY:all
all:client mutiServer
client:client.cpp
g++ -o $@ $^ -std=c++11
mutiServer:server.cpp
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f client mutiServer
コンパイルが完了したらターミナルを開き、2 つのターミナルで 2 つのプログラムをそれぞれ実行します: client
と mutiServer
先执行mutiServer
等待client
发送
別のターミナルで実行client
、パイプを開きます
client
エンドでメッセージを入力し、mutiServer
エンドでメッセージを受信します
プログラムはclient
側で終了し、パイプは閉じられて削除されます
システム V 共有メモリ
System V 共有メモリは、Linux システムのプロセス間でメモリを共有するためのメカニズムです。これは System V IPC (プロセス間通信) メカニズムの一部であり、セマフォやメッセージ キューとともに System V IPC を形成します。
特徴は次のとおりです。
- 共有メモリ セグメント: System V 共有メモリを使用すると、複数のプロセスが論理メモリの同じ部分にアクセスできます。これらのメモリ セグメントは一意の識別子によって識別されるため、複数のプロセスが同じ共有メモリ セグメントをアドレス空間にマップできるようになります。
- 効率: パイプやメッセージ キューなどの他の IPC メカニズムと比較して、共有メモリはより効率的です。カーネルを介してデータをコピーしたり中継したりせずに、プロセスが共有メモリを直接読み書きできるため、頻繁かつ大量のデータ交換が必要なシナリオで使用できます。
- 簡単な操作: System V 共有メモリは、
shmget
、shmat
、shmdt
やshmctl
などは、共有メモリ セグメントの作成、接続、切断、管理に使用されます。 - 明示的なクリーンアップが必要です: ファイルマップされたメモリとは異なり、System V 共有メモリはプロセス終了後に自動的にクリーンアップされません。したがって、プログラマは、リソース リークを防ぐために、適切に切断し、共有メモリ セグメントを削除するようにする必要があります。
共有メモリを使用すると、同じメモリ領域を共有することで、異なるプロセスがデータを交換できます。これは、高いパフォーマンスと頻繁なデータ交換を必要とするアプリケーション シナリオに適していますが、プログラマが明示的なメモリ管理と同期を実行する必要もあります。
1. 共有メモリの図
2. 共有メモリのデータ構造
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 */
};
共有メモリ セグメント情報を維持するために使用される System V 共有メモリの構造shmid_ds
。この構造には、共有メモリ セグメントに関する次のような複数の情報が含まれています。
shm_perm
: 操作権限などの共有メモリ セグメントの権限に関する情報を含むstruct ipc_perm
タイプの構造体です。shm_segsz
: 共有メモリセグメントのサイズをバイト単位で表します。shm_atime
、shm_dtime
、shm_ctime
: それぞれ、最後の取り付け、取り外し、および変更の時間を表します。shm_cpid
: は作成者プロセスの PID です。shm_lpid
: は、共有メモリを最後に操作したプロセスの PID です。shm_nattch
: この共有メモリ セグメントに現在接続されているプロセスの数を示します。- 其他未使用的字段,如
shm_unused
,shm_unused2
,shm_unused3
。
この構造は主に、この共有メモリ セグメントの作成者、最後にアクセスした人、サイズなど、共有メモリ セグメントのプロパティとステータスを追跡および管理するために使用されます。この情報は、共有メモリのライフサイクルとアクセス権を維持および制御するために重要です。
3. 共有メモリ機能
3.1 shmget関数
shmget
function は、Linux システムの System V IPC メカニズムで使用される System V 共有メモリ セグメントを作成または取得するために使用される関数です。
声明
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
パラメータ
key
: 共有メモリセグメントを一意に識別するために使用されるキー値。通常、ftok()
関数はキーの生成に使用されます。size
: 作成または取得する共有メモリセグメントのサイズをバイト単位で指定します。shmflg
: 許可フラグや動作オプションなどの追加操作を指定するために使用されるフラグ パラメーターです。複数のフラグを|
演算子と組み合わせて使用できます。
戻り値
- 成功した場合は、共有メモリ識別子 (
shmid
) と呼ばれる、共有メモリ セグメントを識別する識別子 (負ではない整数) を返します。 - 失敗した場合は -1 が返され、
errno
が失敗の具体的な理由を示すように設定されます。
動作原理
- 渡された
key
に対応する共有メモリ セグメントがすでに存在する場合、shmget
はその識別子 (shmid
)、新しい共有メモリ セグメントは作成されません。 - 受信した
key
に対応する共有メモリ セグメントがなく、shmflg
にIPC_CREAT
フラグが含まれている場合、< /span> a>shmget
は、新しい共有メモリ セグメントを作成し、その識別子を返します。 shmflg
パラメータには、共有メモリ セグメントの権限を決定する権限フラグIPC_CREAT | 0666
など、他のフラグを含めることができます。
この関数は、共有メモリ セグメントを作成または取得するための最初のステップです。正常に作成されたら、 shmat
を使用してプロセスのアドレス空間に接続し、読み取りおよび書き込み操作を実行する必要があります。
3.2 shmat関数
shmat
この関数は、呼び出しプロセスのアドレス空間に共有メモリを接続するために使用され、プロセスが共有メモリ セグメントにアクセスして操作できるようにします。
声明
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
パラメータ
shmid
:shmget
によって返される共有メモリ セグメントの識別子。shmaddr
: 共有メモリセグメント接続が予想されるアドレス。通常は NULL にして、オペレーティング システムに適切なアドレスを選択させます。shmflg
: フラグ パラメータ。共有メモリへの読み取り専用接続のSHM_RDONLY
など、いくつかの特定のオプションを含めることができます。
戻り値
- 成功した場合は、共有メモリ セグメントの開始アドレスを返します。失敗した場合は、
(void *) -1
を返し、errno
を設定して、その具体的な理由を示します。失敗。
動作原理
shmat
共有メモリセグメントを呼び出し側プロセスのアドレス空間に接続します。shmaddr
が NULL の場合、オペレーティング システムは、共有メモリを呼び出し側プロセスのアドレス空間に接続するための適切なアドレスを自動的に選択します。- 接続が成功すると、プロセスは返されたアドレス ポインタを使用して共有メモリの読み書きを行うことができます。
予防
- 共有メモリがプロセスのアドレス空間に接続されると、プロセスは共有メモリに対して直接読み書きできるようになります。したがって、共有メモリ内のデータは、データの破損や不整合を避けるために慎重に扱う必要があります。
- 共有メモリが必要なくなったら、
shmdt
関数を使用してプロセスのアドレス空間から共有メモリを切り離す必要があります。
3.3 shmdt関数
shmdt
この関数は、プロセスが共有メモリ セグメントにアクセスできないように、プロセスのアドレス空間から共有メモリを切り離すために使用されます。
声明
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
パラメータ
shmaddr
: 共有メモリセグメントの追加アドレスへのポインタ。
戻り値
- 成功した場合は 0 を返します。
- 失敗した場合は -1 が返され、
errno
が失敗の具体的な理由を示すように設定されます。
動作原理
shmdt
呼び出しプロセスのアドレス空間から共有メモリを切り離すために使用されますが、共有メモリ セグメントは削除されません。- プロセスがこの関数を使用してアドレス空間から共有メモリを切り離した後は、この共有メモリの内容にアクセスできなくなります。
予防
- 共有メモリを切り離した後も、共有メモリに接続されている他のプロセスは引き続き共有メモリにアクセスして操作できます。
- 共有メモリを切り離した後、通常、共有メモリは解放されませんが、共有メモリに対するプロセスのアクセス権が取り消されるだけです。共有メモリが不要になった場合は、通常、
shmctl
関数を使用して共有メモリ セグメントを削除する必要があります。
3.4 shmctl関数
shmctl
関数は、System V 共有メモリを制御するために使用される関数であり、共有メモリ セグメント上でさまざまな制御操作を実行するために使用できます。
声明
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
パラメータ
-
shmid
:shmget
によって返される共有メモリ セグメントの識別子。 -
cmd
: 実行する動作を指定するための制御コマンド。一般的に使用されるコマンドは次のとおりです。-
IPC_STAT
: 共有メモリのステータスを取得し、buf
パラメータが指す構造体に情報を書き込みます。 -
IPC_SET
: 共有メモリのステータスを設定します。buf
パラメータで提供される情報によって設定されます。 -
IPC_RMID
処置: 共有メモリセグメントをシステムから削除してください。
-
-
buf
:struct shmid_ds
構造体へのポインタ。共有メモリ セグメントに関する情報を保存または転送するために使用されます。
戻り値
- 成功すると、負ではない整数が返されます。具体的な戻り値は、実行したコマンドによって異なります。
- 失敗した場合は -1 が返され、
errno
が失敗の具体的な理由を示すように設定されます。
動作原理
shmctl
関数は、共有メモリセグメントに関連する制御操作を実行するために使用されます。- 共有メモリのステータスの取得、共有メモリのステータスの設定、共有メモリ セグメントの削除など、
cmd
パラメータを通じて実行する操作を指定します。
予防
IPC_RMID
コマンドを使用して、共有メモリ セグメントを削除します。削除後、共有メモリ セグメントに接続されているすべてのプロセスは共有メモリにアクセスできなくなりますが、システムはすべてのプロセスが切断されるまで共有メモリを再利用しません。
3.5フィートック関数
声明
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
パラメータ
pathname
: パス名を指す文字列で、通常は既存のファイル パスです。ftok
このパス名を使用してキーを生成します。proj_id
: は、一意のキーの生成に使用されるユーザー指定の整数です。
戻り値
- 成功すると、
pathname
とproj_id
によって生成された一意のキーが返されます。 - 失敗した場合は -1 が返され、
errno
が失敗の具体的な理由を示すように設定されます。
動作原理
ftok
は、pathname
とproj_id
に基づいてキーを作成します。- 2 つのファイル属性
st_dev
とst_ino
(stat
構造内のデバイス番号とノード番号) を使用します。proj_id
を使用して一意のキーを作成します。
予防
ftok
はファイル属性を使用してキー値を生成するため、ftok
に渡されるファイルは存在し、アクセス可能である必要があります。そうでない場合、生成されたキーは一意ではない可能性があります。または失敗しました。- 生成されたキー値は通常、共有メモリ、メッセージ キュー、セマフォなどの System V IPC 内のリソースを識別するために使用されます。
4. 共有メモリの例
4.1 通信.hpp
#pragma once
#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cassert>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "Log.hpp"
using namespace std;
#define PATH_NAME "/home/kingxzq"
#define PROJ_ID 0x66
#define SHM_SIZE 4096 //共享内存的大小,最好是页(PAGE: 4096)的整数倍
#define FIFO_NAME "./FIFO"
class Init
{
public:
Init()
{
umask(0);
int n = mkfifo(FIFO_NAME, 0666);
assert(n == 0);
(void)n;
Log("create fifo success",Notice) << "\n";
}
~Init()
{
unlink(FIFO_NAME);
Log("remove fifo success",Notice) << "\n";
}
};
#define READ O_RDONLY
#define WRITE O_WRONLY
int OpenFIFO(std::string pathname, int flags)
{
int fd = open(pathname.c_str(), flags);
assert(fd >= 0);
return fd;
}
void Wait(int fd)
{
Log("等待中....", Notice) << "\n";
uint32_t temp = 0;
ssize_t s = read(fd, &temp, sizeof(uint32_t));
assert(s == sizeof(uint32_t));
(void)s;
}
void Signal(int fd)
{
uint32_t temp = 1;
ssize_t s = write(fd, &temp, sizeof(uint32_t));
assert(s == sizeof(uint32_t));
(void)s;
Log("唤醒中....", Notice) << "\n";
}
void CloseFifo(int fd)
{
close(fd);
}
Init
このクラスには、名前付きパイプを作成および削除するためのメソッドが含まれています。名前付きパイプはコンストラクターで作成され、デストラクターで削除されます。OpenFIFO
関数は名前付きパイプを開き、ファイル記述子を返します。Wait
この関数はファイル記述子を待機し、シグナルを受信します。Signal
関数は、ファイル記述子にデータを書き込み、シグナルを送信するために使用されます。CloseFifo
ファイル記述子を閉じるために関数が使用されます。
4.2 Log.hpp
#ifndef _LOG_H_
#define _LOG_H_
#include <iostream>
#include <ctime>
#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3
const std::string msg[] = {
"Debug",
"Notice",
"Warning",
"Error"
};
std::ostream &Log(std::string message, int level)
{
std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;
return std::cout;
}
#endif
- いくつかのログ レベル (デバッグ、通知、警告、エラー) と、各レベルに対応する説明文字列が定義されています。
Log
この関数は、メッセージとメッセージ レベルを受信し、タイムスタンプとレベルを含むメッセージをコンソールに出力するために使用されます。- この関数は
std::ostream
オブジェクトを返すため、この関数を使用してstd::cout
と同じ方法でログを出力できます。
4.3 shmServer.cpp
#include "comm.hpp"
Init init;
string TransToHex(key_t k)
{
char buffer[32];
snprintf(buffer, sizeof buffer, "0x%x", k);
return buffer;
}
int main()
{
// 1. 创建公共的Key值
key_t k = ftok(PATH_NAME, PROJ_ID);
assert(k != -1);
Log("create key done", Debug) << " server key : " << TransToHex(k) << endl;
// 2. 创建共享内存
int shmid = shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666);
if (shmid == -1)
{
perror("shmget");
exit(1);
}
Log("create shm done", Debug) << " shmid : " << shmid << endl;
// 3. 将指定的共享内存,挂接到自己的地址空间
char *shmaddr = (char *)shmat(shmid, nullptr, 0);
Log("attach shm done", Debug) << " shmid : " << shmid << endl;
int fd = OpenFIFO(FIFO_NAME, READ);
for(;;)
{
Wait(fd);
printf("%s\n", shmaddr);
if(strcmp(shmaddr, "quit") == 0) break;
}
// 4. 将指定的共享内存,从自己的地址空间中去关联
int n = shmdt(shmaddr);
assert(n != -1);
(void)n;
Log("detach shm done", Debug) << " shmid : " << shmid << endl;
// 5. 删除共享内存,IPC_RMID即便是有进程和当下的shm挂接,依旧删除共享内存
n = shmctl(shmid, IPC_RMID, nullptr);
assert(n != -1);
(void)n;
Log("delete shm done", Debug) << " shmid : " << shmid << endl;
CloseFifo(fd);
return 0;
}
このコードは、共有メモリの作成、共有メモリへの接続、共有メモリからのデータの読み取り、そして最後に共有メモリ リソースと名前付きパイプのクリーンアップを担当します。
Init init;
:Init
クラスのコンストラクターが呼び出され、名前付きパイプが作成されます。TransToHex
機能:key_t
型のキー値を 16 進文字列に変換します。main
この関数で実行される主な手順は次のとおりです。ftok
関数を使用して共有メモリのキー値を作成します。shmget
を使用して共有メモリ セグメントを作成し、同じ共有メモリが存在しないように IPC_CREAT フラグと IPC_EXCL フラグを設定します。shmat
を介して共有メモリに接続し、共有メモリのアドレスを取得します。OpenFIFO
関数を使用して名前付きパイプを開きます。- ループで使用される 関数
Wait
は、名前付きパイプから受信されるシグナルを待機し、共有メモリからデータを読み取り、「終了」メッセージが表示されるまで出力します。読む。 shmdt
を使用して共有メモリを切断します。- 共有メモリを削除するには
shmctl
を使用します。 - 名前付きパイプを閉じます。
プロセス全体はサーバーの機能を実装し、共有メモリと名前付きパイプを介してクライアントと通信します。
4.4 shmClient.cpp
#include "comm.hpp"
int main()
{
Log("child pid is : ", Debug) << getpid() << endl;
key_t k = ftok(PATH_NAME, PROJ_ID);
if (k < 0)
{
Log("create key failed", Error) << " client key : " << k << endl;
exit(1);
}
Log("create key done", Debug) << " client key : " << k << endl;
// 获取共享内存
int shmid = shmget(k, SHM_SIZE, 0);
if(shmid < 0)
{
Log("create shm failed", Error) << " client key : " << k << endl;
exit(2);
}
Log("create shm success", Error) << " client key : " << k << endl;
char *shmaddr = (char *)shmat(shmid, nullptr, 0);
if(shmaddr == nullptr)
{
Log("attach shm failed", Error) << " client key : " << k << endl;
exit(3);
}
Log("attach shm success", Error) << " client key : " << k << endl;
int fd = OpenFIFO(FIFO_NAME, WRITE);
while(true)
{
ssize_t s = read(0, shmaddr, SHM_SIZE-1);
if(s > 0)
{
shmaddr[s-1] = 0;
Signal(fd);
if(strcmp(shmaddr,"quit") == 0) break;
}
}
CloseFifo(fd);
// 去关联
int n = shmdt(shmaddr);
assert(n != -1);
Log("detach shm success", Error) << " client key : " << k << endl;
return 0;
}
このコードは、共有メモリと名前付きパイプを介してサーバーと通信するクライアント プログラムを実装します。
- まず、プログラムはプロセス ID を出力します。
- 次に、
ftok
関数を使用して、共有メモリを取得するためのキー値を作成します。 shmget
経由で共有メモリ セグメントを取得します。shmat
を使用して共有メモリに接続し、共有メモリのアドレスを取得します。- 名前付きパイプを開き、無限ループに入ります。
- ループでは、プログラムは標準入力からデータを読み取り、共有メモリにデータを書き込み、サーバーにシグナルを送信してデータ送信を実現します。
- 「quit」を入力するとループを終了します。
- 名前付きパイプを閉じ、共有メモリから切断し、プログラムを終了します。
このプログラムの機能は、サーバーへのデータの送信とサーバーからのシグナルの受信であり、データを読み取った後、共有メモリに書き込み、名前付きパイプを介してサーバーとの同期と通信を実現します。
メイクファイルのコンパイル
.PHONY:all
all:shmClient shmServer
shmClient:shmClient.cpp
g++ -o $@ $^ -std=c++11
shmServer:shmServer.cpp
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f shmClient shmServer
コンパイルが完了したらターミナルを開き、2 つのターミナルで 2 つのプログラムをそれぞれ実行します: shmClient
と shmServer
最初に実行shmServer
送信を待機shmClient
し、shmClient
メッセージを送信した後、shmServer
クライアントが入力するまで受信は再び待機しますquit
クライアント側で不正終了を引き起こすと、サーバーの再起動に失敗する可能性があります。
サーバーを再起動します
これは、ipc リソースが削除されていないため、リソースが占有され、新しい ipc リソースを初期化できなくなるためです。
コマンドipcs -m
View ipc resource を使用します。
コマンドを入力してくださいipcrm -m [shmid号]
shm ipc リソースを削除します。注意してください
IPC リソースは削除する必要があります。削除しない場合、再起動しない限り自動的にクリアされないため、システム V IPC リソースのライフ サイクルはカーネルによって異なります。
ipc リソースの削除に加えて、名前付きパイプ ファイルを再生成する必要があります。ここでの名前付きパイプの役割は、主にプロセス間の同期と制御を実現することです。この例では、名前付きパイプを使用してサーバーとクライアント間を同期し、共有メモリへのアクセスと操作を調整します。
具体的には:
- サーバーはシグナルを待ちます: サーバーは、共有メモリからデータを読み取るタイミングを示す、名前付きパイプを介したクライアントからのシグナルを待ちます。
- クライアントはシグナルを送信します: 共有メモリにデータを書き込んだ後、クライアントは名前付きパイプを介してサーバーにシグナルを送信し、共有メモリにデータが存在することをサーバーに通知します。メモリは読み取れます。
この設計により、2 つのプロセスが同期的にデータを共有できるようになり、競合状態やデータの不整合が回避されます。名前付きパイプは同期信号ブリッジとして機能し、共有メモリを適切なタイミングで読み書きできるようにプロセス間の操作を調整します。
共有メモリ自体には、プロセスの同期と制御のメカニズムは含まれていません。共有メモリ領域を提供するだけで、複数のプロセスが同じメモリ空間にアクセスできるようになります。したがって、データ競合や不整合を回避するために、共有メモリへのプロセス アクセスを調整および制御するには、他のメカニズムが必要です。
通常、共有メモリへの安全なアクセスを確保するには、次のような他の形式の同期および制御メカニズムを組み合わせる必要があります。
- セマフォ: は、共有リソースへのアクセスを制御し、同時に 1 つのプロセスのみが共有メモリにアクセスできるようにするために使用されます。
- ミューテックス ロック: により、同時に 1 つのプロセスだけがクリティカル セクション コードを実行できるようになり、複数のプロセスが同時に共有メモリに書き込むことがなくなります。
- 条件変数: は、プロセス間の待機および通知メカニズムを実装するために使用され、プロセスが操作を実行する前に特定の条件が満たされるまで待機できるようにします。
これらのメカニズムにより、プロセス間の動作を調整し、データの一貫性と安全なアクセスを確保できます。この例では、プロセス間の同期と制御の実現を支援するために名前付きパイプが使用されています。
システム V メッセージキュー
1. システム V メッセージキューとは
System V メッセージ キューは、Linux システムの IPC (プロセス間通信) メカニズムであり、プロセス間通信の方法を提供し、異なるプロセス間でのデータ転送を可能にします。
特徴は次のとおりです。
- メッセージ キューの構造: メッセージ キューはメッセージのコレクションであり、各メッセージにはタイプと本文があります。
- 送信者と受信者に依存しない: 送信者はメッセージを送信し、受信者は相互に直接接続することなくメッセージを受信できます。これにより、プロセスが非同期に通信できるようになります。
- メッセージ タイプ: 各メッセージにはタイプがあり、受信者は受信するメッセージの特定のタイプを選択できます。
- 永続性: メッセージ キューは永続的であり、明示的に削除されるまで存在します。
- サイズ制限: 各メッセージ キューにはサイズ制限があり、オペレーティング システムや構成によって制限が異なる場合があります。
System V メッセージ キューは、msgget
(メッセージ キューの作成または取得)、msgsnd
(メッセージの送信) などの一連の関数を通じて実装されます。キューへの送信)、msgrcv
(キューからのメッセージの受信) など。これらの関数を使用すると、プロセスはメッセージを送受信し、メッセージ キューのプロパティを制御できます。
この通信メカニズムは、非同期通信、データ転送、プロセス間の分離が必要なシナリオに適しています。これは信頼性の高いプロセス間通信方法を提供し、異なるプロセスが低い結合度でデータを交換できるようにします。
2. 擬似コードの例
次の例のコードは概念的なデモであり、System V IPC 関数には正しいパラメーターとエラー処理が必要なため、直接実行可能なコードではありません。さらに、System V IPC 関数は C 関数インターフェイスを使用するため、オペレーティング システムが異なると微妙な違いが生じる場合があります。
// Sender Process
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
struct msgbuf {
long mtype;
char mtext[100];
};
int main() {
key_t key = ftok("path", 'A');
int msgid = msgget(key, IPC_CREAT | 0666);
struct msgbuf message;
message.mtype = 1; // 设置消息类型为1
strcpy(message.mtext, "FileTransfer: file.txt, size: 1024");
msgsnd(msgid, &message, sizeof(message), 0);
// 继续其他任务或等待接收者的确认
return 0;
}
// Receiver Process
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
struct msgbuf {
long mtype;
char mtext[100];
};
int main() {
key_t key = ftok("path", 'A');
int msgid = msgget(key, 0666);
struct msgbuf message;
msgrcv(msgid, &message, sizeof(message), 1, 0);
printf("Received message: %s\n", message.mtext);
// 提取文件名和大小
// 创建文件
// 发送确认消息
return 0;
}
このサンプル コードは、System V メッセージ キューを使用して 2 つのプロセス間の単純な通信を実行する方法を示します。送信者はファイル情報を含むメッセージを送信し、受信者はメッセージからデータを抽出して適切な操作を実行し、確認メッセージを送信します。
3. システム V のメッセージ キュー プロセスの通信メカニズムは徐々に低下しています。
System V メッセージ キューは効果的な IPC (プロセス間通信) メカニズムですが、主に次の理由により、他のより最新のプロセス間通信方法に徐々に置き換えられています。
- 複雑さ: System V メッセージ キューを使用するには、IPC API の処理が必要で、
msgget
、msgsnd
およびmsgrcv
やその他の関数を使用する場合、これらの操作は開発者にとって煩わしく、エラーが発生しやすく、直感的ではない場合があります。 - パフォーマンス: メッセージ キューのパフォーマンスは、他の IPC メカニズムと比較して比較的低い場合があります。たとえば、共有メモリと比較して、メッセージ キューではデータのコピーが必要となるため、追加のオーバーヘッドが発生する可能性があります。
- 制限事項: System V メッセージ キューには固定のメッセージ サイズ制限があるため、メッセージ サイズと送信の柔軟性が制限されます。
- 移植性: オペレーティング システムが異なると、System V メッセージ キューのサポートのレベルや詳細が異なる場合があり、これによりシステムが異なると互換性の問題が発生する可能性があります。
- 代替テクノロジー: 時間の経過とともに、POSIX メッセージ キュー、パイプ、ソケットなど、より現代的で便利なプロセス間通信方法が登場し、より使いやすくなりました。優れたパフォーマンスと柔軟性を提供します。
これらの理由により、開発者は IPC メカニズムを選択するときに他のより単純で効率的な方法を使用する傾向があり、その結果、System V メッセージ キューが徐々に置き換えられるか削除されることになります。