当前位置: 首页 > news >正文

Linux:进程信号(二.信号的保存与处理、递达、volatile关键字、SIGCHLD信号)

上次介绍了:(Linux:进程信号(一.认识信号、信号的产生及深层理解、Term与Core))[https://blog.csdn.net/qq_74415153/article/details/140624810]


文章目录

  • 1.信号保存
    • 1.1递达、未决、阻塞等概念
    • 1.2再次理解信号产生与保存
    • 1.3信号集操作函数
      • sigset_t类型
      • sigprocmask系统调用
      • sigpending系统调用
  • 2.信号的处理/递达
    • 2.1信号处理时机与过程
    • 2.2用户态和内核态
    • 2.3再看进程地址空间
      • 谁来运行OS
    • 2.4信号的捕捉—sigaction()函数
  • 3.补充知识
    • 3.1可重入函数
    • 3.2volatile关键字
    • 3.3 SIGCHLD信号


1.信号保存

1.1递达、未决、阻塞等概念

  1. 信号未决(Pending):当信号产生时,会首先进入未决状态,即信号还没有被进程处理。此时,信号被标记为未决状态,等待进程处理。

  2. 信号递达(Delivery):当进程解除对信号的阻塞时,信号才会被递达,即信号被传递给进程的信号处理函数进行处理

    三种信号处理方式:

    1. 默认处理(Default Handling):每个信号都有一个默认的处理方式,当信号递达时,操作系统会执行默认的信号处理动作,传入SIG_DFL

    2. 自定义处理(Custom Handling):进程可以通过设置信号处理函数(一般是handler)来自定义对信号的处理方式。当信号递达时,操作系统会调用进程设置的信号处理函数来处理信号

    3. 忽略处理(Ignore Handling):进程还可以选择忽略某个信号,即在信号递达时不做任何处理。通过将信号处理函数设置为 SIG_IGN,进程可以忽略某个信号

  3. 阻塞信号:进程可以选择阻塞某个或多个信号,使其在未决状态下等待。被阻塞的信号不会递达,保持在未决状态,直到进程解除对此信号的阻塞

1.2再次理解信号产生与保存

在操作系统中,进程信号相关的"Pending位图"和"Block位图"是两种数据结构,用于跟踪进程当前挂起/未决(pending)的信号和已经阻塞(blocked)的信号

  1. Pending位图

    • 作用:Pending位图用于记录当前对进程发送但尚未被处理的信号。当操作系统向进程发送信号时,如果进程当前不能立即处理该信号(比如正在处理其他信号或忙于执行其他任务),该信号会被添加到进程的Pending位图中。
    • 操作:操作系统会定期检查进程的Pending位图,并根据信号处理方式(默认处理、自定义处理、忽略处理)来决定如何处理挂起的信号。
  2. Block位图

    • 作用:Block位图用于记录当前被阻塞的信号。进程可以选择阻塞某些信号,使得这些信号被阻塞不会被递送给进程。
    • 操作:当信号被阻塞时,该信号会被添加到进程的Block位图中。被阻塞的信号不会被递送给进程,直到解除阻塞。
    • 特点:Block位图记录了进程当前被阻塞的信号,帮助进程控制哪些信号可以递送到进程。

在这里插入图片描述

其中信号的阻塞与否,跟是否收到信号毫无关系

对应信号在进程的信号未决位图中的比特位会在信号递达前被设置为1,表示信号需要处理,而在信号被处理完后会被清零,即改为0

是先清0,再进行递达

在这里插入图片描述

而进程能识别信号,也是因为早在未收到信号之前,我们就已经知道是否堵塞,怎么处理了(利用上述三个表)

  • 信号处理表:在进程创建时,内核会为其分配一个信号处理表,用于记录每个信号对应的信号处理函数(Signal Handler)。当进程收到一个信号时,内核会根据信号处理表中对应信号的处理函数来执行相应的操作。
  • 信号未决位图:在进程接收到一个信号时,内核会更新进程的信号未决位图,用于记录当前未被屏蔽的信号。这个位图帮助进程确定是否有信号需要处理。
  • 信号挂起位图:当一个信号被进程接收但尚未处理时,内核会将这个信号标记为挂起,即更新进程的信号挂起位图。这个位图帮助进程确定哪些信号需要等待处理。

这三个表是操作系统内核为了管理进程信号处理而设计的数据结构,它们在进程创建时被初始化并与进程关联,帮助进程识别和处理信号

1.3信号集操作函数

sigset_t类型

每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次(只表示收到否信号,对于信号的数量没办法也没必要),阻塞标志也是这样表示的。

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

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

阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略

对于我们使用者来说,应该将sigset_t类型看作一个抽象的信号集合,而不需要关心其内部的具体实现细节。sigset_t类型的具体表示方式可能会因系统而异,可能是一个位图、一个数组或其他数据结构,但这些细节对于使用者来说并不重要。

我们使用者应该通过系统提供的函数来操作sigset_t变量,比如sigemptyset、sigfillset、sigaddset、sigdelset等函数来对信号集进行操作。这些函数会根据系统的具体实现来正确处理信号集的操作,确保其正确性和可移植性。

因此,直接打印sigset_t变量是没有意义的,因为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);
int sigismember(const sigset_t* set, int signo);
  • 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号。

  • 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系统支持的所有信号。

  • 在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号

