linux入门---信号的理解

news2024/11/18 13:52:58

目录标题

  • 如何理解计算机中的信号
  • 如何查看计算机中的信号
  • 初步了解信号的保存和发送
  • 如何向目标进程发送信号
    • 情景一:使用键盘发送信号
    • 情景二:系统调用发送信号
    • 情景三:硬件异常产生信号
    • 情景四:软件条件产生信号
  • 核心转储
  • 信号的两个问题
    • 问题一
    • 问题二

如何理解计算机中的信号

我们首先通过生活中的信号来理解一下计算机中的信号,生活中会遇到很多的信号,比如说运动会上的发令枪,晚上睡觉前定的闹钟,过马路开车需要查看的红绿灯,以及手机在收到信息发出的响声这也是信号,手机发出的响声是一个信号,并且我们人是可以识别信号的,这里的识别指是人能够认识这个信号,并且我们能因为这个信号而做出对应的行为,那我们为什么能够识别红绿灯呢?为什么当听到手机发出响声之后知道是手机接收到一些信息的呢?原因是有人教育过你有经验告诉过你,当这些信号产生的时候意味着一些事情要被处理,通过教育的手段和以往经验的积累,让你在大脑里面记住了一些信号的属性(为什么会产生这样的信号?)和与之对应处理行为(我们人该如何处理这个信号),比如说我们开车的过程中发现红灯亮了起来,那我们就得在合适的地方将车停下来了,红灯就是一个信号,将车停下来就是对这个信号的处理,那为什么会有红灯这个信号呢?原因是为了维持社会交通的便利,那为什么我们知道红灯是一个信号遇到红灯是就得停车呢?原因是从小爸爸妈妈和老师就教育我们遇到红灯的时候就得停止向前运动直到红灯变成了绿灯,这是一个教育的过程让我们知道了红灯是一个信号,以及信号的处理行为,再比如说手机发出的响声就是一个信号,当手机发出声响的时候我们就知道有人或者有软件给我们发了消息,我们就可以打开手机对消息进行查看,那么我们是如何知道手机发出声响是一个信号的呢?原因是在之前使用手机的过程中每当有人或者有软件给我们发消息时手机发出声响,即使我们不用手机将他放到一边当收到消息的时候他也会发出声响,这样我们人就会积攒经验知道手机发出声响就意味着我们收到了消息,该消息可能要被处理,那么这就是我们为什么会认识信号的原因,那接收到了信号就一定得立即对其进行处理吗?答案是不是的,因为信号时可以随便产生的,但是在信号产生的时候我们可能有更重要的事情要做,比如说手机叮咚响了一声我们就必须得马上打开手机进行查看吗?对吧!没必要我们可以选择忽略这个信号直到我们忙完了再查看这个信号都不晚。信号到来的时候我们不一定要立马处理这个信号,因为我们当前可能得做着更重要的事情,所以信号产生到信号被处理的过程中存在一个时间窗口,所以在这个时间窗口里面我们必须得记住这个信号,那么如何保存信号就是我们后面要学习的内容,我们知道什么是信号是因为有人教育过我们,那对信号的处理是固定的吗?我们可不可以直接忽略掉这个信号,比如说红灯亮起的时候我们依然选着向前移动,手机不停地响但是我们依然选继续干正在做的事,答案是可以的在处理信号的时候我们可以选择直接忽略掉信号,那么同样的道理我们可以修改以往对信号处理的行为吗?比如说之前有人一直教育我们遇到红灯就得停止向前运动,这是一个默认的信号处理行为,我们可以对这个行为进行修改,当红点亮了我们也对对其进行做出行为,但是这个行为是将运动向前变成向后,同样的道理当手机响的时候默认动作是打开手机并查看消息,那么我们能对这个行为进行修改,当手机响了之后我们可以直接将手机进行关机,那么这就是对信号默认行为的修改,看到这里想必大家应该能够理解信号,但是大家心里一定存在个疑问,人可以被教育可以从经验中总结结果,那计算机又是如何认识信号的呢?进程识别信号的方式就是:认识信号+处理信号,而信号是发送给进程的,那进程如何识别信号的呢?人能够认识信号是因为人受到过教育,进程能够认识信号则是因为进程本身是程序员编写的属性和逻辑的集合,这些都是程序员编码完成的,所以我们写的程序能够识别到信号,当进程收到信号的时候,进程可能正在执行更重要的代码,所以信号不一定会被立即处理,所以进程本身必须要有对信号的保存能力,进程在处理信号的时候一般有三种动作(默认,自定义,忽略),我们把处理信号的动作称为信号捕捉。那么接下来我们就来逐步逐步的理解信号。

