PHP Conference Japan 2024

shm_attach

(PHP 4, PHP 5, PHP 7, PHP 8)

shm_attach建立或開啟共享記憶體區段

描述

shm_attach(int $key, ?int $size = null, int $permissions = 0666): SysvSharedMemory|false

shm_attach() 返回一個 ID,該 ID 可用於存取具有給定 key 的 System V 共享記憶體,第一次呼叫會建立具有 size 和可選權限位元 permissions 的共享記憶體區段。

第二次對同一個 key 呼叫 shm_attach() 將會返回一個不同的 SysvSharedMemory 實例,但這兩個實例都會存取相同的底層共享記憶體。 sizepermissions 將會被忽略。

參數

key

數值共享記憶體區段 ID

size

記憶體大小。如果未提供,預設為 php.ini 中的 sysvshm.init_mem,否則為 10000 位元組。

permissions

可選的權限位元。預設為 0666。

返回值

成功時返回一個 SysvSharedMemory 實例,失敗時返回 false

更新日誌

版本 描述
8.0.0 成功時,此函式現在會返回一個 SysvSharedMemory 實例;之前,會返回一個 resource
8.0.0 size 現在可為 null。

參見

  • shm_detach() - 從共享記憶體區段中斷連線
  • ftok() - 將路徑名稱和專案識別符號轉換為 System V IPC 金鑰

新增註解

使用者貢獻的註解 24 則註解

5
Uther Pendragon
17 年前
由於沒有單獨的函式用於建立和附加共享記憶體(我認為這是一個錯誤),您可能需要進行一些測試,以檢查您是否已建立它,因為您可能不希望主/從配對中的從屬永遠建立共享記憶體。

測試此問題的一種方法是讓從屬將大小設定為較小的值,然後透過放入一個太大而無法容納的變數來測試大小,例如:

function get_shmem() {
return shm_attach(ftok('somefile.txt', 'T'), 100, 0644);
}

$shm = get_shmem();

while (!@shm_put_var($shm, 1, str_repeat('.....', 20))) {
shm_remove($shm);
sleep(1);
//我們建立了它,因此我們將移除它並睡眠以等待主機建立它,然後再試一次。
$shm = get_shmem();
}
shm_remove_var($shm, 1);
//在這裡,我們知道共享記憶體已經建立,因為我們可以在其中放入一個大於請求大小的變數。

您可以處理此問題的另一種方法是讓所有程式使用相同的參數初始化共享記憶體...當我的客戶端啟動太快並在沒有傳遞 memsize 值的情況下建立 shmem 時,我遇到了這個問題,因此它預設為 10k,這太小了。
8
novayear # hotmail ; com
10 年前
小型 shm 類別..
範例用法
$shx= new shmSmart;
$shx->put("key_name_apple","key_val_peach"); //設定範例..
$shx->put("key name alternative array",array(1=>"banana","apricot","blablabla"=>array("new-blaala"))); //設定陣列範例..
echo $shx->get("key_name_apple"); // 取得範例金鑰值。
$shx->del("key_name_apple"); // 刪除金鑰
unset($shx); // 在 php 中釋放記憶體..

class shmSmart{
public $shm; //保存共享記憶體資源
public function __construct(){
if(function_exists("shm_attach")===FALSE){
die("\n您的 PHP 設定需要調整。請參閱:http://us2.php.net/manual/en/shmop.setup.php. 若要啟用 System V 共享記憶體支援,請使用 --enable-sysvshm 選項編譯 PHP。");
}
$this->attach(); //建立資源(共享記憶體)
}
public function attach(){
$this->shm=shm_attach(0x701da13b,33554432); //配置共享記憶體
}
public function dettach(){
return shm_detach($this->shm); //配置共享記憶體
}
public function remove(){
return shm_remove($this->shm); //釋放共享記憶體
}
public function put($key,$var) {
return shm_put_var($this->shm,$this->shm_key($key),$var); //儲存變數
}
public function get($key){
if($this->has($key)){
return shm_get_var($this->shm,$this->shm_key($key)); //取得變數
}else{
return false;
}
}
public function del($key){
if($this->has($key)){
return shm_remove_var($this->shm,$this->shm_key($key)); // 刪除變數
}else{
return false;
}
}
public function has($key){
if(shm_has_var($this->shm,$this->shm_key($key))){ // 檢查是否已設定
return true;
}else{
return false;
}
}
public function shm_key($val){ // 啟用所有世界語言和字元!
return preg_replace("/[^0-9]/","",(preg_replace("/[^0-9]/","",md5($val))/35676248)/619876); // 文字轉數字系統。
}
public function __wakeup() {
$this->attach();
}
public function __destruct() {
$this->dettach();
unset($this);
}
}
3
Uther Pendragon
17 年前
關於我上次關於 shm_attach 及其了解其建立方式的限制能力的後續文章....

