Linux-->进程概念

文章目录

  • 进程
    • 进程概念
    • 操作系统管理进程
      • 描述进程-PCB
      • 组织进程
      • task_strcut
    • 查看Linux下进程信息
      • 通过系统文件查看
      • 通过命令查看
    • 通过系统调用获取进程标示符
    • 通过系统调用frok创建进程
    • fork的使用
    • 进程状态
      • 运行
      • 阻塞
      • 挂起
        • Linux下具体的进程状态
      • 前台进程和后台进程
        • kill
        • 僵尸进程
        • 孤儿进程
    • 进程优先级
      • PRI和NI
      • 修改进程优先级
    • 进程的特性
    • 进程地址空间
      • 虚拟地址
      • 内核对地址空间的描述

进程

进程概念

当执行一个可执行程序时,会先把可执行程序加载到内存中。将程序加载到内存中并执行的时候,操作系统为该程序创建一个进程,进程中包含程序的代码、数据、PCB(用于描述和管理进程状态的数据结构),进程地址空间(描述进程在内存中布局的数据结构),页表(管理进程内存的重要数据结构)等。
所以进程 = 内核数据结构+程序

操作系统管理进程

操作系统创建进程时,会创建一个PCB(进程控制块)的数据结构,操作系统从此管理进程,不会去管理进程的程序,将其转换对特定数据结构的管理,先将进程状态描述起来,在将其用特定的数据结构组起来(Linux下用的是双向链表)。

  • 描述起来:用struct结构体
  • 组织起来:用特定的数据结构

描述进程-PCB

  • 操作系统创建进程之后,会给每一个进程建立一个PCB对象
  • 操作系统管理进程时会将进程的信息放在一个叫做进行控制块的数据结构中,即进行属性的集合,一般称之为PCB.
  • Linux下的PCB叫做task_struct

组织进程

  • 操作系统将每一个进程都进行描述,形成了一个个的进程控制块(PCB),并将这些PCB以双向链表数据结构组织起来
    image.png
  • 这样一来。操作系统对所有进程的控制和操作,都只和进程的PCB有关,与进程的可执行程序无关。
  • 同时将进程的管理转换为对特定数据结构的增删查改。

task_strcut

  • sk_strcut是PCB的一种
  • 在Linux中描述进程的结构体叫做task_struct。
    task_struct当中主要包含以下信息:
  • 标示符: 描述本进程的唯一标示符,用来区别其他进程。
  • 状态: 任务状态,退出代码,退出信号等。
  • 优先级: 相对于其他进程的优先级。
  • 程序计数器(pc): 程序中即将被执行的下一条指令的地址。
  • 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
  • 上下文数据: 进程执行时处理器的寄存器中的数据。
  • I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
  • 记账信息: 可能包括处理器时间总和,使用的时钟总和,时间限制,记账号等。
  • 其他信息

查看Linux下进程信息

通过系统文件查看

进程的信息可以通过 /proc 统文件夹查看

image.png
在Linux系统中,/proc 文件夹提供了对内核和正在运行的进程的信息的访问。

通过命令查看

ps
列出当前用户进程的简要信息
非root用户
image.png
root用户
image.png

ps aux:显示所有进程信息这,包括用户、PID(进程ID)、CPU利用率、内存利用率等。

image.png
top命令:以交互式的方式显示系统中运行的进程信息。它实时更新

image.png
htop命令 :它以直观和友好的方式显示系统的实时性能信息。相比于经典的 top 命令,htop 提供了更多的功能和更直观的用户界面。top的完美替代品

image.png

通过系统调用获取进程标示符

task_strut是操作系统内部数据,想要访问,必须通过系统调用。

  • 进程id:pid,通过getpid()系统调用获取。
  • 父进程id:ppid,通过getppid()系统调用获取。

在Linux中普通进程都有父进程,在命令行中启动的进程都是bash(命令行解释器)的子进程。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
    printf("PID = %d\n",getpid());
    printf("PPID = %d\n",getppid());
    return 0;
}

这段代码就可以查看当前进程的pid和ppid
image.png
当前进程的ppid就是bash

image.png

通过系统调用frok创建进程

fork:在Linux系统中,fork 是一个用于创建新进程的系统调用。fork 调用会创建一个与父进程几乎完全相同的子进程,这两个进程在执行时拥有相同的代码、数据。

