【Linux】阻塞信号|信号原理|深入理解捕获信号|内核态|用户态|sigaction|可重入函数|volatile|SIGCHILD|万字详解
目录
编辑
一,常见的信号术语
二,信号在内核中的表示
信号标志位
Pending表
Block表
handler表
POSIX.1标准
三,sigset_t
信号集操作函数
sigemptyset
sigfillset
sigaddset
sigdelset
sigismember
sigprocmask
sigpending
信号集实验
四,深入理解捕捉信号
编辑
CPU
内核态和用户态
捕捉信号
五,sigaction函数
六,可重入函数
七,再次理解C语言关键字volatile
为什么要有volatile关键字?
八,SIGCHLD
一,常见的信号术语
信号递达(Delivery):
- 信号实际被执行处理的过程;(当一个信号被递达给进程时,该信号的处理动作已经开始执行实际执行信号的处理动作);
信号未决(Pending):
- 信号从产生到递达的中间状态;(信号已经生成但尚未被递达的状态。在此期间,信号处于阻塞状态,直到它被递达给目标进程)。
信号阻塞(Block):
- 进程可以选择阻塞某个信号;(即使信号已经生成,也会保持在未决状态,不会被递达给进程。信号只有在解除阻塞后才能被递达)。
信号忽略:
- 信号被递达后,进程选择不做任何响应。不同于信号阻塞,被忽略的信号已经完成了递达,只是进程对其没有任何反应。
捕获信号:
- 进程通过自定义信号处理程序来处理信号,而不是使用系统的默认动作;
二,信号在内核中的表示
在上篇中介绍了信号是由位图保存的,信号如何产生的;
信号是用户,操作系统,进程交互用的;而进程是如何知道操作系统给它发了信号呢;
信号标志位
- 阻塞 (block):如果设置,该信号会阻塞,不会被递达给进程;
- 未决 (pending):如果设置,表示该信号已生成但未处理;
信号通过三种结构来表示和管理,这些结构存储在进程的task_struct中;
- Pending 位图:表示哪些信号已经生成但尚未被递达或处理;(跟踪信号的未决状态)
- Block 位图:记录了哪些信号被当前进程所阻塞。控制信号是否可以被递达;
- Handler 函数指针:指向一个函数指针,用于指定当信号递达时要执行的处理动作;
Pending表
- 在信号位图(Signal Bitmap)中,每个比特位的位置表示某个信号,比特位的内容代表是否收到该信号(信号是否未决);如果有信号发送给该进程,那么这个位图对应的信号位会置为1,没有就是0;
- 假如给进程发送2号信号,那么该位图的第3位会置位1,但不会立即处理该信号,会在合适的时候处理,这个合适的时候是进程从内核态返回到用户态的时候进行处理;
- Pending表的存在让进程知道接受了哪些信号并准备处理信号;而信号是否要被处理,还要查看该信号是否被阻塞,此时需要查看进程中Block表的内容;
Block表
进程用一张位图表来表示被进程阻塞的信号集;每个比特位的位置代表着某个信号编号,比特位的内容代表着该信号是否被阻塞;内容为1表示对该信号进行阻塞,为0表示没有对该信号进行阻塞。第一个比特位位置代表 1号信号,以此类推(位图结构)
有了上面两张位图表,进程就能知道接受了哪些信号,信号是否阻塞(不需要处理),而信号也有需要处理的;这个就要看handler表;
handler表
进程中这张handler表与上面两张位图表有所不同,这是函数指针数组(sighandler_t handler[32]);数组的下标是信号的编号,数组的内容是函数指针(该信号的处理函数);处理函数(处理动作)包括默认,忽略以及自定义;
- 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。
- block、pending和handler这三张表的每一个位置是一一对应的;
- 信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,1号信号SIGHUP 未阻塞也未产生过(pending位图对应信号位置标志位为0,block位图对应信号位置标志位为0),当它递达时执行默认处理动作(SIG_DFL)。
- 对于2号信号SIGINT,该信号产生过(pending位图对应信号位置标志位为1,处于未决),但正在被阻塞(block位图对应信号位置标志位为1),所以暂时不能递达。虽然它的处理动作是忽略(SIG_IGN),但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
- 3号信号SIGQUIT 未产生过(pending位图对应信号位置标志位为0),该信号正在被阻塞(block位图对应信号位置标志位为1),一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。
总结:
一个信号哪怕没有被产生,也不影响该信号被阻塞;
POSIX.1标准
允许系统在信号解除阻塞之前,递送该信号一次或多次给进程。
如果在进程接触对某信号的阻塞之前,该信号产生过多次,该如何处理❓
常规信号(普通信号):
如果一个常规信号在被解除阻塞之前产生了多次,那么仅会递送一次该信号给进程。
实时信号:
实时信号在被解除阻塞之前产生的多次可以被依次放入一个队列中等待递送。
三,sigset_t
由于block和pending 位图是内核内部的数据结构,用户程序无法直接访问和修改它们,OS也不能提供十几个参数的函数给用户使用吧,所以就有了 sigset_t。为了能够修改这些信号集,用户程序需要通过系统调用来进行操作:sigset_t类型的数据结构让用户可以方便地设置和查询信号集的状态;
- 在block,pending位图中,比特位非0即1,只表示信号是否产生;不记录产生了多少次;
- 未决和阻塞标志可以用相同的数据类型 sigset_t 来存储(在用户层);sigset_t 称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态。
在阻塞信号集中 :
“有效”状态表示信号被阻塞,“无效”状态表示信号未被阻塞。
在未决信号集:
“有效”状态表示信号处于未决状态,“无效”状态表示信号没有被生成或已被处理。
信号集操作函数
操作系统提供了一些函数来管理信号集(sigset_t,包括Block和pending表),以下是常见的信号集操作函数(都包含在signal.h头文件中):
sigemptyset
int sigemptyset(sigset_t *set);
- 将set指向的信号集清空
- 成功返回0,否则返回-1.
sigfillset
初始化一个满的信号集,跟sigemptyset函数相反,让信号集中每一个比特位都置为1。
int sigfillset(sigset_t *set);
- 将set指向的信号集填满
- 成功返回0,否则-1.
sigaddset
从信号集中添加一个指定的信号。
int sigaddset(sigset_t *set, int signum);
- 给set指向的信号集中添加一个signum信号
- 成功放回0,否则-1.
sigdelset
从信号集中删除一个指定的信号
int sigdelset(sigset_t* set,int signum);
- 从set指向的信号集中删除一个signum信号
- 成功返回0,失败则返回-1.
sigismember
检查一个指定的信号是否在信号集中
int sigismember(const sigset_t *set,int signum);
- 如果set指向的信号集中有signum信号就返回1,不在返回0,查看失败返回-1。
sigprocmask
检查或者修改信号屏蔽字(阻塞信号集)。用来阻塞或者解阻信号。
跟上面的sigaddset和sigdelset函数不一样的是,sigprocmask可以修改进程的Block表。
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数:
how:指定操作类型。以下是常见的操作类型:
SIG_BLOCK:将set指向的信号集中的信号添加到阻塞信号集中
SIG_UNBLOCK:从阻塞信号集中移除set指向的信号集中的信号
SIG_SETMASK:将set指向的信号集设置为新的阻塞信号集
set:指向一个sigset_t类型的对象,用于修改阻塞信号集。如果该参数为NULL,则表示不修改阻塞信号集。
oldset:指向一个sigset_t类型的对象,用于存储被set修改之前的阻塞信号集。
成功返回0,失败返回-1.
sigpending
获取当前进程的未决信号集(pending表)
int sigpending(sigset_t *set);
- 将set指向的信号集重置为当前进程的未决信号集
- 成功返回0,否则-1.
信号集实验
#include <signal.h>
#include <unistd.h>// 定义一个函数来打印信号集的内容
void PrintSet(sigset_t *set)
{// 循环遍历信号集中的每个信号位for (int i = 31; i >= 1; i--) //只看默认的{// 如果信号集中的信号i被设置,则输出1 if (sigismember(set, i)) {putchar('1');}// 否则输出'0'else{putchar('0');}}// 输出换行符puts("");
}int main()
{sigset_t s, p;// 初始化信号集s为空sigemptyset(&s); // 将 SIGINT (2) 添加到信号集s中sigaddset(&s, SIGINT);// 设置信号屏蔽字,将信号集s中的信号设置为阻塞状态// SIGINT将不会被进程接收sigprocmask(SIG_BLOCK, &s, NULL);// 主循环while (1){// 查询当前进程的未决信号集,并将其存储在p中sigpending(&p);// 打印未决信号集p的内容PrintSet(&p);// 让进程休眠一秒sleep(1);}return 0;
}
按下ctrl+c后程序没有被终止,说明这个信号确实被阻塞了,然后未决表显示该信号一直出于未决状态,没有被递达
四,深入理解捕捉信号
实际上进程地址空间由内核空间和用户空间组成;
用户所写的代码和数据位于用户空间,操作系统(OS)的代码和数据都位于内核空间
用户空间大小:[0, 3]GB;
内核空间大小:[3, 4]GB;
用户空间对应的页表映射到物理地址,内核空间也有自己的页表映射(内核级页表);
但是不同的是,每个进程都有一份用户级页表,对应着进程的独立性,但是内核级页表只有一份,所有进程共享;
内核空间存储的实际上是操作系统代码和数据,通过内核级页表与物理内存之间建立映射关系
前文提过当进程pending改变时(收到未决信号)表示有进程要处理,这个合适的处理时间就是从内核态到用户态;
很多时候我们使用系统调用时,都在访问OS内核数据,而OS不相信任何人,按理说是不能这样做的;
当我们使用系统调用时,系统会自动进行身份切换,user->kernel。OS是如何识别现在的状态是用户态还是内核态的❓先从CPU起。
CPU
首先先简单介绍一下CPU内存在大量的寄存器,寄存器分为两类:
- 可见寄存器
- 不可见寄存器
这些进程都是和当前进程强相关的,寄存器内的数据都是该进程的上下文数据,有的寄存器指向进程的task_struct,另一个寄存器指向进程的用户级页表,所以CPU可以找到当前进程的task_struct和用户级页表(OS可以从寄存器的相关数据和使用的页表来判断是内核态还是用户态,因为进程共享一份内核页表,可以通过权限提升访问),又因为页表的MMU(内存管理单元)集成在CPU里面,所以在CPU这里虚拟地址到物理地址的转换就直接完成了
其中最重要的是:
CPU内有一个寄存器:CR3,这个寄存器用于表示当前进程的运行级别,0表示进程处于内核态级别,3表示处于用户态级别
内核态和用户态
进程如果访问的是用户空间的代码,此时的状态就是用户态,如果访问的是内核空间,此时的状态就是内核态;处于用户态的程序只能访问用户空间的内存地址,而无法访问内核空间的地址。内核态可以访问整个虚拟空间地址,包括用户空间和内核空间。
- 内核态通常用来执行操作系统的代码,是一种权限非常高的状态
- 用户态是一种用来执行普通用户代码的状态,是一种受监管的普通状态
捕捉信号
五,sigaction函数
sigaction和signal都是用来处理信号的函数,但是sigaciton是一种更为强大和灵活的喜好处理机制,提供了更多的选项和更高的可靠性。
头文件:#include <signal.h>int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);参数:第一个参数signum代表指定信号的编号第二个参数act是一个结构体指针变量,输入型参数,若act指针非空,则根据act修改该信号的处理动作第三个参数oldact也是一个结构体指针变量,输出型参数,若oldact指针非空,则通过oldact传出该信号原来的处理动作返回值
调用成功返回0,失败返回-1,错误码被设置
sigaction本身是一个结构体
结构体的第一个成员变量: sa_handler
- 将sa_handler赋值为常数SIG_IGN传给sigaction函数,表示忽略信号。
- 将sa_handler赋值为常数SIG_DFL传给sigaction函数,表示执行系统默认动作。
- 将sa_handler赋值为一个函数指针,表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函数
注:该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用
结构体的第二个成员变量: sa_sigaction
- sa_sigaction是实时信号的处理函数,我们不使用,这里我们只谈普通信号,直接设置为空即可或者不理会
结构体的第四个成员变量: sa_flags
第五个成员变量是 sa_restorer
- sa_flags 直接设置为0即可,sa_restorer也是不使用,设置为空即可
结构体的第三个成员变量:sa_mask
sa_mask 是信号集,
注:如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,可以把需要屏蔽的信号加入到 sa_mask中,当信号处理函数返回时,自动恢复原来的信号屏蔽字所以,在这个结构体里面,只需关心 sa_handler 和 sa_mask 即可,其他无需关心
当我们传递某个信号期间,同类信号无法被递达;因为当该信号被正在递达时,OS将当前信号假如信号屏蔽字中(block),只有当前信号完成动作,OS才会自动解除对该信号的屏蔽;
进程处理信号的原则是串行的处理同类型的信号,不允许递归
这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止
当信号递达了,pending位图对应信号相应的内容就会由1置0,如果这时又检测到了信号,pending位图对应信号相应的内容就会由0置1,当上一个递达动作完成,OS会自动解除对该信号的屏蔽,也就是说该信号被解除屏蔽后,OS会自动递达当前屏蔽的信号所以在上面的测试中发送了多次2号信号,第一次信号处理动作完成后,2号信号还会被捕捉一次并递达。如果没有检测到信号,pending位图对应信号相应的内容依旧是0,是0就不做任何动作
六,可重入函数
当一个带头单链表进行头插,主函数中调用insert函数向链表中插入结点node;
在这时,如果某信号的处理函数也调用了insert函数向链表插入结点node2
首先,当main函数调用inset函数进行向链表插入时,操作分为两步;当刚做完第一步(p->next = head)的时候,因为硬件中断使进程切换到内核,再次回到用户态之前检查到有信号待处理,于是切换到信号处理动作(sighandler)。示意图如下;
进入信号处理动作(sighandler)后,该处理函数也调用了insert函数,向该链表head中插入结点node2,sighandler函数调用的insert函数也完成了插入节点的第一步,(p->next = head) ;
sighandler函数调用的 insert函数插入操作的两步都做完之后从sighandler返回内核态,再次回到用户态
返回到用户态之后,从main函数调用的 insert函数中继续往下执行,先前做第一步之后被打断,现在继续做完第二步。
结果显然,程序中mian函数和信号处理函数sighandler都调用了insert函数来向同一个全局链表中插入节点。在这种情况下,如果insert函数被不同控制流同时调用,可能会导致重入问题,进而引发内存泄漏。
像上例这样,insert 函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为 不可重入函数,反之,如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。
一般,main执行流,和信号捕捉执行流是两个执行流
- 如果在main中,和在handler中,该函数被重复进入,出现问题,该函数是不可重入函数
- 如果在main中,和在handler中,该函数被重复进入,没有出现问题,该函数是可重入函数
如果一个函数符合以下条件之一则是不可重入的:
- 调用了 malloc或 free,因为 malloc也是用全局链表来管理堆的
- 调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构
七,再次理解C语言关键字volatile
volatile是C语言的一个关键字,该关键字的作用是保持内存的可见性
为什么要有volatile关键字?
编译器的优化机制:
编译器在优化代码的时候,会大概推断出哪些变量在特定的范围内不会改变。对于这样的变量,编译器会把它们的值缓存在寄存器中,这也就不用再去内存上访问,需要的时候就在寄存器上读取。但是,如果一个变量的值是通过硬件中断、另一个线程或信号处理函数来改变的,那么编译器之前缓存在寄存器中的值就不准确了,从而导致程序出现错误的行为。
//全局变量
int flag = 0;
void handler(int signo)
{cout<<"信号:"<<signo<<":号被捕获"<<endl;cout<<"flag:"<<flag<<endl;flag = 1;cout<<"flag:"<<flag<<endl;cout<<"flag 0 -> 1 "<<endl;}
int main()
{//屏蔽信号signal(2,handler);while(!flag){cout<<"执行中..."<<endl;sleep(1);}cout<<"进程结束"<<endl;return 0;
}
上述代码中当按下 CTRL-C ,2号信号被捕捉,执行自定义动作,修改 flag=1 , while
条件不满足,退出循环,进程退出。
而如果我们编译的时候带上-O3选项,优化编译代码,即使按下Ctrl+C也不会退出循环。这是为什么呢❓
其中 -o3是最高的优化等级,上面的代码使用 -O3 进行编译
在编译器优化级别较高的时候,编译器可能会把flag直接设置进寄存器里面,这个寄存器保存的只是临时的数据。由于编译器优化,while循环检查quit的值,直接看寄存器内的值,并不会到内存里面查看 也就是说, while循环检查的flag,并不是内存中最新的flag。从 handler执行流返回main执行流时,内存中的 flag值已经更新(由0置1),但是寄存器内的值并没有更新。因为寄存器的值只是临时数据,改了没有意义,只是把flag更新到内存。 结果就出现了存在数据二异性的问题,while循环检查的是寄存器里面的值,寄存器里面的值依旧是0,没有更新,所程序依旧死循环不退出 由于寄存器的存在,遮盖了内存中的更新后数据,所以while循环眼里只有寄存器,没有内存,这是编译器优化产生的问题
虽然执行了handler,也成功修改了flag的值,但是由于编译器优化,早就将flag的值拷贝到寄存器中了,CPU只会在寄存器中读取flag的值。即使后来通过执行handler函数修改了flag的值。
C语言的关键字 volatile 的作用就是:保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量的任何操作,都必须在真实的内存中进行操作
八,SIGCHLD
用于通知父进程其一个子进程已经终止或停止。如果没有设置信号处理函数,SIGCHLD信号的默认行为是忽略;
父子进程最容易出现僵尸进程, 使用wait和wiatpid函数可以清理僵尸进程;
父进程可以阻塞等待子进程结束,也可以非阻塞查询是否有子进程结束等待清理(轮询方式);
这两种都麻烦;当子进程结束时,其实会向父进程发送SIGCHLD信号(17)父进程可以自定义该信号处理动作,这样父进程可以专心处理自己的工作,不需要关心子进程退出;
void handler(int signo)
{pid_t id;while ((id = waitpid(-1, NULL, WNOHANG)) > 0)// WNOHANG非阻塞式等待{printf("wait child success, pid: %d\n", id);}printf("child is quit! %d\n", getpid());//处理子进程退出时触发的信号// pid_t pid = waitpid(-1,nullptr,WNOHANG); // 非阻塞等待//加上循环可以回收多个子进程/* while(true)if(pid >0) //等待成功{cout<<"wiat childprocess sucess!"<<endl;}else{cout<<"fail!"<<endl;}*/}int main()
{//将子进程退出触发的信号自定义处理signal(SIGCHLD,handler);//创建子进程pid_t id = fork();if(id == 0){int cnt = 5;while(cnt--){cout<<"i am child process!"<<endl;sleep(1);}//子进程结束 退出_exit(1);}//fatherwhile(true){cout<<"father process do something!"<<endl;sleep(1);} return 0;
}
说明:
SIGCHLD属于普通信号,记录该信号的pending位只有一个,如果在同一时刻有多个子进程同时退出,那么在handler函数当中实际上只清理了一个子进程,因此在使用waitpid函数清理子进程时需要使用while不断进行清理
使用waitpid函数时,需要设置 WNOHANG 选项,即非阻塞式等待;否则当所有子进程都已经清理完毕时,由于while循环,会再次调用waitpid函数,此时就会在这里进行阻塞;
事实上,由于UNIX 的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调用 sigaction将 SIGCHLD的处理动作置为 SIG_IGN,这样 fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。
系统默认的忽略动作和用户用 sigaction函数自定义的忽略通常是没有区别的,但这是一个特例。此方法对于Linux可用,但不保证在其它UNIX系统上都可用注意:这里手动传的SIG_IGN,与系统中的默认SIG_IGN不一样,系统中的默认SIG_IGN与Term、Core的流程一样,手动传的SIG_IGN的特性是:子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程
int main()
{signal(SIGCHLD, SIG_IGN);//显示的设置对SIGCHLD进行忽略pid_t cid;if ((cid = fork()) == 0){ // childprintf("child pid: %d\n", getpid());sleep(3);//3秒后子进程退出exit(1);}// parentprintf("parent pid: %d\n", getpid());while (1){printf("father proc is doing some thing!\n");sleep(1);}return 0;
}
相关文章:

【Linux】阻塞信号|信号原理|深入理解捕获信号|内核态|用户态|sigaction|可重入函数|volatile|SIGCHILD|万字详解
目录 编辑 一,常见的信号术语 二,信号在内核中的表示 信号标志位 Pending表 Block表 handler表 POSIX.1标准 三,sigset_t 信号集操作函数 sigemptyset sigfillset sigaddset sigdelset sigismember sigprocmask sig…...

基于Linux对 【进程地址空间】的详细讲解
研究背景: ● kernel 2.6.32 ● 32位平台 –❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀-正文开始-❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀– 在学习操作系统中想必大家肯定都见过下面这…...

[python]使用Pandas处理多个Excel文件并汇总数据
在数据分析和处理过程中,经常需要处理多个Excel文件,并将其中的数据进行汇总和分析。本文介绍使用Python的Pandas库来读取多个Excel文件,并汇总不同类型的数据,例如员工工资、工件数量等。 代码示例 以下是一个完整的代码示例&a…...

提升体验:UI设计的可用性原则
在中国,每年都有数十万设计专业毕业生涌入市场,但只有少数能够进入顶尖企业。尽管如此,所有设计师都怀揣着成长和提升的愿望。在评价产品的用户体验时,我们可能会依赖直觉来决定设计方案,或者在寻找改善产品体验的切入…...

x264 编码器 SSIM 算法源码分析
SSIM SSIM(Structural Similarity Index)是一种用于衡量两幅图像之间视觉相似度的指标。它不仅考虑了图像的亮度、对比度和饱和度,还考虑了图像的结构信息。SSIM的值介于-1到1之间,值越接近1表示两幅图像越相似。 SSIM是基于以下三个方面来计算的: 亮度(Luminance):比…...

echarts使图表组件根据屏幕尺寸变更而重新渲染大小
效果图: 通过 window.addEventListener(resize, this.resizeChart); 实现 完整代码: <template><div class"dunBlock"><div class"char2" id"char2" ref"chart"></div></div…...

电脑图片损坏打不开怎么办?能修复吗?
照片和视频是记录和保存现实生活中的事件的最好方式。由于手机储存空间有限,一般我们会把有纪念意义的照片放到电脑上进行保存,但有时难免会遇到照片被损坏打不开的情况,一旦遇到这种情况,先不要急,也不要因为照片打不…...

vue-cli(二)
箭头函数 一般的函数: 这里window是用来调用函数的 function fun(){console.log(this) } window.fun(); 箭头函数: 1、如果只有一个参数,形参的小括号可以省略 2、如果只有一条语句,{}可以省略 完整的写法 let fun2 a>…...

今日头条的账号id在哪里看(网页版)
今日头条的账号id在哪里看(网页版) 1.https://mp.toutiao.com/profile_v4/index2.登录今日头条账号3.设置->头条号ID 1.https://mp.toutiao.com/profile_v4/index 2.登录今日头条账号 3.设置->头条号ID 打开下方链接: https://mp.to…...

单体应用提高性能和高并发处理-合理使用多核处理
合理使用多核处理能力是提升单体应用性能和处理高并发能力的重要手段。以下是关于如何合理利用多核处理器的详细讲解,包括多线程编程、线程池的使用、并行计算、以及如何避免常见的性能陷阱。 1. 多线程编程 多线程编程是利用多核处理器的直接方式。每个线程可以在…...

基于STM32/GD32的双CAN、一路485开发板
双CAN开发板 双CAN、一路485开发板的设计开发板配置器件选型CAN设计硬件设计软件设计 485设计硬件设计软件设计 其他设计LED硬件按键硬件 PCB板子和实物图开发板测试视频其他资料 双CAN、一路485开发板的设计 最近工作经常会出现一些小问题。就想设计一款带CAN的开发板用来测试…...

快排/堆排/归并/冒泡/
常见的内排序算法 插入排序 直接插入排序 原理:相当于扑克牌变成有序,先拿第一张,把他调节成有序,再拿第二张,与第一张相比找到第二张的位置,再继续拿第三张,以此类推。 void InsertSort(in…...

React基础教程(08):state体验
文章目录 7、state再体验7.1 异步更新状态7.2 同步更新状态方式17.3 同步更新状态方式27.4 betterScroll7.5 列表案例7、state再体验 7.1 异步更新状态 完整代码 import React from "react";export default class App extends React.Component{state = {count:1,}…...

Win10 创建新的桌面2,并实现桌面切换
1. Win10 创建新的桌面2 Win - Tab 2. Win10 桌面切换 Ctrl - Win - ←/→ 我们下期见,拜拜!...

MySQL数据库介绍及基础操作
目录: 一.数据库介绍 二.数据库分类 三. 数据库的操作 四. 常用数据类型 五. 表的操作 一.数据库介绍 1.文件保存数据有以下几个缺点: 1.1文件的安全性问题 1.2文件不利于数据查询和管理 1.3文件不利于存储海量数据 1.4文件在程序中控制不方便 为了解决上述问题&…...

【C语言篇】C语言常考及易错题整理DAY2
文章目录 C语言常考及易错题整理选择题编程题至少是其他数字两倍的最大数两个数组的交集图片整理寻找数组的中心下标多数元素除自身以外数组的乘积不使用加减乘除求两个数的加法 C语言常考及易错题整理 选择题 下列 for 循环的次数为( ) for(int i 0…...

javase入门
最近在学习大数据,学到flume拦截器的时候发现自定义拦截器需要使用java编写,现在开始学一些java入门的东西. 一. java相关组成 path环境变量: 环境变量用于记住程序路径,方便在命令行窗口任意目录启动程序. 二 java中的变量 变量要先定义在使用. int age 15 定义变量要定义其…...

Wireshark显示过滤器大全:快速定位网络流量中的关键数据包
文章目录 一、简介二、wireshark中的逻辑运算符三、过滤示例集合3.1 过滤指定日期和时间3.2 过滤指定协议3.2.1 例:仅显示SMTP(端口 25)和ICMP流量:3.2.2 例如:Windows 客户端 - DC 交换 3.3 过滤指定网段(…...

OOP笔记4----抽象类、接口、枚举
抽象类 简介 父类可以封装不同子类的共同特征或者共同行为.而有的时候,父类中封装的方法无法具体完成子类中需要的逻辑,因此我们可以将此方法设计成抽象方法,即使用关键字abstract进行修饰。而有抽象方法的类,也必须使用abstract…...

MySQL面试题全解析:准备面试所需的关键知识点和实战经验
MySQL有哪几种数据存储引擎?有什么区别? MySQL支持多种数据存储引擎,其中最常见的是MyISAM和InnoDB引擎。可以通过使用"show engines"命令查看MySQL支持的存储引擎。 存储方式:MyISAM引擎将数据和索引分别存储在两个不…...

01_Electron 跨平台桌面应用开发介绍
Electron 跨平台桌面应用开发介绍 一、Electron 的介绍二、关于 NW.js 和 Electron 介绍三、搭建 Electron 的环境1、准备工作:2、安装 electron 环境3、查看 electron 的版本,electron -v 一、Electron 的介绍 Electron 是由 Github 开发的一个跨平台的…...

【C语言-扫雷游戏】mineweeper【未完成】
编程小白如何成为大神?大学新生的最佳入门攻略 编程已成为当代大学生的必备技能,但面对众多编程语言和学习资源,新生们常常感到迷茫。如何选择适合自己的编程语言?如何制定有效的学习计划?如何避免常见的学习陷阱&…...

psychopy stroop 实验设计
斯特鲁stroop实验就是色词一致/不一致实验。 设计步骤如下: 1. 先去设置中将Input改为PsychToolbox, 2. 然后左上角File-New新建一个 3. 右键trial,rename改名 改成自己想要的名字即可,比如欢迎界面welcome。 4. 接下来添加提示语…...

c++精品小游戏(无错畅玩版)
一、俄罗斯方块 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <time.h> #include <conio.h> #include <windows.h>#ifdef _MSC_VER // M$的编译器要给予特殊照顾 #if _MSC_VER < 1200 // VC6及以下版本 #err…...

应急响应-主机安全之系统及进程排查相关命令(Linux操作系统-初级篇)
目录 概述lscpu-显示有关CPU架构的信息uname-查看系统信息lsmod-输出加载的所有模块lastb-输出最后登录失败的用户last-展示用户最近登录信息lastlog-展示所有用户最后的登录时间systemctl-系统服务,开机自启排查crontab-计划任务选项 history-查看历史命令选项常用…...

java中RSA分段加解密及Data must not be longer than异常处理
谈到RSA非对称加密,作为开发的我们第一想到的是安全,几乎不会被破解,以及公钥加密,私钥解密这些。在Java代码中,我们常使用一些现成的工具类如hutool中提供的工具类、网上在线的或者博客上的RSAUtils工具类来实现公钥私…...

MySQL数据分析进阶(十二)设计数据库——PART3
※食用指南:文章内容为‘CodeWithMosh’SQL进阶教程系列学习笔记,笔记整理比较粗糙,主要目的自存为主,记录完整的学习过程。(图片超级多,慎看!) 【中字】SQL进阶教程 | 史上最易懂S…...

Kubernetes-1.22.0 可视化部署
目录 Kubeadm方式部署3master,2work集群(Kubernetes-1.22.0)-CSDN博客 1. 官方Dashboard 2. Kuboard 部署 3. Rainbond 部署 4. Kubesphere 部署 1. 官方Dashboard kubectl apply -f https://kuboard.cn/install-script/k8s-dashboard/v2…...

在 vue3 中动态路由问题记录
第一种 如果这样子的话需要加上 /* vite-ignore / ,但是在这样用这行部署服务器上跳转会有问题 component: () > import(/ vite-ignore */ ../views/ e.component .vue) 第二种 // 解决跳转问题const modeules imporet.meta.glob(/views/**/**.vue)component: modules…...

进程编程及其函数的使用
1. 创建进程 创建进程的核心操作是使用 fork() 系统调用。 1.1 fork() 系统调用 fork() 创建一个新进程(子进程),新进程几乎是父进程的完整拷贝。fork() 返回两次: 在父进程中,返回子进程的 PID。在子进程中&#…...