Linux篇:信号

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

一点五、详谈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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Word公式居中+序号右对齐

Word公式居中序号右对齐 # 号制表位法表格法Mathtype法 # 号 制表位法 表格法 Mathtype法 参考1 参考2

力扣每日一题:2132. 用邮票贴满网格图(2023-12-14)

力扣每日一题 题目&#xff1a;2132. 用邮票贴满网格图 日期&#xff1a;2023-12-14 用时&#xff1a;38 m 32 s 思路&#xff1a;使用前缀和&#xff0b;差分&#xff0c;只是往常是一维&#xff0c;现在变二维了&#xff0c;原理差不多 时间&#xff1a;22ms 内存&#xff1…

certum ev ssl证书1180元一年,360浏览器显示公司名

Certum旗下的EV SSL证书是审核最严的数字证书&#xff0c;不仅对网站传输数据进行加密&#xff0c;还可以对网站身份进行验证&#xff0c;除此之外&#xff0c;它独有的绿色地址栏提升了网站的真实性&#xff0c;增强了客户对网站的信任感。今天就随SSL盾小编了解Certum旗下的E…

【Spring Boot】视图渲染技术之Freemarker

一、引言 1、什么是Freemarker FreeMarker是一款模板引擎&#xff0c;基于模板和要改变的数据&#xff0c;并用来生成输出文本&#xff08;HTML网页、电子邮件、配置文件、源代码等&#xff09;的通用工具。它不是面向最终用户的&#xff0c;而是一个Java类库&#xff0c;是一款…

LAMP平台部署及应用

1、安装PHP软件包 1.1、准备工作 检查软件是否安装&#xff0c;避免冲突 [rootyang ~]# rpm -e php php-cli php-ldap php-common php-mysql --nodeps 错误&#xff1a;未安装软件包 php 错误&#xff1a;未安装软件包 php-cli 错误&#xff1a;未安装软件包 php-ldap 错误…

使用 Python 使用贝叶斯神经网络从理论到实践

一、说明 在本文中&#xff0c;我们了解了如何构建一个机器学习模型&#xff0c;该模型结合了神经网络的强大功能&#xff0c;并且仍然保持概率方法进行预测。为了做到这一点&#xff0c;我们可以构建所谓的贝叶斯神经网络。 这个想法不是优化神经网络的损失&#xff0…

2. PyTorch——Tensor和Numpy

2.1Tensor和Numpy Tensor和Numpy数组之间具有很高的相似性&#xff0c;彼此之间的互操作也非常简单高效。需要注意的是&#xff0c;Numpy和Tensor共享内存。由于Numpy历史悠久&#xff0c;支持丰富的操作&#xff0c;所以当遇到Tensor不支持的操作时&#xff0c;可先转成Numpy…

获取和移除cookie的方法

