深度解析:嵌入式之uboot

来源自:雷电竞下载APP官网    点击数:1   发布时间:2025-06-01 11:32:10

  【下载】嵌入式Linux与物联网软件开发——C语言内核深度解析 (朱有鹏)

  【帖子】[求助]嵌入式linux万里长征第一坑,UBOOT遇到密码怎么办?

  【帖子】嵌入式学习丨4412开发板-uboot源码-汇编-源码分析(一)

  【帖子】做嵌入式应用层QT C++的待遇和发展前途,普遍要比嵌入式linux底层驱动开发好的多吗?

  【设计】DepthAI:一个结合深度和人工智能的嵌入式平台,围绕 Myriad X 构建

  【文章】uboot-2011.12移植到S3C2440(序四)—— uboot.lds分析与解析

  【文章】干货分享|解锁 TI 培训课程,TI Edge AI Cloud - 嵌入式深度学习评估工具系列视频

  有奖直播报名中基于英飞凌AIROCTM CYW20829低功耗蓝牙芯片的无线组网解决方案

  MPS 隔离式稳压 DCDC 模块——MIE系列,小且不凡!痛点讨论你理想中的电源模块是怎样的?

  【实时抽奖】MPS AI电源解决方案合集,咨询抽【按摩眼罩、体脂秤、电脑支架】等好礼

  自动驾驶SoC研究:10-20万乘用车,50-100T大算力SoC将扎堆量产

  5999元起,iPhone 15系列发布:C口+钛合金+全员灵动岛+5倍潜望长焦

  丹麦最神奇门将:踢球时思考数学,37岁拿诺奖,与爱因斯坦吵吵一辈子,开创了量子力学

  与小编一起完成8000公里穿越,在electronica 2018来一场华丽的邂逅

  The JPEG Still Picture Compression Standard

  (1)计算机系统就是以CPU为核心来运行的系统。典型的计算机系统有:PC机(台式机+笔记本)、嵌入式设备(手机、平板电脑、游戏机)、单片机(家用电器像电饭锅、空调)

  (2)计算机系统的组成部件非常多,不同的计算机系统组成部件也不同。但是所有的计算机系统运行时需要的主要核心部件都是3个东西:

  CPU + 外部存储器(Flash/硬盘) + 内部存储器(DDR SDRAM/SDRAM/SRAM)

  典型的PC机的BIOS程序部署在PC机主板上(随主板出厂时已经预制了),操作系统部署在硬盘上,内存在掉电时无作用,CPU在掉电时不工作。

  PC上电后先执行BIOS程序(实际上PC的BIOS就是NorFlash),BIOS程序负责初始化DDR内存,负责初始化硬盘,然后从硬盘上将OS镜像读取到DDR中,然后跳转到DDR中去执行OS直到启动(OS启动后BIOS就无用了)

  )上、OS部署在FLash(嵌入式系统中用Flash代替了硬盘)上、内存在掉电时无作用,CPU在掉电时不工作。

  (2)启动过程:嵌入式系统上电后先执行uboot、然后uboot负责初始化DDR,初始化Flash,然后将OS从Flash中读取到DDR中,然后启动OS(OS启动后uboot就无用了)

  总结:嵌入式系统和PC机的启动过程基本上没有两样,只是BIOS成了uboot,硬盘成了Flash。

  (1)Android系统的启动和Linux系统(前面讲的典型的嵌入式系统启动)几乎一样。几乎一样意思是前面完全一样,只是在内核启动后加载根文件系统后不同了。

  (2)可以认为启动分为2个阶段:第一个阶段是uboot到OS启动;第二个阶段是OS启动后到rootfs加载到命令行执行;现在我们主要研究第一个阶段,android的启动和linux的差别在第二阶段。

  uboot经过多年发展,慢慢的变成了事实上的业内bootloader标准。

  现在大部分的嵌入式设备都会默认使用uboot来做为bootloader。

  (2)uboot的核心部分几乎没怎么变化,越新的版本支持的开发板越多而已,对于一个老版本的芯片来说,新旧版本的uboot并没有差异。

  (1)uboot就是universal bootloader(通用的启动代码),通用的意思是在各种地方都可以用。

  (2)uboot具有可移植性并不是说uboot在哪个开发板都可以随便用,而是说uboot具有在源代码级别的移植能力,可以针对多个开发板进行移植,移植后就可以在这个开发板上使用了。

  (1)一般的SoC都支持多种启动方式,譬如SD卡启动、NorFlash启动、NandFlash启动等·····uboot要能够开机启动,

  (2)uboot一定要进行和硬件相对应的代码级别的更改和移植,才能确保可以从相应的启动介质启动。uboot中第一阶段的start.S文件中具体处理了这一块。

  uboot中事先给linux内核准备一些启动参数放在内存中特定位置然后传给内核

  内核启动后会到这个特定位置去取uboot传给他的参数,然后在内核中解析这些参数,这些参数将被用来指导linux内核的启动过程。

  (1)uboot一定要能被人借助而完成总系统(包括uboot、kernel、rootfs等的镜像)在Flash上的烧录下载工作。

  fastboot功能将各种镜像烧录到iNand中,然后从iNand启动。

  (1)uboot中实现了一部分硬件的控制能力(uboot中初始化了一部分硬件),因为uboot为完成一些任务必须让这些硬件工作。譬如uboot要实现刷机必须能驱动iNand,譬如uboot要在刷机时LCD上显示进度条就必须能驱动LCD,譬如uboot可以通过串口提供操作界面就必须驱动串口。譬如uboot要实现网络功能就必须驱动网卡芯片。

  (2)SoC级(譬如串口)就是SoC内部外设,板级就是SoC外面开发板上面的硬件(譬如网卡、iNand)

  (1)uboot的生命周期就是指:uboot何时开始运行,何时结束运行。

  不是操作系统),一旦uboot开始SoC就会单纯运行uboot(意思是uboot运行的时候别的程序是不可能同时运行的),一旦uboot结束运行则无法再回到uboot(所以uboot启动了内核后uboot自己本身就死了,要想再次看到uboot界面只能重启系统。重启并不是复活了刚才的uboot,重启只是uboot的另一生)

  (3)uboot的入口和出口。uboot的入口就是开机自动启动,uboot的

  uboot还可以执行很多别的任务(譬如烧录系统),但是其他任务执行完后都可以回到uboot的命令行继续执行uboot命令,而启动内核命令一旦执行就回不来了。

  (1)uboot的本质就是一个裸机程序,和我们裸机全集中写的那些裸机程序xx.bin并没有本质区别。

  我们写的大部分小于16KB,而uboot大于16KB(一般uboot在180k-400k之间)

  (2)uboot本身是一个开源项目,由若干个.c文件和.h文件组成,配置编译之后会生成一个uboot.bin,这就是uboot这个裸机程序的镜像文件。

  也就是说uboot在没有运行时表现为uboot.bin,一般躺在启动介质中。

  (3)uboot运行时会被加载到内存中然后一条指令一条指令的拿给CPU去运行。

  (2)有些程序需要和人进行交互,于是乎程序中就实现了一个shell(shell就是提供人机交互的一个界面,回想ARM裸机全集第十六部分),uboot就实现了一个shell。

  linux中打开一个终端后就得到了一个shell,可以输入命令回车执行。

  uboot中的shell工作方式和linux中的终端shell非常像(其实几乎是一样的,只是命令集不一样。

  (1)uboot启动后大部分时间和工作都是在shell下完成的(譬如uboot要部署系统要在shell下输命令、要设置环境变量也得在命令行地下,要启动内核也要在命令行底下敲命令)

  uboot中有几十个命令,其中有一些常用另一些不常用(我们还能自己给uboot添加命令),后面会用几节课时间来依次学习uboot中常用命令。

  (3)uboot的环境变量和操作系统的环境变量工作原理和方式几乎完全相同。

  uboot在设计时借助了操作系统的设计理念(命令行工作方式借鉴了linux终端命令行,环境变量借鉴了操作系统的环境变量,uboot的驱动管理几乎完全照抄了linux的驱动框架)。

  (4)环境变量可以被认为是系统的全局变量,环境变量名都是系统内置的(认识就认识,不认识就不认识,这部分是系统自带的默认的环境变量,譬如PATH;

  但是也有一部分环境变量是自己添加的,自己添加的系统就不认识但是我们自己认识)。

  这样设计的优点是灵活,譬如我们要让一个程序更改运行方法,不用去重新修改程序代码再重新编译运行,而只要修改相应的环境变量就可以了。

  当我们向终端命令行输入命令的时候,这些命令没有立即被系统识别,而是被缓冲到一个缓存区(也就是系统认为我们还没有输入完),当我们按下回车键(换行)后系统就认为我们输入完了,然后将缓冲区中所有刚才输入的作为命令拿去分析处理。

  (3)有些命令有简化的别名,譬如printenv命令可以简化为print,譬如setenv可以简化为set

  (4)有些命令会带参数(注意格式是固定的),uboot的每个命令都有事先规定好的各种格式。

  有些命令带可选的参数(可以带也可以不带,当然带不带参数的执行结果是不同的);

  (5)采用“help+命令名”来查询命令的详情信息,只输入help时,则打印出命令列表。

  (1)命令族意思就是好多个命令开头都是用同一个命令关键字的,但是后面的参数不一样,这些命令的功能和作用也不同。

  (2)同一个命令族中所有的命令都有极大的关联,譬如movi开头的命令族都和moviNand(EMMC、iNand)操作有关。

  程序中任何地方都能够准确的通过需要去调用或者更改环境变量(一般都是调用),环境变量和全局变量不同之处在于:

  全局变量的生命周期是在程序的一次运行当中,开始运行时诞生程序结束时死亡,下次运行程序时从头开始;

  但是环境变量被存储在Flash的另一块专门区域(Flash上有一个环境变量分区),一旦我们在程序中保存了该环境变量,那么下次开机时该环境变量的值将维持上一次更改保存后的值。

  saveenv/save命令不带参数,直接执行,作用是将内存中的环境变量的值同步保存到Flash中环境变量的分区。

  环境变量的保存是整体的覆盖保存,也就是说内存中所有的环境变量都会整体的将Flash中环境变量分区中原来的内容整体覆盖。

  有时候我们只是想测试下这个环境变量,不希望影响到下一次开机,那就只set不save,这样set后当前本次运行的uboot已经起效果了,只不过没save下一次开机还是会恢复到原来的状况。

  设置主机windows的本地连接IPv4地址为192.168.1.10

  最重要的是ipaddr(这个环境变量表示当前开发板的IP地址),这个地址必须和

  一部分是网段地址,另一部分是网段内的主机地址(由子网掩码来区分哪一部分是网段地址,哪一部分是IP地址)。

  在子网掩码是255.255.255.0的情况下,192.168.1.10这个IP地址的前三部分(192.168.1.)属于网段地址,第4部分(10)属于主机地址。

  uboot中的ipaddr和主机windows本地连接地址已经设置到一个网段,但是实际还ping不通。

  1、我把2个的网段都从192.168.1.x改到192.168.0.x时会ping通一次,第二次开始就ping不通了;

  2、有同学说ping不通原因是uboot中gatewayip没设置,我就实际测试设置网管为同网段.1,再次测试结论是第一次ping通了,第二次开始又不通了。

  (1)先将开发板刷机成linux+QT镜像(刷机见裸机教程第三部分),然后启动进入linux命令行终端下。

  (2)在linux下使用ifconfig命令将开发板中linux系统的IP地址设置为和主机windows同一网段(为了上课方便,以后就固定:

  主机windows地址192.168.1.10,开发板uboot或linux的地址为192.168.1.20,虚拟机ubuntu地址为192.168.1.141)

  首先开发板和主机的网络部分硬件都是好的,网络连接也是好的,主机windows中的网络软件设置是好的。

  虚拟机的网卡设置可以再一次进行选择好几种方式,常用的就是NAT和桥接(bridged)。

  如果选了自动,那么虚拟机会自动桥接到无线网卡上,但是我们却是通过有线网卡来连接开发板的,自然ping不通)

  在虚拟机ubuntu中设置IP地址为192.168.1.141(能够最终靠/etc/network/interfaces文件来设置静态的然后重启;

  (1)刚才开发板运行linux时和主机windows、虚拟机ubuntu都ping通了,说明硬件和连接和主机设置没错。

  (2)此时开发板重启进入uboot,设置好ipaddr、gatewayip,然后去ping windows发现还是不通。

  (3)然后同样情况下尝试去ping通虚拟机ubuntu,理论分析应该也不通,但是实际发现是通的。

  uboot和虚拟机ubuntu互相ping通(前提是虚拟机ubuntu设置为桥接,且桥接到有线网卡,且ip地址设置正确的情况下)

  开发板中运行的uboot有点小bug,ping windows就不通,ping虚拟机ubuntu就通。

  (1)uboot本身主要目标是启动内核,为完成启动内核必须要能够部署内核,uboot为了部署内核就需要将内核镜像从主机中下载过来然后烧录到本地flash中。

  uboot如何从主机(windows或者虚拟机ubuntu)下载镜像到开发板上?

  (2)tftp方式下载时实际上uboot扮演的是tftp客户端程序角色,主机windows或虚拟机ubuntu中必须有一个tftp服务器,然后将要下载的镜像文件放在服务器的下载目录中,然后开发板中使用uboot的tftp命令去下载即可。

  (3)有些人习惯在windows中搭建tftp服务器,一般是用一些软件来搭建(譬如tftpd32,用起来最简单);

  有些人习惯在linux下搭建tftp服务器,可以借鉴网盘中的虚拟机下载目录下的一个教程《嵌入式开发环境搭建-基于14.04.pdf》,这里面有ubuntu中搭建tftp服务器的教程,也能自己上网搜索教程尝试。

  (4)我的虚拟机搭建的时候设置的tftp下载目录是/tftpboot,将要被下载的镜像复制到这个目录下。

  (5)检查开发板uboot的环境变量,注意serverip必须设置为虚拟机ubuntu的ip地址。

  (serverip这个环境变量的意义就是主机tftp服务器的ip地址)

  (6)然后在开发板的uboot下先ping通虚拟机ubuntu,然后再尝试下载:

  tftp 0x30000000 zImage-qt(意思是将服务器上名为zImage-qt的文件下载到开发板内存的0x30000000地址处。

  (7)镜像下载到开发板的DDR中后,uboot就可以用movi指令进行镜像的烧写了。

  如果你是用的windows下的tftp服务器,那uboot的serverip就要设置为和windwos下tftp服务器的ip地址一样(windows下的tftp服务器软件设置的时候就有个步骤是让你设置服务器的ip地址,这个ip地址和主机windows必须在一个网段)。

  (1)开发板如果用SD卡/EMMC/iNand等作为Flash,则在uboot中操作flash的指令为movi(或mmc)

  (2)movi指令是一个命令集,有很多子命令,具体用法可以help movi查看。

  (3)movi的指令都是movi read和movi write一组的,movi read用来读取iNand到DDR上,movi write用来将DDR中的内容写入iNand中。

  (4)movi read  {u-boot kernel} {addr}   这个命令使用了一种通用型的描述方法来描述:

  movi 和read外面没有一点标记说明每一次使用这个指令都是必选的;

  (5)指令有多种用法,譬如 movi read u-boot 0x30000000,意思是把iNand中的u-boot分区读出到DDR的0x30000000起始的位置处。

  (uboot代码中将iNand分成了很多个分区,每个分区有地址范围和分区名,uboot程序操作中能够正常的使用直接地址来操作iNand分区,也能够正常的使用分区名来操作分区。

  注意这里的0x30000000也可以直接写作30000000,意思是一样的(uboot的命令行中所有数字都被默认当作十六进制处理,不管你加不加0x都一样)。

  (1)DDR中是没有分区的(只听说过对硬盘、Flash进行分区,没听说过对内存进行分区····),但是内存使用时要注意,绝对不可以越界踩到别人了。

  因为uboot是一个裸机程序,不像操作系统会由系统整体管理所有内存,系统负责分配和管理,系统会保证内存不会随便越界。

  然后裸机程序中uboot并不管理所有内存,内存是散的随便用的,所以如果程序员(使用uboot的人)自己不注意就也许会出现自己把自己的数据给覆盖了。

  (4)mm就是memory modify,修改内存中的某一块,说白了还是写内存(若需要批量的逐个单元的修改内存,用mm最合适)

  (1)uboot的终极目标就是启动内核,启动内核在uboot中表现为一个指令,uboot命令行中调用这个指令就会启动内核(不管成功与否,所以这个指令是一条死路)。

  go命令本来不是专为启动内核设计的,go命令内部其实就是一个函数指针指向一个内存地址然后直接调用那个函数,go命令的实质就是PC直接跳转到一个内存地址去运行而已。

  go命令可拿来在uboot中执行任何的裸机程序(有一种调试裸机程序的方法就是事先启动uboot,然后在uboot中去下载裸机程序,用go命令去执行裸机程序)

  uboot开机时一次性从Flash中读取全部环境变量到DDR中作为环境变量的初始化值,然后使用的过程中都是用DDR中这一份,用户都能够用saveenv指令将DDR中的环境变量重新写入Flash中去更新Flash中环境变量。

  (2)环境变量在uboot中是用字符串表示的,也就是说uboot是按照字符匹配的方式来区分各个环境变量的。

  (2)serverip是开发板通过tftp指令去tftp服务器下载东西时,tftp服务器的IP地址。

  (1)uboot启动后会开机自动倒数bootdelay秒,假如没有人按下回车打断启动,则uboot会自动执行启动命令来启动内核。

  (2)uboot开机自动启动时实际就是在内部执行了bootcmd这个环境变量的值所对应的命令集:

  bootcmd=movi read kernel 30008000; bootm 30008000

  将iNand的kernel分区读取到DDR内存的0x30008000地址处,然后使用bootm启动命令从内存0x30008000处去启动内核。

  然后重启则会看到启动倒数后自动执行printenv命令打印出环境变量。

  set bootcmd movi read kernel 30008000; bootm 30008000

  (1)linux内核启动时可以接收uboot给他传递的启动参数,这些启动参数是uboot和内核约定好的形式、内容,linux内核在这些启动参数的指导下完成启动过程。

  这样的设计是为了灵活,为了内核在不重新编译的情况下可以用不同的方式启动。

  在uboot的环境变量中设置bootargs,然后bootm命令启动内核时会自动将bootargs传给内核。

  (3)环境变量bootargs=console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3

  console=ttySAC2,115200 控制台使用串口2,波特率115200.

  root=/dev/mmcblk0p2 rw 根文件系统在SD卡端口0设备(iNand)第2分区,根文件系统是可读可写的

  在内核移植的时候,新手经常因为忘记给内核传参,或者给内核传递的参数不对,造成内核启动不起来。

  (2)PC机等产品中,因为大家都是在操作系统下使用硬盘的,整个硬盘由操作系统统一管理,操作系统会使用文件系统帮我们管理硬盘空间。

  (管理保证了文件之间不会互相堆叠),于是乎使用者不用自己太过在意分区问题。

  (3)在uboot中是没有操作系统的,因此我们对Flash(相当于硬盘)的管理必须事先使用分区界定(实际上在uboot中和kernel中都有个分区表,分区表就是我们在做系统移植时对Flash的整体管理分配方法)。

  有了这个界定后,我们在部署系统时按照分区界定方法来部署,uboot和kernel的软件中也是按照这一个分区界定来工作,就不会错。

  但是在一个移植中必须事先设计好定死,一般在设计系统移植时就会定好,定的标准是:

  uboot:uboot必须从Flash起始地址开始存放(也许是扇区0,也许是扇区1,也许是其他,取决于SoC的启动设计),uboot分区的大小一定要保证uboot肯定能放下,一般设计为512KB或者1MB(因为一般uboot肯定不足512KB,给再大其实也可以工作,但是浪费);

  剩下的就是自由分区,一般kernel启动后将自由分区挂载到rootfs下使用

  (4)各分区的大小由系统移植工程师自己来定,一般定为合适大小(不能太小,太小了容易溢出;

  (5)分区在系统移植前确定好,在uboot中和kernel中使用同一个分区表。

  (1)DDR的分区和Flash的分区不同,还在于Flash是掉电存在的,而DDR是掉电消失,因此能说DDR是每次系统运行时才开始部署使用的。

  (2)内存的分区主要是在linux内核启动起来之前,linux内核启动后内核的内存管理模块会接管整个内存空间,那时候就不用我们来管了。

  (3)注意内存分区关键就在于内存中哪一块用来干什么必须分配好,以避免各个不同功能使用了同一块内存造成的互相踩踏。

  譬如说我们tftp 0x23E00000 zImage去下载zImage到内存的0x23E00000处就会出错,因为这个内存处实际是uboot的镜像所在。