【嵌入式基础】嵌入式软件开发:笔试总结

从CSDN各个博客上摘选的一些容易做错的嵌入式软件的笔试题,做一下记录,让自己记住。

文章转自:嵌入式软件开发:笔试总结


编程语言的基础考察

1、以下代码运行结果为:

#include <iostream>
using namespace std;
int func(int x)
{
    int count=0;
    while(x)
    {
        count++;
        x=x&(x-1);
    }
    return count;
}
int main()
{
    cout<<(9867)<<endl;
    return 0;
}

解答:本题func函数返回值是形参x转化成二进制后包含1的数量。理解这一点很容易解答出来。是因为x&(x-1)每执行一次就会消去一个1,这样只要判断x的二进制有多少个1就可以了。


ARM嵌入式的相关基本概念

2、简述一下boot loader的作用。

解答:在一般情况下嵌入式Linux系统中的软件主要分为以下及部分:

  • 引导加载程序: 其中包括内部ROM中的固化启动代码和Boot Loader两部分。而这个内部固化ROM是厂家在芯片生产时候固化的,作用基本上是引导Boot Loader。有的芯片比较复杂,比如Omap3,它在flash中没有代码的时候有许多启动方式:USB、UART或以太网等等。而S3C24x0则很简单,只有Norboot和Nandboot;
  • Linux kernel 和drivers;
  • 文件系统。 包括根文件系统和建立于Flash内存设备之上的文件系统(EXT4、UBI、CRAMFS等等)。它是提供管理系统的各种配置文件以及系统执行用户应用程序的良好运行环境的载体;
  • 应用程序。 用户自定义的应用程序,存放于文件系统之中。

在嵌入式Linux中为什么要有BootLoader?

在linux内核的启动运行除了内核映像必须在主存的适当位置(操作系统文件的来源,可以是flash、sd card、PC(可以通过网络,USB,甚至串口传输)等等,所谓的EBOOT、UBOOT,其实就是表明了系统文件是通过Ethernet或者USB从PC传输过去的。),CPU还必须具备一定的条件:

CPU条件
CPU 寄存器的设置 R0=0;
R1=Machine ID(即Machine Type Number,定义在linux/arch/arm/tools/mach-types);
R2=内核启动参数在 RAM 中起始基地址;
CPU 模式 必须禁止中断(IRQs和FIQs);
CPU 必须 SVC 模式;
Cache和MMU的设置 MMU 必须关闭;
指令 Cache 可以打开也可以关闭;
数据 Cache 必须关闭;

但是在CPU刚上电启动的时候,一般连内存控制器都没有配置过,根本无法在内存中运行程序,更不可能处在Linux内核的启动环境中。为了把操作系统映像文件拷贝到RAM中去、初始化CPU及其他外设,然后跳转到它的入口处去执行,使得Linux内核可以在系统主存中跑起来,并让系统符合Linux内核启动的必备条件,必须要有一个先于内核运行的程序,他就是所谓的引导加载程序(Boot Loader)。

而Boot Loader并不是Linux才需要,是几乎所有的运行操作系统的设备都具备的。

Boot Loader的功能和选择 :

通过上面的讲述,我们可以知道:Boot Loader是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境,最后从别处(Flash、以太网、UART)载入内核映像并跳到入口地址。

由于BootLoader直接操作硬件,所以它严重依赖于硬件,而且依据所引导的操作系统的不同,也有不同的选择对于嵌入式世界中更是如此。所以在嵌入式世界中建立一个通用的 BootLoader 几乎是不可能的,而有可能的是让一个 Boot Loader代码支持多种不同的构架和操作系统,并让它方便移植。

3、简述一下嵌入式处理器的分类。

嵌入式系统中的处理器可以分成下面四大类类:

  • 嵌入式微处理器(MPU):它是由计算机中的CPU演变而来。它的特征是具有32位以上的处理器,具有较高的性能,价格也相对比较昂贵。但与计算机处理器不同的不是,它保留和嵌入式应用紧密相关的功能硬件,去除其他的冗余功能部分,这样就以最低的功耗和资源实现嵌入式应用的特殊要求;
  • 嵌入式微控制器(MCU):其典型代表是单片机;
  • 嵌入式DSP处理器(DSP):专门处理信号方面的处理器,其系统结构和指令算法方面进行了特殊的设计。具有很高的编译效率和代码执行能力。在数字滤波、FFT、谱分析等各种仪器上DSP获得了大规模的运用;
  • 嵌入式片上系统(SOC):一般来说称之为系统芯片。它是一个产品,是一个有专用目标的集成电路。其中包含完整系统和嵌入软件的全部。从广义上而言,SOC是一个微小型系统,如果说CPU是大脑,那么SOC就包括大脑、眼睛和鼻子等。

4、嵌入式交叉编译环境有哪两部分组成?简述交叉编译过程。

  • 本地编译可以理解为,在当前编译平台下,编译出来的程序只能放到当前平台下运行。平时我们常见的软件开发,都是属于本地编译;
  • 交叉编译可以理解为,在当前编译平台下,编译出来的程序能运行在体系结构不同的另一种目标平台上,但是编译平台本身却不能运行该程序。

