【Linux】进程信号详解

news2024/11/15 10:51:14

一、信号

1. 信号的概念

       Linux提供的让用户或进程给其他进程发送异步信息的一种方式,信号由进程发送的,属于软件中断。

2. 信号的作用

  • 当 进程执行出现致命错误进程所需的软件条件不具备 时,给操作系统提供的一种及时终止进程的机制
  • 用户想在某一时刻终止进程时,给用户提供的一种终止进程的机制

3. 信号的种类

(1)查看信号的种类

kill -l

(2)1~31:不可靠信号(非实时)

信号编号信号名称作用说明
1SIGHUP挂起信号,常用于通知进程控制终端已关闭或需要重新初始化
2SIGINT中断信号,通常由用户在终端按下 Ctrl + C 产生,用于请求进程终止
3SIGQUIT退出信号,通常由用户在终端按下 Ctrl + \ 产生,会导致进程产生核心转储并终止
4SIGILL非法指令信号,指示进程执行了非法的机器指令
5SIGTRAP跟踪/断点陷阱信号,常用于调试
6SIGABRT异常终止信号,通常由 abort 函数调用产生
7SIGBUS总线错误信号,通常表示访问内存时出现总线错误
8SIGFPE浮点异常信号,例如除零错误
9SIGKILL强制终止信号,无法被捕获或忽略,用于立即终止进程
10SIGUSR1用户自定义信号 1,可由用户程序自定义用途
11SIGSEGV段错误信号,通常表示访问非法的内存地址
12SIGUSR2用户自定义信号 2,可由用户程序自定义用途
13SIGPIPE管道破裂信号,当向一个没有读端的管道写入数据时产生
14SIGALRM闹钟信号,由 alarm 函数设置的定时时间到达时产生
15SIGTERM终止信号,可被进程捕获并进行自定义处理
16SIGSTKFLT栈错误信号
17SIGCHLD子进程状态改变信号,当子进程终止、暂停或恢复时产生
18SIGCONT继续信号,用于恢复被暂停的进程
19SIGSTOP暂停信号,无法被捕获或忽略,用于暂停进程
20SIGTSTP终端停止信号,通常由用户在终端按下 Ctrl + Z 产生
21SIGTTIN后台进程试图从控制终端读取时产生
22SIGTTOU后台进程试图向控制终端写入时产生
23SIGURG紧急数据到达套接字的信号
24SIGXCPU超过 CPU 时间限制信号
25SIGXFSZ超过文件大小限制信号
26SIGVTALRM虚拟定时器信号
27SIGPROF性能分析定时器信号
28SIGWINCH窗口大小改变信号
29SIGIOI/O 就绪信号
30SIGPWR电源故障信号
31SIGSYS系统调用错误信号

(3)34~64:可靠信号(实时信号,暂不考虑)

(4)可靠信号与不可靠信号的区别点

区别点可靠信号不可靠信号
信号丢失不会丢失可能丢失
排队机制支持排队不支持排队
信号处理函数阻塞期间新信号不会被丢弃,排队等待处理新信号可能被丢弃
发送次数记录准确记录发送次数可能不准确
默认处理方式默认终止进程不一定终止进程

4. 不同属性的信号对进程的默认操作

(1)查看信号属性

man 7 signal
Term默认操作终止进程
Ign默认操作忽略信号
Core默认操作终止进程,并核心转储(core dump)
Stop默认操作暂停进程
Cont默认操作继续执行当前暂停的进程

(2)Term 与 Core 的不同之处

        Term 是直接终止掉进程,不做其他的处理

        Core 在终止进程的同时,会将进程在内存中的核心数据(与 Debug 有关)转储到磁盘中形成 core(Ubuntu) core.pid(CentOS)文件,我们就可以通过 core文件 定位到进程为什么退出,以及执行到哪行代码退出的

【注】:

        我们目前看不到 Term 与 Core 的区别,是因为云服务器与虚拟机默认将进程的

core dump 功能关闭的

Core功能:

  1. 确认是否打开 core dump 功能 
    ulimit -a
  2. 打开 core dump 功能
    ulimit -c size
    // size 换成大于0的就行(表示核心转储文件的上限,设置为0就是不进行核心存储)
  3. 关闭 core dump 功能
    ulimit -c 0

        此时我们就可以测试发现,打开 core dump 功能后,会出现core文件

