Linux篇:信号

news2024/12/27 11:53:37

一、信号的概念:
①进程必须识别+能够处理信号,信号没有产生,也要具备处理信号的能力---信号的处理能力属于进程内置功能的一部分
②进程即便是没有收到信号,也能知道哪些信号该怎么处理。
③当进程真的受到了一个具体的信号的时候,进程可能并不会立即处理这个信号,而会在合适的时候处理。
④一个进程必须当信号产生到信号开始被处理,就一定会有时间窗口,要求进程具有临时保存哪些信号已经发生了的能力。

一点五、详谈ctrl c:

1、Ctrl c为什么能够杀掉我们前台进程呢?
Linux中一次登录中,一个终端一般会配上一个bash进程,每一次登录只允许一个进程为前台进程,可允许多个进程是后台进程。而前后台进程的区别,取决于谁来获取键盘输入(获取键盘输入的为前台进程)。
ctrl c本质是被进程解释成为收到了信号(是2号信号,默认动作就是终止自己)。

2、从硬件的角度,谈谈键盘数据是如何输入给内核的?ctrl c又是如何变成信号的?

a.键盘读取本质上是把外设数据拷贝到键盘文件对应的缓冲区中(输入的过程),操作系统可以通过库函数(如read/scanf/fget)通过文件把数据读到对应的进程缓冲区/用户缓冲区中。
b.键盘在硬件上可以给CPU发送硬件中断,每一个中断都有中断号,通过各中断单元向CPU特定针脚发送中断后,CPU会保存相应的中断号,从而完成了键盘向CPU,通知其数据已就绪的任务(显示器,网卡等设备同理)。
c.在操作系统内,都会在操作系统开机启动的时候,形成一张中断向量表,存储直接访问外设方法(主要是磁盘,还有显示器键盘等设备)的地址。其中,中断表的下标是中断号。
CPU中的寄存器保存数据的本质上是对CPU寄存器充放电的过程。

对于Ctrl c这样的组合键呢,OS会判断输入的是数据还是控制,将ctrl c这样的预定义信号转化为2号信号发送给进程。

综上所述,操作系统就是通过不断向外设接收中断,从而处理外设的。而我们学习的信号就是用软件的方式对进程模拟的硬件中断。
(信号是进程之间事件异步通知的一种方式,属于软中断。)

二、信号的产生:

1. 键盘组合键:
ctrl c   发送2号信号。
ctrl \   发送3号信号。
ctrl z   发送19号信号。
不是所有的信号都是可以被signal捕捉的,9和19号无法被捕捉。这两个进程都跟进程的执行有关。

2. kill命令:kill -signo pid

3、系统调用接口: 

设置对特定进程的自定义处理方法(信号编号,自定义动作)(返回函数指针类型)

①修改特定进程对于信号的处理动作:

//mysignal.cc

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>

using namespace std;

//int: 收到了哪一个信号
void myhandler(int signo)
{
    cout << "process get a signal: " << signo << endl;
    // exit(1);
}

int main()
{
    signal(SIGINT, myhandler); // 只需要设置一次,往后都有效!
    signal(3, myhandler);
    signal(19, myhandler); // 无法被捕捉 
    // 信号的产生和我们自己的代码的运行是异步的

    while(true)
    {
        cout << "I am a crazy process" << endl;
        sleep(1);
    }
}

② 给任意进程发任意信号:

 

//mykill.cc

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

using namespace std;

void Usage(string proc)
{
    cout << "Usage:\n\t" << proc << " signum pid\n\n";
}

// mykill signum pid
int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    int signum = stoi(argv[1]);
    pid_t pid = stoi(argv[2]);

    int n = kill(pid, signum);
    if(n == -1)
    {
        perror("kill");
        exit(2);
    }

    return 0;
}

 ③给调用者发送指定信号:

 

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

using namespace std;

