[Linux]进程信号

news2024/11/26 1:29:26

[Linux]进程信号

文章目录

  • [Linux]进程信号
    • 进程信号的定义
    • 信号的特点
    • 信号的生命过程
    • 发送信号的原理
    • 进程处理信号的方式分类
    • 使用指令查看Linux系统定义的信号
    • 信号产生
      • 使用终端按键产生信号
      • 使用指令向进程发送信号
      • 调用系统调用向进程发送信号
      • 由软件条件产生信号
      • 硬件异常产生信号
    • 核心转储
      • 进程信号和核心转储的关系
      • 使用指令操作系统核心转储信息
      • 核心转储文件的使用
      • 核心转储和退出信号的关系
    • 阻塞信号
    • 信号在内核的表示(信号保存)
    • 信号集操作
      • 信号集数据类型
      • 信号集操作函数
      • sigprocmask函数
      • sigpending函数
    • 捕捉信号(信号处理)
      • 用户态和内核态
      • 信号处理的时机
      • 信号处理的过程
      • signal函数
      • sigaction函数
    • volatile关键字
    • SIGCHLD信号

进程信号的定义

进程之间事件异步通知的一种方式。它是一种软件中断,用于向进程发送通知和指令,以便对其进行控制或传递信息。进程信号由整数值来标识,每个值对应一个特定的信号。不同的信号对应不同的状况。

信号的特点

  • 信号产生前,进程就知道如何处理
  • 信号一旦产生,进程能够识别信号。
  • 进程接收到信号后,不一定会立即处理,进程在收到信号后会先记录下来。
  • 信号的产生对于进程是异步的。

信号的生命过程

信号的生命过程分为三个阶段:

  • 信号产生
  • 信号保存
  • 信号处理

image-20230913193125910

发送信号的原理

进程信号有许多个,操作系统为了管理进程接收的信号,需要使用一定的结构描述信号,操作系统采用了位图结构来记录不同的信号,该位图结构记录在进程的pcb中,当操作系统向进程发送信号时,就向进程pcb中描述信号的位图结构写入数据。

进程处理信号的方式分类

  1. 忽略此信号。
  2. 执行该信号的默认处理动作。
  3. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉 (Catch)一个信号。

使用指令查看Linux系统定义的信号

使用kill-l可以查看Linux系统定义的信号列表:

image-20230912131515776

使用man 7 signal可以查看Linux系统定义的信号详细说明:

image-20230912132501892

信号产生

使用终端按键产生信号

在Linux系统中输入crtl + c可以给进程发送2号信号,2号信号默认的处理动作是终止进程。给进程发送2号信号的示例如下:

键盘发送信号

使用指令向进程发送信号

Linux系统中提供了kill -信号数 进程pid指令用于向指定进程发送信号:

指令信号

调用系统调用向进程发送信号

kill接口

Linux系统中提供了kill系统调用用于向指定进程发送信号:

//kill所在的头文件和声明
#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);
  • pid参数 – 要发送信号的进程编号。
  • sig参数 – 要给进程发送的信号。
  • 成功返回0,失败返回-1,错误码被设置。

raise接口

Linux系统中提供了raise系统调用用于向进程自身发送信号:

//raise所在的头文件和声明
#include <signal.h>

int raise(int sig);
  • sig参数 – 向进程发送的信号。
  • 成功返回0,失败返回失败原因对应的非0值。

abort接口

Linux系统下C语言库中提供了abort库函数用于向进程自身发送信号 SIGABRT

//abort所在的头文件和声明
#include <stdlib.h>

void abort(void);
  • abort是C语言库中提供的接口。
  • abort向调用进程自身发送6号信号 SIGABRT

由软件条件产生信号

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

//alarm所在的头文件和声明
#include <unistd.h>

unsigned int alarm(unsigned int seconds);
  • 调用alarm函数操作系统会创建闹钟并建立对应数据结构组织起来。

硬件异常产生信号

硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除 以0的指令,CPU的运算单元会产生异常,内核将这个异常解释 为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。

核心转储

Linux操作系统可以在进程异常时,将核心代码部分和相关的内存数据全部导出到磁盘外设中,这一功能被称作核心转储。

进程信号和核心转储的关系

image-20230912170508835

不同的进程异常情况,进程会收到不同的进程信号,使用man 7 signal指令查看进程信号的信息,其中Action列中为Core的信号发送给进程后,进程就会进行核心转储。

使用指令操作系统核心转储信息

查看核心转储信息

使用ulimit -a指令可以查看到系统核心转储文件的信息:

image-20230912170936766

云服务中默认设定核心转储文件的大小上限为0,也就是不进行核心转储。

修改核心转储信息

使用ulimit -c指令可以修改核心转储文件的大小上限:

image-20230912171123251

核心转储文件的使用

核心转储文件的主要作用是当程序发生崩溃或异常终止时,通过分析核心转储文件,可以了解程序崩溃的原因、定位错误的位置以及查找潜在的缺陷。

运行一个进程用于,然后给它发送8号信号,让他产生核心转储文件:

image-20230912171514803

使用gdb调试可执行程序,然后使用core-file指令打开核心转储文件,就能从调试中看到进程异常原因:

image-20230912171807299

核心转储和退出信号的关系

如果某一进程产生异常生成了核心转储文件,Linux系统下进程使用系统接口等待回收该进程,获得的退出信息中倒数第八位会被置为1:

image-20230912173222507

也就是core dump标志被置为1。

阻塞信号

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

信号在内核的表示(信号保存)

信号在内核中的表示示意图如下:

image-20230912194206031

操作系统在内核数据结构task_struct中为信号维护了三张表:

  • block: 位图结构,比特位的位置,表示一种信号,比特位的内容,对应的信号是否被阻塞。
  • pending: 位图结构,比特位的位置,表示一种信号,比特位的内容,对应的信号是否接收到了。
  • handler: 函数指针数组,数组的下标,表示信号的编号,数组的特定下标的内容,表示信号的递达动作。(用户自定义递达动作就是通过修改该数组实现的)

信号集操作

信号集数据类型

  • 每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。 因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集

  • sigset_t类型可以表示每个信号 的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有 效”和“无效”的含义是该信号是否处于未决状态。

  • sigeset_t类型的底层实现也是一种位图结构。

信号集操作函数

sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,这个类型内部如何存储这些bit则依赖于系统实现,用户不需要关心具体实现,只要能够按需求操作信号集即可,因此Linux系统提供了如下函数来操作信号集:

//信号集操作函数所在的头文件及函数声明
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo); 
  • 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。

  • 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号。

  • 注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptysetsigfillset做初始化,使信号集处于确定的状态。

  • 初始化sigset_t变量之后就可以在调用sigaddsetsigdelset在该信号集中添加或删除某种有效信号。

  • 前四个函数都是成功返回0,出错返回-1。

  • sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1。

sigprocmask函数

调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集–block表)。

//sigprocmask所在的头文件及函数声明
#include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 
  • 如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。
  • 如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。
  • 如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。
  • 返回值:若成功则为0,若出错则为-1

假设当前的信号屏蔽字为mask,下表说明了how参数的可选值:

SIG_BLOCKset包含了我们希望添加到当前信号屏蔽字的信号,相当于mask=mask|set
SIG_UNBLOCKset包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask&~set
SIG_SETMASK设置当前信号屏蔽字为set所指向的值,相当于mask=set

sigpending函数

调用函数sigpending可以读取当前进程的未决信号集,通过set参数传出:

//sigpending所在的头文件及函数声明
#include <signal.h>

int sigpending(sigset_t *set);
  • 调用成功则返回0,出错则返回-1。

捕捉信号(信号处理)

用户态和内核态

