Linux——进程信号详解

目录

一.进程信号的理解

1.1定义:

1.2举例:

1.3总结:

二.进程信号地使用:

        2.1信号种类:

        2.2而操作系统向进程发送信号地方式有四种:

 2.2.1以键盘的方式向进程发送信号

        接下来介绍一个系统调用函数signal():

2.2.2 以系统调用地方式向进程发送信号——kill函数

        abort():

        raise():

2.2.3硬件异常产生相应的信号

        论证:a/=0语句的错误是否来自于OS发送给进程的SIGFPE8号信号: 

        总结:

2.2.4.软件条件的产生

        alarm();

三.总结


     

一.进程信号的理解

1.1定义:

       Linux的信号是可执行程序在被CPU运行计算的过程中,发生了某些异常原因导致进程被迫中断的方式。

1.2举例:

        在现实生活中,我们有也可以处处见到信号:

1.过马路的红绿灯

2.微信的消息提醒

3.手机的来电铃声

4.闹钟声响起

5.母亲叫孩子起床

        以上这些例子全是信号的代表。   

        我拿第一个例子:过马路说吧:

        在过十字路口时,总是会有红绿灯,这是交通规则,管制着人车出行的安全。而我们能够识别出“红灯停绿灯行,黄灯亮了等一等”——这是第一点,因为老师或者父母教育过你,我们的大脑记住了对应的红灯、绿灯、黄灯的属性和需要做出的行为;绿灯亮了,表示我们可以过马路了,但是我不一定要立刻过这个马路!我可能正处于和朋友分别的情况下,我正在和他说离别的话语——这是第二点,当信号到来时,我可能正做着更重要的事情,没有办法立刻处理这个信号;但绿灯亮的那个瞬间,我明白了一件事,我记住了要在绿灯的有限时间内穿过这个马路,我可能在和朋友说离别话的5秒后过马路,也有可能在10秒后过,总之我有一个时间上的概念——这是第三点,信号的到来,就必须记住这个信号,它需要被处理;最后,我有多种选择,选择a:我按照一般逻辑,绿灯亮了就和朋友分别过了马路(默认动作);选择2:我玩着手机过马路(自定义动作);选择3:绿灯亮了我并不过马路(忽略动作)

在这个例子中,共有4个重点:

        1.人为什么能够识别出红绿灯的信号?——因为人们都认识它,对这些信号的产生会有相应的决策动作产生;

         2.信号到来时,是否需要立刻处理?  ——不一定要立刻处理,因为这时候你可能在做着比处理信号还重要的事情;

        3.在第二点的基础上,信号到来的话,该怎么办?——需要先记住这个信号!

        4.处理信号时,有几种方式?——a.默认动作、b.自定义动作、c.忽略动作

根据这4个重点,我们就可以很容易的了解到信号。

举一反三:

        我中午点外卖,一个小时后,外卖小哥到了我家楼下并打了电话。根据第一重点,因为我之前点过外卖,知道了外卖员给我打电话就需要我下楼去取了;根据第二点,我接到了电话,回复了外卖员说:"我马上就会下去!" 但我此时正在打游戏,已经在打至关重要的团战了,走不开。所以我没办法下去取,只能等到我打完团战才能去处理这件事;根据第三点,外卖员给我打了电话,我脑子里已经记下了需要取外卖这件事情;根据第四点,我接到了电话,团战正好打完,我下楼去取(默认动作);我正在打团战,过了3分钟才下去取(自定义动作);我打游戏上头了,忘了这件事,导致外卖员走了并且我也没吃上午饭(忽略动作)

          我小时候摔了一跤,哇哇大哭,被妈妈哄了半天没有用。妈妈不耐烦了,收起了慈爱的脸庞,用严厉的神情警告我说:“3、2、1!” 根据第一点,因为之前我也遇到过类似321的情况,但我没听话挨过好几次揍,我的大脑中死去的记忆突然又攻击了我~;根据第二点,我听到了妈妈说的3,2,1,我知道了妈妈生气了,要准备揍我了,但是我摔得太疼了,哭得停不下来,没办法安静;根据第三点,我记住了妈妈说得这句话,需要尽快安静下来;根据第四点,我听到妈妈说的话,停下来了,不哭了,免去一顿打(默认动作);我去找老爸求助,让老妈别打我(自定义动作);我头铁,老妈说了3,2,1 ,我还在哭,老妈又揍了我一顿(忽略动作)。

