Linux内核的编译和运行

嵌入式技术

1334人已加入

描述

编译

想让Linux内核代码跑起来,得先搭建编译和运行代码的环境。

Linux代码尽量在Linux环境下编译,以减少不必要的麻烦,我选择的是ubuntu-18.04:

编译

1、linux源码下载

我们依旧使用5.4版本的linux,其下载链接:https://codeload.github.com/torvalds/linux/tar.gz/refs/tags/v5.4

并将源码放置如下文件,为了方便后续管理,我在gitee上创建了一个仓库,为自己后续阅读源码添加注释做准备。

/home/damon/00_code/02_gitee/linux_5.4/linux-5.4

2、编译内核源码

2.1 选择编译工具

ubuntu本身自带gcc编译器,不过是针对X86平台的,我们现在要编译ARM架构的代码,也就是在X8平台上编译ARM架构的工具,叫做ARM GCC交叉编译器。

编译

我将其解压放置在以下目录:

/usr/local/arm/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf

2.2 编译配置

基于ARM架构的配置,我们选择*./arch/arm/configs/vexpress_defconfig*配置。

编译

直接上编译脚本:

注意事项:如果你的环境是第一次配置,第一次编译的时候大概率会出错,按照错误提示按照依赖工具即可继续编译。

# 根据个人存放的Linux源码目录,修改成自己的目录路径
cd /home/damon/00_code/02_gitee/linux_5.4/linux-5.4


# 清理工程
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- distclean


# 配置文件选择:./arch/arm/configs/vexpress_defconfig
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- vexpress_defconfig


# 打开图形配置界面,我们选择默认配置
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- menuconfig

编译

2.3 编译内核

编译内核时,执行如下命令:

# -j8,可以根据实际核数修改,可以提速编译
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- -j8

编译成功后,我们可以得到内核镜像文件和设备树文件。内核镜像文件就是我们要跑代码的执行文件,设备树文件就是内核启动后加载它并生成设备节点的文件。

得到的内核镜像文件叫 zImage ,放在如下路径:

/home/damon/00_code/02_gitee/linux_5.4/linux-5.4/arch/arm/boot

编译

同时,对应vexpress_defconfig配置下生成的设备树文件:vexpress-v2p-ca9.dtb

编译

内核编译好了,如何将它启动呢?

3、如何启动内核

一般地,学习嵌入式linux,手头上要有一块开发板,一个支持linux开发的开发板少则几百块,贵则上千块。

如果你只是想玩一玩,看一看,完全没必要花这些钱,那么没有开发板该如何启动Linux内核呢?

我们可以采用 qemu搭建运行环境

qemu是“Quick Emulation”的缩写,是一个用C语言编写的开源虚拟化软件。它可以模拟硬件,在指定的硬件平台上搭建运行开发环境。

我们暂时无需知道其工作原理,直接拿来安装使用。

3.1 qemu安装

安装qemu工具

sudo apt-get install qemu

测试qemu是否安装成功:

编译

qemu起kernel

现在我们有了kernel镜像文件和设备树文件,我们按照qemu的指令启动:

qemu-system-arm -M vexpress-a9 -m 512M -kernel /home/damon/00_code/02_gitee/linux_5.4/linux-5.4/arch/arm/boot/zImage -dtb /home/damon/00_code/02_gitee/linux_5.4/linux-5.4/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -nographic -append "console=ttyAMA0"

启动日志:

