【Linux系统编程】信号的保存与处理

目录

一,信号的保存

1-1,core与Term终止信号

1-2,进程退出与信号的关系

1-3,信号在内核中的表示

1-4,信号操作函数

二,信号的处理

2-1,信号被处理的时期

2-2,内核实现信号的捕捉

2-3,sigaction函数

2-4,信号中的volatile

2-5,可重入函数


一,信号的保存

1-1,core与Term终止信号

        Linux系统中,大部分信号的功能是有关终止进程的,而进程终止有两种方案,一种叫做Core,一种叫做Term。使用man 7 signal信号说明手册中下面各个信号功能Action对应有说明(百分之六十以上都是终止信号)。

         Core与Term虽然功能都是终止进程,但是两者却存在本质区别。信号在终止进程时,Core与Term是两种不同的行为,具体说明与区别如下:

 Term

  • 定义:表示直接终止进程,系统内部不做任何处理。
  • 行为:当进程接收到Term信号时,系统会立即停止该进程的执行,但不会生成额外的文件或数据来记录进程的状态。
  • 结果:进程被简单地终止,没有留下太多关于为何终止或终止时状态的信息。

Core

  • 定义:表示终止进程,但是系统会将进程在内存中的核心数据(与调试有关)转储到磁盘中形成core文件(CentOS下形成的文件名后缀有进程的pid,即core.pid;unubtu系统下形成的文件名没有任何后缀,即core)。
  • 行为:当进程接收到导致Core行为的信号(如SIGSEGV、SIGABRT等)时,系统会终止该进程,并将进程在内存中的核心数据(主要是与调试有关的数据,如变量值、函数调用栈等)转储到磁盘上,形成Core Dump核心转储文件。
  • 结果:除了进程被终止外,还生成了一个包含进程崩溃时内存状态信息的文件。这个文件可以帮助定位程序崩溃的原因、在代码中退出的具体位置,以及分析可能存在的内存泄漏等问题。

        总的来说, Core与Term的主要区别在于,Core在终止进程的同时还将内核数据中的异常信息存储到一个文件中(核心转储文件),而Term则仅仅是简单地终止进程,不提供任何额外的信息。

        注意:平常终止信号使用core终止时我们可能没有发现核心转储文件,最可能的原因是系统资源的默认设置中(也可能是core文件生成的路径不是在当前路径下),core文件最大大小是0,默认关闭core文件,意味着就算是core终止也不能形成核心转储文件。系统下可使用 ulimit -a 指令详细查看系统资源限制,这里面就有对应core文件的限制。想要打开核心转储功能,需要使用 ulimit -c [最大core文件大小](-c选项在core资源限制的查看中有提到) 重新设置core文件最大空间大小。

        补:系统默认关闭核心转储功能是为了防止未知的core dump一直在运行,导致服务器磁盘被打满。云服务器默认将进程core关闭退出,进行了特定的设定。还有就是core文件作用在于协助我们进行调试,正常情况下都不会用到。比如使用gdb调试代码的除0错误时,将core文件加载进去后,我们查看到进程退出时收到的信号和出错的代码具体在第几行,也就是说core是用于事后调试的(代码崩溃后使用core文件找到原因所在)。

1-2,进程退出与信号的关系

        我们先来观察进程退出状态的位掩码后十六位的结构图:

       首先要说明的是信号编号中没有0编号是因为0表示进程正常退出,即进程退出状态信息(status指向的位掩码)的终止信号字段是0,表示没有收到异常信号。

        下面再来说明下信号处理进程时的core dump标志。进程退出状态的core dump标志位占一个比特位(要么是0,要么是1),若信号是core处理,标志位是1表示此进程发生了核心转储功能(生成了core文件);标志位是0表示此进程不发生核心转储功能(没有生成core文件),即便是core终止。这点我们可使用代码来观察。

int main()

