信号保存和信号处理

目录

信号保存中重要的概念

内核中信号的保存

对sigset_t操作的函数

对block,pendding,handler三张表的操作

sigpromask

​编辑

sigpending

是否有sighandler函数呢?

案例

信号处理

操作系统是如何运行的?

硬件中断 

时钟中断

软中断

死循环

缺⻚中断?内存碎⽚处理?除零野指针错误?

理解用户态和内核态

信号的自定义捕捉 

sigaction

验证阻塞信号

处理完解除对信号的阻塞

验证在处理前将pending表清零 

验证sa_mask的作用

可重入函数

案例

volatile

SIGCHLD信号

waitpid补充

验证子进程退出会给父进程发送SIGCHLD信号

 基于信号对子进程回收

问题1:

解决方法循环回收

问题2: 


信号保存中重要的概念

信号捕捉的三种方式:1.默认 SIG_DFL  default

                                    2.忽略  SIG_ING    ingore

                                    3.自定义

信号递达:实际执行信号的处理动作

信号未决:信号从产生到递达的状态

信号阻塞:进程可以阻塞某个信号  -->阻塞/屏蔽特定信号,信号产生了,一定把信号pendding(保存),并且信号永远不递达,除非解除阻塞。

os信号的保存都与以上的三个概念有关

阻塞 vs 忽略

阻塞发生在为信号递达之前

忽略发生在信号递达

内核中信号的保存

内核数据结构示意图

pending:信号未决

block: 信号阻塞

handler:信号递达

进程维护了以上的结构,所以进程是可以识别信号的。

以后对进程信号的操作都是为绕着这三张表展开的。

内核中的相应的数据结构

 sigset_t 是信号集,也是信号屏蔽字(signal mask)(有block,pending)

类似umask

对sigset_t操作的函数

对block,pendding,handler三张表的操作

sigpromask

读取/修改进程屏蔽字 block表 

set:进程中的block表修改基于set 

oldset:修改前block表的模样

how:以何种方式修改mask(block表)

1. SIG_BLOCK:  mask = mask | set  新增

2.SIG_UNBLOCK : mask = mask & ~set  去除指定信号

3.SIG_SETMASK: mask = set  覆盖

sigpending

通过该函数可以知道pending表的情况

但为什么不设置一个输入型参数,修改pending表呢?

因为信号的产生都是在修改pending表

是否有sighandler函数呢?

答案:没有

那么如何修改handler表呢?

signal就一直在修改啊

是否有恍然大悟的感觉啊。

案例

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <functional>
#include <sys/types.h>
#include <wait.h>
#include <vector>
void PrintBlock(const sigset_t &block)
{
    std::cout<<"block"<<"["<<getpid()<<"]: ";
    for (int i = 31; i > 0; i--)
    {

        if (sigismember(&block, i))
        {
            std::cout << 1;
        }
        else
        {
            std::cout << 0;
        }
    }
    std::cout << std::endl;
}

void handler(int signo)
{
    std::cout <<"signo:"<<signo<<" 信号递达"<<std::endl;
    exit(0);
}
int main()
{
    signal(2,handler);
    sigset_t block, oldblock;

    sigemptyset(&block);
    // 将二号新号加入
    sigaddset(&block, 2);
    //阻塞二号信号
    int ret = sigprocmask(SIG_SETMASK, &block, &oldblock);
    if (ret < 0)
    {
        perror("sigpromask");
        exit(1);
    }
    int cnt = 0;
    while (true)
    {
        PrintBlock(block);
        sleep(3);
        if(cnt==5)
        {
            PrintBlock(oldblock);
            //取消对二号信号的阻塞
            sigprocmask(SIG_SETMASK, &oldblock, nullptr);
            
        }
        cnt++;
    }
    return 0;
}

 执行结果

一开始block表中二号信号被阻塞,即使收到了二号信号也不能处理,当修改block表二号没被阻塞时,在执行二号信号相应的处理方法。

信号处理

信号没被处理,被保存起来---->合适的时候

那么什么是合适的时候?

进程从用户态切换会用户态时,检测额pending && block,决定是否用handler表处理信号。

os的运行状态:

1.用户态 ---> 执行用户写的代码

2.内核态  -->执行内核代码

信号自定义捕捉的流程

操作系统是如何运行的?

硬件中断 

硬件中断的处理流程

 

中断向量表在os启动时就填充好了。