為了獲得更多控制,請使用 shmop_* 系列函式,因為它們比這些函式具有更精細的控制。

順帶一提:SHMOP 函式應該在所有 SHM* 包裝函式的「參見」下列出(我假設它們是 SHMOP* 函式的包裝函式)。
3
h dot raaf at i-k-c dot net
25 年前
請注意,共享記憶體的「int key」與信號使用的金鑰共享。因此,當您將相同的金鑰值用於信號和共享記憶體時,您會遇到麻煩!
2
nathanbruer at gmail dot com
13 年前
我當時在玩這些函式,並在這個過程中建立了一個類別。當然,這會比直接存取區域變數慢,但它提供了在共享環境中儲存變數的能力,並讓許多正在執行的腳本理解應該從共享區域存取它們。當沒有任何腳本連結到這些資料時(當所有腳本都使用這個類別時),這也應該會自動銷毀共享記憶體區域。

<?php
class SharedMemory{
private
$nameToKey = array();
private
$key;
private
$id;
function
__construct($key = null){
if(
$key === null){
$tmp = tempnam('/tmp', 'PHP');
$this->key = ftok($tmp, 'a');
$this->id = shm_attach($this->key);
$this->nameToKey[] = '';
$this->nameToKey[] = '';
$this->updateMemoryVarList();
shm_put_var($this->id, 1, 1);
}else{
$this->key = $key;
$this->id = sem_get($this->key);
$this->refreshMemoryVarList();
shm_put_var($this->id, 1, shm_get_var($this->id, 1) + 1);
}
if(!
$this->id)
die(
'無法建立共享記憶體區段');
}
function
__sleep(){
shm_detach($this->id);
}
function
__destruct(){
if(
shm_get_var($this->id, 1) == 1){
// 我是最後一個監聽者,所以要銷毀共享記憶體空間
$this->remove();
}else{
shm_detach($this->id);
shm_put_var($this->id, 1, shm_get_var($this->id, 1) - 1);
}
}
function
__wakeup(){
$this->id = sem_get($this->key);
shm_attach($this->id);
$this->refreshMemoryVarList();
shm_put_var($this->id, 1, shm_get_var($this->id, 1) + 1);
}
function
getKey(){
return
$this->key;
}
function
remove(){
shm_remove($this->id);
}
function
refreshMemoryVarList(){
$this->nameToKey = shm_get_var($this->id, 0);
}
function
updateMemoryVarList(){
shm_put_var($this->id, 0, $this->nameToKey);
}
function
__get($var){
if(!
in_array($var, $this->nameToKey)){
$this->refreshMemoryVarList();
}
return
shm_get_var($this->id, array_search($var, $this->nameToKey));
}
function
__set($var, $val){
if(!
in_array($var, $this->nameToKey)){
$this->refreshMemoryVarList();
$this->nameToKey[] = $var;
$this->updateMemoryVarList();
}
shm_put_var($this->id, array_search($var, $this->nameToKey), $val);
}
}

