Linux信号详解

文章目录

  • 一、Linux信号
    • 1. 信号的概念
    • 2. 信号的定义
    • 3. 系统定义的信号
  • 二、信号产生的方式
    • 1.通过键盘产生
    • 2. 通过系统调用
    • 3. 软件条件
    • 4. 硬件异常
  • 三、信号处理函数
    • 1. OS发送信号的实质
    • 2. 指令发送信号
    • 3. signal()
    • 4. sigaction()
  • 四、信号屏蔽机制
    • 1. 信号处理方式
    • 2.信号集操作函数
  • 五、临界资源和临界区

  Linux 信号是操作系统中的重要组成部分,可以用于进程间通信、处理异常等多种场景。本文将深入介绍 Linux 信号的相关知识,包括信号的定义、类型、发送和接收、处理等内容,帮助读者更好地理解和使用 Linux 信号

一、Linux信号

1. 信号的概念

生活角度的信号

  • 你在网上买了很多件商品,再等待不同商品快递的到来。但即便快递没有到来,你也知道快递来临时,你该怎么处理快递。也就是你能“识别快递”
  • 当快递员到了你楼下,你也收到快递到来的通知,但是你正在打游戏,需5min之后才能去取快递。那么在在这5min之内,你并没有下去去取快递,但是你是知道有快递到来了。也就是取快递的行为并不是一定要立即执行,可以理解成“在合适的时候去取”。
  • 在收到通知,再到你拿到快递期间,是有一个时间窗口的,在这段时间,你并没有拿到快递,但是你知道有一个快递已经来了。本质上是你“记住了有一个快递要去取”
  • 当你时间合适,顺利拿到快递之后,就要开始处理快递了。而处理快递一般方式有三种
      1. 执行默认动作(幸福的打开快递,使用商品)
      1. 执行自定义动作(快递是零食,你要送给你你的女朋友)
      1. 忽略快递(快递拿上来之后,扔掉床头,继续开一把游戏)
  • 快递到来的整个过程,对你来讲是异步的,你不能准确断定快递员什么时候给你打电话

技术应用角度的信号

  • 用户输入命令,在Shell下启动一个前台进程。 用户按下Ctrl-C ,这个键盘输入产生一个硬件中断,被OS获取,解释成信号,发送给目标前台进程
  • 前台进程因为收到信号,进而引起进程退出


2. 信号的定义

信号是 Linux 操作系统中用于进程间通信、处理异常等情况的一种机制。它是由操作系统向一个进程或者线程发送的一种异步通知,用于通知该进程或线程某种事件已经发生,需要做出相应的处理。

信号的作用:

  • 进程间通信:进程可以通过向其他进程发送信号的方式进行通信,例如某个进程在完成了某项工作之后,可以向另一个进程发送 SIGUSR1 信号,通知其进行下一步的操作。

  • 处理异常:信号可以被用来处理程序中的异常情况,例如当一个进程尝试访问未分配的内存或者除以 0 时,系统会向该进程发送 SIGSEGV 或 SIGFPE 信号,用于处理这些异常情况。

  • 系统调试:信号可以用于程序的调试,例如在程序运行时,可以向该进程发送 SIGUSR2 信号,用于打印程序的状态信息等。

3. 系统定义的信号

Linux 中,信号分为标准信号实时信号,每个信号都有一个唯一的编号。

  • 标准信号:最基本的信号类型,由整数编号表示,编号范围是 1 到 31。
  • 实时信号:Linux 中的扩展信号类型,由整数编号表示,编号范围是 32 到 64。

可以用kill -l命令可以察看系统定义的信号列表

kill -l

查询结果如下:
在这里插入图片描述

常见信号编号以及对应信号的名称:
在这里插入图片描述

注意不同的操作系统可能对信号的编号有所不同,因此在跨平台开发时应当注意信号编号的兼容性。

  • term 表示终止进程
  • core 表示生成核心转储文件,核心转储文件可用于调试
  • ignore 表示忽略信号
  • cont 表示继续运行进程
  • stop 表示停止进程(注意停止不等于终止,而是暂停)