时钟中断

进程是由操作系统调度的,那么操作系统谁有谁调度的呢?

有个设备时钟源定期向cpu发送中断,cpu处理中断,执行中断处理历程,而查表查到的终端服务是进程调度。

这样操作系统不就能自主调度了吗

时钟源是外设,将中断传给cpu效率有点低,现在的时钟源都是继承在cpu中。

主频就是控制CPU工作的时钟信号的频率

主频越快,相应单位时间内操作系统执行的中断服务越多,进程调度越频繁,os的性能越高。

进程调度不一定切换进程

软中断

是否能不通过外设来产生中断呢?

可以,比如os支持系统调用,cpu设计了汇编指令(int 0x80 或 syscall),让cpu在内部触发中断逻辑,

这样就可已在软件中触发中断-->再根据中断号,查系统调用表,执行相应的系统调用

系统调用号的本质就是数组下标

死循环

无论是软中断还是硬件中断

os需要什么方法直接,向中断向量表中添加就可以了:os的本质就是一个死循环

void main(void) /* 这⾥确实是void,并没错。 */
{ /* 在startup 程序(head.s)中就是这样假设的。 */
...
/*
* 注意!! 对于任何其它的任务,'pause()'将意味着我们必须等待收到⼀个信号才会返
* 回就绪运⾏态,但任务0(task0)是唯⼀的意外情况(参⻅'schedule()'),因为任
* 务0 在任何空闲时间⾥都会被激活(当没有其它任务在运⾏时),
* 因此对于任务0'pause()'仅意味着我们返回来查看是否有其它任务可以运⾏,如果没
* 有的话我们就回到这⾥,⼀直循环执⾏'pause()'。
*/
for (;;)
pause();
} // end main

---------------------------------------------------------------------------------------------------------------------------------

用户怎么把中断号给os?寄存器(如eax)

os怎么把返回值返回给用户? 也是寄存器

用户和内核之间传递信息用的是寄存器。

系统调用-->是通过软中断完成的

中断号-->系统调用号写入寄存器eax-->软中断-->查表--->执行相应的方法-->将返回值写入寄存器返回给用户

既然执行系统调用需要触发软中断,那我们使用系统调用函数值没有用 int 0x80 或 syscall?

因为linux提供的系统调用接口,不是C语言函数,而是  系统调用号 + 约定的传递参数 ,用于内核和用户间传递信息的 系统调用号和返回值的寄存器  + syscall  或 int 0x80

也就是说GNU glibc 把真正的系统调用封装--->C语言封装版的系统调用

OS是躺在中断处理历程上的代码块。


 

缺⻚中断?内存碎⽚处理?除零野指针错误?

这些错误都是通过软中断来处理的。

软中断有: 1.陷阱  (int 0x80 或 syscall)

                    2.异常

CPU内部的软中断,⽐如int 0x80或者syscall,我们叫做 陷阱
CPU内部的软中断,⽐如除零/野指针等,我们叫做 异常。(所以,能理解“缺⻚异常”
为什么这么叫了吗?)

理解用户态和内核态

内核页表:整个系统只有一张

用户页表:每个进程都有一张

 对于任何进程,不管如何调度,任何进程都只能找到一个os

为什么只有一张内核页表?

因为用户最关心的是系统调用。

用户关心系统调用的地址吗?

不关心,只需要知道系统调用号就可以了。

系统调用是在进程的地址空间中发起的,但其执行是在内核地址空间中完成的。

1.调用任何函数(库中)都是在我们自己进程中的地址空间中进行的

操作系统无论怎么切换进程,进程都只能找到一个操作系统

2.os调用方法的执行是在内核地址空间中执行的

不管是通过哪个进程的地址空间进入内核,都是通过软中断进行操作的。

⽤⼾态就是执⾏⽤⼾[0,3]GB时所处的状态
内核态就是执⾏内核[3,4]GB时所处的状态
如何区分内核态还是用户态?
cpu中有cs段寄存器

用户如何进入内核态?

1.时钟/外设中断

2.异常   cpu--->软中断

3.陷阱    系统调用(int 0x80,syscall)

信号的自定义捕捉 

1.前三步很好理解,那么为什么要转为用户态来调用处理方法,直接在内核态来处理不好吗?

 内核和用户的权限不同,有安全风险;并且用户来处理,如果出了错误用户担责任。

