【Linux】第三十六站:信号

文章目录

  • 一、信号的概念
    • 1.信号概念
    • 2.前台与后台进程
    • 3.信号的处理
    • 4.硬件层面
    • 5.信号与我们的代码是异步的
  • 二、信号的产生
    • 1.产生的方式
    • 2.键盘组合键
    • 3.kill命令
    • 4.系统调用
      • 4.1 kill系统调用
      • 4.2 raise
      • 4.3 abort
    • 5.异常软件条件
      • 5.1 异常产生信号
      • 5.2 alarm(软件条件产生信号)
    • 6.core dump

一、信号的概念

1.信号概念

我们常见的信号有信号弹、下课上课铃声、求偶、红绿灯、快递发短信取件码等等…

a.那么我们是怎么认识这些信号的??

当然是有人教我们,最后我们记住了

这个认识,首先我们要识别信号,其次还要知道信号的处理方法。最后记住他们

b.即便是我们现在没有信号产生,我也知道信号产生了之后,我该干什么

c.信号产生了,我们可能并不立即处理这个信号,在合适的时候,因为我们可能正在做更重要的事情。 — 所以,信号产生后一直到信号处理时,中间一定有一个时间窗口。在这个时间窗口内,我们必须记住信号到来!

上面这些我们指代的就是进程!

所以

  1. 进程必须识别 + 能够处理信号 — 即便信号没有产生,也要具有处理信号的能力 — 信号的处理能力,属于进程内置功能的一部分
  2. 进程即便是没有收到信号,也能知道哪些信号该怎么处理
  3. 当进程真的收到了一个具体的信号的时候,进程可能并不会立即处理这个信号,在合适的时候会进行处理
  4. 一个进程必须当信号产生,到信号开始被处理,就一定会有时间窗口,所以进程具有临时保存哪些信号已经发生了的能力

如下图所示,我们前面所作的相当于是一个信号的预备部分

image-20240123232831963


2.前台与后台进程

我们还记得,我们使用CTRL + C可以杀掉前台进程。(像如下所示的,就是前台进程,该进程运行时,shell不会接收其他命令了)

image-20240124221138981

但是如果我们这样做,它就是一个后台进程了,即在程序后面加上一个&即可,而且我们还发现,我们直接CTRL + C已经杀不掉它了。

image-20240124221652570

在我们的Linux中,一次登录中,一个终端,一般会配上一个bash。每一个登录只允许一个进程是前台进程,可以允许多个进程是后台进程。

如果我们要杀掉后台进程,我们只能使用kill -9命令了

image-20240124222011674

一般来说,前台进程和后台进程的区别就是谁能获取键盘输入

键盘输入首先是被前台进程收到的

那么既然一开始bash是前台进程,那么为什么使用CTRL+C时候,bash不退出呢?

这当然是因为bash在里面对这个信号做了特殊处理

CTRL +C 本质是被进程解释成为收到了信号,2号信号

我们知道我们的系统一共有62个信号(没有0号,32号,33号)

image-20240124235545846

我们将前31个信号称之为普通信号。后面的34~64我们称之为实时信号

一旦信号产生,不立即处理就是普通信号,立即处理是实时信号

这些信号本质就是一些数字,在linux中它们是以宏的方式定义的,就是这些数字。

3.信号的处理


信号的处理方式

  1. 默认动作
  2. 忽略
  3. 自定义动作

我们现在可以验证一下,进程收到2号信号的默认动作,就是终止自己!

我们先来看一下这个函数

image-20240125000914422

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

这个函数的作用是设置对于signum信号的处理方法,处理方法为handler

这个signal函数是一个系统调用

它可以修改进程对于特定信号的处理动作

我们用如下代码来验证

#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;void myhandler(int signo)
{cout << "process get a signal: " << signo << endl;
}
int main()
{signal(SIGINT, myhandler);while(true){cout << "I am a crazy process" << endl;sleep(1);}return 0;
}

运行结果为

image-20240125001657759

可见我们已经证明了2号信号的默认动作

