【Linux】-进程间通信-共享内存(SystemV),详解接口函数以及原理(使用管道处理同步互斥机制)

在这里插入图片描述

💖作者:小树苗渴望变成参天大树🎈
🎉作者宣言:认真写好每一篇博客💤
🎊作者gitee:gitee✨
💞作者专栏:C语言,数据结构初阶,Linux,C++ 动态规划算法🎄
如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!

文章目录

  • 前言
  • 一、共享内存的原理
  • 二、直接代码
    • 2.1关于共享内存的四大接口
    • 2.2如何通信
  • 三、扩展知识
    • 3.1 看看维护共享内存的结构体属性
    • 3.2 使用管道来实现同步互斥机制
  • 四、总结


前言

今天我们来讲进程间通信的的另一个通信方式,在第一篇讲解进程间通信的博客中,博主就提到了SystemV标准的通信方式,我们前面讲解的匿名管道和命名管道都是基于文件的,但是共享内存不是基于文件的,他的所有进程间通信最快的,因为他的拷贝少,共享内存的难点就在于他的接口多,复杂,因为SystemV标准下不止一个共享内存,还有消息队列和信号量,都需要类似的接口,为了可以更好的复用接口函数,接下来博主就来带大家学习共享内存。


讲解逻辑:

  1. 直接原理,讲解周边问题
  2. 通过原理,写一部分代码,认识系统接口,进行测试
  3. 扩展代码去讲解

一、共享内存的原理

使用共享内存的目的是让进程间进行通信,但是进程间通信的本质是让不同的进程看到同一份资源,由共享内存这个名字可知,这篇共享的资源是一块内存,计算机中我们一般由的地址要不是虚拟地址,要不是物理地址,想形成可执行程序里面的地址我们目前不谈,而虚拟地址是每个进程特有的,所以我们猜测这块共享内存是物理内存的一块,因为有了前面的两次通信方式的铺垫,我们已经慢慢找到规律了,那博主就以一份图给大家讲解一下共享内存的原理。
在这里插入图片描述

共享内存的原理很简单,就上幅这个图片,但是博主要讲一些周边问题:

  1. 释放共享内存,先去挂接,再释放内存,是相反的操作
  2. 上面的操作都是进程直接做的吗??不是,是直接由os去做的,原因涉及到物理内存了。
  3. 那既然有os去操作的,那么我们去创建,使用或者释放都需要经过系统调用接口去让os帮助我们实现
  4. 我们的不同进程通过共享内存进行通信,另外的进程也需要通过共享内存来进行通信,那么共享内存就不止一块,由许多快,那么这块共享内存都是需要管理起来的,所以先描述再组织,就对应我们上图的struct结构体。里面存放的是对共享内存的管理属性。

所以我们一会对共享内存的使用里面肯定会涉及到这个结构体里面的属性,等会遇到了一个讲一个,现在都讲解出来读者大概率不会理解。

二、直接代码

我们通过刚才的原理分析,而且这些操作是需要通过系统调用接口的,所以我们一步步的来介绍这些系统调用接口。

2.1关于共享内存的四大接口

一、申请共享内存接口
在这里插入图片描述

  1. 返回值(用户层)shmid:此函数申请一块共享内存,返回共享内存标识符,可以先理解为和文件描述符唯一标志文件一样的道理。
  2. 第二个参数,是申请共享内存的大小。单位是字节
  3. 第三个参数:共享内存是为了给不同的进程使用,那么使用这块内存之前,只要由一个进程创建,其他进程拿来用就行了,那这个参数就是控制对共享内存的权限操作,来看我们自己要掌握的权限在这里插入图片描述
    (1)IPC_CREAT:(单独使用)如果你申请的共享内存不存在就创建,存在就获取返回
    (2)IPC_CREAT | IPC_EXCL:如果你申请的共享内存不存在就创建,存在就报错,这是保证了你创建的共享内存是最新的。IPC_EXCL不单独使用
    (3)第三个就是传我们对应的权限,如0666

