Linux:信号

目录

1.信号

2.信号的过程

a.信号的产生

1:键盘产生, 异常产生

2:系统调用产生信号

3.软件条件产生信号

4.硬件异常产生信号

b.信号的发送

c.信号的处理

d.总结与思考

3.信号保存

1.信号及其它相关常见概念

2.在内核中的表示

3.sigset_t

4. 信号集操作函数

4.信号的捕捉

1.信号捕捉流程图

2.sigaction

3.可重入函数

5.代码编写

1.signal函数

2.alarm定时器

3.信号的保存(信号集操作函数)

4.信号的捕捉

5.sigchild

补充

核心转储

volatile

1.信号

1.1.信号的概念

1.1.1什么是linux信号?

本质是一种通知机制, 用户或者OS,通过发送一定的信号,通知进程某些事情已经发生,你可以在后续进行处理, 信号是进程之间事件异步通知的一种方式,属于软中断

1.1.2信号的结论

a.进程要处理信号,必须具备信号 “识别” 的能力(看到 + 处理)

b.凭什么进程能够 “识别” 信号? 程序员!

c.信号产生式随机的,进程可能在忙别的事情,信号的处理可能不是立即处理

d.信号会临时记录下对应的信号,方便后续处理

e.信号什么时候处理?合适的时候

g.信号的产生相对于进程式异步的(一般而言)

1.2为什么要有信号?

可以根据收到的信号~~>执行相应的操作

1.3信号的使用

用kill -l命令可以察看系统定义的信号列表

普通信号 , 实时信号(编号34以上)

9,18,19信号是管理员信号,它们无法被进程阻塞或忽略

2.信号的过程

产生 ~~> 发送 ~~> 处理

a.信号的产生

1:键盘产生, 异常产生

2:系统调用产生信号

--kill接口

头文件:  <signal.h>
函数: int kill(pid_t pid, int signo);
功能: 向指定进程发送指定信号

--raise接口: 向自己发送指定的信号     (头文件:signal.h)

头文件:  <signal.h>
函数: int raise(int signo);
功能: 给当前进程发送指定的信号(自己给自己发信号)

--abort接口: 自己终止自己(发送确认)   (头文件:stdlib.h)

头文件:<stdlib.h> 
函数:void abort(void);
功能:abort函数使当前进程接收到信号而异常终止

通过系统调用接口,向进程发送信号

3.软件条件产生信号

--管道:SIGPIPE是一种由软件条件产生的信号, 如:管道的读端不再读且关闭了,写端再写没有意义,OS会自动终止对应的写端进程(通过发送信号的方式,SIGPIPE)

--alarm:seconds秒之后给当前进程被SIGALRM信号终止。

头文件:<unistd.h>
函数: unsigned int alarm(unsigned int seconds); 
功能:告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。

4.硬件异常产生信号

--除0异常(进行计算的是cpu这个硬件)

CPU的运算单元会产生异常,内核将这个异常解释 为SIGFPE信号发送给进程

--野指针或者越界问题(使用指针需要找到目标未知,将虚拟地址转换为物理地址,通过MMU)

MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。

--异常的检测:cpu内部是有记存器的, 状态寄存器, 有对应的状态标记位, 有溢出标记为, OS会自动进行执行完毕的检测!

硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号

b.信号的发送

由OS识别, 解释, 发送

c.信号的处理

  1. 默认 (程序自带,程序员写好的)
  2. 忽略
  3. 自定义动作(捕捉信号)
功能:通过回调的方式,修改捕捉信号对应的方法
头文件:#include<signal.h>
函数:sighandler_t signal(int signum, sighandler_t handler); //回调函数
     typedef void (*sighandler_t)(int);  //函数指针   

d.总结与思考

所有的信号, 有它的来源, 但最总全部都是被OS识别, 解释, 并发送的

1.上面所说的所有信号产生,最终都要有OS来进行执行,为什么?

因为OS是进程的管理者

2.信号的处理是否是立即处理的?

在合适的时候

3.信号如果不是被立即处理,那么信号是否需要暂时被进程记录下来?记录在哪里最合适呢?

PCB对应的信号位图当中

4.一个进程在没有收到信号的时候,能否能知道,自己应该对合法信号作何处理呢?

能,程序员已经写好

5.如何理解OS向进程发送信号?能否描述一下完整的发送处理过程?

OS去修改位图: 根据编号~~>修改比特位~~>比特位由0置1就完成了信号的发送

--系统调用

