面试题 | LINUX驱动、系统底层

LINUX驱动、系统底层

时间:2023-04-08

就业模拟测试题-LINUX驱动、系统底层工程师职位

本试卷从考试酷examcoo网站导出,文件格式为mht,请用WORD/WPS打开,并另存为doc/docx格式后再使用

试卷编号:143921
试卷录入者:yisonghua(远见)
试卷总分:80
出卷时间:2012-09-13 14:53
答题时间:150分钟
姓名: 学号: 班级:

说明: 以下个体中的分数是说明题目的重要性而言的,并不是具体题目的考试分数,此试卷中的题目主要是出现在笔试之后的面试中,大部分题目要直接能说的出来,多去整理,完善自己的表达

1.你平常是怎么用C写嵌入式系统的死循环的? [3分]

参考答案:

while(1)
{
	//...
}

或者

for(;;)
{
	//...
}

2.写一条命令,实现在dir以及其子目录下找出所有包含“hello world”字符串的文件[2分]

参考答案:

grep -r "hello world" ./dir
# 或者
grep -rHn "hello world" ./

3.下面的两段程序中,循环能否执行?为什么?

A:

unsigned short i;
unsigned short index = 0;

for(i = 0; i <index-1; i++){
	printf(“a\n”);
}

不能,当执行到语句 i 时,由于类型不匹配,右边的index和1相减时会发生隐式类型转换 ,即index将被转换成有符号整型 ,转换之后的index还是0,因此程序片段A中的index-1的结果就是 -1 ,此时判断 i,即 0<-1,显然不成立。立即退出循环。

B:

unsigned short i;
unsigned long index = 0;

for(i = 0; i <index-1; i++){
	printf(“b\n”);
}

能,indexunsigned long型,当执行到语句 i 时,由于类型不匹配,右边的index1相减时也会发生由低精度类型向高精度方向的隐式类型转换 ,即1将被转换成无符号长整型 ,因此程序片段B中的index-1的过程用十六进制数表示实际上就是0x00000-0x0001=0xffff,此时再把左边的 i 隐式转换成无符号长整型之后判断 i,即 0<0xffff,显然成立。立即进入循环。 [每空2分]

解析:
隐式类型转换规则

C语言自动转换不同类型的行为称之为隐式类型转换 ,转换的基本原则是:低精度类型高精度类型转换,具体是:

int -> unsigned int -> long -> unsigned long -> long long -> unsigned long long -> float -> double -> long double

注意,上面的顺序并不一定适用于你的机器,比如当intlong具有相同字长时,unsigned int的精度就会比long的精度高(事实上大多数针对32机的编译器都是如此)。另外需要注意的一点是并没有将charshort型写入上式,原因是他们可以被提升到int也可能被提升到unsigned int

提升数据的精度通常是一个平滑无损害的过程,但是降低数据的精度可能导致真正的问题。原因很简单:一个较低精度的类型可能不够大,不能存放一个具有更高精度的完整的数据。一个1字节的char变量可以存放整数101但不能存放整数12345。当把浮点类型数据转换为整数类型时,他们被趋零截尾舍入

强制类型转换:

通常我们应该避免自动类型转换,当我们需要手动指定一个准确的数据类型时,我们可以用强制类型转换机制来达到我们的目的,使用方法很简单,在需要强制转换类型的变量或常量前面加上(type),例如(double)i; 即把变量 i 强制转换成double型。

4.一个计划跑LINUX系统的ARM系统把bootloader烧录进去后,上电后串口上没有任何输出,硬件和软件各应该去检查什么?

提示:

1.跑LINUX的系统一般都需要外扩DRAM,一般的系统也经常有NORNAND FLASH;
2.bootloader一般是由汇编和C编写的裸奔程序[5分]

参考答案:

单片机系统:
硬件上:
1.确认电源电压是否正常。用电压表测量接地引脚跟电源引脚之间的电压,看是否是电源电压,例如常用的5V

2.检查复位引脚电压是否正常。分别测量按下复位按钮和放开复位按钮的电压值,看是否正确。

3.检查晶振是否起振了,一般用示波器来看晶振引脚的波形,另一个办法是测量复位状态下的IO口电平,按住复位键不放,然后测量IO口(没接外部上拉的IO口除外)的电压,看是否是高电平,如果不是高电平,则多半是因为晶振没有起振。