为什么要默认关闭core dump 功能 ?

        防止有未知的 core dump 一直在进行,从而产生大量的 core 文件,将磁盘打满。

(如:一个进程死循环创建子进程,并且在子进程中故意创造致命错误)

        所以新版内核为了防止此类事故,将 core 文件统一命名为 core,就可以保证无论怎么进行 core dump ,都永远只是一个 core 文件,只保存最新的出错信息及中断代码行。

core dump 的作用:

        协助调试,在 gdb 中,可以使用 core-file core 快速定位出错代码行(事后调试)

(3)知识链接:进程退出码

        这里的 core dump 标志位若为1,则表示当前进程已经发生了 core dump 核心转储

core dump 标志位取决于

  1. 是否开启 core dump 功能
  2. 是否为 core 退出

5. 进程看待信号的方式

  1. 进程默认知道信号的种类与默认处理方式(表现在 task_struct 的三张位图)
  2. 信号到来,可以不立即处理,在合适的时候处理,此时在 task_struct 中保存该信号
  3. 进程不会等待信号的到来,信号是异步产生的

二、信号的保存形式

1. 基本概念:

  • 进程可以选择阻塞信号
  • 信号递达:实际执行信号的处理动作,即处理了信号就是递达。
                      分为:默认处理方法、自定义处理方法、忽略信号
  • 信号未决:信号产生 与 信号递达之间的状态,即被阻塞的信号

2. 信号在内核中的保存形式

        在OS创建进程时,会先创建 task_struct 同时初始化内部信息,就包括信号的三张位图,这也说明进程在一开始就是认识信号的。

block 位图        表示信号是否被屏蔽

bit 位置:信号编号

bit 内容:是否屏蔽该信号

pending 位图表示信号是否被捕捉

bit 位置:信号编号

bit 内容:信号是否到来

handler 位图表示信号的处理方法

默认处理方法(SIG_DFL)
自定义处理方法:调用系统调用接口

忽略处理方法(SIG_IGN)

3. 相关问题


(1)如果一个信号被阻塞,那么这个信号永远不会被递达处理,除非解除阻塞。

         被阻塞的信号处于未决状态。


(2)阻塞是不让信号被递达处理,忽略是信号递达的一种方式


(3)阻塞 是否捕捉到信号 有关吗?

        无关。因为阻塞位图与未决位图本身就是两个位图,互不影响。其次进程选择阻塞一个信号,无需关心是否收到。


(4)若信号被阻塞,则不管是否收到信号,都不做处理;反之收到信号,直接递达处理


(5)OS 发送信号的本质,是向进程的 pending 位图中将对应编号的信号的内容置为1,至于是否做递达处理,看进程自身是否屏蔽了该信号


(6)9号(SIGKILL)19号信号(SIGSTOP)无法被屏蔽;18号信号(SIGCONT)做了特殊处理

        OS 至少要保证有一种信号可以终止进程


(7)对信号做递达处理时,先将 pending 对应 bit 位的内容由 0 置为 1,再进行递达处理

        主要是考虑在递达处理的期间,有相同信号到来。而递达也是需要花费时间的,所以先将 pending 的 bit 位的内容置为0,在递达处理时,可以继续收到相同信号,防止信号被覆盖而丢失。

4. 三个位图匹配的操作与系统调用接口

(1)block 位图

int sigprocmask(int how, const sigset_t* set, sigset_t* oldset);

① how

SIG_BLOCK

添加一个信号到 block 位图中

注意这是添加,即 屏蔽字 = 原本屏蔽的信号 |set

SIG_UNBLOCK解除 set 中屏蔽的信号
SIG_SETMASK设置当前信号屏蔽字 set

② sigset_t 类型

  • 是一个用户层提供的位图类型,可以代表 block、pending 位图的含义

相关操作函数:

sigemptyset初始化信号集,将所有 bit 位的内容置为0
sigfillset初始化信号集,将所有 bit 位的内容置为1
sigaddset添加一个信号
sigdelset删除一个信号
sigismember判断一个信号是否在信号集中

 (2)pending 位图

int sigpending(sigset_t *set);

        通过参数 set 传出当前进程的未决信号集。如果成功返回 0,若出错则返回-1。