int main()
{
    int cnt = 5;
    while(true)
    {
        cout << "I am a process, pid: " << getpid() << endl;
        sleep(1);
        cnt--;
        if(cnt == 0) raise(2); // 相当于kill(getpid(), sig)
    }
}

 ④引起一个正常进程进行终止:(跟直接kill -6 pid的效果不一样)

#include <iostream>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>

using namespace std;

int main()
{
    int cnt = 5;
    while(true)
    {
        cout << "I am a process, pid: " << getpid() << endl;
        sleep(1);
        cnt--;
        if(cnt == 0) abort(); // 效果好和kill(getpid(), 6)不一样:不仅发送6号信号,还强行让进程终止。
    }
}

4、异常:
为什么除0/野指针会让进程崩溃呢?
为什么除0/野指针会给进程发信号呢?

①当前进程在执行时发生除0操作时,CPU中状态寄存器中的溢出标志位由无效改为有效。
进程在被调度期间,CPU寄存器里储存的都是当前进程的上下文。一旦出现异常,对应的状态寄存器由0置1,故该进程是否出异常与进程切换无关,不会影响其他进程。该设计从调度和硬件层面上保证了进程之间的独立性。
任何异常只会影响进程本身,不会波及到操作系统。但操作系统必须要知道该进程出异常,因为CPU是硬件,操作系统是硬件的管理者。故操作系统向进程发信号,使该进程崩溃。

②该进程指向进程地址空间通过其页表访问其代码和数据。
前几篇文章我们谈过,页表是一个软件结构,通过虚拟与物理地址的关系映射找到物理内存。为了提高效率,页表查表的过程并不是由软件完成,而是由一个MMU(内存管理单元)来完成的(MMU在当代芯片当中已经被集成在了CPU内部)。
访问野指针时,虚拟地址到物理地址转换失败,将把转化失败的地址放入CPU中的某寄存器中。CPU内部会出现硬件报错,这也能被OS识别到。
当然,如果进程不崩溃,那它就要一直被调度运行。从而实现在上层给用户更大的宽容度。

//mysignal.cc

#include <iostream>
#include <unistd.h>
#include <signal.h>

using namespace std;

void handler(int signo)
{
    cout << "...get a sig, number: " << signo << endl; // 仅起到捕捉打印作用
    exit(1);
}

// 此处我们会观察到信号一直被触发
int main()
{
    // signal(SIGFPE, hand  ler);
    signal(SIGSEGV, handler);
    // cout << "div before" << endl;
    cout << "point error before" << endl;

    sleep(5);

    // int a = 10;
    // a /= 0; // 异常:Floating point exception(信号8)

    int *p = nullptr; // 野指针
    *p = 100; //段错误: Segmentation fault(信号11)
    // cout << "div after" << endl;
    cout << "point error after" << endl;

    sleep(1);
    return 0;
}

5. 软件条件:异常不只会由硬件产生。

①软件资源不就绪的情况:

#include <iostream>
#include <unistd.h>
#include <signal.h>

using namespace std;

void handler(int signo)
{
    cout << "...get a sig, number: " << signo << endl; // 仅起到捕捉打印作用
    exit(1);
}

// 此处我们会观察到信号一直被触发
int main()
{
    signal(SIGSEGV, handler);

    sleep(5);


    char buffer[1024];
    int n = 1024;
    n = read(4, buffer, n); // 4号文件默认不打开
    printf("n = %d\n", n);
    perror("read"); // read: Bad file descriptor

    sleep(1);
    return 0;
}

②特殊事件:闹钟(返回值为剩余时间)。

#include <iostream>
#include <unistd.h>
#include <signal.h>

using namespace std;

void work()
{
    cout << "print log..." << endl;
}

void handler(int signo)
{
    work();
    cout << "...get a sig, number: " << signo << endl; // 仅起到捕捉打印作用
}

// 此处我们会观察到信号一直被触发
int main()
{
    signal(SIGALRM, handler);
    int n = alarm(5); // 闹钟不是异常,只响一次

    while(1)
    {
        cout << "proc is running..." << endl;
        sleep(1);
    }
    
    sleep(1);
    return 0;
}

