嵌入式软件高频面试题_嵌入式开发高阶面试

strcpy函数会导致内存溢出。

strcpy拷贝函数不安全,他不做任何的检查措施,也不判断拷贝大小,不判断目的地址内存是否够用。

char *strcpy(char *strDest,const char *strSrc)

strncpy拷贝函数,虽然计算了复制的大小,但是也不安全,没有检查目标的边界。

strncpy(dest, src, sizeof(dest)); 

strncpy_s是安全的

strcmp(str1,str2),是比较函数,若str1=str2,则返回零;若str1<str2,则返回负数;若str1>str2,则返回正数。(比较字符串)

strncat()主要功能是在字符串的结尾追加n个字符。

char * strncat(char *dest, const char *src, size_t n);

strcat()函数主要用来将两个char类型连接。例如:

char d[20]="Golden";
char s[20]="View";
strcat(d,s);
//打印d
printf("%s",d);

输出 d 为 GoldenView (中间无空格)

延伸:

memcpy拷贝函数,它与strcpy的区别就是memcpy可以拷贝任意类型的数据,strcpy只能拷贝字符串类型。

memcpy 函数用于把资源内存(src所指向的内存区域)拷贝到目标内存(dest所指向的内存区域);有一个size变量控制拷贝的字节数;

函数原型:

void *memcpy(void *dest, void *src, unsigned int count);

5 、static的用法(定义和用途)(必考)

1)用static修饰局部变量:使其变为静态存储方式(静态数据区),那么这个局部变量在函数执行完成之后不会被释放,而是继续保留在内存中。

2)用static修饰全局变量:使其只在本文件内部有效,而其他文件不可连接或引用该变量。

3)用static修饰函数:对函数的连接方式产生影响,使得函数只在本文件内部有效,对其他文件是不可见的(这一点在大工程中很重要很重要,避免很多麻烦,很常见)。这样的函数又叫作静态函数。使用静态函数的好处是,不用担心与其他文件的同名函数产生干扰,另外也是对函数本身的一种保护机制。

6、const的用法(定义和用途)(必考)

const主要用来修饰变量、函数形参和类成员函数:

1)用const修饰常量:定义时就初始化,以后不能更改。

2)用const修饰形参:func(const int a){};该形参在函数里不能改变

3)用const修饰类成员函数:该函数对成员变量只能进行只读操作,就是const类成员函数是不能修改成员变量的数值的。

被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。

参考一个大佬的回答:

