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

Linux——进程间信号(超级详解!!)

索引

  • 一.初始信号
      • 1.什么是信号
      • 2.前后台进程
      • 3.信号的种类
      • 4.信号的管理
  • 二.信号产生前
      • 1.验证键盘是可以产生信号的
      • 2.通过系统调用接口发送信号
      • 3.由软件条件产生信号
      • 4.硬件异常产生信号
      • 5.总结
      • 6.core dump
  • 信号产生中
      • 1.信号在内核中的表示
      • 2.信号集操作函数
  • 信号产生后
      • 1.了解内核态和用户态
      • 2.内存如何实现信号的捕捉
      • 3.sigaction

一.初始信号

1.什么是信号

生活的角度: 红绿灯,闹钟,下课铃等
1.我们是如何得知这些东西的?有人教,(能够认识这些场景下的信号以及其表示的含义)也就是能够识别这些信号
2.我们提前知道这些信号产生时要做什么也就是我们已经提前知道了信号处理的方法

从上述可以看出

即使信号没有产生,我们已经具备了处理信号的能力!

因此:信号是给进程发送的,进程要具备处理信号的能力

1.该能力一定是预先已经早就有了的
2.进程能够识别对应的信号
3.进程能够处理对应的信号
这个能力是OS给我们提供的

对于进程来讲,即使信号还没有产生,我们进程已经具有识别和处理这个信号的能力了

2.前后台进程

while (true){sleep(1);}return 0;

当我们直接运行上述程序时,该程序会变成一个前台进程,此时直接ctrl+C可以直接终止,是因为ctrl+C可以发送一个信号给前台进程,使得该进程退出。
但当我们将前台进程变成后台进程时,其接不到类似ctrl + C的信号,也就无法退出了

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

在这里插入图片描述
注意:

  • 1.只有前台进程才能收到Ctrl+C产生的信号,后台进程无法收到,一个运行进程的命令后面+&可以使得前台进程转化成后台进程,转化为后台进程之后shell不必等到进程结束才可以接受新的命令,可以直接启动新进程
  • 2.前台进程在运行过程中用户随时按下ctrl+C而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能受到SIGINT(就是Ctrl+C)而终止,所以信号相对于进程的控制流程来说是异步的。

3.信号的种类

kill -l 可以显示信号列表

在这里插入图片描述
数字是信号编号,右侧是宏,二者一个意思

1-31是分时信号,产生信号了不用立即处理
34-64是实时信号,信号产生了就必须处理。
我们学习的是1-31的普通信号。

4.信号的管理

在这里插入图片描述
那么进程又是如何管理信号的呢?
是在进程的PCB中

eg:
task_struct {
uint32_t sig;//位图 0000 0000

}

位图的内容表示有没有该信号,位图的位置表示是哪一个信号,由于PCB是在内核数据结构,所以只有OS有资格修改位图,OS是进程的管理者,进程的所有属性的获取和设置只能又操作系统来设置,因此无论信号怎么产生,最终一定是OS帮我们进行信号的设置

下面我将从三个部分:信号产生前,信号产生中,信号产生后来叙述进程间信号

二.信号产生前

上述可以了解到,信号在OS中是由位图表示的,所以信号的产生OS发送给进程的时候不如说是写入信号。

1.验证键盘是可以产生信号的

sighandler_t signal(int signum, sighandler_t handler); 对信号设置回调

 #include <signal.h>typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);

验证Ctrl + C是2号信号,我们先对
-定义捕捉动作当我们在键盘上按Ctrl+C的时候,如果调用了我们的自定义函数,验证成功!

void handler(int signo)
{cout << "i am a process ,我获取了一个信号: " << signo << endl;
}
int main()
{signal(SIGINT, handler);sleep(3);cout << "自定义信号捕捉函数设置完毕" << endl;while (true){cout << "我是一个正在运行的进程" << endl;sleep(1);}return 0;
}

signal(SIGINT, handler); 这里不是在调用handler方法,只有信号产生的时候,才会调用handler方法.
实验结果如下
在这里插入图片描述
因此可以得出结论:Ctrl + C :本质就是给前台进程发送2号信号给目标进程,目标进程默认对2号信号的处理动作就是终止自己,然而现在我们设置了用户对信号的自定义处理动作。

Ctrl + C 产生2号信号
Ctrl +\ 产生3号信号,同样也是终止进程

注意:9号信号是不能设置自定义的,即使设置了,kill -9 PID 照样也可以杀死进程,因此9号信号也叫做管理员信号

2.通过系统调用接口发送信号

int kill(pid_t pid, int sig);不仅是一个命令,还是一个系统调用接口,表示对某个进程发送某个信号
我自己写一个mykill进程,该进程是调用了kill这个函数的,可以得出结论,代码如下

mykill.cc

static void Usage(const string &proc)
{cerr << " Usage :\n\t" << proc << " signo pid " << endl;
}
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}if (kill(static_cast<pid_t>(atoi(argv[2])), atoi(argv[1])) == -1){cerr << "kill :" << strerror(errno) << endl;}}

myproc.cc

while (true){sleep(1);cout << "我的PID是: " << getpid() << endl;}

在这里插入图片描述
int raise(int sig); 给自身发信号
在这里插入图片描述

NAMEabort - cause abnormal process terminationSYNOPSIS#include <stdlib.h>void abort(void);//直接终止进程

abort()直接终止进程
在这里插入图片描述

3.由软件条件产生信号

NAMEalarm - set an alarm clock for delivery of a signalSYNOPSIS#include <unistd.h>unsigned int alarm(unsigned int seconds);

调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发送SIGALRM信号,该信号的默认处理动作是终止当前进程。函数的返回值是0或者是设定闹钟时间的剩余秒数。

int cnt = 0;void handler(int signo)
{cout << "i am a process ,我获取了一个信号: " << signo << endl;cout << "cnt: " << cnt << endl;
}
int main()
{signal(SIGALRM, handler);alarm(1);while (1){cnt++;}
}

在这里插入图片描述
一秒钟之后就会捕捉SIGALRM信号

4.硬件异常产生信号

下面先列举两个崩溃的进程

在这里插入图片描述
猜想一下:上述两个进程崩溃的本质也是收到了某个信号,验证一下,先将每个信号都设置自定义动作,然后再运行。
在这里插入图片描述

进程崩溃的本质:是该进程收到了异常信号,因为硬件异常,而导致OS向目标进程发信号,进而导致进程终止的现象。
除0:CPU内部,状态寄存器,当我们除0的时候,CPU内的状态寄存器会被设置成为有报错:浮点数越界,CPU内部寄存器(硬件),OS就会识别到CPU内有报错->1,然后OS就会向目标进程发信号,目标进程在合适的时候处理信号,终止进程
越界&野指针 我们在语言层面使用的地址(指针),其实都是虚拟地址->物理地址->物理内存->读取对应的数据和代码,如果虚拟地址有问题,而地址转化的工作是由MMU(硬件)+页表(软件)构成,转化过程就会引起问题->表现在硬件MMU上->OS就会发现硬件出了问题->OS向目标进程发送信号->目标进程在合适的时候处理信号->终止进程

由此可得出结论:我们在C/C++ 中除0,内存越界等异常,在系统层面上是被当成信号处理的。

5.总结

  1. 上面所说的信号的产生,信号的发送,最终都是要由OS来执行的,因为OS是进程的管理者
  2. 信号不是被立即执行,而是在进程合适的时候
  3. 信号不是被立即执行的,那么信号就会被记录下来,记录在进程的PCB
  4. 一个进程在没有收到信号的时候,能否知道自己应该对合法信号做何处理?能,处理方式已经由之前的程序员写在内核了

6.core dump

Coredump叫做核心转储,是进程在运行时突然崩溃的一个内存快照。

pid_t wait(int *status);pid_t waitpid(pid_t pid, int *status, int options);

在这里插入图片描述
wait 与waitpid都有一个status参数,该参数是一个输出型参数,由OS填充,其他比特位在我之前的博客有提过,当生成Core dump文件的时候,该标记位会设置成1.

在这里插入图片描述
由上可以看出,并不是所有的信号都会生成core文件的,只有程序自身内部出了问题才会产生core文件。

int main()
{pid_t id = fork();if (id == 0) // 子进程{int *p = nullptr;*p = 10000; // 野指针问题exit(1);}int status = 0;waitpid(id, &status, 0);printf("exitcode: %d,signo: %d, core dump flag: %d \n", (status >> 8) & 0xff, status * 0x7f, (status >> 7) & 0x1);
}

