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

【Linux】深入理解进程信号机制:信号的产生、捕获与阻塞

52bc67966cad45eda96494d9b411954d.png

🎬 个人主页:谁在夜里看海.

📖 个人专栏:《C++系列》《Linux系列》《算法系列》

⛰️ 时间不语,却回答了所有问题


目录

📚前言

📚一、信号的本质

📖1.异步通信

📖2.信号列表

📖3.信号发送的本质

📚二、信号的产生

📖1.终端按键

🔖core dump

📖2.系统调用

🔖kill()

🔖raise()

🔖abort()

🔖alarm()

📚三、信号的捕获

📖1.signal信号处理

🔖函数原型

🔖示例

📖2.sigaction信号处理

🔖函数原型

🔖示例 

📚四、信号的阻塞与未决

📖1.阻塞的本质

🔖未决信号

🔖阻塞信号

⚠️阻塞≠忽略

📖2.阻塞的应用

🔖函数原型

🔖示例

📚五、可重入函数

📖1.定义

🔖特性

🔖示例

📖2.volatile关键字

🔖-O1优化 


📚前言

上一章我们谈论了进程间的通信机制,由于不同进程之间有时需要相互协作,所以引入了进程间通信的概念,使得不同的进程可以共享同一份资源,完成数据的传输。进程间通信时是用户层面的通信,为什么这么说呢,因为在这个过程中,用户空间的进程通过特定的通信机制来传递数据或信息,而这些操作并不涉及操作系统的内核执行逻辑,通信的内容也是由用户自定义的。

然而系统层面也需要通信,比如一个进程执行异常时,操作系统必须对异常进程进行处理,那么有哪些处理方式呢?

✅①:直接“杀死”该进程,将进程从CPU运行队列中移除,但是存在隐患,比如文件还没关闭,资源还未释放,有内存泄漏的风险;

✅②:告知进程退出,让进程主动退出,这样可以保证资源被释放。

系统告知进程退出,不就是系统与进程间的通信嘛,这一类通信不需要用户层的参与,在操作系统底层完成,并且通信的内容也不由用户定义,而是由系统定义,这种通信方式就叫做信号

📚一、信号的本质

📖1.异步通信

在用户层的通信中,比如管道通信,我们可以明确通信何时发生,数据何时进行交换吗?答案是可以,管道通信是借助一个管道文件完成的,进程需要对管道文件进行打开和关闭操作,而这些操作在执行流中是确定的,也就是说,用户规定了进程在何时发送数据,何时接收数据,我们称这种通信为同步通信

然而在系统层的通信中,我们可以明确通信何时发生吗,就比如程序执行异常时,需要发送信号告知程序终止,我们能确定程序何时异常吗,并不能确定(如果能确定的话,我们就完全可以避免异常的发生了,就不会引发异常了),我们称这种通信为异步通信

📖2.信号列表

❓为什么操作系统需要与进程进行通信呢?

✅通常,进程和操作系统之间的通信发生在一些特殊情况下,而不是无缘无故的通信。这些特殊情况往往是有限的,比如异常、外部中断、资源不足等。针对这些情况,操作系统有相应的处理方法,例如遇到异常时,操作系统会终止进程;当外部中断发生时,操作系统会中断进程的执行并进行相应处理。因此,操作系统只需将所有可能出现的特殊情况及其对应的处理办法保存在一个集合中,当进程遇到某种情况时,操作系统就发送相应的信号进行处理,这个集合被称为信号列表。

通过kill -l命令可以查看系统定义的信号列表:

我们可以看到,每个信号都有一个编号以及宏定义名称,这些宏定义可以signal.h中找到。其中编号32号以下的信号是标准信号,它们用于处理进程中的常见情况,如进程终止、异常、退出等;32号以上的信号是是实时信号,通常由用户定义并用于更精细的进程控制。

📖3.信号发送的本质

在操作系统中,每一个信号都有一个对应的编号,这些编号的范围是1~64,所以在信号传输的过程中,我们并不需要传输具体的信号宏名称,只需要一个位图即可实现,比如传输SIGINT信号时,只需要将位图中第二位置为1,即可完成信号的传输。因此,每一个进程都会维护一个用于表示信号集的位图,每一位对应一个信号的状态。

