泰晓科技 -- 聚焦 Linux - 追本溯源,见微知著!
网站地址:https://tinylab.org

泰晓Linux知识星球:1300+知识点,520+用户
请稍侯

Linux Lab:难以抗拒的十大理由 V2.0

Wu Zhangjin 创作于 2020/05/15

By Falcon of TinyLab.org May 15, 2020

Linux Lab 是一套用于 Linux 内核学习、开发和测试的即时实验室,可以极速搭建和使用,功能强大,用法简单!

Linux Lab Boot example

经过 3 年多的开发与迭代,Linux Lab 已经发布了 v0.4 rc3 版,其易用性和功能逐渐强大,非常推荐各位 Linux 内核和嵌入式 Linux 开发者尝试。

下面列举了十大使用 Linux Lab 的案例。

1 学习 C 语言

安装完以后,如果选择 bash 登陆方式,会立即进入到 Linux 命令行。

1.1 C 语言开发

里头已经安装好了必备的编辑器 vim 和编译器 gcc,可以直接上手 C 语言开发。

  1. $ tools/docker/bash linux-lab
  2. ubuntu@linux-lab:/labs/linux-lab$ cd examples/c/hello/
  3. ubuntu@linux-lab:/labs/linux-lab/examples/c/hello$ gcc -o hello hello.c
  4. hello.c Makefile README.md
  5. ubuntu@linux-lab:/labs/linux-lab/examples/c/hello$ ./hello
  6. Hello, World!

1.2 简单 C 语言工程管理 Makefile

另外,里面已经提供了一个简单的 Makefile 例子,可以直接通过 make 生成 hello.i, hello.s 和 hello.o。

  1. ubuntu@linux-lab:/labs/linux-lab/examples/c/hello$ make hello.i
  2. ubuntu@linux-lab:/labs/linux-lab/examples/c/hello$ make hello.s
  3. ubuntu@linux-lab:/labs/linux-lab/examples/c/hello$ make hello.o

在这个基础上,结合任何一本经典的 C 语言书籍,就可以学习基本的语法、算法和 Posix API 用法了。

2 学习汇编语言和处理器指令集

Linux Lab 不只准备了 x86 的 gcc 编译器,而且准备了其他几大主流处理器架构的编译器,学习各种架构编译器不在话下,而且可以立即使用 qemu 来执行。

2.1 X86 汇编

先来看一段 x86 的汇编:

  1. $ tools/docker/bash linux-lab
  2. ubuntu@linux-lab:/labs/linux-lab$ cd examples/assembly/
  3. ubuntu@linux-lab:/labs/linux-lab/examples/assembly$ ls
  4. aarch64 arm mips64el mipsel powerpc powerpc64 README.md riscv32 riscv64 x86 x86_64
  5. ubuntu@linux-lab:/labs/linux-lab/examples/assembly/x86$ make
  6. as --32 -o x86-hello.o x86-hello.s
  7. ld -m elf_i386 -o x86-hello x86-hello.o
  8. /labs/linux-lab/examples/assembly/x86/x86-hello
  9. Hello, world!
  10. ubuntu@linux-lab:/labs/linux-lab/examples/assembly/x86$ cat x86-hello.s
  11. .data # section declaration
  12. msg:
  13. .string "Hello, world!\n"
  14. len = . - msg # length of our dear string
  15. .text # section declaration
  16. # we must export the entry point to the ELF linker or
  17. .global _start # loader. They conventionally recognize _start as their
  18. # entry point. Use ld -e foo to override the default.
  19. _start:
  20. # write our string to stdout
  21. movl $len,%edx # third argument: message length
  22. movl $msg,%ecx # second argument: pointer to message to write
  23. movl $1,%ebx # first argument: file handle (stdout)
  24. movl $4,%eax # system call number (sys_write)
  25. int $0x80 # call kernel
  26. # and exit
  27. movl $0,%ebx # first argument: exit code
  28. movl $1,%eax # system call number (sys_exit)
  29. int $0x80 # call kernel

2.2 ARM 汇编

arm 的也不在话下:

  1. ubuntu@linux-lab:/labs/linux-lab/examples/assembly/arm$ cat arm-hello.s
  2. .data
  3. msg:
  4. .ascii "Hello, ARM!\n"
  5. len = . - msg
  6. .text
  7. .globl _start
  8. _start:
  9. /* syscall write(int fd, const void *buf, size_t count) */
  10. mov %r0, $1 /* fd -> stdout */
  11. ldr %r1, =msg /* buf -> msg */
  12. ldr %r2, =len /* count -> len(msg) */
  13. mov %r7, $4 /* write is syscall #4 */
  14. swi $0 /* invoke syscall */
  15. /* syscall exit(int status) */
  16. mov %r0, $0 /* status -> 0 */
  17. mov %r7, $1 /* exit is syscall #1 */
  18. swi $0 /* invoke syscall */
  19. ubuntu@linux-lab:/labs/linux-lab/examples/assembly/arm$ make
  20. arm-linux-gnueabi-as -o arm-hello.o arm-hello.s
  21. arm-linux-gnueabi-ld -o arm-hello arm-hello.o
  22. Hello, ARM!

2.3 RISC-V 汇编