(3)handler 位图

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

        用户通过系统调用自定义处理方法,待信号到来时,执行自定义方法

三、信号的生命周期

1. 信号的产生

(1)键盘 产生信号:

         ctrl + c (2号信号,SIGINT)
         ctrl + \ (3号信号,SIGQUIT)
         ctrl + z (19号信号,SIGSTOP)

(2)命令 产生信号:

kill -num pid
kill -sign_name pid

(3)系统调用 产生信号:

kill给进程发送信号
raise给自己发送信号
abort给自己发送6号信号,SIGABRT

(4)软件条件 产生信号

a. 软件条件不具备,发送信号

        如:管道的读端不读了&&读端关闭了,那操作系统就会向斜段发送13号信号(SIGPIPE),终止写端。

b. alarm闹钟
unsigned int alarm(unsigned int seconds);
  • seconds:闹钟开始计时的时长,单位:秒
  • 返回值:上一个闹钟剩余的秒数
  • alarm( 0 ) :取消闹钟
  • 一个进程有且只有一个闹钟时间(若循环调用alarm,则会不断重置闹钟时长,永远不会为0)

        在seconds秒后,向当前进程发送14号信号(SIGALRM),并终止当前进程,默认是响一次。

        若要求响很多次,我们可以在自定义处理方法中再次设置alarm函数。

        设置完alarm之后,不会停留在该函数处,而是继续向后执行。所以这个跟server端设置listen一样,由操作系统来管理,无需用户管理。

为什么alarm是软件条件呢?

        首先,OS中一定同时存在许多的定时任务,而这些定时任务都不是由用户自己来管理的,所以OS一定会管理这些定时任务。

如何管理????

先描述

struct alarm
{
    pid_t pid; // 设置定时任务的进程pid
    uint64_t expired; // 过期时间
    // ...其他属性
};

再组织

        使用小堆组织这些定时任务,实现每次都让最快到达计时时间的任务第一个被OS拿到,并发送信号给对应进程。

        这些用数据结构组织起来的就是软件,即alarm为软件条件

(5)异常产生信号

非法内存的访问11号信号 - SIGSEGV
除0异常8号信号 - SIGFPE

2. 关于信号产生的各种情况的理解

(1)键盘输入产生信号

        首先键盘是一个字符设备,字符输入 与 组合键输入 本质上都是 字符输入,但是组合键代表的是命令,所以OS一定要对输入的数据来进行判断,是字符还是命令

a. 第一步:在输入的时候,OS必须要将键盘输入的数据拿到键盘文件的文件缓冲区中

        当键盘按下按键的时候,会发生硬件中断,向 CPU 的针脚发射高电频,CPU 的 reg 寄存器存放接收到高电频的针脚的编号(中断号),OS 拿到 reg 寄存器中的中断号,再去中断向量表中去找中断号对应的方法,执行该方法,就把键盘输入的数据加载到内存的键盘文件的文件缓冲区中了。

b. 第二步:OS要识别出输入的是 字符 还是 命令

        当数据加载到键盘文件缓冲区时,OS 会创建一个辅助进程来读取缓冲区的数据,从而来判定数据是普通字符还是命令。

若为字符:

        我们当前运行的进程就会读取里面的内容

若为命令:

        辅助进程会将其解释为信号,并发送给当前进程(这个很简单,映射就OK)

什么叫解释成信号?发送给当前进程?

解释成信号:

        辅助进程通过组合键与命令的映射关系完成        

发送给当前进程:

        前面提过,进程可以不立即处理信号,可以在合适的处理。而信号发送给当前进程也不代表进程处理信号了。在信号到来的时候,进程可能暂时不处理信号,所以就必须对信号进行临时保存,在 task_struct 中用 pending 位图来保存,bit 的位置表示信号的编号,bit 位的内容代表信号是否存在。

        所以发送给当前进程是指:将当前进程关于信号的位图 bit 内容由0置为1,至于如何处理,就是进程自己的事情。

(2)除0异常发送信号

        在 CPU 执行到 a /= 0 的指令时,会将标志位寄存器设置成溢出标记,通知OS有错误发生,OS就会看标志位寄存器中的错误标记,发现是 除0 错误,就会向进程发送 8 号信号

(SIGFPE)

(3)非法内存访问发送信号

虚拟地址到物理地址转换用到的寄存器:

CR2存放导致页表转换错误的虚拟地址
CR3

保存页表的起始地址

可在虚拟地址到物理地址之间转换时,快速定位页表

MMU虚拟地址到物理地址的转换

        我们程序员看到的地址都是虚拟地址,不是物理地址。访问虚拟地址时,在底层 OS 会与 CPU 的 MMU 通过页表将虚拟地址转换为物理地址,而转换一定是对应成功和失败的。所以当我们访问一个非法内存地址时,CR3 寄存器先将页表的起始地址给 MMU,MMU 定位到页表后,再把访问的地址拿到,进行转换,但该地址在页表中没有对应的物理地址,或该地址是只读属性,不允许转化。这两种都会转换失败,此时将错误信息存入到 CR2 寄存器中,交给 OS,OS 得知是非法内存访问的异常,发送 11 号信号(SIGSEGV)终止进程。

【总结】

        向进程发送信号,就是将 task_struct 的pending 位图的 bit 位由 0 置为 1,而task_struct 为内核数据结构,只有 OS 有权限来写入,用户若想改变信号位图,就必须通过系统调用。所以无论信号产生的方式有多少种,都是向 task_struct 中的 pending 位图写入,都必须由 OS 写入!

3. 信号的处理

(1)基本概念

① 内核态 与 用户态

  • 内核态具有更高的权限,可以直接访问系统硬件资源和执行关键操作
    用户态则只可以执行用户的代码。
  • 内核态与用户态主要是对 CPU 运行时的权限状态进行划分的,当进程在 CPU 上调度时,CPU 会根据其代码指令的类型,从而选择运行时的权限(用户态或内核态),以便控制进程对系统资源的访问
  • CPU 的 CS 寄存器的低 2 个 bit 位是权限标识位,0 代表内核态,3 代表用户态

② 进程地址空间

  • 进程地址空间的内核区 [3G, 4G] 映射的就是 OS
  • 每个进程都有内核空间,也就都可以找到 OS,访问 OS 的本质就是通过内核区访问的
  • 系统调用在底层是用 函数指针数组 组织起来的

        在代码执行的期间,若遇到系统调用,CPU 就会提高权限,去访问内核区,执行相关系统调用,执行完毕后,再降低权限,继续执行用户区的代码

(2)信号处理的时期

        进程从 内核态转换到用户态之前,OS 会检测进程的 pengind 位图,如果有 bit 的内容为1,则去查看 block 位图,若对应信号被屏蔽了,则不做处理,反之执行 handler 位图的方法

(3)信号处理的流程图

在执行主控制流程的某条指令时,因为硬件中断、异常、系统调用而切换至内核态
内核处理硬件中断、异常、系统调用
在处理结束后,返回用户态前,处理当前进程中可以递达的信号,若信号执行的是默认处理方法,则走;反之,走④
调用 sys_sigreturn ,返回用户态,从主控制流程中被中断的地方继续向下执行
信号选择自定义处理方法,切换到用户态,执行自定义处理方法
执行自定义处理方法结束,调用系统调用 sigreturn 切换回内核态
调用 sys_sigreturn ,返回用户态,从主控制流程中被中断的地方继续向下执行

(4)问题


为什么在信号捕捉时,执行自定义处理方法时,要从内核态转换到用户态,直接内核态不可以吗?

        虽然在权限上,内核态确实可以执行用户态的代码,但是OS不相信任何人,万一自定义处理方法中,有越权访问的代码(内核态可以执行,用户态不可以),就会有风险。所以用户态就应该执行用户态的代码,内核态就执行内核态的代码。


为什么在执行完自定义处理方法后,要从用户态切换回内核态?

        一方面是在信号处理后,需要进行清理操作,这都是 OS 的任务,所以必须切换回内核态;另一方面是我们自定义方法没有终止进程,则必须要在中断位置恢复进程的上下文数据,重新让 CPU 调度,这也是 OS 的任务。


我们自定义处理方法的时候,内部不退出进程,会发生什么?

        一直判定错误,并发送信号

我们将 8 号信号进行捕获,自定义处理方法中不退出进程,并写一段会产生除零异常的代码

        过程如下:

        在发现除零异常的时候,由用户态转换为内核态,并处理异常,OS 向进程发送 8 号信号,在转换为用户态之前,OS 会检查进程的信号集,此时发现了信号,并去执行自定义处理方法,由于没有退出该进程,处理完信号后,会先转换为内核态,恢复进程的上下文数据,而由于进程没有被终止,所有上下文呢数据都和之前的中断时一样,即标志位寄存器的内容仍然是溢出标志,再换到用户态,所以就会一直判定错误,OS会一直向进程发送 8 号信号。

        但是有的 OS 为了保证安全和稳定,通常在恢复上下文数据的时候,要保证寄存器的内容都是正确的,所以会重置寄存器的错误内容,但我们依旧是从中断位置执行,也就会再次遇到除零操作,从而产生除零异常


四、用户捕获信号的方式

1. signal

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

        用户通过系统调用自定义处理方法,待信号到来时,执行自定义方法

2. sigaction

int sigaction(int signum, const struct sigaction *act,
                          struct sigaction *oldact);
struct sigaction 
{
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
};
  • signum:信号编号
  • sa_handler:自定义处理方法
  • sigaction:实时信号的自定义处理方法
  • sa_flags:设置为0
  • sa_restorer:不用,设置为nullptr

sa_mask

        在调用该函数时,内核会通过sa_mask添加 signum 对应的信号 到 block 位图中,保证在处理该信号的时候,不会再次被同信号所影响,而导致信号的循环嵌套处理。若是处理完该 signum 信号时,会默认从block 位图中移除。

        如果想要去屏蔽其他信号,可以添加到 sa_mask 中。

五、扩展

1. OS 是如何运行的?(简单说明)

        OS是一个死循环,不断在接受外部硬件的中断,从而运行的。

        硬件会高频率地给CPU发送中断,CPU就会不断地处理中断,reg寄存器存的是中断号,OS就会根据中断号来查中断向量表的对应中断号的方法,最后去执行任务。

2. OS 是如何分辨出各种中断的?

        通过在CPU的reg寄存器中读取到的中断号来分辨出不同的中断,再与中断向量表中的中断号与处理方法的映射来执行不同的方法。

3. kill 一个进程的实现流程

        kill 一个进程,OS 会通过系统调用来识别出是给哪个进程发送几号信号,OS将信号写入对应进程的 pending 位图中,此时已经完成信号的发送了。

        那什么时候进程处理信号呢?

        进程在等到CPU的时间片中断时,会从用户态转换为内核态,处理中断,在返回用户态前,OS会查看进程的pending、block、handler表,判断是否处理信号。

4. 异常 从产生到处理的流程

        在CPU执行主流程的代码时,发现了除0 / 非法内存的访问,就会将标志位寄存器由0置为1,reg寄存器会保存异常的中断号,从而 OS 通过这两个寄存器的信息,查中断向量表的对应方法,去向该进程发送信号。

        那什么时候进程处理信号呢?

        此时已经发生了异常,就处于内核态,并且已经处理完异常,直接对信号处理

5. 键盘输入信号到处理的流程

        当CPU执行主流程的代码时,键盘的输入会引发硬件中断,向CPU的某个针脚发射高电频,CPU的 reg 寄存器保存该针脚号(中断号),与此同时 OS 去拿到这个中断号,去中断向量表中查到对应方法,从而将键盘输入的数据读取到键盘文件的文件缓冲区中,OS 会创建进程来判断读取到的内容是普通字符还是命令,若读取到的是命令,则该进程会转换为kill 命令,发送给当前进程(就是将进程的 pending 表由 0 置为 1 )。至此处理中断结束

        那什么时候进程处理信号?

        此时就处于内核态,所以直接处理即可!

六、volatile 关键字

#include <iostream>
#include <signal.h>
int g_flag = 0;
void ChangeFlag(int signum)
{
    g_flag = 1;
}
int main()
{
    signal(2, ChangeFlag);
    while(!g_flag)
    {
        std::cout << "..."  << std::endl;
    }
    return 0;
}

        有的编译器在进行对代码的扫描时,会认为没有对 g_flag 修改,就会将 g_flag 的值放入寄存器中,而当我们发送 2 号信号时,即使内存中 g_flag 的值已经为 1 了,但是也不会退出循环,这是因为 CPU 拿到是寄存器中的 g_flag。

如何解决?

        使用 volatile 关键字 保持内存的可见性,即CPU拿到的 g_flag 的值都是从内存中拿的

volatile int g_flag = 0;

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【qt】考试系统项目

话不多说,先一睹芳颜 咱们的账号,题库和答案都是通过文件获取的. 话不多说,直接开干 目录 一.登录窗口1.界面设计2.邮箱验证3.登录验证 二.题库窗口1.考试计时2.布局管理器3.题库显示4.按钮布局5.计算分数 三.窗口交互四.完整代码五.结语 一.登录窗口 1.界面设计 这里添加背…

跟着操作,解决iPhone怎么清理内存难题

在如今智能手机功能日益强大的时代&#xff0c;我们使用手机拍照、录制视频、下载应用、存储文件等操作都会占用手机内存。当内存空间不足时&#xff0c;手机运行会变得缓慢&#xff0c;甚至出现卡顿、闪退等现象。因此&#xff0c;定期清理iPhone内存是非常必要的。那么&#…

人工智能与专家系统:构建智慧决策的未来

引言 随着信息技术的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;已经成为当今科技领域的一个重要分支&#xff0c;并在多个行业中展现出了巨大的潜力和影响力。人工智能通过模拟人类的智能行为&#xff0c;使计算机能够执行诸如学习、推理、解决问题和理解自然语言…

vite-服务端渲染(ssr)项目线上频繁刷新(踩坑记录)

今天来分享一个我在公司修改之前前端留下来的项目的坑。来说说大致的一个经过把&#xff0c;我老板说这个项目是之前的一个前端做的&#xff0c;用的是ssr服务端渲染的技术&#xff0c;不过他项目在线上会一直频繁的刷新&#xff0c;据说他想破脑袋都想不出来&#xff0c;最终因…

周鸿祎为什么建议Java、前端、大数据、PHP开发都要学一下大模型?_ai大模型全栈工程师跟java有关吗

ChatGPT的出现在全球掀起了AI大模型的浪潮&#xff0c;2023年可以被称为AI元年&#xff0c;AI大模型以一种野蛮的方式&#xff0c;闯入你我的生活之中。 从问答对话到辅助编程&#xff0c;从图画解析到自主创作&#xff0c;AI所展现出来的能力&#xff0c;超出了多数人的预料&…

【企业级监控】Zabbix监控网站并发连接数

Zabbix自定义监控项与触发器 文章目录 Zabbix自定义监控项与触发器资源列表基础环境前言一、什么是zabbix的Key值二、获取远程Key值2.1、获得主机的Key值2.2、被监控端安装Agent2.3、zabbix_get命令获取Agent数据举例2.3.1、zabbx_get获取cpu核心数2.3.2、获取目标主机系统和内…

windows中超详细深度学习环境配置之安装显卡驱动、cuda、cudnn、pytorch、torchvision、pycharm

超详细介绍安装Gpu版本的pytorch深度学习环境 一、显卡驱动安装1.1 下载驱动1.2 安装驱动 二、cuda安装2.1 下载cuda2.2 安装cuda2.3 检查cuda是否安装成功 三、安装cudnn3.1 cudnn下载3.2 cudnn安装 四、安装miniconda4.1 miniconda下载4.2 miniconda安装4.3 添加环境变量 五、…

数字营销以打造“会员体系”为主要目标的好处和优势

​蚓链数字化营销实践观察&#xff1a;在数字化时代&#xff0c;企业的营销方式发生了深刻的变革。会员体系作为一种常见的营销策略&#xff0c;在数字营销领域中发挥着越来越重要的作用。 首先&#xff0c;我们来总结一下会员体系的特点和优势 &#xff08;一&#xff09;个性…

图注意力网络

【图书推荐】《图神经网络基础、模型与应用实战》_搭建神经网络需要看什么书-CSDN博客 图注意力网络的由来和发展 图注意力网络&#xff08;GAT&#xff09;是一种图神经网络&#xff08;GNN&#xff09;模型&#xff0c;最早由Petar Velickovic等在2017年提出。它的设计灵感…

Java基础-I/O流

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 字节流 定义 说明 InputStream与OutputStream示意图 说明 InputStream的常用方法 说明 OutputStrea…

Qt基础 | Qt Creator的基本介绍与使用 | 在Visual Studio中创建Qt项目

文章目录 一、Qt Creator的基本介绍与使用1.新建一个项目2.项目的文件组成3.项目文件介绍3.1 项目管理文件3.2 界面文件3.3 主函数文件3.4 窗体相关的文件 4.项目的编译、调试与运行 二、在Visual Studio中创建Qt项目 Qt C开发环境的安装&#xff0c;请参考https://liujie.blog…

C1W1.LAB.Preprocessing+Word frequencies+Logistic_regression_model

理论课&#xff1a;C1W1.Sentiment Analysis with Logistic Regression 文章目录 预处理导入包Twitter dataset简介查看原始文本处理原始文本处理超链接、Twitter 标记和样式分词去除标点和停用词词干处理 process_tweet() 词频构建与可视化导入包加载数据集字典字典实例添加或…

cesium 实现地图环境功能 - 雨,雪,雾特效

需求背景解决效果Codeindex.vuefogEffect.tsrain.glslsnow.glslfog.glsl 需求背景 需要实现天气模拟&#xff0c;日照模拟功能&#xff0c;提高三维实景效果 解决效果 Code 注意&#xff1a;我以下glsl文件时基于 webgl1.0&#xff0c;即cesium&#xff0c;创建球的时候&…

ES的使用示例

1.安装 ES的安装对springboot的版本配置要求很高&#xff0c;需要根据如下的目录下载对应的版本。 查看自己项目所使用的springboot和spring的版本&#xff0c;对应下载文件。 下载链接地址&#xff1a;https://www.elastic.co/cn/downloads/past-releases/elasticsearch-7-…

微软GraphRAG原理介绍(附带部分源码)

我们前几天写了一篇文章&#xff0c;简单跑了一下GraphRAG看了下效果&#xff0c;没看过这篇文章的可以看下https://www.luxinfeng.top/article/动手实操微软开源的GraphRAG。今天我们介绍一下GraphRAG的实现原理&#xff0c;关于实验对比的内容&#xff0c;我会在下一篇文章中…

48V电源架构解析

48V电源架构解析 48V系统的诞生 汽车在1918年引入蓄电池&#xff0c;到1920年逐渐普及&#xff0c;当时的电池电压是6V。后来&#xff0c;随着内燃机排量的增加以及高压缩比内燃机的出现&#xff0c;6V系统已经不能满足需求&#xff0c;于是在1950年引入了12V系统。大多数汽车…

【python学习】标准库之数学相关math库的定义、功能、使用场景、代码示例和第三方数学相关库NumPy

引言 math模块是Python标准库的一部分&#xff0c;它提供了一系列基本的数学函数和常数。这些函数和常数对于日常的数学运算非常有用&#xff0c;例如计算平方根、计算余弦值等。 文章目录 引言一、math的定义二、math的功能2.1 基本的数学运算2.2 数学常数2.3 随机数 三、math…

八臂-聚乙二醇-生物素;8ARM-PEG-Biotin

一、基本信息 名称&#xff1a;八臂PEG生物素、八臂-聚乙二醇-生物素、Octa-arm PEG Biotin、8ARM-PEG-Biotin 结构&#xff1a;具有八个分支的PEG链&#xff0c;每个分支末端连接生物素分子 状态&#xff1a;固体/粉末/溶液&#xff0c;具体取决于产品规格和存储条件 纯度&…

论文去AI痕秘籍:轻松几步,守护你的学术原创性

如何有效降低AIGC论文的重复率&#xff0c;也就是我们说的aigc如何降重&#xff1f;AIGC疑似度过高确实是个比较愁人的问题。如果你用AI帮忙写了论文&#xff0c;就一定要在交稿之前做一下AIGC降重的检查。一般来说&#xff0c;如果论文的AIGC超过30%&#xff0c;很可能会被判定…

Qt|QTreewidget类下函数qt助手详解说明示例(二)

上篇&#xff1a;Qt|QTreewidget类下函数qt助手详解说明示例&#xff08;一&#xff09; 该系列持续更新&#xff0c;喜欢请一键三连&#xff0c;一起学习进步&#xff0c;升职加薪&#xff0c;感谢各位大佬。 QT5.14.2 参考官方QT助手 Kimi辅助说明 文章目录 insertTopLevelI…