【Linux从入门到精通】信号(信号保存 信号的处理)

news2024/9/20 12:36:58

  本篇文章接着信号(初识信号 & 信号的产生)进行讲解。学完信号的产生后,我们也了解了信号的一些结论。同时还留下了很多疑问:

  1. 上篇文章所说的所有信号产生,最终都要有OS来进行执行,为什么呢?OS是进程的管理者
  2. 信号的处理是否是立即处理的?在合适的时候。具体是指什么时候呢?
  3. 信号如果不是被立即处理,那么信号是否需要暂时被进程记录下来?会的。记录在哪里最合适呢?
  4. 一个进程在没有收到信号的时候,能否能知道,自己应该对合法信号作何处理呢?知道。程序员已经在操作系统內部提供了对信号的处理机制
  5. 如何理解OS向进程发送信号?能否描述一下完整的发送处理过程呢?

  接下来带着上述的疑问,本篇文章都会进行详细解释。

文章目录

一、阻塞信号

1、1 信号的相关概念

1、2 阻塞信号的引入

1、3 sigset_t 及其相关操作

1、3、1 sigset_t 介绍

1、3、2 sigprocmask函数详解

1、3、3 阻塞信号演示

二、9号信号 SIGKILL 与 19号信号 SIGSTOP

2、1 自定义捕捉所有信号(No)

2、2 阻塞所有信号(No)

三、信号处理

3、1 什么时候处理信号合适呢?

3、2 信号处理的过程

3、3 sigaction()函数


🙋‍♂️ 作者:@Ggggggtm 🙋‍♂️

👀 专栏:Linux从入门到精通  👀

💥 标题:信号保存和处理💥

 ❣️ 寄语:与其忙着诉苦,不如低头赶路,奋路前行,终将遇到一番好风景 ❣️  

一、阻塞信号

1、1 信号的相关概念

  我们之前学了信号的一些概念。接下来在学习一下信号的常用专业概念:

  • 实际执行信号的处理动作称为信号递达(Delivery)。

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

  只有概念是不行的,我们不妨来看一下在内核中是怎么进行表示这新概念的。

1、2 阻塞信号的引入

  之前我们说到,信号是存储在进程控制块内部的一个位图中。那么我们来看一下在内核中到底是怎么进行表示的。具体如下图:

  block 位图的结构与 pending 位图的结构一摸一样,都是用位图来表示的。pending 位图表示的该信号发送了但是还没有被处理(也就是暂时保存了起来)。block 位图表示的是信号被阻塞了(不能够直接处理该信号,除非清除该信号所对应block位图的标记)handler 所对应的就是就是一个函数指针数组。里面包括了 SIG_IGN 、SIG_DFL 和我们自己定义的捕捉信号的方法。

  SIG_DFL、SIG_IGN 分别对应的是0和1。只不过是进行了强制类型转换,转换成了函数指针类型。我们也不难理解,函数指针数组的下标就是信号的编号,函数指针数组所指向的内容就是信号标号的处理方法

  为了更好的理解阻塞信号,我们在对上图进行详细解释:

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

  下面我们来看一个实际的例子,来理解阻塞信号。

1、3 sigset_t 及其相关操作

1、3、1 sigset_t 介绍

  我们知道:在语言层面,语言会给我们提供 .h/.hpp 头文件。并且语言有属于自己的自定义类型。那么操作系统也会给我们提供 .h 和 OS自定义的类型。sigset_t 就是OS的自定义类型。

  sigset_t是一个数据类型,用于在C/C++语言中表示信号集。通过使用信号集,可以管理给定进程或线程中的各种信号。通俗理解,sigset_t 底层就是位图未决和阻塞标志可以用相同的数据类型sigset_t来存储,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。

  sigset_t 类型对象不允许用户自己直接进行位操作,可以使用一些函数来对sigset_t进行操作,包括添加、删除、检查、清空信号。下面是一些与sigset_t相关的常用函数:

  1. sigemptyset(sigset_t *set):将信号集set清空,表示不包含任何信号。

  2. sigfillset(sigset_t *set):将信号集set设置为包含所有信号。

  3. sigaddset(sigset_t *set, int signum):将指定信号signum添加到信号集set中。

  4. sigdelset(sigset_t *set, int signum):将指定信号signum从信号集set中删除。

  5. sigismember(const sigset_t *set, int signum):检查指定信号signum是否在信号集set中存在。

  6. sigprocmask(int how, const sigset_t *set, sigset_t *oldset):用于修改进程的信号屏蔽字(signal mask)。how参数指定了如何修改信号屏蔽字,可以是SIG_BLOCK、SIG_UNBLOCK或SIG_SETMASK。set参数是一个要更新的新信号屏蔽字,而oldset参数则是保存之前的信号屏蔽字。

  7. sigpending(sigset_t *set):获取进程中当前未决(pending)的信号集合。未决信号是已经产生但还没有被处理的信号。