可以使用man 2 fork简单认识一下fork系统调用

image.png

  • fork 调用会返回两次:一次在父进程中,一次在子进程中。
  • 在父进程中,fork 返回子进程的进程ID(PID);在子进程中,fork 返回0。
  • 如果发生错误,fork 返回-1。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{  
    int ret = fork();
    printf("Heloo proc :pid = %d,ppid = %d,ret = %d\n",getpid(),getppid(),ret);
    sleep(1);
    return 0;
}

这个程序执行结果:

image.png
可以看出,这个程序会执行两次打印。

  • 第一次打印的是当前进程的pid和ppid
  • 第二次打印的是fork()创建的子进程的pid和ppid
  • fork()创建的子进程的ppid是当前进程的pid,可以得出,当前进程和fork创建的子进程是父子关系。
  • 打印的信息还有fork的返回值ret,可以看出,当前进程的fork返回值是子进程的pid,而子进程的fork返回值是0

子进程的代码和数据从哪里来?

  • 每一个进程都有自己的可执行程序和PCB,fork创建的子进程也会有自己的可执行程序和PCB。(可执行程序中包含代码和数据)
  • 上面的例子可以看出,子进程和父进程都可以使用printf函数进行打印,但是同一个变量,子进程和父进程的结果却不一样。
  • 可以说明,子进程和父进程代码共享,数据各自开辟空间,私有一份。采用写时拷贝

fork返回值:

  • 实际上fork函数会有两个返回值 (如果成功,则在父进程中返回子进程的PID,并在子进程中返回0。如果失败,父进程返回-1,不创建子进程,)
  • fork之后通常要采用if分流

注意:上面代码中一个变量ret会保存两个不同的值,这和进程地址空间有关。

fork的使用

  • fork之后一般要通过if分流,让父子进程做不同的事情
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
    pid_t id  = fork();
    if(id < 0 ) return -1;
    else if(id == 0)
    {
        //子进程
        while(1)
        {
            printf("i am child ,pid = %d,ppid = %d\n",getpid(),getppid());
            sleep(1);
        }
    }
    else
    {
        //父进程
        while(1)
        {
             printf("i am parent ,pid = %d,ppid = %d\n",getpid(),getppid());
             sleep(1);
        }
    }
    return 0;
}

让子进程循环打印自己的信息,让父进程打印自己的进程,一个程序两个死循环一直执行
运行结果
PixPin_2023-12-17_12-25-59.gif

进程状态

操作系统创建进程时把进程的状态信息保存在进程PCB中,进程的状态主要有运行、阻塞、挂起状态

运行

一般每个CPU核心拥有一个运行队列,当进程PCB放到CPU的运行队列中,准备好随时被CPU调度时,就称之为运行状态

阻塞

int main()
{
   int a = 0;
   scanf("%d",&a);
   printf("%d\n",a);
   return 0;
}
  • 上面这个代码执行后,只有等待用户输入后才可以继续运行。
  • 这种情况,当进程在等待某种软硬件资源时,资源没有就绪时,操作系统会把进程的PCB设置为阻塞状态,将进程的PCB链入到等待资源提供的运行队列中。
  • 进程状态的变迁,本质就是进程的PCB被操作系统变迁到不同的运行队列中。

挂起

  • 挂起状态并不常见,挂起的前提是计算机资源比较吃紧(比如内存不足)。
  • 内存吃紧时,磁盘当中有swap分区,操作系统会把内存中的数据拷贝到磁盘的swap分区(这个过程称之为换出操作),等内存资源足够时,在将swap分区中的数据拷贝到别内存中(这个过程称之为换入操作)。

image.png

  • 当进程的代码和数据不在内存当中,就称之为挂起
Linux下具体的进程状态

Linux内核源码对进程状态的定义

/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char *task_state_array[] = {
    "R (running)",       /*  0*/
    "S (sleeping)",      /*  1*/
    "D (disk sleep)",    /*  2*/
    "T (stopped)",       /*  4*/
    "T (tracing stop)",  /*  8*/
    "Z (zombie)",        /* 16*/
    "X (dead)"           /* 32*/
};

