开发者社区> 玄学酱> 正文

Linux基础命令介绍十三:启动流程

简介:
+关注继续查看

固件(firmware)是指设备最底层的,让设备得以运行的程序代码。简单理解就是:固定在硬件上的软件。计算机中的许多设备都拥有固件(如硬盘、鼠标、光驱、U盘等),在计算机启动过程中,最先读取的就是位于主板上的固件,这个固件当前有两种类型:传统的BIOS和新的通用性更强的UEFI。

Linux基础命令介绍十三:启动流程

在上一篇中,我们提到另一种磁盘分区格式GTP也是UEFI标准的一部分。于是,当前计算机启动中,出现了两种不同的方式:BIOS/MBR和UEFI/GTP。

在linux操作系统的世界中,同样在经历着变革,系统初始化软件sysvinit正逐渐被systemd取代。

本文将主要讲述传统的BIOS/MBR-->sysvinit启动方式,同时,作为补充,也将简述UEFI/GTP-->systemd的启动方式。

BIOS/MBR-->sysvinit

1、BIOS阶段

系统加电后会立即读取BIOS中内容并执行,BIOS中程序的执行包括两个步骤:

1)加电自检POST(power-on self test),主要负责检测系统外围设备(如CPU、内存、显卡、键盘鼠标等)是否正常。如果硬件出现问题,主板会发出不同含义的蜂鸣声,启动终止。如果没有问题,屏幕就会显示出CPU、内存、硬盘等信息。

2)自检完成后,BIOS会执行一段程序来枚举本地设备(如光盘、U盘、硬盘、网络等,可以在BIOS中设置枚举顺序)寻找下一阶段的启动程序所在位置。BIOS会将控制权交给启动顺序(Boot Sequence)中排在第一位的设备,此时,计算机读取该设备中的最前面的512个字节,如果这512个字节的最后两个字节是0x55和0xAA(Magic Number),表明这个设备可以用于启动;如果不是,表明该设备不能用于启动,控制权于是转交给启动顺序中的下一个设备。如上一篇所述,硬盘中的最前面的512字节即为主引导记录 MBR。

2、MBR阶段

前一篇中我们描述过MBR的结构,其中包括446字节的Bootloader,64字节的DPT和2字节的Magic Number。

Bootloader(引导加载程序)中较常用的一种是grub,grub引导分为两个阶段(有些grub还定义了1.5阶段):

1)BIOS将stage1载入内存中的指定位置(0x7C00)并跳转执行,stage1的内容即为MBR中起始的446字节;此阶段执行作用主要是将硬盘0磁头0磁道2扇区的内容载入到内存0x8000处并跳转执行。

1.5)由于stage2的代码(较大)存放在文件系统下的/boot分区中(或者/boot没有单独分区的/etc/),因此识别stage2文件需要文件系统环境(此时还只能直接读取硬盘指定位置的内容,并不能识别文件系统)。stage1.5的作用就是为stage2提供文件系统环境,使系统能够找到位于文件系统中的stage2文件。

2)stage2被载入内存并执行,它首先会解析grub的配置文件menu.lst即/boot/grub/grub.conf,该文件中指定了系统内核文件所处的位置,如果没有找到该文件,就会执行一个shell,等待用户手动指定内核文件的位置。此阶段的最终状态就是执行boot命令,将内核和initrd镜像载入内存,进而将控制权交给内核。

grub.conf内容(版本:GNU GRUB 0.97):


  1. # grub.conf generated by anaconda 
  2. # Note that you do not have to rerun grub after making changes to this file 
  3. # NOTICE:  You have a /boot partition.  This means that 
  4. #          all kernel and initrd paths are relative to /boot/, eg. 
  5. #          root (hd0,0) 
  6. #          kernel /vmlinuz-version ro root=/dev/sda3 
  7. #          initrd /initrd-version.img 
  8. #boot=/dev/sda 
  9. default=0 
  10. timeout=5 
  11. splashimage=(hd0,0)/grub/splash.xpm.gz 
  12. hiddenmenu 
  13. title CentOS (2.6.18-407.el5) 
  14.         root (hd0,0) 
  15.         kernel /vmlinuz-2.6.18-407.el5 ro root=LABEL=/ rhgb quiet 
  16.         initrd /initrd-2.6.18-407.el5.img 
  17. title CentOS (2.6.18-398.el5) 
  18.         root (hd0,0) 
  19.         kernel /vmlinuz-2.6.18-398.el5 ro root=LABEL=/ rhgb quiet 
  20.         initrd /initrd-2.6.18-398.el5.img  

