信号【Linux】

news2024/11/25 3:25:28

1、信号就算没有产生,进程也必须识别和能够处理信号,要具备处理信号的能力,信号的处理能力,属于进程内置功能的一部分
2、当进程收到一个具体的信号时,进程可能并不会立即处理这个信号,在合适的时候处理信号

3、当信号产生,到信号开始被处理,这段时间,哪些信号已经产生,进程必须知道

信号处理方式(信号递达)

信号处理方式:
1、进程内置了对信号的处理,默认动作
2、进程可以忽略信号
3、自定义动作,会更改进程内置的默认动作,比如:信号的捕捉

信号捕捉 ,会将信号的默认处理方式修改,比如一个信号本来默认处理方式是终止进程并退出进程,信号捕捉之后该信号的默认处理方式被修改,会出现只捕捉而不退出进程了

signal,修改特定进程对于信号的处理动作的

 typedef void (*sighandler_t)(int);
   sighandler_t signal(int signum, sighandler_t handler);

handler这个参数是自定义的动作

前后台进程

关于前后台进程:
前台进程:能获取键盘输入

后台进程:不能获取键盘输入

./myprocess & ,加了& ,就变成后台进程了

[cxq@iZwz9fjj2ssnshikw14avaZ lesson31]$ ll
total 36
-rw-rw-r-- 1 cxq cxq    90 Jul 15 22:41 Makefile
-rwxrwxr-x 1 cxq cxq 26440 Jul 15 15:31 myprocess
-rw-rw-r-- 1 cxq cxq   443 Jul 15 22:41 mysignal.cc
-rw-rw-r-- 1 cxq cxq     0 Jul 14 16:59 test.c
[cxq@iZwz9fjj2ssnshikw14avaZ lesson31]$ ./myprocess &

Linux中,一次登陆中,一个终端,一般会配上一个bash,每一个登陆,只允讲一个进程是前台进程,但是可以允许多个进程是后台进程

终端按键产生信号

ctrl +c为什么能够杀掉我们前台进程?

ctrl +c产生的信号只能发给前台进程, ctrl +c本质是被进程解释成为收到了信号,2号信号

信号是进程之间事件异步通知的一种方式,属于软中断

信号的产生和我们自己的代码的运行时异步的 ,
异步:代码在运行时,信号什么时候产生是不清楚的,代码不用管信息什么时候产生,代码继续运行就可以了

ctrl +c : 2号信号

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


void myhandler(int signo)
{
    cout << "process get a signal: " << signo <<endl;
    // exit(1);
}
int main()
{
//捕捉 2号信号
   signal(2 , myhandler);//myhandler是函数指针
   //signal只需要设置一次,往后都有效
   //信号的产生和我们自己的代码的运行时异步的 ,异步:代码在运行时,信号什么时候产生是不清楚的,代码不用管信息什么时候产生,代码继续运行就可以了
 while(true)
    {
        cout << "I am a crazy process : " << getpid() << endl;
        sleep(1);
    }
    return 0 ;
}

ctrl + : 3号信号
不是所有的信号都是可以被signal捕捉的,比如:19 (暂停进程), 9(杀死进程)

kill系统调用接口向进程发信号

kill给pid进程发sig信号

int kill(pid_t pid, int sig);
#include<iostream>
#include<stdio.h>
 #include <signal.h>
 #include<unistd.h>
  #include <stdlib.h>
  #include<string>
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);
  }
 string signumStr(argv[1]);
    string pidStr(argv[2]);

   int signum = stoi(signumStr); 
   pid_t pid = stoi(pidStr);

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

raise:发送一个信号给调用者

  int raise(int sig);

举例:

#include<iostream>
#include<stdio.h>
 #include <signal.h>
 #include<unistd.h>
  #include <stdlib.h>
  #include<string>
using namespace std ;
void myhandler(int signo)
{
    cout << "process get a signal: " << signo <<endl;
  //   exit(1);
}
int main()
{
    int cnt =5 ;
    signal(2,myhandler); //捕捉2号信号
     while (true)
    {
        cout << "I am a process, pid: " << getpid() << endl;
        sleep(1);
        cnt--;
        if(cnt== 0) 
        {
            raise(2);  //等价于 kill(getpid() , 2) 
        }
    }
    return 0 ;
}