③再谈进程等待: 

 

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

using namespace std;

// 再谈进程等待
int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //child
        int cnt = 500;
        while(cnt)
        {
            while(cnt)
            {
                cout << "i am a child prcess, pid: " << getpid() << " cnt: " << cnt << endl;
                sleep(1);
                cnt--; 
            }

            exit(0);
        }

        // father
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if(rid == id)
        {
            cout << "child quit info, rid: " << rid << " exit code: " << 
                ((status>>8)&0xFF) << "exit signal: " << (status&0x7F) << 
                " core dump: " << ((status>>7)&1) << endl;
        }
    }
}
// Makefile

mysignal:mysignal.cc
	g++ -o $@ $^ -g -std=c++11
.PHPNY:clean
clean:
	rm -f mysignal

虚拟机在进行除零错误的时候,一段程序崩溃,对应的当前目录下会产生一个临时文件xxx.core,而默认云服务器上面的core功能是被关闭的(保证服务重启的功能一直有效,会形成code dump文件来冲击磁盘,影响服务)。打开系统的core domp功能,一旦进程出异常,OS会将进程在内存中的运行信息dump(转储)到进程的当前目录(磁盘)中,形成core.pid文件:核心转储(core dump)。这样做能够直接复现问题之后,直接定位到出错行。这种先运行再生成core-file称为事后调试。

总结:这些都是信号产生的方式,但是无论信号如何发生,最终一定是OS发送给进程的。因为OS是进程的管理者。

三、信号的发送与保存:

1、对于普通信号而言,对于进程而言,自己有还是没有,收到哪一个信号,是给进程的PCB发。

2、OS用比特位的位置来表示是哪个信号:用01来描述信号,用位图来管理信号。
①比特位的内容是0还是1,表示是否收到。
②比特位的位置(第几个),表示信号的编号。
③所谓“发信号”,本质就是OS去修改task_struct的信号位图对应的比特位。

2、信号保存的原因:进程收到信号之后可能不会立即处理这个信号。信号不会被处理,就要有一个时间窗口。

3、信号的范围[1,31],每一种信号都要有自己的一种处理方法。Handler数组中的函数指针指向操作系统提供的默认方法方法。如果用户自己提供了方法,那么,就将用户提供方法的地址填入该数组(signal方法)。

4、阻塞信号:

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

②内核表示:

pending表表示,当前进程是否信收到了信号以及收到了哪些信号。
block表表示,进程信号是否被屏蔽。
hander表表示,每种信号对应的处理关系。
我们通过以上所述的三张表和两张位图,和一个函数指针数组就可以,实现对普通信号的记录、保存相关的管理工作了。

 · 调用函数sigpromask可以读取或更改进程的信号屏蔽字(阻塞信号集)。

③s igset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态。
④信号级操作函数:
#include <signal.h>
int sigemptyset(sigset_t *set); // 初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。
int sigfillset(sigset_t *set); // 初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号。
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
// 注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号
 · sigprocmask: 读取或更改进程的信号屏蔽字 ( 阻塞信号集 )

 ·  sigpending:读取当前进程的未决信号集。

#include <iostream>
#include <unistd.h>
#include <signal.h>

using namespace std;

void PrintPending(sigset_t &pending)
{
    for (int signo = 31; signo >= 1; signo--)
    {
        if (sigismember(&pending, signo))
        {
            cout << "1";
        }
        else
        {
            cout << "0";
        }
    }
    cout << "\n\n";
}

void handler(int signo)
{
    cout << "catch a signo: " << signo <<endl;
}

