1 Linux内核启动流程
引导加载阶段
计算机通电后,首先由 BIOS 或 UEFI 进行初始化,完成硬件自检等操作。
找到可启动设备,读取其第一个扇区的 MBR,MBR 中的引导加载程序(如 GRUB)被加载到内存并运行。
内核初始化阶段
引导加载程序将内核映像加载到内存后,内核开始初始化,首先进行体系结构相关的初始化,如设置 CPU 寄存器、内存映射等。
接着进行内核的基本初始化,包括内存管理、进程调度、中断处理等子系统的初始化。
系统服务启动阶段
内核初始化完成后,启动系统的第一个进程 init,init 进程会根据配置文件来启动其它系统服务和进程,如网络服务、文件系统服务等。
系统进入运行状态,等待用户操作。
2 什么是bootloader?在嵌入式系统当中bootloader的作用是什么?
Bootloader 是一段在操作系统内核运行之前执行的小程序。在嵌入式系统中,它具有至关重要的作用:
系统初始化:Bootloader 会对嵌入式系统中的硬件进行初始化,包括 CPU、内存、时钟、串口等设备,为后续内核的运行搭建好基础环境,确保硬件处于合适的工作状态。
加载内核:从存储设备(如 Flash、SD 卡)中读取操作系统内核映像到内存指定位置,并将系统控制权交给内核,使内核能够开始执行,完成系统的启动。
参数传递:在加载内核的过程中,Bootloader 可以向内核传递一些必要的参数,如硬件配置信息、内存布局等,帮助内核更好地适应硬件环境。
系统调试与维护:提供交互界面,允许开发人员通过串口或网络等方式与系统进行交互,实现对系统的调试、更新内核和文件系统等操作。
3 为什么汇编语言对硬件平台有依赖性而C语言却可以不依赖硬件平台?
汇编语言对硬件平台有依赖性而 C 语言相对不依赖硬件平台,主要原因如下:
指令集相关性:汇编语言是直接面向硬件的编程语言,它的每条指令都与特定硬件平台的指令集紧密相关,不同硬件平台的指令集和寄存器结构差异很大,所以汇编语言程序通常只能在特定的硬件平台上运行。而 C 语言是高级编程语言,它通过编译器将代码转换为目标硬件平台的机器码,编译器会根据不同平台的特点进行适配,开发者无需直接针对特定平台的指令集编程。
内存管理差异:汇编语言需要程序员直接操作内存地址和寄存器来进行数据存储和读取,要清楚了解硬件的内存布局等细节。C 语言有相对统一的内存管理机制,通过指针等概念操作内存,编译器会负责将这些操作映射到具体硬件平台的内存管理方式上,降低了对硬件平台内存细节的依赖。
4 什么叫做交叉编译?
交叉编译是指在一个平台上生成能在另一个不同平台上运行的目标代码的编译过程。比如,在 PC 机(x86 架构)上安装交叉编译工具链,然后用它来编译适用于 ARM 架构嵌入式设备的程序。
在嵌入式开发等场景中,交叉编译非常重要。由于目标设备(如嵌入式芯片)的资源有限,往往无法直接在上面进行编译工作,所以需要在性能较强的主机上进行编译。通过交叉编译,开发者可以利用主机的计算资源,针对不同架构和操作系统的目标设备生成可执行代码,方便将软件部署到各种目标平台上,而无需在每个目标设备上都搭建完整的开发环境。
5 Linux平台下的可执行文件是什么格式?
Linux 平台下常见的可执行文件格式主要是 ELF 格式,以下是具体介绍:
ELF(Executable and Linking Format):是 Linux 系统中最常用的可执行文件格式,可用于可执行程序、共享库和目标文件等。它具有良好的可移植性和扩展性,支持多种硬件平台和操作系统。ELF 文件包含了多个段,如代码段、数据段、符号表等,这些段记录了程序的指令、数据以及与链接和运行相关的信息,系统的加载器能够根据 ELF 文件的结构和信息,将程序正确地加载到内存中并运行。
此外,在 Linux 早期还存在 a.out 格式,但因其局限性已逐渐被 ELF 格式取代。
6 什么叫做反汇编?
反汇编是将机器语言程序转换为汇编语言程序的过程,与汇编过程相反。具体来说,就是把计算机可执行的二进制机器码,按照一定的规则和算法,转换为人类可读的汇编语言指令形式。
在软件逆向工程、程序调试、漏洞分析等场景中,反汇编技术应用广泛。通过反汇编,分析人员可以查看程序的汇编代码,了解程序的执行逻辑、算法实现,检查是否存在安全漏洞或恶意代码等。例如,在调试程序时,反汇编可以帮助开发人员查看机器码对应的汇编指令,更深入地理解程序的运行情况,找出程序出现问题的原因。
7 简述nfs服务的概念与作用?
NFS(Network File System)即网络文件系统,是一种基于网络的文件共享服务,允许网络中的计算机之间通过网络协议共享和访问彼此的文件系统资源。
NFS 的作用主要体现在以下几个方面:
资源共享:可使多台计算机共享同一组文件和数据,不同主机上的用户能像访问本地文件一样访问远程 NFS 服务器上的文件,提高了数据的共享性和利用率。
便于管理:系统管理员能在 NFS 服务器上集中管理和维护共享文件,如进行文件的更新、备份等操作,而无需在每台客户端机器上分别进行,降低了管理成本和复杂度。
增强灵活性:客户端可根据自身需求随时挂载或卸载 NFS 共享目录,方便灵活地使用服务器端的文件资源,有利于构建灵活的分布式计算环境。
8 简述一个装有linux内核的开发板的启动过程?
装有 Linux 内核的开发板的启动过程一般可分为以下几个阶段:
上电复位阶段
开发板上电后,硬件电路会产生复位信号,使 CPU 进入复位状态,从预设的地址开始执行代码,通常这个地址对应的是启动 ROM 中的代码。
Bootloader 阶段
启动 ROM 中的代码会首先加载 Bootloader 到内存中。Bootloader 是在操作系统内核运行之前运行的一段小程序,它的主要作用是初始化硬件设备、建立内存空间映射图,为加载内核做好准备。
完成自身初始化后,Bootloader 会根据配置从存储设备(如 SD 卡、NAND Flash 等)中读取 Linux 内核镜像和设备树等文件到内存指定位置。
内核启动阶段
Bootloader 将内核镜像加载到内存后,会将控制权交给内核。内核开始初始化,首先进行体系结构相关的初始化,如 CPU 初始化、内存管理初始化等。
接着进行设备驱动的初始化,探测和初始化各种硬件设备,如网卡、串口、存储设备等。
最后启动系统的第一个进程 init,内核初始化完成。
系统服务启动阶段
init 进程会根据配置文件启动系统的其它进程和服务,如网络服务、文件系统服务等。
系统进入运行状态,等待用户操作。
9 简述uboot的主要功能有哪些?
U-Boot(Universal Boot Loader)是广泛用于嵌入式系统的开源引导加载程序,其主要功能如下:
硬件初始化:启动时对 CPU、内存、时钟、串口等硬件设备进行初始化配置,为后续系统运行搭建稳定环境,如设置内存控制器参数,保证内存正常读写。
引导内核:从存储设备(如 SD 卡、NAND Flash)读取 Linux 内核镜像到内存指定位置,还会传递内核启动所需参数,如内存大小、设备树信息,助内核适配硬件。
提供交互界面:有命令行接口,开发者可通过串口等与之交互,执行查看硬件信息、修改环境变量、读写存储设备等操作,便于调试和维护。
支持网络功能:能借助 TFTP 等网络协议从服务器下载内核、文件系统等,方便远程更新与调试。
10 uboot如何设置环境变量?
U-Boot 设置环境变量有以下常见方式:
命令行设置
在 U-Boot 命令行界面,使用 setenv 命令可临时设置环境变量。例如,要设置 bootdelay 变量为 3,可输入 setenv bootdelay 3 。这种方式设置的变量仅在本次启动有效,重启后会丢失。
保存环境变量
若想让设置的环境变量在重启后仍生效,可使用 saveenv 命令。它会将当前的环境变量保存到存储设备(如 Flash)中,下次启动时,U-Boot 会从存储设备读取这些变量。
脚本设置
还能通过编写脚本文件设置环境变量。将一系列 setenv 命令写在脚本里,再使用 source 命令执行该脚本,可批量设置环境变量。
配置文件设置
可在 U-Boot 源码配置文件里预定义环境变量,编译时将其固化到 U-Boot 中,不过这种方式修改不够灵活。
11 简述uboot中bootcmd环境变量的作用?
在 U-Boot(Universal Boot Loader)里,bootcmd 环境变量起着关键作用。它本质上是一个包含一系列命令的字符串,在 bootdelay(启动延迟时间)到期且用户未进行干预时,U - Boot 会自动执行 bootcmd 中的命令,以完成系统的启动操作。
bootcmd 可包含读取内核镜像、设备树等文件到内存,以及启动内核等操作。例如,常见的 bootcmd 可能会从存储设备(如 SD 卡、NAND Flash)读取内核镜像到内存指定地址,再读取设备树文件,最后传递必要参数并启动内核。借助修改 bootcmd,能灵活调整系统的启动流程,适配不同的硬件环境和应用需求。
12 简述uboot中bootargs环境变量的作用?
在 U-Boot 中,bootargs 环境变量扮演着重要角色,它主要用于向 Linux 内核传递启动参数。以下简述其作用:
硬件配置信息传递:能告知内核硬件的相关信息,如根文件系统所在设备、内存布局、串口参数等。例如指定根文件系统位于哪个分区,让内核能正确挂载并访问系统文件。
内核行为控制:可控制内核启动过程中的一些行为,像是否开启调试信息输出、是否使用特定的调度算法等。通过设置不同参数,可按需调整内核运行方式。
系统初始化配置:为内核提供系统初始化所需的关键信息,辅助内核在启动时对硬件设备进行正确初始化和配置,确保系统正常启动与运行。
13 如何编译uboot生成二进制文件?
编译 U-Boot 生成二进制文件,可按以下步骤操作:
获取源码:从 U-Boot 官方仓库或开发板厂商指定的仓库下载对应版本的 U-Boot 源码,例如使用 git clone 命令克隆。
配置环境:确保系统安装了必要的编译工具,如 GCC 交叉编译工具链。
选择配置:进入 U-Boot 源码目录,使用 make <开发板配置名>_defconfig 命令来配置编译选项,开发板配置名可参考文档或咨询厂商。
编译:执行 make 命令,开始编译 U-Boot 源码。编译过程可能需要一段时间,完成后会在源码目录下生成二进制文件,通常是 u-boot.bin 。
14 简述设备树的作用?
设备树是一种描述硬件设备信息的数据结构,在嵌入式系统等领域有重要作用,主要体现在以下方面:
分离硬件与软件描述:将硬件细节从内核代码中分离出来,使内核更具通用性和可移植性,无需为每种硬件平台大幅修改内核代码。
描述硬件拓扑:清晰描述系统中各硬件设备的连接关系、地址、中断等信息,让内核能了解硬件架构,像可说明 CPU 与各外设的连接及通信方式。
协助驱动开发:驱动开发者可依据设备树提供的信息编写驱动程序,确定设备寄存器地址等资源,减少对特定硬件平台的依赖,提高驱动的可复用性。
15 编写设备树文件的主要依据是什么?
编写设备树文件的主要依据包括硬件电路设计、芯片数据手册和内核文档等,以下是具体说明:
硬件电路设计:这是最基础的依据,包括芯片引脚连接、各设备的地址分配、总线连接方式等硬件细节决定了设备树中节点的属性和关系。
芯片数据手册:芯片制造商提供的数据手册详细说明了芯片的各种特性、寄存器配置、支持的接口等信息,如某款处理器支持的 SPI 接口数量、信号引脚等,是编写设备树中对应节点属性的关键参考。
内核文档:Linux 内核文档中有关于设备树的规范和各设备类型的标准写法等内容,如设备树的语法规则、常见设备的节点命名规范等,能确保设备树文件与内核兼容。
参考已有设备树:对于类似硬件平台或相同芯片的设备树文件可作为参考,根据实际硬件差异进行修改和调整。
16 简述如何将一个内核源码中已有的驱动程序编译到内核中?
将内核源码中已有的驱动程序编译到内核里,一般按以下步骤操作:
配置内核:进入内核源码目录,执行 make menuconfig 等命令打开配置界面。通过上下左右键和回车键定位到驱动相关选项,根据需求选择要编译的驱动,可将其设为内置(*)或模块(M)。
保存配置:在配置界面完成设置后,保存退出,配置信息会存于 .config 文件。
编译内核:执行 make 命令,系统会依据配置文件对内核和驱动进行编译。若驱动设为内置,会直接集成进内核;若为模块,则会生成 .ko 文件。
安装内核:编译完成后,使用 make install 等命令安装新内核和驱动。
17 简述如何将一个自己编写的驱动程序编译到内核中?
将自己编写的驱动程序编译到内核中,可按以下步骤操作:
放置代码:把驱动代码(如 .c 文件)放到内核源码合适目录,如字符设备驱动放 drivers/char 目录。
修改配置文件:在驱动目录下的 Kconfig 文件添加配置项,像 “config MY_DRIVER” 等,确定编译选项。在 Makefile 里添加编译规则,如 “obj-$(CONFIG_MY_DRIVER) += my_driver.o”。
配置内核:进入内核根目录,运行 make menuconfig,找到驱动配置项,选好编译方式(内置或模块)并保存。
编译安装:执行 make 编译内核,若选内置则驱动集成进内核;选模块会生成 .ko 文件。之后可 make install 安装新内核。
18 开发板中为什么一般不需要安装静态库?
开发板中一般不需要安装静态库,主要有以下原因:
存储空间有限:开发板通常存储空间较小,静态库会占用大量空间,安装过多可能导致空间不足,影响系统和其他重要程序的运行。
可执行文件体积增大:静态库链接时会将库代码全部复制到可执行文件中,使可执行文件体积大幅增加,这会增加程序加载时间,降低运行效率,在资源受限的开发板上影响更明显。
更新维护不便:若静态库有更新或修复漏洞,需要重新编译链接使用该库的所有程序,在开发板环境中操作复杂,且可能影响其他功能。
动态库可替代:动态库能在多个程序间共享,节省空间,且更新方便,可满足开发板大多数场景需求,一定程度上减少了静态库的必要性。