当前位置:   article > 正文

大疆嵌入式软件工程师秋招笔试题B卷-做后有感_大疆嵌入式笔试题难吗

大疆嵌入式笔试题难吗


前言

昨天答完题之后,我只有一句话,我不配大疆!!!我昨天做的是应该最后一批秋招正式批笔试题了,根据昨天的做题情况和自己的一些感受与大家分享一下。


一、题目难度

我感觉昨天那份题说难也没有难得很离谱,但还是有难度的,个人认为有以下几点原因:
1、考察的知识非常全面,涉及到了操作系统、编译原理、汇编、C语言、进程线程、linux命令、数据传输的时间计算、通讯协议、ARM体系与架构等等。

2、除了考的多,考的还非常细,涉及到了很多的细节,比如内存对齐,linux命令执行后的结果,通讯协议的一些细节,内存的一些特点等等;

3、此外就是考的非常有深度,或者说有一定难度,比如:

(1)经典的offsetof宏与container_of宏,这个可谓是指针与结构体结合的高级应用,要是对指针以及地址偏移没有深刻的理解,基本白给。

(2)用户进程和内核进程抢占执行的时机

4、每道题单独做的话或许不是很难,但是每个题都需要花时间去思考,不是一眼就可以看出来的(可能我比较菜,水平太低了),虽然有90分钟,但我开始做编程题也就二十来分钟了,两道题写了一个,一个都没时间写(不过这两道题不是很难,时间足够还是可以写出来的),写出的那个还没调试通过。

二、题目考察知识分享(只记得一部分了)

1、const修饰指针有4种形式,区分清楚这4种即可全部理解const和指针:

第一种:const int *p;(指针指向的变量是常量,指针是可变的)
第二种:int const *p;(指针指向的变量是常量,指针是可变的)
第三种:int * const p;(指针是常量,其指向的变量是可变的)
第四种:const int * const p;(指针和其指向的变量都是常量)

2、offsetof宏与container_of宏
2.1、由结构体指针进而访问各元素的原理
通过结构体整体变量来访问其中各个元素,本质上是通过指针方式来访问的,形式上是通过 ‘.’ 的方式来访问的(这时候其实是编译器帮我们自动计算了偏移量)。

2.2、offsetof宏:

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
  • 1

(1)offsetof宏的作用是:用宏来计算结构体中某个元素和结构体首地址的偏移量(其实质是通过编译器来帮我们计算)。

(2)offsetof宏的原理:我们虚拟一个type类型结构体变量,然后用type.member的方式来访问那个member元素,继而得到member相对于整个变量首地址的偏移量。

(3)学习思路:第一步先学会用offsetof宏,第二步再去理解这个宏的实现原理。

(TYPE *)0这是一个强制类型转换,把0地址强制类型转换成一个指针,这个指针指向一个TYPE类型的结构体变量。 (实际上这个结构体变量可能不存在,但是只要我不去解引用这个指针就不会出错)。

((TYPE *)0)->MEMBER (TYPE *)0是一个TYPE类型结构体变量的指针,通过指针指针来访问这个结构体变量的member元素

&((TYPE *)0)->MEMBER 等效于&(((TYPE *)0)->MEMBER),意义就是得到member元素的地址。但是因为整个结构体变量的首地址是0,故其的地址值就是结构体元素相对结构体首地址的偏移量。

2.3、container_of宏:
(1)作用:知道一个结构体中某个元素的指针,反推这个结构体变量的指针。有了container_of宏,我们可以从一个元素的指针得到整个结构体变量的指针,继而得到结构体中其他元素的指针。

 #define container_of(ptr, type, member) ({   \
     const typeof( ((type *)0)->member ) *__mptr = (ptr); \
     (type *)( (char *)__mptr - offsetof(type,member) );})
  • 1
  • 2
  • 3

(2)typeof关键字的作用是:typeof(a)时由变量a得到a的类型,typeof就是由变量名得到变量数据类型的。

(3)这个宏的工作原理:先用typeof得到member元素的类型定义成一个指针,然后用这个指针减去该元素相对于整个结构体变量的偏移量(偏移量用offsetof宏得到的),减去之后得到的就是整个结构体变量的首地址了,再把这个地址强制类型转换为type *即可。

第一部分:const typeof( ((type *)0)->member ) *__mptr = (ptr);
  通过typeof定义一个member指针类型的指针变量__mptr,并将__mptr赋值为ptr。
第二部分:(type *)( (char *)__mptr - offsetof(type,member) );
  通过offsetof宏计算出member在type中的偏移,
  然后用member的实际地址__mptr减去偏移,得到type的起始地址,即指向type类型的指针。

3、用户进程和内核进程抢占执行的时机?

3.1 用户抢占
(1)一般来说, 当进程从系统调用或者从中断(异常)处理程序返回用户空间时会触发主调度器进行用户抢占

(2)从系统调用返回用户空间

(3)从中断(异常)处理程序返回用户空间