其他架构也可以类似使用,有个别架构,比如 RISC-V,需要先切到 Linux Lab 根目录下载工具链:

  1. $ tools/docker/bash linux-lab
  2. ubuntu@linux-lab:/labs/linux-lab$
  3. ubuntu@linux-lab:/labs/linux-lab$ make BOARD=riscv64/virt
  4. [ riscv64/virt ]:
  5. ARCH = riscv
  6. XARCH = riscv64
  7. CPU = any
  8. SMP = 4
  9. MEM = 1024M
  10. QEMU = v4.0.0
  11. QEMU_US = 1
  12. LINUX = v5.1
  13. BUILDROOT = 2019.05
  14. NETDEV_LIST = virtio
  15. NETDEV = virtio
  16. SERIAL = ttyS0
  17. NET9PDEV = virtio-9p-device
  18. ROOTDEV_LIST = /dev/vda /dev/ram0 /dev/nfs
  19. ROOTDEV = /dev/vda
  20. FSTYPE = ext2
  21. PORIIMG = fw_jump.elf
  22. PKIMAGE = /labs/linux-lab/boards/riscv64/virt/bsp/kernel/v5.1/fw_jump.elf
  23. KRN_ADDR = 0x80200000
  24. ORIIMG = arch/riscv/boot/Image
  25. KIMAGE = /labs/linux-lab/output/riscv64/linux-v5.1-virt/arch/riscv/boot/Image
  26. ROOTFS = /labs/linux-lab/boards/riscv64/virt/bsp/root/2019.05/rootfs.cpio.gz
  27. HROOTFS = /labs/linux-lab/boards/riscv64/virt/bsp/root/2019.05/rootfs.ext2
  28. QTOOL = /labs/linux-lab/boards/riscv64/virt/bsp/qemu/v4.0.0/bin/qemu-system-riscv64
  29. CCORI = gnu-mcu-eclipse
  30. ubuntu@linux-lab:/labs/linux-lab$ make bsp
  31. Downloading bsp source ...
  32. Previous HEAD position was c6c47f0... README: Update usage
  33. HEAD is now at e2963d2... buildroot: 2019.05: speed up root building
  34. ubuntu@linux-lab:/labs/linux-lab$ make toolchain
  35. [ gnu-mcu-eclipse 8.2.0-2.2-20190521-0004 ]:
  36. Remote.: https://github.com/gnu-mcu-eclipse/riscv-none-gcc/releases/download/v8.2.0-2.2-20190521/gnu-mcu-eclipse-riscv-none-gcc-8.2.0-2.2-20190521-0004-centos64.tgz
  37. Local..: /labs/linux-lab/prebuilt/toolchains/riscv64/gnu-mcu-eclipse/riscv-none-gcc/8.2.0-2.2-20190521-0004/bin
  38. Tool...: riscv-none-embed-gcc
  39. Version: riscv-none-embed-gcc (GNU MCU Eclipse RISC-V Embedded GCC, 64-bit) 8.2.0

之后同样可以使用:

  1. ubuntu@linux-lab:/labs/linux-lab$ cd examples/assembly/riscv32
  2. ubuntu@linux-lab:/labs/linux-lab/examples/assembly/riscv32$ make
  3. ... riscv-none-embed-as -o riscv32-hello.o riscv32-hello.s
  4. ... riscv-none-embed-ld -o riscv32-hello riscv32-hello.o
  5. Hello Risc-V

3 免费使用某块虚拟开发板

3.1 7 大架构 + 16 块板子

Linux Lab 已经集成了 7 大架构的 16 块开发板,平均每个架构支持两款以上。常用的开发板基本都有了,包括 32 位和 64 位架构基本可以任选。

所有的板子都有:

  • 编译好的 Qemu 的模拟器
  • 编译好的 文件系统以及配置文件
  • 编译好的 内核镜像、dtb 以及配置文件
  • 可直接启动到串口的 Qemu 启动脚本

所有仓库都可以单独下载,可以在 Linux Lab 内使用,也可以单独使用。这里是已经集成到主线的部分:

  1. ubuntu@linux-lab:/labs/linux-lab$ cat .gitmodules | grep qemu-
  2. url = https://gitee.com/tinylab/qemu-aarch64-raspi3/
  3. url = https://gitee.com/tinylab/qemu-aarch64-virt.git
  4. url = https://gitee.com/tinylab/qemu-arm-versatilepb.git
  5. url = https://gitee.com/tinylab/qemu-arm-vexpress-a9.git
  6. url = https://gitee.com/tinylab/qemu-i386-pc.git
  7. url = https://gitee.com/tinylab/qemu-mipsel-malta.git
  8. url = https://gitee.com/tinylab/qemu-ppc-g3beige.git
  9. url = https://gitee.com/tinylab/qemu-riscv32-virt.git
  10. url = https://gitee.com/tinylab/qemu-riscv64-virt.git
  11. url = https://gitee.com/tinylab/qemu-x86_64-pc.git
  12. url = https://gitee.com/tinylab/qemu-arm-mcimx6ul-evk.git

3.2 任选一块板子立即启动