我们也可以设置让2号信号可以退出

image-20240125002151798

运行结果为

image-20240125002215607

对于这个signal函数,只需要设置一次即可,往后都有效

只有收到了对应的信号,才会调用这个方法

注意:不是所有的信号都可以自定义的。有些信号不能自定义

4.硬件层面

键盘数据是如何输入给内核的,CTRL + C又是如何变成信号的?

键盘被摁下,肯定是OS先知道的

那么OS怎么知道键盘上有数据了???

如下图所示,是冯诺依曼体系结构

image-20240125003524986

我们知道,Linux下一切皆文件。键盘也是,它也有自己对应的struct file。以及缓冲区

image-20240125003730349

往键盘输入数据本质就是把输入的数据拷贝到缓冲区上。所以操作系统就知道了

所以我们就可以用read,write通过文件的方式把数据读到进程当中

image-20240125003934697

那么操作系统要把键盘上的数据拷贝到缓冲区中。在这个过程中,操作系统怎么知道键盘上有数据了?

其实CPU上是有很多针脚的。我们的CPU是直接插到主板上的。而键盘是可以间接的和CPU直接物理上连接到的。虽然CPU不从键盘读数据。但是键盘可以给CPU发送一个硬件中断。

也就是说,一旦外设有数据,就可以给CPU发送一个中断,从而让操作系统去完成文件拷贝。键盘、网卡等都可以给CPU发送中断。

可是外设一多,CPU如何知道是谁给发送的中断呢?

这就会有一个中断号的概念。他们会通过这些针脚,直接将中断号发送给CPU

一旦硬件CPU知道键盘上有数据了。

CPU的寄存器凭什么能保存数据呢??

其实这个本质就是充放电的过程。如果是高电平代表充电了,就有1了。

image-20240125005906919

在软件层面上,操作系统一启动,就会形成一张,中断向量表。里面放的是方法的地址。这些方法是直接访问外设的方法—主要是磁盘,显示器,键盘

image-20240125010244301

然后最后这个读取键盘的方法,才是将键盘的数据放到缓冲区的方法

image-20240125010528251

所以其实整个流程就是,键盘一旦有数据,会通过中断将中断号给CPU,CPU会利用这个中断号,让操作系统直接去通过中断向量表,找到对应的读取键盘的方法,然后通过这个方法就会让数据从键盘拷贝到这个文件缓冲区上。

所以键盘这个外设是通过中断来工作的。这个就是硬件中断

而我们前面所说的信号,也是通过一堆数字来进行控制。这两者其实比较相似,但是没有关系。一个是软硬件结合的,一个是纯软件行为。

我们所用的信号,就是用软件的方式,对进程模拟的硬件中断

当我们键盘读取的是CTRL + C这样的组合键的时候,操作系统其实还会对键盘上的数据进行判断。判断是数据还是控制,如果是控制,比如CTRL+ C会把这个转化为2号信号发送给进程。而不是放到缓冲区中。所以进程就收到了2号信号

像我们之前所谓的输入数据后往显示器上回显的过程其实是这样的,先将数据放到键盘的缓冲区,然后将键盘的缓冲区的数据放到显示器的缓冲区,最后就能输出了

image-20240125012101505

如果这里不给显示器的缓冲区,就是不回显

当然,在这个过程中,也会有其他的进程给显示器的缓冲区上放数据

image-20240125012217825

所以即便在显示器上的是乱的,但是我们还是能成功的执行指令

5.信号与我们的代码是异步的

信号的产生的和我们自己的代码的运行是异步的

同步就是发生一件事后等这件事发生完了才继续做我们的事情

异步就是这件事发生后我们不管这个事情,继续做我们的事情

信号是进程之间事件异步通知的一种方式,属于软中断

二、信号的产生

1.产生的方式

  1. 键盘组合键
  2. kill命令
  3. 系统调用
  4. 异常软件条件

以上是信号产生的方式!但是无论信号如何产生,最终一定是谁发送给进程的?

当然是OS

