0%

IPC

IPC是什么呢?网络摄像头?我们在linux中,所以只能使用进程间通信啦

IPC原理
IPC操作
IPC内核过程
IPC特性
总结

设备树

这个不是linus设计的,是linus借鉴别人的。这个是Open Firmware的设计,常见于powerPC等架构中,linus觉得很好,就把他引进到了linux中了,使用它的目的,也是为了统一管理和分配硬件,减少代码量。所以,遇到哪个of开头的文件和api,因为它来自Open Firmware哦。

使用设备树的时候,先看看uboot是否支持设备树,不支持就找打uboot的一些头文件增加#define CONFIG_OF_LIBFDT /* Device Tress support */试试

再看看菜单中是否开启了设备树功能,在cpu.c中引用了头文件 。比如kernel/arch/arm/mach-s5p6818/cpu.c。

#include <linux/of.h>

#include <linux/of_address.h>

#include <linux/of_fdt.h>

#include <linux/of_platform.h>

在cpu_init_machine添加“加载设备树函数of_platform_populate”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const struct of_device_id of_default_bus_match_table[] = {
{ .compatible = "simple-bus", },
#ifdef CONFIG_ARM_AMBA
{ .compatible = "arm,amba-bus", },
#endif /* CONFIG_ARM_AMBA */
{} /* Empty terminated list */
};

static void __init cpu_init_machine(void)
{
/* set shutdown */
pm_power_off = nxp_cpu_shutdown;
arm_pm_restart = nxp_cpu_reset;
/*
* register platform device
*/
nxp_cpu_devs_register();
nxp_board_devs_register();

//加载设备树
of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);
}

在比较新的linux内核中,设备树dts已经取代了传统的machine board device description,dts

在内核中以各种device node的形式存在,而这些device node对于大部分的内核驱动模块

platform_driver来说,最终需要有对应的platform device来与他匹配才可以完成一次device和

driver的probe过程。

所有有必要将dts中需要加载为device的device node转为platform device,而这个过程是交给

of_platform_populate来完成的(dts相关的device node tree是在main.c中的start_kernel-

>setup_arch->unflatten_device_tree来加载dtb并解析)。

1
2
3
4
5
6
7
8
9
10
11
12
void __init unflatten_device_tree(void)

{
__unflatten_device_tree(initial_boot_params,

&allnodes,early_init_dt_alloc_memory_arch);

/* Get pointer to "/chosen" and "/aliasas" nodes for use everywhere */

of_alias_scan(early_init_dt_alloc_memory_arch);

}

MACHINE_START 改为DT_MACHINE_START,并在其中增加设备树匹配信息。

设备树文件

DTS 、DTC、DTB、

DTS是设备树源码,就是描绘了硬件的配置;如地址、大小、种类、外设配置等

DTC是编译设备树的编译工具,这个需要install下单编译器

1
2
3
4
5
6
7
8
9
10
11
#dtc 工具安装:
sudo apt-get install device-tree-compiler

#编译设备树
dtc -I dts -O dtb -o xxx.dtb xxx.dts

#编译设备树插件
dtc -I dts -O dtb -o xxx.dtbo xxx.dts

#设备树反汇编
dtc -I dtb -O dts -o xxx.dts xxx.dtb

DTB是可执行文件,经过上面的编译之后,可以被编译器识别的文件。可以加到特定位置同内核一起运行,也可以加到内核所在设备树目录中一起编译到内核之中。

设备树语法

设备树该怎么写呢?首先,一个树肯定就是有主干,这个主干,不是我们这些小卡拉米写的,所以,顶多是在芯片原厂的基础上增加一些自己的设备。

枝叶这个该怎么写呢?首先,你得让编译器和主干识别到我这个枝叶吧,这就像一个叶子,一定有叶柄才能和主干连接,而我们修改不过是叶子的形状和功能,所以,开头一定要有一个和主干一模一样的开头。这个就是叶柄了。如下面这种结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
* http://www.samsung.com
** This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/

/dts-v1/;

/ {
model = "csm6818 based on Samsung s5p6818"; /*这一段就是叶柄了,有了它才能正常工作*/
compatible = "csm,s5p6818";
chosen {
compatible = "gec,chosen";
bootargs = "lcd=at070tn92 tp=gslx680-linux root=/dev/mmcblk0p2 rootfstype=ext4 rw ";
};

/*这下面此时就是叶子了,可以随意定义*/
myled{
compatible = "csm,myled"; /*这个compatible不要改,也算是开头和索引号,后面的随便改都行*/
led-num = <4>; /*这个led-num是自定义的,可以修改哦。比如led-nums*/
led-names ="led-d7","led-d8","led-d9","led-d10"; /*字符串*/
led-gpios = <141 81 72 71>; /*数据*/
default-state = "off"; /*字符串 表示状态,当然用数据也行*/
};

};

语法开始:这个就简单的说一下设备树语法,只有使用的时候再去仔细看一下需要的。

Linux设备树学习笔记(一、设备树语法规范)_reg = <0x00 0x80000000 0x00 0x60000000>;-CSDN博客

设备树5个使用方法
1.将dtb文件拷贝到根文件系统(推荐,利于调试)

/* 从根文件系统拷贝dtb文件,更方便,更利于调试 */

setenv bootcmd ext4load mmc 2:2 0x42000000 gec6818.dtb ;ext4load mmc 2:1 0x48000000

uImage ;bootm 0x48000000 - 0x42000000

saveenv

2.挂载开发板内核镜像分区,将dtb拷贝到该分区(安全性高,用户不能随意修改或删除)

mount -t ext4 /dev/mmcblk0p1 /mnt

cp gec6818.dtb /mnt

umount /mnt

/* 拷贝到内核镜像的分区 */

setenv bootcmd ext4load mmc 2:1 0x42000000 gec6818.dtb ;ext4load mmc 2:1 0x48000000

uImage ;bootm 0x48000000 - 0x42000000

saveenv

3.使用网络,配置uboot环境变量。

setenv bootcmd tftp 0x42000000 gec6818.dtb ; ext4load mmc 2:1 0x48000000 uImage ;

bootm 0x48000000 - 0x42000000

4.不使用网络,可以使用串口下载dtb文件到内存地址0x42000000。

loady 0x42000000

setenv bootcmd ext4load mmc 2:1 0x48000000 uImage ; bootm 0x48000000 - 0x42000000

saveenv

5.将dtb文件编译到boot.img,重新烧录内核。

在ubuntu中

将文件拷贝到一个/target/product/6818/boot的目录之下

./mk -k

在uboot中

setenv bootcmd ext4load mmc 2:1 0x42000000 gec6818.dtb ;ext4load mmc 2:1 0x48000000

uImage ;bootm 0x48000000 - 0x42000000

驱动使用设备树节点

驱动想要使用设备树,只能使用平台设备方法的,此时平台设备platform_driver中的dirver结构体中的of_match_table这个就要指向所满足需求的

