多进程通信的时候,会涉及到共享内存。
shmop_open()
创建或打开一个内存块
PHP_FUNCTION(shmop_open)
{
long key, mode, size;
struct php_shmop *shmop;
struct shmid_ds shm;
int rsid;
char *flags;
int flags_len;
//解析传PHP进来的参数
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lsll", &key, &flags, &flags_len, &mode, &size) == FAILURE) {
return;
}
if (flags_len != 1) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s is not a valid flag", flags);
RETURN_FALSE;
}
//创建一个共享内存块
shmop = emalloc(sizeof(struct php_shmop));
//初始化共享内存块
memset(shmop, 0, sizeof(struct php_shmop));
shmop->key = key;
shmop->shmflg |= mode;
switch (flags[0])
{
case 'a':
shmop->shmatflg |= SHM_RDONLY;
break;
case 'c':
shmop->shmflg |= IPC_CREAT;
shmop->size = size;
break;
case 'n':
shmop->shmflg |= (IPC_CREAT | IPC_EXCL);
shmop->size = size;
break;
case 'w':
/* noop
shm segment is being opened for read & write
will fail if segment does not exist
*/
break;
default:
php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid access mode");
goto err;
}
if (shmop->shmflg & IPC_CREAT && shmop->size < 1) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Shared memory segment size must be greater than zero");
goto err;
}
//C语言shmget(得到一个共享内存标识符或创建一个共享内存对象)
shmop->shmid = shmget(shmop->key, shmop->size, shmop->shmflg);
if (shmop->shmid == -1) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to attach or create shared memory segment");
goto err;
}
//shmctl(共享内存管理) IPC_STAT 获取内存状态
if (shmctl(shmop->shmid, IPC_STAT, &shm)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to get shared memory segment information");
goto err;
}
//shmat(把共享内存块对象映射到调用进程的地址空间) 通俗的来说,用来标识是哪块进程在使用。
shmop->addr = shmat(shmop->shmid, 0, shmop->shmatflg);
if (shmop->addr == (char*) -1) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to attach to shared memory segment");
goto err;
}
shmop->size = shm.shm_segsz;
//将内存块插入到zend资源列表(后续继续说)
rsid = zend_list_insert(shmop, shm_type TSRMLS_CC);
RETURN_LONG(rsid);
err:
efree(shmop);
RETURN_FALSE;
}
解释一下shmget()、shmat()
int shmget(key_t key, size_t size, int shmflg)
得到一个共享内存标识符或创建一个共享内存对象并返回共享内存标识符
<key>
0(IPC_PRIVATE):会建立新共享内存对象
大于0的32位整数:视参数shmflg来确定操作。通常要求此值来源于ftok返回的IPC键值
<size>
大于0的整数:新建的共享内存大小,以字节为单位
0:只获取共享内存时指定为0
<shmflg>
0:取共享内存标识符,若不存在则函数会报错
IPC_CREAT:当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符
IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的共享内存,则新建一个消息队列;如果存在这样的共享内存则报错
void *shmat(int shmid, const void *shmaddr, int shmflg)
连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问
msqid
共享内存标识符
shmaddr
指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置
shmflg
SHM_RDONLY:为只读模式,其他为读写模式
shmop_read()函数
读取内存的里面的数据
PHP_FUNCTION(shmop_read)
{
long shmid, start, count;
struct php_shmop *shmop;
int type;
char *startaddr;
int bytes;
char *return_string;
//解析出PHP函数传入的参数
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lll", &shmid, &start, &count) == FAILURE) {
return;
}
PHP_SHMOP_GET_RES
if (start < 0 || start > shmop->size) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "start is out of range");
RETURN_FALSE;
}
if (count < 0 || start > (INT_MAX - count) || start + count > shmop->size) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "count is out of range");
RETURN_FALSE;
}
//获取共享内存的地址
startaddr = shmop->addr + start;
bytes = count ? count : shmop->size - start;
//分配一个内存空间
return_string = emalloc(bytes+1);
//开始从指定的位置拷贝数据,
//return_string 是返回值
//startaddr 开始地址
//bytes 要读的字节数
memcpy(return_string, startaddr, bytes);
return_string[bytes] = 0;
RETURN_STRINGL(return_string, bytes, 0);
}
shmop_write()函数
往一个内存块里面写数据
PHP_FUNCTION(shmop_write)
{
struct php_shmop *shmop;
int type;
int writesize;
long shmid, offset;
char *data;
int data_len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lsl", &shmid, &data, &data_len, &offset) == FAILURE) {
return;
}
PHP_SHMOP_GET_RES
if ((shmop->shmatflg & SHM_RDONLY) == SHM_RDONLY) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "trying to write to a read only segment");
RETURN_FALSE;
}
if (offset < 0 || offset > shmop->size) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "offset out of range");
RETURN_FALSE;
}
//判断共享空间是否满足大小,防止写入溢出
writesize = (data_len < shmop->size - offset) ? data_len : shmop->size - offset;
//开始往共享内存里面进行写入
//shmop->addr 是共享内存的地址
//offset 是写入的偏移量
//writesize 写入数据的多少
memcpy(shmop->addr + offset, data, writesize);
RETURN_LONG(writesize);
}
由上面的两个函数的源码看来,其实都是共用了一个函数memcpy()
,这个函数我们可以在源码里面的main/php.h
追踪到。
define memcpy(d, s, n) bcopy((s), (d), (n))
可以看出,是对C函数bcopy()的封装。
解释bcopy()
原型:void bcopy(const void *src, void *dest, int n)
用法:#include <string.h>
功能:将字符串src的前n个字节复制到dest中。
shmop_size()函数
获取内存块的大小
/* {{{ proto int shmop_size (int shmid)
returns the shm size */
PHP_FUNCTION(shmop_size)
{
long shmid;
struct php_shmop *shmop;
int type;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &shmid) == FAILURE) {
return;
}
PHP_SHMOP_GET_RES
//直接获取结构体的size值
RETURN_LONG(shmop->size);
}
shmop_delete()函数
删除一个内存块
PHP_FUNCTION(shmop_delete)
{
long shmid;
struct php_shmop *shmop;
int type;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &shmid) == FAILURE) {
return;
}
PHP_SHMOP_GET_RES
//本质是对C语言函数的shmctl()的封装
//IPC_RMID 这常量表示删除共享空间
if (shmctl(shmop->shmid, IPC_RMID, NULL)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "can't mark segment for deletion (are you the owner?)");
RETURN_FALSE;
}
RETURN_TRUE;
}
解释一下shmctl()函数
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
<msqid> 共享内存标识符
<cmd>
IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中
IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、 mode复制到共享内存的shmid_ds结构内
IPC_RMID:删除这片共享内存
<buf> 共享内存管理结构体
shmop_close()
关闭一个内存块
PHP_FUNCTION(shmop_close)
{
long shmid;
struct php_shmop *shmop;
int type;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &shmid) == FAILURE) {
return;
}
PHP_SHMOP_GET_RES
//将一个内存块从当前进程的符号表删除
zend_list_delete(shmid);
}