2.信号处理完只能从内核返回,因为处理函数的调用与main()函数之间不存在调用关心(栈区)

3.如何继续执行原来的程序?

cpu中有pc寄存器(pc指针),指向下一条要执行的命令,只需要恢复pc指针的值就可以了。

sigaction

作用检查并修改handler表

sigaction结构 

sa_handler :函数指针

sa_mask:自定义屏蔽的信号 

信号处理期间,如果处理方法有系统调用,那么就陷入内核态

os不允许信号处理方法嵌套--->实现该方法的方式是将block表相应的信号置为1(阻塞该信号),但信号处理完会自动解除阻塞

什么时候pending表清0?

在调用信号处理之前,因为如果是处理完之后清零,那么就无法区分pending中的1是处理进程时 收到的1,还是处理完时的1                                                                                                                                                                                                                

验证阻塞信号

void PrintBlock()
{
    sigset_t block;
    sigprocmask(SIG_SETMASK,nullptr,&block);
    for(int i = 31;i>0;i--)
    {
        if(sigismember(&block,i))
            std::cout<<1;
        else
            std::cout<<0;
    }
    std::cout<<std::endl;
}
void handler(int signo)
{
    std::cout<<"signo: "<<signo<<std::endl;
    while(true)
    {
        PrintBlock();
        sleep(3);
    }
}

int main()
{
    //signal(2,handler);
    struct sigaction act,oldact;
    act.sa_handler = handler;
    sigaction(2,&act,&oldact);
    //std::cout<<"执行"<<std::endl;
    PrintBlock();
    while(true)
    {
        pause();
    }
}

处理完解除对信号的阻塞

void PrintBlock()
{
    sigset_t block;
    sigprocmask(SIG_SETMASK,nullptr,&block);
    for(int i = 31;i>0;i--)
    {
        if(sigismember(&block,i))
            std::cout<<1;
        else
            std::cout<<0;
    }
    std::cout<<std::endl;
}
void handler(int signo)
{
    std::cout<<"signo: "<<signo<<std::endl;
    // while(true)
    // {
    //     PrintBlock();
    //     sleep(3);
    // }
    PrintBlock();
    sleep(3);
}

int main()
{
    //signal(2,handler);
    struct sigaction act,oldact;
    act.sa_handler = handler;
    sigaction(2,&act,&oldact);
    //std::cout<<"执行"<<std::endl;
    PrintBlock();
    while(true)
    {
        pause();
        PrintBlock();
    }
}

验证在处理前将pending表清零 

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <functional>
#include <sys/types.h>
#include <wait.h>
#include <vector>
void PrintBlock()
{
    sigset_t block;
    sigprocmask(SIG_SETMASK, nullptr, &block);
    std::cout<<"block: ";
    for (int i = 31; i > 0; i--)
    {
        if (sigismember(&block, i))
            std::cout << 1;
        else
            std::cout << 0;
    }
    std::cout << std::endl;
}
void PrintPending()
{
    sigset_t pending;
    sigpending(&pending);
    sigprocmask(SIG_SETMASK, nullptr, &pending);
    std::cout<<"pending: ";
    for (int i = 31; i > 0; i--)
    {
        if (sigismember(&pending, i))
            std::cout << 1;
        else
            std::cout << 0;
    }
    std::cout << std::endl;
}
void handler(int signo)
{
    std::cout << "signo: " << signo << std::endl;
    // while(true)
    // {
    //     PrintBlock();
    //     sleep(3);
    // }
    PrintBlock();
    PrintPending();
    sleep(3);
}

int main()
{
    // signal(2,handler);
    struct sigaction act, oldact;
    act.sa_handler = handler;
    sigaction(2, &act, &oldact);
    // std::cout<<"执行"<<std::endl;
    PrintBlock();
    PrintPending();
    while (true)
    {

        pause();
        PrintBlock();
        PrintPending();
    }
}

 

验证sa_mask的作用

void handler(int signo)
{
    std::cout << "signo: " << signo << std::endl;
    // while(true)
    // {
    //     PrintBlock();
    //     sleep(3);
    // }
    PrintBlock();
    //PrintPending();
    while(true)
    {

    }
    //sleep(3);
}

