Linux系统编程:信号

news2024/11/18 0:29:02

目录

1.信号概念

2.信号产生

2.1 终端

2.2 系统调用

2.3 硬件异常

2.4 软件条件

2.5 小结

3. 进程退出时的核心转储问题

4. 信号捕捉初识

5. 阻塞信号

5.1 相关概念

5.2 在内核中的表示

6. 信号捕捉

6.1 知识铺垫

6.2 信号捕捉流程

6.3 sigset_t

6.4 信号集操作函数

6.5 sigaction

7. 可重入函数

8. 关键字volatile


1.信号概念

        信号是进程之间事件异步通知的一种方式,属于软中断。例:用户输入命令,在Shell下启动一个前台进程。用户按下Ctrl-C ,这个键盘输入产生一个硬件中断,被OS获取,解释成信号,发送给目标前台进程,前台进程因为收到信号,进而引起进程退出。这里的ctrl+c就被OS解释成为了一种信号

注:
  • kill -l命令可以察看系统定义的信号列表
  • 当进程收到信号的时候,进程可能正在执行更重要的代码,信号不一定会被立即处理--因为信号可以随时产生(异步)
  • 进程本身必须要有对信号的保存能力
  • 进程在处理信号时,有三种动作--默认动作、自定义动作、忽略动作
kill -l

  每个信号都有一个编号和宏定义     --     [1, 31]:普通信号            [34, 64]:实时信号 

一个共识就是:信号是发送给信号的,进程是被保存到哪里了呢?-- task_strut中。当进程收到信号后,会修改PCB中的信号位图,unsigned int signal -- 其有32个比特位,比特位的位置代表编号,比特位的内容表示是否收到信号:0(未收到)、1(收到)

由上得出,信号的发送就是修改PCB中的位图结构,只有OS有权修改。本质就是OS向目标进程发送信号,所以会提供一系列的系统调用,使得用户可以通过OS发送信号。

2.信号产生

2.1 终端

        通过终端的按键产生信号,ctrl+c 和 ctrl+\ 默认动作都是终止进程,分别对应2和3号信号

2.2 系统调用

#include <sys/types.h>

#include <signal.h>

int kill(pid_t pid, int sig);

参数:pid:目标进程pid

           sig:发送几号信号

返回值:成功返回0,失败返回-1
可以向任意进程发送任意信号

 demo代码:

mysignal.cc/
 #include <iostream>    
  #include <signal.h>    
  #include <unistd.h>    
  #include <sys/types.h>    
  #include <string>    
      
  using namespace std;    
      
  static void Usage(const string& proc)    
  {    
      cout << "\nUsage:" << proc << " pid sino\n" << endl;    
  }    
  
      
  int main(int argc, char *argv[])    
  {    
       if(argc != 3)    
       {    
           Usage(argv[0]);    
       exit(1);    
   }    
       // 1.通过键盘发送信号    
       // 2.通过系统调用发送信号    
       pid_t pid = atoi(argv[1]);    
       int signo = atoi(argv[2]);    
       int n = kill(pid, signo);    
       if(n != 0)                                                                                            
       {    
           perror("kill");    
       }    
    return 0;
}

///mytest.cc
#include <iostream>                                                                                          
#include <sys/types.h>    
#include <unistd.h>    
    
using namespace std;    
    
int main()    
{    
    while(1)    
    {    
        cout << "我是一个正在运行的进程,pid:" << getpid() << endl;    
        sleep(1);    
    }    
    
    return 0;    
}    

上面代码的目的是,先运行mytest进程,进程mysignal通过命令行参数找到mytest进程来终止该进程

int raise(int sig); -- 给自己发送任意信号 

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

  int main(int argc, char *argv[])    
  {  
      int cnt = 0;    
       while(cnt <= 10)    
       {
           cout << "signal ss" << cnt++ << endl;
           if(cnt >= 5) raise(9);
       }
      return 0;
}

