精华内容
下载资源
问答
  • 基于路由器的开源嵌入式操作系统学习平台.pdf
  • 嵌入式操作系统的主要好处就是屏蔽了底层硬件的差别,给上层应用提供统一的接口,下面一起来看看
  • 这不是rtos源码分析的课程,而是为初级的同学设计,从基础原理讲师,一步步不断迭代设计rtos的课程!用不到【2000行代码,汇编代码仅18行】(不含注释)实现一个...学习之后,再去学习ucos之类的系统将没有什么问题。
  • 从硬件底层原理到软件构架到后期的代码讲解,可以说是非常全面的一本书了。让人可以非常全面的学习嵌入式相关知识的一个作品集。
  • 河北工业大学《嵌入式操作系统》实验报告 实验一 Linux下C编程 实验二 搭建NFS服务器 实验三 守护进程 实验四 进程间通信——有名管道 实验五 进程间通信——信号机制 实验六 进程间通信——共享内存 综合实验——...
  • 嵌入式学习二:怎么学习Linux操作系统

    千次阅读 多人点赞 2019-02-23 19:21:23
    2. 怎么学习嵌入式Linux操作系统 本文假设您是零基础,以实用为主,用最快的时间让你入门;后面也会附上想深入学习时可以参考的资料。 在实际工作中,我们从事的是“操作系统”周边的开发,并不会太深入学习、修改...

    2.  怎么学习嵌入式Linux操作系统

    本文假设您是零基础,以实用为主,用最快的时间让你入门;后面也会附上想深入学习时可以参考的资料。

    在实际工作中,我们从事的是“操作系统”周边的开发,并不会太深入学习、修改操作系统本身。

     

    ①操作系统具有进程管理、存储管理、文件管理和设备管理等功能,这些核心功能非常稳定可靠,基本上不需要我们修改代码。我们只需要针对自己的硬件完善驱动程序

     

    ②学习驱动时必定会涉及其他知识,比如存储管理、进程调度。当你深入理解了驱动程序后,也会加深对操作系统其他部分的理解

     

    ③Linux内核中大部分代码都是设备驱动程序,可以认为Linux内核由各类驱动构成

     

    但是,要成为该领域的高手,一定要深入理解Linux操作系统本身,要去研读它的源代码。

    在忙完工作,闲暇之余,可以看看这些书:

     

    ①赵炯的《linux内核完全注释》,这本比较薄,推荐这本。他后来又出了《Linux 内核完全剖析》,太厚了,搞不好看了后面就忘记前面了。

    ②毛德操、胡希明的《LINUX核心源代码情景分析》,此书分上下册,巨厚无比。当作字典看即可:想深入理解某方面的知识,就去看某章节。

    ③其他好书还有很多,我没怎么看,没有更多建议

     

    基于快速入门,上手工作的目的,您先不用看上面的书,先按本文学习。

     

    2.2 学习驱动程序之前的基础知识

    2.2.1 C语言

    只要是理工科专业的,似乎都会教C语言。我见过很多C语言考试90、100分的,一上机就傻了,我怀疑他们都没在电脑上写过程序。

    理论再好,没有实践不能干活的话,公司招你去干嘛?

     

    反过来,实践出真知,学习C语言,必须练练练、写写写

    当你掌握基本语法后,就可以在电脑上练习一些C语言习题了;

    当你写过几个C程序后,就可以进入下一阶段的裸机开发了。

     

    ①不需要太深入

    作为快速入门,只要你会编写“Hello, world!”,会写冒泡排序,会一些基础的语法操作,暂时就够了。

     

    指针操作是重点,多练习;

    不需要去学习过多的数据结构知识,只需要掌握链表操作,其他不用学习,比如:队列、二叉树等等都不用学;

    不需要去学习任何的函数使用,比如文件操作、多线程编程、网络编程等等;

    这些知识,在编写Linux应用程序时会用,但是在操作系统特别是驱动学习时,用不着!

    永往直前吧,以后碰到不懂的C语言问题,我们再回过头来学习。

    在后续的“裸机开发”中,会让你继续练习C语言,那会更实战化。

    C语言是在写代码中精进的。

     

    ②可以在Visual Studio下学习,也可以在Linux下学习,后者需要掌握一些编译命令

     

    我们暂时没有提供C语言的教程,找一本C语言书,网上找找免费的C语言视频(主要看怎么搭建环境),就可以自学了。

     

     

    2.2.2 PC Linux基本操作:

    对于PC Linux,我们推荐使用Ubuntu,在它上面安装软件非常简便。

    我们的工作模式通常是这样:在Windows下阅读、编写代码,然后把代码上传到PC Linux去编译。

     

    实际上,Ubuntu的桌面系统已经很好用了,我们拿到各种智能机可以很快上手,相信Ubuntu的桌面系统也可以让你很快上手。

    为了提高工作效率,我们通常使用命令行来操作Ubuntu。

     

    不用担心,你前期只需要掌握这几条命令就可以了,它们是如此简单,我干脆列出它们:

    ①cd : Change Directory(改变目录)

    cd 目录名    // 进入某个目录

    cd ..       // cd “两个点”:返回上一级目录

    cd -        // cd “短横”:返回上一次所在目录

     

    ②pwd : Print Work Directory(打印当前目录 显示出当前工作目录的绝对路径)

    ③mkdir : Make Directory(创建目录)

    mkdir abc         // 创建文件夹abc

    mkdir -p a/b/c   // 创建文件夹a,再a下创建文件夹b,再在b下创建文件夹c

     

    ④rm : Remove(删除目录或文件)

    rm  file     // 删除名为file的文件

    rm -rf dir  // 删除名为dir的目录

     

    ⑤ls : List(列出目录内容)

    ⑥mount : 挂载

    mount -t nfs -o nolock,vers=2 192.168.1.123:/work/nfs_root /mnt

    mount -t yaffs /dev/mtdblock3 /mnt

     

    ⑦chown : Change owner(改变文件的属主,即拥有者)

    chown book:book /work -R  // 对/work目录及其下所有内容,属主改为book用户,组改为book

     

    ⑧chmod : Change mode(改变权限),下面的例子很简单粗暴

    chmod 777 /work -R  // 对/work目录及其下所有内容,权限改为可读、可写、可执行

     

    ⑨vi : Linux下最常用的编辑命令,使用稍微复杂,请自己搜索用法。

     

    要练习这些命令,你可以进入Ubuntu桌面系统后,打开终端输入那些命令;或是用SecureCRT、putty等工具远程登录Ubuntu后练习。

     

    2.2.3 硬件知识

    我们学习硬件知识的目的在于能看懂原理图,看懂通信协议,看懂芯片手册;不求能设计原理图,更不求能设计电路板。

    对于正统的方法,你应该这样学习:

    ①学习《微机原理》,理解一个计算机的组成及各个部件的交互原理。

    ②学习《数字电路》,理解各种门电路的原理及使用,还可以掌握一些逻辑运算(与、或等)。

    ③《模拟电路》?好吧,这个不用学,至少我在工作中基本用不到它,现在全忘光了。

     

    就我个人经验来说,这些课程是有用的,但是:

    ①原理有用,实战性不强。

    比如《微机原理》是基于x86系统,跟ARM板子有很大差别,当然原理相通。

    我是在接触嵌入式编程后,才理解了这些课程。

    ②每本书都那么厚,内容都很多,学习时间过长,自学有难度。

     

     

    针对这些校园教材的不足,并结合实际开发过程中要用到的知识点,我们推出了《学前班_怎么看原理图》系列视频:

    学前班第1课第1节_怎么看原理图之GPIO和门电路.wmv

    学前班第1课第2.1节_怎么看原理图之协议类接口之UART.wmv

    学前班第1课第2.2节_怎么看原理图之协议类接口之I2C.wmv

    学前班第1课第2.3节_怎么看原理图之协议类接口之SPI.wmv

    学前班第1课第2.4节_怎么看原理图之协议类接口之NAND Flash.wmv

    学前班第1课第2.5节_怎么看原理图之协议类接口之LCD.wmv

    学前班第1课第3节_怎么看原理图之内存类接口.wmv

    学前班第1课第4.1节_怎么看原理图之分析S3C2410开发板.wmv

    学前班第1课第4.2节_怎么看原理图之分析S3C2440开发板.wmv

    学前班第1课第4.3节_怎么看原理图之分析S3C6410开发板.wmv

     

    即使你只具备初中物理课的电路知识,我也希望能通过这些视频,让你可以看懂原理图,理解一些常见的通信协议;如果你想掌握更多的硬件知识,这些视频也可以起个索引作用,让你知道缺乏什么知识。

     

    这些视频所讲到的硬件知识,将在《裸板开发》系列视频中用到,到时可以相互对照着看,加深理解。

     

    2.2.4 要不要专门学习Windows下的单片机开发

    很多学校都开通了单片机的课程,很多人都是从51单片机、AVR单片机,现在比较新的STM32单片机开始接触嵌入式领域,并且使用Windows下的开发软件,比如keil、MDK等。

     

    问题来了,要不要专门学习Windows下的单片机开发?

    ①如果这是你们专业的必修课,那就学吧

    ②如果你的专业跟单片机密切相关,比如机械控制等,那就学吧

    ③如果你只是想从单片机入门,然后学习更广阔的嵌入式Linux,那么放弃在Windows下学习单片机吧!

     

    理由如下:

    ①Windows下的单片机学习,深度不够

    Windows下有很好的图形界面单片机开发软件,比如keil、MDK等。

    它们封装了很多技术细节,比如:

    你只会从main函数开始编写代码,却不知道上电后第1条代码是怎么执行的;

    你可以编写中断处理函数,但是却不知道它是怎么被调用的;

    你不知道程序怎么从Flash上被读入内存;

    也不知道内存是怎么划分使用的,不知道栈在哪、堆在哪;

    当你想裁剪程序降低对Flash、内存的使用时,你无从下手;

    当你新建一个文件时,它被自动加入到工程里,但是其中的机理你完全不懂;

    等等等。

     

    ②基于ARM+Linux裸机学习,可以学得更深,并且更贴合后续的Linux学习。

    实际上它就是Linux下的单片机学习,只是一切更加原始:所有的代码需要你自己来编写;哪些文件加入工程,需要你自己来管理。

    在工作中,我们当然倾向于使用Windows下更便利的工具,但是在学习阶段,我们更想学习到程序的本质。

     

    一切从零编写代码、管理代码,可以让我们学习到更多知识:

    你需要了解芯片的上电启动过程,知道第1条代码如何运行;

    你需要掌握怎么把程序从Flash上读入内存;

    需要理解内存怎么规划使用,比如栈在哪,堆在哪;

    需要理解代码重定位;

    需要知道中断发生后,软硬件怎么保护现场、跳到中断入口、调用中断程序、恢复现场;

    你会知道,main函数不是我们编写的第1个函数;

    你会知道,芯片从上电开始,程序是怎么被搬运执行的;

    你会知道,函数调用过程中,参数是如何传递的;

    你会知道,中断发生时,每一个寄存器的值都要小心对待;

    等等等。

     

    你掌握了ARM+Linux的裸机开发,再回去看Windows下的单片机开发,会惊呼:怎么那么简单!并且你会完全明白这些工具没有向你展示的技术细节。

     

    驱动程序=Linux驱动程序软件框架+ARM开发板硬件操作,我们可以从简单的裸机开发入手,先掌握硬件操作,并且还可以:

    ①掌握如何在PC Linux下编译程序、把程序烧录到板子上并运行它

    ②为学习bootloader打基础:掌握了各种硬件操作后,后面一组合就是一个bootloader

     

     

    2.2.5 为什么选择ARM9 S3C2440开发板,而不是其他性能更好的

    有一个错误的概念:S3C2440过时了、ARM9过时了。

    这是不对的,如果你是软件工程师,无论是ARM9、ARM11、A8还是A9,对我们来说是没有差别的。

     

    一款芯片,上面有CPU,还有众多的片上设备(比如UART、USB、LCD控制器)。我们写程序时,并不涉及CPU,只是去操作那些片上设备。

    所以:差别在于片上设备,不在于CPU核;差别在于寄存器操作不一样。

    因为我们写驱动并不涉及CPU的核心,只是操作CPU之外的设备,只是读写这些设备的寄存器。

    之所以推荐S3C2440,是因为它的Linux学习资料最丰富,并有配套的第1、2期视频。

     

     

    2.2.6 怎么学习ARM+Linux的裸机开发

    学习裸机开发的目的有两个:

    ①掌握裸机程序的结构,为后续的u-boot作准备

    ②练习硬件知识,即:怎么看原理图、芯片手册,怎么写代码来操作硬件

     

    后面的u-boot可以认为是裸机程序的集合,我们在裸机开发中逐个掌握各个部件,再集合起来就可以得到一个u-boot了。

    后续的驱动开发,也涉及硬件操作,你可以在裸机开发中学习硬件知识。

     

    注意如果你并不关心裸机的程序结构,不关心bootloader的实现,这部分是可以先略过的。在后面的驱动视频中,我们也会重新讲解所涉及的硬件知识。

     

    推荐两本书:杜春蕾的《ARM体系结构与编程》,韦东山的《嵌入式Linux应用开发完全手册》。后者也许是国内第1本涉及在PC Linux环境下开发的ARM裸机程序的书,如果我说错了,请原谅我书读得少。

     

    对于裸机开发,我们提供有2部分视频:

    ①环境搭建

    第0课第1节_刚接触开发板之接口接线.wmv

    第0课第2节_刚接触开发板之烧写裸板程序.wmv

    第0课第3节_刚接触开发板之重烧整个系统.wmv

    第0课第4节_刚接触开发板之使用vmwae和预先做好的ubuntu.wmv

    第0课第5节_刚接触开发板之u-boot打补丁编译使用及建sourceinsight工程.wmv

    第0课第6节_刚接触开发板之内核u-boot打补丁编译使用及建sourceinsight工程.wmv

    第0课第7节_刚接触开发板之制作根文件系统及初试驱动.wmv

    第0课第8节_在TQ2440,MINI2440上搭建视频所用系统.wmv

    第0课第9节_win7下不能使用dnw烧写的替代方法.wmv

     

    ②裸机程序开发

    第1课 环境搭建及工具、概念介绍.wmv

    第2课 GPIO实验.wmv

    第3课 存储管理器实验.wmv

    第4课 MMU实验.wmv

    第5课 NAND FLASH控制器.wmv

    第6课 中断控制器.wmv

    第7课 系统时钟和UART实验.wmv

    第8课 LCD实验.wmv

     

     

    声明的是:

    录制上述《裸机程序开发》视频时,本意是结合《嵌入式Linux应用开发完全手册》的《第2篇 ARM9嵌入式系统基础实例篇》来讲解,所以视频里没有完全从零编写代码,需要结合书本来学习。

     

    ①书和视频并不是完全配套的,不要照搬,其中的差异并不难解决。

    《嵌入式Linux应用开发完全手册》发表于2008年,使用了很多款开发板,并且那时的开发板配置较低(Nand Flash是64M);

    《裸机程序开发》视频使用JZ2440开发板录制。

    ②书和视频,适用于所有S3C2440开发板,包括mini2440、tq2440等

    天下S3C2440配置都是相似的,基本也就是LED、按键所用引脚不同,LCD型号不同;你学习了书、视频,如果连这些差异都搞不定的话,那就是你我的失败了。

     

    学习方法是这样的:

    ①先看《环境搭建》视频来搭建开发环境

    ②书(第2篇)和视频(裸机程序开发)结合,看完一章,练习一章

    一定要编写代码,即使是照抄也要写。

    ③如果对于ARM架构相关的知识,觉得模糊或是想了解得更深入,参考《ARM体系结构与编程》

     

    学习程度:

    ①理解一个裸机程序的必要结构:异常向量、硬件初始化、代码重定位、栈

    ②知道如何操作GPIO、Flash、LCD、触摸屏等硬件

    ③很多人觉得MMU难以理解,可以放过它

     

    2.2.7 bootloader的学习

    bootloader有很多种,vivi、u-boot等等,最常用的是u-boot。

    u-boot功能强大、源码比较多,对于编程经验不丰富、阅读代码经验不丰富的人,一开始可能会觉得难以掌握。

     

    但是,u-boot的主要功能就是:启动内核。它涉及:读取内核到内存、设置启动参数、启动内核。按照这个主线,我们尝试自己从零编写一个bootloader,这个程序相对简单,可以让我们快速理解u-boot主要功能的实现。

     

    从零编写bootloader的视频有:

    毕业班第1课第1.1节_自己写bootloader之编写第1阶段.wmv

    毕业班第1课第1.2节_自己写bootloader之编写第2阶段.wmv

    毕业班第1课第2节_自己写bootloader之编译测试.wmv

    毕业班第1课第3节_自己写bootloader之改进.wmv

     

     

    分析u-boot 1.1.6的视频有:

    第9课第1节 u-boot分析之编译体验.wmv

    第9课第2节 u-boot分析之Makefile结构分析.wmv

    第9课第3节 u-boot分析之源码第1阶段.wmv

    第9课第3节 u-boot分析之源码第2阶段.wmv

    第9课第4节 u-boot分析之u-boot命令实现.wmv

    第9课第5节 u-boot分析_uboot启动内核.wmv

     

    移植一个全新u-boot的视频有:

    毕业班第2课第1节_移植最新u-boot之初试.wmv

    毕业班第2课第2.1节_移植最新u-boot之分析启动过程之概述.wmv

    毕业班第2课第2.2节_移植最新u-boot之分析启动过程之内存分布.wmv

    毕业班第2课第2.3节_移植最新u-boot之分析启动过程之重定位.wmv

    毕业班第2课第3.1节_移植最新u-boot之修改代码之建新板_时钟_SDRAM_UART.wmv

    毕业班第2课第3.2节_移植最新u-boot之修改代码支持NAND启动.wmv

    毕业班第2课第3.3节_移植最新u-boot之修改代码支持NorFlash.wmv

    毕业班第2课第3.4节_移植最新u-boot之修改代码支持NandFlash.wmv

    毕业班第2课第3.5节_移植最新u-boot之修改代码支持DM9000网卡.wmv

    毕业班第2课第4.1节_移植最新u-boot之裁剪和修改默认参数.wmv

    毕业班第2课第4.2节_移植最新u-boot支持烧写yaffs映象及制作补丁.wmv

     

     

    《嵌入式Linux应用开发完全手册》上对u-boot的讲解有如下章节:

    15.1  Bootloader简介

    15.1.1  Bootloader的概念

    15.1.2  Bootloader的结构和启动过程

    15.1.3  常用Bootloader介绍

    15.2  U-Boot分析与移植

    15.2.1  U-Boot工程简介

    15.2.2  U-Boot源码结构

    15.2.3  U-Boot的配置、编译、连接过程

    15.2.4  U-Boot的启动过程源码分析

    15.2.5  U-Boot的移植

    15.2.6  U-Boot的常用命令

    15.2.7  使用U-Boot来执行程序

     

    学习方法如下:

    ①先学习《从零编写bootloader的视频》,这可以从最少的代码理解bootloader的主要功能

    ②再看书上对u-boot的讲解,并结合《分析u-boot 1.1.6的视频》来理解

    ③最后,有时间有兴趣的话,看《移植一个全新u-boot的视频》,这不是必须的。

     

    学习程度:

    ①理解u-boot的启动过程,特别是u-boot代码重定位:怎么从Flash上把自己读入内存

    ②理解u-boot的核心:命令

    ③知道bootloader如何给内核传递参数

    ④知道bootloader是根据“bootcmd”指定的命令启动内核

    ⑤作为入门:只求理解,不要求能移植u-boot

     

    2.2.8 Linux内核的学习

    前面说过,内核本身不是我们学习的重点,但是了解一下内核的启动过程,还是很有必要的:工作中有可能要修改内核以适配硬件,掌握了启动过程才知道去修改哪些文件。

     

    分析内核的视频有:

    第10课第1节 内核启动流程分析之编译体验.wmv

    第10课第2节 内核启动流程分析之配置.wmv

    第10课第3节 内核启动流程分析之Makefile.wmv

    第10课第4节 内核启动流程分析之内核启动.wmv

     

    移植内核的视频有:

    毕业班第3课第1节_移植3.4.2内核之框架介绍及简单修改.wmv

    毕业班第3课第2节_移植3.4.2内核之修改分区及制作根文件系统.wmv

    毕业班第3课第3节_移植3.4.2内核之支持yaffs文件系统.wmv

    毕业班第3课第4节_移植3.4.2内核之裁剪及ECC简介及制作补丁.wmv

     

    《嵌入式Linux应用开发完全手册》上对内核的讲解有如下章节:

    16.1  Linux版本及特点

    16.2  Linux移植准备

    16.2.1  获取内核源码

    16.2.2  内核源码结构及Makefile分析

    16.2.3  内核的Kconfig分析

    16.2.4  Linux内核配置选项

    16.3  Linux内核移植

    16.3.1  Linux内核启动过程概述

    16.3.2  修改内核以支持S3C2410/S3C2440开发板

    16.3.3  修改MTD分区

    16.3.4  移植YAFFS文件系统

    16.3.5  编译、烧写、启动内核

     

     

    学习方法如下:

    ①先看书,并结合《分析内核的视频》进行理解

    ②如果有兴趣,再根据《移植内核的视频》自己掌握移植内核,这不是必须的

     

    学习程度:

    ①知道机器ID的作用,根据机器ID找到单板对应的文件

    ②知道Makefile、Kconfig的作用,知道怎么简单地配置内核

    ③知道怎么修改分区

    ④作为入门:只求理解,不要求能移植

     

    2.2.9 根文件系统

    在驱动程序开发阶段,我们喜欢搭建一个最小根文件系统来调试驱动;

    在开发应用程序时,也需要搭建文件系统,把各种库、配置文件放进去;

    在发布产品时,你还需要修改配置文件,使得产品可以自动运行程序;

    甚至你想实现插上U盘后自动启动某个程序,这也要要修改配置文件;

    这一切,都需要你理解根文件系统的构成,理解内核启动后是根据什么配置文件来启动哪些应用程序。

     

    分析根文件系统的视频有:

    第11课第1节 构建根文件系统之启动第1个程序.wmv

    第11课第2节 构建根文件系统之init进程分析.wmv

    第11课第3节 构建根文件系统之busybox.wmv

    第11课第4节 构建根文件系统之构建根文件系统.wmv

     

    《嵌入式Linux应用开发完全手册》上对文件系统的讲解有如下章节:

    17.1  Linux文件系统概述

    17.1.1  Linux文件系统的特点

    17.1.2  Linux根文件系统目录结构

    17.1.3  Linux文件属性介绍

    17.2  移植Busybox

    17.2.1  Busybox概述

    17.2.2  init进程介绍及用户程序启动过程

    17.2.3  编译/安装Busybox

    17.3  使用glibc库

    17.3.1  glibc库的组成

    17.3.2  安装glibc库

    17.4  构建根文件系统

    17.4.1  构建etc目录

    17.4.2  构建dev目录

    17.4.3  构建其他目录

    17.4.4  制作/使用yaffs文件系统映象文件

    17.4.5  制作/使用jffs2文件系统映象文件

     

    学习方法:结合书和视频学习。

    学习程度:

    ①理解配置文件的作用

    ②知道根文件系统中lib里的文件来自哪里

    ③可以制作、烧写文件系统映象文件

     

     

    2.3 驱动程序的学习

    《嵌入式Linux应用开发完全手册》对驱动程序的讲解不多,我们推出的“韦东山Linux视频第2期_驱动现场编写调试”,可以认为完全脱离了书本。

    所以,驱动程序的学习完全按照视频来就可以了

     

    第2期的视频,对每一个驱动,先讲解硬件原理,然后从零写代码,从简单到复杂,逐渐完善它的功能。

    我们不会罗列专业术语,会参考日常生活的例子,力争用最形象的比喻让你轻松入门,同时又会很深入。

    注意我们可以让你入门时很轻松,但是要深入理解的话,这需要你跟着视频练习代码,这是个要慢慢思考的过程,不会轻松。

    轻松的话,凭什么拿高工资?

    再次申明:即使照抄也要写代码!很多人视频看得很高兴,但是写代码时就傻了。

     

    2.3.1 经典字符设备驱动程序

    视频中以LED、按键驱动为例,讲解并练习开发过程中碰到的机制:查询、休眠-唤醒、中断、异步通知、poll、同步、互斥等等。

    后续更复杂的驱动程序,就是在这些机制的基础上,根据硬件特性设计出精巧的软件框架。

     

    相关的视频有(文件名中带“_P”的属于第2期加密视频):

    第12课第1节 字符设备驱动程序之概念介绍.wmv

    第12课第2.1节 字符设备驱动程序之LED驱动程序_编写编译.wmv

    第12课第2.2节 字符设备驱动程序之LED驱动程序_测试改进.wmv

    第12课第2.3节 字符设备驱动程序之LED驱动程序_操作LED.wmv

    第12课第3节 字符设备驱动程序之查询方式的按键驱动程序.wmv

    第12课第4.1节 字符设备驱动程序之中断方式的按键驱动_Linux异常处理结构.wmv

    第12课第4.2节 字符设备驱动程序之中断方式的按键驱动_Linux中断处理结构.wmv

    第12课第4.3节 字符设备驱动程序之中断方式的按键驱动_编写代码.wmv

    第12课第5节 字符设备驱动程序之poll机制.wmv

    第12课第6节 字符设备驱动程序之异步通知.wmv

    第12课第7节 字符设备驱动程序之同步互斥阻塞.wmv

    第12课第8节 字符设备驱动程序之定时器防抖动_P.wmv

    第13课第1节 输入子系统概念介绍_P.wmv

    第13课第2节 输入子系统第编写驱动程序_P.wmv

     

    《嵌入式Linux应用开发完全手册》上对字符设备驱动程序的讲解有如下章节:

    第19章  字符设备驱动程序

    19.1  Linux驱动程序开发概述

    19.1.1  应用程序、库、内核、驱动程序的关系

    19.1.2  Linux驱动程序的分类和开发步骤

    19.1.3  驱动程序的加载和卸载

    19.2  字符设备驱动程序开发

    19.2.1  字符设备驱动程序中重要的数据结构和函数

    19.2.2  LED驱动程序源码分析

     

    第20章  Linux异常处理体系结构

    20.1  Linux异常处理体系结构概述

    20.1.1  Linux异常处理的层次结构

    20.1.2  常见的异常

    20.2  Linux中断处理体系结构

    20.2.1  中断处理体系结构的初始化

    20.2.2  用户注册中断处理函数的过程

    20.2.3  中断的处理过程

    20.2.4  卸载中断处理函数

    20.3  使用中断的驱动程序示例

    20.3.1  按键驱动程序源码分析

    20.3.2  测试程序情景分析

     

    学习方法:

    ①沿着数据流向,从应用程序的对驱动程序的使用进行情景分析。

    所谓情景分析,就是假设应用程序发起某个操作,你去分析其中的运作过程。比如应程序调用open、read、ioctl等操作时涉及驱动的哪些函数调用。

    你要思考一个问题:一个应用程序,怎么获得按键信息,怎么去控制LED。把其中数据的流向弄清楚了,对字符驱动程序也就基本理解了。

    ②学习异常和中断时,可以结合书和视频;对于驱动程序中其他内容的学习,可以不看书。

     

     

    2.3.2 工作中各类驱动程序

    我们的视频中讲解的驱动程序非常多,目的有二:

    ①在你工作中遇到同类驱动时提供借鉴

    ②供你学习、练习,煅炼阅读驱动程序的“语感”,提升编写程序的能力,增加调试经验

    我们还打算扩充驱动视频,把它打造成“Linux驱动程序大全”视频,基本上都会采取从零现场编写的方式。

    也许有人说:在工作中我们基本上只是移植、修改驱动而已,很少从头编写。这话没错,但是能修改的前提是理解;想更好地理解,最好的方法是从零写一个出来。在学习阶段,不要怕耗费太多时间,从零开始编写,慢慢完善它,在这过程中你既理解了这个驱动,也煅炼了能力,做到触类旁通。

     

    如果有时间,建议你学完这些所有的视频,直到你自认为:

    ① 给你一个新板,你可以很快实现相关驱动

    ② 给你一个新硬件,你可以很快给它编写/移植驱动。

     

    我们录制的视频很多,下面只罗列到“课”,不罗列到“节”。

     

    第2期视频:

    第14课 驱动程序分层分离概念_总线驱动设备模型

    第15课 LCD驱动程序

    第16课 触摸屏驱动程序

    第17课 USB驱动程序

    第18课 块设备驱动程序

    第19课 NAND FLASH驱动程序

    第20课 NOR FLASH驱动程序

    第21课 网卡驱动程序

    第22课 移植DM9000C驱动程序

    第23课 I2C设备裸板程序

    第24课 I2C驱动程序    (不看此课,看第32课,第32课讲得更好)

    第26课 声卡驱动程序    (不看此课,看第3期的ALSA驱动,那讲得更好)

    第27课 DMA驱动程序

    第28课 hotplug_uevent机制

    第32课 3.4.2内核下的I2C驱动程序

    第3期的驱动视频:

    摄像头驱动_虚拟驱动vivi

    摄像头驱动_USB摄像头

    摄像头驱动_CMOS摄像头

    WIFI网卡驱动程序移植

    3G网卡驱动程序移植

    ALSA声卡驱动程序

     

    学习方法:

    ①再次强调,不能光看不练:一定要写程序,即使照抄也得写

    ②必学:LCD、触摸屏、NAND Flash、Nor Flash、hotplug_uevent机制

    ③学完之后,强烈建议换一个不同的开发板,尝试在新板上写驱动程序。

    按视频学习会一切顺利,很多问题你可能没想到、没想通,换一个新板会让你真正掌握。

     

    2.3.3 调试方法

    有一种说法,程序是三分写七分调,我们从操作系统的角度提供了一些很有用的调试方法。

    相关的视频有:

    第29课第1节_裸板调试之点灯法_P.wmv

    第29课第2节_裸板调试之串口打印及栈初步分析_P.wmv

    第29课第3.1节_裸板调试之JTAG原理_P.wmv

    第29课第3.2节_裸板调试之JTAG调试体验_P.wmv

    第29课第3.3节_裸板调试之JTAG调试命令行调试_P.wmv

    第29课第3.4节_裸板调试之JTAG调试源码级调试_P.wmv

     

    第30课第1.1节_驱动调试之printk的原理_P.wmv

    第30课第1.2节_驱动调试之printk的使用_P.wmv

    第30课第1.3节_驱动调试之打印到proc虚拟文件_P.wmv

    第30课第2.1节_驱动调试之段错误分析_根据pc值确定出错的代码位置_P.wmv

    第30课第2.2节_驱动调试之段错误分析_根据栈信息确定函数调用过程_P.wmv

    第30课第3节_驱动调试之自制工具_寄存器编辑器_P.wmv

    第30课第4节_驱动调试之修改系统时钟中断定位系统僵死问题_P.wmv

     

    第31课第1节_应用调试之使用strace命令跟踪系统调用_P.wmv

    第31课第2节_应用调试之使用gdb和gdbserver_P.wmv

    第31课第3节_配置修改内核打印用户态段错误信息_P.wmv

    第31课第4.1节_应用调试之自制系统调用_P.wmv

    第31课第4.2节_应用调试之使用自制的系统调用_P.wmv

    第31课第5.1节_应用调试之输入模拟器之设计思路_P.wmv

    第31课第5.2节_应用调试之输入模拟器之编写保存功能_P.wmv

    第31课第5.3节_应用调试之输入模拟器之编写测试模拟功能_P.wmv

     

     

    2.4 Linux应用程序的学习

    对于大多数人来说,第1个C程序是在Windows的Visual Studio C++(简称VC)上写的,所以你们关心的也许是:嵌入式Linux应用程序,跟VC应用程序之间的区别:

    ①编译方法不同:

    在VC上点点鼠标即可编译,对于嵌入式Linux应用程序,我们需要“交叉编译”:程序要在PC Linux上编译,但是运行时要放到单板上。

    并且,它的编译环境需要你自己搭建:解压出工具链后设计PATH,还要自己构造一套Makefile系统。

     

    ②调试方法不同:

    在VC上点点鼠标就可以调试,对于嵌入式Linux应用程序,你可以更喜欢用打印;或是在PC Linux上通过GDB观察应用程序在单板上的运行状况。

     

    ③可用的资源不同:

    对于VC程序,你可以直接使用微软公司提供的各种类库;对于嵌入式Linux应用程序,很多时候需要去寻找、下载、编译、使用开源库。

     

    ④功能不同:

    VC程序运行在PC上,一般是用来解决某些纯软件的问题,比如整理数据、修图、联网播放音乐之类。嵌入式Linux应用程序一般都要操作若干种硬件,比如监控设备中要操作摄像头、存储音视频,无人机中要操作GPS、螺旋桨,POS机中要操作银行卡等等。它跟单板上的硬件联系很大,很多时候需要你懂点硬件知识,至少是知道怎么通过驱动程序来操作这些硬件。

    上述4点的不同,花很少的时间就可以掌握。

    如果你有志于开发应用程序,那么一定要有算法、数据结构、网络编程等基础,然后再掌握一些设计模式,最后就是多参加一些实际项目的开发了。

     

    基于我们提供的视频,你可以这样学习:

    ①先掌握第1期讲解的根文件系统:

    在后续学习中你会经常构建根文件系统,比如往里面添加库、修改配置文件让你的程序自动运行。

     

    ②掌握怎么编译、烧写u-boot、内核:

    在实际工作中,一般来说不需要你去烧写u-boot、内核,但是在自学阶段还是自己掌握吧,免得去麻烦别人。

    按开发板手册即可操作,你甚至不用管里面的原理。

     

    ③掌握Makefile:

    可以看如下第3期视频,以后编译程序时只要执行make命令即可:

    第1课第4节_数码相框_编写通用的Makefile

     

    ④学习第1个项目:数码相框

    该项目不使用任何开源的GUI项目,完全是自己构建一套GUI系统,实现了文件浏览、文件显示(文本和图片)、图片操作(放大、缩小、自动播放)等功能;涉及网络编程、多线程编程、开源库使用等等。

    虽然数码相框作为一个产品已经落伍了,但是该项目所涉及的技术,特别是以面向对象的编程思想设计出一个模块化的、易扩展的系统,非常适合没有大型项目开发经验的人。很多同学,都是根据该项目所教会的编程思想找到了心怡的工作。

     

    第3期视频取名为“项目开发”,而非“应用开发”,它的第2、3个项目跟内核、驱动耦合很大。如果只关心应用开发,或是急于找一份工作,可以先看第1个项目。

    第2个项目涉及摄像头、ALSA声卡、WIFI网卡、3G网卡,这些都是在实际工作过程中经常用到的设备,比如我们后面补充的QQ物联就用到摄像头、声卡、WIFI网卡。

    第3个项目是电源管理,讲解怎么讲你的单板休眠以省电。

                                                

    扫码关注本人微信公众号,有惊喜奥!公众号每天定时发送精致文章!回复关键词可获得海量各类编程开发学习资料!

    例如:想获得Python入门至精通学习资料,请回复关键词Python即可。

     

    展开全文
  • 嵌入式操作系统学习书籍以及方法指导,嵌入式操作系统学习书籍以及方法指导,
  • ucos嵌入式操作系统

    2018-06-09 11:07:03
    讲述ucos实时嵌入式操作系统,讲的言简意赅,不错的学习资料,需要的伙伴们请拿走,谢谢!
  • 熟悉嵌入式开发的同学都知道,一般没有操作系统的程序都是在main函数有一个死循环来完成相关任务,一些紧急的操作放在中断里来完成,通常称作前后台系统,如下图所示: 对于业务逻辑简单的程序,这么做没什么不好...

    1.前后台系统与多任务系统

    熟悉嵌入式开发的同学都知道,一般没有操作系统的程序都是在main函数有一个死循环来完成相关任务,一些紧急的操作放在中断里来完成,通常称作前后台系统,如下图所示:
    这里写图片描述
    对于业务逻辑简单的程序,这么做没什么不好的。但是代码复杂后,很多个中断包含嵌套中断会使复杂性急剧膨胀,中断间的交互将会变得十分困难,可维护性差,增加一个新功能对代码的改动较大,如果中断函数执行时间太长,同级中断将会受到影响。所以为了减少复杂性,通过在中断里置标志位,在主循环里查询,但是这样查询又是按照顺序来的,后面的任务实时性将会降低,而且如果每个任务的执行周期不一样,又会额外增加很多工作量。另外,这种系统的应用层代码和硬件代码的耦合性较高也带来了移植的困难。

    实时操作系统(RTOS)正是为了解决前后台系统应对复杂应用的不足,通过芯片内核的软件中断来实现多任务系统。多任务系统如下图所示
    这里写图片描述
    每个模块的任务保持独立,任务可以自行设置执行周期而不受其他任务的影响。高优先级的任务能够抢占低优先级的任务,保证了系统的实时性,当任务优先级相同的时候还可以按照时间片的方式运行,看起来就像在同时运行多个任务一样。任务由系统调度器统一管理,通过信号量、消息、事件标志等机制使任务和任务、任务和中断的交互变得更加容易,但又保持了各模块的独立性,总之引入实时操作系统使程序处理复杂逻辑业务变得更加容易,很好地隔离了应用层和硬件驱动层,极大地提高了系统的可扩展性。

    接下来就来介绍FreeRTOS和µC/OS-III是如何管理任务运行的,主要从任务启动和任务切换2个方面来分析。

    2.FreeRTOS的任务启动和切换

    2.1堆栈初始化

    每一个任务都会有一个任务控制块(TCB),TCB是一个非常复杂的结构体,包含了大量任务的重要信息如任务堆栈、任务运行状态、任务优先级等等,它与任务的调度息息相关,这里我们不细讲。

    通过调用xTaskCreate()函数来创建一个任务,来初始化TCB相关信息,这里我们需要知道TCB的第一个变量为堆栈的栈顶指针,在任务启动和切换时会用到,新建任务时会初始化堆栈,要注意堆栈的偏移地址与Cortex-M3的内核寄存器存在对应关系,之后在任务启动后会与Cortex-M3的线程堆栈指针挂接在一起,任务切换后堆栈的内容会pop到内核寄存器,CPU从而开始执行任务代码。

    //更新任务控制块的栈顶指针
    pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
    //初始化堆栈
    StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
    {
        /* Simulate the stack frame as it would be created by a context switch
        interrupt. */
        pxTopOfStack--; /* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */
        *pxTopOfStack = portINITIAL_XPSR;   /* xPSR */
        pxTopOfStack--;
        *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;    /* PC */
        pxTopOfStack--;
        *pxTopOfStack = ( StackType_t ) prvTaskExitError;   /* LR */
        pxTopOfStack -= 5;  /* R12, R3, R2 and R1. */
        *pxTopOfStack = ( StackType_t ) pvParameters;   /* R0 */
        pxTopOfStack -= 8;  /* R11, R10, R9, R8, R7, R6, R5 and R4. */
    
        return pxTopOfStack;
    }

    入栈时堆栈地址从高地址到低地址增长,xPSR、PC、LR、R12、R3~R0这8个寄存器会由CPU自动入栈,入栈顺序如下图所示:
    这里写图片描述
    其他寄存器R11~R4需要手工入栈

    最后在FreeRTOS里通过全局变量pxCurrentTCB指向当前优先级最高的第一个就绪任务控制块,任务切换时只需改变pxCurrentTCB的指向即可

    2.2任务启动

    通过调用vTaskStartScheduler()来启动任务,此时会创建一个空闲任务

        /* The Idle task is being created using dynamically allocated RAM. */
            xReturn = xTaskCreate( prvIdleTask, "IDLE", configMINIMAL_STACK_SIZE, ( void * ) NULL, ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), &xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */

    之后根据不同的硬件平台会调用xPortStartScheduler()来启动任务,此时会先设置系统定时器周期,再调用vPortStartFirstTask()来启动第一个任务,这是一个汇编函数

    vPortStartFirstTask
        /* Use the NVIC offset register to locate the stack. */
        ldr r0, =0xE000ED08 ;中断向量表的第一个地址
        ldr r0, [r0]        ;获取第一个中断地址
        ldr r0, [r0]        ;获取栈顶地址
        /* Set the msp back to the start of the stack. */
        msr msp, r0
        /* Call SVC to start the first task, ensuring interrupts are enabled. */
        cpsie i             ;开中断
        cpsie f             ;开异常
        dsb                 ;数据隔离
        isb                 ;指令隔离,保证前面的指令先执行完
        svc 0               ;跳转到SVC中断
    
        END

    进到这里之后,以后不会再返回主循环了,所以代码先复位主堆栈指针,然后跳转到SVC中断

    vPortSVCHandler:
        /* Get the location of the current TCB. */
        ldr r3, =pxCurrentTCB
        ldr r1, [r3]
        ldr r0, [r1] ;获取栈顶地址
        /* Pop the core registers. */
        ldmia r0!, {r4-r11} ;手工将寄存器r4-r11出栈
        /*让线程堆栈指针PSP指向pxCurrentTCB->pxTopOfStack
        此后上面提到的8个寄存器就会由M3内核自动出栈*/
        msr psp, r0   
        isb
        mov r0, #0
        msr basepri, r0  ;不屏蔽任何中断
        orr r14, r14, #13
        bx r14     ;跳转到任务代码,任务地址将会从PSP自动出栈到PC指针

    上面有一句话是orr r14, r14, #13表示返回线程模式,使用的是线程堆栈PSP,没有这一句则会使用主堆栈MSP,r14即为LR寄存器,在调用子函数时会保存返回地址,而在进入中断时只有3个合法值,这个值是CPU自动设置的,意义如下

    • 0xFFFFFFF1
      表示中断返回时从MSP堆栈恢复寄存器值,中断返回后进入Handler模式,使用MSP堆栈,(相当于从中断返回到另一个中断)。

    • 0xFFFFFFF9
      表示中断返回时从MSP堆栈恢复寄存器值,中断返回后进入线程模式,使用MSP堆栈(这种用于不使用PSP只使用MSP堆栈的情况)。

    • 0xFFFFFFFD
      表示中断返回时从PSP堆栈恢复寄存器值,中断返回后进入线程模式,使用PSP堆栈(这是常见的,OS处理完中断后返回用户程序)。

    这里需要返回任务代码,并使用PSP堆栈,所以使用0xFFFFFFFD作为返回值

    2.3任务切换

    任务切换和任务启动的代码其实是类似的,只不过多了2个步骤:

    • 在当前任务堆栈中,手动将R4~R11寄存器入栈
    • 调用vTaskSwitchContext寻找当前最高优先级任务TCB,并保存在pxCurrentTCB

    切换操作在xPortPendSVHandler中断里进行

    xPortPendSVHandler:
        mrs r0, psp
        isb
        ldr r3, =pxCurrentTCB           /* Get the location of the current TCB. */
        ldr r2, [r3]
    
        stmdb r0!, {r4-r11}             /* Save the remaining registers. */
        str r0, [r2]                    /* Save the new top of stack into the first member of the TCB. */
    
        stmdb sp!, {r3, r14}
        /*关中断,防止全局变量pxCurrentTCB被外部修改*/
        mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
        msr basepri, r0
        dsb
        isb
        bl vTaskSwitchContext
        mov r0, #0
        msr basepri, r0
        ldmia sp!, {r3, r14}
    
        ldr r1, [r3]
        ldr r0, [r1]                    /* The first item in pxCurrentTCB is the task top of stack. */
        ldmia r0!, {r4-r11}             /* Pop the registers. */
        /*将新的堆栈挂接到PSP,PC等8个寄存器会自动出栈,跳转到新任务代码执行任务*/
        msr psp, r0
        isb
        bx r14

    在系统心跳或发送信号量时都会触发一次请求任务切换,即进入xPortPendSVHandler中断,都会调用portYIELD,这个函数直接修改寄存器的相应bit来进入中断

    /* Scheduler utilities. */
    #define portYIELD()                                         \
    {                                                           \
        /* Set a PendSV to request a context switch. */         \
        portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;         \
        __DSB();                                                \
        __ISB();                                                \
    }

    3.µC/OS-III的任务启动和切换

    同样的µC/OS-III也有一个任务TCB,TCB的第一个成员就是任务堆栈指针,新建任务也要初始化任务堆栈

    CPU_STK  *OSTaskStkInit (OS_TASK_PTR    p_task,
                             void          *p_arg,
                             CPU_STK       *p_stk_base,
                             CPU_STK       *p_stk_limit,
                             CPU_STK_SIZE   stk_size,
                             OS_OPT         opt)
    {
        CPU_STK  *p_stk;
    
    
        (void)opt;                                              /* Prevent compiler warning                               */
    
        p_stk = &p_stk_base[stk_size];                          /* Load stack pointer                                     */
                                                                /* Registers stacked as if auto-saved on exception        */
        *--p_stk = (CPU_STK)0x01000000u;                        /* xPSR                                                   */
        *--p_stk = (CPU_STK)p_task;                             /* Entry Point                                            */
        *--p_stk = (CPU_STK)OS_TaskReturn;                      /* R14 (LR)                                               */
        *--p_stk = (CPU_STK)0x12121212u;                        /* R12                                                    */
        *--p_stk = (CPU_STK)0x03030303u;                        /* R3                                                     */
        *--p_stk = (CPU_STK)0x02020202u;                        /* R2                                                     */
        *--p_stk = (CPU_STK)p_stk_limit;                        /* R1                                                     */
        *--p_stk = (CPU_STK)p_arg;                              /* R0 : argument                                          */
                                                                /* Remaining registers saved on process stack             */
        *--p_stk = (CPU_STK)0x11111111u;                        /* R11                                                    */
        *--p_stk = (CPU_STK)0x10101010u;                        /* R10                                                    */
        *--p_stk = (CPU_STK)0x09090909u;                        /* R9                                                     */
        *--p_stk = (CPU_STK)0x08080808u;                        /* R8                                                     */
        *--p_stk = (CPU_STK)0x07070707u;                        /* R7                                                     */
        *--p_stk = (CPU_STK)0x06060606u;                        /* R6                                                     */
        *--p_stk = (CPU_STK)0x05050505u;                        /* R5                                                     */
        *--p_stk = (CPU_STK)0x04040404u;                        /* R4                                                     */
    
        return (p_stk);
    }

    在启动第一个任务前会调用OSStart()启动调度器,在这里会调用 OSStartHighRdy()来启动第一个任务,这个函数是用汇编写的

    OSStartHighRdy
        LDR     R0, =NVIC_SYSPRI14                                  ; Set the PendSV exception priority
        LDR     R1, =NVIC_PENDSV_PRI     ;设置最低优先级中断
        STRB    R1, [R0]
    
        MOVS    R0, #0                                              ; Set the PSP to 0 for initial context switch call
        MSR     PSP, R0
    
        LDR     R0, =OS_CPU_ExceptStkBase                           ; Initialize the MSP to the OS_CPU_ExceptStkBase
        LDR     R1, [R0]                 ;设置主堆栈指针为OS_CPU_ExceptStkBase
        MSR     MSP, R1    
    
        LDR     R0, =NVIC_INT_CTRL                                  ; Trigger the PendSV exception (causes context switch)
        LDR     R1, =NVIC_PENDSVSET     ;跳转到OS_CPU_PendSVHandr中断
        STR     R1, [R0]
    
        CPSIE   I                                                   ; Enable interrupts at processor level
    
    OSStartHang
        B       OSStartHang                                         ; Should never get here

    由代码知道,首先设置OS_CPU_PendSVHandr中断为最低优先级,并初始PSP指针为0,表示这是第一次中断,不同于FreeRTOS,µC/OS-III并没有使用SVC中断,第一次启动任务和任务切换使用同一个中断。之后把主堆栈指针设为指定值,而FreeRTOS中是设为复位值。接着就进入OS_CPU_PendSVHandr切换任务,代码如下:

    OS_CPU_PendSVHandler
        CPSID   I                                                   ; Prevent interruption during context switch
        MRS     R0, PSP                                             ; PSP is process stack pointer
        CBZ     R0, OS_CPU_PendSVHandler_nosave                     ; Skip register save the first time
    
        SUBS    R0, R0, #0x20                                       ; Save remaining regs r4-11 on process stack
        STM     R0, {R4-R11}
    
        LDR     R1, =OSTCBCurPtr                                    ; OSTCBCurPtr->OSTCBStkPtr = SP;
        LDR     R1, [R1]
        STR     R0, [R1]                                            ; R0 is SP of process being switched out
    
                                                                    ; At this point, entire context of process has been saved
    OS_CPU_PendSVHandler_nosave
        PUSH    {R14}                                               ; Save LR exc_return value
        LDR     R0, =OSTaskSwHook                                   ; OSTaskSwHook();
        BLX     R0
        POP     {R14}
    
        LDR     R0, =OSPrioCur                                      ; OSPrioCur   = OSPrioHighRdy;
        LDR     R1, =OSPrioHighRdy
        LDRB    R2, [R1]
        STRB    R2, [R0]
    
        LDR     R0, =OSTCBCurPtr                                    ; OSTCBCurPtr = OSTCBHighRdyPtr;
        LDR     R1, =OSTCBHighRdyPtr
        LDR     R2, [R1]
        STR     R2, [R0]
    
        LDR     R0, [R2]                                            ; R0 is new process SP; SP = OSTCBHighRdyPtr->StkPtr;
        LDM     R0, {R4-R11}                                        ; Restore r4-11 from new process stack
        ADDS    R0, R0, #0x20
        MSR     PSP, R0                                             ; Load PSP with new process SP
        ORR     LR, LR, #0x04                                       ; Ensure exception return uses process stack
        CPSIE   I
        BX      LR                                                  ; Exception return will restore remaining context
    
        END
    

    如果PSP为0则表示是第一次启动任务,则直接进入OS_CPU_PendSVHandler_nosave切换任务,否则需要事先把当前的寄存器R4~R11手工压入当前任务堆栈,再进行切换。

    在切换前会调用OSTaskSwHook对原来的任务做一些统计工作,之后就会更新OSPrioCur为新任务优先级,OSTCBCurPtr为新任务TCB,最后将新任务堆栈出栈到R4-R11寄存器,剩余的寄存器在绑定PSP后由M3内核自动出栈。

    µC/OS-III中通过调用OS_TASK_SW() 或OSIntCtxSw()触发OS_CPU_PendSVHandler来切换任务,在此前已经获取最高优先级的就绪任务,所以在切换时直接把OSTCBCurPtr更新为OSTCBHighRdyPtr即可

    void  OSSched (void)
    {
        ......
    
        CPU_INT_DIS();
        //此处获取最高优先级就绪任务
        OSPrioHighRdy   = OS_PrioGetHighest();                  /* Find the highest priority ready                        */
        OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
        if (OSTCBHighRdyPtr == OSTCBCurPtr) {                   /* Current task is still highest priority task?           */
            CPU_INT_EN();                                       /* Yes ... no need to context switch                      */
            return;
        }
        ......
        //此处触发切换中断
        OS_TASK_SW();                                           /* Perform a task level context switch                    */
        CPU_INT_EN();
    }
    展开全文
  • <基于嵌入式实时操作系统的程序设计技术> <嵌入式实时操作系统ucos-II原理及应用> <嵌入式实时操作系统uCOS-II(中文版)>
  • 太原理工大学 大三下-嵌入式操作系统c实验报告
  • 本资源为韩辉老师在长沙·中国1024程序员节中主题演讲内容,仅供学习,更多详情见:https://1024.csdn.net/
  • 嵌入式操作系统学习(3)FreeRTOS的任务调度机制

    万次阅读 多人点赞 2018-07-09 16:29:15
    1.任务状态 FreeRTOS可以创建多个任务,但是对于单核cpu来说,在任意给定时间,实际上只有一个任务被执行,这样就可以把任务分成2个状态,即运行状态和非运行状态。...如下图所示,从整体上操作系统调...

    1.任务状态

    FreeRTOS可以创建多个任务,但是对于单核cpu来说,在任意给定时间,实际上只有一个任务被执行,这样就可以把任务分成2个状态,即运行状态和非运行状态。

    当任务处于运行状态时,处理器就执行该任务的代码。处于非运行态的任务,它的所有寄存器状态都保存在自己的任务堆栈中,当调度器将其恢复到运行态时,会从上一次离开运行态时正准备执行的那条指令开始执行。

    如下图所示,从整体上操作系统调度可以看作是把任务从运行态和非运行态来回切换。
    这里写图片描述

    每个任务都有一个任务控制块(TCB),而pxCurrentTCB则保存了当前运行的TCB,当任务发生切换后,pxCurrentTCB选择就绪任务列表里优先级最高的任务轮流执行。

    上面提到的只是一个最粗略的任务状态模型,事实上为了完成复杂的任务调度机制,将任务的非运行态又划分成了3个状态,分别是阻塞状态,挂起状态和就绪状态,完整的状态转移图如下所示:
    这里写图片描述

    从图中可以看到,运行态的任务可以直接切换成挂起、就绪或阻塞状态,但是只有在就绪态里的任务才能直接切换成运行态。

    2.任务列表

    要理解调度器是如何将任务从这些状态切换,首先必须明白任务列表这个概念。处于运行态的任务只有一个,而其他所有任务都处于非运行态,所以一个状态往往存在很多任务,为了让调度器容易调度,把相同状态下的任务通过列表组织在一起。

    • 就绪态

      任务在就绪状态下,每个优先级都有一个对应的列表,最多有configMAX_PRIORITIES个

      static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];

    • 暂停态

      当任务被挂起时,任务将会放在暂停列表里:

      static List_t xSuspendedTaskList;

    • 阻塞态

     当任务因为延时或等待事件的发生时会处于阻塞状态,延时任务列表里的任务通过延时的先后顺序由小到大排列,延时列表必须有2个,当延时唤醒后的时间戳会溢出的任务放在溢出任务列表里。等到系统时间戳溢出后,再把溢出任务列表和当前任务列表切换。
    `
    static List_t xDelayedTaskList1;                        /*< Delayed tasks. */
    static List_t xDelayedTaskList2;                        /*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
    static List_t * volatile pxDelayedTaskList;             /*< Points to the delayed task list currently being used. */
    static List_t * volatile pxOverflowDelayedTaskList;     /*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */
        if( xConstTickCount == ( TickType_t ) 0U )
        {
            taskSWITCH_DELAYED_LISTS();
        }
    
    #define taskSWITCH_DELAYED_LISTS()                                                                  \
    {                                                                                                   \
        List_t *pxTemp;                                                                                 \
                                                                                                        \
        /* The delayed tasks list should be empty when the lists are switched. */                       \
        configASSERT( ( listLIST_IS_EMPTY( pxDelayedTaskList ) ) );                                     \
                                                                                                        \
        pxTemp = pxDelayedTaskList;                                                                     \
        pxDelayedTaskList = pxOverflowDelayedTaskList;                                                  \
        pxOverflowDelayedTaskList = pxTemp;                                                             \
        xNumOfOverflows++;                                                                              \
        prvResetNextTaskUnblockTime();                                                                  \
    }
    

    在阻塞态下除了延时任务列表,还要等待事件发生的任务列表,这在信号量,消息队列,任务通知时都会用到。队列里的任务列表通常有2个,一个是发送,一个是接收,列表项是按照优先级排序的,这里先不展开,以后学习队列的时候再详细分析。

    pxQueue->xTasksWaitingToSend
    pxQueue->xTasksWaitingToReceive
    • pending态
      当任务从挂起或阻塞状态被激活时,如果调度器也处于挂起状态,任务会先放进xPendingReadyList队列,等到调度器恢复时(xTaskResumeAll)再将这些xPendingReadyList里的任务一起放进就绪列表。

      那么为什么要来一个中间步骤而不直接放进就绪任务列表呢?
      这是因为任务从挂起到恢复可能出现优先级大于当前运行任务,高优先级任务要抢占低优先级任务,由于之前调度器被挂起,所以无法执行抢占操作。等调度器恢复后,再将xPendingReadyList里的任务一一取出来,判定是否有抢占操作发生或任务延时到期。

    while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )
    {
        pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) );
        ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
        ( void ) uxListRemove( &( pxTCB->xStateListItem ) );
        prvAddTaskToReadyList( pxTCB );
    
        /* If the moved task has a priority higher than the current
        task then a yield must be performed. */
        //弹出的任务高于当前优先级,需要进行任务抢占
        if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
        {
            xYieldPending = pdTRUE;
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    
    UBaseType_t uxPendedCounts = uxPendedTicks; /* Non-volatile copy. */
    
    if( uxPendedCounts > ( UBaseType_t ) 0U )
    {
        do
        {
            //检查恢复的任务有没有延时到期的
            if( xTaskIncrementTick() != pdFALSE )
            {
                xYieldPending = pdTRUE;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
            --uxPendedCounts;
        } while( uxPendedCounts > ( UBaseType_t ) 0U );
    
        uxPendedTicks = 0;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    3.列表结构

    列表由列表头和列表项组成,列表头和列表项组成一个双向循环链表。
    列表的结构定义如下

    typedef struct xLIST
    {
        listFIRST_LIST_INTEGRITY_CHECK_VALUE                /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
        configLIST_VOLATILE UBaseType_t uxNumberOfItems;
        ListItem_t * configLIST_VOLATILE pxIndex;           /*< Used to walk through the list.  Points to the last item returned by a call to listGET_OWNER_OF_NEXT_ENTRY (). */
        MiniListItem_t xListEnd;                            /*< List item that contains the maximum possible item value meaning it is always at the end of the list and is therefore used as a marker. */
        listSECOND_LIST_INTEGRITY_CHECK_VALUE               /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
    } List_t;

    listFIRST_LIST_INTEGRITY_CHECK_VALUE不用管,在代码里宏定义为空。pxIndex表示当前列表的的索引,通过这个值来遍历整个列表,在相同优先级调度时会通过时间片来轮流执行每个任务,这时候就会用到这个值。xListEnd标记列表的结尾是一个简化的列表项,不存储数据,只存储链表的前后指针。
    struct xMINI_LIST_ITEM
    {
    listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. /
    configLIST_VOLATILE TickType_t xItemValue;
    struct xLIST_ITEM * configLIST_VOLATILE pxNext;
    struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
    };
    这里xItemValue固定为0xffffffff,标记链表结束,重新开始新一轮循环。

    真正的列表项则还需要存储该列表项所在的列表和列表项对应的TCB,如果在延时列表里,则xItemValue表示下一次唤醒的时间戳,如果在队列里,则xItemValue表示任务优先级,在一些事件列表里该值还用来表示相关事件。

    struct xLIST_ITEM
    {
        listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE           /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
        configLIST_VOLATILE TickType_t xItemValue;          /*< The value being listed.  In most cases this is used to sort the list in descending order. */
        struct xLIST_ITEM * configLIST_VOLATILE pxNext;     /*< Pointer to the next ListItem_t in the list. */
        struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; /*< Pointer to the previous ListItem_t in the list. */
        void * pvOwner;                                     /*< Pointer to the object (normally a TCB) that contains the list item.  There is therefore a two way link between the object containing the list item and the list item itself. */
        void * configLIST_VOLATILE pvContainer;             /*< Pointer to the list in which this list item is placed (if any). */
        listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE          /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
    };
    typedef struct xLIST_ITEM ListItem_t;                   /* For some reason lint wants this as two separate definitions. */

    下面还是通过结构图来描述列表、列表项和任务TCB的对应关系:
    这里写图片描述

    每个任务TCB都有2个列表项指针,用来标记任务的状态,分别是状态列表项和事件列表项

    pxTCB->xStateListItem
    pxTCB->xEventListItem

    pxTCB->xStateListItem用来标记任务的状态,pxTCB->xEventListItem表示任务在某一个事件等待队列里。

    4.任务调度

    在移植时,我们把系统时钟中断xPortSysTickHandler加入到了中断向量表,这个中断周期设置为1ms。这个中断是系统的核心,我们称作调度器,在这里会调用xTaskIncrementTick()把时间计数值加1,并检查有哪些延时任务到期了,将其从延时任务列表里移除并加入到就绪列表里。如果到期的任务优先级>=当前任务则开始一次任务切换。如果当前任务就绪态里有多个任务,也需要切换任务,优先级相同需要在一个系统时钟周期的时间片里轮流执行每个任务。另外在应用程序里也可以通过设置xYieldPending的值来通知调度器进行任务切换。

    在一个延时阻塞的任务里,如下面的程序:

    void vTask1(void *pvParameters)
    {
      while(1)
      {  
        UARTprintf("task1 %d\n",i++);
        vTaskDelay(1000/portTICK_RATE_MS);
      }
    }

    程序每1s执行一次,执行完后vTaskDelay会将当前任务添加到延时任务列表里,并强行切换任务。过了1s后系统时钟中断检测到任务该任务延时到期,会重新添加到就绪任务列表里。此时如果延时到期的任务优先级最高,将会被唤醒执行。如果优先级不是最高,那么任务将得不到执行,只有当最高优先级的任务进入阻塞状态才会执行。

    了解信号量和消息队列的同学都知道,通常在编程时一个死循环任务在等待消息或信号量,此时这个任务会卡在那里,直到另外的任务或中断发送了信号量后,这个任务才能往下走。举个例子,有如下代码

    void vTask1(void *pvParameters)
    {
      while(1)
      {  
        xSemaphoreGive(xSemaphore);
        UARTprintf("task1 %d\n",i++);
        vTaskDelay(1000/portTICK_RATE_MS);
      }
    }
    
    
    void vTask2(void *pvParameters)
    {
      while(1)
      {
        xSemaphoreTake( xSemaphore, portMAX_DELAY );
        UARTprintf("task2\n");
      }
    }

    这个代码中任务2一直在等待信号量,处于阻塞状态。而任务1每秒执行1次,唤醒后发送信号量激活任务2。这里先粗略介绍一下信号量的调度机制,后面讲队列的时候在详细介绍。xSemaphoreTake没收到信号量时会将任务加入到阻塞队列,并开始一次任务切换。

    BaseType_t xQueueGenericReceive(
    {
        for( ;; )
        {
            taskENTER_CRITICAL();
            {
                const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
    
                /* Is there data in the queue now?  To be running the calling task
                must be the highest priority task wanting to access the queue. */
                if( uxMessagesWaiting > ( UBaseType_t ) 0 )
                {
                    ......
                    //收到消息或信号量函数返回
                    //使得任务往下执行
                }
                else
                {
                    ......
                }
           }
           taskEXIT_CRITICAL();
           ......
           vTaskSuspendAll();
           ......
           //在这里把任务添加到等待队列,并从就绪任务列表里执行
           vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
           prvUnlockQueue( pxQueue );
           //在这里切换任务,如果是TURE,则在xTaskResumeAll()函数里面已经切换好了
           if( xTaskResumeAll() == pdFALSE )
           {
                //发送切换任务的请求
                //任务后面的代码不会被执行
                //任务被激活后从这里开始执行下一条指令
                portYIELD_WITHIN_API();
           }
       }
    }

    发送信号量时在xQueueGenericSend()函数里会把任务从等待队列中移除,重新加入到就绪列表里,如果优先级比当前任务高,则开始一次任务抢占切换,否则把任务交给调度器切换。

    if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
    {
        /* The unblocked task has a priority higher than
        our own so yield immediately.  Yes it is ok to do
        this from within the critical section - the kernel
        takes care of that. */
        queueYIELD_IF_USING_PREEMPTION();
    }

    5.临界区

    进入临界区即屏蔽中断,但这里不屏蔽所有中断,FreeRTOS有一个临界区中断优先级,在FreeRTOSConfig.h里配置

    #define configMAX_SYSCALL_INTERRUPT_PRIORITY 191 

    关闭中断时设置BASEPRI寄存器为该值,这个寄存器会并屏蔽掉掉优先级比设定值低的,而优先级高的不受影响,如果BASEPRI设为0则不屏蔽任何中断。
    假如M3的中断优先级为3位,则191(0b10111111)的前3位有效,即优先级为5,此时优先级为5~7的中断优先级低于设定值值会被屏蔽,而优先级为0~4的中断优先级高于设定值不受影响,不受影响的中断称作非临界区中断,不能调用FreeRTOS的API,否则会破会系统数据。
    进入临界区时会屏蔽临界区内的所有中断,进入和离开临界区的API需要成对使用

    taskENTER_CRITICAL()
    ......
    taskEXIT_CRITICAL() 

    这是一个宏定义,我们看最后的实现:

    void vPortEnterCritical( void )
    {
        portDISABLE_INTERRUPTS();
        uxCriticalNesting++;
    
        /* This is not the interrupt safe version of the enter critical function so
        assert() if it is being called from an interrupt context.  Only API
        functions that end in "FromISR" can be used in an interrupt.  Only assert if
        the critical nesting count is 1 to protect against recursive calls if the
        assert function also uses a critical section. */
        if( uxCriticalNesting == 1 )
        {
            configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
        }
    }
    
    void vPortExitCritical( void )
    {
        configASSERT( uxCriticalNesting );
        uxCriticalNesting--;
        if( uxCriticalNesting == 0 )
        {
            portENABLE_INTERRUPTS();
        }
    }

    这里我们发现多了一个嵌套计数值,这是为了防止调用函数进入临界区出现嵌套时,里面那一层taskEXIT_CRITICAL()提前将中断打开了,外面那一层的数据就不受保护了。

    在中断里屏蔽临界区用一种不同的方式,进入临界区时时先读取当前BASEPRI寄存器的值,退出后在设定为之前读取的值,所以发生嵌套后里面那一层并不改变寄存器的值。

    uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
    ......
    portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );

    使用这种临界区屏蔽中断的API函数的结尾都加上了FromISR后缀表示在中断里调用。

    这里有一个问题,既然这2种临界区的实现效果相同,为什么不统一成一种?这个问题一直没想明白,尝试给出一个强行的解释,如果taskENTER_CRITICAL()用在中断里,则会触发configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );断言,所以只用在任务里,但是非临界区的中断发生也会触发这个断言,所以用这个断言解释感觉不是很说的通。那么任务里为什么不用portSET_INTERRUPT_MASK_FROM_ISR取代taskENTER_CRITICAL()呢?这里可能是为了方便的角度考虑,因为portSET_INTERRUPT_MASK_FROM_ISR这种方式还要额外再定义一个全局变量,在临界区较多的任务代码里使用起来比较麻烦。

    展开全文
  • 本文主要讲解嵌入式系统编程的内存操作的问题。
  • 任务的特性:   独立性: 表现为逻辑上的平等性和信息传输的异步性。平等性,即可以独占CPU,一个任务“看不见”另外一个任务;...——《基于嵌入式实时操作系统的程序设计技术》[ 周航慈 ] 书摘

    任务的特性:

           独立性:表现为逻辑上的平等性和信息传输的异步性。平等性,即可以独占CPU,一个任务“看不见”另外一个任务;异步性,指任务之间信息传递必须通过媒介。

           并发性:任务交替进行,宏观上并发进行。

           动态性:任务的状态是动态变化的,不能一直占有CPU。(休眠、就绪、运行、等待、中断)

     

    任务划分小结:

           关键任务:必须得到运行机会的,如火灾监控

           紧迫任务:必须尽快执行的

    1)、首先,以CPU为中心,将与各种输入/输出设备(或端口)相关的功能分别划分为独立的任务;

    2)、发现“关键”功能,将其最“关键”部分“剥离”出来,用一个独立任务(或ISR)完成,剩余部分用另外一个任务实现,两者之间通过通信机制沟通;

    3)、发现“紧迫”功能,将其最“紧迫”部分“剥离”出来,用一个独立的高优先级任务(或ISR)完成,剩余部分用另外一个任务实现,两者之间通过通信机制沟通;

    4)、对于既“关键”又“紧迫”的功能,按“紧迫”功能处理;

    5)、将消耗机时较多的数据处理功能划分出来,封装为较低优先级任务;

    6)、将关系密切的若干功能组合成为一个任务,达到功能聚合的效果;

    7)、将由相同事件触发的若干功能组合成为一个任务,从而免除事件分发机制;

    8)、将运行周期相同的功能组合成为一个任务,从而免除时间事件分发机制;

    9)、将若干按固定顺序执行的功能组合成为一个任务,从而免除同步接力通信的麻烦。

     

    任务的优先级安排原则如下:

    1)、中断关联性:与中断服务程序(ISR)有关联的任务应该安排尽可能高的优先级,以便及时处理异步事件,提高系统的实时性。如果优先级安排得比较低,则CPU有可能被优先级比较高的任务长期占用,以致于在第二次中断发生时连第一次中断还没有处理,从而产生信号丢失现象;

    2)、紧迫性:因为紧迫任务对响应时间有严格要求,在所有紧迫任务中,按响应时间要求排序,越紧迫的任务安排的优先级越高。紧迫性为最高原则。紧迫任务通常与ISR关联。

    3)、关键性:任务越关键安排的优先级越高,以保障其执行机会。

    4)、频繁性:对于周期性任务,执行越频繁,则周期越短,允许耽误的时间也越短,故应该安排的优先级也越高,以保障及时得到执行。

    5)、快捷性:在前面各项条件相近时,越快捷(耗时短)的任务安排的优先级越高,以使其他就绪任务的延时缩短。

    6)、传递性:信息传递的上游任务的优先级高于下游任务的优先级,如信号采集任务的优先级高于数据处理任务的优先级。

     

    行为同步 任务之间的动作配合和协调关系

    类型

    方法

    ISR和任务之间同步(单向同步关系)

    关联任务挂起等待,ISR发送信号量

    两个任务单向同步

    任务A在同步点挂起等待任务B发送同步的信号量(可能需要考虑AB的优先级)

    可以考虑合并任务

    两个任务双向同步

    生产者提供消息来同步消费者,消费者回复消息来同步生产者,达到产销平衡

    两个以上任务同步一个任务

    总装型

    逻辑与事件标记组

    “或”关系

    逻辑或事件标记组

    多个任务相互同步

    逻辑与事件标记组,同步点发出一个信号并挂起等待,多个任务尽数到达后再各自往下执行。

    问题:

    必须加入一个判断自身是不是最后一个到达同步点的任务是否需要清除标记组。

    在最后一个任务未清除标记组的时候,前面的任务已经二次到达同步点

    在同步点判断是不是最后一个到达,不是则挂起count+1,是则发送广播消息。整个过程最好关中断,可以将这个过程封装成一个函数。

     

     

    资源同步

    保障数据可靠性

    例子:多任务相互同步时的count必须准确计数,方法在上表。

    保障数据完整性

    关中断

    (适合访问规模小的资源)

    对结构体资源进行访问的时候,它同时也在更新(例如访问RTC),访问过程如果遇到打断,则访问过程时间变长,可能导致读取一半资源后被打断回来已经刷新了(如7:59:59→8:00:00),这样得到的结果就是错的(如7:00:00,读取数字7之后被打断)。

    解决方法:需要访问资源时,关中断,并且立即备份到自定义的结构体来。

    关掉度

    (访问规模大的资源,访问者不是ISR)

    缺点:使无关任务收到牵连

    使用互斥信号量

    最优

    使用计数信号量

    多实体共享资源,加以一套管理机制


    ——《基于嵌入式实时操作系统的程序设计技术》[周航慈] 书摘

    展开全文
  • 不过没有关系我们发烧友专注于在快乐中学习,要学习STM32,我们首先了解下五大嵌入式操作系统:μClinux、μC/OS-II、eCos、FreeRTOS和RT-thread。 TOP1:μClinux μClinux是一种的嵌入式Linux版本,从字面意思看...
  • 嵌入式 Linux 操作系统学习规划 ARM+LINUX 路线主攻嵌入式 Linux 操作系统及其上应用软件开发目标 1 掌握主流嵌入式微处理器的结构与原理初步定为 arm9 2 必须掌握一个嵌入式操作系统 初步定为uclinux 或 linux,版本...
  • 本文是针对初学者所介绍的如何学习嵌入式linux的学习规划。
  • 十年经验教你如何学习嵌入式系统

    千次阅读 2019-11-08 18:23:32
    一、如何学习嵌入式系统- - 嵌入式系统的概念着重理解“嵌入”的概念 ,主要从三个方面上来理解。1、从硬件上,“嵌入”将基于CPU的处围器件,整合到CPU芯片内部,比如早...
  • 嵌入式操作系统产品在学习的过程中,了解产品特性能够有助于学习产品功能,并且能够从用户角度发掘产品需要满足的重要特征,从而提炼产品特点,并且化作产品亮点,转化为产品未来占领市场、提升竞争力的关键切入点。...
  • 此项目提供课程《自己动手从0到1写嵌入式操作系统》所有配套资料的下载,包含源码,参考文档等。 如该资料有兴趣,可以使用GitHub的打包下载功能。或者使用Git工具,SourceTree等将整个项目克隆到本地。 由于课程...
  • 嵌入式操作系统原理与应用教,内容详实,有助于初学者学习
  • 讲的内容细致,详细,浅显易懂。是初学嵌入式的好帮手 。
  • 关于嵌入式系统学习路线图

    万次阅读 多人点赞 2018-04-28 21:31:36
    来源:本文乃同济大学软件学院王院长(JacksonWan)在同济网论坛发表的帖子《谈谈软件学院高年级同学的学习方向》的第二部分。三部分依次为:一、关于企业计算方向;二、关于嵌入式系统方向;三、关于游戏软件方向。...
  • 对ucosii中不必要的内容进行了裁剪。因为51单片机的idata很小,这份代码用了51单片...但因为51单片机的汇编语句少,简单,可以作为其他处理器移植ucosii的模版和参考,同时也是学习51单片机递增以及高级应用的实例。
  • linux算是目前用的比较多的嵌入式操作系统,但是对于很多想要学习嵌入式的新手而言,这些内容还是很陌生。  今天就来给大家介绍一些常用的嵌入式操作系统吧!!学习嵌入式,必不可少的就是要掌握好嵌入式操作系统...
  • 系统的介绍STM32嵌入式操作系统学习STM32上操作系统的实现。
  • 嵌入式操作系统实验相关学习 构建嵌入式Linux系统实验讲义.doc
  • 1 引言 TRON(The Real-time Operating SYSTEM Nucleus)是一种在国内的知名度非常低的嵌入式实时操作系统,但是却占据了全球微处理器操作系统市场大约60%的份额,这远远超过了Windows的普及程度。它已经安装到了...
  • 嵌入式操作系统PPT学习教案.pptx

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 89,752
精华内容 35,900
关键字:

嵌入式操作系统学习