如何把容器变成物理机

news2024/11/26 10:34:58

如何把容器变成物理机

本文的主题是把容器变成物理机,根据所学的知识。以及通过各种搜索引擎。他们都告诉我们,这是不可能的。这真的是不可能的吗?我不信,那我就要创造奇迹。请继续往下看。本文将教你如何把容器变成物理机。

这里只讲硬货,不废话!!!

什么是容器

在这里插入图片描述

​ 简单来说,容器是一个隔离的操作系统沙盒,目的是隔离所有操作系统的进程,那么我们也可以称容器的名称为被隔离的进程,在不同维度隔离级别有6个。隔离进程的名称空间是内核提供的功能,必须内核支持才可以。也就是说容器的内核跟宿主机的是同一个内核。Docker官方把它称作集装箱,我觉得这非常的形象,他们都工作在Linux 内核这条船上。

既然现在知道容器是个什么了,接下来要了解的是容器的运行逻辑。在用法上,一般把软件包直接放到容器内,然后在容器启动的时候,执行一个命令或者某个脚本文件,熟悉docker的人如果自己动手构建过自己的容器,就会知道CMD命令和ENTRYPOINT这个指令是定义docker的启动之后的执行逻辑。然后docker会安装启动之后的逻辑执行,若这个进程exit了,那么这个容器的运行状态也是exit状态。但是这些对我们本文的主题来讲并不重要。

docker的资源限制是通过cgroup来实现的,但是这也是针对运行状态的容器才会有资源消耗,我们是要转化这个容器,对我们的操作而言并不重要,另外顺便提一嘴,cgroup也是Linux内核的功能。

在空间占用方向上,通常一个容器只需要几MB到数10MB不等,而咱们操作系统都需要G这个单位去计算,这是为什么呢?后面将会说容器与操作系统共享的几个文件和目录,这个是它变小的关键。

Docker的六个维度的隔离

内容备注内核版本
PID进程编号2.6.24
NET网络设备、网络协议栈、端口2.6.29
IPC信号量、消息队列、共享内存2.6.19
MOUNT文件系统2.4.19
UTS用户名和主机域2.6.19
USER操作系统的用户和用户组3.8.x

为什么容器比物理机占用空间小呢?这个问题涉及到了Linux设计架构,linus大佬在设计Linux的时候将Linux分成了rootfs,内核,vfs(虚拟文件系统)几部分,当然这又是一个巨大的话题,毕竟内核源码超过2760万行,想了解具体逻辑,这并不简单。我们需要知道,虚拟文件系统包括/proc /dev /sys /dev/pts,且不仅仅包括这些,因为过于复杂,对于容器来讲,只需要了解这几个即可。

  • /sys 他是内核的接口,是一个虚拟文件系统,由内核生成。
  • /proc 这个是内存和进程保存的地方,也就是物理硬件的RAM。
  • /dev 这个目录里存储了物理硬件的抽象为文件。
  • /dev/pts 这里面存储了一些虚拟终端

总之这些目录必须存在且由内核生成。刚刚聊到容器占用磁盘空间会比物理机小很多,根本原因是因为物理机是已经启动起来了,大部分文件在物理机种都已经包含,包括内核模块都不需要安装,这些东西对于一个已经启动了的容器来讲是完全不必要的。另外一个方面就是容器内部极度精简,所以到你们看到的时候就只有几十MB。我们今天要做的步骤就有这一步,将所有缺少了的东西全部补全。

什么是物理机

相对而言,物理机抽象概念上来讲是一个完整的操作系统。包括容器内的全部内容,并且包括各种物理硬件的驱动,EFI或者bios引导,内核模块,内核源码。

总而言之,它是一个标准,它是用来定义“完整的系统”。

容器和虚拟机机区别和联系

从用户眼里来看,它们相同的部分是都具有文件系统,都能运行应用程序。在我看来,这只是浅尝辄止罢了。

我不想用传统的眼光看待它们,又是通过运行模式啦,又是通过运行速度,又是性能损耗啦。我觉得这些都是没有太大意义的。这些比较讲了操作系统和容器出现之后的事情,而不是在创造创造这个东西之前。