如果要使用模块板子,可以直接:

  1. $ make BOARD=aarch64/virt
  2. $ make boot
  3. sudo env PATH=/labs/linux-lab/boards/aarch64/virt/bsp/qemu/v4.0.0/bin/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin qemu-system-aarch64 -M virt -m 1024M -net nic,model=virtio -net tap -device virtio-net-device,netdev=net0,mac=30:d7:8a:81:bc:8a -netdev tap,id=net0 -smp 2 -bios /labs/linux-lab/output/aarch64/uboot-v2019.10-virt/u-boot.bin -no-reboot -device loader,file=tftpboot/env.img,addr=0x49000000 -drive if=pflash,file=tftpboot/pflash.img,format=raw,unit=1 -drive if=none,file=/labs/linux-lab/boards/aarch64/virt/bsp/root/2019.11.1/rootfs.ext2,format=raw,id=virtio-vda -device virtio-blk-device,drive=virtio-vda -nographic -cpu cortex-a57 -serial mon:stdio -machine virt,gic_version=3 -machine type=virt,virtualization=true -d guest_errors -nodefaults
  4. U-Boot 2019.10-dirty (May 10 2020 - 23:59:50 +0800)
  5. DRAM: 1 GiB
  6. Flash: 128 MiB
  7. *** Warning - bad CRC, using default environment
  8. In: pl011@9000000
  9. Out: pl011@9000000
  10. Err: pl011@9000000
  11. Net:
  12. Warning: virtio-net#31 using MAC address from ROM
  13. eth0: virtio-net#31
  14. Hit any key to stop autoboot: 0
  15. ## Warning: defaulting to text format
  16. ## Flattened Device Tree blob at 44000000
  17. Booting using the fdt blob at 0x44000000
  18. Loading Device Tree to 000000007edf3000, end 000000007edf7c95 ... OK
  19. Starting kernel ...
  20. Booting Linux on physical CPU 0x0000000000 [0x411fd070]
  21. Linux version 5.1.0 (ubuntu@linux-lab) (gcc version 7.4.1 20181213 [linaro-7.4-2019.02 revision 56ec6f6b99cc167ff0c2f8e1a2eed33b1edc85d4] (Linaro GCC 7.4-2019.02)) #1 SMP Wed May 6 03:15:08 CST 2020
  22. Machine model: linux,dummy-virt
  23. earlycon: pl11 at MMIO 0x0000000009000000 (options '')
  24. printk: bootconsole [pl11] enabled
  25. efi: Getting EFI parameters from FDT:
  26. efi: UEFI not found.
  27. psci: probing for conduit method from DT.
  28. psci: PSCIv0.2 detected in firmware.
  29. psci: Using standard PSCI v0.2 function IDs
  30. psci: Trusted OS migration not required
  31. random: get_random_bytes called from start_kernel+0xa8/0x4b4 with crng_init=0
  32. percpu: Embedded 23 pages/cpu s55000 r8192 d31016 u94208
  33. Detected PIPT I-cache on CPU0
  34. CPU features: detected: ARM erratum 832075
  35. CPU features: detected: GIC system register CPU interface
  36. CPU features: detected: EL2 vector hardening
  37. Built 1 zonelists, mobility grouping on. Total pages: 258048
  38. Kernel command line: route=172.17.0.3 iface=eth0 rw fsck.repair=yes rootwait root=/dev/vda earlycon console=ttyAMA0
  39. Dentry cache hash table entries: 131072 (order: 8, 1048576 bytes)
  40. Inode-cache hash table entries: 65536 (order: 7, 524288 bytes)
  41. Memory: 1018660K/1048576K available (6140K kernel code, 790K rwdata, 1472K rodata, 832K init, 335K bss, 29916K reserved, 0K cma-reserved)
  42. SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=2, Nodes=1
  43. rcu: Hierarchical RCU implementation.
  44. rcu: RCU event tracing is enabled.
  45. rcu: RCU restricting CPUs from NR_CPUS=256 to nr_cpu_ids=2.
  46. rcu: RCU calculated value of scheduler-enlistment delay is 25 jiffies.
  47. rcu: Adjusting geometry for rcu_fanout_leaf=16, nr_cpu_ids=2
  48. NR_IRQS: 64, nr_irqs: 64, preallocated irqs: 0
  49. GICv3: GIC: Using split EOI/Deactivate mode
  50. GICv3: Distributor has no Range Selector support
  51. GICv3: no VLPI support, no direct LPI support
  52. GICv3: CPU0: found redistributor 0 region 0:0x00000000080a0000
  53. arch_timer: cp15 timer(s) running at 62.50MHz (phys).
  54. clocksource: arch_sys_counter: mask: 0xffffffffffffff max_cycles: 0x1cd42e208c, max_idle_ns: 881590405314 ns
  55. sched_clock: 56 bits at 62MHz, resolution 16ns, wraps every 4398046511096ns
  56. Console: colour dummy device 80x25
  57. Calibrating delay loop (skipped), value calculated using timer frequency.. 125.00 BogoMIPS (lpj=250000)
  58. pid_max: default: 32768 minimum: 301
  59. Mount-cache hash table entries: 2048 (order: 2, 16384 bytes)
  60. Mountpoint-cache hash table entries: 2048 (order: 2, 16384 bytes)
  61. *** VALIDATE proc ***
  62. *** VALIDATE cgroup1 ***
  63. *** VALIDATE cgroup2 ***
  64. ASID allocator initialised with 32768 entries
  65. rcu: Hierarchical SRCU implementation.
  66. EFI services will not be available.
  67. smp: Bringing up secondary CPUs ...
  68. Detected PIPT I-cache on CPU1
  69. GICv3: CPU1: found redistributor 1 region 0:0x00000000080c0000
  70. CPU1: Booted secondary processor 0x0000000001 [0x411fd070]
  71. smp: Brought up 1 node, 2 CPUs
  72. SMP: Total of 2 processors activated.
  73. CPU features: detected: 32-bit EL0 Support
  74. CPU features: detected: CRC32 instructions
  75. CPU: All CPU(s) started at EL2
  76. alternatives: patching kernel code
  77. devtmpfs: initialized
  78. clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 7645041785100000 ns
  79. futex hash table entries: 512 (order: 3, 32768 bytes)
  80. DMI not present or invalid.
  81. NET: Registered protocol family 16
  82. vdso: 2 pages (1 code @ (____ptrval____), 1 data @ (____ptrval____))
  83. hw-breakpoint: found 6 breakpoint and 4 watchpoint registers.
  84. DMA: preallocated 256 KiB pool for atomic allocations
  85. Serial: AMBA PL011 UART driver
  86. 9000000.pl011: ttyAMA0 at MMIO 0x9000000 (irq = 40, base_baud = 0) is a PL011 rev1
  87. printk: console [ttyAMA0] enabled
  88. printk: console [ttyAMA0] enabled
  89. printk: bootconsole [pl11] disabled
  90. printk: bootconsole [pl11] disabled
  91. SCSI subsystem initialized
  92. clocksource: Switched to clocksource arch_sys_counter
  93. NET: Registered protocol family 2
  94. tcp_listen_portaddr_hash hash table entries: 512 (order: 1, 8192 bytes)
  95. TCP established hash table entries: 8192 (order: 4, 65536 bytes)
  96. TCP bind hash table entries: 8192 (order: 5, 131072 bytes)
  97. TCP: Hash tables configured (established 8192 bind 8192)
  98. UDP hash table entries: 512 (order: 2, 16384 bytes)
  99. UDP-Lite hash table entries: 512 (order: 2, 16384 bytes)
  100. NET: Registered protocol family 1
  101. RPC: Registered named UNIX socket transport module.
  102. RPC: Registered udp transport module.
  103. RPC: Registered tcp transport module.
  104. RPC: Registered tcp NFSv4.1 backchannel transport module.
  105. hw perfevents: enabled with armv8_pmuv3 PMU driver, 5 counters available
  106. workingset: timestamp_bits=62 max_order=18 bucket_order=0
  107. NFS: Registering the id_resolver key type
  108. Key type id_resolver registered
  109. Key type id_legacy registered
  110. 9p: Installing v9fs 9p2000 file system support
  111. Block layer SCSI generic (bsg) driver version 0.4 loaded (major 252)
  112. io scheduler mq-deadline registered
  113. io scheduler kyber registered
  114. cacheinfo: Unable to detect cache hierarchy for CPU 0
  115. virtio_blk virtio0: [vda] 14336 512-byte logical blocks (7.34 MB/7.00 MiB)
  116. NET: Registered protocol family 10
  117. Segment Routing with IPv6
  118. sit: IPv6, IPv4 and MPLS over IPv4 tunneling driver
  119. NET: Registered protocol family 17
  120. NET: Registered protocol family 15
  121. 9pnet: Installing 9P2000 support
  122. Key type dns_resolver registered
  123. registered taskstats version 1
  124. hctosys: unable to open rtc device (rtc0)
  125. EXT4-fs (vda): mounting ext2 file system using the ext4 subsystem
  126. EXT4-fs (vda): warning: mounting unchecked fs, running e2fsck is recommended
  127. EXT4-fs (vda): mounted filesystem without journal. Opts: (null)
  128. VFS: Mounted root (ext2 filesystem) on device 254:0.
  129. devtmpfs: mounted
  130. Freeing unused kernel memory: 832K
  131. Run /sbin/init as init process
  132. EXT4-fs (vda): re-mounted. Opts: block_validity,barrier,user_xattr
  133. Starting syslogd: OK
  134. Starting klogd: OK
  135. Running sysctl: OK
  136. Initializing random number generator: OK
  137. Saving random seed: random: dd: uninitialized urandom read (512 bytes read)
  138. OK
  139. Starting network: eth: eth0, ip: 172.17.0.181, gw: 172.17.0.3
  140. OK
  141. Welcome to Linux Lab
  142. linux-lab login: root
  143. #
  144. # reboot -f

