第三章 嵌入式Linux内核裁剪和制定的讲解及实例

3.1 Linux 内核开发概述

本文所说的内核开发其实是指结合实际开发需求对嵌入式linux的产品的内核和驱动进行相关的修改等开发工作,以此来满足项目的需求,这个含义与Linux团队的内核开发有很大差异。
本文所说的是对嵌入式linux内核进行二次开发,但仍然需要相关的研发人员需要拥有以下的一些技能点,才能够很好的胜任内核二次开发的工作:
(1)熟悉操作系统的基础知识,能够理解操作系统原理;
(2)熟练掌握C语言的使用,因为在Linux内核中绝大部分是使用C语言来完成的。
(3)对Linux内核源码的结构布局要有一个大概的了解;
(4)常用的设备驱动开发工程师们还需要对外围设备的工作原理以及驱动的编写方法需要有一定了解。

3.2 内核源码概述

3.2.1 内核源码目录组成

本文讲解的Linux内核源码以及后续的程序代码部分是主要针对Linux3.4.39的,当前的Linux操作系统一般由进程管理、内存管理、文件系统、驱动程序以及网络协议栈等组成的,如下为该内核源码的主要目录构成。
(1)arch目录:该文件夹下主要是包含了与硬件和平台相关的代码,其中每种平台对应着一个相应的目录,常见的平台有arm、x86和i386等。
(2)block目录:主要是包含了与块设备驱动程序I/O调度相关的代码。
(3)crypto目录:主要是包含一些常规的算法方面的代码,例如加密、散列、压缩以及CRC校验的算法代码等。
(4)Documentation目录:主要是包含内核中各个通用代码或模块的注释讲解等文件。
(5)drivers目录:这个目录下文件主要是各种类型的设备驱动,这个目录对于驱动开发工程师也是需要经常查阅的,同时里面的文档也是对初学者具有很大的实用学习价值,其中里面包含了char、block、net、spi和i2c等驱动的代码。
(6)fs目录:主要包含不同类型的文件系统,如常用的EXT、FAT以及JFFS2等类型文件系统。
(7)include目录:该目录如它名字一样的含义,即是包含了系统中代码的头文件,其中系统相关的头文件主要存放在include/linux的各个子目录下。
(8)init目录:主要包含内核系统的初始化代码。
(9)ipc目录:主要是包含进程间通信的代码,通过对这类文件的阅读可以让读者能够对内核的进程及多任务的运行原理有一个更加深入的理解。
(10)kernel目录:这个目录下代码是整个linux内核代码中最为重要的部分,包括进程调度和相关定时器的代码等,但是对于与平台相关的部分代码则存放在arch/*/kernel的目录下。
(11)net目录:主要包含网络管理相关的代码,用于实现各个网络协议的功能。
(12)mm目录:主要是包含内存管理相关的代码。
(13)scripts目录:主要包含用于内核配置的相关脚本文件。
(14)sound目录:主要包含音频相关的代码,如ALSA等的驱动代码等。

3.2.2 内核组成部分

从上一小结中的目录构成可以看出,Linux内核主要构成部分由进程调度、内存管理、虚拟文件系统、网络接口以及进程间通信这五个子系统构成的。如下图3.1所示为内核组成部分的相关依赖关系示意图。
图片说明
图3.1 Linux各个子系统相关依赖关系
Figure 3.1 Dependencies Linux each subsystem

1.进程调度子系统

对于Linux内核中的五个子系统中,进程调度子系统处于它们的核心位置,因为进行调度子系统需要对内核系统中的多个进程进行控制,来实现进程间能够并行的执行,而且其他各个子系统都需依赖于进程调度子系统来完成它们的挂起和恢复进程的任务,这也是进程调度的主要工作。

2.内存管理子系统

内存管理子系统主要作用是可以控制多个进程能够安全合法的共享主内存中的区域。由于随着实际需求的难度的增加,单单依赖原本的CPU内存大小不足以满足需求,此时在Linux内存管理中具备实际内存与虚拟内存转换能力,Linux的内存管理能够为每个进程进行虚拟内存与物理内存之间的转换,这部分也在驱动开发中我们需要设置申请内存资源及将其与虚拟内存进行转换步骤中有体现。

3.虚拟文件子系统

在Linux的世界中常有个思想是“一切皆文件”,在Linux系统中不仅仅是普通文本文件和目录被当作文件处理,而且连字符设备、块设备等等都是当作文件来进行处理的,在它的背后就存在了一个就存在了一个软件抽象层(虚拟文件系统)来向用户空间提供文件系统的接口,与此同时还提供了一个抽象的功能,来实现不同的类型文件系统能够协同运行,如下图3.2所示为虚拟内存在内核中关系结构图。
图片说明
图3.2 虚拟文件系统关系结构图
Figure 3.2 Virtual File System Relationship Structure

4.网络管理子系统

在我们使用socket编程来完成以太网通信的过程中,是否有想过它是如何实现的呢?没错,在调用socket的相关的函数时是通过内核的网络管理子系统完成的,该子系统主要分为四个主要组成部分,采用了面向对象的抽象编程思想来实现的,主要包括:网络协议、套接字、网络设备接口以及网络缓存区,通过它们实现了对各种网络标的存取以及各种网络设备硬件的支持。

5.进程间通信子系统

进程间通信子系统主要完成的任务是能够让Linux系统支持进程间多种不同通信机制,例如信号量、共享内存及管道等,让它们能够实现协同多个进程、不同资源的互斥访问、进程间进行同步以及传递消息等功能

3.3 Makefie文件介绍

在Linux开发中无论是应用开发或者是内核驱动开发,开发工程师都需要对Makefile文件有一定的认识和理解,因为通过Makefile来完成程序的编译与链接功能。本文主要是讲述的是内核源代码中各个子目录的kbuild的Makefile,这个部分的Makefile也是开发工程师最常接触到的。Makefile一般包含如下部分:

(1)目标定义

目标定义其实是用来定义哪些文件需要用来作为模块编译,哪些需要进行编译和链接进内核之中。如下代码为“\kernel\drivers\video”中的Makefile其中的一行编译代码,表示为由fb_notify.c或者fb_notify.s文件编译得到的fb_notify.o并将其并入到内核中,对于“obj-m”则表示将目标文件作为模块编译出来。

obj-y += fb_notify.o

以下的方法是更为常见,就是根据.config文件中的CONFIG_变量来确定文件的编译方式,可以的值为“y或者m”,例如以下:

obj-$(CONFIG_FB_CFB_FILLRECT)  += cfbfillrect.o
obj-$(CONFIG_FB_CFB_COPYAREA)  += cfbcopyarea.o

(2)多个文件模块的定义

对于简单的Makefile编译可以使用上面(1)的方法一句的形式就足够满足要求了,但我们实际的项目中往往会存在是需要将多个文件编译为一个模块的情况,这个时候有两种方法可以完成该任务,可以通过模块名加-y或者-objs后缀形式来定义模块的组成文件。
a)使用-y的例子如下:

obj-$(CONFIG_FOO_FS) += foo.o
foo-y := balloc.o dir.o file.o fsync.o ialloc.o inode.o \
ioctl.o namei.o super.o symlink.o
foo-$(CONFIG_FOO_FS_XATTR) += xattr.o xattr_user.o xattr_trusted.o
foo-$(CONFIG_FOO_FS_POSIX_ACL) += acl.o
foo-$(CONFIG_FOO_FS_SECURITY) += xattr_security.o
foo-$(CONFIG_FOO_FS_XIP) += xip.o

模块的名字为foo,它是由indoe.o、ialloc.o等多个目标文件链接生成的foo.o,最终生成foo.ko驱动模块文件,对于acl.o和xip.o等目标文件是否链接进foo.o中则取决于配置文件的配置情况。
b)使用-objs来进行编译的例子如下:

obj-m +=sahuLB.o
sahuLB-objs:=simpLB.o sahu_lb_tools.o
all:
  make -C /lib/modules/`uname -r`/build M=`pwd`
clean:
  make -C /lib/modules/`uname -r`/build M=`pwd` clean
install:
  /sbin/insmod sahuLB.ko
remove:
  /sbin/rmmod sahuLB

上述例子是使用-objs来将simpLB.o和sahu_lb_tools.o链接进sahuLB.o中,最终编译出sahuLB.ko模块文件。

(3)目录层次的迭代

为了利于文件分类管理,我们编译生成出来的文件往往需要迭代到指定的目录中,如下例子为当将CONFIG_FOO_FS的值设置为y或者m时,kbuild将把foo目录列入向下迭代的目标中。

obj-$(CONFIG_FOO_FS) += foo/

3.4 Kconfig文件介绍

在上面讲述完Makefile文件之后,必不可少的需要讲解Kconfig文件,因这个两个文件是阅读内核文件时会起到地图导航的作用,在编译器编译内核时也是根据这个两个文件来确定需要编译哪些模块以及知道模块的依赖关系等。
通常情况下的Kconfig配置文件的语法是比较通俗易懂的,主要有如下部分:

(1)菜单入口

一般情况下内核配置的选项与Kconfig对应一个菜单的入口,例如:

config PCI_DEBUG
    bool "PCI Debugging"
    depends on PCI && DEBUG_KERNEL
    help
      Say Y here if you want the PCI core to produce a bunch of debug
      messages to the system log.  Select this if you are having a
      problem with PCI support and want to see more of what is going on.

“config”表示新的配置选项,后面几行的是该配置选项的相关属性,其中该属性包括类型、数据范围、输入提示等信息。
每个配置选项都需要指定类型,其中常规的类型有:bool、tristate、string、hex和int等,类型定义后可以加入输入提示方法,该两中方法是等价的。

bool "PCI Debugging"

或者

bool 
Prompt "PCI Debugging"

a)输入提示一般格式

prompt <prompt> [if <ezpr>]

可通过可选的if来提示依赖的关系。
b)默认值的格式
depends of
c)选择关系格式

select <symbol> [if <expr>]

A如果选择了B,则在A被选择的情况下,B将会被自动的选上。
d)help帮助信息格式

help
开始
.......
结束

(2)菜单结构

菜单入口在菜单数结构中的位置可以由两种方法的确定。
(a)使用”menu”和”endmenu”方式

menu "Graphics support"
    depends on HAS_IOMEM
config HAVE_FB_ATMEL
.......
endmenu

所有在”menu”和“endmenu”之间的菜单入口将会变成“Graphics support”的子菜单,同时所有的子菜单将会继承父菜单的依赖关系,例如“Graphics support”对“HAS_IOMEM”的依赖会被加到了配置选项HAVE_FB_ATMEL的依赖列表中。
值得注意的是,使用这个方法的menu后面跟的“Graphics support”项目只是一个菜单,没有对应真实的配置选项,也没有3种不同的状态值,这也是和“config”的主要区别。
(b)通过分析依赖关系来生成菜单结构
若菜单选项中存在一定的依赖选项,它就可以成为该选项的子菜单,当父选项为“N”时,子选项则不可见;反之父选项选上了,子选项可见。例子如下:

#
# PCI configuration
#
config ARCH_SUPPORTS_MSI
    bool
default n
config PCI_MSI
    bool "Message Signaled Interrupts (MSI and MSI-X)"
    depends on PCI
    depends on ARCH_SUPPORTS_MSI
    help
       This allows device drivers to enable MSI (Message Signaled
       Interrupts).  Message Signaled Interrupts enable a device to
       generate an interrupt using an inbound Memory Write on its
       PCI bus instead of asserting a device IRQ pin.
       Use of PCI MSI interrupts can be disabled at kernel boot time
       by using the 'pci=nomsi' option.  This disables MSI for the
       entire system.
       If you don't know what to do here, say Y.
config PCI_DEBUG
    bool "PCI Debugging"
    depends on PCI && DEBUG_KERNEL
    help
      Say Y here if you want the PCI core to produce a bunch of debug
      messages to the system log.  Select this if you are having a
      problem with PCI support and want to see more of what is going on.
      When in doubt, say N.

PCI_MSI直接依赖于ARCH_SUPPORTS_MSI,只有当ARCH_SUPPORTS_MSI不为“n”时,该选项才可以看见。
对于Kconfig配置脚本和Makefile脚本的编写详细内容,可以分别参考Documentation目录的 kbuild 子目录下的 Kconfig-language.txt 和 Makefiles.txt 文件。

3.5 配置和编译内核

在上述讲解完Makefile和Kconfig的文件及其作用后,我们接下来就是需要通过配置和编译内核来生成一个.config文件,最后内核根据这个.config来编译生成一个zImage或者uImage等镜像文件。对内核的配置需要正确配置后,才能够进下一步的编译,否则往往由于配置不当会在编译或者运行时出现错误。

3.5.1 快速配置内核

进入Linux内核源代码的顶部目录中,可以输入命令行make menuconfig,可以进入如下图3.3所示的内核配置界面,对于该菜单配置的操作方法有如下介绍:
(1)可通过键盘的方向键移动光标,选中子菜单;
(2)可通过TAB键实现在菜单区和功能区的切换;
(3)在子菜单或者选项高亮,将光标移动到功能区选中推荐

相关推荐

三题看不懂四题不明白二题无法AC&nbsp;T=int(input())&nbsp;for&nbsp;_&nbsp;in&nbsp;range(T):&nbsp;n=int(input())&nbsp;s=input().split()&nbsp;k,mx=1,1&nbsp;for&nbsp;i&nbsp;in&nbsp;range(len(s)-1):&nbsp;if&nbsp;len(s[i])&lt;len(s[i+1]):&nbsp;k+=1&nbsp;elif&nbsp;len(s[i])==len(s[i+1]):&nbsp;if&nbsp;s[i]&lt;=s[i+1]:&nbsp;k+=1&nbsp;...
恭喜臭臭猴子:第二题用栈就行。合法的括号直接出栈了,剩下的是不合法的,肯定都得一个一个走。出入栈的过程中得记下进栈的括号的下标。最后栈里剩下的括号如果相邻两个的下标不连续,说明它们中间有一个合法的括号序列被出栈,结果加一
投递拼多多集团-PDD等公司10个岗位 > 拼多多求职进展汇总 笔试
点赞 评论 收藏
分享
04-08 13:31
已编辑
门头沟学院 前端工程师
D0cC:京东营收1万多亿人民币,阿里9000多亿,虽然他俩利润都没腾讯和字节多,但是很恐怖了啊,负担了多少打工人的薪水
投递拼多多集团-PDD等公司10个岗位
点赞 评论 收藏
分享
评论
点赞
1
分享

全站热榜

更多

创作者周榜

更多
正在热议
更多
牛客网
牛客企业服务