Linux信号【systemV】
目录
前言
正文:
1消息队列
1.1什么是消息队列?
1.2消息队列的数据结构
1.3消息队列的相关接口
1.3.1创建
1.3.2释放
1.3.3发送
1.3.4接收
1.4消息队列补充
2.信号量
2.1什么是信号量
2.2互斥相关概念
2.3信号量的数据结构
2.4信号量相关接口
2.4.1创建
2.4.2释放
2.4.3操作
2.5信号量补充
3.深入理解 System V通信方式
总结:
前言
在 System V 通信标准中,还有一种通信方式:消息队列,以及一种实现互斥的工具:信号量;随着时代的发展,这些陈旧的标准都已经较少使用了,但作为 IPC 中的经典知识,我们可以对其做一个简单了解,扩展 IPC 的知识栈,尤其是 信号量,可以通过它,为以后多线程学习中 POSIX 信号量的学习做铺垫
正文:
1消息队列
1.1什么是消息队列?
消息队列(Message Queuing
)是一种比较特殊的通信方式,它不同于管道与共享内存那样借助一块空间进行数据读写,而是 在系统中创建了一个队列,这个队列的节点就是数据块,包含类型和信息
- 假设现在进程 A、B 想要通过消息队列进行通信,首先创建一个消息队列
- 然后进程 A 将自己想要发送给进程 B 的信息打包成数据块(其中包括发送方的信息),将数据块添加至消息队列队尾处
- 进程 B 同样也可以向消息队列中添加数据块,同时也会从消息队列中捕获其他进程的数据块,解析后进行读取,这样就完成了通信
遍历消息队列时,存数据块 还是 取数据块 取决于 数据块中的类型 type
注意: 消息队列跟共享内存一样,是由操作系统创建的,其生命周期不随进程,因此在使用结束后需要删除
下面有关于消息队列详解的文章
- 《什么是消息队列》
- 《消息队列详解》
1.2消息队列的数据结构
同属于 System V
标准,消息队列也有属于自己的数据结构
注:msg
表示 消息队列
struct msqid_ds
{struct ipc_perm msg_perm; /* Ownership and permissions */time_t msg_stime; /* Time of last msgsnd(2) */time_t msg_rtime; /* Time of last msgrcv(2) */time_t msg_ctime; /* Time of last change */unsigned long __msg_cbytes; /* Current number of bytes in queue (nonstandard) */msgqnum_t msg_qnum; /* Current number of messages in queue */msglen_t msg_qbytes; /* Maximum number of bytes allowed in queue */pid_t msg_lspid; /* PID of last msgsnd(2) */pid_t msg_lrpid; /* PID of last msgrcv(2) */
};
和 共享内存 一样,其中 struct ipc_perm
中存储了 消息队列的基本信息,具体包含内容如下:
struct ipc_perm
{key_t __key; /* Key supplied to msgget(2) */uid_t uid; /* Effective UID of owner */gid_t gid; /* Effective GID of owner */uid_t cuid; /* Effective UID of creator */gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions */unsigned short __seq; /* Sequence number */
};
可以通过 man msgctl
查看函数使用手册,其中就包含了 消息队列 的数据结构信息
1.3消息队列的相关接口
论标准的重要性,消息队列的大小接口风格与共享内存一致,都是出自 System V
标准
1.3.1创建
使用 msgget
函数创建 消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgget(key_t key, int msgflg);
关于 msgget
函数
与 共享内存 的 shmget
可以说是十分相似了,关于 ftok
函数计算 key
值,这里就不再阐述,可以在这篇文章中学习 《Linux进程间通信【共享内存】》
简单使用函数 msgget
创建 消息队列,并使用 ipcs -q
指令查看资源情况
#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>using namespace std;int main()
{//创建消息队列int n = msgget(ftok("./", 668), IPC_CREAT | IPC_EXCL | 0666);if(n == -1){cerr << "msgget fail!" << endl;exit(1);}return 0;
}
程序运行后,创建出了一个 msqid
为 0
的消息队列
因为此时并 没有使用消息队列进行通信,所以已使用字节 used-bytes
和 消息数 messages
都是 0
注意:
- 消息队列在创建时,也需要指定创建方式:
IPC_CREAT
、IPC_EXCL
、权限
等信息 - 消息队列创建后,
msqid
也是随机生成的,大概率每次都不一样 - 消息队列生命周期也是随操作系统的,并不会因进程的结束而释放
1.3.2释放
消息队列也有两种释放方式:通过指令释放、通过函数释放
释放指令:ipcrm -q msqid
释放消息队列,其他 System V
通信资源也可以这样释放
ipcrm -m shmid
释放共享内存ipcrm -s semid
释放信号量集
释放函数:msgctl(msqid, IPC_RMID, NULL)
释放指定的消息队列,跟 shmctl
删除共享内存一样
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgctl(int msqid, int cmd, struct msqid_ds *buf);
关于 msgctl
函数
简单回顾下参数2部分可传递参数:
IPC_RMID
表示删除共享内存IPC_STAT
用于获取或设置所控制共享内存的数据结构IPC_SET
在进程有足够权限的前提下,将共享内存的当前关联值设置为buf
数据结构中的值
同样的,消息队列 = 消息队列的内核数据结构(struct msqid_ds
) + 真正开辟的空间
1.3.3发送
利用消息队列发送信息,即 将信息打包成数据块,入队尾,所使用函数为 msgsnd
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
关于 msgsnd
函数
参数2 表示待发送的数据块,这显然是一个结构体类型,需要自己定义,结构如下:
struct msgbuf
{long mtype; /* message type, must be > 0 */char mtext[1]; /* message data */
};
mtype
就是传说中数据块类型,据发送方而设定;mtex
是一个比较特殊的东西:柔性数组,其中存储待发送的 信息,因为是 柔性数组,所以可以根据 信息 的大小灵活调整数组的大小
1.3.4接收
消息发送后,总得接收吧,既然发送是往队尾中添加数据块,那么接收就是 从队头中取数据块,假设所取数据块为自己发送的,那么就不进行操作,其他情况则取出数据块,使用 msgrcv
函数接收信息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
关于 msgrcv
函数
同样的,接收的数据结构如下所示,也包含了 类型 和 柔性数组
struct msgbuf
{long mtype; /* message type, must be > 0 */char mtext[1]; /* message data */
};
1.4消息队列补充
System V
版的 消息队列 使用起来比较麻烦,并且过于陈旧,现在已经较少使用了,所以我们不必对其进行深究,知道个大概就行了,如果实际中真遇到了,再查文档也不迟
2.信号量
2.1什么是信号量
信号量(semaphore
)一种特殊的工具,主要用于实现 同步和互斥
信号量 又称 信号灯,是各大高校《操作系统》课程中老师提及的高频知识点,往往伴随着 P、V
操作出现,但大多数老师都只是提及了基本概念,并未对 信号量 的本质及使用场景作出详细讲解
在正式学习 信号量 相关知识前,需要先简单了解下 互斥相关四个概念,为后续 多线程中信号量的学习作铺垫(重点)
2.2互斥相关概念
1、并发
是指系统中同时存在多个独立的活动单元
- 比如在多线程中,多个执行流可以同时执行代码,可能访问同一份共享资源
2、互斥
是指同一时刻只允许一个活动单元使用共享资源
- 即在任何一个时刻,都只允许一个执行流进行共享资源的访问(可以通过加锁实现)
3、临界资源
与 临界区
,多执行流环境中的共享资源就是 临界资源
,涉及 临界资源
操作的代码区间即 临界区
- 在多线程环境中,全局变量就是
临界资源
,对全局变量的修改、访问代码属于临界区
4、原子性
:只允许存在 成功 和 失败 两种状态
- 比如对变量的修改,要么修改成功,要么修改失败,不会存在修改一半被切走的状态
所以 互斥 是为了解决 临界资源 在多执行流环境中的并发访问问题,需要借助 互斥锁 或 信号量 等工具实现 原子操作,实现 互斥
关于互斥锁(mutex
) 的相关知识在 多线程 中介绍,现在先来学习 信号量,搞清楚它是如何实现 互斥 的
2.3信号量的数据结构
下面来看看 信号量 的数据结构,通过 man semctl
进行查看
sem
表示 信号量
struct semid_ds
{struct ipc_perm sem_perm; /* Ownership and permissions */time_t sem_otime; /* Last semop time */time_t sem_ctime; /* Last change time */unsigned long sem_nsems; /* No. of semaphores in set */
};
System V
家族基本规矩,struct ipc_perm
中存储了 信号量的基本信息,具体包含内容如下:
struct ipc_perm
{key_t __key; /* Key supplied to semget(2) */uid_t uid; /* Effective UID of owner */gid_t gid; /* Effective GID of owner */uid_t cuid; /* Effective UID of creator */gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions */unsigned short __seq; /* Sequence number */
};
显然,无论是 共享内存、消息队列、信号量,它们的 ipc_perm 结构体中的内容都是一模一样的,结构上的统一可以带来管理上的便利,具体原因可以接着往下看
2.4信号量相关接口
2.4.1创建
信号量的申请比较特殊,一次可以申请多个信息量,官方称此为 信号量集,所使用函数为 semget
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>int semget(key_t key, int nsems, int semflg);
关于 semget
函数
除了参数2,其他基本与另外俩兄弟一模一样,实际传递时,一般传 1
,表示只创建一个 信号量
使用函数创建 信号量集,并通过指令 ipcs -s
查看创建的 信号量集 信息
#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>using namespace std;int main()
{//创建一个信号量int n = semget(ftok("./", 668), 1, IPC_CREAT | IPC_EXCL | 0666);if(n == -1){cerr << "semget fail!" << endl;exit(1);}return 0;
}
程序运行后,创建了一个 信号量集,nsems
为 1
,表示在当前 信号量集 中只有一个 信号量
- 信号量集在创建时,也需要指定创建方式:
IPC_CREAT
、IPC_EXCL
、权限
等信息 - 信号量集创建后,
semid
也是随机生成的,大概率每次都不一样 - 信号量集生命周期也是随操作系统的,并不会因进程的结束而释放
2.4.2释放
老方法: 指令释放:直接通过指令 ipcrm -s semid
释放信号量集(略)
通过函数释放:semctl(semid, semnum, IPC_RMID)
,信号量中的控制函数有一点不一样
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>int semctl(int semid, int semnum, int cmd, ...);
关于 semctl
函数
注意:
- 参数2 表示信号量集中的某个信号量编号,从
1
开始编号 - 参数3 中可传递的动作与共享内存、消息队列一致
- 参数4 就像
printf
和scanf
中最后一个参数一样,可以灵活使用
2.4.3操作
信号量的操纵比较ex,也比较麻烦,所以仅作了解即可
使用 semop
函数对 信号量 进行诸如 +1
、-1
的基本操作
#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>int semop(int semid, struct sembuf *sops, unsigned nsops);
关于 semop
函数
重点在于参数2,这是一个结构体,具体成员如下:
unsigned short sem_num; /* semaphore number */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
其中包含信号量编号、操作等信息,需要我们自己设计出一个结构体,然后传给 semop
函数使用
可以简单理解为:sem_op
就是要进行的操作,如果将 sem_op
设为 -1
,表示信号量 -1
(申请),同理 +1
表示信号量 +1
(归还)
2.5信号量补充
信号量 是实现 互斥 的其中一种方法,具体表现为:资源申请,计数器 -1,资源归还,计数器 +1,只有在计数器不为 0 的情况下,才能进行资源申请,可以设计 二元信号量 实现 互斥
System V 中的 信号量 操作比较麻烦,但 信号量 的思想还是值得一学的,等后面学习 多线程 时,也会使用 POSIX 中的 信号量 实现 互斥,相比之下,POSIX 版的信号量操作要简单得多,同时应用也更为广泛
因为 信号量 需要被多个独立进程看到,所以 信号量 本身也是 临界资源,不过它是 原子 的,所以可以用于 互斥
多个独立进程看到同一份资源,这就是 IPC 的目标,所以 信号量 被划分至进程间通信中
3.深入理解 System V通信方式
不难发现,共享内存、消息队列、信号量的数据结构基本一致,并且都有同一个成员 struct ipc_perm
,所以实际对于 操作系统 来说,对 System V
中各种方式的描述管理只需要这样做
- 将 共享内存、消息队列、信号量对象描述后,统一存入数组中
- 再进行指定对象创建时,只需要根据 ipc_id_arr[n]->__key 进行比对,即可当前对象是否被创建!
- 因为 struct shmid_ds 与 struct ipc_perm shm_perm 的地址一致(其他对象也一样),所以可以对当前位置的指针进行强转:((struct shmid_ds)ipc_id_arr[0]) 即可访问 shmid_ds 中的成员,这不就是多态中的虚表吗?
这样一来,操作系统可以只根据一个地址,灵活访问 两个结构体中的内容,比如 struct ipc_perm shm_perm 和 struct shmid_ds,并且操作系统还把多种不同的对象,描述融合入了一个 ipc_id_arr 指针数组中,真正做到了 高效管理
注:默认 ipc_id_arr[n] 访问的是 struct ipc_perm 中的成员
注:上述图示只是一个草图,目的是为了辅助理解原理,并非操作系统中真实样貌
操作系统在进行比较判断时,如何判断类型呢?
这就是操作系统设计的巧妙之处了,ipc_id_arr 没那么简单,它会存储对象的相应类型信息
通过下标(id) 访问对象,这与文件系统中的机制不谋而合,不过实现上略有差异,间接导致 System V 的管理系统被边缘化(历史选择了文件系统)
shmid、msqid 和 semid 都是 ipc_id_arr 的下标,为什么值很大呢?
在进行查找时,会将这些 id % 数组大小 进行转换,确保不会发生越界,事实上,这个值与开机时间有关,开机越长,值越大,当然到了一定程度后,会重新轮回
总结:
上面就是关于信号量部分的内容,有个大概了解就行。
相关文章:

Linux信号【systemV】
目录 前言 正文: 1消息队列 1.1什么是消息队列? 1.2消息队列的数据结构 1.3消息队列的相关接口 1.3.1创建 1.3.2释放 1.3.3发送 1.3.4接收 1.4消息队列补充 2.信号量 2.1什么是信号量 2.2互斥相关概念 2.3信号量的数据结构 2.4…...

node.js最准确历史版本下载
先进入官网:Node.js https://nodejs.org/en 嫌其他博客多可以到/release下载:Node.js,在blog后面加/release https://nodejs.org/en/blog/release/ 点击next翻页,同样的道理...

UE5 C++ 单播 多播代理 动态多播代理
一. 代理机制,代理也叫做委托,其作用就是提供一种消息机制。 发送方 ,接收方 分别叫做 触发点和执行点。就是软件中的观察者模式的原理。 创建一个C Actor作为练习 二.单播代理 创建一个C Actor MyDeligateActor作为练习 在MyDeligateAc…...

前端学习、CSS
CSS可以嵌入到HTML中使用。 每个CSS语法包含两部分,选择器和应用的属性。 div用来声明针对页面上的哪些元素生效。 具体设置的属性以键值对形式表示,属性都在{}里,属性之间用;分割,键和值之间用:分割。 因为CSS的特殊命名风格…...

