当前位置: 首页 > news >正文

《操作系统》by李治军 | 实验5.pre - switch_to 汇编代码详解

目录

【前言】

一、栈帧的处理

1. 什么是栈帧

2. 为什么要处理栈帧

3. 执行 switch_to 前的内核栈

4. 栈帧处理代码分析

二、PCB 的比较

1. 根据 PCB 判断进程切换与否

2. PCB 比较代码分析

三、PCB 的切换

1. 什么是 PCB 的切换

2. PCB 切换代码分析

四、TSS 内核栈指针的重写

1. 为什么要重写 TSS 中的内核栈

2. 内核栈重写代码分析

五、内核栈的切换

1. 如何完成内核栈切换

2. 内核栈切换代码分析

六、LDT 的切换

1. LDT 切换代码分析

七、用户栈的切换

1. switch_to 退出代码分析


【前言】

       在李治军老师的《操作系统》课程的实验 5(基于内核栈切换的进程切换)中,需要完成 switch_to 函数的汇编代码编写。代码很容易获取,网上的资源非常多,但是拿到了看不懂……

       所以本文章将会对 switch_to 的代码进行逐条分析,希望能帮助理解基于内核栈的进程切换的整体流程。

switch_to() 完整汇编代码:

.align 2
switch_to:// 因为该汇编函数要在c语言中调用,所以要先在汇编中处理栈帧pushl %ebpmovl %esp,%ebppushl %ecxpushl %ebxpushl %eax// 将ebp+8指向的数据(目标进程的PCB)传递给ebx,然后进行判断:// 如果目标进程的pcb <<等于>> 当前进程的pcb => 不需要进行切换,直接退出函数调用// 如果目标进程的pcb <<不等于>> 当前进程的pcb => 需要进行切换,直接跳到下面去执行movl 8(%ebp),%ebxcmpl %ebx,currentje 1f/** 执行到此处,就要进行真正的基于堆栈的进程切换了 **/// 切换PCBmovl %ebx,%eaxxchgl %eax,current// 重写TSS中内核栈的指针movl tss,%ecxaddl $4096,%ebxmovl %ebx,ESP0(%ecx)// 切换内核栈movl %esp,KERNEL_STACK(%eax)movl 8(%ebp),%ebxmovl KERNEL_STACK(%ebx),%esp// 切换LDTmovl 12(%ebp),%ecxlldt %cx// 切换 LDT 之后movl $0x17,%ecxmov %cx,%fs// 这一段先不用管cmpl %eax,last_task_used_mathjne 1fclts// 现在进入新进程的内核栈工作了,所以接下来做的四次弹栈以及ret处理使用的都是新进程内核栈中的东西
1:	popl %eaxpopl %ebxpopl %ecxpopl %ebpret

一、栈帧的处理

1. 什么是栈帧

       大多数 CPU 上的程序实现都是通过使用来支持函数调用操作。栈被用来传递函数参数、存储返回地址、临时保存寄存器原有值以备恢复以及用来存储局部数据。单个函数调用操作所使用的栈部分被称为栈帧结构。
       一个函数栈帧结构的两端由两个指针来指定:① ebp:用作指针(指向栈帧部);② esp:用作指针(指向栈帧部)。在函数执行过程中,肯定会有数据的入栈和出栈,而栈指针 esp 就会随之变化(esp 始终指向栈顶)。因此,函数中对大部分数据的访问都是基于帧指针 ebp 进行的。

>> 强烈推荐先看看这篇文章再继续:栈帧_yxysdcl的博客-CSDN博客

2. 为什么要处理栈帧

       现在我们知道,每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中保存着该函数所需要的各种信息。寄存器 ebp 指向当前栈帧的底部,寄存器 esp 指向当前栈帧的顶部。

       当调用一个函数时,就意味着要创建一个属于这个函数自己的栈帧。而进入该函数后这个栈帧就变成了当前栈帧,也就是说退出上一个函数的栈帧进入新的栈帧。所以要让 ebp 重新指向当前栈帧的底部,让 esp 重新指向当前栈帧的顶部,同时还要保存上一个函数的栈帧底部和栈帧顶部。

