嵌入式Linux常见问题

2019-07-12 16:04发布

1) Linux内核的组成部分
Linux内核主要有进程调度(SCHED)、内存管理(MM)、虚拟文件系统(VFS)、网络接口(NET)和进程间通信(IPC)5个子系统组成。 2) 进程调度处于系统的中心位置,内核中的其他子系统都依赖它,因为每个子系统都需要挂起或回复进程。 3) 在设备驱动编程中,当请求的资源不能得到满足时,驱动一般会调度其他进程执行,并使驱动对应的进程进入睡眠状态,直到它请求的资源被释放,才会被唤醒而进入就绪状态。睡眠分为可被打断的睡眠和不可被打断的睡眠,两者的区别在于可被打断的睡眠在收到信号的时候会醒来。 4) 设备驱动中,如果需要几个并发执行的任务,可以启动内核线程,启动内核线程的函数为: int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags); 具体的使用环境及使用方式需要继续研究。 5) 内存管理的主要作用是控制多个进程安全地恭喜那个主内存区域。 6) 虚拟文件系统独立于各个具体的文件系统,是对各种文件系统的一个抽象,它使用超级块super block存放文件系统的相关信息,使用索引节点inode存放文件的物理信息,使用目录项dentry存放文件的逻辑信息。
super block, inode, dentry的工作方式需要继续研究。 7) 网络接口可分为网络协议和网络驱动程序,网络协议部分负责实现每一种可能的网络传输协议,网络设备驱动程序负责与硬件设备进行通信。 8) ARM处理器的7种工作模式:
  • 用户模式(usr):大多数的应用程序运行在此模式下,在此模式下,某些被保护的系统资源是不能被访问的。
  • 快速终端模式(fiq):用于高速数据传输或通道处理。
  • 外部中断模式(irq):用于通用的中断处理。
  • 管理模式(svc):操作系统使用的保护模式。uboot/裸机程序运行在此模式下。
  • 数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于虚拟存储及存储保护。
  • 系统模式(sys):运行具有特权的操作系统任务。
  • 未定义指令中止模式(und):当未定义的指令执行时进入该模式,可用于支持硬件些处理器的软件仿真。
9) X86处理器由Ring0~Ring3四个不同的特权级。Ring0下可以执行特权级指令,对任何I/O设备都有访问权。Ring3则有很多操作限制,Linux系统使用了Ring0和Ring3这两级。在Linux系统中,内核可以进行任何操作,而应用程序则被禁止对硬件的直接访问和对内存的未授权访问。若使用x86处理器,则用户代码运行在Ring3,而系统内核代码则运行在Ring0。 10) initrd (Bootloader initialized RAM disk)是指由Bootloader初始化的内存盘。在Linux内核启动前,Bootloader会将存储介质中的initrd文件加载到内存,内核启动时会在访问真正的根文件系统前先访问该内存中的initrd文件系统。为使得启动时加载initrd,只需修改grub.conf引导配置文件,在最后添加“initrd /boot/initrd-2.6.15.5.img”。在bootloader配置了initrd的情况下,内核启动被分为两个阶段,第一阶段先执行initrd文件系统的文件,完成加载驱动模块等任务,第二阶段才会执行真正的根文件系统中的/sbin/init进程。第一阶段启动的目的是为第二阶段的启动扫清障碍,最主要的是加载根文件系统存储介质的驱动模块。 11) 编译内核: make ARCH=arm CROSS_COMPILE=arm-linux- smdk2410_defconfig make ARCH=arm CROSS_COMPILE=arm-linux- zImage make ARCH=arm CROSS_COMPILE=arm-linux- modules 指定体系结构为ARM,交叉编译器为arm-linux-gcc, 采用smdk_2410defconfig配置。
如果在源代码根目录的Makefile中将ARCH和CROSS_COMPILE直接定为arm和arm-linux-后,就没有必要每次编译的时候都指定体系结构和交叉编译器。 12) 使用make config、make menuconfig命令之后,会生成一个config配置文件(隐含在顶层的Makefile中),记录哪些部分被编译入内核、哪些部分被编译为内核模块。 13) 在Linux内核中增加程序需要完成以下三项工作:
  • 将编写的源代码复制到Linux内核源代码的相应目录。
  • 在目录的Kconfig文件中增加新源码对应项目的编译配置选项。
  • 在目录的Makefile文件中增加对新源代码的编译条目。