// 範例
$sharedMem = new SharedMemory();
$pid = pcntl_fork();
if(
$pid){
//父進程
sleep(1);
echo
"父進程說: " . $sharedMem->a . "\n";
echo
"父進程改為 0\n";
$sharedMem->a = 0;
//父進程剛把它改為 0
echo "父進程說: " . $sharedMem->a . "\n";
sleep(2);
// 父進程認為它是 0,但子進程已將其改為 1
echo "父進程說: " . $sharedMem->a . "\n";
}else{
//子進程
$sharedMem->a = 2;
echo
"子進程改為 2\n";
// 應該是 2,因為子進程剛將其設定為 2
echo "子進程說: " . $sharedMem->a . "\n";
sleep(2);
// 子進程認為它是 2,但父進程已將其設定為 0。
echo "子進程說: " . $sharedMem->a . "\n";
echo
"子進程加 1\n";
$sharedMem->a++;
echo
"子進程說: " . $sharedMem->a . "\n";
}
?>
2
Daniel Knecht
10 年前
如果您收到類似 "PHP Warning: shm_attach(): failed for key 0x61040bb5: Cannot allocate memory" 的錯誤,則您可能需要調整您的共享記憶體設定。

要查看您的系統值,請輸入 "sysctl kern.sysv."。
重要的值是 kern.sysv.shmmax 和 kern.sysv.shmall。
* kern.sysv.shmmax 是一個共享記憶體區段可能擁有的最大位元組數。
* kern.sysv.shmall 是所有共享記憶體區段加總可以消耗的最大記憶體頁面數。
一個記憶體頁面是 4096 個位元組,這表示如果您將 kern.sysv.shmmax 設定為 1073741824 (1GB),則 kern.sysv.shmall 必須至少為 262144,才能配置 1GB 的記憶體區段(因為 262144 * 4096 = 1073741824)。

簡而言之,某些系統上的預設值非常低,因此只增加 kern.sysv.shmmax 是不夠的 - kern.sysv.shmall 也需要相應地增加!
2
webmaster at mail dot communityconnect dot com
25 年前
在 Sun Solaris 2.x 中,允許的最大共享記憶體值為 1,048,576。可以使用指令 /usr/sbin/sysdef 來確定允許的最大值。在 Linux 上,似乎沒有系統強制的最大大小限制。若要在 Solaris 2.x 上更改允許的最大大小,請使用 set shmsys:shminfo_shmmax=[新值]。
1
zeppelinux at comcast dot net
16 年前
<?php

//如何計算儲存變數 $foo (其中 $foo='foobar') 所需的最小 $memsize。

// 當第一次呼叫 shm_attach() 時,PHP 會將標頭寫入共享記憶體的開頭。
$shmHeaderSize = (PHP_INT_SIZE * 4) + 8;

// 當呼叫 shm_put_var() 時,變數會被序列化,並在寫入共享記憶體之前,在其前面放置一個小標頭。
$shmVarSize = (((strlen(serialize($foo))+ (4 * PHP_INT_SIZE)) /4 ) * 4 ) + 4;

// 現在將兩者加總以獲得所需的總記憶體。當然,如果您儲存多個變數,則不需要為每個變數都加上 $shmHeaderSize,只需要加一次。
$memsize = $shmHeaderSize + $shmVarSize;

//這將為您提供足夠的記憶體來使用 shm_put_var() 儲存一個變數。
$shm_id = shm_attach ( $key, $memsize, 0666 ) ;
shm_put_var ( $shm_id , $variable_key , $foo );

任何嘗試儲存另一個變數都會導致'記憶體不足'錯誤
請注意,如果您將 $foo 的內容更改為更大的值,並且您然後嘗試使用 shm_put_var() 將其再次寫入共享記憶體則會收到'記憶體不足'錯誤在這種情況下,必須調整您的共享記憶體區段大小,然後寫入新的

如果您只儲存包含單一整數值的變數那麼您可以避免調整大小,方法是始終分配儲存 int 所需的最大記憶體量這應該是:
$shmIntVarSize = (((strlen(serialize(PHP_INT_MAX))+ (4 * PHP_INT_SIZE)) /4 ) * 4 ) + 4;

?>
1
hetii at poczta dot onet dot pl
16 年前
嗨 :)
我寫了一個小的類別,用於在我的應用程式之間建立明亮的訊息。