Linux系统中进程地址空间分为两个部分,一个部分是用户空间,另一个部分是内核空间,两个部分都有自身对应的页表,一个是用户级页表,另一个是内核级页表,操作系统计算机开机时会将自身的代码和数据加载到内存中,这部分代码和数据通过同一张内核级页表映射到每个进程的地址空间中的内核部分,示意图如下:image-20230913193735144

由于操作系统的代码和数据通过同一张内核级页表映射到每个进程的地址空间中的内核部分,因此在进程运行时,只需要像跳转到动态库一样,跳转到操作系统的代码和数据,使得进程代码和操作系统代码的切换可以在一张进程地址空间中跳转完成,但操作系统为了不让用户通过进程地址空间中的内核部分的随意访问操作系统的代码和数据,操作系统设置了两种状态:**用户态和内核态,当处于用户态时,进程只能访问用户进程的代码和数据,当处于内核态时,进程只能访问操作系统的代码和数据。**为了记录当前处于用户态还是内核态,将标志信息记录在CPU的寄存器中。

小总结一下:

  • 所有进程地址空间的用户空间中的内容是不一样的,每一个进程都有自己的用户级页表,看到属于自己的代码和数据。
  • 所有进程地址空间的内核空间中的内容是一样的,每一个进程都会看到同一个内核级页表,看到同一个操作系统。
  • 操作系统实际是通过进程的地址空间完成运行。
  • 使用系统调用的本质就是跳转到进程地址空间的内核空间部分运行。
  • 为了保护操作系统中的代码和数据,操作系统设置了用户态和内核态。

信号处理的时机

当进程从内核态切换回用户态的时候(信号记录在内核中,只能在内核态访问),进程会在操作系统的指导下,进程信号的检测和处理。

用户态切换至内核态的情况如下:

  • 发生系统调用:当用户程序需要访问受保护的系统资源或请求操作系统提供的服务时,它会发起系统调用。操作系统会接收到系统调用请求,然后执行相应的系统代码来处理该请求,并返回结果给用户程序。
  • 异常或中断事件:当发生硬件故障、软件错误或外部中断等事件时,操作系统需要对其进行处理。操作系统会通过中断处理程序或异常处理程序来响应这些事件,并执行必要的系统代码来处理它们。
  • 定时器事件:操作系统通常会使用定时器来进行时间管理和调度。当定时器触发时,操作系统会响应该事件,执行系统代码以更新任务调度和执行状态。

补充说明: 计算机存在一个计时器硬件,当计时器记录进程运行到一定时间后,操作系统会执行对应的中断方法,检查时间片,如果时间片到了就会调用操作系统中的调度函数,完成进程的切换。

信号处理的过程

在进程运行执行对应代码时,遇到需要切换至内核态的情况,产生中断跳转至内核态,进入内核态后完成对应中断处理后,开始检测信号,进行,执行信号对应的处理动作,若是默认处理或者忽略处理后就会直接进入用户态跳转回用户态中断前代码,如果是自定义处理动作,会进入用户态跳转执行对应的自定义处理动作,然后调用sigreturn回到内核态,再调用sys_sigreturn进入用户天跳转回用户态中断前代码。执行自定义处理动作的过程示意图如下:

image-20230913213158930

  • 在检测信号后,会将要处理的信号在block表对应位置置为1,将pending表中对应位置置为0,然后再执行处理动作。

signal函数

调用函数signal可以将对应信号的处理动作改为自定义处理动作:

//signal所在的头文件及函数声明
#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);
  • signum参数 – 要修改的信号。
  • handler参数 – 自定义处理函数的函数指针。传入SIG_DFL为默认处理动作,SIG_IGN为忽略动作。
  • 返回值是一个函数指针,如果成功设置信号处理程序,则返回先前的信号处理程序的函数指针;如果出现错误,则返回 SIG_ERR
  • SIGKILLSIGSTOP 信号不能被修改为自定义处理动作。

编写如下代码进行测试:

#include <iostream>
#include <unistd.h>
#include <signal.h>

using namespace std;

void handler(int signo)
{
    cout << "get the signal: " << signo << endl;
}