文件中#开头的行是注释行,最重要的部分是两个title下面指定的内核位置及具体文件(kernel和initrd项)

3、内核阶段

grub的stage2将initrd文件加载到内存中,内核于是开始执行initrd中的init文件,此文件是一个脚本,主要作用是加载各种存储介质相关的设备驱动程序。当所需的驱动程序加载完成后,会创建一个根设备,然后将根文件系统(rootfs)以只读的方式挂载。这一步结束后,释放未使用的内存,转换到真正的根文件系统中运行程序/sbin/init,启动系统PID为1的进程。此后系统的控制权就交给/sbin/init进程了。

4、init阶段

当init进程接管了系统的控制权之后,它首先会读取/etc/inittab文件,此文件描述了在特定的运行级别(runlevel)下,init进程该如何初始化系统。


  1. linux中定义了7种运行级别: 
  2. 0 表示关机 
  3. 1 表示单用户模式 
  4. 2 表示无网络的多用户模式 
  5. 3 表示多用户模式 
  6. 4 未使用 
  7. 5 表示图形界面模式 
  8. 6 表示重启  

inittab文件中指定了系统的默认运行级别,如id:3:initdefault:表示默认运行级别为3(多用户模式)。

init进程根据inittab文件,运行一系列指定的初始化脚本:

1)/etc/rc.d/rc.sysinit系统初始化脚本,它的作用包括设置主机名和默认网关、决定是否启用SELinux、加载用户自定义模块、根据文件/etc/sysctl.conf设置内核参数、设置raid及LVM等硬盘功能、重新以读写方式挂载根文件系统等等

2)执行/etc/rc.d/rc文件,该文件确认由inittab指定的运行级别N,并启动相应级别下的服务(通过执行/etc/rc.d/rcN.d中的文件),例如运行级别为3时,则先执行/etc/rc.d/rc3.d下以K开头的文件,然后执行以S开头的文件。这些文件都是指向/etc/init.d下的符号链接。以K开头的文件表示此运行级别下需要关闭的服务,以S开头的文件表示此运行级别下需要开启的服务。

3)在运行级别2、3、4、5中最后一个执行的文件均指向文件/etc/rc.local,用户可以在此文件中自定义启动内容。

4)之后根据inittab中设置,运行6个终端,以便用户登录系统,如果是运行级别5,则还会执行/etc/X11/prefdm -nodaemon启动相应的桌面环境。

5)然后执行/bin/login程序用于接收和验证来自mingetty的用户名和密码。

至此整个系统即启动完毕了

UEFI/GTP-->systemd

UEFI的出现是为了代替BIOS,同样,GTP和systemd也是为了弥补MBR和sysvinit的不足。和BIOS只负责POST和找到MBR不同,UEFI将贯穿系统加电到关机的整个过程。粗略划分,UEFI系统启动分为4个阶段:

1、UEFI初始化阶段

1)SEC(安全验证):接收并处理系统启动和重启信号,初始化临时存储区域,传递系统参数给下一阶段(即PEI)。

2)PEI(EFI前期初始化):为DXE准备执行环境,将需要传递到DXE的信息组成HOB(Handoff Block)列表,最终将控制权转交到DXE手中。

3)DXE(驱动执行环境):根据HOB列表初始化系统服务,然后遍历固件中的所有Driver,当驱动的依赖资源满足时,调度Dirver到执行队列执行,直到所有满足条件的Dirver都被加载。

2、操作系统加载器作为UEFI应用程序运行阶段

1)BDS(启动设备选择):初始化控制台设备,加载必要的设备驱动,根据系统设置加载和执行启动项,用户选中某个启动项(或系统进入默认的启动项)后,OS Loader启动,系统进入TSL阶段。

UEFI中程序能够识别存储介质上的分区信息和文件系统(如:fat32),此时会将/EFI/boot/grub2.efi(位于GTP格式硬盘的一个分区ESP,安装时自动生成)作为UEFI应用程序运行。

