信号(二)【信号的产生】

目录

  • 1. 键盘组合键
  • 2. kill 命令
  • 3. 系统调用
  • 4. 异常
  • 5. 软件条件
  • 6. Term 和 Core 的区别

本篇文章介绍五种信号产生的方式,键盘组合键、kill 命令、系统调用、代码异常(进程异常)、软件条件来产生信号。

1. 键盘组合键

信号(一)【概念篇】 在第一篇信号的文章,我们就介绍了 Ctrl + c 的组合键,可以向进程发送 2 号信号,终止前台进程。

Crtl + \ :也是终止进程的一种键盘组合键,会向进程发送 3 号信号,与 2 号信号不同的是,3 号信号不仅会终止进程,还会产生核心转储(core dump)文件,用于调试目的。

void myhandler(int signo)
{
    cout << "process get a signal: " << signo << endl;
}
int main()
{
    signal(3, myhandler);     // 捕捉三号信号
    ....
    return 0;
}

在这里插入图片描述

在代码中调用 signal 捕捉三号信号时,当我们键盘按下 Crtl + \ ,就产生了 3 号信号,因此证明 Crtl + \ 就是向进程发送 3 号信号(SIGQUIT)。

Crtl + z:暂停一个进程,向进程发送 19 号信号,等同于 kill -19 命令。

void myhandler(int signo)
{
    cout << "process get a signal: " << signo << endl;
}
int main()
{
    signal(19, myhandler);     // 捕捉19号信号
    ....
    return 0;
}

在这里插入图片描述

执行 Crtl + z 与 kill -19 向一个进程发送信号的结果是一样的,因此可以证明 Crtl + z 就是向一个进程发送 19 号信号的键盘组合键。

但与 2 号、3 号信号不同的是,不管是通过键盘产生的信号,还是 kill 命令,都没有看到信号被捕捉(因为自定义信号处理的动作没有被执行),这也就说明了,并不是所有的信号都可以被 signal 捕捉的,比如此处的 19 号信号,还有 9 号信号,这两个信号都无法被捕捉(1 ~ 31 普通信号,不讨论 34 以上的实时信号)。

操作系统为什么要这样设计呢? ---- 很显然这两个信号都跟进程的执行有关,像 2、3 号信号都只能作用与前台进程,9 号信号是掌管所有类型的进程的生死信号。如果有人捕捉了 9 号信号,再自定义信号处理,那可以做到让所有用户都杀不掉某个进程。而 19 号暂停信号,当一个进程执行的工作很重要时,并且此时有点失控了,我可以选择不杀掉它,而是暂停它,把它的影响先降到最低。如果 19 号被捕捉了,我还怎么暂停?


2. kill 命令

kill -signo pid  	# 向pid进程发送一个signo信号

3. 系统调用

NAME
      kill - send signal to a process		// 向一个进程发送指定信号

SYNOPSIS
      #include <sys/types.h>
      #include <signal.h>
	
      int kill(pid_t pid, int sig);		

RETURN VALUE
       On success (at least one signal was sent), zero is returned.  On error, -1 is returned, and errno is set appropriately.
// 模拟 kill 命令
void Usage(string proc)
{
    cout << "Usage:\n\t" << proc << " signum pid\n\n";
}
int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    char* sigstr = argv[1] + 1;		// 去掉 -19 的 - 符号
    int signum = stoi(argv[1]);
    pid_t pid = stoi(argv[2]);

    int n = kill(pid, signum);  // 调用成功返回0,失败返回-1
    if(n == -1) 
    {
        perror("kill");
        exit(1);
    }
    return 0;
}

在这里插入图片描述


NAME
      raise - send a signal to the caller		// 发送一个信号给调用者,即发送给自己这个进程

SYNOPSIS
      #include <signal.h>

      int raise(int sig);
       
RETURN VALUE
      raise() returns 0 on success, and nonzero for failure.
int main(int argc, char* argv[])
{
    signal(2, myhandler);  
    int cnt = 0;
    while(true)
    {
        cout << "I am a process, pid: " << getpid() << endl;
        sleep(1);
        if(cnt & 1) raise(2);   // 每两次发送一次 2 号信号
    }
    return 0;
}

在这里插入图片描述

raise 这个系统调用,其实是对 kill 的一个封装,raise(2) 的本质就是 kill(getpid(), 2)