1
2
3
4
5
6
7
8
9
10
11
12
13
static const struct of_device_id of_led_match[]={
{.compatible="gec,myled",}
}

static struct platform_driver led_plat_driver = {
.probe = myled_probe,
.remove = __devexit_p(myled_remove),
.driver = {
.name = "myled",
.onwer = THIS_MODULE,
.of_match_table = of_led_match, //指针指向
}
}

此时,这个驱动就知道,该找到compatible=”gec,myled”的要求的设备树节点,读取这个节点上面的数据(引脚号和介绍),此时驱动就知道有这个led设备了,就可以控制对应的GPIO引脚的高低电平。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct platform_device *pdev;//已经对应上的平台设备

/* init */

char Buff[30]={0};
//设置节点指向
struct device_node * pdevnode = pdev->dev.of_node;

//混杂设备注册

//申请gpio设备

//调用读取节点字符串函数
of_property_read_string(pdevnode,"目标",&Buff);
of_property_read_string_index(pdevnode,"目标",&Buff); //出现 "xxx","yyy","zzz"的时候
//Buff就是目标字符串了 目标可以是名字介绍,这个是设备树中自定义的

//调用读取节点数据函数
of_property_read_u8_array(pdevnode,"目标",Buff,Numsize);
//Buff就是目标数据了,Numsize就是个数
/*还有of_property_read_u16_array;of_property_read_u32_array;
of_property_read_u64_array*/

//得到了所需要的数据,就可以写对应的驱动了
//gpio操作

/* init结束 */
总结

设备树,就是省去了,对硬件的描述的复杂和多种写法,统一写法,节约空间和利于管理。

平台

我们经常可以看到短视频平台,直播平台,购物平台,那么此时,这个平台的作用是什么?就是不论你是干什么的,你是卖什么的,我都给你一个店铺,你把你的服务和你的物品端上来,这是不是就是平台啊?没错,那对于我们消费者和管理者来说,我们是不是不需要就不需要去分类那么形形色色的货物了,我们还可以细分和归纳合并,这样可以分成平台中卖水果平台,这样是不是就把苹果香蕉梨那些都汇聚到一起了,而且他们用的都是一样的店铺。

驱动
平台设备
平台设备应用
总结

J-link全家桶

什么是j-link全家桶呢?我们下载好j-link的驱动之后,一般还会顺便下载j-link的sdk,这些sdk就是j-link全家桶啦这是干嘛的?说白了,就是为了方便用户下载和调试代码的。只是如果没用过,一开始会用点懵逼,啊!那么多sdk我怎么用啊?我拿头玩?所以,很多时候就没管它,其实,他是一个非常牛逼的东西哦!下面就大致说一下这个sdk的作用。

J-flash

这个是j-link其中的一种,一般可以看到它有啥j-flash-spi、j-flash反正都是一个目的就是下载代码。我们这里使用j-flash举例子,打开之后,就会显示一个操作界面,这个界面可以选择打开下载文件,可以选择烧录的mcu型号和初始地址,所以,这样还可以依靠bootloader跳转到其他地方执行我下载的这个函数。选择swd下载,连接,清空flash,任何下载即可。

J-mem

看名字大概就知道是干嘛得了吧,没错就是修改和查看内存的。J-Mem 是一个 GUI 应用程序,用于在目标运行时显示和修改目标系统的 RAM 和 SFR(特殊功能寄存器)。打开之后也是一个类似于j-flash的页面,可以选择读取特定地址的内容,查看和保持甚至是修改内容,这个还是不错的,可以用来后续的一些序列号的烧录呢,设置周期性刷新显示的内存内容。逆向的话这应该有可能吧。

看名字又知道是什么意思了,命令行操作,这个就有点像debug操作了,可以通过命令来进行pc指针和运行过程的跳转,hold内核、单步、全速、设置断点、查看内核和外设寄存器、读取flash代码等等,方便大家拥有最高的权限查看在运行中的MCU情况,查找非IDE仿真情况下,MCU运行异常的原因。

Jlink commander使用方法(附指令大全)-CSDN博客

可以参考这个博客哦。

GNU 项目调试器 (GDB) 是一个免费提供的开源调试器,根据 GPL 条款分发。 它可以在命令行模式下使用,这个原本是linux环境下的debug调试器,现在用了这个gdb,就可以调用和操作了,但是这个东西真的不如keil自身的debug,不够如果使用的其他ide装备了GBD,那没办法了,只能靠这个使用了,毕竟keil界面确实老了。

J-Scope

这个就是一个图形显示,可视化可在目标运行时实时分析和可视化微控制器上的数据。就是说,项目通过特定的函数来实现发送任何这个应用接受之后就会显示在UI上面,可以就是一个示波器的作用。具体怎么使用呢?找到Scope的安装位置,找到sample中就可以看到很多没有解压的包了,还包括下面的rtt的包,把它解压了,把c和h文件复制到项目中。进行一些配置和处理,在项目中使用这些c文件中的api就可以进行向这个应用发送波形啦。其实本质就是调用api,配置好对应的宏,J-Scope进行捕捉显示罢了。

工具 | 教你使用Jlink+JScope显示波形_j-scopev7.66a怎么打印正弦波-CSDN博客

可以参考这个博客哦。

RTT全称是Real Time Transmit(实时传输)是Segger公司推出的调试手段之一。它是一种用于嵌入式中与用户进行交互的技术。这个就是一个互通手段而已,因为要知道swd是串行调试接口,本来就是可以双向通信的,所以还是和上面一样的操作,进行一些ch添加到项目,配置config属性,调用api进行读取和发送就实现了。其实直接使用打印也是一个好办法呢!不过这个方法省了一个串口还是不错的哦。

详解J-Link RTT打印 - 知乎 (zhihu.com)

可以参考这个博客哦。

结语

下面这个是j-link官方文档的地址,有什么疑惑或者歧义,我i们都应该以官方文档为标准,以实际开发中的现象为依据。其实功能不止那么点,只是说了一下常用的,更多功能可以到官网去查看使用哦。官方有api调用实例,可以参考分析哦。

UM08001 J-Link / J-Trace User Guide - SEGGER Wiki

这个是一个很好用的工具,但是这个有和没有都不会对项目开发有什么致命影响,就是开发效率的作用,一个好的工程师,肯定会借助多个工具。正所谓,君子生非异也,善假于物也。

起因

​ 我最近确实在备战软考,其中发现许多的编程思路。本来就打算随便学学应付考试的,但是今天,学习rust的时候,学到一个面向函数编程,这让我一时间有了兴趣,仔细想想,如果可以系统级别学习一些编程思路,确实是可以增加我的眼见和对事物的理解,也方便我以为开创和使用更好的思路去编程,增加效率,所以还是写一篇来系统总结一下吧。

面向过程编程

