linux信号集与信号掩码-保护信号处理程序,确保进程正确运行


在Linux环境下,当进程收到信号时,如何优雅地处理并确保程序的正常运行?这就需要借助信号集和信号掩码的功能。本文将为你揭开信号集和信号掩码的神秘面纱,并通过生动的代码示例,让你彻底掌握在C++程序中使用它们的技巧。


一、信号集:表示信号的数据结构

信号集(signal set)是一种用于表示进程当前阻塞了哪些信号的数据结构,它本质上是一个数组 bitmap,使用sigset_t结构体来表示。每一种信号对应一个位,如果该位被置位(值为1),则表示该信号被阻塞,否则(值为0)表示未被阻塞。


在这里插入图片描述


我们可以使用以下函数来操作信号集:

  • sigemptyset(&set): 将set中所有位清零,即不阻塞任何信号
  • sigfillset(&set): 将set中所有位置1,即阻塞全部信号
  • sigaddset(&set, sig): 将信号sig对应的位置1,即阻塞该信号
  • sigdelset(&set, sig): 将信号sig对应的位清零,即解除对该信号的阻塞
  • sigismember(&set, sig): 检查信号sig是否被set阻塞

示例代码:

#include <signal.h>
#include <iostream>

int main() {
    sigset_t set;
    
    // 初始化为空集
    sigemptyset(&set); 
    
    // 阻塞所有信号的传递
    sigset_t newset;
	  sigfillset(&newset);
    
    // 添加SIGINT(Ctrl+C)信号
    sigaddset(&set, SIGINT);
    
    // 检查SIGINT是否在集合中
    if (sigismember(&set, SIGINT)) {
        std::cout << "SIGINT is blocked" << std::endl;
    }
    
    // 移除SIGINT
    sigdelset(&set, SIGINT);
    
    return 0;
}

二、信号掩码:阻塞信号的机制


1、信号掩码(signal mask)

信号掩码(signal mask)是Linux内核为每个进程维护的一个信号集,用于暂时阻塞该进程接收某些信号。除了SIGKILL和SIGSTOP这两个特殊信号外,其他信号都可以被阻塞。

内核会为每一个进程都维护一个信号掩码,也就是一组信号,阻塞其针对该进程的传递,直到进程从信号掩码中将该信号移除。

我们可以通过sigprocmask系统调用来修改进程的当前信号掩码:

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • how 参数指定如何修改掩码
    • SIG_BLOCK: 将set指向的信号集并入当前掩码
    • SIG_UNBLOCK: 将set指向的信号集从当前掩码中移除
    • SIG_SETMASK: 使用set指向的信号集作为新的信号掩码
  • oldset 用于保存修改前的信号掩码,如果不需要可设为NULL

例如,阻塞SIGINT(Ctrl+C)信号:

#include <signal.h>
#include <iostream>

int main() {
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGINT);
    
    // 阻塞SIGINT信号
    sigprocmask(SIG_BLOCK, &set, NULL);
    
    // 此时按下Ctrl+C将不会终止程序
    while (true) {
        std::cout << "Program running..." << std::endl;
        sleep(1);
    }
    
    return 0;
}

2、系统的默认行为

前面我们提到过,当前进程正在调用
SIGX
的信号处理函数,那么紧接着而来的
SIGX
信号将会被阻塞,直到上一个信号处理函数结束,这一现象我们可以用一个简单的例子证明 :

void handler(int signum)
{ 
  printf("Got a SIGINT\n"); 
  sigset_t currentset; 
  sigprocmask(SIG_BLOCK,NULL,&currentset); 
  int res=sigismember(&currentset,SIGINT);
  printf("SIGINT is blocked ?:%d\n",res); 
}

我们用
SIGINT
作为捕获信号,当我们键入
Ctrl-C 时,将会发现 SIGINT
信号的确是在当前进程的信号掩码中。

引发对处理器程序调用的信号将自动添加到进程信号掩码中。这意味着,当正在执行处理器程序时,如果同一个信号 实例第二次抵达,信号处理器程序将不会递归中断自己。


3、sigpending 获取等待的信号集


有时候我们想要知道当前进程阻塞了哪些信号,此时可以使用
sigpending() 来获得处于等待状态的信号集。

