物联网4(嵌入式系统启动过程、U-Boot、Linux内核、根文件系统)(1)

操作系统内核(简称内核或kernel)是初始化内存、设备驱动、中断服务程序、网络堆栈等硬件的程序代码。内核由设备生产商提供或从互联网上下载。

  • 第4步:挂载根文件系统

根文件系统(rootfs)是构成系统启动和运行所需文件和目录的程序代码。

rootfs****的作用:

提供文件和目录组织:使得用户和应用程序能够方便地访问和管理存储在系统中的文件和目录。

启动初始化:根文件系统包含了系统启动所需的各种文件,如内核映像、初始化脚本(如init脚本或Systemd)以及设备文件等。

  • 第5步:启动系统进程

根文件系统挂载完成后,会启动第一个进程,即init进程,其进程编号为1,是其它进程的父进程。init进程在启动后会继续启动其他系统进程和服务,从而为系统提供完整的功能。

  • 第6步:执行应用程序

上面五步执行完成后,嵌入式系统就启动成功了。用户就可以执行应用程序了。

2.S5P6818开发板启动过程

本章节结合S5P6818开发板详述嵌入式系统启动的过程。S5P6818开发板启动过程如下图:

  • 第①步:运行固件

S5P6818开发板上电后,首选运行iROM内的固件,此固件会初始化PLL、时钟和堆栈以及设置看门狗等基础任务,然后选择外存(S5P6818开发板的外存有eMMC、USB1、USB2、USB3、SD0、SD1等,从中选择一个外存),并从选中的外存中将Bootloader加载到iRAM中、最后跳转到Bootloader在iRAM的起始位置。此阶段称为BL0。

  • 第②步:运行引导加载程序

此步骤分为两个阶段:BL1和BL2。对于S5P6818开发板在第①步实际加载到iRAM中的Bootloader只有部分代码,通常是Bootloader前16KB代码,加载并跳转完成后,运行此部分代码,此部分代码会初始化DDR3、关闭缓存、设置栈等基础任务,和将剩余的Bootloader代码加载到DDR3中,并跳转到剩余的Bootloader代码在DDR3的起始位置。此阶段称为BL1。

BL1运行完成后,运行DDR3中剩余的Bootloader代码,此部分代码会初始化外部设备、检测系统内存映射、为内核设置启动参数等,和将内核加载到DDR3中,并跳转到内核在DDR3的起始位置。此阶段称为BL2。

特别说明:并不是所有嵌入式系统都分为BL1和BL2两个阶段,而是一个阶段就可以完成这两个阶段的工作,那么在第①步就会将全部的Bootloader代码加载到内存中。只有一个阶段被称为单阶段,多于一个阶段被称为多阶段。多阶段的优点:能够提供更复杂的功能,及有更好的可读性和移植性。

  • 第③步:运行操作系统内核

内核初始化内存、设备驱动、中断服务程序、网络堆栈等。

  • 第④步:挂载根文件系统

内核初始化完成后,将rootfs加载到DDR3中,并跳转到rootfs在DDR3的起始位置。加载并跳转完成后,运行rootfs,rootfs会创建使用户和应用程序能够方便访问和管理的文件和目录以及初始化脚本和设备文件等。

  • 第⑤步和第⑥步的描述同“嵌入式系统启动过程”章节相同

3.Bootloader种类

Bootloader是严重地依赖于硬件而实现的,特别是嵌入式系统。因此,在嵌入式系统里建立一个通用的Bootloader几乎是不可能的,不同处理器架构和板级设备的嵌入式系统使用不同的Bootloader。Bootloader种类如下表:

种类描述
U-BootU-Boot是由PPCBoot、ARMboot和其他一些arch的Loader合并而成的一个开源Bootloader。它支持多种处理器架构,如ARM、X86、PowerPC、MIPS等。
RedbootRedboot是由Redhat公司发布的一个开源Bootloader。它支持多种处理器架构,如ARM、X86、PowerPC、MIPS、MN10300、Renesas SHx、v850等。
ARMbootARMboot是专为ARM处理器架构设计的一个开源Bootloader。2002年ARMboot终止了维护。
vivivivi是由韩国mizi公司发布的一个Bootlader,适用于ARM9处理器架构。

U-Boot适用于S5P6818开发板,后续会结合U-Boot****对嵌入式系统介绍

4.U-Boot移植

4.1.获取U-Boot源码

获取U-Boot源码的方法有两种:官网下载和从设备厂家获取。

  • 官网下载