面向过程是一种以事件为中心的编程思想,编程的时候把解决问题的步骤分析出来,然后用函数把这些步骤实现,在一步一步的具体步骤中再按顺序调用函数。我们都是有一个目标,然后不断设计多个函数,然后把一个一个过程组装起来,这就是面向过程编程。面向过程编程强调将程序功能分解成一系列可重用和有序执行的过程或函数。这种范式是从更早的结构化编程演变而来,侧重于明确的步骤和过程的顺序。

特点:
1.函数和过程:程序被分解成一组函数或过程,每个过程完成特定的任务。函数可以调用其他函数,实现复杂逻辑的分层和复用。
2.数据与逻辑分离:数据通常被定义为全局结构,而逻辑则在函数中处理。函数根据需要操作这些数据。
3.控制流:程序的执行流程通过结构化控制语句(如if条件语句、循环语句等)来管理。这种控制结构帮助管理复杂逻辑的执行顺序。
4.模块化:面向过程编程鼓励将程序分解成模块或单元,每个单元具有明确的功能。这些单元可以独立开发和测试,然后组合成完整的程序。
常见就是c语言。shell编程等

面向过程编程的优点:
1.简单性:面向过程的方法由于其直接性和透明性,通常更易于理解和实现,特别是对于简单的问题。
2.性能:过程调用通常比面向对象编程中的方法调用更快,因为它们操作的数据结构较少,且内存使用更直接。
3.复用性:通过函数,可以复用代码,减少重复代码的编写。

面向过程编程的缺点:
1.可扩展性问题:对于大型系统,面向过程编程可能导致代码难以管理和维护。随着系统复杂度的增加,修改程序可能会变得更加困难。
2.数据安全:由于数据通常是全局访问的,这可能会引起数据访问的问题,尤其是在多线程环境中。
3.代码重复:在大型项目中,可能会发现相似的代码块在不同的地方重复,增加了维护的负担。

应用:
面向过程编程适用于相对简单的、过程驱动的任务,如脚本编写、小型项目、或者在性能要求较高且逻辑较为直接的应用中。它在嵌入式系统、操作系统的开发和其他需要高性能、低资源消耗的环境中也非常普遍。

面向对象编程

这个就是模拟现实生活中的种种事物,每一个都可以看做一个对象,而每个对象都有自己的属性和行为,对象与对象之间通过方法来交互。面向对象是一种以“对象”为中心的编程思想,把要解决的问题分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个对象在整个解决问题的步骤中的属性和行为。好处就是可以多次复用,而且封装和使用方便。

特点:
1.封装:将数据(属性)和操作这些数据的代码(方法)捆绑在一起形成对象。这样做的主要优点是隐藏内部细节,只对
外提供有限的接口进行交互。
2.继承:允许新创建的对象继承现有对象的属性和方法。这促进了代码的重用,并可以建立一个层次化的对象模型。
3.多态性:允许不同的对象对同一消息作出响应。例如,父类有一个方法,通过不同的子类可以有不同的实现,但调用接
口(方法名)保持一致。
4.抽象:允许程序员使用类的实例创建具体的对象,并且使用接口定义非具体的操作。这样可以操作具有某些共同特征的对
象,而不需要考虑对象的具体类型。

优点:
1.提高软件开发效率:通过继承和重用已有的代码,可以减少重复代码的编写。
2.提高软件可维护性:封装使得代码更容易被理解,也更容易被修改,不会影响到无关的系统部分。
3.增强软件的灵活性:多态性允许系统更容易地扩展,可以在不改变现有系统的情况下引入新的对象类型。

缺点:
1.性能问题:面向对象系统可能会因为抽象和动态特性而牺牲一些性能。
2.过度设计:错误的或过度的使用面向对象设计可能导致系统复杂度过高,难以理解和维护。
3.学习曲线:对于初学者来说,理解所有OOP概念及其正确应用可能相对困难。

应用:面向对象编程由于其模块化和直观性,成为了现代软件工程中的一个基石。尽管有一些挑战和限制,但它在处理复杂
系统设计和团队协作方面的优势使其成为企业和大型项目的首选编程范式。

常见就是cpp python java rust那些的编程思路,其实也可以使用c语言的结构体里面使用函数指针,也可以实现面向对
象的存在,看linux的驱动开发就知道了,很多都是有操作集。

函数式编程

函数式编程(Functional Programming, FP)是一种编程范式,它将计算视为数学函数的评估,并避免使用程序状态以及可变对象。与面向对象编程侧重于对象(数据及其相关行为)不同,函数式编程强调的是无副作用的函数应用。

特点:
1.不可变性:在函数式编程中,变量一旦被赋值后就不会改变。任何修改都会产生新的数据结构而不是改变已有的。
2.函数是一等公民:函数可以作为参数传递,可以作为返回值,也可以被赋值给变量,这意味着函数的使用方式与其他数据类型无异。
3.纯函数:纯函数的输出只依赖于输入,并且不产生外部可观察的副作用(如修改全局变量或进行输入/输出操作)。
4.递归:由于函数式编程通常不使用循环(for 或 while),递归是实现循环逻辑的一种重要手段。
5.高阶函数:这些是接受其他函数作为参数或将函数作为结果返回的函数。

应用:
函数式编程语言通常提供一系列便利的功能来支持这些概念,如高阶函数、匿名函数(lambda 表达式)、惰性计算和模式匹配等。

许多现代编程语言,如 JavaScript、Python、Ruby、rust

面向数据结构

Jackson Structured Programming(JSP),这是一种系统的软件开发方法,主要用于结构化编程。Jackson Structured Programming由Michael A. Jackson在1970年代开发,用于设计高质量的结构化程序。JSP特别强调数据结构在软件设计中的重要性,并提供了一套方案来分析数据和根据这些数据的结构来设计程序。

特点:
1.数据驱动:在JSP中,设计过程从对数据结构的分析开始。这包括数据的组织、时序和处理方式。数据结构的理解直接影响程序结构的设计。
2.结构图:使用结构图(也称为Jackson图)来可视化数据和程序结构。这些图帮助设计者清晰地理解和规划程序的逻辑流程。
3.模块化设计:JSP鼓励将大的程序分解为小的、可管理的模块。每个模块都处理数据结构的一部分,并可以被单独开发和测试。
4.同步与变换:JSP中重要的概念是程序结构应该映射(或同步)到数据结构。如果数据结构发生变化,相应的程序结构也需要调整。

应用:广泛应用于数据密集型的应用领域,如金融服务、电信和企业数据处理等。
它特别适用于处理复杂的数据流和大规模数据集。
JSP是一种比较传统的方法,随着面向对象和其他现代编程范式的兴起,它的使用频率有所下降。
然而,对于一些特定的应用场景,特别是那些依赖于复杂数据处理的领域,JSP依然是一个有效的工具。

原型化编程

原型化编程(Prototype-based programming)是一种编程范式,它不依赖于类的概念,而是通过克隆现有的对象来创建新对象。这种范式是面向对象编程的一种变体,它消除了类和实例之间的区别,取而代之的是原型对象的概念。