sigpending 是一个用于检查哪些信号当前正被一个进程阻塞的系统调用。在 Unix 和类 Unix 系统中,进程可以选择性地阻塞某些信号,这意味着在它们被阻塞期间,这些信号不会被传递给进程。当进程准备好处理这些信号时,可以解除信号的阻塞。

sigpending 函数通常与一个信号集(sigset_t 类型)一起使用,这个信号集包含了所有当前被阻塞的信号。

以下是 sigpending 的典型用法:

#include <signal.h>
#include <iostream>

int main() {
    sigset_t pending_set;

    // 获取当前被阻塞的信号集
    if (sigpending(&pending_set) == -1) {
        perror("sigpending");
        return 1;
    }

    // 检查特定信号是否在被阻塞的信号集中
    if (sigismember(&pending_set, SIGINT)) {
        std::cout << "SIGINT is pending." << std::endl;
    } else {
        std::cout << "SIGINT is not pending." << std::endl;
    }

    // ... 其他操作 ...

    return 0;
}

在上面的示例中,我们首先声明了一个 sigset_t 类型的变量 pending_set,然后调用 sigpending 函数来填充这个变量,它包含了当前所有被阻塞的信号。接着,我们使用 sigismember 函数来检查 SIGINT 信号是否在被阻塞的信号集中。

sigpending 函数的原型如下:

int sigpending(sigset_t *set);
  • set 是一个指向 sigset_t 结构的指针,该结构将被 sigpending 填充为包含当前被阻塞信号的集合。

如果 sigpending 成功执行,它将返回 0,并将被阻塞的信号集存储在 set 指向的 sigset_t 结构中。如果发生错误,它将返回 -1 并设置 errno 以指示错误类型。

使用 sigpending 可以帮助进程了解哪些信号正在等待被处理,这在多线程环境中尤其有用,因为信号通常只能被传递给线程组中的一个线程。了解哪些信号被阻塞可以帮助进程做出适当的响应。


三、信号集和信号掩码的应用场景


通过合理使用信号集和信号掩码,我们可以更好地控制进程对信号的响应方式,确保关键代码路径不被意外打断。具体应用场景包括但不限于:


1、保护信号处理函数

在执行信号处理函数期间,自动将相同的信号添加到进程掩码中,以避免递归调用导致的问题。

保护信号处理函数通常意味着在信号处理函数执行期间,系统会自动将该信号添加到进程的信号掩码中,以防止递归调用。这是通过操作系统自动处理的,通常不需要程序员手动设置。

然而,如果你需要手动控制信号掩码,可以使用sigprocmask函数。以下是一个C++示例,演示如何在信号处理函数执行前后手动修改信号掩码,以确保信号处理函数不会被递归调用:

#include <iostream>
#include <csignal>
#include <setjmp.h>
#include <sys/types.h>
#include <unistd.h>
#include <cerrno>

void signalHandler(int signum) {
    static int inHandler = 0; // 用于检测递归调用

    if (inHandler) {
        std::cout << "SignalHandler: Already in handler, recursion detected." << std::endl;
        return;
    }

    inHandler = 1; // 标记信号处理函数正在执行

    // 执行信号处理逻辑
    std::cout << "SignalHandler: Handling signal " << signum << std::endl;

    // 重置标记并解除信号阻塞
    inHandler = 0;
}

int main() {
    // 设置信号处理函数
    signal(SIGINT, signalHandler);

    // 主循环
    while (true) {
        pause(); // 等待信号
    }

    return 0;
}

在这个示例中,我们定义了一个静态变量inHandler来检测递归调用。当信号处理函数被调用时,我们检查inHandler是否已经被设置,如果是,则表示我们已经在处理一个信号,并且现在又收到了相同的信号,这是递归调用。我们打印一条消息并返回,而不执行任何操作。

请注意,这个示例中的inHandler变量用于演示目的,实际上操作系统会自动处理信号的递归调用问题,通常不需要程序员手动设置。

signal函数在多线程环境中可能不够安全,可以考虑使用sigaction函数来设置信号处理行为,其中sigaction允许你更精确地控制信号处理的行为,包括信号掩码。


2、进程间通信

父进程可以通过发送SIGCHLD信号来等待子进程结束;而子进程也可以通过发送指定信号来通知父进程某些事件发生。


(1)、父进程等待子进程结束

父进程可以通过捕获SIGCHLD信号来等待子进程结束。通常,当子进程结束时,内核会向父进程发送SIGCHLD信号。

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