NAME
      abort - cause abnormal process termination	// 使一个正常运行的进程终止,即 6 号信号(SIGABRT)	

SYNOPSIS
      #include <stdlib.h>

      void abort(void);
       
RETURN VALUE
      The abort() function never returns.
int main(int argc, char* argv[])
{
    signal(SIGABRT, myhandler);  
    int cnt = 0;
    while(true)
    {
        cout << "I am a process, pid: " << getpid() << endl;
        sleep(1);
        if(cnt & 1) abort();
        ++cnt;
    }
}

在这里插入图片描述

这里有点奇怪了。在 信号(一)【概念篇】 介绍信号的三种处理方式时,不是讲的,只能三选一吗??不是说有了自定义信号处理,就不会触发信号的默认处理动作了吗??那为什么我向进程发送了一个 6 号信号,也调用 signal 注册捕捉了,但为什么还是触发了 6 号信号的默认动作??

这就是 abort() 这个函数内部做的行为了,在对信号处理这件事上,不同的函数内部可能都会对信号处理做一些特别的处理,当我们不调用 abort(),转而通过 kill 命令向进程发送 6 号信号,是不会同时执行自定义动作和默认动作的,因此可以确定这是 abort() 内部做的工作了。

在这里插入图片描述

  • 以上这些都是信号产生的方式,但无论哪种方式产生的信号,最终都是操作系统将信号发送个进程的。因为进程是操作系统的一部分,因此对于经常的一切管理,只能由操作系统来完成。

4. 异常

int main()
{
    int a = 10;
    a /= 0;
    return 0;
}

在这里插入图片描述

在介绍 进程的创建、终止 的时候,我们曾经说过,一个进程异常终止的本质是收到了信号。诸如上述代码发生的除0错误,而导致的进程异常退出,本质就是收到了 8 号信号,8 号信号的默认信号处理动作即终止进程,所以我们的进程才会退出。

void myhandler(int signo)
{
    cout << "process get a signal: " << signo << endl;
}

int main()
{
    signal(8, myhandler);	
    int a = 10;
    a /= 0;
    return 0;
}

在这里插入图片描述

而如果我们对除零错误所受到的信号做捕捉,并且自定义信号处理中不退出该进程,那么即便进程异常了,它也不会终止退出(因为我们做了信号捕捉,自定义了处理方式,因此不会执行默认的处理方式)!

但是!捕捉了信号,不仅进程没有退出,而且信号一直在被触发,这是为什么呢?

首先我们可以排除,这是针对代码异常后做信号捕捉的特性,而不是除零错误才会这样的

int main()
{
    signal(11, myhandler);	
    int* p = nullptr;
    *p = 100;
    return 0;
}

在这里插入图片描述

诸如对野指针 或者 内存越界访问等问题,它也会导致进程异常终止,而对信号做捕捉,它也同样会出现信号一直被触发的现象。

因此,我们可以得出结论,代码发生异常了,进程不一定退出!因为异常(信号)可以被用户捕捉。而如果发生异常时,进程退出了,那一定是执行了信号处理方法。

