Linux的发展一直是朝着多cpu、多硬件设备、支持更旷阔的领域的应用、提供更好的性能的这三个方向发展。
一个内核任务可以被抢占,从而提高系统的实时性
在2.4内核的版本中:
IRQ1的中断服务程序唤醒RT任务之后,必须等待前一个Normal(普通)任务的系统调用完成,返回用户空间的时候,RT任务才能够切入。
在2.6内核的版本中:
Normal任务在自旋锁结束的时候,RT任务就可以切入了。
在2.6版本中,线程采用NPTL,本地的POSIX线程库模型,操作速度得到了很大的提高
2.4版要回收页时,内核的做法是遍历每个进程的所有的pte,以判断PTE是否于该页建立了映射关系,如果建立了就要挨个取消,无映射关系之后才会回收该页
2.6版则实现了反向映射的关系,可以通过页结构体快速找到页面的映射
PTE (Page Table Entry)
用于描述物理内存和虚拟内存之间的页面映射关系,他是页表中的一个条目,每个虚拟内存页面都对应一个PTE
操作系统可以通过访问PTE来进一步访问物理地址,实现虚拟内存的管理。
PTE不单单存在于page table中,实际上可分为三级页表结构,每一级都含有PTE
三级页表结构
目录表(Page Directory)
页中间表(Page Middle Directory)
页表(Page Table)
这个找一个内核下载之后就可以看
Linux内核主要由:进程调度(SCHED)、进程间通信(IPC)、内存管理(MMU)、虚拟文件系统(VFS)、网络接口(NET),5个子系统组成
进程调度
能使多个进程”宏观并行“,”微观串行“地执行
进程调用属于5个子系统的中间位置,因为5个子系统都需要进程的恢复和挂起
根据资源和信号的存在与否可分为进程的状态:
在Linux内核中,使用task_struct结构体来描述进程,该结构体中包含了很多的字段,其中重要的包括:
大多数进程或者是线程都是在用户空间中创建的,再由系统调用进入内核空间;如果需要几个并发执行的任务,可以启动内核线程,这些线程没有用户空间。启动内核空间线程的函数:
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags);
内存管理
虚拟文件系统
网络接口
进程间通信
Linux支持多种通信机制包括
Android内核则新增了Binder进程间通信方式
Linux内核5个部分之间的依赖关系如下:
CPU内部往往实现了不同的模式有不同的功能,高层程序往往不能访问低级功能,而必须由某种方式切换到低级模式
ARM处理器的7中工作模式:
usr用户模式
irq普通中断
fiq快速中断
abt异常数据访问终止
svc保护模式
sys系统模式
und未定义指令终止模式
ARM Linux的系统调用实现原理是采用swi软中断从用户(usr)模式陷入管理模式(svc)
又如X86处理器下包含4个特权级,Ring0~Ring3,在Ring0下面可以执行特权级指令,对任何的IO设备都有访问权限,而Ring3则被限制很多操作。
在Linux系统中,内核可以进行任何操作,而用户空间则会被禁止对硬件的直接访问和对内存的未授权访问。
内核空间和用户空间用来区分程序执行的两种不同的状态,他们使用不同的地址空间,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
#根据需求来更新,增加或者删除选项
构建自己的内核需要指定相关的体系结构和编译器,所以不一定是本地构建:
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
在Linux内核中增加程序需要完成以下3项工作
Makefile
表示哪些目标被定义是作为模块编译,哪些要编译并链接进内核
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
如果一个模块由多个文件组成,则需要使用模块名-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
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
#<choice options>
#<choice block>
#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系统的过程
一般的SOC中都镶嵌了bootrom,上电时bootrom运行,对于CPU0而言会使得bootrom去引导bootloader,而其他的CPU会去判断自己是不是CPU0,如果不是则会进行WFI状态等待CPU0唤醒。
bootrom会去引导内核,启动内核,在启动阶段CPU0会发送中断唤醒CPU1,随后都会投入运行
对于CPU0处于用户空间的init程序被调用,init程序就会派生出其他进程:
用户空间的init程序通常包含busybox init、Sys Vinit、systemd,通常把整个系统启动,最后形成一个进程树
在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))。
//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包括: