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 现在可以为空。

参见

  • shm_detach() - 从共享内存段断开连接
  • ftok() - 将路径名和项目标识符转换为 System V IPC 密钥

添加注释

用户贡献的注释 24 条注释

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);
//在这里我们知道共享内存已经被创建,因为我们可以放入一个大于请求大小的变量。

另一种处理方式是让所有程序都使用相同的参数初始化共享内存……当我的客户端启动太快并创建了 shmem 而不传递 memsize 值时,我遇到了这个问题,因此它默认为 10k,这太小了。
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);
}
}
Uther Pendragon
17 年前
作为我上次关于 shm_attach 及其了解其创建方式的限制能力的帖子的后续...

为了获得更多控制,请使用 shmop_* 系列函数,因为它们比这些函数具有更细粒度的控制。

顺便说一句:对于所有 SHM* 包装函数,都应该在“另请参见”下列出 SHMOP 函数(我假设它们是 SHMOP* 函数的包装函数)。
h dot raaf at i-k-c dot net
25 年前
请注意,共享内存的“int key”与用于信号量的键共享。因此,当您对信号量和共享内存使用相同的键值时,您会遇到麻烦!
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";
}
?>
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)。

tl;dr 一些系统上的默认值非常低,因此仅增加 kern.sysv.shmmax 不够 - kern.sysv.shmall 也需要相应地增加!
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=[新值]。
zeppelinux at comcast dot net
16 年前

<?php

//如何计算存储变量 $foo 所需的最小 $memsize,其中 $foo='foobar'。

// 当第一次调用 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;

?>
hetii at poczta dot onet dot pl
16 年前

你好 :)
我编写了一个小型类,用于在我的应用程序之间构建明文消息。

<?
类 Bright_Message
{

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

函数 Bright_Message($SHM_KEY=null)
{
$this->my_pid = getmypid();//获取自身进程ID
如果 (is_null($SHM_KEY)) $this->SHM_KEY = '123123123';
$this->bright = shm_attach($this->SHM_KEY, 1024, 0666);
$this->one_instance();
}

函数 get_msg($id,$remove=true)
{
如果(@$msg=shm_get_var($this->bright,$id))
{
如果 ($remove) @shm_remove_var($this->bright,$id);
返回 $msg;
} 否则返回 false;
}

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

函数 one_instance()
{
$SHM_PID = $this->get_msg(1,false);
如果((strpos(exec('ps p'.$SHM_PID),$_SERVER['SCRIPT_FILENAME'])) === false)
$this->snd_msg(1,$this->my_pid); 否则
{
echo "此程序已存在于进程ID:$SHM_PID\r\n\r\n";
退出;
}
}

}
?>

send.php
<?
包含 "bridge_message.class.php";
$shm = new Bright_Message();
$shm->snd_msg(2,'这是一个简单的消息');
?>

receive.php
<?
包含 "bridge_message.class.php";
$shm = new Bright_Message();
$msg = get_msg(2);
echo print_r($msg,1);
?>
jpeter1978 at yahoo dot com
17 年前
我尝试了上面所有获取对象大小(以字节为单位)的建议,以便用于 $memsize,但它们对尝试的两种对象类型(字符串和字符串数组)并不通用。

在进行了一些谷歌搜索和实验后,我发现了以下神奇公式

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

我在别人的代码中找到了这个,所以我无法解释它。
Katzenmeier
18 年前
一个 SHM 块的限制似乎是 32 MB,但如果需要,您可以将数据拆分为几个 SHM 块。SHM 的总限制似乎约为 8 GB。

我不确定这是否适用于所有配置。
muytoloco at yahoo dot com dot br
18 年前
如果一个进程对一个不存在的内存区域进行 shm_attach,则该区域将在运行脚本用户的相同权限下创建。如果另一个进程尝试创建或访问同一区域,则由其他用户运行,并且其权限与第一个用户不同,则会发生错误。
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);
?>
andreyKEINSPAM at php dot net
20 年前
据我从 ext/sysvshm 的源代码中看到,不需要对 "perm" 进行算术(位)OR(|)操作以及 IPC_CREAT(和 IPC_EXCL)。扩展程序将为您完成此操作。它尝试附加到内存段,如果尝试失败,则尝试附加,但标志设置为 user_flag | IPC_CREAT | IPC_EXCL。
确切的代码(shm_flag 是函数的第三个参数)
如果 ((shm_id = shmget(shm_key, 0, 0)) < 0) {
如果 (shm_size < sizeof(sysvshm_chunk_head)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "键 0x%x 失败:内存大小太小", shm_key);
efree(shm_list_ptr);
RETURN_FALSE;
}
如果 ((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;
}
}
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
eric at superstats dot com
25 年前
对象在 shm_put_var 中以序列化方式存储,因此要查找 memsize 的值,可以使用 strlen(serialize($object_to_store_in_shm))。
bobhairgrove
6 年前
在“注释”部分,说明了从 PHP 5.3.0 开始,可以将 shm_attach() 返回的资源 ID 转换为整数;但是,这将与在 Linux 上从本机 SysV 函数 shmget() 返回的 shmid 值不同(正如我所希望的那样)。返回的整数仅仅是资源的 ID 号,据我所知,在这些函数的上下文之外,它完全没用。

为了验证这一点,可以使用 shm_attach()(或 shmop_open())分配内存块,打印出返回的资源,并将其与在 Linux(或其他 *nix 操作系统)上在终端中运行“ipcs”时“shmid”列中的值进行比较。
somepay at gmail dot com
14 年前
在我的日志中,我发现了字符串
"shm_attach(): failed for key 0x366f: No space left on device"
但在 php.net 和 google 上没有找到任何关于此的建议。

所以问题是如何释放 shm_attach 使用的内存。
首先查看(Linux)分配了多少段,使用
:~# ipcs -mu
然后
限制
:~# ipcs -ml

要删除段,请使用
:~# ipcrm -m [shmid]

否则,您可以重新启动服务器,或根据上述命令启动 sh 脚本。

为避免“No space left on device” 的问题,请始终在调用 shm_attach() 后使用
shm_remove() & shm_detach()。
koester at x-itec dot de
23 年前
在 FreeBSD 上,4194304 表示 4 MB,而不是 4 GB 的共享内存。如果需要,您可以在运行时增加共享内存。
info at tristat dot org
13 年前
您可能期望 SHM 函数比从 MySQL 操作构建变量快得多。不幸的是,对于大型多维数组而言,情况并非如此。我测试了使用 SHM 的二维关联数组的写入、读取、更新和删除操作,并与使用 MySQL 的类和使用 SHMOP 函数的类(大部分偏移量准确的序列化)进行了比较。这两个类都生成了与 SHM 存储的相同的数组。与 SHMOP 不同,SHM 的性能随着数组变大而显著下降。在 2000 条指令中,$array[$row][$key] 有 200 行和 5 个键,即使是 MySql 也比 SHM 快。这可能是由于 SHM 处理任何类型的数组并内部使用 PHP 强大的 serialize() 函数。
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 \"This is no 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;
}
?>
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 可能只是实现了标题填充,但这肯定没有坏处。
php at cytrax dot de
22 年前
共享内存所需的内存大小(在 Linux 系统上测试)可以使用以下方法计算:

对于要存储的每个变量:24 字节
+ 序列化变量大小(见下文)按 4 字节对齐
+ 16 字节

对于使用相同键更新变量,旧变量的内存将需要额外分配。
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