特点:
1.无类(Classless):在原型编程中,没有类的概念。所有对象都是通过复制现有对象(称为原型)来创建的。
2.对象的克隆:新对象是通过复制一个原型对象生成的。这个过程可以包括对原型对象的属性和方法的复制。
3.动态性:对象可以在运行时动态地添加或修改属性和方法。这种灵活性是原型编程的一大优势,允许动态调整系统的行为。
4.直接的继承机制:一个对象可以作为其他对象的原型,允许属性和方法的直接继承。这与基于类的编程中通过类继承来实现的继承机制不同。

应用:
原型编程常用于那些需要高度灵活性和动态性的应用中,例如快速原型开发、动态环境(如Web开发)、游戏开发等领域。由于其在对象创建和继承方面的灵活性,它可以用来快速试验和迭代开发新的功能和对象模型。原型化编程提供了一种与基于类的编程截然不同的思考和实现面向对象编程的方法,通过实际操作和应用可以进一步理解和掌握它的概念和技术细节。

JavaScript:是最著名的支持原型编程的语言。在JavaScript中,几乎所有的对象都是从其他对象克隆得来的,并且可以随时修改对象的结构。
Self:是原型编程的先驱之一,它完全基于原型的概念,放弃了传统的类和实例的区分。
Lua:虽然是一种多范式的脚本语言,但它也支持原型继承。
Io:这是一种小巧的脚本语言,同样采用原型编程范式。

泛型编程

泛型编程是一种编程范式,主要在编程语言中用来实现算法和数据结构的通用性。它允许程序员编写代码以操作任意数据类型,从而使得代码更加模块化、可重用,并减少重复。

特点:
1.类型抽象:泛型编程允许开发者定义操作任意类型(Type-agnostic)的函数和数据结构,而不需要在编写时指定具体的数据类型。
2.代码复用:通过使用泛型,可以创建可应用于多种数据类型的单一实现,从而提高代码的复用率。
3.类型安全:泛型提供了一种类型安全的方式来处理不同的数据类型,可以在编译时捕获类型错误,减少运行时错误。
4.性能优化:泛型编程可以在编译时解析类型,这意味着无需运行时的类型检查,可以提高程序的执行效率。

应用:泛型编程非常适用于需要处理多种数据类型但又要保持代码简洁和安全的场景。
1.集合库:如列表、映射、集等数据结构,它们可以存储任何类型的元素。
2.算法库:可以操作各种类型的数据结构,如排序、搜索等。
3.工具和框架:在开发框架和库时使用泛型可以极大地增强其灵活性和可用性。

C++:通过模板机制支持泛型。模板允许在类和函数级别上操作泛型类型,非常灵活但有时可能导致编译时间较长。
Java:在Java中,泛型是通过使用类型参数来实现的,例如 List。Java的泛型在编译时进行类型擦除,以确保向后兼容性。
C#:提供了强类型的泛型支持,不仅在集合类库中广泛使用,还能够用于自定义的数据结构和函数。
Python:泛型在Python中通常是通过鸭子类型(Duck Typing)实现的,不过最近的版本通过类型提示(Type Hints)和模块如typing,提供了更明确的泛型支持。
Rust:提供了非常强大的泛型系统,允许高度的类型安全和性能优化,通过特性(Traits)和生命周期标注进一步增强了其表达能力

事件驱动编程

事件驱动编程是一种编程范式,它强调的是在软件系统中以事件为核心来设计和处理程序流程。在事件驱动的系统中,程序的执行流程主要是由外部事件(如用户操作、系统消息或其他条件触发的事件)来决定。这种模式特别适合于处理异步或非连续交互的应用程序,例如用户界面、网络服务和任何需要响应外部活动的场景。

特点:
1.基于事件的逻辑:程序的主要逻辑围绕事件的发生和处理构建。事件可以是用户的点击、系统的通知或是时间的推移。
2.非阻塞设计:在等待事件的同时,程序可以继续执行其他任务,这是通过非阻塞操作或多线程实现的。
3.回调函数:事件处理通常通过回调函数实现,即一个事件触发时,相应的处理函数被调用。
4.解耦:事件驱动编程通常可以降低程序组件之间的耦合度,因为组件不需要知道其他组件的具体实现,只需关注事件的发布和订阅。
5.可扩展性:事件驱动的架构使得增加新的事件处理器或修改现有逻辑变得相对简单,有助于应对不断变化的需求。

应用:事件驱动编程适用于多种场景,尤其是那些需要快速响应外部事件的应用
1.用户界面:图形用户界面(GUI)应用程序广泛采用事件驱动模式来响应用户交互。
2.网络应用:服务器需要处理大量并发请求,例如HTTP请求,事件驱动模型能够有效地管理这些并发操作。
3.游戏开发:游戏程序需要响应用户的输入和内部事件,如碰撞检测和时间触发器。
4.实时系统:需要快速响应外部信号的系统,如交易系统、实时监控和自动化控制系统。
5.系统和驱动开发:进行注册和注销的的设计思路

JavaScript:在Web开发中,JavaScript是实现客户端事件驱动编程的主要语言,广泛用于处理用户界面事件如点击、滚动、键盘输入等。
Node.js:利用JavaScript的异步特性,Node.js 是一个服务器端平台,它使用事件驱动的非阻塞I/O模型,非常适合于处理大量并发连接。
C#:在.NET框架中,C# 支持事件和委托(Delegates),使得编写响应系统事件或自定义事件的程序变得简洁明了。
Java:使用监听器(Listeners)和事件对象来处理事件,常见于GUI应用程序(如Swing库)和Android开发。
cpp和c也可以利用这种思想,比如注册函数指针等。linux底层的设计和开发等

面向切面编程

面向切面编程(Aspect-Oriented Programming,简称AOP)是一种编程范式,旨在通过将横切关注点(cross-cutting concerns)从业务主体逻辑中分离出来,增强模块化。这种方法特别适用于处理那些与主要业务逻辑正交但在多处应用中重复出现的问题,如日志记录、事务管理、权限检查、异常处理等。

特点:
1.切面(Aspect):一个模块化的组件,封装了一个跨多个点的关注点(横切关注点)。
2.连接点(Join point):程序执行过程中的某个特定点,比如方法的调用或特定的字段被访问。
3.通知(Advice):在连接点上执行的动作。通知定义了在目标方法执行之前、之后或周围执行的代码。
4.切入点(Pointcut):匹配一个或多个连接点的表达式。它告诉AOP框架在哪里应用通知(即在哪些连接点执行某个通知)。
5.目标对象(Target Object):被一个或多个切面所通知的对象。
6.织入(Weaving):是将切面与其他应用类型或对象链接起来,可以在编译时、加载时或运行时进行。12.3.4.5.6..

