Linux-进程信号

Linux进程信号

  • 初步认识信号
  • 信号的存储结构
  • 信号的处理方式
  • 信号的产生
  • 硬件异常产生的信号
  • 核心转储
  • sigset_t信号集
  • 信号集的操作函数
  • 对block表的操作
  • 对pending表的操作
  • 对handler表的操作
  • 信号的捕捉
    • 用户态和内核态
  • 信号的处理过程
  • 可重入函数
  • volatile关键字

初步认识信号

生活中有哪些信号?例如:红绿灯、闹钟、手势等都叫做信号,我们接收到这些信号都会采取一些措施来应对这些信号。

操作系统里面也是有信号的,进程也是可以接受信号的,接收之后进程也会采取信号所相对应的措施。

信号可能随时产生,信号的产生对于进程来讲是异步的,所以在接收到信号时,进程可能在做优先级更高的事情,不能立即处理信号,所以进程需要有保存信号的能力,在后续合适的时间去处理这个信号。

信号会保存在进程的PCB中,进程的PCB只能由OS(operating system 操作系统的意思,后面都用OS简写代替)修改,所以无论有多少种信号的产生,最终只能由OS来完成最后的发送。

系统自定义的信号列表

1-31为普通信号:普通信号被进程接收首先保存,可以等待进程执行完优先级更高的指令再来处理信号。重点讲解普通信号。因为Linux、windows、安卓等系统都是分时操作系统,用的是分时信号

31-64为实时信号:实时信号必须马上被处理,直到处理完毕。车载系统等实时操作系统会用实时信号。
在这里插入图片描述
信号的其他相关概念:

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

信号的存储结构

进程中接收信号的时候可能不会立即处理,但是不代表不会处理,所以需要先保存起来,然后等待合适的时机去处理。所以进程需要记录一个信号在进程中是否存在,0代表不存在,1代表存在,普通信号有31个,所以PCB中信号的保存就可以用位图结构,从低位开始,第一个比特位代表1号信号,依次类推。所以给进程发送信号就是直接修改特定进程的信号位图中的特定的比特位。

实时信号在操作系统中用的是队列的存储方式(了解即可)

在PCB中有三张表分别是:pending、block、handler。

pending表:位图结构。比特的位置,表示哪一个信号,从低位到高位第一个比特位表示1号信号,比特位的内容表示是否接收到该信号,1表示接收,0表示未接收。

block表:位图结构。比特的位置,表示哪一个信号,从低位到高位第一个比特位表示1号信号,比特位的内容表示该信号是否被阻塞。

handler表:函数指针数组。该数组的下标对应信号的递达动作。SIG_DEL表示默认处理,SIG_ING表示忽略,还有就是自定义方法的函数指针。用signal自定义信号递达动作时,就是往指针数组中存放函数地址。

在这里插入图片描述

信号的处理方式

当进程收到信号时,有三种处理信号的方法:

  1. 默认方式
  2. 忽略信号
  3. 用户自定义处理

​ 用户自定义处理,有一个接口sighandler_t signal(int signum, sighandler_t handler);

在这里插入图片描述

第一个参数是信号的编号,第二个参数是一个函数指针,当进程处理signum时,会执行handler函数的方法。但是只是执行了singal方法,handler方法并不会立马执行,singal方法只是改变了信号产生时对应的执行动作。只有signum信号真正产生时,才会执行handler方法。

9号信号不能被自定义。

信号的产生

当一个进程被执行的时候,分为前台进程和后台进程,前台进程是可以被ctrl+c直接中断的,本质上就是向这个前台进程发送一个2号信号,或者ctrl+\给前台进程发送一个3号信号。

那么我们从键盘输入的时候,计算机怎么知道我从键盘里输入了数据呢?

CPU的背面有很多针脚,每一个针脚连接一个硬件,每一个针脚都有自己的编号,表示中断号,当键盘被按下的时候,连接键盘的针脚会接收到键盘被按下的信号,内存中会保存一个中断向量表,向量表中存放的都是函数指针,CPU会根据中断号去中断向量表中查询对应的函数,键盘的函数就是让OS读取数据,键盘被输入的数据就会被OS发送到前台进程。

系统接口

给对应的进程发送信号。

在这里插入图片描述

自己给自己发送信号

在这里插入图片描述

结束调用abort的进程。相当于raise(6)。6号信号即便自定义处理方式了,也会执行完自定义操作之后退出。

在这里插入图片描述

调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。

