Linux:在使用UEFI固件的计算机上内核是如何被启动的

news2024/11/23 16:58:57

前言

启动计算机通常不是一件难事:按下电源键,稍等片刻,你就能看到一个登录界面,再输入正确的密码,就可以开启一天的网上冲浪之旅了。

但偶尔这件事没那么顺利,有时候迎接你的不是熟悉的登录界面,而是一个令人生畏的命令提示符界面,一闪一闪的提示符告诉你:“你碰到麻烦了”。于是你对着错误提示查找解决方法,按照网页上的步骤,你对着提示符输入并执行了几条你完全不理解的命令,计算机又能正常启动了,但同时你发现你那存有大学舍友糗照的硬盘分区被清空了。

为了防止这样的悲剧发生,了解一下计算机的启动流程是非常有必要的,这能帮助你下次再碰到计算机启动问题时不至于手忙脚乱,而误把硬盘分区格式化掉。

下面开始正题,我会以一个Linux使用者(而不是专业的UEFI开发工程师)的视角来叙述一下UEFI固件计算机的启动流程。虽然BIOS看起来已经成为历史,但它实际上还运行在许多存量设备上——我数年前供职的第一家公司其生产的基于Linux的设备就仍在使用BIOS固件,因此也会对其进行介绍。

BIOS+MBR

在BIOS+MBR时代,对于固件来说,并没有文件的概念,甚至没有分区的概念,它只认识扇区。在把对CPU的控制权交给存储于硬盘MBR中的bootloader代码后,固件就完成了它在启动过程中的大部分工作,后续的流程由bootloader来负责。

标准MBR结构1

标准MBR结构
MBR分区表项格式2

MBR分区表项格式
而MBR空间非常有限,最多只有446个字节,这并不足以容纳全部的启动流程,因此通常bootloader会把启动流程划分为多个阶段,每个阶段的逻辑存储在不同的地方。

以曾经广泛使用的GRUB Legacy为例,它的启动流程分为三个阶段:stage1、stage1.5和stage2,其中stage1.5是可选的。stage1只是一个用来加载stage1.5或者stage2的入口;stage1.5提供了文件系统驱动,如果存在stage1.5,后面的stage2的代码就可以通过文件路径来加载,否则还是要通过扇区列表来加载;stage2才是最终启动内核的地方,根据配置,它可以加载指定路径下的内核镜像,也可以加载安装在其他分区PBR中的bootloader,由另外的bootloader完成系统的启动。3

UEFI+GPT

UEFI和BIOS很不一样,根据标准,UEFI固件需实现对FAT文件系统的支持4,而有了对文件系统的支持以后,许多事情都变得豁然开朗了:bootloader再也不用争抢MBR这块弹丸之地,也不需要切割逻辑,把代码见缝插针地安置在磁盘分区之间的空隙中,bootloader现在完全可以作为文件系统中的合法公民,不再是游离在外的幽灵。

GPT

UEFI虽然也可以以CSM模式从MBR硬盘中启动,但如今大多数情况都是配合GPT硬盘使用的,因此了解一下GPT分区表是很有必要的。本文后续也都基于UEFI+GPT的组合,并且不考虑U盘和光盘等移动存储设备。

GPT分区表的结构5

GPT分区表的结构
出于兼容性的考虑,在GPT硬盘的第0个LBA仍然保存有一份MBR格式的分区表,但这个分区表将硬盘余下的所有区域标记为一个受保护的分区6

GPT disk layout with protective MBR
GPT硬盘的第1个LBA才是真正的GPT分区表头,其格式如下5

分区表头的格式
GPT分区表头后面跟随着分区表项,每个分区表项大小为128字节,其格式如下5

GPT分区表项的格式
需要注意的是,这里的“分区类型GUID”里面的“分区类型”并不是根据FAT32、NTFS和ext4等具体格式划分的,而是根据用途划分的。下面是gnome-disk-utility在编辑分区时支持的部分分区类型GUID:

分区类型GUID
既然GPT分区表项中并没有表示分区格式的字段,那么磁盘管理工具是如何获取分区格式的呢?以libblkid为例,它实现了一系列的probe_*函数,通过读取分区头部的superblock中的数据来探测分区格式:

probe_vfat
probe_ext4
probe_ext3
probe_ext2
probe_ntfs

需要注意的是,在lsblk等工具的输出中,有一列UUID,但这个UUID并不是GPT分区表项中的分区类型GUID或者分区GUID,而是文件系统UUID,它不是GPT标准的一部分。文件系统的UUID通常存储于superblock中,并且由于没有统一的标准,其存储位置和大小也不尽相同,ext系列文件系统的UUID长度为16个字节,FAT系列文件系统的UUID长度为4个字节,NTFS文件系统的UUID长度为8个字节7

struct ext2_super_block {
    // *
    unsigned char		s_uuid[16];
    // *
};

struct msdos_super_block {
    // *
/* 27*/	unsigned char	ms_serno[4];
    // *
};

struct vfat_super_block {
    // *
/* 43*/	unsigned char	vs_serno[4];
    // *
};

struct ntfs_super_block {
    // *
    uint64_t	volume_serial;
    // *
};

在lsblk中,分区类型GUID和分区GUID字段名分别为PARTTYPE和PARTUUID:

$ lsblk /dev/sda --fs -o +PARTTYPE,PARTUUID
NAME FSTYPE FSVER LABEL UUID                                 FSAVAIL FSUSE% MOUNTPOINTS PARTTYPE                             PARTUUID
sda                                                                                                                          
├─sda1
│    vfat   FAT32       81DE-2849                             504.9M     1% /boot/efi   c12a7328-f81f-11d2-ba4b-00a0c93ec93b 3c1a61a2-5829-7598-8673-16494a668884
└─sda2
     ext4   1.0         5ae3cdb0-e1d3-372b-82d9-253144874891  395.5G     5% /           0fc63daf-8483-4772-8e79-3d69d8477de4 01c4ade9-2f93-e266-b517-858b6af37179

EFI系统分区(ESP)

在UEFI+GPT时代,bootloader不再存放于MBR,而是作为普通文件存放在EFI系统分区(ESP)。ESP实际上就是一个FAT格式的分区(通常是FAT32),其分区类型GUID为为c12a7328-f81f-11d2-ba4b-00a0c93ec93b,其GPT分区表项中属性字段的bit1被设置为08,除此之外,它与普通的FAT格式分区并没有什么不同。

ESP在Windows上默认是隐藏的,在Linux上可以作为普通的块设备正常挂载。在Ubuntu上,ESP被默认挂载于/boot/efi目录,/etc/fstab中有这样一个配置项:

# /boot/efi was on /dev/sda1 during installation
UUID=81DE-2849  /boot/efi       vfat    umask=0077      0       1

这个配置项的一个细节是被挂载的分区并没有使用/dev/sd1这样的设备路径,而是使用了UUID=81DE-2849来指定,这样可以防止由于硬件配置变动导致错误的分区被挂载到/boot/efi目录。

bootloader也不是随意地放在ESP中,UEFI标准还对ESP的目录结构做了规定。按照标准,ESP的根目录中需包含一个名为EFI的目录,所有的bootloader均需放置在EFI目录的子目录下9

\EFI
   \<OS Vendor 1 Directory>
         <OS Loader Image>
   \<OS Vendor 2 Directory>
         <OS Loader Image>
  …

   \<OS Vendor N Directory>
         <OS Loader Image>
   \<OEM Directory>
         <OEM Application Image>
   \<BIOS Vendor Directory>
         <BIOS Vendor Application Image>
   \<Third Party Tool Vendor Directory>
         <Third Party Tool Vendor Application Image>
   \BOOT
         BOOT{machine type short name}.EFI

其中BOOT目录是一个缺省目录,缺省目录中又有一个缺省bootloader,当没有其他可加载的bootloader时,固件就会尝试加载它。这个缺省bootloader文件名和处理器架构相关,在x86_64机器上,它的名字是BOOTX64.EFI9

我本机ESP目录结构如下:

/boot/efi
└── EFI
    ├── BOOT
    │   ├── BOOTX64.EFI
    │   ├── fbx64.efi
    │   └── mmx64.efi
    └── ubuntu
        ├── BOOTX64.CSV
        ├── grub.cfg
        ├── grubx64.efi
        ├── mmx64.efi
        └── shimx64.efi

UEFI镜像

虽然我前面一直在说bootloader,但UEFI固件能够加载的不仅仅是bootloader,任何符合格式的文件都可以被加载,它们被统称为UEFI镜像,其后缀名为efi。UEFI镜像可分为两类:UEFI应用和UEFI驱动,bootloader是UEFI应用的一个子类,它实现了加载操作系统的功能。除了bootloader之外还有其他类型的UEFI应用,例如提供了命令行交互接口的UEFI shell,能够以菜单形式选择不同bootloader加载的boot manager,甚至连python都被移植成了UEFI应用10