1.3总结:

        那么根据生活中例子,我们可以应用到Linux操作系统中去:

1.Linux的信号只能是由OS操作系统给进程发送,进程接收到信号所做的工作几乎都是中断,这是因为程序员写出的代码规则教给进程的,进程认识了各个信号,也就能做出相应的动作。

2.当进程收到了信号的时候,进程可能在执行着更重要的事情,该信号可能不会被马上进行处理。

3.进程本身一定是具有保持信号的能力,而保持的位置在PCB中。

4.进程对信号的到来,有三种对应方式:默认动作、自定义动作、忽略动作

对于第三点,一个信号发送给进程,进程会将信号进行保存,保存的位置在PCB中,证明:


二.进程信号地使用:

        2.1信号种类:

    在长时间学习Linux的过程中,我们已经在很多情况下默默见识过了信号,而常见的信号指令为kill指令,使用kill -l指令可以查看Linux操作系统中带有的所有信号列表:

如上:1-31是普通信号,34-64是实时信号。而之后我们常用的是1-31的信号。 

        2.2而操作系统向进程发送信号地方式有四种:

1.以键盘的方式向进程发送信号

2.以系统调用地方式向进程发送信号——kill函数

3.硬件异常产生相应的信号

4.软件条件的产生

先来说第一点:

 2.2.1以键盘的方式向进程发送信号

实验案例:

运行结果: 

       代码解析: 通过上图代码可知,采用while死循环一直打印一句话,之后我使用了ctrl+c键,终止了进程的执行。而ctrl+c键: 也相当于是一个信号,0S将其定义为2号信号。

SIGINT
进程中断信号
产生方式:键盘Ctrl+C
产生结果:只对当前前台进程,和他的所在的进程组的每个进程都发送 SIGINT 信号,之后这些进程会执行信号处理程序 再终止 

验证方式: 


除此之外,ctrl+\键也可以用于终止进程运行! 

ctrl+\也是一种信号,对应kill指令中的第三信号:SIGQUIT 

 实验方式:

接下来介绍一个系统调用函数signal():

signal是拥有两个参数的函数,这个函数有些特殊

signum参数是想要捕捉的某个信号,可以填指定的信号数字,也可以填信号的名称;

handler参数是一个回调函数,在handler函数中可以将接收到信号做任意的处理。

该函数的作用:准备捕捉或屏蔽的信号由参数signum给出,接收到指定信号时将要调用的函数有handler给出

实验案例: 

         在代码中,signal意味着是一个信号受理的函数,如果进程没有接收到OS发送来的任何信号,那么signal函数就永远不会被调用!!!进程会跳过该函数往下执行。所以signal放在那里就相当于安保的作用,只要无人闹事,它就不会管制别人的行动,但是只要有人闹事,它就会出来镇场子。

运行结果:

        在进程运行的过程中,我们只要一使用ctrl+c键——2号信号,就会被signal函数给捕捉到,捕捉到后会调用singal的第二参数回调函数,做出相应的动作。

        注:signal函数并不能捕捉所有信号,它只能对指定的信号进行捕捉,例如,在代码中,signal函数的第一参数设置为了2,意味着signal函数只能捕捉2号信号,对于OS发送过来的其他类型信号,signal并不能进行捕捉。

 


2.2.2 以系统调用地方式向进程发送信号——kill函数

 kill函数/命令产生信号:


kill命令产生信号: kill -SIGKILL pid
kill函数: 给指定进程发送任意信号

返回值:

成功:0;失败:-1 (ID非法,信号非法,设置errno)


参数:

1.sig:中的任意一个发送给进程,表示你想要发送给某个进程的信号参数,可以选第1-第31个信号。
2.pid > 0:发送信号给指定的进程。(一般情况下都是这个)
   pid = 0:发送信号给 与调用kill函数进程属于同一进程组的所有进程

   pid < 0:取 pid 发给对应进程组。
   pid =-1:发送给进程有权限发送的系统中所有进程

实验案例:

  signal_transfer.cc:

代码解析:该进程用于一直打印一句话,死循环方式。 

 trans_test.cc:

代码解析:该进程的作用是向 signal_transfer进程使用指定的信号,进而中断signal_transfer进程的执行。里面使用了argv[ ]数组。

 

运行结果:

         trans_test进程向signal_transfer进程使用了3号信号,上图可知3号信号为SIGQUIT中断信号。想要使用系统调用去给进程发送信号,必须得说明进程的文件名称,进程的Pid,以及想要发送的信号。

  trans_test进程向signal_transfer进程使用了19号信号,该信号的作用是让进程暂停运行,并不会直接中断,我们可以对该进程使用18号信号,让该进程恢复运行:


abort():

 该函数无参数,无返回值,用于中断自己进程的执行,该函数对应SIGABRT信号

实验案例:

        代码解析:通过在死循环中,每一秒就输出一句话,cnt作为计数器,当cnt为5时,使用abort函数,给自己发送信号,中断自己的执行。

运行结果:

论证abort函数是否为SIGABRT信号: 


raise():

 

该函数返回值为int,返回值为0代表调用成功;返回非0则代表调用失败。 

实验案例:

代码解析:在死循环中,若cnt值为5,则调用raise函数,给自己进程发送3号信号,中断自己进程的执行。 

运行结果:

 注:

        1.信号的处理行为的不同: 很多情况下,进程收到的大部分信号,都是进行进程中断的操作;
        2.信号的意义:在我们学习的1-31号信号中,大多都是中断信号操作,但是信号的不同,所代表的意义也不同不同的信号代表不同的事件,就好比我们写代码,写好后运行的时候,会出现各种各样的编译错误比如参数有误类型不兼容等错误,它们最终都会让代码无法运行,虽然错误都会导致同一种结果(代码无法运行).但错误并不是同一种错误。

就好比:absort(); ===> kill(pid,SIGABRT);
               raise(19) ; ====> kill (pid,SIGSTOP) ;
这两种函数的应用都可以被替换成kill函数去执行


2.2.3硬件异常产生相应的信号

        通过结果可知:发现while循环只进行了一次就异常终止了,Floating point exception 表示浮点异常浮点异常表明是算术方面的异常。原因就在于:a/=0语句,该语句表示除零错误——即除数的值不能为0,否则会引发除零错误。

        而除零错误代表的信号为SIGFPE8号信号!

 

论证:a/=0语句的错误是否来自于OS发送给进程的SIGFPE8号信号: 

运行结果:

        我们发现结果一直是死循环状态,显示屏一直在捕获8号信号,大家千万不要以为这是循环的问题,千万不要以为是编译器一直在循环中一次又一次的执行a/a=0导致的8号信号的发送!大家可以试一试将a/=0语句放在循环前,也是一样的结果——显示屏会一直显示捕获到8号信号!

        造成进程陷入死循环的原因在于:

        

        左图紫色的这四个框是Cpu中的寄存器。当CPU执行代码时,如右图,进行算数运算,需要用到寄存器进行对数据的保存,例如eax中存放着变量a的数值,第二个寄存器中进行a/0,因为 a/=0 ==> a=a/0,将a/0的结果再赋值给a
        而第三个寄存器则放着最终的结果。并且CPU中还有一个状态寄存器,状态寄存器会对寄存器进行的算数运算结果进行衡量,若无异常,则状态寄存器中的溢出标志位(默认为0)不会变,若算术过程中出现异常,则状态寄存器中的溢出标志位会从0变成1(溢出),出现溢出,会被CPU察觉,CPU会将其定义为运算异常并且告知OS操作系统,0S识别出状态寄存器的溢出标志位出现了异常,就知道CPU内部出错了继而知道了代码中哪步出现了错误。所以说第三个寄存器中保存的结果被状态寄存器发现异常所以其中的溢出标志位转换为1。
        接受到信号,不一定会引起进程退出,有可能还会被调到CPU内部的寄存器只有一份,但是寄存器中的内容,属于当前进程的上下文!这时需要注意一个大问题,我们是否有能力或者动作去修正这个异常问题呢? 答案是:没有!所以出现了死循环的过程,当进程被切换的时候,就有无数次状态寄存器被保存和回复的过程。所以每一次恢复的时候,就让OS识别到了CPU内部的状态寄存器中的溢出标志位是1。

        所以造成死循环最终的原因就是:CPU在运算的过程中遇到了除零错误,然后CPU向OS操作系统打报告,说:“我的状态寄存器标志位溢出了”,OS得知后根据CPU的报告找出了具体的错误,向进程发送了浮点数SIGFPE信号,在进程中,signal函数接收了SIGFPE信号,调用了它的第二参数,进行相应动作的语句输出。然后编译器返回到a/=0该语句中,发现除零错误仍然没被处理掉。于是CPU又向操作系统打报告,OS又向该进程发送SIGFPE信号......,就这样,CPU不停的报错误,OS不停的发送信号,signal函数不停的捕获该信号,但没有能力处理,就这样造成了死循环问题。

        