Flink基本原理 + WebUI说明 + 常见问题分析
Flink 概述 Flink 是一个用于进行大规模数据处理的开源框架,它提供了一个流式的数据处理 API,支持多种编程语言和运行时环境。Flink 的核心优点包括: 低延迟:Flink 可以在毫秒级的时间内处理数据,提供了低延迟的数据…...
3. 文档概述(Documentation Overview)
3. 文档概述(Documentation Overview) 本章节简要介绍一下Spring Boot参考文档。它包含本文档其它部分的链接。 本文档的最新版本可在 docs.spring.io/spring-boot/docs/current/reference/ 上获取。 3.1 第一步(First Steps) …...
【vue3 路由使用与讲解】vue-router : 简洁直观的全面介绍
# 核心内容介绍 路由跳转有两种方式: 声明式导航:<router-link :to"...">编程式导航:router.push(...) 或 router.replace(...) ;两者的规则完全一致。 push(to: RouteLocationRaw): Promise<NavigationFailur…...
ubuntu创建账号和samba共享目录
新建用于登录Ubuntu图形界面的用户 sudo su #切换为root用户获取管理员权限用于新建用户 adduser username #新建用户(例如用户名为username) adduser username sudo #将用户添加到 sudo 组 新建只能用于命令行下登录的用户 sudo su #切换为root用户…...

李沐动手学习深度学习——3.6练习
本节直接实现了基于数学定义softmax运算的softmax函数。这可能会导致什么问题?提示:尝试计算exp(50)的大小。 可能存在超过计算机最大64位的存储,导致精度溢出,影响最终计算结果。 本节中的函数cross_entropy是根据交叉熵损失函数…...
机器学习_10、集成学习-Bagging(自举汇聚法)
Bagging(自举汇聚法) Bagging(Bootstrap Aggregating,自举汇聚法)是一种集成学习方法,由Leo Breiman于1996年提出。它旨在通过结合多个模型来提高单个预测模型的稳定性和准确性。Bagging方法特别适用于减少…...
【力扣hot100】刷题笔记Day20
前言 今天学习了一句话“自己如果不努力,屎都吃不上热乎的”,话糙理不糙,与君共勉 35. 搜索插入位置 - 力扣(LeetCode) 二分查找 class Solution:def searchInsert(self, nums: List[int], target: int) -> int:n…...
Redis 之八:Jdeis API 的使用(Java 操作 Redis)
Jedis API 使用 Jedis 是 Redis 官方推荐的 Java 客户端,它提供了一套丰富的 API 来操作 Redis 服务器。通过 Jedis API,开发者可以方便地在 Java 应用程序中执行 Redis 的命令来实现数据的增删查改以及各种复杂的数据结构操作。 以下是一些基本的 Jedis…...

Docker 应用入门
一、Docker产生的意义 1‘解决环境配置难题:在软件开发中最大的麻烦事之一,就是环境配置。为了跑我们的程序需要装各种插件,操作系统差异、不同的版本插件都可能对程序产生影响。于是只能说:程序在我电脑上跑是正常的。 2’解决资…...
朱维群将出席用碳不排碳碳中和顶层科技路线设计开发
演讲嘉宾:朱维群 演讲题目:“用碳不排碳”碳中和顶层科技路线设计开发 简介 姓名:朱维群 性别:男 出生日期:1961-09-09 职称:教授 1998年毕业于大连理工大学精细化工国家重点实验室精细化工专业&#x…...
linux如何查看磁盘占用情况
要查看Linux系统中磁盘的占用情况,可以使用一些命令来获取相关信息。以下是一些常用的命令: df命令: df命令用于显示文件系统的磁盘空间使用情况,包括磁盘分区的总空间、已用空间、可用空间等信息。 df -h使用 -h 参数可以以人类可…...

【C++庖丁解牛】类与对象
📙 作者简介 :RO-BERRY 📗 学习方向:致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 📒 日后方向 : 偏向于CPP开发以及大数据方向,欢迎各位关注,谢谢各位的支持 目录 1.面向过程和面向对象…...

在什么时候企业档案才会发生调整
档案在企业中通常会调整在以下几个时刻: 1. 入职时:员工入职时,企业会要求员工提供个人档案,包括身份证件、学历证明、工作经历等相关文件。 2. 离职时:员工离职时,企业会整理员工的离职档案,包…...
Linux或Windows下判断socket连接状态
前言 场景:客户端程序需要实时知道和服务器的连接状态。比较通用的做法应用层是采用心跳机制,每隔一端时间发送心跳能回复说明服务器正常。 实际应用场景中,服务端和客户端并不是一家厂商的,比如说笔者这种情况,服务端…...
编译链接实战(25)gcc ASAN、MSAN检测内存越界、泄露、使用未初始化内存等内存相关错误
文章目录 1 ASAN1.1 介绍1.2 原理编译时插桩模块运行时库2 检测示例2.1 内存越界2.2 内存泄露内存泄露检测原理作用域外访问2.3 使用已经释放的内存2.4 将漏洞信息输出文件3 MSAN1 ASAN 1.1 介绍 -fsanitize=address是一个编译器选项,用于启用AddressSanitizer(地址...

[HackMyVM]靶场 VivifyTech
kali:192.168.56.104 主机发现 arp-scan -l # arp-scan -l Interface: eth0, type: EN10MB, MAC: 00:0c:29:d2:e0:49, IPv4: 192.168.56.104 Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan) 192.168.56.1 0a:00:27:00:00:05 (Unk…...

Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...

QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...

Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...