4.检查基本的外扩设备(这里主要是DRAM,特别是DDR/DDR2/DDR3)的pcb layout的走线是否符合要求。


软件上:
如果软件代码中:
1.检查CPUDRAM是否正确初始化(CPU的初始化包括一些典型步骤如: 关闭看门狗,关键FIQ,IRQ中断,关闭MMUCACHE,调整CPU的频率);

2.检查堆栈指针是否正确设置了;

3.若如NAND FLASH做系统启动部分,则需注意一般需要的从NAND FLASH中拷贝代码到DRAM中的步骤是否能正常完成;

5.列举最少3种你所知道的嵌入式的体系结构,并请说明什么是ARM体系结构。[7分]

参考答案:
嵌入式的体系结构包括ARM,MIPS,POWERPC,X86,AVR32,SH
这个没有非常标准的答案,但由经常面试的时候会问到,关于什么是ARM体系结构主要请参考讲义的ARM相关章节去总结,下面是我的总结,仅供参考:
什么是ARM体系结构?


: 首先,ARM体系结构是ARM公司设计,并授权其合作伙伴生产的占嵌入式市场份额最大的一种RISC(精简指令集)的CPU,它具有高性能低功耗低成本的特点。
ARM体系结构从工作模式工作状态,指令集几个方面简述以下ARM
ARM体系支持7种工作模式,包括系统(Sys)未定义指令(und)数据存取异常(abt)管理(SVC)中断(IRQ)快速中断(FIQ)用户模式(usr)。其中,除了用户模式以外的其它模式,我们称之为特权模式。它们之间的区别在于有些操作只能在特权模式下才被允许,如直接改变模式和中断使能等. 除了用户模式和系统模式以外的其它5种模式,我们又称之为异常模式。当特定的异常出现的时候,程序就会进入到相应的异常模式中。
备注: 在LINUX系统中, Linux的应用程序工作在usr模式,而内核在正常情况下工作在svc模式,当中断或异常时工作在异常模式。
ARM体系结构中CPU有2种工作状态thumb(指令为16位)和ARM状态(指令为32位),相对寄存器不多,总共37个,它包括通用寄存器r0~r12FIQ 有自己的r8 ~ r12),栈指针寄存器SP(r13)链接寄存器lr(r14),PC指针寄存器PC(r15)程序状态寄存器CPSR保存程序状态寄存器SPSR,在上面提到几种异常中,用户(usr)和系统模式(sys)使用相同寄存器, 而其他异常模式有自己独立的SP,LR,SPSR寄存器。
当异常产生时, 硬件上(ARM core)会完成以下动作:
拷贝 CPSRSPSR_
设置适当的 CPSR 位:
改变处理器状态进入 ARM
改变处理器模式进入相应的异常模式
设置中断禁止位禁止相应中断 (如果需要)‏}保存返回地址到 LR_
设置 PC 为相应的异常向量
返回时, 软件的异常处理程序需要:
SPSR_恢复CPSR
LR_恢复PC
Note:这些操作只能在 ARM 态执行.
ARM处理器是基于精简指令集计算机(RISC)原理设计的,发展过程中商用的指令集经过了v4v5v6v7(cortex系列) 4个系列,ARM内核的通用处理器型号比较常见的有arm7tdmi(v4), arm920/arm920t/arm926ejs,arm10,arm11,cortex-a8
为了提高指令执行效率,大部分的ARM指令为单周期指令,并从软件设计角度看,ARM处理器的指令流水线采用3级流水线模型,并提供了LDM/STM类似的批量数据操作指令。

为了提高CPU访问外部设备数据效率,ARM处理器除部分ARM7采用冯.洛伊曼结构外,其他得都采用哈佛架构,从而实现了对指令和数据存储器的同时访问。并且,ARM CPU提供了现代操作系统所需的虚拟内存管理机制(MMU)和指令、数据cache,并提供了协议处理器(cp15)来协助管理CPUMMUCACHE

扩展概念:以上叙述里面提及的概念也要稍微去总结一下,比如:
1.什么是RISC?
2.ARM中断在ARM9CORTEX-A8是怎么处理的?LINUX中为什么需要把中断分为上半部分,下半部分?
3.MMUCACHE的一些基本原理和知识