sigprocmask系统调用

sigprocmask是一个系统调用,用于检查或修改当前进程的信号屏蔽集(signal mask)。信号屏蔽集是一个用来指定哪些信号在进程处理信号时应该被阻塞的集合。通过操作信号屏蔽集,进程可以控制哪些信号可以被接收和处理,哪些信号应该被暂时屏蔽。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

参数说明:

  • how:表示对信号屏蔽集的操作方式,有三种取值:
    • SIG_BLOCK:将set中指定的信号添加到当前信号屏蔽集中。
    • SIG_UNBLOCK:从当前信号屏蔽集中移除set中指定的信号。
    • SIG_SETMASK:将当前信号屏蔽集设置为set中指定的信号集。
  • set:指向一个sigset_t类型的指针,用于指定要操作的信号集
  • oldset:指向一个sigset_t类型的指针,用于存储之前的信号屏蔽集

返回值:

  • 如果函数调用成功,返回0;如果出现错误,返回-1,并设置errno变量来指示错误类型。

功能:

  • sigprocmask函数允许进程检查或修改当前进程的信号屏蔽集。
  • 通过how参数指定的操作,可以添加、移除或替换信号屏蔽集中的信号。
  • 如果oldset参数不为NULL,则会将之前的信号屏蔽集存储到oldset中。

sigpending系统调用

sigpending是一个系统调用,用于获取当前进程挂起/未决(pending)的信号集。挂起的信号是指已经发送给进程但尚未被处理的信号。通过sigpending函数,进程可以查询当前有哪些信号处于挂起状态,以便进一步处理这些信号。

#include <signal.h>
int sigpending(sigset_t *set);

参数说明:

  • set:指向一个sigset_t类型的指针,用于存储当前进程挂起的信号集。

返回值:

  • 如果函数调用成功,返回0;如果出现错误,返回-1,并设置errno变量来指示错误类型。

功能:

  • sigpending函数允许进程获取当前进程挂起的信号集。
  • 通过set参数返回当前进程挂起的信号集,可以进一步对这些信号进行处理。

2.信号的处理/递达

在信号处理中,一般情况下有三种处理方式,分别是:

  1. 忽略信号(Ignore):进程可以选择忽略某些信号,这样当该信号到达时,系统不会采取任何操作,也不会调用任何信号处理函数。一些信号(比如SIGKILL和SIGSTOP)是不能被忽略的,它们具有特殊的含义和作用。
  2. 执行默认操作(Default Action):每个信号都有一个默认的处理方式,当进程接收到信号时,系统会执行该信号的默认操作。比如,当进程接收到SIGINT信号(通常由Ctrl+C触发),系统会默认终止进程的执行。
  3. 捕捉信号并执行处理函数(Signal Handling):进程可以捕捉信号并注册相应的信号处理函数,当接收到信号时,系统会调用该处理函数来处理信号。进程可以自定义信号处理函数,根据需要对信号进行处理,比如记录日志、关闭文件、释放资源等。

2.1信号处理时机与过程

我们之前只是泛泛的讲:进程会在合适时候进行对信号的处理,那什么是合适的时候?——进程从内核态切换会用户态的时候,信号会被检测并处理

每次进程从内核态切换到用户态时,操作系统会依次检查进程是否有未处理的信号。如果有未处理的信号,操作系统会根据信号的处理方式(比如忽略、捕获、默认处理等)来进行相应的处理。如果信号没有被阻塞,操作系统会执行信号处理程序来处理该信号,然后继续执行用户态程序。

在这里插入图片描述

在第三步我们讨论的是自定义处理,如果是默认和忽略呢?

  • 默认:更改PCB的状态即可观在是内核身份,直接杀掉进程
  • 忽略:处理这个信号什么都不做,直接把pending表对应比特位置为0

为什么在第四步里,特地回到用户态执行自定义处理函数:操作系统不相信任何人,不会轻易执行用户的代码,因为用户代码可能包含恶意代码或错误代码,可能会导致系统崩溃、数据泄露等安全问题

2.2用户态和内核态

用户态和内核态是操作系统中的两种运行模式,用于区分程序的权限和访问级别。下面是它们的主要特点和区别:

  1. 用户态(User Mode):

    • 用户态是指程序在执行时所处的一种权限较低的状态,程序在用户态下只能访问受限的资源和执行受限的操作。
    • 在用户态下,程序运行在用户空间,只能访问自己的内存空间和受限的系统资源,不能直接访问操作系统内核或其他进程的内存空间(内核空间)。
    • 用户态下的程序通常是普通应用程序,如文本编辑器、浏览器等,它们无法直接执行特权指令或访问系统底层资源。
  2. 内核态(Kernel Mode):

    • 内核态是指程序在执行时所处的一种权限较高的状态,程序在内核态下具有更多的权限和访问系统资源的能力。
    • 在内核态下,程序运行在内核空间,可以直接访问系统内核和底层资源,执行特权指令和进行敏感操作。
    • 内核态下的程序通常是操作系统内核的一部分,如设备驱动程序、系统调用处理程序等,它们负责管理系统资源、处理中断、执行特权操作等。

我们不同的状态主要是不同的权限:通过改变CPU内的执行权限,设置了寄存器内的特定标志位,来改变状态

2.3再看进程地址空间

在这里插入图片描述

进程无论如何切换,总能找到OS:我们访问OS,本质就是通过进程的地址空间的[3,4]GB的内核空间来访问的

调用系统调用也是在地址空间内进行的

在操作系统内核中,通常会有一个系统调用表(System Call Table)用于存储系统调用号与对应系统调用处理程序的映射关系。当用户进程发起系统调用时,会将系统调用号放入特定寄存器中,CPU根据系统调用号找到对应的系统调用处理程序在系统调用表中的位置,然后跳转到该函数的地址进行调用。

在这个过程中,操作系统内核会确保系统调用表的起始虚拟地址是已知的,并且系统调用号与处理程序的映射关系是正确的。通过这种方式,CPU能够根据系统调用号正确地找到对应的系统调用处理程序,并执行相应的操作。

谁来运行OS

在这里插入图片描述

2.4信号的捕捉—sigaction()函数

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字。如果我们处理完对应的信号,该信号默认也会从信号屏蔽字中进行移除——不想让信号,嵌套式进行捕捉处理(正在处理时你又来了,那就又去调用处理函数)

sigaction()函数是用于设置和修改信号处理程序的系统调用函数。通过sigaction()函数,进程可以指定在接收到特定信号时应该执行的处理程序。这个处理程序可以是系统默认的处理方式,也可以是用户自定义的处理函数。

sigaction()函数的原型如下:

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

其中,signum参数指定了要设置的信号的编号,act参数指定了新的信号处理方式,oldact参数用于保存之前的信号处理方式。

二者都是struct sigaction类型的,对于struct sigaction