那么是为什么呢?

操作系统是进程的管理者!

2.键盘组合键

比如CTRL+C是2号信号

我们可以试一下捕捉三号信号

使用CTRL + \即可捕捉3号信号

#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;void myhandler(int signo)
{cout << "process get a signal: " << signo << endl;//exit(1);
}
int main()
{//signal(SIGINT, myhandler);signal(3, myhandler);while(true){cout << "I am a crazy process" << endl;sleep(1);}return 0;
}

image-20240125013753341

CTRL + Z是19号信号

#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;void myhandler(int signo)
{cout << "process get a signal: " << signo << endl;//exit(1);
}
int main()
{//signal(SIGINT, myhandler);//signal(3, myhandler);signal(19, myhandler);while(true){cout << "I am a crazy process" << endl;sleep(1);}return 0;
}

image-20240125014500820

如下所示,我们似乎会发现,我们上面似乎并没有将19号信号用自定义的方法进行处理

其实这是因为不是所有的信号,都是可以被signal捕捉的,比如19,9号信号

我们可以用下面的代码进行测试。这里就不做演示了

#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;void myhandler(int signo)
{cout << "process get a signal: " << signo << endl;//exit(1);
}
int main()
{//signal(SIGINT, myhandler);//signal(3, myhandler);//signal(19, myhandler);for(int i = 1; i <= 31; i++){signal(i, myhandler);}while(true){cout << "I am a crazy process" << endl;sleep(1);}return 0;
}

19号是用来暂停进程的,9号信号是用来杀掉进程的。

这两个都是跟执行相关的。这是为了预防进程出现意外所设计的。不能被捕捉的

3.kill命令

kill -信号 进程pid

4.系统调用

4.1 kill系统调用

image-20240125151455582

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

它的两个参数分别是pid和信号的编号。与命令行中的kill是很相似的

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

我们可以简单的利用这个系统调用接口实现一个kill命令

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <sys/types.h>
using namespace std;void Usage(string proc)
{cout << "Usage:\n\t" << proc << "signum pid\n\n";
}int main(int argc, char* argv[])
{if(argc != 3){Usage(argv[0]);exit(1);}int signum = stoi(argv[1]);pid_t pid = stoi(argv[2]);int n = kill(pid, signum);if(n == -1){perror("kill");exit(2);}return 0;
}

测试结果如下所示

image-20240125153352529

4.2 raise

image-20240125153520451

#include <signal.h>
int raise(int sig);

它的作用,发送一个信号给调用本方法者

我们可以用如下代码进行测试

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <sys/types.h>
using namespace std;
void myhandler(int signo)
{cout << "process get a signal: " << signo << endl;exit(1);
}
int main()
{signal(2, myhandler);int cnt = 5;while(true){cout << "I am a process, pid: " << getpid() << endl;sleep(1);cnt--;if(cnt == 0) raise(2);}return 0;
}

运行结果为

image-20240125154306514

这个raise相当于

kill(getpid(), 2);

4.3 abort

image-20240125154538468

它的作用是引起一个正常的进程直接终止

它相当于给自己发送一个6号信号

我们先用下面代码进行测试

int main()
{int cnt = 5;while(true){cout << "I am a process, pid: " << getpid() << endl;sleep(1);cnt--;if(cnt == 0) abort();}return 0;
}

image-20240125155156642

如果我们继续将代码改为下面的,让6号信号可以被捕捉

void myhandler(int signo)
{cout << "process get a signal: " << signo << endl;//exit(1);
}
int main()
{signal(6, myhandler);int cnt = 5;while(true){cout << "I am a process, pid: " << getpid() << endl;sleep(1);cnt--;if(cnt == 0) abort();}return 0;
}

那么结果为

image-20240125155415576

我们会注意到,虽然我们捕捉了以后,并没有将他给终止,但是abort依然将他给终止了。

我们可以再来观察一下

在下面的代码中,我们不让他自己abort了,我们现在在命令行上发送6号信号