官网下载地址:点击下载

不建议通过此种方法获取U-Boot。因为,从官网下载U-Boot源码是一个通用的、未经特定硬件平台定制的版本。不同的嵌入式系统板级设备具有不同的硬件配置、接口和特性,因此需要对U-Boot源码进行定制和修改,以适应特定硬件平台的需求。

  • 从设备厂家获取

建议通过此种方法获取U-Boot。因为,设备厂家提供的U-Boot源码是经过设备厂家定制和修改的,特别适合本家的设备。适用于S5P6818开发板的源码链接地址:   (此链接的U-Boot也是设备厂家提供的)或从设备厂家获取。后续会将此种方法获取的U-Boot移植到S5P6818开发板。

4.2.U-Boot源码目录介绍

目录描述
api这个目录里包含的API是U-Boot本身使用的,与嵌入式系统的硬件无关,通常是演示测试的代码。
arch这个目录存放了与CPU架构相关的文件和代码。每个子目录对应一个特定的CPU架构,如arm、x86、powerpc等,在这些子目录中,你可以找到与架构相关的启动代码、中断处理、时钟设置等。可对不需要的子目录可删除,只保留需要的目录,如针对S5P6818开发板只保留arm子目录即可。
board这个目录包含了与开发板相关的文件和代码。每个子目录都对应一个特定的开发板或评估板,其中包含了该板的初始化代码、硬件配置、外设驱动等。同“arch”目录一样,只保留需要的目录。在U-Boot源码中,没有针对S5P6818开发板的文件,需要开发板厂家提供。
common这个目录存放了U-Boot支持的通用命令的代码。每个命令通常对应一个文件。
disk这个目录包含了与磁盘相关的代码,如分区表、文件系统等。
doc这是U-Boot的文档目录,其中包含了U-Boot的介绍、使用手册、开发指南等文档。
drivers这个目录包含了U-Boot支持的各种设备的驱动程序,如串口、以太网、USB、I2C、SPI等。
dts这个目录存放了设备树源文件(.dts文件),用于描述硬件的结构和配置。
examples这个目录包含了一些示例代码,用于演示U-Boot的功能和用法。
fs这个目录包含了U-Boot支持的文件系统的代码,如FAT、JFFS2等。
include这个目录包含了U-Boot所需的各种头文件,如公共定义、架构相关定义、板级相关定义等。
lib这个目录存放了与架构相关的库文件,如汇编程序、启动代码等。
Licenses这个目录包含了U-Boot源码的许可证文件。
net这个目录包含了与网络相关的代码,如TCP/IP协议栈、BOOTP/DHCP协议等。
post这个目录包含了上电自检相关的代码。
scripts这个目录包含了一些用于生成配置文件、编译U-Boot等任务的脚本文件。
test这个目录包含了一些用于测试U-Boot功能和性能的测试代码和工具。
tools这个目录包含了一些用于开发U-Boot的辅助工具,如二进制文件转换工具、镜像生成工具等。

4.3.U-Boot编译

U-Boot编译是指将U-Boot源码编译成机器语言,即二进制代码。由于设备厂家会对U-Boot的源码进行修改和配置,因此针对不同厂家的设备,编译U-Boot源码的过程会有差异。U-Boot源码的编译过程一般会由设备厂家提供,本教程不讲解U-Boot编译。

编译完成后,会生成一个二进制的ubootpak.bin镜像。

4.4.U-Boot烧写

烧写是指将二进制代买写入到目标设备存储介质中的过程。U-Boot烧写是将ubootpak.bin镜像烧写到eMMC中,以便在设备启动时加载到iRAM和DDR3中并运行。

  • 第1步:在Ubuntu上执行sudo apt-get install android-tools-fastboot命令以安装fastboot驱动

  • 第2步:使用Micro USB数据线链接开发板的OTG USB接口和电脑上安装的Ubuntu

  • 第3步:启动开发板,在uboot三秒倒计时时按下空格键进入uboot模式

  • 第4步:在开发板的uboot模式下输入fastboot命令进入fastboot模式

  • 第5步:输入fastboot命令后,Ubuntu会弹出如下图弹窗,选择“链接到虚拟机”并点击“确定”按钮

  • 第6步:在Ubuntu上进入ubootpak.bin所在目录,输入fastboot flash ubootpak ubootpak.bin命令进行烧写

  • **第7步:**烧写成功后,在开发板的fastboot模式下可以看到“Flash : ubootpak – DONE”提示。按Ctrl+C退出fastboot模式,进入uboot模式,然后输入reset命令以重启开发板