由上述现象可以看到,raise在循环五次后发送9号信号,杀死进程。  

#include <stdlib.h>

void abort(void); -- 使当前进程接收到信号而异常终止,abort函数总是会成功的 -- 6号信号

下面两个接口都可以通过kill接口实现 

对信号处理行为的理解:

  • 很多情况,进程收到大部分的信号默认处理动作都是终止进程
  • 信号的意义:信号的不同,代表不同的事件,但对事件发生之后的处理都做都可以一样。

2.3 硬件异常

        如:当前进程执行了除 以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。还有野指针问题 

野指针问题:

#include <stdio.h>
#include <signal.h>
void handler(int sig)
{
 printf("catch a sig : %d\n", sig);
}
int main()
{
 signal(SIGSEGV, handler);
 sleep(1);
 int *p = NULL;
 *p = 100;
 while(1);
 return 0;
}

若不进行信号捕捉怎么发生段错误

信号捕捉以后会捕捉到11号信号打印:

2.4 软件条件

2.4.1 管道

SIGPIPE是一种由软件条件产生的信号,管道中已经介绍过了。在管道中,读端关闭,写段一直写,会发生一场,OS向写端发送SIGPIPE信号:13号信号

2.4.2 定时器软件条件

#include <unistd.h>

unsigned int alarm(unisegned int seconds);

参数:seconds:秒数 -- 若为0,则意味着取消闹钟

返回值:提前唤醒时,会返回剩余秒数

告诉内核在seconds秒后,给当前进程发SIGALRM信号

#include <iostream>    
  #include <signal.h>    
  #include <unistd.h>    
  #include <sys/types.h>    
  #include <string>    
      
  using namespace std;

int main(int argc, char *argv[])    
  {  
     int cnt = 0;
        alarm(1);
       while(1)
       {
           cnt++;
            cout << "cnt = " << cnt++ << endl; // 外设打印 拖慢了计算的节奏
       }
return 0;                                                    
  } 

 这段代码帮我们统计了计算机在一秒内可以累加并打印多少次。

#include <iostream>    
  #include <signal.h>    
  #include <unistd.h>    
  #include <sys/types.h>    
  #include <string>    
      
  using namespace std;

int cnt = 0;    
      
void catchSignal(int signo)    
  {    
      cout << "获得一个信号,信号编号为:" << cnt << endl;    
      // exit(1);    
  }  

int main(int argc, char *argv[])    
  {  
     
        alarm(1);
       while(1)
       {
           cnt++;
            
       }
return 0;                                                    
  } 

这段代码为何与前一段代码打印出来的结果相差如此之大呢,是因为计算机进行打印(IO)是很费时的。

2.5 小结

  • 所有的信号产生方式都是由OS来执行的,因为OS是进程的管理者

  • 信号并不是立即处理的,是在合适的时间

  • 信号需要被保存在PCB中

  • 进程若没有收到信号的时候,进程就知道如何对信号处理

  • OS向进程发信号就是OS修改目标进程的PCB中的信号位图 

3. 进程退出时的核心转储问题

进程退出时有两种方式:Term、Core,以core方式退出的进程可以利用核心转储来快速定位错误

ulimit -a : 用于显示当前shell的各种资源限制(ulimits)

ulinit -c 1024, 打开云服务器的core file选项,将size设置为1024

demo代码:

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

int main()
{
    while(1)
    {
      int a[10];
      a[10000] = 106;
   }
    return 0;
}

 当打开core file选项前:

  当打开core file选项后结果为下,并且当前目录下会生成一个core.pid文件:

 core dumped:核心转储—当进程出现异常时,我们将进程在对应的时刻,在内存中的有效数据转储到磁盘中;那么它存在的意义是什么呢?为了支持调试,如何支持?--gdb 文件

在gdb上下文中输入: core-file core.pid   即可找到异常位置 ,如下图所示,就能找到上面代码中的问题。这种方式称为事后调试