void myhandler(int signo)
{cout << "process get a signal: " << signo << endl;//exit(1);
}
int main()
{signal(6, myhandler);int cnt = 5;while(true){cout << "I am a process, pid: " << getpid() << endl;sleep(1);cnt--;//if(cnt == 0) abort();}return 0;
}

image-20240125155613395

我们发现进程并没有被终止

所以其实是abort里面封装了一层,必须让进程终止

5.异常软件条件

5.1 异常产生信号

我们先使用如下代码

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;int main()
{cout << "div before" << endl;sleep(1);int a = 10;a /= 0; cout << "div after" << endl;sleep(1);return 0;
}

运行结果为

image-20240125162723106

像这种情况就是收到了信号了

收到的是8号信号

我们可以用七号手册

man 7 signal

往下翻就可以找到这个信号详情了,可以看到确实是八号信号

image-20240125163007468

如果我们用下面的代码进行测试

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
void handler(int signo)
{cout << "get a signal, number: " << signo << endl;
}int main()
{signal(SIGFPE, handler);cout << "div before" << endl;sleep(1);int a = 10;a /= 0; cout << "div after" << endl;sleep(1);return 0;
}

那么运行结果为。打印很多的收到八号信号

image-20240125164356868

而且这个过程中进程是不会退出的

image-20240125164730496

这里的都还是比较好解释的:

不过我们可能比较好奇的是为什么在这里他会一直发送这个八号信号呢?

即信号为什么会一直被触发?

我们先看下面的代码

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
int main()
{//signal(SIGFPE, handler);cout << "point error before" << endl;sleep(1);// int a = 10;// a /= 0; int* p = nullptr;*p = 10;cout << "point error after" << endl;sleep(1);return 0;
}

运行结果为

image-20240125170943491

这里也是代码没有跑完直接崩溃了,这个本质也是收到了信号。收到的是11号信号

我们在捕捉一下11号信号

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
void handler(int signo)
{cout << "get a signal, number: " << signo << endl;
}int main()
{signal(SIGSEGV, handler);cout << "point error before" << endl;sleep(1);// int a = 10;// a /= 0; int* p = nullptr;*p = 10;cout << "point error after" << endl;sleep(1);return 0;
}

运行结果为

image-20240125171249312

和前面十分类似,会不断的捕捉11号信号,而且还不会退出

所以进程收到异常信号,不一定会退出。但是一定会执行异常处理方法

虽然上面捕捉后一直在打印,没有退出,不过其实我们大概率还是要将他们给退出的。因为一直打印也没什么用

image-20240125171650007

运行结果为

image-20240125171721591

为什么 /0 ,野指针会给进程发送信号?导致进程崩溃?

这里是因为,除零,野指针会给系统带来问题,操作系统识别到这些问题,然后OS给进程发送的信号。信号的默认处理动作就是终止自己,所以就崩了

操作系统为什么能检测到除零,野指针呢?

首先如下所示,如果是除零错误

在CPU上有一个eip/pc寄存器可以用来记录当前执行的是哪一行代码

image-20240125172949278

还有一种寄存器是状态寄存器。它里面的每一位都有特定的含义

image-20240125173431114

其中有一个就是溢出标志位

一旦我们的代码除零了,这个溢出标志位就变为1了

像我们在CPU的这些数据,都是这个进程的上下文

image-20240125173701835

也就是说,这里虽然我们修改的是CPU内部的状态寄存器,但是这里只影响我们自己。不会影响其他进程,进程切换的时候,其他进程会将自己的上下文数据放上去

在这里操作系统一定会知道这里出错了。因为CPU是硬件,OS是硬件的管理者。

所以操作系统才会向进程发送信号


如果是野指针异常

如下图所示,在CPU里面有一个内存管理单元,因为直接查页表太慢了,所以有一个MMU硬件来进行查表。

一旦异常,也就是地址转化失败了。虚拟到物理转化失败了。

在CPU内还有一个寄存器,一旦转化失败了。它会把转化失败的虚拟地址放在这里

image-20240125180552673

也就是说,一旦转化失败,CPU也能识别


而且因为他们是用的不同的寄存器,所以CPU也能区分出来是哪种报错,操作系统也就知道了


这里我们进程出异常以后,我们本应该退出。但是如果我们非要不退出。

意味着这个进程一直被调度运行。

硬件一直存在这个问题。我们也没有修正。随着我们的调度。操作系统一直在检测到这个异常,然后我们也一直在捕捉这个信号。所以就一直打印。


所以捕捉异常其实就不是让我们不让进程退出的,而是让我们知道我们这个进程是怎么死掉的

那么异常只能由硬件产生吗?

当然不是。

比如我们之前的管道,如果一开始读写端都打开,但是我们突然关闭了读端。那么写端进程就会被杀掉。会收到一个SIGPIPE(13)号信号。这就是一种软件异常。

也有的异常,操作系统只是会返回值出错的形式进行处理

image-20240125183437279

运行结果为

image-20240125183450658

5.2 alarm(软件条件产生信号)

image-20240126155127170

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

alarm 系统调用用于设置一个定时器,当定时器计时器达到指定的时间时,内核会发送一个 SIGALRM 信号(14号信号)给调用进程。这可以用于实现定时器功能,例如在一定时间间隔内执行某个特定的操作或执行定时任务

seconds 参数表示定时器的秒数。如果 seconds 参数为非零值,表示设置定时器,在指定秒数后会发送 SIGALRM 信号给进程。如果 seconds 参数为零,则表示取消之前设置的定时器。

返回值是剩余的未完成的定时器秒数。如果之前有一个定时器已经设置,调用 alarm 会取消之前的定时器,并返回剩余的秒数。如果没有之前的定时器,或者之前的定时器已经到期,返回值为 0。

比如如下的代码中

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
int main()
{int n = alarm(5);while(1){cout << "proc is running..." << endl;sleep(1);}return 0;
}

运行结果为

image-20240126160304788

我们可以捕捉一下这个信号

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
void handler(int signo)
{cout << "get a signal, number: " << signo << endl;//exit(1);
}int main()
{   signal(SIGALRM, handler);int n = alarm(5);while(1){cout << "proc is running..." << endl;sleep(1);}return 0;
}

image-20240126160519144

这里并不是异常导致的,所以不会循环式的疯狂捕捉。

我们也可以下面这样做,就可以每隔三秒打印一次了

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
void handler(int signo)
{cout << "get a signal, number: " << signo << endl;int n = alarm(3);//exit(1);
}
int main()
{   signal(SIGALRM, handler);int n = alarm(3);while(1){cout << "proc is running..." << endl;sleep(1);}return 0;
}

image-20240126160814824

有了这个闹钟,我们就可以在执行主要任务的同时,去定时完成其他任务了。像下面这样即可

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;void work()
{cout << "print log ..." << endl;
}void handler(int signo)
{work();//cout << "get a signal, number: " << signo << endl;int n = alarm(3);//exit(1);
}
int main()
{   signal(SIGALRM, handler);int n = alarm(3);while(1){cout << "proc is running..." << endl;sleep(1);}return 0;
}

关于它的返回值,我们可以这样做

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
void handler(int signo)
{cout << "get a signal, number: " << signo << endl;int n = alarm(5);cout << "剩余时间: " << n << endl; }int main()
{   signal(SIGALRM, handler);int n = alarm(50);while(1){cout << "proc is running..., pid: " << getpid() << endl;sleep(1);}return 0;
}

运行结果如下

image-20240126161754595

理解alarm

我们知道每个进程都可以使用alarm设置闹钟,所以操作系统中一定有大量的闹钟。所以操作系统要管理闹钟,所以闹钟就会用struct结构体描述,然后用链表等数据结构管理起来。这样所谓的闹钟管理就变成了对链表等的增删查改。

这个alarm结构体里面,一定有pid,或者pcb的指针。

操作系统底层中alarm的底层所用的时间用的是时间戳。这样最简单。

只要系统的当前时间大于等于里面设置的时间,就会发信号。

不过我们遍历链表的时候是比较浪费时间的。所以用一个小堆是最简单的。

6.core dump

我们可以看一下信号的详细信息手册

image-20240126163842284

我们可以注意到,常见的信号中大部分是终止信号的。还有一些是暂停(Stop),继续(Cont),忽略(Ign)等。

我们可以注意到终止信号中,有一些是Core,有一些是Term

在我们当时提到进程退出的时候,有一个这个字段core dump标志

image-20240126164328929

这个是用来表示是Core方式被杀还是Term方式

接下来我们使用这段代码来看看这个标志位

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>using namespace std;
int main()
{pid_t id = fork();if (id == 0){int cnt = 500;while (cnt){cout << "I am a chid process, pid: " << getpid() << "cnt: " << cnt << endl;sleep(1);cnt--;}exit(0);}int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid == id){cout << "child quit info, rid: " << rid << " exit code: " << ((status>>8)&0xFF) << " exit signal: " << (status&0x7F) << " core dump: " << ((status>>7)&1) << endl;}
}

