问一:编译出来的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 ...

android kernel详情 kernel app_android kernel详情

 

总结:
libc 是内核函数的用户态封装,供用户态的app调用内核函数用的。
它仅仅是内核的一层皮肤。
所以,你知道 gcc 加上 -static 参数去编译一份小exe源码是在静态链接些什么了吗?

其它:
PID为1的init进程,不能调用内核exit()函数,调用了就会结束init进程返回内核,
然后内核接着就是panic ....