linux 系统 最详细 启动流程

news2024/11/18 7:26:42

文章目录

  • 详细分析 系统启动过程
    • 主要流程
    • 阶段说明
      • BIOS
      • MBR(Stage 1 bootloader)
      • GROUB(Stage 2 bootloader)
      • kernel
        • vmlinuz
        • initrd.img
      • Init

详细分析 系统启动过程

主要流程

PC 启动主要流程,分为四个阶段:

BIOS -> MBR -> GRUB -> KERNEL -> INIT

  1. 主机加电后,系统首先加载BIOS,这个BIOS是烧录在主板上的ROM芯片上的。

  2. BIOS启动后,执行了一些例如开机自检,硬件初始化等工作,然后读取硬盘MBR分区的第 一个扇区(前512字节),其中前446字节储存了一个小程序叫做boot loader,中间的64 字节是磁盘分区表,最后两个字节是固定的0x55AA的文件类型识别标记。

​ (1) UEFI对比BIOS本身就是一个微型的操作系统,直接读取FAT格式的文件分区(EFI 程序),相比BIOS读取MBR文件分区快了不少。

​ (2) 常见的boot loader有GRUB、U-boot、UEFI、Etherboot、ARMboot等。

  1. bootloader程序主要用的两个文件,/boot/vmlinuz内核文件,/boot/initramfs虚拟文件系 统。

  2. 这两个文件加载到内存运行后,系统会加载真正的文件系统,然后启动1号进程systemd(init)。 这样真正使用的系统就启动了。

换句话说,也可以换成下图,方便理解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VDLxDRHp-1688020450504)(C:\Users\cjj\AppData\Roaming\Typora\typora-user-images\image-20230629112437517.png)]

需要说明的是,这里有 bootloader 学过嵌入式的同学都知道,ARM 板子使用的 bootloader 是 UBOOT 去启动的,uboot 一般使用 busybox 或者buildroot,今天主要讲下 pc 的启动,就不详细说嵌入式的 UBOOT;

其实常见的 boot loader 有GRUB、U-boot、UEFI、Etherboot、ARMboot等。

PC 相对于 嵌入式 arm 会复杂一些,所以在 x86 PC 是 GRUB、UEFI,arm PC 是 UEFI

阶段说明

BIOS

主机加电后,系统首先加载BIOS,这个BIOS是烧录在主板上的ROM芯片上的。

BIOS(基本输入输出系统)是烧录计算机在主板芯片上的程序,它保存着计算机基本的输入输出的程序,开机后的硬件自检程序和系统自启动程序,它从COMS中读写系统的设置的具体信息,为计算机提供最底层的硬件控制。

BIOS设置硬件参数,设置显示类型核显显示、独立显示和自动,设置串口协议和波特率,常见协议有RS-232,RS-422,RS-485,设置启动硬盘(安装双系统建议使用两块硬盘),控制风扇转速和模式,设置TPM模式,是否上电自启,设置CPU功耗,设置网络的POE供电功能,设置BOOT启动模式传统模式(Legacy Boot Type)、UEFI模式和双模式,设置PXE网络协议启动,设置是否开启门口看门狗-WDT(当检测到系统程序非正常运行后,会强制CPU发送复位信号使整个系统复位)

(BIOS硬件图:双排直插式封装 DIP,上面有”BIOS“ 丝印)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SmbUIoy7-1688020450505)(C:\Users\cjj\AppData\Roaming\Typora\typora-user-images\image-20230629113243667.png)]

MBR(Stage 1 bootloader)

1、BIOS启动后,执行了一些例如开机自检,硬件初始化等工作,然后读取硬盘MBR分区的第 一个扇区(前512字节),其中前446字节储存了一个小程序叫做boot loader,中间的64 字节是磁盘分区表,最后两个字节是固定的0xAA55的文件类型识别标记。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gxLPbiwU-1688020450505)(C:\Users\cjj\AppData\Roaming\Typora\typora-user-images\image-20230629134811092.png)]

2、具体作用