14) 多文件模块的定义
如果一个模块由多个文件组成,这个时候应采用模块名加-objs后缀或者-y后缀的形式来定义模块的组成文件。例如: obj-$(CONFIG_EXT2_FS) += ext2.o ext2-y := balloc.o bitmap.o ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o 模块的名字为ext2,由balloc.o和bitmap.o两个目标文件最终链接生成ext2.o直至ext2.ko文件,是否包括xattr.o取决于内核配置文件的配置情况。如果CONFIG_EXT2_FS的值是y也没有关系,在此过程中生成的ext2.o将被链接进built-in.o最终连接进内核。这里需要注意的一点是,该kbuild Makefile所在的目录中不能再包含和模块名相同的源文件如ext2.c/ext2.s。
或者写成如-objs的形式: obj-$(CONFIG_ISDN) += isdn.o isdn-objs := isdn_net_lib.o isdn_v110.o isdn_common.o 15) 实例:在内核中新增驱动代码目录和子目录
目标:在内核源码drivers目录下为ARM体系结构新增如下用于test driver的树形目录: |--test |-- cpu |-- cpu.c |-- test.c |-- test_client.c |-- test_ioctl.c |-- test_proc.c |-- test_queue.c 在内核中增加目录和子目录,我们需要为相应的新增目录创建Kconfig和Makefile文件,而新增目录的父目录中的Kconfig和Makefile也需要修改,以便新增的Kconfig和Makefile文件能被引用。
在新增的test目录下,应该包含如下Kconfig文件: # # TEST driver configuration # menu "TEST Driver" comment "TEST Driver" config CONFIG_TEST bool "TEST support" config CONFIG_TEST_USER tristate "TEST user-space interface" depends on CONFIG_TEST endmenu 由于TEST driver对于内核来说是新的功能,所以首先需要创建一个菜单TEST Driver;然后显示“TEST Support”等待用户选择;接下来判断用户是否选择了TEST Driver,如果是(CONFIG_TEST=y),则进一步显示子功能:用户接口与CPU功能支持;由于用户接口功能可以被编译成内核模块,所以这里的询问语句使用了tristate。
为了使这个Kconfig文件能起作用,需要修改arch/arm/Kconfig文件,增加一下内容: source "drivers/test/Kconfig" 脚本中的source意味着引用新的Kconfig文件。
在新增的test目录下,应该包含如下Makefile文件: # drivers/test/Makefile # # Makefile for the TEST # 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/ 该脚本根据配置变量的取之构建obj-*列表。由于test目录中包含一个子目录cpu,当CONFIG_TEST_CPU=y时,需要将cpu目录加入列表。
test目录中的cpu子目录也需要包含如下Makefile文件: # drivers/test/cpu/makefile # # Makefile for the TEST CPU # obj-$(CONFIG_TEST_CPU) += cpu.o 为了使整个test目录能够被编译命令作用到,test目录父目录中的Makefile文件也需要增加如下脚本: obj-$(CONFIG_TEST) += test/ 在drivers/Makefile中加入obj-$(CONFIG_TEST) += test/, 使得用户在进行编译时能够进入test目录。
增加了Kconfig和Makefile文件之后的新的test树形目录如下所示: |--test |-- cpu |-- cpu.c |-- Makefile |-- test.c |-- test_client.c |-- test_ioctl.c |-- test_proc.c |-- test_queue.c |-- Makefile |-- Kconfig 16) 在Linux2.6 内核中,可以使用request_module(const char *fmt, …)函数加载内核模块,驱动开发人员可以通过调用: request_module(module_name);request_module("char-major-%d-%d", MAJOR(dev), MINOR(dev)); 来加载其他内核模块。 17) 在Linux内核中,所有标识为_ init的函数在连接的时候都放在.init.text这个区段内,此外,所有的 init函数在区段.initcall.init中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些 _init函数,并在初始化完成后释放init区段(包括.init.text, .initcall.init等)。 参考资料:《linux设备驱动开发详解》 宋宝华