【Linux进程信号(一)】信号概念/产生/保存/处理

目录

入门

前&后台进程

前台进程:

后台进程

常用命令

./XXX &

 fg命令 & bg命令

Ctrl + c / Ctrl + \

信号的概念

信号的产生

1.键盘产生

2.系统调用指令

3.异常

4.软件条件

信号的保存

信号的处理

1.信号屏蔽字

2.未决信号表

3.信号处理函数表

四种情况

系统接口

1.signal

作用:

参数:

返回值:

2.sigprocmask

作用:

参数:

返回值:

3.sigempty及一系列

作用:

参数:

返回值:

4.sigpending

作用:

参数:

返回值:

5.sigaction

作用:

参数:

返回值:


入门

在生活角度上

  • 在网上买了很多商品,在等待不同商品快递的到来;但就即使快递没有到来,你也知道快递来临时,你该怎么处理快递,也就是你能“识别快递”
  • 当快递员到了你楼下,你也收到快递到来的通知,但是你正在打游戏,需要 5分钟之后才能去取快递;那么在这 5分钟内,你并没有下楼取快递,但是你知道有快递来了;也就是取快递的行为不一定要立即执行,可以理解为“在合适的时候去取”
  • 在收到通知,再到你拿到快递期间,是有一个时间窗口的;在这段时间,你并没有拿到快递,但是你知道有一个快递已经来了;本质上是你“记住了有一个快递要去取”
  • 当你时间合适,顺利拿到快递之后,就要开始处理快递了;而处理快递的方式一般有三种:1.执行默认动作(幸福的打开快递,使用商品);2.执行自定义动作(快递是零食,你要送给你的女朋友);3.忽略快递(快递拿上来之后,扔到床头,继续开一把游戏)
  • 快递到来的整个过程,对你来讲是异步的,你不能确定快递员什么时候到来

在技术角度上

  • 用户输入命令,在Shell下启动一个前台进程:用户按下Ctrl+c,这个键盘输入产生一个硬件中断,被OS获取,解释成信号,发送给目标前台进程,前台进程因为收到信号,进而引起进程退出;
  • 将Ctrl+c 信号处理的过程和生活的例子相结合,我们可以这么理解:进程就是你,操作系统就是快递员,信号就是快递

前&后台进程

刚才我们说到,Ctrl+c是发给前台进程的,那么什么是前台进程,什么是后台进程呢

前台进程:

  • 前台进程是指正在终端(或控制台)上活动的进程,它会占用终端的输入和输出;当用户在终端中启动一个进程时,默认情况下该程序将在前台运行;
  • 前台进程会接受用户输入,并将输出显示在终端上,用户可以与其进行交互,可以通过按下Ctrl+c来发送中断信号(SIGINT)给前台进程,以终止它的执行

后台进程

  • 后台进程是指在终端上运行,但不会占用终端输入和输出的进程;它们在后台默默地执行,用户不会直接与其交互
  • 用户可以通过在命令的末尾添加“&”符号来将进行置于后台运行;例如,./my_program & 将会让my_program进程在后台运行
  • 用户 也可以使用 bg命令将一个已经在前台运行的进程移到后台继续执行,或使用fg命令将一个后台进程切换到前台

所以,在Linux系统中,前台进程和后台进程是指进程在终端中的运行状态

常用命令

./XXX &

在进程后面加上 & 符号可以让进程置于后台运行,下面我举个简单的例子

可以发现,在将进程置于后台运行后,我们执行Ctrl+c,并不会中断进程

并且我们在终端输入 ll 命令也能够正常执行,这就代表进程已经在后台运行了,我们可以使用kill -9将其终止

 fg命令 & bg命令

  • fg命令:fg命令可以将一个后台进程放到前台运行,fg  x (x为进程的作业号)
  • bg命令:bg命令可以将一个前台进程移动到后台运行,bg x;
  • 作业号:在终端使用jobs命令,可以查看当前运行的作业。

Ctrl + c / Ctrl + \

Ctrl + c :向前台进程发送2号中断信号,默认情况会中断前台进程;

