[Linux]-----进程信号

news2024/11/30 8:44:00

文章目录

  • 前言
  • 一、什么是信号
    • 我们是如何得知这些信号呢?
    • 我们知道对应的信号产生时,要做什么呢?
  • 二、进程信号
    • 前台进程和后台进程
    • 注意
  • 三、信号列表
    • 信号的捕捉
  • 四、信号产生前
    • 用户层产生信号的方式
    • signal函数
    • kill
    • raise
    • abort
    • 由软件条件产生信号
    • 硬件异常产生信号
    • 总结
    • core dump字段
  • 五、信号产生中
    • 阻塞信号
      • 1.信号其他相关常见概念
      • 2. 在内核中的表示
      • 3.sigset_t
      • 4.信号集操作函数
    • 信号的捕捉
  • 六、信号处理后
    • signaction
  • 总结


前言


正文开始!

一、什么是信号

生活中的信号有哪些呢?

红绿灯,下课铃声,信号枪,烽火台,旗语…

我们是如何得知这些信号呢?

别人教(能够认识这些场景下的信号以及所表示的含义)

我们知道对应的信号产生时,要做什么呢?

我早就知道了信号产生之后我们要做什么!即便当前信号还没有产生!—>我们提前已经知道了这个信号的处理方法!

即便这个信号还没产生,我们已经拥有处理信号的能力!

二、进程信号

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

  1. 该能力一定是预先已经拥有的。
  2. 进程能够识别对应的信号。
  3. 进程能够处理对应的信号。

进程处理信号的能力是程序员给予的!OS帮我们提供!
对于进程来讲,即便这个信号还没产生,我们进程已经具有识别和处理这个信号的能力!

前台进程和后台进程

在这里插入图片描述
前台进程运行的时候你输入其他的命令Bash是无法给你做出响应的!

./程序名 &

以后台进程去运行,你输入其他的命令是可以继续执行的!
但是打出来的东西很乱!

在这里插入图片描述
那么如何终止这个进程呢?

jobs

查看后台进程任务编号就是1
在这里插入图片描述
将该进程提至前台运行

fg(front ground) 编号

在这里插入图片描述
然后Ctrl+c终止进程即可!

让进城在放置后台运行

bg 编号

注意

  1. Ctrl+C 产生的信号只能发给前台进程。一个命令后面加个&可以放到后台运行,这样Shell不必等待进程结束就可以接受新的命令,启动新的进程。
  2. Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像 Ctrl-C 这种控制键产生的信号。
  3. 前台进程在运行过程中用户随时可能按下 Ctrl-C 而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到 SIGINT 信号而终止,所以信号相对于进程的控制流程来说是异步(Asynchronous)的。

三、信号列表

用kill -l命令可以察看系统定义的信号列表
在这里插入图片描述

每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到,例如其中有定 义 #define SIGINT 2

Linux下一共有62个信号!
Linux下的信号被分为了两批

  1. 1-31(普通信号)—>常用的信号
  2. 34-64(实时信号)—>常用于实时操作系统!

我们常用的计算机都是分时操作系统,所以就着重带大家了解前31个信号即可!

因为信号产生时是异步的,当信号产生的时候,对应的进程可能正在做更重要的事情,我们进程可以暂时不处理这个信号!

所以进程可能不需要立即处理这个信号!—>不代表这个信号不会处理!

但是必须记住这个信号已经来了(有信号来了吗?什么信号?)

信号的捕捉

  1. 默认动作
  2. 忽略信号
  3. 自定义动作

进程是如何记住这个信号的,在哪呢?

保存在进程的PCB中的!!

tash_struct{
	uint_32 sig;//位图结构,每一位代表一个信号,1表示发生
};
task_strcut是内核的数据结构!--->所以只有操作系统有权利修改task_struct内的数据位图!!
OS是进程的管理者,进程的所有属性的获取和设置,只能由OS来!-->无论信号怎么产生,最终一定是OS帮我们进行信号的设置的!!!

有没有产生?[bit位的内容]
什么信号产生?[bit位的位置]

举个栗子:0000 0010 就代表有信号产生,是二号信号!(默认从右往左数)