int main()
{
    signal(SIGINT, handler);
    while(true)
    {
        cout << "i am running, pid: " << getpid() << endl;
        sleep(1);
    }
    return 0;
}

编译代码运行查看结果:

signal

sigaction函数

sigaction函数可以读取和修改与指定信号相关联的处理动作。

//sigaction所在的头文件及函数声明
#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
  • signo参数 – 指定信号的编号。
  • 若act指针非空,则根据act修改该信号的处理动作。
  • 若oact指针非空,则通过oact传出该信号原来的处理动作。
  • 调用成功则返回0,出错则返回- 1。

sigaction函数需要使用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_mask: 在进行信号处理时需要额外屏蔽的信号
  • sa_flags: 默认为0即可。

sigaction函数使用结构体作为参数修改信号处理动作,相比signal功能更为强大。

volatile关键字

在C语言中,volatile 是一个关键字,用于告诉编译器某个变量是易变的(volatile)并且不应该进行优化。

为了体会volatile关键字的作用,创建如下文件及文件内容:

makefile:

mysignal:mysignal.cc
	g++ -o $@ $^ -std=c++11 -O2//注意此处采用O2优化级别

.PHONY:clean
clean:
	rm -rf mysignal

mysignal.cc:

#include <iostream>
#include <signal.h>

using namespace std;

int quit = 0;

void handler(int signo)
{
    cout << "get the signal: " << signo << endl;
    quit = 1;
    cout << "quit: " << quit << endl;
}

int main()
{
    signal(2, handler);
    while(!quit);
    cout << "process quit" << endl;
    return 0;
}

编译代码并查看运行结果:

volatile1

从现象中可以看出,在键盘中输入ctrl+c传入二号信号后,执行了自定义的处理动作quit改为了1,但是循环依旧继续,程序并未终止。这是因为编译器的优化,在无优化的情况下,从汇编来看应该是在每次循环时将quit数据从内存加载到CPU寄存器中进行判断,但是由于main函数中没有quit修改的代码,编译器以为quit不会修改,直接将汇编优化成了只从内存中加载一次quit,然后只进行判断,信号处理后修改的是内存的数据,因此不会使得循环停止。

使用volatile关键字修改代码:

#include <iostream>
#include <signal.h>

using namespace std;

volatile int quit = 0;

void handler(int signo)
{
    cout << "get the signal: " << signo << endl;
    quit = 1;
    cout << "quit: " << quit << endl;
}

int main()
{
    signal(2, handler);
    while(!quit);
    cout << "process quit" << endl;
    return 0;
}

编译代码并查看运行结果:

volatile2

volatile关键字让quit不再优化,每次判断都从内存加载quit到CPU中,因此现象如上图。

SIGCHLD信号

子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号 的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可,具体自定义代码如下:

void handler(int signo)
{
    while(true)
    {
        pid_t id = waitpid(-1, NULL, WNOHANG);
        if (id > 0) printf("waitpid: %d, my pid: %d\n", id, getpid());
        else break;
    }
}

说明:

  • waitpid传入-1让其接收任意一个僵尸状态的子进程。
  • 循环调用waitpid是为了解决如果一个进程回收时,其他进程进入僵尸状态。
  • 采用WNOHANG非阻塞是为了解决暂时没有子进程需要回收,进入阻塞状态。

除了以上自定义处理外,Linux系统还提供了另一种方法解决僵尸状态的子进程:父进程调用sigactionSIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。(此方法对于Linux可用,但不保证在其它UNIX系统上都可用。)

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

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

相关文章

超轻巧的电竞鼠标,手感不错反应精准,雷柏VT9Pro体验

作为一家国内很受欢迎的外设厂商&#xff0c;雷柏近年推出了不少很有创新性的产品&#xff0c;像是之前主打轻量化的鼠标VT9&#xff0c;凭借78g的超轻重量&#xff0c;以及模块化的设计&#xff0c;得到了了广大游戏玩家的认可。最近雷柏在此基础上又推出了一款VT9 Pro&#x…