所有默认准备好的文件系统帐号是 root,密码为空,直接敲击回车即可进去。大部分板子都可以用 reboot -f 退出,个别板子可能会卡死,可以用 “CTRL+a x” 退出 Qemu。

3.3 传统的 Qemu 启动脚本:boot.sh

当然,也可以直接用最简单易懂的 boot.sh 脚本,这个脚本甚至可以在主机下直接使用:

  1. ubuntu@linux-lab:/labs/linux-lab$ cd boards/aarch64/virt/bsp/
  2. ubuntu@linux-lab:/labs/linux-lab/boards/aarch64/virt/bsp$ ./boot.sh
  3. Welcome to Linux Lab
  4. linux-lab login: root
  5. #
  6. # reboot -f

不过,boot.sh 可没 “make boot” 灵活方便。比如说,想通过 tftp 加载镜像,并通过 nfsboot 加载文件系统,要用 boot.sh 得手动改一堆不是那么容易理解和上手的参数,特别地,要配置网络并没有那么简单。

3.4 无比优雅的 “make boot”

用 Linux Lab 的 “make boot” 是这么的优雅:

  1. ubuntu@linux-lab:/labs/linux-lab$ make boot ROOTDEV=/dev/nfs BOOTDEV=tftp

4 Linux 内核开发

直接使用编译好的当然不够酷,得能自己修改内核并编译内核才行。

4.1 编译 Linux v5.6

假如我们想学习 x86_64 架构上的 Linux v5.6 内核,可以切到 x86_64/pc 这块板子,然后升级到内核 v5.6:

  1. $ make BOARD=x86_64/pc
  2. $ make kernel-clone LINUX_NEW=v5.6
  3. $ make kernel-cleanup
  4. $ make kernel

4.2 编译其他版本 Linux

如果想用默认的版本,则不需要使用 kernel-clone,查看已经验证过的内核版本。

  1. $ make list linux
  2. v3.2 v5.0.10 v5.0.21 [v5.1] v5.4 v5.4.29
  3. $ make local-config LINUX=v5.4
  4. v3.2 v5.0.10 v5.0.21 v5.1 [v5.4] v5.4.29
  5. $ make kernel

需要注意的是,kernel-clone 会使用当前板子默认配置的内核配置文件,如果内核版本差异太大,现有配置文件可能不会工作,建议直接从 linux-stable 目录下复制一份现有配置到 boards/<BOARD>/bsp/configs 目录下。之后再配置和编译。

如果是更早的 Linux 版本,可能会涉及到编译器不兼容,需要修改部分代码或者调整编译器版本。

4.3 更换编译器版本

首先我们查看哪些版本可以用,包括内置的版本和外置的版本,内置的版本用 CCORI=internal 指定后,配置 GCC 即可,外部的直接指定 CCORI 进行切换。