前446字节是primary bootloader,包含了可执行代码和错误信息字符串。接下去64字节是磁盘的分区表,该分区表中包含了四条分区记录,每条分区记录为16字节,分区记录可以为空,若为空则表示分区不存在。最后是2个字节的magic number,这两个字节是固定的0xAA55,这两个字节的magic number可以用于判断该MBR记录是否存在;

primary bootloader的作用就是用于寻找并定位secondary bootloader,也就是Stage 2 bootloader。它通过遍历分区表寻找可用的分区,当它发现可用的分区的时候,还是会继续扫描其他分区,确保其他分区是不可用的。然后从可用的分区中读取secondary bootloader到内存中,并执行

3、MBR的内容,在Linux中可以通过以下命令获取:

dd if=/dev/sda of=mbr.bin bs=512 count=1
od -xa mbr.bin

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aPXGEVkE-1688020450506)(C:\Users\cjj\AppData\Roaming\Typora\typora-user-images\image-20230629135259011.png)]

注:UEFI对比BIOS本身就是一个微型的操作系统,直接读取FAT格式的文件分区(EFI 程序),相比BIOS读取MBR文件分区快了不少

GROUB(Stage 2 bootloader)

bootloader程序主要用的两个文件,/boot/vmlinuz内核文件,/boot/initramfs虚拟文件系 统。

Stage 2 bootloader 也称为 secondary bootloader,更恰当是 kernel loader,它的任务是把 kernel 加载到 内存中,并根据设置,有选择性的 将 initial RAM disk 也加载到内存中。

在x86 PC环境中,Stage 1 bootloader和Stage 2 bootloader合并起来就是 LILO (Linux Loader)或者GRUB(GRand Unified Bootloader)。因为LILO中存在一些缺点,并且这些缺点在GRUB中得到了比较好的解决,所以这里将会以GRUB为准进行讲解。

GRUB的一大优点是,它能够正确识别到Linux文件系统。相对于像LILO那样只能读取原始扇区数据,GRUB则可以从ext2和ext3的文件系统中读取到Linux内核。为了实现这个功能,GRUB将原本2个步骤的bootloader变成了3个步骤,多了Stage 1.5 bootloader即在Stage 1 bootloader和Stage 2 bootload中间加载一个可以识别Linux文件系统的bootloader(Stage 1.5 bootloader),例如reiserfs_stage1_5(用于识别Reiser日志文件系统)或者e2fs_stage1_5(用于识别ext2和ext3文件系统)。当Stage 1.5 bootloader被加载和执行后,就可以继续Stage 2 bootloader的加载和执行了。

当Stage 2 bootloader被加载到内存后,GRUB就能够显示一系列可启动的内核(这些可启动的内核定义于/etc/grub.conf文件中,该文件是指向/etc/grub/menu.lst和/etc/grub.conf的软链接)。你可以在这些文件中配置,让系统自己默认选择某一个内核启动,并且可以配置内核启动的相应参数(如下图)。

当Stage 2 bootloader已经被加载到内存中,文件系统被识别到,并且默认的内核镜像和initrd镜像被加载到内存中,这就意味着镜像都已经准备好了,可以直接调用内核镜像开始内核的启动了。

在Ubuntu中bootloader的相关信息可以在/boot/grub/目录下找到,主要是/boot/grub/grub.cfg,但是该文件是自读的,需要在其他地方(如/etc/default/grub)更改,然后执行update-grub。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uLsjPubV-1688020450506)(C:\Users\cjj\AppData\Roaming\Typora\typora-user-images\image-20230629140449748.png)]

kernel

既然内核镜像已经准备好,并且控制权已经从Stage 2 bootloader传递过来,启动过程的Kernel阶段就可以开始了。内核镜像并非直接可以运行,而是一个被压缩过的。通常情况下,它是一个通过zlib压缩的zImage(compressed image小于51KB)或者bzImage(big compressed image,大于512KB)文件。在内核镜像的开头是一个小程序,该程序对硬件进行简单的配置并将压缩过的内核解压到高内存地址空间中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bn7Zyz4l-1688020450506)(C:\Users\cjj\AppData\Roaming\Typora\typora-user-images\image-20230629141021206.png)]

在./init/main.c:start_kernl()函数中,一长串的初始化函数将会被调用到用于设置中断、执行更详细的内存配置、加载initial RAM disk等。接着,将会调用./arch/i386/kernel/process.c:kernel_thread()函数来启动第一个用户空间进程,该进程的执行函数是init。最后,idle进程将(cpu_idle)会被启动,并且调度器其将接管整个系统。当中断使能时,可抢占的调度器周期性地接管系统,用于提供多任务同时运行的能力。

在内核启动的时候,原本由Stage 2 bootloader加载到内核的initial RAM disk(initrd)将会被挂载上。这个位于RAM里面的initrd将会临时充当根文件系统(initramfs.img,下文会把它的原理和制作进行展开),并且允许内核直接启动,而不需要挂载任何的物理磁盘。因为那些用于跟外设交互的内核模块可以被放置到initrd中,所以内核可以做得非常小,并且还能支持很多的外设配置。当内核启动起来后,这个临时的根文件系统将会被丢弃(通过pivot_root()函数),即initrd文件系统将会被卸载,而真正的根文件系统将会被挂载。

initrd功能让驱动不需要直接整合到内存中,而是以可加载的模块存在,从而让Linux内核能够做到很小。这些可加载模块为内核提供访问磁盘和文件系统的方法,同时也提供了访问其他硬件设备的方法。因为根文件系统其实是位于磁盘的一个文件系统,initrd提供了访问磁盘和挂载真正根文件系统的方法。在没有磁盘的嵌入式文件系统中,initrd可以作为最终的根文件系统,或者最终的根文件系统可以通过NFS(Network File System)挂载。

vmlinuz

vmlinuz是可引导的、压缩的内核。“vm”代表 “Virtual Memory

  • Linux 支持虚拟内存,不像老的操作系统比如DOS有640KB内存的限制。Linux能够使用硬盘空间作为虚拟内存,因此得名“vm”。vmlinuz是可执行的Linux内核,它位于/boot/vmlinuz,它一般是一个软链接。
  • vmlinuz自然就是内核,initrd.img是一个小的映象,包含一个最小的linux系统。通常的步骤是先启动内核,然后内核挂载initrd.img,并执行里面的脚本来进一步挂载各种各样的模块,然后发现真正的root分区,挂载并执行/sbin/init… …。