所以如果你愿意,进程异常时,对信号做捕捉,然后不让进程退出。但是,进程一旦异常了,继续执行下去是没有任何意义的,它也无法继续执行下去,会一直处于在异常信号触发那里。

  • 为什么诸如上述的除零错误、野指针访问等异常错误,会向进程发送信号呢?

    简单概括,即这些异常引起系统问题,然后操作系统向进程发送信号,而进程受到信号后,对特定进程的默认处理动作就是终止自己。

  • 操作系统如何得知,进程会出现除零错误 或 野指针访问等异常的?(如果操作系统不知道,那肯定就不会向进程发送特定喜欢,然后进程再做信号处理)

    在 cpu 内部会有一些寄存器,诸如 eip/pc,用于记录下一条指令的地址;通用寄存器 eax、ebx、ecx、edx 等等,用于配合 cpu 做硬件计算;还有一种状态寄存器,这个寄存器是按照比特位来设置标志位的,其中就有一个溢出标志位,而例如除零错误,本质就是除以一个无限小的数,那么就会发生数值溢出问题,因此溢出标志位就由 0 (表示无效) 变为 1 (表示有效)。

    这是当进程发生除零异常,在状态寄存器(硬件层面)表现出来的处理动作。而除了意识到这是在硬件上对进程异常的处理动作,还要清楚 cpu 内部很多的寄存器内的数据,都属于进程的上下文数据。所以当发生进程切换时,这个进程出现了异常,此时的状态寄存器的标志位是异常的,但是这并不影响其它进程!因为这个发生异常的进程在被切换时,会带走所有它的上下文,包括状态寄存器中的标志位数据,而新来的进程,同样会有自己硬件上下文,在切换到 cpu 上执行时,也会把自己状态寄存器的内容放上来。因此,虽然进程异常时,修改的是 cpu 内部的状态寄存器,但是这并不影响其它进程。这也就是从调度、硬件层面上保证了进程的独立性!

    所以不要认为只要硬件出错了,整个计算机就会出问题。因为有进程的存在,又因为所有用户的任务都是被进程包裹起来的,所以任何异常只会影响进程本身,并不会波及至操作系统。

    当前进程在运行时,状态计存器中的溢出标志位出问题了,操作系统必须要知道它溢出了。因为操作系统也是硬件的管理者,状态计存器属于 cpu 内部的寄存器硬件单元,cpu 也属于硬件,那么操作系统要管理硬件,就必须要清楚它们的健康状态!而当一个进程出现异常了,也就没必要继续往后执行了(都异常了,即便执行出来一个结果,用户敢用这个结果吗?),所以当操作系统发现 cpu 的状态出问题了,它的状态寄存器溢出了,便直接向进程发送信号,然后进程收到信号后,就终止了自己这个进程。

    换言之,操作系统为何能够得知除零或者野指针等异常发生了,本质就是进程异常都会被转化成硬件问题,而操作系统作为硬件的管理者,能够识别硬件的状态,然后处理问题。而操作系统处理异常,并不影响整个操作系统的稳定性,只会影响出现异常的进程。

    而对于野指针等内存越界访问的异常错误,访问真实的物理内存之前,都是需要先经过页表,即先查表,而查表这件事并不是操作系统直接做的,而是由 MMU(内存管理单元) 来完成的,这个 MMU 是被集成到 cpu 内部的。而当访问野指针时查表,可能是页表中没有对应的映射关系,也可能是权限问题导致无法映射访问,本质都是虚拟到物理地址的转换失败所导致的野指针异常!当转换失败时,MMU 内存管理单元报错,然后会把转换失败的虚拟地址存储到 cpu 内部的寄存器中。并且同样的,野指针造成的进程异常,不会影响其它进程。因为在 cpu 内部会存在一个 cr3 寄存器,这个寄存器会保存当前进程的页表的起始地址(物理地址),当异常发生,进程被 cpu 切换时,cr3 寄存器的内容也是进程的硬件上下文,也要被该进程一起带走!

    所以,不管什么异常,只要进程异常,cpu 内部的硬件都会报错!因此操作系统作为硬件的管理者,就能够识别到硬件出问题了,进而向异常进程发送特定信号,然后终止进程。

  • 如果对进程异常做了信号捕捉,但是不退出进程呢??

    操作系统并没有限制进程异常一定得退出,因为其提供了捕捉信号、信号自定义处理等功能。但是进程异常之后不退出,也要一直被调度。但是当被 cpu 调度运行时,cpu 内部的硬件立马报错,操作系统识别到进程异常,又一次把进程换下了,就这样循环反复。因为自始至终,这个进程引发的硬件问题并没有得到修正,这也为什么我们进程异常不退出时,信号会一直被触发,因为操作系统一直识别到进程异常,一直给进程发送信号!

    但是,用户(进程) 也没有权限修正 cpu 内部硬件的数据,所以进程异常了,除了退出,别无所择。异常了为什么还要继续往后执行呢,就算真的执行出来个结果,用户它敢信这个结果是正确的吗?用户不敢!

  • 那进程异常终止就完事了,为什么用户还要捕捉异常呢?

    捕捉信号不是为了解决问题,而是为了让用户更加清楚的确定进程异常出错的原因,甚至可以在进程退出之前完善错误日志等善后工作。