4. 信号捕捉初识

前文提到过,信号是可以被自定义捕捉的,siganl函数就是来进行信号捕捉的

#include <signal.h>

sighandler_t signal(int signum, sighandler_t handler);

参数:signum:信号编号或宏定义

           handler:回调函数,用来如何处理这个信号

#include <stdio.h>    
#include <signal.h>    
void handler(int sig)    
{    
 printf("catch a sig : %d\n", sig);    
}    
int main()    
{    
 signal(2, handler); //前文提到过,信号是可以被自定义捕捉的,siganl函数就是来进行信号捕捉的,提了解一下                                                                                 
 while(1);    
 return 0;    
}   

可以看到,一开始,代码进入死循环,当我们在命令行按 ctrl+c 时,进程不会退出,而是执行了我们自己定义的动作。-- 信号处理的自定义动作

对所有的[1, 31]信号捕捉后,是否这个进程就无法杀死了呢? -- 不会 kill -9 pid 会杀死任意进程

5. 阻塞信号

5.1 相关概念

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

5.2 在内核中的表示

block(block又称为信号屏蔽字)中位置内容为1的信号不会被递达,除非阻塞解除。即使没有收到某一个信号,也可以将该信号设置为阻塞状态--设置block表。若一个信号在产生前被设置为阻塞状态,当该信号产生后,不会被递达,直到阻塞解除。下面为一个伪代码:

在内核中除了两个位图外还有:typedef void(*handler_t)(int signo); -- 函数指针
handler_t handler[32] -- 数组内容为指针,指向对每个信号的处理方法(函数)。

结论: 

  • 如果一个信号没有产生,并不妨碍其先被阻塞
  • 进程通过三种结构的结合来识别信号
  • POSIX.1允许系统递送该信号一次 或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可 以依次放在一个队列里。

6. 信号捕捉

6.1 知识铺垫

用户态:处于用户态的 CPU 只能访问受限资源,不能直接访问内存等硬件设备,不能直接访问内存等硬件设备,必须通过「系统调用」陷入到内核中,才能访问这些特权资源。

内核态:处于内核态的 CPU 可以访问任意的数据,包括外围设备,比如网卡、硬盘等,处于内核态的 CPU 可以从一个程序切换到另外一个程序,并且占用 CPU 不会发生抢占情况,一般处于特权级 0 的状态我们称之为内核态

用户为了访问内核或者硬件资源,必须通过系统调用完成

用户无法以用户态的身份执行系统调用,那么执行系统调用的是进程,但是身份是内核态身份

(系统调用是比较费时的,应尽量避免频繁的系统调用)

CPU中存在指向页表的寄存器、指向task_struct的寄存器、CR3寄存器:表征当前进程的运行级别,0内核态,3用户态

看上图,OS中还有唯一的一个内核级页表,将不同进程的内核空间映射到物理内存的同一块区域,那么访问OS的接口,只需要在自己的地址空间进行跳转就可以了

6.2 信号捕捉流程

信号在产生时,不是被立即处理的,是从内核态返回用户态的时候进行处理的,那么是什么时候进入的内核态呢?-- 系统调用/进程切换 

信号捕捉流程如下:

  • 进程由于中断/异常进入内核态返回用户态之前会检查当前进程PCB中的block、pending、handler表;
  • 先查看blockblock表若blok为1无论是否产生信号都不处理,直接返回;为0,继续检查pending表,查看信号是否产生;
  • 若一个位置block为0,pending为1,则继续查handler表匹配的方法,执行对应的处理方法;
  • 若handler表中的方法是自定义方法,由于自定义方法处于用户态,此时进程还要通过特定的调用从内核态变为用户态执行对应的方法(注意这里是无法在内核态执行用户态代码的)
  • 执行完处理方法后,再返回内核态(不能直接执行完处理方法后直接返回带用户态的代码处),继续返回到用户态执行到的对应的代码处

 如果信号的处理方法为自定义的那么一定涉及到四次状态的切换