运行结果
在这里插入图片描述
可以子进程的退出信号是11,符合野指针出错信号,但此时的core dump标记位还是0,为什么呢?
在这里插入图片描述
因为我是线上的的云服务器,厂商设置的默认core文件个数就是0,也就是说禁止生成core文件。
在这里插入图片描述
设置之后此时允许生成core文件。
在这里插入图片描述
但这不是真正的用途,此时我们要生成可调试版本的程序。
在这里插入图片描述

为什么线上的云服务器将core dump文件关闭?
因为云服务器上的产品一般都是release版本,但是我们生成的core文件是可调式版本的,并且如果线上的产品挂掉了,最重要的不是找bug而是重启,并且一旦服务挂掉了,会直接重启,eg一秒钟重启一万次的话,每次都有core文件的话,此时磁盘占据大量的文件,磁盘被打满了会危急到操作系统,很危险!
总结一下:

信号从产生到递达之间的状态称为信号未决
进程可以选择阻塞某个信号,当进程阻塞信号时,信号无法被抵达
被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作
阻塞和忽略是不同的,只要信号被阻塞那么信号即使产生了也不会被递达,忽略是信号处理的一个动作

信号产生中

1.信号在内核中的表示

信号在内核中的表示
在这里插入图片描述

信号在内核中task_struct指向三张表,三张表都是用
位图表示的。

pending:未决信号集,表示该信号是否产生
block:阻塞信号集,表示该信号是否被阻塞
handler:指向的是每个信号的自定义函数.
以上述的SIGQUIT为例,此时该信号未产生,一旦产生该信号,他的处理动作是用户的自定义函数sighandler,但是由于此时该信号被阻塞了,此时该信号不会抵达,除非接触对该信号的阻塞,才会抵达。

同时,如果一个信号最初未被阻塞,但是在某信号抵达之前,也可以说是该信号正在处理的时候,如果继续产生该信号,该信号也还是只会被记录一次,实时信号在抵达之前可以产生多次,这里不讨论。

2.信号集操作函数

从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也一样,因此,未决和阻塞标志可以用相同的数据类型sigset_t来储存。
sigset_t类型对于每种信号用一个bit表示有效无效,这个类型内部如何储存这些bit则依赖于系统实现,我们不必关心,我们只要会使用如下几个函数就可以了。

typedef __sigset_t sigset_t;
......
typedef struct{unsigned long int __val[_SIGSET_NWORDS];} __sigset_t;