{

    pid_t id = fork();

    //Child

    if(id == 0)

    {

        sleep(2);

        int a = 10;

        a /= 0; // 故意异常,收到SIGFPE-> core

        exit(0);

    }

    //father

    int status = 0;

    pid_t rid = waitpid(id, &status, 0);

    if(rid > 0)

    {

        //进程的退出状态,即退出码。信号终止时无意义

        cout << "exit code: " << ((status>>8) & 0xFF) << endl; 

        //退出信号

        cout << "exit signal: " << (status & 0x7F) << endl;  

        //core dump标志位(这里设置核心转储功能演示)

        cout << "core dump: " << ((status>>7) & 0x1) << endl; 

    }

    return 0;

}

1-3,信号在内核中的表示

        首先这里要先认识几种概念。

        1,进程开始执行信号的行为动作叫做信号递达。信号递达有三种方式:默认、忽略、自定义,即处理信号的三种方式。

        2,进程从收到信号后到开始执行信号时的状态叫做信号未决(注意:进程收到信号后有可能没立即执行,信号被进程保存起来了,这种情况是典型的信号未决)。

        3,进程可以选择阻塞(或屏蔽)某个信号。信号在进程中是以位图形式呈现的,其中,比特位的位置表示信号编号,比特位的内容表示是否收到了指定信号。比如若收到了信号4,那么第4个比特的值就是1,即00...1000,此时也就是信号未决,当把信号交给上层处理完后也就从1变为了0。信号的阻塞表示即便进程收到了该信号也不处理它。信号的阻塞在进程内部也是以位图结构呈现的,其中,比特位的位置表示信号编号,比特位的内容表示是否屏蔽该信号(1表示屏蔽,0表示正常运行)。比如若进程收到了4号信号,这时如果该位图中的第4个比特位置的值是1,那么即便该进程收到了4号信号也无法正常递达,除非解除屏蔽。总的来说信号能不能正常递达是由进程PCB结构中的两个位图结构决定的,被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。关于阻塞,这里有两个注意点:

        1,阻塞和忽略是不同的两个概念。忽略是一种信号递达的方式,是信号在处理过程中的一种动作。阻塞仅仅是不让指定信号进程递达,是处理信号的一种状态。当进程收到信号时,进程根据阻塞位图结构决定是否阻塞(状态),若没有阻塞,当信号被上层处理时,信号可能会被忽略。

        2,不是所有的信号都能被阻塞屏蔽。系统中,9号信号和19号信号是无法被屏蔽,而18号信号会做特殊处理,可以解除屏蔽信号(这里先不考虑18号信号)。下面的代码会说明。

        信号在内核中的表示示意图如下:

        其中,阻塞位图也叫block位图或信号屏蔽字。信号位图也叫pending位图或未决位图。这里出现的函数方法表handler其实是一张信号方法表,表示处理动作。它本质上是一个函数指针数组(如sighandler_t handler[32]),其数组的下标通常与信号的编号相对应。这意味着,对于每一个可能的信号,handler表中都有一个对应的元素(即函数指针)。这个元素指向的函数是对应信号编号的处理函数,即指向当信号N发生时应该被调用的处理函数。若使用signal方法,这里会将自定义方法的地址填入指定编号的函数方法表中,从而执行自定义方法。

        信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志(比特位从1变成0)。以上图为例,SIGHUP信号未阻塞也未产生过,当它被递达时若不做特殊处理,系统会执行默认处理动作。SIGINT信号虽然产生,但它正在被阻塞,所以暂时不能递达,虽然它的处理动作是忽略,但在没有解除阻塞之前是不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。

        这里存在个问题,如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?在Liunx中,常规信号在递达之前产生多次只计一次(因为每个信号只有一个bit的信号标志,也叫未决标志,非0即1,它不能记录该信号产生了多少次,只能表示信号是否产生,阻塞标志也是如此),而实时信号在递达之前产生多次可以依次放在一个队列里。这里先不讨论实时信号,下面所说的信号全都是普通信号。

1-4,信号操作函数