我只要一听到被面试者说:“const意味着常数”,我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里完全概括了const的所有用法,因此ESP(译者:Embedded Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.如果你从没有读到那篇文章,只要能说出const意味着"只读"就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。如果应试者能正确回答这个问题,我将问他一个附加的问题:下面的声明都是什么意思?

const int a;
int const a;
const int *a;
int * const a;
int const * a const;

前两个的作用是一样,a是一个常整型数。

第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。

第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。

最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。

7、volatile作用和用法

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量在内存中的值,而不是使用保存在寄存器里的备份(虽然读写寄存器比读写内存快)。

回答不出这个问题的人是不会被雇佣的。这是区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。

以下几种情况都会用到volatile:

1、并行设备的硬件寄存器(如:状态寄存器)

2、一个中断服务子程序中会访问到的非自动变量

3、多线程应用中被几个任务共享的变量

8、const常量和#define的区别(编译阶段、安全性、内存占用等)

用#define max 100 ; 定义的常量是没有类型的(不进行类型安全检查,可能会产生意想不到的错误),所给出的是一个立即数,编译器只是把所定义的常量值与所定义的常量的名字联系起来,define所定义的宏变量在预处理阶段的时候进行替换,在程序中使用到该常量的地方都要进行拷贝替换;

用const int max = 255 ; 定义的常量有类型(编译时会进行类型检查)名字,存放在内存的静态区域中,在编译时确定其值。在程序运行过程中const变量只有一个拷贝,而#define所定义的宏变量却有多个拷贝,所以宏定义在程序运行过程中所消耗的内存要比const变量的大得多

9、变量的作用域(全局变量和局部变量)

全局变量:在所有函数体的外部定义的,程序的所在部分(甚至其它文件中的代码)都可以使用。全局变量不受作用域的影响(也就是说,全局变量的生命期一直到程序的结束)。

局部变量:出现在一个作用域内,它们是局限于一个函数的。局部变量经常被称为自动变量,因为它们在进入作用域时自动生成,离开作用域时自动消失。关键字auto可以显式地说明这个问题,但是局部变量默认为auto,所以没有必要声明为auto。

局部变量可以和全局变量重名,在局部变量作用域范围内,全局变量失效,采用的是局部变量的值。

10、sizeof 与strlen (字符串,数组)

1.如果是数组

#include<stdio.h>
int main()
{
    int a[5]={1,2,3,4,5};
    printf(“sizeof  数组名=%d\n”,sizeof(a));
    printf(“sizeof *数组名=%d\n”,sizeof(*a));
}

运行结果

sizeof  数组名=20
sizeof *数组名=4

2.如果是指针,sizeof只会检测到是指针的类型,指针都是占用4个字节的空间(32位机)。

sizeof是什么?是一个操作符,也是关键字,就不是一个函数,这和strlen()不同,strlen()是一个函数。

那么sizeof的作用是什么?返回一个对象或者类型所占的内存字节数。我们会对sizeof()中的数据或者指针做运算吗?基本不会。例如sizeof(1+2.0),直接检测到其中类型是double,即是sizeof(double) = 8。如果是指针,sizeof只会检测到是指针的类型,指针都是占用4个字节的空间(32位机)。

char *p = "sadasdasd";
sizeof(p):4
sizeof(*p):1//指向一个char类型的

除非使用strlen(),仅对字符串有效,直到’\0’为止了,计数结果不包括\0。

要是非要使用sizeof来得到指向内容的大小,就得使用数组名才行, 如

char a[10];
sizeof(a):10  //检测到a是一个数组的类型。

Image

关于strlen(),它是一个函数,考察的比较简单:

strlen “\n\t\tag\AAtang”

答案:11

11、经典的sizeof(struct)和sizeof(union)内存对齐

内存对齐作用:

1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

结构体struct内存对齐的3大规则:

1.对于结构体的各个成员,第一个成员的偏移量是0,排列在后面的成员其当前偏移量必须是当前成员类型的整数倍;

2.结构体内所有数据成员各自内存对齐后,结构体本身还要进行一次内存对齐,保证整个结构体占用内存大小是结构体内最大数据成员的最小整数倍;

3.如程序中有#pragma pack(n)预编译指令,则所有成员对齐以n字节为准(即偏移量是n的整数倍),不再考虑当前类型以及最大结构体内类型。

#pragma pack(1)
 
struct fun{
  int i;
  double d;
  char c;
};

sizeof(fun) = 13

struct CAT_s
{
    int ld;
    char Color;
    unsigned short Age;
    char *Name;
    void(*Jump)(void);
}Garfield;

1.使用32位编译,int占4, char 占1, unsigned short 占2,char* 占4,函数指针占4个,由于是32位编译是4字节对齐,所以该结构体占16个字节。(说明:按几字节对齐,是根据结构体的最长类型决定的,这里是int是最长的字节,所以按4字节对齐);

2.使用64位编译 ,int占4, char 占1, unsigned short 占2,char* 占8,函数指针占8个,由于是64位编译是8字节对齐(说明:按几字节对齐,是根据结构体的最长类型决定的,这里是函数指针是最长的字节,所以按8字节对齐)所以该结构体占24个字节。

//64位
struct C 
{ 
 double t;   //8   1111 1111
 char b;  //1      1
 int a;   //4      0001111  
 short c;  //2     11000000
};  
 sizeof(C) = 24;  //注意:1 4 2 不能拼在一起

char是1,然后在int之前,地址偏移量得是4的倍数,所以char后面补三个字节,也就是char占了4个字节,然后int四个字节,最后是short,只占两个字节,但是总的偏移量得是double的倍数,也就是8的倍数,所以short后面补六个字节

联合体union内存对齐的2大规则:

1.找到占用字节最多的成员;

2.union的字节数必须是占用字节最多的成员的字节的倍数,而且需要能够容纳其他的成员

//x64
typedef union {
    long i;
    int k[5];
    char c;
}D

要计算union的大小,首先要找到占用字节最多的成员,本例中是long,占用8个字节,int k[5]中都是int类型,仍然是占用4个字节的,然后union的字节数必须是占用字节最多的成员的字节的倍数,而且需要能够容纳其他的成员,为了要容纳k(20个字节),就必须要保证是8的倍数的同时还要大于20个字节,所以是24个字节。

引申:位域(大疆笔试题)

C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”( bit field) 。利用位段能够用较少的位数存储数据。一个位段必须存储在同一存储单元中,不能跨两个单元。如果第一个单元空间不能容纳下一个位段,则该空间不用,而从下一个单元起存放该位段。

1.位段声明和结构体类似

2.位段的成员必须是int、unsigned int、signed int

3.位段的成员名后边有一个冒号和一个数字

typedef struct_data{
   char m:3;
   char n:5;
   short s;
    
   union{
   int a;
   char b;
   };
    
   int h;
}_attribute_((packed)) data_t;

答案12

m和n一起,刚好占用一个字节内存,因为后面是short类型变量,所以在short s之前,应该补一个字节。所以m和n其实是占了两个字节的,然后是short两个个字节,加起来就4个字节,然后联合体占了四个字节,总共8个字节了,最后int h占了四个字节,就是12个字节了

attribute((packed))   取消对齐

GNU C的一大特色就是__attribute__机制。__attribute__可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。

__attribute__书写特征是:__attribute__前后都有两个下划线,并且后面会紧跟一对括弧,括弧里面是相应的__attribute__参数。

跨平台通信时用到。不同平台内存对齐方式不同。如果使用结构体进行平台间的通信,会有问题。例如,发送消息的平台上,结构体为24字节,接受消息的平台上,此结构体为32字节(只是随便举个例子),那么每个变量对应的值就不对了。

不同框架的处理器对齐方式会有不同,这个时候不指定对齐的话,会产生错误结果

12、inline函数

在C语言中,如果一些函数被频繁调用,不断地有函数入栈,即函数栈,会造成栈空间或栈内存的大量消耗。为了解决这个问题,特别的引入了inline修饰符,表示为内联函数。

大多数的机器上,调用函数都要做很多工作:调用前要先保存寄存器,并在返回时恢复,复制实参,程序还必须转向一个新位置执行C++中支持内联函数,其目的是为了提高函数的执行效率,用关键字 inline 放在函数定义(注意是定义而非声明)的前面即可将函数指定为内联函数,内联函数通常就是将它在程序中的每个调用点上“内联地”展开。

内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。

13、内存四区,什么变量分别存储在什么区域,堆上还是栈上。

Image

Image

文字常量区,叫.rodata,不可以改变,改变会导致段错误

int a0=1;
static int a1;
const static a2=0;
extern int a3;

void fun(void)
{
 int a4;
 volatile int a5;
 return;
}

a0 :全局初始化变量;生命周期为整个程序运行期间;作用域为所有文件;存储位置为data段。

a1 :全局静态未初始化变量;生命周期为整个程序运行期间;作用域为当前文件;储存位置为BSS段。

a2 :全局静态变量

a3 :全局初始化变量;其他同a0。

a4 :局部变量;生命周期为fun函数运行期间;作用域为fun函数内部;储存位置为栈。

a5 :局部易变变量;

14、使用32位编译情况下,给出判断所使用机器大小端的方法。

Image

联合体方法判断方法:利用union结构体的从低地址开始存,且同一时间内只有一个成员占有内存的特性。大端储存符合阅读习惯。联合体占用内存是最大的那个,和结构体不一样。

a和c公用同一片内存区域,所以更改c,必然会影响a的数据

#include<stdio.h>

int main(){
  union w
  {
      int a;
      char b;
  }c;
  c.a = 1;
  if(c.b == 1)
   printf("小端存储\n");
  else
   printf("大端存储\n");
 return 0;
}

指针方法

通过将int强制类型转换成char单字节,p指向a的起始字节(低字节)

#include <stdio.h>
int main ()
{
    int a = 1;
    char *p = (char *)&a;
    if(*p == 1)
    {
        printf("小端存储\n");
    }
    else
    {
        printf("大端存储\n");
    }
    return 0;
}

15、用变量a给出下面的定义

a) 一个整型数;
b)一个指向整型数的指针;
c)一个指向指针的指针,它指向的指针是指向一个整型数;
d)一个有10个整型的数组;
e)一个有10个指针的数组,该指针是指向一个整型数;
f)一个指向有10个整型数数组的指针;
g)一个指向函数的指针,该函数有一个整型参数并返回一个整型数;
h)一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数
答案:
a)int a
b)int *a;
c)int **a;
d)int a[10];
e)int *a [10];
f) int a[10], *p=a;
g)int (*a)(int)
h) int( *a[10])(int)