相关信号的解读:

2) SIGINT
程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。3) SIGQUIT
和SIGINT类似, 但由QUIT字符(通常是Ctrl-/)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。4) SIGILL
执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。5) SIGTRAP
由断点指令或其它trap指令产生. 由debugger使用。6) SIGABRT
调用abort函数生成的信号。7) SIGBUS
非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)8) SIGFPE
在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误。9) SIGKILL
用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。10) SIGUSR1
留给用户使用11) SIGSEGV
试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.12) SIGUSR2
留给用户使用13) SIGPIPE
管道破裂。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止。14) SIGALRM
时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号.15) SIGTERM
程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出,shell命令kill缺省产生这个信号。如果进程终止不了,我们才会尝试SIGKILL。17) SIGCHLD
子进程结束时, 父进程会收到这个信号。
如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程。这种情 况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程 来接管)18) SIGCONT
让一个停止(stopped)的进程继续执行. 本信号不能被阻塞. 可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作. 例如, 重新显示提示符19) SIGSTOP
停止(stopped)进程的执行. 注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略.20) SIGTSTP
停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号21) SIGTTIN
当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号. 缺省时这些进程会停止执行.22) SIGTTOU
类似于SIGTTIN, 但在写终端(或修改终端模式)时收到.23) SIGURG
有"紧急"数据或out-of-band数据到达socket时产生.24) SIGXCPU
超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变。25) SIGXFSZ
当进程企图扩大文件以至于超过文件大小资源限制。26) SIGVTALRM
虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.27) SIGPROF
类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间.28) SIGWINCH
窗口大小改变时发出.29) SIGIO
文件描述符准备就绪, 可以开始进行输入/输出操作.30) SIGPWR
Power failure31) SIGSYS
非法的系统调用。
  • 在以上列出的信号中,程序不可捕获、阻塞或忽略的信号有:SIGKILL,SIGSTOP
  • 不能恢复至默认动作的信号有:SIGILL,SIGTRAP
  • 默认会导致进程流产的信号有:SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ
  • 默认会导致进程退出的信号有:
    SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM
  • 默认会导致进程停止的信号有:SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU
  • 默认进程忽略的信号有:SIGCHLD,SIGPWR,SIGURG,SIGWINCH
  • 此外,SIGIO在SVR4是退出,在4.3BSD中是忽略;SIGCONT在进程挂起时是继续,否则是忽略,不能被阻塞。

二、信号产生的方式

1.通过键盘产生

Ctrl + C 

通过键盘敲入 Ctrl + C ,可以向进程发送 2 号信号使其终止。

2. 通过系统调用

① kill

#include<signal.h>
#include<sys/types.h>int kill(int pid,int signal);  //通过函数向指定进程发送信号

② raise

raise接口可以向当前调用进程发送任意信号

可以给当前进程发送指定的信号,即自己给自己发信号

#include <stdio.h>
#include <signal.h>raise(11);   //向当前进程发送 11 号信号

③ abort

使当前进程接收到信号而异常终止

#include <stdio.h>
#include <stdlib.h>abort();

3. 软件条件

alarm

调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号,该信号的默认处理动作是终止当前进程

这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数

打个比方,某人要小睡一觉,设定闹钟为30分钟之后响,20分钟后被人吵醒了,还想多睡一会儿,于是重新设定闹钟为15分钟之后响,“以前设定的闹钟时间还余下的时间”就是10分钟。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数