Ctrl + \ :向前台进程发送3号退出信号,默认情况会终止前台进程;

信号的概念

在计算机科学中,信号是用于通知进程发生了某些事件的一种机制。这些事件可以是各种各样的,包括外部硬件事件(如键盘输入)、软件事件(如某个进程终止)、或者由操作系统或其他进程生成的事件。

下面是一些常见的信号

  • SIGHUP(1):挂起信号,通常在终端连接断开时发送给进程。
  • SIGINT(2):中断信号,通常由用户键入 Ctrl+C 时发送给前台进程组中的所有进程。
  • SIGQUIT(3):退出信号,通常由用户键入 Ctrl+\ 时发送给前台进程组中的所有进程,用于请求进程退出并生成核心转储。
  • SIGKILL(9):强制终止信号,用于强制终止进程。
  • SIGTERM(15):终止信号,通常用于请求进程正常终止。
  • SIGSTOP(19):停止信号,用于暂停进程的执行。
  • SIGCONT(18):继续信号,用于恢复被停止的进程的执行。

除了上述常见的信号外,还有其他信号,每个信号都有一个唯一的编号。当进程接收到一个信号时,它可以选择忽略、捕获、或者使用默认行为处理该信号,可以通过kill -l查看所有信号

信号的产生

1.键盘产生

当进程运行时,我们可以在终端上按下按键来直接向进程发送信号,SIGINT的默认处理动作是中断进程,SIGQUIT的默认处理动作是终止进程并core dump,这里我们就要说一下core dump了。

core dump:

核心转储(core dump):是指在程序因为异常情况(如段错误、非法指令的能)而终止时,操作系统将当前程序的内存状态保存到一个称为核心转储文件(core dump file)的文件中,这个文件包含了程序崩溃时的内存映像,通常包括了程序的代码、数据、堆栈等信息

核心转储文件对于调试程序异常非常有用,因为它提供了程序崩溃时的内存状态快照,可以帮助开发人员确定程序崩溃的原因。开发人员可以使用调试工具(如GDB)加载核心转储文件,并查看程序崩溃时的内存状态,从而定位和修复问题。

一般情况下,系统设置是不开启核心转储文件的,我们需要通过ulimit -a进行查看

core file size就是核心转储文件的大小,通常系统会将其设置为0,我这里是将其设置成unlimited了。我们可以输入ulimit -c 1024,设置其大小,然后运行一个异常的文件,就会生成核心转储文件,如果运行之后还是没有生成核心转储文件,可以参考下面的博客:

 http://t.csdnimg.cn/TVIyg

2.系统调用指令

我们可以使用kill函数向进程发送信号

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);

 pid是进程的唯一标识符,sig是想要向pid对应进程发送的信号编号。

也可以在终端中使用kill指令向进程发送信号

kill -sig pid

3.异常

void handler(int signo)
{
    std::cout<<"get a signo:"<<signo<<std::endl;
    sleep(1);
}
int main()
{
    signal(8,handler);
    int a = 10;
    a/=0;
    return 0;
}

 signal函数是用来设置信号处理的函数,它会捕获8号信号,并运行handler函数,而不会执行原来8号信号的默认处理方式。

注意:不是所有的信号被signal函数捕获后,都不去执行默认处理方式,比如9号信号。

运行这个程序,当a /= 0时,程序会报错,然后被终止,下面我们讲一下为什么程序会被终止

在现代计算机架构中,CPU通常会在运行计算式维护一系列的状态寄存器,其中包括一个溢出标志位( Overflow Flag)。这个溢出标志位用来指示在运算过程中是否发生了溢出

溢出标志位在进行运算时会根据结果是否发生了溢出而被设置或清除当发生溢出时,溢出标志位会被设置为1;否则,被清除为0。程序可以通过检查溢出标志位来判断运算结果是否溢出,以便进行相应的处理,比如错误处理或者调整数据类型以避免溢出。

我们运行这个程序,当a / 0的时候,发生溢出,溢出标志位被设置为1,然后CPU通知操作系统,操作系统解释为kill(targetprocess,signo),然后操作系统将进程中存储信号的表格中,这个表格我们接下来会讲。