三、网络编程

1 、TCP、UDP的区别

TCP—传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。

UDP—用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。

1)TCP是面向连接的,UDP是面向无连接的

2)UDP程序结构较简单

3)TCP是面向字节流的,UDP是基于数据报的

4)TCP保证数据正确性,UDP可能丢包

5)TCP保证数据顺序到达,UDP不保证

2 、TCP、UDP的优缺点

TCP优点:可靠稳定

TCP的可靠体现在TCP在传输数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完之后,还会断开来连接用来节约系统资源。

TCP缺点:慢,效率低,占用系统资源高,易被攻击

在传递数据之前要先建立连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞机制等都会消耗大量时间,而且要在每台设备上维护所有的传输连接。然而,每个连接都会占用系统的CPU,内存等硬件资源。因为TCP有确认机制、三次握手机制,这些也导致TCP容易被利用,实现DOS、DDOS、CC等攻击。

UDP优点:快,比TCP稍安全

UDP没有TCP拥有的各种机制,是一种无状态的传输协议,所以传输数据非常快,没有TCP的这些机制,被攻击利用的机会就少一些,但是也无法避免被攻击。

UDP缺点:不可靠,不稳定

因为没有TCP的这些机制,UDP在传输数据时,如果网络质量不好,就会很容易丢包,造成数据的缺失。