#include <stdio.h>
#include <unistd.h>int main()
{int ret = alarm(20);while (1) {printf("I am a process, ret = %d\n", ret);sleep(5);int res = alarm(0); //取消闹钟printf("res = %d\n", res);}return 0;
}

4. 硬件异常

#include <stdio.h>
#include <signal.h>void handler(int sig){printf("catch a sig : %d\n", sig);}int main(){signal(SIGSEGV, handler);sleep(1);int *p = NULL;*p = 100;while(1);return 0;}

这里出现野指针异常。系统本该直接清除掉该进程,但因为修改了野指针异常所对应的操作函数,导致系统只打印了一句话,而不正常的对该进程进行清除。但系统不断地检测到有野指针异常。但操作系统做的工作仅仅是打印一句话。那么这里就造成了循环打印catch a sig : xxx

在这里插入图片描述

三、信号处理函数

1. OS发送信号的实质

信号产生的方式种类虽然非常多,但是无论产生信号的方式千差万别,但是最终一定都是通过OS向目标进程发送信号。产生信号的方式,其实都是OS发送信号数据给task_struct

struct task_struct 中有进程的各种属性,那么其中也一定有对应的数据变量,来保存是否收到了对应的信号,而信号的编号也是有规律的1~31

进程中采用 uint32_t sigs ;——位图结构来标识该进程是否收到信号

0000 0000 0000 0000 0000 0000 0000 0000

比特位的位置(第几个)代表的就是哪一个信号,比特位的内容(0或1),代表的就是是否收到了信号

故本质是OS向指定进程的task_struct中的信号位图写入比特1,即完成信号的发送,也可以说是信号的写入

struct task_struct
{//信号位图0000 0000 10...uint32_t sigmap;
}

2. 指令发送信号

  1. kill 命令

    kill 命令是 Linux 中最常用的发送信号的命令,语法如下:

     kill [-signal] PID
    

    其中,-signal 可选参数表示要发送的信号类型,如果省略该参数,则默认发送 SIGTERM 信号。PID 表示接收信号的进程 ID。

    例如,要向进程 ID 101080 发送 SIGINT 信号,可以执行以下命令:

    kill -SIGINT 101080
    kill -2 101080
    

    当然可以发送对应的信号编号

  2. kill 函数

    除了使用 kill 命令,程序中也可以通过 kill 函数来发送信号。kill 函数的原型如下:

    int kill(pid_t pid, int sig);
    

    其中,pid 表示接收信号的进程 ID,sig 表示要发送的信号类型。如果函数调用成功,则返回 0,否则返回 -1 并设置 errno。

    例如,要向进程 ID 123 发送 SIGINT 信号,可以执行以下代码:

    #include <signal.h>
    #include <unistd.h>int main() 
    {pid_t pid = 123;int sig = SIGINT;if (kill(pid, sig) == -1) {perror("kill");return 1;}return 0;
    }
    

3. signal()

signal 函数可以用来自定义信号捕捉函数

#include <signal.h>
typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);//示例
void handler(int signum)
{cout<<"get a signum: "<<signum<<endl;
}int main()
{signal(14,handler);  //此时进程收到14号信号的默认处理动作就是执行handler函数
}

signal 函数的两个宏:

signal(2,SIG_DFL);   //default 使对应的2号信号恢复默认动作
signal(2,SIG_IGN);   //ignore 忽略二号信号
  1. 参数:

    • signum:此参数指定需要进行设置的信号,可使用信号名(宏)或信号的数字编号,建议使用信号名。
    • handler:sighandler_t 类型的函数指针,指向信号对应的信号处理函数,当进程接收到信号后会自动执行该处理函数;参数 handler 既可以设置为用户自定义的函数,也就是捕获信号时需要执行的处理函数,也可以设 置为 SIG_IGN 或 SIG_DFL,SIG_IGN 表示此进程需要忽略该信号,SIG_DFL 则表示设置为系统默认操作。
    • sighandler_t 函数指针的 int 类型参数指的是,当前触发该函数的信号,可将多个信号绑定到同一个信号处理函数 上,此时就可通过此参数来判断当前触发的是哪个信号。
  2. 返回值:此函数的返回值也是一个 sig_t 类型的函数指针

    • 成功:返回值则是指向在此之前的信号处理函数;
    • 出错:则返回 SIG_ERR,并会设置 errno。