所谓的状态本质就是一个变量,不同的值表示不同的状态。

  • 运行状态(R,Running):进程正在运行或准备运行
  • 睡眠状态(S ,sleeping) :因为等待某种资源而暂时睡眠,也叫可中断睡眠(可以被中断)
  • 磁盘休眠状态(D,disksleep):因为等待某种资源而暂时休眠,也叫不可中断睡眠(不可以被中断)
  • 停止状态(T,stopped):进程收到信号被停止运行
  • 跟踪状态(t,tracing stop):进程受到了跟踪,一般是被调试器跟踪,调式时可以看见
  • 僵尸状态(Z,zombie):进程已经终止,但其父进程尚未调用 wait() 来获取其终止状态。
  • 死亡状态(X,dead):进程已经死亡,父进程没调用wiat()获取终止状态
#include<stdio.h>
int main()
{
    while(1);
    return 0;
}

运行上面的程序,使用ps命令查看进程信息

image.png
STAT列就是进程状态信息列,上面进程的状态是R+,也就是运行状态。

前台进程和后台进程

  • 使用ps命令查看进程信息,进程状态后面带+的是前台进程,
  • 没有+的是后台进程。
  • 在运行程序时最后加一个&就会变为后台进程
  • 前台进程可以用ctrl+c杀掉,并且运行时不能运行其他进程。
  • 后台进程不能用ctrl+C杀掉,只能使用kill命令杀掉,进程运行时还可以运行其他进程。

后台进程

image.png
运期间还可以运行其他程序,并且不能用ctrl+c 命令杀死
PixPin_2023-12-26_19-42-42.gif

kill

kill 命令用于向进程发送信号。默认情况下,kill 发送的是 SIGTERM(终止)信号,通知进程正常退出。

kill-l:可以查看kill命令的所有信号

image.png

  • kill <PID>默认使用的是15号信号IGTERM
  • kill -9 <PID>强制终止进程
  • pkill <process_name>根据程序名终止进程
僵尸进程
  • 僵尸进程是一种特殊的进程状态,当一个进程结束后,父进程没有对其进行善后处理(通过调用waitwaitpid等系统调用),就会造成僵尸进程。
  • 在上面利用fork创建进程时,父进程没有对子进程调用wait或者waitpid方法进行善后处理,子进程就会变为僵尸进程。
  • 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
孤儿进程
  • 父进程创建一个子进程,父进程先退出,子进程就会变为孤儿进程
  • 子进程变为孤儿进程,会被1号进程领养,1号进程就是操作系统,孤儿进程被操作吸引回收了。
  • 被领养的孤儿进程会变为后台进程。
    孤儿进程例子
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
  //孤儿进程demo
  pid_t id = fork();
  if(id < 0 )
  {
    perror("fork");
    return -1;
  }
  else if(id == 0)
  {
    //子进程
    printf("i am child process pid = %d, ppid = %d\n",getpid(),getppid());
    sleep(3);//子进程等待三秒后退出
    printf("child process exit\n");
  }
  else{
    //父进程
    printf("partent process ,pid = %d\n",getpid());
    exit(0);//父进程直接退。
  }
  return 0;

上面程序运行结果如下

PixPin_2023-12-26_19-42-42.gif
父进程先退出,子进程被1号进程领养。三秒后,子进程执行结束.

进程优先级

  • CPU分配资源的先后顺序,就指的是进程的优先级
  • 进程的优先级是通过进程调度器来管理的,它使用动态优先级调度策略来分配CPU时间。每个进程都有一个调度优先级,该优先级决定了进程在系统中被调度执行的顺序。
  • 需要区分优先级和权限,优先级决定了先后的问题,而权限决定了能不能的问题
  • 进程要有优先级的原因就是因为CPU资源有限

ps -l命令查看系统进程

image.png

  • UID:代表执行者的身份
  • PID:进程标示符。
  • PPID:父进程标示符
  • PRI:代表进程优先级,数值越小,优先级越高,越早被执行
  • NI:进程的nice值

PRI和NI

PRI:

  • 进程优先级就是PCB中的一个整形变量,默认的优先级是80
  • 优先级可以被修改,允许修改的范围是[60,99],包括60和99,一共40个优先级
  • 修改进程优先级是有范围的,如果不进行限制,不能确保有人乱修改,将自己的进程调整到最高,而常规进程很难享受到CPU资源,从而造成进程饥饿问题