信号集类型

        block与pending两个位图都是系统内部结构,用户不能直接访问,因此,操作系统提供了专门的操作函数和用户级的数据类型sigset_t,它是一种位图类型,本质是一个结构体,称为信号集。这个类型对于每种信号用一个bit表示“有效”或“无效”状态。在阻塞信号集中,“有效”和“无效”的含义是该信号是否被阻塞;而在未决信号集中,“有效”和“无效”的含义是该信号是否处于未决状态。其中,阻塞信号集也叫做当前进程的信号屏蔽字。

        注意:由于sigset_t本质是一个结构体,所以该类型不支持系统操作函数直接打印,比如printf或cout直接打印。        

信号集操作函数               

        有关信号集的操作函数常用的有以下几种:

头文件:

        #include <signal.h>

函数格式与功能:

        //清空set位图集合,即全部清0。该函数一般用于初始化

        int sigemptyset(sigset_t *set);

        //将set位图集合全部置1  

        int sigfillset(sigset_t *set);  

        //将指定signo信号编号添加到set信号集合中

        int sigaddset (sigset_t *set, int signo); 

        //将指定signo信号编号从set信号集合中去掉

        int sigdelset(sigset_t *set, int signo);  

        //判定指定signo信号编号是否在set集合中

        int sigismember(const sigset_t *set, int signo);  

        上面几个函数中,除了sigismember外,其余的都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1。

信号操作函数

        1,sigprocmask调用。sigprocmask函数可以读取或更改进程的信号屏蔽字(阻塞信号集),即修改进程的block位图。

头文件:

        #include <signal.h>

格式:

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

参数说明:

  • how:决定如何修改当前信号屏蔽字。其可选值包括:

    • SIG_BLOCK:将set中的信号添加到当前的信号屏蔽字中,这样原block位图中就会阻塞set中的信号。
    • SIG_UNBLOCK:将set中的信号从当前的信号屏蔽字中删除,这样原block位图中就不会阻塞set中的信号。
    • SIG_SETMASK:将当前的信号屏蔽字设置为set指向的值。
  • set:指向一个信号集(sigset_t类型),这个信号集用来改变当前的信号屏蔽字。如果只想读取当前的信号屏蔽字而不改变它,可以将此参数设置为NULL。

  • oldset:如果不为NULL,则在修改信号屏蔽字之前,旧的信号屏蔽字将被保存在里面。

返回值:

        成功返回0;出错返回-1并设置错误码。

        2,sigpending调用。sigpending函数用于读取当前进程的未决信号集,通过set参数传出。这些未决信号是指已经发送给进程但尚未被处理的信号,它们可能因为被进程的信号屏蔽字所阻塞而无法立即传递给进程。

头文件:

        #include <signal.h>

格式: 
        int sigpending(sigset_t *set);

参数说明:

  • set:指向一个sigset_t类型的信号集合的指针,用于存储当前进程的未决信号集合。在函数调用成功后,该信号集合将被填充为当前进程的未决信号集。

返回值:

        成功执行时,sigpending函数返回0;失败时,返回-1,并设置错误码。

        总的来说,signal操作的是函数方法表,sigprocmask操作的是block位图表,sigpending操作的是未决信号位图表。下面用代码来模拟一个场景综合运用这三种系统操作。首先会先屏蔽2号信号,然后获取此时该进程的pending位图(打印出该位图是一张全0位图),接下来会给目标进程发送2号信号,由于该进程已经屏蔽了2号信号,所以2号信号不会被递达,将会一直在pending位图中。

        程序总代码:信号的综合代码模拟运用


二,信号的处理

2-1,信号被处理的时期

        进程会在从内核态切换回用户态的时候处理发送过来的信号。这里重点说明下用户态和内核态以及两种状态下的关联。

        用户态是操作系统中用户进程的运行状,是用户层自己编写的代码运行而成的,比如:自定义for循环、自定义函数等等。

        内核态是操作系统内核的运行状态,比如:read等系统调用在内核代码层中所运行而成的进程。内核态下的代码负责处理系统调用、中断、异常等底层任务,确保系统的稳定性和安全性。

        用户态与内核态之间的切换是操作系统中的常见操作。从用户级操作上来讲,由于用户不能直接访问内核数据结构,所以,系统专门提供了系统调用将接口暴漏出来,用户态进程通过系统调用请求操作系统服务时,会发生从用户态到内核态的切换。不仅如此,平常写程序时,若发生代码错误(即异常情况)或代码中断情况,这时CUP会会触发从用户态到内核态的切换以处理异常或中断情况,即执行异常或中断内核程序。执行完毕后,再恢复之前的用户态上下文,继续执行用户态程序。