4.5.U-Boot命令介绍

4.5.1.查看U-Boot****命令
  • 查看U-Boot支持的命令

启动开发板,在uboot三秒倒计时按下空格键进入uboot模式,然后输入help命令,即可查看U-Boot所有的命令。如下图:

  • 查看某个U-Boot****命令的详细用法

用法:help 。为命令名称。示例如下图:

4.5.2.U-Boot命令分类
分类命令描述
基础命令help显示命令的用法
version显示U-Boot的版本信息
bdinfo显示板卡信息
reset重启系统
sleep使U-Boot延迟执行一段时间
test执行一些简单的自检程序
环境变量操作命令printenv打印环境变量的值
setenv设置环境变量的值
saveenv保存环境变量的更改
内存操作命令md显示内存内容
mw修改内存内容
cp在内存中复制数据块
cmp比较内存区域的内容
crc32计算内存区域的CRC32校验和
网络命令ping发送ICMP ECHO_REQUEST到网络主机
dhcp通过DHCP获取IP地址
tftpboot过TFTP协议从网络服务器下载文件
nfs通过NFS协议挂载网络文件系统
httpd启动一个简单的HTTP服务器
存储命令mmc操作MMC/SD/eMMC存储设备
nand操作NAND闪存设备
ubi操作UBI
ubifs操作UBIFS文件系统
fatload用于从外部存储设备(如 USB 闪存驱动器、SD 卡等)加载数据到内存中
fatls用于查询 FAT 格式设备的目录和文件信息
fatinfo用于查询指定 MMC 设备分区的文件系统信息
ext2load用于从EXT2格式的设备中加载文件到内存中
ext2ls用于列出EXT2格式设备的目录和文件信息
ext4load用于从EXT4格式的设备中加载文件到内存中
ext4ls用于列出EXT4格式设备的目录和文件信息
引导加载命令bootm从内存引导内核映像
bootp通过BOOTP/DHCP协议获取网络引导信息
bootz引导zImage格式的内核映像
go跳转到指定地址执行代码
Flash内存操作命令base用于设置或显示操作的基地址
erase用于擦除Flash内存的一个或多个区块
protect用于设置Flash内存区域的写保护状态。写保护的区域将无法被擦除或写入新数据
unprotect移除Flash内存区域的写保护状态,使得这些区域可以再次被擦除或写入新数据
加载命令loadb通过串行接口以字节为单位加载数据
loads通过串行接口以字符串为单位加载数据

上述未全部列出S5P6818开发板已有的命令,和列出了S5P6818开发板未有的命令

每个命令的具体用法在后续用到时再详细介绍

4.6.添加U-Boot命令

U-Boot支持用户添加自定义命令,为嵌入式系统的开发和维护提供了便利。

4.6.1.U-Boot命令添加过程

第1步:在common目录中创建一个C源文件(文件名常以cmd_开头)

第2步:在文件中添加<common.h>和<command.h>头文件

第3步:在文件中添加命令处理函数,代码如下:

static fun_name(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
    // 命令处理代码
    return 0;  // 返回0表示成功,非0表示出错
}

/*
 * 形参说明:
 * cmdtp是一个指向cmd_tbl结构体的指针,它包含了命令的元信息。cmd_tbl结构体定义了命令的名称、最大参数数量、重复标志、处理函数指针以及帮助字符串等。
 * flag是一个整数,用于传递命令的标志。标志可以指示命令的特定行为或状态。这个形参并不常用,经常被忽略或设置为0。
 * argc表示传递给命令的参数数量。
 * argv是一个字符串数组,包含了传递给命令的实际参数值。
 */

第4步:在文件中添加U_BOOT_CMD宏,用来注册新命令,代码如下:

U_BOOT_CMD(
    mycommand,                     // 命令名称
    CONFIG_SYS_MAXARGS,            // 最大参数数量
    repeatable,                    // 是否可重复(1表示可重复,0表示不可重复)
    fun_name,                      // 命令处理函数名称
    "description of command",      // 命令描述
    "Usage:\n"
    "command arg1 arg2 ...\n"      // 使用方法
);

第5步:在common/Makefile添加如下代码:

obj-y += <创建的.c文件名>.o

第6步:编译并烧写

第7步:测试是否添加成功

4.6.2.示例:自我介绍

第1步:在common目录中创建一个cmd_introduce.c文件

第2、3、4步:在cmd_introduce.c添加头文件、命令处理函数和U_BOOT_CMD宏,代码如下:

#include <common.h>
#include <command.h>

static introduce(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
    printf("大家好,我是%s,今年%s岁,来自%s\n", argv[1], argv[2], argv[3]);

    return 0;
}

U_BOOT_CMD(
    itd,  // itd是introduce的缩写
    4,
    1,
    introduce,
    "自我介绍",
    "Usage:\n"
    "itd [name] [age] [homeplace]\n"
    "  - [name] 姓名\n"
    "  - [age] 年龄\n"
    "  - [homeplace] 出生地\n"
);

第5步:在common/Makefile添加如下代码

obj-y += cmd_introduce.o

第6步:编译并烧写,烧写成功后输入reset命令重启

第7步:测试

  • 输入help命令,查看是否有itd命令

  • 输入help itd命令,查看itd命令用法

  • 输入itd命令,查看是否按照规则输出

5.内核种类

市场上有很多内核,常见的有Linux内核、Windows内核、macOS内核和FreeBSD内核等,广泛应用于嵌入式系统的是Linux内核。

6.Linux内核移植

6.1.获取Linux内核源码

获取Linux内核源码的方法同获取“U-Boot源码”的方法一样有两种:官网下载和从设备厂家获取。同样建议使用设备厂家提供的Linux内核源码。

官网下载地址:点击下载

6.2.Linux内核源码目录介绍

目录描述
arch这个目录存放了与CPU架构相关的文件和代码。每个子目录对应一个特定的CPU架构,如arm、x86、powerpc等。可对不需要的子目录可删除,只保留需要的目录,如针对S5P6818开发板只保留arm子目录即可
block这个目录存放了块设备驱动相关代码
crypto这个目录包含了加密相关的代码,用于提供安全的数据加密和解密功能
Documentation这是内核的文档目录,其中包含了内核的介绍、使用手册、开发指南等文档
drivers此目录包含了各种硬件设备的驱动程序,如USB总线驱动程序、PCI总线驱动程序、显卡驱动程序、网卡驱动程序等。这些驱动程序使得操作系统能够与硬件设备进行通信和交互
firmware这个目录存放了与固件相关的代码。固件是硬件设备的一部分,负责提供设备的初始化和配置信息,以使设备能够正常工作
fs这个目录存放了虚拟文件系统相关代码。每个虚拟文件系统(如ext2、ext3、ext4、fat、nfs等)都在这个目录下有其对应的目录
include这个目录包含了内核源码所依赖的大部分头文件代码,这些头文件定义了内核中使用的各种数据结构、函数原型和宏定义
init这个目录包含了Linux内核的初始化相关代码,这些代码负责在内核启动时进行系统资源的初始化和配置
ipc这个目录包含了进程间通信相关代码,如信号量、消息队列、共享内存等机制的实现
kernel这个目录包含了内核的核心代码,如调度器、进程管理、系统调用等功能的实现
lib这个目录包含了内核的库代码,这些库为内核的其他部分提供了通用的函数和数据结构
mm这个目录包含了所有的内存管理代码,如页框分配、虚拟内存管理、交换空间管理等
net这个目录包含了网络相关的代码,如网络协议栈、网络设备驱动等
samples这些目录包含了示例代码,用于辅助内核的开发、测试和部署
scripts这些目录包含了脚本,用于辅助内核的开发、测试和部署
tools这些目录包含了工具,用于辅助内核的开发、测试和部署
security这些目录包含了与安全相关的代码,如访问控制、安全模块等
sound这些目录包含了音频设备驱动和相关的代码
virt这些目录包含了虚拟化相关的代码,用于支持虚拟机、容器等虚拟化技术

6.3.Linux内核编译

Linux内核编译同U-Boot编译一样,也是将内核源码编译成机器语言。编译的源码也是采用设备厂家提供的源码,编译步骤使用设备厂家提供的教程,而本教程不讲解Linux内核编译。

编译完成后,会生成一个二进制的boot.img镜像。

6.4.Linux内核烧写

Linux内核的烧写与U-Boot的烧写一样,只是在第⑥步有所不同:在Ubuntu上进入boot.img所在目录,输入fastboot flash boot boot.img命令进行烧写

6.5.定制Linux内核

用户根据自己的需求,可以定制Linux内核。例如,只选择自己需要的模块进行编译,以减小内核体积;或者修改各种参数以优化性能;还可以开启或关闭各种功能以满足特定的应用场景等。