查看当前架构支持的编译器,可以看到暂时没有配置外部的版本,但是内部的有三个:

  1. $ make gcc-list
  2. Listing prebuilt toolchain ...
  3. [ internal gcc-8 ]:
  4. Remote.:
  5. Local..:
  6. Tool...: gcc
  7. Version: gcc (Ubuntu 8.3.0-16ubuntu3~14.04.2) 8.3.0
  8. More...: /usr/bin/gcc-4.4 /usr/bin/gcc-4.8 /usr/bin/gcc-8

直接切换:

  1. $ make gcc-switch GCC=4.4

或者更持久的配置:

  1. $ make local-config GCC=4.4

如果要为某个版本配特定 GCC:

  1. $ make local-config GCC[LINUX_v5.6]=8

查看记录:

  1. $ make local-edit
  2. LINUX := v5.6
  3. GCC := 4.4
  4. GCC[LINUX_v5.6] := 8

如果发现某个已经配置好的内核必须用特定 GCC 才能编译,请往 Linux Lab 发送 PR。

4.4 修改代码后编译启动

接着,随便改一点你想修改的代码,然后就可以重新编译并启动了:

  1. $ git diff init/main.c
  2. diff --git a/init/main.c b/init/main.c
  3. index ee4947a..acd6c71 100644
  4. --- a/init/main.c
  5. +++ b/init/main.c
  6. @@ -815,6 +815,7 @@ asmlinkage __visible void __init start_kernel(void)
  7. build_all_zonelists(NULL);
  8. page_alloc_init();
  9. + pr_info("Hello Linux\n");
  10. pr_notice("Kernel command line: %s\n", saved_command_line);
  11. /* parameters may set static keys */
  12. jump_label_init();
  13. $ make kernel
  14. $ make boot
  15. ...
  16. Welcome to Linux Lab
  17. linux-lab login: root
  18. #
  19. # dmesg | grep Hello
  20. [ 0.447766] Hello Linux
  21. # reboot -f
  22. [ 107.481353] reboot: Restarting system
  23. [ 107.482477] reboot: machine restart

堪称完美,我们改动的地方生效了,日志被打印了出来。

4.5 单独编译某个文件并查看效果

比如说刚刚修改了 init/main.c,希望立即查看预处理的结果、汇编以及二进制文件。

预处理的结果:

  1. $ make kernel-run init/main.i
  2. $ grep Hello -ur output/x86_64/linux-v5.6-pc/init/main.i
  3. printk("\001" "6" "Hello Linux\n");

汇编:

  1. $ make kernel-run init/main.s
  2. $ grep -A5 -B1 Hello -ur output/x86_64/linux-v5.6-pc/init/main.s
  3. .LC27:
  4. .string "\0016Hello Linux\n"
  5. .LC28:
  6. .string "\0015Kernel command line: %s\n"
  7. .LC29:
  8. .string "Booting kernel"
  9. .LC30:
  10. --
  11. call page_alloc_init #
  12. # /labs/linux-lab/linux-stable/init/main.c:818: pr_info("Hello Linux\n");
  13. movq $.LC27, %rdi #,
  14. call printk #
  15. # /labs/linux-lab/linux-stable/init/main.c:819: pr_notice("Kernel command line: %s\n", saved_command_line);
  16. movq saved_command_line(%rip), %rsi # saved_command_line,
  17. movq $.LC28, %rdi #,

目标文件:

  1. $ make kernel-run init/main.o
  2. $ ls output/x86_64/linux-v5.6-pc/init/main.o

5 Linux 内核模块开发

要学习和开发一个 Linux 内核模块是如此的简单。

5.1 编译一个准备好的 hello 模块

examples/hello 下面已经准备了一个极其简单的内核模块,直接编译:

  1. $ make module module=hello
  2. Building module: hello ...
  3. LOG: m=hello ; M=/labs/linux-lab/modules/hello
  4. Current using module is /labs/linux-lab/modules/hello.
  5. to compile modules under linux-stable, use 'make kernel-modules'.
  6. make O=/labs/linux-lab/output/x86_64/linux-v5.6-pc -C linux-stable ARCH=x86 LOADADDR= CROSS_COMPILE= V= CONFIG_INITRAMFS_SOURCE= -j4 modules_prepare
  7. GEN Makefile
  8. DESCEND objtool
  9. CALL /labs/linux-lab/linux-stable/scripts/atomic/check-atomics.sh
  10. CALL /labs/linux-lab/linux-stable/scripts/checksyscalls.sh
  11. CC [M] /labs/linux-lab/modules/hello/hello.o
  12. MODPOST 1 modules
  13. CC [M] /labs/linux-lab/modules/hello/hello.mod.o
  14. LD [M] /labs/linux-lab/modules/hello/hello.ko

5.2 修改、编译并简单测试该模块

修改:

  1. ubuntu@linux-lab:/labs/linux-lab$ git diff modules/hello/
  2. diff --git a/modules/hello/hello.c b/modules/hello/hello.c
  3. index 59f1c55..2321857 100644
  4. --- a/modules/hello/hello.c
  5. +++ b/modules/hello/hello.c
  6. @@ -4,14 +4,14 @@
  7. static int __init my_hello_init(void)
  8. {
  9. - pr_info("hello module init\n");
  10. + pr_info("mymodule init\n");
  11. return 0;
  12. }
  13. static void __exit my_hello_exit(void)
  14. {
  15. - pr_info("hello module exit\n");
  16. + pr_info("mymodule exit\n");
  17. }
  18. module_init(my_hello_init);

编译:

  1. ubuntu@linux-lab:/labs/linux-lab$ make module module=hello

启动后简单验证:

  1. ubuntu@linux-lab:/labs/linux-lab$ make module-install module=hello
  2. ubuntu@linux-lab:/labs/linux-lab$ make root-rebuild
  3. Welcome to Linux Lab
  4. linux-lab login: root
  5. #
  6. # modprobe hello
  7. [ 15.777975] hello: loading out-of-tree module taints kernel.
  8. [ 15.811822] mymodule init
  9. #
  10. # modprobe -r hello
  11. [ 27.279818] mymodule exit
  12. # reboot -f
  13. [ 37.218398] reboot: Restarting system
  14. [ 37.218612] reboot: machine restart