如果被2号信号所杀,如下所示,这个标志为为0。而2号信号对应的Term方式

image-20240126171331475

如果我们用8号信号所杀,我们发现这个core dump 标志位还是0。但是8号信号对应的是Core。

image-20240126171709965

我们发现这两个都没有什么变化。

其实如果我们当前用的是虚拟机的话。如果进程崩掉的话。我们会发现当前目录会形成一个临时文件,这个临时文件是XXX.core文件。而我们当前的云服务器上会发现没有,这是因为默认云服务器上的core功能是被关闭的

那么这是为什么呢?是什么呢?怎么办呢?

我们使用下面这个命令,它可以查看系统当中一些标准的配置

ulimit -a 

image-20240126173808287

其中这个core file size选项,我们可以使用下面指令去查

ulimit -c

image-20240126174000025

我们可以看到结果为0,也就是说这个,core默认是被关掉的

我们可以使用下面指令去设置它

ulimit -c 10240(要设置的大小)

如下所示,也就是说,最大是这么大

image-20240126174145061

上面就是开启core

如果要关闭的话,我们可以直接设置为0

image-20240126174350554

当我们设置好了以后,我们再去使用2号信号和8号信号,结果就不一样了

image-20240126175209648

现在此时,八号信号的core dump就是1了

更关键的是,我们发现现在确实有这个临时文件了