2)TSL(临时系统加载):操作系统加载器(OS Loader也位于ESP分区)执行的第一阶段,在这一阶段OS Loader作为一个UEFI应用程序运行,系统资源仍然由UEFI内核控制。当启动服务的ExitBootServices()服务被调用后,系统进入RT(Run Time)阶段。

3、操作系统运行阶段

RT(运行时):系统的控制权从UEFI内核转交到OS Loader手中,UEFI占用的各种资源被回收到OS Loader,仅有UEFI运行时服务保留给OS Loader和OS使用。随着OS Loader的执行,OS最终取得对系统的控制权。

在init作为系统初始化程序时,服务是通过/etc/rc.d/init.d中的脚本来管理并且是顺序执行的,当使用systemd作为系统初始化程序后,这些脚本被服务单元替换,并尽可能的并行启动进程。

在systemd中,一个单元配置文件可以描述如下内容之一:


  1. 系统服务(.service) 
  2. 挂载点(.mount) 
  3. 套接字(.sockets) 
  4. 系统设备(.device) 
  5. 交换分区(.swap) 
  6. 文件路径(.path) 
  7. 启动目标(.target) 
  8. 由systemd管理的计时器(.timer) 
  9. ....  

systemd为保持向下兼容性还保留了一些init命令和概念,但所对应的文件都是指向systemd对应命令或文件的符号链接:


  1. [root@centos7 temp]# ls -l /sbin/init  
  2. lrwxrwxrwx. 1 root root 22 1月  15 2016 /sbin/init -> ../lib/systemd/systemd 
  3. [root@centos7 temp]# ls -l /usr/lib/systemd/system/runlevel*.target 
  4. lrwxrwxrwx. 1 root root 15 1月  15 2016 /usr/lib/systemd/system/runlevel0.target -> poweroff.target 
  5. lrwxrwxrwx. 1 root root 13 1月  15 2016 /usr/lib/systemd/system/runlevel1.target -> rescue.target 
  6. lrwxrwxrwx. 1 root root 17 1月  15 2016 /usr/lib/systemd/system/runlevel2.target -> multi-user.target 
  7. lrwxrwxrwx. 1 root root 17 1月  15 2016 /usr/lib/systemd/system/runlevel3.target -> multi-user.target 
  8. lrwxrwxrwx. 1 root root 17 1月  15 2016 /usr/lib/systemd/system/runlevel4.target -> multi-user.target 
  9. lrwxrwxrwx. 1 root root 16 1月  15 2016 /usr/lib/systemd/system/runlevel5.target -> graphical.target 
  10. lrwxrwxrwx. 1 root root 13 1月  15 2016 /usr/lib/systemd/system/runlevel6.target -> reboot.target  

systemd启动后执行的第一个目标是default.target,但实际上default.target是指向graphical.target的符号链接。


  1. [root@centos7 temp]# ls -l /usr/lib/systemd/system/default.target 
  2. lrwxrwxrwx. 1 root root 16 1月  15 2016 /usr/lib/systemd/system/default.target -> graphical.target 
  3. [root@centos7 temp]# cat /usr/lib/systemd/system/graphical.target 
  4. #  This file is part of systemd. 
  5. #  systemd is free software; you can redistribute it and/or modify it 
  6. #  under the terms of the GNU Lesser General Public License as published by 
  7. #  the Free Software Foundation; either version 2.1 of the License, or 
  8. #  (at your optionany later version. 
  9.  
  10. [Unit] 
  11. Description=Graphical Interface 
  12. Documentation=man:systemd.special(7) 
  13. Requires=multi-user.target 
  14. Wants=display-manager.service 
  15. Conflicts=rescue.service rescue.target 
  16. After=multi-user.target rescue.service rescue.target display-manager.service 
  17. AllowIsolate=yes  

其中Requires行指明了本单元的依赖关系(其他各项意义可以通过命令man systemd.unit查看),顺着此文件,可以找到需要执行的单元:multi-user.target、basic.target、sysinit.target、local-fs.target swap.target、local-fs-pre.target。

4、关机阶段

AL(After-life):当系统硬件或操作系统出现严重错误不能继续正常运行时,固件会尝试修复错误,这时系统进入AL期。UEFI标准并没有定义此阶段的行为和规范。系统供应商可以自行定义。

相关命令