5. 软件条件

  • 异常只会由硬件产生吗??

    不一定。还记得管道通信吗,当管道没有写入数据时,读端阻塞;那么当写端一直往管道写入数据,而读端的文件描述符被关闭了,那么写端就会被操作系统干掉!并且向写端发送 SIGPIPE 信号(即 13 号信号),由类似管道的这种情况导致的进程终止,称为软件异常!所以异常也可以是软件产生的。(为什么这是软件异常?因为管道是操作系统创建的,进程也是操作系统的,而管道读端关闭了,操作系统识别到后就会干掉写端,操作系统是一个软件,所以这是一个由软件产生的异常)。

    int main()
    {
        char buffer[1024];
        int n = read(4, buffer, sizeof(buffer));
        cout << "n = " << n << endl;
        perror("read");
        return 0;
    }
    

    在这里插入图片描述

    像这个案例,读取一个没有打开的文件,它并不像我们所说的读端不存在,写端直接被操作系统异常终止,而是以函数返回式的形式报错。无论是写还是读,这其实都属于文件操作的范畴,但是我们可以看到操作系统是有着截然不同的态度的。这其实是与操作系统的设计有关,对于有些地方,操作系统会以产生异常形式通知,有些地方则是直接以函数调用出错的形式返回,一个问题如何反馈到用户层,都是取决于操作系统本身的。

而软件不只可以产生异常,还可以产生一些特殊事件,即软件条件,比如闹钟。

操作系统既然能够识别到管道写端还是读端某一端关闭,以此来决策处理这些情况,说明操作系统具备对软件状态信息检测的能力。其中,操作系统就可以通过对软件条件做检测来完成一些功能,比如闹钟。

NAME
      alarm - set an alarm clock for delivery of a signal	 // 设置一个闹钟,闹钟响了即向进程传递一个信号,即 14) SIGALRM	

SYNOPSIS
      #include <unistd.h>

      unsigned int alarm(unsigned int seconds);		// seconds 秒之后闹钟响起

RETURN VALUE		
	  // alarm() 返回上一次设置的那个闹钟,距离现在还剩下的秒数。
      alarm() returns the number of seconds remaining until any previously scheduled alarm was due to be delivered,
      or zero if there was no previously scheduled alarm.		
int main()
{
    int n = alarm(5);
    ....
}

在这里插入图片描述

现象:闹钟 5 秒后响起,向进程发送了 14) SIGALRM 信号,信号的默认处理动作是终止进程。

int main()
{
	signal(14, myhandler);
    int n = alarm(5);
    ....
}

在这里插入图片描述

现象:当我们对信号做捕捉,信号的默认处理动作就不会被触发,因此进程没有被终止,转而执行信号的自定义处理动作,并且闹钟只响一次,没有一直响起,因为代码中只设置了一次闹钟,闹钟不是异常,因此也不会像异常一样,一直处于信号触发的状态。

而我们设置闹钟时可能不止设置了一次,可能是重复设置的,因此 alarm 的返回值即表示设置的上一个闹钟距离现在还剩余多少时间才会响起。

void myhandler(int signo)
{
	...
    int n = alarm(5);
    cout << "上一次设置的闹钟的剩余时间:" << n << endl;
}

int main()
{
    signal(14, myhandler);
    int n = alarm(50);
    ....
}

在这里插入图片描述

在操作系统中是存在大量进程的,并且每一个进程都可以设置闹钟,因此操作系统中是可能存在大量的闹钟的,那么操作系统就需要对闹钟进行管理。所以对于每一个闹钟,都在内核当以类似 struct Alarm 的结构体描述起来,并且里面一定会包含一个指向 task_struct 的指针或者进程的 pid,用于指明该闹钟属于哪个进程设置的,并且在闹钟响起时,能够快速的找到该进程,然后向该进程发送信号;还得记录一下闹钟超时的时间(以时间戳的形式记录),将来就可以根据系统中维护的当前时间与闹钟的描述结构体中记录的未来的超时时间进行比对,即可确定该闹钟有没有超时,然后决策何时向对应进程发送信号的工作。在这之后,在通过链表把各个 struct Alarm 描述结构体组织起来,以后操作系统对闹钟的管理就转变为对链表的增删查改。