2-2,内核实现信号的捕捉

        捕捉信号的处理流程如下图(默认处理模式和忽略处理模式比较简单,下面会说明):

        当进程因中断、异常或系统调用从用户态进入内核态时,内核会检查当前进程中是否有待处理的信号。如果有待处理的信号,且该信号的处理方式为用户自定义的函数,内核会准备切换到该信号处理函数的执行环境,即切换到用户态中,然后再通过特殊的系统调用sigreturn返回到内核态处理该进程,最后从内核态返回到用户态。也就是说,在信号的捕捉过程中,一共有四次的状态切换(从内核态到用户态或从用户态到内核态)。具体说明情况如下:

        如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下:用户程序注册了SIGQUIT信号的处理函数sighandler。当前正在执行main函数,这时发生中断或异常切换到内核态。在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系。是两个独立的控制流程。sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。

        如果信号的处理动作是忽略,那么当从用户态进入内核态时,系统直接将未决位图中对应的1该为0;如果信号的处理动作是默认处理,那么当从用户态进入内核态时,系统不会再跳转到用户态,而是直接再内核态完成后返回。这两种情况的处理流程都比较简单的,都是按照系统主控制流程。

        这里存在一个问题,为什么我们在信号捕捉的时候,执行我们自定义的方法时,还要从内核态切换到用户态?难道就不能直接内核态处理自定义方法吗?要明白,用户态与内核态是两种不同权限级别的操作,只有操作系统有权利访问内核数据,用户要向访问内核数据就必须使用系统上所提供的系统调用,说白了就是系统将指定接口暴漏出来,用户按照系统的规定进行内部调用。若执行自定义方法时内核态直接执行处理,就相当于用户直接按照自己的方式随意访问内核数据,这是极其危险的。

2-3,sigaction函数

        sigaction函数可以读取和修改与指定信号相关联的处理动作,通常用于对指定的信号进行自定义捕捉,与signal类似。

头文件: 

        #include <signal.h>

格式:

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

sigaction结构体如下:

        struct sigaction {

               /*普通信号的处理方法,如同signal中的handler捕捉信号处理,将sa_handler赋                   值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系                 统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册                 了一个信号处理函数,该函数返回值为void*/
               void     (*sa_handler)(int);

               /*一般是实时信号的处理方法,本文不做说明*/
               void     (*sa_sigaction)(int, siginfo_t *, void *);

               /*这是一个信号集,指定在信号处理函数执行期间应当被阻塞的信号集合。这可以                 防止在信号处理函数执行期间这些信号被递达,从而可能导致不确定的行为*/
               sigset_t   sa_mask;

               /*标志字段,用于修改sigaction调用行为,使用时一般这里我们直接设为0即可*/
               int        sa_flags;

               /*一个已废弃的字段,这里不用理会*/
               void     (*sa_restorer)(void);
        };

参数说明:

  • signum:指定信号的编号(一个整型数或宏定义,表示特定的信号)。
  • act:指向struct sigaction结构的指针,该结构定义了新的信号处理方式,修改了信号的默认处理动作。如果此参数为NULL,则不改变信号的处理方式,只是用来查询当前的信号处理方式(如果oldact非NULL)。
  • oldact:指向struct sigaction结构的指针,用于输出该信号之前的处理方式。如果此参数为NULL,则不保存旧的处理方式。

返回值:

        成功时返回0;出错时返回-1,并设置相应的错误码。

        当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字;当信号处理函数返回时,又将自动恢复原来的信号屏蔽字,比如2号信号开始被进程的处理函数处理时,这时对应的信号屏蔽字会从0变为1,当2号信号处理完毕返回后,对应的信号屏蔽字又会从1变为0。这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。

        如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则需要用sa_mask字段说明这些额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。综合代码运用如下:

#include <iostream>

#include <unistd.h>

#include <signal.h>

#include <sys/types.h>

#include <sys/wait.h>

using namespace std;

//输出未决位图

void Print(sigset_t &pending)

{

    std::cout << "curr process pending: ";

    for(int sig = 31; sig >= 1; sig--)

    {

        if(sigismember(&pending, sig))

        {

            cout << "1";

        }

        else

        {

            cout << "0";

        }

    }

    cout << endl;

}

void handler(int signo)

{

    cout << "signal : " << signo << endl;

    //不断获取当前进程的pending信号集合并打印

    sigset_t pending;

    sigemptyset(&pending);

    while(true)

    {

        sigpending(&pending);

        Print(pending);

        sleep(1);

    }

}

int main()

{

    struct sigaction act, oact;

    //如同signal(信号编号, handler);

    act.sa_handler = handler;

    act.sa_flags = 0;

    sigemptyset(&act.sa_mask);

    //这里屏蔽信号集合3,4,5

    sigaddset(&act.sa_mask, 3);

    sigaddset(&act.sa_mask, 4);

    sigaddset(&act.sa_mask, 5);

    sigaction(2, &act, &oact);

    //防止进程退出

    while (true) sleep(1);

    return 0;

}

2-4,信号中的volatile

        信号中还存在volatile关键字的用法。我们先来观看以下代码现象。

#include <stdio.h>

#include <unistd.h>

#include <signal.h>

//volatile int g_flag = 0;

int g_flag = 0;

void changeflag(int signo)

{

    printf("将g_flag,从%d->%d\n", g_flag, 1);

    g_flag = 1;

}

int main()

{

    signal(2, changeflag);

    //编译器默认会对代码进行自动优化,下面循环可观看到

    while(!g_flag);

    printf("process quit normal\n");

    return 0;

}

        编译器这里会进行优化,将flag直接存入了CUP中的寄存器里面。在这种情况下,当信号被捕捉并修改 flag=1 时,由于这里只是在内存中修改其值,而寄存器中还保留着原本数值没有被修改,所以当CUP检测 while 循环中的flag时,并不是内存中最新的flag,这就存在了数据二异性的问题,volatile关键字专门用于解决这块问题。

        volatile关键字用于修饰一个变量,可以禁止编译器把变量优化到寄存器中。也就是说当CUP每次访问使用关键字volatile修饰后的变量时,都需要从内存中读取,不能直接从CUP寄存器中读取。这样做的目的都是为了保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量的任何操作,都必须在真实的在内存中进行操作。

2-5,可重入函数

        可重入函数指的是一个函数可以在其执行过程中被中断,随后可以从中断点重新进入并安全地执行,而不会导致数据损坏或不一致的状态。这种函数的设计允许它在多线程或多任务环境中被多个线程或任务同时调用,而不会相互干扰。

可重入函数的关键特性:

  1. 不使用静态或全局变量。静态或全局变量在函数多次调用之间保持其值,这可能导致数据竞争或不一致。

  2. 不使用malloc或free。malloc也是用全局链表来管理堆的。

  3. 不调用任何不可重入的函数。如果一个函数调用了另一个不可重入的函数,那么它本身也将变得不可重入。

  4. 不使用标准I/O库函数。标准I/O库函数,如printfscanf,通常它们使用静态缓冲区。

示例:

int counter = 0; //全局变量  

void increment() {  
    counter++; //修改全局变量  
}

        上面的函数使用了静态变量,若多个控制流调用了此函数,那么counter的值可能会因为多个控制流同时修改它而变得不可预测,因此它是不可重入函数。

void increment(int *counter) {  
    (*counter)++; //修改传入的指针指向的值  
}

        在这个修改后的版本中,increment函数接受一个指向整数的指针作为参数,并修改该指针指向的值。这样,每个控制流都可以传递自己的计数器变量的地址,而不会相互干扰,因此它是可重入函数。

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

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

相关文章

zip-password-finder