​ 由此可知,signal()函数可以根据第二个参数 handler 的不同设置情况,可对信号进行不同的处理。

4. sigaction()

在Linux中,sigaction函数是用于设置和检索信号处理器的函数。

sigaction函数有以下语法:

#include <signal.h>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:指定信号处理函数的地址。如果设置为SIG_IGN,则表示忽略该信号。如果设置为SIG_DFL,则表示使用默认处理器,也可以自己设置需处理的函数逻辑。

  • sa_sigaction:指定一个信号处理器函数,这个函数包含三个参数:一个整数表示信号编号,一个指向siginfo_t结构体的指针,和一个指向void类型的指针。

  • sa_mask:指定了在执行信号处理函数期间要阻塞哪些信号。

  • sa_flags:是一个标志位,可以包括以下值:

    • SA_NOCLDSTOP:如果设置了该标志,则当子进程停止或恢复时不会生成SIGCHLD信号。
    • SA_RESTART:如果设置了该标志,则系统调用在接收到信号后将被自动重启。
    • SA_SIGINFO:如果设置了该标志,则使用sa_sigaction字段中指定的信号处理器。
  • sa_restorer:是一个指向恢复函数的指针,用于恢复某些机器状态。

返回值:如果成功,则返回0,否则返回-1,并设置errno错误号。可以使用以下代码来检查errno

注意:一般情况下,sa_handlersa_mask 使用较多,这里再次详细说明:

  1. sa_handler:

    • 赋值为常数SIG_IGN表示忽略信号

    • 赋值为常数SIG_DFL表示执行系统默认动作

    • 赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函数

      该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信 号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。

  2. sa_mask:

    • 当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来 的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。
    • 如果 在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号, 则用sa_mask字段说明这些需 要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。

    注意:信号在被处理前,pendding位图就修改了对应的比特位

四、信号屏蔽机制

1. 信号处理方式

  1. 信号的处理方式有三种,称为信号的递达

    • 信号的忽略( SIG_IGN ),即不采取任何动作
    • 信号的默认,使用系统默认的处理方法
    • 信号的自定义捕捉,采用用户定义的方法处理
  2. 信号从产生到递达之间的状态,称为信号的未决( Pending )

    • 信号在位图中,未被处理(未决)
  3. 进程选择阻塞( Block )某个信号

    • 未决之后,暂时不递达,直到解除对信号的阻塞,期间对该信号为屏蔽

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

  1. block 信号阻塞表

    在该表中标记为 1 的信号将被阻塞

  2. pending 信号未决表

    信号被进程接收,但还未被处理的信号,就存放在该表中

    • 普通信号:只在pending表记录一次,如果产生多次,也只记录一次,之后系统对该信号的处理动作为 1 次
    • 实施信号:采用队列的形式,记录历史中产生的所有信号,后系统对该信号的处理动作为 1历史中产生的次数

    注意:pendding 表存在多个信号时,会全部处理完,才返回用户端

  3. bandler 信号对应处理方法表

    该表中记录了信号对应操作方法的函数指针

2.信号集操作函数

头文件:

#include <signal.h>  //头文件
  1. sigemptyset

    int sigemptyset(sigset_t *set);
    

    初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号。

  2. sigemptyset

    int sigemptyset(sigset_t *set);
    

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

  3. sigfillset

    int sigfillset(sigset_t *set);
    

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

  4. sigaddset

    int sigaddset (sigset_t *set, int signo); 
    

    添加屏蔽信号

  5. sigdelset

    int sigdelset(sigset_t *set, int signo);
    

    删除屏蔽信号

  6. sigismember

    int sigismember(const sigset_t *set, int signo); //查询是否有该屏蔽信号示例:
    sigset_t  set oset;
    sigismember(pending , int signo)
    

    查询是否有该屏蔽信号

  7. sigprocmask

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

    示例:

    sigset_t  set, oset;
    sigprocmask(SIG_BLOCK, &set, &oset);
    
    1. 参数:
    • how

      SIG_BLOCK    //添加屏蔽字
      SIG_UNBLOCK  //删除
      SIG_SETMASK  //直接用新的替换
      
    • const sigset_t

      当前需要修改的 sigset_t

    • oset

      传出之前的pending码

    使用示例:

    int main()
    {
    sigset_t set, oset;
    // 初始化,全置空
    //  sigfillset 全置1
    sigemptyset(&set);
    sigemptyset(&oset);// 设置屏蔽字
    //  sigdelset 清除
    sigaddset(&set, 2); // 对于set位图,对2号信号设置屏蔽字// 将 set 信号设置进入当前进程sigprocmask(SIG_BLOCK, &set, &oset); // oset为之前的屏蔽字  
    }
    
  8. sigpending

    int sigpending(sigset_t *set);示例:
    sigset_t pending;
    sigemptyset(&pending);
    
    • 功能:读取当前进程的未决信号集,通过set参数传出。调用成功则返回 0 ,出错则返回 -1

    示例:

     //通过此中方式读出void Printsignal (const sigset_t &pending)
    {for(int signo = 31; signo > 0; signo--){if(sigismember(&pending, signo)){std::cout << "1";}else{std::cout << "0";}}std::cout << "\n";}
    

五、临界资源和临界区

  1. 临界资源

    • 概念:被保护起来的公共资源,一次仅允许一个进程使用的共享资源。其他都是非临界资源
  2. 临界区

    • 概念:每个进程中访问临界资源的那段程序(代码)称之为临界区

    • 临界区不是内核对象,而是系统提供的一种数据结构,程序中可以声明一个该类型的变量,之后用它来实现对资源的互斥访问。

      ​ 当欲访问某一临界资源时,先将该临界区加锁(若临界区不空闲则等待),用完该资源后,将临界区释放。

    • 补充(待定):分类:临界区也是代码的称呼,所以一个进程可能有多个临界区,分别用来访问不同的临界资源。

      • 内核程序临界资源:系统时钟

      • 普通临界资源:普通I/O设备,如打印机(进程访问这些资源的时候,很慢,会自动阻塞,等待资源使用完成)

  3. 信号量

    ​ 表示资源数目的计数器。每一个执行流想访问公共资源内的某一份资源,不应该让执行流直接访问,而是先申请信号量。资源其实就是对信号量计数器进行自减操作,本质上只要自减成功。就完成了对资源的预定机制。如果申请不成功,执行流将被挂起阻塞。

  4. 进程进入临界区的调度原则

    • 如果有若干进程请求进入空闲的临界区(空闲即0进程访问),一次仅允许 一个进程进入。
    • 任何时候,处于临界区内的进程不可多于一个(0 或 1),若已有进程进入自己的临界区,则其它想进入自己临界区的进程必须等待。
    • 进行临界区的进程要在有限时间内退出,以便其它进程能及时进入自己的临界区。
    • 如果其它进程不能进入自己的临界区,则应让出 CPU,避免进程出现 “忙等” 现象。
    • 访问临界资源时先访问信号量资源(可以理解为信号量引用计数自减1,如果自减成功,说明还能访问,未达到上限,允许访问,访问结束后信号量自增1)
    • 临界资源的访问是原子的
  5. 细节问题

    • 每个进程都得先看到同一个信号量资源,就只能由OS提供IPC体系。

    • 信号量本身也是公共资源。

    • 单个信号量

      struct sem
      {int count;  //引用计数task_struct *wait_queue;  //进程等待队列
      }//如果当前引用计数不为零,那么新的进程将会直接被运行。
      //如果当前引用计数为零,那么新的进程将会加入等待队列。当别的进程结束时,引用计数自增,同时等待队列中的进程将会被执行,同时引用计数自减
      

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

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