四、信号产生前

用户层产生信号的方式

  1. 键盘产生
    谁给进程发送的信号呢?—> 只能是操作系统—>更改PCB中的位图结构,该信号比特位由0置1

  2. 通过系统接口完成对进城发送信号的过程

  3. 由软件条件产生信号

  4. 硬件异常产生信号

signal函数

·

#include <iostream>
#include <string>
#include <stdlib.h>
#include <cstring>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;
void handler(int signo)
{
    cout << "我是一个进程,刚刚获取了一个信号: " << signo << endl;
    cout << cnt << endl;
}
int main()
{
    //这里不是调用hander的方法,这里只是设置了一个回调,让SIGINT(2)产生的时候,刚方法才会被调用
    //如果不产生SIGINT,该方法不会被调用!
    // ctrl + c :本质就是给前台进程产生了2号信号,发送给目标进程,目标进程默认对2号信号的处理是终止自己
    //我们更改了对二号信号的处理,设置了用户自定义处理方法
    signal(SIGINT, handler);
    sleep(3);
    cout << "进程已经设置完了!" << endl;
    while (true)
    {
        cout << "我是一个正在运行中的进程: " << getpid() << endl;
        sleep(1);
    }
    return 0;
}

在这里插入图片描述

给上面代码设置3号信号的捕捉动作

signal(3, handler);

在这里插入图片描述

Ctrl+\是发送三号信号

注意:九号信号就算设置了默认处理函数,也照样可以杀掉该进程!

kill

在这里插入图片描述

自己实现kill命令

#include <iostream>
#include <string>
#include <stdlib.h>
#include <cstring>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

static void Usage(const std::string &proc)
{
    cerr << "Usage:\n\t" << proc << " signo pid" << endl;
}
//我想写一个kill命令
// mykill 9 1234
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;
    }
    return 0;
}

在这里插入图片描述
在这里插入图片描述

raise

在这里插入图片描述

int main(int argc, char *argv[])
{
    signal(2, handler); //没有调用对应的handler方法,仅仅是注册
    while (1)
    {
        sleep(1);
        raise(2);
    }
    return 0;
}

在这里插入图片描述

abort

在这里插入图片描述
发送的信号是SIGABRT信号

在这里插入图片描述
我们把SIGABRT设为默认处理会怎么样呢?

int main(int argc, char *argv[])
{
    signal(SIGABRT, handler); //没有调用对应的handler方法,仅仅是注册
    while (1)
    {
        sleep(1);
        abort();
    }
    return 0;
}

在这里插入图片描述
所以六号信号是可以被捕捉的,但是还是要终止掉进程的!!!

由软件条件产生信号

在这里插入图片描述

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

int main(int argc, char *argv[])
{
    alarm(1);
    for(;;)
    {
        //是谁在推动操作系统做一系列的动作呢?
        //硬件---时钟硬件--给OS发送时钟中断
        printf("hello : %d\n",cnt++);//统计一个我们的进程1S cnt++多少次
    }
    return 0;
}

在这里插入图片描述

统计一个我们的进程1s会把cnt++多少次!

硬件异常产生信号

int main(int argc, char *argv[])
{
    int a=1;
    a/=0;
    return 0;
}

在这里插入图片描述
除零:CPU内部有一个状态寄存器,
当我们除零的时候,CPU内的状态寄存器会被设置成为,有报错:浮点数越界
CPU的内部寄存器(硬件),OS就会识别到CPU内有报错啦–>1.谁干的?
2.是什么报错?
(OS–>构建信号)—>目标进程发送信号–>目标进程在合适的时候–>终止进程

int main(int argc, char *argv[])
{
    int* p=nullptr;
    *p=1;
    return 0;
}

在这里插入图片描述
越界&&野指针:我们在语言层面上使用的地址(指针),
其实都是虚拟地址—>物理地址—>物理内存–>读取对应的数据和代码的
如果虚拟地址有问题,地址转化的工作是由(MMU(硬件)+页表(软件)),转化过程就会引起问题,表现在硬件MMU上—>OS发现硬件出现了问题

