【Linux】24.进程间通信(3)

news/2025/2/12 17:42:20/

文章目录

    • 3.6 systemv共享内存
      • 3.6.1 共享内存函数
      • 3.6.3 一个简单的共享内存代码实现
      • 3.6.4 一个复杂的共享内存代码实现
      • 3.6.4 key和shmid的主要区别:
    • 3.7 systemv消息队列(了解)
    • 3.8 systemv信号量(了解)
      • 进程互斥
      • 四个问题
      • 理解信号量


3.6 systemv共享内存

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。

进程间通信的本质是:先让不同的进程,看到同一份资源。

72881d84255fc303b20a3ab2711e9853

如果要释放共享内存:要去除关联,释放共享内存

上面的操作都是进程直接做的吗?

不是。直接由操作系统来做。

共享内存的生命周期是随内核的。

用户不主动关闭,共享内存会一直存在。除非内核重启(用户释放)


生成IPC(进程间通信)的key值:

函数原型:

key_t ftok(const char *pathname, int proj_id);

参数含义:

  1. pathname:一个已存在的文件路径
    • 必须是一个已存在的文件或目录的路径
    • 用于生成唯一的 key
    • 程序需要有该路径的访问权限
  2. proj_id:项目标识符
    • 项目标识符,用于区分不同的IPC资源
    • 只有低8位有效(1-255
    • 通常使用字符或数字

返回值:

  • 成功:成功:返回一个非负的 key 值
  • 失败:返回-1

3.6.1 共享内存函数

shmget函数

功能:用来创建共享内存
原型int shmget(key_t key, size_t size, int shmflg);
参数key:这个共享内存段名字size:共享内存大小shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

shmat函数

功能:将共享内存段连接到进程地址空间
原型void *shmat(int shmid, const void *shmaddr, int shmflg);
参数shmid: 共享内存标识shmaddr:指定连接的地址shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1

说明:

shmaddr为NULL,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - (shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

shmdt函数

功能:将共享内存段与当前进程脱离
原型int shmdt(const void *shmaddr);
参数shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

shmctl函数

功能:用于控制共享内存
原型int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数shmid:由shmget返回的共享内存标识码cmd:将要采取的动作(有三个可取值)#define IPC_STAT    2   // 获取共享内存状态#define IPC_SET     1   // 设置共享内存状态#define IPC_RMID    0   // 删除共享内存段buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

981fea39a7c0c97a0551b9f2613d1954

  1. shmget - 创建/获取共享内存
int shmid = shmget(key, 1024, IPC_CREAT | 0666);
  • 相当于"申请"一块共享内存
  • 类比文件操作中的 open 创建文件
  • 返回共享内存标识符(shmid),用于后续操作
  1. shmat - 挂载/连接共享内存
void *addr = shmat(shmid, NULL, 0);
  • 将共享内存映射到进程的地址空间
  • 类比把硬盘上的文件加载到内存
  • 返回可以直接操作的内存指针
  1. shmdt - 断开连接
shmdt(addr);
  • 解除进程与共享内存的映射关系
  • 类比关闭打开的文件
  • 不会删除共享内存,只是当前进程不再使用
  1. shmctl - 控制共享内存
shmctl(shmid, IPC_RMID, NULL);  // 删除共享内存
  • 用于删除或管理共享内存
  • 类比文件的删除、权限修改等操作

3.6.3 一个简单的共享内存代码实现

写进程 (writer.cpp):

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>#define SHM_SIZE 1024int main() {// 1. 生成keykey_t key = ftok(".", 'x');// 这里的 "." 表示当前目录必须是一个存在且可访问的路径// 这里的 'x' 是一个字符,会被转换为8位整数,范围是1-255,只有低8位有效if(key == -1) {std::cout << "ftok失败" << std::endl;return 1;}// 2. 创建共享内存int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);if(shmid == -1) {std::cout << "shmget失败" << std::endl;return 1;}// 3. 连接共享内存void* shmaddr = shmat(shmid, NULL, 0);if(shmaddr == (void*)-1) { //(void*)-1 是 shmat 失败时的返回值,等同于 MAP_FAILED 或 -1std::cout << "shmat失败" << std::endl;return 1;}// 4. 写入数据const char* message = "Hello from shared memory!";strcpy((char*)shmaddr, message); // shmaddr 是共享内存的起始地址// (char*) 是类型转换,将 void* 转换为 char*// strcpy 将字符串复制到共享内存中std::cout << "写入数据: " << message << std::endl;// 5. 分离共享内存shmdt(shmaddr);return 0;
}

读进程 (reader.cpp):

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>#define SHM_SIZE 1024int main() {// 1. 生成相同的keykey_t key = ftok(".", 'x');if(key == -1) {std::cout << "ftok失败" << std::endl;return 1;}// 2. 获取共享内存int shmid = shmget(key, SHM_SIZE, 0666);if(shmid == -1) {std::cout << "shmget失败" << std::endl;return 1;}// 3. 连接共享内存void* shmaddr = shmat(shmid, NULL, 0);if(shmaddr == (void*)-1) {std::cout << "shmat失败" << std::endl;return 1;}// 4. 读取数据std::cout << "读取数据: " << (char*)shmaddr << std::endl;// 5. 分离共享内存shmdt(shmaddr);// 6. 删除共享内存shmctl(shmid, IPC_RMID, NULL);return 0;
}