init

1、init

init除了在系统初始化时起的重要作用外,还可以用来执行关机、重启、切换运行级别的作用:


  1. #关机 
  2. init 0 
  3. #重启 
  4. init 6 
  5. #切换到单用户模式 
  6. init 1  

2、runlevel 显示运行级别


  1. [root@centos7 temp]# runlevel  
  2. N 3 
  3. [root@centos7 temp]#   

输出中N表示当前运行级别,如果系统启动后切换过运行级别,则输出类似于3 5表示之前运行级别为3,现在的运行级别为5。

3、halt reboot poweroff shutdown


  1. #立即关机 
  2. shutdown -h now 
  3. #在11:50分执行关机 
  4. shutdown -h 11:50 
  5. #如果要取消指定时间的关机,则在另一个终端中执行: 
  6. shutdown -c 
  7. #过30分钟之后重启系统,并且重启时不进行磁盘检测 
  8. shutdown -fr +30  

4、chkconfig 更新或查询服务的运行级别信息


  1. #列出服务(还会列出xinetd管理的服务) 
  2. chkconfig --list 
  3. #增加一个服务 
  4. chkconfig --add httpd 
  5. #使服务在运行级别2、3、5时自启动 
  6. chkconfig --level 235 httpd on  

5、service 运行服务脚本(服务脚本位于/etc/init.d内,service本身也是脚本,位于/sbin内)


  1. #列出所有服务状态 
  2. service --status-all 
  3. #列出单个服务状态 
  4. service nginx status 
  5. #启动服务 
  6. service nginx start 
  7. #停止服务 
  8. service nginx stop 
  9. #重启服务 
  10. service nginx restart 
  11. #重新加载配置文件 
  12. service nginx reload  

systemd

systemd并不是一个命令,而是一组命令,涉及到系统管理的方方面面。

1、systemctl 控制systemd系统和管理服务


  1. systemctl [OPTIONS...] COMMAND [NAME...] 

如切换运行级别或开关机:


  1. #重启(将执行reboot.target) 
  2. systemctl reboot 
  3. #暂停(将执行suspend.target) 
  4. systemctl suspend 
  5. #休眠(将执行hibernate.target) 
  6. systemctl hibernate 
  7. #切换至救援模式(单用户,将执行rescue.target) 
  8. systemctl rescue 
  9. #列出运行级别 
  10. systemctl get-default 
  11. #切换到运行级别5,即图形模式 
  12. systemctl isolate graphical.target  

系统服务单元相关:


  1. #列出正在运行的 Unit 
  2. systemctl list-units 
  3. #列出所有的 Unit 
  4. systemctl list-units --all 
  5. #列出所有加载失败的 Unit 
  6. systemctl list-units --failed 
  7. #列出Unit时指定类型 
  8. systemctl list-units --type=socket 

系统和服务管理:


  1. #系统状态 
  2. systemctl status 
  3. #服务状态(.service可以省略) 
  4. systemctl status nginx.service 
  5. #启动服务 
  6. systemctl start nginx 
  7. #停止服务 
  8. systemctl stop nginx 
  9. #重启服务 
  10. systemctl restart nginx 
  11. #重新加载配置文件 
  12. systemctl reload nginx 
  13. #设置服务开机启动 
  14. systemctl enable nginx 
  15. #列出所有安装的服务 
  16. systemctl list-unit-files 
  17. #指定类型 
  18. systemctl list-unit-files --type=target  

还有许多其他选项,这里就不一一列举了。

2、systemd-analyze 查看启动用时


  1. [root@centos7 ~]# systemd-analyze  
  2. Startup finished in 730ms (kernel) + 1.904s (initrd) + 9.909s (userspace) = 12.544s  

输出显示了系统启动过程中各部分耗时


  1. #各服务初始化用时 
  2. [root@centos7 ~]# systemd-analyze blame 
  3.           5.424s NetworkManager-wait-online.service 
  4.           1.830s dev-mapper-centos\x2droot.device 
  5.           1.055s firewalld.service 
  6.            980ms kdump.service 
  7.            549ms network.service 
  8.            .... 
  9. #输出各服务用时细节并写入文件(该文件可以用浏览器或图片查看器打开) 
  10. [root@centos7 ~]# systemd-analyze plot > file.svg 
  11. #序列化输出各服务详细完整的状态信息(输出内容很多,略) 
  12. [root@centos7 ~]# systemd-analyze dump  

