Linux之进程信号详解【上】

🌎 Linux信号详解


文章目录:

Linux信号详解

    信号入门

    技术应用角度的信号

    信号及信号的产生
      信号的概念
      信号的处理方式

      信号的产生方式
        键盘产生信号
        系统调用产生信号
        软件条件产生信号
        异常产生信号

    对信号产生方式的理解
      键盘产生信号
      异常信号的理解


🚀 信号入门


现实生活中的信号

  •  你在网上买了很多件商品,再等待不同商品快递的到来。但即便快递没有到来,你也知道快递来临时,你该怎么处理快递。也就是你能“识别快递”
  •  当快递员到了你楼下,你也收到快递到来的通知,但是你正在打游戏,需5min之后才能去取快递。那么在在这5min之内,你并没有下去去取快递,但是你是知道有快递到来了。也就是取快递的行为并不是一定要立即执行,可以理解成“在合适的时候去取”。
  •  在收到通知,再到你拿到快递期间,是有一个时间窗口的,在这段时间,你并没有拿到快递,但是你知道有一个快递已经来了。本质上是你“记住了有一个快递要去取”
  •  当你时间合适,顺利拿到快递之后,就要开始处理快递了。而处理快递一般方式有三种:1. 执行默认动作(幸福的打开快递,使用商品) 2. 执行自定义动作(快递是零食,你要送给你你的女朋友) 3. 忽略快递(快递拿上来之后,扔掉床头,继续开一把游戏)
  •  快递到来的整个过程,对你来讲是 异步 的,你不能准确断定快递员什么时候给你打电话

🚀技术应用角度的信号

  用户输入命令,在Shell下运行一个前台进程,用户键盘输入 Ctrl C (2号信号)则会产生一个硬件中断,被OS获取,解释成为信号,发送给目标前台进程,前台进程收到信号之后,引起进程退出。

在这里插入图片描述

  运行程序,生成前台进程,我们使用信号杀死前台进程:

在这里插入图片描述

注意

  • Ctrl-C 产生的信号只能发给前台进程。一个命令后面加个 & 可以放到后台运行,这样Shell不必等待进程结束就可以接受新的命令,启动新的进程。
  •   Shell可以 同时运行 一个前台进程 和任意 多个后台进程,只有 前台进程才能接到像 Ctrl-C 这种控制键产生的信号。
  •  前台进程在运行过程中用户随时可能按下 Ctrl-C 而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到 SIGINT 信号而终止,所以信号相对于进程的控制流程来说是 异步(Asynchronous) 的。

🚀信号及信号的产生

✈️信号的概念

  信号是进程之间事件异步通知的一种方式,属于 软中断

  Linux中存在许多信号,我们可以使用 kill -l 命令查看Linux中有哪些信号:

在这里插入图片描述

  Linux中,有 62种信号,前31种(1~31)信号被称为 标准信号,每个信号都有特殊的含义及用途。后31种(34~64)信号被称为 实时信号,可用于实时应用程序的拓展信号,提供了更多的灵活性。注意中间并没有32和33号信号

  我们可以通过man手册来查询不同信号的含义 man 7 signal :

在这里插入图片描述

  并且每个信号的编号都有自己的名字,这些 名字 其实就 C 语言的 ,如果调用信号,既可以通过信号的名称调用,也可通过信号的编号调用。当然,这么多的信号并不需要你全部记下来,我们在运用的过程中就会知道哪些信号常用,哪些不常用。


✈️信号的处理方式

  信号的 常见处理方式 有以下几种:

  1. 忽略此信号
  2. 执行该信号的默认处理动作
  3. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉(Catch)一个信号

  我们以信号的方式来终止进程,我们通过一下代码进行测试:

#include <iostream>
#include <unistd.h>
#include <sys/types.h>