在这里插入图片描述

  1. void (*sa_handler)(int):这是一个函数指针,用于指定信号处理函数的地址。当接收到信号时,系统会调用这个函数来处理信号。函数接受一个整型参数,表示接收到的信号编号。如果将sa_handler设置为SIG_IGN,表示忽略该信号;将其设置为SIG_DFL,表示使用系统默认的信号处理方式。

  2. void (*sa_sigaction)(int, siginfo_t *, void *):这也是一个函数指针,用于指定扩展的信号处理函数的地址。与sa_handler不同的是,sa_sigaction函数接受三个参数:第一个参数是信号编号,第二个参数是一个指向siginfo_t结构体的指针,其中包含了关于信号的更多信息,第三个参数是一个指向void类型的指针。(一般用于实时信号,我们不管这个

  3. sigset_t sa_mask:这是一个信号集合,用于指定在信号处理函数执行期间需要屏蔽的信号。如果有信号在sa_mask指定的信号集合中,则这些信号会被阻塞,直到信号处理函数执行完毕。

  4. int sa_flags:用于指定信号处理的行为。可以是以下几个标志的组合:

  • SA_RESTART:表示系统调用在接收到信号后会自动重启。
  • SA_NOCLDSTOP:子进程暂停和继续时不会产生SIGCHLD信号。
  • SA_NODEFER:不会在执行信号处理函数期间阻止同一信号的传递。
  • SA_SIGINFO:表示使用sa_sigaction字段指定的信号处理函数。(我们一般设置为0就行了
  1. void (*sa_restorer)(void):这是一个保留字段,已经废弃,不再使用

返回值为0表示函数调用成功,返回-1表示函数调用失败。在函数调用失败的情况下,可以通过errno全局变量获取具体的错误信息。

通过sigaction()函数,进程可以设置信号的处理方式为以下几种之一:

  • 忽略信号(SIG_IGN
  • 执行默认处理方式(SIG_DFL
  • 指定自定义的信号处理函数
#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;void handler(int signum)
{cout << "收到了信号:" << signum << endl;
}int main()
{struct sigaction act, oldact;act.sa_handler = handler;act.sa_flags = 0;sigemptyset(&act.sa_mask);sigaction(2, &act, &oldact); // 进行信号捕捉while (true)sleep(2);return 0;
}

3.补充知识

3.1可重入函数

被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,函数有可能因为重入而造成错乱,像这样的函数称为不可重入函数

反之,如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数

可重入函数(Reentrant Function)也称为可重入代码(Reentrant Code)或重入函数(Reentrant Routine),是指在并发执行环境中,能够被多个线程同时调用的函数。这种函数能够在任何时候被中断,并在之后从中断点恢复执行,而不会导致数据错误或系统崩溃。

为了实现可重入性,可重入函数必须满足以下条件:

  1. 不使用静态(全局)非常量数据:静态或全局非常量数据可能在多个线程之间共享,如果一个线程修改了这些数据,其他线程可能无法正确地读取或写入这些数据,导致数据错误。
  2. 不调用不可重入函数:如果一个函数调用了另一个不可重入的函数,那么它本身也将是不可重入的。
  3. 不返回指向静态(全局)非常量数据的指针:与第一条类似,返回这样的指针可能导致其他线程错误地修改或读取数据。
  4. 使用局部变量:局部变量存储在函数的栈帧中,每个函数调用都有自己的栈帧,因此局部变量是线程私有的,不会被其他线程干扰。
  5. 对共享资源的访问进行保护:如果函数需要访问共享资源(如文件、数据库、共享内存等),则需要使用适当的同步机制(如互斥锁、信号量等)来保护这些资源,防止数据竞争和冲突。

3.2volatile关键字

volatile 关键字在 C 和 C++ 语言中是一个类型限定符,它告诉编译器不要对访问该关键字声明的变量的代码进行优化,即每次都需要从内存中读取变量的值,而不是使用存储在寄存器中的副本。这是为了确保多线程环境或者硬件中断等场景下,对该变量的访问总是最新的、未被其他线程或硬件修改过的值。

有时因为编译器优化的原因,会导致我们代码出错

#include <stdio.h>
#include <unistd.h>
#include <signal.h>int g_flag = 0;void changeflag(int signo)
{(void)signo;printf("将g_flag,从%d->%d\n", g_flag, 1);g_flag = 1;
}int main()
{signal(2, changeflag);while(!g_flag); // 故意写成这个样子, 编译器默认会对我们的代码进行优化//因为,g_flag一直都没使用过printf("process quit normal\n");return 0;
}

这里,如果编译器进行优化,会把内存里的g_flag拷贝一份到寄存器里,那下一次判断直接从寄存器里拿。不用再去内存里拿,收到信号2后我们更改的是内存里的g_flag,但是我们while判断的是寄存器里的g_flag——寄存器屏蔽了内存

3.3 SIGCHLD信号

SIGCHLD信号是在Linux系统中用于进程间通信的一种机制。具体来说,当子进程终止或停止时,子进程会向其父进程发送SIGCHLD信号。这个信号是子进程状态改变时发送给父进程的信号,用于通知父进程其子进程的状态已经发生了变化。

父进程可以捕获这个信号,并通过调用如wait()或waitpid()等函数来获取子进程的退出状态、终止原因等信息。SIGCHLD信号常用于以下几种情况:

  1. 子进程终止,父进程需要回收子进程的资源。
  2. 父进程需要等待子进程的状态改变,比如子进程终止或停止。
  3. 父进程需要在子进程终止后进行一些操作。

处理SIGCHLD信号时,通常会在信号处理函数中循环调用waitpid()函数来非阻塞等待子进程状态改变,以避免僵尸进程的产生。

有可能:有100个子进程,有50个退出了,50个还没有。那么在循环到51次时,waitpid会一直堵塞住,父进程就一直卡在那里,所以不能堵塞等待

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>void CleanupChild(int signum)
{while (true){pid_t rid = waitpid(-1, nullptr, WNOHANG); // -1 : 回收任意一个子进程;这里非堵塞if (rid > 0)//等待成功{std::cout << "wait child success: " << rid << std::endl;}else if (rid <= 0)break;}
}int main()
{signal(SIGCHLD, CleanupChild);for (int i = 0; i < 100; i++){pid_t id = fork();if (id == 0){// childint cnt = 5;while (cnt--){std::cout << "I am child process: " << getpid() << std::endl;sleep(1);}std::cout << "child process died" << std::endl;exit(0);}}// fatherwhile (true)sleep(1);return 0;
}

事实上,由于UNIX的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调 用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。系统默认的忽略动作和用户用sigaction函数自定义的忽略 通常是没有区别的,但这是一个特例。此方法对于Linux可用

  signal(SIGCHLD, SIG_IGN);//直接这样就行

好了今天就到这里啦

相关文章:

Linux:进程信号(二.信号的保存与处理、递达、volatile关键字、SIGCHLD信号)

上次介绍了&#xff1a;(Linux&#xff1a;进程信号&#xff08;一.认识信号、信号的产生及深层理解、Term与Core&#xff09;)[https://blog.csdn.net/qq_74415153/article/details/140624810] 文章目录 1.信号保存1.1递达、未决、阻塞等概念1.2再次理解信号产生与保存1.3信号…...

最值得推荐的5个AI大模型API

在这个以人工智能为主导的新时代&#xff0c;选择一个卓越的AI模型API接口&#xff0c;对于企业和个人在AI驱动的商业和技术革新中取得成功至关重要。 在人工智能的浪潮中&#xff0c;大型AI模型API接口正成为推动技术创新和业务发展的重要力量。随着2024年技术的持续进步和应用…...

PyTest+Allure生成测试报告

一、官网文档&#xff08;权威&#xff09; 1. Allure Report 官网&#xff1a;Allure Report Docs — Introduction 2. Allure GitHub地址&#xff1a;GitHub - allure-framework/allure2: Allure Report is a flexible, lightweight multi-language test reporting tool. It …...

ROS2教程(10) - 编写接收程序、添加frame - Linux

注意 : 本篇文章接上节 (点击此处跳转到上节) 编写接收程序 cpp <the_work_ws>/src/learning_tf2_cpp/src/turtle_tf2_listener.cpp #include <chrono> #include <functional> #include <memory> #include <string>#include "geometry_…...

Arraylist与LinkedList的区别

Arraylist 概念 Arraylist非线程安全Arraylist 底层使用的是Object数组ArrayList 采用数组存储&#xff0c;插入和删除元素的时间复杂度受元素位置的影响ArrayList 支持快速随机访问,就是通过元素的序号快速获取元素对象ArrayList的空间浪费主要体现在列表的结尾会预留一定的容…...

Nestjs使用Redis的最佳实践

前几天在项目中有用到Redis JWT实现服务端对token的主动删除(退出登录功能)。故此介绍下如何在Nestjs中使用Redis&#xff0c;并做下总结。 知识准备 了解Redis - 网上很多简介。了解Nestjs如何使用jwt生成token - 可移步看下我之前的文章 效果展示 一、mac安装与使用 示…...

Cadence23学习笔记(十四)

ARC就是圆弧走线的意思&#xff1a; 仅打开网络的话可以只针对net进行修改走线的属性&#xff1a; 然后现在鼠标左键点那个走线&#xff0c;那个走线就会变为弧形&#xff1a; 添加差分对&#xff1a; 之后&#xff0c;分别点击两条线即可分配差分对&#xff1a; 选完差分对之后…...

socket 编程

1. socket 套接字 Socket 是一个用于网络通信的技术。Socket 通信允许客户端——服务器之间进行双向通信。它可以使任何客户端机器连接到任何服务器&#xff0c;安装在客户端和服务器两侧的程序就可以实现双向的通信。Socket的作用就是把连接两个计算机的通信软件“中间接”起来…...

如何使用 HTTPie 进行高效的 HTTP 请求

如何使用 HTTPie 进行高效的 HTTP 请求 引言 HTTPie 是一个命令行 HTTP 客户端&#xff0c;它以其简洁的语法和人性化的输出格式赢得了广大开发者的喜爱。与 curl 相比&#xff0c;HTTPie 提供了更加直观和用户友好的接口&#xff0c;使得执行 HTTP 请求变得轻松愉快。本文将…...

Lingo求解器百度云下载 ling 8.0/lingo 18安装包资源分享

如大家所熟悉的&#xff0c;Lingo是Linear Interaction and General Optimizer的缩写&#xff0c;中文名称为“交互式线性和通用优化求解器”&#xff0c;是一套专门用于求解最优化问题的软件包。 在大部分人认知里&#xff0c;Lingo可用于求解线性规划、二次规划、整数规划、…...

文献综述如何为研究的理论框架做出贡献

VersaBot一键生成文献综述 文献综述在几个关键方面对塑造和巩固研究的理论框架起着至关重要的作用&#xff1b; 1. 识别相关理论和概念&#xff1a; 通过对现有研究的探索&#xff0c;您将遇到与您的主题相关的突出理论和概念。这些可以作为您自己的理论框架的构建块。 2. 理…...

FastAPI(七十九)实战开发《在线课程学习系统》接口开发-- 加入课程和退出课程

源码见&#xff1a;"fastapi_study_road-learning_system_online_courses: fastapi框架实战之--在线课程学习系统" 加入课程 我们先看下加入课程 1.是否登录 2.课程是否存在 3.是否已经存在 4.添加 首先实现逻辑 def get_student_course(db: Session, course: int…...

【赛事推荐】2024中国高校计算机大赛人工智能创意赛

“中国高校计算机大赛”&#xff08;China Collegiate Computing Contest&#xff0c;简称C4&#xff09;是面向全国高校各专业在校学生的科技类竞赛活动&#xff0c;于2016年由教育部高等学校计算机类专业教学指导委员会、教育部高等学校大学软件工程专业教学指导委员会、教育…...

C++沉思:预处理和编译

预处理和编译 条件编译源代码使用方式典型示例原理 使用static_assert执行编译时断言检查使用方式原理 在C中&#xff0c;编译是将源代码转换为机器代码并组织在目标文件中&#xff0c;然后将目标文件链接在一起生成可执行文件的过程。编译器实际上一次只处理一个文件&#xff…...

交通数据处理-计算途径某些路段的车辆数

根据车辆的运行轨迹&#xff0c;计算先经过某些路段&#xff0c;再经过某些路段的车辆数。 欢迎关注本人公众号--交通数据探索师 如下表&#xff0c; 其中&#xff1a;vehicle: 车辆编号&#xff1b;route: 车辆轨迹。 以第一行为例&#xff0c;车辆car1按顺序经过了路段123…...

从0到1入门系列 | 崖山公开课再加码,三小时带你入门崖山数据库!

对不断更新的技术心生迷茫 不知如何正确的提升自己&#xff1f; 对新兴的国产数据库领域充满好奇 却不知从何入手&#xff1f; 崖山专家团队精心筹备 《从0到1入门》系列直播课 6节课 三小时 助力数据库小白变身技术高手 掌握最前沿的数据库技术 现在开始 开启职场“金…...

Powershell自定义带参数的别名

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、函数二、使用步骤总结 前言 之前写了一篇文章定义别名让powershell尽可能接近Unix风格&#xff0c;增强两者的互操作性&#xff0c;今天给出方法可以定义带…...

文件操作相关的精讲

目录&#xff1a; 思维导图 一. 文件定义 二. 文件的打开和关闭 三. 文件的顺序读写操作 四. 文件的随机读写操作 五. 文本文件和二进制文件 六. 文件读取结束的判断 七.文件缓冲区 思维导图&#xff1a; 一. 文件定义 1.文件定义 C语言中&#xff0c;文件是指一组相…...

05 循环神经网络

目录 1. 基本概念 2. 简单循环网络 2.1 简单循环网络 2.2 长程依赖问题 3. 循环神经网络的模式与参数学习 3.1 循环神经网络的模式 3.2 参数学习 4. 基于门控的循环神经网络 4.1 长短期记忆网络 4.2 LSTM网络的变体网络 4.3 门控循环单元网络 5. 深层循环神经网络…...

C#初级——条件判断语句、循环语句和运算符

条件判断语句 简单的条件判断语句&#xff0c;if()里面进行条件判断&#xff0c;如果条件判断正确就执行语句块1&#xff0c;如果不符合就执行语句块2。 if (条件判断) { 语句块1 } else { 语句块2 } int age 18;if (age < 18){Console.WriteLine("未…...

通过Wrangler CLI在worker中创建数据库和表

官方使用文档&#xff1a;Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后&#xff0c;会在本地和远程创建数据库&#xff1a; npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库&#xff1a; 现在&#xff0c;您的Cloudfla…...

线程与协程

1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指&#xff1a;像函数调用/返回一样轻量地完成任务切换。 举例说明&#xff1a; 当你在程序中写一个函数调用&#xff1a; funcA() 然后 funcA 执行完后返回&…...

前端导出带有合并单元格的列表

// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

系统设计 --- MongoDB亿级数据查询优化策略

系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log&#xff0c;共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题&#xff0c;不能使用ELK只能使用…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配

AI3D视觉的工业赋能者 迁移科技成立于2017年&#xff0c;作为行业领先的3D工业相机及视觉系统供应商&#xff0c;累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成&#xff0c;通过稳定、易用、高回报的AI3D视觉系统&#xff0c;为汽车、新能源、金属制造等行…...

数据结构:递归的种类(Types of Recursion)

目录 尾递归&#xff08;Tail Recursion&#xff09; 什么是 Loop&#xff08;循环&#xff09;&#xff1f; 复杂度分析 头递归&#xff08;Head Recursion&#xff09; 树形递归&#xff08;Tree Recursion&#xff09; 线性递归&#xff08;Linear Recursion&#xff09;…...

【PX4飞控】mavros gps相关话题分析,经纬度海拔获取方法,卫星数锁定状态获取方法

使用 ROS1-Noetic 和 mavros v1.20.1&#xff0c; 携带经纬度海拔的话题主要有三个&#xff1a; /mavros/global_position/raw/fix/mavros/gpsstatus/gps1/raw/mavros/global_position/global 查看 mavros 源码&#xff0c;来分析他们的发布过程。发现前两个话题都对应了同一…...

python3GUI--基于PyQt5+DeepSort+YOLOv8智能人员入侵检测系统(详细图文介绍)

文章目录 一&#xff0e;前言二&#xff0e;技术介绍1.PyQt52.DeepSort3.卡尔曼滤波4.YOLOv85.SQLite36.多线程7.入侵人员检测8.ROI区域 三&#xff0e;核心功能1.登录注册1.登录2.注册 2.主界面1.主界面简介2.数据输入3.参数配置4.告警配置5.操作控制台6.核心内容显示区域7.检…...

在Android13上添加系统服务的好用例子

在Android13上添加一个自动的system service例子 留好&#xff0c;备用。 --- .../prebuilts/api/30.0/plat_pub_versioned.cil | 76 - .../prebuilts/api/31.0/plat_pub_versioned.cil | 94 - .../prebuilts/api/32.0/plat_pub_versioned.cil | 94 - frameworks/base/co…...