理解了信号的本质,我们来谈谈信号的操作,即信号是如何产生的,进程对信号又是如何处理的

📚二、信号的产生

📖1.终端按键

其实,我们早就接触过信号了,平常我们写代码时,偶尔会遇到程序陷入死循环或卡住的情况,这个时候我们按住ctrl+c就可以退出进程,结束死循环,其实,按下ctrl+c的过程就是向进程发送一个SIGINT信号,用于通知进程中断当前操作,这就是第一种信号产生的方式:终端按键产生。

不止ctrl+c可以产生信号,其他按键也可以产生相应的信号,例如ctrl+\,用于发送SIGQUIT信号,该信号可以强制终止进程,这与SIGINT类似,但不同的是,SIGQUIT可以生成核心转储文件(core dump)以便于调试:

🔖core dump

Core dump(核心转储)是操作系统在进程崩溃时将该进程的内存内容保存到一个文件中的机制,能够为开发者提供有关崩溃时程序状态的详细信息。

⚠️触发core dump时,系统会向磁盘写入一个核心转储文件,用于保存程序崩溃的完整快照,但是并不是所有程序崩溃的场景都需要一个快照,因为这样会导致磁盘空间被大量占用,当磁盘空间被大量占用时,可能会引发比程序崩溃更严重的问题,所以默认情况下,禁用core dump文件

然而可以通过ulimit命令接触限制:

① 使用 ulimit -c 命令查看当前系统的核心转储限制。如果输出为 0,则表示禁用了核心转储。

② 通过 ulimit -c unlimited 命令可以取消核心转储大小限制,允许生成核心转储文件。

📖2.系统调用

🔖kill()

通常情况下,我们是通过系统调用 kill 来实现信号的发送的,例如:

kill -SIGINT (进程pid)
# 或者
kill -2 (进程pid)

这种方式等价与按键ctrl+c,都是向指定进程发送SIGINT信号。

除了在终端输入kill指令,我们还可以在函数内部调用kill()函数:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>int main() {pid_t pid = getpid();  // 获取当前进程IDprintf("Sending SIGINT signal to process %d\n", pid);// 向当前进程发送 SIGINT 信号if (kill(pid, SIGINT) == -1) {perror("Error sending signal");}// 除了向当前进行发送,还可以向其他进程发送 SIGINT 信号,只要知道它的pidreturn 0;
}

❓为什么是kill?

kill是“杀死”的意思,为什么发送信号的指令要取名为“杀死”呢?

✅这大概是因为,在信号被设计之初,其作用主要就是“杀死”一个进程,所以当时直接将信号发送取名为kill,到了后面,信号的种类越来越多,但是kill这个指令名称被保留了下来,沿用至今。但是不要因为指令叫做kill就认为它只能“杀死”进程,实际上它用于发送多种不同类型的信号。

除了kill()之外,其他一些与信号相关的系统调用和库函数也能触发信号的发送或控制信号的行为。 

🔖raise()

raise()函数用于向当前进程发送信号(自己给自己发信号):

#include <signal.h>
int raise(int sig);

sig表示要发送的信号。

🔖abort()

abort()函数也可以向当前进程发送信号,但与raise不同的是,它只能发送SIGABRT信号,因此函数内部不包含任何参数:

#include <stdio.h>
#include <stdlib.h>int main() {printf("Before abort\n");abort();  // 终止进程并生成核心转储printf("This line will never be executed.\n");return 0;
}
🔖alarm()

alarm()函数的作用是在指定时间之后发送一个SIGALRM信号:

#include <unistd.h>unsigned int alarm(unsigned int seconds);

参数

        seconds:设置定时器的时间,单位为秒,当 alarm() 被调用时,系统会在 seconds 秒后向当前进程发送 SIGALRM 信号。

返回值

        如果成功设置定时器,它返回剩余的时间。如果定时器已经存在,返回值是该定时器剩余的时间。

示例

#include <iostream>
using namespace std;
#include <unistd.h>
#include <signal.h>int main()
{alarm(5);while(1){cout<<"I am a process..."<<endl;sleep(1);}return 0;}

📚三、信号的捕获

进程对于信号的处理有以下三种方式:

忽略信号:进程可以选择忽略某个信号,即在接收该信号时,不执行任何操作;

