Linux进程信号的处理

目录

一、信号的引入

二、信号的产生

1.通过键盘产生

(1)发送2号信号

(2)只能向前端进程传递信号

2.程序异常收到信号

(1)程序异常发送信号的现象

(2)程序异常发送信号的原因

(3)如何定位程序异常

3.系统调用产生的信号

4.软件条件产生信号

5.如何理解OS向进程发送信号

三、信号产生中

1.基本概念

2.三张表

3.sigset_t

(1)系统调用类型

(2)信号集处理函数

4.sigprocmask与sigpending

(1)sigprocmask

(2)sigpending

5.举例

(1)屏蔽2号信号

(2)打印信号位图

(3)将2号信号递达

四、信号的处理

1.信号的处理方式

2.信号检测和处理

(1)用户态和内核态

(2)信号检测与处理流程

3.修改信号执行的两个函数

五、补充概念

1.可重入函数

2.volatile关键字

3.SIGCHLD信号

六、总结


信号的发送与进程间通信是不同的,信号只能够由操作系统来进行发送,而进程的作用是请求操作系统来发送信号。我们使用control+C可以终止一个进程的本质上其实就是向该进程发送一个2号信号。

一、信号的引入

1.在生活中有很多信号的场景的存在,比如红绿灯,闹钟,老师的脸色等,当我们获得了这些信号之后,我们立刻就知道下一步要去做什么了。注意,信号与进程之间通信的信号量之间是没有任何关系的。

2.同时,只有当绿灯量起来的时候我们才知道应该在绿灯的时候行走吗?显然不是的,进程也是这样,不管是否接收到了信号,进程都知道如果收到这些信号应该做什么。进程收到信号之后应该做什么是由操作系统工程师已经处理好的。

3.当我们收到某一种信号时,不一定立刻去处理该信号,因为可能有重要的事情需要去做。此时就需要将信号存储。信号存储的位置就是进程的PCB,信号的本身也是数据,因此在向进程中传递信号的本质就是向PCB中写入数据。

4.信号的发出者只有操作系统,无论我们如何发送信号都是请求操作系统来进行发送的。

通过以上分析,我们可以将信号的发送分为三大部分:分别是信号产生,信号保存和信号处理。在Linux系统中,我们可以使用

kill -l

来查看所有信号:

注意观察,是没有32和33号信号的,我们只研究前31个信号。

二、信号的产生

1.通过键盘产生

当一个进程是一个死循环的进程时,我们可以使用键盘进程control+C来终止掉进程。control+C的本质就是向该进程传递2号信号从而使该进程终止注意,键盘只能向前端进程传递信号。

下面来验证一下以上内容:

(1)发送2号信号

要验证这一问题,我们需要认识一个函数:

在signal这一函数中,它的第一个参数代表的是信号编号。它的第二个参数是一个函数指针,它指向的是一个返回值为void,参数为int的函数,该函数的参数就是signum,该参数会被signal函数自动传入到该函数中。

注意该函数执行的前提是收到了signum号信号,否则不会执行该函数。

当向该进程发送signum信号,则执行handler所指向的函数。我们可以根据该函数这一功能来间接判断发送的是哪一个信号。

#include<stdio.h>    
#include<signal.h>    
#include<unistd.h>    
void handler(int signo)    
{    
  printf("get a signo:%d\n",signo);    
}    
int main()    
{    
  signal(2,handler); //当收到2号信号时执行handler函数   
  while(1)    
  {    
    sleep(1);                                                                                                                                          
    printf("assistant is stupid!\n");    
  }    
}    

此时在进程运行的过程中,我们使用control+C就不会使进程退出了,而是直接执行handler函数:

我们可以使用control+\,即三号信号来终止进程。

(2)只能向前端进程传递信号

如果我们让进程在后台中运行,那么键盘将无法向进程中输入信号:

./test &

此时我们发现,当在键盘输入control+C的时候,并没有向进程传递信号,此时进程只能通过系统向进程传递9号信号来关闭。

2.程序异常收到信号

(1)程序异常发送信号的现象