进程崩溃的本质是该进程收到了异常的信号!

为什么呢?

因为硬件异常,而导致OS向目标进程发送信号,进而导致进程终止的现象!

崩溃了,一定会导致进程终止吗??—>不一定!!

int main(int argc, char *argv[])
{   
    signal(3,handler);
    for(int i=1;i<32;i++)
    {
        signal(i,handler);
    }
    sleep(3);
    cout<<"进程已经设置完了!"<<endl;
    int a=10;
    a/=0;
    return 0;
}

在这里插入图片描述

总结

  • 上面所说的所有信号的产生,最终都要有OS来进行执行,为什么?—>OS是进程的管理者!
  • 信号的处理是否是被立即处理的?–>在合适的时候
  • 信号如果不是被立即处理,那么信号是否需要暂时被进程记录下来?记录在哪里最合适呢?
  • 一个进程在没有收到信号的时候,能否知道,自己应该对合法信号作何处理呢?
  • 如何理解OS向进程发送信号?能否描述一下完整的发送处理过程?

core dump字段

这是在我带大家了解进程控制时留的小尾巴,因为当时没有办法去讲!
[ Linux ] 进程控制(下)----进程等待与进程程序替换

在这里插入图片描述

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

在这里插入图片描述

man 7 signal

在这里插入图片描述我们可以看到不同的信号有不同的Action,有的是Term直接终止了。。。。
那么Core是什么呢?

ulimit -a

在这里插入图片描述
线上生产中,core字段默认是0,也就是关闭的!
打开

ulimit -c 100000

在这里插入图片描述
打开core字段后,我们发现core dump标记位直接被设置为1了

并且还多了core.28389这个大文件

28389就是我们引起进程发生错误的id号!

在这里插入图片描述
core dump会把进程在运行中,对应得异常上下文数据,core dump到磁盘上,方便调试的!—>并且将status中的core dump标记位置为1!
在这里插入图片描述
然后我们进行gdb调试,运行该文件,我们就直接定位到出现异常的那一行了!!!

但是,core dump位一般是关闭的,因为他所占内存大小太大了!

五、信号产生中

阻塞信号

1.信号其他相关常见概念

  • 实际执行信号的处理动作称为信号递达(Delivery)。
  • 信号从产生到递达之间的状态,称为信号未决(Pending)。
  • 进程可以选择阻塞(block)某个信号。
  • 被阻塞的信号产生时将保持在未决状态,知道进程解除对此信号的阻塞,才执行递达的操作。
  • 注意,阻塞和忽略是不同的,只要信号被阻塞就不会被递达,而忽略实在递达之后可选的一种处理动作!

2. 在内核中的表示

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

在这里插入图片描述

  • 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到该信号递达才清除该标志。在上图的例子中,SIGHUP信号为阻塞也未产生过,当他递达时执行默认处理动作。
  • SIGINT信号产生过,但是正在被阻塞,所以暂时不能抵达。虽然他的处理动作是忽略,但在没有接触阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后在解除阻塞。
  • SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函sighandler。如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次。

3.sigset_t

从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞可以使用想的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的"有效"和"无效"的含义是该信号是否被阻塞,而在未决信号集中"有效"和"无效"
的含义是该信号是否处于未决状态。稍后我来带大家了解信号集的各种操作。阻塞信号集也叫作当前进程的信号屏蔽字(Signal Mask),这里的"屏蔽"应该理解为阻塞而不是忽略。

4.信号集操作函数

sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释,比如用printf直接打印sigset_t变量是没有意义的.

#include <signal.h>
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置位,表示 该信号集的有效信号包括系统支持的所有信号。
  • 注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信。

函数的使用