3. 执行 switch_to 前的内核栈

       现在我们应该清楚,在执行上面的 switch_to 汇编代码前,当前进程内核栈的情况应该如下图所示(我们以在 schedule 中调用 switch_to 为例)。此时还没有进入 switch_to 函数,所以 ebp 和 esp 应该分别指向 schedule 函数的栈帧底部和栈帧顶部(栈帧底部具体在哪儿就不用管了)。

       schedule() 调用 switch_to() 的时候会依次将 switch_to 的参数(从右至左):_LDT(next) 和 pnext,以及 switch_to 的返回地址:{  依次入栈。

4. 栈帧处理代码分析

接下来我们逐条分析 switch_to 中栈帧处理的部分。

pushl %ebp

>> 将 ebp 入栈 <<

       这条指令的目的就是保存 schedule() 的栈帧底部。因为现在进入了 switch_to 函数,也就是说要切换到 switch_to 的栈帧了,所以必须把上面函数的栈帧底部和栈帧顶部保存起来,这样 switch_to 结束返回时才能成功返回到之前调用函数的位置。ps:这里不用另外保存栈顶,因为上一个栈帧的顶部就是下一个的栈帧的底部(两栈帧相邻)。

所以 ebp 入栈后内核栈变为:(这里 switch_to 的栈帧底部和顶部是一样的)

movl %esp,%ebp

>> 将 esp 中内容传递给 ebp <<

       原来 ebp 指向 schedule 函数的栈帧底部,但这句代码执行完后,ebp 和 esp 就都指向刚刚压入的 ebp 位置,也就是 switch_to 函数的栈帧底部。

pushl %ecx

pushl %ebx

pushl %eax

>> 将 ecx、ebx、eax 依次入栈 <<

       入栈后内核栈如下图所示。可以看到现在 ebp 指向 switch_to() 栈帧底部,esp 指向 switch_to()栈帧顶部,而且上一个函数 schedule 的栈帧底部指针就保存在调用的 switch_to 函数的栈帧底部位置,之后 switch_to 结束时就要通过这个 ebp 返回 schedule。现在栈帧就处理完毕了!

二、PCB 的比较

1. 根据 PCB 判断进程切换与否

① 目标进程的 PCB  =  当前进程的 PCB  =>  无需进行切换,直接退出函数调用

② 目标进程的 PCB    当前进程的 PCB  =>  需要进行切换,接着进行切换操作

2. PCB 比较代码分析

movl 8(%ebp),%ebx

>> 将 ebp 指针 + 8 指向的数据传递给了 ebx 寄存器 <<

       Linux 0.11 内核栈的地址顺序从上往下,是由高到低的。所以 ebp + 8 指向的就是 pnext(目标进程的 PCB ),所以 ebx 现在就存储着 pnext。

cmpl %ebx,current
je 1f

>> 比较 ebx 中的内容和 current <<

ebx 中保存着目标进程的 PCB,current 是当前进程的 PCB。

① 如果两个进程的 PCB 相同,则跳转到 1f 位置处,switch_to 接下来的代码也不用执行,不会进行进程切换。

② 如果两个进程的 PCB 不同,则继续执行 switch_to 接下来的代码进行进程切换。

三、PCB 的切换

1. 什么是 PCB 的切换

       切换 PCB 就是要让 current 切换为目标进程的 PCB。

2. PCB 切换代码分析

movl %ebx,%eax

xchgl %eax,current

>> 将 ebx 中数据置给 eax ,再交换 eax 和 current 的内容 <<

       执行前 ebx 中保存着 pnext(目标进程的 PCB),current 是当前进程的 PCB。这两句代码执行后,ebx 和 current 都指向目标进程的 PCB,eax 则指向当前进程的 PCB。PCB 切换完成!

四、TSS 内核栈指针的重写

1. 为什么要重写 TSS 中的内核栈

       执行 INT 0x80 中断之后,进程进入内核,这时要先找到内核栈的位置,将用户态下的 SS:ESP、CS:EIP、EFLAGS 都压入内核栈中保存下来,实现用户栈到内核栈的切换。以上的寻找内核栈和压栈操作都是系统自动完成的。也就是说系统会根据一些硬件寄存器(TR)知道这个哪个进程,以及该进程对应的内核栈在哪里,同时将用户态下的 SS:ESP、CS:EIP、EFLAGS 都压入内核栈中保存下来。

       虽然此时不再使用 TSS 进行进程切换,但是 Intel 的中断处理机制还是要保持,因为中断机制就是通过 TR 指向的 TSS 来找到当前进程的内核栈,并自动将用户栈等相关信息压入对应内核栈。也就是说,找到内核栈还得依靠 TR 指向的当前 TSS。所以每个进程仍需要一个TSS,这样系统才能并通过 TSS 中的内核栈指针 esp0 找到当前进程的内核栈并进行压栈操作。

       这里采用的方案是让所有进程共用一个TSS(即 0 号进程的TSS),并且这个 TSS 指向当前进程。在 sched.c 中定义的全局变量  struct tss_struct *tss = &(init_task.task.tss);  就是 0 号进程的 TSS,所有的进程都共用这个 TSS,任务切换时再发生变化。

这个唯一的 TSS 的目的就是:在中断处理时,帮助 CPU 找到当前进程的内核栈的位置

2. TSS 内核栈重写代码分析

movl tss,%ecx

>> 将 tss(TR 指向的 TSS)赋给 ecx 寄存器 <<

所以现在 ecx 也保存了当前进程的 TSS。

addl $4096,%ebx

>> ebx + 4096 <<

       ebx 本来指向目标进程的 PCB,执行该指令后,ebx 就指向目标进程的内核栈。为什么就指向内核栈了?因为 Linux 0.11 中进程的 PCB内核栈同一页内存上(即一块 4KB 大小的内存)。其中 PCB 位于这页内存的低地址,内核栈位于这页内存的高地址。也就是说,低地址空间 base 用来存放进程的 PCB,而 base + PAGE_SIZE 则作为该进程的内核栈的栈底。

       为什么偏移量是 4096 ?因为 4096 = 4KB = PAGE_SIZE,所以 ebx 加 4096 就可以得到内核栈的地址。

movl %ebx,ESP0(%ecx)

>> 将 ebx 中内容(目标进程的内核栈地址)复制到 ecx + ESP0 指向的位置 <<

       ecx 指向当前进程的 TSS,而 ESP0 = 4。我们再看 tss_struct 的定义,发现偏移为 4 的地方就是 TSS 中的内核栈指针 esp0,所以 ecx + ESP0 对应位置就是 tss 中的内核栈指针 esp0。将目标进程的内核栈地址赋给 esp0,实现了 tss 中内核栈指针的重写!

五、内核栈的切换

1. 如何完成内核栈切换

       上一步只是修改了 tss 中的内核栈指针 esp0,在进入内核时帮助 CPU 找到当前进程的内核栈以进行相关入栈操作,但并没有实际切换内核栈,因为 esp 还是指向当前进程的内核栈栈顶。要判断目前位于哪个进程的内核栈中,就看 esp 指向哪儿。

       所以完成内核栈的切换非常简单,就是将寄存器 esp(内核栈使用到当前情况时的栈顶位置)的值保存到当前进程 PCB 中的对应位置,再从目标进程 PCB 中的对应位置取出保存的内核栈栈顶放入 esp 寄存器中。这样处理完后,再通过 esp 使用内核栈时使用的就是目标进程的内核栈了。

2. 内核栈切换代码分析

movl %esp,KERNEL_STACK(%eax)

>> 将 esp 中内容保存到 eax + KERNEL_STACK 位置 <<

       eax 指向当前进程的 PCB(如果忘了可以返回 “PCB 的切换” 去看看),而 KERNEL_STACK 的数值没有明确定义,因为 Linux 0.11 中 PCB 的定义里并没有保存内核栈指针这个域(kernelstack),所以需要我们自己找位置加上这个定义,而宏 KERNEL_STACK 就是我们添加的那个位置。添加位置不同,KERNEL_STACK 的值也会不同。

       但是不管 KERNEL_STACK 的值是什么,eax + KERNEL_STACK 就是当前进程的 PCB 中对应存储内核栈指针的位置。所以这条指令实现了将寄存器 esp(内核栈使用到当前情况时的栈顶位置)的值保存到当前进程 PCB 中的对应位置。

movl 8(%ebp),%ebx

>> 将 ebp 指针 + 8 指向的数据传递给了 ebx 寄存器 <<

ebp + 8 指向的还是 pnext(目标进程的 PCB ),所以现在 ebx 存储着目标进程的 PCB。

movl KERNEL_STACK(%ebx),%esp

>> 将 ebx + KERNEL_STACK 位置的内容保存到 esp 中 <<

       ebx + KERNEL_STACK 位置的内容就是目标进程的 PCB 中对应存储内核栈指针的位置,所以这条指令实现了从目标进程 PCB 中的对应位置取出保存好的内核栈栈顶放入 esp 寄存器中,现在的 esp 就指向新进程的内核栈栈顶了ebp 指向的还是原来进程的内核栈(假设从进程 A 切换到进程 B)。到此正式完成了内核栈的切换!

六、LDT 的切换

1. LDT 切换代码分析

movl 12(%ebp), %ecx

>> 将 ebp 指针 + 12 指向的数据传递给了 ecx 寄存器 <<

       现在的内核栈(进程 A)中数据如下图所示。注意现在 ebp 指向的还是原来进程的内核栈,但 esp 指向的是新进程的内核栈。所以 ebp + 12 指向的就是 _LDT(next)(目标进程 - 进程 B 的 LDT ),这条指令就是负责取出对应 LDT(next) 的那个参数,这里暂时不用深入理解。

lldt %cx

       这条指令负责修改 LDTR 寄存器。一旦完成了修改,下一个进程在执行用户态程序时使用的映射表就是自己的 LDT 表了,地址空间就实现了分离。

movl $0x17,%ecx

mov %cx,%fs

       这两条指令在 LDT 切换完成之后,作用是重新取一下段寄存器 fs 的值。这两句指令必须要加、且必须出现在切换完 LDT 之后。因为 fs 的作用——通过 fs 访问进程的用户态内存,而 LDT 切换完成就意味着切换了分配给进程的用户态内存地址空间,所以前一个 fs 指向的是上一个进程的用户态内存,而现在需要执行下一个进程的用户态内存,所以需要用这两条指令来重取 fs。

七、用户栈的切换

1. switch_to 退出代码分析

cmpl %eax,last_task_used_math
jne 1f
clts

在 PCB、内核栈和 LDT 切换完成之后还有上面这段代码,我们暂时忽略不管,直接来到最后的 4 条出栈指令和 ret :

popl %eax
popl %ebx
popl %ecx
popl %ebp
ret

上面的 5 条指令执行前内核栈情况如图所示:

       可以看出进程 A 和进程 B 的内核栈整体结构是差不多的。因为任何一个进程进入内核,用户态下的 SS:ESP、CS:EIP、EFLAGS 等都会被自动压入内核栈中,所以内核栈栈底的内容都是该进程用户态下的相关信息(SS ~ CS)。而进程需要切换时通过 schedule() 进行调度找到目标进程,schedule() 退出前又调用了 switch_to 进行进程切换,这段过程对每个进程都是一样。所以从调用 switch_to 开始,_LDT(next) ~ eax 就依次入栈,每个进程都如此。

       回到这 5 条指令,此时已经切换了PCB、内核栈、LDT,剩用户栈还没有切换(通过 iret 实现)。esp 指向新进程的内核栈栈顶,但 ebp 还指向原来进程的内核栈。经过前 3 条出栈指令后,内核栈中情况如图所示:

popl %eax
popl %ebx
popl %ecx

       此时 esp 指向的进程 B 内核栈栈顶的 ebp 是什么?就是当前进程——进程 B 上一次调用 schedule() 时 schedule 的栈帧底部!如果忘了可以返回 “栈帧的处理” 看一看。现在继续执行下一条指令:

popl %ebp

执行后内核栈情况如下图所示,也就是回到了当时进程 B 停止时调用 schedule 的状态。

现在执行最后一条指令:

ret

       这条指令就是 switch_to 的返回指令,执行之后就会弹出 schedule() 的  }  并执行,而这个 是 schedule() 的返回指令。之后进程 B 继续在内核中运行一段代码后,就会退出内核(因为系统调用只是进入内核溜达一圈)。

       进入内核通过 INT 0x80,退出内核就通过 iret,这个 iret 指令就实现用户栈的切换。iret 会执行一系列的操作,其中源 CS ~ 源 SS 也会出栈。而 SS、SP 指向用户栈,就切换到了进程 B 的用户栈,接下来就从进程 B 停止时的位置开始继续往下执行。至此就完成了进程 A 到进程 B 的全部切换。