当我们对一个空指针进行解引用的时候,程序会发生崩溃推出的,而程序退出的本质是收到了某种信号,导致了程序的退出,我们可以通过一下代码来找到令空指针解引用程序退出的信号。

#include<stdio.h>    
#include<signal.h>    
#include<unistd.h>    
#include<stdlib.h>    
void handler(int signo)    
{    
  printf("get the signal:%d\n",signo);    
  exit(1);    
}    
int main()    
{    
  int sig=1;    
  for(;sig<=31;sig++)    
  {    
     signal(sig,handler);//接收所有信号    
  }    
  int* p=NULL;    
  *p=100;//Segmentation fault段错误
}            

此时我们发现,传入的是第11号信号。

我们还可以测试一下,如果进行除0操作而造成崩溃使程序退出的信号:

我们发现传入的是8号信号。

因此我们可以得出结论:在win或者Linux系统下,进程崩溃的本质是进程收到了与崩溃对应的信号,然后进程执行信号的默认动作,即杀死进程。

(2)程序异常发送信号的原因

那么为什么会受到信号呢?

首先我们需要明确,计算机的一切计算操作都是在CPU中完成的,当CPU进程除0操作的时候会出现异常,而操作系统是硬件的管理者,当操作系统得知CPU运算出现异常的时候就会向产生异常进程发出信号,使其终止。

因此我们可以得到这样一个结论,程序的异常最终其实都会体现在其他的软件或者硬件上。

(3)如何定位程序异常

当程序异常崩溃时,我们最想知道的就是程序崩溃的原因,在哪里崩溃的。

在Linux系统中,当一个进程退出时,它的退出码和退出信号都会被设置(正常情况),当一个进程异常的时候,进程的终止信号会被设置,表明进程退出的原因。如果必要,OS还会设置退出信息中的标志位core dump(它在status的第8位),并将进程在内存中数据转移到磁盘中,方便后期调试。

在默认情况下,这种基于core dump的调试方式是被关掉的。当需要进行coredump调试,coredump位被设为1

我们可以通过ulimit指令来进行查看:

ulimit -a //查看系统资源
ullimit -c 10240 //允许进行coredump,设置大小为10240

此时当我们再运行问题代码时,会带一个core dump的提示,并且在当前目录下,还会找到生成的一个文件,这个就是我们的调试文件:

此时我们使用gdb来进行调试(注意如果要使用gdb的话需要在生成可执行文件的时候使用-g选项):
此时当我们使用r选项令代码运行起来时,gdb就会自动查看core.2206这个文件,从而给出问题出现的位置:

这里表示异常发生在代码的28行。

注意,并不是所有的异常(信号)可以进行上述coredump调试。

3.系统调用产生的信号

说人话就是用代码输入信号。

我们不仅可以在终端使用kill -x输入信号,系统也提供了一个名为kill的函数可以用于输入信号:

它的第一个参数为进程的pid,它的第二个参数为信号序号。通过这个函数我们可以向pid这个进程发送sig号信号。

我们可以使用它以及命令行传参,来模拟实现kill进程:

我们令argv第二个参数为信号,第三个参数为进程的pid,与kill进行对应。

static void Usage(const char* proc)//说明函数    
{    
  printf("Usage:\n\t %s signo who\n",proc);    
}    
int main(int argc,char* argv[])    
{    
//./mytest signo who
if(argc!=3)
{
  Usage(argv[0]);
  return 1;
}
int signo=atoi(argv[1]);
int who=atoi(argv[2]);
printf("signo:%d,who:%d\n",signo,who);
kill(who,signo);
}

此时就成功地模拟实现了kill进程。
kill函数可以给任何pid的进程输入信号,同时还有一个专门给自己输入信号的函数:raise。

raise(8)代表给自己输入8号信号

还可以使用abort()来给自己的进程输入abort信号(6号信号)。

4.软件条件产生信号

通过某种软件出发信号的发送,系统层面设置定时器,或者某种操作而导致条件不就绪的场景下,出发的信号发送。