相关文章

更改QTabWidget的选项卡的位置

选项卡位置函数&#xff1a; QTabWidget::setTabPosition(QTabWidget::North); //默认为上面 上北下南 参数&#xff1a; QTabWidget::North //上面 QTabWidget::South); //下面 QTabWidget::West //左侧 QTabWidget::East)//右侧 选项卡外观函数&#xff1a; QTabWidget::setT…

nodejs+vue+ElementUi废品废弃资源回收系统

系统主要是以后台管理员管理为主。管理员需要先登录系统然后才可以使用本系统&#xff0c;管理员可以对系统用户管理、用户信息管理、回收站点管理、站点分类管理、站点分类管理、留言板管理、系统管理进行添加、查询、修改、删除&#xff0c;以保障废弃资源回收系统系统的正常…

Qt_纯虚函数的信号和槽

简介 在C中&#xff0c;纯虚函数是一个在基类中声明但没有实现的虚函数。纯虚函数的声明以 “ 0” 结尾。纯虚函数的目的是为了提供一个接口&#xff0c;但是不提供实现。派生类必须实现纯虚函数&#xff0c;否则它也会成为一个抽象类。纯虚函数可以在基类中定义&#xff0c;也…

python中的类与对象(1)

目录 一. 引子&#xff1a;模板 二. 面向过程与面向对象 &#xff08;1&#xff09;面向过程编程 &#xff08;2&#xff09;面向对象编程 三. 对象与类 &#xff08;1&#xff09;对象 &#xff08;2&#xff09;类 四. 面向对象程序设计的特点&#xff1a;封装&#…

【C语言】linux内核ipoib模块 - ipoib_ib_handle_rx_wc

一、中文注释 // 定义一个处理InfiniBand接收完成工作请求的函数 static void ipoib_ib_handle_rx_wc(struct net_device *dev, struct ib_wc *wc) {// 通过网络设备获取私有数据结构struct ipoib_dev_priv *priv ipoib_priv(dev);// 获取工作请求ID&#xff0c;并屏蔽掉接收…

探索未来:Web3如何改变我们的生活方式

在数字化的时代&#xff0c;技术的不断发展和创新已经成为了我们生活的常态。而在这个不断变革的过程中&#xff0c;区块链技术作为一种颠覆性的技术&#xff0c;正逐渐成为人们关注的焦点。作为区块链技术的下一代&#xff0c;Web3正日益崭露头角&#xff0c;成为了未来的发展…

橘子学es原理01之准备工作

es本身是具备很好的使用特性的&#xff0c;我指的是他的部署方面的&#xff0c;至于后期的使用和运维那还是很一眼难尽的。 我们从这一篇开始就着重于es的一些原理性的的一些探讨&#xff0c;当然我们也会有一些操作性的&#xff0c;业务性的会分为多个栏目来写。比如前面我写的…

Flutter开发进阶之Package

Flutter开发进阶之Package 通常我们在Flutter开发中需要将部分功能与整体项目隔离&#xff0c;一般有两种方案Plugin和Package&#xff0c;Application是作为主体项目&#xff0c;Module是作为原生项目接入Flutter模块。 当独立模块不需要与原生项目通讯只需要Plugin就可以&a…

【广度优先搜索】【网格】【割点】1263. 推箱子

作者推荐 视频算法专题 涉及知识点 广度优先搜索 网格 割点 并集查找 LeetCode:1263. 推箱子 「推箱子」是一款风靡全球的益智小游戏&#xff0c;玩家需要将箱子推到仓库中的目标位置。 游戏地图用大小为 m x n 的网格 grid 表示&#xff0c;其中每个元素可以是墙、地板或…

利用LaTex批量将eps转pdf、png转eps、eps转png、eps转svg、pdf转eps

1、eps转pdf 直接使用epstopdf命令&#xff08;texlive、mitex自带&#xff09;。 在cmd中进入到eps矢量图片的目录&#xff0c;使用下面的命令&#xff1a; for %f in (*.eps) do epstopdf "%f" 下面是plt保存eps代码&#xff1a; import matplotlib.pyplot as…

计算机网络面经-TCP的拥塞控制

写在前边 前边我们分享了网络分层协议、TCP 三次握手、TCP 四次分手。今天我们继续深入分享一下 TCP 中的拥塞控制。 对于 TCP 的拥塞控制,里边设计到很多细节,平平无奇的羊希望通过这一节能够将这部分内容串通起来,能够让你更深刻的记忆这部分内容。 思维导图 1、什么…

封装(encapsulation)

封装[encapsulation] 封装介绍封装好处封装的实现步骤&#xff08;三步&#xff09;入门案例封装与构造器 封装介绍 封装就是把抽象的数据[属性]和对数据的操作[方法]封装在一起&#xff0c;数据被保护在内部&#xff0c;程序的其它部分只有通过被授权的操作[方法]&#xff0c;…

vue项目的前端工程化思路webpack(持续更新中)

写在前面&#xff1a;现在的前端网页功能丰富&#xff0c;特别是SPA&#xff08;single page web application 单页应用&#xff09;技术流行后&#xff0c;JavaScript的复杂度增加和需要一大堆依赖包&#xff0c;还需要解决Scss&#xff0c;Less……新增样式的扩展写法的编译工…

DC与DCT DCG的区别

先进工艺不再wire load model进行静态时序分析&#xff0c;否则综合结果与后端物理电路差距很大&#xff0c;因此DC综合工具也进行了多次迭代&#xff0c;DC工具有两种模式&#xff0c;包括wire load mode和Topographical Mode&#xff0c;也就是对应的DC Expert和DC Ultra。 …

unity hub (第一部)初学配置

1、安装Unity Hub 2、设置中文 3、安装编辑器 4、新建项目 5、新建完成后进入编辑器 6、 编辑器设置中文 editPreferencesLanguages选择中文

机器学习基础(五)监督与非监督学习的结合

导语&#xff1a;上一节我们详细探索非监督学习的进阶应用&#xff0c;详情可见&#xff1a; 机器学习基础&#xff08;四&#xff09;非监督学习的进阶探索-CSDN博客文章浏览阅读613次&#xff0c;点赞15次&#xff0c;收藏13次。非监督学习像一位探险家&#xff0c;挖掘未标…

电路设计(25)——4位数字频率计的multisim仿真及PCB设计

1.设计要求 使用4位数码管&#xff0c;显示输入信号的频率。完成功能仿真后&#xff0c;用AD软件&#xff0c;画出原理图以及PCB。 2.电路设计 输入信号的参数为&#xff1a; 可见&#xff0c;输入为168HZ&#xff0c;测量值为170HZ&#xff0c;误差在可接受的范围内。 3.PCB设…

Bluesky数据采集框架-2

访问保存的数据 到此&#xff0c;自然想到了"我如何访问我保存的数据&#xff1f;"。从bluesky的视角&#xff0c;那真的不是bluesky的关注&#xff0c;但它是一个合理的问题&#xff0c;因此我们将强调一个特定的场景。 注意&#xff1a;本章假设你正在使用databr…

AI:134-基于深度学习的社交媒体图像内容分析

🚀点击这里跳转到本专栏,可查阅专栏顶置最新的指南宝典~ 🎉🎊🎉 你的技术旅程将在这里启航! 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带有在本地跑过的关键代码,详细讲解供…

C语言——实用调试技巧——第2篇——(第23篇)

坚持就是胜利 文章目录 一、实例二、如何写出好&#xff08;易于调试&#xff09;的代码1、优秀的代码2、示范&#xff08;1&#xff09;模拟 strcpy 函数方法一&#xff1a;方法二&#xff1a;方法三&#xff1a;有弊端方法四&#xff1a;对方法三进行优化assert 的使用 方法五…