相关文章:

《操作系统》by李治军 | 实验5.pre - switch_to 汇编代码详解

目录 【前言】 一、栈帧的处理 1. 什么是栈帧 2. 为什么要处理栈帧 3. 执行 switch_to 前的内核栈 4. 栈帧处理代码分析 二、PCB 的比较 1. 根据 PCB 判断进程切换与否 2. PCB 比较代码分析 三、PCB 的切换 1. 什么是 PCB 的切换 2. PCB 切换代码分析 四、TSS 内核…...

c++11基础

文章目录&#xff1a; c11简介统一的列表初始化{}初始化std::initializer_list 声明autodecltypenullptr 范围for循环STL中的一些变化arrayforward_listunordered_map和unordered_set 字符串转换函数 c11简介 在2003年C标准委员会曾经提交了一份技术勘误表(简称TC1)&#xff0…...

Linux:centos:修改临时ip永久ip

使用 ifconfig 查看网卡信息以及ip 临时配置ip 找到要修改ip的网卡的名称&#xff08;我这里使用名称为&#xff1a;ens33网卡&#xff09; # ifconfig 网卡名 ip /子网掩码 ifconfig ens33 192.168.1.2/24 配置永久ip 去配置网卡文件 vi /vim 或 nano vim /etc/s…...