如何查看计算机中的信号

kill -l可以查看所有的信号
在这里插入图片描述
数字就表示信号的编号,数字后面的名称就是数字对应的宏,所以未来我们既可以使用编号也可以使用宏来操控信号,通过观察我们可以看到一共有62个信号,我们把1-31称为普通信号 34-64称为实时信号(这个不学)。

初步了解信号的保存和发送

在前面的介绍中我们知道信号发送之后可能不会立即处理,所以得将信号保存起来,那信号应该保存在哪里呢?答案是保存在task_struct里面,那该保存什么内容呢?答案是保存是否收到了指定的信号,如果收到了指定的信号就保存1没有收到就保存0,那么这里有31个信号,所以在task_struct里面就有一个unsigned int signal来存储是否收到了信号,比特位的位置代表信号的编号,比特位的内容,代表是否收到了该信号,0表示没有,1表示有,既然存储信号的方法是通过PCB中的一个位图来进行存储,那么发送信号的本质就是修改PCB中的信号位图,将位图中的某个比特位由0变成1,PCB是内核维护的数据结构对象,所以PCB的管理者是操作系统,那谁有权利修改PCB的内容呢?答案是操作系统,所以无论未来我们学习了多少种发送信号的方式,本质都是通过操作系统向目标进程发送信号,所以操作系统必须要提供发送信号处理型号的相关系统调用,我们使用的kill命令底层一定是调用了对应的系统调用,比如说我们提供了下面这样的程序:

#include<iostream>
#include<unistd.h>
using namespace std;
int main()
{
    while(1)
    {
        cout<<"我是一个死循环的进程"<<endl;
        sleep(1);
    }
    return 0;
}

生成可执行文件
在这里插入图片描述
然后运行一下程序就可以看到下面这样的场景:
在这里插入图片描述
可以看到这里在死循环的打印语句,那么想要结束这个进程我们就可以按下键盘上的ctrl+c,然后我们就可以程序终止了:
在这里插入图片描述
那么ctrl+c就是一个热键—本质就是一个组合键,操作系统会将这个组合键识别成成为二号信号发送给进程,通过man 7 signal可以查看所有信号的信息和与之对应的默认操作:
在这里插入图片描述
signal是信号的名称,value表示信号的值,action表示信号的动作,comment是信号的描述,通过这张图片我们便可以知道二号信号的名称就是sigint,动作是Term对这个信号的描述就是用键盘来中断当前运行的进程,所以我们可以看到程序停止向屏幕上进行打印了,那么为了验证这一点我们可以用signal函数来进行判断,signal函数的作用就是修改信号的处理动作
在这里插入图片描述
signal函数的第一个参数表示信号的编号也就是你要对哪个信号进行修改,第二个参数的类型为sighandler_t通过上面显示我们知道sighandler是一个重命名类型,该类型的本质就是一个函数指针,函数的返回类型是void参数就是一个整形,该参数的作用就是将信号原来的默认动作修改成函数指针指向的函数,比如说下面的程序:

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
using namespace std;
void handler(int sign)
{
    cout<<"我收到了信号,信号的编号为:"<<sign<<endl;
}

int main()
{
    signal(2,handler);
    while(1)
    {
        cout<<"我是一个死循环的进程,我的pid为:"<<getpid()<<endl;
        sleep(1);
    }
    return 0;
}

运行的结果如下:
在这里插入图片描述
可以看到程序的运行是没有问题的,然后当我们按下ctrl c给进程发送信号的时候程序并不会终止,而是执行了我们之前设置好的内容,打印了一句话:
在这里插入图片描述
打印完这句话后接着执行死循环:
在这里插入图片描述
那么这也就验证了,当我们使用键盘输入ctrl c的时候本质上就是给当前的进程发送2号信号,2号信号的作用就是将当前的程序终止。