<?
class Bright_Message
{

var $bright;
var $SHM_KEY;
var $my_pid;

function Bright_Message($SHM_KEY=null)
{
$this->my_pid = getmypid();//取得自己的 pid
if (is_null($SHM_KEY)) $this->SHM_KEY = '123123123';
$this->bright = shm_attach($this->SHM_KEY, 1024, 0666);
$this->one_instance();
}

function get_msg($id,$remove=true)
{
if(@$msg=shm_get_var($this->bright,$id))
{
if ($remove) @shm_remove_var($this->bright,$id);
return $msg;
} else return false;
}

function snd_msg($id,$msg)
{
@shm_put_var($this->bright,$id,$msg);
return true;
}

function one_instance()
{
$SHM_PID = $this->get_msg(1,false);
if((strpos(exec('ps p'.$SHM_PID),$_SERVER['SCRIPT_FILENAME'])) === false)
$this->snd_msg(1,$this->my_pid); else
{
echo "此程式已存在於 pid:$SHM_PID\r\n\r\n";
exit;
}
}

}
?>

send.php
<?
include "bridge_message.class.php";
$shm = new Bright_Message();
$shm->snd_msg(2,'這是我的簡單訊息');
?>

receive.php
<?
include "bridge_message.class.php";
$shm = new Bright_Message();
$msg = get_msg(2);
echo print_r($msg,1);
?>
1
jpeter1978 at yahoo dot com
17 年前
我嘗試了以上所有關於取得 $memsize 的物件大小(以位元組為單位)的建議,但它們並非對我嘗試的兩種物件類型(字串和字串陣列)都通用。

在經過一些 Google 搜尋和實驗後,我找到了以下神奇公式

$memsize = ( strlen( serialize( $object ) ) + 44 ) * 2;

我是在別人的程式碼中找到這個的,所以我無法解釋它。
1
Katzenmeier
18 年前
一個 SHM 區塊的限制似乎是 32 MB,但如果需要,您可以將資料分割成多個 SHM 區塊。總 SHM 限制似乎約為 8 GB。

我不確定這是否適用於所有組態。
1
muytoloco at yahoo dot com dot br
18 年前
如果一個程序對一個不存在的記憶體區域進行 shm_attach,則此區域將在執行腳本使用者的相同權限下建立。如果另一個程序嘗試建立或存取同一區域,而該程序由具有與第一個使用者不同權限的其他使用者執行,則會發生錯誤。
1
rch at todo dot com dot uy
19 年前
Cecil,變數的鍵是一個整數(不是名稱)。您可以在同一個共享中放置多個變數。

#!/usr/local/bin/php -q
<?PHP

$SHM_KEY
= ftok(__FILE__, chr( 4 ) );

$data = shm_attach($SHM_KEY, 1024, 0666);

$test1 = array("hello","world","1","2","3");
$test2 = array("hello","world","4","5","6");
$test3 = array("hello","world","7","8","9");

shm_put_var($data, 1, $test1);
shm_put_var($data, 2,$test2);
shm_put_var($data, 3,$test3);

print_r(shm_get_var($data, 1));
print_r(shm_get_var($data, 2));
print_r(shm_get_var($data, 3));

shm_detach($data);
?>
1
andreyKEINSPAM at php dot net
20 年前
就我從 ext/sysvshm 的原始碼來看,不需要對 "perm" 與 IPC_CREAT(和 IPC_EXCL)進行算術(位元)OR (|) 運算。擴充功能會為您執行此操作。它會嘗試附加到記憶體區段,如果嘗試失敗,則會嘗試附加,但會將旗標設定為 user_flag | IPC_CREAT | IPC_EXCL。
確切的程式碼(shm_flag 是函數的第三個參數)
if ((shm_id = shmget(shm_key, 0, 0)) < 0) {
if (shm_size < sizeof(sysvshm_chunk_head)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "金鑰 0x%x 失敗:記憶體大小太小", shm_key);
efree(shm_list_ptr);
RETURN_FALSE;
}
if ((shm_id = shmget(shm_key, shm_size, shm_flag | IPC_CREAT | IPC_EXCL)) < 0) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "金鑰 0x%x 失敗:%s", shm_key, strerror(errno));
efree(shm_list_ptr);
RETURN_FALSE;
}
}
1
Cecil
20 年前
這是一個如何使用一個共享記憶體區塊來儲存多個變數或陣列的範例。不幸的是,為了儲存超過一個的變數,您必須多次使用 sem_get()。對於 shm_attach()、shm_put_var() 和 shm_get_var() 也是如此。