6.请简述下面这段代码的功能[2分]

mov r12, #0x0
ldr r13, =0x30100000
mov r14, #4096
loop:
ldmia r12!, {r0-r11}
stmia r13!, {r0-r11}
cmp r12, r14
blo loop 

参考答案:
借助r0~r11,将内存地址0x0开始的4KB数据拷贝到0x30100000

7.嵌入式中常用的文件系统有哪些?说出它们的主要特点和应用场合?[5分]

参考答案:
嵌入式相关的文件系统:

嵌入式文件系统包括只读可读写文件系统,一般情况下,只读文件系统启动速度快于可读写的文件系统;

嵌入式相关的文件系统包括以下几种:


只读文件系统
cramfs: 压缩的只读文件系统
特点: 启动快,文件最大支持256MB,单个文件最大16MB


squashfs: 只读文件系统
特点: 压缩比最大,启动比cramfs
案例:路由器,ubuntu的发行光盘 可结合LZMA压缩算法
可读写的文件系统:


JFFS2: 支持NORNAND FLASH (对NAND的支持天生不足)
特点:
1.可读写
2.挂载慢(特别是在小文件很多的文件系统中,就更慢)
3.当数据占到JFFS2分区的75~80%左右时,性能会急剧下降


YAFFS2: 只支持NAND FLASH
特点:
1.可读写
2.挂载快(特别是在小文件很多的文件系统中,优势更明显)
3.它不是标准内核中的,需通过补丁添加


ubifs:
起码支持NAND FLASH
特点:
1.可读写
2.挂载快
3.它的实现和其他的文件系统不一样,引进了一个"卷"的概念


在内存中的文件系统:
ramdisk: 描述的是功能,不是格式
  启动快,防止用户修改
ramfs: 在内存中的文件系统
tmpfs: 临时文件系统


实时反映系统状态: procfs, sysfs
另外,一些支持SD卡,U盘功能的系统还需要支持


windows文件系统: fat: FAT32
另外,一些带硬盘的嵌入式系统(比如DVR)还需要支持


硬盘的文件系统: EXT3/EXT4

另外,很重要很重要的一点,需要去总结文件过程中遇到的问题,总结比如文件体系挂不上的可能原因
(给个提示,可能有比如网卡FLASH驱动没加载,内核启动参数传的不对,文件系统制作的步骤不对等好像原因)

8.某外设寄存器rGpioBase的地址是0x56000000,寄存器的0~15位有效,请写出给外设寄存器高八位(8~15位)设置成0xc3的代码[7分]

参考答案:

C 运算符

#define rGpioBase (*((volatile unsigned int *)0x56000000))
rGpioBase &= ~0xff00; // 0xff00,使用~进行按位取反,然后与上(&),高八位进行清零
rGpioBase |= 0xc300; // 按位或,赋值

9.根据时序图和说明编写程序:
  面试题 | LINUX驱动、系统底层_第1张图片

GPIO已经设置好,只需要调用函数gpio_seet_level(int gpio, int level)即课使某个GPIO输出高电平或者低电平

图中用于产生时序的gpio已经分别定义为SSP_XCS,SSP_SCLK,SSP_DIN,level的定义分别为GPIO_LOGPIO_HI,需要编写函数的原型为:void ssp_io_write_word(u32 command),该函数用来输出一个字(如上图中的A0C0一组9位),这9个位是在参数command中的低9位。 [5分]

参考答案:
这道题立意非常好,做为一个底层工程师,看时序是必须的,相关的代码写法:

void ssp_io_wirte_word(u32 command)
{
    int i;
    
    //片选
    gpio_set_level(SSP_XCS, GPIO_LO);
    
    //送COMMAND
    for (i=0; i++; i<9) { //依次送A0,C7~C0
        gpio_set_level(SSP_SCLK,GPIO_LO);
        gpio_set_level(SSP_DIN, (command >>(8-i))&0x1);
        gpio_set_level(SSP_SCLK,GPIO_HI);
    }
    
    //结束片选
    gpio_set_level(SSP_SCLK,GPIO_LO);
    gpio_set_level(SSP_XCS, GPIO_HI);
    
    return;
}

如果实际结果并没有把数据正确的送出,那么就需用示波器或者逻辑分析仪看一下波形是否正确,再根据计算得到的CLK周期看一下CLK的延时是否合适,否则就加一定延迟处理