UEFI目前使用的镜像格式为PE32+,这是一种和Windows上的exe可执行文件大同小异的格式,感兴趣的可以自己查找相关资料。在Linux下使用file命令识别它们,输出如下:

$ file BOOTX64.EFI 
BOOTX64.EFI: PE32+ executable (EFI application) x86-64 (stripped to external PDB), for MS Windows

objdump也认识它们:

$ objdump -f BOOTX64.EFI 

BOOTX64.EFI:     file format pei-x86-64
architecture: i386:x86-64, flags 0x00000133:
HAS_RELOC, EXEC_P, HAS_SYMS, HAS_LOCALS, D_PAGED
start address 0x0000000000023000

NVRAM变量

前面提到了UEFI标准对ESP目录结构有规定,bootloader需要放置在EFI目录的子目录下,但是这还不够,想要能够被UEFI固件直接加载,还要配合NVRAM变量。

UEFI使用NVRAM来保存配置,这些配置在机器断电重启后也依然能够保持,其中有一些与启动过程息息相关。

名为Boot####的NVRAM变量定义了启动项条目,保存了bootloader的路径,其中####是一个16进制的序号。当机器启动后,我们手动选择启动项时,实际上就是在选择不同的Boot####变量中保存的bootloader路径进行加载。因此想要添加一个新的启动项时,不但要把bootloader放在符合UEFI标准的路径下,还要创建一个指向这个bootloader的Boot####变量。

名为BootOrder的NVRAM变量定义了启动项顺序,当我们没有手动选择启动项时,UEFI固件就会按照这个变量中配置的顺序依次加载bootloader,直到有bootloader被成功加载为止11

在Linux上可以使用efibootmgr工具来管理它们:

$ efibootmgr -v
BootCurrent: 0000
Timeout: 0 seconds
BootOrder: 0002,0004,2001,2002,2003
Boot0000* USB HDD: KingstonDataTraveler 3.0	PciRoot(0x0)/Pci(0x14,0x0)/USB(17,0)/HD(1,GPT,a3382272-59c0-2911-3af9-29919cc5c581,0x800,0x1ce77df)
Boot0002* ubuntu	HD(1,GPT,3c1a61a2-5829-7598-8673-16494a668884,0x800,0x100000)/File(\EFI\ubuntu\shimx64.efi)
Boot0004* Windows Boot Manager	HD(1,GPT,44b82d72-7651-803f-9a23-2cf241e4c839,0x800,0x32000)/File(\EFI\Microsoft\Boot\bootmgfw.efi)
Boot2001* EFI USB Device	RC
Boot2002* EFI DVD/CDROM	RC
Boot2003* EFI Network	RC

至于如何增删启动项,可以自行查阅efibootmgr手册。

如果因为某种原因NVRAM中数据被清空了,那么计算机该如何启动呢?一种方式是前面提到过的缺省bootloader,你可以把一个GRUB2的UEFI镜像重命名为BOOTX64.EFI放到BOOT目录下来引导机器启动,也可以使用fbx64.efi这种能够重建NVRAM启动项的UEFI应用来修复启动项,甚至把UEFI shell作为缺省启动项,手动加载要启动的bootloader;另一种方式是依赖UEFI固件的一些非标准逻辑——你机器上的UEFI固件很可能无论如何都会查看ESP中的EFI/Microsoft/Boot/bootmgfw.efi文件是否存在并尝试加载它,而无视NVRAM中的配置。12

GRUB2

前面是任何使用UEFI固件的计算机启动时都会涉及的流程,而当bootloader被成功加载后,操作系统如何被加载,就不在UEFI标准之中了。下面以GRUB2为例,看一下Linux内核是如何被加载的。

GRUB2安装在ESP中的bootloader镜像在x86_64机器上名字是grubx64.efi,即使在UEFI时代,GRUB2也没有把全部的代码和数据统统塞进ESP中,主配置和数据仍然保存在普通数据分区中。GRUB2在ESP中有一份简单的配置文件grub.cfg,它的作用就是告诉grubx64.efi应当去哪里加载主配置文件,下面是我机器上ESP分区中grub.cfg的内容:

search.fs_uuid 5ae3cdb0-e1d3-372b-82d9-253144874891 root hd0,gpt2 
set prefix=($root)'/boot/grub'
configfile $prefix/grub.cfg

grubx64.efi可以根据这份配置文件的内容,首先按照文件系统UUID找到存放主配置文件的分区,然后再根据路径找到主配置文件加载。

而主配置文件的内容就比较长了,此处列出其中三个启动项的配置:

第一个是Ubuntu启动项,在以正常方式启动Ubuntu时,这个启动项会被GRUB2加载。这个启动项被加载时,GRUB2首先会进行必要的配置,使用insmod命令加载必要的模块,然后根据文件系统UUID找到存放内核镜像的分区,找到内核镜像后,使用linux命令向其传递启动参数并加载,使用initrd命令加载initrd,最后会执行隐含的boot命令,启动内核13

menuentry 'Ubuntu' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-simple-5ae3cdb0-e1d3-372b-82d9-253144874891' {
	recordfail
	load_video
	gfxmode $linux_gfx_mode
	insmod gzio
	if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi
	insmod part_gpt
	insmod ext2
	set root='hd0,gpt2'
	if [ x$feature_platform_search_hint = xy ]; then
	  search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt2 --hint-efi=hd0,gpt2 --hint-baremetal=ahci0,gpt2  5ae3cdb0-e1d3-372b-82d9-253144874891
	else
	  search --no-floppy --fs-uuid --set=root 5ae3cdb0-e1d3-372b-82d9-253144874891
	fi
	linux	/boot/vmlinuz-5.19.0-50-generic root=UUID=5ae3cdb0-e1d3-372b-82d9-253144874891 ro  quiet splash $vt_handoff
	initrd	/boot/initrd.img-5.19.0-50-generic
}

第二个是Windows Boot Manager启动项,GRUB2并不单纯是一个bootloader,它还可以使用chainloader命令加载其他UEFI镜像,在这个启动项中,它就加载了Windows的bootloader:

menuentry 'Windows Boot Manager (on /dev/nvme0n1p1)' --class windows --class os $menuentry_id_option 'osprober-efi-9493-2BA0' {
	insmod part_gpt
	insmod fat
	search --no-floppy --fs-uuid --set=root 9493-2BA0
	chainloader /efi/Microsoft/Boot/bootmgfw.efi
}

第三个是UEFI Firmware Settings,GRUB2还可以放弃加载内核或者其他bootloader,调用fwsetup重启机器,并进入UEFI固件配置界面:

menuentry 'UEFI Firmware Settings' $menuentry_id_option 'uefi-firmware' {
	fwsetup
}

总结

总结一下,使用UEFI固件的计算机从开机到Linux内核启动的典型流程如下:

  1. UEFI固件初始化
  2. UEFI固件根据NVRAM配置,或者手动选择,在ESP中加载bootloader。如果想要启动Linux,这个bootloader大部分情况是GRUB2
  3. GRUB2加载ESP分区的配置文件,找到主配置文件路径
  4. 加载主配置文件,展示GRUB2启动项选择菜单
  5. 手动或者默认选择Linux启动项
  6. 通过一系列的insmod、linux、initrd和boot命令加载并启动Linux内核

当然了,UEFI固件的实现并不一定会完全按照标准来,Linux也并不只是有GRUB2这一个bootloader可用,所以每台计算机实际的启动流程并不一定和这里完全相符。但是万变不离其宗,在正常启动的情况下,CPU的控制权总还是会按照UEFI固件->UEFI应用->Linux内核的顺序流转的。


  1. https://zh.wikipedia.org/wiki/%E4%B8%BB%E5%BC%95%E5%AF%BC%E8%AE%B0%E5%BD%95 ↩︎

  2. https://wiki.osdev.org/Partition_Table ↩︎

  3. https://www.system-rescue.org/disk-partitioning/Grub-boot-stages/ ↩︎

  4. https://uefi.org/specs/UEFI/2.10/13_Protocols_Media_Access.html#file-system-format ↩︎

  5. https://zh.wikipedia.org/wiki/GUID%E7%A3%81%E7%A2%9F%E5%88%86%E5%89%B2%E8%A1%A8 ↩︎ ↩︎ ↩︎

  6. https://uefi.org/specs/UEFI/2.10/05_GUID_Partition_Table_Format.html#protective-mbr ↩︎

  7. https://github.com/util-linux/util-linux/tree/master/libblkid/src/superblocks ↩︎

  8. https://uefi.org/specs/UEFI/2.10/13_Protocols_Media_Access.html#partition-discovery ↩︎

  9. https://uefi.org/specs/UEFI/2.10/13_Protocols_Media_Access.html#directory-structure ↩︎ ↩︎

  10. https://github.com/tianocore/edk2-staging/tree/MicroPythonTestFramework ↩︎

  11. https://uefi.org/specs/UEFI/2.10/03_Boot_Manager.html#globally-defined-variables ↩︎

  12. https://www.rodsbooks.com/efi-bootloaders/fallback.html ↩︎

  13. https://www.gnu.org/software/grub/manual/grub/grub.html#boot ↩︎

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

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

相关文章

SSM(Vue3+ElementPlus+Axios+SSM前后端分离)--功能实现[五]

文章目录 SSM--功能实现实现功能09-带条件查询分页显示列表需求分析/图解思路分析代码实现测试分页条件查询带条件分页查询显示效果 实现功能10-添加家居表单前端校验需求分析/图解思路分析代码实现完成测试测试页面效果 实现功能11-添加家居表单后端校验需求分析/图解思路分析…

【HTML】<input>

分类 text password number button reset submit hidden radio checkbox file image color range tel email&#xff08;火狐有校验&#xff0c;360浏览器无校验。&#xff09; url datetime&#xff08;火狐、360浏览器不支持&#xff09; search date、month、week、time、da…

计算机网络-三种交换方式

计算机网络-三种交换方式 电路交换(Circuit Switching) 电话交换机接通电话线的方式称为电路交换从通信资源分配的角度来看&#xff0c;交换(Switching)就是按照某种方式动态的分配传输线路的资源 电话交换机 为了解决电话之间通信两两之间连线过多&#xff0c;所以产生了电话…

【Docker】docker镜像+nginx部署vue项目:

文章目录 一、文档&#xff1a;二、打包vue项目&#xff1a;三、配置nginx&#xff1a;四、配置Dockerfile&#xff1a;五、构建镜像&#xff1a;六、运行容器&#xff1a;七、最终效果&#xff1a; 一、文档&#xff1a; 【1】菜鸟教程&#xff1a;https://www.runoob.com/do…

windows下以指定用户访问SMB服务器进行读写

一 概述 最近遇到一个问题&#xff0c;linux 的 smb服务器开启匿名访问&#xff0c;windows访问linux文件夹不需要用户名密码就可以进去使用&#xff0c;但是存在一个问题&#xff0c;ssh连接到linux 后修改的文件&#xff0c;在windows已smb方式下打开某个文件修改 是没有权限…

HTML5 Canvas和Svg:哪个简单且好用?

HTML5 Canvas 和 SVG 都是基于标准的 HTML5 技术&#xff0c;可用于创建令人惊叹的图形和视觉体验。 首先&#xff0c;让我们花几句话介绍HTML5 Canvas和SVG。 什么是Canvas? Canvas&#xff08;通过 标签使用&#xff09;是一个 HTML 元素&#xff0c;用于在用户计算机屏幕…

Vue3+SpringBoot快速开发模板

起因&#xff1a;个人开发过程经常会使用到Vue3SpringBoot技术栈来开发项目&#xff0c;每次在项目初始化时都需要涉及一些重复的整理工作&#xff0c;于是结合一些个人觉得不错的前后端模板进行整合&#xff0c;打通一些大多数项目都需要的实现的基础功能&#xff0c;以便于快…

探讨|使用或不使用机器学习

动动发财的小手&#xff0c;点个赞吧&#xff01; 机器学习擅长解决某些复杂问题&#xff0c;通常涉及特征和结果之间的困难关系&#xff0c;这些关系不能轻易地硬编码为启发式或 if-else 语句。然而&#xff0c;在决定 ML 是否是当前给定问题的良好解决方案时&#xff0c;有一…

opencv基础-38 形态学操作-闭运算(先膨胀,后腐蚀)cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)

闭运算是先膨胀、后腐蚀的运算&#xff0c;它有助于关闭前景物体内部的小孔&#xff0c;或去除物体上的小黑点&#xff0c;还可以将不同的前景图像进行连接。 例如&#xff0c;在图 8-17 中&#xff0c;通过先膨胀后腐蚀的闭运算去除了原始图像内部的小孔&#xff08;内部闭合的…

PCIe枚举源码分析

枚举的过程也就是RC的系统软件通过配置空间访问来确定以及扫描整个总线拓扑的过程。 PCIe的拓扑结构如下&#xff1a; • Root Complex是树的根&#xff0c;它一般实现了一个主桥设备(host bridge), 一条内部PCIe总线(BUS 0)&#xff0c;以及通过若干个PCI bridge扩展出一些r…

性能测试监控指标及分析调优指南

目录 一、哪些因素会成为系统的瓶颈 二、哪些指标做为衡量系统的性能 三、性能测试注意的问题 四、定位性能问题的时候&#xff0c;可以使用自下而上的策略分析排查 五、优化性能问题的时候&#xff0c;可以使用自上而下的策略进行优化 一、哪些因素会成为系统的瓶颈 CPU&…

Vercel 部署的项目发现APIkeys过期了怎么办

好不容易部署的Vercel&#xff0c;发现APIkeys过期了显示&#xff0c;查了查资料发现只要更新下新的apikeys&#xff0c;然后再重新部署下就好了。 重新设置APIkeys 1.1. 进去 Vercel 项目内部控制台&#xff0c;点击顶部的 Settings 按钮&#xff1b; 1.2 点击环境变量Enviorn…

K8S系列文章之 开源的堡垒机 jumpserver

一、jumpserver作为一款开源的堡垒机&#xff0c;不管是企业还是个人&#xff0c;我觉得都是比较合适的&#xff0c;而且使用也比较简单。 二、这里记录一下安装和使用过程。 1、安装&#xff0c;直接docker不是就行 version: 3 services:xbd-mysql:image: mysql:8.0.19restart…

Spring Data JPA源码

导读: 什么是Spring Data JPA? 要解释这个问题,我们先将Spring Data JPA拆成两个部分&#xff0c;即Sping Data和JPA。 从这两个部分来解释。 Spring Data是什么? 摘自: https://spring.io/projects/spring-data Spring Data’s mission is to provide a familiar and cons…

Nginx可视化NginxWebUI

Nginx可视化Web Github:https://github.com/cym1102/nginxWebUI 支持window、linux 安装方式支持docker、window直接运行 jar包cmd运行&#xff1a;port可自行替换 java -jar -Dfile.encodingUTF-8 D:/软件/Nginx-Ui/nginxWebUI-3.6.3.jar --server.port8380 --project.hom…

nvm下载安装配置

一、作用 nvm是node版本管理的工具&#xff0c;具有管理、下载、切换node版本等能力。经常不同项目需要依赖不同版本的node&#xff0c;此时nvm就能有效的解决node版本切换的问题。 二、nvm下载安装配置 &#xff08;1&#xff09;安装包地址 https://github.com/coreybutl…

Net强大混淆和代码保护 LogicNP Crypto Obfuscator

.Net 的强大混淆和代码保护确实有效&#xff01; .Net 汇编代码保护和混淆 自动异常报告 优化和性能改进 更小和简化的部署 您希望您的混淆器... 使用高级混淆技术确保对您的代码和知识产权提供最佳保护。使用智能规则和自动排除 避免常见混淆问题。 拥有简单的用户…

P2824 [HEOI2016/TJOI2016] 排序

题目 思路 直接模拟排序肯定会TLE&#xff0c;所以我们想一种离线的方法&#xff1a;01排序 利用二分答案check一下d&#xff0c;设序列中大于等于d的数为1&#xff0c;小于d的数为0 完成后再进行排序&#xff1a;这样升序排列就是将0放前面1放后面&#xff0c;降序排列则相反…

Linux下C/C++的gdb工具与Python的pdb工具常见用法之对比

1、gdb和pdb分别是什么&#xff1f; 1.1、gdb GDB&#xff08;GNU Debugger&#xff09;是一个功能强大的命令行调试工具&#xff0c;由GNU项目开发&#xff0c;用于调试C、C等编程语言的程序。它在多个操作系统中都可以使用&#xff0c;包括Linux、MacOS和Windows&#xff0…

C#与C/C++交互(1)——需要了解的基础知识

【前言】 C#中用于实现调用C/C的方案是P/Invoke&#xff08;Platform Invoke&#xff09;&#xff0c;让托管代码可以调用库中的函数。类似的功能&#xff0c;JAVA中叫JNI&#xff0c;Python中叫Ctypes。 常见的代码用法如下&#xff1a; [DllImport("Test.dll", E…