这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。打个比方,某人要小睡一觉,设定闹钟为30分钟之后响,20分钟后被人吵醒了,还想多睡一会儿,于是重新设定闹钟为15分钟之后响,“以前设定的闹钟时间还余下的时间”就是10分钟。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数

在这里插入图片描述

硬件异常产生的信号

什么叫做硬件异常产生的信号呢?举个例子:

int a = 10;
a = a/0;

这两行代码如果在Linux下编译会被警告,但是还是会生成可执行程序,在运行的时候会报错(float point exception 8号信号)。然后程序就不会继续向下执行了。

这个进程是怎么接收到这个8号信号的呢?

进程被执行的时候,代码会从上到下依次执行,在CPU中有若干寄存 器,其中有一个状态寄存器会记录每行代码的状态,如果结果不正确,有数据溢出,状态寄存器由0置1。这个寄存器就有了硬件异常。OS就会给引起硬件异常的进程发送信号。如果这个进程自定义了8号信号的处理方式,然后没有退出,操作系统一直调度这个进程一直执行8号信号的自定义动作。

下面这个也是硬件异常产生的信号:

int* p = nullptr;
p = 100;//p是一个指针变量,内部有空间,可以被强行赋值
*p = 100;//野指针

*是一个解引用的操作,就是要对p指向的空间进行访问,p指向的是nullptr,也就是0号空间的地址,进程中想要对变量进行赋值,存放在虚拟地址空间需要通过页表(MMU)去访问对应的物理内存,MMU也是一个硬件,被集成在CPU中的,通过MMU访问物理内存如果访问失败,有两种失败原因,一个是MMU中没有该虚拟地址的映射关系,另一个就是有映射关系但是没有访问权限,无论哪一种。MMU都会产生硬件异常,然后OS给进程发送信号。

核心转储

Linux下有这样一个功能,在进程发生异常的时候,核心代码部分进行核心转储,将内存中进程的相关数据dump到磁盘里面,一般称为核心转储文件,以core命名。如果是云服务器的话默认是关闭的。

ulimit可以设置/显示用户可以使用资源的限制。

在这里插入图片描述

我们可以看到core file size是被设置为0的,所以默认不会进行核心转储,如果想打开core文件,可以使用ulimit -c 1024(文件大小)

在学习进程等待的时候,waitpid返回结果存储在status,status为整形,32个比特位,返回结果存储在高八位,退出信号存储在第0-7个比特位。第七个比特位就是core dump的标志位,如果为0说明核心转储是关闭的或者进程正常退出,如果为1,说明进程异常退出并且核心转储是打开的。

那怎么样才能进行核心转储呢?首先我们了解了,进程收到信号后默认动作是退出,但是信号退出动作有其中两种trem和core,有什么区别呢?core和上面的core文件有什么关系呢?

在这里插入图片描述

如果信号的action是Term,进程收到信号后会直接退出。如果是Core,OS会进行核心转储。

核心转储有什么用

如果一个进程异常退出了,退出原因是最重要的,而异常退出之后生成的核心转储文件可以帮我们很快的定位到是因为哪一行代码退出的,收到几号信号退出的。

在这里插入图片描述

生成的可执行程序默认是release,如果需要gdb调试,需要在g++编译的时候加-g选项。

gdb调试时,直接使用core-file命令,后面跟着core文件就可以定位到错误部分。

为什么核心转储是关闭的

因为核心转储文件一般都很大,这种有问题的可执行程序每执行一次就会生成一个核心转储文件,在公司里假如某个服务挂掉了,他就会一直重启,重启一次就会生成一个核心转储文件,很快就会把磁盘占满。

sigset_t信号集

信号集的操作函数

#include <signal.h>
int sigemptyset(sigset_t *set);//初始化信号集
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);//把signo信号添加到set信号集中
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);//查看signo信号是否存在这四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含
某种 信号,若包含则返回1,不包含则返回0,出错返回-1。

对block表的操作

在这里插入图片描述

对pending表的操作

sigpending函数,把当前进程的pending表设置进set里面

在这里插入图片描述

对handler表的操作

上面介绍了signal这个函数可以修改handler表,还有一个函数也可以修改handler表。

NAMEsigaction - examine and change a signal action
SYNOPSIS#include <signal.h>int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);struct sigaction {void     (*sa_handler)(int);void     (*sa_sigaction)(int, siginfo_t *, void *);sigset_t   sa_mask;int        sa_flags;void     (*sa_restorer)(void);
};
  • sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非 空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体;
  • 将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函 数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。

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

示例代码:

void handler(int signo){cout<<"接收到了"<<signo<<"信号"<<endl;
}
int main(){struct sigaction act,oldact;memset(&act,0,sizeof(act));memset(&oldact,0,sizeof(oldact));act.sa_handler = handler;act.sa_flags=0;sigemptyset(&act.sa_mask);sigaddset(&act.sa_mask,3);sigaddset(&act.sa_mask,4);sigaddset(&act.sa_mask,5);sigaction(2,&act,&oldact);}

信号的捕捉

当进程接收到信号的时候,信号可能不会被立即处理,因为进程在做优先级更高的事情,那么什么时候会处理呢? 答案是当进程从内核态切换到用户态的时候,进程会在OS的指导下进行信号的检测和处理。

补充:当信号之前被block,block解除后对应的信号会被立即递达;

用户态和内核态

进程被加载到内存中被执行的时候分为用户态和内核态

用户态:执行我们自己写的代码时,进程所处的状态。

内核态:执行OS的代码时,进程所处的状态。例如:进程时间片到了,需要执行进程切换逻辑代码的时候。或者调用系统接口的时候。都处于内核态。

重新理解进程地址空间

在这里插入图片描述

  • [0GB,3GB]是用户空间,每个进程的用户空间是不一样的。每个进程都有自己的用户级页表。
  • [3GB,4BG]是内核空间,每个进程的内核空间都是一样的。所有进程都用同一张内核级页表。
  • 操作系统就是在进程中运行的。
  • 调用系统接口,就和调用自己写的库函数一样,都是在进程地址空间内完成调用。
  • 进程在用户态的时候,无法访问OS的数据和代码。CPU中有一个寄存器(CR3)中比特位为3表示用户态,比特位为0表示内核态。但是用户无法直接修改寄存器的状态,我们调用系统接口的时候就需要从用户态切换成内核态。所以系统调用接口内部会帮我们做这个事情,在刚开始进入系统函数的时候没有立马进入内核态,还没有触发状态检测,系统接口会先修改CR3的寄存器状态,然后再去执行函数代码。

进程是如何被调度的?

我们都知道进程是被OS管理和调度的。那么到底如何调度的呢?

OS的本质也是软件,是一个一直死循环的软件。电脑的开机操作本质上就是把OS加载到内存。上面提到了OS在每一个进程中的内核空间中运行。在没有进程被OS调度的时候,OS也有自己的进程可以执行,在centos7中叫做systemd也就是1号进程。

Linux是一个分时系统,如果内存中有若干个进程,它会让每个进程都能被执行到,所以每个进程都有时间片。如果进程的时间片到了,就需要切换到别的进程,OS如果执行某段逻辑代码,例如死循环等,怎么知道该进程的时间片到了,需要切换进程呢?

在电脑主板上有一个时钟硬件,它是用来记录时间的。就算我们的电脑关机很久并且不联网,再开机电脑的时间也不会错误。就是依赖这个时钟硬件。这个时钟硬件会每隔很短的时间给OS发送一次硬件中断。OS就会执行对应的中断处理方法,会检查当前进程的时间片,如果超时。OS会将当前进程进行保存等一系列处理。OS会调用一个叫做schedule();的系统接口完成进程的切换调度。而这一切都是OS在当前进程的内核空间内完成的操作。

信号的处理过程

上面说了,信号只有在内核态向用户态转换的时候才会被处理,那么具体过程是什么呢?

== 当我们执行用户态的代码时,会因为系统调用等原因陷入内核态,进入内核态后完成某种任务之后,内核态要向用户态转换,转换之前会检查一下block表和pending表,如果block为0,pending表为1,就会执行对应的handler方法,有三种处理方式,其中是SIG_DEL、SIG_IGN、自定义函数。其中自定义函数在进程的用户空间内定义的,所以需要跳转到用户态去执行自定义方法,之所以不用内核态是因为防止自定义函数利用内核态权限修改OS数据或代码。执行完自定义方法之后不能直接返回到用户态的上下文中,因为自定义方法并不知道用户态到内核态的位置,需要先返回到内核态(sigreturn),然后在内核态用sys_sigreturn()系统接口返回。==

内核对信号进行处理之前会先把pending表中的bit位置为0。

可重入函数

在这里插入图片描述

main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的 时候,因为硬件中断使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换 到sighandler函数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的 两步都做完之后从sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续 往下执行,先前做第一步之后被打断,现在继续做完第二步。结果是,main函数和sighandler先后 向链表中插入两个节点,而最后只有一个节点真正插入链表中了。

像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为 不可重入函数,反之,如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。