NI:

  • 进程的nice值。其表示进程可被执行的优先级的修正数值
  • 加入nice值后,进程的PRI = PRT(old) + nice
  • 调整进程的优先级就是修改进程的nice值
  • nice值也是有范围的[-20,19],一共40个优先级

修改进程优先级

top 命令中可以交互式地修改进程的优先级。top 提供了一个交互式的界面,你可以在其中选择要调整优先级的进程,并对其进行一些操作,包括修改 Nice 值。

当前进程的PRI是默认值80

image.png
使用top命令修改优先级
1:输入top命令

image.png
2:按入r在输入要修改进程的pid

image.png
3:输入nice值([-20,19]之间)

image.png

修改之后的优先级

image.png

进程的特性

  • 竞争性:系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高 效完成任务,更合理竞争相关资源,便具有了优先级
  • 独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰
  • 并行:多个进程在多个CPU下分别同时进行,称之为并行
  • 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为 并发

进程地址空间

  • 进程地址空间是描述进程在内存中布局的数据结构,因为操作系统要对进程地址空间进行管理,(先用结构体描述起来,再用特定的数据结构组织起来)。
  • 进程地址空间是指操作系统为每个运行的进程分配的虚拟内存空间。每个进程都有其独立的地址空间,32位下取值范围是[0,4GB]。这是操作系统提供给进程用于存储程序代码、数据、堆、栈以及其他相关信息的虚拟内存区域。

image.png

  • 正文代码:也称为代码段,存放可执行代码/只读常量 不允许被修改。
  • 初始化数据 && 未初始化数据:叫做数据段,存储全局变量和静态变量,包括初始化和未初始化的数据。上图将数据段分为两个区域。
  • 堆区:动态分配的内存空间,用于存储程序运行时动态分配的数据。堆的大小可以在运行时动态增长或缩小。
  • 共享区:进程间的共享数据或通信
  • 栈区:又叫做堆栈 存放非静态局部变量/函数参数/返回值等等
  • 命令行参数环境变量:存放命令行参数和环境变量相关(命令行参数和环境变量相关可以另一篇博客Linux命令函参数&环境变量 - 掘金 (juejin.cn))

在Linux系统下进行验证:

#include <stdio.h>
#include <stdlib.h>

int g_unval;
int g_val = 0;

int main(int argc, char* argv[],char* envp[])
{
  const char* str= "Hello World";
  printf("code_address:%p\n",str);//代码段

  printf("g_val_address:%p\n",&g_val);//初始化数据段(全局变量)

  printf("g_unval_address:%p\n",&g_unval);//未初始化数据段

  int*p = (int*) malloc(sizeof(int));
  printf("heap_address:%p\n",p);//堆区
  
![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/606f90be65a44626a275913aa6fb39ff~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1544&h=788&s=150650&e=png&b=000006)
  printf("stack_address:%p\n",&str);//栈区

  for(int i = 0 ;argv[i]; ++i)
  {
    printf("argv_address:%p\n",argv[i]);//命令行参数
  }

  for (int  i = 0; envp[i]; i++)
  {
    printf("envp_address:%p\n",envp[i]);//环境变量
  }

  return 0;
}

image.png

虚拟地址

int main()
{
      pid_t id = fork();
      if(id < 0)
      {
        perror("fork");
      }
      else if(id == 0)
      {
        //child 
        g_val  = 100;//子进程中修改全局变量g_val
        printf("i am child process, g_val=%d,&g_val= %p\n",g_val,&g_val);
      }
      else
      {
        sleep(2);
        printf("i am parent process,g_val=%d,&g_val=%p\n",g_val,&g_val);
      }
      return 0;
}

让子进程先执行,修改全局变量的值,再让父进程执行,
运行结果:

image.png
发现和预想的不一样,本应该子进程修改后,全局变量的值已经变为了100,而父进程打印时还是0,并没有被修改,而且同一块地址空间,存了两个不同的值。
这可以验证,在语言层面上,所有的地址都是虚拟地址,如果是真实的物理内存,就不会出现一块地址空间存放两个不同的值。物理地址用户是看不到的,由操作系统进行统一管理。
在上面的例子中,父子进行中的虚拟地址是相同的,但是变量的值是不同的。
这里由操作系统将虚拟地址映射到物理内存中,从而实现,语言层面上的一块地址存放不同的值。

image.png

内核对地址空间的描述