void sigchld_handler(int signum) {
    // 等待所有已终止的子进程
    while (waitpid(-1, NULL, WNOHANG) > 0)
        ;
    std::cout << "SIGCHLD received, child process has terminated." << std::endl;
}

int main() {
    // 设置SIGCHLD信号的处理函数
    struct sigaction sa;
    sa.sa_handler = sigchld_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; // SA_NOCLDSTOP 阻止父进程接收子进程停止的信号
    sigaction(SIGCHLD, &sa, NULL);

    // 创建子进程
    pid_t pid = fork();
    if (pid == -1) {
        std::cerr << "Fork failed" << std::endl;
        return 1;
    } else if (pid == 0) {
        // 子进程
        std::cout << "Child process exiting." << std::endl;
        exit(0);
    } else {
        // 父进程
        std::cout << "Parent process waiting for child to exit." << std::endl;
        pause(); // 等待信号
    }

    return 0;
}

在这个示例中,父进程设置了SIGCHLD信号的处理函数sigchld_handler。当子进程结束时,内核会发送SIGCHLD信号给父进程,父进程接收到信号后会调用sigchld_handler函数,该函数使用waitpid系统调用来收集子进程的退出状态。

(2)、子进程通知父进程事件

子进程可以通过发送指定的信号来通知父进程某些事件的发生。例如,子进程可以通过发送SIGUSR1信号来通知父进程它已经完成了某个任务。

#include <iostream>
#include <signal.h>
#include <unistd.h>

void sigusr1_handler(int signum) {
    std::cout << "Parent process received SIGUSR1." << std::endl;
    // 处理信号,例如更新状态或执行其他任务
}

int main() {
    // 设置SIGUSR1信号的处理函数
    struct sigaction sa;
    sa.sa_handler = sigusr1_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    sigaction(SIGUSR1, &sa, NULL);

    // 创建子进程
    pid_t pid = fork();
    if (pid == -1) {
        std::cerr << "Fork failed" << std::endl;
        return 1;
    } else if (pid == 0) {
        // 子进程
        std::cout << "Child process is about to notify parent." << std::endl;
        kill(getppid(), SIGUSR1); // 向父进程发送SIGUSR1信号
        exit(0);
    } else {
        // 父进程
        pause(); // 等待信号
    }

    return 0;
}

在这个示例中,父进程设置了SIGUSR1信号的处理函数sigusr1_handler。子进程使用kill函数发送SIGUSR1信号给父进程。父进程接收到信号后会调用sigusr1_handler函数进行处理。


3、同步与互斥

信号可以作为一种简单的通知机制,在某些情况下辅助实现同步和互斥。例如,一个进程可以发送信号给另一个进程以指示某个事件的发生,接收进程在其信号处理函数中执行同步或互斥操作。


以下是一个简化的示例,演示如何使用信号在两个进程之间进行基本的同步操作:

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

// 定义一个全局变量作为锁
volatile bool lock = false;

// 信号处理函数,用于释放锁
void signal_handler(int signum) {
    if (signum == SIGUSR1) {
        std::cout << "Signal received, releasing lock." << std::endl;
        lock = false;
    }
}

int main() {
    // 设置信号处理函数
    signal(SIGUSR1, signal_handler);

    // 创建子进程
    pid_t pid = fork();
    if (pid == -1) {
        std::cerr << "Fork failed" << std::endl;
        return 1;
    } else if (pid == 0) {
        // 子进程
        while (lock) {
            // 等待锁被释放
            std::cout << "Child process waiting for lock to be released." << std::endl;
            sleep(1);
        }
        std::cout << "Child process continuing after lock is released." << std::endl;
        // 子进程的其余逻辑...
        exit(0);
    } else {
        // 父进程
        lock = true; // 设置锁
        std::cout << "Lock is set." << std::endl;

        // 执行一些操作...
        sleep(5); // 模拟长时间运行的任务

        // 释放锁,并通知子进程
        lock = false;
        kill(pid, SIGUSR1);

        // 等待子进程结束
        wait(NULL);
        std::cout << "Parent process finished." << std::endl;
    }

    return 0;
}

在这个示例中,我们使用一个全局变量`lock`作为锁。父进程在开始执行任务时设置锁,并在任务完成后释放锁,同时发送`SIGUSR1`信号给子进程。子进程在一个无限循环中检查锁的状态,如果锁被设置(`lock`为`true`),则等待。一旦收到信号,子进程知道锁已经被释放,可以继续执行。