1、3、2 sigprocmask函数详解

  sigprocmask函数是一个操作信号屏蔽字的系统调用函数,用于设置当前进程的信号屏蔽字(signal mask),从而控制信号的阻塞和解除阻塞。函数原型:

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

参数解释:

  • how:表示信号屏蔽字的操作方式,可以使用下面的常量:
    • SIG_BLOCK:将set中的信号集合添加到当前的信号屏蔽字中。
    • SIG_UNBLOCK:将set中的信号集合从当前的信号屏蔽字中移除。
    • SIG_SETMASK:将set中的信号集合替换为当前的信号屏蔽字。
  • set:指向要设置的信号集合的指针。
  • oldset:用于保存之前的信号屏蔽字的指针。

函数返回值:

  • 成功执行时,返回0。
  • 出现错误时,返回-1,并设置errno来指示错误类型。

函数功能:

  • 通过调用sigprocmask函数,可以在进程运行过程中更改信号屏蔽字,从而控制哪些信号被阻塞,哪些信号被接收。
  • 当我们设置一个信号屏蔽字时,相关信号将被阻塞,无法触发信号处理函数。
  • 通过修改信号屏蔽字,可以在需要的时候阻塞指定信号,也可以在不需要阻塞时解除信号屏蔽。

使用示例:

#include <signal.h>

int main() {
    sigset_t mask;
    sigemptyset(&mask);   // 清空信号集合
    sigaddset(&mask, SIGINT);   // 添加SIGINT信号到信号集合
    
    // 将SIGINT信号添加到当前进程的信号屏蔽字中
    if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) {
        perror("sigprocmask");
        return 1;
    }

    // 执行其他操作

    // 解除阻塞SIGINT信号
    if(sigprocmask(SIG_UNBLOCK, &mask, NULL) == -1){
        perror("sigprocmask");
        return 1;
    }
 
    return 0;
}

  上述示例中,首先创建一个空的信号集合mask,并将SIGINT信号添加到该集合中。然后调用sigprocmask函数将SIGINT信号添加到当前进程的信号屏蔽字中,从而阻塞该信号的触发。在某些需要保证信号不被处理的临界区代码中,可以使用这种方式阻塞信号。接下来的操作可以在无需考虑SIGINT信号的影响下进行。最后,通过调用sigprocmask函数并传递SIG_UNBLOCK参数,可以解除对SIGINT信号的阻塞。

1、3、3 阻塞信号演示

  通过我们上述了解到的sigset_t 和其相关操作函数,那么接下来我们就写一段代码来演示一下阻塞的信号。

  我们的主要思路是:先阻塞(屏蔽)的信号2(SIGINT)。代码通过循环不断地打印pending信号位图,在发送信号2之前,信号2的pending状态一直为0。当我们发送信号2后,由于信号2被阻塞,所以并不能立刻处理信号2。此时信号2的pending状态就会变为1。在信号被解除屏蔽之前,信号2的pending状态一直为1,表示有一个信号2没有被递达。而在解除屏蔽后,如果在运行过程中产生了信号2,就会立即处理该信号,pending位图中的位置将显示为0,表示所有的信号都已经被递送处理