4.软件条件

Linux 中的软件条件指的是在 Linux 操作系统下运行的软件所需的系统要求和依赖条件。这些条件可能包括硬件要求、操作系统版本、库文件、依赖软件等。通常情况下,开发者在发布软件时会提供相关的系统要求和依赖信息,以确保用户能够顺利地在他们的 Linux 系统上安装和运行该软件。

操作系统中的时间

  • 所有用户的行为,都是以进程的形式在OS中表现的
  • 操作系统只要把进程调度好,就能完成所有的用户任务
  • CMOS中的实时时钟芯片,周期性、高频率的向CPU发送时钟中断

补充:CMOS和实时时钟芯片

CMOS是一种电路技术,用于制造半导体集中电路。在计算机系统中,CMOS经常用于描述存储BIOS设置和实时时钟的特殊RAM区域,这种CMOS RAM通常由一个电池供电,即使计算机关闭,也能保持存储的数据不丢失。CMOS RAM中存储的信息包括系统的基本配置、日期和时间等。

实时时钟芯片(RTC芯片)通常包含于CMOS芯片中,用于生成系统时钟信号和实时时间。RTC芯片会生成时钟中断信号,然后通过主板上的控制器发送给CPU,CPU在接收到时钟中断信号后,会暂停当前任务并执行中断任务。

信号的保存

概念

  • 实际执行信号的处理动作叫做信号的递达
  • 信号从产生到递达之间的状态,称为信号未决
  • 进程可以选择阻塞某个信号
  • 被阻塞的信号产生时将保持在未决状态,直接进程解除对该信号的阻塞

信号的递达又可以分为

  • 信号的忽略
  • 信号的默认
  • 信号的自定义捕捉

信号的处理

信号的处理分为三步

1.信号屏蔽字

  • 信号屏蔽字是一个位掩码,用于表示进程当前正在阻塞的信号集合。
  • 当信号的对应位被设置为1时,表示该信号被阻塞。被阻塞的信号不会被递达
  • 进程可以通过系统调用 sigprocmask()  来设置和修改信号屏蔽字,以阻塞或解除阻塞特定的信号。

2.未决信号表

  • 未决信号表是一个数据结构,用于记录当前进程正在等待处理的信号。
  • 通常,它是一个位图,每个位对应一个可能的信号,被设置位1表示相应的信号是未决的,即已经发送给进程但尚未被处理;被设置0代表该信号已经被处理或从未发送给进程
  • 进程在运行时会定期检查未决信号表,并且根据每个信号的处理方式来处理这些未决信号。

3.信号处理函数表

  • 信号处理函数表是一个数据结构,用来存储每个信号对应的处理函数。
  • 通常,它是一个函数指针数组,里面的每个函数指针对应一个信号,呈强相关
  • 当进程收到一个信号时,会根据该信号的索引在处理函数表中查找相应的处理函数,并执行

经过上面三步,一个信号就可以被信号接收并处理,图解可以为:

四种情况

根据信号屏蔽字和未决信号表,我们可以找出一个信号在进程中的四种情况

1.信号屏蔽字中的位为1 ,未决信号表中的对应位为1

  • 这种情况表示该信号被阻塞,并且当前进程有一个未决信号,等待进程解除信号的阻塞后,该信号会被递送给进程。         

2.信号屏蔽字中的位为1 ,未决信号表中的对应位为0

  • 这种情况表示该信号被阻塞,但是当前进程没有一个未决信号等待被处理。

3.信号屏蔽字中的位为0 ,未决信号表中的对应位为1

  • 这种情况通常不会出现,因为信号被发送给进程时,如果信号未被阻塞,,则会被立即添加到未决信号表中。

4.信号屏蔽字中的位为0 ,未决信号表中的对应位为0

  • 这种情况表示该信号既没有被阻塞,也没有未决状态,即该信号从未被发送给进程或者已经被处理。

系统接口

1.signal

       #include <signal.h>

       typedef void (*sighandler_t)(int);

       sighandler_t signal(int signum, sighandler_t handler);