如何向目标进程发送信号

情景一:使用键盘发送信号

通过前面的学习我们知道使用组合键ctrl c可以向前台进程发送信号从而终止进程:
在这里插入图片描述
那么这里还有一个ctrl \的快捷键,他的作用就是给前台进程发送3号信号从而终止进程,那么这里的代码如下:
在这里插入图片描述
那么这就是通过键盘向进程发送信号。

情景二:系统调用发送信号

1.kill函数
我们学习的第一个函数就是kill,该函数的声明形式如下:
在这里插入图片描述
第一个参数表示向哪个进程发送信号,第二个参数表示向进程发送几号信号,我们再看看这个函数的返回值:
在这里插入图片描述
可以看到当信号发送成功就返回0,如果失败就返回-1,那么这就是该函数的作用,那么接下来我们就可以写一个人程序,专门用来给指定的进程发送信号,程序使用的方法就是运行程序的时候传递两个参数第一个参数表示进程的pid第二个参数用来表示发送的具体信号比如说这样:./myproc 进程的pid 信号的编号,那么我们的程序就可以这样设计,因为运行程序的时候会传递参数,所以该程序中的main函数得包含两个参数:

#include<iostream>
using namespace std;
int main(int argc,char*argv[])
{
    
}

然后在程序的开始我们得判断一下该程序的使用是否正确,如果传递argc的值不等于3的话我们就得调用函数来告诉使用者该程序的正确使用方法,所以该函数就得有一个参数用来告诉使用者是哪个进程的使用方法:

#include<iostream>
#include<string>
using namespace std;
void usage(const string& proc)
{
    cout<<"\nusage:"<<proc<<"pid signo"<<endl;
}
int main(int argc,char*argv[])
{
    if(argc!=3)
    {
        usage(argv[0]);
        exit(1);
    }
}

然后我们就可以通过main函数的第二个参数来获取对应的pid和信号,然后将其转换成为整形最后就可以调用kill函数发送对应的信号即可:

#include<iostream>
#include<string>
#include<signal.h>
#include<stdlib.h>
#include<sys/types.h>
using namespace std;
void usage(const string& proc)
{
    cout<<"\nusage:"<<proc<<"pid signo\n"<<endl;
}
int main(int argc,char*argv[])
{
    if(argc!=3)
    {
        usage(argv[0]);
        exit(1);
    }
    pid_t pid=atoi(argv[1]);
    int signo=atoi(argv[2]);
    kill(pid,signo);
    return 0;
}

那么接下来我们就可以先测试一下myproc.cc文件是否运行正常:
在这里插入图片描述
可以看到当前的程序是可以正常运行的,那么这里我们就可以先运行一下死循环程序:
在这里插入图片描述
然后再打开一个回话,并运行myproc程序并传递20261和2号信号:
在这里插入图片描述
然后就可以看到程序自动的终止了,同样的道理我们还可以发送3号信号给进程,这样我们就可以看到打印出来quit的字样:
在这里插入图片描述
那么这就是kill函数的用法。

第二个:raise
该函数的声明如下:
在这里插入图片描述
这个函数也可以给进程发送信号,但是他不能指定任意进程,他只能给自己发送对应的信号,比如说下面的代码:

int main()
{
    int cnt=1;
    while(true)
    {
        cout<<"cnt的值为: "<<cnt<<endl;
        cnt++;
        if(cnt==4)
        {
            raise(2);
        }
    }
    return 0;
}

那么这里我们就可以看到屏幕上打印了3句话话之后就自动的结束了进程,运行的结果如下:
在这里插入图片描述
那么这就是raise函数的用法。

第三个:abort
kill函数能够对任意的进程发送任意的信号,raise函数能够给本进程发送任意的信号,那么abort函数就只能给本进程发送指定的信号也就是6号新号,我们来看看这个函数的介绍:
在这里插入图片描述
我们可以看看6号新号的名字:
在这里插入图片描述
可以看到6号信号对应的宏就是SIGABRT,那么这里就不再介绍,大家理解了即可。

情景三:硬件异常产生信号

信号的产生,不一定非得用户显示的发送,比如说除0会终止进程的时候会终止进程,比如说下面的代码:

int main()
{
    int i=10;
    int j=0;
    int c=i/0;
    while(true)
    {
        cout<<"如果没有收到信号就会死循环"<<endl;
    }
    return 0;
}

将程序运行起来便可以看到下面的场景:
在这里插入图片描述可以看到这里没有死循环的打印语句,而是报出了异常的内容,那么这就说明当出现除0情况是进程是收到信号的,那这个信号是谁发送的呢?答案是当前进程会受到来自操作系统的信号并且是8号SIGFPF信号,那么这里我们可以使用signal函数修改一下对应的8号信号,修改的操作就是不终止信号并输出信息:

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
using namespace std;
void handler(int sign)
{
    cout<<"我收到了信号,信号的编号为:"<<sign<<endl;
}

int main()
{
    signal(8,handler);
    int i=10;
    int j=0;
    int c=i/0;
    while(true)
    {
        cout<<"如果没有收到信号就会死循环"<<endl;
    }
    return 0;
}

程序的运行代码如下:
在这里插入图片描述
可以看到出现/0错误的时确实会收到8号新号,并且会不停的执行8号信号的处理方法,那么这里就存在一个问题我们只除了一次0,为什么会不停的捕捉呢?操作系统又是如何得知应该给当前进程发送8号信号的呢?那么要想解决这里的问题我们就得了解一下cpu的构造,cpu中存在着大量的寄存器,这些寄存器会存储大量的数据比如说各种表达式或者函数的计算结果,寄存器不仅得存储计算的结果还得保存表达式的计算状态,所以有一种寄存器称为状态寄存器,该寄存器中存在一个比特位称为溢出标记位,10除以0得到的结果是无穷大,那么无穷大这个结果就会导致状态寄存器中的溢出由0变成了1,虽然这次表达式计算出现了问题,但是寄存器中依然会存储一些数据(这里就体现出溢出标记位的作用,因为都会保存一些值,那么这个值是否是对的就有状态寄存器来决定),cpu出现运算异常,那么操作系统是肯定要知道,所以操作系统就可以通过状态寄存器上的标记位知道发生了什么异常?然后再查询当前运行的是哪个进程以及进程运行的位置这样便知道是哪个进程的哪里出现了异常,然后操作系统修改标记位并发送信号,那为什么会一直打印信息呢?我们之前说过收到信号进程不一定会退出,没有退出的话就说明该进程还会被调度,cpu内部的寄存器只有一份,但是寄存器中的内容属于当前进程的上下文,一旦出现了异常了我们修改之后的动作有能力或者行为来修改这个问题吗?答案是没有的,所以进程会被多次切换,与之对应的寄存器的内容就会被多次保存或者回复,所以每一次回复的时候操作系统就会识别到状态寄存器中的溢出标记位,然后修改进程pcb中的标记位并发送信号所以就可以看到当我们对8号信号的动作进行修改时会不停执行8号信号的处理动作。同样场景也会出现在对野指针解引用上,当我们对野指针进行解引用时也会出现崩溃,结束进程,比如说对nullptr地址进行解引用,那么这个时候就会发送11号信号,那操作系统为什么会知道出现野指针的错误呢?答案是虚拟地址转换到物理地址需要页表加上mmu,mmu是内存管理单元这是一个硬件集成在cpu上,当我们访问0号地址时,虚拟地址空间会拒绝我们的访问,然后mmu就会因为我们的越界访问出现了异常,当一个硬件发生了异常时,操作系统便会得知在地址转换的过程中出现了异常,然后操作系统就会发送11号信号,按摩这就是硬件层面上的异常。

情景四:软件条件产生信号

我们之前学习过进程之间的通信,在那里我们知道了管道文件的特点,当管道的读端关闭,写段一直写的时候,操作系统就会通过发送sigpipe信号的方式来终止进程,那么这就是一个经典的软件条件产生的异常,那么这里我们再举一个例子:alarm函数:
在这里插入图片描述

alarm函数的作用就是让程序运行一段时间,时间到了就会发送14号-信号来结束进程,那么alarm的参数就表示在多少秒之后发送信号,所以我们就可以使用闹钟来实现下面这样的代码:

int main()
{
    alarm(1);//程序1秒后会被信号终止
    int cnt=0;
    while(true)
    {
        cout<<"cnt: "<<cnt++<<endl;
    }
    return 0;
}