3、systemd-cgls 递归显示控制组(Cgroups)信息

linux内核从版本2.6.24开始,引入了一个叫做控制组(control groups)的特性,是用于限制、记录、隔离进程组(process groups)所使用的物理资源(如:cpu,memory,IO等等)的机制。关于Cgroups的内容本文不再展开。


  1. [root@centos7 ~]# systemd-cgls  
  2. ├─1 /usr/lib/systemd/systemd --switched-root --system --deserialize 21 
  3. ├─user.slice 
  4. │ └─user-0.slice 
  5. │   ├─session-182.scope 
  6. │   │ ├─5165 sshd: root@pts/1     
  7. │   │ ├─5167 -bash 
  8. │   │ ├─5409 systemd-cgls 
  9. .... 
  10. ....  

4、systemd-cgtop 显示各控制组的使用量(CPU,内存,IO)

显示效果类似命令top


  1. [root@centos7 ~]# systemd-cgtop 
  2. Path                                                Tasks   %CPU   Memory  Input/s Output/s 
  3.  
  4. /                                                   161    0.2   400.5M        -        - 
  5. /system.slice/NetworkManager.service                  1      -        -        -        - 
  6. /system.slice/auditd.service                          1      -        -        -        - 
  7. /system.slice/crond.service                           1      -        -        -        - 
  8. /system.slice/dbus.service                            1      -        -        -        - 
  9. /system.slice/firewalld.service                       1      -        -        -        - 
  10. ....  

5、systemd-loginctl 控制systemd登录管理

此命令是命令loginctl的符号链接


  1. #列出当前会话 
  2. [root@centos7 ~]# systemd-loginctl list-sessions 
  3.    SESSION        UID USER             SEAT             
  4.        182          0 root                              
  5.        154          0 root                              
  6.  
  7. 2 sessions listed. 
  8. #列出当前登录用户 
  9. [root@centos7 ~]# loginctl list-users 
  10.        UID USER             
  11.          0 root             
  12.  
  13. 1 users listed. 
  14. #列出显示指定用户的信息 
  15. [root@centos7 ~]# loginctl show-user root 
  16. UID=0 
  17. GID=0 
  18. Name=root 
  19. Timestamp=三 2016-12-21 08:38:54 CST 
  20. TimestampMonotonic=77015538361 
  21. RuntimePath=/run/user/0 
  22. Slice=user-0.slice 
  23. Display=154 
  24. State=active 
  25. Sessions=182 154 
  26. IdleHint=no 
  27. IdleSinceHint=0 
  28. IdleSinceHintMonotonic=0 
  29. Linger=no 
  30. [root@centos7 ~]#  

6、timedatectl 系统时间和日期控制


  1. [root@centos7 ~]# timedatectl  
  2.       Local time: 三 2016-12-21 13:47:31 CST 
  3.   Universal time: 三 2016-12-21 05:47:31 UTC 
  4.         RTC time: 三 2016-12-21 05:47:31 
  5.        Time zone: Asia/Shanghai (CST, +0800) 
  6.      NTP enabled: n/a 
  7. NTP synchronized: no 
  8.  RTC in local TZ: no 
  9.       DST active: n/a 
  10. #设置时间 
  11. [root@centos7 ~]# timedatectl set-time "2012-10-30 18:17:16" 
  12. #列出时区 
  13. [root@centos7 ~]# timedatectl list-timezones 
  14. Africa/Abidjan 
  15. Africa/Accra 
  16. Africa/Addis_Ababa 
  17. Africa/Algiers 
  18. Africa/Asmara 
  19. .... 
  20. #设置时区 
  21. [root@centos7 ~]# timedatectl set-timezone America/New_York  