下载npm的cookie插件, 在utils.js文件中引入插件: 封装原始的Cookies.get()方法: 在xxxx.vue文件中引入方法: 使用getCookie方法获取cookie: 封装 移除cookie: export const removeCookie name>{ const options { path: /, domain: xxx.com }; Cookies.remove(name, opti…

18 5G - NR物理层解决方案支持6G非地面网络中的高移动性

文章目录 非地面网络场景链路仿真参数实验仿真结果 非地面网络场景 链路仿真参数 实验仿真结果 Figure 5 && Figure 6&#xff1a;不同信噪比下的BER和吞吐量 变量 SISO 2x2MIMO 2x4MIMO 2x8MIMOReyleigh衰落、Rician衰落、多径TDL-A(NLOS) 、TDL-E(LOS)(a)QPSK (b)16…

玩期货,千万要注意不同软件的设置啊

参加某公司的期货交易模拟选拔&#xff0c;用的是博易大师&#xff0c;结果这个软件的止损线巨坑&#xff0c;当天下午设置的止损线&#xff0c;在收盘之后软件关闭的情况下就自动作废了&#xff0c;到了晚上夜盘如果价格超相反方向走了&#xff0c;那可能导致巨亏。 幸亏是模拟…

nginx的location与rewrite

目录 一.location 二.rewrite rewrite跳转实现&#xff1a; 语法格式&#xff1a;rewrite [flag]; flag标记说明&#xff1a; 三.基于域名跳转 四.基于ip跳转 五.基于旧域名跳转到新域名后面加目录 六.基于参数匹配的跳转 可以同过全局变量来匹配&#xff1a; 基于目…

最高级别认可!喂车科技荣获国际CMMI5级认证

近日&#xff0c;喂车科技顺利通过全球软件领域最高级别认证CMMI 5级&#xff08;简称CMMI5&#xff09;认证&#xff01; 此次荣获CMMI5级认证&#xff0c;意味着喂车科技在研发管理体系、项目实施交付服务以及项目管理水平等方面均已达到国际领先水平&#xff0c;能够为客户…

如何远程访问Axure RP制作的本地web站点实现协同办公

文章目录 前言1.在AxureRP中生成HTML文件2.配置IIS服务3.添加防火墙安全策略4.使用cpolar内网穿透实现公网访问4.1 登录cpolar web ui管理界面4.2 启动website隧道4.3 获取公网URL地址4.4. 公网远程访问内网web站点4.5 配置固定二级子域名公网访问内网web站点4.5.1创建一条固定…

在windows系统搭建LVGL模拟器(codeblock工程)

1.codeblock准备 下载codeblock(mingw)&#xff0c;安装。可参考网上教程。 2.pc_simulator_win_codeblocks 工程获取 仓库地址&#xff1a;lvgl/lv_port_win_codeblocks: Windows PC simulator project for LVGL embedded GUI Library (github.com) 拉取代码到本地硬盘&…

机器学习算法应用场景与评价指标

机器学习算法&#xff08;一&#xff09;——分类 机器学习算法&#xff08;二&#xff09;——回归 机器学习算法&#xff08;三&#xff09;——异常检测 一、应用场景 机器学习的算法选择大部分依赖于具体的问题类型和数据特征。下面是一些典型的场景以及对应的常用算法&am…

面对知识经济的发展,企业该如何做好知识管理?

面对知识经济的发展&#xff0c;企业犹如逆水行舟&#xff0c;不进则退&#xff0c;而知识管理已经成为了企业赖以生存和发展的关键。大家可能对知识经济这个词比较陌生&#xff0c;简单来说&#xff0c;知识经济就是指在经济活动中&#xff0c;知识的产生、获取、传播和应用成…

点石成金》》》从“沙粒”蜕变到“芯片”

每个半导体产品的制造都需要数百个工艺&#xff0c;Lam Research将整个制造过程分为八个步骤&#xff1a;晶圆加工-氧化-光刻-刻蚀-薄膜沉积-互连-测试-封装。 01 晶圆加工 所有半导体工艺都始于一粒沙子&#xff01;因为沙子所含的硅是生产晶圆所需要的原材料。晶圆是…

C++ Qt开发:DateTime日期时间组件

Qt 是一个跨平台C图形界面开发库&#xff0c;利用Qt可以快速开发跨平台窗体应用程序&#xff0c;在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置&#xff0c;实现图形化开发极大的方便了开发效率&#xff0c;本章将重点介绍QDateTime日期与时间组件的常用方法及灵活运用…

【评测脚本】agent资源监控

背景 在之前的文章中提到过,我们在测试过程中需要对机器的资源进行评测。在实际工作中,我们还会经常遇到的场景就是对于agent-server类型的业务,当部署完成后,需要对部署在机器上的agent进行资源占用的观测,不能舍本逐末,由于agent的异常资源占用,导致原有业务受机器资…

直播美颜SDK开发实战:从入门到精通

直播美颜SDK的应用已经成为许多直播平台和开发者关注的焦点。本文将带领读者深入探讨直播美颜SDK的开发实战&#xff0c;从入门到精通的过程。 1.引言 直播美颜SDK是一种集成了图像处理、人脸识别、滤镜算法等技术的开发工具包。通过使用该SDK&#xff0c;开发者能够为直播应…