image-20240126175425600

这个很明显就是刚刚的pid

打开系统的core dump功能

一旦进程出现异常,OS会将进程在内存中的运行信息,给我dump(转储)到进程的当前目录(磁盘),形成core.pid文件。

这就是核心转储(core dump)

而这个功能在云服务器上默认是被关闭的

那么为什么要进行核心转储呢?

上面的错误一定是运行时错误。

此时我们要知道什么原因错误了。在哪一行错误了。

所以我们要用core dump来进行定位我们的原始在运行时哪里出错了

我们可以先生成调试版本的可执行程序

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>using namespace std;
int main()
{
int a = 10;
int b =0;a /= b;cout << "a = " << a << endl;
}

运行结果如下所示

image-20240126180623130

此时很显然,已经帮我们核心转储了

然后我们使用gdb调试时候,可以直接使用下面命令

core-file core.23652

image-20240126181049695

我们发现就可以直接定位出错原因了

所以core可以直接复现问题之后,直接定位到出错行

也就是说,先运行,在core-file,是事后调试

所以它就可以为这些最常出现的问题,有一个core功能去终止。

image-20240126181341219

所以这个就相当于Term + Core

为什么这个功能云服务器是关闭的呢?

因为core dump功能消耗的内存比较大。而我们的服务器一般一旦挂掉就会自动重启。计算机的速度是很快的。如果重启后又挂掉了。这样瞬间会冲击磁盘。磁盘被写满后,可能操作系统也会挂掉。

此时问题就严重了。所以一般都要禁掉这个功能的。

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

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

相关文章

