查看原文
其他

嵌入式未来一段时间还是Linux天下,一位嵌入式er初探Linux kernel经验

嵌入式ARM 嵌入式ARM 2021-01-31



首先阐述一下我为什么想学习一下Linux kernel。最早是因为对嵌入式的一时脑热,我买了开发板,买了不少资料,前前后后投进去了1000多了。不过好歹还是有点回报的,虽然还没有怎么着调,但又似乎拓宽了不小的知识面。

 

慢慢的我发现,对于从学软件入手的我来说,硬件知识的薄弱是个不容忽视的缺陷,毕竟软硬件间的代沟还是不小的,就像现在的老爹和fashion闺女一样,鸿沟还是忽视不得的。这有点让我望而却步,不过多大的困难都无法阻挡我的前进啊。我对策略稍作调整,因为我发现,嵌入式无论是现在,还是未来一段时间都还是Linux的天下;另外是真正的做Linux的大牛们似乎也都有涉足embedded system 的经历。这就使得仅仅会管理Linux系统和服务,简单的用几个Shell commands ,编译安装几个Linux应用,读懂几个Makefile……根本满足不了需求;另外也为了不让学习的OS知识只是空洞的理论。这都要求着自己必须有编写自己Shell程序的能力;要求着可以在Linux做程序开发的能力;要求着可以自己往kernel中附加自定义的系统调用、重组内核、添加自写驱动……的能力。这就要求着必须深入了解,正如任何一门技术一样,接触久了你就有种相地层实现挖掘的冲动。

 

调整后的策略就是先把Linux这个OS的机制弄明白,才可以迁移定制满足需求的系统,才可以写出高效率的Linux应用。整体路线就是农村包围城市,不断补充必要知识,循序渐进,最终呈现星星之火可以燎原之势。额,扯得够远。废话不多说,下面开始。

 

Kernel入门,要选本好的入门书籍,我从网上download一本《Linux内核设计与实现》。这本书简单易读,有OS基础和Linux应用基础的人一读即懂,我现已阅过3章,感觉很不错,另外配合《Linux操作系统内核实习》效果更佳。我想尽可能通过更加通俗的形式向你阐述kernel的机理,让我们一起如喝凉水般拿下kernel。

 

首先介绍一下内核源码的根目录描述:

arch(architecture)    特定体系结构的源码

crypto                         crypto API

Documention               内核源码文档

drivers                        设备驱动程序

fs                               VFS和各种文件系统

include                        内核头文件

init                              内核引导和初始化

ipc                              进程间通讯代码

kernel                          像调度程序这样的核心子系统

lib                               通用内核函数

mm                             内存管理子系统和VM

net                              网络子系统

scripts                         编译内核所用到的脚本

security                       Linux安全模块

sound                          语音子系统

usr                              早期用户空间代码(所谓的initramfs)

 这里只是简单阐述个目录及系统模块分布。随着慢慢地学习我相信一定可以把它们搞明白是怎么一回事的。

 

另外,你需要明白一些Linux必备的一些常识性名词解释知识,这里罗列一些名词,不明白的不再一一阐述,自己百度,Google:管态、目态、内核空间、用户空间、POSIX、system V、GNU、GPL、GNOME、KDE、QT、GTK+、openGL、shell、awk、Makefile、CC、GCC、G++、GDB、Perl……


下面来介绍一下内核开发和应用程序开发的差别:

  • 内核编程时不能访问C库(因为Linux下很多C库函数是对Linux系统调用的封装,自身怎么可以调用自身呢?)

  • 内核编程时必须使用GNU C。

  • 内核编程时缺乏像用户空间那样的内存保护机制。

  • 内核编程时浮点数很难使用。

  • 内核只有一个很小的定长堆栈。

  • 由于内核支持异步中断、抢占和SMP,因此必须时刻注意同步和并发。

  • 要考虑可移植性的重要性。

对以上这几点进行描述。

1、首先就是内核不能访问C库的问题,你想啊Linux下很多C库函数是对Linux系统调用的封装,自身怎么可以调用自身呢?这里的系统调用学过OS的应该都清楚,是系统应用给用户提供的编程接口,注意这里的对象是用户。注意啦,程序员也是分等级的,在kernel级别的编程(这里指纯kernel编程),你已经看不到系统调用,此时你的职责可能就是为系统添加一个系统调用(后边会讲到),且C库是应用层对底层系调(系统调用,为了便于我打字,后边可能会多次出现)的封装,从逻辑的角度你也该明白了吧。那么问题就出来了,没有c库怎么办,还谈什么模块化,难不成都自己写?这就是接下来的问题。