总结:

        当程序在运行中产生了异常(除零,野指针等)导致进程终止,我的程序会触发操作系统内CPU或者内存管理单元的相关报错,进而被探作系统识别到,操作系统识别到会向我们的目标进程发送信号,我们的进程就自动会被终止,我可以选择不终止,选择捕捉异常,但没有什么意义,如上就算硬件异常产生的信号发送,不会调用系统接口,也不会自己主动去发送, 这是由软件行为自动触发,在操作系统内部自动工作;


        异常也有不同原因的异常,收到不同的异常信号,就代表着不同的原因,根据进程收到异常信号的不同,我们可以反向的进行定位问题!不同的信号,代表不同的事件,可以根据信号,去标定进程出现的问题,进而去修改异常,让程序正常化!! !

         所以这就是一般情况下为什么我们对进程使用各类信号后,进程接收到信号的反应几乎都是中断执行,强制退出的情况,因为我们没有能力去处理这些错误,只有从根源上(改正代码)去解决才行。


2.2.4.软件条件的产生

        当初在学习管道的过程中,写代码: 两个进程通信个进程只负责读,当读进程关闭了读端,写进程立即一个进程只负责写,被终止继续写入,并退出。 写进程被立即终止退出的情况也是因为OS操作系统向该进程发送了信号,导致写进程退出。因为读端被关闭,写端无论写多少都没用,操作系统不允许资源浪费,所以立刻发出信号给写进程。

        接下来,根据软件条件实现一个案例,让我们能够更加具体形象的看到软件条件产生的信号造成的进程终止:

alarm();

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

        alarm函数是设置一个计时器,在计时器超时的时候,产生SIGALRM信号.alarm也称为闹钟函数,注:一个进程只能有一个计时器时间如果不忽略或捕捉此信号,它的默认操作是终止调用该alarm函数的进程。


        它的主要功能是设置信号传送闹钟。其主要功能用来设置信号SIGALRM在经过seconds指定的秒数后传送给目前的进程,如果在定时未完成的时间内再次调用了alarm函数,则后一次定时器设置将覆盖前面的设置,当seconds设置为0时,定时器将被取消。它返回上次定时器剩余时间,如果是第一次设置则返回0。

        代码解析:通过采用死循环的方式一直运行,采用alarm函数的意义在于,让进程在执行代码一秒后直接调用alarm函数,让系统发送SIGALRM(14号信号)给该进程,然后该进程就会被终止。

运行结果:

       当程序被运行后,进程会执行1秒钟的死循环,在这1秒钟内,CPU一直在cout输出cnt++的值,一直运行了68077次,因为alarm函数的参数为1,然后进程被动调用了该函数因为是CPU进行进程的代码执行,cpu速度很快,一秒钟就能实现数以万次的计算,而cout输出是外设进行的,外设的速度很慢,所以1秒内,cpu完成的万次计算想要让外设进行打印输出这么多内容需要好几秒才能完整打出来。

(14) SIGALRM
        在POSIX兼容的平台上,SIGALRM是在定时器终止时发送给进程的信号。计算机程序通常使用SIGALRM作为长时间操作的超时信号,或提供一种隔一定时间间隔处理某些操作的方式。

        任意一个进程,都可以通过alarm系统调用在内核中设置闹钟,OS内可能会存在着很多的闹钟,那么操作系统要不要管理这些闹钟呢?    ——先描述,再组织