如何真正开启docker远程访问2375

注意看官方文档 Configure remote access for Docker daemon | Docker Documentation 1. windows上Docker Desktop开启远程访问端口2375 系统版本&#xff1a; win10专业版 Docker Desktop版本&#xff1a;4.18.0 很简单勾上&#xff0c; 应用并重启即可 2. linux上开启 尝…...

nodejs连接mysql

npm i express #node后端框架npm i corsnpm i mysqlconst app require(express)(); const cors require(cors); const port 5000; const mysql require(mysql) //引入mysql 模块app.use(cors({}))const conn mysql.createConnection({user: root,password: qwertyuiop…...

异构跨库数据同步还在用Datax?来看看这几个开源的同步方案

在遇到跨库或者异库数据同步时&#xff0c;我们一般都会借助ETL工具来实现数据同步功能。比如目前大家较为熟知的Kettle和Datax。但是&#xff0c;这两个需要定时去查询数据库的数据&#xff0c;会存在一定的延迟&#xff0c;而且&#xff0c;默认采用全量同步的方式&#xff0…...

msvcp140.dll丢失怎么办?msvcp140.dll重新安装的解决方法

msvcp140.dll是微软编译器系统中的一个动态链接库文件&#xff0c;它存储了许多的代码和数据&#xff0c;能帮助计算机程序正常运行。当系统中出现了msvcp140.dll丢失的情况时&#xff0c;则会出现程序无法正常运行的错误。这篇文章将为大家介绍如何解决msvcp140.dll丢失的问题…...

