大疆的面试体验很好(没有手撕代码的面试,体验都很好哈哈哈哈哈)
自我介绍(1min),项目,大疆业务了解,Linux
自我介绍,项目技术点,项目遇到的难点,大疆产品哪里可以改进,Linux虚拟文件系统
工作意向,生活经历等等,不聊技术
1min和3min各准备一个,随机应变。
我叫xxx,来自xxxx大学xxxxxxx专业,非常高兴参加咱们xx公司的面试,我想用3个关键词来介绍自己:第一个是xxxx,主要讲学历背景第二个是xxxx,主要讲项目经验最后一个是xxxx,主要讲个人技能及亮点我对xxxxxx很感兴趣,同时咱们公司是这一行业的龙头企业,非常感谢有机会参加这次面试。谢谢!
消费级产品、影像系统、行业应用、教育产品,车载布局
虚拟文件系统(VirtualFileSystem,简称VFS)是Linux内核的子系统之一,它为用户程序提供文件和文件系统操作的统一接口,屏蔽不同文件系统的差异和操作细节。借助VFS可以直接使用open()、read()、write()这样的系统调用操作文件,而无须考虑具体的文件系统和实际的存储介质。
通过VFS系统,Linux提供了通用的系统调用,可以跨越不同文件系统和介质之间执行,极大简化了用户访问不同文件系统的过程。另一方面,新的文件系统、新类型的存储介质,可以无须编译的情况下,动态加载到Linux中。
"一切皆文件"是Linux的基本哲学之一,不仅是普通的文件,包括目录、字符设备、块设备、套接字等,都可以以文件的方式被对待。实现这一行为的基础,正是Linux的虚拟文件系统机制。
管道是一种在两个进程间进行单向通信的机制。因为管道传递数据的单向性,管道又称为半双工管道。数据只能由一个进程流向另一个进程(其中一个读管道,一个写管道);如果要进行双工通信,需要建立两个管道。
管道是由内核管理的一个缓冲区,相当于我们放入内存中的一个纸条。管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。一个缓冲区不需要很大一般为4K大小,它被设计成为环形的数据结构,以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。
内核态,操作系统在内核态运行——运行操作系统程序
用户态,应用程序只能在用户态运行——运行用户程序
当一个进程在执行用户自己的代码时处于用户运行态(用户态),此时特权级最低,为3级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态。3级状态不能访问0级的地址空间,包括代码和数据;当一个进程因为系统调用陷入内核代码中执行时处于内核运行态(内核态),此时特权级最高,为0级。执行的内核代码会使用当前进程的内核栈,每个进程都有自己的内核栈。
1)系统调用。用户空间进程通过系统调用进入内核空间,访问指定的内核空间数据;
2)驱动程序。用户空间进程可以使用封装后的系统调用接口访问驱动设备节点,以和运行在内核空间的驱动程序通信;
4)copy_to_user()、copy_from_user(),是在驱动程序中调用接口,实现用户空间与内核空间的数据拷贝操作,应用于实时性要求不高的项目中。
段错误是指访问的内存超出了系统给这个程序所设定的内存空间,例如访问了不存在的内存地址、访问了系统保护的内存地址、访问了只读的内存地址、栈溢出等等情况。
段错误的调试方法:使用printf输出信息、使用gcc和gdb、使用core文件和gdb、使用objdump、使用catchsegv
启动顺序:bootloader->linuxkernel->rootfile->app
Bootloader全名为启动引导程序,是第一段代码,它主要用来初始化处理器及外设,然后调用Linux内核。
Linux内核在完成系统的初始化之后需要挂载某个文件系统作为根文件系统(RootFilesystem),然后加载必要的内核模块,启动应用程序。
(一个嵌入式Linux系统从软件角度看可以分为四个部分:引导加载程序(Bootloader),Linux内核,文件系统,应用程序。)
Stage1:汇编语言
1)基本的硬件初始化(关闭看门狗和中断,MMU(带操作系统),CACHE。配置系统工作时钟)
2)为加载stage2准备RAM空间
3)拷贝内核映像和文件系统映像到RAM中4)设置堆栈指针sp5)跳到stage2的入口点
Stage2:C语言
1)初始化本阶段要使用到的硬件设备(UART等)2)检测系统的内存映射3)加载内核映像和文件系统映像4)设置内核的启动参数
嵌入式系统中广泛采用的非易失性存储器通常是Flash,而Bootloader就位于该存储器的最前端,所以系统上电或复位后执行的第一段程序便是Bootloader。
1.系统硬件资源:CPU,多核与超线程
消耗CPU的业务:动态WEB服务,Mail服务器
2.内存:物理内存与swap的取舍,64操作系统
消耗内存的业务:内存数据库(Redis、hbase、mongodb)
3.磁盘:RAID技术(RAID0/1/5/10),SSD
消耗磁盘的业务:数据库服务器
4.网络带宽:网卡/交换机/双网卡绑定
消耗带宽的业务:hadoop平台,视频业务平台
CPU主频:F103系列72MHz,F4系列168MHz;
C8T6:FLASH64K,RAM20K;RCT6:FLASH256K,RAM48K;ZET6:FLASH512K,RAM64K
你在stm32上都做过哪些开发?几个单片机项目
你开发过哪些驱动吗?屏幕驱动、无线模块的驱动、GUI开发
你了解整个SPI的通信过程吗?SPI有几根线,分别是什么?你使用SPI的时候速率配置的是多少?
SPI是同步全双工串行通信协议。SPI通信有4种不同的模式,不同的从设备在出厂就已配置为某种模式,这是不能改变的;但我们的通信双方必须是工作在同一模式下,所以需要对主设备的SPI模式进行配置,通过CPOL(时钟极性)和CPHA(时钟相位)来控制我们主设备的通信模式。
SPI定义了4根信号线:SCK:时钟线,主机提供;MISO:主入从出;MOSI:主出从入;SS:片选。
SPI2设置8分频36MHz/8=4.5MHz
SPI的4种模式
SPI通过时钟极性CPOL和时钟相位CPHA定义了4种通信模式:
CPOL(时钟极性)位:控制在没有数据传输时时钟的空闲状态电平,此位对主模式和从模式下的设备都有效。
CPHA(时钟相位):用来配置数据采样是在第几个边沿。
如果CPOL被清’0’,SCK引脚在空闲状态保持低电平;
如果CPOL被置’1’,SCK引脚在空闲状态保持高电平。
如果CPHA(时钟相位)位被置’1’,SCK时钟的第二个边沿(CPOL位为0时就是下降沿,CPOL位为’1’时就是上升沿)进行数据位的采样,数据在第二个时钟边沿被锁存。
如果CPHA位被清’0’,SCK时钟的第一个边沿(CPOL位为’0’时就是下降沿,CPOL位为’1’时就是上升沿)进行数据位采样,数据在第一个时钟边沿被锁存。
I2C总线
开始:SCL为高电平时,SDA有下降沿。
数据传输:数据传输以字节为单位,第一个字节表示从机地址+读写方向,后续数据格式由器件自己定义。数据传输中,SDA只能在SCL低电平时变化,并在SCL上升沿进行数据采样。
应答:每发送一个字节后,接收方必须回复应答信号ACK,但发送最后一个字节后,回复非应答信号NACK。
停止:SCL为高电平时,SDA有上升沿。
说一下SPI和I2C和UART的各自的工作方式优缺点。
SPI同步全双工,I2C同步半双工,UART异步全双工
SPI速度快协议简单但线多;I2C的速度比SPI慢一点,协议比SPI复杂一点,但是连线也比标准的SPI要少;UART距离远但是只能1对1
直接内存存取技术(DirectMemoryAccess),DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实现和完成的。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,而是通过硬件为RAM和IO设备开辟一条直接传输数据的通道,使得CPU的效率大大提高。
在实现DMA传输时,是由DMA控制器直接掌管总线,因此,存在着一个总线控制权转移问题。即DMA传输前,CPU要把总线控制权交给DMA控制器,而在结束DMA传输后,DMA控制器应立即把总线控制权再交回给CPU。
一个完整的DMA传输过程必须经过DMA请求、DMA响应、DMA传输、DMA结束4个步骤。
请求中断→响应中断→关闭中断→保留断点→中断源识别→保护现场→中断服务子程序→恢复现场→中断返回。
经典的软件开发模型—瀑布模型
这个模型把市场与项目紧紧的联系在一起了,如果一个项目的市场前景很清楚,软件需求十分明确,那么软件大多就是采用这种模型进行开发。但是这个开发过程是逐步向下的,那么就会导致软件后期的维护不方便、软件的开发周期也很长。
保证项目精准性的模型—V模型
该模型最大的特点就是将测试环节贯穿了整个软件的各个阶段,由此达到一种反馈的目的,使得项目的精准性得到很大提高。
快速开发上市的模型—敏捷开发模型
这个模型简化了开发人员的开发流程、实现快速迭代、循序渐进!简化开发人员的开发流程体现在不注重各种标准文档的书写,注重人的沟通。而快速迭代、循环渐进指的是产品的迭代要快速、分阶段进行开发。
以用户为基础的模型—原型
原型会先快速构造一个功能模型,演示给用户看,待用户确认后再继续开发。在完善的过程中不断的演示与用户沟通。这样一来就不会出现产品与用户要求不符合的情况了!这种开发模型最优越的地方就是可以快速定位用户的需求。
项目分层驱动层、中间层和应用层。
底层一般是直接访问硬件的接口,以串口而言如寄存器操作函数;中间层一般是在底层与上层之间进行数据及信息的转换,以串口而言如封包/拆包/消息产生/消息响应;应用层就是在很少考虑硬件实现的前提下以通用的方式实现所需的功能,以串口而言如printf。
优点:开发的快速性、系统安全性与程序可读性、代码可移植性
FreeRTOS,UCOS,RT-thread,Vxworks等实时系统
容器技术
容器的本质,就是一组受到资源限制,彼此间相互隔离的进程。容器是没有自己的OS的,直接共享宿主机的内核,也没有hypervisor这一层进行资源隔离和限制,所有对于容器进程的限制都是基于操作系统本身的能力来进行的,由此容器获得了一个很大的优势:轻量化,对应用友好,又具备了一定的隔离性。
中间件
中间件是一种独立的系统软件服务程序,分布式应用软件借助这种软件在不同的技术之间共享资源,中间件位于客户机服务器的操作系统之上,管理计算资源和网络通信。从这个意义上可以用一个等式来表示中间件:中间件=平台+通信,这也就限定了只有用于分布式系统中才能叫中间件,同时也把它与支撑软件和实用软件区分开来。
(1)信号量(2)读写锁(3)条件变量(4)互斥锁(5)自旋锁
在Linux中,线程是由进程来实现,线程就是轻量级进程(lightweightprocess),因此在Linux中,线程的调度是按照进程的调度方式来进行调度的,也就是说线程是调度单元。Linux这样实现的线程的好处的之一是:线程调度直接使用进程调度就可以了,没必要再搞一个进程内的线程调度器。在Linux中,调度器是基于线程的调度策略(schedulingpolicy)和静态调度优先级(staticschedulingpriority)来决定那个线程来运行。
一般有这样两种模式:分时调度和抢占式调度。
分时调度就是按照顺序平均分配;
抢占式调度就是按照优先级来进行分配。
如果现在多个线程,怎么确定哪一个会先被执行?
首先是看优先级,其次看是否处于就绪状态
14.进程通信有使用过一些锁和同步的东西吗?
15.多个进程在获取不到锁的时候会进入什么状态?A:阻塞。
16.假设一个低优先的进程A获取先到锁,高优先级的进程B获取不到锁,那高优先级进程B的进行也会阻塞吗?A:还是会阻塞。
17.那如果还有一个任务C,优先级介于A和B之间,那么C任务会去抢占CPU资源吗?A:会的.
18.那这样导致B等待资源的时候会越来越长,这样的问题有方法可以解决吗?因为B的优先级才最高。A:先把A优先级提高,等A释放后再给B。
19.用什么方式提高A的优先级?RTOS有函数可以提高。
20.那么A的优先级需要提高到多少?
(3)高响应比优先调度算法:对FCFS方式和SJF方式的一种综合平衡
(5)优先权调度算法(分非抢占和抢占式)
(6)多级反馈队列调度算法:轮转算法和优先级算法的综合和发展
(1)栈(stack):由编译器自动分配释放,存放函数的参数值、局部变量的值、返回地址等,其操作方式类似于数据结构中的栈。
(2)堆(heap):一般由程序员动态分配(调用malloc函数)和释放(调用free函数),若程序员不释放,程序结束时可能由操作系统回收。
堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。
(3)数据段(data):存放的是常量、常变量(const变量)、静态变量、全局变量等。根据存放的数据,数据段又可以分为普通数据段(包括可读可写/只读数据段,存放静态初始化的全局变量或常量)、BSS数据段(存放未初始化的全局变量)。在采用段式内存管理的架构中,BSS段(bsssegment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文BlockStartedbySymbol的简称。BSS段属于静态内存分配。程序结束后由系统释放
(4)常量存储区:常量占用内存,只读状态,决不可修改,常量字符串就是放在这里的。
(5)代码段(code):用于存放程序代码。代码段(codesegment/textsegment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读,某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
inta=0;//全局初始化区char*p1;//全局未初始化区main(){intb;//栈中chars[]="abc";//栈中char*p2;//栈中char*p3="123456";//123456\0在常量区,p3在栈上staticintc=0;//全局(静态)初始化区//以下分配得到的10和20字节的区域就在堆区p1=(char*)malloc(10);p2=newchar[20];//(char*)malloc(20);strcpy(p1,"123456");//123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。}什么是函数指针和指针函数?指针函数本质是一个函数,其返回值为指针。
函数指针本质是一个指针,其指向一个函数。
指针函数:int*fun(intx,inty);
函数指针:int(*fun)(intx,inty);
1、用一个变量记录栈大小
2、重新设置堆栈指针,指向新的堆栈,并设置堆栈两端页面为保护页面,一旦堆栈溢出,就会产生保护异常
解决的方法:
1、使用虚拟空间vector存储数据时,会分配一个存储空间,如果继续存储,该分配的空间已满,就会分配一块更大的内存,把原来的数据复制过来,继续存储。2、动态分配:就是不要静态分配,用new动态创建,是从堆中分配的,堆的空间足够大,不过记得写析构函数,delete你申请的堆空间。类结束的时候会自动调用析构函数释放空间。一般情况:因为栈一般默认为1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出。
方法:用栈把递归转换成非递归堆栈指针(SP)堆栈指针总是指向栈顶位置。一般堆栈的栈底不能动,数据入栈前要先修改堆栈指针,使它指向新的空余空间然后再把数据存进去,出栈的时候相反。
最先入栈的数据要到最后才能出栈,而最后入栈的数据最先出栈。
数据流的TopK问题,所用到的数据结构,以及操作的复杂度
如何在main函数之前进行控制台输出,在C++中如何实现,在C中如何实现
定义在main()函数之前的全局对象、静态对象的构造函数在main()函数之前执行。
可以用main调用main实现在main前执行一段代码
#include
//C++11#include
指针是类型,有空间,可以为null,适用于动态分配内存。
(1)指针是实体,引用是别名,没有空间。
(2)引用定义时必须初始化,指针不用。
(3)指针可以改,引用不可以。
(4)引用不能为空,指针可以。
(5)Sizeof(引用)计算的是它引用的对象的大小,而sizeof(指针)计算的是指针本身的大小。
(6)不能有NULL引用,引用必须与一块合法的存储单元关联。
(7)给引用赋值修改的是该引用与对象所关联的值,而不是与引用关联的对象。
(8)如果返回的是动态分配的内存或对象,必须使用指针,使用引用会产生内存泄漏。
(9)对引用的操作即是对变量本身的操作。****
构造:把尾结点的next指向头结点。
判断是否是循环链表时,也设置两个指针,慢指针和快指针,让快指针比慢指针每次移动快两次。如果快指针追赶上慢指针,则为循环链表,否则不是循环链表,如果快指针或者慢指针指向NULL,则不是循环链表。