比如在进程通信的管道通信中,当读端关闭,写端仍然在继续写,操作系统就会给写端发送sigpipe信号。就是一种典型的软件条件触发的信号发送。

我们再来介绍一个软件条件产生的信号,信号发出的软件OS

它表示的是一个闹钟,意思是seconds秒之后发送14号信号。它的返回值是上一个闹钟剩余的时间。当之前已经设定闹钟且该闹钟没有结束时,再次调用alarm只会取消闹钟,而不是设定新的闹钟。

int ret=alarm(30);
printf("assistant is stupid!\n");
while(1)
{
    sleep(1);
    int res=alarm(0);
    printf("ret:%d,res:%d\n",ret,res);
}

此时我们就可以捕捉到alarm的返回值,即上一个alarm剩余的时钟时间。

我们还可以利用这个alarm函数来记录一下5s内程序运行的速度:

int count=0;
alarm(5);
printf("hello world!\n");
while(1)
{
    count++;
    printf("%d\n",count);
}

可以看到5s内count被加到了446706。

5.如何理解OS向进程发送信号

OS发送的信号时直接发送给进程的PCB的。在进程的PCB中存在一个32位整数,我们将该整数看成一个位图的结构,它的每一位都代表一个信号,当值为1时,表示收到了该信号,当值为0时,表示没有收到该信号。

信号的传递本质是信号的写入,即OS向进程的PCB中的位图中写入比特位,就完成了信号的发送。

三、信号产生中

1.基本概念

1.信号递达:实际执行的信号处理动作叫做信号递达。递达其实就是信号的执行过程。

2.信号未决:信号从产生到递达之间的状态,称为信号未决。

3.信号阻塞:进程可以选择阻塞某个信号。

当进程阻塞某个信号时,该信号产生时将处于未决状态,直到进程取消阻塞,才执行递达的动作。只要被阻塞就不会被递达。这其实就是信号保存的过程。

阻塞与忽略的却别在于,忽略已经开始进行处理信号了,只不过处理信号的方式是不处理。

2.三张表

操作系统根据三张表来确定是否处理信号,以及如何处理信号:

这三张表分别是:block,pending,handler

其中block也是一个32位整数,我们也将其看成一个位图,比特位的位置代表信号的编号,比特位的内容代表是否被阻塞。block也称位屏蔽字。

pending表示的是是否收到该信号。即32位位图。

handler的内容是函数指针,它是一个函数指针数组,如果信号成功递达,则执行该函数所指向的函数,则该函数的地址会被传递给对应信号的handler中。

第一行表示的是,没有收到信号1,信号1没有被阻塞,如果信号1被递达执行默认操作。

第二行表示的是,收到了信号2,信号2被阻塞了,如果信号2递达执行忽略策略。

第三行表示的是,没有收到信号3,信号3被阻塞了,如果收到信号3,执行默认的操作。

等等。。

注意,如果某个信号被屏蔽了,操作系统就不会关心该信号是否被接收。

3.sigset_t

(1)系统调用类型

系统调用除了可以体现在函数上之外,还体现在操作系统提供的数据类型上。这些数据类型的最终目的也是配合系统调用函数来使用的。

比如在以上的三张表中,block表和pending表的本质其实就是一个32位整数,这个32位整数的类型就是sigset_t,由于它们并不是在用户层出现而是在希层出现的。因此需要操作系统的接口来实现对表的更改。

因此要修改sigset_t类型的数据,不能让用户直接进行修改,而是需要使用系统调用接口来进行修改。

sigset_t s;//定义一个sigset_t的变量

s=10;//错误,用户不能直接修改该变量的值

(2)信号集处理函数

修改sigset_t类型的变量,操作系统提供了如下函数:

int sigemptyset(sigset_t set);//初始化set所指向的信号集,将其中所有信号对应的bit清零。

int sigfillset(sigset_t set);//初始化set所指向的信号集,将所有信号对应的bit置为1。

int sigaddset(sigset_t set,int signo);//在信号集中添加signo号信号。

int sigdelset(sigset_t set,int signo);//在信号集中删除signo号信号。

4.sigprocmask与sigpending