#!/usr/local/bin/php -q
<?PHP
// test.php

$SHM_KEY = ftok(__FILE__,'A');

$shmid = sem_get($SHM_KEY, 1024, 0644 | IPC_CREAT);
$shmid2 = sem_get($SHM_KEY, 1024, 0644 | IPC_CREAT);
$shmid3 = sem_get($SHM_KEY, 1024, 0644 | IPC_CREAT);

$data = shm_attach($shmid, 1024);
$data2 = shm_attach($shmid2, 1024);
$data3 = shm_attach($shmid3, 1024);

$test = array("hello","world","1","2","3");
$test2 = array("hello","world","4","5","6");
$test3 = array("hello","world","7","8","9");

shm_put_var($data,$inmem,$test);
shm_put_var($data2,$inmem2,$test2);
shm_put_var($data3,$inmem3,$test3);

print_r(shm_get_var($data,$inmem));
print_r(shm_get_var($data2,$inmem2));
print_r(shm_get_var($data3,$inmem3));

shm_detach($data);
shm_detach($data2);
shm_detach($data2);
?>

要真的測試它,請建立第二個腳本,如下所示並執行它..

#!/usr/local/bin/php -q
<?PHP
// test2.php

$SHM_KEY = ftok(__FILE__,'A');

$shmid = sem_get($SHM_KEY, 1024, 0644 | IPC_CREAT);
$shmid2 = sem_get($SHM_KEY, 1024, 0644 | IPC_CREAT);
$shmid3 = sem_get($SHM_KEY, 1024, 0644 | IPC_CREAT);

$data = shm_attach($shmid, 1024);
$data2 = shm_attach($shmid2, 1024);
$data3 = shm_attach($shmid3, 1024);

print_r(shm_get_var($data,$inmem));
print_r(shm_get_var($data2,$inmem2));
print_r(shm_get_var($data3,$inmem3));

shm_detach($data);
shm_detach($data2);
shm_detach($data2);
?>

如您所見,test2.php 沒有將任何內容插入共享記憶體中.. 但它卻提取了 3 個已經儲存的完全不同的陣列..

希望這有幫助.. 我花了一些時間才弄對.. 每個人似乎都有自己對如何使用 shm 的想法。 哈哈。

順便說一下,老實說,我不確定 ftok 如何運作,因為我沒有更改 __FILE__ 以匹配 test.php 的檔案路徑或任何東西.. 我認為檔案路徑必須完全相同才能正常運作.. 沒關係,它按原樣運作了! 哈哈..

- Cecil
1
eric at superstats dot com
25 年前
物件以序列化的形式儲存在 shm_put_var 中,因此要找到 memsize 的值,您可以使用 strlen(serialize($object_to_store_in_shm))。
0
bobhairgrove
6 年前
在「注意事項」部分中,說明了從 PHP 5.3.0 開始,可以將 shm_attach() 傳回的資源 ID 強制轉換為整數;但是,這與 Linux 上原生 SysV 函式 shmget() 傳回的 shmid 值不同(正如我希望的那樣)。強制轉換傳回的整數僅是資源的 ID 號碼,在這些函式的上下文之外是完全無用的,AFAICT。

為了驗證這一點,可以使用 shm_attach() (或 shmop_open()) 分配一個記憶體區塊,列印出傳回的資源,並將其與在 Linux(或某些其他 *nix OS)上的終端機中執行「ipcs」時,「shmid」欄中的值進行比較。
0
somepay at gmail dot com
14 年前
在我的日誌中,我發現了字串
「shm_attach():key 0x366f 失敗:裝置上沒有剩餘空間」
但在 php.net 和 google 上都沒有找到任何關於此的建議。

所以問題是如何釋放 shm_attach 使用的記憶體。
首先要查看 (Linux) 分配了多少區段,請使用
:~# ipcs -mu
然後
限制
:~# ipcs -ml

要移除區段,請使用
:~# ipcrm -m [shmid]