作用:

signal函数的功能是设置在接收到特定信号时要调用的处理函数,它的行为有三种不同的方式

  1. 如果handler参数指定为SIG_DEL(默认处理方式),则该信号的默认处理方式将被恢复
  2. 如果handler参数指定为SIG_IGN(忽略该信号),则进程将忽略该信号,不做任何处理
  3. 如果handler参数是一个有效的函数指针,那指定的函数将成为接收到信号时的处理函数。

参数:

  • ing signum:代表信号编号
  • sidhandler_t handler:可以为SIG_DEL、SIG_IGN或一个有效的函数指针

返回值:

如果函数执行成功, 那么将返回之前的信号处理函数指针的值,否则返回SIG_ERR。

2.sigprocmask

       #include <signal.h>

       int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

作用:

sigprocmask函数用来检查和修改当前进程的信号屏蔽字

参数:

  • int how:指定了函数的行为,可以是以下三值之一
    • SIG_BLOCK:将 set 指定的信号集合添加到当前信号屏蔽字中。
    • SIG_UNBLOCK:从当前信号屏蔽字中移除 set 指定的信号集合。
    • SIG_SETMASK:设置当前信号屏蔽字为 set 指定的信号集合。
  • const sigset_t *set:指向要修改的新信号屏蔽字的指针。如果 howSIG_SETMASK,那么该参数指定了要设置的新信号屏蔽字。如果 howSIG_BLOCKSIG_UNBLOCK,那么该参数指定了要添加或删除的信号集合。
  • sigset_t *oldset:指向用于存储之前信号屏蔽字状态的指针。如果不为 NULL,则将当前信号屏蔽字的状态存储在 oldset 指向的位置。

返回值:

成功返回0,失败返回-1,并设置错误码。

3.sigempty及一系列

       #include <signal.h>

       int sigemptyset(sigset_t *set);

       int sigfillset(sigset_t *set);

       int sigaddset(sigset_t *set, int signum);

       int sigdelset(sigset_t *set, int signum);

       int sigismember(const sigset_t *set, int signum);

作用:

  • sigemptyset(sigset_t *set):将信号集合清空,即设置所有位为 0。
  • sigfillset(sigset_t *set):将信号集合填满,即设置所有位为 1。
  • sigaddset(sigset_t *set, int signum):将指定信号加入到信号集合中。
  • sigdelset(sigset_t *set, int signum):将指定信号从信号集合中删除。
  • sigismember(const sigset_t *set, int signum):检查指定信号是否在信号集合中。

参数:

  • sigset_t *set:信号集合位图
  • signum:信号编号

返回值:

sigsimember如果信号存在信号集合中,返回1,否则返回-1,并设置错误码;

其他几个成功返回0,否则返回-1,并设置错误码。

4.sigpending

       #include <signal.h>

       int sigpending(sigset_t *set);

作用:

获取当前进程的未决信号集

参数:

  • sigset_t  *set:信号集合

返回值:

成功返回0,失败返回-1,并设置错误码

5.sigaction

       #include <signal.h>

       int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);

作用:

sigaction 是一个用于检查和修改信号处理方式的函数,它允许程序员更加灵活地处理信号

参数:

  • int signum:信号编号
  • const struct sigaction *act:结构体指针,用来设置新的信号处理方式
  • struct sigaction *oldact:结构体指针,用于储存之前的信号处理方式,不需要可以传入nullptr

struct sigaction结构体

           struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };

  • sa_handler 是一个函数指针,指向信号处理函数。如果 sa_flags 中没有设置 SA_SIGINFO 标志,则使用 sa_handler;否则,使用 sa_sigaction
  • sa_mask 是一个 sigset_t 类型的信号屏蔽字,用于设置在执行信号处理函数时需要阻塞的其他信号。
  • sa_flags 是一个标志位,用于指定额外的处理选项,比如 SA_RESTART 标志表示在接收到信号后自动重启系统调用。
  • sa_sigaction 是一个函数指针,指向带有三个参数的信号处理函数。这个函数可以获取有关信号的更多信息,如信号的来源等。