根据上述源码可以看到sigset_t的实现与系统自身有关,所以我们不必关心。
int sigemptyset(sigset_t *set)
初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号
int sigfillset(sigset_t *set)
使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号
int sigaddset(sigset_t *set, int signo)
该函数允许将一个指定的信号添加到一个自定义信号集中,也就是将该信号的标准为设为1,表示阻塞该信号。
int sigdelset(sigset_t *set,int signo)
与上述函数相反,表示解除该信号的阻塞
int sigismember(const sigset_t *set, int signo)
判断一个信号集的有效信号中是否包含某种信号,也就是检查是否屏蔽该信号,如果包含则返回1,反之0
int sigpending(sigset_t *set);
获取当前进程(谁调用,获取谁)的pending信号集,通过set参数传出,调用成功返回0,失败返回-1
int sigpromask(int how, const sigset_t *set, sigset_t *oset
成功返回0,失败返回-1
若oset非空:当前进程的信号屏蔽字通过oset传出
set非空:更改进程的信号屏蔽字
how:指示如何更改
如果oset 和 set都非空,则将原来的信号屏蔽字备份到oset,然后根据set和how参数更改信号屏蔽字。 假设当前的信号屏蔽字为mask,下述说明了how参数的可选值

SIG_BLOCKset包含了我们希望添加到当前信号屏蔽字的信号
SIG_UNBLOCKset包含了从当前信号屏蔽字接触的·信号
SIG_SETMASK设置当前信号屏蔽字为set所指向的值

V1.1版本的代码先指示将2号信号添加到信号屏蔽字中
预期结果:初始的pending信号集都是0,当我们向进程发送2号信号后,pending信号集中表示2号信号的比特位变成1

#include <unistd.h>
#include <iostream>
#include <signal.h>
using namespace std;void showpending(sigset_t *pendings)
{for (int sig = 1; sig <= 31; sig++){if (sigismember(pendings, sig)){cout << "1";}else{cout << "0";}}cout << endl;
}
void handler(int signo)
{cout << "我是一个进程,刚刚获得了一个信号: " << signo << endl;
}
int main()
{// 2.屏蔽掉2号信号sigset_t bsig, obsig;sigemptyset(&bsig);sigemptyset(&obsig);// 2.1添加2号信号到信号屏蔽字中sigaddset(&bsig, 2);// 2.2 设置用户级的信号屏蔽字到内核中,让当前进程屏蔽2号信号sigprocmask(SIG_SETMASK, &bsig, &obsig);cout << "pid: " << getpid() << endl;signal(SIGINT, handler);// 1.不断获取当前进程的pending信号集sigset_t pendings;int cnt = 0;while (true){// 1.1清空信号集sigemptyset(&pendings);// 1.2获取当前进程pending信号集(谁调用就获取谁)if (sigpending(&pendings) == 0){// 1.3打印一下当前进程的pending信号集sleep(1);showpending(&pendings);cnt++;}cout << "cnt: " << cnt << endl;}return 0;
}

在这里插入图片描述
V2.0
先将所有的信号都屏蔽,在20秒之后解除2号和3号信号
下面只贴部分更改的代码

 sigset_t bsig, obsig;sigemptyset(&bsig);sigemptyset(&obsig);// 2.1添加1-31号信号到信号屏蔽字中for (int sig = 1; sig < 32; sig++){sigaddset(&bsig, sig);}// 2.2 设置用户级的信号屏蔽字到内核中,让当前进程屏蔽2号信号sigprocmask(SIG_SETMASK, &bsig, &obsig);cout << "pid: " << getpid() << endl;signal(SIGINT, handler);signal(3, handler);// 1.不断获取当前进程的pending信号集sigset_t pendings;int cnt = 0;while (true){// 1.1清空信号集sigemptyset(&pendings);// 1.2获取当前进程pending信号集(谁调用就获取谁)if (sigpending(&pendings) == 0){// 1.3打印一下当前进程的pending信号集sleep(1);showpending(&pendings);cnt++;}if (cnt == 20){// sigprocmask(SIG_SETMASK,&obsig,nullptr); 直接用该方法可以直接解除所有信号集sigset_t sigs;sigemptyset(&sigs);sigaddset(&sigs, 2);sigaddset(&sigs, 3);sigprocmask(SIG_UNBLOCK, &sigs, nullptr);}cout << "cnt: " << cnt << endl;}

在这里插入图片描述
根据上述结果可以看出,当我们将信号添加到信号集之后,我们向进程发送信号时,此时代表该信号的比特位由0 --> 1
解除信号屏蔽之后,就会重新由1 --> 0
但是根据结果可以看出 9号信号即使被屏蔽了还是可以杀死进程

信号产生后

1.了解内核态和用户态

上述提到信号产生后,OS系统是在什么时候处理信号呢?
实在合适的时候,那合适的时候具体是什么时候呢?
当前进程从内核态切换回用户态的时候进行信号的检测与处理!
每个进程都有自己的task_struct指向其虚拟地址,虚拟地址到物理地址的转化是通过页表实现的,而每个进程对于自己的用户空间3G是独立的,还有一份公共的内核页表,如下所示.
在这里插入图片描述
那么OS在不在内存中被加载?答案是肯定的
无论进程如何切换,我们都可以找到内核的代码和数据,前提是你要有足够的权利进行访问!
那么当前的进程如何具备权利访问内核页表乃至访问内核数据呢?
要进行身份切换。我们要让OS知道此时访问数据的是内核还是页表
CPU内有对应的状态寄存器CR3寄存器,当比特位是0的时候表示内核态,当比特位是3的时候表示用户态:

用户态:只能访问用户级页表
内核态:既能访问内核级页表也能访问用户级页表
内核态相比于用户态拥有更高的权限

那么一般什么时候会从用户态切换回内核态呢?

  • 系统调用的时候
  • 时间片到了进行进程间切换等

2.内存如何实现信号的捕捉

我们必须要了解一个知识:
我们的程序,会无数次直接或者间接的访问系统级软硬件资源(管理是OS),本质上,你并没有去操作这些软硬件资源,而是必须通过OS–>无数次陷入内核(1.切换身份 2.切换页表) -->调用内核的代码–>完成访问的动作–>结果返回给用户(1.切换身份 2.切换页表)–>得到结果
eg:
while(1);
仅仅是这一行代码存在从用户态切换成内核态吗?
一定是有的,因为每个进程都有自己的时间片,当时间片到了,需要转换成内核态然后更换内核级页表 -->为了保护上下文,执行调度算法–>切换新的进程–>恢复新进程的上下文–>再切换成用户态–>CPU执行的就是新进场的代码!

下面一个场景,当我们调用完系统调用之后,返回内核态时,检测出了错误;
在这里插入图片描述
快速记忆

在这里插入图片描述

3.sigaction

该函数可以读取和修改指定信号相关联的处理动作,成功返回0,失败返回-1.

#include<signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};

参数解析
signum:指定信号编号
act:非空,根据指针act修改该信号的处理动作
oldact:非空,通过其传出该信号原来的处理动作
act和oldact都是sigaction结构体
上述有该结构体的具体组成
sa_handler:表示该信号的处理动作
当某个信号正在被处理时,内核会自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复成原来的信号屏蔽字,这样的话,当前进程正在被处理时,如果这个信号再次产生,该信号会被阻塞直到当前信号处理结束。
如果在调用信号处理函数时,除了希望自动屏蔽当前信号,还希望自动屏蔽其他信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时恢复成原来的状态
sa_flags设为0.

实验一:先不给sa_mask添加信号

void handler(int signo)
{cout << "我是一个进程,刚刚获得了一个信号: " << signo << endl;sigset_t pending;// 此时会永远在处理某个信号while (true){sigpending(&pending);for (int i = 1; i <= 31; i++){if (sigismember(&pending, i))cout << "1";elsecout << "0";}cout << endl;sleep(1);}
}
int main()
{cout << "my pid: " << getpid() << endl;struct sigaction act, oldact;act.sa_handler = handler;act.sa_flags = 0;sigemptyset(&act.sa_mask);// sigaddset(&act.sa_mask, 3);sigaction(2, &act, &oldact);while (true){cout << "main running" << endl;sleep(1);}return 0;
}

在这里插入图片描述

实验二:给sa_mask添加信号

    sigaddset(&act.sa_mask, 3);

在这里插入图片描述

相关文章:

Linux——进程间信号(超级详解!!)

索引 一.初始信号1.什么是信号2.前后台进程3.信号的种类4.信号的管理 二.信号产生前1.验证键盘是可以产生信号的2.通过系统调用接口发送信号3.由软件条件产生信号4.硬件异常产生信号5.总结6.core dump 信号产生中1.信号在内核中的表示2.信号集操作函数 信号产生后1.了解内核态和…...

C++ STL库的介绍和使用

文章目录 C STL库的介绍和使用STL六大组件算法的分类迭代器 一个简单的例子容器和自定义类型容器嵌套容器常用容器stringvectordequestackqueuelistset/multisetpairmap/multimap 容器的使用时机函数对象&#xff08;仿函数&#xff09;谓词内建函数对象适配器bind2nd和bind1st…...

Excel数学、工程和科学计算插件:FORMULADESK Studio

如果 Excel 是您的武器 - 让我们磨砺您的剑&#xff01;为整天使用 Excel 的人们提供创新的 Excel 加载项&#xff0c;你需要这个 FORMULADESK Studio。。。 Excel 插件为任何使用 Excel 执行数学、工程和科学计算的人提供了必备工具。 * 将公式视为真正的数学方程 * 为您的公…...

大规模 Spring Cloud 微服务无损上下线探索与实践

文章目录 什么是无损上下线&#xff1f;大规模 Spring Cloud 微服务架构实现无损上下线的挑战无损上下线的实践1. 使用负载均衡器2. 使用数据库迁移工具3. 动态配置管理4. 错误处理和回滚 未来的趋势1. 容器编排2. 服务网格3. 自动化测试和验证 结论 &#x1f389;欢迎来到云原…...

【LeetCode】剑指 Offer 54. 二叉搜索树的第k大节点

题目&#xff1a; 给定一棵二叉搜索树&#xff0c;请找出其中第 k 大的节点的值。 示例 1: 输入: root [3,1,4,null,2], k 13/ \1 4\2 输出: 4 示例 2: 输入: root [5,3,6,2,4,null,null,1], k 35/ \3 6/ \2 4/1 输出: 4 限制&#xff1a; 1 ≤ k ≤ 二叉搜索树…...

C++设计模式_03_模板方法Template Method

文章目录 1. 设计模式分类1.1 GOF-23 模式分类1.2 从封装变化角度对模式分类 2. 重构&#xff08;使用模式的方法&#xff09;2.1 重构获得模式 Refactoring to Patterns2.2 重构关键技法 3. “组件协作”模式4. Template Method 模式4.1 动机&#xff08; Motivation&#xff…...

【LeetCode-中等题】79. 单词搜索

文章目录 题目方法一&#xff1a;递归 回溯 题目 方法一&#xff1a;递归 回溯 需要一个标记数组 来标志格子字符是否被使用过了先找到word 的第一个字符在表格中的位置&#xff0c;再开始递归递归的结束条件是如果word递归到了最后一个字符了&#xff0c;说明能在矩阵中找到单…...

揭秘iPhone 15 Pro Max:苹果如何战胜三星

三星Galaxy S23 Ultra在我们的最佳拍照手机排行榜上名列前茅有几个原因&#xff0c;但iPhone 15 Pro Max正在努力夺回榜首——假设它有一个特定的功能。别误会我的意思&#xff0c;苹果一直在追赶三星&#xff0c;因为它的iPhone 14 Pro和14 Pro Max都表现强劲。尽管如此&#…...

分布式秒杀方案--java

前提&#xff1a;先把商品详情和秒杀商品缓存redis中&#xff0c;减少对数据库的访问&#xff08;可使用定时任务&#xff09; 秒杀商品无非就是那几步&#xff08;前面还可能会有一些判断&#xff0c;如用户是否登录&#xff0c;一人一单&#xff0c;秒杀时间验证等&#xff0…...

高频golang面试题:简单聊聊内存逃逸?

文章目录 问题怎么答举例 问题 知道golang的内存逃逸吗&#xff1f;什么情况下会发生内存逃逸&#xff1f; 怎么答 golang程序变量会携带有一组校验数据&#xff0c;用来证明它的整个生命周期是否在运行时完全可知。如果变量通过了这些校验&#xff0c;它就可以在栈上分配。…...

【2023年数学建模国赛C题解题思路】

第一问 要求分析分析蔬菜各品类及单品销售量的分布规律及相互关系。该问题可以拆分成三个角度进行剖析。 1&#xff09;各种类蔬菜的销售量分布、蔬菜种类与销售量之间的关系&#xff1b;2&#xff09;各种类蔬菜的销售量的月份分布、各种类蔬菜销售量与月份之间的相关关系&a…...

Jenkins+Allure+Pytest的持续集成

一、配置 allure 环境变量 1、下载 allure是一个命令行工具&#xff0c;可以去 github 下载最新版&#xff1a;https://github.com/allure-framework/allure2/releases 2、解压到本地 3、配置环境变量 复制路径如&#xff1a;F:\allure-2.13.7\bin 环境变量、Path、添加 F:\a…...

yo!这里是进程控制

目录 前言 进程创建 fork()函数 写时拷贝 进程终止 退出场景 退出方法 进程等待 等待原因 等待方法 1.wait函数 2.waitpid函数 等待结果&#xff08;status介绍&#xff09; 进程替换 替换原理 替换函数 进程替换例子 shell简易实现 后记 前言 学习完操作…...

多线程快速入门

线程与进程区别 每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。线程是一组指令的集合&#xff0c;或者是程序的特殊段&#xff0c;它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程&#xff0c;它负责在单个程序里…...

Redis 7 第七讲 哨兵模式(sentinal)架构篇

哨兵模式 哨兵巡查监控后台master主机是否故障,如果出现故障根据投票时自动将某一个从库转换成新的主库,继续对外服务。 作用 1. 监控redis运行状态,包括master和slave 2. 当master down机,能自动将salve切换成新的master 应用场景 主从监控监控主从redis库运行的状态…...

laravel框架系列(一),Dcat Admin 安装

介绍 Laravel 是一个流行的 PHP 开发框架&#xff0c;它提供了一套简洁、优雅的语法和丰富的功能&#xff0c;用于快速构建高质量的 Web 应用程序。 以下是 Laravel 的一些主要特点和功能&#xff1a; MVC 架构&#xff1a;Laravel 使用经典的模型-视图-控制器&#xff08;MV…...

Linux:工具(vim,gcc/g++,make/Makefile,yum,git,gdb)

目录 ---工具功能 1. vim 1.1 vim的模式 1.2 vim常见指令 2. gcc/g 2.1 预备知识 2.2 gcc的使用 3.make,Makefile make.Makefile的使用 4.yum --yum三板斧 5.git --git三板斧 --Linux下提交代码到远程仓库 6.gdb 6.1 gdb的常用指令 学习目标&#xff1a; 1.知道…...

小节1:Python字符串打印

1、字符串拼接 用可以将两个字符串拼接成一个字符串 print("你好 " "这是一串代码") 输出&#xff1a; 2、单双引号转义 当打印的字符串中带有引号或双引号时&#xff0c;使用\或\"表示 print("He said \"Let\s go!\"") 输…...

2023国赛C题解题思路代码及图表:蔬菜类商品的自动定价与补货决策

2023国赛C题&#xff1a;蔬菜类商品的自动定价与补货决策 C题表面上看上去似乎很简单&#xff0c;实际上23题非常的难&#xff0c;编程难度非常的大&#xff0c;第二题它是一个典型的动态规划加仿真题目&#xff0c;我们首先要计算出销量与销售价格&#xff0c;批发价格之间的…...

数据可视化工具中的显眼包:奥威BI自带方案上阵

根据经验来看&#xff0c;BI数据可视化分析项目是由BI数据可视化工具和数据分析方案两大部分共同组成&#xff0c;且大多数时候方案都需从零开始&#xff0c;反复调整&#xff0c;会耗费大量时间精力成本。而奥威BI数据可视化工具别具匠心&#xff0c;将17年经验凝聚成标准化、…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度​

一、引言&#xff1a;多云环境的技术复杂性本质​​ 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时&#xff0c;​​基础设施的技术债呈现指数级积累​​。网络连接、身份认证、成本管理这三大核心挑战相互嵌套&#xff1a;跨云网络构建数据…...

利用ngx_stream_return_module构建简易 TCP/UDP 响应网关

一、模块概述 ngx_stream_return_module 提供了一个极简的指令&#xff1a; return <value>;在收到客户端连接后&#xff0c;立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量&#xff08;如 $time_iso8601、$remote_addr 等&#xff09;&a…...

CMake基础:构建流程详解

目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

什么是EULA和DPA

文章目录 EULA&#xff08;End User License Agreement&#xff09;DPA&#xff08;Data Protection Agreement&#xff09;一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA&#xff08;End User License Agreement&#xff09; 定义&#xff1a; EULA即…...

HTML前端开发:JavaScript 常用事件详解

作为前端开发的核心&#xff0c;JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例&#xff1a; 1. onclick - 点击事件 当元素被单击时触发&#xff08;左键点击&#xff09; button.onclick function() {alert("按钮被点击了&#xff01;&…...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版&#xff0c;柱状图PPT模版&#xff0c;线状图PPT模版&#xff0c;折线图PPT模版&#xff0c;饼状图PPT模版&#xff0c;雷达图PPT模版&#xff0c;树状图PPT模版 图表类系列各种样式PPT模版分享&#xff1a;图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配

目录 一、C 内存的基本概念​ 1.1 内存的物理与逻辑结构​ 1.2 C 程序的内存区域划分​ 二、栈内存分配​ 2.1 栈内存的特点​ 2.2 栈内存分配示例​ 三、堆内存分配​ 3.1 new和delete操作符​ 4.2 内存泄漏与悬空指针问题​ 4.3 new和delete的重载​ 四、智能指针…...

根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的----NTFS源代码分析--重要

根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的 第一部分&#xff1a; 0: kd> g Breakpoint 9 hit Ntfs!ReadIndexBuffer: f7173886 55 push ebp 0: kd> kc # 00 Ntfs!ReadIndexBuffer 01 Ntfs!FindFirstIndexEntry 02 Ntfs!NtfsUpda…...

苹果AI眼镜:从“工具”到“社交姿态”的范式革命——重新定义AI交互入口的未来机会

在2025年的AI硬件浪潮中,苹果AI眼镜(Apple Glasses)正在引发一场关于“人机交互形态”的深度思考。它并非简单地替代AirPods或Apple Watch,而是开辟了一个全新的、日常可接受的AI入口。其核心价值不在于功能的堆叠,而在于如何通过形态设计打破社交壁垒,成为用户“全天佩戴…...

tomcat指定使用的jdk版本

说明 有时候需要对tomcat配置指定的jdk版本号&#xff0c;此时&#xff0c;我们可以通过以下方式进行配置 设置方式 找到tomcat的bin目录中的setclasspath.bat。如果是linux系统则是setclasspath.sh set JAVA_HOMEC:\Program Files\Java\jdk8 set JRE_HOMEC:\Program Files…...