用户调用系统接口->执行OS对应的系统调用代码->OS提取参数或设置特定数值->
OS向目标进程写信号->修改对应进程信号标记为->进行后续会处理信号->执行对应处理动作

--如何理解信号发送的本质?

信号位图是在task_struct ->tasj_struct 内核数据结构 ~> OS

信号发送的本质: OS向目标进程写信号 ,OS直接修改pcb中指定的位图结构,完成 “发送” 信号的过程

--如何理解信号被进程保存?

a.什么信号 b.怎么产生 ~~>进程必须具有保存信号的相关数据结构(位图)
PCB内部保存了信号位图字段

--信号处理的常见方式

a.默认 (程序自带,程序员写好的)b.忽略 c.自定义动作(捕捉信号)

--如何理解组合键变成信号?

键盘的工作方式是通过中断方式进行的,当然也能够识别组合键
OS解释组合键 ~>查找进程列表 ~> 前台运行的进程 ~> OS写入对应的信号到进程内部的位图结构中

3.信号保存

1.信号及其它相关常见概念

  • 实际执行信号的处理动作称为信号递达(Delivery)
  • 信号从产生到递达之间的状态,称为信号未决(Pending)。
  • 进程可以选择阻塞 (Block )某个信号。
  • 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
  • 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作

2.在内核中的表示

  • 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
  • SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前,不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
  • SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里

3.sigset_t

从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。

因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,

在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态

4. 信号集操作函数

sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释,比如用printf直接打印sigset_t变量是没有意义的

#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
//这四个函数都是成功返回0,出错返回-1。

int sigismember(const sigset_t *set, int signo);
//sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号
//若包含则返回1,不包含则返回0,出错返回-1
  • 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号。
  • 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号。
  • 注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号

sigprocmask

功能:调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)。
头文件:#include <signal.h>
函数:int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1
  • 如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。
  • 如果set是非空指针,则 更改进程的信号屏蔽字,参数how指示如何更改。
  • 如果oset和set都是非空指针,则先将原来的信号 屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。

how参数的可选值:

sigpending

功能:读取当前进程的未决信号集,通过set参数传出
头文件:#include <signal.h>
函数:int sigpending(sigset_t *set);
返回值:调用成功则返回0,出错则返回-1

4.信号的捕捉

1.信号捕捉流程图

信号产生以后可能无法立即处理,在合适的时候处理(从内核态返回用户态的时候)

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,

举例如下: 用户程序注册了SIGQUIT信号的处理函数sighandler.

  1. 当前正在执行main函数,这时发生中断或异常切换到内核态。
  2. 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。
  3. 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函 数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。
  4. sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。如果没有新的信号要递达
  5. 这次再返回用户态就是恢复main函数的上下文继续执行了

用户态与内核态

CPU寄存器有2套,一套可见,一套不可见,CR3->表示当前CPU的执行权限: 1内核态 3用户态

我们凭什么有权利执行OS的代码? 根据我们是用户态还是内核态

内核也是在所有进程地址空间上下文中跑得

int80用来切换内核态和用户态(调相关系统接口)

2.sigaction

功能:可以读取和修改与指定信号相关联的处理动作
头文件:#include <signal.h>
函数int sigaction(int signo,const struct sigaction *act,struct sigaction *oact);
返回值:用成功则返回0,出错则返回-1
  • signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体
  • 将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用

--处理信号的时候,执行自定义动作,如果在处理信号期间,又来了同样的信号,OS如何处理?(本质:为什么要有block)

某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。 sa_flags字段包含一些选项

3.可重入函数

一个函数如果被多个执行流重复进入了, 不会出问题, 就叫可重入函数 , 反之就叫不可重入函数

5.代码编写

1.signal函数

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;

void catchsig(int signum)
{
    cout << "捕捉到一个信号: " << signum << "Pid: " << getpid() << endl;
}

int main()
{
    // signal(2,fun);
    signal(SIGINT, catchsig); // 特定信号的处理动作一般只有一个
    // signal函数,仅仅是修改进程对待特定信号的后续处理动作,不是直接调用函数

    while (true)
    {
        cout << "Runing , Pid: " << getpid() << endl;
        sleep(1);
    }

    return 0;
}

效果:

2.alarm定时器

设置一个闹钟,1s后会向进程发送SIGALRM信号

捕捉这个信号,改为执行部分功能,并重新设置一个闹钟(catchsig)

使用vector存放函数指针,catchsig中,执行里面的函数

代码:

3.信号的保存(信号集操作函数)

1.如果我们对所有的信号都进行了自定义捕捉--我们是不是就写了一个不会被异常或者用户杀掉的进程?

并不是, 操作系统的设计者也考虑了~~>9号信号--管理员信号-->无法设定自定义捕捉动作

2.如果我们将2号信号block,并且不断的获取并打印当前进程的pending信号集, 并突然发送一个2号信号,我们是不是可以看到pending信号集中, 有一个比特位0~>1

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <cassert>

static void showPending(sigset_t pending)
{
    for (int sig = 1; sig <= 31; sig++)
    {
        if (sigismember(&pending, sig))
            std::cout << "1";
        else
            std::cout << "0";
    }
    std::cout << std::endl;
}

void handler(int signum)
{
    std::cout << "捕捉了一个信号: " << signum << std::endl;
}

int main()
{
    signal(2, handler);
    // 1.定义信号集对象
    sigset_t bset, obset;
    sigset_t pending;
    // 2.初始化
    sigemptyset(&bset);
    sigemptyset(&obset);
    sigemptyset(&pending);
    // 3.添加要屏蔽的信号
    sigaddset(&bset, 2);
    // 4.设置set到对应进程内部(默认情况,进程不会对任何信号进行block)
    int n = sigprocmask(SIG_BLOCK, &bset, &obset);
    assert(n == 0);
    (void)n;
    std::cout << "block 2号信号成功, pid: " << getpid() << std::endl;
    // 5.重复打印当前进程的pending信号集
    int count = 0;
    while (true)
    {
        // 获取当前进程的pending信号集
        sigpending(&pending);
        // 显示pending信号集中,没有被递达的信号
        showPending(pending);
        sleep(1);
        count++;
        if (count == 15)
        {
            // 默认情况下,恢复对于2号信号的block的时候,进行递达,
            // 但是2号信号的默认处理动作是终止进程
            // 需要对2号信号捕捉
            int n = sigprocmask(SIG_SETMASK, &obset, nullptr);
            assert(n == 0);
            (void)n;
            std::cout << "解除2号信号block成功" << std::endl;
        }
    }

    return 0;
}

​​​​​​​

3.如果我们对所有的信号进行block--我们是不是就写了一个不会被异常或者用户杀掉的进程?

  1. SIGKILL (9): 这是用来强制终止进程的信号,它可以无视进程的阻塞状态直接终止进程。

  2. SIGSTOP (19): 发送这个信号会暂停目标进程的执行,无法被阻塞。

  3. SIGCONT (18): 发送这个信号会恢复已经被暂停的进程

这些信号的特殊性在于它们无法被进程阻塞或忽略,甚至在进程安装了信号处理函数(signal handler)进行处理时也不例外。这确保了在需要强制终止或暂停进程时能够始终生效。

4.信号的捕捉

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;

void handler(int signum)
{
    cout << "获取了一个信号:" << signum << endl;
}

int main()
{
    struct sigaction act, oact;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    act.sa_handler = handler;

    // 设置进当前进程的PCB中
    sigaction(2, &act, &oact);

    cout << "default action:" << (int)oact.sa_handler << endl;

    while (1) sleep(1);
    return 0;
}

5.sigchild

SIGCHLD 是一个由操作系统发送给父进程的信号,用于通知父进程一个子进程已经终止或暂停。当一个子进程变为僵尸进程(zombie process)时,操作系统将会发送 SIGCHLD 信号给父进程。父进程可以通过捕获 SIGCHLD 信号并调用 wait() 或 waitpid() 等函数来处理子进程的终止状态,避免僵尸进程的积累。

#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;

void handler(int signum)
{
    cout << "获取到了一个信号: " << signum << endl;
}

int main()
{
    signal(SIGCHLD, handler);
    if (fork() == 0)
    {
        sleep(1);
        exit(1);
    }

    while (true)
        sleep(1);
    return 0;
}

子进程退出后,父进程收到信号(父进程对其捕捉)

--不想等待子进程 + 子进程退出之后, 自动释放僵尸子进程:SIG_IGN

#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;

int main()
{
    //不想等待子进程 + 子进程退出之后, 自动释放僵尸子进程
    signal(SIGCHLD,SIG_IGN);

    if (fork() == 0)
    {
        cout << "child: " << getpid() << endl;
        sleep(5);
        exit(0);
    }

    while (1)
    {
        cout << "parent:" << getpid() << "执行自己的任务"<< endl;
        sleep(1);
    }
}