我要做鲁迅笔下的勇士,不管是蜘蛛还是螃蟹,在尝之前都不知道什么味道,而不是要把尝了之后的味道从侧面告诉大家 “吃螃蟹的人比较多“,”几乎没有吃蜘蛛的人”。而我们就到这个阶段,来比较吃螃蟹和吃蜘蛛的口感,这往往是世俗之人忘记了的根本。

让我们来重新认识操作系统吧

从时间发生的顺序来讲,首先你按了开机键。bios开始从主板芯片CMOS里加载。然后开始读取硬盘里的esp分区,根据grub引导,找到系统内核,开始加载内核,生成vfs文件系统,加载init进程,开机解锁。这时,完整的操作系统就可以运行了。

那么容器的启动过程呢? docker run -it --rm alpine bash?么,不不不。首先他会将rootfs以及overlay进行叠加,放到宿主机系统的某个目录。然后调用内核api生成六个名称空间,将部分系统文件映射到容器内部(fs名称空间内),调用内核生成vfs,然后执行bash进程,这个是这条命令要干的事。

现在我将这俩进行对比,我们补全容器在启动过程中跟物理机不同的地方,这样是不是容器就能像是物理机一样运行了呢?答案是肯定的。让我们实际操作一下试试。

我们需要做什么

  • 制作一个容纳这个容器的磁盘

    • 分区表
    • 分区类型
    • 更新fstab
  • 生成rootfs,并补全缺少的目录和vfs

    • bindmount
    • /sys
    • /proc
    • /dev
    • /dev/pts
    • /etc/reslove.conf
    • hostname
    • /etc/fstab
  • 补全内核

    • linux-image
    • linux-header
  • 补全进程管理器

    • systemd
  • 补全init进程

    • init
  • 补全网络管理

    • NetworkManager
  • 补全grub引导

    • grub-install
  • 重启测试

    • 独立测试

开始干

制作一个容纳这个容器的磁盘

这一步我们通过VMware生成vmdk硬盘并使用DiskGenius进行分区。

VMware步骤:

创建一个空的虚拟机然后增加磁盘,这里我们用50G,实测1G都用不了。

在这里插入图片描述

开启EFI引导

在这里插入图片描述

最终我的虚拟机配置是这样的。

在这里插入图片描述

DiskGenius步骤

在diskgenius里面选择打开刚刚创建的vmdk文件

在这里插入图片描述

转换为GPT分区表

在这里插入图片描述

分区结果展示,我这里使用了1G的esp分区,49G的根分区

在这里插入图片描述

这样我们的磁盘准备工作就完成了。

生成rootfs

回到VMware,开机使用iso镜像进行引导,进入体验模式。如下图所示。

在这里插入图片描述

安装docker

apt update
# 安装docker
apt install curl -y && curl -sSL get.docker.com | bash

分区挂载

# 查看我们刚刚创建的硬盘分区名称
root@ubuntu:~# lsblk
NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
loop0    7:0    0   2.2G  1 loop /rofs
loop1    7:1    0     4K  1 loop /snap/bare/5
loop2    7:2    0    62M  1 loop /snap/core20/1611
loop3    7:3    0  54.2M  1 loop /snap/snap-store/558
loop4    7:4    0 346.3M  1 loop /snap/gnome-3-38-2004/115
loop5    7:5    0  91.7M  1 loop /snap/gtk-common-themes/1535
loop6    7:6    0    47M  1 loop /snap/snapd/16292
sda      8:0    0    50G  0 disk
├─sda1   8:1    0     1G  0 part
└─sda2   8:2    0    49G  0 part 
sr0     11:0    1   3.6G  0 rom  /cdrom
root@ubuntu:~# mount /dev/sda2 /mnt/
root@ubuntu:~# mkdir -pv /mnt/boot/efi
mkdir: 已创建目录 '/mnt/boot'
mkdir: 已创建目录 '/mnt/boot/efi'
root@ubuntu:~# mount /dev/sda1 /mnt/boot/efi/
root@ubuntu:~# df -h
文件系统        容量  已用  可用 已用% 挂载点
udev            1.9G     0  1.9G    0% /dev
tmpfs           389M  1.9M  388M    1% /run
/dev/sr0        3.6G  3.6G     0  100% /cdrom
/dev/loop0      2.2G  2.2G     0  100% /rofs
/cow            1.9G  776M  1.2G   40% /
tmpfs           1.9G     0  1.9G    0% /dev/shm
tmpfs           5.0M  4.0K  5.0M    1% /run/lock
tmpfs           1.9G     0  1.9G    0% /sys/fs/cgroup
tmpfs           1.9G     0  1.9G    0% /tmp
/dev/loop1      128K  128K     0  100% /snap/bare/5
/dev/loop2       62M   62M     0  100% /snap/core20/1611
/dev/loop3       55M   55M     0  100% /snap/snap-store/558
/dev/loop6       47M   47M     0  100% /snap/snapd/16292
/dev/loop5       92M   92M     0  100% /snap/gtk-common-themes/1535
/dev/loop4      347M  347M     0  100% /snap/gnome-3-38-2004/115
tmpfs           389M   60K  389M    1% /run/user/999
tmpfs           389M     0  389M    0% /run/user/0
/dev/sda2        49G   16K   46G    1% /mnt
/dev/sda1      1022M  4.0K 1022M    1% /mnt/boot/efi