1.zip-password-finder 对于传统ZIP文件密码的破解&#xff0c;采用密码匹配的方式进行实现&#xff0c;该github库的地址是&#xff1a; GitHub - agourlay/zip-password-finder: Find the password of protected ZIP files.Find the password of protected ZIP files. Cont…

整数二分算法和浮点数二分算法

整数二分算法和浮点数二分算法 二分 现实中运用到二分的就是猜数字的游戏 假如有A同学说B同学所说数的大小&#xff0c;B同学要在1~100中间猜中数字65&#xff0c;当B同学每次说的数都是范围的一半时这就算是一个二分查找的过程 二分查找的前提是这个数字序列要有单调性 基…

java--JDBC-连接池----JDBC小总结

一.连接池 1.连接池概述 目的&#xff1a;为了解决建立数据库连接耗费资源和时间很多的问题&#xff0c;提高性能。 Connection对象在JDBC使用的时候就会去创建一个对象,使用结束以后就会将这个对象给销毁了(close).每次创建和销毁对象都是耗时操作.需要使用连接池对其进行优…

类加载器详细介绍

类加载器我们要聊一个神秘而又重要的角色——Java类加载器。这家伙&#xff0c;就像是个超级英雄&#xff0c;总是在关键时刻挺身而出&#xff0c;为我们的Java程序提供强大的支持。我会尽量用简单易懂的方式来介绍它。 一 、类加载器介绍 1、类加载器是什么&#xff1f; 想象…

【python设计模式2】创建型模式1

目录 简单工厂模式 工厂方法模式 简单工厂模式 简单工厂模式不是23中设计模式中的&#xff0c;但是必须要知道。简单工厂模式不直接向客户端暴露对象创建的细节&#xff0c;而是通过一个工厂类来负责创建产品类的实例。简单工程模式的角色有&#xff1a;工厂角色、抽象产品角…

Redis(redis基础,SpringCache,SpringDataRedis)

文章目录 前言一、Redis基础1. Redis简介2. Redis下载与安装3. Redis服务启动与停止3 Redis数据类型4. Redis常用命令5. 扩展数据类型 二、在Java中操作Redis1. Spring Data Redis的使用1.1. 介绍1.2. 环境搭建1.3. 编写配置类&#xff0c;创建RedisTemplate对象1.4. 通过Redis…

Linux入门学习:git

文章目录 1. 创建仓库2. 仓库克隆3. 上传文件4. 相关问题4.1 git进程阻塞4.2 git log4.3 上传的三个步骤在做什么 本文介绍如何在Linux操作系统下简单使用git&#xff0c;对自己的代码进行云端保存。 1. 创建仓库 &#x1f539;这里演示gitee的仓库创建。 2. 仓库克隆 &…

Zookeeper 3.8.4 安装和参数解析

安装 zookeeper 之前必须先安装 JDK&#xff0c;有关Linux环境JDK可以参考我以前写的博文 1、关于Linux服务器配置java环境遇到的问题 2、Linux环境安装openJDK 3、Centos7.3云服务器上安装Nginx、MySQL、JDK、Tomcat环境 文章目录 1. zookeeper 安装2. 参数解析 1. zookeeper …

VScode快速配置c++(菜鸟版)

1.vscode是什么 Visual Stdio Code简称VS Code&#xff0c;是一款跨平台的、免费且开源的现代轻量级代码编辑器&#xff0c;支持几乎 主流开发语言的语法高亮、智能代码补全、自定义快捷键、括号匹配和颜色区分、代码片段提示、代码对比等特性&#xff0c;也拥有对git的开箱即…

[乱码]确保命令行窗口与主流集成开发环境(IDE)统一采用UTF-8编码,以规避乱码问题

文章目录 一、前言二、命令行窗口修改编码为UTF-8三、Visual Studio 2022修改编码为UTF-8四、Eclipse修改编码为UTF-8五、DevCPP修改编码为UTF-8六、Sublime Text修改编码为UTF-8七、PyCharm、IDEA、VS Code及Python自带解释器修改编码为UTF-8 一、前言 在学习的征途中&#x…