否則,您可以重新啟動伺服器,或根據上述命令啟動 sh 腳本。

為了避免「裝置上沒有剩餘空間」的問題,請在呼叫 shm_attach() 後始終使用
shm_remove() & shm_detach()。
0
koester at x-itec dot de
23 年前
4194304 表示 FreeBSD 上共享記憶體的 4 MB,而不是 4 GB。 您可以根據需要增加執行時間的共享記憶體。
-1
info at tristat dot org
13 年前
您可能會期望 SHM 函式比從 MySQL 操作建立變數快得多。 不幸的是,對於大型多維陣列,情況並非如此。 我使用 SHM 直接測試了二維關聯陣列的寫入、讀取、更新和刪除操作,並與使用 MySQL 的類別和使用 SHMOP 函式的類別(主要是偏移量準確的序列化)進行了比較。 這兩個類別產生了與 SHM 儲存的相同陣列。 與 SHMOP 不同,SHM 的效能會隨著較大的陣列而顯著降低。 在 200 個 row 和 5 個 key 的 $array[$row][$key] 有 2000 個指令時,即使是 MySql 也比 SHM 快。這可能是因為 SHM 處理任何類型的陣列並在內部使用 PHP 強大的 serialize() 函式。
-1
pail dot luo at gmail dot com
15 年前
一個簡單的 SHM 介紹範例。

<?php
if ( sizeof($argv)<2 ) {
echo
"用法: $argv[0] send|recv|rem|dele ID [msg] \n\n" ;
echo
" 範例: $argv[0] send 1 \"這是訊息 1\" \n" ;
echo
" $argv[0] recv 1 \n" ;
echo
" $argv[0] rem 1 \n" ;
echo
" $argv[0] dele \n" ;
exit;
}

// $SHMKey = ftok(__FILE__, "Z");
$SHMKey = "123456" ;

## 建立/開啟共享記憶體
$seg = shm_attach( $SHMKey, 1024, 0666 ) ;

switch (
$argv[1] ) {
case
"send":
shm_put_var($seg, $argv[2], $argv[3]);
echo
"訊息傳送完成...\n" ;
break;

case
"recv":
$data = shm_get_var($seg, $argv[2]);
echo
$data . "\n" ;
break;

case
"rem":
shm_remove_var($seg, $argv[2]);
break;

case
"dele":
shm_remove($seg);
break;

case
"dele2":
`
/usr/bin/ipcrm -M 123456`;
break;
}
?>
-1
nkatz at yahooo dot com
16 年前
回覆 jpeter1978 at yahoo dot com ... 假設一個字元通常是兩個位元組,序列化變數(包含陣列或物件)的大小為其 strlen() 的 2 倍。44 比 php at cytrax dot de 建議的 24 + 16 = 40 多 4,外加最壞情況 4 位元組對齊的 4 個位元組。因此,如果您懶得使用類似 zeppelinux at comcast dot net 的方法對齊,這可能是一個可靠的公式,如下所示:

($strlen($serialized_array_or_obj) /4 ) * 4 ) + 40;

zeppelinux 公式將使用 20(適用於 4 位元組整數 CPU)或 36(適用於 64 位元或 8 位元組 CPU)作為結尾常數,因此 40 或 44 可能只是實現標頭填充,但肯定沒有壞處。
-1
php at cytrax dot de
22 年前
共享記憶體所需的記憶體大小(在 Linux 系統上測試)可以用以下方法計算:

對於您要儲存的每個變數:24 個位元組
+ 序列化變數大小(請參閱下方),以 4 位元組對齊
+ 16 個位元組

對於使用相同鍵更新變數,舊變數的記憶體將需要額外空間。
-1
lew at persiankitty dot com
23 年前
找出 FreeBSD 中的共享記憶體核心設定

sys1# sysctl -a | grep -i SHM

kern.ipc.shmmax: 4194304
kern.ipc.shmmin: 1
kern.ipc.shmmni: 96
kern.ipc.shmseg: 64
kern.ipc.shmall: 1024
kern.ipc.shm_use_phys: 0

顯示我們可以分配總共 4GB(哇)的共享記憶體,等等...
To Top