但是系统中可能有非常多的闹钟,如果每次都去遍历链表查看闹钟的状态,可能很多闹钟都处于未触发时刻,那么操作系统不就白忙活了,遍历了半天,结果一个闹钟没响。而又因为时间是线性增长的数据,因此操作系统可以借助最小堆数据结构来管理每一个闹钟对象,之后检查闹钟状态时,如果发现堆顶的闹钟都没有超时,那么系统中其它所有闹钟就没有超时!如果堆顶的闹钟超时了,那么向该进程发送信号,然后把该闹钟对象从堆顶 pop 出去,再调整一下堆结构,直到检测堆顶的闹钟没有超时为止,这样操作系统即完成了对闹钟管理的工作。

6. Term 和 Core 的区别

在这里插入图片描述

在 linux 中的 7号手册中就记录了普通信号的默认处理动作,其中我们可以看到有一部分信号都是 Term 或者 Core,这两者都表示终止的意思。那么 Term 和 Core 有什么区别呢?

在这里插入图片描述

在 waitpid 进程等待中,就有一个 status 参数,记录了等待的进程的退出情况。这个整数被分为了几部分使用,次低8位用于表示进程的退出码,而低7位表示进程退出时收到的信号,那么第8个比特位 —— core dump 是什么呢??

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        int cnt = 500;
        while(cnt)
        {
            cout << "I am a child process, pid: " << getpid() << endl;
            sleep(1);
            cnt--;
        }
        exit(0);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid == id)
    {
        cout << "child quit info, rid: " << rid << ", exit code: " << ((status >> 8) & 0xFF)
            << ", exit signal: " << ((status >> 7) & 0x7F1) << " core dump: " << ((status >> 7) & 1 ) << endl;   }
    return 0;
}

在这里插入图片描述
在这里插入图片描述

现象:向进程发送 2 号信号,如 man 手册说明一样,信号的处理动作是 Term,因此 core dump 的标志位为 0,而当向进程发送一个处理动作是 Core 的信号,core dump 的标志位即为 1。

  • 所以什么是 core dump 呢?

    当进程异常终止时,操作系统会将进程在内存中的运行信息,给用户 dump(转储) 到进程的当前目录(即磁盘)形成一个形如 core.pid 文件,这个文件就是核心转储 (core dump)。

如果是云服务器运行的,上述两种情况可能 core dump 都为 0,这是因为云服务器的 core 文件默认被关闭了,因此系统不会形成核心转储(core dump) 文件。

ulimit -a:可查看系统标准配置,其中就有 core file size
ulimit -c 数值:设置 core file 的大小,这样即可打开系统的 core dump 功能。

  • 为什么要有 core dump?

    进程运行时出问题了,终止信号、退出码,我们都可以通过 status 获取,但是有了 core dump,我们就能够直接复现问题,定位到源码中具体哪一行代码出错。这种先运行,在 core-file,即为事后调试,本质都是为了提高解决 bug 的效率。

  • 如何利用 core dump 定位错误?

    // 示例代码
    int main()
    {
        int a = 10;
        int b = 0;
        a /= b;
        ....
    }
    

    编译源文件时带上 -g 选项,生成调试版可执行程序,先跑一遍程序,生成核心转储(core dump) 文件。 然后 gdb 启动程序,直接输入 core-file core.pid 文件,即可定位到源码中具体出错位置。

    在这里插入图片描述

  • 拓展:为什么云服务要默认禁用 core 文件呢?

    因为每一次进程异常终止了,都会当前的目录下形成一个比较大的文件(core 文件)。而在大型企业,后端服务器集群主机是非常多的,所以一般服务挂掉了,都是由自动化运维立马把服务恢复起来的,后续运维部门在根据日志等信息排查问题。所以假如公司混了几位很水的程序员,写的服务漏洞百出,测试也是水,也没测出来,然后服务就这样上线了,然后马上就挂掉了,之后又立刻被自动恢复,然后又挂,又恢复(并且这种挂掉之后被自动恢复的过程是非常快速的,基本无感知的),但是每次挂掉,又会形成一个不小的 core 文件,那么磁盘可能一下子就被填满了,之后可能连操作系统都直接挂掉了。这样的影响,可比没有core dump 文件来的影响大的多。而服务器都是线上的生产环境,所以一般都默认关闭了 core dump 的转储。


如果感觉该篇文章给你带来了收获,可以 点赞👍 + 收藏⭐️ + 关注➕ 支持一下!

感谢各位观看!

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

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

相关文章