另外,这道题还提醒我们,I2C的时序是要能记得的,如果不记得,再去复习I2C协议;

10.简述LINUX系统从上电开始到系统起来的主要流程?
提示:

1.可以uboot内核文件系统的主要功能去总结
2.这个题主要是在笔试之后的面试,需要在3~5分钟之内表述清楚[8分]

参考答案:

系统启动流程应该从4个方面去总结,bootloader,内核,文件系统挂载,应用程序运行4个方面去总结,先总结大功能,再总结小功能:下面的手绘稿中,先说第一层,再说分开说第二层,在说第二层的时候,可以三星的ARM CPU,以从NAND FLASH启动为例,并在我们的图上加上硬件的相应部分:CPU上电时,CPU里面的ROM CODE负责把booloader的前面部分代码搬移到SRAM,并把SRAM映射成0x0地址,然后跳到0x0地址,另外,bootloader第二层里面,说完初始化CPU(可补充一下CPU的初始化包括进入到管理模式关闭看门狗中断MMUCACHE)和DRAM后,省略号(…)的位置是在补充一行文字: 把bootloader完整代码拷贝到DRAM中 
另外,很重要很重要的一点,需要去总结移植过程中遇到的典型问题和以及自己当时是怎么思考这个问题,并找到解决方法的过程(至少应该总结2~3个问题),也到网上去以比如(uboot, ARM 移植,问题)或(内核 移植 问题)和(文件 移植 问题)这样的关键词去搜看看别人经常遇到什么问题,总结一下!!
    面试题 | LINUX驱动、系统底层_第2张图片

11.如何编写一个LINUX驱动?
提示:主要说字符设备的编写过程 [7分]

参考答案:

这个得对着自己相应模块的驱动的找出其初始化部分并总结,下面是我总结的,仅仅供参考,不要照搬这些东西:切忌照搬,得自己去总结一下主要流程;

以字符设备为例,现在平台设备的驱动一般包括(注意,以下部分要结合一个具体的驱动去说):

.在系统的资源文件代码中定义platform_device,里面填写对应设备的外设IO起始地址地址长度中断DMA资源等信息资源信息,并把资源信息添加到系统启动初始化流程里面,比如:

/* LCD Controller */
static struct resource s3c_lcd_resource[] =
{
    [0] = {
        .start = S3C24XX_PA_LCD,
        .end = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = IRQ_LCD,
        .end = IRQ_LCD,
        .flags = IORESOURCE_IRQ,
    }
};

static u64 s3c_device_lcd_dmamask = 0xffffffffUL;
struct platform_device s3c_device_lcd = {
    .name = "s3c2410-lcd",
    .id = -1,
    .num_resources = ARRAY_SIZE(s3c_lcd_resource),
    .resource = s3c_lcd_resource,
    .dev = {
        .dma_mask = &s3c_device_lcd_dmamask,
        .coherent_dma_mask = 0xffffffffUL 
    }
};

EXPORT_SYMBOL(s3c_device_lcd);

. 通过module_init(xxx_init)moule_exit(xxx_init)定义驱动入口出口函数;
.写出模块加载xxx_init()和退出的实际处理函数xxx_exit(),这里以xxx_init()为例:
在里面调用platform_driver_resigter()注册一个platform_driver结构体,实现其中的probe()remove()函数以及driver成员结构体中nameowner成员,比如:

static struct platform_driver s3c2410fb_driver = {
  .probe = s3c2410fb_probe,
  .remove = s3c2410fb_remove,
  .driver = {
      .name = "s3c2410-lcd",
      .owner = THIS_MODULE,
  },
};

、在xxx_probe()函数里面主要做一下事情:
1.获取平台设备资源的外设IO地址,中断,DMA资源等信息;
2.映射外设控制寄存器的外设IO地址到内核的虚拟地址空间;
3.使能外设时钟,注册外设中断的处理函数(如果有中断);
4.扫描和初始化硬件;
5.最后向LINUX内核注册相应设备并通知应用层的udev/mdev守护进程创建相应的设备节点,或者通过子系统(比如输入子系统,I2C子系统等)注册相应设备并创建设备节点;
6.然后,根据字符设备相应的数据结构file_operations的实现里面的比如open,release,read,write,mmap等关键函数,或者通过子系统去注册的话,按子系统要求去实现相应的代码就行了;

