面试问题

来源:互联网 发布:远程关掉电脑软件 编辑:程序博客网 时间:2024/06/05 06:26

面临找工作,总结下面试常见的问题及答案;此篇博文会不断更新。

1.bootloader的启动流程是怎么样的?

一个嵌入式 Linux 系统从软件角度看可以分为四个部分:引导加载程序(Bootloader), Linux 内核,文件系统,应用程序。
当系统首次引导时,或系统被重置时,处理器会执行一个位于Flash/ROM中的已知位置处的代码,Bootloader就是这第一段代码。它主要用来初始化处理器及外设,然后调用 Linux 内核。Linux 内核在完成系统的初始化之后需要挂载某个文件系统作为根文件系统(Root Filesystem),然后加载必要的内核模块,启动应用程序。这就是嵌入式Linux系统启动过程 Linux 引导的整个过程。
根文件系统是 Linux 系统的核心组成部分,它可以作为Linux 系统中文件和数据的存储区域,通常它还包括系统配置文件和运行应用软件所需要的库。应用程序可以说是嵌入式系统的“灵魂”,它所实现的功能通常就是设计该嵌入式系统所要达到的目标。如果没有应用程序的支持,任何硬件上设计精良的嵌入式系统都没有实用意义。
从以上分析可以看出 Bootloader在运行过程中虽然具有初始化系统和执行用户输入的命令等作用,但它最根本的功能就是为了启动 Linux 内核,让我们进一步分析 Bootloader 和 Linux 内核在嵌入式系统中的关系和作用。
Bootloader
1、Bootloader基本概述
Bootloader是嵌入式系统的引导加载程序,它是系统上电后运行的第一段程序,其作用类似于 PC 机上的 BIOS。Bootloader是依赖于硬件而实现的,特别是在嵌入式领域,为嵌入式系统建立一个通用的Bootloader是很困难的,但为了能达到启动Linux 内核的目的,所有的 Bootloader都必须具备以下功能:
1) 初始化 RAM
因为 Linux 内核一般都会在 RAM 中运行,所以在调用 Linux 内核之前 Bootloader 必须设置和初始化 RAM,为调用 Linux内核做好准备。初始化 RAM 的任务包括设置 CPU 的控制寄存器参数,以便能正常使用 RAM 以及检测RAM 大小等。
2) 初始化串口端口
在 Linux 的启动过程中有着非常重要的作用,它是 Linux内核和用户交互的方式之一。Linux 在启动过程中可以将信息通过串口输出,这样便可清楚的了解 Linux 的启动过程。虽然它并不是 Bootloader 必须要完成的工作,但是通过串口输出信息是调试 Bootloader 和Linux 内核的强有力的工具,所以一般的 Bootloader 都会在执行过程中初始化一个串口作为调试端口。
3) 检测处理器类型
Bootloader在调用 Linux内核前必须检测系统的处理器类型,并将其保存到某个常量中提供给 Linux 内核。Linux 内核在启动过程中会根据该处理器类型调用相应的初始化程序。
4) 设置 Linux启动参数
Bootloader在执行过程中必须设置和初始化 Linux 的内核启动参数。
5) 调用 Linux内核映像
Bootloader完成的最后一项工作便是调用 Linux内核。如果 Linux 内核存放在 Flash 中,并且可直接在上面运行(这里的 Flash 指 Nor Flash),那么可直接跳转到内核中去执行。但由于在 Flash 中执行代码会有种种限制,而且速度也远不及 RAM 快,所以一般的嵌入式系统都是将 Linux内核拷贝到 RAM 中,然后跳转到 RAM 中去执行。
2、Bootloader启动过程
嵌入式Linux系统通过Bootloader引导,一上电,就要执行Bootloader来初始化系统。在完成对系统的初始化任务之后,它会将非易失性存储器(通常是 Flash或 DOC 等)中的Linux 内核拷贝到 RAM 中去,然后跳转到内核的第一条指令处继续执行,从而启动 Linux 内核。Bootloader 和 Linux 内核有着密不可分的联系。
Bootloader多数有两个阶段的启动过程:
Stage1:
基本的硬件初始化
为加载stage2准备RAM空间
拷贝内核映像和文件系统映像到RAM中
设置堆栈指针sp
跳到stage2的入口点
Stage2:
初始化本阶段要使用到的硬件设备
检测系统的内存映射
加载内核映像和文件系统映像
设置内核的启动参数
嵌入式系统中广泛采用的非易失性存储器通常是 Flash,而 Bootloader就位于该存储器的最前端,所以系统上电或复位后执行的第一段程序便是 Bootloader。Bootloader在flash中的存储示意图如下:

Bootloader启动流程图

3、Bootloader 的启动方式
3.1网络启动方式
这种方式的开发板不需要较大的存储介质,跟无盘工作站有点类似,但是使用这种启动方式之前,需要把Bootloader安装到板上的EPROM或者Flash中。Bootloader通过以太网接口远程下载Linux内核映像或者文件系统。Bootloader下载文件一般都使用TFTP网络协议,还可以通过DHCP的方式动态配置IP地址。
3.2硬盘启动方式
传统的Linux系统运行在台式机或者服务器上,这些计算机一般都使用BIOS引导,并使用磁盘作为存储介质。Linux传统上是LILO (Linux Loader) 引导,后来又出现了GUN的软件 (Grand Unified Bootloader) 。 这两种Bootloader广泛应用在X86的Linux系统上。
3.3 Flash启动方式
大多数嵌入式系统上都使用Flash存储介质。Flash有很多类型,包括NOR Flash、NAND Flash和其它半导体盘。它们之间的不同在于: NOR Flash 支持芯片内执行(XIP, eXecute In Place),这样代码可以在Flash上直接执行而不必拷贝到RAM中去执行。而NAND Flash并不支持XIP,所以要想执行 NAND Flash 上的代码,必须先将其拷贝到 RAM中去,然后跳到 RAM 中去执行。NOR Flash 使用最为普遍。Bootloader一般放在Flash的底端或者顶端,这需要根据处理器的复位向量来进行设置。可以配置成MTD设备来访问Flash分区
2.编写 strcpy 函数
//C语言标准库函数strcpy的一种典型的工业级的最简实现。
 
//返回值:目标串的地址。
 
//对于出现异常的情况ANSI-C99标准并未定义,故由实现者决定返回值,通常为NULL。
 
//参数:des为目标字符串,source为原字符串。
 
 
 
charstrcpy(char* des,const char* source)
 
{
 
 char* r=des;
   
  assert((des != NULL) && (source != NULL));
 
 while((*r++ = *source++)!='\0');
 
 return des;
 
}
 
 
//while((*des++=*source++));的解释:赋值表达式返回左操作数,所以在赋值'\0'后,循环停止。
3.二分法/折半查找
思想:
折半查找法是效率较高的一种查找方法。假设有已经按照从小到大的顺序排列好的五个整数a0~a4,要查找的数是X,其基本思想是: 设查找数据的范围下限为l=0,上限为h=4,求中点m=(l+h)/2,用X与中点元素am比较,若X等于am,即找到,停止查找;否则,若X大于am,替换下限l=m+1,到下半段继续查找;若X小于am,换上限h=m-1,到上半段继续查找;如此重复前面的过程直到找到或者l>h为止。如果l>h,说明没有此数,打印找不到信息,程序结束。[2] 
该方法是查找的范围不断缩小一半,所以查找效率较高;
优缺点:
折半查找法的优点是比较次数少,查找速度快,平均性能好;
其缺点是要求待查表为有序表,且插入删除困难。
因此,折半查找方法适用于不经常变动而查找频繁的有序列表
算法步骤:
① 首先确定整个查找区间的中间位置 mid = ( left + right )/2 。
② 用待查关键字值与中间位置的关键字值进行比较;  若相等,则查找成功  若大于,则在后(右)半个区域继续进行折半查找  若小于,则在前(左)半个区域继续进行折半查找。
③ 对确定的缩小区域再按折半公式,重复上述步骤。最后,得到结果:要么查找成功, 要么查找失败。折半查找的存储结构采用一维数组存放[3]  。
#include <stdio.h>
int main()
{
int a[11]={0,1,2,3,4,5,6,7,8,9,10},
min=0,
max=10,
mid,n; //max为数列长度,a[0]作为第一个数组元素
printf("请输入您要查找的数:\n");
scanf("%d",&n);
while(min+1!=max)
{
mid=(min+max)/2;
if (n>a[mid])
min=mid;
else if (n<a[mid])
max=mid;
else
{
printf("输入的数在数列的第%d位\n",mid);
exit(0);
}
}
if(n==a[max])
{
max+=1;
printf("\n输入的数在数列的第%d位\n",max);
}
else if(n==a[min])
{
min+=1;
printf("\n输入的数在数列的第%d位\n",min);
}
else if(n!=a[mid])
printf("\n输入的数不在数列中");
}
通用写法:
int binary_search(int*arr, intkey, intn)