然后程序的运行结果如下:
在这里插入图片描述
可以看到循环执行了6w多次,那么这段代码的意义就是统计1s左右,我们的计算机能够将数据累加多少次,但是大家不难发现1秒访问6万多次好想有点少,那么这里的原因就是在循环里面要不停的访问外设显示屏,所以运行的速度非常的慢,那么下面我们可以对代码进行修改将cnt改成全局变量,循环里面就不往屏幕上打印数据而是直接对变量++

int cnt=0;
void handler(int sign)
{
    cout<<"我收到了信号,信号的编号为:"<<sign<<endl;
    cout<<"cnt的值为: "<<cnt<<endl;
    exit(1);
}
int main()
{
    signal(SIGALRM,handler);
    alarm(1);//程序1秒后会被信号终止
    while(true)
    {
        ++cnt;
    }
    return 0;
}

代码的运行结果如下:
在这里插入图片描述
可以看到这时程序的运行速度就变得十分的快,但是我们为什么把这个函数发出的异常称为软件异常呢?因为闹钟其实就是用软件实现的,任意一个进程都能通过alarm系统调用在内核中设置闹钟,os中可能会存在很多的闹钟,那么操作系统要不要对这些闹钟进行管理呢?答案是要的,管理的方式就是先描述再组织,所以操作系统就会有对应的结构体来描述闹钟,然后操作系统中就会有数据结构来管理这些结构体,比如说链表,堆,操作系统就会不停的检查这些对象中是否有哪些闹钟超时,超时了操作系统就会给对应的进程发送信号,检查闹钟是否超时是操作系统这样的软件来执行的,条件就是现在超时这个条件,所以闹钟就是软件条件。

核心转储

在这里插入图片描述
在信号的行为中我们知道Term和Core都是将进程终止,那这两种终止方式有什么区别呢?term的终止表示是正常的结束,操作系统不会做额外的操作,而core的终止表示不是正常的终止,操作系统会做出额外的操作,比如说SIGFPF就是一个Core类型的终止,当我们写出这样的代码时操作系统就会给我们发送该类型的异常:

int main()
{
    while(true)
    {
        int arr[10];
        arr[10000]=100;
    }
    return 0}

运行的结果如下:
在这里插入图片描述
可以看到这里确实发生了段错误,但是在云服务器上如果进程是core终止我们暂时看不到明显的现象,如果想看到我们得打开一个选项:ulimit -a查看云服务器的各种选项
在这里插入图片描述
这里会显示当前云服务器的各种性质,比如说管道的大小,最多能打开的文件个数,其中有个属性为core file size,他表示表示核心转储的大小,如果大小为0的话就表示云服务器默认关闭了核心转储,如果想要看到的话 ulimit -c 1024 表示打开核心转储并设计一个大小为1024的数据块
在这里插入图片描述
再运行一下当前的程序就会出现core dumped的标识

在这里插入图片描述

这个core dumped就是核心转储的意思,并且当前路径下还多出来了一个文件,文件名后面有一串数字表示引起核心转储的进程的pid

在这里插入图片描述

核心转储的意思就是:当进出现异常的时候,我们将进程对应的时刻在内存中的有效数据转储到磁盘中就称为核心转储。我们可以查看一下文件里面的内容:

在这里插入图片描述

文件里面的内容我们是看不懂的,那为什么要有核心转储呢?原因是当进程崩溃的时候,我们想知道进程为什么会崩溃,在哪里崩溃,所以操作系统为了方便我们查看程序崩溃的原因,操作系统就会该进程上下文的数据保存到磁盘中用来支持我们调试,那如何查看这些数据呢?答案是先调试我们的文件
在这里插入图片描述

然后输入core-file 新生成的核心转储文件

在这里插入图片描述

按下回车然后就会显示当前出错的原因:

在这里插入图片描述
大家仔细的观察一下便可以看到上面显示了在程序的第18行出现了异常,第18行的内容为arr[10000]=100;那么这就是核心转储的功能,他可以告诉我们程序在哪出现了异常。

信号的两个问题

问题一