3 、TCP UDP适用场景

TCP:传输一些对信号完整性,信号质量有要求的信息。

UDP:对网络通讯质量要求不高时,要求网络通讯速度要快的场景。

4、 TCP为什么是可靠连接?

因为tcp传输的数据满足3大条件,不丢失,不重复,按顺序到达。

5、OSI典型网络模型,简单说说有哪些

Image

6、三次握手、四次挥手

三次握手

Image

1、TCP服务器进程先创建传输控制块TCB,时刻准备接受客户进程的连接请求,此时服务器就进入了LISTEN(监听)状态;

2、TCP客户进程也是先创建传输控制块TCB,然后向服务器发出连接请求报文,这是报文首部中的同部位SYN=1,同时选择一个初始序列号 seq=x ,此时,TCP客户端进程进入了 SYN-SENT(同步已发送状态)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。

3、TCP服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己初始化一个序列号 seq=y,此时,TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。

4、TCP客户进程收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,自己的序列号seq=x+1,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。

5、当服务器收到客户端的确认后也进入ESTABLISHED状态,此后双方就可以开始通信了。

四次挥手

Image

1、客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。

2、服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。

3、客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。

4、服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。

5、客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗ *∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。

6、服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

四、常见算法

十种常见排序算法可以分为两大类:

非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序。

线性时间非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。

Image

算法优劣评价术语

稳定性:

稳定:如果 a 原本在 b 前面,而 a = b,排序之后 a 仍然在 b 的前面;

不稳定:如果 a 原本在 b 的前面,而 a = b,排序之后 a 可能会出现在 b 的后面;

排序方式:

内排序:所有排序操作都在内存中完成,占用常数内存,不占用额外内存。

外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行,占用额外内存。

复杂度:

时间复杂度: 一个算法执行所耗费的时间。

空间复杂度: 运行完一个程序所需内存的大小。

Image

Image

至于各种算法的原理以及代码实现,由于太多并且比较复杂,不在本文列出。但推荐两本入门的书:《啊哈!算法》、《大话数据结构》。电子版我会发在交流群里。

