赞
踩
一、题型分析
1、选择题(单选题、多选题);
2、填空题;
3、简答题;
4、编程题。
二、题目解析
1、选择题
(1)sizeof union和struct
解答: 涉及知识点:数据类型字节长度和内存对齐。
可参考:快速理解内存对齐以及#pragma pack
(2)ARM指令和Thumb指令
解答:在ARM的体系结构中,可以工作在三种不同的状态,一是ARM状态,二是Thumb状态及Thumb-2状态,三是调试状态。而ARM状态和Thumb状态可以直接通过某些指令直接切换,都是在运行程序,只不过指令长度不一样而已。
ARM状态:
arm处理器工作于32位指令的状态,所有指令均为32位;
Thumb状态:
arm执行16位指令的状态,即16位状态;
thumb-2状态:
这个状态是ARM7版本的ARM处理器所具有的新的状态,新的thumb-2内核技术兼有16位及32位指令,实现了更高的性能,更有效的功耗及更少地占用内存。总的来说,感觉这个状态除了兼有arm和thumb的优点外,还在这两种状态上有所提升、优化;
调试状态:
处理器停机时进入调试状态。
也就是说:ARM状态,此时处理器执行32位的字对齐的ARM指令;Thumb状态,此时处理器执行16位的,半字对齐的THUMB指令。
ARM状态和Thumb状态切换程序:
从ARM到Thumb: LDR R0,=lable+1 BX R0(状态将寄存器的最低位设置为1,BX指令、R0指令将进入thumb状态);
从Thumb到ARM: LDR R0,=lable BX R0(寄存器最低位设置为0,BX指令、R0指令将进入arm状态)。
当处理器进行异常处理时,则从异常向量地址开始执行,将自动进入ARM状态。
关于这个知识点还有几个注意点:
注意:
1、ARM处理器复位后开始执行代码时总是只处于ARM状态;
2、Cortex-M3只有Thumb-2状态和调试状态;
3、由于Thumb-2具有16位/32位指令功能,因此有了thumb-2就无需Thumb了。
4、具有Thumb-2技术的ARM处理器也无需再ARM状态和Thumb-2状态间进行切换了,因为thumb-2具有32位指令功能。
(3)哪种总线方式是全双工类型、哪种总线方式传输的距离最短?
解答:
(4)TCP与UDP的区别
**解答:**TCP/IP协议是一个协议簇。里面包括很多协议。UDP只是其中的一个。之所以命名为TCP/IP协议,因为TCP,IP协议是两个很重要的协议,就用他两命名了。
(5)Linux的用户态与内核态的转换方法
解答:
当一个任务(进程)执行系统调用而执行内核代码时,称进程处于内核内核态,此时处理器处于特权级最高的(0级)内核代码中执行,当进程处于内核态时,执行的内核代码会使用当前进程的内核栈,每个进程都有自己的内核栈。当进程执行用户代码时,称其处于用户态,此时处理器在特权级最低的(3级)用户代码中运行。
当正在执行用户程序而奕然被中断程序中断时,此时用户程序也可以象征性地称为处于进程的内核态,因为中断处理程序将使用当前进程的内核栈。这与处于内核态的进程的状态有些类似。内核态与用户态是操作采统的两种运行级别,跟intel cpu没有必然的联系, intel epu提供RingO-Ring3三种级别的运行模式, Ring0级别最高,Ring3最低。
些代码完成操作,完成后,切换回Ring3,回到用户态。这样,用户态的程序就不能随意操作内核地址空间,具有一定的安全保护作用。
保护模式,通过内存页表操作等机制,保证进程间的地址空间不会互相冲奕,一个进程的操作不会修改另一个进程的地址空间中的数据。在内核态下,CPU可执行任何指合,在用户态下CPU只能执行非特权指合。当CPU处于内核态,可以随意进入用户态;而当CPU处于用户态,只能通过中断的方式进入内核态。一般程序一开始都是运行于用户态,当程序需要使用系统资源时,就必须通过调用软中断进入内核态。
用户态切换到内核态的3种方式
1.系统调用
这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作,比如前例中fork()实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。
系统调用实质上是一个中断,而汇编指令int就可以实现用户态向内核态切换, iret实现内核态向用户态切换。
2. 异常
当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
3. 外围设备的中断
当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指合转而去执行与中断信号对应的处理程序,如果先前执行的指合是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。
(6)linux目录结构,选项是/usr、/tmp、/etc目录的作用
1、/usr:不是user的缩写,其实usr是Unix Software Resource的缩写, 也就是Unix操作系统软件资源所放置的目录,而不是用户的数据啦。这点要注意。 FHS建议所有软件开发者,应该将他们的数据合理的分别放置到这个目录下的次目录,而不要自行建立该软件自己独立的目录;
2、/tmp:这是让一般使用者或者是正在执行的程序暂时放置档案的地方。这个目录是任何人都能够存取的,所以你需要定期的清理一下。当然,重要资料不可放置在此目录啊。 因为FHS甚至建议在开机时,应该要将/tmp下的资料都删除;
3、/etc:系统主要的设定档几乎都放置在这个目录内,例如人员的帐号密码档、各种服务的启始档等等。 一般来说,这个目录下的各档案属性是可以让一般使用者查阅的,但是只有root有权力修改。 FHS建议不要放置可执行档(binary)在这个目录中。 比较重要的档案有:/etc/inittab, /etc/init.d/, /etc/modprobe.conf, /etc/X11/, /etc/fstab, /etc/sysconfig/等等
(7)下面这段程序的运行结果?
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;
int * const a;
const int * const a;
int const * const a;
(1)前两个的作用是一样,a是一个常整型数;
(2)第三个意味着a是一个指向常整型数的指针,指针所指向的数据是只读的,也就是a 本身的值可以修改(指向不同的数据),但它们指向的数据不能被修改,本体就是错在这里。;
(3)第四个意思a是一个指向整型 数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的);
(4)最后两个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数 是不可修改的,同时指针也是不可修改的)。
也就是说:本题x是一个常量,不能改变;ptr是一个指向常整型数的指针。而当*ptr=10;的时候,直接违反了这一点。同时要记得一点,const是通过编译器在编译的时候执行检查来确保实现的。
(8)在32位系统中,有如下结构体,那么sizeof(fun)的数值是();
#pragma pack(1)
struct fun{
int i;
double d;
char c;
};
解答:对齐系数为1,则以上数据是紧密衔接的,如下图:
一共占据13字节的内存。
该题主要涉及内存对齐的问题,关于内存对齐可参考以下博文:快速理解内存对齐以及#pragma pack
(9)Linux中的文件/目录权限设置命令是什么?
解答:chmod命令。
sudo chmod 777 path
sudo chmod 777 files_name
可以用来修改文件夹或者文件的权限,“path”代表你要修改的文件夹的路径;“files_name”代表你要修改的文件名。
sudo chmod -R 777 path
可以用来修改该路径及其路径下文件夹和文件的权限。
(10)下面四个选项是四个整数在内存中的存储情况,请选择其中最大的一个。
设计知识点:大小端储存方式
所谓的大端模式(Big-endian),是指数据的高字节,保存在内存的低地址中,而数据的低字节,保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;
所谓小端模式(Little-endian), 是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内在的低地址中,这种存储模式将地址的高低和数据位 权有效结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致。
所以以上数值分别是:
A-----12345678
B-----56781234
C-----12785634
D-----56341278
最大数值是B
(11)函数指针数组 int (*int a[10])(int)
2、填空题
(1)分析一段程序中出现的各个变量的存储区域
解答:
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*dest, const void *src, size_t n);
功能
由src指向地址为起始地址的连续n个字节的数据复制到以destin指向地址为起始地址的空间内。
当n大于一个数据字节而小于两个数据字节的时候,会自动选择复制两个数据。例如该题,int型数据是4个字节,函数中n是5,即复制5个字节,由结果可知复制了两个数据0 1。
(3)C语言编译过程中,volatile关键字和extern关键字分别在哪个阶段起作用?
解析:
1)extern 有三个作用:
"C"
一起连用。如:extern "C" void fun(int a)
; 这个告诉编译器在编译fun
这个函数名时安C的规则去翻译相应的函数名,而不是C++,因为C++在翻译的时候会把这个fun名字变得面目全非,以支持C++的函数重载。extern
对该变量作“外部变量声明”,表示该变量是一个已经定义的外部变量。有了此声明,就可以从“声明”处起,合法地使用该外部变量。extern
关键字加以声明即可。在编译的过程中编译器只需要知道数据类型和名字,以便知道如何使用它所以不会报错。一旦编译完成后,链接器会针对使用extern
变量的模块,到包含的该变量的模块中生成的目标代码中找到此变量。即extern关键字是在链接阶段起作用。
2)volatile
确保编译器不会帮你对volatile
进行优化,让一切判断如你预期的执行。
例如:
int some_int = 100;
while(some_int == 100)
{
}
编译器可以优化此代码,因为它发现程序在此没有改变过some_int于是将while(some_int ==100)
优化为while(true)
, 然而,有时这个值可能是通过外面某种方式改变,而编译器无法观察到。因此,为了确保获得所需的结果,您需要以某种方式阻止编译器优化while
循环。这就是volatile
关键字的作用。
综上:extern 作用于链接阶段;volatile 作用于编译阶段。
(4)linux系统打开设备文件,进程可能处于三种基本状态,如果多次打开设备文件,驱动程序应该实现什么?
解答:运行态、就绪态、等待态。
3、简答题
(1)简述实时操作系统和非实时操作系统特点和区别。
4、编程题
(1)、已知循环缓冲区是一个可以无限循环读写的缓冲区,当缓冲区满了还继续写的话就会覆盖我们还没读取到的数据。下面定义了一个循环缓冲区并初始化,请编写它的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 = 0;
}
2、已知两个已经按从小到大排列的数组,将它们中的所有数字组合成一个新的数组,要求新数组也是按照从小到大的顺序。请按照上述描述完成函数:
int merge(int *array1, int len1, int *array2, int len2, int *array3);
参考资料:
【机试题】2019大疆嵌入式笔试题A卷(附超详细解答)
extern、static与volatile的作用
图解实时操作系统和非实时操作系统的区别
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。