initrd.img

  • initrd.img 当然是可选的了,如果没有 initrd.img ,内核就试图直接挂载root分区。

    之所以要有initrd,那是为了启动的时候有更大的灵活性。比如,你把ext3支持编译成模块了。偏偏你真正的root分区又是ext3的。这下就麻烦了。因为内核需要挂载root分区之后才能加载ext3支持。但是没有ext3支持就没法挂载root分区。initrd就是用来解决这个问题的。

    类似的用这个东西还可以做其他的事情,比如从usb盘启动linux也会面临上面类似的问题。用initrd就能搞定了。

  • 实际根文件系统可用之前挂载到系统中的一个初始根文件系统,initrd中包含了实现这个目标所需要的目录和可执行程序的最小集合

  • 在桌面或服务器 Linux 系统中,initrd是一个临时的文件系统。其生存周期很短,只会用作到真实文件系统的一个桥梁。在没有存储设备的嵌入式系统中,initrd是永久的根文件系统

  • 作用?

    • 因为它是在内核启动之后加载,通过加载 initrd 最小系统中加载设备驱动,这些设备驱动有些是在系统运行中必须的(GPU、网卡等),为真正的的 rootfs 启动做准备,当initrd 系统中的 设备驱动都启动正常后,再加载真正的文件系统,
  • 排错?

    • 如果没指定 initrd.img 或者指定的 initrd.img 中并没有包含正确的驱动模块,则系统启动时会挂起,并报告"kernel panic: VFS:
  • 常见修改方式?

    • 设备驱动模块更新:下载内核源码包,(具体方法,网上有很多相关资料,或许有时间我会写一篇),用 make menuconfig 时最好直接选上随着原来 kernel &

      initrd.img 在一起的config文件,重新编译内核。如果不出错,执行make modules_install 后就生成了需要的模块(通常在目录/lib/modules/kernel-version)。之后我用新的/lib/modules/kernel-version/lib/下的modules目录及其文件替换掉旧的initrd.img中的modules目录(当然先得拆解之,方法见第2部分)。

      备注:通过cpio -i < initrd-kernel_version.img

  • 如果不能直接修改 ISO 镜像怎么办?

    • 直接在系统中重新制作 initramfs.fs(mkinitramfs -o /boot/initrd.img-xxx然后重新启动系统)
  • 流程?

    • boot loader 把内核以及 initrd 文件加载到内存的特定位置
    • 内核判断 initrd 的文件格式,如果是 cpio 格式
    • 将 initrd 的内容释放到 rootfs 中(作为临时根文件系统,加载少量必要的驱动模块)
    • 执行 initrd 中的 /init 文件,执行到这一点,内核的工作全部结束,完全交给 /init 文件处理
  • 制作

    • 一个initramfs至少包含一个文件,即systemd,内核将这个文件执行起来的进程设

      为main init进程,pid=1。内核挂载initramfs时,文件系统的根分区并没有挂载,所以无法访问 文件系统中的文件。多数的嵌入式设备需要一个shell,那么也会在initramfs打包进一个shell。如 果还需要其他工具或脚本,也可以打包到initramfs。

      注意:打包时,必须包含依赖,因为initramfs是一个能够独立运行的ram文件系统。

      1、手动解压制作
      流程如下:
      解压:
      mkdir initrd
      cp isolinux/initrd.img initrd
      mv initrd.img initrd.img.xz
      xz -l initrd.img.xz (看一下当前压缩详细)
      xz -d initrd.img.xz (得到initrd.img)
      cd initrd
      cpio -i -F …/initrd.img
      替换其中的 lib/modules/ 目录
      压缩:
      find . |cpio -oc -O …/initrd.img
      xz -z initrd.img --check=crc32

      2、mkinitrd
      mkinitrd命令 建立要载入ramdisk的映像文件,以供Linux开机时载入ramdisk。
      这个是重新封包核心的命令,例如你自己修改了一个设备的驱动,如果这个驱动要加入核心级别的话,就需要对核心进行重新封包,把新加的配置编译到核心内部去!
      -f:若指定的映像问家名称与现有文件重复,则覆盖现有的文件;
      -v:执行时显示详细的信息;
      –omit-scsi-modules:不要载入SCSI模块;
      –preload=<模块名称>:指定要载入的模块;
      –with=<模块名称>:指定要载入的模块;
      –version:显示版本信息。

      mkinitrd -v -f myinitrd.img $(uname -r)

      3、dracut是用来制作更轻量化initramfs的工具,它的使用方式跟mkinitrd非常接近,迁移成本较低。

Init

当内核启动并初始化完毕后,内核就会开始启动第一个用户空间程序,这个被调用的程序是第一个使用标准C库编译的程序,在这之前,所有的程序都不是使用标准C库编译得到的。

在Linux桌面系统中,虽然不是强制规定的,但是第一个启动的应用程序通常是/sbin/init。嵌入式系统中通常很少要求init程序通过/etc/inittab提供大量的初始化工作。很多情况下,用户可以通过调用一个简单的shell脚本来启动所需的应用程序

什么是 init?

Linux在完成核内引导(已经被载入内存,开始运行,并已初始化所有的设备驱动程序和数据结构等)之后,就通过启动一个用户级程序init的方式来启动其他用户级的进程或服务.所以,init始终是第一个进程,其PID始终为1(ps -aux | less),它是系统所有进程的父进程.

内核会在过去曾使用过init的几个地方查找它,它的正确位置(对Linux系统来说)是/sbin/init.如果内核找不到init,它就会试着运行/bin/sh,如果运行失败,系统的启动也会失败.

init程序需要读取配置文件/etc/inittab.inittab是一个不可执行的文本文件,它有若干行指令所组成.

很多系统 init 为啥被 systemd 取代?

init启动时只能串行执行脚本,一个服务启动后才能启动下一个服务,而systemd则可以并行启动服务。 使用systemd在启动速度上比init快的多。 这也是使用systemd替代init的原因。 但systemd也有自身的缺点,systemd虽然功能强大,但是其体系庞大,非常复杂。