【MySQL】学习如何通过DML更新数据库的数据

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-QIqURn9fNFMjLD9l {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

【Go 快速入门】数组 | 切片 | 映射 | 函数 | 结构体 | 方法和接收者

文章目录 数组切片append 函数copy 函数删除元素 映射delete 函数 函数init 特殊的函数defer 语句panic / recover 错误处理 类型结构体内存对齐JSON 序列化与反序列化方法和接收者 项目代码地址&#xff1a;03-ArraySliceMapFuncStruct 数组 基本格式&#xff1a;var 数组变…

Go 命令行解析 flag 包之快速上手

本篇文章是 Go 标准库 flag 包的快速上手篇。 概述 开发一个命令行工具&#xff0c;视复杂程度&#xff0c;一般要选择一个合适的命令行解析库&#xff0c;简单的需求用 Go 标准库 flag 就够了&#xff0c;flag 的使用非常简单。 当然&#xff0c;除了标准库 flag 外&#x…

Linux 网络流量相关工具

本文聚焦于网络流量的查看、端口占用查看。至于网络设备的管理和配置&#xff0c;因为太过复杂且不同发行版有较大差异&#xff0c;这里就不赘述&#xff0c;后面看情况再写。 需要注意的是&#xff0c;这里列出的每一个工具都有丰富的功能&#xff0c;流量/端口信息查看只是其…

使用vue_cli脚手架创建Vue项目(cmd和图形化方式)

使用vue_cli脚手架创建Vue项目&#xff08;cmd和图形化方式&#xff09; 创建项目(cmd方式) vue create vue_cli1.方向键选择manually select feature(手动选择方式创建)&#xff0c;回车 2.按空格键选择需要的组件&#xff1a;Babel、PWA、Router、Vuex、CSS&#xff0c;回…

Linux - 数据流重定向、管道符、环境变量配置文件的加载

概述 想了解Linux编程&#xff0c;shell脚本是绕不开的关键知识点&#xff0c;原计划写一个整篇来分享shell的来龙去脉&#xff0c;但知识点过于繁杂&#xff0c;先分享一下学习shell的准备工作&#xff0c;数据流重定向、管道符、环境变量配置文件的加载&#xff0c;有助于知…

Linux之安装配置CentOS 7

一、CentOS简介 CentOS&#xff08;Community Enterprise Operating System&#xff0c;中文意思是社区企业操作系统&#xff09;是Linux发行版之一&#xff0c;它是来自于Red Hat Enterprise Linux依照开放源代码规定释出的源代码所编译而成。由于出自同样的源代码&#xff0c…

YOLOv8改进 | Conv篇 | 结合Dual思想利用HetConv创新一种全新轻量化结构CSPHet(参数量下降70W)

一、本文介绍 本文给大家带来的改进机制是我结合Dual的思想利用HetConv提出一种全新的结构CSPHet,我们将其用于替换我们的C2f结构,可以将参数降低越75W,GFLOPs降低至6.6GFLOPs,同时本文结构为我独家创新,全网无第二份,非常适合用于发表论文,该结构非常灵活,利用Dual卷…

CSS探索浏览器兼容性

学习如何探索浏览器的兼容性对于编写跨浏览器兼容的CSS代码非常重要。以下是一些学习CSS兼容性的方法&#xff1a; MDN文档&#xff1a;Mozilla开发者网络&#xff08;MDN&#xff09;提供了广泛而详细的CSS文档&#xff0c;其中包含有关CSS属性、选择器和功能的信息。在MDN上…

机器学习之pandas库学习

这里写目录标题 pandas介绍pandas核心数据结构SeriesDataFrameDataFrame的创建列访问列添加列删除行访问行添加行删除数据修改 pandas介绍 pandas是基于NumPy 的一种工具&#xff0c;该工具是为了解决数据分析任务而创建的。Pandas 纳入 了大量库和一些标准的数据模型&#xff…

谷歌seo服务商如何选择?

选择谷歌SEO服务商时&#xff0c;要考虑他们的经验、专业知识、成功案例、透明度、合规性、定制能力、时间线、客户支持、沟通以及是否能够建立长期合作关系。综合评估这些因素&#xff0c;确保找到一个可信赖的合作伙伴&#xff0c;能够帮助您提升网站在谷歌搜索中的表现&…

相机与镜头

一、相机视场 相机的视场角&#xff0c;也就是相机能够看到物像角度的最大值&#xff0c;视场角与焦距的关系为像高f*tan(fov/2)。由于相机的感光面是矩形&#xff0c;所以相机能够看到的区域也是矩形。探究相机的视场角&#xff0c;便于分析物面上那些区域属于相机盲区&#x…

STM正点mini-新建工程模板,GPIO及寄存器(介绍)

一.新建工程模板(基于固件库) 1.1库函数与寄存器的区别 这里的启动文件都是根据容量来进行区分的 对MDK而言即使include了&#xff0c;也不知道在哪里找头文件 STM32F10X_HD,USE_STDPERIPH_DRIVER 二.新建工程模板(基于寄存器) 上面的大部分配置与固件库的一样 具体可以看手…

带延迟的随机逼近方案(Stochastic approximation schemes):在网络和机器学习中的应用

1. 并行队列系统中的动态定价Dynamic pricing 1.1 系统的表述 一个含有并行队列的动态定价系统&#xff0c;该系统中对于每个队列有一个入口收费(entry charge) &#xff0c;且系统运行的目标是保持队列长度接近于某个理想的配置。 这里是这个系统的几个关键假设&#xff1a;…

redis-4 搭建redis集群

1.为什么需要redis集群&#xff1f; Redis 集群提供了高可用性、横向扩展和数据分片等功能&#xff0c;使得 Redis 能够应对大规模的数据存储和高并发访问的需求。以下是一些需要使用 Redis 集群的常见情况&#xff1a; 高可用性&#xff1a;通过在多个节点之间进行数据复制和…

计算机网络——TCP协议

&#x1f4a1;TCP的可靠不在于它是否可以把数据100%传输过去&#xff0c;而是 1.发送方发去数据后&#xff0c;可以知道接收方是否收到数据&#xff1b;2.如果接收方没收到&#xff0c;可以有补救手段&#xff1b; 图1.TCP组成图 TCP的可靠性是付出代价的&#xff0c;即传输效率…

苹果提审被拒反馈崩溃日志.text | iOS 审核被拒crashLog

iOS审核人员拒绝后每个截图&#xff0c;只给了几个text文件&#xff0c;这种情况就是审核的时候运行你的代码&#xff0c;崩溃了。 仅仅看text文件&#xff0c;是看不出所以然来的&#xff0c;所以我们要将日志转换成.crash格式 1.将.text文件下载下来&#xff0c;将 .text手动…

【Linux】进程间通信概念 | 匿名管道

文章目录 一、什么是进程间通信进程间通信的概念进程间通信的目的进程间通信的分类进程间通信的本质 二、什么是管道三、匿名管道匿名管道的原理✨站在内核角度理解管道✨站在文件描述符角度理解管道 pipe系统调用fork后在父子进程间使用管道通信代码实现 匿名管道的读写规则管…

stable diffusion学习笔记——文生图(一)

模型设置 基本模型 基本模型也就是常说的checkpoint&#xff08;大模型&#xff09;&#xff0c;基本模型决定了生成图片的主体风格。 如上图所示&#xff0c;基本模型的后缀为.safetensors。需要存放在特定的文件夹下。 如果用的是启动器&#xff0c;可以在启动器内直接下载。…

GPT-SoVITS 本地搭建踩坑

GPT-SoVITS 本地搭建踩坑 前言搭建下载解压VSCode打开安装依赖包修改内容1.重新安装版本2.修改文件内容 运行总结 前言 传言GPT-SoVITS作为当前与BertVits2.3并列的TTS大模型&#xff0c;于是本地搭了一个&#xff0c;简单说一下坑。 搭建 下载 到GitHub点击此处下载 http…