int main()
{
    // 0、对2号信号进行自定义捕捉
    signal(2, handler);
    // 1、先对2号信号进行屏蔽——数据预备
    sigset_t bset, oset; // 在用户区栈上开辟的空间
    sigemptyset(&bset);
    sigemptyset(&oset);
    sigaddset(&bset, 2); // 此处还未将2号信号屏蔽,因为并没有将数据设置进入进程的task_struct

    // 1.2 调用系统调用,将数据设置进内核
    sigprocmask(SIG_SETMASK, &bset, &oset); // 此处已将2号信号屏蔽

    // 2、重复打印当前进程的pending 00000000000000000000000000000000
    sigset_t pending;
    int cnt = 0;
    while (true)
    {
        // 2.1 获取
        int n = sigpending(&pending);
        if (n < 0)
            continue;
        // 2.2 打印
        PrintPending(pending);

        sleep(1);
        cnt++;
        // 2.3 解除阻塞
        if (cnt == 20)
        {
            cout << "unblock 2 signo" << endl;
            sigprocmask(SIG_SETMASK, &oset, nullptr);
        }
    }
    // 3、发送2号 00000000000000000000000000000010

    return 0;
}
#include <iostream>
#include <unistd.h>
#include <signal.h>

using namespace std;

void PrintPending(sigset_t &pending)
{
    for (int signo = 31; signo >= 1; signo--)
    {
        if (sigismember(&pending, signo))
        {
            cout << "1";
        }
        else
        {
            cout << "0";
        }
    }
    cout << "\n\n";
}

void handler(int signo)
{
    cout << "catch a signo: " << signo <<endl;
}

int main()
{
    // 4、 思考:如果我们将所以信号全部屏蔽,那么信号不就都不会处理了吗?9和19号信号不仅不可被捕捉,也不可被屏蔽。
    sigset_t bset, oset;
    sigemptyset(&bset);
    sigemptyset(&oset);
    for(int i = 1; i <= 31; i++)
    {
        sigaddset(&bset, i); // 此处还未将所有信号屏蔽,因为并没有将数据设置进入进程的task_struct
    }
    sigprocmask(SIG_SETMASK, &bset, &oset);

    sigset_t pending;
    while(true)
    {
        // 2.1 获取
        int n = sigpending(&pending);
        if (n < 0)
            continue;
        // 2.2 打印
        PrintPending(pending);

        sleep(1);
    }

    return 0;
}

 四、信号的处理:

1、信号的处理方式:
①默认动作。
②忽略。
③自定义动作(信号的捕捉)。

2、信号是什么时候被处理的?
 · 当我们的进程从内核态返回到用户态的时候,进行信号的检测与处理。典型的例子就是调用系统调用(此时操作系统是自动会做身份切换的)。
 · CPU可以响应来自外部的中断,让操作系统执行。它自己也可以在它自己内部直接产生中断,称为int 80:x86英特尔CPU下,从用户态陷入内核态的汇编语句。

3、信号是如何被处理的?(重谈地址空间3)
 · 内核空间映射的是操作系统的代码和数据。
和用户空间一样的是,内核空间也需要内核级页表和操作系统中的代码和数据建立映射。(不过和用户空间不一样的是,内核空间和物理内存中操作系统的代码和数据也可以直接建立映射。)
 · 假设有多个进程,有几个进程就有几份用户级页表---因为进程具有独立性。而内核级页表只有一份。所以每一个进程看到的3~4GB的东西都是一样的。所以进程再怎么切换,3~4GB空间的内容是不变的。
站在进程角度,在调用系统调用方法时,相当于在自己的地址空间里调用该方法,调用后直接返回到自己的地址空间里。
站在操作系统角度,任何一个时刻都有进程执行,用户想执行操作系统的代码就可以随时执行。
操作系统的本质:基于时钟中断的一个死循环。计算机硬件中有一个时钟芯片,每个很短的时间,向计算机发送时钟中断。当操作系统检测到时钟信号到来时,CPU就会执行时钟中断所对应的方法。
 · CPU有一个ecs寄存器(代码段寄存器)。当它最低的两个比特位(低两位权限位)为00时则为内核态,允许访问操作系统的代码和数据;为11时,则为用户态,只能访问用户自己的代码。CPU为用户提供了int 80(陷入内核)方法来更改CPU的工作级别。

上图整个过程进行4次身份切换,1次信号检测。 

4、信号捕捉:

//mysignal.cc