排序算法很多,嵌入式要求的不会太多,你会冒泡排序、快速排序、插入排序就可以解决很多问题。难的比如动态规划问题,图的路径问题,嵌入式考的比较少,纯软才会考这些。(大公司和独角兽公司考的会相对难一些)

五、Linux操作系统题目

1、 Linux内核的组成部分

Linux内核主要由五个子系统组成:进程调度,内存管理,虚拟文件系统,网络接口,进程间通信。

Image

2、Linux系统的组成部分

Linux系统一般有4个主要部分:

内核、shell、文件系统和应用程序。

Image

Image

3、用户空间与内核通信方式有哪些?

1)系统调用。用户空间进程通过系统调用进入内核空间,访问指定的内核空间数据;

2)驱动程序。用户空间进程可以使用封装后的系统调用接口访问驱动设备节点,以和运行在内核空间的驱动程序通信;

3)共享内存mmap。在代码中调用接口,实现内核空间与用户空间的地址映射,在实时性要求很高的项目中为首选,省去拷贝数据的时间等资源,但缺点是不好控制;

4)copy_to_user()、copy_from_user(),是在驱动程序中调用接口,实现用户空间与内核空间的数据拷贝操作,应用于实时性要求不高的项目中。

以及:

procfs(/proc)
sysctl (/proc/sys)
sysfs(/sys)
netlink 套接口

4、系统调用与普通函数调用的区别

系统调用:

1.使用INT和IRET指令,内核和应用程序使用的是不同的堆栈,因此存在堆栈的切换,从用户态切换到内核态,从而可以使用特权指令操控设备

2.依赖于内核,不保证移植性

3.在用户空间和内核上下文环境间切换,开销较大

4.是操作系统的一个入口点

普通函数调用:

1.使用CALL和RET指令,调用时没有堆栈切换

2.平台移植性好

3.属于过程调用,调用开销较小

4.一个普通功能函数的调用

5、内核态,用户态的区别

内核态,操作系统在内核态运行——运行操作系统程序

用户态,应用程序只能在用户态运行——运行用户程序

当一个进程在执行用户自己的代码时处于用户运行态(用户态),此时特权级最低,为3级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态。Ring3状态不能访问Ring0的地址空间,包括代码和数据;当一个进程因为系统调用陷入内核代码中执行时处于内核运行态(内核态),此时特权级最高,为0级。执行的内核代码会使用当前进程的内核栈,每个进程都有自己的内核栈。

6、 bootloader、内核 、根文件的关系

启动顺序:bootloader->linux kernel->rootfile->app

Bootloader全名为启动引导程序,是第一段代码,它主要用来初始化处理器及外设,然后调用Linux内核。Linux内核在完成系统的初始化之后需要挂载某个文件系统作为根文件系统(RootFilesystem),然后加载必要的内核模块,启动应用程序。(一个嵌入式Linux系统从软件角度看可以分为四个部分:引导加载程序(Bootloader),Linux内核,文件系统,应用程序。)

7 、Bootloader启动的两个阶段:

Stage1:汇编语言

1)基本的硬件初始化(关闭看门狗和中断,MMU(带操作系统),CACHE。配置系统工作时钟)

2)为加载stage2准备RAM空间

3)拷贝内核映像和文件系统映像到RAM中

4)设置堆栈指针sp

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!

闭看门狗和中断,MMU(带操作系统),CACHE。配置系统工作时钟)

2)为加载stage2准备RAM空间

3)拷贝内核映像和文件系统映像到RAM中

4)设置堆栈指针sp

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-DdxAXfkg-1715715812216)]

[外链图片转存中…(img-V7WH8qLP-1715715812217)]

[外链图片转存中…(img-QWAqutkW-1715715812217)]

[外链图片转存中…(img-UW2jCqyt-1715715812217)]

[外链图片转存中…(img-yh1DmM6e-1715715812218)]

[外链图片转存中…(img-Lqo5TtL9-1715715812218)]

[外链图片转存中…(img-YAYnVgJ6-1715715812219)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!

  • 9
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值