处理信号:进程可以指定一个信号处理函数来处理指定信号;

默认处理:如果没有指定默认的信号处理函数,操作系统会采用该信号的默认行为。

📖1.signal信号处理

为了捕捉并处理信号,我们通常调用signal()函数,通过该函数,我们可以自定义信号处理行为。

🔖函数原型
#include <signal.h>typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);

signum是我们要捕获的信号编号;

对于handler参数,我们有两种选项:

采用关键字:SIG_IGN,表示当前信号被忽略;SIG_DFL,表示采用默认处理方式;

传入自定义信号处理函数,函数内部带有一个int型参数,表示信号编号

🔖示例
#include <iostream>
using namespace std;
#include <unistd.h>
#include <signal.h>void sigcb(int sig)
{if(sig == 2)cout<<"catch a sig: "<<sig<<endl;
}int main()
{signal(2,sigcb); // 调用自定义的信号处理函数cout<<"This is a process,pid is: "<<getpid()<<endl;while(1);return 0;
}

此时通过ctrl+c发生SIGINT信号时程序执行自定义行为:

此时我们将signal的第二个参数修改成SIG_IGN,表示忽略当前信号:

#include <iostream>
using namespace std;
#include <unistd.h>
#include <signal.h>int main()
{signal(2,SIG_IGN);cout<<"This is a process,pid is: "<<getpid()<<endl;while(1);return 0;
}

此时输入ctrl+c,SIGINT信号被忽略,不执行任何行为:

📖2.sigaction信号处理

sigaction函数不仅可以指定信号处理程序,还可以设置额外的选项来控制信号处理的细节。

🔖函数原型
#include <signal.h>int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

参数:

① signum: 指定要操作的信号编号;

② act:指向一个struct sigaction 类型的结构体,用于定义信号处理行为,可设置为NULL;

③ oldact:保存之前的信号处理行为(结构体),可设置为NULL。

struct sigaction:

该结构体用于定义信号处理行为:

struct sigaction {void     (*sa_handler)(int);     // 信号处理函数,或以下宏之一:// SIG_DFL:使用默认行为// SIG_IGN:忽略信号void     (*sa_sigaction)(int, siginfo_t *, void *); // 备用信号处理函数sigset_t sa_mask;                // 在处理此信号时需要阻塞的其他信号集合int      sa_flags;               // 信号处理的标志void     (*sa_restorer)(void);   // 已废弃
};

结构体成员:

① sa_handler:指定信号处理函数(简单处理);

② sa_sigaction:用于处理更复杂信号信息(需要设置 SA_SIGINFO 标志);

③ sa_mask:在处理信号时需要阻塞的其他信号;