定制Linux内核常使用Menuconfig工具,它提供一个交互式的菜单界面(如下图),使得用户可以方便地选择需要编译的内核选项和模块。

6.5.1.Menuconfig工具操作说明
按键描述
上下方向键切换菜单
左右方向键切换Menuconfig工具底部、、三个按钮
Enter键确认键,起到确认作用
高亮字母如上图菜单和底部按钮的第一个字母是高亮。在键盘上按下对应高亮字母键,可直接选择对应的菜单或底部按钮
空格键选择目标菜单后按空格键切换启用、禁用和可加载模块(启用是指菜单对应的功能会被编译;禁用是指菜单对应功能不会被编译;可加载模块是指将菜单对应的功能编译为可加载模块,可加载模块特点在于功能不被编译进镜像,在编译时会被单独处理,生成独立的文件(通常以.ko为扩展名)。在需要这个功能时,可通过insmod、rmmod或modprobe等命令将这个功能动态地加载进内核;而不需要时,可以将它从内存中卸载掉,从而避免功能驻留在内存中,造成内存浪费)。启用、禁用和可加载模块对应的快捷键分别是Y、N和M
双击Esc键放回上一级或退出Menuconfig工具。功能同底部键
?键查看相应菜单的帮助信息。功能同底部键
/键按下/键后进入搜索界面,然后输入文字进行搜索
其它说明: [ * ]和< * >:表示启用 [ ]和< >:表示禁用 < M >:表示可加载模块
6.5.2.定制Linux内核步骤

第1步:执行sudo apt-get install libncurses5-dev命令安装ncurses库(ncurses库为Menuconfig工具提供界面支持)

第2步:在顶层目录下输入make menuconfig命令打开Menuconfig工具。注意:若报Your display is too small to run Menuconfig!错误,则说明终端窗口太小,请调大终端窗口

第3步:根据自己的需求按照“Menuconfig工具操作说明”章节操作菜单

第4步:退出Menuconfig工具,在退出时请选择“yes”按钮保存配置,如下图:

保存成功后,会将配置结果更新于顶层目录的.config文件中,以便编译时使用。

第5步:编译并烧写

6.6.添加Linux内核功能

为满足特定的需求,用户可以根据需求编写代码添加到Linux内核中。

6.6.1.过程

第1步:在相应目录中创建一个C源文件

第2步:在C源文件中编写功能代码

第3步:在C源文件所在目录中的Kconfig文件中添加配置选项代码

  • 备注1:若目录中没有Kconfig文件,则需要创建Kconfig文件
  • 备注2:Kconfig文件的语法规则参考“Kconfig系统”章节
  • 备注3:此步骤可选,若想要此功能像“定制Linux内核”章节介绍的一样可配置,则需要此步骤,否则可忽略此步骤

第4步:在C源文件所在目录中的Makefiel文件中添加如下代码:

obj-y += <创建的.c文件名>.o
或
obj-$(CONFIG_<第②步添加的配置选项标识>) += <创建的.c文件名>.o

  • 备注1:若在Makefile文件中添加的是上述第一句代码,则创建的C源文件会被无条件的编译
  • 备注2:若在Makefile文件中添加的是上述第二句代码,则创建的C源文件是否被编译由第3步添加的配置选项控制

第5步:编译并烧写

第6步:测试是否添加成功

6.6.2.示例:添加Beep驱动

第1步:在drivers/char目录中创建一个beep_driver.c文件

第2步:在beep_driver.c文件中编写功能代码,代码如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/io.h>
#include <mach/gpio.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <mach/soc.h>
#include <mach/platform.h>

#define LED_0 0

struct class *beep_class;
struct device *beep_device;
static int major = 0;

static int beep_open(struct inode *pnode, struct file *filp)
{
	return 0;
}

static ssize_t beep_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
	int value = 0;
	
	if(count!=1)
		return -1;
	if(nxp_soc_gpio_get_io_dir(PAD_GPIO_C+14))
		value = nxp_soc_gpio_get_out_value(PAD_GPIO_C+14);
	else
		value = nxp_soc_gpio_get_in_value(PAD_GPIO_C+14);

	return sizeof(int)-copy_to_user(buff,&value,sizeof(int));
}

static ssize_t beep_write(struct file *pnode,const char __user *buff,size_t count,loff_t *offp)
{
	int value = 0;
	int len = 0;
	
	if(count>sizeof(int))
		return -1;
	len = copy_from_user(&value,buff,sizeof(int));
 	nxp_soc_gpio_set_out_value(PAD_GPIO_C+14,(!(!value)));
	
	return (1-len);
}