2、既然kernel不能调用C库,那么它就得拥有自己独立的c语言库,这样才能高内聚低耦合,并且提升其安全性,所以就用了GNU C 。短小精悍效率高,毕竟是专才专用。这里需要注意一点,内核中没有实现printf();但是有功能更为强大的printk();其实也谈不上功能强大(因为printf()本身就很强大,尤其是在调试时,这里也显出了printk的优势),它和printf()的显著区别就是printk()允许通过指定一个标志来设置优先级。Syslog会根据这个优先级标志来决定什么地方显示这条系统消息。如:


printk( KERN_ERR “This is an error!”);    //不理解吧,我也不理解,后边内核调试时肯定还会将这东西,不怕。


3、内核编程时缺乏像用户空间那样的内存保护机制。

你在做什么?内核好不好,这是一个OS的核心部分,控制着整个系统的运转,自然要有处理协调整个系统的权利,在内核coding的东西就是OS核心的一部分,是给别人或者自己在OS的上一层用的。既然你是把握这一切的,且又是在硬件基础上的第一层抽象,另外还把握着全局,内存的控制自然也不能束缚你,就像在公司工作一样,领导要在可能的范围内尽可能的下放权力,下属才能发挥他的极致,估计kernel也是这样,内存访问,包括其他的内核结构都不对出于内核的你进行束缚,当然也没有进行相应的保护机制。因为这是的内核是你的,你没必要傻逼到写程序让自己的系统崩溃吧,哈哈。这也留了一个问题,就是在coding过程中要斟酌好啊。


4、内核编程时浮点数很难使用。这里你需要知道的是在用户空间的进程进行浮点操作时,kernel会完成从整数到浮点数的模式转换,一般是通过捕获陷阱并作相应的处理的实现的。//陷阱可以算是一种特殊的异常,是从用户态进入内核态的途径,以后会进一步介绍。


与用户空间不同的是,kernel并不能完美的支持浮点操作,因为自身不能陷入自身。在kernel中使用浮点数时,除了要人工保存和恢复浮点寄存器,还有很多琐碎的事要做。直截了的说就是:不要在kernel中使用浮点数!!!


5、内核只有一个很小的定长堆栈。在x86上,kernel的栈是在编译时配置的,可为4k或者8k,且每个处理器都有自己的栈。为什么这么小呢?大了不可以么?我的理解是,现在都在追求微内核,这是一方面原因,还有就是内核也是一个软件,只不过是包含了硬件抽象且富含大量系统管理功能的进程,而其他功能的进程(系统调用,其他内核进程),在调度是,存在内核抢占和代替内核执行的情况,这是要将要执行的进程的上下文切换过来,当然这些数据都会出现在栈里边,你想如果栈很大,那么操作时内存访问地址会很长,访问时占内存且费时间,会降低效率,所以要尽可能的小且灵活……


6、由于内核支持异步中断、抢占和SMP,因此必须时刻注意同步和并发。这不仅仅是linux kernel ,是所有现有的多进程/多线程并发OS都要注意的,包括多线程编程也一样,你就把kernel想成是一个多线程的执行程序就OK了,不难理解。


7、要考虑可移植性的重要性。这个更不用多说了,因为这是linux的一个灰常显著的特征,大到企业的服务器,小到嵌入式的android,IP camera……等很多东西不同平台构架的cpu……应该是目前跑的平台最多的OS了,所以他的内核要有足够的可移植性,似乎有点java的感觉......


由于篇幅原因,今天就介绍到这里了,感兴趣的同学明天继续收看哦!~


推荐阅读:

1.你的Coding style规范否?大神谈谈为Linux内核写驱动的编码规范

2.嵌入式Linux下最常用的C语言编译器GCC命令详解

3.嵌入式Linux设备驱动之总线、设备、驱动之间有何关系?

4.嵌入式Linux上的应用程序开发只有高手才能完成?一起来打破这种“迷信”


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存