应用:面向切面编程适用于处理各种跨应用程序多个部分的问题
1.日志记录:自动记录方法的调用,这在调试和监控中非常有用。
2.事务管理:在数据库操作前后自动开始和提交事务。
3.安全性:在方法执行前检查权限,确保只有具有适当权限的用户才能执行操作。
4.异常处理:为方法提供统一的异常处理逻辑,避免代码重复。
5.性能监控:自动记录方法的执行时间,用于性能分析。

AspectJ:是Java语言中最知名的AOP实现,提供了语言扩展来支持AOP。
Spring AOP:是Spring框架的一部分,提供了AOP支持,但它不像AspectJ那样强大,主要支持基于代理的运行时织入。

元编程

元编程是一种编程技术,它涉及编写可以操纵、生成或变形代码的程序。简单来说,元编程是“编写代码的代码”。这种方法允许开发者编写更灵活、更智能的程序,通过动态地创建或修改代码,或者在编译时或运行时改变程序的行为。

特点:
1.代码生成:编写能够生成其他代码的程序。这种方式可以用于自动化重复的编程任务,提高开发效率和减少错误。
2.反射:程序在运行时访问、检测和修改其自身结构的能力,如类、方法和属性等。这使得程序可以根据自身的状态或行为动态地做出决策。
3.宏(Macro):在编译时扩展的代码片段。宏可以在代码编译之前进行代码的插入或修改,使得可以在编译器层面改变代码的行为。
4.模板元编程:尤其在C++中,利用模板来生成编译时计算的代码。模板元编程可以用于创建编译时决定的高效代码。

优势:
1.灵活性:程序可以根据运行时的数据和条件动态地改变其行为。
2.代码重用和抽象:通过生成代码,可以避免编写重复的代码,提高抽象层级。
3.性能优化:在编译时计算复杂的表达式或决策,运行时性能得到提升。

挑战:
1.可读性和维护性:高度的动态性可能使代码难以理解和维护。
2.复杂性:错误可能难以调试,尤其是当代码在编译时或运行时生成和修改时。
3.性能开销:运行时的元编程可能引入性能开销,尤其是在使用大量反射或动态代码生成时。
元编程是一种强大的工具,适用于需要高度抽象和灵活性的场景,但它也需要谨慎使用,以避免过度复杂化代码和降低程序的可维护性。

C++:提供了强大的模板功能,允许进行高度复杂的编译时计算和类型操作。
Python:通过装饰器、元类(metaclass)和其他反射机制,支持运行时的元编程。
Lisp:其宏系统允许开发者写出可以在编译时展开和转换为其他Lisp代码的程序,非常强大和灵活。
Ruby:提供了开放类和元编程能力,允许在运行时动态地添加或改变对象的方法和属性。
JavaScript:通过原型链和高阶函数,允许在运行时修改和扩展对象和类的行为。

面向响应编程

面向响应编程(Reactive Programming)是一种编程范式,关注于异步数据流和变化的传播。这种编程模式允许程序在数据变化时自动传递这些变化,从而响应地更新相关的处理逻辑。面向响应编程特别适用于需要处理实时数据、高并发请求、大规模分布式系统以及响应用户界面的变化。

特点:
1.数据流和变化传播:在响应式编程中,数据流被视为一系列随时间推移的事件。这些数据流可以被创建、组合、转换或消费。
2.异步和非阻塞:响应式编程鼓励使用异步和非阻塞的操作,这有助于提高应用的可伸缩性和响应性。
3.观察者模式:响应式编程经常使用观察者模式,其中数据流的订阅者会在观察到数据变化时自动接收通知并做出响应。
4.函数式编程技术:响应式编程常常与函数式编程技术结合使用,利用函数式的无副作用和数据不可变性原则来处理数据流。

优点:
1.提高性能:通过非阻塞操作和异步处理,可以更高效地利用系统资源,特别是在IO密集型操作中。
2.易于处理复杂的动态系统:响应式编程可以简化动态和事件驱动系统的处理,如用户界面响应、实时数据处理等。
3.提高错误处理能力:响应式编程支持在数据流中进行错误处理,可以集中控制错误并进行适当的反应。

挑战:
1.学习曲线:响应式编程引入了许多新的概念和抽象,对于新手来说可能较难掌握。
2.调试困难:由于异步和非阻塞的特性,调试响应式编程中的问题可能比传统模式更复杂。
3.资源管理:管理异步操作和数据流需要精心设计,以避免资源泄露和性能问题。

应用场景:
1.用户界面开发:如JavaScript的Web应用,响应式编程可以用于管理DOM事件和状态变化。
2.实时数据处理:如金融市场的实时报价系统,响应式编程可以用于高效处理和传输大量动态数据。
3.网络应用:例如在Spring WebFlux中,使用响应式编程来处理和优化Web请求的处理。

常用的响应式编程工具和库:
RxJava/RxJS/Rx.NET:一系列的库,支持多种编程语言,用于实现响应式编程。
Reactor:Java的响应式编程库,常用于Spring框架。
Akka:用于构建并行、分布式和容错系统的工具集,支持Scala和Java语言

并发编程

并发编程是指在同一时间内处理多个任务的编程方法。在现代计算环境中,这通常涉及到同时执行的线程或进程,以提高程序的性能和响应性。并发编程对于充分利用多核处理器以及处理多任务操作如网络请求、数据库操作和用户界面管理等非常重要。

特点:
1.线程和进程:
进程:操作系统分配资源的基本单位,拥有独立的内存空间。
线程:操作系统调度执行的基本单位,多个线程可以共享同一个进程的资源。线程比进程更轻量,切换成本更低。
2.任务并行性:同时执行多个任务,每个任务可以是一段代码的执行流。
3.数据并行性:将数据分割,同时在多个核心或处理器上进行处理。
4.同步与异步:同步操作需要等待任务完成才能继续,而异步操作可以在等待任务完成的同时继续执行其他任务。
5.死锁、活锁和饥饿:
死锁:两个或更多的执行线程互相等待对方持有的资源,导致无法继续执行。
活锁:线程不断重试一个总是失败的操作,没有停滞,但也无法前进。
饥饿:一个或多个线程无法获得必需的资源,因而无法执行。
6.线程安全:代码在多线程环境下能够正确执行,不会因为线程调度或时间顺序的不同而产生错误。

并发编程的技术和工具:
1.锁(例如互斥锁、读写锁):用于控制对共享资源的访问,保证一次只有一个线程可以使用资源。
2.信号量:限制对资源访问的线程数量,用于实现资源的公平分配。
3.原子操作:不可被线程调度机制中断的操作,用以保证其完整性,常用于更新共享状态。
4.线程局部存储:为每个线程提供独立的变量副本,以避免共享。
5.并发集合:如Java中的ConcurrentHashMap,提供线程安全的数据结构。
6.协程:轻量级的线程,如在Python、Go和Kotlin中实现,提供更高效的任务调度。

应用场景:
1.Web服务器:同时处理多个网络请求。
2.数据库系统:处理多个查询和事务。
3.实时系统:如游戏服务器或交易系统,需要快速响应外部事件。
4.科学计算和数据分析:处理大规模数据集,利用多核性能优化计算。

常用的并发编程语言:
Java:提供了广泛的并发编程工具,包括java.util.concurrent库。
C++:支持多线程库,如C++11以后标准中的线程支持。
Python:通过线程和asyncio库支持并发,虽然受全局解释器锁(GIL)影响。
Go:内建支持并发,通过Goroutines和Channels实现。
Rust:提供数据竞争安全保证,支持无锁并发。

网络编程

网络编程是计算机编程的一个分支,它涉及编写可以在网络中进行通信的软件。这种通信可以在本地网络(如局域网)或跨越广阔的网络(如互联网)。网络编程使得不同计算机上的程序能够互相发送和接收数据,从而执行分布式计算和信息共享。

特点:
1.套接字(Sockets):
套接字是网络通信的基本构建块,它提供了程序间的通信端点。套接字可以是面向连接的(通常是TCP套接字),确保数据正确、完整地传 输;也可以是无连接的(通常是UDP套接字),传输速度快,但不保证数据的完整性或顺序。
协议:
2.TCP/IP(传输控制协议/互联网协议)是最常用的网络通信协议。TCP负责确保数据包的正确顺序和完整性,而IP处理数据包的路由。
HTTP(超文本传输协议)是用于从Web服务器传输网页的协议。
FTP(文件传输协议)、SMTP(简单邮件传输协议)等其他协议支持文件传输和电子邮件发送等功能。
3.IP地址和端口号:
IP地址用于标识网络中的设备,而端口号用于标识设备上的特定应用程序。在网络编程中,一个套接字通过IP地址和端口号的组合来标识通 信的端点。
4.客户端-服务器模型:
在这个模型中,服务器程序在网络中的某台机器上运行,等待和处理来自客户端的请求。客户端程序生成请求发送到服务器,然后接收服务器的响应。
5.非阻塞和异步编程:
在处理网络请求时,非阻塞和异步编程技术可以帮助程序继续执行其他任务,而不是等待网络操作完成,从而提高应用程序的性能和响应性。

常用的网络编程工具和库:
Berkeley套接字(BSD Sockets):几乎所有操作系统都支持的一种编程接口,用于访问底层网络服务。
Java网络编程:Java提供了java.net包,用于实现包括套接字通信在内的网络应用。
Python的socket模块:Python标准库中的socket模块简化了网络通信的实现。
.NET Framework:C#和其他.NET语言使用System.Net和System.Net.Sockets命名空间来进行网络编程。
Node.js:使用JavaScript进行网络编程,特别是异步网络应用。

应用场景:
Web应用开发:服务器和客户端(浏览器)之间的交互。
远程服务:如远程数据库访问、云服务和在线游戏服务器。
物联网(IoT):设备通过网络相互通信,进行数据收集和远程控制。
分布式计算:通过网络分布式处理大规模数据处理任务。

结语

​ 选择不同侧重方案是有利于整体的维护和开发的,世界上不是只有面向过程和面向对象,这些都只是一些好的编程思路和编程规范,没人强制你这样编程,你甚至写汇编都没人说你。但是一个好的思路,一个正确的切入点带来的收益是巨大的,它带来的思想和理解,可以帮助你克服更大的困难。比如说侧重于多任务,那么就是使用rtos或者并发编程的那些思路去设计和开发。因为此时底层那些软件都是已经写好的了,此时想要解决的是多个任务的运行设计,所以,此时要转换编程思路才能事半功倍哦。

上下文

首先就要知道,什么是上下文(Context)。一直非常疑惑,到底什么叫上下文。也没太好意思问人,感觉就是很简单的概念。这个观点,也是我去年使用ai的时候,才知道上下文,但是,我理解为上一句话和下一句话,全部组合起来就是上下文,当时,我就是这样去询问ai的。

百度百科的介绍就是:技术系指将一长串的文字或内容,从其中分析出该个段落的摘要以及大意,甚至更进一步,将整篇文章的文意整理出来。此项技术可以应用在解读影片、音讯等档案,使得搜索引擎能够搜寻到文字以外的物件,方便使用者省去大量时间观看影片、聆听音讯,同时也可以帮助使用者提前了解影片与音讯的内容。

上下文简单说来就是一个环境,相对于进程而言,就是进程执行时的环境。
具体来说就是各个变量和数据,包括所有的寄存器变量、进程打开的文件、内存信息等。

进程上下文
可以说:
当一个进程在执行时,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容被称为该进程的上下文。

也可以说:
所谓的“进程上下文”,可以看作是用户进程传递给内核的这些参数以及内核要保存的那一整套的变量和寄存器值和当时的环境等。

进程切换:
当内核需要切换到另一个进程时,它需要保存当前进程的 所有状态,
即保存当前进程的上下文,以便在再次执行该进程时,能够必得到切换时的状态执行下去。

上下文切换

其实,我们可以把上下文当成一个环境来理解,在RTOS中,每个任务都是属于自己的进程堆栈空间,此时的切换就是可以看成是不同任务之间的切换。这时候上下文环境就是每个任务专属的堆栈空间。

进程上下文的三个部分:用户级上下文、寄存器上下文以及系统级上下文
一个进程的上下文可以分为三个部分:用户级上下文、寄存器上下文以及系统级上下文。

  • 用户级上下文: 正文、数据、用户堆栈以及共享存储区;
  • 寄存器上下文: 通用寄存器、程序寄存器(IP)、处理器状态寄存器(EFLAGS)、栈指针(ESP);
  • 系统级上下文: 进程控制块task_struct、内存管理信息(mm_struct、vm_area_struct、pgd、pte)、内核栈。

当发生进程调度时,进行进程切换就是上下文切换(context switch)。
操作系统必须对上面提到的全部信息进行切换,新调度的进程才能运行。

中断上下文

硬件通过触发信号,导致内核调用中断处理程序,进入内核空间。
这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。

特点:
中断上下文也称原子上下文,该上下文中执行的代码不可阻塞

中断上下文切换:
在发生中断时,内核就在被中断进程的上下文中,在内核态下执行中断服务例程。
但同时会保留所有需要用到的资源,以便中继服务结束时能恢复被中断进程 的执行。

中断上下文代码中注意事项
1.运行于进程上下文的内核代码是可抢占的,但中断上下文则会一直运行至结束,不会被抢占。
所以中断处理程序代码要受到一些限制,在中断代码中不能出现实现下面功能的代码:

2.不能-睡眠或者放弃CPU。
因为内核在进入中断之前会关闭进程调度,一旦睡眠或者放弃CPU,这时内核无法调度别的进程来执行,系统就会死掉。牢记:中断服务子程序一定不能睡眠(或者阻塞)。