以Linux-arm而言,我们在PC上开发的程序,只能够在Linux平台上能够运行,那我们的终极目的是能够在ARM产品上进行运行。对于这种跨平台而言,就需要交叉编译工具(arm-linux-gcc)进行编译,这样的代码才能够在ARM上进行运行。

至于为什么进行交叉编译?

  • Speed: 目标平台的运行速度往往比主机慢得多,许多专用的嵌入式硬件被设计为低成本和低功耗,没有太高的性能;
  • Capability: 整个编译过程是非常消耗资源的,嵌入式系统往往没有足够的内存或磁盘空间;
  • Availability: 即使目标平台资源很充足,可以本地编译,但是第一个在目标平台上运行的本地编译器总需要通过交叉编译获得;
  • Flexibility: 一个完整的Linux编译环境需要很多支持包,交叉编译使我们不需要花时间将各种支持包移植到目标板上。

整个编译过程包括了预处理、编译、汇编、链接等功能。既然有不同的子功能,那每个子功能都是一个单独的工具来实现,它们合在一起形成了一个完整的工具集。同时编译过程又是一个有先后顺序的流程,它必然牵涉到工具的使用顺序,每个工具按照先后关系串联在一起,这就形成了一个链式结构。

因此,交叉编译链就是为了编译跨平台体系结构的程序代码而形成的由多个子工具构成的一套完整的工具集,一般由编译器、连接器、解释器和调试器组成。同时,它隐藏了预处理、编译、汇编、链接等细节,当我们指定了源文件(.c)时,它会自动按照编译流程调用不同的子工具,自动生成最终的二进制程序映像(.bin)。

交叉编译的基本过程,用一个图来详解一下:


5、ARM的体系要点。


Linux系统相关知识

6、给了一个TCP/IP的通信例程,画出socket通信的流程图。

解答:网络编程中,一般是服务端程序先启动,等待客户端程序启动并连接,一般来说在服务端程序在一个端口上监听,直到有一个客户端的程序发来了请求。如果没有客户端程序发送请求,则服务端程序会一直阻塞。直到客户端程序发来请求为止。

在网络编程主要会有三个方面:基于TCP/IP协议的网络编程、基于UDP/IP协议的网络编程、并发服务器程序设计。

关于TCP客户服务器程序设计的基于TCP连接的socket编程流程图如下图:


演示了 Linux 下的代码,server.cpp 是服务器端代码,client.cpp 是客户端代码,要实现的功能是:客户端从服务器读取一个字符串并打印出来。

服务器端代码 server.cpp:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(){
    //创建套接字
    int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    //将套接字和IP、端口绑定
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));  //每个字节都用0填充
    serv_addr.sin_family = AF_INET;  //使用IPv4地址
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
    serv_addr.sin_port = htons(1234);  //端口
    bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    //进入监听状态,等待用户发起请求
    listen(serv_sock, 20);
    //接收客户端请求
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size = sizeof(clnt_addr);
    int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
    //向客户端发送数据
    char str[] = "Hello World!";
    write(clnt_sock, str, sizeof(str));

    //关闭套接字
    close(clnt_sock);
    close(serv_sock);
    return 0;
}

客户端代码 client.cpp:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
int main(){
    //创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    //向服务器(特定的IP和端口)发起请求
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));  //每个字节都用0填充
    serv_addr.sin_family = AF_INET;  //使用IPv4地址
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
    serv_addr.sin_port = htons(1234);  //端口
    connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

    //读取服务器传回的数据
    char buffer[40];
    read(sock, buffer, sizeof(buffer)-1);

    printf("Message form server: %s\n", buffer);

    //关闭套接字
    close(sock);
    return 0;
}

先编译 server.cpp 并运行:

[admin@localhost ~]$ g++ server.cpp -o server
[admin@localhost ~]$ ./server
|

正常情况下,程序运行到 accept() 函数就会被阻塞,等待客户端发起请求。 接下来编译 client.cpp 并运行:

[admin@localhost ~]$ g++ client.cpp -o client
[admin@localhost ~]$ ./client
Message form server: Hello World!
[admin@localhost ~]$

client 运行后,通过 connect() 函数向 server 发起请求,处于监听状态的 server 被激活,执行 accept() 函数,接受客户端的请求,然后执行 write() 函数向 client 传回数据。client 接收到传回的数据后,connect() 就运行结束了,然后使用 read() 将数据读取出来。 

需要注意的是: 

  • server 只接受一次 client 请求,当 server 向 client 传回数据后,程序就运行结束了。如果想再次接收到服务器的数据,必须再次运行 server,所以这是一个非常简陋的 socket 程序,不能够一直接受客户端的请求; 
  • 上面的源文件后缀为.cpp,是C++代码,所以要用g++命令来编译。

参考文章:socket编程

猜你喜欢

转载自blog.csdn.net/qq_38410730/article/details/80932932