请注意,这个示例仅用于演示目的,实际使用中应避免在信号处理函数中执行复杂的逻辑或访问非线程安全的全局变量。在多线程环境中,应使用线程安全的同步机制,如互斥锁或条件变量,来实现同步和互斥。此外,信号的发送和接收可能会有竞态条件,因此在实际应用中需要仔细设计以确保正确性和安全性。


4、非阻塞I/O

信号驱动I/O(也称为异步I/O)是一种高效的非阻塞I/O操作方式。它允许程序在等待I/O操作完成时继续执行,当I/O操作准备就绪时,操作系统会发送一个信号通知程序。


以下是一个使用信号驱动I/O的简单示例,演示如何在网络编程中实现非阻塞I/O操作:

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/ioctl.h>

volatile int socket_ready = 0;

void sigio_handler(int signum) {
    if (signum == SIGIO) {
        socket_ready = 1; // 标记socket操作准备就绪
    }
}

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket");
        return 1;
    }

    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8080);
    inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);

    if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        if (errno == EINPROGRESS) {
            std::cout << "Connection in progress, waiting for socket to become ready..." << std::endl;
        } else {
            perror("connect");
            close(sockfd);
            return 1;
        }
    }

    // 设置信号驱动I/O
    fcntl(sockfd, F_SETOWN, getpid());
    fcntl(sockfd, F_SETFL, O_ASYNC);

    // 设置SIGIO信号处理函数
    struct sigaction sa;
    sa.sa_handler = sigio_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    sigaction(SIGIO, &sa, NULL);

    while (!socket_ready) {
        std::cout << "Waiting for socket to become ready..." << std::endl;
        sleep(1); // 在等待期间休眠,避免占用CPU资源
    }

    std::cout << "Socket is ready!" << std::endl;

    // 执行I/O操作...

    close(sockfd);
    return 0;
}

在这个示例中,我们首先创建了一个socket,并尝试连接到指定的服务器地址。如果连接操作立即完成,则表示连接成功;如果连接操作返回EINPROGRESS错误,则表示连接正在进行中,是非阻塞的。

接下来,我们使用fcntl函数设置socket以支持信号驱动I/O。我们通过F_SETOWN设置当前进程为接收SIGIO信号的所有者,并通过F_SETFL设置O_ASYNC标志,以使socket操作变为异步。

然后,我们设置SIGIO信号的处理函数sigio_handler。当socket操作准备就绪时(例如,连接建立),操作系统会发送SIGIO信号给进程,调用sigio_handler函数,并将socket_ready标志设置为1。

最后,我们在主循环中等待socket_ready标志变为1,表示socket操作已经准备就绪。一旦收到信号,程序就可以执行I/O操作。

请注意,信号驱动I/O是一种高级特性,需要对系统调用和信号处理有深入的理解。此外,不同的操作系统和编译器可能有不同的实现和限制。上述代码仅供学习和参考,实际应用时需要根据具体情况进行调整。


四、信号处理的高级API:sigaction


虽然signal函数也可以注册信号处理程序,但由于其移植性和功能局限性,在实际开发中通常使用sigaction函数:

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

sigaction结构体中除了包含信号处理函数指针外,还有sa_mask和sa_flags字段,用于设置更多选项:

  • sa_mask: 一个信号集,在执行该信号处理程序期间将自动阻塞该信号集中的信号
  • sa_flags: 控制信号处理过程的标志位,比如SA_RESTART可以自动重启被信号中断的系统调用

示例:

#include <signal.h>
#include <iostream>

void handler(int sig) {
    std::cout << "Caught signal " << sig << std::endl;
}

int main() {
    struct sigaction sa;
    sa.sa_handler = handler;
    
    // 在处理SIGINT时阻塞SIGQUIT信号
    sigemptyset(&sa.sa_mask);
    sigaddset(&sa.sa_mask, SIGQUIT); 
    
    sa.sa_flags = SA_RESTART;
    
    sigaction(SIGINT, &sa, NULL);
    
    // 进程将一直运行直到收到SIGQUIT信号
    while (true) {
        std::cout << "Program running..." << std::endl;
        sleep(1);  
    }
    
    return 0;
}