static void showPending(sigset_t *pendings)
{
    for (int sig = 1; sig < 32; sig++)
    {
        if (sigismember(pendings, sig))
        {
            cout << '1';
        }
        else
            cout << '0';
    }
    cout << endl;
}
int main(int argc, char *argv[])
{   
    sigset_t bsig,obsig;
    sigemptyset(&bsig);
    sigemptyset(&obsig);
    for(int sig=1;sig<=31;sig++)
    {
        //2.给2号信号设置自定义选项
        signal(sig,handler);
        //3.屏蔽二号信号

        //3.1添加二号信号到信号屏蔽字中
        sigaddset(&bsig,sig);
    }
    //3.2设置用户级的信号屏蔽字到内核中,让当前进程屏蔽到二号信号
    sigprocmask(SIG_SETMASK,&bsig,&obsig);

    //1.不断的获取当前进程的pending信号集
    sigset_t pendings;
    int cnt=0;
    while(true)
    {
        //1.1清空信号集
        sigemptyset(&pendings);
        //1.2获取当前进程(谁调用,获取谁)的pendings信号集
        if(sigpending(&pendings)==0)
        {
            showPending(&pendings);
        }
        sleep(1);
    }
    return 0;
}

在这里插入图片描述
在这里插入图片描述
while(true)中加入部分代码

while(true)
    {
        //1.1清空信号集
        sigemptyset(&pendings);
        //1.2获取当前进程(谁调用,获取谁)的pendings信号集
        if(sigpending(&pendings)==0)
        {
            showPending(&pendings);
        }
        sleep(1);
        cnt++;
        if(cnt==10)
        {
            cout<<"解除对所有信号的block "<<endl;
            sigprocmask(SIG_SETMASK,&obsig,nullptr);
        }
    }

在这里插入图片描述

信号的捕捉

进程处理信号,不是理解处理的。—>在合适的时候!---->是什么时候呢?

当当前进程从内核态,切换回用户态的时候,进行信号的检测与处理!
在这里插入图片描述

六、信号处理后

在这里插入图片描述
在这里插入图片描述

进程的信号在被合适的时候处理----从内核态返回到用户态的时候—>检测----处理

  1. 如何理解内核态和用户态

  2. 进程的生命周期中,会有很多次机会去陷入内核(中断,陷阱,系统调用,异常…),一定会存在很多次的机会进行内核态返回用户态!

对信号处理使用自定义方法,我们执行的是用户代码!!!只能用用户态的身份去执行的!—>因为这部分代码是用户写的!!—>如果写的是一段恶意的代码呢!!!

signaction

在这里插入图片描述


#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;

void handler(int signo)
{
    cout << "获取到一个信号,信号的编号是: " << signo << endl;
}
int main()
{
    struct sigaction act, oact;
    act.sa_handler = handler;//自定义方法
    // act.sa_handler=SIG_IGN;//忽略方法
    // act.sa_handler=SIG_DFL;//默认处理方法
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaction(2, &act, &oact);
    while (true)
    {
        cout << "main running" << endl;
        sleep(1);
    }
    return 0;
}

当某个信号的处理函数被调用的时候,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么他会被阻塞到当前处理结束为止。如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。sa_flags字段包含一些选项,本章的代码都把sa_flags设为0,sa_sigaction是实时信号的处理函数,此处我们不做研究!

void handler(int signo)
{
    cout << "获取到一个信号,信号的编号是: " << signo << endl;
    sigset_t pending;
    //增加handler信号的处理时间,永远都会在处理二号信号
    while (true)
    {
        sigpending(&pending);
        for (int i = 1; i <= 31; i++)
        {
            if (sigismember(&pending, i))
                cout << "1";
            else
                cout << "0";
        }
        cout << endl;
        sleep(1);
    }
}
int main()
{
    struct sigaction act, oact;
    act.sa_handler = handler;//自定义方法
    // act.sa_handler=SIG_IGN;//忽略方法
    // act.sa_handler=SIG_DFL;//默认处理方法
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaction(2, &act, &oact);
    while (true)
    {
        cout << "main running" << endl;
        sleep(1);
    }
    return 0;
}

在这里插入图片描述


总结

(本章完!)

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

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

相关文章

【Java】才疏学浅·小石Java问道之路

大家好&#xff0c;这里是喜爱编程&#xff0c;又热爱生活的小石~———— ————个人情报昵称&#xff1a;小石&#xff08;起源于姓氏啦~&#xff09;破壳日&#xff1a;4月12日身高&#xff1a;1 m ↑技术基础&#xff1a;c node.js mysql 爱好&#xff1a;上网冲浪 听…

【学习笔记45】JavaScript的分页效果

highlight: an-old-hope 一、分页效果 (一) 首次打开页面 从数组内截取部分数据展示调整页码信息为&#xff1a;当前页 / 总页码处理按钮 3.1 如果当前在第一页&#xff0c;禁用上一页按钮(添加类名disable)3.2 如果当前页在最后一页(当前页 总页码),禁用下一页按钮(添加类名…

SpringCloud微服务实践之六 Feign最佳实践(抽取)

传统Feign面临的问题&#xff1a; 1、每个子项目都要写所要调用服务的pojo 2、每个子项目都要写所要调用服务的feign client客户端 优化思路&#xff1a;由提供服务服务的子项目统一归集代码&#xff0c;统一对外提供接口服务、Feign子项目统一管理服务远程调用、 将FeignClien…

【菜菜的sklearn课堂笔记】逻辑回归与评分卡-用逻辑回归制作评分卡-分箱

视频作者&#xff1a;菜菜TsaiTsai 链接&#xff1a;【技术干货】菜菜的机器学习sklearn【全85集】Python进阶_哔哩哔哩_bilibili 分训练集和测试集 from sklearn.model_selection import train_test_split X pd.DataFrame(X) y pd.DataFrame(y)Xtrain,Xtest,Ytrain,Ytest …

DSP-FIR滤波器设计

目录 Gibbs现象:用三角函数逼近间断点: Gibbs现象特点: 常见窗函数&#xff1a; 窗函数的主要频谱参数: 矩形窗(Rectangular)&#xff1a; 汉宁窗(Hanning)&#xff1a; 汉明窗(Hamming)&#xff1a; 布莱克曼窗(Blackman)&#xff1a; 窗函数之间的性能对比&#xff…

Script file ‘F:.....\pip-script.py‘ is not present 原因及解决办法

一 报错类型 二 原因 可能我们使用pip install --upgrade pip或者conda安装一下包时因为网络原因导致只是卸载旧版本而未安装。 三 解决策略 3.1 Anaconda 切换到你的anaconda安装目录并进入Scripts文件夹内(D:\Apps\anaconda3\Scripts) 运行以下代码&#xff1a; conda i…

【Kafka】Kafka基础架构及相关概念

文章目录前言一、Kafka基础知识二、Kafka分区副本参考前言 在以前的定义中&#xff0c;Kafka被定义为一个分布式的基于发布/订阅模式的消息队列&#xff08;Message Queue&#xff09;&#xff0c;主要应用于大数据实时处理领域&#xff0c;类似的产品主要有ActiveMQ、RabbitM…

flutter useRootNavigator属性的作用

useRootNavigator 用于确定是否将对话框推到距给定上下文最远或最接近给定上下文的Navigator 问题&#xff1a; 在使用showDatePicker的时候&#xff0c;android手机如果侧滑返回的时候&#xff0c;页面会关闭&#xff0c;showDatePicker弹出的dailog缺没有关闭。 使用如下…

【学习笔记42】操作DOM

操作DOM一、操作DOM1、步骤2、创建元素节点3、创建文本节点4、增加dom(添加到指定父节点的最后)5、增加dom(添加到指定父节点的最后)6、增加dom(添加到父节点的最前边)7、删除DOM8、修改某一个节点二、克隆DOM1、说明2、复制(克隆)一个LI三、获取元素尺寸(占地面积)四、获取浏览…

WordPress怎么禁止用户使用HTML标签,自动过滤HTML代码?

WordPress怎么禁止用户使用HTML标签&#xff0c;自动过滤HTML代码&#xff1f;出于安全考虑WordPress默认禁止角色为作者的用户写文章时直接添加HTML代码&#xff0c;包括读者留言时也是不允许的。如果想开放此限制&#xff0c;允许作者撰写文章和读者留言时添加HTML代码&#…

Java项目——博客系统(前后端交互)

项目展示 项目说明 使用servlet&#xff0c;实现前后端交互&#xff0c;通过jdbc和mysql数据库进行数据传输&#xff0c;使得可以将用户信息和博客列表存储在数据库中&#xff0c;实现真正的服务器&#xff0c;客户端&#xff0c;数据库三者的交互 项目代码 数据库 在sql数…

进阶的风控策略篇:如果筛选最佳策略帮我们锁定优质客群

在番茄风控往期的内容中&#xff0c;我们一直在跟大家介绍风控策略干货内容&#xff0c;相关内容包括&#xff1a; ①风控的拒绝捞回策略 ②多规则的策略筛选 ③策略的调优 ④策略的开发与应用 … 策略相关的内容可谓干货满满&#xff0c;比如关于策略开发与应用的内容上&#…

SpringBoot SpringBoot 原理篇 1 自动配置 1.15 自动配置原理【1】

SpringBoot 【黑马程序员SpringBoot2全套视频教程&#xff0c;springboot零基础到项目实战&#xff08;spring boot2完整版&#xff09;】 SpringBoot 原理篇 文章目录SpringBootSpringBoot 原理篇1 自动配置1.15 自动配置原理【1】1.15.1 看源码了1.15.2 Import({AutoConfig…

初识图学习

初识图学习 一、简单图的基础 什么是图 七桥问题的定义是&#xff1a;一个步行者怎样才能不重复&#xff0c;不遗漏的一次走完七座桥。最后回到出发点。 当年&#xff0c;大数学家在解答七桥问题的同时&#xff0c;也开创了数学的一个新分支图论。 可以毫不夸张的说&#xff…

半小时制作简单版澳大利亚导游地图,太简单了,你也可以

目录 1 前言 2 QQ扫码注册一个账号 3 编辑景区 3.1 新建一个景区 3.2 增加景点 4 预览 5 申请管理员审核 6 看一下发布后的效果 1 前言 不少景区为了提升游客旅游体验&#xff0c;需要制作自己的导游地图&#xff0c;游客扫一下二维码就可以看到景区全貌和景点介绍。制作这…

销售词汇Sell In、Sell Through、Sell Out辨析

原文出处&#xff1a;https://zhuanlan.zhihu.com/p/89563704 销售的过程其实是一个货物和资金双向流动的过程&#xff0c;货物从厂家流向消费者&#xff0c;资金从消费者流向厂家。 但是大多数情况下&#xff0c;厂家是没办法直接把货物卖给消费者的&#xff0c;或者说厂家是没…

虚拟机安装openEuler/MobaXterm工具登录系统

✨✨个人主页:沫洺的主页 &#x1f4da;&#x1f4da;系列专栏: &#x1f4d6; JavaWeb专栏&#x1f4d6; JavaSE专栏 &#x1f4d6; Java基础专栏&#x1f4d6;vue3专栏 &#x1f4d6;MyBatis专栏&#x1f4d6;Spring专栏&#x1f4d6;SpringMVC专栏&#x1f4d6;SpringBoot专…

一个用C#开发的操作系统的开源项目

自从C#的AOT编译机制发布以来&#xff0c;有趣的项目越来越多&#xff0c;今天给大家推荐一个开源项目&#xff0c;用C#开发的64位操作系统。 文章目录项目简介项目源码开发环境虚拟机与裸机硬件支持功能列表完善的编译、安装文档操作系统界面项目地址项目简介 这是一个使用.N…

SpringBoot SpringBoot 原理篇 2 自定义starter 2.2 IP计数业务功能开发【自定义starter】

SpringBoot 【黑马程序员SpringBoot2全套视频教程&#xff0c;springboot零基础到项目实战&#xff08;spring boot2完整版&#xff09;】 SpringBoot 原理篇 文章目录SpringBootSpringBoot 原理篇2 自定义starter2.2 IP计数业务功能开发【自定义starter】2.2.1 大概看看别人…

阿里云服务器安装mysql8

1. 安装前准备 查看是否安装&#xff1a; rpm -qa | grep mysql移除不想要的版本&#xff1a; yum remove 名称查找关于mysql的所有文件&#xff08;配置文件&#xff09; find / -name mysql ##或者 whereis mysql删除配置文件 rm -rf 文件最后需要注意的是&#xff1a;卸载后…