systemd 系统:CentOS、Ubuntu、等

参考:
https://www.cnblogs.com/yi-mu-xi/p/13084582.html#:~:text=%E9%80%9A%E5%B8%B8%E7%9A%84%E6%AD%A5%E9%AA%A4%E6%98%AF%E5%85%88%E5%90%AF,t…%E3%80%82

https://blog.csdn.net/lindahui2008/article/details/82525975
https://zhuanlan.zhihu.com/p/567076094

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

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

相关文章

6.3.5 修改文件时间或创建新文件: touch

我们在 ls 这个指令的介绍时&#xff0c;有稍微提到每个文件在linux下面都会记录许多的时间参数&#xff0c; 其实是有三个主要的变动时间&#xff0c;那么三个时间的意义是什么呢&#xff1f; modification time &#xff08;mtime&#xff09;&#xff1a; 当该文件的“内容…

1.6、JAVA 分支结构 switch结构 for循环

1 分支结构 1.1 概述 顺序结构的程序虽然能解决计算、输出等问题 但不能做判断再选择。对于要先做判断再选择的问题就要使用分支结构 1.2 形式 1.3.1 练习&#xff1a;商品打折案例 创建包: cn.tedu.basic 创建类: TestDiscount.java 需求: 接收用户输入的原价。满1000打9折…

消息通知模块的设计原理

目录 介绍 一、数据库设计 公告消息记录应该全局唯一&#xff0c;还是为每个用户创建一条公告消息&#xff1f; 用MongoDB存储消息数据 1. 搞冷热数据分离&#xff0c;热数据定期归档 2. 冷数据存储一段时间后就销毁&#xff0c;释放存储空间 二、系统消息的发送与收…

肺癌的成因

中国医师协会 2023 年呼吸医师年会暨第二十二届中国呼吸医师论坛&#xff08;CACP 2023&#xff09;于 2023 年 6 月 15-18 日在大连如期举行。肺癌是我国目前发病率和死亡率最高的癌症&#xff0c;它的早期筛查和诊断十分关键。 丁香园呼吸时间特邀四川大学华西医院院长、呼吸…

软件测试报告办理解决方案分享,为什么要选择CMA认证或CNAS认可测试报告?

在进行软件测试时&#xff0c;合格的测试报告对于软件产品的质量保障至关重要。那么软件测试报告又该如何办理呢?软件企业为什么要选择CMA认证或CNAS认可的测试报告呢?因为CMA认证的测试报告和CNAS认可的测试报告都具有不可忽视的好处。 一、软件测试报告办理解决方案 1. 测…

我蒙了面试官一上来就说:请你介绍一下你测试过的项目

测试人员在找工作中&#xff0c;基本都会碰到让介绍项目的这种面试题&#xff0c;如何正确介绍自己的项目&#xff1f;需要做哪些技术准备&#xff1f; 今天这篇文章&#xff0c;围绕这些问题&#xff0c;跟大家一起聊一聊。 关于介绍自己的项目&#xff1f; 可以从以下几个方面…

赛效:如何将PDF文件分割成单页的PDF文档

1&#xff1a;打开wdashi点击PDF处理菜单里的“PDF分割”。 2&#xff1a;将本地PDF文件添加上去&#xff0c;在下方选择转换页码&#xff0c;在这里我们选择转换每一页。 3&#xff1a;点击右下角“开始转换”。 4&#xff1a;转换好后&#xff0c;点击绿色下载按钮将分割好的…

七、Docker安装MySQL/Tomcat/Redis等

学习参考&#xff1a;尚硅谷Docker实战教程、Docker官网、其他优秀博客(参考过的在文章最后列出) 目录 前言一、安装步骤二、Docker安装Tomcat2.1 搜索镜像2.2 拉取镜像2.3 查看镜像2.4 启动镜像&#xff08;端口映射&#xff09;2.5 停止容器2.6 移除容器 三、Docker安装MySQL…

23年下半年软考软件测评师难考吗?最近考虑要不要考?