返回值:

成功返回0,失败返回-1,并设置错误码

这就是我们关于LINUX信号部分的讲解了,喜欢这篇博客的点个赞~

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

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

相关文章

layui 数据表格 自动定位新增行位置

由于数据表格新增行后没有到新增到当前位置 继续增加的需求&#xff1a; 因为自己是新增行后到最后一行的 所以 就定位到最后一行 并且 高亮 高亮颜色浅 可自行更改 整理了一下 可根据 情况 修改 // 初始化滚动条位置变量 let tableScroll {scrollTob: 0,scrollLeft: 0,…

【busybox记录】【shell指令】comm

目录 内容来源&#xff1a; 【GUN】【comm】指令介绍 【busybox】【comm】指令介绍 【linux】【comm】指令介绍 使用示例&#xff1a; 逐行比较两个排序后的文件 - 默认输出 逐行比较两个排序后的文件 - 如果一个文件的排序有问题&#xff0c;那么反错&#xff08;默认&…

口感与风味的完善结合:精酿啤酒的多样风格

啤酒的世界是丰富多彩的&#xff0c;不同的啤酒有着各自与众不同的口感和风味。而Fendi club啤酒&#xff0c;作为精酿啤酒的代表&#xff0c;以其多样化的风格和卓着的口感&#xff0c;吸引了无数啤酒爱好者的目光。 Fendi club啤酒的多样风格&#xff0c;首先体现在其原料的选…

医药垃圾分类管理系统|基于SSM医药垃圾分类管理系统的系统设计与实现(源码+数据库+文档)

医药垃圾分类管理系统 目录 基于SSM医药垃圾分类管理系统设计与实现 一、前言 二、系统设计 三、系统功能设计 1系统登录模块 2管理员模块实现 3用户模块实现 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博…

1天搞定SpringBoot+Vue全栈开发 (8)前端路由VueRouter(进行组件切换)

1.VueRouter安装与使用 2.参数传递 创建路由组件 在项目中定义Discover.vue、Friends.vue、My.vue三个组件&#xff0c;将来要使用vue-router来控制它们的展示与切换&#xff1a; Discover.vue <template><div><h1>发现音乐</h1></div> <…

PE文件(四)FileBuffer-ImageBuffer

文件执行的总过程 当文件从硬盘中读入虚拟内存&#xff08;FileBuffer&#xff09;中时&#xff0c;文件数据会被原封不动的复制一份到虚拟内存中&#xff0c;然后进行拉伸对齐。此时虚拟内存中文件数据叫做文件印象或者内存印象&#xff0c;即ImageBuffer。此时ImageBuffer中…

Prompt提示词教程 | 提示工程指南 | 提示词示例 入门篇

在上一节中&#xff0c;我们介绍并给出了如何赋能大语言模型的基本示例。如果还没看而且是刚入门的同学建议看下&#xff0c;有个基本概念。 Prompt提示词教程 | 提示工程指南 | 提示工程简介https://blog.csdn.net/HRG520JN/article/details/138523705在本节中&#xff0c;我…

crossover怎么打开软件 mac怎么下载steam crossover下载的软件怎么运行

CrossOver是一款Mac和Linux平台上的类虚拟机软件&#xff0c;通过CrossOver可以运行Windows的可执行文件。如果你是Mac用户且需要使用CrossOver&#xff0c;但是不知道CrossOver怎么打开软件&#xff0c;如果你想在Mac电脑上玩Windows游戏&#xff0c;但不知道怎么下载Steam&am…

free5gc+ueransim配置

ueransim已完成配置&#xff0c;可以观察到在make后&#xff0c;ueransim的build下生成以下几个文件 nr-gnb-------------5G gnb&#xff08;RAN&#xff09;的主要可执行文件 nr ue---------------5G ue的主要可执行文件 nr-cli---------------5G gNB和UE的cli工具 nr-binder-…

从零开始的软件测试学习之旅(六)测试网络基础知识