我们学习的大多数函数都是不可重入的。

volatile关键字

我们先看一个现象 然后解释这个关键字

int quit=0;
void handler(int signo)
{cout<<"quit from zero to one"<<endl;quit=1;cout<<quit<<endl;
}
int main(){signal(2,handler);while(!quit);cout<<"main formal quit"<<endl;return 0;}

上面这段代码正常应该是接收到2号信号 然后修改quit退出死循环 然后正常退出。看看结果:

在这里插入图片描述

实际上确实是这样,没什么不对,但是Linux下gcc的优化级别分为O0 O1 O2 O3,O3的优化级别是最高的,O0是默认编译方式,不做优化,不优化就不会有什么问题,如果把优化级别改成O1,就不一样了。

g++ -o $@ $^ -std=c++11 -O1

再进行编译运行

在这里插入图片描述

无论发送多少2号信号都不会退出,为什么?解释一下:

quit这个变量是在内存中存放的,cpu想要执行这段代码,对这个循环进行逻辑判断,需要先把quit的数据从内存中获取到cpu再进行运算,但是如果编译级别优化之后,cpu对这个quit变量使用频率非常高并且发现main函数里面只是对quit变量进行取反再判断,并没有进行修改。所以把变量存放在寄存器内部,每次判断从寄存器读取数据即可,即使变量在内存中被修改cpu也不会重新读取,这就叫做内存位置不可见了。解决方法加上volatile关键字,就是告诉cpu每次要从内存中获取数据,保证内存可见性。

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

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

相关文章

LeetCode、136. 只出现一次的数字【简单,位运算】

文章目录 前言LeetCode、136. 只出现一次的数字【简单&#xff0c;位运算】题目链接与分类思路异或一遍运算 资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝2W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客之星、阿里云平台优质作者、专注于Java后端技术…

冲击可以通过峭度指标来检测,是如何来检测的,python示例代码

问题 冲击可以通过峭度指标来检测&#xff0c;是如何来检测的&#xff0c;可以给1个示例代码吗 思路 带冲击的信号其峭度指标>3不带冲击的信号其峭度指标在3左右可以通过滑动窗来检测在哪一段 示例代码 带冲击的信号峭度指标值 import numpy as np import matplotlib.…

2024.02.12

使用STM32Cubemx创建一个工程并且给出每一步的含 选择芯片创建工程 开启调试功能 配置时钟 配置时间树 工程管理 配置仿真器

幻兽帕鲁服务器原来的存档不想玩了,怎么清档?如何重来?

如果需要备份原存档的话&#xff0c;就先把存档导出来备份。或者手动去服务器文件里找到游戏存档文件夹&#xff0c;保存下载。 如无需备份原存档&#xff0c;则可以直接使用幻兽帕鲁应用模板&#xff0c;来重装服务器的操作系统。 方法很简单&#xff1a; 详细教程地…

odoo封装字段widget中无感知刷新数据

常规操作是直接刷新页面 window.location.reload(); 深入源码&#xff0c;发现页面controller层有reload 对应tree的字段widget this.__owl__.parent.parentWidget.__parentedParent.reload(); 对应form的字段widget this.__parentedParent.__parentedParent.reload()

APP inventor零基础移动应用开发

1.Android平台简介 Android由谷歌和开放手机联盟共同创建的一款针对手机的开源软件工具包 主要特色 ---开放性 – 丰富的硬件选择 – 开发商不受任何限制 – 无缝集成互联网服务 App Inventor是由Google公司开发的一款在线开放的Android编程工具软件&#xff0c;通过图形化…

huggingface学习|用dreambooth和lora对stable diffusion模型进行微调

目录 用dreambooth对stable-diffusion-v1-5模型进行微调&#xff08;一&#xff09;模型下载和环境配置&#xff08;二&#xff09;数据集准备&#xff08;三&#xff09;模型微调&#xff08;四&#xff09;运行微调后的模型 用lora对stable-diffusion-v1-5模型进行微调&#…

【C语言】动态内存深入了解(一口气刨根问底学完系列,全乎,建议三连点赞收藏)

目录 1.动态内存分配的原因 2.动态内存函数的介绍 2.1malloc和free函数 2.2calloc函数 2.3realloc函数 3. 常见的动态内存错误 3.1 对NULL指针的解引用操作 3.2 对动态开辟空间的越界访问 3.3 对非动态开辟内存使用free释放 3.4 使用free释放一块动态开辟内存的一部…

Swift 初见