(4)为了对一个进程需要被调度进行标记, 内核在thread_info的flag中设置了一个标识来标志进程是否需要重新调度, 即重新调度need_resched标识TIF_NEED_RESCHED, 内核在即将返回用户空间时会检查标识TIF_NEED_RESCHED标志进程是否需要重新调度,如果设置了,就会发生调度, 这被称为用户抢占

3.2 内核抢占
(1)如果内核处于相对耗时的操作中, 比如文件系统或者内存管理相关的任务, 这种行为可能会带来问题. 这种情况下, 内核代替特定的进程执行相当长的时间, 而其他进程无法执行, 无法调度, 这就造成了系统的延迟增加, 用户体验到”缓慢”的响应. 因此linux内核引入了内核抢占.

(2)linux内核通过在thread_info结构中添加了一个自旋锁标识preempt_count, 称为抢占计数器(preemption counter)来作为内核抢占的标记,

(3)内核抢占的触发大致也是两类, 内核抢占关闭后重新开启时, 中断返回内核态时

(4)内核重新开启内核抢占时使用preempt_schedule检查内核抢占

4、编程题:已知一组数据中有n个元素,随机选取一个进行复制得到N(n+1)个数据,乱序后构成一个数组nums,从数组中找到复制的那个数,并打印出来:

//注:该程序在只支持C89标准的编译器可能编译不过,建议使用
//支持C99标准的编译器
#include <stdio.h>

int main(int argc, char *argp[])
{
	int n, sum1 = 0, sum2 = 0, min = 0, max = 0;
	scanf("%d",&n);
	int nums[n];
	for(int i = 0; i < n; i++)
	{
		scanf("%d",&nums[i]);
		sum1 += nums[i];
	}
	min = nums[0];
	max = nums[0];
	for(int j = 1; j < n; j++)
	{
		if (nums[j] < min)
			min = nums[j];		
	}
	for(int k = 0; k < (n-1); k++)
	{
		sum2 += min;
		min++;
	}
	printf("%d\n", (sum1-sum2));
	
	return 0;
}
//这个是比较蠢笨的一种方法,不过好处是易于理解,使用求和再求差
//的方法得到结果,会哈希表的同学,可利用哈希表冲突的思路进行求
//解
  • 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
  • 29
  • 30
  • 31
  • 32
  • 33

在这里插入图片描述

三、个人建议(不一定对,仅供参考)

如果你只是单纯的使用单片机做项目/比赛,而不深究一些理论知识,这样固然会提高你的问题解决能力并积累一定的实战经验。但是这样对于很多技术的知识细节你可能根本不了解,从长远角度来看,这并不利于自己技术的进一步提升,例如你知道为什么IIC通信需要一个上拉电阻、起始信号是怎样的、SPI通信的四种工作方式,为什么C程序烧录到MCU中就可以直接工作等等。

如果你只是单纯的去使用例如STM32单片机实现一些功能你可能很少会去想这些问题,因为只需要调库就行了。但是对于求职要应对的笔试和面试所用到的知识,这些基础知识显得尤为重要,这些东西可以速成吗,或许可以,但我觉得应在平时的开发中顺便去学习,这样既可以加深对知识的理解,而且有助于解决开发中的问题,构建一个完整的知识体系。

要想成为一名嵌入式软件工程师,本身就需要懂很多知识:操作系统的基本知识、内存和flash、编译原理一些基础知识、汇编语言、通信协议(SPI、IIC、UART、TCP、UDP)、C语言比较深层次的一些知识(内存对齐、函数指针、面向对象的基本思想)、shell脚本、makefile的一些基本语法;虽然不是要求你要对这些知识都了如指掌,但要做到知道了解会简单使用。

有些人认为做一些实战项目比学一些理论知识要重要的多,我在这里说一下个人看法;
1、你认为很牛的东西,可能在大公司眼里并不算什么(大牛级人物除外,只是说对大多数普通本科生而言),面试官面试一个人,首先会去关注一些基础知识的掌握程度,然后再看你项目的匹配度,而且面试官往往会针对你的项目从而问一些技术细节知识。

2、其次如果面试官对你的项目不感兴趣基本都不会问,而是可能给你几页题,让你边做边讲给他(这个我是遇到过的)这个时候自己的技术知识积累就显得比较重要了,而且他一般都会问的很深。

此外你现在做的项目可能和你在公司做的根本就不是一个方向,因为公司做的都是一些专业性领域,往往会偏向于某个方向比如做显示屏驱动,这需要你对显示屏参数要有一定的了解,并且学会去分析公司的代码框架(有些公司的代码是没注释的),而且代码量一般都很大(例如一千行左右的文件按有几百个吧),这就体现出你的代码分析能力了,这需要你有着不错的编程语言功底。

项目经验固然重要,但是知识体系的完善也很重要,基础知识如同建楼的原材料,项目如同建楼,原材料都不够,怎么能快速建出一栋质量达标的高楼。

仅代表个人观点,不喜勿喷,欢迎各位技术爱好者在评论区交流。本博客部分内容参考别人的博客,若有侵权,请联系删除!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/994063
推荐阅读
相关标签
  

闽ICP备14008679号