上面的方式我们再讲解文件操作的时候就讲解过了,write函数里面需要传这样的参数,这些大写字母起始就是对应的宏。

  1. 第一个参数:通过第三个参数,我们怎么知道这个共享内存存不存在,就好比你怎么保证让不同的进程看到同一份共享内存是一样的,此时就有了我们的第一个参数,接下来谈谈这个key。
    (1)key是一个数字,这个数字是多少不重要。关键在于他必须再内核中具有唯一性,能够让不同的进程进程唯一标识
    (2)第一个进程可以通过key来创建共享内存。第二个进程之后的进程,只要拿着这个key就可以和第一个进程看到同一个共享内存了
    (3)对于一个已经创好的共享内存,key在哪??大家还记得一个说管理共享内存的结构体吗,key就在共享内存的描述对象里
    (4)通过第一点想要key去唯一标识共享内存,大家再回想一下命名管道是怎么唯一标识的,是不是通过就和文件名,所以这个key应该也类似于命名管道的标识方式。
    (5)通过第二点,我们通过key创建共享内存,那么第一次创建的时候,这个key怎么有???

我们总结出四个结论和一个问题,问题来到了这个key一开始时怎么产生的了,按照第四点的结论,我们来介绍一下这个函数ftok
在这里插入图片描述
第一个参数:路径这个随便写
第二个参数,这个是工程id,我们可以随便去指定是一个数字
返回值(内核层):是一个共享内存标识符

我们上面的两个参数都是由用户自己去定义的,所以可能会和系统中的key产生冲突,这个函数是通过一个算法将两个参数进行运算的出来的这样的一个key,每次生成的结果都是不一样的,不是你每次传的参数一样计算出来的结果就是一样的。这样为什么就可以做到key是唯一的呢,我们的路径是唯一的,而且第二个参数是我们自己传,大概率也是唯一的,这样就导致我们的key是唯一的,而且一旦创建这个key就是这个共享内存所独有了,如果再生成这个key,只能获取,不会再创建一个新的了
为什么key不由os自己创建呢,我们自己创建还有可能造成key冲突的问题??
(1)再谈谈key的时候的第二点我们知道这个我们通过创建共享内存是由一个进程去创建另一个进程去使用就可以,如果这个key是os生成的,创建好的共享内存,那另一个没有关系的进程怎么获取这块共享内存,因为共享内存不是唯一的,所以os里面的key也不是唯一的,所以没有办法给另一个进程让他获取啊,有的人说传给另一个进程,这样就出现蛋生鸡的问题,另一个进程要key才能进行通信,但是要key必须先通信,如果共享内存的个数是唯一的,那么可以让os自己生成,大家自己理解一下
(2)这个key的获取可以说是用户的约定,和哪个进程通信只有用户知道,就是程序员知道,两个进程使用ftok这个相同的方式就可以获取唯一的key,因为这两个参数是唯一的
(3)有的人会说我们将系统自己生成的key通过管道传给另一个进程就可以了,答案确实可以,但是这样我们学习共享内存的成本就搞了,还要先学习管道,这样也不嫩恶搞保证共享内存是一个独立通信模块了

大家看到这里对于key的理解应该到位了,但是有一个关键的点,key vs shmid

这两个都是共享内存的标识符,他两有一个不就行了,key是内核中唯一标识的,shmid只有再进程里唯一标识的,我们操作共享共享内存的函数都是使用shmid。

通过上面的一系列分析,我们来申请一块共享内存:shmget+ftok
sham.hpp:

#ifndef __COMM_HPP__
#define __COMM_HPP__#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/stat.h>#include "log.hpp"using namespace std;Log log;const int size = 4096; 
const string pathname="/home/xdh";
const int proj_id = 0x6666;key_t GetKey()
{key_t k = ftok(pathname.c_str(), proj_id);if(k < 0){log(Fatal, "ftok error: %s", strerror(errno));exit(1);}log(Info, "ftok success, key is : 0x%x", k);return k;
}int GetShareMemHelper(int flag)
{key_t k = GetKey();int shmid = shmget(k, size, flag);if(shmid < 0){log(Fatal, "create share memory error: %s", strerror(errno));exit(2);}log(Info, "create share memory success, shmid: %d", shmid);return shmid;
}int CreateShm()//创建共享内存得到标识符shmid,进行了封装
{return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}
#endif

processa.cc:

#include"sham.hpp"
//这是进程a,有这个进程创建共享内存
int main()
{//申请共享内存int shmid=CreateShm();sleep(5);return 0;
}

在这里插入图片描述

通过结果我们发现,我们第一次运行程序的时候申请了一块共享内存获得了共享内存标识符,但是第二次运行的时候显示就存在了,我们使用ipcs -m 查看共享内存,我们得出结论,进程结束了,我们的共享内存还是存在的,共享内存的生命周期是随着内核的,不是随着进程的,通过原理图也不难理解这点,没有关闭共享内存,这也可能会造成内存泄漏,类似于malloc。

这里面我们再来研究一个点,我们申请4097个字节大小的空间看看效果
在这里插入图片描述
我们看到大小是4097,在内核里面,我们的os实际上会给我们的4096*2大小的空间,但是我们只能使用4097,这个大家要记住,所以建议还是申请4096点整数倍,折合人民币我们内存的页宽有关系,大家先不用了解。

二、.挂接共享内存:shmat函数
我们的共享内存申请好了,我们就需要将其挂接到我们的地址空间上,就是原理图上的第二步
在这里插入图片描述

  1. 第一个参数:就是传刚才使用shmget函数的返回值即可是共享内存的唯一标识符
  2. 第二个参数:指定挂接到那个位置,我们申请好了共享内存,要挂接到我们进程的地址空间的共享群位置,这么多位置总要找到一个位置的其实位置吧,这样也方便我们页表进行映射,所以需要制定,我们在这里传空指针就好了,意思让系统自己决定
  3. 第三个参数:是挂接的方式

在这里插入图片描述
我们在这里传0进去就好了

  1. 返回值:我们就是把挂接到地址空间的那块位置的首地址返回出来,让用户能拿到,进行操作,所以返回值是void需要强转,和malloc类似,失败就返回(void)-1

我们来看代码实现:

//将共享内存挂接到自己的地址空间char* shmaddr=(char*)shmat(shmid,nullptr,0);if(*shmaddr<0){log(Fatal,"shmat flase:%s",strerror(errno));exit(3);}log(Info,"shmat sucessful:%s",strerror(errno));sleep(3);

在这里插入图片描述

我们来观察一下nattch这个属性,他就是表示这块共享内存当前的挂接树,没调用这个shmat函数之前为0,调用之后为1,而且当进程退出他的挂接数自然的就减少了1

三.去掉挂接关系:shmdt
刚才是因为程序结束,挂接数减少了,但我们有时候程序没结束就像去挂接,怎么做??我们通过shmdt来去挂接,来看文档
在这里插入图片描述

这个函数非常的简单,就是传刚才挂接函数返回值就可以了,我们直接来看使用效果:
我们分析,我们3秒后创建共享内存,5秒后挂接进程,挂接数变成1,3秒后,去挂接,挂接数变成1,在3秒后程序终止,

int n=shmdt(shmaddr);if(n<0){log(Fatal,"shmdt flase:%s",strerror(errno));}log(Info,"shmdt sucessful:%s",strerror(errno));sleep(3);

在这里插入图片描述

和我们预测的一样,我们的挂接数不一定非得在程序结束才会减1

四.释放共享内存:shmctl
我们想要将我们的共享概念内存释放掉使用shmctl
在这里插入图片描述

第一个参数:共享内存唯一标识符
第三个参数:是一个描述共享内存的状态和访问权限的数据结构,也就是我们开头说的描述共享内存的结构图,看到key在里面了吧,对于这个参数我们可以传一个null,因为不需要将状态获取到,这是一个输出型参数和status一样。
第二个参数:将要采取的动作,就是对第三个参数实行什么样的操作,有三个操作

在这里插入图片描述
我们关注的是最后一个,删除共享内存

来看操作:

 int n1=shmctl(shmid,IPC_RMID,nullptr);if(n1<0){log(Fatal,"shmctl flase:%s",strerror(errno));}log(Info,"shmctl sucessful:%s",strerror(errno));sleep(3);

在这里插入图片描述

通过结果验证我们的讲解,我们也可以通过ipcrm -m +shmid来删除共享内存,这个大家下去试试,但是shmctl传进去的操作不一样,功能就不一样,如果传IPC_STAT,就可以查看属性。


我们将另一个进程也挂接到这个共享内存上吧,因为申请和释放进程a帮助我们做了,我们做的就是挂接和去挂接就可以了,来看进程b的代码:
先展示进程a的代码:

#include"sham.hpp"
//这是进程a,有这个进程创建共享内存
int main()
{sleep(3);//申请共享内存int shmid=CreateShm();sleep(5);//将共享内存挂接到自己的地址空间char* shmaddr=(char*)shmat(shmid,nullptr,0);if(*shmaddr<0){log(Fatal,"shmat flase:%s",strerror(errno));exit(3);}log(Info,"shmat sucessful:%s",strerror(errno));sleep(3);//去挂接int n=shmdt(shmaddr);if(n<0){log(Fatal,"shmdt flase:%s",strerror(errno));}log(Info,"shmdt sucessful:%s",strerror(errno));sleep(3);//释放共享内存int n1=shmctl(shmid,IPC_RMID,nullptr);if(n1<0){log(Fatal,"shmctl flase:%s",strerror(errno));}log(Info,"shmctl sucessful:%s",strerror(errno));sleep(3);return 0;
}

进程b:

#include "sham.hpp"int main()
{sleep(3);int shmid=GetShm();//这个函数在sham.hpp里面写就行了,获取shmidsleep(5);//将共享内存挂接到自己的地址空间char* shmaddr=(char*)shmat(shmid,nullptr,0);if(*shmaddr<0){log(Fatal,"shmat flase:%s",strerror(errno));exit(3);}log(Info,"shmat sucessful:%s",strerror(errno));sleep(3);//去挂接int n=shmdt(shmaddr);if(n<0){log(Fatal,"shmdt flase:%s",strerror(errno));}log(Info,"shmdt sucessful:%s",strerror(errno));sleep(3);return 0;
}

在这里插入图片描述

我们也成功看到了挂接数变成了2,上面讲解的一切都是让两个不同的进程之间看到同一份资源,还没有开始通信

2.2如何通信

我们通过上面一系列的操作终于实现我们再原理图讲的内容了,该说不说,确实太复杂的,但是这一系列的操作,让他的通信显得非常的简单,我们共享内存就是一块物理内存,映射到我们进程的地址空间上,我们程序通过这块地址空间上的地址就可以直接访问这块物理空间,此时他就很想malloc申请空间,然后去使用这块空间的方法很想,我们一起来看操作,让b写,a读

a:

while(true){cout<<"a say@"<<shmaddr<<endl;sleep(1);}

b:

while(true){cout<<"b enter@";fgets(shmaddr,4096,stdin);sleep(1);}

在这里插入图片描述

结论:

  1. 我们我们两个进程对这块空间的操作是你搞你的我搞我的,两者不受任何影响,所以说明共享内存间是没有同步互斥机制的
  2. 我们的共享内存是所有进程中通信速度最快,因为拷贝少
  3. 我们的共享内存的数据是用户自己去维护的,所以这些看到和管道有不同的地方,没有清空数据,这是需要用户自己去决定的。

但是我们确实实现了两个进程间通信了,有问题我们一会来解决。

三、扩展知识

3.1 看看维护共享内存的结构体属性

我们刚才的参数都是为了描述共享内存的,所以维护共享概念给内存的属性有哪些呢,刚才其实也大致看到了一些。
在这里插入图片描述
我们通过代码看看我们刚才提到一下属性:

再a进程把通信代码改成下面的

 int count=0;struct shmid_ds shmds;while(true){sleep(1);if(count==0){shmctl(shmid, IPC_STAT, &shmds);cout << "shm size: " << shmds.shm_segsz << endl;cout << "shm nattch: " << shmds.shm_nattch << endl;printf("shm key: 0x%x\n",  shmds.shm_perm.__key);cout << "shm mode: " << shmds.shm_perm.mode << endl;}count++;}

在这里插入图片描述

3.2 使用管道来实现同步互斥机制

我们因为目前只学了System V的共享内存,我们想要解决这个问题,还可以使用信号量,但是这个我们不做重点介绍,等有机会我们在给大家讲解信号量是怎么解决共享内存的这个缺点,我们今天,就使用管道去解决这个问题吧,因为是不相关的进程,所以使用命名管道。