// 打印 pending 位图
void showPending(sigset_t &pending)
{
    for (int sig = 1; sig <= 31; sig++)
    {
        // 检查是否在信号集pending中,并打印
        if (sigismember(&pending, sig))
            std::cout << "1";
        else
            std::cout << "0";
    }
    std::cout << std::endl;
}

int main()
{
    // 1. 定义信号集对象
    sigset_t bset, obset;
    sigset_t pending;

    // 2. 初始化
    sigemptyset(&bset);
    sigemptyset(&obset);
    sigemptyset(&pending);

    // 3. 添加要进行屏蔽的信号
    sigaddset(&bset, 2 /*SIGINT*/);

    // 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)
    {
        // 5.1 获取当前进程的pending信号集
        sigpending(&pending);

        // 5.2 显示pending信号集中的没有被递达的信号
        showPending(pending);
        sleep(1);
        count++;
        if (count == 20)
        {
            std::cout << "解除对于2号信号的block" << std::endl;
            int n = sigprocmask(SIG_SETMASK, &obset, nullptr);
            assert(n == 0);
            (void)n;
        }
    }
    return 0;
}

  那我们接下来看看是否与我们所预期的结果相同。具体如下:

  结果与我们所预料的基本上相同。只不过是看不到解除2号信号后的 pending 位图。因为一旦解除,就会执行该信号进程就会退出。当然,我们可以进行自定义捕捉,然后再打印。

二、9号信号 SIGKILL 与 19号信号 SIGSTOP

  学完信号阻塞后,如果我们对所有的信号都进行设置block阻塞,我们是不是就写了一个不会被异常或者用户杀掉的进程? 

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

  我们不妨来自己验证一下,看看到底是否能像完成我们所想的那样。

2、1 自定义捕捉所有信号(No)

  代码很简单,我们不再作过多解释:

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

int main()
{
    for(int sig = 1; sig <= 31; sig++) signal(sig, catchSig);

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

  那我们来看看运行结果:

  其实并不是能够将所有信号进行自定义捕捉。通过上述测试,我们发现9号信号不可被自定义捕捉。实际上,19号信号SIGSTOP和9号信号SIGKILL是两个信号,不可被应用程序进行自定义捕获的

2、2 阻塞所有信号(No)

  我们直接看代码:

void blockSig(int sig)
{
    sigset_t bset;
    sigemptyset(&bset);
    sigaddset(&bset, sig);
    int n = sigprocmask(SIG_BLOCK, &bset, nullptr);
    assert(n == 0);
    (void)n;
}

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;
}
int main()
{
    for(int sig = 1; sig <= 31; sig++)
    {
        blockSig(sig);
    }

    sigset_t pending;
    while(true)
    {
        sigpending(&pending);
        showPending(pending);
        sleep(1);
    }
    return 0;
}

  运行结果如下:

  实际上也不能阻塞所有信号。如上图情况。

  SIGSTOP和SIGKILL是两个特殊的信号,它们有一些不同于其他信号的特性。以下是它们不可被应用程序捕获、阻塞或忽略的原因:

  1. SIGSTOP:SIGSTOP是一个用于暂停进程执行的信号。当进程接收到SIGSTOP信号时,进程会立即停止执行并进入暂停状态。这个信号是由操作系统发出的,应用程序无法捕获、处理或阻塞该信号。这是为了保证系统能够强制终止进程的执行,以防止进程对系统造成不可预知的影响。

  2. SIGKILL:SIGKILL是一个用于强制终止进程的信号。当进程接收到SIGKILL信号时,进程会被立即终止,而且无法被阻塞或忽略。这个信号同样由操作系统发出,目的是确保应用程序无法继续执行。与SIGKILL不同的是,应用程序也不能捕获或处理SIGKILL信号。这是为了保证系统能够彻底终止某个进程,即使该进程可能不响应其他信号。

三、信号处理