int main()
{
    while(true)
    {
        std::cout << "I'am a proc, I'm running now..., pid: " << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

在这里插入图片描述

  我们使用9号信号和2号信号杀死了进程,这种发命令做出对应行为的方式 就是进程执行了信号的默认动作。好似阿熊在十字路口知道红灯停、绿灯行一样。

  但是今天我们以 信号捕捉 的方式来处理发来的信号,我们就需要用到 signal 接口:

typedef void (*signhandler_t)(int);
signhandler_t signal(int signum, sighandler_t handler);

在这里插入图片描述

  • signum参数传入需要捕捉的信号(名字或编号),当进程收到与其相匹配的信号时则会调用第二个参数,否则不会有任何动作
  • handler参数handlder方法,此方法为自定义方法,当收到signum信号则不会执行该信号的默认动作,变为执行该方法
  • 返回值返回前一个信号处理方法

  值得注意的是,我们在设置信号捕捉时,并不需要将此接口放入循环之中,只需要调用该接口一次,在整个程序中则一直循环有效。我们设置一个signal方法做测试:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>

void handler(int signo)// 回调函数,收到信号则执行handler方法
{
    std::cout << "get a sig, number is: " << signo << std::endl;
}

int main()
{
    signal(SIGINT, handler);// 设置信号捕捉

    while(true)
    {
        std::cout << "I'am a proc, I'm running now..., pid: " << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

在这里插入图片描述

  signal函数执行时并不会立刻对handler执行回调,只有当收到对应的信号时,才会执行回调。比如,阿熊跟室友打赌输了,接下来一周等红绿灯需要听室友的,本来红灯停变为了红灯唱歌,但是我们并不知道室友什么时候让阿熊唱歌。也就是说,因为 信号是异步触发,所以只有收到信号时才会执行回调。

  信号的默认处理方式还剩下忽略信号,也就是信号发送到进程,但是进程对其不管不顾。在程序中,使用捕捉的方式实现信号忽略,那么signal第二个参数应该调用 SIG_IGN(signal ignore):

在这里插入图片描述

  在底层,SIG_IGN 是将整形1强转为 __sighandler_t 类型,于是就可以告诉OS,进程运行时以忽略的方式将 signal 函数的第一个参数忽略:

在这里插入图片描述


✈️信号的产生方式
🚩键盘产生信号

  上面我们已经将以kill命令产生信号的方式介绍完了,而信号的产生方式还有一种,键盘产生信号,我们在上面也说了,使用 Ctrl-C 的方式可以终止进程,这就是一种键盘产生信号的方式,键盘产生信号的流程:

键盘特定输入 ——> OS解释为信号 ——> 向目标进程发送信号 ——> 进程收到信号 ——> 进程做出响应

  当我们把二号信号进行捕捉,并且回调函数只对信号进行打印动作,我们再使用 Ctrl-C 就杀不死该进程了:

在这里插入图片描述

  而键盘中并不只有 Ctrl-C 组合,还有 Ctrl-\ 组合,这里我使用的就是 Ctrl-\ 退出进程的,其对应信号的 3号信号


🚩系统调用产生信号

  除了键盘产生信号,我们还可以使用系统调用产生信号,Linux中存在 kill 接口:

int kill(pid_t pid, int signo);
  • 功能给指定的进程发送指定的信号
  • pid参数传入进程pid
  • signo参数传入对进程发送的信号
  • 返回值发送成功返回0,否则返回-1,并设置错误码

  我们编写一段代码测试kill调用:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cerrno>
#include <cstring>
#include <sys/types.h>

int main(int argc, char* argv[])
{
    if(argc != 3)// 参数控制为三个
    {
        std::cout << "Usage: " << argv[0] << " -signumber pid" << std::endl;
        return 1;
    }
    // argv[0]: ./testSig 运行可执行程序
    // argv[1]: 发送的信号
    // argv[2]: 进程pid

    int signumber = std::stoi(argv[1]);// argv[1]
    int pid = std::stoi(argv[2]);// argv[2]

    int n = kill(pid, signumber);
    if(n < 0)
    {
        std::cerr << "kill error, " << strerror(errno) << std::endl;
    }

    return 0;
}

在这里插入图片描述

  除了kill 系统调用之外,Linux还提供了一个 raise 接口:

int raise(int sig);

在这里插入图片描述

  • 功能传入信号给当前进程(调用此接口的进程)
  • sig参数需要传入的信号
  • 返回值0表示成功,非0为失败

  我们编写一段代码测试raise调用:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cerrno>
#include <string>
#include <cstring>
#include <sys/types.h>

int main(int argc, char* argv[])
{
    int cnt = 0;
    while(true)
    {
        std::cout << "cnt: " << cnt++ << std::endl;
        sleep(1);
        if(cnt == 5)// 5s 后退出进程
        {
            std::cout << "send 9 signal to caller" << std::endl;
            raise(9);
        }
    }

    return 0;
}

在这里插入图片描述
  除了raise系统调用之外,Linux还提供了一个 abort 接口:

void abort(void)

在这里插入图片描述

  • 功能对自己发送指定的信号,为6号信号 SIGABRT

我们编写一段代码测试abort调用:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cerrno>
#include <string>
#include <cstring>
#include <sys/types.h>

int main(int argc, char* argv[])
{
    int cnt = 0;
    while(true)
    {
        std::cout << "cnt: " << cnt++ << std::endl;
        sleep(1);
        if(cnt == 5)// 5s 后abort进程
        {
            std::cout << "send 6 signal to caller" << std::endl;
            abort();
        }
    }

    return 0;
}

在这里插入图片描述


🚩软件条件产生信号

  我们曾经学过管道,而管道四种特性有一种是 当没有读端时,写端会被终止。管道不具备写的软件条件了,所以触发了终止信号,这种信号为 SIGPIPE(13号信号)

  除此之外,由软件条件产生的信号还有 alarm 函数 和 SIGALRM(14号信号) 信号:

unsigned int alarm(unsigned int seconds);

在这里插入图片描述

  • 功能用于试着进程闹钟,指定时间(以秒为单位)后,向调用它的进程发送 SIGALRM 信号
  • seconds参数表示在多少秒后发送14号新号,如果为0,则任何未响应的 闹钟被取消
  • 返回值无符号整形,表示上次设置的闹钟还剩余的秒数。之前未设置闹钟,则返回0

  我们在代码上直观感受一下闹钟的相应:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cerrno>
#include <string>
#include <cstring>
#include <sys/types.h>

int main(int argc, char* argv[])
{
    // 设置一个闹钟
    alarm(1);
    
    int cnt = 0;
    while(true)
    {
        std::cout << "cnt: " << cnt++ << std::endl;
    }
    return 0;
}

在这里插入图片描述
  大概有3万七千次左右,虽然我们云服务器配置不高,但是3万7千次未免太少了。我们把打印信息注释掉,并且设置一个全局变量,让其在循环内一直做++,对14号信号再进行捕捉,捕捉回调方法打印全局变量:

在这里插入图片描述

  这次运行居然有5亿多次累加,至于为什么我们前面打印次数如此的少,这里我给出两个原因:

  1.在Linux下,一切皆文件,前面对屏幕打印文字的行为,本质上是对显示器文件进行疯狂打印。在前后对比下,我们能直观的发现其实 IO很慢
  2.由于我们是使用了云服务器,真正运行并不在本地运行,我们向云服务器发送命令,以及云服务器将信息从远端发到本地,都是经过网络的

  闹钟在被设置的时候,其默认动作只会响一次!如果我们想要设置多个闹钟,我们可以在回调handler方法里再加上n秒的闹钟,这样,第一次闹钟响了之后,进程收到闹钟信号执行回调方法,而main函数是被循环卡死的,所以往后就每隔n秒响一次闹钟。

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cerrno>
#include <string>
#include <cstring>
#include <sys/types.h>

void handler(int signo)// 收到闹钟信号进行回调
{   
    std::cout << "get a sign: " << signo << std::endl;
    int n = alarm(2);
    // exit(0);
}

int main(int argc, char* argv[])
{
    signal(SIGALRM, handler);
    // 设置一个闹钟
    alarm(4);

    int cnt = 0;
    while(true)
    {
        sleep(1);
        std::cout << "cnt: " << cnt++ << std::endl;
    }

    return 0;
}

在这里插入图片描述

  如果我们设定一个闹钟需要很久之后才会响应,但是我在此期间发送14号信号提前对SIGALRM进行捕捉,执行handler方法的回调,返回值是什么呢?

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cerrno>
#include <string>
#include <cstring>
#include <sys/types.h>

void handler(int signo)// 收到闹钟信号进行回调
{   
    std::cout << "get a sign: " << signo << std::endl;
    unsigned int n = alarm(2);

    std::cout << "闹钟剩余时间: " << n << std::endl;
    // exit(0);
}

int main(int argc, char* argv[])
{
    signal(SIGALRM, handler);
    // 设置一个闹钟
    alarm(100);

    int cnt = 0;
    while(true)
    {
        sleep(1);
        std::cout << "cnt: " << cnt++ << ", pid is: " << getpid() << std::endl;
    }

    return 0;
}

在这里插入图片描述

  我们只对闹钟进行了一次kill信号,我们第一个闹钟设置了100s,而提示信息是每隔一秒打印一次,算下来刚好过去5s,打印出的返回值为95s,而第二次回调的时候闹钟剩余时间就变为0了,这也就证实了alarm接口返回值是上一次闹钟剩余时间。

  接下来我来解释一下为什么闹钟也能作为软件条件?我们知道,alarm接口是系统调用接口,也就是说,设定闹钟,实际上是在操作系统内部设定的。而操作系统中存在的闹钟定然不止一个,所以OS一定要对这些闹钟做管理,如何管理?先描述,再组织

  根据以往经验,闹钟一定是有自己的结构体,结构体内有着必要的属性成员,那么在结构体的属性成员当中,就必定有闹钟的过期时间成员,用来记录闹钟的过期时间!

struct alarm
{
	uint64_t expired_time;// 闹钟过期时间
	// ...其他属性字段
}

  而闹钟的过期时间实际上是一个时间戳,一个线性增长的时间。那我们应该以什么样的结构组织起来这些闹钟呢?经常看我博客的小伙伴第一反应很可能是链表。设置一个双链表,按照闹钟过期时间来排序,之后我只要找到第一个过期的闹钟,那么在此之后必然全部都是过期闹钟。

  虽然这种想法很好,但是我们有更优解,我在很早之前写过一篇博客:堆与堆排序, 而操作系统就是采用最小堆的方法组织闹钟结构!以最小堆的堆顶一定是最近一次即将超时的闹钟。所以往后,OS只需要查找堆顶元素,过期了就释放掉,再通过堆调整,将次要过期的闹钟调整到堆顶。


异常产生信号

  第五种信号的产生方式,程序出了异常,操作系统定然不会在放任这个问题进程不管,会采取一定的措施,OS为了能让程序员知道程序出了问题,于是设置了一些常出现的异常信号,当进程出现异常时,OS将会对进程发送异常信号,爆出异常缘由。

  异常并不一定是由程序的语法、逻辑问题带来的,可很有可能是外部设备出了问题,所以异常又被分为 软件异常硬件异常

  软件异常通常有除零错误或者溢出错误引起的。而硬件异常通常是有进程访问无效地址引起的,一般有段错误等。具体情况具体分析,我们先对除零错误进行模拟:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cerrno>
#include <string>
#include <cstring>
#include <sys/types.h>

int main(int argc, char* argv[])
{
    int a = 10;
    a /= 0;

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

在这里插入图片描述

  除零错误会在Shell上爆出 Floating point exception 错误信息,并且除零错误对应的信号是 SIGFPE(8号信号)。我们再来模拟一下野指针异常:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cerrno>
#include <string>
#include <cstring>
#include <sys/types.h>

int main(int argc, char* argv[])
{
    int* p = nullptr;
    *p = 50;
    
    return 0;
}

在这里插入图片描述

  野指针异常通常会爆 Segmentation fault 错误,想必学C/C++的小伙伴对这种报错会经常见到,而其对应的异常信号为 SIGSEGV(11号信号)

  硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释 为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。


🚀对信号产生方式的理解

✈️键盘产生信号

  键盘产生数据毫无问题,但是操作系统是如何对组合键做出特殊处理的,OS怎么知道我使用Ctrl C 就是要发送2号信号给进程呢?我们都知道,操作系统向下管理软硬件资源,那么键盘硬件资源当然也属于操作系统管理范畴。

  所以,我们从键盘输入组合键,是由操作系统说的算的,而你使用的组合键是被OS解释为了命令。输入方在与键盘,解释方为操作系统,也就是说,键盘输入的到底是普通数据还是特殊命令,是由键盘驱动和OS联合解释的

  这么看来操作系统只需要解释我们输入的信息即可,但是对于OS来说,用户输入的动作一定是异步的。而我们在日常使用键盘的时候,操作系统似乎是优先处理的。这样对于操作系统的压力是不是有些大了?要知道OS可不止需要监测键盘,还有其他异步硬件,网卡就是典型的例子。OS并不能保证何时会不会有信息从网络发来。

在这里插入图片描述

  显然操作系统如果一直对这些异步发生的硬件进行监视的话,会将操作系统的性能拉低,而操作系统存在的意义就是,向下对软硬件资源管理,向上为用户提供良好服务。所以定然不会无时无刻监视这些硬件,于是就有人提出了 硬件中断技术

  早在我们电脑开机的时候,操作系统就给我们生成了一张 中断向量表 ,这张表提前注册对软硬件资源操作的方法。比如此表的二号下标的方法就是用来读取键盘输入的数据。而所谓的中断向量表实际上是一个 函数指针数组。那么其究竟是如何实现通过程序来访问硬件资源的呢?

  通过冯诺依曼结构,我们知道,CPU在数据层面只和内存级打交道。但是在每个CPU上都存在许多的针脚,这些针脚是物理性的,在主板上可以和各个硬件相连接,包括键盘也是通过CPU的针脚连接的。每个针脚都有自己的编号。而未来我们在按键盘时,通过针脚,使 CPU触发硬件中断!当然也没这么简单,他们之间可能存在 8259可中断控制器,这个我们目前不需要了解。

在这里插入图片描述

  不管如何,键盘和CPU可以通过针脚相互连接,而用户在键盘上输入数据时(发送高电平),就会触发硬件中断,此时CPU就可以检测到这个针脚有高电平,从而识别到键盘。而这时,CPU中的寄存器会将中断号(针脚编号)保存在寄存器内部,至此,硬件的动作就完成了!

  寄存器收到中断号后,被操作系统检测到,此时操作系统就会停下手头的工作。拿着这个中断号从中断向量表中查询(中断号就是中断向量表的下标索引)对应处理键盘资源的方法,进而调用这个方法去收集键盘发来的数据了。于是就可以把从键盘输入的数据读取到内存当中了。严格意义上来说,键盘文件也是文件,OS会先将数据读入到键盘文件的缓冲区里。

在这里插入图片描述

  读取到的键盘数据经过操作系统对字符的判定,判定为数据则发送到当前进程打开的键盘文件缓冲区中,而被判定为控制命令的组合键,则会被解释为信号,比如我输入了Ctrl C,那么OS就会将其解释为2号信号,而并非普通字符信息。这时,这个信号就会发送给调用键盘文件的进程,从而执行对应的动作。

  那么操作系统如何解释控制命令?实际上,信号在到来时,我们在处理更重要的事情,暂时不能处理到来的信号,所以我们一定需把信号保存到PCB中!要知道信号可是有整整62种,一个进程可能会存在多个信号,所以OS定要对这些信号做管理,如何管理?先描述,再组织

  而这些信号则是由位图这结构描述组织的!并且这个位图只需要32位比特位,因为标准信号只有31种,所以32位比特位完全够用。如果用户通过键盘对当前用户输入了Ctrl C,则会被操作系统解释为2号信号,通过位图,将第对应的比特位由0置1即可完成OS对进程发送信号。也就是说,操作系统向进程发送信号的本质是对进程PCB的位图进行写入操作!

在这里插入图片描述


✈️异常信号的理解

  前面出现了 除零错误(SIGFPE),以及野指针错误(SIGSEGV)都属于异常产生的信号,首先我们来分析除零错误。

计算错误

  除零错误,实际上也就是计算错误,在硬件方面,计算错误表现在CPU的寄存器上,我们知道,程序的计算都是在寄存器内完成的,寄存器可以存贮少量数据,而当计算发生错误,CPU停止对进程的操作,转而告诉操作系统当前处理的进程发生了计算错误。

  在CPU中存在一个标志寄存器,EFLAGS/RFLAGS标志寄存器,其 用于检测有符号数运算是否发生了溢出。这个寄存器存在一个名为 OF(OverFlow) 的标志位。当发生溢出错误时,OF被置为1,否则被置为0。

在这里插入图片描述

  操作系统收到CPU发来的信息,发现进程不再被调度了,于是操作系统就会检查EFLAGS/RFLAGS寄存器的溢出标记位OF,从而检测出当前进程出了计算异常,于是 OS就对当前进程发送(向pcb内写入)8号信号

  当我们对除零错误的异常信号进行捕捉,并且保持进程一直在运行状态:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cerrno>
#include <string>
#include <cstring>
#include <sys/types.h>

void handler(int signo)
{
    std::cout << "get a sig: " << signo << std::endl;
}

int main(int argc, char* argv[])
{
    signal(SIGFPE, handler);

    int a = 10;
    int b = 0;
    a = a / b;

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

在这里插入图片描述

  我们把异常信号的默认执行方式进行了捕捉,从而执行我们的handler方法回调。而handler方法我们仅仅打印了一句话,所以这个异常信号依旧存在。我们都知道,在CPU中寄存器只有一套,而 寄存器的数据可以有很多,这些数据我们称为 进程的上下文数据

  但是当前进程被我们设置为一直在运行,异常在进程中仍然存在,这个时候OS又会向OF读取异常数据,进而再一次的对当前进程发送8号信号,这样不断的循环,就导致了上图的结果。

野指针异常

  野指针异常是因为非法访问地址,而我们通过之前的学习,我们知道野指针内的地址一定是虚拟地址,而虚拟地址要映射到物理内存需要经过 由OS和CPU(MMU) 的 转化。而既然存在转化,就一定存在转化成功或者失败,我们来讨论一下转化失败的情况。

  在CPU中还存在两个很重要的寄存器:CR2CR3 寄存器,其中cr2寄存器 用于存储导致页表映射错误的虚拟地址。而cr3寄存器用于 存储页表的基地址,指向当前页表。现代电脑上的 MMU单元 都是被集成的CPU上的,其用于 虚拟到物理地址之间的转换

  当程序中发生了野指针错误,比如对空地址解引用赋值。此时当CPU执行到该语句的时候,会将指针内保存的虚拟地址由MMU和OS经过页表转化为物理地址,但是在转化的时候,页表中可能没有该虚拟地址的映射或者该映射的物理地址不可被写入(无w权限)。此时MMU就会转化失败,于是就向cr2寄存器写入出错的虚拟地址,cr2将虚拟地址保存。

在这里插入图片描述

  当进程出现了野指针异常时,当前进程就会停止调度,OS就会来检查为何当前进程停止调度,而CPU对cr2寄存器进行读取,发现当前进程出现了野指针错误,于是OS就对当前进程发送11号信号(SIGSEGV)从而终止进程!

总结

  所以产生信号不论是系统调用还是软件条件,亦或者是键盘、异常产生的信号,都是由操作系统同一发送的,因为OS作为软硬件资源的管理者,当进程出现异常时,需要对进程做相应的处理,这也就是为什么我们在windows下运行一些带有错误的程序时,进程会直接终止。


  今天的文章到此结束,感谢您的观看,如果对您有帮助的话还望给博主一个小小的三连呀~~

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

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

相关文章

Nvidia/算能 +FPGA+AI大算力边缘计算盒子:隧道和矿井绘图设备

RockMass 正在努力打入采矿业和隧道工程利基市场。 这家位于多伦多的初创公司正在利用 NVIDIA AI 开发一款绘图平台&#xff0c;帮助工程师评估矿井和施工中的隧道稳定性。 目前&#xff0c;作为安全预防措施&#xff0c;地质学家和工程师会站在离岩石五米远的地方&#xff0…

Llama模型家族之Stanford NLP ReFT源代码探索 (二)Intervention Layers层

LlaMA 3 系列博客 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;一&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;二&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;三&#xff09; 基于 LlaMA…

【Java 百“练”成钢】Java 基础:类和对象

Java 基础&#xff1a;类和对象 01.打印信息02.打印类的简单名称03.打印类的 ClassLoader04.获取类的方法05.获取类的Package06.创建一个对象数组07.计算圆的面积08.计算圆的周长09.创建具有私有访问修饰符的成员10.创建带访问修饰符的成员11.将对象作为参数传递12.通过类对象获…

开源多平台AI音乐生成器本地安装结合cpolar内网穿透实现远程访问

文章目录 前言1. 本地部署2. 使用方法介绍3. 内网穿透工具下载安装4. 配置公网地址5. 配置固定公网地址 前言 本文主要介绍如何在Windows系统电脑上快速本地部署一个文字生成音乐的AI创作工具MusicGPT&#xff0c;并结合cpolar内网穿透工具实现随时随地远程访问使用。 MusicG…

Linux 35.5 + JetPack v5.1.3@ ego-planner编译安装

Linux 35.5 JetPack v5.1.3 ego-planner编译安装 1. 源由2. 编译&安装Step 1&#xff1a;依赖库安装Step 2&#xff1a;建立工程Step 3&#xff1a;编译工程Step 4&#xff1a;安装工程 3. 问题汇总3.1 planner/plan_env - OpenCV3.2 uav_simulator/local_sensing - CUDA优…

基于非下采样小波包分析的滚动轴承故障诊断(MATLAB R2021B)

小波变换具有良好的时频局部化特性和多分辨率特性&#xff0c;可准确定位信号的突变点并可在不同尺度上描述信号的局部细节特征&#xff0c;被广泛应用于信号降噪。但标准正交小波变换不具有平移不变性&#xff0c;采用标准正交小波对信号消噪后&#xff0c;会在脉冲尖峰处产生…

进口电动防爆调节阀的性能-美国品牌

进口电动防爆调节阀以其卓越的防爆性能、安全可靠、精确控制以及广泛的技术规格和应用场合&#xff0c;成为工业领域中不可或缺的重要设备。 进口电动防爆调节阀的性能可以归纳如下&#xff1a; 一、防爆性能 防爆设计&#xff1a;进口电动防爆调节阀采用专门的防爆设计和制…

使用 PAI-DSW x Free Prompt Editing图像编辑算法,开发个人AIGC绘图小助理

教程简述 在本教程中&#xff0c;您将学习在阿里云交互式建模平台PAI-DSW x Free Prompt Editing&#xff08;CVPR2024中选论文算法&#xff09;图像编辑算法&#xff0c;开发个人AIGC绘图小助理&#xff0c;实现文本驱动的图像编辑功能单卡即可完成AIGC图片风格变化、背景变化…

5. 刷题

上海市计算机学会竞赛平台 | YACSYACS 是由上海市计算机学会于2019年发起的活动,旨在激发青少年对学习人工智能与算法设计的热情与兴趣,提升青少年科学素养,引导青少年投身创新发现和科研实践活动。https://www.iai.sh.cn/problem/868 题目描述 小爱需要完成 𝑛n 道题目…

算法训练营day04

一、24. 两两交换链表中的节点 题目链接&#xff1a;https://leetcode.cn/problems/swap-nodes-in-pairs/description/ 文章讲解&#xff1a;https://programmercarl.com/0024.%E4%B8%A4%E4%B8%A4%E4%BA%A4%E6%8D%A2%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9.h…

经典文献阅读之--P2O-Calib(利用点对空间遮挡关系的相机-激光雷达标定)

Tip: 如果你在进行深度学习、自动驾驶、模型推理、微调或AI绘画出图等任务&#xff0c;并且需要GPU资源&#xff0c;可以考虑使用UCloud云计算旗下的Compshare的GPU算力云平台。他们提供高性价比的4090 GPU&#xff0c;按时收费每卡2.6元&#xff0c;月卡只需要1.7元每小时&…

Android 13.0 Launcher3单层模式workspace中app列表页排序功能实现

1.概述 在13.0的定制化开发中,对于Launcher3的功能定制也是好多的,而对于单层app列表页来说排序功能的开发,也是常有的功能这就需要了解加载app数据的流程,然后根据需要进行排序就可以了,接下来就来实现这个功能 如图: 2. Launcher3单层模式workspace中app列表页排序功能…

[AI Google] 在 Android 上以更多方式体验 Google AI

总结 通过将 AI 直接构建到 Android 操作系统中&#xff0c;Google 正在重新想象用户与手机的互动方式。最新的更新包括用于家庭作业帮助的 Circle to Search、增强的 Gemini 助手功能以及通话期间的实时诈骗检测。Circle to Search 现在通过逐步指导帮助学生解决数学和物理问…

ChatTTS 开源文本转语音模型本地部署、API使用和搭建WebUI界面(建议收藏)

ChatTTS&#xff08;Chat Text To Speech&#xff09;是专为对话场景设计的文本生成语音(TTS)模型&#xff0c;特别适用于大型语言模型(LLM)助手的对话任务&#xff0c;以及诸如对话式音频和视频介绍等应用。它支持中文和英文&#xff0c;还可以穿插笑声、说话间的停顿、以及语…

Boom 3D软件最新版下载及详细安装教程

值得肯定的是Boom 3D最新版新增的Boom音量控制器和Controlled Boost功能为使用者提供了一个完美的控制&#xff0c;通过一个整齐的设计切换栏的系统音频输出&#xff0c;帮助他们轻松调整音量&#xff0c;从而让他们实现理想的音频输出&#xff0c;有需要的欢迎来开心电玩下载使…

如何从清空的回收站恢复照片

担心如何从清空的回收站中恢复已删除的照片&#xff1f;您删除的文件和文件夹暂时存储在 Windows 回收站中。它使 Windows 用户能够快速恢复意外或故意丢失的项目。但是&#xff0c;为了释放计算机驱动器上的某些空间&#xff0c;有时可能需要清理回收站。 然而&#xff0c;有…

B3900 [NICA #3] 数字显示屏

[NICA #3] 数字显示屏 题目描述 有一块显示屏&#xff0c;显示着 { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } \{\verb!0!,\verb!1!,\verb!2!,\verb!3!,\verb!4!,\verb!5!,\verb!6!,\verb!7!,\verb!8!,\verb!9!\} {0,1,2,3,4,5,6,7,8,9} 上的数字。每个数字都由如下图所示的…

后端服务器启动访问

VisualStudioCode(VSCode) 服务器启动 浏览器中测试访问 http://localhost:3000

【机器学习基础】Python编程10:五个实用练习题的解析与总结

Python是一种广泛使用的高级编程语言&#xff0c;它在机器学习领域中的重要性主要体现在以下几个方面&#xff1a; 简洁易学&#xff1a;Python语法简洁清晰&#xff0c;易于学习&#xff0c;使得初学者能够快速上手机器学习项目。 丰富的库支持&#xff1a;Python拥有大量的机…

C#操作MySQL从入门到精通(19)——插入数据

前言: 谈到数据库,大家最容易脱口而出的就是增删改查,在本篇文章之前一直都是说的各种查询,本文就是说的增删改查中的增,所谓增也就是增加数据的意思,插入数据就是增加数据。 本文测试使用的数据库如下: 1、插入完整行 所谓插入完整行就是一行所有列的数据都是自己插…