文章目录
1. 嵌入式实时操作系统的基本概念
-
操作系统的概念:
操作系统(Operating System, OS)是一种系统软件。它在计算机硬件与计算机应用程序之间,通过提供应用程序接口(Application Programming Interface, API),屏蔽了计算机硬件工作的一些细节,从而使应用程序的设计人员得以在一个友好的平台上进行应用程序的设计和开发,大大提高了应用程序的开发效率。
-
操作系统的作用:
计算机的操作系统为应用程序提供了一个界面友好,性能稳定、安全,效率高,操作方便的虚拟计算机。
-
操作系统的功能:
-
处理器的管理: 一是对中断的管理,二是对处理器的工作进行调度。
因为处理器硬件只能发现外部事件的中断申请,而不能对中断进行管理和处理,于是对中断的管理和处理只能由操作系统来承担。
现代计算机应用程序大多是多道程序结构,那么一个处理器如何来运行这多道程序就是一个较为复杂的问题。它要求操作系统应该能按照某种规则对处理器的使用进行合理的分配,这样才能使多道应用程序协调有秩序的运行,这就是操作系统所应该具有的处理器调度功能。
-
存储器的管理: 存储器是计算机的重要资源,如何合理地分配和使用该资源当然是计算机操作系统责无旁贷的重要责任。
-
设备的管理: 计算机系统一般都配有外部设备,因此计算机操作系统还必须有对这些外部设备管理的功能,以便完成用户提出的I/O)请求,加快输入/输出的速度,提高I/O设备的利用率。当然,作为一个完善的计算机操作系统还要提供外部设备的驱动程序。
-
文件的管理: 在计算机中,程序和数据通常都以文件的形式存储在外存(例如硬盘、光盘等)中,由于在这些外存中文件量极其巨大,如果对它们没有良好的管理方式,就会导致严重的后果。为此,计算机操作系统必须具备文件管理功能。
-
网络和通信的管理: 使用网络的计算机除了需要配备连网硬件之外,其操作系统必须还要具有管理网上资源、通过网络进行通信、故障管理、安全管理、性能管理等网络功能。
-
提供用户接口: 计算机操作系统除了提供以上所说的各项功能之外,还要为用户提供良好使用上述功能的接口,以便用户能方便地使用操作系统的功能,从而能有效地组织作业及其工作,并使系统能高效地运行。
-
-
嵌入式系统:
嵌入式系统是对对象进行自动控制而使其具有智能化并可嵌入对象体系中的专用计算机系统。
-
嵌入式系统的特点:
- 专用性强;
- 可裁剪性好;
- 实时性与可靠性好;
- 功耗低。
-
实时操作系统的要求:
第一,实时系统的计算必须产生正确的结果,称为逻辑或功能正确(Logical or Functional Correctness);第二,实时系统的计算必须在预定的时间内完成,称为时间正确(Timing Correctness)。
-
实时操作系统的条件:
- 实时操作系统必须是多任务系统。
- 任务的切换时间应与系统中的任务数无关。
- 中断延迟的时间可预知并尽可能短。
-
嵌入式系统与嵌入式操作系统的区别与联系:
区别:
-
定义和范围:
- 嵌入式系统(Embedded System): 是一个专用的计算机系统,设计用于执行特定任务。它通常包括硬件和软件,并且嵌入在更大的设备中。嵌入式系统的硬件部分包括处理器、存储器、输入输出接口等,而软件部分包括固件和操作系统。
- 嵌入式操作系统(Embedded Operating System): 是专为嵌入式系统设计的操作系统,用于管理硬件资源和运行应用程序。嵌入式操作系统具有轻量级、实时性和高效能等特点,常见的嵌入式操作系统有
FreeRTOS
、VxWorks
、μC/OS
等。
-
功能和职责:
- 嵌入式系统: 执行特定的功能或任务,如家电控制、汽车发动机管理、工业自动化控制等。嵌入式系统的设计注重实时性、低功耗、可靠性等。
- 嵌入式操作系统: 提供任务调度、资源管理、内存管理、中断处理等功能,使嵌入式系统能够高效地运行多个任务,并确保实时性和稳定性。
-
组成部分:
- 嵌入式系统: 包括硬件和软件两部分。硬件部分包含处理器、传感器、接口等;软件部分包含操作系统、应用程序、驱动程序等。
- 嵌入式操作系统: 仅指软件部分,负责硬件抽象、任务调度、资源分配等。
-
开发复杂度:
- 嵌入式系统: 开发包括硬件设计、软件开发和系统集成,需要跨学科的知识。
- 嵌入式操作系统: 专注于软件开发,重点在于系统软件的设计和优化。
联系:
-
共存关系:
- 嵌入式操作系统是嵌入式系统的重要组成部分。大多数复杂的嵌入式系统都需要一个嵌入式操作系统来管理系统资源和调度任务。
-
相互依赖:
- 嵌入式系统的硬件需要嵌入式操作系统来提供抽象层,使应用程序能够访问底层硬件。而嵌入式操作系统需要嵌入式系统的硬件平台来运行。
-
共同目标:
- 二者都致力于实现特定任务的高效、稳定和实时执行。嵌入式操作系统通过提供轻量级、实时的操作系统内核,帮助嵌入式系统满足这些要求。
-
优化设计:
- 嵌入式操作系统通常针对特定的嵌入式系统硬件进行优化,以确保在资源受限的环境下能够高效运行。
-
2. 预备知识
2.1. 指针
- 指针是一个程序实体所占用内存空间的首地址。
- 指针变量是用来保存指针的变量。
- 指针变量必须赋值之后才能使用。
2.2. 函数指针
一个函数就是一段代码,C编译器会为这段代码分配一段连续内存空间,同时把首地址作为常量值赋予以函数名定义的常量。这就是说,函数名就是该函数的指针。
-
函数指针的定义:
返回值类型 (* 变量名) (参数1类型,参数2类型,……);
其中,返回值类型 (*) (参数1类型,参数2类型,……); 为函数指针变量的类型
# include<stdio.h> int function_1(int, float) { printf("%d\n", "function_1"); return 0; } int function_2(int, float) { printf("%d\n", "function_2"); return 0; } //定义函数指针pf int (* pf) (int, float); int main() { pf = function_1; pf(10, 3,14); pf = function_2; pf(100, 63.24); return 0; }
通过函数指针变量来间接调用函数,而且同一个函数指针变量还可以随时通过改变与之关联的函数名来改变它所指向的函数,因而使得同一个指针变量可以调用不同的函数,只要它们的返回类型和参数类型相同。
-
函数指针作为函数参数及回调函数
void function(int (* pf)(int,float)) { pf(10,8.888); }
函数的参数pf就是一个函数指针,其类型为int(*)(int,float)。即具有如下返回值及参数类型的函数的函数名(函数指针)均可以作为上述function()函数的实参,例如int usr_I(int,float)、int usr_2(int,float)、int usr_3(int,float),等等。
#include <stdio.h> int usr_1(int, float) { printf("%s\n", "usr_1"); return 0; } int usr_2(int, float) { printf("%s\n", "usr_2"); return 0; } int usr_3(int, float) { printf("%s\n", "usr_3"); return 0; } int (* pf)(int, float); //以函数指针为参数的函数 void sys_function(int (* pf)(int, float)) { printf("这是function函数中调用用户函数:"); pf(10, 8,888); printf("本函数自己的功能\n"); } int main() { sys_function(usr_1); sys_function(usr_2); sys_function(usr_3); return 0; }
将函数指针作为函数参数,可以将一个函数传递到另一个函数内来调用。这种用法在操作系统这类系统软件中应用得相当频繁,因为操作系统的某些函数的功能需要用户配合才能实现,常需要调用用户提供的函数。目前,大致有两种方法来实现用户函数的调用:一种方法就是像上例中的sys_function()那样以函数指针来传递被调用的用户函数;另一种方法就是在系统函数中设置所谓的“钩子函数”,即在系统函数中需要调用用户功能的地方调用一个空函数,然后由用户去实现这个空函数的功能。在第一种方法中,由于是系统函数调用用户函数,与常用的用户程序调用系统函数的调用方向不同,故人们将这种调用叫做“回调”,而被系统调用的这个函数就叫做“回调函数”。
2.3. typedef常用方法
-
复杂的数据类型名称的简化
typedef void(* PFON)(int,int); PFON function; = void(* function)(int, int); void (* b[10])(void (*)(int,int)); //由于这个定义中的程序实体名称为b,因为其后面有一个数组符号“[]”,所以这一定是一个名称为b的数组定义。为了方便,先把数组符号“[]”去掉,那么剩下的便是如下的样子: void (* b)(void (*)(int,int)); //所以它的右边括号里的void(*)(int,int)是一个类型,并且是一个指针类型,于是先为这个指针类型定义一个别名: typedef void (* pFunParam)(int,int); //于是原来的初始定义就可以写成如下形式: void (* b)(pFunParam); //b是一个无返回值,参数类型为pFunParam的函数指针,如果在该定义前使用typedef,并用pFunx代替变量名b: typedef void (* pFunx)(pFunParam); //于是这个函数指针类型别名就是这个pFux,原来声明语句就可表示成如下语句: pFunx b[10]; //即声明的是一个其元素为函数指针的数组。
-
用typedef来定义与平台无关的数据类型
typedef long double RFAL; typedef double RFAL; //当跨平台时,只要修改typedef本身就行,不用对其他源码做任何修改
-
增强代码的可读性
-
避免错误
2.4. 常用数据类型及数据结构
2.4.1. 控制块
typedef struct tcb{
char * code_name; //代码名称
int p; //重要性级别
int v_number; //版本号
void (* fun)(void); //指向被管理代码的函数指针
}TCB;
代码示例:
#include <stdio.h>
#include <string.h>
typedef struct tcb
{
char * code_name;
int p;
int v_num;
void (* fun)();
}TCB;
void function_1()
{
int i;
for(i=0;i<10;i++)
{
printf("111111111\n");
}
}
void function_2()
{
int i;
for(i=0;i<10;i++)
{
printf("222222222\n");
}
}
void function_3()
{
int i;
for(i=0;i<10;i++)
{
printf("333333333\n");
}
}
//创建控制块函数
TCB GreateTCB(char * name, int pp, int vnum, void (* f)())
{
TCB tcb;
tcb.code_name = name;
tcb.p = pp;
tcb.v_num = vnum;
tcb.fun = f;
return tcb;
}
int main()
{
char code_name[10];
int t,i;
TCB tcbTbl[3];
tcbTbl[0] = GreateTCB("F1", 2, 1, function_1);
tcbTbl[1] = GreateTCB("F2", 2, 4, function_2);
tcbTbl[2] = GreateTCB("F3", 4, 5, function_3);
printf("Input CodeNume:");
scanf("%s", code_name);
t=0;
for(i=0; i<3; i++)
{
if(strcmp(tcbTbl[i].code_name, code_name) == 0)
{
tcbTbl[i].fun();
t = 1;
}
if(i == 2 && t == 0)
printf("No %s\n", code_name);
}
return 0;
}
定义一个任务控制块(TCB)结构体,用于描述一个任务的基本属性(名字、优先级、变量数量和要执行的函数)。
GreateTCB
函数用于创建并初始化一个TCB实例,方便任务的创建与管理。
通过上述结构体和初始化函数,可以方便的创建任务:
void task_function()
{
// 执行一些任务
}
int main()
{
TCB myTask = GreateTCB("Task1", 1, 10, task_function);
myTask.fun(); // 执行任务函数
return 0;
}
2.4.2. 链表、堆栈、位图
在操作系统中,链表、堆栈和位图是重要的数据结构,它们在管理内存、处理进程和任务调度等方面起着关键作用。以下是它们的具体用途:
链表
链表是一种动态数据结构,可以灵活地管理内存,常用于以下几种情况:
- 内存管理:链表用于实现空闲内存块的管理,例如在动态内存分配中,操作系统使用链表来跟踪已分配和未分配的内存块。最常见的是自由链表(free list),它存储所有未分配的内存块。
- 进程和任务管理:操作系统使用链表来维护进程控制块(PCB)和任务控制块(TCB)。这些链表可以帮助操作系统高效地调度和管理进程。
- 设备管理:设备驱动程序中,链表用于管理设备队列,处理输入输出请求。
- 文件系统:在文件系统中,链表用于管理文件块。例如,FAT(文件分配表)文件系统使用链表来跟踪文件的块。
堆栈
堆栈是一种后进先出(LIFO)的数据结构,主要用于以下几种情况:
- 进程控制:每个进程或线程都有自己的堆栈,用于保存函数调用、局部变量和返回地址。操作系统在切换进程时,会保存和恢复进程的堆栈。
- 中断处理:当硬件中断发生时,CPU将当前状态压入堆栈,然后跳转到中断处理程序。处理完中断后,CPU从堆栈中恢复之前的状态。
- 递归调用:堆栈用于支持递归函数调用,保存每个递归调用的返回地址和局部变量。
- 参数传递:在函数调用中,参数通常通过堆栈传递,尤其是在C语言和汇编语言中。
位图
位图是一种紧凑的表示法,使用位来表示资源的使用情况,主要用于以下几种情况:
- 内存管理:位图用于跟踪内存块的分配情况。每个内存块对应一个位,1表示已分配,0表示空闲。操作系统可以快速找到空闲内存块进行分配。
- 磁盘空间管理:位图用于管理磁盘上的数据块。文件系统使用位图来表示哪些块已被使用,哪些块是空闲的。这样可以高效地分配和回收磁盘空间。
- 进程和线程管理:位图可以用于跟踪进程或线程的状态。例如,一个位图可以表示系统中哪些进程ID(PID)或线程ID(TID)是空闲的,可以分配新的ID。
- 资源管理:在多处理器系统中,位图用于管理处理器核心的分配状态,表示每个核心是否被占用。