如何通过 4 种方法恢复 Mac 上删除/未保存的 Excel 文件

您花了数小时在 MacBook 上处理 Excel 工作簿&#xff0c;但现在它不见了。或者&#xff0c;当您退出 Excel 文件时&#xff0c;您无意中选择了“不保存”。这是否意味着您的所有努力都白费了&#xff1f;本文系统地解释了如何在 Mac 上恢复丢失的 Excel 文件。使用我们的 4 种…

如何在Android上实现RTSP服务器

技术背景 在Android上实现RTSP服务器确实是一个不太常见的需求&#xff0c;因为Android平台主要是为客户端应用设计的。在一些内网场景下&#xff0c;我们更希望把安卓终端或开发板&#xff0c;作为一个IPC&#xff08;网络摄像机&#xff09;一样&#xff0c;对外提供个拉流的…

Linux学习记录十四----------线程的创建和回收

文章目录 五、Linux线程1.守护进程1.1.守护进程的特点1.2.进程组1.3会话1.4创建守护进程模型 2.线程的概念3.线程的创建及相关函数3.1.创建线程‐‐pthread_create3.2.单个线程退出 --pthread_exit3.3.阻塞等待线程退出&#xff0c;获取线程退出状态--pthread_join3.4.线程分离…

无限制使用OpenAI最新o1-mini、o1-preview模型:经济高效的AI推理模型

OpenAI 最新推出的 o1 模型是该公司推理模型家族的首位成员&#xff0c;它通过创新的“思维链”训练模式&#xff0c;显著提升了逻辑推理和问题解决的能力。o1 模型在编程竞赛问题、数学奥林匹克资格赛以及物理、生物和化学问题的基准测试中表现出色&#xff0c;甚至在某些领域…

学成在线练习(HTML+CSS)

准备工作 项目目录 内部包含当前网站的所有素材&#xff0c;包含 HTML、CSS、图片、JavaScript等等 1.由于元素具有一些默认样式&#xff0c;可能是我们写网页过程中根本不需要的&#xff0c;所有我们可以在写代码之前就将其清除 base.css /* 基础公共样式&#xff1a;清除…

Tongweb7启动的时候显示要输入java参数(by lqw)

问题描述&#xff1a; 启动tongweb7的时候&#xff0c;提示要输入java参数&#xff0c;如下图所示&#xff1a; 原因&#xff1a; tongweb安装目录bin目录下的external.vmoptions文件改动&#xff0c;在# 符号后加多了一个空格。 external.vmoptions记录的是启动参数&#xf…

【读书】原则

后面的 太长了&#xff0c;而且太多了 我看作者 49年的 0多岁的老人的谆谆教诲 太多了 一下子吃不消 分为 生活原则 和 工作原则 倡导 人要以 原则而活 要做到极度透明 极度求真和极度透明&#xff1a;在软件开发中&#xff0c;对事实的执着追求和对信息的透明度是至关重要的。…

论文阅读 - SELF-REFINE: Iterative Refinement with Self-Feedback

https://arxiv.org/pdf/2303.17651 目录 Abstract Introduction 2 Iterative Refinement with SELF-REFINE Evaluation 3.1 Instantiating SELF-REFINE 3.2 Metrics 3.3 Results Abstract 与人类一样&#xff0c;大型语言模型&#xff08;LLMs&#xff09;并非总能在首次…

【刷题日记】螺旋矩阵

54. 螺旋矩阵 这个是一道模拟题&#xff0c;但我记得我大一第一次做这道题的时候真的就是纯按步骤模拟&#xff0c;没有对代码就行优化&#xff0c;导致代码写的很臃肿。 有这么几个地方可以改进。 看题目可以知道最终的结果一定是rows*cols个结点,所以只需要遍历rows*cols次…

java十进制码、六进制码和字符码的转换

一、字符转换为ASCII码&#xff1a; int i(int)1; 二、ASCII码转换为字符&#xff1a; char ch (char)40; 三、十六进制码转换为字符&#xff1a; char charValue (char)\u0040; package week3;public class check_point4_8 {public static void main(String[] args) {S…