在这里插入图片描述

在这里插入图片描述

这个使用管道的方法其实和共享内存是一点关系没有,之根据他会阻塞就不会执行下面的代码,这样间接控制了。我们后面会简单介绍一下信号量是怎么解决这个问题的,但是知识带大家了解一下。

四、总结

今天我们学习了共享内存,学习成本和前面两个差不多,前面是原理的铺垫大家不容易理解,但是使用简单,二共享内存有了前面的原理铺垫,理解起来不难,但是后面的使用接口对大家来说可能是一个难度,大家下去好好把四大接口函数理解一下,这对博主下一篇讲解消息队列以及信号量有很大帮助,希望大家下来可以去自己实现博主这篇博客上面的内容,我们下篇再见

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/160925.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

中低压MOSFET 2N7002T 60V 300mA 双N通道 采用SOT-523封装形式

2N7002KW小电流双N通道MOSFET&#xff0c;电压60V电流300mA&#xff0c;采用SOT-523封装形式。低Ros (on)的高密度单元设计&#xff0c;坚固可靠&#xff0c;具有高饱和电流能力&#xff0c;ESD防护门HBM2KV。可应用于直流/直流转换器&#xff0c;电池开关等产品应用上。

成为AI产品经理——AI产品经理工作全流程

一、业务背景 背景&#xff1a;日常排球训练&#xff0c;中考排球项目和排球体测项目耗费大量人力成本和时间成本。 目标&#xff1a;开发一套用于实时检测排球运动并进行排球垫球计数和姿势分析的软件。 二、产品工作流程 我们这里对于产品工作流程的关键部分进行讲解&…

「Docker」如何在苹果电脑上构建简单的Go云原生程序「MacOS」

介绍 使用Docker开发Golang云原生应用程序&#xff0c;使用Golang服务和Redis服务 注&#xff1a;写得很详细 为方便我的朋友可以看懂 环境部署 确保已经安装Go、docker等基础配置 官网下载链接直达&#xff1a;Docker官网下载 Go官网下载 操作步骤 第一步 创建一个…

汽车智能座舱/智能驾驶SOC -2

第二篇&#xff08;笔记&#xff09;。 未来智能汽车电子电气将会是集中式架构&#xff08;车载数据中心&#xff09;虚拟化技术&#xff08;提供车载数据中心灵活性和安全性&#xff09;这个几乎是毋庸置疑的了。国际大厂也否纷纷布局超算芯片和车载数据中心平台。但是演进需…

日期格式转化成星期几部署到linux显示英文

异常收集 原因&#xff1a;解决办法仰天大笑出门去&#xff0c;我辈岂是蓬蒿人 传入一个时间获取这个时间对应的是星期几&#xff0c;在开发环境&#xff08;window系统&#xff09;中显示为星期几&#xff0c;部署到服务器&#xff08;linux系统&#xff09;中会显示英文的时间…

Youtube新手运营——你需要的技巧与工具

对于有跨境意向的内容创作者或者品牌企业来说&#xff0c;YouTube是因其巨大的潜在受众群和商业价值成为最值得投入变现与营销计划的平台。 据统计&#xff0c;98% 的美国人每月访问 YouTube&#xff0c;近三分之二的人每天访问。但是&#xff0c;YouTube还远未达到过度饱和的…

Leetcode—53.最大子数组和【中等】

2023每日刷题&#xff08;三十四&#xff09; Leetcode—53.最大子数组和 前缀和算法思想 参考灵茶山艾府 实现代码 #define MAX(a, b) ((a > b) ? (a) : (b)) #define MIN(a, b) ((a < b) ? (a) : (b)) int maxSubArray(int* nums, int numsSize) {int ans INT_…

VMware 16 Pro 安装以及下载

1、下载地址&#xff1a; https://www.aliyundrive.com/s/nj3PSD4TN9G 2、安装文件 右击打开 下一步 密钥&#xff1a;ZF3R0-FHED2-M80TY-8QYGC-NPKYF 到此&#xff0c;安装完毕

postgreSQL如何快速查询大表数据量