(1)sigprocmask

使用man手册查询结果如下:

该函数是与屏蔽字有关的函数(block):

第二个参数表示传入一个新的sigset_t 类型的变量set,是一个输入性参数。

第三个参数表示返回之前的屏蔽字,是一个输出型参数。

第一个参数表示的是对屏蔽字进行的操作,可以传入如下几个变量:

SIG_BLOCK:包含了我们希望添加到屏蔽字的信号,相当于mask=mask|set。

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

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

(2)sigpending

它的用法很简单:

读取当前进程的pending表,通过set传出。

5.举例

下面用一个例子来具体地使用这些函数:

(1)屏蔽2号信号

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    sigset_t iset,oset;
    sigemptyset(&iset);
    sigemptyset(&oset);
    sigaddset(&iset,2);
    sigprocmask(SIG_SETMASK,&iset,&oset);
    while(1)
    {
        sleep(1);
        printf("hello world!\n");
    }
}

(2)打印信号位图

如果进程首先屏蔽掉2号信号,此时传入2号信号,并不断获取pending位图,并打印显示。由于2号信号不会递达,因此它一直在pending位图中,我们就可以进行观察了。

void showpending(sigset_t* set)
{
    int i=1;
    for(i=1;i<=31;i++)
    {
        if(sigismember(set,i))
        {
            printf("1");
        }
        else
        {
            printf("0");
        }
    }
    printf("\n");
}

int main()
{
    sigset_t iset,oset;
    sigemptyset(&iset);
    sigemptyset(&oset);
    sigprocmask(SIG_SETMASK,&iset,&oset);
    sigset_t pending;
    while(1)
    {
        sigemptyset(&pending);
        sigpending(&pending);
        showpending(&pending);
        sleep(1);
    }
}

此时当我们发送2号信号时,就可以看到pending表的变化:

(3)将2号信号递达

当将信号屏蔽之后,再解除屏蔽我们就可以看到pending表中2号信号由1变成0的过程。

但是由于2号信号递达之后进程会立刻退出,因此我们需要修改2号信号的执行方式来方便进行观察。

我们令20s之后2号信号递达:

  #include<stdio.h>    
  #include<signal.h>    
  #include<stdlib.h>    
  #include<unistd.h>    
  void showpending(sigset_t* set)    
  {    
    int i=1;                                                                                                                                           
    for(i=1;i<=31;i++)    
    {    
      if(sigismember(set,i))    
      {    
        printf("1");    
      }    
      else{    
        printf("0");    
      }    
    }    
    printf("\n");    
  }    
void handler(int signo)    
  {    
    printf("2号信号被递达了!\n");    
  }    
  int main()    
  {    
    signal(2,handler);    
    sigset_t iset,oset;    
    sigemptyset(&iset);    
    sigemptyset(&oset);    
    sigaddset(&iset,2);    
    sigprocmask(SIG_SETMASK,&iset,&oset);    
    sigset_t pending;    
    int count=0;
        while(1)
    {
      count++;
      sleep(1);
      if(count==20)
      {
        sigprocmask(SIG_SETMASK,&oset,NULL);
        printf("恢复2号信号\n");
      }
      sigemptyset(&pending);
      sigpending(&pending);
      showpending(&pending);
    }
  }                                                

四、信号的处理

1.信号的处理方式

一般而言,信号的处理由三种情况:

1.默认动作:即系统默认处理信号的方式,通常是暂停或者终止。

2。忽略动作:是一种信号处理方式,只不过处理方式是什么也不干。

3.自定义动作:使用signal函数就是在修改信号的处理方式,也称为信号的捕捉。注意,9号信号不能被自定义处理。

2.信号检测和处理

在信号产生中的模块中,我们了解到OS会对进程中信号的三张表进行检测,从而判断是否要执行某个信号。这里提到了两个关键词,分别是检测和执行,那么这两个动作发生在哪里呢?

要了解这一部分,我们需要了解进程处理过程中的两个状态:用户态和内核态

(1)用户态和内核态

用户态:用户代码和数据被访问或者执行到我所处的状态,我们自己 的代码都是在用户被执行的。