使用方法:

  1. 编译:
g++ writer.cpp -o writer
g++ reader.cpp -o reader
  1. 运行:
# 终端1
./writer# 终端2
./reader

主要函数说明:

  1. ftok(): 生成IPC键值
  2. shmget(): 创建或获取共享内存段
  3. shmat(): 连接共享内存段到进程地址空间
  4. shmdt(): 断开共享内存段连接
  5. shmctl(): 控制共享内存段(如删除)

执行时序:

写进程                     共享内存                      读进程|                          |                           ||                          |                           ||---ftok(".", 'x')         |                           ||生成key                    |                           ||                          |                           ||---shmget()               |                           ||创建共享内存--------------->|                           ||                          |                           ||---shmat()                |                           ||连接共享内存<-------------->|                           ||                          |                           ||---strcpy()               |                           ||写入数据------------------>|                           ||                          |                           ||---shmdt()                |                           ||断开连接------------------>|                           ||                          |                           ||                          |   ftok(".", 'x')----------||                          |   生成相同的key             ||                          |                           ||                          |   shmget()----------------||                          |<--获取共享内存              ||                          |                           ||                          |   shmat()-----------------||                          |<->连接共享内存              ||                          |                           ||                          |   读取数据-----------------||                          |-->读取内容                 ||                          |                           ||                          |   shmdt()-----------------||                          |<--断开连接                 ||                          |                           ||                          |   shmctl()----------------||                          |x--删除共享内存              ||                          |                           |

3.6.4 一个复杂的共享内存代码实现

makefile

.PHONY:all
all:processa processbprocessa:processa.ccg++ -o $@ $^ -g -std=c++11
processb:processb.ccg++ -o $@ $^ -g -std=c++11.PHONY:clean
clean:rm -f processa processb

log.hpp