6.3 sigset_t

        每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。 因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号 的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。

6.4 信号集操作函数

#include <signal.h>
int sigemptyset(sigset_t *set); // 初始化,将所有信号对应的比特位清0
int sigfillset(sigset_t *set);        // 初始化,将所有信号对应的比特位置1 
int sigaddset (sigset_t *set, int signo);  // 将signo信号的比特位置为1
int sigdelset(sigset_t *set, int signo);    // 将signo信号的比特位置0
int sigismember(const sigset_t *set, int signo);  // 判断set中是否包含signo信号

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

参数:how:指示如何更改

                      SIG_BLOCK:mask = mask | set -- set包含了我们希望添加到当前信号屏                                                                                        蔽字的信号

                      SIG_UNBLOCK:mask = mask & ~set  -- set包含了我们希望从当前信号                                                                                                  屏蔽字中解除阻塞的信号

                      SIG_SETMASK:mask = set -- 设置当前信号屏蔽字为set所指向的值

           set:按照how更改信号屏蔽字

           oset:读取当前进程的信号屏蔽字

返回值:成功返回0,失败返回-1

int sigpending(sigset_t *set); // 获取当前进程的pending位图/未决信号集

成功返回0,失败返回-1

对上述接口使用的demo代码:

#include <iostream>    
#include <unistd.h>    
#include <signal.h>    
#include <vector>    
    
#define MAXSIGNUM 31    
    
using namespace std;    
    
static vector<int> sigarr = {2};    
    
void printPending(sigset_t *pending)    
{    
    for(int i = MAXSIGNUM; i >= 1; i--)    
    {    
        if(sigismember(pending, i)) cout << "1";    
        else cout << "0";    
    }    
    cout << endl;    
}    
    
int main()    
{    
    // 1.先尝试屏蔽指定的信号    
    sigset_t block, oblock, pending;    
    // 1.1 初始化    
    sigemptyset(&block);  // 将位图结构中都置为0                                                                                                                                             
    sigemptyset(&oblock);  // 将位图结构中都置为0    
    sigemptyset(&pending);  // 将位图结构中都置为0    
    // 1.2 添加要屏蔽的信号    
    //for(const auto &e:sigarr)     
    sigaddset(&block, 2);    
    // 1.3 开始屏蔽    
    sigprocmask(SIG_SETMASK, &block, &oblock);    
    
    // 2.遍历打印pending的信号集    
    int cnt = 10;    
    while(1)  
    {    
        // 2.1 初始化pending信号集
        sigisemptyset(&pending);
        // 2.2 获取当前进程的未决信号集
        sigpending(&pending);
        // 2.3 打印
        printPending(&pending);

        sleep(1);
        if(cnt-- <= 0)
        {
            cout << "恢复对信号的屏蔽,不屏蔽任何信号\n" << endl;
            sigprocmask(SIG_SETMASK, &oblock, &block); // 一旦解除信号的屏蔽,且此时信号已经处于未决状态时,会立马把该信号递达,执行对应的处理动作
        }
    }


    return 0;
}

这段代码实现的功能是,阻塞2号信号(ctrl+c),打印pending表,在10s后解除对所有信号的屏蔽。

若在10s内收到2号信号时,不会直接处理,在10s后解除对2号信号的屏蔽后,执行2号信号的处理方法,默认为终止程序。结果如下:

6.5 sigaction

struct sigaction:

其中sigset_t sa_mask:当正在处理某种信号时,想顺便屏蔽其他信号,就可以添加到这个                                                   sa_mask 

int sigaction(int signo, const struct sigaction* act, struct sigaction* oact);

参数:signo:

           act:输入性参数

           oact: 输出型参数:获取对应信号旧的处理方法

返回值:成功返回0,失败返回-1