测试网络基础知识 HTTP和HTMLURLDNS客户端和服务器请求方法和状态码面试高频Fiddler抓包工具教学弱网 HTTP和HTML 概念 html: HyperText Markup Language 超文本标记语言 http: HyperText Transfer Protocol 超文本传输协议 超文本: 图片, 音频, 视频 关系:http 可以对 html 的…

Cargo - 构建 rust项目、管理依赖包

文章目录 关于 Cargo构建项目创建工程编译运行buildclean 管理依赖添加依赖updatecheck计时 manual rust 安装可参考&#xff1a;https://blog.csdn.net/lovechris00/article/details/124808034 关于 Cargo Cargo 官方文档 &#xff1a; https://doc.rust-lang.org/cargo/crat…

QSPI的使用

Quad SPI接口(QSPI)是一种同步串行数据链路,在主模式下提供与外部设备的通信。它类似于SPI协议,只是它有额外的数据线。 普通SPI有四条通信线路:芯片选择、时钟、MOSI和MISO。对于QSPI,可提供额外的数据线。因此,命令/数据/地址是根据所选模式通过单、四或双IO发送的。由…

网络安全之动态路由OSPF基础

OSPF&#xff1a;开放式最短路径优先协议。 1、协议使用范围&#xff1a;IGP。 2、协议算法特点&#xff1a;链路状态型路由协议。 3、协议是否传递网络掩码&#xff1a;传递网络掩码&#xff08;无类别的路由协议&#xff09;。 4、协议封装&#xff1a;基于IP协议封装&am…

【Linux系统编程】1-文件IO操作

文章目录 1 概述2 文件描述符3 文件I/O操作3.1 打开文件操作3.2 关闭文件操作3.3 向文件写入数据3.4 从文件读取数据 4 给文件描述符添加非阻塞特性4.1 当此文件描述符不存在4.2 当此文件描述符存在 5 获取文件状态信息6 文件目录操作6.1 打开目录操作6.2 读取目录信息6.3 关闭…

窃鈇逃债,赧然惭愧——“天下共主”周赧王的结局

引子&#xff0c;债台高筑 周赧王五十九年&#xff08;前256年&#xff09;&#xff0c;雒邑王都内&#xff0c;大周第三十七代天子、年近八十的周赧王姬延困坐在王宫内的高台上&#xff0c;愁容满面、沮丧悲切、束手无策&#xff1b;而王宫宫墙外不远处&#xff0c;是一大帮举…

VMware与CentOS的安装

VMware与CentOS的安装 第一章 VMware安装第二章 CentOS上网虚拟机网络IP修改地址配置修改主机名和hosts文件修改主机名称配置Linux克隆机主机名称映射hosts文件&#xff0c;打开/etc/hosts 安装Xshell7和Xftp7 第一章 VMware安装 VMware Workstation Pro 安装包 …

【再探】设计模式—适配器、装饰及外观模式

结构型设计模式是用于设计对象和类之间关系的一组设计模式。一共有7种&#xff1a;适配器模式、装饰器模式、外观模式、桥接模式、组合模式、享元模式及代理模式。 1 适配器模式 需求&#xff1a;在软件维护阶段&#xff0c;已存在的方法与目标接口不匹配&#xff0c;需要个中…

Windows系统和unbtun系统连接usb 3.0海康可见MVS和红外艾睿相机

一.海康可见USB3.0工业面阵相机 海康usb相机需要去海康官网上下载对应系统的MVS客户端及SDK开发包 海康机器人-机器视觉-下载中心 选择Windows系统和unbtun&#xff08;我是linux aarch64,所以选择了对应压缩包解压&#xff09; Windows系统 1.双击安装包进入安装界面&…

【Qt 学习笔记】Qt常用控件 | 输入类控件 | Date/Time Edit的使用及说明

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 输入类控件 | Spin Box的使用及说明 文章编号&#xff1…

【牛客】[HNOI2003]激光炸弹

原题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 二维前缀和板题。 注意从&#xff08;1,1&#xff09;开始存即可&#xff0c;所以每次输入x,y之后&#xff0c;要x,y。 因为m的范围最大为…
最新文章