static long beep_ioctl(struct file *filp, unsigned int cmd, unsigned long data)
{
	if(cmd)
		nxp_soc_gpio_set_out_value(PAD_GPIO_C+14,1);
	else
		nxp_soc_gpio_set_out_value(PAD_GPIO_C+14,0);
	
	return 0;
}

static int beep_release(struct inode *pnode, struct file *filp)
{
	return 0;
}

static struct file_operations fops = {
	.owner = THIS_MODULE,
	.read = beep_read,
	.write = beep_write,
	.open = beep_open,
	.unlocked_ioctl = beep_ioctl,
	.release =  beep_release,
};


static int __init beep_init(void) 
{
	major = register_chrdev(0,"vmotor_drv", &fops);
	if(major<0)
	{
		printk(KERN_CRIT "error register_chrdev\n");
		goto err_reg;
	}
	beep_class=class_create(THIS_MODULE, "vmotor_cls");
	if(IS_ERR(beep_class))
	{
		printk(KERN_CRIT "error class_create\n");
		goto err_cls;
	}
	beep_device=device_create(beep_class, NULL,MKDEV(major,0),NULL,"vmotor");
	if(IS_ERR(beep_device))
	{
		printk(KERN_CRIT "error device_create\n");
		goto err_dev;
	}
	nxp_soc_gpio_set_io_dir(PAD_GPIO_C+14, 1);

	
	return 0;
err_dev:
	class_destroy(beep_class);
err_cls:
	unregister_chrdev(major,"vmotor_drv");
err_reg:
	return -1;
};

static void __exit beep_exit(void) 
{
	unregister_chrdev(major,"vmotor_drv");
	device_destroy(beep_class,MKDEV(major,0));
	class_destroy(beep_class);

	return;
};

module_init(beep_init);
module_exit(beep_exit);
MODULE_LICENSE("GPL");

第3步:在drivers/char目录中的Kconfig文件中添加如下代码

config BEEP_DRIVER
	bool "Provide the on and off interface for the Beep"
	default y

第4步:在drivers/char目录中的Makefiel文件中添加如下代码

obj-$(CONFIG_BEEP_DRIVER) += beep_driver.o

第5步:编译并烧写

第6步:测试

  • 第①步:在Ubuntu中创建beep.c文件,代码如下。此代码用于调用beep_driver.c提供的接口,以便测试“beep驱动”是否添加成功
#include <stdio.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <string.h>
#include <asm/ioctl.h>
#include <linux/input.h>

#define BCT3288A_CLOSE  _IO('B',1)
#define BCT3288A_CLEAR  _IO('B',2)

int main(int argc,char *argv[])
{
	int fd;
	int value;
	unsigned char buf[2];
	
	fd = open("/dev/beep",O_RDWR);
	if(fd<0)
	{
		perror("dev tain open:");
		return 1;
	}
	if(argc > 0)
	{
		value = atoi(argv[1]);
	}

	ioctl(fd,value);
	close(fd);
	
	return 0;
}

  • 第②步:交叉编译beep.c文件,执行交叉编译的代码如下
arm-linux-gcc -o beep beep.c
  • 第③步:通过nfs、U盘或SD卡等方式将交叉编译生成的beep文件拷贝到开发板
  • 第④步:在开发板的用户模式下执行如下命令
./beep 1        // 此命令会使开发板开启Beep响
./beep 0        // 此命令会使开发板关闭Beep响

6.7.Kconfig系统

Kconfig系统是Linux内核的一个配置系统,方便用户通过配置界面浏览和修改内核的选项,如上述“Linux内核选项裁剪”章节就是通过Kconfig系统的配置界面选中或取消选中相应选项来决定对应功能是否被编译。

Kconfig系统通过Kconfig文件来定义和描述内核中的选项,这些Kconfig通常位于内核源码的各子目录中,如顶层目录的Kconfig文件是Kconfig系统的入口点,arch/arm/Kconfig文件用于定义ARM架构相关的选项,driver/char/Kconfig文件用于定义字符设备驱动的选项。