#include <iostream>    
#include <signal.h>    
#include <cstdio>    
#include <unistd.h>    
    
using namespace std;    
    
void Count(int cnt)    
{    
    while(cnt)    
    {    
        printf("cnt: %2d\r", cnt--);    
        fflush(stdout);    
        sleep(1);    
    }    
    cout << endl;    
}    
    
void handler(int signo)    
{    
    cout << "正在处理" << signo << "号信号" << endl;                                                                                             
    Count(20);    
}    
    
int main()    
{    
    struct sigaction act, oact;    
    act.sa_handler = handler;    
    act.sa_flags = 0;    
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, 3);

    sigaction(SIGINT, &act, &oact);
    // sigaction(3, &act, &oact);
    while(1)
    {
        cout << "我是一个进程" << endl;
        sleep(1);
    }

    return 0;
}

上述代码想要实现的功能是,在处理2号信号期间同时屏蔽掉3号信号

当我们在递达一个信号期间,同类型的信号无法被递达。

        当前信号正在被捕捉,系统会将当前信号加入到进程的信号屏蔽字block,完成捕捉动作,系统又会自动解除对该信号的屏蔽。一般一个信号被解除屏蔽时,如果该信号已经被pending,会自动递达当前信号

        当某个信号的处理函数被调用时, 内核自动将当前信号加入进程的信号屏蔽字 , 当信号处理函数返回时自动恢复原来的信号屏蔽字, 这样就保证了在处理某个信号时 , 如果这种信号再次产生 , 那么 它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时, 除了当前信号被自动屏蔽之外 , 还希望自动屏蔽另外一些信号 , 则用 sa_mask 字段说明这些需要额外屏蔽的信号, 当信号处理函数返回时自动恢复原来的信号屏蔽字

7. 可重入函数

  • main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的 时候,因 为硬件中断使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换 到sighandler函 数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的 两步都做完之后从 sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续 往下执行,先前做第一步之后被打断,现在继续做完第二步。结果是,main函数和sighandler先后 向链表中插入两个节点,而最后只有一个节点真正插入链表中了
  • 像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为 不可重入函数;反之,如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数

(1).一般而言,认为main执行流和信号捕捉执行流是两个执行流

(2).如果在main中和在sighandler中,某函数被重复的进入,出问题--该函数为不可重入函数;若未出问题则为可重入函数 

可重入/不可重入不是一个问题,也不需要解决。目前大部分情况都为不可重入函数

如果一个函数符合以下条件之一则是不可重入的:
  • 调用了malloc或free,因为malloc也是用全局链表来管理堆的。
  • 调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构

8. 关键字volatile

关键字volatile:保持内存可见性,避免编译器优化导致的错误

#include <stdio.h>                                                   
  #include <signal.h>    
      
volatile int quit = 0;    
      
  void handle(int signo)    
  {    
      printf("%d号信号,正在被捕捉!\n", signo);    
      printf("quit: %d", quit);    
      quit = 1;    
      printf(" -> quit: %d", quit);    
  }    
      
  int main()    
  {    
      signal(2, handle);    
      while (!quit)    
      {    
          printf("正在循环!\n");    
        sleep(1);    
      };    
      printf("注意,我是正常退出的!\n");    
      
      return 0;    
  }  

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

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

相关文章

VaRest插件常用节点以及Http请求数据

1.解析json &#xff08;1&#xff09;Construct Json Object&#xff1a;构建json对象 &#xff08;2&#xff09;Decode Json&#xff1a;解析json 将string转换为json &#xff08;3&#xff09;Encode json&#xff1a;将json转换为string &#xff08;4&#xff09;Get S…

麒麟v10-sp3安装kkfileview

1、上传包到服务器 执行&#xff1a;/bin/startup.sh 会自动安装LibreOffice&#xff0c;因为/bin/install.sh判断了不是redhat-release就是ubuntu&#xff0c;导致麒麟系统会走ubuntu&#xff0c;所以会失败&#xff0c;这里改一下如果是麒麟也走install_redhat就可以了 也…