在这里插入图片描述
通过上面的图片我们可以看到很多信号的处理行为都是直接将进程终止,那既然处理的行为都是终止的话那为什么还要分这么多种信号呢?意义是什么呢?答案是不同的信号代表不同的事件,但是对于不同的事件处理的动作可以是一样的,这就意味着虽然都是直接将进程终止,但是出现了不同的信号可以让我们知道是哪些原因导致了信号的发出,然后我们就可以根据这些原因来更改程序,那么这就是不同信号的意义。

问题二

我们能不能对所有的信号都进行自定义捕捉?比如说下面的程序:

void handler(int sign)
{
    cout<<"我收到了信号,信号的编号为:"<<sign<<endl;
}
int main()
{
    for(int i=0;i<=31;i++)
    {
        signal(i,handler);
    }
    while(true)
    {
        cout<<"我是一个进程,我的pid为:"<<getpid()<<endl;
    }
    return 0;
}

如果我们对所有的信号都做捕捉,那是不是就不能杀掉这个进程了呢?程序运行的结果如下:
在这里插入图片描述
我们之前讲过2号新号和3号新号都可以结束进程,那现在还能够结束掉吗?我们来尝试一下:
在这里插入图片描述
可以看到是不行的,并且其他的一些信号也不行
在这里插入图片描述
那么这是不是就说明这个进程无法被终止掉了呢?不会的我们使用9号信号依然可以将其 终止掉:
在这里插入图片描述
所以操作系统为了能够结束恶意进程是不允许修改某些信号的处理方法的我们把这样信号称为管理员信号,那么这就是本篇文章的全部内容希望大家能够理解。

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

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

相关文章

CentOS如何查找java安装路径

目 录 背景 详细步骤 1.使用指令查看有关javad安装路径 2.填入java路径 3.查找java安装路径 4.配置文件展示 背景 准备部署分布式hadoop的时候&#xff0c;校验hadoop版本发现java没配置 但是又有java版本信息 详细步骤 1.使用指令查看有关javad安装路径 java -verb…

敏捷项目管理中产品负责人– PO的核心职责

在敏捷项目管理中&#xff0c;产品负责人的角色非常重要。他们代表利益相关者&#xff0c;负责确保团队开发的产品具有价值、符合期望&#xff0c;满足客户需求。 产品负责人核心职责有&#xff1a; 规划产品的方向和路线图&#xff0c;决定产品要做什么。清晰的将产品的路线…

基于Java的银行记账与审核系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

使用win_b64做CATIA的开发测试

文章目录 一、把win_b64文件夹放在无中文的路径下二、启动环境文件编辑器三、新建环境文件四、集成CAA代码五、去用户环境目录查看环境文件是否生成六、关闭环境文件编辑器七、去桌面双击新建的快捷方式即可启动CATIA&#xff0c;进行测试八、进入对应的开发模块&#xff0c;查…

postgresql-聚合函数增强功能

postgresql-聚合函数增强功能 按季度统计入职员工 按季度统计入职员工 select -- extract截取&#xff0c;按季度进行统计入职员工总数 extract(year from hire_date), count(*) filter(where extract(quarter from hire_date) 1) "第一季度", count(*) filter(wh…

计算机竞赛 题目: 基于深度学习的疲劳驾驶检测 深度学习

文章目录 0 前言1 课题背景2 实现目标3 当前市面上疲劳驾驶检测的方法4 相关数据集5 基于头部姿态的驾驶疲劳检测5.1 如何确定疲劳状态5.2 算法步骤5.3 打瞌睡判断 6 基于CNN与SVM的疲劳检测方法6.1 网络结构6.2 疲劳图像分类训练6.3 训练结果 7 最后 0 前言 &#x1f525; 优…

【软考】5.1 七层模型/局域网/TCP-IP协议

《网络功能和分类》 即计算机技术与通信技术相结合的产物&#xff0c;实现了远程通信、远程信息处理和资源共享计算机网络的功能&#xff1a;数据通信、资源共享、负载均衡&#xff08;给多个服务器负担&#xff09;、高可靠性 分布范围 拓扑结构分类 总线型&#xff1a;一般局…

Git小书系列笔记

Git准备 首先根据自己的系统安装git&#xff0c;安装成功后可以通过如下指令查看git版本。 使用Git之前&#xff0c;需要配置用户名称和电子邮件。 1.设置全局的用户名和电子邮件 git config --global user.name "Your Name" git config --global user.email &quo…