abort 发送6号信号和终止进程

       void abort(void);

信号产生的方式,但是无论信号如何产生,最终操作系统发送给进程,因为操作系统是进程的管理者

alarm ,设置一个闹钟

 unsigned int alarm(unsigned int seconds);

return val:
alarm 函数返回的是之前设置的定时器的剩余时间。如果之前没有设置定时器,或者定时器已经到期,返回 0。
返回的剩余时间是指在调用新的 alarm 之前,定时器还有多少秒才会发送 SIGALRM 信号。

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

using namespace std;
void myhandler(int signo)
{
    
    cout<<"xxxxxxxxxx"<<signo<<endl;

    //理解alarm的返回值
     int n = alarm(3);
     cout<<"剩余时间:"<<n<<endl;
    //exit(1);
}


int main()
{
    int n = alarm(50);//alarm发送的是14号信号
    signal(14,myhandler);//myha ndler是一个函数指针
     while(1)
    {
        cout << "proc is running  pid :%d" <<" "<<getpid()<< endl;
        sleep(1);
    }
    return 0 ;
}

打开系统的core dump (核心转储)功能,一旦进程出异常,操作系统会将进程在内存中的运行信息,给转诸到进程的当前目录,并形成core.pid文件

ulimit :显示和设置用户可以启动的进程的资源限制

[cxq@iZwz9fjj2ssnshikw14avaZ output]$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 6943
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 65535
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 4096
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

打开系统的core dump (核心转储)功能,将core file size 设置为10240

[cxq@iZwz9fjj2ssnshikw14avaZ output]$ ulimit -c 10240
[cxq@iZwz9fjj2ssnshikw14avaZ output]$ ulimit -a
core file size          (blocks, -c) 10240
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 6943
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 65535
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 4096
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

测试 core dump功能

#include <iostream>
 #include <unistd.h>
  #include <signal.h>
 #include <sys/types.h>
       #include <sys/wait.h>