创建rootfs

使用当前最新镜像23.10

在这里插入图片描述

root@ubuntu:~# cd /mnt/
root@ubuntu:/mnt# docker export $(docker create ubuntu:23.10) | tar -C . -xvf -


在这里插入图片描述

临时代替内核补全vfs

for i in run sys proc dev dev/shm dev/pts; do echo "mount /$i"; mount --bind /$i /mnt/$i; done
root@ubuntu:/mnt# findmnt | grep mnt
│ │ └─/run/snapd/ns/snap-store.mnt         nsfs[mnt:[4026532702]] nsfs            rw
├─/mnt                                     /dev/sda2              ext4            rw,relatime
│ ├─/mnt/boot/efi                          /dev/sda1              vfat            rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
│ ├─/mnt/run                               tmpfs                  tmpfs           rw,nosuid,nodev,noexec,relatime,size=398272k,mode=755,inode64
│ ├─/mnt/sys                               sysfs                  sysfs           rw,nosuid,nodev,noexec,relatime
│ ├─/mnt/proc                              proc                   proc            rw,nosuid,nodev,noexec,relatime
│ └─/mnt/dev                               udev                   devtmpfs        rw,nosuid,noexec,relatime,size=1952868k,nr_inodes=488217,mode=755,inode64
│   ├─/mnt/dev/shm                         tmpfs                  tmpfs           rw,nosuid,nodev,inode64
│   └─/mnt/dev/pts                         devpts                 devpts          rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000

root@ubuntu:/mnt#

进入rootfs补全系统文件

使用下面的命令进入新的根分区

# chroot /mnt/

补全DNS配置

root@ubuntu:/# echo "nameserver 8.8.8.8" > /etc/resolv.conf

补全挂载的配置文件

root@ubuntu:/# echo "/dev/sda2 / ext4 defaults 0 1" >> /etc/fstab
root@ubuntu:/# echo "/dev/sda1 /boot/efi vfat umask=0077,shortname=winnt 0 2" >> /etc/fstab
root@ubuntu:/# cat /etc/fstab
# UNCONFIGURED FSTAB FOR BASE SYSTEM
/dev/sda2 / ext4 defaults 0 1 
/dev/sda1 /boot/efi vfat umask=0077,shortname=winnt 0 2

安装dialog

root@ubuntu:/# apt install dialog

安装时区

 apt install tzdata
 
 Configuring tzdata
------------------

Please select the geographic area in which you live. Subsequent configuration questions will narrow this down by presenting a list of cities, representing the time zones in which they are located.

  1. Africa  2. America  3. Antarctica  4. Arctic  5. Asia  6. Atlantic  7. Australia  8. Europe  9. Indian  10. Pacific  11. US  12. Etc
Geographic area: 5

