先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Golang全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024b (备注go)
正文
下面是描述startup.A51的流程图,作为这次学习的总结。
二、STM32的完整启动流程分析
已剪辑自: https://blog.csdn.net/Setul/article/details/121685929
关于STM32的启动流程,网上有的资料在讨论几种boot模式,有的在回答启动文件的内容,在查阅了很多资料后,本文给出一个比较全面的总结和回答。
1. 根据boot引脚决定三种启动模式
复位后,在 SYSCLK 的第四个上升沿锁存 BOOT 引脚的值。BOOT0 为专用引脚,而 BOOT1 则与 GPIO 引脚共用。一旦完成对 BOOT1 的采样,相应 GPIO 引脚即进入空闲状态,可用于其它用途。BOOT0与BOOT1引脚的不同值指向了三种启动方式:
- 从主Flash启动。主Flash指的是STM32的内置Flash。选择该启动模式后,内置Flash的起始地址将被重映射到0x00000000地址,代码将在该处开始执行。一般我们使用JTAG或者SWD模式下载调试程序时,就是下载到这里面,重启后也直接从这启动。
- 从系统存储器启动。系统储存器指的是STM32的内置ROM,选择该启动模式后,内置ROM的起始地址将被重映射到0x00000000地址,代码在此处开始运行。ROM中有一段出厂预置的代码,这段代码起到一个桥的作用,允许外部通过UART/CAN或USB等将代码写入STM32的内置Flash中。这段代码也被称为ISP(In System Programing)代码,这种烧录代码的方式也被称为ISP烧录。关于ISP、ICP和IAP之间的区别将在后续章节中介绍。
- 从嵌入式SRAM中启动。显然,该方法是在STM32的内置SRAM中启动,选择该启动模式后,内置SRAM的起始地址将被重映射到0x00000000地址,代码在此处开始运行。这种模式由于烧录程序过程中不需要擦写Flash,因此速度较快,适合调试,但是掉电丢失。
总结:上面的每一种启动方式我都描述了“xxx的起始地址被重映射到了0x00000000地址,从而代码从xxx开始启动”,如下图是STM32F4xx中文参考手册中的图,可以看到类似的表述。同时,在下图中也展示了STM32F4xx中统一编址下,各内存的地址分配,注意一点,即使相应的内存被映射到了0x00000000起始的地址,通过其原来地址依然是可以访问的。
2. 启动后bootloader做了什么?
根据BOOT引脚确定了启动方式后,处理器进行的第二大步就是开始从0x00000000地址处开始执行代码,而该处存放的代码正是bootloader。
bootloader,也可以叫启动文件,无论性能高下,结构简繁,价格贵贱,每一种微控制器(处理器)都必须有启动文件,启动文件的作用便是负责执行微控制器从“复位”到“开始执行main函数”中间这段时间(称为启动过程)所必须进行的工作。最为常见的51,AVR或MSP430等微控制器当然也有对应启动文件,但开发环境往往自动完整地提供了这个启动文件,不需要开发人员再行干预启动过程,只需要从main函数开始进行应用程序的设计即可。同样,STM32微控制器,无论是keiluvision4还是IAR EWARM开发环境,ST公司都提供了现成的直接可用的启动文件。
网上有很多资料分析了STM32的启动文件的内容,在此我只进行简单的表述。启动文件中首先会定义堆栈,定义中断/异常向量表,而其中只实现了复位的异常处理函数Reset_Handler,该函数内容如下(STM32F4XX,IAR编译器),可以看到其主要执行了SystemInit和__iar_program_start两个函数,其主要功能除了初始化时钟,FPU等,还会执行一个重要功能,那就是内存的搬移、初始化操作。 这是我想重点介绍的内容,同时也会回答一个疑问,就是如果从Flash启动的话,代码究竟是运行在哪儿的?在我之前接触ARM9、CortexA系列的时候,一般都是把代码搬到内部的SRAM或者外部DDR中执行的,STM32是如何呢?答案下一小节揭晓。
3. bootloader中对内存的搬移和初始化
本节针对程序在内置Flash中启动的情况进行分析。
我们知道烧录的镜像文件中包含只读代码段.text,已初始化数据段.data和未初始化的或者初始化为0的数据段.bss。代码段由于是只读的,所以是可以一直放在Flash中,CPU通过总线去读取代码执行就OK,但是.data段和.bss段由于会涉及读写为了,为了更高的读写效率是要一定搬到RAM中执行的,因此bootloader会执行很重要的一步,就是会在RAM中初始化.data和.bss段,搬移或清空相应内存区域。
因此我们知道,当启动方式选择的是从内置Flash启动的时候,代码依旧是在Flash中执行,而数据则会被拷贝到内部SRAM中,该过程是由bootloader完成的。bootloader在完成这些流程之后,就会将代码交给main函数开始执行用户代码。
- 现在让我们思考一个问题,PC机在运行程序的时候将程序从外存(硬盘)中,调入到RAM中运行,CPU从RAM中读取程序和数据;而单片机的程序则是固化在Flash中,CPU运行时直接从Flash中读取程序,从RAM中读取数据,那么PC机能从Flash之类的存储介质中直接读代码执行吗?
- 答案是不行。因为x86构架的CPU是基于冯.诺依曼体系的,即数据和程序存储在一起,而且PC机的RAM资源相当丰富,从几十M到几百M甚至是几个G,客观上能够承受大量的程序数据。但是单片机的构架大多是哈弗体系的,即程序和数据分开存储,而且单片的片内RAM资源是相当有限的,内部的RAM过大会带来成本的大幅度提高。
4. ISP、IAP、ICP三种烧录方式
虽然这个小节稍稍偏题,但是由于上面在3中启动方式中介绍过了ISP烧录,因此一并在此介绍剩下的两种烧录方式。
本小节摘自 https://blog.csdn.net/zhuimeng_ruili/article/details/119709888。
- ICP(In Circuit Programing)。在电路编程,可通过CPU的Debug Access Port 烧录代码,比如ARM Cortex的Debug Interface主要是SWD(Serial Wire Debug)或JTAG(Joint Test Action Group);
- ISP(In System Programing)。在系统编程,可借助MCU厂商预置的Bootloader 实现通过板载UART或USB接口烧录代码。
- IAP(In Applicating Programing)。在应用编程,由开发者实现Bootloader功能,比如STM32存储映射Code分区中的Flash本是存储用户应用程序的区间(上电从此处执行用户代码),开发者可以将自己实现的Bootloader存放到Flash区间,MCU上电启动先执行用户的Bootloader代码,该代码可为用户应用程序的下载、校验、增量/补丁更新、升级、恢复等提供支持,如果用户代码提供了网络访问功能,IAP 还能通过无线网络下载更新代码,实现OTA空中升级功能。
- IAP和ISP 的区别。
a、ISP程序一般是芯片厂家提供的。IAP一般是用户自己编写的
b、ISP一般支持的烧录方式有限,只有串口等。IAP就比较灵活,可以灵活的使用各种通信协议烧录
c、isp一般需要芯片进行一些硬件上的操作才行,IAP全部工作由程序完成,不需要去现场
d、isp一般只需要按格式将升级文件通过串口发送就可以。IAP的话控制相对麻烦,如果是OTA的话还需要编写后台的。
e、注意,这里介绍的bootloader功能显然跟之前介绍的启动文件bootloader有所区别,其目的是为了能接受外部镜像进行烧录,而不是为了运行普通用户程序。
5. 参考资料
- STM32 BOOT模式配置以及作用: https://www.cnblogs.com/huanzxj/p/6273014.html
- 高手带你分析STM32 的启动过程: https://blog.csdn.net/zhuimeng_ruili/article/details/108789033
- [原创]在main()之前,IAR都做了啥?https://www.cnblogs.com/mssql/archive/2011/01/29/tt146.html
- STM32启动过程启动文件分析:https://www.sohu.com/a/77623300_119709
- STM32中的程序在RAM还是FLASH里运行?https://blog.csdn.net/u012252959/article/details/80800559
从上电到启动,一文读懂STM32启动全流程
- Boot1 Boot0
- bootloader
- 中断向量表
- 启动流程
很多同学会使用STM32进行应用/驱动开发,一般开发就是从main()函数开始,绝大多数人没有关注或并不关注类似STM32的MCU是怎么从上电运行到mian()函数的,今天我就针对这部分的内容进行了一个大整合,帮助大家理解这个过程。
1、直接上电?不,上电前还暗藏玄机
也许你会说可不就是直接给电就行吗?我啥也没配置,就轻松进入main()函数了。
其实,在上电前有两个引脚对MCU的启动有些关键的影响:BOOT0和BOOT1,这两个引脚的电平状态直接影响了芯片在复位上电后从芯片的哪个位置开始执行代码。
Boot1=x Boot0=0 从内置闪存flash开始启动,这是正常的启动模式。
通过boot引脚设置可以将中断向量表定位于SRAM区,即起始地址为0x2000000,同时复位后PC指针位于0x2000000处;
STM32的内存flsh,一般我们使用JTAG或者SWD模式下载程序时,就是下载到这个里面,重启后也直接从这启动程序。
Boot1=0 Boot0=1 从系统存储器启动
通过boot引脚设置可以将中断向量表定位于内置Bootloader区
这是芯片一块特殊的区域,由ST出厂烧录预置的一段BootLoader,也就是我们常说的 ISP(In System Programing,在系统中编程 )程序;这是一块ROM,一旦出厂烧录即无法修改,只读,使用较少。
一般来说,我们选用这种启动模式时,是为了从串口下载程序,因为在厂家提供的BootLoader中,提供了串口下载程序的固件,可以通过这个BootLoader将程序下载到系统的Flash中。
从System Memory启动就可以使用串口来下载我们的程序,因为在芯片出厂前ST官方已经把一段自举程序(BootLoader程序)固化到这一块存储区。其通过串口来接收数据并烧写到用户闪存存储器的起始地址(0x08000000)。只能烧写到这个地址,若keil里设置的地址不是这个地址,则编译出来的文件将烧录不成功。
用户闪存,即User Flash,同时也称为Main Flash。
注意:不同系列不同型号的STM32固化的BootLoader是不同的,即使用的通讯接口是不同的。
IAP程序与ISP程序有什么不同?
https://www.cnblogs.com/zhengnian/p/11538465.html
ISP程序、IAP程序、应用程序的关系示意图如下:
1234
如果我们的产品中的程序只有应用程序,则此时0x08000000地址存放的程序就是应用程序。ISP程序、应用程序的关系示意图如下:
Boot1=1 Boot0=1 从SRAM启动
通过boot引脚设置可以将中断向量表定位于FLASH区,即起始地址为0x8000000,同时复位后PC指针位于0x8000000处;
因为是使用SRAM启动,自然没有程序存储的能力,一般用于代码调试的时候,减少了调试阶段还要反复全擦除flsh的操作。
2、Bootloader
启动文件的作用便是负责执行微控制器从“复位”到“开始执行main函数”中间这段时间(称为启动过程)所必须进行的工作。最为常见的51,AVR或MSP430等微控制器当然也有对应启动文件,但开发环境往往自动完整地提供了这个启动文件,不需要开发人员再行干预启动过程,只需要从main函数开始进行应用程序的设计即可。
Cortex-M3内核规定,起始地址必须存放堆顶指针,而第二个地址则必须存放复位中断入口向量地址,这样在Cortex-M3内核复位后,会自动从起始地址的下一个32位空间取出复位中断入口向量,跳转执行复位中断服务程序。
无论是哪种启动方式,上电之后,都会有一段BootLoader引导程序,如果是从内置flash启动,则直接从0x08000000地址开始读取;如果是从系统存储器启动,则从ISP引导程序开始,该地址位置根据不同芯片地址也不同,可以查看芯片手册找到起始地址。然后引导跳转到0x08000000,IAP程序+应用程序,若无IAP程序,则0x08000000存放的就是应用程序。
IAP程序即是我们常常关注的用户BootLoader程序,可以根据实际需求修改。
- 上电启动后,首先找到
- 单片机首先进入复位中断 Reset_Handler,即汇编文件的复位中断处理函数。
Bootloader中的关键部分为:
1、划分bootloader和app的空间
2、接收编译好的app的bin文件,写入flash
3、实现跳转
App里面主要修改的地方是
1、ROM起始地址和分配的空间大小
2、中断向量表重定向
3、生成bin文件
123456789
三、VxWorks的启动过程
已剪辑自: https://www.vxworks.net/bsp/88-vxworks-boot-process
摘要: 镜像种类不同,VxWorks的启动过程会有所不同。 我们项目中使用的是加载型VxWorks镜像
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8i9N1EVn-1664767544921)(C:\Users\10521\AppData\Roaming\Typora\typora-user-images\image-20221003090219500.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5R3iFU8Z-1664767544922)(C:\Users\10521\AppData\Roaming\Typora\typora-user-images\image-20221003090239491.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-je5zbWWJ-1664767544922)(C:\Users\10521\AppData\Roaming\Typora\typora-user-images\image-20221003090252827.png)]
四、操作系统(RTOS)的启动流程是怎样的?
已剪辑自: https://www.toutiao.com/article/6830717264627499523/?&source=m_redirect
一般说到实时系统(RTOS)时,我们都会想到嵌入式领域的应用。由于嵌入式板卡硬件和资源的限制,往往我们操作系统的映像文件都会存储在Flash之中,上电启动时,一般会依靠一个启动软件,比如常见的Linux下的Uboot或者VxWorks下的Bootrom软件。这些Boot软件会初始化硬件资源如:CPU的时钟、DDR和Cache等关键参数,然后将Flash中的系统内核文件引导至DDR之中,进而执行系统的核心功能。
Boot软件的一种
本文以VxWorks为例说明一下RTOS是如何启动的,启动过程中都做了哪些工作?在进入系统启动工作前,先来了解一下VxWorks常用的Bootrom类型:
Boot软件
1、用于可加载VxWorks映象的BOOTROM
用于可加载VxWorks映象的BOOTROM包含两部分:起始引导程序(BootStrap Programs)和ROM引导程序(ROM Boot Programs)
起始引导程序驻留在ROM中,主要包含:
汇编级的硬件初始化程序romInit.s,用于系统的基本初始化,设置一些重要寄存器的初始值,进行存储器的映射搬移程序bootInit.c,将ROM引导程序拷贝至RAM的高端地址RAM_HIGH_ADRS,然后跳转到此处执行ROM引导程序。
ROM引导程序起初存放在ROM中,初始化时被拷贝到RAM中,主要用于系统的进一步初始化,并配置加载方式,将VxWorks映象加载至RAM。可分为三种不同的类型:
1)压缩的ROM引导程序,在拷贝的过程中需要解压缩,在RAM中执行;
2)未压缩的ROM引导程序,可直接拷贝,在RAM中执行;
3)驻留ROM的ROM引导程序,仅拷贝ROM引导程序的数据段,代码段仍旧在ROM中执行。
2、用于基于ROM的VxWorks映象的BOOTROM
用于该映象的BOOTROM包含两部分:
起始引导程序(BootStrap Programs)和基于ROM的VxWorks映象。搬移程序bootInit.c负责将VxWorks映象的文本段和数据段搬移到用户定义的低端内存地址RAM_LOW_ADRS,如果需要进行必要的解压缩,然后直接启动VxWorks映像。
因此BOOTROM的容量相对于可加载VxWorks映象的BOOTROM要大一些,但无需在主机目录下维护一个可用的VxWorks映象。
基于ROM的VxWorks BOOTROM有压缩和未压缩之分。
3、用于驻留ROM的VxWorks映象的BOOTROM
用该映象的BOOTROM包含两部分:
起始引导程序(BootStrap Programs)和驻留ROM的VxWorks映象,VxWorks系统文本段驻留在ROM,搬移程序bootInit.c负责将数据段和bss段搬移到用户定义的低端内存地址RAM_LOW_ADRS,直接启动VxWorks映像(含符号表)。此时,RAM_LOW_ADRS是VxWorks映象的加载点,它也是VxWorks数据段的起始点。
VxWorks的启动过程
根据上述所采用的BOOTROM的不同,VxWorks的启动过程会有所不同,下面主要介绍一下使用可加载VxWorks映像的启动过程。
操作系统
1、目标板加电运行(汇编语言)
目标板加电之后,程序指针指向RESET中断程序入口处,开始执行初始化程序romInit.s,设置机器状态字及其它硬件相关寄存器,关闭中断,禁止程序和数据CACHE,初始化内存,并设置堆栈指针,保存启动类型,调用romStart.c中的romStart( )。
2、开始运行第一个C程序
第一个C程序romStart.c的函数romStart( )入口地址,根据堆栈中的参数决定是否清零内存RAM(如是冷启动cold start,则清零),根据不同的bootRom文件,把ROM中数据段和文本段拷贝到RAM(如果ROM代码是压缩的,还要解压)。
C语言程序
3、运行RAM中的程序
程序跳到RAM入口地址(usrConfig.c中的函数usrInit( ) ),usrInit( )中清零bss段(这也是未赋初始值的全局变量在编译后初始值为0的原因),调用excVecInit() 安装异常向量(excVecInit会将excIntHandle注册到相应的异常上),初始化异常处理程序,调用cacheLibInit(),设置cache的指令与数据工作模式,调用sysHwInit( )对板级硬件初始化,调用usrKernelInit( )配置wind Kernel, 调用KernelInit( ) 进行内核初始化。
4、创建第一个根任务
初始化内核及内存池, 主要是中断堆栈及根任务堆栈初始化,初始化任务Tcb 并生成根任务usrRoot( )。kernelInit( ) 调用intLockLevelSet( ),关闭循环模式,创建一个中断堆栈(如果结构支持的话)。然后从内存池的顶部创建一个根堆栈和TCB,创建一个根任务usrRoot,并终止usrInit( )线程的执行。此时使能中断,所有的中断源已被关闭,未决中断已被清除。
本系统的根任务函数usrRoot( )在prjConfig.c中。在该任务中初始化内存,系统时钟,I/O系统,标准输入输出错,异常处理,外围设备等。BPC初始任务usrRoot具体所处理的内容如下:
void usrRoot (char *pMemPoolStart, unsigned memPoolSize)
{
excIntNestLogInit();
vxMsrSet(vxMsrGet() | taskMsrDefault);
usrKernelCoreInit ();
memInit (pMemPoolStart, memPoolSize);
memPartLibInit (pMemPoolStart, memPoolSize);
usrMmuInit ();
sysClkInit ();
selectInit (NUM_FILES);
usrIosCoreInit ();
usrKernelExtraInit ();
usrIosExtraInit ();
usrNetworkInit ();
selTaskDeleteHookAdd ();
usrToolsInit ();
cplusCtorsLink ();
usrAppInit ();
}
最后会调用usrAppInit.c中的usrAppInit ( )进行用户级应用模块的初始化。
usrAppInit( )最后会调用BootApp.c中的**BootAppStart( )**进入autoboot或bootLoad, 根据单板设计选择不同方式加载VxWorks映像文件,如通过串口、网口、硬盘等方式加载。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M03UPZPo-1664767544922)(https://p3-sign.toutiaoimg.com/pgc-image/fa609643cc61482a99d265dc68e5b9b9~noop.image?_iz=58558&from=article.pc_detail&x-expires=1665363786&x-signature=z4GjAY9zxxqBgx9d%2BREfU0RjodE%3D)]
VxWorks系统启动流程
5、系统开始启动运行
启动VxWorks系统的第一步就是将系统映象加载到主内存, 在开发初期, 这通常是在VxWorks boot Rom 的控制下,从开发主机上下载, 我们正式运行的系统中。VxWorks映象被加载到ram后, boot Rom会复位系统并将控制权交给VxWorks的起始进入点sysInit()。 在makefile和 config.h文件里,已将这个进入点设置成位于地址RAM_LOW_ADRS。
函数sysInit()位于系统特定的汇编语言模块sysALib.s中。它可以锁住中断,关闭cache(如果使用了话),初始化处理器的寄存器(包括C堆栈指针)至缺省值。它还会关闭跟踪,清除所有未决的中断,并调用一个位于usrConfig.c 模块的C语言子程序:usrInit()。对于某些目标板,sysInit()还执行一些必要的与系统有关的硬件初始化,以便在usrInit()中执行完剩余的初始化内容。仅供usrInit()使用的初始堆栈指针,被设置成位于系统映象(RAM_LOW_ADRS)以下, 向量表以上的位置。
函数usrInit()(位于usrConfig.c中),储存有关引导类型的信息,处理在内核启动之前必须执行的初始化,而后启动内核执行。它是运行于VxWorks内的第一个C函数。此时,所有的中断都已被锁住。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GsHNBOHr-1664767544922)(https://p3-sign.toutiaoimg.com/dfic-imagehandler/cfd7ee2e-893e-4aaa-a03d-de9b771f3a0e~noop.image?_iz=58558&from=article.pc_detail&x-expires=1665363786&x-signature=%2FitpwbOmwsKCT7JrC%2B1bwQOEewo%3D)]
多任务
6、启动第一个任务
当多任务内核启动执行以后,所有的VxWorks多任务工具就可以用了。控制权被传送至**usrRoot( )**任务,并完成初始化系统。usrRoot( ) 执行以下操作:
- 初始化系统时钟
- 初始化I/O系统和驱动
- 创建控制台设备
- 设置标准输入和标准输出
- 安装异常处理和登陆
- 初始化管道驱动器
- 初始化标准I/O
- 创建文件系统设备并安装磁盘驱动器
- 初始化浮点支持
- 初始化性能监视工具
- 初始化网络
- 初始化可选的工具
- 初始化WindView
- 初始化目标代理
- 执行一个用户提供的启动脚本
- 初始化VxWorks Shell
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DcykuiaI-1664767544923)(https://p3-sign.toutiaoimg.com/dfic-imagehandler/7507a567-3156-4733-8174-4c684d43d7a9~noop.image?_iz=58558&from=article.pc_detail&x-expires=1665363786&x-signature=IDyUE2gyGGMaqKsFY35WBfvK5qE%3D)]
第一个任务
总结
本文以VxWorks为例说明了实时系统(RTOS)从上电启动到内核开始执行shell的整个过程,对于大多数的RTOS来说,启动过程都大同小异,同样的方法可以在别的RTOS中见到,整个过程基本分为汇编语言Flash运行阶段、C语言运行阶段、单任务阶段和多任务阶段。
五、Linux 系统启动流程
已剪辑自: https://jaminzhang.github.io/linux/Linux-boot-process/
引言
最近在看 Cobbler 相关资料,提到了要熟悉 Linux 系统的启动流程,很久没有接触这方面了,有些生疏遗忘了,
于是重新复习了下,真要深究,可以分为好多步。
首先贴一张总结得比较好图:
下面进行文字版本再说明:
第一阶段:硬件引导启动阶段
1.1 POST(Power On Self Test) 加电自检
1.2 BIOS
1.2.1 初始化硬件
1.2.2 查找启动介质
HDD: 查找启动硬盘的第一个扇区(MBR/BootSector)
1.3 MBR
1.3.1 Bootloader(启动装载程序)
GRUB
分区表
第二阶段:BootLoader 启动引导阶段
2.1 Stage1
执行 BootLoader 主程序(位于 MBR 前 446个字节),它的作用是启动 Stage1.5 或 Stage2
2.2 Stage1.5
Stage1.5 是桥梁,由于 Stage2 较大,存放在文件系统中,需要 Stage1.5 引导位于文件系统中的 Stage2
2.3 Stage2
Stage2 是 GRUB 的核心映像
2.4 grub.conf
Stage2 解析 grub.conf 配置文件,加载内核到内存中
第三阶段:内核引导阶段
3.1 /boot/kernel and Kernel parameter
内核初始化,加载基本的硬件驱动
3.2 /boot/initrd
引导 initrd 解压载入
3.2.1 阶段一:在内存中释放供 kernel 使用的 root filesystem
执行 initrd 文件系统中的 init,完成加载其他驱动模块
3.2.2 阶段二:执行真正的根文件系统中的 /sbin/init 进程
第四阶段:Sys V init 初始化阶段
4.1 /sbin/init
4.1.1 /etc/inittab
init 进程读取 /etc/inittab 文件,确定系统启动的运行级别
4.1.2 /etc/rc.d/rc.sysinit
执行系统初始化脚本,对系统进行基本的配置
4.1.3 /etc/rc.d/rcN.d
根据先前确定的运行级别启动对应运行级别中的服务
4.1.4 /etc/rc.d/rc.local
执行用户自定义的开机启动程序
4.2 登录
4.2.1 /sbin/mingetty (命令行登录)
验证通过 执行 /etc/login
加载 /etc/profile ~/.bash_profile ~/.bash_login ~/.profile
取得 non-login Shell
4.2.2 /etc/X11/prefdm (图形界面登录)
gdm kdm xdm
Xinit
加载 ~/.xinitrc ~/.xserverrc
第五阶段:启动完成
Linux 的启动流程分析
Linux 的启动流程
Linux 操作系统启动过程详解
详解 Linux 系统的启动过程及系统初始化
Linux 系统启动过程
六、STM32单片机是如何软硬件结合的
转载于:https://mp.weixin.qq.com/s/ADtZRR7Fewb2jxOOdbKlpg
七、单片机驱动开发
已剪辑自: https://zhuanlan.zhihu.com/p/34283108
首发平台:微信公众号baiwenkeji
这是arm裸机1期加强版第1课第2节课程的wiki文字版。
为什么没前途也要学习单片机?
因为它是个很好的入口。
学习单片机可以让我们抛开复杂的软件结构,先掌握硬件操作,如:看原理图、芯片手册、写程序操作寄存器等。 在上一节视频里,我刚把单片机贬得一无是处,说单片机没前途了,这节视频,我又要告诉你们,没有前途,也要学习单片机。为什么?
首先,我说不用学习单片机,是指不要使用老一套得学习方法学习单片机。什么叫老一套的方法?
· 硬件上:不要使用C51、STM32这些专用的单片机开发板。如果以后,你不打算从事单片机开发,你用这些芯片干嘛,研究了两三个月,把这些寄存器都用清楚了,你又用不上,没必要啊。
· 软件上:不要使用Keil、MDK等集成度太高的软件。你用这些软件,你写个main()就可以了,然后调用各种库,进行傻瓜式操作。这些好用的工具,封装了很多技术细节,使得我们没法了解裸机、单片机的本质。
以后我们会使用新一套的方法来进行单片机的开发。新一套的方法,我们后面再介绍。
我们之所以还要学习单片机,是因为它里面的知识,对我们后续学习Linux还是有用的。我们首先来看看,一个Linux系统是怎么一回事。 一个嵌入式Linux系统的软件组成: 单片机大全Bootloader–>Linux驱动–>Linux APP–>Linux GUI(Android/QT) 。我们PC机一上电的时候,黑色屏幕上会显示BIOS,这个BIOS目的是去启动Windows内核。Windows内核再挂载C盘(系统盘)、D盘(应用盘),最后再去启动应用程序,像QQ、网游等。 同样的道理,我们的Android手机或者工控设备,也有BIOS,但嵌入式Linux系统里面不叫BIOS,叫Bootloader,他的目的是去启动Linux内核。 它首先也是识别应用程序所在的存储设备,挂载根文件系统(在Windows系统里面的C盘、D盘,在Linux里面称为根文件系统)。最后去启动应用程序。
仔细的分析下Bootloader,它去启动内核,它去哪里启动内核呢? 显然是去某个地方读出内核,就比如说BIOS是去C盘上读出Windows内核,我们的Bootloader是去Flash或者SD卡读取内核。 因此Bootloader要拥有读取Flash或者SD卡的能力。有些Bootloader还要显示logo,因此还要具有操作LCD的能力。Bootloader还要设置开发板的环境,比如,初始化时钟、初始化内存、还要设置网卡等。 这么多事情,都是在Bootloader里面实现的,太复杂了,如果你一来就分析整个Bootloader是非常困难的。
那我们怎么学习呢? 把他拆开,写出单独的程序,比如:LED点灯、时钟、网卡、Flash都单独写个程序来练习,这些不就是单片机程序吗?所以说,Bootloader是单片机程序的大全。我们为了更好的学习Bootloader,我们应该事先一个一个练习硬件,当我们熟悉每个硬件后,再组合起来,就是一个Bootloader。
我们再来看看Bootloader启动内核之后,内核再去挂载根文件系统,意味着内核也要有操作硬件的能力,这就是驱动程序。我们首先来看看一个简单的驱动程序是什么样子。 首先我们的应用程序是调用open()、read()、write()这些标准的接口去访问硬件。那么就进入驱动程序里面,驱动程序里面有对应的drive_open()、drive_read()、drive_write()。最后在驱动程序里面,去配置硬件。 比如一个LED点灯驱动,那么drive_open()要把GPIO设置为输出引脚,drive_read(),返回GPIO状态,driver_write()则写GPIO,让引脚输出高电平或者低电平。
对于我们的LED驱动程序,你需要提供drive_open()、drive_read()、drive_write()这些接口,这就是它的框架。具体的怎么操作硬件,就是硬件操作。 所以说,我们事先在单片机里面,熟悉熟练的掌握硬件操作。即驱动程序的组成:
驱动程序=软件框架+硬件操作
你需要学会看原理图、看硬件怎么连接、看芯片手册、知道怎么读写寄存器。这一切都可以先在单片机里面学习,去掌握。以后学习Linux驱动时,把重点放在软件框架就行了。
我们可以事先学习单片机,单片机的学习可以让我们先抛开复杂的软件结构,先掌握硬件的操作,如:看原理图、芯片手册、写程序操作寄存器等。 这就是为什么单片机没有前途,我们也要学习。是因为他里面涉及的硬件操作,对我们后续的学习,非常有用处。
现在我们知道了,我们学习单片机,不是为了掌握单片机的开发技能,而是为了掌握Bootloader,掌握硬件操作。
八、从单片机步入Linux驱动开发(概念和Demo)
单片机的裸跑能实现很多功能,代码量也不大,但是在嵌入式领域不学习Linux,那写再多的代码也枉然,因为有无数的大牛给Linux添砖加瓦,并且Linux有开放源码,易于移植,资源丰富,免费等优点。
首先我们看一个Linux系统的内容:可以分为应用程序、库、操作系统、驱动程序。一共五个层次,如下图:
应用程序层是直接使用open read write ioctl这些库函数。Linux驱动开始是要根据项目需求编写具体的驱动程序,也就是open read write ioctl这些函数的具体内容。不同的外设有不同的open read…内容。
Linux驱动程序的分类:
可以分为3类。1、字符设备(character device) 2、块设备(block device) 3、网络接口(network interface)。
字符设备是能够像字节流一样被访问的设备,就是说对它的读写是以字节为单位的。比如串口,和简单的点灯驱动程序。
块设备上的数据以块的形式存放,比如NAND Flash上的数据就是以页为单位存放的。
网络接口同时具有字符设备,块设备的部分特点。网络设备有小到几个字节的,也有大到几千个字节的。UNIX式的操作系统访问网络接口的方法是给他们分配一个唯一的名字(比如eht0),但这个名字在文件系统中不存在对应的节点项。应用程序,内核和网络驱动程序间的通信完全不同于字符设备,块设备,库、内核提供了一套和数据包传输相关的函数,而不是open,read等函数。
驱动程序的加载和卸载:
可以将驱动程序编译进内核中,也可以将它作为模块在使用时再加载。在配置内核时,如果某个配置项被设为m,就表示它将会被编译成一个模块。
当使用insmod加载模块时,模块的初始化函数被调用,它用来向内核注册驱动程序;当使用rmmod卸载模块时,模块的清除函数被调用。在驱动代码中,这两个函数要么取固定的名字:init_module 和 cleanup_module,要么使用以下两行来标记它们
module_init(demo_init);
module_exit(demo_cleanup);
12
记住,这是固定格式。
Linux操作系统将所有的设备都看成文件,以操作文件的方式访问设备。应用程序不能直接操作硬件,而是使用统一的接口函数调用硬件驱动程序。这组接口被称为系统调用。
对于字符设备,open read write这些函数在集合在file_operation类型的数据结构中。file_operation结构在Linux内核的include/linux/fs.h文件中定义,整个结构体如下:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*dir_notify)(struct file *filp, unsigned long arg);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
};
当应用程序使用open函数打开某个设备时,上面结构体中的open成员就会调用。所以从这个角度来说,编写字符设备的驱动程序就是为具体硬件的file_operation结构编写具体的函数内容。不用全部函数实现,需要什么函数就编写什么函数。
每个设备都有一个file_operation,都用用open函数来调用这些设备,那他们之间有什么区别呢。有两个区分要点:
1、设备文件有主/次设备号。在PC上运行命令ls /dev/ttyS0 /dev/hda1 -l可以看到以下信息:
brw-rw---- 1 root disk 3, 1, Jan 30 2003 /dev/hda1
crw-rw---- 1 root uucp 4, 64, Jan 30 2003 /dev/ttyS0
brw-rw----中的b表示/dev/had1是个块设备,它的主设备号为3,次设备号为1;
crw-rw----中的c表示/dev/ttyS0是个字符设备,它的主设备号为4,次设备号为64.
2、模块初始化时,将主设备号与file_operation结构一起向内核注册。在初始化一个设备时,就会向内核注册,用register_chrdev函数注册。
所以编写一个(简单的)字符驱动程序的过程就可以分为两步:
1、编写驱动程序初始化函数。
2、构造file_operation结构中要用到的各个成员函数。
下面开始分析一个LED驱动程序Demo 。Demo是运行在s3c24c10芯片上的。
首先需要分析原理图,我们假设:
要点亮一个LED,引脚输出0 。
要熄灭一个LED,引脚输出1 。
模块的初始化函数和卸载函数如下:
static int __init s3c24xx_leds_init(void)
{
int ret;
/* 注册字符设备驱动程序
- 参数为主设备号、设备名字、file_operations结构;
- 这样,主设备号就和具体的file_operations结构联系起来了,
- 操作主设备为LED_MAJOR的设备文件时,就会调用s3c24xx_leds_fops中的相关成员函数
- LED_MAJOR可以设为0,表示由内核自动分配主设备号
*/
ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c24xx_leds_fops);
if (ret < 0) {
printk(DEVICE_NAME " can’t register major number\n");
return ret;
}
printk(DEVICE_NAME " initialized\n");
return 0;
}
/*
- 执行”rmmod s3c24xx_leds.ko”命令时就会调用这个函数
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
,引脚输出1 。
模块的初始化函数和卸载函数如下:
static int __init s3c24xx_leds_init(void)
{
int ret;
/* 注册字符设备驱动程序
- 参数为主设备号、设备名字、file_operations结构;
- 这样,主设备号就和具体的file_operations结构联系起来了,
- 操作主设备为LED_MAJOR的设备文件时,就会调用s3c24xx_leds_fops中的相关成员函数
- LED_MAJOR可以设为0,表示由内核自动分配主设备号
*/
ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c24xx_leds_fops);
if (ret < 0) {
printk(DEVICE_NAME " can’t register major number\n");
return ret;
}
printk(DEVICE_NAME " initialized\n");
return 0;
}
/*
- 执行”rmmod s3c24xx_leds.ko”命令时就会调用这个函数
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-YbJpeU0x-1713216412329)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!