#include <iostream>
#include <cstring>
#include <ctime>
#include <unistd.h>
#include <signal.h>

using namespace std;

//问题1: pending位图,什么时候从1->0. 执行信号捕捉方法之前,先清0,在调用
//问题2: 信号被处理的时候,对应的信号也会被添加到block表中,防止信号捕捉被嵌套调用

 void PrintPending()
 {
     sigset_t set;
     sigpending(&set);

     for (int signo = 1; signo <= 31; signo++)
     {
         if (sigismember(&set, signo))
             cout << "1";
         else
             cout << "0";
     }
     cout << "\n";
 }

 void handler(int signo)
 {
     cout << "catch a signal, signal number : " << signo << endl;
     while (true)
     {
         PrintPending();
         sleep(1);
     }
 }

 int main()
 {
      struct sigaction act, oact;
      memset(&act, 0, sizeof(act));
      memset(&oact, 0, sizeof(oact));

      sigemptyset(&act.sa_mask);
      sigaddset(&act.sa_mask, 1);
      sigaddset(&act.sa_mask, 3);
      sigaddset(&act.sa_mask, 4);
      act.sa_handler = handler; // SIG_IGN SIG_DFL
      sigaction(2, &act, &oact);

      while (true)
      {
          cout << "I am a process: " << getpid() << endl;
          sleep(1);
      }

     return 0;
 }

5、可重入函数:

现象:insert函数被main和handler执行流重复进入(重入)了。导致节点丢失,内存泄漏。这被称为不可重入函数,否则为可重入函数。(目前学到的大部分函数都是不可重入的。)

6、volatile关键字:防止编译器过度优化,保持内存的可见性:

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <signal.h>

using namespace std;

volatile int flag = 0;

void handler(int signo)
{
    cout << "catch a signal: " << signo << endl;
    flag = 1;
}

int main()
{
    signal(2, handler);
    // 在优化条件下, flag变量可能被直接优化到CPU内的寄存器中
    while(!flag); // flag 0, !falg 真

    cout << "process quit normal" << endl;
    return 0;
}

7、SIGCHID:子进程退出时,会主动向父进程发送SIGCHID17号信号。
 · 子进程在进行等待的时候,我们可以采用基于信号的方式进行等待。
 · 等待的好处:
①获取子进程的退出状态,释放子进程的僵尸。
②虽然不知道父子谁先进行,但是我们清楚,一定是父进程最后退出。
 · 不过还是要调用wait/waitpid这样的接口,父进程必须保证自己是一直在运行的。

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

using namespace std;

// 必须要等待吗?必须要调用wait吗?
// void handler(int signo)
// {
//     sleep(5);
//     pid_t rid;
//     while ((rid = waitpid(-1, nullptr, WNOHANG)) > 0)
//     {
//         cout << "I am proccess: " << getpid() << " catch a signo: " << signo << "child process quit: " << rid << endl;
//     }
// }

int main()
{
    signal(17, SIG_IGN); // SIG_DFL -> action -> IGN

    // srand(time(nullptr));
    // signal(17, handler);
    // 如果我们有10个子进程同时退出呢?
    // 如果退出一半呢?
    for (int i = 0; i < 10; i++)
    {
        pid_t id = fork();
        if (id == 0)
        {
            while (true)
            {
                cout << "I am child process: " << getpid() << ", ppid: " << getppid() << endl;
                sleep(5);
                break;
            }
            cout << "child quit!" << endl;
            exit(0);
        }
        // sleep(rand()%5+3);
        sleep(1);
    }
    // father
    while (true)
    {
        cout << "I am father process: " << getpid() << endl;
        sleep(1);
    }

    return 0;
}

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

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

相关文章

2021实战面试

1、Rem , em , px , % , vw 之间的区别 PX: px像素&#xff08;Pixel&#xff09;。相对长度单位。像素px是相对于显示器屏幕分辨率而言的。 em: 1,子元素字体大小的em是相对于父元素字体大小 2,元素的width/height/padding/margin用em的话是相对于该元素的font-size rem:1rem是…