Please select the city or region corresponding to your time zone.

  1. Aden      9. Baghdad   17. Chita       25. Dushanbe     33. Irkutsk    41. Kashgar       49. Macau         57. Omsk        65. Riyadh         73. Tashkent     81. Urumqi
  2. Almaty    10. Bahrain  18. Choibalsan  26. Famagusta    34. Istanbul   42. Kathmandu     50. Magadan       58. Oral        66. Sakhalin       74. Tbilisi      82. Ust-Nera
  3. Amman     11. Baku     19. Chongqing   27. Gaza         35. Jakarta    43. Khandyga      51. Makassar      59. Phnom_Penh  67. Samarkand      75. Tehran       83. Vientiane
  4. Anadyr    12. Bangkok  20. Colombo     28. Harbin       36. Jayapura   44. Kolkata       52. Manila        60. Pontianak   68. Seoul          76. Tel_Aviv     84. Vladivostok
  5. Aqtau     13. Barnaul  21. Damascus    29. Hebron       37. Jerusalem  45. Krasnoyarsk   53. Muscat        61. Pyongyang   69. Shanghai       77. Thimphu      85. Yakutsk
  6. Aqtobe    14. Beirut   22. Dhaka       30. Ho_Chi_Minh  38. Kabul      46. Kuala_Lumpur  54. Nicosia       62. Qatar       70. Singapore      78. Tokyo        86. Yangon
  7. Ashgabat  15. Bishkek  23. Dili        31. Hong_Kong    39. Kamchatka  47. Kuching       55. Novokuznetsk  63. Qostanay    71. Srednekolymsk  79. Tomsk        87. Yekaterinburg
  8. Atyrau    16. Brunei   24. Dubai       32. Hovd         40. Karachi    48. Kuwait        56. Novosibirsk   64. Qyzylorda   72. Taipei         80. Ulaanbaatar  88. Yerevan
Time zone: 69


Current default time zone: 'Asia/Shanghai'
Local time is now:      Tue May 16 15:12:32 CST 2023.
Universal Time is now:  Tue May 16 07:12:32 UTC 2023.
Run 'dpkg-reconfigure tzdata' if you wish to change it.

Setting up tzdata-icu (2023c-4exp1ubuntu1) ...

安装systemd进程管理器 和 init进程

apt install systemd init

安装tar

apt install tar

安装最新Linux内核

这时我们会看到很多的依赖包下载下来了,439M+。

安装过程种会有很多报错,需要安装其他依赖。

root@ubuntu:/# apt install linux-image-6.2.0-21-generic linux-headers-6.2.0-21-generic
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
  adduser busybox-initramfs cpio dmsetup gettext-base grub-common grub-gfxpayload-lists grub-pc grub-pc-bin grub2-common initramfs-tools initramfs-tools-bin initramfs-tools-core klibc-utils kmod
  libbrotli1 libdevmapper1.02.1 libefiboot1 libefivar1 libelf1 libfreetype6 libfuse3-3 libklibc libkmod2 libpng16-16 libssl3 libzstd1 linux-base linux-headers-6.2.0-21 linux-modules-6.2.0-21-generic
  os-prober systemd-hwe-hwdb ucf udev zstd
Suggested packages:
  liblocale-gettext-perl perl cron ecryptfs-utils libarchive-dev multiboot-doc grub-emu mtools xorriso desktop-base console-setup bash-completion fuse3 fdutils linux-doc | linux-source-6.2.0
  linux-tools linux-modules-extra-6.2.0-21-generic
The following NEW packages will be installed:
  adduser busybox-initramfs cpio dmsetup gettext-base grub-common grub-gfxpayload-lists grub-pc grub-pc-bin grub2-common initramfs-tools initramfs-tools-bin initramfs-tools-core klibc-utils kmod
  libbrotli1 libdevmapper1.02.1 libefiboot1 libefivar1 libelf1 libfreetype6 libfuse3-3 libklibc libkmod2 libpng16-16 libssl3 linux-base linux-headers-6.2.0-21 linux-headers-6.2.0-21-generic
  linux-image-6.2.0-21-generic linux-modules-6.2.0-21-generic os-prober systemd-hwe-hwdb ucf udev zstd
The following packages will be upgraded:
  libzstd1
1 upgraded, 36 newly installed, 0 to remove and 6 not upgraded.
Need to get 81.5 MB of archives.
After this operation, 439 MB of additional disk space will be used.
Do you want to continue? [Y/n]


安装NetworkManager网络管理


apt install network-manager

生成引导目录,安装efi引导

错误示范,和解决方法