内核态:执行OS代码和数据的时候,计算机所处的状态,叫做内核态。OS代码全部在内核态执行。

两者的区别在于权限,它们之间的切换表现为系统调用。

当用户调用系统接口时,处理进入函数,身份也会发生变化,会由用户的身份变成内核的身份。我们知道当执行某个进程的时候,用户的代码和数据会被加载到内存中,同理内核的代码和数据也一定要加载到内存中。当我们开机的时候就是将内存和数据加载到内存中的过程。

除了用户区的页表之外,还有内核区的页表,对于任意一个进程来说,它的内核区的内容都是一样的。因此内核区的页表只需要存在一份即可,被多个进程所使用。由于内核页表的存在,我们能够保证所有的进程都能找到同一个操作系统。所谓系统调用,其实就是进程的身份转换为内核,然后根据内核页表找到系统函数并执行。

同时在CPU内部,有一个CR3的寄存器,它是用来判断当前进程执行的是用户态还是内核态,如果是内核态,它的值就会被赋值为0,如果是用户态它的值就会被赋值为3。

(2)信号检测与处理流程

当我们执行用户代码的时候,执行到一个系统调用,此时进入内核态,执行完系统调用的函数的代码后需要返回用户态。继续执行用户的下一条代码。在返回之前需要进行信号的检测与处理操作。当没有信号,或者信号被阻塞,或者信号的处理方式不是自定义的,此时直接进入用户态执行用户的下一条代码。

但如果信号被递达了,且是自定义处理的。此时就需要进入用户态执行该自定义函数,然后再返回内核态的sigret,然后再返回用户态执行下一条代码:

整个处理流程类似数学中的无穷大:

在处理信号的过程中:

1.当handler为默认状态的时候,直接释放资源,进程结束。(因为在内核态)

2.当handler为忽略状态的时候,直接将pending的1置为0。

3.当handler为自定义的时候,进程由用户态->内核态->用户态->内核态->用户态,一共经历了四次转变。

但是为什么要切换到用户态去执行handler的代码呢?OS显然也有权限去执行,但是它不相信任何人,用户只能使用OS的接口去让OS执行一些列的操作。

结论:当内核态即将切换为用户态的时候,进行信号的检测和处理。

3.修改信号执行的两个函数

其中一个函数就是signal函数,这个在前面已经介绍了,这里不多赘述。

另一个函数是sigaction。我们可以通过man手册进行查询:

它的第二个参数是一个输入型参数,表示的是一个结构体(这个结构体和函数是同名的),这个结构体中的第一个参数是一个函数指针,指向自定义的信号处理方法:

它的第三个参数是一个和第二个参数相同类型的结构体,它的第一个元素指向未修改之前的信号处理方法。

下面使用这个函数来自定义一个信号处理的方法:

#include<stdio.h>    
#include<signal.h>    
#include<stdlib.h>    
#include<unistd.h>    
#include<string.h>    
void handler(int signo)    
{    
  printf("get a signo:%d\n",signo);                                                                                                                    
}    
int main()    
{    
  struct sigaction act;    
  memset(&act,0,sizeof(act));    
  act.sa_handler=handler;    
  sigaction(2,&act,NULL);    
  while(1)    
  {    
    printf("hello world!\n");    
    sleep(1);    
  }    
}    

此时就对2号信号完成了修改:

下面来介绍一个该结构体中的另一个sa_mask:

当某个信号的处理函数在被调用的时候,内核自动将当前信号加入进程的屏蔽字。当函数结束后,屏蔽字恢复。这样保证了当处理一个信号时,如果该信号再次出现,则不会被处理。直到前一个信号已经被处理结束。

当处理一个信号时,除了该信号,我们还想屏蔽另一些信号,此时就需要sg_mask发挥作用了。

比如进程正在处理2号信号,此时在传入2号信号,信号不会被处理。我们希望不仅仅传入2号信号不会被处理,传入3号信号也不会被处理,就可以将sg_mask的3号信号置为1(sg_mask也是sigset_t类型)。

  sigemptyset(&act.sa_mask);    
  sigaddset(&act.sa_mask,3);          

