Linux内核及内核编译

Linux内核及内核编译

文章目录

  • Linux内核及内核编译
  • Linux内核的发展
    • 内核抢占
    • 线程模型
    • 虚拟内存的变化
  • Linux内核组成
      • Linux内核源代码的目录结构
      • Linux内核的组成部分
    • Linux内核空间与用户空间
  • Linux内核的编译及加载
    • Linux内核的编译
    • Kconfig和Makefile
    • 内核的引导
  • Linux下的编码风格
  • 编译工具链

Linux内核的发展

Linux的发展一直是朝着多cpu、多硬件设备、支持更旷阔的领域的应用、提供更好的性能的这三个方向发展。

内核抢占

一个内核任务可以被抢占,从而提高系统的实时性

在2.4内核的版本中:

IRQ1的中断服务程序唤醒RT任务之后,必须等待前一个Normal(普通)任务的系统调用完成,返回用户空间的时候,RT任务才能够切入。

在2.6内核的版本中:

Normal任务在自旋锁结束的时候,RT任务就可以切入了。

Linux内核及内核编译_第1张图片

线程模型

在2.6版本中,线程采用NPTL,本地的POSIX线程库模型,操作速度得到了很大的提高

虚拟内存的变化

2.4版要回收页时,内核的做法是遍历每个进程的所有的pte,以判断PTE是否于该页建立了映射关系,如果建立了就要挨个取消,无映射关系之后才会回收该页

2.6版则实现了反向映射的关系,可以通过页结构体快速找到页面的映射

  • PTE (Page Table Entry)

    用于描述物理内存和虚拟内存之间的页面映射关系,他是页表中的一个条目,每个虚拟内存页面都对应一个PTE

    • PTE中包含一些字段
      1. 物理页框号:虚拟内存页所映射的 物理内存页 的地址
      2. 访问权限位:指示当前页面可读可写可执行的权限
      3. 脏位:表示当前位置是否修改过
      4. 共享位:表示页面能否被多个进程所共享
      5. 缓存位:用于控制页面的缓存

    操作系统可以通过访问PTE来进一步访问物理地址,实现虚拟内存的管理。

    PTE不单单存在于page table中,实际上可分为三级页表结构,每一级都含有PTE

    • 三级页表结构

      目录表(Page Directory)

      页中间表(Page Middle Directory)

      页表(Page Table)

Linux内核组成

Linux内核源代码的目录结构

这个找一个内核下载之后就可以看
Linux内核及内核编译_第2张图片

Linux内核的组成部分

Linux内核主要由:进程调度(SCHED)、进程间通信(IPC)、内存管理(MMU)、虚拟文件系统(VFS)、网络接口(NET),5个子系统组成

  1. 进程调度

    1. 能使多个进程”宏观并行“,”微观串行“地执行

    2. 进程调用属于5个子系统的中间位置,因为5个子系统都需要进程的恢复和挂起

    3. 根据资源和信号的存在与否可分为进程的状态:

      1. 就绪态
      2. 占有CPU执行态:
        1. 时间片耗尽即可返回就绪态
      3. 深度睡眠态
        1. 资源到位即可成为就绪态
      4. 浅睡眠态
        1. 资源到位或者收到信号即可成为就绪态
      5. 僵尸态
        1. do_exit() 基本上没救了
      6. 暂停态
        1. 收到信号即可成为就绪态

      Linux内核及内核编译_第3张图片

    在Linux内核中,使用task_struct结构体来描述进程,该结构体中包含了很多的字段,其中重要的包括:

    • 进程的状态
    • 线程的信息
    • 指向父进程的指针
    • 子进程链表的头部
    • 进程地址空间描述符
    • 文件描述符表

    大多数进程或者是线程都是在用户空间中创建的,再由系统调用进入内核空间;如果需要几个并发执行的任务,可以启动内核线程,这些线程没有用户空间。启动内核空间线程的函数:

    pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags);
    
  2. 内存管理

    1. 主要是控制各个进程安全的共享主内存区域。当cpu提供内存管理单元时,Linux内存管理完成虚拟内存到物理内存之间的转换
    2. 一般而言32位的处理器,含有4GB的内存空间,其中03GB属于用户空间,34GB属于内核空间

    Linux内核及内核编译_第4张图片

  3. 虚拟文件系统

    1. 虚拟文件系统提供了一个统一的接口而隐藏了各种硬件的接口,为上层的用户空间提供了修改驱动的接口,如vfs_read()\vfs_write()等接口,并调用具体的底层文件系统或者设备驱动实现file_operations结构体的成员函数

    Linux内核及内核编译_第5张图片

  4. 网络接口

    1. 在Linux中网络接口可分为网络协议和网络驱动程序
    2. 上层的应用程序统一使用套接字接口
      Linux内核及内核编译_第6张图片
  5. 进程间通信

    1. Linux支持多种通信机制包括

      1. 传统通信方式:FIFO管道、pipe管道、信号
      2. 共享内存、消息队列、信号集合
      3. BSD套接字
    2. Android内核则新增了Binder进程间通信方式

    3. Linux内核5个部分之间的依赖关系如下:

      1. 进程调度与内存管理的关系:创建进程的第一件事情就是将数据存放在内存之中,所以需要存在内存的管理
      2. 进程间通信与内存管理之间的关系:进程间的通信支持共享内存的方式进行通信
      3. 虚拟文件系统于网络接口之间的关系:虚拟文件系统利用网络接口支持网络文件系统(NFS)
      4. 虚拟文件系统与内存管理之间的关系:利用内存管理支持RAMDISK设备
      5. 内存管理与虚拟文件系统的管理:内存管理依靠虚拟文件系统支持交换,交换进程定期由进程调度。当一个进程存取的内存映射被换出时,内存管理向虚拟文件系统发出请求,同时,挂起当前正在运行的进程。

      Linux内核及内核编译_第7张图片