mysql超全语法大全

mysql安装教程 一、登录&#xff08;使用可视化工具&#xff0c;可忽略&#xff09; 打开命令行工具&#xff0c;输入以下命令&#xff0c;根据提示输入 root 用户的密码。 mysql -u root -p mysql -u root -p -D 数据库名二、创建数据库 显示数据库&#xff1a;SHOW DATAB…...

【VR】手柄定位技术

1. 关于Quest Pro头显、控制器的规格分析&#xff08;终篇&#xff09;及Quest 3分辨率 &#xff08;2022年07月29日&#xff09;被认为是“Quest Pro”的高端一体机Project Cambria将于今年秋季正式发布。对于一直关注和分享所述设备情报的YouTuber布拉德利林奇&#xff08;B…...

TDengine 启动 taosAdapter,提供基于6041端口的RESTful 接口,建立REST 连接

一、前文 TDengine 入门教程——导读 二、开发指南 TDengine 完整的软件包包括&#xff1a; 服务端&#xff08;taosd&#xff09;&#xff1b;命令行程序 (CLI&#xff0c;taos) 和一些工具软件&#xff1b;用于与第三方系统对接并提供 RESTful 接口的 taosAdapter&#xff1…...

SY8205同步降压DCDC可调电源模块(原理图和PCB)