软考中级难度是适中的&#xff0c;可以考啊&#xff01; 因为当代随着各种应用技术层出不穷&#xff0c;随着社会发展&#xff0c;需要大量的软件人才支持&#xff0c;同时软件的更新速度越来越快&#xff0c;市场竞争极其激烈。相关国际认证有微软的&#xff0c;Orical&#…

新西兰访问学者签证申请注意事项

新西兰是一个美丽而富有文化多样性的国家&#xff0c;许多学者都梦想着前往这里进行学术交流和研究。如果你计划申请新西兰的访问学者签证&#xff0c;以下是知识人网小编整理的一些你需要注意的事项&#xff1a; 1. 确认申请资格&#xff1a;在开始申请之前&#xff0c;确保你…

旧手机不要轻易扔掉,将其设置为无线网卡,不消耗流量

如果你有一部旧手机正在闲置着&#xff0c;或者正考虑要将其丢弃&#xff0c;那么请暂停一下。因为这个旧手机可以成为你的无线网卡&#xff0c;帮助你在家中或出行时实现更快的网络下载速度&#xff0c;而且毫不费流量。接下来&#xff0c;我将告诉你如何将旧手机变成无线网卡…

安装 Prometheus 指标存储 观测 dubbo /windows_exporter指标 windows 版本 其他系统换个语法思路一样

目录 下载 Prometheus 访问Prometheus Targets 发现服务 对应的 dubbo 指标就出来了 Dubbo脚手架生成个最简单的项目 导入 Prometheus 相关包 或者使用这个包即可 启动后就自动上报指标了 Windows_exporter or node_exporter 端口 9182 Prometheus 配置 windows_exp…

基于SpringBoot+vue的旧物置换网站设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

【产品应用】一体化步进电机在全自动纸张分切机的应用

全自动纸张分切机是现代印刷业中的重要设备之一&#xff0c;它能够将大的纸张切割成相同大小的小纸张&#xff0c;并具有高精度、高速度和高效率等优点。一体化步进电机作为全自动纸张分切机的重要部件&#xff0c;其应用对于提高设备的性能和稳定性具有重要意义。 01.设备简介…

【Java.SE】数组的练习

作者简介&#xff1a; 辭七七&#xff0c;目前大一&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a;Java.SE&#xff0c;本专栏主要讲解运算符&#xff0c;程序逻辑控制&#xff0c;方法的使用&…

Servlet从入门到精通

概述&#xff1a; Servlet&#xff08;Server Applet&#xff09;是Java Server的简称&#xff0c;称为小服务程序或服务网连接器&#xff0c;用Java编写的服务器端服务&#xff0c;具有独立于平台和协议的特性&#xff0c;主要功能在于交互式的浏览和生成数据&#xff0c;生成…

【Linux】线程同步(互斥锁和读写锁)

概念 线程同步是指多个线程之间协调和管理彼此的执行顺序&#xff0c;以避免竞态条件和不确定的结果。线程同步的目的是确保共享资源的正确访问和保护临界区的完整性。 作用 避免竞态条件&#xff1a;当多个线程同时访问和修改共享资源时&#xff0c;可能会导致竞态条件的发生…

初创企业办公室租赁现状

概述&#xff1a; 随着创业生态的不断发展&#xff0c;越来越多的初创企业开始涌现。租赁办公室是初创企业成立和运营的必要条件之一&#xff0c;然而&#xff0c;由于各种原因&#xff0c;租赁办公室对于初创企业来说仍然存在一些挑战和难点。本文将探讨初创企业租赁办公室的…

功能测试的技术

目录 前言&#xff1a; 1) 基于最终用户/系统测试 2) 等价测试 3) 边界值测试 4) 基于决策的测试 5) 备用流量测试 6) 临时测试 前言&#xff1a; 功能测试是软件测试中最常见的一种测试类型&#xff0c;它旨在验证系统的功能是否符合设计要求和预期行为。在进行功能测…

a==1a==2a==3 与 a===1a===2a===3如何实现?

前言 首先&#xff0c;我们来看个demo let a {value: 1,toString() {// console.log("toString")return this.value;} }看一下输出结果&#xff1a; console.log(a 1 && a 2 && a 3) // falseconsole.log(a 1 && a 2 && a …