省略......
CPU: 0 PID: 1 Comm: swapper/0 Not tainted 5.4.0+ #1
Hardware name: ARM-Versatile Express
[< 80110e90 >] (unwind_backtrace) from [< 8010c4a4 >] (show_stack+0x10/0x14)
[< 8010c4a4 >] (show_stack) from [< 80764c24 >] (dump_stack+0x90/0xa4)
[< 80764c24 >] (dump_stack) from [< 8012147c >] (panic+0x110/0x310)
[< 8012147c >] (panic) from [< 80a01584 >] (mount_block_root+0x204/0x2b4)
[< 80a01584 >] (mount_block_root) from [< 80a01758 >] (mount_root+0x124/0x148)
[< 80a01758 >] (mount_root) from [< 80a018d0 >] (prepare_namespace+0x154/0x198)
[< 80a018d0 >] (prepare_namespace) from [< 8077bd44 >] (kernel_init+0x8/0x110)
[< 8077bd44 >] (kernel_init) from [< 801010e8 >] (ret_from_fork+0x14/0x2c)
Exception stack(0x9e493fb0 to 0x9e493ff8)
3fa0:                                     00000000 00000000 00000000 00000000
3fc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
3fe0: 00000000 00000000 00000000 00000000 00000013 00000000
---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) ]---

看最后一行:启动到一半出错了,Kernel Painc!!!

看到Panic,"慌不慌" ?不过kernel会将Panic的原因也打印出来:Unable to mount root fs

不能挂在到rootfs上,什么是rootfs呢?没有它,我们该如何做一个呢?

4、 什么是rootfs?

root fs的中文名叫根文件系统。

文件我们好理解,比如我们常见的office文件、图片、视频、压缩包等等都叫文件。

文件系统可以理解为能解析识别这些文件的文件。

昨天我老婆下载了一个压缩包,解压出来发现一个jar文件windows无法识别,这时候我们就要安装JAVA JDK,我们可以粗略地将JAVA JDK叫做能解析jar文件的文件系统(这个比喻不太恰当)。

那什么是根呢?就是Linux内核启动所挂载的第一个文件系统,所以这个根字也体现了它的重要性!

一句话总结:rootfs是Linux内核启动挂载的第一个文件系统,系统引导启动程序会在根文件系统挂载之后,从中把一些基本的初始化脚本和服务等加载到内存中去运行。

4.1 如何制作一个rootfs

制作rootfs我们要借用一个工具:BusyBox,忙碌盒子,这个盒子会提供大量Linux命令和工具的软件。

BusyBox的官网地址为: https://busybox.net/ ,最新的版本已经达到1.36.0。

编译

我们不追求最新,因为最新的往往会存在一些兼容性或未知BUG,使用我之前用过的1.32.0版本,下载链接:https://busybox.net/downloads/busybox-1.32.0.tar.bz2

解压、配置、编译BusyBox和编译Linux的流程差不多,此处就直接贴脚本了:

cd /home/damon/00_code/02_gitee/busy_box
tar -vxjf busybox-1.32.0.tar.bz2
/home/damon/00_code/02_gitee/busy_box/busybox-1.32.0
# 手动修改CROSS_COMPILE和ARCH
make defconfig
make menuconfig

编译

配置完后编译

make
# 编译好的东西,我把它安装到 /home/damon/00_code/02_gitee/busy_box/rootfs目录下
make install CONFIG_PREFIX=/home/damon/00_code/02_gitee/busy_box/rootfs

安装完毕,文件目录显示如下:

编译

可以看到rootfs中有3个文件夹和一个链接文件,具体作用先不展开,此时busybox的工作就完成了,但是此时的rootfs还不能使用,还缺少一些东西,接下来继续补充。

cd /home/damon/00_code/02_gitee/busy_box/rootfs
# 创建lib目录,并添加库文件(这些库文件都是从GCC中获取的)
mkdir lib
cd /usr/local/arm/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/libc/lib
cp *so* /home/damon/00_code/02_gitee/busy_box/rootfs/lib/ -d
cd /usr/local/arm/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/lib
cp *so* *.a /home/damon/00_code/02_gitee/busy_box/rootfs/lib/ -d
# 向usr/lib目录添加库文件
cd /usr/local/arm/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/libc/usr/lib
cp *so* *.a /home/damon/00_code/02_gitee/busy_box/rootfs/usr/lib/ -d
# 创建其他文件目录
cd /home/damon/00_code/02_gitee/busy_box/rootfs
mkdir dev proc mnt sys tmp etc  root

此时一个可以使用的rootfs便制作完成了,rootfs目录如下图所示:

编译

