一、设备树
- 设备树(Device Tree)是 Linux 系统中用于描述硬件信息的一种机制,尤其在 ARM 平台上广泛使用。
- 在早期版本的 Linux(如 2.6 及之前),设备的硬件信息通常是通过硬编码方式写在平台相关的文件中。这种方式给维护和扩展带来了很大麻烦,尤其是对于那些支持众多嵌入式平台的内核代码而言,几乎每个开发板都需要独立的板级支持代码,造成了内核代码的臃肿。
- Linux2.6版本之前没有设备树
- 从 Linux 3.0 开始,设备树引入到内核中,作为一种描述硬件的方法。设备树将板级的硬件描述从内核代码中分离出来,以一种结构化的方式定义硬件信息,并使得内核代码对硬件的依赖性大大减少。
- 设备树文件路径:设备树文件通常位于 arch/arm/boot/dts/ 目录下,文件名格式为 xxxxxx.dts,例如 exynos4412-origen.dts。
1. 主要作用
- 设备树的主要作用是将硬件信息从内核代码中分离出来,使得内核变得更加通用,支持更多硬件时无需修改内核源码。设备树文件可以描述硬件的各种属性,比如:
- 寄存器地址(register addresses):设备在系统中的物理地址。
- 中断号(interrupt numbers):设备所使用的中断信息。
- 时钟源(clock sources):设备所依赖的时钟。
- GPIO 配置:设备所使用的 GPIO 管脚。
- 其他硬件属性:如 DMA 通道、设备型号等。
- 设备树文件是用一种层次化的结构来表示硬件的信息,以树形结构表示不同设备和其相关的属性。设备树可以很方便地被驱动程序使用,使驱动能够正确地访问和控制硬件。
2. 文件结构
- 设备树文件(.dts)使用一种类 C 的语法描述硬件。设备树文件的扩展名为 .dts,而编译后的二进制文件称为设备树 blob(.dtb),它会被内核在启动时加载并解析。
- 设备树文件的构成类似于类 C 语言的结构,通过节点和属性来描述系统中的硬件。每一个节点代表一个硬件设备或子系统,属性则描述该设备的具体配置(如寄存器地址、中断号等)。
- 设备树的基本元素
- 节点(Node):节点可以理解为设备或子系统,通常对应硬件中的某个设备。
- 属性(Property):属性用于描述节点的一些具体信息,如地址、名称、功能等。
- 节点和属性的关系:
设备树是由节点和属性共同组成的。节点可以包含子节点,形成树形结构;属性则是节点的具体描述信息。 - 示例:描述一所大学的设备树
/ {
university = "重庆高等大学"; // 根节点,描述学校
address = "重庆大足区xxx村235号"; // 属性:学校地址
established_year = <5>; // 属性:学校成立年份
president_name = "张校长"; // 属性:校长姓名
president { // 子节点:校长
name = "张XX";
title = "副教授";
age = <56>;
gender = "女";
};
college_of_electronics { // 子节点:电子信息与自动化学院
name = "电子信息和自动化学院";
class_info_1 { // 子节点:信息1班
class_name = "信息1班";
monitor { // 子节点:班长
name = "小李";
age = <18>;
gender = "男";
};
student_xiaowang { // 子节点:小王
name = "小王";
age = <19>;
gender = "男";
};
};
class_info_2 { // 子节点:信息2班
monitor {
name = "班长";
};
};
};
college_of_broadcasting { // 子节点:广播电视学院
class_1 {
// 广播1班信息
};
class_2 {
// 广播2班信息
};
};
};
- 设备树结构说明
- 根节点 /:设备树的根节点,所有硬件描述都在这个节点下。类似于学校的根节点,描述学校的基本信息(地址、校长等)。
- 节点(Node):每个节点代表系统中的一个设备或子系统,类似于大学中的一个学院(如电子信息与自动化学院)。节点可以包含多个子节点。
- 属性(Property):属性是节点的具体描述,如设备的地址、中断号等。属性是以 key = value 的形式出现的,类似于描述学校的地址、校长的姓名等。
- 公有属性和私有属性
- 公有属性
- 公有属性指的是可以在多个节点中共享的属性。例如,姓名(name)、年龄(age)、地址(address)等是所有学生、老师等节点中都可能会存在的公有属性
- 在设备树中,诸如寄存器地址、设备兼容性(compatible)等信息通常属于公有属性。
- 私有属性
- 私有属性是仅在特定节点中存在的属性。例如,校长的职称(title)、年薪等信息是校长独有的属性,其他节点不会拥有这些属性。
- 在设备树中,特定硬件设备的专有属性,如时钟源、特殊功能标志,通常属于私有属性
- 公有属性
3. 设备树在硬件描述中的应用
在实际硬件中,设备树文件通常用于描述各种硬件设备的配置信息
gpio@11400000 {
compatible = "samsung,exynos4412-gpio"; // 描述与该设备兼容的驱动
reg = <0x11400000 0x1000>; // 描述 GPIO 控制器的寄存器地址
interrupts = <0 1 4>; // 定义 GPIO 使用的中断号
};
i2c@13860000 {
compatible = "samsung,exynos4-i2c"; // I2C 控制器的兼容性描述
reg = <0x13860000 0x100>; // I2C 寄存器地址和大小
interrupts = <14>; // I2C 使用的中断号
status = "okay"; // 设备状态,表示设备启用
};
//gpio@11400000:该节点描述了一个 GPIO 控制器,地址为 0x11400000,大小为 0x1000。
//compatible:这个属性描述该设备与哪些驱动兼容。驱动程序会根据这个属性来匹配硬件设备。
//reg = <寄存器起始地址, 寄存器空间长度>:寄存器地址,表示设备的物理地址范围。
//interrupts = <中断号, 触发方式>:定义设备使用的中断号。
//status="okay"/"disabled"; 使能该节点/禁止该节点
//interrupt-parent:属性指定中断控制器的父节点,表示当前设备的中断由哪个中断控制器管理。
4. 设备树的编译与加载
设备树文件(.dts)在使用前需要编译为二进制格式(.dtb),并由内核加载。
- 编译
dtc -I dts -O dtb -o exynos4412-fs4412.dtb exynos4412-fs4412.dts//手动不推荐
推荐如下
# 编译所有设备树文件
make dtbs ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
# 编译你指定的 exynos4412-fs4412.dtb 文件
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- arch/arm/boot/dts/exynos4412-fs4412.dtb
- 加载
设备树文件在内核启动时加载。内核通过 U-Boot 等引导加载程序将设备树二进制文件传递给内核
tftp 0x41000000 uImage
tftp 0x42000000 exynos4412-fs4412.dtb
bootm 0x41000000 - 0x42000000
文件系统
- 在 Linux 中,文件系统是系统最原始的用户与硬件交互界面。Linux 系统中采用了一种抽象机制:一切皆文件。换句话说,系统中的每个硬件设备、进程、内核信息等都被抽象为文件,用户和应用程序通过访问这些文件的方式来与硬件和系统进行交互。
普通文件与目录
- /bin:
存放基本的系统命令,所有用户都可以使用这些命令,例如 ls、cp、mv 等。 - /sbin:
存放系统管理员使用的系统命令,通常只有 root 用户可以执行的命令,例如 ifconfig、reboot 等。 - /etc:
存放系统的配置文件,包括用户信息、网络设置、服务启动脚本等。例如 /etc/passwd(用户信息)、/etc/hostname(主机名)。 - /linuxrc:
在某些嵌入式 Linux 系统中,linuxrc 是在系统启动时的初始化程序,指向启动脚本或可执行文件。 - /mnt:
Linux 中推荐的挂载点目录,通常用于临时挂载文件系统。例如,可以将 USB 设备或远程文件系统挂载到 /mnt/usb。 - /usr:
usr 代表 Unix System Resources,是用户级的文件系统,存放用户的二进制程序和库文件。- /usr/bin:用户可以执行的命令和工具。
- /usr/sbin:系统管理员执行的命令和工具。
- 以后所有的个人文件、第三方工具都建议放在 /usr 下。
- /lib:
存放系统库文件,包括 C 标准库、C++ 标准库、线程库等。这些库为系统中的应用程序和命令提供支持。 - /lost+found:
文件系统检查时,系统会将修复的孤立文件放在这个目录中。通常在 ext4 文件系统中出现。 - /var:
存放可变数据,例如日志文件、临时文件、缓存数据等。- /var/log:系统日志文件存放目录。
- /var/tmp:系统的临时文件夹,用于存放临时文件,系统重启后不一定清空。
- /tmp:
存放临时文件的目录,系统重启后会清空其中的内容。程序的临时缓存文件通常会放在这个目录中。
特殊文件
特殊文件包括内存文件系统和硬件设备文件,通常映射到内核数据或硬件设备,访问这些文件等同于与内核或硬件直接交互。
-
/proc - 内存文件系统:
/proc 是 Linux 系统中的虚拟文件系统,存放与系统和进程状态相关的信息。/proc 中的文件实际上并不占据磁盘空间,而是由内核动态生成,内容是系统的实时信息。访问 /proc 中的文件,可以获取系统状态和硬件信息。- 常见的 /proc 文件:
- cmdline:系统启动时传递给内核的命令行参数,通常与 bootargs 相关。
- cpuinfo:记录当前 CPU 的信息,如处理器型号、频率、核数等。
- device-tree:描述设备树信息,驱动程序会根据设备树信息进行设备初始化。
- devices:列出系统中所有注册的设备信息。
- diskstats:提供磁盘的 I/O 统计信息。
- interrupts:列出内核的中断处理统计信息,包括中断号和处理的次数。
- meminfo:显示系统的内存使用情况,例如总内存、可用内存、缓存等。
- partitions:显示磁盘分区的信息。
- uptime:系统的运行时间和负载信息。
- 进程目录:每个进程都会有一个以其 PID 命名的目录,里面包含该进程的状态、内存、打开的文件描述符等信息。
- 常见的 /proc 文件:
-
/dev - 设备文件系统:
/dev 目录是设备文件系统,所有硬件设备在 Linux 系统中都会映射为一个文件,用户通过读写这些文件来操作硬件。例如:- /dev/sda:表示第一个硬盘。
- /dev/ttyS0:表示第一个串口设备。
- /dev/null:表示虚拟的“黑洞”,写入的数据都会被丢弃。
- /dev/zero:提供无限的零数据输出。
- /dev/random:提供随机数生成设备。
每个设备文件都有主设备号和次设备号,主设备号标识设备类型,次设备号标识设备的具体实例。
-
/sys - 设备与驱动信息系统:
- /sys 是另一个虚拟文件系统,存放系统中设备和驱动的信息。与 /proc 类似,/sys 中的文件也并不占用实际的磁盘空间,而是由内核动态生成。
- /sys 主要用于内核与用户空间之间的交互,用户和应用程序可以通过修改 /sys 中的文件来配置设备。
文件系统的核心理念:一切皆文件
在 Linux 中,一切皆文件 是核心理念。硬件设备、内存信息、进程状态等都通过文件系统进行抽象,统一为文件的形式提供给用户和应用程序。
如何操作硬件?
硬件设备在 Linux 系统中被抽象为文件,用户可以通过标准的文件操作(如打开、读取、写入、关闭等)与硬件进行交互。
1. 读取硬件设备: 例如,通过 cat /proc/cpuinfo 可以获取当前系统 CPU 的信息,或者通过 cat /dev/ttyS0 读取串口设备的数据。
2. 写入硬件设备: 通过向设备文件写入数据来控制设备,例如向 /dev/led 写入控制信号可以点亮或关闭 LED。
3. 设备节点: 每个设备文件都有其主设备号和次设备号,主设备号用于标识设备的类型,次设备号用于标识同一类型的不同设备实例。通过 mknod 命令可以手动创建设备节点。
开机启动过程
Linux 系统的启动过程是一个复杂的过程,从启动内核到启动第一个用户进程,再到执行用户定义的初始化脚本,最后完成系统启动。以下是对 开机启动过程 的详细补充与完善。
1. Linux 启动后内核的初始化
- 内核加载
- 当系统启动时,首先加载 Linux 内核。内核会初始化硬件设备、加载必要的驱动程序,并准备好启动用户空间进程。
- 启动第一个用户进程
- 在内核完成初始化后,内核会启动第一个用户进程,也就是 init 进程(PID=1)。
- init 是系统的第一个用户进程,负责启动其他所有用户空间的进程。它是所有进程的祖先。
2. init 进程的工作
init 进程 的主要作用是根据配置文件执行一系列的系统初始化任务。传统的 Linux 系统使用 /sbin/init 作为初始化进程,通常会读取 /etc/inittab 配置文件,执行里面定义的初始化任务。
2.1 读取 /etc/inittab 文件
init 进程在启动后,会读取 /etc/inittab 文件,根据文件中定义的行为执行相应的操作。
/etc/inittab 文件示例
# 系统初始化时执行的任务
::sysinit:/etc/init.d/rcS # 系统初始化时执行 /etc/init.d/rcS 脚本
# 终端相关的任务
::respawn:-/bin/sh # 在终端 respawn /bin/sh,允许重新登录 shell
# 重启相关的任务
::restart:/sbin/init # 当重启 init 进程时,重新运行 /sbin/init
# 按下 Ctrl+Alt+Del 时执行的任务
::ctrlaltdel:/sbin/reboot # 按下 Ctrl+Alt+Del 时执行 /sbin/reboot
2.2inittab 文件的解释
- sysinit:这个标志告诉 init 进程在系统初始化时要执行的操作。在上面的示例中,/etc/init.d/rcS 是在系统启动时执行的脚本。
- respawn:指定当某个进程结束时是否重新启动该进程。这里 respawn /bin/sh 表示在终端上启动 shell,且 shell 结束后会重新启动。
- restart:指定当 init 进程重新启动时要执行的操作。
- ctrlaltdel:定义按下 Ctrl + Alt + Del 键组合时执行的命令。在上面的示例中,按下组合键会执行 /sbin/reboot,即系统重启。
2.3 init 进程的主要任务
init 进程在系统启动时做了以下几个重要任务:
- 系统初始化:
执行 /etc/init.d/rcS 脚本,完成系统的基础初始化任务,如挂载文件系统、启动虚拟内存、加载系统守护进程等。 - 启动登录进程:
init 进程会在系统的每个终端上启动一个 getty 进程,等待用户登录。用户登录后,getty 会启动一个 login 进程处理用户的登录请求。 - 响应重启与关机事件:
init 还会监控系统的事件,例如用户按下 Ctrl + Alt + Del 键组合时触发的重启命令,或者系统发出的关机请求。
3. 如何开机启动某个程序
-
可以通过修改系统的初始化脚本,来实现开机启动自定义程序。通常,系统在启动时会执行 /etc/init.d/rcS 脚本,所有初始化任务和服务都在这个脚本中启动。
-
具体步骤
- 打开 /etc/init.d/rcS 脚本
sudo vi /etc/init.d/rcS
- 添加自定义程序到脚本中
- 在脚本的尾部添加你希望开机启动的程序。例如,如果你想启动一个叫 my_program 的可执行文件,你可以添加如下行
/path/to/my_program & //确保在程序执行后加上 &,表示在后台执行该程序。
- 保存并退出
保存文件并退出编辑器。 - 使脚本可执行(如果需要):
确保脚本具有可执行权限
sudo chmod +x /etc/init.d/rcS
- 验证程序开机启动
重启系统后,程序会随着系统启动自动执行。
总结
-
系统启动:系统加载内核,内核初始化硬件设备,并启动第一个用户进程 init。
-
读取 inittab 文件:init 进程读取 /etc/inittab 文件,执行里面定义的初始化任务。
-
执行 rcS 脚本:init 进程执行 /etc/init.d/rcS 脚本,完成系统的初始化操作,包括挂载文件系统、启动关键服务等。
-
用户程序启动:通过将自定义程序加入 /etc/init.d/rcS 脚本的尾部,系统在启动时会自动运行该程序。