哈喽,我是子牙老师。今天咱们聊聊这个话题吧,Linux作为当今科技世界的地基,我们越来越接近真理了,有木有?
这个文章的角度,你可能全网都很难找到第二篇如此系统讲透这个问题的文章
你可能想问:你之前不是写过操作系统吗,怎么又写一个Linux系统?
我之前写的,或者说做的课程,或者你们从这个网站上下载的,都是操作系统内核
操作系统内核,是一套管理硬件的程序,用户是用不了的。但是内核给开发用户态程序提供了丰富的API。基于Linux内核的API,开发出了Linux系统,即用户可以使用的用户态。其中包括:ubuntu、centos、redhat、Android、鸿蒙……Linux内核牛不牛逼?
基于Linux内核提供的API,基于Linux操作系统,又诞生了今天科技世界的基础设施:AI层、redis、MySQL、nginx、docker…还有很多很多关键基础设施,比如kvm……
我之前说Linux是当今科技世界的地基,有人说我乱说,真是无知者无畏。我后来想明白了,所处高度不同,眼前看到的风景自然不同,何必与夏虫语冰
写一个操作系统内核,是你学习计算机专业课《操作系统导论》《计算机组成原理》《数据结构》……或者说计算机学科所有专业课,最顶级的方式!而且生动有趣形象,那些看不见摸不着的理论,你在写的过程中,都变得具象化了:页表、内存映射、缺页异常、写时复制、进程切换、线程调度、线程上下文、中断触发……
那怎么开发一个用户态的Linux系统呢?以下,enjoy
缘起
看到这种图后,我的大脑开始抛出无数的问题
我后面理清了思绪,大概有这些问题:
- 用户态能看到的顶层进程是1号进程,那有没有0号进程呢?
- 0号进程在Linux内核的设计中承担了什么样的角色?
- 我记得最开始玩Linux的时候,用户态的顶层进程是init,与当前systemd之间什么关系?
- 用户态的顶层进程systemd,在内核态长啥样
- 自己开发的Linux系统,是如何与Linux内核关联起来的,比如busybox
- Linux内核是如何进入用户态的
- Linux内核是如何基于一个可执行文件起一个进程的
……
如果你恰好也有这些问题,受实力所限,无法得到答案,那太好了。BTW,技术还行哦,对Linux的理解能到这个程度
瓦特?你都不知道我在说什么?那你现在的水平,在AI时代,是非常危险的!赶紧去提升实力吧!ChatGPT能帮你写代码?ChatGPT能帮任何使用他的人写代码,你跟别人的区别在哪呢?或者说,谁能更好的使用AI呢?是那些对行业理解得更广更深的人,对吧
同样使用ChatGPT,你觉得你跟我,谁能最大化发挥ChatGPT的价值?
Linux始祖进程
看《玫瑰的故事》,黄亦玫的女儿名字叫太初,我觉得蛮好听的。太初其实就是始祖的意思,只不过含蓄一些。你们觉得,黄亦玫希望她的女儿是谁的始祖呢?我觉得应该是自己的始祖吧,永远做自己,不被世俗污染与束缚,满满的大爱
《道德经》中说“道生一,一生二,二生三,三生万象”。在道家的角度,零即为道。我猜Linux内核如果是中国人写的,它的进程结构应该是这样的
那Linux内核的始祖进程是谁呢?init_task,即0号进程。这个进程不像其他进程,是通过函数__do_fork创建出来的,这个进程是内核开发工程师编织出来的,如图
你是不是想问:为什么是编织,而不是通过程序创建?那你是否想过,今天的科技世界的源头,一定有一个编译器,是用二进制写出来的。一样的道理,总得有鸡才有蛋。你是不是想问,没有蛋哪来的鸡?鸡可以造出来,或者由其他物种变异而来,蛋就真的没办法
CPU何时切入0号进程执行的呢?这个有点特别,CPU不会切入0号进程,内核开发工程师会将0号进程与BSP核(CPU启动核)进行绑定。这个操作是在Linux内核的很早期完成的,在创建1号进程、启用所有AP核之前,在这两个阶段之前,很关键。代码没找着,自己写代码测的,Linux内核是这么干的
关于0号进程,还有一个关键点就是,当BSP核执行完所有初始化动作,0号进程就进化为idle进程,就是当CPU没事干的时候执行的进程,对应的代码:CPU进入低功耗,响应中断唤醒
更特别的是,进化为idle进程的0号进程可以被多个CPU核同时运行!
Linux1号进程
1号进程就好理解很多了。但是如果你想搞明白它与用户态,与一个可执行文件是如何关联上的,牵扯的东西就比较多了。但是于我而言,没啥难度,毕竟对于Linux,我已经建立了较为完整的认知
先看1号进程的创建,0号进程创建了两个内核线程:kernel_init(1号)、kthread(2号)
kernel_init内核线程就是1号进程systemd的内核态,kernel_init是如何进入用户态的呢?三个途径
接下来从源码层面讲解内核线程kernel_init进入用户态细节,其实就是函数run_init_process
函数run_init_process的调用链,我已经画好了图。
进入用户态
从上一段观点可知,Linux内核进入用户态,默认会去找init程序,先说对init的处理吧
run_init_process调用链中,会进入一个非常核心的函数:load_elf_binary,这个函数就是完成了进程的创建,内存空间长这样。其实就是将硬盘上的init程序,按照进程内存空间布局规范,实现程序内存化,又称进程态
看代码区吧,这是kernel_init内核线程进入用户态要执行的地方。理论上每个进程的代码区都是从0x40000开始的,但是实际中略有偏差,我们看init程序的代码入口点:0x400890,记住这个位置,后面会讲到
这时候进程就创建完了,等待调度。还有一点讲下,所有进程的入口函数都不是可执行文件中的那个,而是ret_from_fork,在这设置的
这个函数非常非常非常关键,就是所有进程由内核态进入用户态的那座桥!关于内核第一次由内核态切用户态,这个不懂汇编、中断的小伙伴可能看不懂了。这里是模拟中断跨态切换实现的。我就不展开讲了,直接看代码跟内存吧。当init进程获得调度,会进入函数ret_from_fork
在这个函数的执行过程中,我们找到init程序的入口点0x400890就找到答案了
至此,秘密全部揭开!太畅快了!
对了,还有init与systemd,它们都是内核线程kernel_init进入用户态的存在形式。早期是init,现在是systemd
如果你看懂了这篇文章,你就知道如何写一个Linux系统。