在代码中加入这两段代码,即将sa_mask的信号3添加。此时当处理信号2的时候,信号也就被阻塞了。

五、补充概念

1.可重入函数

有这样一种场景,当我们在进行链表的插入时,本质上分为两步:

p->next=head;

head=p;

我们假设这段代码在一个名为insert的函数中执行。

如果执行完第一步之后,接收到了信号,信号的处理方式还是这个insert函数,又向其中插入一个新的节点,这种情况我们称之为函数的重入,他可能带来不好的后果:

此时就会造成无法找到node2这个节点的后果。

注意,并不是只有遇到系统调用接口的时候进程才会进入到内核态,当CPU调度不同的进程,将其从运行队列前端移动到后端的时候,是操作系统进行操作的,因此我们成insert为不可重入函数。与之对应的还有可重入函数。

大部分函数都是不可重入的。

2.volatile关键字

该关键字是C语言的比较冷门的关键字,我们站在信号的角度来认识一下它:

  int flag=0;    
  void handler(int signo)                                                                                                                              
  {    
    flag=1;    
    printf("change flag 0 to 1\n");    
  }    
  int main()    
  {    
    signal(2,handler);    
    while(!flag);    
    printf("进程正常退出\n");    
    return 0;    
  }    

这段代码表达的意思是,这是一个死循环的程序,当收到2号信号的时候,flag由0置为1,结束死循环,程序退出。执行结果是这样的:

如果我们编译过程中采用优化编译的方式:

gcc test.c -o mytest -O3

此时运行的结果,我们发现死循环不会被终止。

下面来分析一下原因:

在main执行流的过程中,gcc发现main函数中没有人对flag进行修改,就会进行优化:即将flag永久地存放在寄存器中,下一次CPU调度该进程的时候就不需要进行寻址访问了,而是直接使用寄存器中的值。

当CPU读取完flag之后,就不会再对其进行读取了。而信号到来,内存中的flag发生了变化再CPU中的flag是不知道的。而判断操作都是再CPU中进行的,因此会一直死循环。

为了避免这一情况,只需要将flag定义为:

volatile int flag=0

volatile的作用就是告诉编译器不要对这个变量做任何优化,要读取必须贯穿式地读取内存,不要读取缓冲区中的数据,保存内存的可见性。

3.SIGCHLD信号

该进程是在子进程退出后对父进程发出的信号。该信号的默认处理动作是忽略。

void Getchild(int signo)    
{    
  printf("get a signo:%d\n",signo);                                                                                                                    
}    
int main()    
{    
  signal(SIGCHLD,Getchild);    
  pid_t id=fork();    
  if(id==0)    
  {    
    int count=5;    
    while(count)    
    {    
      printf("I am a child:%d\n",getpid());    
      sleep(1);    
      count--;    
    }    
    exit(0);    
  }    
  while(1);    
  return 0;    
}  

当子进程退出时,就会捕捉到17号信号:

而SIGCHLD更重要的作用在于,当如果我们显示设置忽略17号信号的话,子进程退出后会直接被父进程回收,而不用等待父进程结束而变成僵尸进程:

signal(SIGCHLD,SIG_IGN)

这种做法目前只在Linux系统下有效。

六、总结

信号的内容有很多,总结起来可以分为三个大方向:
信号产生前,信号产生中,信号产生后。
信号的产生共有四种方式,分别是由键盘产生,进程崩溃产生,系统调用产生,和软件条件产生。
信号产生中要记住三张表以及它们代表的含义。以及处理三张表的各种接口。
信号产生后需要了解用户态和内核态,以及进程对信号的检测和处理的"合适时间"所指。以及两个可以修改信号的函数。
最后补充了三个概念,分别是可重入函数,volatile关键字以及SIGCHLD信号。

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

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

相关文章

Nginx、Tomcat等项目部署问题及解决方案详解