# 出错示范,如果出现这个问题,需要一个带有EFI系系统,来修复这个efi,网络上种种使用bios修复,其实是使用了grub-bios引导。
# 解决方法是安装grub-pc-bin安装包,使用--removable 选项
root@ubuntu:/# grub-install --removable /dev/sda
Installing for i386-pc platform.
grub-install: warning: this GPT partition label contains no BIOS Boot Partition; embedding won't be possible.
grub-install: warning: Embedding is not possible.  GRUB can only be installed in this setup by using blocklists.  However, blocklists are UNRELIABLE and their use is discouraged..
grub-install: error: will not proceed with blocklists.


apt install grub-pc-bin
apt install grub-efi-amd64
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=GRUB --recheck --no-floppy


grub-install --target=x86_64-efi --bootloader-id=Shoulong --recheck --no-floppy

设置密码

passwd

重启测试

效果展示

可以看到根分区只有893M

在这里插入图片描述

总结脚本

快速挂载

mount /dev/sda2 /mnt/
mount /dev/sda1 /mnt/boot/efi/
for i in run sys proc dev dev/shm dev/pts; do echo "mount /$i"; mount --bind /$i /mnt/$i; done

最后,因为Linus大佬设计系统的兼容性真的是太好了,真的可以容器和宿主机之间随意变换。致敬大佬(鞠躬)。
这样就做成了不到800M+的一个系统,是不是比官方的mini版更加迷你呢?

有疑问或有需求或有工作推荐可以dd我。

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

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

相关文章

java多线程_01

文章目录 1. 线程的概念1. 程序2. 进程3. 线程4. Java程序的运行原理5. 并发与并行概念1. 并发2. 并行3. 并发编程和并行编程 2. Java中的Thread线程类1. Thread类构造方法2. Thread类普通方法3. Thread类静态方法4. Thread类特殊方法 3.线程的创建方式1. 继承Thread类2. 实现R…

C++ 仿函数(一)

目录 一、仿函数是什么? 二、仿函数的特点 1.仿函数在使用时,可以像普通函数那样调用, 可以有参数,可以有返回值 2.仿函数超出普通函数的概念,可以有自己的状态 ​编辑3.仿函数可以作为参数传递。 三、谓词 一元谓词示例&a…

38【源码】数据可视化:基于 Echarts + Python 动态实时大屏 - 全国图书零售监测数据

效果图展示 1.动态效果演示 2.静态切片效果图 一、确定需求方案 1.确定产品上线部署的屏幕LED分辨率 本案例基于16:9 屏宽比,F11全屏显示。 2.部署方式 浏览器打开播放,Chrome浏览器、360浏览器等。 二、整体架构设计 前端基于 Echarts开源库设计…

leetcode27.移除元素

个人主页:平行线也会相交 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创 收录于专栏【LeetCode】 🍓希望我们一起努力、成长,共同进步。 👉题目链接 题目描述 给你一个数组 nums 和一个…

从0开始学习数据库(持续更新)

一个数据库最重要的部分是什么? 关系型数据库mysql有着四大特性,原子性,隔离性,一致性,持久性。 kv数据库有着原子性,持久性,弱一致性。 可见,不管数据库的存储引擎是什么&#xff0…

LeetCode_递归_中等_138.复制带随机指针的链表

目录 1.题目2.思路3.代码实现(Java) 1.题目 给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random,该指针可以指向链表中的任何节点或空节点。 构造这个链表的深拷贝。 深拷贝应该正好由 n 个全新节点组成&#…

vector源码解析及扩容优化

一、vector源码解析 没有任何一个东西可以在原地扩充,因为要了一块内存后,后面这块内存有可能被使用了,或者能不能用也不知道。链表可以保留原有节点,再将指针指向别处开辟的新内存,但这个也不算原地扩充。 对于vecto…

不需要等待列表,也不用魔法上网的Claude,能否比肩ChatGPT?

近期,国外Anthropic公司发布了Claude聊天机器人,堪比ChatGPT的最大竞争对手。一经推出,市场上就经常拿它俩来对比,因为推出Claude产品的Anthropic 公司是由多位前OpenAI前员工组成,两家公司,以及他们推出的…

ssm框架之SpringMVC:乱码问题