自动验证(无需手动安装,直接取代上述所有命令):

  1. ubuntu@linux-lab:/labs/linux-lab$ make test module=hello ROOTDEV=/dev/ram0

如果有些动作做过了,想直接测试,更简单:

  1. ubuntu@linux-lab:/labs/linux-lab$ make test module=hello ROOTDEV=/dev/ram0 FEATURE_INIT=0

6 Linux 内核特性开发

先列出当前已经验证过的 feature:

  1. ubuntu@linux-lab:/labs/linux-lab$ make list features
  2. [ feature/linux ]:
  3. + 9pnet
  4. - config
  5. - config.aarch64.virt.broken
  6. - config.pc
  7. - config.versatilepb
  8. + cmdline_size
  9. - patch.sh
  10. - README.md
  11. + core
  12. - debug
  13. * config
  14. - initrd
  15. * config
  16. - module
  17. * config
  18. - nfsroot
  19. * config
  20. * README.md
  21. + ftrace
  22. - v2.6.36
  23. * config
  24. * env.g3beige
  25. * env.malta
  26. * env.pc
  27. * env.versatilepb
  28. - v2.6.37
  29. * config
  30. * env.g3beige
  31. + gcs
  32. - v2.6.36
  33. * config
  34. * env.g3beige
  35. * env.malta
  36. * env.pc
  37. * env.versatilepb
  38. * patch
  39. + kft
  40. - v2.6.36
  41. * config
  42. * config.pc
  43. * env.malta
  44. * env.pc
  45. * patch
  46. + rt
  47. - config
  48. - v5.0.21
  49. * download.sh
  50. * patch-5.0.21-rt16.patch
  51. * patch-5.0.21-rt16.patch.xz
  52. - v5.2
  53. * download.sh
  54. * patch-5.2-rt1.patch
  55. * patch-5.2-rt1.patch.gz
  56. + uksm
  57. - v2.6.38
  58. * config
  59. * patch
  60. * version

接着来跑一下 rt preempt 特性:

  1. ubuntu@linux-lab:/labs/linux-lab$ make BOARD=x86_64/pc
  2. ubuntu@linux-lab:/labs/linux-lab$ make local-config LINUX=v5.0.21
  3. ubuntu@linux-lab:/labs/linux-lab$ make kernel-cleanup
  4. ubuntu@linux-lab:/labs/linux-lab$ make kernel-defconfig
  5. ubuntu@linux-lab:/labs/linux-lab$ make feature feature=rt
  6. ubuntu@linux-lab:/labs/linux-lab$ make kernel-olddefconfig
  7. ubuntu@linux-lab:/labs/linux-lab$ make kernel
  8. ubuntu@linux-lab:/labs/linux-lab$ make boot
  9. [ 26.744772] 001: Run /init as init process
  10. Starting syslogd: OK
  11. Starting klogd: OK
  12. Initializing random number generator... [ 31.520793] 001: random: dd: uninitialized urandom read (512 bytes read)
  13. done.
  14. Starting network: [ 36.352869] 002: ifconfig (1146) used greatest stack depth: 13904 bytes left
  15. eth: eth0, ip: 172.17.0.61, gw: 172.17.0.3
  16. [ 37.091929] 001: ip (1150) used greatest stack depth: 13600 bytes left
  17. OK
  18. Welcome to Linux Lab
  19. linux-lab login: root
  20. #
  21. # dmesg | grep -i Preempt
  22. [ 0.000000] 000: Linux version 5.0.21-rt16+ (ubuntu@linux-lab) (gcc version 8.3.0 (Ubuntu 8.3.0-16ubuntu3~14.04.2)) #2 SMP PREEMPT RT Fri May 15 04:38:39 CST 2020
  23. [ 3.539891] 000: rcu: Preemptible hierarchical RCU implementation.

7 Uboot 开发

7.1 支持 Uboot 的板子

当前已经验证可以在 arm32 和 arm64 位的板子上进行 Uboot 开发。已经验证过的板子有:

  • arm/versatilepb
  • arm/vexpress-a9
  • aarh64/virt

7.2 启动时禁用 Uboot

默认情况下,会直接启动 Uboot,然后加载内核再启动。

如果想直接启动内核:

  1. ubuntu@linux-lab:/labs/linux-lab$ make BOARD=arm/vexpress-a9
  2. ubuntu@linux-lab:/labs/linux-lab$ make boot U=0