商汤科技十周年公布新战略,将无缝集成算力、模型及应用

10月18日&#xff0c;恰逢商汤科技十周年庆典&#xff0c;“2024商汤十周年国际论坛&#xff1a;迈向AI 2.0共融新时代”在香港科学园成功举办。 据「TMT星球」了解&#xff0c;来自全球的行业领袖、政府代表、AI专家共聚于此&#xff0c;共同探讨AI行业的未来。 活动上&…

Linux隐藏权限介绍

隐藏权限概览 在Linux系统中&#xff0c;有时即便是以root用户身份&#xff0c;你也可能遇到无法修改特定文件的情况。这种限制往往源自chattr命令的应用&#xff0c;该命令用于为文件或目录设置“隐藏权限”&#xff0c;即底层属性&#xff0c;以增强系统安全性。值得注意的是…

Standard IO

为了提高可移植性&#xff0c;将通用IO接口经过再封装就形成了标准IO&#xff0c;标准IO不仅适用于Unix环境&#xff0c;也兼容非Unix环境&#xff0c;这也是为什么说我们应该尽可能的使用标准IO&#xff0c;通用IO通过文件描述符fd来与文件交互&#xff0c;为了以示区分&#…

极氪MIX:一台只有你想不到,没有它做不到的“家用神车”

了解极氪品牌的朋友应该都知道 极氪一直都在尝试打破目前汽车或者生活的一些现状 更愿意创造一些破界、超前的产品 比如说将家庭城市通勤、假日露营、自驾旅行、户外垂钓、朋友相聚等多场景融入一个空间的极氪MIX 这款车突破了SUV或MPV车型形态的固有限制 前悬仅 865mm&am…

【ArcGIS Pro实操第八期】绘制WRF三层嵌套区域

【ArcGIS Pro实操第八期】绘制WRF三层嵌套区域 数据准备ArcGIS Pro绘制WRF三层嵌套区域Map-绘制三层嵌套区域更改ArcMap地图的默认显示方向指定数据框范围 Map绘制研究区Layout-布局出图 参考 本博客基于ArcGIS Pro绘制WRF三层嵌套区域&#xff0c;具体实现图形参考下图&#x…

Centos安装Nginx 非Docker

客户的机器属于 Centos7 系列&#xff0c;由于其较为陈旧&#xff0c;2024开始众多镜像和软件源都已失效。此篇文章将详细记录在 Centos7 操作系统上从零开始安装 Nginx 的整个流程。 本文Nginx是安装在/usr/local/nginx下 详细步骤如下&#xff1a; 准备Nginx安装包&#x…

安防监控摄像头图传模组,1公里WiFi无线传输方案,监控新科技

在数字化浪潮汹涌的今天&#xff0c;安防监控领域也迎来了技术革新的春风。今天&#xff0c;我们就来聊聊这一领域的产品——摄像头图传模组&#xff0c;以及它如何借助飞睿智能1公里WiFi无线传输技术&#xff0c;为安防监控带来未有的便利与高效。 一、安防监控的新篇章 随着…

程序员适合玩的游戏:《人力资源机器》提升编程思维【Human Resource Machine】

程序员适合玩的游戏&#xff1a;《人力资源机器》提升编程思维【Human Resource Machine】 在当今这个技术日新月异的时代&#xff0c;编程已经成为一门不可或缺的技能。对于程序员来说&#xff0c;不仅需要扎实的专业知识&#xff0c;还需要不断锻炼逻辑思维和解决问题的能力…

用.NET开发跨平台应用程序采用 Avalonia 与MAUI如何选择

Avalonia是一个强大的框架&#xff0c;使开发人员能够使用.NET创建跨平台应用程序。它使用自己的渲染引擎绘制UI控件&#xff0c;确保在Windows、macOS、Linux、Android、iOS和WebAssembly等不同平台上具有一致的外观和行为。这意味着开发人员可以共享他们的UI代码&#xff0c;…

RNN、LSTM 与 Bi-LSTM

一. RNN 循环神经网络&#xff08;Recurrent Neural Network, RNN&#xff09;是深度学习领域一类具有内部自连接的神经网络能够学习复杂的矢量到矢量的映射。 最大特点&#xff1a;前面的序列数据可以用作后面的结果预测中。 一个简单的循环神经网络结构&#xff0c;其结构包…