一种修改tomcat配置文件 如果tomcat乱码修改Tomcat的conf的server.xml文件加上 URIEncoding“UTF-8” 添加一个URIEncoding“UTF-8” tomcat 如果7.0 不这样设置,无论get还是post后台都显示乱码。tomcat如果是8.0版本,只有post后台显示是乱码 一种过滤…

Redis进阶

主要内容 Redis持久化Redis主从Redis哨兵Redis分片集群 Redis持久化 Redis有两种持久化的方案: RDB持久化AOF持久化 1. RDB持久化 RDB全称Redis Database Backup file(Redis数据备份文件),也被叫做Redis数据快照。简单来说就是把内存中的所…

HTML基本知识与常用标签的使用以及实现一个HTML版本个人简历

文章目录 HTML1. HTML结构1.1 认识标签1.1.2 HTML文件结构 2. HTML常见标签2.1 注释标签2.2 标题标签2.3 段落标签2.4 换行标签2.5 格式化标签2.6 图片标签2.7 超链接标签2.8 表格标签2.9 列表标签2.10 表单标签2.11 label 标签2.12 select 标签2.13 textarea 标签2.14 无语义标…

[架构之路-200]- 性能需求与性能分析:影响性能的主要因素

目录 前言:关于性能的几点说明 第一章 性能需求:提出各种性能指标 1.1 可靠性或可用性: stablity 1.2 处理能力或效率: Performance 1.2.1 指标是吞吐率 1.2.2 指标是响应时间: 1.2.3 指标是资源利用率 1.3 高并发性 1.…

FreeRTOS开启任务调度函数xPortStartScheduler详解

在FreeRTOS中,创建完任务后需要调用vTaskStartScheduler开启调度器,在这个函数主要就是创建空闲任务然后调用xPortStartScheduler函数开启任务的调度,本篇文章就以Cortex-M7为例来分析一下这个函数具体做了什么事,并深入理解其中的…

文献阅读:A Lite Distributed Semantic Communication System for Internet of Things

目录 动机:为什么作者想要解决这个问题?贡献:作者在这篇论文中完成了什么工作(创新点)?规划:他们如何完成工作?理由:通过什么实验验证它们的工作结果自己的看法 动机:为什么作者想要…

Python遍历大量表格文件并筛选出表格内数据缺失率低的文件

本文介绍基于Python语言,针对一个文件夹下大量的Excel表格文件,基于其中每一个文件内、某一列数据的特征,对其加以筛选,并将符合要求与不符合要求的文件分别复制到另外两个新的文件夹中的方法。 首先,我们来明确一下本…

【Linux】多线程 --- POSIX信号量+懒汉模式的线程池+其他常见锁

Linux system sprinkle flowers 文章目录 一、POSIX信号量1.阻塞队列实现的生产消费模型代码不足的地方(无法事前得知临界资源的就绪状态)2.信号量的理解3.初步看一下信号量的操作接口4.环形队列实现的生产消费模型5.环形队列的代码编写(维持…

百度将凭借人工智能改变游戏规则并实现盈利?

来源:猛兽财经 作者:猛兽财经 稳健的财务业绩 在2022年第四季度,百度(BIDU)的收入为48亿美元(331亿人民币),比分析师预测的高出了1.72亿美元,但同比下降了约8%。从细分业务来看,百度…

Android:你真的会用Toast吗(介绍安卓好看简约的Toast快速解锁方法)

目录 概要 开源库地址 如何使用 1、首先我们现在根目录下的build.gradle中添加以下依赖 2、然后我们在我们的模块目录(通常是app)下的build.gradle中添加以下依赖 3、 然后这一步是可选的,你可以在你的app模块下任意位置,添加以下…

ANR原理篇 - ANR弹框是如何显示出来的

系列文章目录 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 例如:第一章 Python 机器学习入门之pandas的使用 文章目录 系列文章目录前言一、ANR弹框是如何显示流程1.1 找到弹框对应类1.2 查找AppNotRespondingDialog引用…

Python程序员职业现状分析,想提高竞争力,就要做到这六点

现今程序员群体数量已经高达几百万,学历和收入双高,月薪普遍过万。今天,我们就围绕90后程序员人群分析、职业现状、Python程序员分析等,进行较为全面的报告分析和观点论述。 一、程序员人群分析 人数规模上:截当前程…