3.不能-尝试获得信号量
如果获得不到信号量,代码就会睡眠,导致如上中的结果。

4.不能-执行耗时的任务
中断处理应该尽可能快,因为如果一个处理程序是IRQF_DISABLED类型,他执行的时候会禁止所有本地中断线,而内核要响应大量服务和请求,中断上下文占用CPU时间太长会严重影响系统功能。中断处理程序的任务尽可能放在中断下半部执行。

5.不能-访问用户空间的虚拟地址
因为中断运行在内核空间。

内核层面看待上下文切换

首先就是切换和保存,这个肯定要调用内核api,然后就是切换虚拟内存,如果是mcu就是直接内存切换,也不用考虑用户和特权之间的虚拟内存不同的问题。给任务一个状态,通知内核去调度它,这有点就绪的意思。

其实没啥东西,就是走一个内核同意,虚拟内存切换,独立堆栈空间切换,用户和特权,cpu使用权等东西

总结

上下文切换,别听着那些工程师吹牛有多厉害,他们自己说不定就解释不清楚,但是,你把它当成一个环境,这样就可以解释很多东西啦

起因

其实,大学的时候,我是一个纯纯的废物,我天天脑子都是玩乐打游戏,自我麻痹,自我欺骗,说没事的,船到桥头自然直。同时,我内心还有一种不甘堕落的心态,现在想想,当初我还真是又当又立,一边娱乐一边反思,乐死了。所以啊,大学里面的好多东西,我都没学,现在就得从头开始了。即便我如此,我脑子里面还是有一点点关于微机的知识,比如51单片机,他的cpu工作地址从0开始,然后0x03 然后 0x10 然后 0x13。但是,还学了一点点汇编。可是,最近,我在思考,为什么我的代码可以被cpu识别到呢?突然我想来了微机原理了!微机不就是计算机吗?不就是cpu?所以,有这么几天,都是思考cpu是怎么工作的。

CPU

CPU是什么?中央处理器。千万不要用人类思想看待cpu,cpu就是一个取指令,取数据,然后控制和运算的一个处理器。啊?不是哥们[虾],这不是废话?这句话,就是揭开cpu神秘面纱的最重要的一句话,那么我回到上面那个问题,cpu是怎么工作的我们的C语言代码三大语句结构,顺序循环判断。其实,不论如何,我们代码是有方向的,他的本质还是从上往下顺序写。巧了,cpu也是从上往下顺序读取地址操作,我们可以用上面的51的cpu来分析(所有的cpu大致逻辑都是相似的),我们是不是只要把代码写到cpu执行的地址上面,是不是就可以实现cpu操作这个地址呢?所以,我们就知道为什么嵌入式要那么多的交叉编译了!这个交叉编译和中间件就是为了把代码生成目标文件而且还把代码映射到正确的地址上面。只要,我们的代码和数据放在cpu提前约定好的地址上,那么,是不是就实现了代码运行了?

这里只是用了pc指针顺序执行的例子,还没用到手动控制pc指针跳转的功能。假设此时我们跳转成功,那么我们得提前在这个目的地址写好代码和准备数据,这个代码也是根据cpu的顺序操作来映射过去的。没东西的话,cpu就直接傻逼了,直接懵了,就会段错误啊,崩溃啥的,根本没东西,让它怎么执行。

还有取数据,也是从特定的位置拿去数据,我们知道内存分为代码段、data、bbs、堆栈所以取数据也是从提前制定的数据位置进行调用哦,同理的。

引导程序

引导程序,一般有bios,uboot,bootloader。那么引导文件是为了干嘛的?还是根据上面的51分析,我们可以看到,它是傻傻的根据提前制定好的地址开始运行,那肯定是不合适的啊?代码有大小,系统有要求,系统还得升级,系统还得跳转到其他模块工作,系统还得要兼容,系统还得要移植。所以,如果还是根据原本的傻乎乎的工作,肯定是满足不了,所寻求的灵活性,还不能理解,为什么要引导程序吗?一个pc,难道只有windows吗、linux、ubuntu、kail、MAC、IOS都是可以作为我们的系统。我们设计开发板的,是硬件有,但是软件从一开始就不是固定的,引导程序就是为了帮助系统可以使用其他系统或者升级系统。引导程序开始之后就会接管cpu,然后看本地是否存在系统,存在就把cpu的指向指向目标区域。这就实现了系统开机。
mcu不是一定要bootloader,但是越高级的设备,它就越需要引导来帮助切换系统,升级系统等诸多操作。这个的本质是利用cpu的控制总线,来控制程序跳转,控制cpu运行方向。

系统

系统,若干部分相互联系、相互作用,形成的具有某些功能的整体。在嵌入式中,就是资源管理和分配。系统还分等级,这里可以看之前的系统选择那些博客,里面就有我对于系统的看法和我个人的理解。
windows、安卓、ios、mac、linux、鸿蒙、澎湃….太多了,那么系统是什么?系统为什么那么厉害?其实,不要神话系统了,不要神话任何技术,去面对和学习它,所谓的系统就是程序。它也是通过把系统放在特定的位置上,等待cpu或者引导程序来调用它。我们可以学习一个freertos,这是一个开源的,系统,所以,我们就可以仔细拜读一下。它有内存管理,系统任何调度,系统延时,系统中断,系统资源管理。它就是一个非常简易的系统了,但是功能可不少,因此,系统就是把可应用程序和系统程序一起编译形成的程序,这个程序变成目标程序就可以被cpu调用了。
千万不要用人类思想看待cpu。cpu是没有脑子的,它就是根据人类的要求,来对数据进行特殊处理。此时,所谓的系统和程序就是一段没有价值的二进制。那不可能啊?我那么大的系统,你跟我说,它没价值?这里,我们要明白,价值不是cpu不是电脑赋予的,是人类赋予的。这里举个例子,我有1斤苹果,1斤西瓜,要得到它们的价格,我就把1斤苹果乘5交给cpu处理,一斤西瓜x2交给cpu处理,所以,此时cpu处理完毕之后就是得到了7。这个7此时有意义吗?没有意义的,因为人类还没给他单位,所以,此时系统再通过特定的程序和指令,让cpu来调度实现对io的控制,此时进行对外输出输入功能了,通过特定的输入输出操作,就可以给这个7赋予一个钱的概念。这个时候,系统才有意义。

多核

​ 看到这里,都知道,我们肯定不能只玩那么简单的啊?那肯定得上多核啊!!!!我们都知道多核,有同构多核和异构多核!这两个多核就会带来非常大得麻烦,这里抛出来问题咯。系统如何实现中断?系统如何实现资源分配?系统如何分配cpu?多核的时候,怎么保证系统不崩溃?
第一个问题,多核中断管理。