OS默认的忽略 ~~> 不回收子进程(忽略程度较低, OS拿不准是否真的忽略)

用户使用忽略~~>告诉OS需要忽略~~>回收子进程

补充

核心转储

--core dump标志: 代表是否发生了核心转储

--核心转储:当接受到信号退出的时候(进程出现某种异常), 将数据dump到磁盘上-->主要是为了调试

一般而言,云服务器(生产环境)的核心转储是被关闭的,(可用ulimit -a 查看当前环境的资源配置)

原因:若写的服务出现异常,且机器自带重启,没过多久,磁盘上就会充满dump来的数据

volatile

作用: 避免编译器优化,让内存中的flag不可见

编译器有时候会自动给我们的代码进行优化:在main函数中没有任何语句是修改flag的,程序启动的时候,直接将flag的值放入CPU中的edx中,while循环做检测的时候,直接检测edx中的(不再检测内存中的flag), 下面这段代码修改的是内存中的flag, 但是检测的是edx中的

加上volatile,可避免编译器的优化

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

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

相关文章

MySQL 管理端口

错误 客户出现 MySQL连接数 超过 最大连接数的现象 ERROR 1040 (HY000): Too many connections 出现该现象&#xff0c;一般的解决方法&#xff1a; 1.修改配置文件中的最大连接数&#xff0c;之后重启数据库 2.如果配置文件中没有设置 连接超时时间的参数。8小时后&#…

前端 TS 语法 接口(2)

介绍 TypeScript的核心原则之一是对值所具有的shape进行类型检查。 它有时被称做“鸭式辨型法”或“结构性子类型化”。 在TypeScript里&#xff0c;接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。 只读属性 readonly 一些对象属性只能在对象刚刚创建的…

iOS开发进阶(六):Xcode14 使用信号量造成线程优先级反转问题修复

文章目录 一、前言二、关于线程优先级反转三、优先级反转会造成什么后果四、怎么避免线程优先级反转五、使用信号量可能会造成线程优先级反转&#xff0c;且无法避免六、延伸阅读&#xff1a;iOS | Xcode中快速打开终端6.1 .sh绑定6.2 执行 pod install 脚本 七、延伸阅读&…

MySQL性能测试及调优中的死锁处理方法

以下从死锁检测、死锁避免、死锁解决3个方面来探讨如何对MySQL死锁问题进行性能调优。 死锁检测 通过SQL语句查询锁表相关信息&#xff1a; &#xff08;1&#xff09;查询表打开情况 SHOW OPEN TABLES WHERE IN_USE> 01 &#xff08;2&#xff09;查询锁情况列表 SEL…

达梦数据实时同步软件DMHS介绍和原理

1、产品介绍 达梦数据实时同步软件&#xff08;以下简称 DMHS&#xff09;是支持异构环境的高性能、高可靠、高可扩展数据库实时同步复制系统。该产品采用基于日志的结构化数据复制技术&#xff0c;不依赖主机上源数据库的触发器或者规则&#xff0c;对主机源数据库系统几乎无影…

计算机msvcp140.dll丢失如何解决,分享3个简单有效的方法

在计算机系统运行过程中&#xff0c;用户有时会遇到一个常见的错误提示——msvcp140.dll文件缺失&#xff0c;这一问题的发生往往会导致部分软件无法正常启动或运行。“针对计算机系统中出现的msvcp140.dll缺失问题&#xff0c;小编将详尽阐述并探讨5种有效的解决策略。每一种方…

Linux的SSH服务

一.SSH服务简介 1.什么是SSH SSH&#xff08;Secure Shell&#xff09;是一种安全通道协议&#xff0c;主要用来实现字符界面的远程登录、远程复制等功能。SSH 协议对通信双方的数据传输进行了加密处理&#xff0c;其中包括用户登录时输入的用户口令&#xff0c;SSH 为建立在应…

java大学生宿舍共享厨房系统宿舍自习室宿舍洗衣房系统源码包含技术文档

主要功能&#xff1a;学生可注册登录&#xff0c;预约自己宿舍楼栋的共享厨房和评价&#xff0c;也可以使用该楼栋的洗衣房&#xff0c;查看洗衣机吹风机的使用情况和报修&#xff0c;还可以进入该楼栋自习室打卡和评价。管理员可管理所有的学生和宿管&#xff0c;分配宿舍&…

【期末不挂科-C++考前速过系列P4】大二C++实验作业-继承和派生(3道代码题)【解析,注释】