SY8205同步buck降压电源模块&#xff0c;输入电压4.5-30V&#xff0c;输出电压0.6-30V可调&#xff0c;效率90%以上&#xff0c;最大连续输出电流5A&#xff0c;峰值电流6A。 开源链接&#xff1a;https://url.zeruns.tech/obGu3 SY8025数据手册下载地址&#xff1a;https://…...

电装光庭汽车电子(武汉)有限公司

电装光庭汽车电子&#xff08;武汉&#xff09;有限公司 &#xff08;汽车座舱显示系统&#xff0c;汽车电子产品及其材料和组件的开发&#xff0c;设计&#xff0c;制造&#xff0c;销售&#xff0c;批发&#xff0c;进出口&#xff09; 一、公司介绍 电装光庭汽车电子是一…...

2023年DAMA-CDGA/CDGP认证合肥/厦门/长春/深圳可以报名

DAMA认证为数据管理专业人士提供职业目标晋升规划&#xff0c;彰显了职业发展里程碑及发展阶梯定义&#xff0c;帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力&#xff0c;促进开展工作实践应用及实际问题解决&#xff0c;形成企业所需的新数字经济下的核心职业…...

android 12.0launcher3中workspace app列表页不显示某个app图标

1.概述 在12.0的开发中,Launcher3 workspace的app列表页 会负责加载系统中app的所有图标 但针对某个不需要显示在桌面的app图标需要过滤掉 所以需要在加载和更新的时候过滤 需要更改两处地方,一处是 加在列表时 一处是安装卸载app 更新app列表时 2.launcher3中workspace ap…...

Java 编写websocket client 压测脚本

对于Java开发者而言&#xff0c;使用Java编写websocket client压测脚本是一件比较容易的事情。下面给出一个基于Java语言的websocket client压测脚本示例&#xff0c;供大家参考。 import java.net.URI; import java.util.concurrent.CountDownLatch; import org.java_websocke…...

设计模式之【备忘录模式】,“后悔药”是可以有的

文章目录 一、什么是备忘录模式1、备忘录模式使用场景2、备忘录模式优缺点3、备忘录模式的三大角色4、白箱备忘录和黑箱备忘录5、思考&#xff1a;备份频率快&#xff0c;备份对象大的备忘录应该如何设计 二、实例1、备忘录模式的一般写法2、使用栈管理富文本编辑器3、游戏状态…...