using namespace std;
int main()
{

    //测试  core dump功能


    cout << " a = " << a << endl;
    pid_t id = fork();
        if(id==0)
        {
            //child
              int cnt = 500;
        while(cnt)
        {
            cout << "i am a child process, 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; //  (0000 0000 ... 0001)
        }
    return 0 ;
}

在这里插入图片描述
生成的core.pid文件,可以通过gdb直接定位到出错行
在这里插入图片描述

阻塞信号

信号常见概念
1、实际执行信号的处理动作,称为信号递达(Delivery)。
例:进程内置了对信号处理的默认动作、进程可以忽略信号、还是自定义动作 这些都是信号的处理动作,都是信号递达

2、信号从产生到递达之间的状态,称为信号未决(pending)。
信号从产生到递达之前,此时信号处于位图中,此时信号处于保存状态 ,这种状态称为信号未决

3、进程可以选择阻塞(Block)某个信号。这里的阻塞可以理解为屏蔽

  • 默认情况下,所有信号都是没有被屏蔽的 , 进程可以屏蔽某些信号,在解除屏蔽之前,信号 也不会被操作系统进行递达

  • 如果进程没有收到某一个信号,也可以屏蔽这个信号

4、被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。

  • 一个信息如果被阻塞了(屏蔽),如果此时用户向进程发送了指定的信号,该信号不会被递达,直到解除了阻塞 ,也就是说阻塞了还是可以给进程发送信号,只不过信号暂时被屏蔽罢了

5阻塞和忽略是完全不同的,只要信号被阻塞就不会递达,而忽略是在递达之后的一种处理动作

  • 只要信号被阻塞就不会递达,即不会被处理,屏蔽和解决屏蔽一定是在递达之前做的
  • 忽略的本质是处理信号,忽略是信号递达的三种方式之一,忽略是在递达之后的一种处理动作
  • 阻塞可以理解为未读,忽略可以理解为已读不回

block表:.比特位的内容是1还是0,表明是否屏蔽信号
比特位的位置(第几个),表示信号的编号

pending表:
比特位的内容是1还是0,表明是否收到信号
比特位的位置(第几个),表示信号的编号
handler表:
handler表本质上是一个函数指针数组,数组的下标代表某一个信号,数组的内容代表该信号递达时的处理动作,处理动作包括默认、忽略以及自定义
block、pending和handler这三张表的每一个位置是一一对应的
在这里插入图片描述

  • 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
  • SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会在改变处理动作之后再接触阻塞。
  • SIGQUIT信号未产生过,但一旦产生SIGQUIT信号,该信号将被阻塞,它的处理动作是用户自定义函数sighandler。如果在进程解除对某信号的阻塞之前,这种信号产生过多次,POSIX.1允许系统递达该信号一次或多次。Linux是这样实现的:普通信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里,这里只讨论普通信号
#include <iostream>
 #include <unistd.h>
  #include <signal.h>
 #include <sys/types.h>
  #include <sys/wait.h>
using namespace std;
int main()
{
	//理解handler表的SIG_IGN,SIG_DFL
  //signal(2,SIG_IGN);//忽略2号信号
  signal(2,SIG_DFL);//使用2号信号的默认处理方法

    while(1)
    {
        cout << "hello signal" << endl;
        sleep(1);
    }

  return  0; 
}

sigset_t

根据信号在内核中的表示方法,每个信号的未决标志只有一个比特位,非0即1,如果不记录该信号产生了多少次,那么阻塞标志也只有一个比特位。因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储

在我当前的云服务器中,sigset_t类型的定义如下:(不同操作系统实现sigset_t的方案可能不同)

typedef __sigset_t sigset_t;
# define _SIGSET_NWORDS	(1024 / (8 * sizeof (unsigned long int)))
typedef struct
  {
    unsigned long int __val[_SIGSET_NWORDS];
  } __sigset_t;

sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态。

  • 在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞。
  • 在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。

阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略

#include <signal.h>

//sigemptyset、sigfillset、sigaddset和sigdelset函数都是成功返回0,出错返回-1。

int sigemptyset(sigset_t *set);//初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号

int sigfillset(sigset_t *set);//始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号。

int sigaddset(sigset_t *set, int signum);//在set所指向的信号集中添加某种有效信号。

int sigdelset(sigset_t *set, int signum);//在set所指向的信号集中删除某种有效信号。

int sigismember(const sigset_t *set, int signum);  //判断在set所指向的信号集中是否包含某种信号,若包含则返回1,不包含则返回0,调用失败返回-1。

sigprocmask

sigprocmask :用于读取或更改进程的信号屏蔽字(阻塞信号集)

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

1、如果oldset是非空指针,则读取进程当前的信号屏蔽字通过oldset参数传出。
2、如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。
3、如果oldset和set都是非空指针,则先将原来的信号屏蔽字备份到oldset里,然后根据set和how参数更改信号屏蔽字

how参数的可选值及其含义: 以下三个只能选一个作为how参数

  • SIG_BLOCK set包含了我们希望添加到当前信号屏蔽字的信号,相当于mask=mask|set
  • SIG_UNBLOCK set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask|~set
  • SIG_SETMASK 覆盖式的设置当前信号屏蔽字为set所指向的值,相当于mask=set

return val: sigprocmask函数调用成功返回0,出错返回-1。

如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask函数返回前,至少将其中一个信号递达

sigpending

sigpending:用于读取进程的未决信号集,

int sigpending(sigset_t *set);

sigpending函数读取当前进程的未决信号集,并通过set参数传出。
return val :该函数调用成功返回0,出错返回-1。

1、将2号信号进行屏蔽(阻塞)。

2、使用kill命令或组合按键向进程发送2号信号。
3、此时2号信号会一直被阻塞,并一直处于pending(未决)状态。
4、使用sigpending函数获取当前进程的pending信号集进行验证
测试代码:

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

void PrintPending(sigset_t & pending)
{
    for(int signo = 31 ;   signo>=1 ; signo--)
    {
        if(sigismember(&pending , signo)  ==1)//判断在set所指向的信号集中是否包含某种信号
        {
            //存在
             cout << "1";
        }
           else
        {
            //不存在
            cout << "0";
        }
    }
        // cout << "\n\n";//将光标从当前位置向下移动两行
        cout<<endl;
        cout<<endl;

}
int main()
{
    //将2号信号进行屏蔽(阻塞)。
// 使用kill命令或组合按键向进程发送2号信号。
// 此时2号信号会一直被阻塞,并一直处于pending(未决)状态。
// 使用sigpending函数获取当前进程的pending信号集进行验证
sigset_t  bset,oset ;
sigemptyset(&bset);//使bset其中所有信号的对应bit清零
sigemptyset(&oset);//使oset其中所有信号的对应bit清零

sigaddset(&bset,2);//在set所指向的信号集中添加2号信号。
sigprocmask(SIG_SETMASK ,&bset, &oset);//把bset覆盖式的设置到进程的block位图中

//代码走到这里,已经屏蔽2号信号了

//重复打印当前进程的pending

sigset_t pending ;
int cnt =0 ;
while(true)
{
      // 2.1 获取
    int n = sigpending(&pending);
       if (n < 0)
             continue;
   // 2.2 打印
    PrintPending(pending);
     sleep(1);
    // 2.3 解除阻塞
  cnt ++;
  if(cnt ==20)
  {
        cout << "unblock 2 signo" << endl;
        sigprocmask(SIG_SETMASK ,&oset,nullptr);//更改进程的信号屏蔽字,覆盖式的设置当前信号屏蔽字为bset所指向的值
  }
}
    return 0 ; 
}

内核态与用户态:

  • 内核态:允许访问操作系统的代码和数据,是一种权限非常高的状态。
  • 用户态:只能访问用户自己的代码和数据,是一种受监管的普通状态。

当进程从内核态返回到用户态时,进行信号的检测和处理

调用系统调用时,操作系统是自动会"身份"切换的 ,从用户身份变成内核身份,或者从内核身份变成用户身份

用户态切换为内核态我们称之为陷入内核。每当我们需要陷入内核的时,本质上是因为我们需要执行操作系统的代码,比如系统调用函数是由操作系统实现的,我们要进行系统调用就必须先由用户态切换为内核态

内核空间与用户空间

每一个进程都有自己的进程地址空间,该进程地址空间由内核空间和用户空间组成:

用户所写的代码和数据位于用户空间,通过用户级页表与物理内存之间建立映射关系。
内核空间存储的实际上是操作系统代码和数据,通过内核级页表与物理内存之间建立映射关系

在这里插入图片描述

CR3 寄存器存储了页表的起始物理地址,这个地址指向了页全局目录的起始位置

用户页表有几份?
有几个进程,就有几份用户级页表 , 进程具有独立性

内核页表有几份?
1份

内核级页表是一个全局的页表,它用来维护操作系统的代码与进程之间的关系。因此,在每个进程的进程地址空间中,用户空间是属于当前进程的,每个进程看到的代码和数据是完全不同的,但内核空间所存放的都是操作系统的代码和数据,所有进程看到的都是一样的内容

总结:

  • 每一个进程看到的3到4GB的东西都是一样的
  • 整个系统中,进程再怎么切换,3到4GB的空间的内容是不变的

进程视角:我们调用系统中的方法,就是在我自己的地址空间中进行执行的。

操作系统视角:任何一个时刻,都有有进程执行。我们想执行操作系统的代码,就可以随时执行
在这里插入图片描述

操作系统的本质:基于时钟中断的一个死循环

计算机硬件中,有一个时钟芯片,每个很短的时间,向计算机发送时钟中断

如何理解进程切换?
1、在当前进程的进程地址空间中的内核空间,找到操作系统的代码和数据。
2、执行操作系统的代码,将当前进程的代码和数据剥离下来,并换上另一个进程的代码和数据。
当你访问用户空间时你必须处于用户态,当你访问内核空间时你必须处于内核态

内核如何实现信号的捕捉

当我们在执行主控制流程的时候,可能因为某些情况而陷入内核,当内核处理完毕准备返回用户态时,就需要进行信号pending的检查。(此时仍处于内核态,有权力查看当前进程的pending位图)

在查看pending位图时,如果发现有未决信号,并且该信号没有被阻塞,那么此时就需要该信号进行处理。

如果待处理信号的处理动作是默认或者忽略,则执行该信号的处理动作后清除对应的pending标志位,如果没有新的信号要递达,就直接返回用户态,从主控制流程中上次被中断的地方继续向下执行即可

在这里插入图片描述
但如果待处理信号是自定义捕捉的,即该信号的处理动作是由用户提供的,那么处理该信号时就需要先返回用户态执行对应的自定义处理动作,执行完后再通过特殊的系统调用sigreturn再次陷入内核并清除对应的pending标志位,如果没有新的信号要递达,就直接返回用户态,继续执行主控制流程的代码。
在这里插入图片描述
sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程

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

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

相关文章

Django—admin后台管理

Django官网 https://www.djangoproject.com/ 如果已经有了Django跳过这步 安装Django&#xff1a; 如果你还没有安装Django&#xff0c;可以通过Python的包管理器pip来安装&#xff1a; pip install django 创建项目&#xff1a; 使用Django创建一个新的项目&#xff1a; …

敲详细的springboot中使用RabbitMQ的源码解析

这里介绍的源码主要是涉及springboot框架下的rabbitmq客户端代码&#xff08;具体在springframework.amqp.rabbit包下&#xff0c;区分一下不由springboot直接接管的spring-rabbit的内容&#xff09;&#xff0c;springboot基于RabbitMQ的Java客户端建立了简便易用的框架。 sp…

jmeter实战(1)- Mac环境安装

一、安装 JDK 这个就不介绍了&#xff0c;本地自行安装 JDK 并且配置好环境变量 二、安装 Jmeter 1. 下载地址 —> 下载链接点击这里 2. 选择合适的版本下载 3. 解压到本地目录 解压后&#xff0c;会得到下面的目录文件&#xff1a; 输入cd bin&#xff0c;进入到bin…

OpenCV 直方图概念,直方图均衡化原理详解

文章目录 直方图相关概念颜色灰度级作用应用场景 C 使用OpenCV绘制直方图单通道直方图关键代码分析&#xff1a;calcHist函数分析使用OpenCV API来绘制直方图 效果图&#xff1a; 彩色三通道直方图效果图&#xff1a; 直方图均衡化概念均衡化作用均衡化效果均衡化数学原理步骤数…

项目实战二 HIS项目

目标&#xff1a; 项目的操作流程&#xff1a; 开发体系 前端开发&#xff1a;负责页面的编写 HTML CSS JavaScript 后端开发&#xff1a;看不到 摸不着的功能 常用开发语言 PHP JAVA Python 框架 &#xff1a; 半成品 做好的功能模块 版本控制 Git 分布式版本控…

vxe-table——实现切换页码时排序状态的回显问题(ant-design+elementUi中table排序不同时回显的bug)——js技能提升

之前写的后台管理系统&#xff0c;都是用的antdelement&#xff0c;table组件中的【排序】问题是有一定的缺陷的。 想要实现的效果&#xff1a; antv——table组件一次只支持一个参数的排序 如下图&#xff1a; 就算是可以自行将排序字段拼接到列表接口的入参中&#xff0c…

【中项】系统集成项目管理工程师-第4章 信息系统架构-4.3应用架构

前言&#xff1a;系统集成项目管理工程师专业&#xff0c;现分享一些教材知识点。觉得文章还不错的喜欢点赞收藏的同时帮忙点点关注。 软考同样是国家人社部和工信部组织的国家级考试&#xff0c;全称为“全国计算机与软件专业技术资格&#xff08;水平&#xff09;考试”&…

vue3 -layui项目-左侧导航菜单栏

1.创建目录结构 进入cmd,先cd到项目目录&#xff08;项目vue3-project&#xff09; cd vue3-project mkdir -p src\\views\\home\\components\\menubar 2.创建组件文件 3.编辑menu-item-content.vue <template><template v-if"item.icon"><lay-ic…

计算机网络八股文(后续更新)

文章目录 一、计算机网络体系结构1、计算机网络的各层协议及作用&#xff1f; 二、物理层三、数据链路层四、网络层五、传输层1、TCP和UDP的区别&#xff1f;2、UDP 和 TCP 对应的应用场景是什么&#xff1f;3、详细介绍一下 TCP 的三次握手机制4、为什么需要三次握手&#xff…

Electron 结合 Selenium + chromedriver 驱动服务实现浏览器多开

背景 在调研浏览器多开的过程中&#xff0c;electron 有自带的 browserview&#xff0c;webview&#xff0c;但是上面两个受制于 electron 内核版本限制&#xff0c;升级不够灵活&#xff0c;对新版的网页支持可能不及时&#xff0c;甚至不兼容&#xff0c;必须通过发布新的客…

MSP430M03507最小系统板的keil环境搭配,用keil编辑ti单片机

转载自嘉立创MSP430M03507开发手册 这篇文章只是因为我的keil版本与嘉立创的不一样&#xff0c;所以添加了我自己遇到的问题解析 先说说为什么要用keil编辑&#xff0c;因为ti单片机自己的ccs编译环境需要对应仿真器&#xff0c;那个加芯片都240了&#xff0c;哪有那么多钱买…

JAVA开发工具IDEA如何连接操作数据库

一、下载驱动 下载地址&#xff1a;【免费】mysql-connector-j-8.2.0.jar资源-CSDN文库 二、导入驱动 鼠标右击下载到IDEA中的jar包&#xff0c;选择Add as Library选项 如图就导入成功 三、加载驱动 Class.forName("com.mysql.cj.jdbc.Driver"); 四、驱动管理…

Bootstrap5 Navbar多级下拉框

实现目标&#xff1a; 1、访问 Bootstrap5-navbar 2、修改dropdown为多级 <!DOCTYPE HTML> <html lang"en-US"> <head><meta charset"UTF-8"><title></title><link rel"stylesheet" href"https…

视频汇聚平台EasyCVR启动出现报错“cannot open shared object file”的原因排查与解决

安防视频监控EasyCVR安防监控视频系统采用先进的网络传输技术&#xff0c;支持高清视频的接入和传输&#xff0c;能够满足大规模、高并发的远程监控需求。EasyCVR平台支持多种视频流的外部分发&#xff0c;如RTMP、RTSP、HTTP-FLV、WebSocket-FLV、HLS、WebRTC、fmp4等&#xf…

python实现图像特征提取算法1

python实现Marr-Hildreth算法、Canny边缘检测器算法 1.Marr-Hildreth算法详解算法步骤公式Python 实现详细解释优缺点 2.Canny边缘检测器算法详解算法步骤公式Python 实现详细解释优缺点 1.Marr-Hildreth算法详解 Marr-Hildreth算法是一个用于图像边缘检测的经典算法&#xff…

一些和颜色相关网站

1.中国传统色 2.网页颜色选择器 3.渐变色网站 4.多风格色卡生成 5.波浪生成 6.半透明磨砂框

基于3D开发引擎HOOPS平台的大型三维PLM系统的设计、开发与应用

产品生命周期管理&#xff08;Product Lifecycle Management&#xff0c;PLM&#xff09;系统在现代制造业中扮演着至关重要的角色。随着工业4.0和智能制造的推进&#xff0c;PLM系统从最初的CAD和PDM系统发展到现在的全面集成、协作和智能化的平台。本文将探讨基于HOOPS平台的…

四大引用——强软弱虚

目录 一、强引用 二、软引用 三、弱引用 四、虚引用 一、强引用 强引用是在程序代码之中普遍存在的&#xff0c;类似于“Object obj new Object()”&#xff0c;obj变量引用Object这个对象&#xff0c;就叫做强引用。当内存空间不足&#xff0c;Java虚拟机宁愿抛出OutOfMe…

乐鑫AWS IoT ExpressLink方案,简化物联网设备连接AWS IoT服务

在现代科技迅速发展的今天&#xff0c;物联网&#xff08;IoT&#xff09;已经成为连接物理世界与数字世界的重要桥梁&#xff0c;越来越多的设备开始接入网络&#xff0c;实现智能化控制。 在这个大背景下&#xff0c;乐鑫携手亚马逊&#xff0c;推出了AWS IoT ExpressLink方…

【机器学习】机器学习解决的问题特点、机器学习学的是什么、怎么学、如何构建高效机器学习模型的策略、机器学习的分类以及机器学习、模式识别、数据挖掘和人工智能的区别

引言 机器学习是人工智能的一个重要分支&#xff0c;主要解决的是如何通过算法让机器从数据中自动学习规律和知识&#xff0c;以完成特定任务或解决特定问题。 文章目录 引言一、机器学习解决的是什么样的问题1.2 数据驱动的预测问题1.3 数据理解与挖掘1.4 优化与决策问题1.5 异…