int main()
{
    // signal(2,handler);
    struct sigaction act, oldact;
    act.sa_handler = handler;
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask,2);
    sigaddset(&mask,3);//把三号信号屏蔽
    act.sa_mask = mask;//设置屏蔽信号
    sigaction(2, &act, &oldact);
    // std::cout<<"执行"<<std::endl;
    PrintBlock();
    //PrintPending();
    while (true)
    {

        pause();
        //PrintBlock();
        //PrintPending();
    }
}

屏蔽了2号和3号信号 

可重入函数

一个函数被两个以上的执行流同时进入--->重入(重复进入)

重入,不出问题--->可重入函数

重入,出问题---> 不可重入函数--->涉及对全局资源的处理

大部分库中的函数都是不可重入函数

重入和不可重入没有好坏之分,只是特性。

案例

因为中断,将node2插入链表,head指向node2,但返回main函数继续执行时,head又指向了node1导致找不到node2,也就造成了内存泄漏。main和信号的处理是两个执行流,进入的都是inset函数。

volatile

volatile是易变关键字

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int flag = 1;
void handler(int signo)
{
    printf("signo:%d\n",signo);
    printf("flag -> 0\n");
    flag = 0;

}
int main()
{
    signal(2,handler);
    while(flag);
    
    printf("quit normal!\n");
    return 0;
}

 没开启编译器优化,会实时更新进程地址空间中flag的值

当把编译器的优化等级开高一些,为什么不退出了呢?

开了优化后,即使进程内存地址空间中的flag改变了,但cpu中的flag不会根据进程地址空间中的flag更新寄存器中的flag,所以一直死循环没有退出 。

volatile flag = 1;//加上volatile关键字

即使优化编译器优化等级开高了,也实时通过进程地址空间中值修改寄存器中的值。 

SIGCHLD信号

当子进程退回后----(给父进程发送)--->SIGCHLD信号

waitpid补充

pid为-1时,可以等待任何子进程。

验证子进程退出会给父进程发送SIGCHLD信号

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
void handler(int signo)
{
    std::cout << "signo: " << signo << std::endl;
    pid_t ret = waitpid(-1, nullptr, 0);//pid==-1,表示等待任何子进程
    if (ret > 0)
    {
        std::cout<<"子进程: "<<ret<<"退出"<<std::endl;
    }
    else if (ret < 0)
    {
        std::cout << "wait error" << std::endl;
    }
    sleep(3);
}
int main()
{
    signal(SIGCHLD, handler);
    pid_t pid = fork();
    if (pid == 0)
    {
        std::cout << "我是子进程: ";
        std::cout << getpid() << std::endl;
        exit(0);
    }
    std::cout << "我是父进程: " << getpid() << std::endl;
    while (true);
    return 0;
}

 基于信号对子进程回收

问题1:

如果有n个子进程同时给父进程发送SIGCHLD信号,这是只能记录和处理一个信号;

如果pending记录了一个信号,其他的信号再发过来也不会被记录。

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
void handler(int signo)
{
    std::cout << "signo: " << signo << std::endl;
    pid_t ret = waitpid(-1,nullptr,0);
    if(ret>0)
    {
            std::cout << "子进程: " << ret << "退出" << std::endl;
    }
    else if(ret<0)
    {
        std::cout<<"wait error"<<std::endl;
    }
}
int main()
{
    signal(SIGCHLD, handler);
    pid_t pid = fork();
    for (int i = 0; i < 5; i++)
    {
        pid_t pid = fork();
        if (pid == 0)
        {
            std::cout << "我是子进程: ";
            std::cout << getpid() << std::endl;
            exit(0);
        }
    }
    while(true);
    return 0;
}

创建了10个子进程只有7个子进程退出,剩余的子进程发出的信号没有被处理,这些子进程变为了僵尸

解决方法循环回收 

void handler(int signo)
{
    while (true)
    {
        pid_t ret = waitpid(-1, nullptr, 0);
        if (ret > 0)
        {
            std::cout << "子进程: " << ret << "退出" << std::endl;
            // std::cout << "wait success" << std::endl;
        }
        else if (ret < 0)
        {
            std::cout << "暂时回收完毕" << std::endl;
            break;
        }
    }
}

运行结果:子进程全都被回收 

 

没有僵尸子进程

问题2: 

基于问题1,如果10个子进程中,有几个进程迟迟不退出那么,就会阻塞在信号处理那里等待子进程的退出

解决方法

options中有个WNOHANG,如果没有子进程结束则立即返回0