#pragma once  // 防止头文件重复包含// 包含必要的系统头文件
#include <iostream>
#include <time.h>
#include <stdarg.h>  // 用于可变参数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>#define SIZE 1024  // 缓冲区大小// 定义日志级别
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4// 定义日志输出方式
#define Screen 1      // 输出到屏幕
#define Onefile 2     // 输出到单个文件
#define Classfile 3   // 根据日志级别输出到不同文件#define LogFile "log.txt"  // 默认日志文件名class Log
{public:Log(){printMethod = Screen;  // 默认输出到屏幕path = "./log/";      // 默认日志路径}// 设置日志输出方式void Enable(int method){printMethod = method;}// 将日志级别转换为字符串std::string levelToString(int level){switch (level){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}}// 根据不同的输出方式打印日志void printLog(int level, const std::string &logtxt){switch (printMethod){case Screen:std::cout << logtxt << std::endl;break;case Onefile:printOneFile(LogFile, logtxt);break;case Classfile:printClassFile(level, logtxt);break;default:break;}}// 输出到单个文件void printOneFile(const std::string &logname, const std::string &logtxt){std::string _logname = path + logname;int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);if (fd < 0)return;write(fd, logtxt.c_str(), logtxt.size());close(fd);}// 根据日志级别输出到不同文件void printClassFile(int level, const std::string &logtxt){std::string filename = LogFile;filename += ".";filename += levelToString(level);printOneFile(filename, logtxt);}// 重载函数调用运算符,支持可变参数的日志打印void operator()(int level, const char *format, ...){// 获取当前时间time_t t = time(nullptr);struct tm *ctime = localtime(&t);char leftbuffer[SIZE];// 格式化时间和日志级别信息snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);// 处理可变参数va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);// 组合完整的日志文本char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);printLog(level, logtxt);}~Log(){}// void logmessage(int level, const char *format, ...)// {//     time_t t = time(nullptr);//     struct tm *ctime = localtime(&t);//     char leftbuffer[SIZE];//     snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),//              ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,//              ctime->tm_hour, ctime->tm_min, ctime->tm_sec);//     // va_list s;//     // va_start(s, format);//     char rightbuffer[SIZE];//     vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);//     // va_end(s);//     // 格式:默认部分+自定义部分//     char logtxt[SIZE * 2];//     snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);//     // printf("%s", logtxt); // 暂时打印//     printLog(level, logtxt);// }private:int printMethod;       // 日志输出方式std::string path;      // 日志文件路径
};// int sum(int n, ...)
// {
//     va_list s; // char*
//     va_start(s, n);//     int sum = 0;
//     while(n)
//     {
//         sum += va_arg(s, int); // printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123);
//         n--;
//     }//     va_end(s); //s = NULL
//     return sum;
// }

comm.hpp

#ifndef __COMM_HPP__
#define __COMM_HPP__// 包含必要的头文件
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <sys/ipc.h>    // 系统IPC功能
#include <sys/shm.h>    // 共享内存
#include <sys/types.h>
#include <sys/stat.h>#include "log.hpp"using namespace std;Log mylog;  // 全局日志对象// 共享内存的大小一般建议是4096的整数倍
const int size = 4096; 
const string pathname="/home/ydk_108";  // 用于生成key的路径
const int proj_id = 0x6666;         // 项目ID// 获取IPC key
key_t GetKey()
{key_t k = ftok(pathname.c_str(), proj_id);if(k < 0){mylog(Fatal, "ftok error: %s", strerror(errno));exit(1);}mylog(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){mylog(Fatal, "create share memory error: %s", strerror(errno));exit(2);}mylog(Info, "create share memory success, shmid: %d", shmid);return shmid;
}// 创建新的共享内存
int CreateShm()
{return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}// 获取已存在的共享内存
int GetShm()
{return GetShareMemHelper(IPC_CREAT); 
}#define FIFO_FILE "./myfifo"  // 命名管道文件路径
#define MODE 0664             // 文件权限// 错误码枚举
enum
{FIFO_CREATE_ERR = 1,FIFO_DELETE_ERR,FIFO_OPEN_ERR
};// 初始化类,用于创建和清理命名管道
class Init
{
public:Init(){// 先尝试删除已存在的管道文件unlink(FIFO_FILE);  // 忽略返回值,因为文件可能不存在// 创建命名管道int n = mkfifo(FIFO_FILE, MODE);if (n == -1){perror("mkfifo");exit(FIFO_CREATE_ERR);}}~Init(){// 删除命名管道int m = unlink(FIFO_FILE);if (m == -1){perror("unlink");exit(FIFO_DELETE_ERR);}}
};#endif

processa.cc

#include "comm.hpp"extern Log mylog;int main()
{Init init;  // 创建命名管道int shmid = CreateShm();  // 创建共享内存// 将共享内存映射到进程地址空间char *shmaddr = (char*)shmat(shmid, nullptr, 0);// 以只读方式打开命名管道int fd = open(FIFO_FILE, O_RDONLY);if (fd < 0){mylog(Fatal, "error string: %s, error code: %d", strerror(errno), errno);exit(FIFO_OPEN_ERR);}struct shmid_ds shmds;while(true){// 读取管道中的通知char c;ssize_t s = read(fd, &c, 1);if(s == 0) break;  // 写端关闭else if(s < 0) break;  // 读取错误// 直接从共享内存读取数据cout << "client say@ " << shmaddr << endl;sleep(1);// 获取并打印共享内存的状态信息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;}// 清理资源shmdt(shmaddr);  // 解除内存映射shmctl(shmid, IPC_RMID, nullptr);  // 删除共享内存close(fd);  // 关闭管道return 0;
}

processb.cc

#include "comm.hpp"int main()
{int shmid = GetShm();  // 获取已存在的共享内存// 将共享内存映射到进程地址空间char *shmaddr = (char*)shmat(shmid, nullptr, 0);// 以只写方式打开命名管道int fd = open(FIFO_FILE, O_WRONLY);if (fd < 0){mylog(Fatal, "error string: %s, error code: %d", strerror(errno), errno);exit(FIFO_OPEN_ERR);}while(true){cout << "Please Enter@ ";// 读取用户输入并直接写入共享内存fgets(shmaddr, 4096, stdin);// 向管道写入一个字符,通知接收端write(fd, "c", 1);}// 清理资源shmdt(shmaddr);  // 解除内存映射close(fd);  // 关闭管道return 0;
}

打印:

c297c18f8ca1b2702ec33357f46ef20e

关于key

  1. key是一个数字,这个数字是几,不重要。关键在于它必须在内核中具有唯一性,能够让不同的进程进行唯一性标识。

  2. 第一个进程可以通过kev创建共享内存,第二个之后的进程,只要拿着同一个key就可以和第一个进程看到同一个共享内存了。

  3. 对于一个已经创建好的共享内存,key在哪?

    key在共享内存的描述对象中。

  4. 第一次创建的时候,必须有一个key了。怎么有?

  5. key 类似路径唯一


3.6.4 key和shmid的主要区别:

  1. 基本概念

    key:是一个用户定义的值,用来标识共享内存段的访问权限,类似于文件路径名

    shmid:是系统分配的共享内存段标识符,是系统内部使用的唯一标识符

  2. 使用时机

    key:在创建或获取共享内存时使用

    shmid:在共享内存创建后由系统返回,后续操作都使用shmid

  3. 代码示例

#include <sys/shm.h>// 使用key创建共享内存
key_t key = ftok("/tmp", 'A');  // 创建key
int shmid = shmget(key, 1024, IPC_CREAT | 0666); // 用key获取shmid// 后续操作使用shmid
void *addr = shmat(shmid, NULL, 0);  // 连接共享内存
shmctl(shmid, IPC_RMID, NULL);  // 删除共享内存
  1. 关系

    一个key可以对应一个shmid

    key是用户层面的标识

    shmid是系统层面的标识

  2. 生命周期

    key:可以重复使用

    shmid:随共享内存段的存在而存在,删除后失效


3.7 systemv消息队列(了解)

消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法

每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值

特性方面:IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核

通过消息队列想让AB进行通信那么首先要让不同进程看到同一份资源。

  1. 必须让不同进程看到同一个队列

  2. 允许不同的进程,向内核中发送带类型的数据块(通过类型来区分数据块是属于谁的)

A进程可以把它的数据块放到队列中,B进程可以把它的数据块放到队列中。

A进程就可以从队列中拿B进程给A进程发的数据块,反之亦然。

可以让A进程 <--以数据块的形式发送数据--> B进程。

2afaf5fef54063425c71bea5f3ab0008


3.8 systemv信号量(了解)

信号量主要用于同步和互斥的。什么是同步和互斥?

进程互斥

由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥。

系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。

在进程中涉及到互斥资源的程序段叫临界区。

特性方面:IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核


四个问题

当我们的A正在写入,写入了一部分,就被B拿走了,导致双方发和收的数据不完整 – 数据不一致问题

  1. A B看到的同一份资源,共享资源,如果不加保护,会导致数据不一致问题

  2. 我们可以通过“加锁”形成互斥访问 – 任何时刻,只允许一个执行流访问共享资源 – 互斥

  3. 共享的,任何时刻只允许一个执行流访问(就是执行访问代码)的资源我们一般称为:临界资源(一般是操作系统和用户维护的内存空间)(管道也是临界资源)

  4. 举例:100行代码,5~10行代码才在访问临界资源。 我们访问临界资源的代码在:临界区


理解信号量

信号量的本质是一个计数器。

描述临界资源数量的多少。

  1. 申请计数器成功,就表示我具有访问资源的权限了

  2. 申请了计数器资源,我当前访问我要的资源了吗?没有。申请了计数器资源是对资源的预订机制

  3. 计数器可以有效保证进入共享资源的执行流的数量

  4. 所以每一个执行流,想访问共享资源中的一部分的时候,不是直接访问,而是先申请计数器资源。

程序员把这个"计数器",叫做信号量。

申请信号量,本质是对计数器--,P操作

释放资源,释放信号量,本质是对计数器进行++操作,V操作

申请和释放PV操作是原子性操作。

要么不做,要做就做完 — 两态的。没有“正在做”这样的概念。

信号量本质是一把计数器,PV操作,原子的。

执行流申请资源,必须先申请信号量资源,得到信号量之后,才能访问临界资源。

信号量值1,0两态的,二元信号量,就是互斥功能

申请信号量的本质:是对临界资源的预订机制。

信号量凭什么是进程间通信的一种?

  1. 通信不仅仅是通信数据,双方互相协同也是。

  2. 要协同,本质也是通信,信号量首先要被所有的通信进程看到。

mmap函数 – 也是共享内存。(仅作了解)

后面学习的信号和这里的信号量没有任何关系。


http://www.ppmy.cn/news/1570733.html

相关文章

【论文翻译】DeepSeek-V3论文翻译——DeepSeek-V3 Technical Report——第一部分:引言与模型架构

论文原文链接&#xff1a;DeepSeek-V3/DeepSeek_V3.pdf at main deepseek-ai/DeepSeek-V3 GitHub 特别声明&#xff0c;本文不做任何商业用途&#xff0c;仅作为个人学习相关论文的翻译记录。本文对原文内容直译&#xff0c;一切以论文原文内容为准&#xff0c;对原文作者表示…

使用requestAnimationFrame减少浏览器重绘

文章目录 介绍使用使用rAF前使用rAF后 介绍 在屏幕中&#xff0c;浏览器通常都以60FPS&#xff08;1/60 s&#xff09;每帧更新屏幕&#xff0c;但是当前端绑定了一些高频事件&#xff0c;如鼠标移动&#xff0c;屏幕滚动、触摸滑动等时&#xff0c;在一帧的周期内&#xff0c;…

机器学习 - 需要了解的条件概率、高斯分布、似然函数

似然函数是连接数据与参数的桥梁&#xff0c;通过“数据反推参数”的逆向思维&#xff0c;成为统计推断的核心工具。理解它的关键在于区分“参数固定时数据的概率”与“数据固定时参数的合理性”&#xff0c;这种视角转换是掌握现代统计学和机器学习的基础。 一、在学习似然函…

深度学习与搜索引擎优化的结合:DeepSeek的创新与探索

目录 引言 1. 传统搜索引擎的局限性 2. 深度学习在搜索引擎中的作用 3. DeepSeek 实现搜索引擎优化的关键技术 3.1 神经网络与搜索引擎优化 3.2 自然语言处理与查询理解 3.3 深度强化学习与搜索结果排序 4. DeepSeek的深度学习架构 4.1 查询解析与语义理解 4.2 搜索排名与相…

DeepSeek R1 Distill Llama 70B(免费版)API使用详解

DeepSeek R1 Distill Llama 70B&#xff08;免费版&#xff09;API使用详解 在人工智能领域&#xff0c;随着技术的不断进步&#xff0c;各种新的模型和应用如雨后春笋般涌现。今天&#xff0c;我们要为大家介绍的是OpenRouter平台上提供的DeepSeek R1 Distill Llama 70B&…

DeepSeek迁移学习与预训练模型应用

迁移学习是一种利用预训练模型的知识来加速新任务训练的技术。通过迁移学习,我们可以在数据量有限的情况下,快速构建高性能的模型。DeepSeek提供了丰富的预训练模型和迁移学习工具,帮助我们高效地完成新任务的训练。本文将详细介绍如何使用DeepSeek进行迁移学习,并通过代码…

C语言时间相关宏定义

在C语言中&#xff0c;预处理器提供了一些与时间相关的宏定义&#xff0c;用于在编译时获取日期、时间等信息。除了 __TIMESTAMP__ 和 __DATE__&#xff0c;还有以下相关的宏定义&#xff1a; __DATE__ 当前编译日期的字符串&#xff0c;格式为 "Mmm dd yyyy"&#x…

上传文件防木马函数

项目环境&#xff1a;TP6、TP5 问题&#xff1a;解决旧项目中上传上来的文件校验不严格。导致会有木马文件入侵的情况发生。除了上篇博文中提及的限制上传文件存储的目录不可执行php文件外。仍需在入口处严格检验上传文件的类型&#xff0c;排除php类可执行文件上传。 解决&a…