HarmonyOS Next开发学习手册——显示图片 (Image)

开发者经常需要在应用中显示一些图片&#xff0c;例如&#xff1a;按钮中的icon、网络图片、本地图片等。在应用中显示图片需要使用Image组件实现&#xff0c;Image支持多种图片格式&#xff0c;包括png、jpg、bmp、svg和gif&#xff0c;具体用法请参考 Image 组件。 Image通过…

亚马逊广告如何设置关键词竞价获取最优广告投入产出比 (ACOS)

在投放亚马逊商品广告的时候&#xff0c;从我们通常的理解来说&#xff0c;关键词竞价CPC设置的越高&#xff0c;广告投入产出比 (ACOS)越高&#xff0c;所以我们通常希望CPC越低越好&#xff0c;但是从我们实际投放广告来看&#xff0c;CPC与ACOS并不是线性相关。有时候CPC设定…

大数据开发助手:Coze平台上一款致力于高效解决大数据开发问题的智能Bot!

大数据开发助手&#xff1a;Coze平台上一款致力于高效解决大数据开发问题的智能Bot 核心技术揭秘1. **自然语言处理&#xff08;NLP&#xff09;**2. **知识图谱构建**3. **个性化推荐算法** 功能特色概览1. **即时问题解答**2. **最佳实践分享**3. **个性化学习路径**4. **社区…

“蓝潮卫士“水位雨量监测一体机,重塑城市防洪新防线!

​ 6月24日&#xff0c;湖南长沙遭遇了一场突如其来的特大暴雨侵袭。天空像破了个口子&#xff0c;雨水倾盆而下&#xff0c;仅仅1小时&#xff0c;就下了54个西湖&#xff0c;降水量突破了历史同期极值。这场暴雨直接导致了严重的城市内涝问题&#xff0c;部分地区瞬间变成一…

DataWhaleAI Tsak1 运行Baseline

题目背景 在当今数字化时代&#xff0c;企业积累了丰富的对话数据&#xff0c;这些数据不仅是客户与企业之间交流的记录&#xff0c;更是隐藏着宝贵信息的宝库。在这个背景下&#xff0c;群聊对话分角色要素提取成为了企业营销和服务的一项重要策略。 群聊对话分角色要素提取…

CAS服务端部署

部署CAS Cas服务端其实就是一个war包。 在资源\cas\source\cas-server-4.0.0-release\cas-server-4.0.0\modules目录下cas-server-webapp-4.0.0.war 将其改名为cas.war放入tomcat目录下的webapps下。启动tomcat自动解压war包。浏览器输入 登录页面 http://localhost:8080/ca…

前端:多服务端接口资源整合与zip打包下载

项目需求 前端项目开发中,有一个页面需要去整合多个服务接口返回的数据资源,并且需要将这多个服务接口接口返回的数据进行资源压缩,最终打包成zip压缩包,并在客户端完成下载。 基本需求梳理如下, 实现思路 这个需求点其实本质上还是传统的“文件下载”功能需求,常见的例如…

昇思25天学习打卡营第6天|网络构建

网络构建 概念模型模型参数 概念 神经网络模型是由神经网络层和Tensor操作构成的&#xff0c;mindspore.nn提供了常见神经网络层的实现&#xff0c;在MindSpore中&#xff0c;Cell类是构建所有网络的基类&#xff0c;也是网络的基本单元。一个神经网络模型表示为一个Cell&…

【AI大模型】在健康睡眠监测中的深度融合与实践案例

文章目录 1. 应用方案2. 技术实现2.1 数据采集与预处理2.2 构建与训练模型2.3 个性化建议生成 3. 优化策略4. 应用示例&#xff1a;多模态数据融合与实时监测4.1 数据采集4.2 实时监测与反馈 5. 深入分析模型选择和优化5.1 LSTM模型的优势和优化策略5.2 CNN模型的优势和优化策略…