目录 前言1. Nginx部署后未按预期显示结果1.1 查看Nginx的启动情况1.2 解决启动失败的常见原因 2. 端口开启问题2.1 Windows环境下的端口开放2.2 Linux环境下的端口开放 3. 重视日志分析3.1 Nginx日志分析3.2 Tomcat日志分析 4. 开发环境与部署后运行结果不同4.1 开发环境与生产…

Android 下载进度条HorizontalProgressView 基础版

一个最基础的自定义View 水平横向进度条&#xff0c;只有圆角、下载进度控制&#xff1b;可二次定制度高&#xff1b; 核心代码&#xff1a; Overrideprotected void onDraw(NonNull Canvas canvas) {super.onDraw(canvas);int mW getMeasuredWidth();int mH getMeasuredHei…

软考:CORBA架构

CORBA过时了吗 CORBA指南 个人小结&#xff1a; IPC&#xff0c;进程间通信&#xff0c;Socket应用在不同机器之间的通信 RPC是一种技术思想而非一种规范 但站在八九十年代的当口&#xff0c;简单来说&#xff0c;就是我在本地调用了一个函数&#xff0c;或者对象的方法&…

深入理解 SQL 中的 WITH AS 语法

在日常数据库操作中&#xff0c;SQL 语句的复杂性往往会影响到查询的可读性和维护性。为了解决这个问题&#xff0c;Oracle 提供了 WITH AS 语法&#xff0c;这一功能可以极大地简化复杂查询&#xff0c;提升代码的清晰度。本文将详细介绍 WITH AS 的基本用法、优势以及一些实际…

1.机器人抓取与操作介绍-深蓝学院

介绍 操作任务 操作 • Insertion • Pushing and sliding • 其它操作任务 抓取 • 两指&#xff08;平行夹爪&#xff09;抓取 • 灵巧手抓取 7轴 Franka 对应人的手臂 6轴 UR构型去掉一个自由度 课程大纲 Robotic Manipulation 操作 • Robotic manipulation refers…

WUP-MY-POS-PRINTER 旻佑热敏打印机票据打印uniapp插件使用说明

插件地址&#xff1a;WUP-MY-POS-PRINTER 旻佑热敏打印机票据打印安卓库 简介 本插件主要用于旻佑热敏打印机打印票据&#xff0c;不支持标签打印。适用于旻佑的各型支持票据打印的热敏打印机。本插件开发时使用的打印机型号为MY-805嵌入式面板打印机&#xff0c;其他型号请先…

spyglass关于cdc检测的一处bug

最近在使用22版spyglass的cdc检测功能&#xff0c;发现struct_check的cdc检测实际时存在一些bug的。 构造如下电路&#xff0c;当qualifier和destination信号汇聚时&#xff0c;如果des信号完全将qualifier gate住&#xff0c;sg仍然会报ac_sync。当然此问题可以通过后续funct…

基于SSM的心理咨询管理管理系统(含源码+sql+视频导入教程+文档+PPT)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 基于SSM的心理咨询管理管理系统拥有三个角色&#xff1a;学生用户、咨询师、管理员 管理员&#xff1a;学生管理、咨询师管理、文档信息管理、预约信息管理、测试题目管理、测试信息管理…

vue 果蔬识别系统百度AI识别vue+springboot java开发、elementui+ echarts+ vant开发

编号&#xff1a;R03-果蔬识别系统 简介&#xff1a;vuespringboot百度AI实现的果蔬识别系统 版本&#xff1a;2025版 视频介绍&#xff1a; vuespringboot百度AI实现的果蔬识别系统前后端java开发&#xff0c;百度识别&#xff0c;带H5移动端&#xff0c;mysql数据库可视化 1 …

从零搭建开源陪诊系统:关键技术栈与架构设计

构建一个开源陪诊系统是一个涉及多种技术的复杂工程。为了让这个系统具备高效、可靠和可扩展的特点&#xff0c;我们需要从架构设计、技术栈选择到代码实现等方面进行全面的考量。本文将从零开始&#xff0c;详细介绍搭建开源陪诊系统的关键技术栈和架构设计&#xff0c;并提供…

iOS调试真机出现的 “__llvm_profile_initialize“ 错误