这样处理方法就有阻塞变为了非阻塞,如果有子进程退出则又会在进入循环回收,一次往复,避免了子进程出现僵尸。 

pid_t ret = waitpid(-1, nullptr, WNOHANG);

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

用vscode编写verilog时,如何有信号定义提示、信号定义跳转(go to definition)、模块跳转(跨文件跳转)这些功能

&#xff08;一&#xff09;方法一&#xff1a;安装插件SystemVerilog - Language Support 安装一个vscode插件即可&#xff0c;插件叫SystemVerilog - Language Support。虽然说另一个插件“Verilog-HDL/SystemVerilog/Bluespec SystemVerilog”也有信号提示及定义跳转功能&am…

初识算法 · 模拟(1)

目录 前言&#xff1a; 替换所有的问号 题目解析 算法原理 算法编写 提莫攻击 题目解析 算法原理 算法编写 外观数列 题目解析 算法原理 算法编写 前言&#xff1a; ​本文的主题是模拟&#xff0c;通过三道题目讲解&#xff0c;一道是提莫攻击&#xff0c;一道是…

〔 MySQL 〕数据类型

目录 1.数据类型分类 2 数值类型 2.1 tinyint类型 2.2 bit类型 2.3 小数类型 2.3.1 float 2.3.2 decimal 3 字符串类型 3.1 char 3.2 varchar 3.3 char和varchar比较 4 日期和时间类型 5 enum和set mysql表中建立属性列&#xff1a; 列名称&#xff0c;类型在后 n…

数据结构王道P234第二题

#include<iostream> using namespace std; int visit[MAxsize]; int color[MaxSize];//1表示红&#xff0c;2表示白&#xff1b; bool dfs(Graph G, int i){visit[i]1;ArcNode *p;bool flag1;for(pG.vertices[i].firsrarc; p ; pp->next){int jp->adjvex;if(!visi…

算法——两两交换链表中的节点(leetcode24)

这是一道对于链表节点进行操作的题目非常考验对于链表操作的基本功&#xff1b; 解法: 本题的解法结合下图来进一步解释 创建一个虚拟节点指向头结点以便使代码逻辑看起来更为简便且操作节点容易,定义cur是为了方便找到cur之后的两个节点进行交换操作定义pre和aft是为了保存执…

【AI图像生成网站Golang】项目架构

AI图像生成网站 目录 一、项目介绍 二、雪花算法 三、JWT认证与令牌桶算法 四、项目架构 五、图床上传与图像生成API搭建 六、项目测试与调试(等待更新) 四、项目架构 本项目的后端基于Golang和Gin框架开发&#xff0c;主要包括的模块有&#xff1a; backend/ ├── …

翼鸥教育:从OceanBase V3.1.4 到 V4.2.1,8套核心集群升级实践

引言&#xff1a;自2021年起&#xff0c;翼鸥教育便开始应用OceanBase社区版&#xff0c;两年间&#xff0c;先后部署了总计12套生产集群&#xff0c;其中核心集群占比超过四分之三&#xff0c;所承载的数据量已突破30TB。自2022年10月&#xff0c;OceanBase 社区发布了4.2.x 版…

ESP32-S3模组上跑通esp32-camera(19)

接前一篇文章&#xff1a;ESP32-S3模组上跑通esp32-camera&#xff08;18&#xff09; 本文内容参考&#xff1a; esp32-camera入门&#xff08;基于ESP-IDF&#xff09;_esp32 camera-CSDN博客 OV5640手册解读-CSDN博客 ESP32_CAM CameraWebServer例程源码解析笔记&#xf…

vmWare虚拟环境centos7安装Hadoop 伪分布式实践

背景&#xff1a;近期在研发大数据中台&#xff0c;需要研究Hadoop hive 的各种特性&#xff0c;需要搭建一个Hadoop的虚拟环境&#xff0c;本来想着使用dock &#xff0c;但突然发现docker 公共仓库的镜像 被XX 了&#xff0c;无奈重新使用vm 搭建虚拟机。 大概经历了6个小时完…

ARM(安谋) China处理器

0 Preface/Foreword 0.1 参考博客 Cortex-M23/M33与STAR-MC1星辰处理器 ARM China&#xff0c;2018年4月established&#xff0c;独立运行。 1 处理器类型 1.1 周易AIPU 1.2 STAR-MC1&#xff08;星辰处理器&#xff09; STAT-MC1&#xff0c;主要为满足AIOT应用性能、功…