方案1,就是各自处理各自的,然后通过中间的共享内存来进行通信告诉对方
方案2,就是各自处理各自的,但是,有主核心和从核心,从核心执行完毕自己的中断必须告诉主核心中断执行完毕
方案3,每一个核心触发了中断都要告诉主控,通过主控中的系统来进行调度,看那个cpu是空闲的
方案4,可以把中断总线连接到一起,这也的话,就可以实现一方控制全场,难度和设计非常复杂

第二个问题,资源分配。
如果是同构多核,那么就需要做很多的pv,互斥,竞争,抢占的处理了
如果是异构多核,那么就需要主控来进行处理和管理了,因为此时每个核心都是各自独立的内存,只有中间有缓冲区
内存和总线可以用来通信。

第三个问题,如何保证不崩溃?
这个问题,是不是有点被问懵了?
如果是异构,这个其实不好遇到的。但是同构就不一样了。我们上面做了我们对于系统的看法和定义,它就是一段代码,对还是不对?代码,就有三大结构,顺序循环判断。系统如果多核心处理?那我代码有顺序、时间、逻辑、过程、通信、顺序、功能内聚。数据、标记、控制、外部、公共、内容耦合。还有互斥怎么办?有先后怎么办?需要同步怎么办?那我的系统还没开始就崩溃了,你这个核心处理一下,那个核心处理一下,顺序都乱没了,玩个屁的系统啊。这怎么办?是不是有点懵了。其实,我们可以回到freertos的实例中看一看。首先就是内存分配,系统初始化,线程创建,然后就是系统调度了!聪明的人已经知道了是怎么解决得了!没错就是系统调度!只有开启系统调度之后,才会可能使用多核操作,再次之前都是系统初始化!我们就有方案了。

方案1,它们是需要同步和顺序的,如果真的需要多核心来初始化的话,那么互斥和顺序的结果,就要增加限制,竞
争管理,同步等诸多操作
方案2,在单一核心上面进行系统初始化完毕,线程创建完毕(这个就是调用api,告诉多核这个是可以多任务运行的),
一切init都ok了,再开启系统调度,运行多个任务多个cpu进行操作,这个方案也是最简单,最好实现的。

如果你看到这里了,你不会以为我的思考就此停止了吧?嘻嘻,还有呢!

多系统

​ 自始至终了,没人定义,只能有一个系统吧?我们知道系统要进行资源分配和管理,那么内存冲突怎么办?这个系统凭什么让另一个系统调度?我们上面考虑都是单系统多核,但是没考虑多系统多核心的情况。此时cpu真的要被玩坏了。
​ 比如使用freertos+fatfs这就是一个实时系统+文件管理系统,它们可以完美的共存,这不会相互冲突?不会相互
打架?两者都是管内存的啊。
​ 这里,我提出一个概念,就是主系统和从系统概念,每个主系统都是需要管理整个项目的所有内存的(包括后续挂载的),而从系统不能设计太底层的内存管理和资源调度,资源调度和底层内存管理只有主系统才有资格管理。那么fatfs怎么办?很简单啊,主系统调用fatfs的api这样就实现了再fatfs中资源统计,但似乎fatfs的初始化中就被考虑到了是主系统来控制内存,fatfs,不过知识提醒主系统,这一段空间中数据是一个文件哦,不是单纯的数据,你可以拿出来用哦!所以,此时fatfs是没有控制内存的,它不过是记录了这段内存是文件罢了。影响不了主系统对于内存的支配。
​ 所以,我们再进行多系统的设计的时候,就要注意,主从之分,主只有一个,从可以多个,但是必须要根据从的api和规则进行调用。从系统之间也要注意,防止冲突了,比如不要来两个文件管理系统,这样很容易混乱的。

总结

cpu的运行其实本质就越是受它的架构影响,有x86的架构,有arm的架构,有ibm的架构,powerpc等,它们的cpu起始位置不同它们的cpu运行方向和逻辑也是不同的,但是还是有一个默认顺序的。此时,我可以通过编程他的pc指针,来告诉它下一步该干什么。注意了,前提是你的引导得在它cpu最初的工作路径上,运行引导才能接管cpu的控制。cpu也是顺序的,cpu也是可以跳转的,它和你的代码是一一对应的,所以,那些复杂的编译和处理就是为了把目标文件映射到正确的路径上,方便cpu和引导来调度。
设计项目的时候,要注意多系统和多核等诸多情况,作为一个合格的嵌入式工程师,完美支配所有内存和过程,这是必修课呢。这只是一个认识和方案思路,不同公司的思路和实现肯定是不一样的。

起因

今天在写软考题目的时候,我无意间才发现,我居然一直在按位进行寻址,但是实际上都是按照字节取地址的。cpu内通用寄存器的长度取决于机器字长。一下子,我就懵了啊?为什么不是按位取地址?为什么是按照字节?

历史原因

早期的计算机科学太夸张了,早期的计算器五花八门,大家都有各自的方法和规格,当时大家都用字进行寻址,我们知道字不是字节,字的长短是受到系统位数的影响,比如32位的字就是4字节,就是比如IBM 370就是4字节寻址,其他也有一些2字节寻址,1字节寻址等,此时内部的指令也会因为这些寻址而变得非常混乱。我个人觉得可能是因为优化了和统一指令,所以,大家一起选择使用了字节。这就是不断探索中总结出来的实践经验吧。

物理结构

其实,我们可以思考一下,如果寻址,总是寻找一个地方的高低电平,这其实是对开发来说,是毁灭的打击,这就像寄存器编程一样非常痛苦,如果是按照字节寻址就可以得到一个字符,这个意义就很大了,可以组成一段话,可以组成有用的数据。所以,就设计成了一个字线上面有8个位的物理结构设计,此时,也可以做到对位操作,也可以寻址,一举两得。

下面就是一个最基础的位的存储单元

下面是一个字线的物理结构

效率

这个字节选择,肯定不是简单你说我们大家都来用字节,我就用字节的,肯定是因为开发效率高,目前c语言最小的数据类型就是char,我们也确实感觉char作为最小用来开发非常简单,如果是一个一个01组合,那不得了,人得疯掉。还有就是执行效率问题,比如我们使用结构体对齐的时候,就知道,这是为了牺牲空间换取时间效率,因为cpu需要提前根据数据类型来进行对应的寄存器调度和处理。如果按照位来操作,那cpu对其和操作就变成非常复杂。其实,还可以举例子呗,就拿flash和eeprom,那我们知道flash都是按照页来进行操作,因为它快啊,不论是刷新还是写入都是快的,因为他是一块一块处理,如果都和eeprom对一个一个位进行操作,不但效率低下,而且内部线路设计也非常复杂。

结语

综上所述,我个人觉得,选择字节作为寻址和编程基础,是经过大家对设计探索和研究所得到最优解。用过keil都知道,都是会选择开启优化2为主的,这也就是处于一个空间和时间相对平衡的过程,不优化和优化3都是一个比较一般的表现。这也是上古时代,前人用时间和实践一步一步走出来的选择,然后再定下来的结构,确实还是很合理的、很可靠的、很好用的。