通过掌握信号集和信号掩码的使用技巧,相信你已经对Linux信号处理机制有了更深入的理解。但这仅仅是信号强大功能的一个缩影,在网络编程、多线程同步等更高级的应用场景中,信号还有更多精彩的应用等待你去发掘!你有兴趣了解更多吗?欢迎在评论区留言交流探讨。


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

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

相关文章

【雷丰阳-谷粒商城 】【分布式基础篇-全栈开发篇】【06】【商品服务】接口文档地址_三级分类_SPU_SKU

持续学习&持续更新中… 学习态度&#xff1a;守破离 【雷丰阳-谷粒商城 】【分布式基础篇-全栈开发篇】【06】【商品服务】接口文档地址_三级分类_SPU_SKU 接口文档地址三级分类效果图建表后台组建数据的树形结构在人人(后台管理系统)中实现管理商品的三级分类路径规则使用…

开关电源基本原理1

目录 内容概述 关于电感 认识电感 电感充电 电感储能 电感充电 电感参数 电感放电 利用电感升压 电感电流波形 伏秒法则 电流纹波率 电感电流三种导电模式 电流纹波率与频率的关系 电流纹波率与电感值的关系 电感值与电感体积 电路纹波率r的最优值 电感值与电…

出行预测:端午打车需求将上涨31%,滴滴发放超2亿司机补贴

作为上半年的“收官”小长假&#xff0c;端午假期接棒“五一”的出行热度&#xff0c;中短途周边游持续升温&#xff0c;海滨旅行、龙舟民俗体验成为新的出行看点。 滴滴出行预测&#xff0c;端午节当天&#xff08;6月10日&#xff09;打车需求将同比去年上涨约31%。今年端午…

[数据集][图像分类]十二生肖分类数据集8492张12类别

数据集类型&#xff1a;图像分类用&#xff0c;不可用于目标检测无标注文件 数据集格式&#xff1a;仅仅包含jpg图片&#xff0c;每个类别文件夹下面存放着对应图片 图片数量(jpg文件个数)&#xff1a;8492 分类类别数&#xff1a;12 类别名称:["dog","dragon&q…

【RISC-V】站在巨人的肩膀上——看开源芯片、软件生态、与先进计算/人工智能/安全的结合

目录 会议议程专题二&#xff1a;RISC-V与先进计算基于RISC-V的后量子密码芯片设计&#xff0c;刘冬生&#xff0c;华中科技大学存算一体集成芯片&#xff0c;刘琦&#xff0c;复旦大学面向端侧大模型计算的RISC-V矩阵扩展架构&#xff0c;复旦大学&#xff0c;韩 军 专题五&am…

电子电气架构 —— 刷写模式:并行刷写

电子电气架构 —— 刷写模式:并行刷写 我是穿拖鞋的汉子,魔都中坚持长期主义的工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 人们会在生活中不断攻击你。他们的主要武器是向你灌输对自己的怀疑:你的价值、你的能力、你的潜力。他们往往会将此…

云原生架构案例分析_5.某体育用品公司云原生架构的业务中台构建

1.背景和挑战 某体育用品公司作为中国领先的体育用品企业之一&#xff0c;在2016年&#xff0c;某体育用品公司启动集团第三次战略升级&#xff0c;打造以消费者体验为核心的“3”&#xff08;“互联网”、“体育”和“产品”&#xff09;的战略目标&#xff0c;积极拥抱云计算…

基于GFlowNets的蚁群抽样组合优化

本文将基于GFACS论文&#xff0c;探讨其核心思想、技术细节以及在实际应用中的优势。 GFlowNet&#xff1a;摊销MCMC成本的有效工具 GFACS的核心是GFlowNet&#xff0c;它通过训练学习状态转移的概率分布&#xff0c;从而替代传统的MCMC采样方法。GFlowNet的优势在于&#xff1…

Java——循环语句

下面我们介绍循环控制结构。如果您想要同样的操作执行多次&#xff0c;就需要使用循环结构。Java中有三种主要的循环结构&#xff1a; for 循环while 循环do…while 循环 一、for 循环 for循环是一种控制结构&#xff0c;用于反复执行一段代码&#xff0c;直到满足指定的条件…

每天五分钟深度学习PyTorch:Tensor张量的索引和切片