c++--------《set 和 map》

c--------《set 和 map》 1 set系列的使⽤1.1 set类的介绍1.2 set的构造和迭代器1.3 set重要接口 2 实现样例2.1: insert和迭代器遍历使⽤样例&#xff1a;2.2: find和erase使⽤样例&#xff1a; 练习3.map系列的使用3.1 map类的介绍3.1.1 pair类型介绍 3.2 map的数据修改3.3mu…

MySQL面试之底层架构与库表设计

华子目录 mysql的底层架构客户端连接服务端连接的本质&#xff0c;连接用完会立马丢弃吗解析器和优化器的作用sql执行前会发生什么客户端的连接池和服务端的连接池数据库的三范式 mysql的底层架构 客户端连接服务端 连接的本质&#xff0c;连接用完会立马丢弃吗 解析器和优化器…

vscode vite+vue3项目启动调试

1、经常我们在普通的项目中&#xff0c;如果算法并不复杂&#xff0c;那么基本上console.log就可以搞定&#xff0c;当然也可以直接alert&#xff0c;打包的时候如果不去掉&#xff0c;还会在发版中上接弹出&#xff0c;给你个惊喜。 2、碰到了有些算法过程比较复杂的情况下&a…

详解八大排序(一)------(插入排序,选择排序,冒泡排序,希尔排序)

文章目录 前言1.插入排序&#xff08;InsertSort&#xff09;1.1 核心思路1.2 实现代码 2.选择排序&#xff08;SelectSort&#xff09;2.1 核心思路2.2 实现代码 3.冒泡排序&#xff08;BubbleSort&#xff09;3.1 核心思路3.2 实现代码 4.希尔排序&#xff08;ShellSort&…

IPv6 NDP 记录

NDP&#xff08;Neighbor Discovery Protocol&#xff0c;邻居发现协议&#xff09; 是 IPv6 的一个关键协议&#xff0c;它组合了 IPv4 中的 ARP、ICMP 路由器发现和 ICMP 重定向等协议&#xff0c;并对它们作出了改进。该协议使用 ICMPv6 协议实现&#xff0c;作为 IPv6 的基…

【包教包会】CocosCreator3.x框架——带翻页特效的场景切换

一、效果演示 二、如何获取 1、https://gitee.com/szrpf/TurnPage 2、解压&#xff0c;导入cocos creator&#xff08;版本3.8.2&#xff09;&#xff0c;可以直接运行Demo演示 三、算法思路 1、单场景 页面预制体 通过loadScene来切换页面&#xff0c;无法实现页面特效。…

拉取docker镜像应急方法

发现许多docker hub镜像网址速度也慢得发指啦&#xff0c;如果想速度快点&#xff0c;可以考虑买个按量计费的公有云服务器&#xff0c;用他们的内网镜像&#xff0c;然后再导出&#xff0c;然后传到本地。 开通服务器 可以考虑个开通最低配的&#xff0c;这里我用的是腾讯的…

Cyberchef配合Wireshark提取并解析HTTP/TLS流量数据包中的文件

本文将介绍一种手动的轻量级的方式&#xff0c;还原HTTP/TLS协议中传输的文件&#xff0c;为流量数据包中的文件分析提供帮助。 如果捕获的数据包中存在非文本类文件&#xff0c;例如png,jpg等图片文件&#xff0c;或者word&#xff0c;Excel等office文件异或是其他类型的二进…

Stable diffusion详细讲解

&#x1f33a;系列文章推荐&#x1f33a; 扩散模型系列文章正在持续的更新&#xff0c;更新节奏如下&#xff0c;先更新SD模型讲解&#xff0c;再更新相关的微调方法文章&#xff0c;敬请期待&#xff01;&#xff01;&#xff01;&#xff08;本文及其之前的文章均已更新&…

机器学习-37-对ML的思考之机器学习发展的三个阶段和驱动AI发展三驾马车的由来

文章目录 1 引言2 机器学习发展的三个阶段2.1 萌芽期(20世纪50年代)2.1.1 达特茅斯会议(人工智能诞生)2.1.2 机器学习名称的由来2.2 知识期(20世纪80年代)2.2.1 知识瓶颈问题2.2.2 机器学习顶级会议ICML2.2.3 Machine Learning创刊2.2.4 神经网络规则抽取2.3 算法期(20世纪90年…