【回眸】Tessy 单元测试软件使用指南(三)怎么打桩和指针测试

目录 前言 Tessy 如何进行打桩操作 普通桩 高级桩 手写桩 Tessy单元测试之指针相关测试注意事项 有类型的指针&#xff08;非函数指针&#xff09;&#xff1a; 有类型的函数指针&#xff1a; void 类型的指针&#xff1a; 结语 前言 进行单元测试之后&#xff0c;但凡…

免费且强大卸载软件工具-Geek Uninstaller

Geek Uninstaller是一款用于Windows操作系统的免费卸载软件。它提供了一种比Windows内置卸载工具更彻底的卸载程序的方法。界面简单没有广告&#xff0c;操作也十分的简单。 特点 完全的程序卸载&#xff1a;Geek Uninstaller 被设计为彻底卸载程序&#xff0c;包括删除剩余…

YOLOv8改进《目标对象计数》多任务实验:深度集成版来了!支持自定义数据集训练自定义模型

💡该教程为改进YOLO专栏,属于《芒果书》📚系列,包含大量的原创改进方式🚀 💡🚀🚀🚀内含改进源代码 按步骤操作运行改进后的代码即可💡更方便的统计更多实验数据,方便写作 YOLOv8改进《目标对象计数》多任务实验:深度集成版来了!支持自定义数据集训练自定…

springboot发送邮件,内容使用thymeleaf模板引擎排版

springboot发送邮件,内容使用thymeleaf模板引擎排版 1、导入jar包2、yml设置3、收件人以及收件信息设置4、发邮件service5、模版页面6、controller 1、导入jar包 <!--发送邮件--><dependency><groupId>org.springframework.boot</groupId><artifac…

使用Axure RP结合内网穿透工具制作本地静态web页面并实现公网访问

作者简介&#xff1a; 懒大王敲代码&#xff0c;正在学习嵌入式方向有关课程stm32&#xff0c;网络编程&#xff0c;数据结构C/C等 今天给大家讲解使用Axure RP结合内网穿透工具制作本地静态web页面并实现公网访问&#xff0c;希望大家能觉得实用&#xff01; 欢迎大家点赞 &am…

订单系统设计-状态机

1. 状态机 1.1 状态机简介 状态机是有限状态自动机的简称&#xff0c;是现实事物运行规则抽象而成的一个数学模型。 有限状态机一般都有以下特点&#xff1a; 可以用状态来描述事物&#xff0c;并且任一时刻&#xff0c;事物总是处于一种状态&#xff1b;事物拥有的状态总数…

线程安全集合类

文章目录 1. ConcurrentHashMap2. LinkedBlockingQueue 阻塞队列3. ConcurrentLinkedQueue4. CopyOnWriteArrayList JDK1.7 hashmap采用数组加链表头插的方式&#xff0c;在扩容时会出现循环死链问题&#xff0c;A->B->C扩容后C->B->A AB BA出现循环死链。 1. Conc…

Dockerfile的介绍和使用

什么是dockerfile? Dockerfile是一个包含用于组合映像的命令的文本文档。可以使用在命令行中调用任何命令。 Docker通过读取Dockerfile中的指令自动生成映像。 docker build命令用于从Dockerfile构建映像。可以在docker build命令中使用-f标志指向文件系统中任何位置的Dockerf…

【Monitor, Maintenance Operation, Script code/prgramme】

Summary of M,M&O,Program JD) Monitor & M&O Symbio信必优) Job chance/opportunities on Dec 12th, 20231.1) Content 招聘JD job description:1.2) suggestions from Ms Liang/Winnie on Wechat app1.3) Java微服务是什么&#xff1f;1.3.1) [URL Java 微服务](…

yarn系统架构与安装

1.1 YARN系统架构 YARN的基本思想是将资源管理和作业调度/监视功能划分为单独的守护进程。其思想是拥有一个全局ResourceManager (RM)&#xff0c;以及每个应用程序拥有一个ApplicationMaster (AM)。应用程序可以是单个作业&#xff0c;也可以是一组作业。 一个ResourceManage…

数据结构与算法之美学习笔记:36 | AC自动机:如何用多模式串匹配实现敏感词过滤功能?

目录 前言基于单模式串和 Trie 树实现的敏感词过滤经典的多模式串匹配算法&#xff1a;AC 自动机解答开篇内容小结 前言 本节课程思维导图&#xff1a; 很多支持用户发表文本内容的网站&#xff0c;比如 BBS&#xff0c;大都会有敏感词过滤功能&#xff0c;用来过滤掉用户输入…

如何做好口译服务,同传和交传哪个服务好

随着中国经济的蓬勃发展和综合实力的不断增强&#xff0c;中国与世界各国的交流也日益频繁。口译作为对外交流的桥梁与纽带&#xff0c;需求量与日俱增&#xff0c;其重要性不言而喻。那么&#xff0c;如何做好口译服务呢&#xff1f;是同传还是交传更好呢&#xff1f; 要做好口…

rabbitmq-windows安装使用-简易后台界面-修改密码

文章目录 1.下载2.安装3.安装 RabbitMQ4.后台访问5.修改密码 1.下载 将erlang运行时和rabbitmq-windows版本&#xff0c;上传在csdn&#xff0c;下载链接。https://download.csdn.net/download/m0_67316550/88633443 2.安装 右键&#xff0c;以管理员身份运行rabbitmq。启动…

如何用Adobe Audition 检测波形的pop和卡顿

在Adobe Audition中&#xff0c;检测卡顿和pop的方法各有不同&#xff1a; 1. **检测卡顿**&#xff1a; - 使用“诊断”面板中的“删除静音”或“标记音频”选项可以帮助识别音频中的静音段落&#xff0c;这可能表明存在卡顿。 - 配置诊断设置&#xff0c;指定静音的振…

探讨前端技术的未来:创新与适应的必要性

一、引言 2023年&#xff0c;IT圈似乎被一种悲观的论调所笼罩&#xff0c;那就是“Java 已死、前端已凉”。然而&#xff0c;真相是否如此呢&#xff1f;本文将围绕这一主题&#xff0c;探讨前端的现状和未来发展趋势。 二、为什么会出现“前端已死”的言论 这一言论的出现并…

Python高级算法——线性规划(Linear Programming)

Python中的线性规划&#xff08;Linear Programming&#xff09;&#xff1a;高级算法解析 线性规划是一种数学优化方法&#xff0c;用于求解线性目标函数在线性约束条件下的最优解。它在运筹学、经济学、工程等领域得到广泛应用。本文将深入讲解Python中的线性规划&#xff0…

python 新手学习 - 简单实用的 Python 周期任务调度工具

如果你想周期性地执行某个 Python 脚本&#xff0c;最出名的选择应该是 Crontab 脚本&#xff0c;但是 Crontab 具有以下缺点&#xff1a; 1.不方便执行秒级任务。 2.当需要执行的定时任务有上百个的时候&#xff0c;Crontab 的管理就会特别不方便。 还有一个选择是 Celery&a…

如何在 JavaScript 中实现任务队列

任务队列的概念 任务队列就是存放任务的队列&#xff0c;队列中的任务都严格按照进入队列的先后顺序执行。 在前一条任务执行完毕后&#xff0c;立即执行下一条任务&#xff0c;直到任务队列清空。 任务队列的基本执行流程如下&#xff1a; 设置任务队列并发数&#xff1b; …

编程导航算法通关村——算法基础

目录 1. 时间复杂度 1.1. 时间复杂度概念 1.2. 几种常见的阶 1.2.1. 常数阶 O(1) 1.2.2. 线性阶 O(n) 1.2.3. 平方阶 (n) 1.2.4. 对数阶 O(logn) 2. 最坏情况和平均情况 3. 空间复杂度 1. 时间复杂度 1.1. 时间复杂度概念 当我们说算法的时间复杂度时&#xff0c;我们…