Swift 初见 学习swift的记录 控制台输出 print("hello world")可以无分号&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 变量声明 let 常量声明 如果在常量声明后再次修改他会报错 var 变量声明 一个常量的值&#xf…

linux应用 进程间通信之共享内存(POSIX)

1、前言 1.1 定义 POSIX共享内存是一种在UNIX和类UNIX系统上可用的进程间通信机制。它允许多个进程共享同一块内存区域&#xff0c;从而可以在这块共享内存上进行读写操作。 1.2 应用场景 POSIX共享内存适用于需要高效地进行大量数据交换的场景&#xff0c;比如多个进程需要…

upload-labs文件上传漏洞靶场

第一关 <?php eval ($_POST[123]);?>发现他这个是通过客户端前端写了一个限制 我们禁用srcipt即可 蚁剑成功打开 第二关 我们上传文件2.php它提示我们文件类型不正确 我们可以联想到做了后缀检测 我们通过burp抓包修改后缀 第三关 我们上传一个.php文件不可上…

Peter算法小课堂—区间模型

Peter Pan来啦…… 最大不重叠区间数 二话不说&#xff0c;先来一道题 大家想想怎么贪心&#xff1f;我们可以将每一个美食摊位抽象成一个区间&#xff0c;区间左端点为开始排队时间&#xff0c;右端点为结束排队时间。其中&#xff0c;时间信息可以用数轴表示。 额……我们…

【分布式技术专题】「Zookeeper中间件」Paxos协议的原理和实际运行中的应用流程分析

Paxo算法介绍 Paxos算法是莱斯利兰伯特(Leslie Lamport)1990年提出的一种基于消息传递的一致性算法。 Paxos产生背景 Paxos算法是基于消息传递且具有高度容错特性的一致性算法&#xff0c;是目前公认的解决分布式一致性问题最有效的算法之一&#xff0c;其解决的问题就是在分…

轮播图 HarmonyOS 鸿蒙 ArkTS ArkUI

第一步&#xff1a;新建图片数组 State swiperimgs:Array<Object>[$r(app.media.a), //本地图片或者网络图片$r(app.media.b),$r(app.media.c),$r(app.media.d)] 第二步&#xff1a;写入轮播图代码 Column(){Swiper(){ForEach(this.swiperimgs, (item) > {Image(item…

【c语言】字符串常见函数 上

&#x1f388;个人主页&#xff1a;甜美的江 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;c语言 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进步&a…

【JVM篇】怎么解决内存泄漏问题

文章目录 &#x1f50e;什么是内存泄漏&#x1f6f8;解决内存泄漏⭐发现问题⭐诊断原因⭐修复问题 &#x1f50e;什么是内存泄漏 在Java中如果不再使用一个对象&#xff0c;但是这个对象仍然在GC Root的引用链上&#xff0c;这个对象就不会被垃圾回收器回收&#xff0c;这种情…

openGauss学习笔记-218 openGauss性能调优-确定性能调优范围-硬件瓶颈点分析-I/O

文章目录 openGauss学习笔记-218 openGauss性能调优-确定性能调优范围-硬件瓶颈点分析-I/O218.1 查看I/O状况218.2 性能参数分析 openGauss学习笔记-218 openGauss性能调优-确定性能调优范围-硬件瓶颈点分析-I/O 获取openGauss节点的CPU、内存、I/O和网络资源使用情况&#xf…

[C++]17:二叉树进阶

二叉树进阶 一.二叉搜索树&#xff1a;1.二叉搜索树的概念&#xff1a;2.二叉搜索树的实现---循环版本&#xff1a;1.二叉搜索树的基本结构&#xff1a;2.查找&#xff1a;3.插入&#xff1a;4.中序遍历&#xff1a;5.删除&#xff1a; 3.二叉搜索树的实现---递归版本&#xff…

随机过程及应用学习笔记(二)随机过程的基本概念

随机过程论就是研究随时间变化的动态系统中随机现象的统计规律的一门数学学科。 目录 前言 一、随机过程的定义及分类 1、定义 2、分类 二、随机过程的分布及其数字特征 1、分布函数 2、数字特征 均值函数和方差函数 协方差函数和相关函数 3、互协方差函数与互相关函…

Java String源码剖析+面试题整理

由于字符串操作是计算机程序中最常见的操作之一&#xff0c;在面试中也是经常出现。本文从基本用法出发逐步深入剖析String的结构和性质&#xff0c;并结合面试题来帮助理解。 String基本用法 在Java中String的创建可以直接像基本类型一样定义&#xff0c;也可以new一个 Str…