原文地址
前不久的大疆嵌入式线上笔试,可能是因为最近只是在做毕设项目,还没有来得及认真系统复习,直接崩了。就凭借着记忆,把一些记得住的笔试题分享一下,作下记录。
整个大疆嵌入式线上笔试,分为选择题(单选题、多选题)、填空题、简答题、编程题。也没有将所有的题目都记得,就分成填空选择题、简答题和编程题三块来介绍吧。
解答:在ARM的体系结构中,可以工作在三种不同的状态,一是ARM状态,二是Thumb状态及Thumb-2状态,三是调试状态。而ARM状态和Thumb状态可以直接通过某些指令直接切换,都是在运行程序,只不过指令长度不一样而已。
也就是说:ARM状态,此时处理器执行32位的字对齐的ARM指令;Thumb状态,此时处理器执行16位的,半字对齐的THUMB指令。
ARM状态和Thumb状态切换程序:
关于这个知识点还有几个注意点:
另外,具有Thumb-2技术的ARM处理器也无需再ARM状态和Thumb-2状态间进行切换了,因为thumb-2具有32位指令功能。
参考文章:ARM处理器的工作状态。
解答:几种总线接口的通信方式的总结如下图所示:
总线接口 | 串/并 | 同步/异步 | 速率 | 工作方式 | 用线 | 总线拓扑结构 | 信距离 |
UART | 串 | 异步 | 慢 波特率设置 |
全双工 | 2线 Rx、Tx |
RS485支持总线式、 星形、树形 |
远 最远1200m |
I2C |
串 |
同步 |
慢 |
半双工 |
2线 SDA、SCL |
总线型(特殊的树形) |
近 |
SPI |
串 |
同步 |
快 |
全双工 |
3线或4线 SCLK、SIMO、 SOMI、SS(片选) |
环形 |
远 |
USB |
串 |
同步 |
快 |
半双工 |
4线 Vbus(5V)、GND、D+、D-(3.3V) |
星形 |
近 |
解答:TCP和UCP的区别总结如下图所示:
角度 | TCP | UCP |
是否连接 | 面向连接(发送数据前需要建立连接) | 无连接(发送数据无需连接) |
是否丢包重试 | 实现了数据传输时各种控制功能,可以进行丢包的重发控制, 还可以对次序乱掉的分包进行顺序控制 |
不会进行丢包重试,也不会纠正到达的顺序 |
模式 | 流模式(面向字节流) | 数据报模式(面向报文) |
对应关系 | 一对一 | 支持一对一,一对多,多对一和多对多的交互通信 |
头部开销 | 最小20字节 | 只有8字节 |
可靠性 | 全双工非常可靠、无差错、不丢失、不重复、且按序到达 | 不保证可靠交付,不保证顺序到达 |
拥塞控制 | 有控制 更多详情 | 有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低 (对实时应用很有用,如IP电话,实时视频会议等) |
资源要求 | TCP程序结构较复杂,较多 | UDP程序结构简单,少 |
参考文章:一看就懂系列之 超级详解TCP与UDP。
解答:Linux下内核空间与用户空间进行通信的方式主要有syscall(system call)、procfs、ioctl和netlink等。
解答:linux目录图:
参考文章:Linux文件目录结构详解。
int main(){
const int x=5;
const int *ptr;
ptr=&x;
*ptr=10;
printf("%d\n",x);
return 0;
}
解答:编译出错。
这道题主要是讲解const与指针的问题:
const int a;
int const a;
const int *a;
int * const a;
const int * const a;
int const * const a;
也就是说:本题x是一个常量,不能改变;ptr是一个指向常整型数的指针。而当*ptr=10;的时候,直接违反了这一点。同时要记得一点,const是通过编译器在编译的时候执行检查来确保实现的。
#pragma pack(1)
struct fun{
int i;
double d;
char c;
};
解答:13。
可能是一般的内存对齐做习惯了,如果本题采用内存对齐的话,结果就是24(int 4 double char 7)。但是#pragma pack(1)让编译器将结构体数据强制按1来对齐。
每个特定平台上的编译器都有自己的默认“对齐系数”(32位机一般为4,64位机一般为8)。我们可以通过预编译命令#pragma pack(k),k=1,2,4,8,16来改变这个系数,其中k就是需要指定的“对齐系数”。
只需牢记:
参考文章:#pragma pack()的解读。
解答:chmod
A | B | C | D |
Big-endian 低地址 高地址 12 34 56 78 |
Big-endian 低地址 高地址 56 78 12 34 |
Little-endian 低地址 高地址 34 56 78 12 |
Little-endian 低地址 高地址 78 12 34 56 |
解答:大端小端问题:
那么A:12345678、B:56781234、C:12785634、D:56341278。
解答:具体的题目内容忘了,但是大体上给出各个变量可能的存储区域:
int main() {
int a[10] = { 0,1,2,3,4,5,6,7,8,9 };
memcpy(a + 3, a, 5);
for (int i = 0; i<10; i++){
printf("%d ", a[i]);
}
return 0;
}
解答:0 1 2 0 1 5 6 7 8 9
首先看一下内存复制函数memcpy()函数的定义:
void * memcpy ( void * destination, const void * source, size_t num );
将source指向的地址处的 num 个字节 拷贝到 destination 指向的地址处。注意,是字节。
因为memcpy的最后一个参数是需要拷贝的字节的数目!一个int类型占据4个字节!这样的话,本题5字节,实际上只能移动2个数字(往大的去)。如果要想达到将a地址开始的5个元素拷贝到a+3地址处,需要这么写:
memcpy(a + 3, a, 5*sizeof(int));
参考文章:memcpy使用时需要注意的地方。
解答:volatile应该是在编译阶段,extern在链接阶段。
volatile关键字的作用是防止变量被编译器优化,而优化是处于编译阶段,所以volatile关键字是在编译阶段起作用。
参考文章:C语言文件的编译与执行的四个阶段并分别描述和volatile为什么要修饰中断里的变量。
不太清楚……
解答:实时操作系统是保证在一定时间限制内完成特定功能的操作系统。实时操作系统有硬实时和软实时之分,硬实时要求在规定的时间内必须完成操作,这是在操作系统设计时保证的;软实时则只要按照任务的优先级,尽可能快地完成操作即可。
实时性最主要的含义是:任务的最迟完成时间是可确认预知的。
比较项目 | 非实时系统 | 实时系统 |
交互能力 | 较强 | 较弱 |
响应时间 | 秒集 | 毫秒、微秒级 |
可靠性 | 一般 | 较高 |
进程完成的截止期限 | 没有 | 有 |
进程切换的要求 | 一般 | 快 |
内核 | 非可剥夺(体现公平) | 可剥夺(体现优先级别) |
解答:在C语言中,static有下3个作用:
在嵌入式系统中,要时刻懂得移植的重要性,程序可能是很多程序员共同协作同时完成,在定义变量及函数的过程,可能会重名,这给系统的集成带来麻烦,因此保证不冲突的办法是显示的表示此变量或者函数是本地的,static即可。在Linux的模块编程中,这一条很明显,所有的函数和全局变量都要用static关键字声明,将其作用域限制在本模块内部,与其他模块共享的函数或者变量要EXPORT到内核中。
解答:无锁编程具体使用和考虑到的技术方法包括:原子操作(atomic operations), 内存栅栏(memory barriers), 内存顺序冲突(memory order), 指令序列一致性(sequential consistency)和顺ABA现象等等。
在这其中最基础最重要的是操作的原子性或说原子操作。原子操作可以理解为在执行完毕之前不会被任何其它任务或事件中断的一系列操作。原子操作是非阻塞编程最核心基本的部分,没有原子操作的话,操作会因为中断异常等各种原因引起数据状态的不一致从而影响到程序的正确。
对于原子操作的实现机制,在硬件层面上CPU处理器会默认保证基本的内存操作的原子性,CPU保证从系统内存当中读取或者写入一个字节的行为肯定是原子的,当一个处理器读取一个字节时,其他CPU处理器不能访问这个字节的内存地址。但是对于复杂的内存操作CPU处理器不能自动保证其原子性,比如跨总线宽度或者跨多个缓存行(Cache Line),跨页表的访问等。这个时候就需要用到CPU指令集中设计的原子操作指令,现在大部分CPU指令集都会支持一系列的原子操作。
而在无锁编程中经常用到的原子操作是Read-Modify-Write (RMW)这种类型的,这其中最常用的原子操作又是 COMPARE AND SWAP(CAS),几乎所有的CPU指令集都支持CAS的原子操作,比如X86平台下中的是 CMPXCHG(Compare Are Exchange)。
继续说一下CAS,CAS操作行为是比较某个内存地址处的内容是否和期望值一致,如果一致则将该地址处的数值替换为一个新值。CAS操作具体的实现原理主要是两种方式:总线锁定和缓存锁定。所谓总线锁定,就是CPU执行某条指令的时候先锁住数据总线的, 使用同一条数据总线的CPU就无法访问内存了,在指令执行完成后再释放锁住的数据总线。锁住数据总线的方式系统开销很大,限制了访问内存的效率,所以又有了基于CPU缓存一致性来保持操作原子性作的方法作为补充,简单来说就是用CPU的缓存一致性的机制来防止内存区域的数据被两个以上的处理器修改。
最后这里随便说一下CAS操作的ABA的问题,所谓的ABA的问题简要的说就是,线程a先读取了要对比的值v后,被线程b抢占了,线程b对v进行了修改后又改会v原来的值,线程1继续运行执行CAS操作的时候,无法判断出v的值被改过又改回来。
解决ABA的问题的一种方法是,一次用CAS检查双倍长度的值,前半部是指针,后半部分是一个计数器;或者对CAS的数值加上版本号。
参考文章:无锁编程技术及实现。
typedef struct RingBuf {
char *Buf;
unsigned int Size;
unsigned int RdId;
unsigned int WrId;
}RingBuf;
void Init(RingBuf *ringBuf, char *buf, unsigned int size) {
memset(ringBuf, 0, sizeof(RingBuf));
ringBuf->Buf = buf;
ringBuf->Size = size;
ringBuf->RdId = 0;
ringBuf->WrId = 0;
}
解答:实际上我觉得提供的初始化代码部分,对WrId的初始化有点问题,Write()函数的完整代码如下:
typedef struct RingBuf {
char *Buf;
unsigned int Size;
unsigned int RdId;
unsigned int WrId;
}RingBuf;
void Init(RingBuf *ringBuf, char *buf, unsigned int size) {
memset(ringBuf, 0, sizeof(RingBuf));
ringBuf->Buf = buf;
ringBuf->Size = size;
ringBuf->RdId = 0;
ringBuf->WrId = strlen(buf);
}
void Write(RingBuf *ringBuf, char *buf, unsigned int len) {
unsigned int pos = ringBuf->WrId;
while (pos + len > ringBuf->Size) {
memcpy(ringBuf->Buf + pos, buf, ringBuf->Size - pos);
buf += ringBuf->Size - pos;
len -= ringBuf->Size - pos;
pos = 0;
}
memcpy(ringBuf->Buf + pos, buf, len);
ringBuf->WrId = pos + len;
}
void Print(RingBuf *ringBuf) {
for (int i = 0; i < ringBuf->Size; i++) {
cout << ringBuf->Buf[i];
}
cout << endl;
}
int main()
{
RingBuf *rb = (RingBuf *)malloc(sizeof(RingBuf));
char init_str[] = "ABC";
int size = 6;
Init(rb, init_str, size);
char p[] = "1234567";
Write(rb, p, 7);
Print(rb);
return 0;
}
int merge(int *array1, int len1, int *array2, int len2, int *array3);
解答:这道题本质上就是一道合并排序,网上一大堆的程序案例,就不多介绍了。下面这段程序是我自己写的,并不是从网上贴的,如果有一些BUG,还请指出。
int merge(int *array1, int len1, int *array2, int len2, int *array3) {
int retn = len1 + len2;
if ((*array1 < *array2 || len2 == 0) && len1 > 0) {
*array3 = *array1;
merge(++array1, --len1, array2, len2, ++array3);
}
if ((*array1 >= *array2 || len1 == 0) && len2 > 0) {
*array3 = *array2;
merge(array1, len1, ++array2, --len2, ++array3);
}
return retn;
}