前言 大家好吖&#xff0c;欢迎来到 YY 滴C考前速过系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的《…

【MATLAB源码-第110期】基于matlab的哈里斯鹰优化算发(HHO)无人机三维路径规划,输出做短路径图和适应度曲线。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 哈里斯鹰优化算法&#xff08;Harris Hawk Optimization, HHO&#xff09;是一种受自然界捕食行为启发的优化算法。它基于哈里斯鹰的捕猎策略和行为模式&#xff0c;主要用于解决各种复杂的优化问题。这个算法的核心特征在于…

神经辐射场(NeRFs)的研究进展

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 摘要Abstract文献阅读&#xff1a;神经辐射场&#xff08;NeRFs&#xff09;的研究进展1、研究背景2、方法发展3、相关方法3.1、Pixel NeRF3.2、RegNeRF3.3、Mip-Ne…

全链路追踪关键技术-TraceId、SpanId生成规则

链路追踪的traceid原理梳理 如何追踪微服务调用&#xff1f; ● traceId&#xff0c;用于标识某一次具体的请求ID。当用户的请求进入系统后&#xff0c;会在RPC调用网络的第一层生成一个全局唯一的traceId&#xff0c;并且会随着每一层的RPC调用&#xff0c;不断往后传递&…

FFmpeg 的使用与Docker安装流媒体服务器

本文阐述的均为命令行的使用方式&#xff0c;并不牵扯FFmpeg 的 C音视频开发内容&#xff0c;补充一句&#xff0c;C的资料真的少&#xff0c;能把C学好的人&#xff0c;我真的是觉得巨佬。 我主要是使用FFmpeg 推流方面的知识&#xff0c;案例大都是靠近这方面。 一、FFmpeg…

leetcode 125. 验证回文串

题目&#xff1a; 如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后&#xff0c;短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。 字母和数字都属于字母数字字符。 给你一个字符串 s&#xff0c;如果它是 回文串 &#xff0c;返回 true &…

【开源项目】深圳智慧城市~超经典CIM/BIM数字孪生可视化项目

飞渡科技数字孪生城市运行管理平台&#xff0c;以数字孪生为核心底层系统&#xff0c;将实景三维、大数据、云计算、人工智能等新一代技术&#xff0c;与城市管理服务相融合&#xff0c;构建高速率、高可靠和低延时的应用场景&#xff0c;打造全程全时、全模式全响应的数字孪生…

跨境电商必备:好用的邮箱推荐与实用指南

对于跨境电商企业而言&#xff0c;一封精炼、高效的邮件往往成为与国外买家或供应商沟通的利器。因此挑选一个得心应手的邮箱供应商&#xff0c;对于确保业务畅通无阻至关重要。本文将深入浅出地探讨跨境电商如何挑选邮箱&#xff0c;并推荐一些备受青睐的邮箱服务供应商。 一、…

LeNet-5(fashion-mnist)

文章目录 前言LeNet模型训练 前言 LeNet是最早发布的卷积神经网络之一。该模型被提出用于识别图像中的手写数字。 LeNet LeNet-5由以下两个部分组成 卷积编码器&#xff08;2&#xff09;全连接层&#xff08;3&#xff09; 卷积块由一个卷积层、一个sigmoid激活函数和一个…

GitHub项目推荐-incubator

项目地址 Github地址&#xff1a;GitHub - apache/incubator-anser 官网&#xff1a;Apache Answer | Free Open-source Q&A Platform 项目简述 这是Apache的一个开源在线论坛&#xff0c;也可以部署成为一个自有的QA知识库。项目主要使用了Go和Typescript来开发&#…

微服务治理:微服务断路器(微服务故障隔离模式)详解

微服务断路器是一种设计模式&#xff0c;可以保护系统免于级联故障&#xff0c;通过限制对故障服务的调用来实现。它的工作原理类似于电气断路器&#xff1a;当服务遇到问题时&#xff0c;它会切断请求流&#xff0c;使其有机会恢复&#xff0c;并防止其他服务被压垮。 工作原…

短视频抖音文案策划创作运营手册资料大全

【干货资料持续更新&#xff0c;以防走丢】 短视频抖音文案策划创作运营手册资料大全 部分资料预览 资料部分是网络整理&#xff0c;仅供学习参考。 抖音运营资料合集&#xff08;完整资料包含以下内容&#xff09; 目录 制作短视频的四部曲 主题 主题是短视频脚本的基调…