案例2:

         当进程在执行0.5秒后,alarm被调用,然后signal函数捕捉到了SIGALRM信号,调用Checksig函数,输出语句,并且继续执行alarm(1);又订了一秒后继续调用alarm,signal不停在0.5秒后的捕捉SIGALRM信号,捕捉到后,调用Checksig函数,再不停的预定1秒后继续预定闹钟,形成死循环。

         于是会不停的调用Checksig函数,,alarm函数的作用也就相当于是sleep函数了,也是每隔一秒调用一次。


三.总结

        1.所有的信号都是由OS操作系统来发送给进程的,操作系统是所有进程的管理者,所以只有它有权利发送信号。

        2.OS发送某一个信号是修改进程PCB的进程信号位图进行的。

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

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

相关文章

Windows系统上安装Node.js图文步骤流程

Windows系统上安装Node.js图文步骤流程&#xff0c;本文以安装Node.js v4.4.3 LTS(长期支持版本)版本为例&#xff1a; 目录 Node.js下载 Windows 上安装 Node.js 1、Windows 安装包(.msi) 2、Windows 二进制文件 (.exe)安装 版本测试 Node.js下载 Node.js 安装包及源码…

nginx七层代理和四层转发的理解

先来理解一下osi七层模型 应用层 应用层是ISO七层模型的最高层&#xff0c;它直接与用户和应用程序交互&#xff0c;提供用户与网络的接口。它包括各种应用协议&#xff0c;如HTTP、FTP、SMTP等&#xff0c;用于实现特定应用的功能和通信表示层 表示层…

Java进程ProcessBuilder类的介绍及使用,ProcessBuilder调用外部程序执行shell命令Linux命令

目录 ProcessBuilder类的介绍及使用 【前言】 【正文】 --构造方法-- --常用方法-- --使用技巧-- --调用本地Shell命令&#xff0c;实例-- 【总结】 【注意】 ProcessBuilder类的介绍及使用 【前言】 在做一个项目的时候需要用到运行时动态执行JAVA命令&#xff0c;一…

leetcode 225.用队列实现栈

⭐️ 题目描述 &#x1f31f; leetcode链接&#xff1a;用队列实现栈 1️⃣ 思路和图解&#xff1a; push&#xff1a; 入栈操作只需要往不为空的队列入数据即可&#xff0c;如果都为空&#xff0c;其中任意一个队列都可以。 void myStackPush(MyStack* obj, int x) {// 往…

CS EXE上线主机+文件下载上传键盘记录

前言 书接上文&#xff0c;CobaltStrike_1_部署教程及CS制作office宏文档钓鱼教程&#xff0c;该篇介绍【使用CS生成对应exe木马&#xff0c;上线主机&#xff1b;对上线主机进行&#xff0c;文件下载&#xff0c;文件上传&#xff0c;键盘记录】。 PS&#xff1a;文章仅供学习…

数仓建设中最常用模型--Kimball维度建模详解

数仓建模首推书籍《数据仓库工具箱&#xff1a;维度建模权威指南》&#xff0c;本篇文章参考此书而作。文章首发公众号&#xff1a;五分钟学大数据&#xff0c;公众号后台发送“维度建模”即可获取此书籍第三版电子书 先来介绍下此书&#xff0c;此书是基于作者 60 多年的实际业…

SpringBoot前后端分离项目,打包、部署到服务器详细图文流程

文章目录 实施步骤一、修改配置文件地址1.修改MySQL配置2.修改Redis配置3.修改日志路径和字符集配置 二、将源码压缩并上传服务器1.上传前端文件2.上传后端文件&#xff08;同上&#xff09; 三、前端项目打包1.安装依赖2.项目打包 四、后端项目打包1.项目打包&#xff08;jar包…

Ubuntu 20.04 LTS 安装 nvidia 驱动 + cuda 11.8 从开始到放弃!

升级 sources.list # 默认注释了源码镜像以提高 apt update 速度&#xff0c;如有需要可自行取消注释 deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restri…

java的断言

断言介绍 Java的断言就是一条assert 声明&#xff0c;其中包含了一个布尔表达式。 断言可以被启用或者禁用&#xff0c;默认是禁用的。 断言被启用的情况下&#xff0c;执行到断言的声明&#xff0c;就会计算布尔表达式的值。如果表达式的值为false&#xff0c;那么就会抛出一…