{ int low = 0;

int high = n -1;

int mid;

while (low<= high)

{

mid = (high + low) / 2;

if (arr[mid]> k)

high = mid -1;

else if (arr[mid] < k)

low = mid + 1;

else

return mid;

}

return -1;

}

memset,memcpy和strcpy的比较

memset

用来对一段内存空间全部设置为某个字符,一般用在对定义的字符串进行初始化为''或'\0'。

函数原型:void *memset(void *s, int c, size_t n);

例如:

Cpp代码
  1. char a[100];
  2. memset(a, '\0'sizeof(a));
[cpp] view plain copy
  1. char a[100];  
  2. memset(a, '\0'sizeof(a));  

memset也可以方便的清空一个结构类型的变量或数组。

Cpp代码
  1. struct sample_struct
  2. {
  3. char csName[16];
  4. int iSeq;
  5. int iType;
  6. };
  7. struct sample_strcut stTest;
  8. /*一般情况下,清空stTest的方法 */
  9. stTest.csName[0]='\0';
  10. stTest.iSeq=0;
  11. stTest.iType=0;
  12. /* 使用memset的方法 */
  13. memset(&stTest, 0, sizeof(struct sample_struct));
[cpp] view plain copy
  1. struct sample_struct  
  2. {  
  3.     char   csName[16];  
  4.     int    iSeq;  
  5.     int    iType;  
  6. };  
  7.   
  8. struct sample_strcut  stTest;  
  9.   
  10. /*一般情况下,清空stTest的方法 */  
  11. stTest.csName[0]='\0';  
  12. stTest.iSeq=0;  
  13. stTest.iType=0;  
  14.   
  15. /* 使用memset的方法 */  
  16. memset(&stTest, 0, sizeof(struct sample_struct));  

如果是数组:

Cpp代码
  1. struct sample_struct TEST[10];
  2. memset(TEST,0,sizeof(struct sample_struct)*10);
[cpp] view plain copy
  1. struct sample_struct   TEST[10];  
  2.   
  3. memset(TEST,0,sizeof(struct sample_struct)*10);  

memcpy

用来做内存拷贝,你可以拿它拷贝任何数据类型的对象,可以指定拷贝的数据长度。

函数原型: void *memcpy( void *dest, const void *src, size_t count );

例如:

Cpp代码
  1. char a[100], b[50];
  2. memcpy(b, a, sizeof(b));
[cpp] view plain copy
  1. char a[100], b[50];   
  2. memcpy(b, a, sizeof(b));  

注意如用sizeof(a),会造成b的内存地址溢出。

strcpy

只能拷贝字符串了,它遇到'\0'就结束拷贝。

函数原型:char *strcpy( char *strDest, const char *strSource);

例如:
Cpp代码
  1. char a[100],b[50];
  2. strcpy(a,b)
[cpp] view plain copy
  1. char a[100],b[50];  
  2. strcpy(a,b)  
如用strcpy(b,a),要注意a中的字符串长度(第一个'\0'之前)是否超过50位。如超过,则会造成b的内存地址溢出。
也可以使用strncpy(a,b,n)
总结:
  • memset主要应用是初始化某个内存空间。
  • memcpy是用于copy源空间的数据到目的空间中。
  • strcpy用于字符串copy,遇到'\0',将结束。
strcpy/strncpy需要在每一步操作时都要比较字符是否为'\0',而memcpy完全不需要判断。而且,相比于strcpy等函数的逐字节拷贝,memcpy 是按照机器字长逐字进行拷贝的,一个字等于4(32位机)。CPU存取一个字节和存取一个字是一样的,都是在一条指令、一个内存周期内 完成的。所以,按字拷贝效率更高。
原创粉丝点击