12.简述LINUX驱动中字符设备和块设备的区别?[5分]

参考答案:

字符设备的特点是数据以字符流的方式进行访问数据的顺序不能错序,乱序和随机读写,字符设备内核中不需要读写的缓冲,其驱动不支持lseek()函数;


块设备的特点是数据是固定块大小(典型值有512字节,2KB,4KB进行读写,块设备可以随机读写,读写的时候内核中需要缓冲,驱动支持lseek()函数,块设备中数据的访问需要先mountLINUX的目录文件后才能访问里面的数据;
LINUX中字符设备架构相对简单,应用编程的系统调用open,close,read,writeioctl等函数驱动里面有相应的file_operations结构体里面的函数与之对应;
LINUX中块设备架构相对复杂,应用程序的读写会通过块设备里面的文件系统转化为读写的IO请求,块设备驱动里面通过gendisk结构体抽象块设备,并通过对请求队列的处理来实现对块设备的读写;

13.试总结单片机底层开发与LINUX驱动开发有哪些异同?[4分]

参考答案:
相同点:

单片机开发和LINUX的驱动开发都有对硬件的操作,最底层对硬件的寄存器操作,对时序的理解是一致的。

不同点:

1.单片机是对外设的IO实地址进行直接操作,而LINUX里面,由于使能了MMU,所以对外设IO地址的操作必须先通过ioremap()或者通过静态映射,把外设IO地址映射到内核的虚拟地址空间后才能正确操作;

2.在单片机编写对应设备的驱动不用考虑系统太多的系统分层问题,重用其他的代码量比较小,而LINUX采用分层抽象的思想,在LINUX中编写设备驱动,要按照LINUX已经搭建好的层次结构进行驱动编写,经常调用LINUX提供的函数和机制,代码重用性大;

3.由于LINUX是一个多任务的系统,即使在单核CPU上也存在资源竞争的情况(思考一下,LINUX里面那些地方可能导致资源竞争),所以在对驱动的编写的时候,对竞争资源需要采用一定的资源保护机制,比如原子变量自旋锁等;

4.单片机中断处理时,一般直接在产生中断的进入到中断处理函数里面在关中断的情况下处理完中断就可以。而LINUX里面把中断分为2部分,上半部分下班部分,在上半部分中,是在关中断情况下,只做最基本和最核心的部分,然后在下半部分在开中断情况下,通过LINUX提供的各种机制来处理(思考: LINUX中断的底半部分有哪些模式)

14.请从网卡、USB HOSTLCD驱动器、NAND FLASHWIFI 、音频芯片中选择一个或者2个(可以以具体的芯片为例),对下面的问题做答:

1.如果是外部扩展芯片,请说出你用的芯片的型号;
请注意相应nand flash芯片型号,LCD屏厂家,型号;WIFI型号,音频芯片型号 [每空5分]

15.画出上题中你选定相应硬件模块与CPU的主要引脚连线[5分]

参考答案:

请在纸上自己把自己项目中做的设备的CPU引脚连线多画几次。

这个需要根据具体模块,画出主要引脚包括数据线控制线(比如片选读写控制,以及控制重要时序的引脚),地址线(如有地址的话)

16. 编写上题中你选定相应硬件模块相应LINUX驱动的流程?[6分]

参考答案:
这个对着自己相应模块的驱动的初始化部分,总结一下主要流程,

现在平台设备的驱动一般包括(注意,以下部分要结合你自己的驱动去说):

1.获取平台设备资源的外设IO地址,中断,DMA资源等信息;

2.映射外设控制寄存器的外设IO地址到内核的虚拟地址空间;

3.使能外设时钟,注册外设中断的处理函数(如果有中断);

4.扫描和初始化硬件;

5.最后向LINUX内核注册相应设备;

6.然后,根据对应设备是字符设备块设备网络设备还是各种子系统的不同,再提供相应的数据结构里面的关键函数(比如字符设备里面file_operations,块设备里面的gendisk,网络设备里面的net_device)的实现;

============ 本试卷共计16题,此处为结束标志。考试酷examcoo ============

你可能感兴趣的:(12.开发笔记,9.嵌入式课程学习,linux,运维,服务器)