Linux服务使用宝塔面板搭建网站,并发布公网访问

文章目录 前言1. 环境安装2. 安装cpolar内网穿透3. 内网穿透4. 固定http地址5. 配置二级子域名6. 创建一个测试页面 前言 宝塔面板作为简单好用的服务器运维管理面板&#xff0c;它支持Linux/Windows系统&#xff0c;我们可用它来一键配置LAMP/LNMP环境、网站、数据库、FTP等&…

ChatGPT帮助一名儿童确诊病因,之前17位医生无法确诊

9月13日&#xff0c;Today消息&#xff0c;一位名叫Alex的4岁儿童得了一种浑身疼痛的怪病&#xff0c;每天需要服用Motrin&#xff08;美林&#xff09;才能止痛。3年的时间&#xff0c;看了17名医生无法确诊病因。&#xff08;新闻地址&#xff1a;https://www.today.com/heal…

短视频批量剪辑--矩阵源码---无人直播如何搭建技术开发

一、核心技术&#xff1a; 1.AI自动直播 智能系统通过丰富可定制的文案库&#xff0c; 拥有有料有趣的灵魂。不仅能自动语音讲解内容&#xff0c; 还可以在直播中和用户灵活互动。直播中可将团购商品同话术自动上下架。 2.AI剪辑 可一键智能批量成片&#xff0c;也可跟着模…

Imu水平放置时的Z轴分量为什么是正值?

前言 常见的一些疑问: 水平放置时&#xff0c;imu的z轴的值是正值&#xff0c;难道imu的z轴是朝下&#xff1f;水平往前倾&#xff0c;根据各轴的读数值&#xff0c;觉得imu的坐标系难道是左手系&#xff1f; 基于以上两个疑问&#xff0c;下面我将按我的理解解释一下。 解…

开箱即⽤!HashData 云数仓上线华为蓝鲸应⽤商城

近⽇&#xff0c;经过华为对企业技术、产品和服务能⼒的综合评估&#xff0c;酷克数据企业级云原⽣数据仓库HashData通过与华为OceanStor Pacific 分布式存储的适配与优化&#xff0c;形成⼀体化解决⽅案&#xff0c;成功上线华为蓝鲸应⽤商城。 图 1&#xff1a;华为蓝鲸商城…

【vue组件】使用element-ui table 实现嵌套表格 点击展开时获取数据

应用场景是这样 主表格的数据是所有的学校 然后点击展开的时候&#xff0c;获取学校下相应班级的数据 并且班级要能选择后生成图表&#xff0c;但所有的班级最多选择5个 首先是嵌套表格 <div><el-table:data"tableDisplayData"id"chartTableExpand&q…

bwapp下载安装

下载地址&#xff1a; https://sourceforge.net/projects/bwapp/ 安装&#xff1a; 解压缩 将压缩包解压到www目录 进入bwapp/admin/,打开setting.php文件&#xff0c;修改数据库用户名密码 安装数据库 打开浏览器&#xff0c;输入 http://localhost/bwapp/bwapp/install.p…

微服务简介

微服务简介 微服务架构是一种软件架构模式&#xff0c;它将一个大型应用程序拆分为一组小型、独立的服务&#xff0c;每个服务都有自己的业务逻辑和数据存储。这些服务可以独立开发、部署和扩展&#xff0c;通常使用HTTP或其他轻量级通信协议进行通信。 以下是微服务架构的一…

【完美世界】柳神为石昊,付出生命代价,石昊称“柳妈”不过分吧

Hello,小伙伴们&#xff0c;我是小郑继续为大家深度解析完美世界。 在完美世界中&#xff0c;荒天帝石昊一生要追赶的人便是柳神。而柳神也在石昊崛起的过程中庇护了他一生&#xff0c;直至终极大决战陨落。人们都说石昊应该叫柳神为柳妈&#xff0c;事实也的确如此。 纵观柳神…