OpenGLES:绘制一个混色旋转的3D立方体

效果展示 混色旋转的3D立方体 一.概述 之前关于OpenGLES实战开发的博文&#xff0c;不论是实现相机滤镜还是绘制图形&#xff0c;都是在2D纬度 这篇博文开始&#xff0c;将会使用OpenGLES进入3D世界 本篇博文会实现一个颜色渐变、旋转的3D立方体 动态3D图形的绘制&#xf…

RabbitMQ-java使用消息队列

1 java操作消息队列 1.1 java实现生产者 新建一个springboot项目&#xff0c;导入依赖 <dependency><groupId>com.rabbitmq</groupId><artifactId>amqp-client</artifactId><version>5.14.2</version> </dependency>导入依…

ctfshow web入门 php特性 web113-web125

1.web113 和上题一样,/proc/self/root代表根目录&#xff0c;进行目录溢出&#xff0c;超过is_file能处理的最大长度就不认为是个文件了 payload: compress.zlib://flag.php /proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/p roc/self/root/p…

操作系统内存管理相关

1. 虚拟内存 1.1 什么是虚拟内存 虚拟内存是计算机系统内存管理的一种技术&#xff0c;我们可以手动设置自己电脑的虚拟内存。不要单纯认为虚拟内存只是“使用硬盘空间来扩展内存“的技术。虚拟内存的重要意义是它定义了一个连续的虚拟地址空间&#xff0c;并且 把内存扩展到硬…

SLAM从入门到精通(用python实现机器人运动控制)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 在ROS下面&#xff0c;开发的方法很多&#xff0c;可以是c&#xff0c;可以是python。大部分接口操作类的应用&#xff0c;其实都可以用python来开…

(五)激光线扫描-位移台标定

线激光属于主动测量方式,但是由于线激光的特性,我们只能通过提取激光中心线获取这一条线上的高度信息,那么要进行三维重建的话,就需要通过平移或者是旋转的方式,来让线激光扫描被测物体的完整轮廓,也就是整个表面。激光线的密度越高还原出来的物体越细腻,但由于数据量大…

计算机中丢失vcomp140.dll解决方案,可以使用这几个最新方法来修复

今天早上&#xff0c;当我打开电脑时&#xff0c;突然看到一个提示窗口&#xff0c;显示找不到 vcomp140.dll 文件。我一下子懵了&#xff0c;不知道这是怎么回事&#xff0c;也不知道如何解决这个问题。于是&#xff0c;我开始了寻找答案的旅程。 首先&#xff0c;我了解到 v…

<C++> 异常

C语言传统的处理错误的方式 传统的错误处理机制&#xff1a; 终止程序&#xff0c;如assert&#xff0c;缺陷&#xff1a;用户难以接受。如发生内存错误&#xff0c;除0错误时就会终止程序。返回错误码&#xff0c;缺陷&#xff1a;需要程序员自己去查找对应的错误。如系统的…

Autosar诊断实战系列21-UDS连续帧(CF)数据接收代码级分析

本文框架 前言1. 长帧数据的连续帧接收2. 连续帧的处理前言 在本系列笔者将结合工作中对诊断实战部分的应用经验进一步介绍常用UDS服务的进一步探讨及开发中注意事项, Dem/Dcm/CanTp/Fim模块配置开发及注意事项,诊断与BswM/NvM关联模块的应用开发及诊断capl测试脚本开发等诊…

计算机类毕业设计选题60套!太全了!快收藏!

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人七年开发经验&#xff0c;擅长Java、Python、PHP、.NET、微信小程序、爬虫、大数据等&#xff0c;大家有这一块的问题可以一起交流&#xff01; &#x1f495;&…

基于MDK-Keil环境如何把STM32程序直接下载到SRAM运行

1. 前言 对于 Cortex-M 内核的微控制器&#xff0c;它们都可以支持在 RAM 中执行程序&#xff0c;有些非 ARM 的微控制器是不支持的。 在内部 SRAM 执行程序&#xff0c;有基于以下几方面的原因&#xff1a; 1、所使用的设备可能具有OTP&#xff08;One-time Programmable&a…