进程的地址空间信息被封装在 mm_struct 结构中。将其进行区域划分,

image.png
进程地址空间就类似于一把尺子,尺子的刻度由0x00000000到0xffffffff,尺子按照刻度被划分为各个区域,例如代码区、堆区、栈区等。而在结构体mm_struct当中,便记录了各个边界刻度,例如代码区的开始刻度与结束刻度。

在mm_struct结构体中,每一个字节都代表着一个虚拟地址,这些虚拟地址通过和页表与物理内存建立映射关系。

每个进程被创建时,对应的PCB,mm_struct,页表,都会随之被创建。
而操作系统可以通过进程的PCB找到其mm_struct。(PCB当中有一个结构体指针存储的是mm_struct的地址)

image.png

在用fork创建子进程时,操作系统以父进程为模板为子进程创建PCB,mm_strcut,页表等。
子进程的PCB,mm_stuct,页表和最初的父进程一样,(子进程和父进程的数据和代码是共享的),即父子进程的代码和数据通过页表映射到物理内存的同一块空间。只有当父进程或子进程需要修改数据时,才将父进程的数据在内存当中拷贝一份,然后再进行修改。这样可以节省内存和提高效率。

image.png

在上面的例子中,子进程修改全局变量g_val为100,操作系统会把父进程中的g_val在物理内存中拷贝一份,在进行修改。通过页表与子进程建立映射关系,从而实现了进程之间的独立性。

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

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

相关文章

两个方法实现echarts散点图的高光圆点

一、效果图&#xff1a; 二、代码 方法一&#xff1a;通过series的itemStyle进行设置&#xff0c;type为scatter 在 ECharts 中&#xff0c;要在二维散点图上实现看似 3D 的高光圆点效果&#xff0c;可以通过自定义散点图的 itemStyle 属性来实现。虽然无法直接创建真正的 3D…

【备战蓝桥杯】Python 内置模块datetime的介绍和应用

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

架构篇02-架构设计的历史背景

文章目录 机器语言&#xff08;1940 年之前&#xff09;汇编语言&#xff08;20 世纪 40 年代&#xff09;高级语言&#xff08;20 世纪 50 年代&#xff09;第一次软件危机与结构化程序设计&#xff08;20 世纪 60 年代~20 世纪 70 年代&#xff09;第二次软件危机与面向对象&…

设计模式——一文即可

对常用设计模式的总结&#xff0c;也是对设计模式专栏的总结 简单工厂模式 简单工厂模式&#xff08;Simple Factory Pattern&#xff09;是一种创建型设计模式&#xff0c;它提供了一种创建对象的最佳方式&#xff0c;通过将对象的创建逻辑封装在一个工厂类中&#xff0c;客…

设备树下Led驱动实验-向设备树文件添加Led设备节点

一. 简介 前面简单学习了设备树文件的内容&#xff0c;语法&#xff0c;以及如何向设备树文件中添加设备节点信息。学习了驱动开发时&#xff0c;会使用到的设备树常用OF操作函数。本文我们就开始第一个基于设备树的 Linux 驱动实验-LED驱动实现。 本文具体学习在设备树文件添…

MES管理系统解决方案在汽配企业质量控制中的作用

在当今高度自动化的制造业环境中&#xff0c;质量控制已成为确保产品高品质的关键。特别是在汽配企业&#xff0c;产品通常由多个部件组装而成&#xff0c;且这些部件可能来自不同的供应商。这种复杂的生产模式带来了一个挑战&#xff1a;如何确保每一次生产操作都是正确的&…

使用WAF防御网络上的隐蔽威胁之命令注入攻击

命令注入攻击是网络安全领域的一种严重威胁&#xff0c;它允许攻击者在易受攻击的应用程序上执行恶意命令。 这种攻击通常发生在应用程序将用户输入错误地处理为操作系统命令的情况下。 什么是命令注入攻击 定义&#xff1a;命令注入攻击发生在攻击者能够在易受攻击的应用程…

Pytorch基础:数据读取与预处理——图像读取与存储

Pytorch基础&#xff1a;数据读取与预处理——图像读取与存储 1.读取图片2. 使用 matplotlib 库显示和保存图像 1.读取图片 图像库 opencv-python、imageio、PIL 等都具有图像读取的功能。 (base) PS C:\Users\阳> conda activate yang (yang) PS C:\Users\阳> python …