④ sa_flags:控制信号处理行为的选项。常用值:

        SA_RESTART:自动重启被信号中断的系统调用

        SA_SIGINFO:启用 sa_sigaction,传递更多信号信息

        SA_NOCLDWAIT:阻止僵尸进程的生成(用于 SIGCHLD

        SA_NODEFER:在处理信号时不自动阻塞该信号

🔖示例 

设置对SIGINT信号的处理行为:

#include <iostream>
using namespace std;
#include <unistd.h>
#include <signal.h>void sigcb(int sig)
{cout<<"catch a sig: "<<sig<<endl;
}int main()
{struct sigaction sa;sa.sa_handler = sigcb;sigemptyset(&sa.sa_mask);sa.sa_flags = 0;sigaction(2,&sa,nullptr);while(1);return 0;
}

📚四、信号的阻塞与未决

然而,对于进程来说,并不是所有信号都需要立即响应的,因为在信号到达时,进程需要立即停止当前执行流,去执行信号处理操作。例如,一个进程的部分执行内容是对一个大文件进行拷贝写入,如果在写入的途中中断了,就会导致文件不完整甚至损坏,所以进程希望写入操作执行完毕后再响应信号,此时就需要信号阻塞操作。

📖1.阻塞的本质

我们前面提到,信号发送的本质是将目标进程的信号集(位图)中相应位置的标志位置为1,实际上,进程要维护两个这样信号集,一个为阻塞信号集,一个为未决信号集

🔖未决信号

当系统向进程发送信号时,修改的就是未决信号集中的指定标志位,在未决信号是指已经由系统发送但进程因为某些原因还未处理的信号,一个信号要想被进程处理的第一个条件就是在未决信号集(Pending)中标志位为1;

🔖阻塞信号

阻塞信号(Block)就是进程希望延迟执行的信号(例如在文件写入完毕之后再执行SIGINT信号,此时就需要先将SIGINT信号阻塞,写入完毕后再取消阻塞),未决信号想要被进程执行还需要判断是否阻塞,即在阻塞信号集中的标志位是否为1,为1则阻塞:

所以一个信号想要被执行需要满足:

未决位为1,表示信号已产生;

阻塞位为0,表示信号不被阻塞

③ 其他条件,例如进程空闲,没有在执行其他信号...

⚠️阻塞≠忽略

信号被阻塞与信号被忽略是两个不同的概念:

信号被阻塞表示信号不会被进程接收,如果产生则处于未决状态(Pending);

信号被忽略是进程对信号的一种处理方式,表示进程接收信号时忽略该信号,不做处理;

📖2.阻塞的应用

🔖函数原型

sigprocmask() 是一个用于设置或获取当前进程阻塞信号集的系统调用。它可以阻塞、解除阻塞或替换进程的阻塞信号集。

#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:指向包含要阻塞或解除阻塞的信号集的指针。

③ oldset:存储之前的阻塞信号集,为NULL表示不作存储。

返回值:

成功返回 0;失败返回 -1,并设置errno。

除了上述函数,我们还需要一系列的信号集操作函数:

#include <signal.h>
// sigset_t set; // 调用下面函数前,需要先定义一个函数集变量
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置位,表示该信号集的有效信号包括系统支持的所有信号

③ sigaddset

向set指向的信号集中添加信号signo,即将signo对应的标志位置为1。

④ sigdelset

将set指向的信号集中的signo信号删除,即将signo对应的标志位置为0。

⑤ sigismember

判断set指向的信号集中是否存在信号signo,也可以理解成返回signo对应的标志位。

🔖示例

设置阻塞信号2与40,观察发生信号时的状态:

#include <iostream>
using namespace std;
#include <unistd.h>
#include <signal.h>void printsigset(sigset_t *set)
{for(int i=0;i<64;++i){if(sigismember(set,i))putchar('1');elseputchar('0');}cout<<endl;
}int main()
{cout<<"This is a process,pid is: "<<getpid()<<endl;sigset_t m,p;sigemptyset(&m);sigaddset(&m,2);sigaddset(&m,40);sigprocmask(SIG_BLOCK,&m,NULL);while(1){sigpending(&p);printsigset(&p);sleep(3);}return 0;
}

此时阻塞了信号2与40,并通过打印未决信号集,观察情况:

发现发送信号2和40后,未决信号集的第2位和第40位置为1,表示信号2和40为未决信号。

📚五、可重入函数

信号处理函数执行过程中可能被任意中断,因此信号处理函数需要使用可重入函数,以避免数据竞争、资源冲突和未定义行为。

📖1.定义

可重入函数是指函数在执行过程中可以被中断,并且在中断期间又可以安全地被调用,且两次调用互不干扰。

🔖特性

① 无全局变量的依赖: 函数内部不依赖于全局或静态变量,或者对这些变量的操作是线程安全的(原子性操作)

② 无静态数据的修改: 函数不能修改其内部的静态变量,因为多次调用可能导致状态不一致

③ 无不可重入函数调用: 函数只能调用其他可重入函数

④ 使用局部资源: 函数仅使用局部变量或参数,不会影响其他调用的状态。 

🔖示例

情况一:两个线程并发地执行以下代码,假设a是全局变量,初始为0,那么输出结果会是什么?

   void foo(){a=a+1;printf("%d ",a);}

✅答案:1 1 、1 2 、2 1 、2 2 都有可能

分析:

a=a+1并不是原子性操作,怎么理解呢?

原子性操作只有未执行执行完毕两种情况,而a=a+1的操作流程是:

①将a从全局数据区提取到寄存器中;②在寄存器中完成+1操作;③将a返回到全局数据区

线程之间是共享虚拟地址空间的(除了栈区,线程之间各自拥有一块栈区空间),所以它们在访问全局变量a的时候会产生竞争访问,它们可能同时将a提取到各自的寄存器中,也有可能进程1先提取,修改完毕返回后进程2才提取,此时进程2提取的就是已经经历过一次+1的a:

情况二:那如果此时a是局部变量呢?

✅答案:只会是 1 1。

因为线程之间各自拥有一块栈空间,a是局部变量的话,那么在两个线程之间各有一份,即线程会在各自的栈空间中提取a到寄存器并返回+1后的结果,不存在竞争访问问题,执行结果只会是1 1。

情况一的foo函数是不可重入函数,因为存在竞争访问;情况二的foo函数是可重入函数,因为没有使用全局变量(只存在于栈空间),不存在竞争访问。

📖2.volatile关键字

看下面代码:

#include <iostream>
using namespace std;
#include <unistd.h>
#include <signal.h>int flag = 0;
void handler(int sig)
{if(sig == 2){cout<<"change flag 0 to 1"<<endl;flag = 1;}
}int main()
{signal(2,handler);while(!flag);exit(0);
}

在标准情况下键入CTRL-C ,2号信号被捕捉,执行自定义动作,修改flag=1,while条件不满足,退出循 环,进程退出:

# makefile
test:volatile.cppg++ -o $@ $^ #标准情况.PHONY:clean
clean:rm -f test

但是在优化情况下,键入CTRL-C ,2号信号被捕捉,执行自定义动作,修改flag=1,但是while条件依旧满足,进程继续运行!

test:volatile.cppg++ -o $@ $^ -O1 #优化情况.PHONY:clean
clean:rm -f test

这是为什么呢?

🔖-O1优化 

-O1是编译器的一个优化级别,在当前级别下,编译器会对代码进行性能优化, 原本存储在全局数据区的变量flag被直接存储到了寄存器,signal函数调用中,对flag值的修改是在另一个寄存器中进行的,而while循环中对flag的判断还是在原先的寄存器中提取,导致signal函数中对flag的修改没有作用到while循环中,键入CTRL-C最终不会退出进程。

为了解决编译器优化带来的这一潜在问题,我们可以使用volatile关键字修饰flag的定义,表示flag不被优化,这样就保证了signal中对flag的修改可以同步到while循环中:

#include <iostream>
using namespace std;
#include <unistd.h>
#include <signal.h>volatile int flag = 0; // volatile修饰
void handler(int sig)
{if(sig == 2){cout<<"change flag 0 to 1"<<endl;flag = 1;}
}int main()
{signal(2,handler);while(!flag);exit(0);
}

在优化情况下,键入CTRL-C ,2号信号被捕捉,执行自定义动作,修改flag=1,while条件不满足,退出循 环,进程退出:

test:volatile.cppg++ -o $@ $^ -O1 #优化情况.PHONY:clean
clean:rm -f test


以上就是【深入理解进程信号机制:信号的产生、捕获与阻塞】的全部内容,欢迎指正~ 

码文不易,还请多多关注支持,这是我持续创作的最大动力!  

相关文章:

【Linux】深入理解进程信号机制:信号的产生、捕获与阻塞

&#x1f3ac; 个人主页&#xff1a;谁在夜里看海. &#x1f4d6; 个人专栏&#xff1a;《C系列》《Linux系列》《算法系列》 ⛰️ 时间不语&#xff0c;却回答了所有问题 目录 &#x1f4da;前言 &#x1f4da;一、信号的本质 &#x1f4d6;1.异步通信 &#x1f4d6;2.信…...

前端基础技术全解析:从HTML前端基础标签语言开始,逐步深入CSS样式修饰、JavaScript脚本控制、Ajax异步通信以及WebSocket持久通信

目录 前言&#xff1a; 1.前端技术html简单了解&#xff1a; 1.1HTML代码是由标签构成的。 1.2.HTML 文件基本结构 1.3.HTML 常见标签 标题标签: 段落标签: p 文本格式化标签 图片标签&#xff1a; 超链接标签: a 测试代码&#xff1a; 展示效果&#xff1a; 表单…...

Linux存储管理之核心秘密(The Core Secret of Linux Storage Management)

Linux存储管理之核心秘密 如果你来自Windows环境&#xff0c;那么Linux处理和管理存储设备的方式对你而言可能显得格外不同。我们知道&#xff0c;Linux的文件系统并不采用Windows那样的物理驱动器表示方式&#xff08;如C:、D:或E:&#xff09;&#xff0c;而是构建了一个以&…...

excel精简使用工具

1.获取sheet1的行填充到sheet2的列 希望在 Excel 中使用 INDEX 函数从不同的列中提取数据&#xff0c;并且每一行都引用不同的列。为了实现这个目标&#xff0c;你可以使用 COLUMN 函数来动态获取列的偏移量。 为了避免手动输入每个单元格的公式&#xff0c;你可以使用以下公…...

Flutter鸿蒙化 在鸿蒙应用中添加Flutter页面

前言 今天这节课我们讲一下 在鸿蒙应用中添加Flutter页面。 作用: 之前有很多朋友和网友问我鸿蒙能不能使用Flutter开发,他们的项目已经用Flutter开发成熟了有什么好的方案呢,今天讲到这个就可以很好的解决他们的问题,例如我们正式项目中可能是一部分native 开发 一部分…...

为什么页面无法正确显示?都有哪些HTML和CSS相关问题?

页面无法正确显示可能由多种原因导致&#xff0c;通常与HTML和CSS的结构、语法错误、浏览器兼容性、资源加载等问题有关。以下是一些常见的原因及其解决方法&#xff0c;结合实际项目代码示例进行讲解&#xff1a; 1. HTML 结构错误 HTML 标签的缺失或错误可能导致页面无法正…...

如何制作一份出色的公司介绍PPT?

制作一份公司介绍的PPT需要精心设计&#xff0c;以确保内容既专业又吸引人。以下是一个基本的框架和一些建议&#xff0c;帮助您创建一份有效的公司介绍PPT&#xff1a; PPT标题页 标题&#xff1a;公司全称&#xff08;可使用公司Logo作为背景或嵌入标题中&#xff09;副标题…...

Selenium 进行网页自动化操作的一个示例,绕过一些网站的自动化检测。python编程

这段代码是使用 Selenium 进行网页自动化操作的一个示例&#xff0c;主要目的是在加载网页时执行一些自定义的 JavaScript 代码&#xff0c;并等待页面上某个元素的出现。以下是代码的详细解释&#xff1a; ### 代码解释 #### 导入必要的模块 python from selenium.webdriver…...

HashMap和HashTable的区别

1、HashMap是线程不安全的&#xff0c;HashTable是线程安全的 HashMap&#xff1a;Fail-fast 机制。表示快速失败&#xff0c;在集合遍历过程中&#xff0c;一旦发现容器中的数据被修改了&#xff0c;会立刻抛出ConcurrentModificationException异常&#xff0c;从而导致遍历失…...

使用redis来进行调优有哪些方案?

Redis的调优方案可以从多个方面进行&#xff0c;以下是一些常见的优化方法及代码示例&#xff1a; 1.使用管道&#xff08;Pipelining&#xff09; 管道技术可以减少客户端与Redis之间的交互次数&#xff0c;从而提高性能。在批量操作时&#xff0c;通过管道可以一次性发送多个…...

macOS 中,默认的 Clang 编译器和 Homebrew 安装的 GCC 都不包含 bits/stdc++.h 文件

在 macOS 中&#xff0c;默认的 Clang 编译器和 Homebrew 安装的 GCC 都不包含 bits/stdc.h 文件&#xff0c;因为它是一个 非标准 的头文件&#xff0c;主要由 MinGW 和某些 Linux 平台的 GCC 提供。 解决方案 : 手动创建 bits/stdc.h 1. 创建文件夹和文件 在你的 GCC 标准…...

2012mfc,自绘列表控件

原文 使用常用控件版本4.70中的自定义绘画功能自定义列表控件的外观. 介绍 常见控件的4.70版引入了一项叫自定义绘画的功能. 可按轻量易用的自画版本对待自定义绘画.易用性来自,即只需处理一条消息(NM_CUSTOMDRAW),且你可让窗口为你干活,因此你不必完成物主绘画中的所有粗活…...

vue3运行时执行过程步骤

在 Vue 3 中&#xff0c;运行时的执行过程是一个复杂但高效的机制&#xff0c;主要包括初始化应用、渲染、响应式更新和销毁等阶段。以下是 Vue 3 运行时的执行过程的核心步骤和流程&#xff1a; 1. 应用初始化 1.1 创建 Vue 应用 调用 createApp 方法&#xff0c;创建一个 V…...

常用的AT命令,用于查看不同类型的网络信息

文章目录 1. ATCSQ‌&#xff1a;2. ATCREG‌&#xff1a;‌3. ATCOPS‌&#xff1a;4. ATCGATT‌&#xff1a;5. ATCGPADDR‌&#xff1a; 在AT命令集中&#xff0c;用于查看网络信息的命令有多种&#xff0c;具体取决于所使用的设备和模块。以下是一些常用的AT命令&#xff0…...

Vue3组件通讯——自定义事件(子->父)

需求如下&#xff1a; 1.在子组件中&#xff0c;当用户点击提交按钮后&#xff0c;更新数据库 2.数据更新成功后&#xff0c;子组件通知父组件getUserInfo函数&#xff0c;重新获取数据&#xff0c;同步更新 3.子组件等待getUserInfo函数执行完毕后&#xff0c;调用init函数…...

GLSL 着色器语言

GLSL 着色器语言 1. 着色器语言基础1.1 数据类型1.2 数据类型的基本使用1.3 运算符1.4 各个数据类型的构造函数1.5 类型转换1.6 存储限定符1.7 插值限定符1.8 一致块1.9 layout 限定符1.10 流程控制1.11 函数的声明和使用1.12 片元着色器中浮点及整型变量精度的指定1.13 程序的…...

如何创建一个 Vue.js 工程

创建一个 Vue.js 工程 可以分为以下几个步骤&#xff1a; 安装 Node.js 和 npm&#xff1a;Vue.js 依赖于 Node.js 和 npm&#xff0c;因此首先需要在计算机上安装 Node.js 和 npm。可以从 Node.js 的官方网站&#xff08;https://nodejs.org/&#xff09;下载并安装。 安装 V…...

Mysql 性能优化:覆盖索引

概述 覆盖索引&#xff08;Covering Index&#xff09;是一个 MySQL 查询优化技术&#xff0c;它指的是一个索引包含了查询所需的所有字段的数据&#xff0c;因此不需要回表&#xff08;访问数据表的行&#xff09;就可以完成查询。使用覆盖索引可以显著提高查询性能&#xff…...

vulnhub靶场【DC系列】之7

前言 靶机&#xff1a;DC-7&#xff0c;IP地址为192.168.10.13 攻击&#xff1a;kali&#xff0c;IP地址为192.168.10.2 都采用VMWare&#xff0c;网卡为桥接模式 对于文章中涉及到的靶场以及工具&#xff0c;我放置在网盘中&#xff0c;链接&#xff1a;https://pan.quark…...

iOS - 消息机制

1. 基本数据结构 // 方法结构 struct method_t {SEL name; // 方法名const char *types; // 类型编码IMP imp; // 方法实现 };// 类结构 struct objc_class {Class isa;Class superclass;cache_t cache; // 方法缓存class_data_bits_t bits; // 类的方法…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析

今天聊的内容&#xff0c;我认为是AI开发里面非常重要的内容。它在AI开发里无处不在&#xff0c;当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗"&#xff0c;或者让翻译模型 "将这段合同翻译成商务日语" 时&#xff0c;输入的这句话就是 Prompt。…...

树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频

使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

练习(含atoi的模拟实现,自定义类型等练习)

一、结构体大小的计算及位段 &#xff08;结构体大小计算及位段 详解请看&#xff1a;自定义类型&#xff1a;结构体进阶-CSDN博客&#xff09; 1.在32位系统环境&#xff0c;编译选项为4字节对齐&#xff0c;那么sizeof(A)和sizeof(B)是多少&#xff1f; #pragma pack(4)st…...

uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖

在前面的练习中&#xff0c;每个页面需要使用ref&#xff0c;onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入&#xff0c;需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

STM32+rt-thread判断是否联网

一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

UDP(Echoserver)

网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法&#xff1a;netstat [选项] 功能&#xff1a;查看网络状态 常用选项&#xff1a; n 拒绝显示别名&#…...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级

在互联网的快速发展中&#xff0c;高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司&#xff0c;近期做出了一个重大技术决策&#xff1a;弃用长期使用的 Nginx&#xff0c;转而采用其内部开发…...