iwebsec靶场 文件包含漏洞通关笔记7-php://input伪协议

目录 前言 1.php://input伪协议原理 2.php://input伪协议使用条件 3.file_get_contents()函数 第07关 php://input伪协议 1.打开靶场 2.源码分析 3.伪协议渗透 &#xff08;1&#xff09;构造post信息 &#xff08;2&#xff09;渗透 前言 1.php://input伪协议原理 …

fcntl函数

#include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd, ... ); 参数&#xff1a; fd&#xff1a;表示需要操作的文件描述符 cmd:表示对文件描述符进行如何操作 1.F_DUPFD&#xff1a;复制文件描述符&#xff0c;复制的是第一个参数fd,得到一个新的文…

小谈设计模式(2)—简单工厂模式

小谈设计模式&#xff08;2&#xff09;—简单工厂模式 专栏介绍专栏地址专栏介绍 简单工厂模式简单工厂模式组成抽象产品&#xff08;Abstract Product&#xff09;具体产品&#xff08;Concrete Product&#xff09;简单工厂&#xff08;Simple Factory&#xff09;三者关系 …

历史重演,2024浙大MPA提面A资格预期不到25%,成败最后一个月

随着浙大mpa项目2024年提前批面试申请的截止&#xff0c;最新的申请人数也基本确定下来&#xff0c;从目前的信息来看&#xff0c;今年的提面申请数量与去年基本持平&#xff0c;很可能意味着今年的最终报考以及后续复试相关的一系列问题都可以去年为参考&#xff0c;这样一来&…

vue-drag-resize 可拖动缩放元素组件

1、安装 npm i -s vue-drag-resize 2、使用 <template><div class"screen-content"><vue-drag-resize w"200" :h"200" resizing"resize" dragging"resize" contentClass"resize-box"><p&…

JavaScript逻辑题:一个篮球的高度为100米 每次落地弹起高度为前一次高度的0.6 问多少次之后高度小于1米?

// 设置篮球的高度let height 100;// 设置次数默认值为0let i 0;// 进行循环while(true){//计算每次弹起的高度height height*0.6;// 并记录次数i;// 如果高度小于1米时&#xff0c;结束循环&#xff0c;并输出次数iif(height < 1){console.log(篮球弹起i次之后高度小于1…

测试的水太深,年轻人把握不住.....

​前言 去阿里面试测试工程师&#xff0c;这里面水太深&#xff0c;什么未来规划&#xff0c;职业发展的东西都是虚拟的&#xff0c;作者还太年轻&#xff0c;没有那个经历&#xff0c;把握不住。项目只有几个&#xff0c;开心快乐就行&#xff0c;不PK&#xff0c;文明PK。 …

简单四边形不等式优化dp(上)

*下文中“优于”一般指的是“不劣于”&#xff0c;请自行分辨。 四边形不等式 四边形不等式定义为&#xff1a; 位于整数集合上的二元函数 f ( x , y ) f(x,y) f(x,y)&#xff0c;对于 a ≤ b ≤ c ≤ d a\leq b\leq c\leq d a≤b≤c≤d&#xff0c;若满足&#xff1a; f ( a…

MyBatis中当实体类中的属性名和表中的字段名不一样,怎么办

方法1&#xff1a; 在mybatis核心配置文件中指定&#xff0c;springboot加载mybatis核心配置文件 springboot项目的一个特点就是0配置&#xff0c;本来就省掉了mybatis的核心配置文件&#xff0c;现在又加回去算什么事&#xff0c;总之这种方式可行但没人这样用 具体操作&…

c语言进阶部分详解(指针初阶)

大家好&#xff01;&#xff0c;前段时间一直在准备数学建模竞赛&#xff0c;现在也是忙完了。抓紧继续给大家带来c语言的内容。今天给大家带来指针初阶部分的讲解 当我们谈论C语言中的指针时&#xff0c;实际上是在讨论一种非常重要的概念&#xff0c;因为指针是C语言的核心之…