6.7.1.Kconfig文件的语法
关键字描述
#注释
source作用:引用Kconfig文件,相当于C语言的include。父目录中的Kconfig文件通常使用source关键字引用其子目录中的Kconfig文件。 格式:source <Kconfig文件名> 示例:source “arch/arm/Kconfig”
menu/endmenu作用:定义一个菜单。menu和endmenu两个关键字成对出现,选项写在两个关键字之间 格式:menu <菜单名称>                   # 选项           endmenu 示例:menu “First Menu”                   config FIRST_CONFIG                           bool “Enable first config”                             config SECOND_CONFIG                           bool “Enable second config”                          default n            endmenu
config作用:选项的标识。 格式:config <标识名称> 示例:config FIRST_CONFIG
bool tristate string hex int作用:定义选项的类型 类型:         bool:布尔类型,使用此类型选项只能选中(即功能被编译进镜像)或不选中(即功能不被编译)。         tristate:三态类型,使用此类型的选项只能选中(即功能编译进镜像)、不选中(即功能不被编译)或选择为M(即功能编译成模块。)。         string:字符串类型,使用此类型的选项需要输入字符串。         hex:十六进制类型,使用此类型的选项需要输入十六进制。         int:整型类型,使用此类型的选项需要输入整数。
prompt作用:定义选项在配置界面上显示的文本,用于介绍选项,或可以理解为选项的标题 格式:prompt <文本>           也可以省略“prompt”关键字,<文本>直接写在选项类型后面 示例:bool                   prompt “用于介绍选项”                  或                  bool “用于介绍选项” 注意:若选项没有定义prompt,且类型后面没有写<文本>,则配置界面不会显示此选项
default作用:设置选项的默认值           bool类型的值可为y(选中)和n(不选中)           tristate类型的值可为y(选中)、n(不选中)和m(编译成模块) 格式:default <值> 示例:bool                   default y           tristate m                   default m           string                   default hellowrold           hex                   default 0x12           int                   default 10
help作用:用于详细描述选项,帮助用户理解该选项的作用 格式:help           <描述选项的文本>
depends on用:用于指定选项依赖于另一个选项。如果依赖的选项没有被选中,那么此选项将不会显示 格式:depends on <依赖的选项的标识> 示例:config PARENT                   bool “被依赖的配选项”            config CHILD                   bool “子选项”                   depends on PARENT
select作用:当一个选项被选中时,自动选中另一个选项 格式:select <要自动选中的选项的标识> 示例:config ACTIVE_SELECTED                   bool “手动选中的选项”                   select AUTO_SELECTED           config AUTO_SELECTED                   bool “自动选中的选项”
choice/endchoice作用:定义一组选项,用户只能从组中选择一个选项 格式:choice                    prompt <组介绍文本>                    # 选项            endchoice 示例:choice                    prompt “选项组”                    config GROUP_OPTION_ONE                            bool “组成员1”                    config GROUP_OPTION_TWO                            bool “组成员2”                    config GROUP_OPTION_THREE                            bool “组成员3”                    config GROUP_OPTION_FOUR                            bool “组成员4”            endchoice

上表关键字只是Kconfig语法的部分关键字,其它关键字请自行百度

6.7.2.Kconfig系统示例

第1步:创建myconfig/Kconfig文件

第2步:在顶层目录中的Kconfig文件添加source “myconfig/Kconfig”

第3步:在“myconfig/Kconfig”文件中编写代码,代码如下:

menu "Example Menu"                           # 定义一个菜单

    config FIRST                              # 创建一个标识为FIRST的选项
        bool "Bool类型选项"                    # 定义选项的类型为bool,且标题为“Bool类型选项”
        default y                             # 选项的默认值为 y

    config SECOND
        tristate "Tristate类型选项"            # 定义选项的类型为tristate
        default m                             # 选项的默认值为 m

    config THIRD
        string "String类型选项"                # 定义选项的类型为string
        default third                         # 选项的默认值为 third

    config FOURTH
        hex "Hex类型选项"                      # 定义选项的类型为hex
        default 0x11                          # 选项的默认值为 0x11

    config FIFTH
        int "Int类型选项"                      # 定义选项的类型为int
        default 10                            # 选项的默认值为 10

    config SIXTH
        bool
        prompt "使用prompt定义选项配置的标题"

    config SEVENTH
        bool                                  # 没有定义选项的名称,则此选项不会在配置界面上显示

    config EIGHTH
        bool "测试help关键词"
        help
            这里用于详细描述此选项的用法


    config PARENT                             # 测试选项间的依赖性
        bool "被依赖的选项"                     # PARENT选项被选中,则显示CHILD选项,否则隐藏
                                              #
    config CHILD                              #
        bool "子选项"                          #
        depends on PARENT                     #


    config ACTIVE_SELECTED                    # 测试选项间的联动性
        bool "手动选中的选项"                   # ACTIVE_SELECTED选项被选中,则AUTO_SELECTED选项也被选中
        select AUTO_SELECTED                  #
                                              #
    config AUTO_SELECTED                      #
        bool "自动选中的选项"                   #


    choice                                    # 单选
        prompt "Option group"                 #
                                              #
        config OPTION_A                       #
            bool "A"                          #
                                              #
        config OPTION_B                       #
            bool "B"                          #
                                              #
        config OPTION_C                       #
            bool "C"                          #
                                              #
        config OPTION_D                       #
            bool "D"                          #
    endchoice                                 #