软考:软件设计师 知识点整理 1

一. 计算机组成与体系结构 1. 数据的表示 &#xff08;1&#xff09;进制转换 进制数码基数位权十进制&#xff08;D&#xff09;0,1,2,3,4,5,6,7,8,910二进制&#xff08;B&#xff09;0,12十六进制&#xff08;H&#xff09;0~9,A,B,C,D,E,F16 按权展开法&#xff1a; 二…

【深入浅出 】——【Python 字典】——【详解】

目录 1. 什么是 Python 字典&#xff1f; 1.1 字典的基本概念 1.2 字典的用途 1.3 字典的优势 2. 字典的基本特点 2.1 键的唯一性 2.2 可变性 2.3 无序性 3. 如何创建字典&#xff1f; 3.1 使用 {} 符号 3.2 使用 dict() 工厂方法 3.3 使用 fromkeys() 方法 4. 字…

js修改scss变量

style.scss $color : var(--color,#ccc); // 默认值 #ccc .color{background: $color; } 定义了一个scss变量&#xff08;$color&#xff09;&#xff0c;用普通的css变量&#xff08;--color&#xff09;给他赋值&#xff0c;这里需要一个默认值&#xff0c;此时css变量(--co…

线性图标设计

创建图标区域 按键A&#xff0c;创建一个24x24的背景。 图标绘制包含几个点 矢量图形绘制&#xff1a;箭头、圆、三角...... 绘制箭头和矩形 1.下载图标 双击矩形选中要删除的点 调整一下即可得到下载的图标。 2.时间图标 按快捷键O画个圆&#xff0c;L加两条线变成一个时钟…

Spire.PDF for .NET【文档操作】演示:在 PDF 中创建目录 (TOC)

目录在增强文档的可读性和可导航性方面起着至关重要的作用。它为读者提供了文档结构的清晰概述&#xff0c;使他们能够快速找到并访问他们感兴趣的特定部分或信息。这对于较长的文档&#xff08;例如报告、书籍或学术论文&#xff09;尤其有价值&#xff0c;因为读者可能需要多…

Games101学习笔记 Lecture 15: Ray Tracing 3 (Light Transport Global Illumination)

Lecture 15: Ray Tracing 3 (Light Transport & Global Illumination 一、BRDF 双向反射分布函数定义 二、反射方程 Reflection Equation三、渲染方程1.重写反射方程2.当其他的点反射的radiance作为入射 一、BRDF 双向反射分布函数 定义 计算不同的反射方向上会分布多少能…

选哪个短剧系统源码好:全面评估与决策指南

在短剧内容创作和分享日益流行的今天&#xff0c;选择合适的短剧系统源码对于构建一个成功的短剧平台至关重要。短剧系统源码不仅关系到平台的稳定性和用户体验&#xff0c;还直接影响到内容创作者和观众的互动质量。本文将提供一份全面的评估指南&#xff0c;帮助您在众多短剧…

道可云AI智能体平台全新升级,加快培育发展新质生产力

数字化时代浪潮下&#xff0c;以人工智能为代表的新一代信息技术正在加速推动社会变革&#xff0c;给各行各业带来巨大发展机遇。在AI技术的加持下&#xff0c;“人工智能”成为时代发展趋势&#xff0c;也是加快培育和发展新质生产力的新动能。 为培育数字经济发展新动能&…

【C语言】C语言 4 个编译过程详解

C语言的编译过程涉及几个关键步骤、概念和细节&#xff0c;每个步骤都有助于将人类可读的源代码转换为可执行的机器码。以下是详细的解释和示例&#xff1a; 一、什么是编译&#xff1f; 编译是将源代码转换为目标代码的过程。它是在编译器的帮助下完成的。编译器检查源代码是…