4.2 rootfs如何使用呢?

rootfs一般放到SD卡或者磁盘中,让内核去读取启动。我手头上没有SD卡,在PC上划出一块磁盘又比较浪费。我们就制作一个SD卡镜像,它是为qemu创建的虚拟SD卡。执行脚本如下:

cd /home/damon/00_code/02_gitee
# 制作rootfs.ext4.img文件
dd if=/dev/zero of=rootfs.ext4.img bs=1M count=500
# 图片截取的是32M,后面实际应用时,发现32M不够用,就修改成了500M
# 格式化
mkfs.ext4 rootfs.ext4.img

编译

# 将rootfs.ext4.img挂载到/mnt/rootfs
mkdir -p /mnt/rootfs
mount -t ext4 -o loop rootfs.ext4.img /mnt/rootfs
# 将rootfs内所有文件拷贝至rootfs.ext4.img
cp -a /home/damon/00_code/02_gitee/busy_box/rootfs/* /mnt/rootfs/
# 卸载
umount /mnt/rootfs
# 此时得到一个装有rootfs的镜像
/home/damon/00_code/02_gitee/rootfs.ext4.img

现在rootfs做好了,我们去修复上面的Panic.

5、 重新启动kernel

我们在原有的qemu命令基础上指定rootfs的镜像文件,重新启动:

qemu-system-arm -M vexpress-a9 -m 512M -kernel /home/damon/00_code/02_gitee/linux_5.4/linux-5.4/arch/arm/boot/zImage -dtb /home/damon/00_code/02_gitee/linux_5.4/linux-5.4/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -nographic -sd /home/damon/00_code/02_gitee/rootfs.ext4.img -append "root=/dev/mmcblk0 rw console=ttyAMA0"

启动日志如下:

编译

之前的kernel panic没有了,但是又有新的告警了,一般错误是必须要修复的,而告警内容是可以忽略的,除非影响到了使用和性能。

当前告警在循环打印无法打开,/dev/tty2 /dev/tty3 /dev/tty4, 看到这个我们显然知道是缺少串口设备,按照如下脚本添加,并更新镜像文件。

sudo mknod dev/tty2 c 5 1
sudo mknod dev/tty3 c 5 1
sudo mknod dev/tty4 c 5 1

此时Linux操作系统起来了,完整的详细的启动日志如下:

damon@ubuntu:~/00_code/02_gitee$
damon@ubuntu:~/00_code/02_gitee$
damon@ubuntu:~/00_code/02_gitee$ qemu-system-arm -M vexpress-a9 -m 512M -kernel /home/damon/00_code/02_gitee/linux_5.4/linux-5.4/arch/arm/boot/zImage -dtb /home/damon/00_code/02_gitee/linux_5.4/linux-5.4/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -nographic -sd /home/damon/00_code/02_gitee/rootfs.ext4.img -append "root=/dev/mmcblk0 rw console=ttyAMA0"
WARNING: Image format was not specified for '/home/damon/00_code/02_gitee/rootfs.ext4.img' and probing guessed raw.
         Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
         Specify the 'raw' format explicitly to remove the restrictions.
pulseaudio: set_sink_input_volume() failed
pulseaudio: Reason: Invalid argument
pulseaudio: set_sink_input_mute() failed
pulseaudio: Reason: Invalid argument
Booting Linux on physical CPU 0x0
Linux version 5.4.0+ (damon@ubuntu) (gcc version 9.2.1 20191025 (GNU Toolchain for the A-profile Architecture 9.2-2019.12 (arm-9.10))) #1 SMP Sat Jan 14 15:12:10 CST 2023
CPU: ARMv7 Processor [410fc090] revision 0 (ARMv7), cr=10c5387d
CPU: PIPT / VIPT nonaliasing data cache, VIPT nonaliasing instruction cache
OF: fdt: Machine model: V2P-CA9
Memory policy: Data cache writeback
Reserved memory: created DMA memory pool at 0x4c000000, size 8 MiB
OF: reserved mem: initialized node vram@4c000000, compatible id shared-dma-pool
cma: Reserved 16 MiB at 0x7f000000
CPU: All CPU(s) started in SVC mode.
percpu: Embedded 19 pages/cpu s45516 r8192 d24116 u77824
Built 1 zonelists, mobility grouping on.  Total pages: 130048
Kernel command line: root=/dev/mmcblk0 rw console=ttyAMA0
printk: log_buf_len individual max cpu contribution: 4096 bytes
printk: log_buf_len total cpu_extra contributions: 12288 bytes
printk: log_buf_len min size: 16384 bytes
printk: log_buf_len: 32768 bytes
printk: early log buf free: 14808(90%)
Dentry cache hash table entries: 65536 (order: 6, 262144 bytes, linear)
Inode-cache hash table entries: 32768 (order: 5, 131072 bytes, linear)
mem auto-init: stack:off, heap alloc:off, heap free:off
Memory: 492108K/524288K available (7168K kernel code, 425K rwdata, 1728K rodata, 1024K init, 155K bss, 15796K reserved, 16384K cma-reserved)
SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=4, Nodes=1
rcu: Hierarchical RCU implementation.
rcu:     RCU event tracing is enabled.
rcu:     RCU restricting CPUs from NR_CPUS=8 to nr_cpu_ids=4.
rcu: RCU calculated value of scheduler-enlistment delay is 10 jiffies.
rcu: Adjusting geometry for rcu_fanout_leaf=16, nr_cpu_ids=4
NR_IRQS: 16, nr_irqs: 16, preallocated irqs: 16
GIC CPU mask not found - kernel will fail to boot.
GIC CPU mask not found - kernel will fail to boot.
L2C: platform modifies aux control register: 0x02020000 -  > 0x02420000
L2C: DT/platform modifies aux control register: 0x02020000 -  > 0x02420000
L2C-310 enabling early BRESP for Cortex-A9
L2C-310 full line of zeros enabled for Cortex-A9
L2C-310 dynamic clock gating disabled, standby mode disabled
L2C-310 cache controller enabled, 8 ways, 128 kB
L2C-310: CACHE_ID 0x410000c8, AUX_CTRL 0x46420001
random: get_random_bytes called from start_kernel+0x310/0x4bc with crng_init=0
sched_clock: 32 bits at 24MHz, resolution 41ns, wraps every 89478484971ns
clocksource: arm,sp804: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 1911260446275 ns
Failed to initialize '/smb@4000000/motherboard/iofpga@7,00000000/timer@12000': -22
smp_twd: clock not found -2
Console: colour dummy device 80x30
Calibrating local timer... 97.07MHz.
Calibrating delay loop... 1179.64 BogoMIPS (lpj=5898240)
pid_max: default: 32768 minimum: 301
Mount-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)
Mountpoint-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)
CPU: Testing write buffer coherency: ok
CPU0: Spectre v2: using BPIALL workaround
CPU0: thread -1, cpu 0, socket 0, mpidr 80000000
Setting up static identity map for 0x60100000 - 0x60100060
rcu: Hierarchical SRCU implementation.
smp: Bringing up secondary CPUs ...
smp: Brought up 1 node, 1 CPU
SMP: Total of 1 processors activated (1179.64 BogoMIPS).
CPU: All CPU(s) started in SVC mode.
devtmpfs: initialized
VFP support v0.3: implementor 41 architecture 3 part 30 variant 9 rev 0
clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 19112604462750000 ns
futex hash table entries: 1024 (order: 4, 65536 bytes, linear)
NET: Registered protocol family 16
DMA: preallocated 256 KiB pool for atomic coherent allocations
cpuidle: using governor ladder
hw-breakpoint: debug architecture 0x4 unsupported.
Serial: AMBA PL011 UART driver
10009000.uart: ttyAMA0 at MMIO 0x10009000 (irq = 29, base_baud = 0) is a PL011 rev1
printk: console [ttyAMA0] enabled
1000a000.uart: ttyAMA1 at MMIO 0x1000a000 (irq = 30, base_baud = 0) is a PL011 rev1
1000b000.uart: ttyAMA2 at MMIO 0x1000b000 (irq = 31, base_baud = 0) is a PL011 rev1
1000c000.uart: ttyAMA3 at MMIO 0x1000c000 (irq = 32, base_baud = 0) is a PL011 rev1
OF: amba_device_add() failed (-19) for /smb@4000000/motherboard/iofpga@7,00000000/wdt@f000
OF: amba_device_add() failed (-19) for /memory-controller@100e0000
OF: amba_device_add() failed (-19) for /memory-controller@100e1000
OF: amba_device_add() failed (-19) for /watchdog@100e5000
irq: type mismatch, failed to map hwirq-75 for interrupt-controller@1e001000!
SCSI subsystem initialized
usbcore: registered new interface driver usbfs
usbcore: registered new interface driver hub
usbcore: registered new device driver usb
Advanced Linux Sound Architecture Driver Initialized.
clocksource: Switched to clocksource arm,sp804
NET: Registered protocol family 2
tcp_listen_portaddr_hash hash table entries: 512 (order: 0, 6144 bytes, linear)
TCP established hash table entries: 4096 (order: 2, 16384 bytes, linear)
TCP bind hash table entries: 4096 (order: 3, 32768 bytes, linear)
TCP: Hash tables configured (established 4096 bind 4096)
UDP hash table entries: 256 (order: 1, 8192 bytes, linear)
UDP-Lite hash table entries: 256 (order: 1, 8192 bytes, linear)
NET: Registered protocol family 1
RPC: Registered named UNIX socket transport module.
RPC: Registered udp transport module.
RPC: Registered tcp transport module.
RPC: Registered tcp NFSv4.1 backchannel transport module.
hw perfevents: enabled with armv7_cortex_a9 PMU driver, 1 counters available
workingset: timestamp_bits=30 max_order=17 bucket_order=0
squashfs: version 4.0 (2009/01/31) Phillip Lougher
jffs2: version 2.2. (NAND) © 2001-2006 Red Hat, Inc.
9p: Installing v9fs 9p2000 file system support
io scheduler mq-deadline registered
io scheduler kyber registered
drm-clcd-pl111 1001f000.clcd: assigned reserved memory node vram@4c000000
drm-clcd-pl111 1001f000.clcd: using device-specific reserved memory
drm-clcd-pl111 1001f000.clcd: initializing Versatile Express PL111
drm-clcd-pl111 1001f000.clcd: core tile graphics present
drm-clcd-pl111 1001f000.clcd: this device will be deactivated
Error: Driver 'vexpress-muxfpga' is already registered, aborting...
drm-clcd-pl111 10020000.clcd: initializing Versatile Express PL111
drm-clcd-pl111 10020000.clcd: DVI muxed to daughterboard 1 (core tile) CLCD
physmap-flash 40000000.flash: physmap platform flash device: [mem 0x40000000-0x43ffffff]
40000000.flash: Found 2 x16 devices at 0x0 in 32-bit bank. Manufacturer ID 0x000000 Chip ID 0x000000
Intel/Sharp Extended Query Table at 0x0031
Using buffer write method
physmap-flash 40000000.flash: physmap platform flash device: [mem 0x44000000-0x47ffffff]
40000000.flash: Found 2 x16 devices at 0x0 in 32-bit bank. Manufacturer ID 0x000000 Chip ID 0x000000
Intel/Sharp Extended Query Table at 0x0031
Using buffer write method
Concatenating MTD devices:
(0): "40000000.flash"
(1): "40000000.flash"
into device "40000000.flash"
physmap-flash 48000000.psram: physmap platform flash device: [mem 0x48000000-0x49ffffff]
libphy: Fixed MDIO Bus: probed
libphy: smsc911x-mdio: probed
smsc911x 4e000000.ethernet eth0: MAC Address: 52:54:00:12:34:56
isp1760 4f000000.usb: bus width: 32, oc: digital
isp1760 4f000000.usb: NXP ISP1760 USB Host Controller
isp1760 4f000000.usb: new USB bus registered, assigned bus number 1
isp1760 4f000000.usb: Scratch test failed.
isp1760 4f000000.usb: can't setup: -19
isp1760 4f000000.usb: USB bus 1 deregistered
usbcore: registered new interface driver usb-storage
rtc-pl031 10017000.rtc: registered as rtc0
mmci-pl18x 10005000.mmci: Got CD GPIO
mmci-pl18x 10005000.mmci: Got WP GPIO
mmci-pl18x 10005000.mmci: mmc0: PL181 manf 41 rev0 at 0x10005000 irq 25,26 (pio)
ledtrig-cpu: registered to indicate activity on CPUs
usbcore: registered new interface driver usbhid
usbhid: USB HID core driver
aaci-pl041 10004000.aaci: ARM AC'97 Interface PL041 rev0 at 0x10004000, irq 24
aaci-pl041 10004000.aaci: FIFO 512 entries
oprofile: using arm/armv7-ca9
NET: Registered protocol family 17
9pnet: Installing 9P2000 support
Registering SWP/SWPB emulation handler
Error: Driver 'vexpress-muxfpga' is already registered, aborting...
drm-clcd-pl111 10020000.clcd: initializing Versatile Express PL111
drm-clcd-pl111 10020000.clcd: DVI muxed to daughterboard 1 (core tile) CLCD
Error: Driver 'vexpress-muxfpga' is already registered, aborting...
drm-clcd-pl111 10020000.clcd: initializing Versatile Express PL111
drm-clcd-pl111 10020000.clcd: DVI muxed to daughterboard 1 (core tile) CLCD
input: AT Raw Set 2 keyboard as /devices/platform/smb@4000000/smb@4000000:motherboard/smb@4000000:motherboard:iofpga@7,00000000/10006000.kmi/serio0/input/input0
mmc0: new SD card at address 4567
Error: Driver 'vexpress-muxfpga' is already registered, aborting...
drm-clcd-pl111 10020000.clcd: initializing Versatile Express PL111
drm-clcd-pl111 10020000.clcd: DVI muxed to daughterboard 1 (core tile) CLCD
mmcblk0: mmc0:4567 QEMU! 500 MiB
Error: Driver 'vexpress-muxfpga' is already registered, aborting...
drm-clcd-pl111 10020000.clcd: initializing Versatile Express PL111
drm-clcd-pl111 10020000.clcd: DVI muxed to daughterboard 1 (core tile) CLCD
rtc-pl031 10017000.rtc: setting system clock to 2023-01-14T15:23:16 UTC (1673709796)
ALSA device list:
  #0: ARM AC'97 Interface PL041 rev0 at 0x10004000, irq 24
input: ImExPS/2 Generic Explorer Mouse as /devices/platform/smb@4000000/smb@4000000:motherboard/smb@4000000:motherboard:iofpga@7,00000000/10007000.kmi/serio1/input/input2
Error: Driver 'vexpress-muxfpga' is already registered, aborting...
drm-clcd-pl111 10020000.clcd: initializing Versatile Express PL111
drm-clcd-pl111 10020000.clcd: DVI muxed to daughterboard 1 (core tile) CLCD
random: fast init done
EXT4-fs (mmcblk0): recovery complete
EXT4-fs (mmcblk0): mounted filesystem with ordered data mode. Opts: (null)
ext4 filesystem being mounted at /root supports timestamps until 2038 (0x7fffffff)
VFS: Mounted root (ext4 filesystem) on device 179:0.
Freeing unused kernel memory: 1024K
Run /sbin/init as init process
random: crng init done
can't run '/etc/init.d/rcS': No such file or directory
Please press Enter to activate this console.
Please press Enter to activate this console.
Please press Enter to activate this console.
Please press Enter to activate this console.
/ #

我们完成了Linux源码的编译和启动,但是此时我们还没有看一行代码!

接下来,我们就开始阅读第一行代码并分析kernel的启动流程。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分