Linux内核空间与用户空间

CPU内部往往实现了不同的模式有不同的功能,高层程序往往不能访问低级功能,而必须由某种方式切换到低级模式

ARM处理器的7中工作模式:

usr用户模式

irq普通中断

fiq快速中断

abt异常数据访问终止

svc保护模式

sys系统模式

und未定义指令终止模式

ARM Linux的系统调用实现原理是采用swi软中断从用户(usr)模式陷入管理模式(svc)


又如X86处理器下包含4个特权级,Ring0~Ring3,在Ring0下面可以执行特权级指令,对任何的IO设备都有访问权限,而Ring3则被限制很多操作。

在Linux系统中,内核可以进行任何操作,而用户空间则会被禁止对硬件的直接访问和对内存的未授权访问。

内核空间和用户空间用来区分程序执行的两种不同的状态,他们使用不同的地址空间,Linux中只能通过系统调用和硬件中断完成从用户空间到内核空间的控制转移。

Linux内核的编译及加载

Linux内核的编译

  1. Linux内核的编译方法:

在编译内核时需要配置内核,使用make menuconfig,一旦选择之后,所有的选项会被存储到源代码树根目录下的 .config文件中。

使用make menuconfig时,配置工具首先分析与体系结构对应的/arch/xxx/Kconfig文件,xxx为传入的ARCH参数,/arch/xxx/Kconfig文件中除本身包含一些与体系结构相关的配置项和配置菜单外,还通过source语句引入一系列Kconfig文件

大多数情况下不需要从头开始配置。每个arch目录下面都有默认的配置文件可用,可以把他们作为配置起点:

ls arch/<you_arch>/configs/

#对于X86系统,内核的配置特别简单
#在arch/x86/configs/找到配置文件:有i386_defconfig和x86_64_defconfig,配置x86_64_defconfig
make x86_64_defconfig
make zImage -j8
make modules
make INSTALL_MOD_PATH </where/to/install> modules_install

#对于i.mx6的主板,可以先执行
ARCH=arm make imx_v6_v7_defconfig
#把默认的内核选项存储到 .config 文件中
#然后在执行
ARCH=arm make menuconfig
#根据需求来更新,增加或者删除选项
  1. 构建自己的内核:

构建自己的内核需要指定相关的体系结构和编译器,所以不一定是本地构建:

ARCH=arm make imx_v6_v7_defconfig
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make zImage -j8
#内核构建完成之后,会在arch/arm/boot/下生成一个单独的二进制文件

#使用以下命令构建模块
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make modules

#使用下列命令安装编译好的模块
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make modules_install
#modules_install目标需要指定一个环境变量INSTALL_MOD_PATH,指出模块安装的目录
#如果没有指定目录,则所有的模块将会安装到/lib/modules/$(KERNELRELEASE)/kernel/目录下

#编译设备树
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make dtbs
#dtbs选项不一定适用于所有的支持设备树的平台。要构建一个单独的DTB,应该使用
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make imx6d- sabrelite.dtb

Kconfig和Makefile

在Linux内核中增加程序需要完成以下3项工作

  • 将编写的源代码复制到Linux内核源代码的相应目录中
  • 在目录的Kconfig文件中增加关于新源代码对应项目的编译配置选项
  • 在目录的Makefile文件中增加对新源代码的编译条目

Makefile

  1. 目标定义

表示哪些目标被定义是作为模块编译,哪些要编译并链接进内核

obj-y += foo.o

表示foo.c或者foo.s要作为模块编译并链接进内核

obj-m 表示只编译成模块

obj-n 表示目标不会被编译

最常见的做法是make menuconfig之后的.config文件的CONFIG_变量来决定文件的编译方式:

obj-$ (CONFIG_ISDN) += isdn.o
obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o

  1. 多模块定义

如果一个模块由多个文件组成,则需要使用模块名-y或者-objs后缀的形式来定义模块的组成文件

obj-$(CONFIG_EXT2_FS) += ext2.o
ext2-y := balloc.o dir.o file.o
ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o xattr_user.o xattr_trusted.o
ext2-$(CONFIG_EXT2_FS_SECURITY) += xattr_security.o