文章目录 场景方案结果 场景 我有一个非常大的表&#xff0c;估计几百万或者几千万。 我开始使用了 select count(*) from my_table_javapub 方式&#xff0c;查询非常慢。 如何解决&#xff1f;&#xff1f;&#xff1f; 方案 如果你需要更快地获取表中的行数&#xff0c…

93.STL-系统内置仿函数

目录 算术仿函数 关系仿函数 逻辑仿函数 C 标准库中提供了一些内置的函数对象&#xff0c;也称为仿函数&#xff0c;它们通常位于 <functional> 头文件中。以下是一些常见的系统内置仿函数&#xff1a; 算术仿函数 功能描述&#xff1a; 实现四则运算其中negate是一元…

Java游戏之飞翔的小鸟

前言 飞翔的小鸟 小游戏 可以作为 java入门阶段的收尾作品 &#xff1b; 需要掌握 面向对象的使用以及了解 多线程&#xff0c;IO流&#xff0c;异常处理&#xff0c;一些java基础等相关知识。一 、游戏分析 1. 分析游戏逻辑 &#xff08;1&#xff09;先让窗口显示出来&#x…

腾讯待办导出的文件在哪找?支持打开ics文件的提醒待办工具

您使用过腾讯待办吗&#xff1f;如果您在平常使用的提醒待办工具为腾讯待办&#xff0c;想必近期您打开这款提醒待办工具时会看到提示您及时导出数据的提示。腾讯旗下的腾讯待办应用&#xff0c;应业务发展方向调整将于2023年12月20日全面停止运营并下架该应用。 面对突如其来…

Redis的主从复制及哨兵模式

一、Redis的主从复制 1.1 Redis主从复制定义 主从复制是redis实现高可用的基础&#xff0c;哨兵模式和集群都是在主从复制的基础之上实现高可用&#xff1b; 主从复制实现数据的多级备份&#xff0c;以及读写分离(主服务器负责写&#xff0c;从服务器只能读) 1.2 主从复制流…

ChatGPT 也并非万能,品牌如何搭上 AIGC「快班车」

内容即产品的时代&#xff0c;所见即所得&#xff0c;所得甚至超越所见。 无论是在公域的电商平台、社交媒体&#xff0c;还是品牌私域的官网、社群、小程序&#xff0c;品牌如果想与用户发生连接&#xff0c;内容永远是最前置的第一要素。 01 当内容被消费过&#xff0c;就…

C++虚析构和纯虚析构解决delete堆区父类指针无法调用子类的构造函数

#include<iostream> #include<string>using namespace std;//虚析构和纯虚析构 class Animal { public:Animal(){cout<<"执行Animal的构造函数"<<endl;}~Animal(){cout<<"执行Animal的析构函数"<<endl;}virtual void …

HTML新手入门笔记整理:HTML基本介绍

网页 静态页面 仅可供用户浏览&#xff0c;不具备与服务器交互的功能。 动态页面 可供用户浏览&#xff0c;具备与服务器交互的功能。 HTML HTML&#xff0c;全称HyperText Markup Language&#xff08;超文本标记语言&#xff09;,是一种用于创建网页的标准标记语言。用于…

HIS医疗项目

文章目录 医疗项目简介HIS项目介绍HIS架构解析HIS业务流程图HIS项目架构图 HIS组件解析——服务支撑 内存设置为4G或以上部署NGINX服务部署web安装JDK部署Elasticsearch安装ik中文分词器 部署rabbitmq部署MySQL服务安装MySQL服务建库、授权用户导入数据 部署Redis测试Redis 部署…

io+day5

1&#xff0c;select服务端 1 #include<myhead.h>2 3 #define PORT 8888 //端口号4 #define IP "192.168.228.165" //IP地址5 6 7 int main(int argc, const char *argv[])8 {9 //1、创建用于接受连接的套接字10 int sfd socket(…

pikachu靶场PHP反序列化漏洞

pikachu靶场PHP反序列化漏洞 源码分析 查看源代码 class S{var $test "pikachu";function __construct(){echo $this->test;} }// O:1:"S":1:{s:4:"test";s:29:"<script>alert(xss)</script>";} $html; if(isset($_PO…