ATECLOUD云测试平台新能源电机测试系统:高效、可扩展的测试利器

随着全球对环境保护的日益重视&#xff0c;新能源的发展越来越受到关注。电动汽车作为新能源领域的重要组成部分&#xff0c;其性能和质量对于消费者来说至关重要。为了确保电动汽车的性能和质量&#xff0c;测试系统平台解决方案变得越来越重要。本文将介绍一种基于ATECLOUD智…...

项目随机问题笔记

一、前端项目启动的命令 启动项目依赖&#xff1a;npm install 安装cross-env模块&#xff1a;npm i cross-env --save-dev 启动报错时试试这个 npm install node-sass (安装sass) 启动项目命令1 npm run dev 启动项目命令2 npm run start 启动项目命令3 npm start 二、前…...

Linux网络编程之recv函数

功能 recv 函数的功能就是从套接字中接收数据。 头文件 #include <sys/types.h> #include <sys/socket.h>原型 ssize_t recv(int sockfd, void *buf, size_t len, int flags);参数 参数描述sockfdsocket 文件描述符buf接收数据缓冲区len接收数据缓冲区的大小f…...

ChatGPT免费使用的方法有哪些?

目录 一、ChatGpt是什么&#xff1f; 二、ChatGPT国内免费使用的方法&#xff1a; 第一点&#xff1a;电脑端 第二点&#xff1a;手机端 三、结语&#xff1a; 一、ChatGpt是什么&#xff1f; ChatGPt是美国OpenAI [1] 研发的聊天机器人程序 。更是人工智能技术驱动的自然语…...

51c自动驾驶~合集58

我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留&#xff0c;CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制&#xff08;CCA-Attention&#xff09;&#xff0c;…...

在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module

1、为什么要修改 CONNECT 报文&#xff1f; 多租户隔离&#xff1a;自动为接入设备追加租户前缀&#xff0c;后端按 ClientID 拆分队列。零代码鉴权&#xff1a;将入站用户名替换为 OAuth Access-Token&#xff0c;后端 Broker 统一校验。灰度发布&#xff1a;根据 IP/地理位写…...

【C语言练习】080. 使用C语言实现简单的数据库操作

080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...

【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)

升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点&#xff0c;但无自动故障转移能力&#xff0c;Master宕机后需人工切换&#xff0c;期间消息可能无法读取。Slave仅存储数据&#xff0c;无法主动升级为Master响应请求&#xff…...

css3笔记 (1) 自用

outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size&#xff1a;0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格&#xff…...

Mobile ALOHA全身模仿学习

一、题目 Mobile ALOHA&#xff1a;通过低成本全身远程操作学习双手移动操作 传统模仿学习&#xff08;Imitation Learning&#xff09;缺点&#xff1a;聚焦与桌面操作&#xff0c;缺乏通用任务所需的移动性和灵活性 本论文优点&#xff1a;&#xff08;1&#xff09;在ALOHA…...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!

简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求&#xff0c;并检查收到的响应。它以以下模式之一…...

AGain DB和倍数增益的关系

我在设置一款索尼CMOS芯片时&#xff0c;Again增益0db变化为6DB&#xff0c;画面的变化只有2倍DN的增益&#xff0c;比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析&#xff1a; 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...

接口自动化测试:HttpRunner基础

相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具&#xff0c;支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议&#xff0c;涵盖接口测试、性能测试、数字体验监测等测试类型…...

QT开发技术【ffmpeg + QAudioOutput】音乐播放器

一、 介绍 使用ffmpeg 4.2.2 在数字化浪潮席卷全球的当下&#xff0c;音视频内容犹如璀璨繁星&#xff0c;点亮了人们的生活与工作。从短视频平台上令人捧腹的搞笑视频&#xff0c;到在线课堂中知识渊博的专家授课&#xff0c;再到影视平台上扣人心弦的高清大片&#xff0c;音…...