模块的名字为ext2,由 balloc.o dir.o file.o等众多文件链接生成ext2.o,最后生成ext2.ko

是否包括{xattr.o xattr_user.o xattr_trusted.o xattr_security.o}这些文件,则需要取决于make menuconfig的配置情况。如果CONFIG_EXT2_FS_SECURITY被选择,则编译xattr_security.c—xattr_security.o最终链接进ext2

  1. 目录层次的迭代

obj-$(CONFIG_EXT2_FS)+= ext2/

将ext2/目录下的源文件添加到名为CONFIG_EXT2_FS的内核配置选项所对应的目标中

Kconfig

内核配置的脚本文件的语法:

config MODULES #config 关键字定义目标的属性,配置选项 
	bool "Enable loadable module support" #bool选择是或否

config MODVERSIONS #直接依赖MODULES,当MODULES选择为"y"时 
	bool "Set version information on all module symbols"
#等价于:
#bool
#prompt "Set version information on all module symbols"
	depends on MODULES 

comment "module support disabled" #直接依赖MODULES,当MODULES选择为"n"时
	depends on !MODULES

bool "foo" if BAR
default y if BAR
# 等价于
depends on BAR
bool "foo"
default y

#choices...endchoice
#choice
#
#
#endchoice

#应用实例:在内核中新增加驱动代码目录和子目录
#
#核心就是为相应的新增目录创建Makefile和Kconfig文件,而新增目录的父目录中的Kconfig和Makefile也需要修改
#-----------------------------------------------------------------------------------------------#
#1.在内核的drivers目录下面添加test目录和程序
#2.在test目录下应该包含以下Kconfig文件
menu "Test Driver"
	comment "Test Driver" #这个不参与选项

config CONFIG_TEST
	bool "Test support"

config CONFIG_TEST_USER
	tristate "Test user-space interface" #区别于bool,这个是有(y,n,m),bool只有y,n
	depends on CONFIG_TEST

endmenu

#为了使得Kconfig能够选择后起作用,修改arch/arm/Kconfig文件,增加:source "drivers/test/Kconfig"
#在test目录下面应该包含如下Makefile文件
obj-$(CONFIG_TEST) += test.o test_queue.o test_client.o
obj-$(CONFIG_TEST_USER) += test_ioctl.o
obj-$(CONFIG_PROC_FS) += test_proc.o

obj-$(CONFIG_TEST_CPU) += cpu/
#---------------------------------------------------------------------------------------------------#
#由于test中包含一个子目录,因此当CONFIG_TEST_CPU=y时,应该将cpu目录加入列表
#所以cpu目录中也应该添加一个makefile
obj-$(CONFIG_TEST_CPU) += cpu.o
#---------------------------------------------------------------------------------------------------#
#为了使得编译命令能够作用到整个目录,test的父目录Makefile也需要增加脚本
obj-$(CONFIG_TEST) += test/
#共需要添加3个makefile和1个Kconfig

内核的引导

引导Linux系统的过程

Linux内核及内核编译_第8张图片
一般的SOC中都镶嵌了bootrom,上电时bootrom运行,对于CPU0而言会使得bootrom去引导bootloader,而其他的CPU会去判断自己是不是CPU0,如果不是则会进行WFI状态等待CPU0唤醒。

bootrom会去引导内核,启动内核,在启动阶段CPU0会发送中断唤醒CPU1,随后都会投入运行

对于CPU0处于用户空间的init程序被调用,init程序就会派生出其他进程:

用户空间的init程序通常包含busybox init、Sys Vinit、systemd,通常把整个系统启动,最后形成一个进程树

Linux下的编码风格

  • 对于函数的命名

在windows下对于函数的命名都是通过首字母大写来命名

在Linux下则使用下划线_De的方式来命名

  • Linux代码的缩进使用[TAB]键

  • if/for/while/switch/struct的{ 不另起一行

  • 对于函数 { 则会另起一行

  • 借助结构体成员初始化结构体:

    struct file_operation ext2_file_operation = {
        
    	.llseek = generic_file_llseek,
    	.read   = xxx,
    	.write  = xxx,
    };
    
  • 打印当前函数的函数名:

    void example()
    {
    	printf("This is function %s\n", __func__);
    }
    
  • 指定一个属性,只需要在声明后添加__attribute__((ATTRIBUTE))。

    • 其中ATTRIBUTE包含:noreturn、format、section、aligned等
    //noreturn表示该函数从不返回
    #define ATTRI_NORE __attribute__((noreturn)) 
    void do_exit(...) ATTRI_NORE;
    
    //aligned指定了结构体的字节对齐方式
    struct example_struct {
    	char a;
    	int b;
    	float c;
    } __attribute((aligned(4)));//表示按照4字节对齐
    

编译工具链

一个典型了ARM工具链包括arm-linux-gnueabihf-xxx
其中xxx包括:

  • strip缩减程序体积
  • objdump反汇编工具

你可能感兴趣的:(Linux设备驱动开发,linux,运维,linux内核,内核编译)