3、1 什么时候处理信号合适呢?

  我们前面一直说会在合适的时候对信号进行处理。合适的时候具体的是什么呢?是从内核态切换到用户态的时候,会对信号进行检测和处理!

  内核态(Kernel Mode)和用户态(User Mode)是两个不同的执行环境,用于区分操作系统内核的特权级别和用户程序的特权级别。下面是对这两者的详细解释:

  1. 用户态(User Mode):

    • 用户态是指用户程序运行的环境,此时程序只能访问自身被授权的资源,如自身的内存空间、文件、设备等。
    • 在用户态下,应用程序不能直接访问和修改底层硬件资源,也无法执行特权指令和访问内核空间。
    • 用户态程序可以通过系统调用(System Call)向内核发起请求,以便获得更高级别的特权或访问系统资源。而系统调用会触发从用户态转换到内核态。
  2. 内核态(Kernel Mode):

    • 内核态是操作系统内核运行的环境,具有最高的特权级别。在这个特权级别下,操作系统具有完全掌控计算机硬件资源的权限。
    • 在内核态下,操作系统能够直接访问和操作所有的硬件资源,如处理器、内存、I/O设备等。它可以执行特权指令,控制内存分配和进程调度等底层操作。
    • 操作系统内核负责处理系统的底层任务,如中断处理、进程管理、内存管理、文件系统等。这些任务要求运行在内核态下才能完成。

  我们这里为什么会进入内核态呢?原因有很多,例如:系统调用、异常和中断处理、时间片等等原因。

3、2 信号处理的过程

  信号处理的整个过程如下:

  如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下: 用户程序注册了SIGQUIT信号的处理函数sighandler。 当前正在执行main函数,这时发生中断或异常切换到内核态。 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是 两个独立的控制流程。 sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。
  为什么在执行sighandler函数时,还需要返回用户态呢?只用在内核态的状态下执行不也可以吗?确实,内核能够且有权限执行sighandler函数,但是并不会去执行。为什么呢?因为操作系统是不信任我们任何人的!!!当我们处于内核态时,是操作系统内核运行的环境,具有最高的特权级别。万一用户自定义的sighandler函数有恶意程序呢?所以是要返回到用户态进行执行的。

3、3 sigaction()函数

  sigaction函数是一个用于设置信号处理函数的系统调用。它可以用于捕捉并处理各种类型的信号,如中断、故障、取消等。 函数原型如下:

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

  其中,signum表示要设置的信号编号,act是一个指向struct sigaction结构体的指针,用于设置该信号的处理方式,oldact是一个指向struct sigaction结构体的指针,用于保存原来的处理方式。struct sigaction结构体定义如下:

struct sigaction {
  void     (*sa_handler)(int);
  void     (*sa_sigaction)(int, siginfo_t *, void *);
  sigset_t sa_mask;
  int      sa_flags;
  void     (*sa_restorer)(void);
};

  sa_handler字段用于指定信号处理函数的地址。当信号到达时,系统会将控制权传递给该函数进行处理。sa_sigaction字段和sa_handler字段是互斥的,如果同时指定了这两个字段,以sa_sigaction字段为准

  sa_mask字段是一个信号屏蔽字,用于指定在信号处理函数执行期间需要被屏蔽的信号集合。也就是说,当信号处理函数执行时,除了属于sa_mask集合的信号外,其他信号都会被阻塞,直到该函数返回。

  sa_flags字段用于设置信号处理的一些标志。常用的标志包括:

  • SA_RESTART:设置系统调用被信号中断后自动重启;
  • SA_SIGINFO:指定信号处理函数有三个参数,可以额外获取信号的附加信息。

  sa_restorer字段是废弃字段,一般不使用。

  调用sigaction函数可以设置信号的处理方式。如果act为NULL,则表示忽略该信号;如果oldact不为NULL,则会将原来的处理方式保存到oldact中。

  成功调用sigaction函数时,返回0;失败时,返回-1,并设置errno以表明具体的错误原因。

  以下是一个使用sigaction函数的示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void sig_handler(int signum) {
    printf("Received signal %d\n", signum);
}