9、架构:CLI 设计

通常大部分的程序员会更加习惯使用 CLI&#xff08;Command-Line Interface 命令行界面&#xff09;来辅助开发业务&#xff0c;包括初始化、更新、构建、发布等功能&#xff0c;可以获得沉浸式一站的开发体验。 在之前有一篇企业级 CLI 开发实战介绍过如何开发一款适用团队的…

阿里开业项目chat2DB-人工智能SQL分析介绍

1. chat2DB简介 1-1. 简介 ​ chat2DB是一款有开源免费的多数据库客户端工具&#xff0c;支持windows、mac本地安装&#xff0c;也支持服务器端部署&#xff0c;web网页访问。和传统的数据库客户端软件Navicat、DBeaver 相比Chat2DB集成了AIGC的能力&#xff0c;能够将自然语…

从C语言到C++_23(多态)抽象类+虚函数表VTBL+多态的面试题

目录 1. 多态&#xff08;polymorphism&#xff09; 1.1 构成多态的两个条件 1.2 虚函数重写(覆盖) 1.3 协变构成多态 1.4 父虚子非虚构成多态 1.5 析构函数的重写 1.6 final 和 override 关键字&#xff08;C11&#xff09; 1.7 重载、覆盖、隐藏的对比 2. 抽象类&am…

11、架构:CI/CD 设计

本章内容是基于 DevOps 体系的精简版本&#xff0c;如果有阅读过之前 DevOps 小册的同学&#xff0c;可以快速掠过。 开局先放一张镇楼图&#xff0c;上图我在行云集团做的通用型 CI/CD 解决方案 ALL IN DOCKER&#xff0c;所有的操作构建与发布过程都在 Docker 中操作。 但很…

J2EE自定义mvc【框架配置及功能】

目录 一、配置步骤 二、配置框架前三步 导入相应的jar 导入相应的Class 导入xml文件 三、优化基本操作&#xff08;增删改&#xff09; 1、基础优化 编写实体类 编写BookDao类 优化BookDao JUnit测试 2、后台优化 3、前端优化 一、配置步骤 将框架打成jar包&…

IDEA使用教程 安装教程

16. Codota 插件 Codota 插件可以根据使用频率优先显示较常用的类和方法。然而&#xff0c;是否使用该插件取决于个人的偏好。有时工具只能作为参考&#xff0c;仍然需要依靠个人记忆来确保准确性。 17. 快速查看类和字段的注释 按下 F2 键可以快速查看某个类或字段的文档注…

从JDK源码级别剖析JVM类加载机制

1 什么是Java虚拟机 一个可执行java字节码的虚拟机进程&#xff1b;跨平台的是java程序&#xff0c;而不是java虚拟机&#xff0c;java虚拟机在各个操作系统是不兼容的&#xff0c;例如windows、linux、mac都需要安装各自版本的虚拟机&#xff0c;java虚拟机通过jdk实现功能。…

【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念

系列文章目录 【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 文章目录 系列文章目录前言一、所有权(Ownership)1.1.、所有权(Ow…

【MySQL】不就是子查询

前言 今天我们来学习多表查询的下一个模块——子查询&#xff0c;子查询包括了标量子查询、列子查询、行子查询、表子查询&#xff0c;话不多说我们开始学习。 目录 前言 目录 一、子查询 1. 子查询的概念 2. 子查询语法格式 2.1 根据子查询结果不同可以分为&#xff1a;…

flutter聊天界面-Text富文本表情emoji、url、号码展示

flutter聊天界面-Text富文本表情emoji、url、号码展示 Text富文本表情emoji展示&#xff0c;主要通过实现Text.rich展示文本、emoji、自定义表情、URL等 一、Text及TextSpan Text用于显示简单样式文本 TextSpan它代表文本的一个“片段”&#xff0c;不同“片段”可按照不同的…

Matlab画等构造图

clc;clear;close all; data xlsread(TOPBRENT等T0构造.xlsx); x data(:,1) xmax max(x); xmin min(x); y data(:,2) ymax max(y); ymin min(y); z data(:,3); N 45; …