7.3 启动时切换不同的设备加载内核等镜像

  1. ubuntu@linux-lab:/labs/linux-lab$ make list bootdev
  2. tftp sdcard sd mmc [flash] pflash
  3. ubuntu@linux-lab:/labs/linux-lab$ make boot BOOTDEV=mmc
  4. mkfs.fat 3.0.26 (2014-03-07)
  5. sudo env PATH=/labs/linux-lab/boards/arm/vexpress-a9/bsp/qemu/v4.1.1/bin/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin qemu-system-arm -M vexpress-a9 -m 1024M -net nic,model=lan9118 -net tap -smp 1 -kernel /labs/linux-lab/output/arm/uboot-v2020.04-vexpress-a9/u-boot -no-reboot -drive if=sd,file=tftpboot/sd.img,format=raw,id=sd0 -drive if=pflash,file=tftpboot/pflash.img,format=raw -nographic
  6. U-Boot 2020.04-dirty (May 06 2020 - 16:19:09 +0800)
  7. DRAM: 1 GiB
  8. WARNING: Caches not enabled
  9. Flash: 128 MiB
  10. MMC: MMC: 0
  11. *** Warning - bad CRC, using default environment
  12. In: serial
  13. Out: serial
  14. Err: serial
  15. Net: smc911x-0
  16. Hit any key to stop autoboot: 0
  17. ## Warning: defaulting to text format
  18. 4471936 bytes read in 1360 ms (3.1 MiB/s)
  19. 1430238 bytes read in 442 ms (3.1 MiB/s)
  20. 14087 bytes read in 22 ms (625 KiB/s)
  21. ## Booting kernel from Legacy Image at 60003000 ...
  22. Image Name: Linux-5.1.0
  23. Image Type: ARM Linux Kernel Image (uncompressed)
  24. Data Size: 4471872 Bytes = 4.3 MiB
  25. Load Address: 60003000
  26. Entry Point: 60003000
  27. Verifying Checksum ... OK
  28. ## Loading init Ramdisk from Legacy Image at 60900000 ...
  29. Image Name:
  30. Image Type: ARM Linux RAMDisk Image (uncompressed)
  31. Data Size: 1430174 Bytes = 1.4 MiB
  32. Load Address: 00000000
  33. Entry Point: 00000000
  34. Verifying Checksum ... OK
  35. ## Flattened Device Tree blob at 60500000
  36. Booting using the fdt blob at 0x60500000
  37. Loading Kernel Image
  38. Loading Ramdisk to 7fd18000, end 7fe7529e ... OK
  39. Loading Device Tree to 7fd11000, end 7fd17706 ... OK
  40. Starting kernel ...
  41. Booting Linux on physical CPU 0x0
  42. Linux version 5.1.0 (ubuntu@524b6f3a0481) (gcc version 4.7.3 (Ubuntu/Linaro 4.7.3-12ubuntu1)) #3 SMP Thu May 30 08:44:37 UTC 2019
  43. CPU: ARMv7 Processor [410fc090] revision 0 (ARMv7), cr=10c5387d
  44. CPU: PIPT / VIPT nonaliasing data cache, VIPT nonaliasing instruction cache
  45. ...

7.4 修改、编译并启动 Uboot

默认先编译一遍,会自动下载、配置和编译:

  1. ubuntu@linux-lab:/labs/linux-lab$ make uboot

修改代码:

  1. ubuntu@linux-lab:/labs/linux-lab$ cd u-boot
  2. ubuntu@linux-lab:/labs/linux-lab$ git diff
  3. diff --git a/common/main.c b/common/main.c
  4. index 3a657c3..cd32bc5 100644
  5. --- a/common/main.c
  6. +++ b/common/main.c
  7. @@ -48,6 +48,8 @@ void main_loop(void)
  8. cli_init();
  9. + printf("Hello Uboot\n");
  10. +
  11. if (IS_ENABLED(CONFIG_USE_PREBOOT))
  12. run_preboot_environment_command();

编译并启动,启动过程中立即按下任意键进入 Uboot 命令行:

  1. ubuntu@linux-lab:/labs/linux-lab$ make boot
  2. sudo env PATH=/labs/linux-lab/boards/arm/vexpress-a9/bsp/qemu/v4.1.1/bin/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin qemu-system-arm -M vexpress-a9 -m 1024M -net nic,model=lan9118 -net tap -smp 1 -kernel /labs/linux-lab/output/arm/uboot-v2020.04-vexpress-a9/u-boot -no-reboot -drive if=pflash,file=tftpboot/pflash.img,format=raw -nographic
  3. U-Boot 2020.04-dirty (May 15 2020 - 02:46:50 +0800)
  4. DRAM: 1 GiB
  5. WARNING: Caches not enabled
  6. Flash: 128 MiB
  7. MMC: MMC: 0
  8. *** Warning - bad CRC, using default environment
  9. In: serial
  10. Out: serial
  11. Err: serial
  12. Net: smc911x-0
  13. Hello Uboot <-- 这里有打印我们添加的字符串
  14. Hit any key to stop autoboot: 0
  15. =>
  16. => reset
  17. resetting ...

8 玩转文件系统

8.1 通过 Buildroot 构建文件系统

目前已经内置支持通过 Buildroot 编译文件系统,当然,也可以用 Busybox 来自己制作。

比如说 aarch64/virt,已经支持到 2019.11.1,可以直接切过去编译:

  1. ubuntu@linux-lab:/labs/linux-lab$ make BOARD=aarch64/virt
  2. ubuntu@linux-lab:/labs/linux-lab$ make list root
  3. [2016.05] 2019.11.1
  4. ubuntu@linux-lab:/labs/linux-lab$ make local-config BUILDROOT=2019.11.1
  5. tools/board/config.sh BUILDROOT=2019.11.1 /labs/linux-lab/boards/arm/vexpress-a9/.labconfig v5.1;
  6. BUILDROOT := 2019.11.1 /labs/linux-lab/boards/arm/vexpress-a9/.labconfig
  7. ubuntu@linux-lab:/labs/linux-lab$ make list root
  8. 2016.05 [2019.11.1]
  9. ubuntu@linux-lab:/labs/linux-lab$ make root

调整配置:

  1. ubuntu@linux-lab:/labs/linux-lab$ make root-menuconfig

调整 Busybox 配置:

  1. ubuntu@linux-lab:/labs/linux-lab$ make root-run busybox-menuconfig

8.2 通过 Linux Lab 运行 mini Ubuntu

同样以 aarch64/virt 为例:

  1. ubuntu@linux-lab:/labs/linux-lab$ make BOARD=aarch64/virt

下载一份 mini 版本的 Ubuntu:

  1. $ mkdir tmp && cd tmp
  2. $ wget -c https://rcn-ee.com/rootfs/eewiki/minfs/ubuntu-20.04-minimal-armhf-2020-05-10.tar.xz
  3. $ tar Jxf ubuntu-20.04-minimal-armhf-2020-05-10.tar.xz
  4. $ cd ubuntu-20.04-minimal-armhf-2020-05-10
  5. $ mkdir rootfs
  6. $ tar xf armhf-rootfs-ubuntu-focal.tar -C rootfs/

