问一:编译出来的Linux内核镜像(".\build\arch\arm64\boot\Image"),可以单独运行吗?
答案是能,但是加载完就提示panic,然后死掉了。
原因是:
内核代码加载完后,一定要切换到低权限模式运行,
内核是设计来为 运行于低CPU权限的 "userSpace app" 服务的。
内核切换到低权限模式去运行的方式,就是去运行一个普通程序——用户态的可执行文件。
注意了,这是必须的!
用户态的可执行文件,下文称为 userapp。
问二:内核加载完成后,去哪找这个userapp?
答案是挂载的rootfs文件系统中,挂载路径是 "/",
要“正常”运行内核,必须要有一个包含着userapp的文件系统,userapp数量至少得有一个以上。
问三:最小的linux userapp / 最小的rootfs镜像,应该是这样!
userapp 源文件名:loop.S 一个 arm64汇编代码写的源文件。
.data
.text
.globl main
main:
b . /** 等效于 while(1); **/
编译、打包:
编译:
bin/aarch64-none-elf-gcc -c -fno-builtin -o init.o loop.S
bin/aarch64-none-elf-ld -e main init.o -o init
打包:
find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img
即可得到一个史上最最最最小的rootfs——能让内核正常启动哦。。。
只不过它不会有任何输出,它的作用,仅仅是让内核不会panic死掉。
重要的编译参数:-fno-builtin,让编译器不要链接它自带的libc代码,不论是动态的还是静态的。
因为这个loop.S啥也没干,不用libc中的代码。
这份代码用C语言同样可以实现:
int main()
{
while(1);
return 0;
}
编译的指令为:
bin/aarch64-none-elf-gcc -c -fno-builtin -o init.o init.c
bin/aarch64-none-elf-ld -e main init.o -o init
find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img
用qemu-aacrh64.exe加载这份rootfs.img+内核镜像:
"/mnt/d/Program Files/qemu/qemu-system-aarch64.exe" \
-kernel ".\build\arch\arm64\boot\Image" \
-initrd ".\build\rootfs.img" \
-append "root=/dev/ram0 rootfstype=ramfs rw init=/init" \
-nographic -machine virt-6.2,gic-version=3,secure=on,virtualization=on -cpu cortex-a53 -m 1024 -semihosting
最重要的部分来了。
上面这个userapp要如何调用内核提供的函数——syscall机制在arm64上如何实现?
直接上代码:loop.S 进化为 -> init.S :
.data
msg:
.ascii "Hello, ARM64!\n"
len = . - msg
.text
.globl main
main:
/* syscall write(int fd, const void *buf, size_t count) */
mov x0, #1 /* fd := STDOUT_FILENO */
ldr x1, =msg /* buf := msg */
ldr x2, =len /* count := len */
mov w8, #64 /* write is syscall #64 */
svc #0 /* invoke syscall */
b .
/* syscall exit(int status) */
mov x0, #0 /* status := 0 */
mov w8, #93 /* exit is syscall #1 */
svc #0 /* invoke syscall */
这段userapp代码的作用,大致等效于:
write(STDOUT, "Hello, ARM64!\n",字符串长度);
或者
printf("Hello, ARM64!\n");
编译指令:
bin/aarch64-none-elf-gcc -c -fno-builtin -o init.o init.S
bin/aarch64-none-elf-ld -e main init.o -o init
find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img
ARM64汇编代码中,系统调用的关键指令是 svc 。
上面的汇编代码,调用了内核提供的 write 函数—— 以这种汇编代码封装的方式,libc 运行库封装了内核实现的一两千函数。
libc 就是这么来的,类似的库,还有很多,newlibc, glibc, bonic ...
总结:
libc 是内核函数的用户态封装,供用户态的app调用内核函数用的。
它仅仅是内核的一层皮肤。
所以,你知道 gcc 加上 -static 参数去编译一份小exe源码是在静态链接些什么了吗?
其它:
PID为1的init进程,不能调用内核exit()函数,调用了就会结束init进程返回内核,
然后内核接着就是panic ....