endmenu

第4步:测试。输入make menuconfig命令打开配置界面

代码配置界面截图
menu “Example Menu”
config FIRST                                        bool "Bool         default y
config SECOND         tristate “Tristate类型选项”               default m
config THIRD         string “String类型选项”                      default third选中此选项,按Enter键,可输入文字,如下图:
config FOURTH         hex “Hex类型选项”                            default 0x11选中此选项,按Enter键,可输入十六进制,如下图:
config FIFTH         int “Int类型选项”                            default 10选中此选项,按Enter键,可输入十进制整数,如下图:
config SIXTH         bool         prompt “使用prompt定义选项配置的标题”
config SEVENTH         bool在配置界面不显示
config EIGHTH         bool “测试help关键词”         help                 这里用于详细描述此选项的用法选中此选项,选此,按Enter键,可查看此选项详细描述,如下图:
config PARENT                                         bool “被依赖的选项”                                                                    config CHILD                                         bool “子选项”                                depends on PARENT未选中隐藏 选中显示
config ACTIVE_SELECTED         bool “手动选中的选项”         select AUTO_SELECTED config AUTO_SELECTED                                bool “自动选中的选项”前者未选中 前者选中,后者自动选中
choice         prompt “Option group”         config OPTION_A                 bool “A”         config OPTION_B                 bool “B”         config OPTION_C                 bool “C”         config OPTION_D                 bool “D” endchoice选中此选项,按Enter键,可选择成员,如下图:

7.跟文件系统

7.1.磁盘分区

磁盘分区是指通过分区工具将磁盘划分为几个逻辑部分,如Windows系统上有C盘、D盘和E盘,就是磁盘的三个分区。

磁盘分区又分为主分区和扩展分区。主分区是能够安装操作系统和进行计算机启动的分区,通常情况下Windows系统的C盘是主分区。

Windows系统是基于盘符进行分区的,如C盘、D盘、E盘,对盘符的操作相当于对于分区的操作。

Linux系统是基于设备文件进行分区的,如sda1,sda2,hda1,hdb1(设备文件的前两个字符表示磁盘的接口类型,如sd表示SATA接口的磁盘,hd表示IDE接口的磁盘,第三个字符表示不同的磁盘,最后一个字符表示该磁盘上的分区编号)。若要操作分区需要将设备文件与目录关联起来,关联的过程被称为挂载,被关联的目录被称为挂载点。例如,将sda2挂载到/home目录上,对/home目录操作就相当于对sda2对应的分区进行操作。

7.2.文件系统

文件系统是负责管理和存储文件信息的程序,简化了用户对文件的各项操作,为用户提供了共享和保护等功能。若是没有文件系统,会给用户带来大的操作成本,如用户需要熟悉外存的物理特性、文件的属性和文件在外存上的存储位置等。

文件系统用于管理外存,即我们通常所说的硬盘、闪存、光盘等存储设备,不用于管理内存。

分区和文件系统的关系:

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!

2对应的分区进行操作。

7.2.文件系统

文件系统是负责管理和存储文件信息的程序,简化了用户对文件的各项操作,为用户提供了共享和保护等功能。若是没有文件系统,会给用户带来大的操作成本,如用户需要熟悉外存的物理特性、文件的属性和文件在外存上的存储位置等。

文件系统用于管理外存,即我们通常所说的硬盘、闪存、光盘等存储设备,不用于管理内存。

分区和文件系统的关系:

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-tjHORDs9-1715759642021)]

[外链图片转存中…(img-GgH9P1QC-1715759642022)]

[外链图片转存中…(img-8RTdB4Xu-1715759642023)]

[外链图片转存中…(img-nDXfoCG8-1715759642023)]

[外链图片转存中…(img-I6ubcePU-1715759642024)]

[外链图片转存中…(img-1DupKjml-1715759642024)]

[外链图片转存中…(img-5IQLUOEX-1715759642025)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值