2014年_大疆创新笔试题目_预处理

发布 | 2018-07-03 | 技术 | 975 浏览

一.编程基础

  1. 有如下CAT_s结构体定义,回答:
    (1)在一台64位得机器上,使用32位编译,Garfield变量占用多少内存空间?64位编译有事如何?(总分5分)
    (2)使用32位编译情况下,给出一种判断所使用机器大小端得方法?(总分5分)
    struct CAT_s{
    int ld;
    char Color;
    unsigned short Age;
    char*Name;
    void(*Jump)(void);
    }Garfield;

    答:1)32位系统是24字节

    首先清晰32位和64位数据类型的字节数 https://blog.csdn.net/zhangxinbin5/article/details/7929591
    
    不同的系统(如32位或16位系统):不同的系统下int等类型的长度是变化的,如对于16位系统,int的长度(字节为2,而在32位系统下,int的长度为4;因此如果结构体中有int等类型的成员,在不同的系统中得到的sizeof值是不相同的。  https://www.cnblogs.com/csdn-sunhuaqiang/articles/5903710.html
    
    编译器设置中的对齐方式:对齐方式的作用常常会让我们对结构体的sizeof值感到惊讶。
    对齐:为了能使CPU对变量进行高效快速的访问,变量的起始地址应该具有某些特性,即所谓的“对齐”。例如对于4字节的int类型变量,其起始地址应位于4字节边界上,即起始地址能够被4整除。变量的对齐规则如下(32位系统):
    
    请看下面的结构:
      struct MyStruct
      
      {
      
         double dda1;
      
         char dda;
      
         int type;
     
     };
    对结构MyStruct采用sizeof会出现什么结果呢?sizeof(MyStruct)为多少呢?也许你会这样求:
    sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13
    但是当在VC中测试上面结构的大小时,你会发现sizeof(MyStruct)为16。你知道为什么在VC中会得出这样一个结果吗?
    其实,这是VC对变量存储的一个特殊处理。为了提高CPU的存储速度,VC对一些变量的起始地址做了”对齐”处理。在默认情况下,VC规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。下面列出常用类型的对齐方式(vc6.0,32位系统)。
    
    类型    对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量)
    char    偏移量必须为sizeof(char)即1的倍数
    int     偏移量必须为sizeof(int)即4的倍数
    float   偏移量必须为sizeof(float)即4的倍数
    double  偏移量必须为sizeof(double)即8的倍数
    short   偏移量必须为sizeof(short)即2的倍数
    
    各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节VC会自动填充。同时VC为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。
    
    下面用前面的例子来说明VC到底怎么样来存放结构的。
    struct MyStruct
    {
        double dda1;
        char dda;
        int type;
    };
    
    为上面的结构分配空间的时候,VC根据成员变量出现的顺序和对齐方式,先为第一个成员dda1分配空间,其起始地址跟结构的起始地址相同(刚好偏移量0刚好为sizeof(double)的倍数),该成员变量占用sizeof(double)=8个字节;接下来为第二个成员dda分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为8,是sizeof(char)的倍数,所以把dda存放在偏移量为8的地方满足对齐方式,该成员变量占用 sizeof(char)=1个字节;接下来为第三个成员type分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为9(8+1),不是sizeof(int)=4的倍数,为了满足对齐方式对偏移量的约束问题,VC自动填充3个字节(这三个字节没有放什么东西),这时下一个可以分配的地址对于结构的起 始地址的偏移量为12(8+1+3),刚好是sizeof(int)=4的倍数,所以把type存放在偏移量为12的地方,该成员变量占sizeof(int)=4个 字节;这时整个结构的成员变量已经都分配了空间,总的占用的空间大小为:8 1 3 4=16,刚好为结构的字节边界数(即结构中占用最大空间的类型所占用的字节sizeof(double)=8)的倍数,所以没有空缺的字节需要填充。所以整个结构的大小为:sizeof(MyStruct)=8+1+3+4=16,其中有3个字节是VC自动填充的,没有放任何有意义的东西。
    
    下面再举个例子,交换一下上面的MyStruct的成员变量的位置,使它变成下面的情况:
    
    struct MyStruct
    {
        char dda;
        double dda1;
        int type;
    };
    
    这个结构占用的空间为多大呢?在VC6.0环境下,可以得到sizeof(MyStruc)为24。结合上面提到的分配空间的一些原则,分析下VC怎么样为上面的结构分配空间的。(简单说明)
    
    struct MyStruct
    
    {
      char dda; //偏移量为0,满足对齐方式,dda占用1个字节;
      double dda1; //下一个可用的地址的偏移量为1,不是sizeof(double)=8的倍数,需要补足7个字节才能使偏移量变为8(满足对齐方式),因此VC自动填充7个字节,dda1存放在偏移量为8的地址上,它占用8个字节。
      int type;//下一个可用的地址的偏移量为16,是sizeof(int)=4的倍数,满足int的对齐方式,所以不需要VC自动填充,type存放在偏移量为16的地址上,它占用4个字节。
    }; //所有成员变量都分配了空间,空间总的大小为1+7+8+4=20,不是结构的节边界数(即结构中占用最大空间的类型所占用的字节数sizeof (double)=8)的倍数,所以需要填充4个字节,以满足结构的大小为sizeof(double)=8的倍数。
    所以该结构总的大小为:sizeof(MyStruc)为1+7+8+4+4=24。其中总的有7+4=11个字节是VC自动填充的,没有放任何有意义的东西。

    答:2)判断机器大小端

    代码实现如下 https://blog.csdn.net/tanxuan231/article/details/48998051
    C语言union(联合体 共用体) http://blog.163.com/yx_xie2007/blog/static/1024642532011101411940162/
    union 
    {
        short s;
        char c[sizeof(short)];
    } un;
    
    un.s = 0x0102;
    if(sizeof(short) == 2) 
    {
        if(un.c[0] == 1 && un.c[1] == 2)
            printf("big-endian\n");
        else if (un.c[0] == 2 && un.c[1] == 1)
            printf("little-endian\n");
        else
            printf("unknown\n");
    } else
        printf("sizeof(short)= %d\n",sizeof(short));
    答案2:https://blog.csdn.net/bitboss/article/details/51247869
        关于大小端:(本人在这个大小端问题上栽倒好多次了)
    
            大端存储:高字节存储在低地址中,即高位先存;
            小端存储:低字节存储在高地址中,即低位先存;
        1.首先,这个概念是怎么来的,我百度了一下,在这里简单说明一下:
    
             出自Jonathan Swift在1726年写的讽刺小说《格列佛游记》(Gulliver's Travels)。小人国的内战就源于吃水煮鸡蛋时究竟是从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生6次叛乱,其中一个皇帝送了命,另一个丢了王位。 ——《程序员的自我修养》
        另外,我在其他地方看到其实最早的时候并不是叫大小端,而是叫 “高尾端” 和 “低尾端” ,高对应大,低对应小,相对于大小端来说还是后者更容易理解!
        // 解释一下就是将  0x11223344 看作字符串 “0x11223344\0”;尾端:44;
        // 高尾端就是 “11 22 33 44” 进行存储;(大端)
        地址      0    1   2   3
            低地址--->高地址:尾端44存在高地址;
        //  低尾端就是 “ 44 33 22 11”进行存储;(小端)
            地址      0   1   2   3
            低地址--->高地址:尾端44存在低地址;
    
  2. 描述下面 XXX 这个宏的作用。(总分 10分)

    define offsetof(TYPE,MEMBER)((size_t)&((TYPE*)0)->MEMBER)

    define XXX(ptr,type,member({consttypeof(((type)0)->member)__mptr=(ptr);(type)(char)__mptr – offsetof(type,member));})

    答:得到一个结构体中的成员,就可以得到这个结构体的指针  https://blog.csdn.net/u014114046/article/details/52122693
    define container_of(ptr, type, member) ({ \
    const typeof( ((type *)0)->member ) *__mptr = (ptr); \
    (type *)( (char *)__mptr - offsetof(type,member) );})
    
    作用是这样的:如果你获得了一个大结构体里面的一个成员的指针,那么通过这个宏,你就能获得这个结构体的指针。
    
    假设存在这么一个结构体: 
    Struct student{ 
    Type1 A; 
    Type2 B; 
    Type3 C; 
    }; 
    这里的type1,2,3就是我们平时常见的int,char之类的。之所以不写具体的类型是避免还要考虑内存中字节对齐等问题。 
    现在用这个结构体来声明一个对象:struct student demo; 
    那么在内存中demo的分布应该是这样的: 
    —————–0x6000 
    A 
    —————- 0x6008 
    B 
    —————–0x600a 
    C 
    —————-0x600e 
    上面的内存地址分布都是我随意写的,因为我并没有具体type1.2.3是什么类型。 
    那么container_of宏的三个参数分别代表什么呢? 
    Ptr :指向结构体内成员的指针,比如type2 * pointer=&B 
    Type :这个结构体类型 ,比如 struct student 
    Member:结构体成员名字,比如 B 
    代入宏看看是什么样的?
    
    define container_of(pointer, struct student, B) ({ \
    const typeof( (( struct student*)0)->B) *__mptr = (pointer);    \
    ( struct student*)( (char *)__mptr - offsetof( struct student,B) );})
    1
    2
    3
    初步的替换就是这样,但是这个里面还有宏offsetof(struct student,B),暂且不看先。 
    const typeof( (( struct student*)0)->B) *__mptr = (pointer); 这句代码的作用是声明了一个指针__mptr。Typeof的作用是获取变量的类型,所以__mptr的类型就是type2。然后把pointer指针的值赋给它,其实就是两个指针指向同一块内存,就是指向0x6008。 
    下面展开offsetof( struct student,B) 看看是什么?
    
    define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
    替换后得到:
    
    define offsetof(struct student, B) ((size_t) &((struct student*)0)->B)
    (struct student*)0)这句话的意思是把student结构体的起始地址强制制定为0,那么B的地址就是相对0地址的偏移量,然后取址,在上图中就是0x0008. 
    回看(char )__mptr这句话的意思:把这个指针强制类型转换为(char),也就是这个指针现在是字节指针了。为什么这么做呢?因为内存存储的最基本单位都是字节。这样的转换可以用来做加减法十分方便。 
    综上所述,0x6008-0x0008=0x6000.这个地址也就是demo结构体的起始地址了,然后转换为struct student类型的指针。
    
    #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
    先分析一下这个 宏的运行机理:
    一共4步 
    1. ( (TYPE *)0 ) 将零转型为TYPE类型指针; 
    2. ((TYPE *)0)->MEMBER 访问结构中的数据成员; 
    3. &( ( (TYPE *)0 )->MEMBER )取出数据成员的地址; 这个实现相当于获取到了 MEMBER 成员相对于其所在结构体的偏移,也就是其在对应结构体中的什么位置。
    4.(size_t)(&(((TYPE*)0)->MEMBER))结果转换类型。巧妙之处在于将0转 换成(TYPE*),结构以内存空间首地址0作为起始地址,则成员地址自然为偏移地址;
    
    &操作如果是对一个表达式,而不是一个标识符,会取消操作,而不是添加。
    比如&*a,会直接把a的地址求出来,不会访问*a。
    &a->member,会把访问a->member的操作取消,只会计算出a->member的地址
  3. 简述C函数:1)参数如何传递(__cdecl调用方式);2)返回值如何传递;3)调用后如何 返回到调用前的下一条指令执行。(总分10分)

    答:c函数参数(__cdecl调用方式)、返回值传递方式,调用后如何返回到调用前的下一条指令执行。
    参数传递 
    __cdecl 
    是C Declaration的缩写(declaration,声明),表示C语言默认的函数调用方法:所有参数从右到左依次入栈,由调用者负责把参数压入栈,最后也是由调用者负责清除栈的内容 
    __stdcall 
    是StandardCall的缩写,是C++的标准调用方式:所有参数从右到左依次入栈,由调用者负责把参数压入栈,最后由被调用者负责清除栈的内容
    
    返回值传递: 
    1.对于小于4个字节的数据函数将返回值存储在eax中。 
    2.5~8个字节对象的情况调用惯例都是采用eax和edx的联合返回方式进行。 
    3.大于8个字节的返回类型,用一下代码测试:
    
    typedef struct big_thing
    {
        char buf[128];
    }big_thing;
    
    big_thing return_test()
    {
        big_thing b;
        b.buf[] = 0;
        return b;
    }
    
    int main()
    {
        big_thing n = return_test();
    }
    
    首先main函数在栈额外开辟了一片空间,并将这块空间的一部分作为传递返回值的临时对象,这里称为temp
    将temp对象的地址作为隐藏参数传递个return_test函数
    return_test 函数将数据拷贝给temp对象,并将temp对象的地址用eax传出。
    return_test返回以后,mian函数将eax 指向的temp对象的内容拷贝给n。
    
    调用函数分配一块临时空间,将该空间的地址作为隐藏参数传递给被调函数,而后被调函数将数据拷贝到这块临时空间中,并将其地址用eax传出。
    
    如果返回值的类型的尺寸太大,c语言在函数的返回时会使用一个临时的栈上内存作为中转,结果返回值对象会被拷贝两次。因而不到万不得已,不要轻易返回大尺寸对象。 
    参考:函数返回值传递
    
    函数返回值为什么一般放在寄存器中,这主要是为了支持中断;如果放在堆栈中有可能因为中断而被覆盖。
    函数的堆栈帧 栈在程序运行中具有举足轻重的地位。最重要的,栈保存了一个函数调用所需要的维护信息,被称为堆栈帧(Stack Frame),一个函数(被调函数)的堆栈帧一般包括下面几个方面的内容: 
    (1) 函数参数,默认调用惯例情况下从右向左的顺序依次把参数压入栈中。由函数调用方执行。 
    (2) 函数的返回地址,即调用方调用此函数(如call func1)的下一条指令的地址。函数调用方(call指令)执行。 
    (3) 保存调用方函数的EBP寄存器,即将调用方函数的EBP压入堆栈,并令EBP指向此栈中的地址:pushl %ebp; movl %esp, %ebp。由被调函数执行。 
    (4) 上下文:保存在函数调用过程中需要保持不变的寄存器(函数调用方的),如ebx,esi,edi等。由被调函数执行。 
    (5) 临时变量,如非静态局部变量
    
  4. 在一个多任务嵌入式系统中,有一个 CPU 可直接寻址的 32位寄存器 REGn , 地址为 0x1F000010,编写一个安全的函数,将寄存器 REGn 的指定位反转(要求保持其他 bit 的值不变)。(总分 10分)

    答:代码如下:  知识点:http://blog.sina.com.cn/s/blog_60e96a410100mjd2.html
    #define REGn_DATA (*(unsigned int*)(0x1F000010))
    unsigned char  InversionREGn(unsigned char  bit)
    {
        REGn_DATA ^= 1<<bit;
    }
    
  5. 有10000个正整数, 每个数的取值范围均在1到1000之间, 变成找出从小到大排在第3400(从0开始算起)的那个数,将此数的值返回,要求不使用排序实现。(总分 10分)

    答:代码如下:https://blog.csdn.net/liuyangstudy/article/details/48861031
    
    #include <iostream>
    #include <algorithm>
    #include<time.h>
    using namespace std;
     
    #define range 1001
    int SaveArray[range] ={0};
    int num[10000];
     
    int main()
    {
        srand((int)time(0)); /*已当前时间为随机数产生的种子*/
        for(int i=0; i<10000; i++)
            num[i]= (rand()%1000); /*产生10000个0-1000之间的随机数*/
        for(int j=0; j<10000; j++)
            SaveArray[num[j]]++; /*讲随机数以其值为下标的方式存在下标范围为0-1000的数组中*/
        int sum = 0, seachnum = 0;
        for(int k=0; k<range; k++)
        {
            sum+= SaveArray[k];
            if(sum >= 3400)/*下标较小的数组成员,其代表的随机值也较小*/
            {
                seachnum = k;
                break;
            }
        }   
        cout <<seachnum<<endl;
        sort(num,num+10000);/*排序一遍,比较结果是否正确*/
        cout <<num[3399]<<endl;
    }
    Python:

    import random

    min_number = 1
    max_number = 1000

    range_number = 1001
    count_number = 10000
    SaveArray = [0] * range_number
    numberArray = [0] * count_number
    search_range_index = 3399
    sum = 0

    for i in range(count_number):

    numberArray[i] = random.randint(min_number,max_number)
    SaveArray[numberArray[i]]+=1

    for i in range(range_number):

    sum += SaveArray[i]
    if sum >= search_range_index:
        search_range_number = i
        break
    

    print "my_search_Index 3400:",search_range_number

    numberArray.sort()
    print "sys_sort_Index 3400:",numberArray[search_range_index]

二.嵌入式基本知识

  1. 简述处理器中断处理的过程(中断向量、中断保护现场、中断嵌套、中断返回等)。(总 分 10分)

        答:
        链接:https://www.nowcoder.com/questionTerminal/2e85847c06684c2faeaf8728f72e5045?toCommentId=426573
        来源:牛客网
    
        中断向量:
        中断服务程序的入口地址。
    
        请求中断
        当某一中断源需要CPU为其进行中断服务时,就输出中断请求信号,使中断控制系统的中断请求触发器置位,向CPU请求中断。系统要求中断请求信号一直保持到CPU对其进行中断响应为止。
    
        中断响应
        CPU对系统内部中断源提出的中断请求必须响应,而且自动取得中断服务子程序的入口地址,执行中断 服务子程序。对于外部中断,CPU在执行当前指令的最后一个时钟周期去查询INTR引脚,若查询到中断请求信号有效,同时在系统开中断(即IF=1)的情 况下,CPU向发出中断请求的外设回送一个低电平有效的中断应答信号,作为对中断请求INTR的应答,系统自动进入中断响应周期。
    
        保护现场
        主程序和中断服务子程序都要使用CPU内部寄存器等资源,为使中断处理程序不破坏主程序中寄存器的内容,应先将断点处各寄存器的内容压入堆栈保护起来,再进入的中断处理。现场保护是由用户使用PUSH指令来实现的。
        
        中断服务
        中断服务是执行中断的主体部分,不同的中断请求,有各自不同的中断服务内容,需要根据中断源所要完成的功能,事先编写相应的中断服务子程序存入内存,等待中断请求响应后调用执行。
        
        恢复现场
        当中断处理完毕后,用户通过POP指令将保存在堆栈中的各个寄存器的内容弹出,即恢复主程序断点处寄存器的原值。
        
        中断返回
        在中断服务子程序的最后要安排一条中断返回指令IRET,执行该指令,系统自动将堆栈内保存的 IP/EIP和CS值弹出,从而恢复主程序断点处的地址值,同时还自动恢复标志寄存器FR或EFR的内容,使CPU转到被中断的程序中继续执行
        
        中断嵌套
        是指中断系统正在执行一个中断服务时,有另一个优先级更高的中断提出中断请求,这时会暂时终止当前正在执行的级别较低的中断源的服务程序,去处理级别更高的中断源,待处理完毕,再返回到被中断了的中断服务程序继续执行,这个过程就是中断嵌套。
    
  2. 简述处理器在读内存的过程中, CPU 核、 cache 、 MMU 如何协同工作?画出 CPU 核、 cache 、 MMU 、内存之间的关系示意图加以说明(可以以你熟悉的处 理器为例)。(总分 10分)
    图1.jpg

        答:CPU访问内存时的硬件操作顺序,各步骤在图中有对应的标号:http://blog.sina.com.cn/s/blog_a15768c70102x7zv.html
        
    
        1:CPU内核(图1中的ARM)发出VA请求读数据,TLB(translation lookaside buffer)接收到该地址,那为什么是TLB先接收到该地址呢?因为TLB是MMU中的一块高速缓存(也是一种cache,是CPU内核和物理内存之间的cache),它缓存最近查找过的VA对应的页表项,如果TLB里缓存了当前VA的页表项就不必做translation table walk了,否则就去物理内存中读出页表项保存在TLB中,TLB缓存可以减少访问物理内存的次数。
        2:页表项中不仅保存着物理页面的基地址,还保存着权限和是否允许cache的标志。MMU首先检查权限位,如果没有访问权限,就引发一个异常给CPU内核。然后检查是否允许cache,如果允许cache就启动cache和CPU内核互操作。
        3: 如果不允许cache,那直接发出PA从物理内存中读取数据到CPU内核。
        4:如果允许cache,则以VA为索引到cache中查找是否缓存了要读取的数据,如果cache中已经缓存了该数据(称为cache hit)则直接返回给CPU内核,如果cache中没有缓存该数据(称为cache miss),则发出PA从物理内存中读取数据并缓存到cache中,同时返回给CPU内核。但是cache并不是只去CPU内核所需要的数据,而是把相邻的数据都取上来缓存,这称为一个cache line。ARM920T的cache line是32个字节,例如CPU内核要读取地址0x30000134~0x3000137的4个字节数据,cache会把地址0x30000120~0x3000137(对齐到32字节地址边界)的32字节都取上来缓存。
    

三.基本通信知识

  1. 请说明总线接口 USRT 、 I2C 、 USB 的异同点(串/并、速度、全/半双工、 总线拓扑等)。 (总分 5分)
    答: http://blog.sina.com.cn/s/blog_a15768c70102x7z7.html

        https://blog.csdn.net/contiune/article/details/53490698
        UART 通用异步串行口,速率不快,可全双工,结构上一般由波特率产生器、UART发送器、UART接收器组成,硬件上两线,一收一发。
        SPI 高速同步串行口,高速,可全双工,收发独立,同步接口,可实现多个SPI设备互联,硬件3~4线。
        I2C 双向、两线、串行、多主控接口标准。速率不快,半双工,同步接口,具有总线仲裁机制,非常适合器件间近距离经常性数据通信,可实现设备组网。
        USB 通用串行总线,高速,半双工,由主机、hub、设备组成。设备可以与下级hub相连构成星型结构。
    
  2. 列举你所知道的 linux 内核态和用户态之间的通信方式并给出你认为效率最高的方式, 说明理由。(总分 5分)

        答:
        1)https://blog.csdn.net/wuruixn/article/details/24960935
        由于每种方法都可以找到大量的示例代码,同时还有详细的函数手册,我就不贴代码了。只列下相关的方法和一个链接。
        procfs
        netlink
        syscall
        IOCTL
        syscall的范围就广了,通过注册字符设备可以使用mmap和ioctl等来进行操作,要注意的是在内核态ioctl已经被废弃,现在应该使用unlocked_ioctl,需要自己来加锁。
        用户态通过系统暴露出来的系统调用来进行操作,如mmap,ioctl,open,close,read,write,内核态通过建立共享内存remap_pfn_range或者copy_to_user, copy_from_user来进行操作。
        选择哪种方式需要考虑是用户态单进程与内核态通信还是多进程的通信,还要考虑通信的数据量。根据不同的需求来使用不同的方法。
        PROCFS
        /proc目录是系统模拟出来的一个文件系统,本身并不存在于磁盘上,其中的文件都表示内核参数的信息,这些信息分两类,一类是可都可写的,这类参数都在“/proc/sys”目录下,另一类是只读的,就是“/proc/sys”目录之外的其他目录和文件,当然这只是一种惯例,实际在其他目录下建立可读写的/proc文件也是可以的。
        /proc文件系统是一个特殊的软件创建的文件系统, 内核用来输出消息到外界. /proc 下的每个文件都绑到一个内核函数上, 当文件被读的时候即时产生文件内容. 我们已经见到一些这样的文件起作用; 例如, /proc/modules, 常常返回当前已加载的模块列表.
        /proc 在 Linux 系统中非常多地应用. 很多现代 Linux 发布中的工具, 例如 ps, top, 以及 uptime, 从 /proc 中获取它们的信息. 一些设备驱动也通过 /proc 输出信息.
        在 /proc 下添加文件是不鼓励的. /proc 文件系统在内核开发者看作是有点无法控制的混乱, 它已经远离它的本来目的了(是提供关于系统中运行的进程的信息). 建议新代码中使信息可获取的方法是利用 sysfs. 如同建议的, 使用 sysfs 需要对 Linux 设备模型的理解.
        内核和用户空间进行通信,大概有如下几种方式可以考虑:
        采用内存映射的方式,将内核地址映射到用户态。这种方式最直接,可以适用大量的数据传输机制。这种方式的缺点是很难进行“业务控制”,没有一种可靠的机制保障内核和用户态的调动同步,比如信号量等都不能跨内核、用户层使用。因此内存映射机制一般需要配合一种“消息机制”来控制数据的读取,比如采用“消息”类型的短数据通道来完成一个可靠的数据读取功能。
        ioctl机制,ioctl机制可以在驱动中扩展特定的ioctl消息,用于将一些状态从内核反应到用户态。Ioctl有很好的数据同步保护机制,不要担心内核和用户层的数据访问冲突,但是ioctl不适合传输大量的数据,通过和内存映射结合可以很好的完成大量数据交换过程。但是,ioctl的发起方一定是在用户态,因此如果需要内核态主动发起一个通知消息给用户层,则非常的麻烦。可能需要用户态程序采用轮询机制不停的ioctl。
        其他一些方式比如系统调用必须通过用户态发起,proc方式不太可靠和实时,用于调试信息的输出还是非常合适的。
        通过前面的项目背景,我需要一种可以在内核态主动发起消息的通知方式,而用户态的程序最好可以采用一种“阻塞调用”的方式等待消息。这样的模型可以最大限度的节省CPU的调度,同时可以满足及时处理的要求,最终选择了netlink完成通信的过程。
    
    2)链接:https://www.nowcoder.com/questionTerminal/b0280112abd14cd28662c3cb2b2791b1
    来源:牛客网
    内核和用户空间进行通信,大概有如下几种方式可以考虑: 
    采用内存映射的方式,将内核地址映射到用户态。这种方式最直接,可以适用大量的数据传输机制。这种方式的缺点是很难进行“业务控制”,没有一种可靠的机制保障内核和用户态的调动同步,比如信号量等都不能跨内核、用户层使用。因此内存映射机制一般需要配合一种“消息机制”来控制数据的读取,比如采用“消息”类型的短数据通道来完成一个可靠的数据读取功能。 
    ioctl机制,ioctl机制可以在驱动中扩展特定的ioctl消息,用于将一些状态从内核反应到用户态。Ioctl有很好的数据同步保护机制,不要担心内核和用户层的数据访问冲突,但是ioctl不适合传输大量的数据,通过和内存映射结合可以很好的完成大量数据交换过程。但是,ioctl的发起方一定是在用户态,因此如果需要内核态主动发起一个通知消息给用户层,则非常的麻烦。可能需要用户态程序采用轮询机制不停的ioctl。 
    其他一些方式比如系统调用必须通过用户态发起,proc方式不太可靠和实时,用于调试信息的输出还是非常合适的
    3)https://blog.csdn.net/jxth152913/article/details/17921581

四.系统设计

有一个使用 UART 进行通信的子系统 X ,其中 UART0进行数据包接收和回复, UART1进行数据包转发。 子系统 X 的通信模块职责是从 UART0接收数据包, 如果 为本地数据包(receiver 为子系统 X ),则解析数据包中的命令码(2字节) 和数据域(0~128字节),根据命令码调用内部的处理程序,并将处理结果通过UART0回复给发送端,如果非本地数据包,则通过 UART1转发。

如果由你来设计子系统 X 的通信模块:
1) 请设计通信数据包格式,并说明各字段的定义;(总分 5分)
2) 在一个实时操作系统中,你会如何部署模块中的任务和缓存数据,画出任务 间的数据流视图加以说明;(总分 5分)
3) 你会如何设置任务的优先级,说说优缺点;(总分 5分)
4) 如果将命令码对应的处理优先级分为高、 低两个等级, 你又会如何设计; (总 分 5分)
答:这题是产品级别的了,不是很难,但是墨迹。

预处理标识符
# 空指令,无任何效果 
#include 包含一个源代码文件 
#define 定义宏 
#undef 取消已定义的宏 
#if 如果给定条件为真,则编译下面代码 
#ifdef 如果宏已经定义,则编译下面代码 
#ifndef 如果宏没有定义,则编译下面代码 
#elif 如果前面的#if给定条件不为真,当前条件为真,则编译下面代码 
#endif 结束一个#if……#else条件编译块 
#error 停止编译并显示错误信息

题:new与malloc的区别
参考网址:
http://blog.csdn.net/miss_acha/article/details/7279915
http://www.cnblogs.com/huhuuu/archive/2013/11/19/3432371.html

题:计算十六进制表示形式中1的个数
PS:唉,做错了,多加了1。对于较大的数,自己应该首先用简单的数验证一下的,而不至于算错!

题:解决hash冲突的方法?

与DES、AES加密方式相比,RSA、ECC加密方式属于非对称加密(答对)?
参考文献:http://www.willrey.com/support/ssl_DES.html
BigEndian形式为 43981,16进制无符号数占2个字节,存储在LittleEndian中的形式为_____?
参考
Big Endian 10110100
Little Endian 00101101
实际上,由于CPU存储数据操作的最小单位是一个字节,其内部的比特序是什么样对我们的程序来说是一个黑盒子。也就是说,你给我一个指向0xB4这个数的指针,对于big endian方式的CPU来说,它是从左往右依次读取这个数的8个比特;而对于little endian方式的CPU来说,则正好相反,是从右往左依次读取这个数的8个比特。而我们的程序通过这个指针访问后得到的数就是0xB4,字节内部的比特序对于程序来说是不可见的,其实这点对于单机上的字节序来说也是一样的。

那可能有人又会问,如果是网络传输呢?会不会出问题?是不是也要通过什么函数转换一下比特序?嗯,这个问题提得很好。假设little endian方式的CPU要传给big endian方式CPU一个字节的话,其本身在传输之前会在本地就读出这个8比特的数,然后再按照网络字节序的顺序来传输这8个比特,这样的话到了接收端不会出现任何问题。而假如要传输一个32比特的数的话,由于这个数在littel endian方存储时占了4个字节,而网络传输是以字节为单位进行的,little endian方的CPU读出第一个字节后发送,实际上这个字节是原数的LSB,到了接收方反倒成了MSB从而发生混乱。

大疆编程题
1.有关矩阵的操作,子矩阵增值与查询其和。
2.考察完全二叉树,给出完全二叉树的顶点数,求出其深度与叶子节点的数目。
3.长度为n的方格,刷3种颜色的颜料,相邻的方格颜料颜色不能相同,且首尾方格颜色不能相同。每个方格必须涂色。计算一共有多少种涂色方式。
4.问答题 集群网关分发请求至处理服务器的方法及其优缺点。

注意事项:要有大局观,该舍弃的还是要舍弃,不要在一道编程题上占用超过30分钟的时间。当你思考了15分钟,还没有好的解决方式的时候,毅然舍弃!总体来说,大疆的题目不算太难。尤其是编程题,思路很清晰,不涉及到太复杂的算法。只有第三题可能涉及到递归操作(软肋)。其中第二题有关完全二叉树的操作,只需要知道完全二叉树的四个重要性质就对了,借助数学运算就可将问题解决。

性质1 在二叉树的第i层上至多有2^(i-1)个节点(i>=1).
性质2 深度为k的二叉树至多有(2^k)-1个节点(k>=1).
性质3 对任何一棵二叉树T,若叶子节点数为m,度为2的节点数为n,则m=n+1.
性质4 具有n个节点的完全二叉树的深度为log2n(向下取整)+1。

标签
没有标签

© 著作权归作者所有

本文由 豆末 创作,采用 知识共享署名4.0 国际许可协议进行许可,本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。

吐槽一下吧

*选项为必填