一、错误形式&#xff1a; app启动就崩溃&#xff0c;如下&#xff1a; Demo__llvm_profile_initialize:0x1045f7ab0 <0>: stp x20, x19, [sp, #-0x20]!0x1045f7ab4 <4>: stp x29, x30, [sp, #0x10]0x1045f7ab8 <8>: add x29, sp, #0x100x1…

如何在 Windows 上安装 Python:一步一步的指南

Python 已成为 当今最受欢迎的编程语言之一&#xff0c;在商业的各个领域中广泛应用。开发者使用 Python 构建应用程序和开发网站&#xff0c;而数据工程师则使用 Python 进行数据分析、统计分析以及构建机器学习模型。 检测是否已安装Python 默认情况下&#xff0c;Windows …

【04】RabbitMQ的集群机制

1、RabbitMQ的性能监控 关于RabbitMQ的性能监控&#xff0c;在管理控制台中提供了非常丰富的展示。例如&#xff1a;首页这个整体监控页面&#xff0c;就展示了非常多详细的信息&#xff1a; 还包括消息的生产消费频率、关键组件的使用情况等等非常多的消息。都可以在这个管理…

python代码中通过pymobiledevice3访问iOS沙盒目录获取app日志

【背景】 在进行业务操作过程中&#xff0c;即在app上的一些操作&#xff0c;在日志中会有对应的节点&#xff0c;例如&#xff0c;下面是查看设备实时视频过程对应的一些关键节点&#xff1a; 1、TxDeviceAwakeLogicHelper&#xff1a;wakeStart deviceId CxD2BA11000xxxx …

Vue笔记-element ui中关于table的前端分页

对于 Element UI 表格的前端分页&#xff0c;可以在组件中使用 JavaScript 来实现数据的分页显示&#xff0c;而不必从后端获取已分页的数据。以下是一个简单的示例&#xff0c;演示如何在前端进行 Element UI 表格的分页&#xff1a; <template><div><el-tabl…

DIY可视化-uniapp悬浮菜单支持拖动、吸附-代码生成器

在Uniapp中&#xff0c;悬浮菜单支持拖动和吸附功能&#xff0c;可以为用户带来更加灵活和便捷的操作体验。以下是对这两个功能的详细解释&#xff1a; 悬浮菜单支持拖动 提高用户体验&#xff1a;用户可以根据自己的需要&#xff0c;将悬浮菜单拖动到屏幕上的任意位置&#x…

一二三应用开发平台自定义查询设计与实现系列2——查询方案功能实现

查询方案功能实现 上面实现了自定义查询功能框架&#xff0c;从用户角度出发&#xff0c;有些条件组合可以形成特定的查询方案&#xff0c;对应着业务查询场景。诸多查询条件的组合&#xff0c;不能每次都让用户来设置&#xff0c;而是应该保存下来&#xff0c;下次可以直接使…

MySql基础:事务

1. 事务的简介 1.1 什么是事务 事务就是一组DML语句组成&#xff0c;这些语句在逻辑上存在相关性&#xff0c;这一组DML语句要么全部成功&#xff0c;要么全部失败&#xff0c;是一个整体。MySQL提供一种机制&#xff0c;保证我们达到这样的效果。事务还规定不同的客户端看到的…

数字IC开发:布局布线

数字IC开发&#xff1a;布局布线 前端经过DFT&#xff0c;综合后输出网表文件给后端&#xff0c;由后端通过布局布线&#xff0c;将网表转换为GDSII文件&#xff1b;网表文件只包含单元器件及其连接等信息&#xff0c;GDS文件则包含其物理位置&#xff0c;具体的走线&#xff1…

Linux 进程优先级 进程切换

目录 优先级 概念 为什么优先级要限制在一定范围内 进程切换 方式 EIP寄存器(程序计数器) 进程在运行时会使用寄存器来保存临时数据 进程的上下文是什么&#xff1f; 进程的上下文保存到哪&#xff1f; 内核栈或专门的上下文结构也在内核空间&#xff1f;那为什么不直…