前言
昨天答完题之后,我只有一句话,我不配大疆!!!我昨天做的是应该最后一批秋招正式批笔试题了,根据昨天的做题情况和自己的一些感受与大家分享一下。
一、题目难度
我感觉昨天那份题说难也没有难得很离谱,但还是有难度的,个人认为有以下几点原因: 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)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) );})
(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,从数组中找到复制的那个数,并打印出来:
#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;
}
三、个人建议(不一定对,仅供参考)
如果你只是单纯的使用单片机做项目/比赛,而不深究一些理论知识,这样固然会提高你的问题解决能力并积累一定的实战经验。但是这样对于很多技术的知识细节你可能根本不了解,从长远角度来看,这并不利于自己技术的进一步提升,例如你知道为什么IIC通信需要一个上拉电阻、起始信号是怎样的、SPI通信的四种工作方式,为什么C程序烧录到MCU中就可以直接工作等等。
如果你只是单纯的去使用例如STM32单片机实现一些功能你可能很少会去想这些问题,因为只需要调库就行了。但是对于求职要应对的笔试和面试所用到的知识,这些基础知识显得尤为重要,这些东西可以速成吗,或许可以,但我觉得应在平时的开发中顺便去学习,这样既可以加深对知识的理解,而且有助于解决开发中的问题,构建一个完整的知识体系。
要想成为一名嵌入式软件工程师,本身就需要懂很多知识:操作系统的基本知识、内存和flash、编译原理一些基础知识、汇编语言、通信协议(SPI、IIC、UART、TCP、UDP)、C语言比较深层次的一些知识(内存对齐、函数指针、面向对象的基本思想)、shell脚本、makefile的一些基本语法;虽然不是要求你要对这些知识都了如指掌,但要做到知道了解会简单使用。
有些人认为做一些实战项目比学一些理论知识要重要的多,我在这里说一下个人看法; 1、你认为很牛的东西,可能在大公司眼里并不算什么(大牛级人物除外,只是说对大多数普通本科生而言),面试官面试一个人,首先会去关注一些基础知识的掌握程度,然后再看你项目的匹配度,而且面试官往往会针对你的项目从而问一些技术细节知识。
2、其次如果面试官对你的项目不感兴趣基本都不会问,而是可能给你几页题,让你边做边讲给他(这个我是遇到过的)这个时候自己的技术知识积累就显得比较重要了,而且他一般都会问的很深。
此外你现在做的项目可能和你在公司做的根本就不是一个方向,因为公司做的都是一些专业性领域,往往会偏向于某个方向比如做显示屏驱动,这需要你对显示屏参数要有一定的了解,并且学会去分析公司的代码框架(有些公司的代码是没注释的),而且代码量一般都很大(例如一千行左右的文件按有几百个吧),这就体现出你的代码分析能力了,这需要你有着不错的编程语言功底。
项目经验固然重要,但是知识体系的完善也很重要,基础知识如同建楼的原材料,项目如同建楼,原材料都不够,怎么能快速建出一栋质量达标的高楼。
仅代表个人观点,不喜勿喷,欢迎各位技术爱好者在评论区交流。本博客部分内容参考别人的博客,若有侵权,请联系删除!
|