如何写一个视频编码器演示篇

先前写过《视频编码原理简介》&#xff0c;有朋友问光代码和文字不太真切&#xff0c;能否补充几张图片&#xff0c;今天我们演示一下&#xff1a; 这是第一帧画面&#xff1a;P1&#xff08;我们的参考帧&#xff09; 这是第二帧画面&#xff1a;P2&#xff08;需要编码的帧&…

Golang | Leetcode Golang题解之第480题滑动窗口中位数

题目&#xff1a; 题解&#xff1a; type hp struct {sort.IntSlicesize int } func (h *hp) Push(v interface{}) { h.IntSlice append(h.IntSlice, v.(int)) } func (h *hp) Pop() interface{} { a : h.IntSlice; v : a[len(a)-1]; h.IntSlice a[:len(a)-1]; return v }…

SCCB协议与IIC协议不同

SCCB开始信号与结束信号都与IIC协议的大概一致&#xff0c;这里就不细讲了 开始、结束信号参考&#xff1a;【I2C】IIC读写时序_iic读时序-CSDN博客 SSCB写时序&#xff1a; 即&#xff1a;start phase_1 phase_2 phase_3 stop SCCB读时序&#xff1a; 即&#xff…

电脑视频剪辑大比拼,谁更胜一筹?

随着短视频的火爆&#xff0c;越来越多的人开始尝试自己动手制作视频&#xff0c;无论是记录生活点滴还是创作个性短片&#xff0c;一款好用的视频剪辑软件是必不可少的。今天&#xff0c;我们就从短视频运营的角度&#xff0c;来聊聊几款热门的电脑视频剪辑软件&#xff0c;看…

在做题中学习(66):两数相加

解法&#xff1a;模拟 思路&#xff1a;定义一个变量t&#xff0c;存储相加后的结果&#xff0c;个位赋给新节点&#xff0c;十位&#xff08;表示有进位&#xff09;留下&#xff0c;累加到下一次加法&#xff08;相当于上进位&#xff09;。while里即便cur1和cur2都为空了&a…

windows文件拷贝给wsl2的Ubuntu

参考&#xff1a; windows文件如何直接拖拽到wsl中_win 移到文件到wsl-CSDN博客 cp -r /mnt/盘名/目标文件 要复制到wsl中的位置e.g.cp -r /mnt/d/byt5 /home Linux文件复制、移动、删除等操作命令_linux移动命令-CSDN博客 Linux 文件、文件夹的复制、移动、删除 - Be-myse…

重生之“我打数据结构,真的假的?”--1.顺序表(无习题)

C语言中的顺序表详细总结 1. 概述 顺序表&#xff08;Sequential List&#xff09;是一种线性数据结构&#xff0c;用于存储具有相同数据类型的一组元素。顺序表采用一段连续的存储空间&#xff0c;使用数组来实现&#xff0c;能够高效地支持随机访问操作。在 C 语言中&#…

No.19 笔记 | WEB安全 - 任意文件操作详解 part 1

1. 任意文件上传漏洞基础 什么是文件上传功能? 在网站和应用中,我们经常会看到允许用户上传文件的功能,比如: 更换头像:让用户上传自己的照片作为头像发布图片:在社交媒体或论坛上传图片提交文档:在办公系统中上传Word、Excel等文档 这些都是常见的文件上传功能。 任意文…

RabbitMQ系列学习笔记(四)--消息应答机制

文章目录 一、消息应答详解1、基本概念2、自动应答3、手动应答4、自动重新入队5、手动应答代码6、手动应答演示 二、不公平分发三、预取值机制 本文参考&#xff1a; 尚硅谷RabbitMQ教程丨快速掌握MQ消息中间件rabbitmq RabbitMQ 详解 Centos7环境安装Erlang、RabbitMQ详细过程…

如何去掉歌曲的人声只剩伴奏?伴奏独享的方法

在音乐制作、后期处理或是个人娱乐中&#xff0c;我们经常遇到需要将歌曲中的人声去除&#xff0c;仅保留伴奏的情况。虽然这一过程可能听起来颇为复杂&#xff0c;但实际上&#xff0c;借助现代音乐技术和软件&#xff0c;我们可以较为轻松地达成这一目标。本文将介绍三种常见…