int main() {
    struct sigaction sa,osa;
    
    sa.sa_handler = sig_handler; // 设置信号处理函数
    sigemptyset(&sa.sa_mask); // 清空信号屏蔽字
    sa.sa_flags = 0; // 标志位置0

    if (sigaction(SIGINT, &sa, NULL) == -1) { // 设置SIGINT信号的处理方式
        perror("sigaction error");
        exit(EXIT_FAILURE);
    }

    printf("Waiting for signal...\n");
    
    // 设置信号屏蔽字
    sigaddset(&sa.sa_mask, 3);
    sigaddset(&sa.sa_mask, 4);
    sigaddset(&sa.sa_mask, 5);
    sigaddset(&sa.sa_mask, 6);
    sigaddset(&sa.sa_mask, 7);

    // 设置进当前调用进程的pcb中
    sigaction(2, &sa, &osa);
    
    while(1) {
        // 程序一直循环等待信号的到来
    }

    return 0;
}

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

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

相关文章

在Android studio 创建Flutter项目运行出现问题总结

在Android studio 中配置Flutter出现的问题 A problem occurred configuring root project ‘android’出现这个问题。解决办法 首先找到flutter配置的位置 在D:\xxx\flutter\packages\flutter_tools\gradle位置中的flutter.gradle buildscript { repositories { googl…

相机坐标系 -> 像素坐标系

代码链接&#xff1a;https://github.com/PanJinquan/python-learning-notes/blob/master/modules/utils_3d/camera_tools.py def __cam2pixel(cam_coord, f, c):"""相机坐标系 -> 像素坐标系: (f / dx) * (X / Z) f * (X / Z) / dxcx,ppx260.166; cy,ppy…

分库分表---理论

目录 一、垂直切分 1、垂直分库 2、垂直分表 3、垂直切分优缺点 二、水平切分 1、水平分库 2、水平分表 3、水平切分优缺点 三、数据分片规则 1、Hash取模分表 2、数值Range分表 3、一致性Hash算法 四、分库分表带来的问题 1、分布式事务问题 2、跨节点关联查询…

【FAQ】本地录像视频文件如何推送到视频监控平台EasyCVR进行AI视频智能分析?

安防监控平台EasyCVR支持多协议、多类型设备接入&#xff0c;可以实现多现场的前端摄像头等设备统一集中接入与视频汇聚管理&#xff0c;并能进行视频高清监控、录像、云存储与磁盘阵列存储、检索与回放、级联共享等视频功能。视频汇聚平台既具备传统安防监控、视频监控的视频能…

Vue2电商前台项目——完成Search搜索模块业务

Vue2电商前台项目——完成Search搜索模块业务 Vue基础知识点击此处——Vue.js 文章目录 Vue2电商前台项目——完成Search搜索模块业务一、项目开发的步骤二、各种请求数据并展示数据1、写Search模块的接口2、写Vuex中的search仓库3、组件拿到search仓库的数据&#xff08;1&…

详解HPE MSA 2040存储初始化配置划分卷

哈喽大家好&#xff0c;欢迎来到虚拟化时代君&#xff08;XNHCYL&#xff09;。 “ 大家好&#xff0c;我是虚拟化时代君&#xff0c;一位潜心于互联网的技术宅男。这里每天为你分享各种你感兴趣的技术、教程、软件、资源、福利……&#xff08;每天更新不间断&#xff0c;福…

mock技术在测试中的应用

技术简介 mock技术又叫测试桩、挡板 在软件测试中&#xff0c;对于一些不容易构造、获取的对象&#xff0c;用一个虚拟的对象来代替它&#xff0c;以达到相同的效果&#xff0c;这个虚拟的对象就是mock。 mock技术并不是只有测试领域用&#xff0c;最早是在开发领域应用&…

互联网电视流氓乱收费被市场惩罚,传统品牌合力挤压互联网电视

市调机构洛图科技&#xff08;RUNTO&#xff09;公布的6月份数据显示&#xff0c;传统电视品牌强势反弹&#xff0c;海信、TCL、创维的销量分别为60万台、58万台、57万台&#xff0c;名次分别为第一名、第三名、第四名&#xff0c;而曾连续数年位居国内电视行业第一名的某互联网…

精品基于NET实现的汽配网上商城系统

《[含文档PPT源码等]精品基于NET实现的汽配网上商城系统》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程等 软件开发环境及开发工具&#xff1a; 开发软件&#xff1a;VS 2017 &#xff08;版本2017以上即可&#xff0c;不能低于2017&#xff09; 数…

第十二章总结

一.集合类概述 java.util包中提供了一些集合类&#xff0c;这些集合类又被称为容器。 集合类与数组的不同之处&#xff1a; 数组的长度是固定的&#xff0c;集合的长度是可变的&#xff1a;数组用来存放基本类型的数据&#xff0c;集合用来存放对象的引用。 常…

windows10系统下Python3.11中安装Numpy库教程

Python3.11中安装Numpy库目录 项目场景&#xff1a;问题描述解决方案&#xff1a;①下载Numpy文件②把NumPy文件放到Python安装的Scripts文件夹里。③安装numpy④安装验证 项目场景&#xff1a; numpy是开源的数值计算扩展&#xff0c;用于数据分析、机器学习、科学计算的重要…

(第十一天)初识SpringMVC SSM框架的学习与应用(Spring + Spring MVC + MyBatis)-Java EE企业级应用开发学习记录

SSM框架的学习与应用(Spring Spring MVC MyBatis)-Java EE企业级应用开发学习记录&#xff08;第十一天&#xff09;初识SpringMVC 今天我们要来学习一下SSM框架的最后一个框架SpringMVC 一、初认SpringMVC 基本概念&#xff1a; ​ Spring MVC&#xff08;Model-View-Co…

Qt应用开发(基础篇)——菜单 QMenu

一、前言 QMenu类继承于QWidget&#xff0c;它提供了一个菜单样式的小部件&#xff0c;用于菜单栏、上下文菜单和一些弹出式菜单。 QMenu菜单的选项是可选的&#xff0c;它可以是一个下拉的菜单&#xff0c;也可以是独立的上下文菜单。下拉菜单通常作用于当用户单击相应的项目或…

Unity——模拟AI视觉

人类的视觉系统有以下几个特点&#xff1a; 距离有限。近处看得清&#xff0c;远处看不清容易被遮挡。不能穿过任何不透明的障碍物视野范围大约为90度。实现正前方信息丰富&#xff0c;具有色彩和细节&#xff1b;实现外侧的部分只有轮廓和运动信息注意力有限。当关注某个具体的…

深入浅出学Verilog--数据类型

1、数值类型 在Verilog可以用4种数值来描述其构建的电路的电平逻辑&#xff0c;除了event类型和real类型外&#xff0c;几乎所有的数据类型都可以用这4种数值来表示。 0&#xff1a;代表逻辑0&#xff0c;或者条件“假”1&#xff1a;代表逻辑1&#xff0c;或者条件“真”x或X…

ubuntu基本配置

记录一下每次重新安装系统之后都要进程的操作 更新源 更新源的教程 sudo bash -c "cat << EOF > /etc/apt/sources.list && apt update deb http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse deb-src http://mirrors.a…

《Envoy 代理:云原生时代的流量管理》

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f405;&#x1f43e;猫头虎建议程序员必备技术栈一览表&#x1f4d6;&#xff1a; &#x1f6e0;️ 全栈技术 Full Stack: &#x1f4da…

持安科技孙维伯:零信任理念下的实战攻防:ISC2023数字小镇演讲

近日&#xff0c;在ISC 2023第十一届互联网安全大会上&#xff0c;持安科技联合创始人孙维伯作为零信任办公安全赛道代表&#xff0c;亮相数字小镇New50&#xff0c;并发表《全方位防御&#xff1a;零信任理念下的实战攻防》主题演讲。 以下是本次演讲实录&#xff1a; 这几年…

第9章_freeRTOS入门与工程实践之任务管理

本教程基于韦东山百问网出的 DShanMCU-F103开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id724601559592 配套资料获取&#xff1a;https://rtos.100ask.net/zh/freeRTOS/DShanMCU-F103 freeRTOS系列教程之freeRTOS入…

考虑负荷满意度的微电网运行多目标优化方法研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…