本文重点 有时候当我们拥有一个Tensor张量的时候,我们可能需要获取它某一维度的信息,那么此时我们就需要索引和切片的技术,它们可以帮助我们解决这些问题。 切片操作 a是四维的,然后默认是从第一维开始取,逗号表示取不同的维度 a[:2]表示第一维取0,1,后面三维取所有 …

JAVA小案例-分别计算100以内奇数和偶数的和

JAVA小案例-分别计算100以内奇数和偶数的和 没啥可说的&#xff0c;就是for循环加if分支&#xff0c;也可以用while写。 代码如下&#xff1a; public class Jiouhe {/*** 分别计算100以内奇数和偶数的和* param args*/public static void main(String[] args){int sum10;in…

C语言数据结构快速排序的非递归、归并排序、归并排序的非递归等的介绍

文章目录 前言一、快速排序非递归二、归并排序五、归并排序非递归总结 前言 C语言数据结构快速排序的非递归、归并排序、归并排序的非递归等的介绍 一、快速排序非递归 快速排序非递归的定义 快速排序非递归&#xff0c;需要使用栈来实现。将左右下标分别push到栈中。在栈为…

【ubuntu软件版本管理】利用update-alternatives管理ubuntu软件

​ 我们有的时候希望在安装了新软件之后保留旧版本的软件&#xff0c;比如希望保留旧版本的gcc&#xff0c;以防以前写的C编译出问题&#xff0c;这时候就需要版本管理软件update-alternatives。 ​ 在此之前我们需要先弄清楚&#xff0c;什么是ubuntu的软件&#xff1f;拿C源…

微服务开发与实战Day02 - Docker

一、Docker快速入门 快速构建、运行、管理应用的工具 安装部署教程&#xff1a;Docs 1. 部署MySQL 测试连接&#xff1a; 镜像和容器 当我们利用Docker安装应用时&#xff0c;Docker会自动搜索并下载应用镜像&#xff08;image&#xff09;。镜像不仅包含应用本身&#xff…

Go微服务: 基于rocketmq:5.2.0搭建RocketMQ环境,以及示例参考

概述 参考最新官方文档&#xff1a;https://rocketmq.apache.org/zh/docs/quickStart/03quickstartWithDockercompose以及&#xff1a;https://rocketmq.apache.org/zh/docs/deploymentOperations/04Dashboard综合以上两个文档来搭建环境 搭建RocketMQ环境 1 ) 基于 docker-c…

RTOS笔记--任务状态与调度

任务状态 freertos中的任务分为四个状态&#xff1a;就绪状态&#xff08;ready&#xff09;、运行状态&#xff08;running&#xff09;、阻塞状态&#xff08;blocked&#xff09;、暂停状态&#xff08;suspended&#xff09; 完整的任务状态转换图&#xff1a; 在使用vTas…

04--Tomcat

前言&#xff1a;本章整理tomcat的知识点&#xff0c;tomcat知识点相较nginx比较少&#xff0c;但是也是运维必会的软件&#xff0c;这里结合实际项目整理一下。 1、tomcat简介 Tomcat 服务器是一个免费的开放源代码的Web 应用服务器&#xff0c;属于轻量级应用服务器&#x…

在线建站流程分析

建站流程是指通过互联网创建一个个人或企业网站的过程。随着互联网的发展&#xff0c;越来越多的人和机构开始意识到网络的重要性&#xff0c;建站成为一种常见的行为。在线建站的流程一般包括以下几个步骤。 首先&#xff0c;选择一个合适的建站平台。目前&#xff0c;有很多在…

英伟达Docker 安装与GPu镜像拉取

获取nvidia_docker压缩包nvidia_docker.tgz将压缩包上传至服务器指定目录解压nvidia_docker.tgz压缩包 tar -zxvf 压缩包执行rpm安装命令&#xff1a; #查看指定rpm包安装情况 rpm -qa | grep libstdc #查看指定rpm包下的依赖包的版本情况 strings /lib64/libstdc |grep GLI…

这才是大模型价格战背后的真相

想必大家今天肯定被各家大模型厂商的降价新闻刷圈了&#xff0c;如果说 Meta Llama 3 的开源是国外大模型市场的搅局者&#xff0c;那 DeepSeek-V2 就是国内大模型市场的鲶鱼&#xff0c;但是价格战背后是大模型基础设施优化带来的物美价廉&#xff0c;还是浑水摸鱼的噱头&…