7、hostnamectl 系统主机名控制


  1. #状态 
  2. [root@centos7 ~]# hostnamectl status 
  3.    Static hostname: centos7 
  4.          Icon name: computer-vm 
  5.            Chassis: vm 
  6.         Machine ID: 956ab824a02d489d85b079cb442d5442 
  7.            Boot ID: 9016d7627d8148ecb7fb77afaa89aeab 
  8.     Virtualization: vmware 
  9.   Operating System: CentOS Linux 7 (Core) 
  10.        CPE OS Name: cpe:/o:centos:centos:7 
  11.             Kernel: Linux 3.10.0-327.el7.x86_64 
  12.       Architecture: x86-64 
  13. #设置主机名(内核参数/proc/sys/kernel/hostname和文件/etc/hostname中都立即更新) 
  14. [root@centos7 ~]# hostnamectl set-hostname MYHOST 
  15. #重新登录后主机名即变为myhost(静态主机名) 
  16. [root@centos7 ~]# hostnamectl 
  17.    Static hostname: myhost 
  18.    Pretty hostname: MYHOST 
  19.          Icon name: computer-vm 
  20.            Chassis: vm 
  21.         Machine ID: 956ab824a02d489d85b079cb442d5442 
  22.            Boot ID: 9016d7627d8148ecb7fb77afaa89aeab 
  23.     Virtualization: vmware 
  24.   Operating System: CentOS Linux 7 (Core) 
  25.        CPE OS Name: cpe:/o:centos:centos:7 
  26.             Kernel: Linux 3.10.0-327.el7.x86_64 
  27.       Architecture: x86-64  

以上systemd相关所有命令(除systemd-cgls和systemd-cgtop外),都可以使用选项-H指定远程基于systemd的主机(使用ssh协议):


  1. [root@centos7 ~]# hostnamectl -H 10.0.1.252 
  2.    Static hostname: idc-v-71252 
  3.          Icon name: computer-vm 
  4.            Chassis: vm 
  5.         Machine ID: 956ab824a02d489d85b079cb442d5442 
  6.            Boot ID: 9016d7627d8148ecb7fb77afaa89aeab 
  7.     Virtualization: vmware 
  8.   Operating System: CentOS Linux 7 (Core) 
  9.        CPE OS Name: cpe:/o:centos:centos:7 
  10.             Kernel: Linux 4.4.4-1.el7.elrepo.x86_64 
  11.       Architecture: x86-64  

systemd功能强大,使用方便,但也比较复杂,体系庞大。本文只介绍一点相关命令,更多内容就不在此展开了。

本文简述了传统的BIOS和新的UEFI启动流程,介绍了init和systemd部分相关命令。





作者:vvpale
来源:51CTO

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
Apache Flink 零基础入门(七):Table API 编程
本文主要包含三部分:第一部分,主要介绍什么是 Table API,从概念角度进行分析,让大家有一个感性的认识;第二部分,从代码的层面介绍怎么使用 Table API;第三部分,介绍 Table API 近期的动态。
2133 0
结合AliOS Things谈嵌入式系统通用问题定位方法(1):CPU相关基础
本文着重从问题定位的角度来介绍如何定位嵌入式软件系统中的问题,并结合AliOS Things提供的部分维测手段来介绍。
118 0
结合AliOS Things谈嵌入式系统通用问题定位方法(2):内核相关基础
内核提供的任务创建接口,会存在参数指定当前任务创建完立即运行还是需要显示调用start运行,需要注意。如果在创建任务时指定了立即执行,而在创建任务后去设置任务参数,可能是不生效的。(尤其posix的pthread接口经常遇到这种问题)
124 0
weblogic启动失败:Could not obtain the localhost address 解决办法
linux下weblogic启动如果出现这个错误,多半是hosts文件不对 1、先输入hostname,查看本机计算机名(比如:server123) 2、sudo vi /etc/hosts 编辑hosts文件,在最后加一行 127.
1023 0
Unity 之 贝塞尔曲线介绍和实际使用
Unity 中对贝塞尔曲线的实战应用,制作可视化操作曲线工具,文末附工具源码链接~
191 0
linux命令行介绍及使用(二)
1.在终端输入gconf-editor打开“Apps->Metacity->Global Keybingdings”找到快捷键的设置,找到“Show desktop”可修改显示桌面的快捷方式,默认为d(注:Ctrl和Alt键之间的键为即Windows键) 2.
781 0
+关注
玄学酱
这个时候,玄酱是不是应该说点什么...
20683
文章
438
问答
文章排行榜
最热
最新
相关电子书
更多
JS零基础入门教程(上册)
立即下载
性能优化方法论
立即下载
手把手学习日志服务SLS,云启实验室实战指南
立即下载