构建未来教育:在线培训系统开发的技术探讨

随着远程学习的崛起和数字化教育的普及&#xff0c;在线培训系统的开发成为了现代教育的核心。本文将深入讨论在线培训系统的关键技术要点&#xff0c;涵盖前后端开发、数据库管理、以及安全性和身份验证等关键方面。 前端开发&#xff1a;提供交互性与用户友好体验 在构建在…

3d模型为什么打光只显示黑色---模大狮模型网

3D建模是现代制作动画、电影、游戏等数字媒体内容的重要工具。在建模过程中&#xff0c;打光是一个重要的环节&#xff0c;它可以让3D模型更加真实、有趣和生动。然而&#xff0c;如果打光不当&#xff0c;3D模型可能会呈现出黑色的效果&#xff0c;这可能会让人感到困惑和沮丧…

MySQL/Oracle 的 字符串拼接

目录 MySQL、Oracle 的 字符串拼接1、MySQL 的字符串拼接1.1 CONCAT(str1,str2,...) : 可以拼接多个字符串1.2 CONCAT_WS(separator,str1,str2,...) : 指定分隔符拼接多个字符串1.3 GROUP_CONCAT(expr) : 聚合函数&#xff0c;用于将多行的值连接成一个字符串。 2、Oracle 的字…

计算机网络(超详解!) 第二节 数据链路层(上)

1.数据链路层使用的信道 数据链路层使用的信道主要有以下两种类型&#xff1a; 1.点对点信道&#xff1a;这种信道使用一对一的点对点通信方式。 2.广播信道&#xff1a;这种信道使用一对多的广播通信方式&#xff0c;因此过程比较复杂。广播信道上连接的主机很多&#xff0…

服务器使用中容易遇见的问题和处理方法

服务器支撑着整个企业的信息数据&#xff0c;对公司的信息储存、业务开展、正常运作等等环节都具有着至关重要的意义。然而&#xff0c;服务器在日常运行过程中&#xff0c;由于其复杂的硬件结构、繁琐的运行原理&#xff0c;经常会出现一些大大小小的问题困扰着各位。下面精心…

java基础:使用冒泡排序求数组的最大值

什么是冒泡排序 冒泡排序是一种简单的排序算法&#xff0c;其基本思想是多次遍历待排序的元素&#xff0c;比较相邻的两个元素&#xff0c;如果顺序不对则交换它们的位置&#xff0c;直到整个序列按照从小到大&#xff08;或从大到小&#xff09;的顺序排列。 具体的步骤如下&…

gsap timeline示例-实现滚动切换手机颜色

前言 最近使用gsap有点上瘾。看过一个手机官网滚动切换手机颜色的效果&#xff0c;初次见还是很炫。所以呢&#xff0c;就去研究了下&#xff0c;发现也不过如此。我们现在使用gsap来实现它。 首先来看最终效果&#xff1a; gsap timeline示例-实现滚动切换手机颜色 实现原理…

Linux用户提权

新建用户 用root账户修改文件&#xff0c;添加信任用户 使用sudo提权&#xff0c;可以使用 **root删除新建账户**

微服务接口工具Swagger2

##1、什么是Swagger? # 官网 https://swagger.io/核心功能 生成接口说明文档生成接口测试工具 2、SpringBoot集成Swagger2 1&#xff09;、添加依赖 <!-- swagger2 --><!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 --><depen…

【昕宝爸爸小模块】深入浅出之JDK21 中的虚拟线程到底是怎么回事(一)

➡️博客首页 https://blog.csdn.net/Java_Yangxiaoyuan 欢迎优秀的你&#x1f44d;点赞、&#x1f5c2;️收藏、加❤️关注哦。 本文章CSDN首发&#xff0c;欢迎转载&#xff0c;要注明出处哦&#xff01; 先感谢优秀的你能认真的看完本文&…

开发设计和迭代管理效率提升:PDManer元数建模

一、引言 在复杂多变的软件开发全生命周期中&#xff0c;数据库设计与建模扮演着举足轻重的角色。这一环节不仅关乎数据存储效率和应用性能优化&#xff0c;而且对于系统架构稳健性及业务逻辑清晰化具有深远影响。因此&#xff0c;选择一款功能强大且高效的数据库建模工具至关…
最新文章