然后以 nfsboot 的方式加载,这里先来删除掉 root 的密码(当前 ubuntu 帐号密码无法登陆):

  1. $ make boot ROOTDIR=$PWD/tmp/ubuntu-20.04-minimal-armhf-2020-05-10/rootfs/ ROOTDEV=/dev/nfs XKCLI="init=/bin/bash" U=0
  2. root # passwd -d root

接着启动并用 root 登陆:

  1. $ make boot ROOTDIR=$PWD/tmp/ubuntu-20.04-minimal-armhf-2020-05-10/rootfs/ ROOTDEV=/dev/nfs U=0
  2. ...
  3. Ubuntu 20.04 LTS arm ttyAMA0
  4. default username:password is [ubuntu:temppwd]
  5. arm login: root
  6. Last login: Thu Jan 1 00:01:44 UTC 1970 on ttyAMA0
  7. root@arm:~#
  8. root@arm:~#

9 Uboot 和 Linux 调试

9.1 Uboot 调试

已经提前配置好了 gdb 调试脚本, 可以直接调试 Uboot。

开一个终端:

  1. $ tools/docker/bash linux-lab
  2. ubuntu@linux-lab:/labs/linux-lab$ make debug uboot

根据上一个提示,再开一个:

  1. $ tools/docker/bash linux-lab
  2. ubuntu@linux-lab:/labs/linux-lab$ env PATH=/labs/linux-lab/prebuilt/toolchains/aarch64/gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin aarch64-linux-gnu-gdb /labs/linux-lab/output/aarch64/uboot-v2019.10-virt/u-boot
  3. (gdb) bt
  4. GNU gdb (Linaro_GDB-2019.02) 8.2.1.20190122-git
  5. Reading symbols from /labs/linux-lab/output/aarch64/uboot-v2019.10-virt/u-boot...done.
  6. Waiting for 1 secs...
  7. Executing gdb commands in local .gdbinit ... my own script
  8. (gdb) target remote :1234
  9. 0xffffff80100876dc in ?? ()
  10. (gdb) break _start
  11. Breakpoint 1 at 0x0: file /labs/linux-lab/u-boot/arch/arm/cpu/armv8/start.S, line 31.
  12. gdb bt
  13. #0 0xffffff80100876dc in ?? ()
  14. #1 0x0000000000000070 in save_boot_params_ret () at /labs/linux-lab/u-boot/arch/arm/cpu/armv8/start.S:117
  15. Backtrace stopped: previous frame identical to this frame (corrupt stack?)

9.2 Linux 调试

也能类似直接调试内核:

  1. ubuntu@linux-lab:/labs/linux-lab$ make debug kernel

如果没有打开调试选项:

  1. ubuntu@linux-lab:/labs/linux-lab$ make feature feture=debug
  2. ubuntu@linux-lab:/labs/linux-lab$ make kernel-olddefconfig
  3. ubuntu@linux-lab:/labs/linux-lab$ make kernel
  4. ubuntu@linux-lab:/labs/linux-lab$ make debug kernel

10 灵活多样的登陆方式

10.1 本地登陆

以上都是用 tools/docker/bash 在本地登陆,大部分实验都能开展,但是跟图形相关的,比如 LCD 显示这些,就需要 vnc 登陆。

  1. $ sudo apt-get install -y vinagre
  2. $ tools/docker/vnc linux-lab
  3. LOG: VNC login information:
  4. VNC Clients: vinagre xvnc4viewer gvncviewer xtightvncviewer
  5. VNC Server: 172.17.0.3
  6. Normal Password: ******
  7. Viewonly Password: ******
  8. Available VNC Clients:
  9. 1 vinagre
  10. 2 xvnc4viewer
  11. 3 gvncviewer
  12. 4 xtightvncviewer
  13. LOG: Choose the vnc client: 1
  14. 1 vinagre
  15. LOG: Running 'vinagre --vnc-scale --geometry 800x600 172.17.0.3'

之后,vinagre 启动并弹出密码输入框,直接粘贴即可。

10.2 远程登陆

如果想在服务器上运行 Linux Lab,那么启动完,需要获取到服务器的外网 IP 地址,配置到 .host_name 即可。

webssh 登陆方式:

  1. $ echo x.y.z.i > .host_name
  2. $ tools/docker/webssh linux-lab
  3. LOG: Please login via ssh client with:
  4. SSH_IP: 172.17.0.3
  5. SSH_PORT: 22
  6. User: ubuntu
  7. Password: ******
  8. Workdir: //labs/linux-lab
  9. LOG: Or access via web browser:
  10. http://x.y.z.i:4433?ssh=ssh://ubuntu:******@172.17.0.3:22

webvnc 登陆方式:

  1. $ tools/docker/webvnc linux-lab
  2. Please login via VNC Client with:
  3. IP: 172.17.0.3
  4. User: 7827c9 (Only for noVNC)
  5. Password: ****** (Normal)
  6. Password: ****** (View)
  7. Login for noVNC web client:
  8. * Normal: http://x.y.z.i:6080/?u=******&p=******
  9. * View: http://x.y.z.i:6080/?r=************
  10. Login for local VNC clients:
  11. * IP: 172.17.0.3
  12. * Normal: ******
  13. * View: ******
  14. The Other Login methods:
  15. * Bash: tools/docker/bash linux-lab
  16. * SSH: tools/docker/ssh linux-lab
  17. * WebSSH: tools/docker/webssh linux-lab
  18. Note: Please make sure network available outside and then replace 'localhost' with the external ip or domain name.
  19. Then 'echo $ip > .host_name' or 'echo $domain_name > .host_name'
  20. Note: firefox, safari and edge work, but google chrome web browser is preferable.


Read Album:

Read Related:

Read Latest: