参考:嵌入式软件工程师面试遇到的经典题目
作者:一只青木呀
发布时间: 2020-11-04 23:43:16
网址:https://blog.csdn.net/weixin_45309916/article/details/109499825
目录
- 1、找错误
- 2、下面的代码输出是什么,为什么?
- 3、C语言编译时动态链接和静态链接得区别是什么?
- 4、C语言关键字static的作用是什么?
- 5、分别说明一下三个变量声明得含义:
- 6、简述TCP/IP镞包含哪些分段,每一层有哪些常用协议?
- 7、从在浏览器地址栏中输入www.baidu.com到看到百度首页,这个过程中间经历了什么?都涉及到哪些网络协议?
- 8、编写strcat函数
- 9、使用C语言中的#define来定义一个常量来表示一年有多少秒?
- 10、实现把字符串转化成整数
- 11、写一个程序验证系统的大小端存储格式</h1>
- 12、如何判断一个byte数据中有多少bit为1?
- 13、 C语言中关键字volatile的含义
- 14、进程间通信的方式有哪些?
- 15、堆和栈的区别
- 16、分别给出bool,int,float,指针变量 与“零值”比较的 if 语句(假设变量名为var)
- 17、 进程和线程的区别
- 18.写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。
- 19.预处理器标识#error的目的是什么?
- 20. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?
- 21. 用变量a给出下面的定义
- 22. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码 ,第一个设置a的bit 3,第二个清除a的bit 3。在以上两个操作中,要保持其它位不变。
- 23. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa55。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。
- 24. C语言同意一些令人震惊的结构,下面的结构是合法的吗,如果是它做些什么?
- 25. 整型数组作为参数传递时,无法在子函数中获得其长度!只有字符串可以,因为它有一个尾巴标识(‘\0’)!所以,整型的数组长度,必须与数组名一同传递到子函数才可以!
- 26.unsigned int compzero = ~0;与unsigned int compzero = 0xFFFF; 的区别
- 27.使用#define和typedef 来声明一个已经存在的数据类型的同义字的区别
- 28.linux中断处理的上半部和下半部
- 29.嵌入式设备,为加快启动速度,可以做哪些方面的优化?
- 30.PSRAM、SDRAM、DDR、DDR2的时序特性?
- 31.驱动中操作物理绝对地址为什么要先ioremap?
1、找错误
char * s1="hello";
char * s2="world";
char * s3=strcat(s1,s2);
这样做对吗,如果不对请说明原因。
解答:
不对,s1与s2都为常量指针,其内容不可修改,运行就会产生段错误。
s1可以是一个数组,注意给足够容量大的数组。
严格地说,C中是没有字符串变量的,一般采用字符数组或字符指针的方式才实现字符串的操作,二者在使用中有诸多类似,但并不是完全等价的,注意下面两句的区别:
char a[] ="Hello world";//存放在栈中,可以修改a数据组的任意值
char*s ="Hello world";//指针s存放在栈中,字符串常量在代码段,不可修改
下面的代码有何问题?
#include<stdio.h>
#include<string.h>
#include<stdlib.h>void test1()
{char string1[10];char* str1 = "0123456789";strcpy( string1, str1 );printf("%s",string1);}int main()
{test1();return 0;
}
字符串strl的末尾是以’ \0 ’结尾的,所以他的长度是11,而string的长度不够。。。。
但是我自己写了这样的程序,他是可以拷贝的。。。。
改成9的话,编译通过,运行出错!
*** stack smashing detected ***: <unknown> terminated
Aborted (core dumped)
2、下面的代码输出是什么,为什么?
#include<stdio.h>
#include<string.h>
#include<stdlib.h>void foo(void){long int ret = 0;unsigned int a=6;int b=-20;(a+b>6)?puts(">6"):puts("<6");ret = a+b;printf("%ld\n",ret);printf("%d\n",a+b);
}int main()
{foo();return 0;
}
输出结果:
>6
4294967282
-14
输出“>6”,应为无符号数和有符号相加,①有符号的整形数会转化成一个无符号的整型数;②而且负的有符号整型数转换后会变得非常大,所以相加会大于6。
3、C语言编译时动态链接和静态链接得区别是什么?
动态库:
1、链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序可以共用,节省内存。
2、程序升级简单,因为app里面没有库的源代码,升级之后只要库的名字不变,函数名以及参数不变,只是实现做了优化,就能加载成功。
3、. 加载速度比静态库慢
4、发布程序需要提供依赖的动态库
静态库:
- 1、静态库被打包到程序中加载速度快
- 2 、发布程序无需提供静态库,应为已经在app中,移植方便
- 3 、 链接时完整的拷贝至可执行文件中,被多次使用就会有多次冗余拷贝
- 4、 更新,部署,发布麻烦
4、C语言关键字static的作用是什么?
- ①隐藏作用, 可以在不同的文件中定义同名变量和同名函数。
- ②对于变量来说, 保持变量持久, 静态数据区的变量会在程序刚刚运行时就完成初始化, 也是唯一一次初始化; 储存在静态数据区, 静态存储区只有两种变量(全局变量和 static 静态变量) 。
- ③默认初始化为 0x00,和全局变量一样的属性, 减少程序员的工作量。
5、分别说明一下三个变量声明得含义:
int const *p;int * const p;int const *p const;
int const*p=const int *p; const修饰的是指针 p,表示指针p的值不能改变,而p(即地址)是可以改变的;
int * const p; const修饰的是p(即地址)是常量,不可改变,但是*p的值可以改变。
int const *p const ; 上面两种情况兼得,表示只读,其地址以及地址中的值都不可改变
6、简述TCP/IP镞包含哪些分段,每一层有哪些常用协议?
应用层:http dns telnet ftp TFTP 。。。。
传输层:tcp udp
网络层:ip ICMP ARP rarp
数据链路层:ethnet ethnet2 802.3 ppp fr x.25 hdlc
物理层:比特流
7、从在浏览器地址栏中输入www.baidu.com到看到百度首页,这个过程中间经历了什么?都涉及到哪些网络协议?
按照时间顺序:
1.客户端浏览器获取用户在地址栏输入的域名。
2.客户端浏览器将域名发送给DNS域名系统,请求解析。
3.DNS解析域名得到相应的IP,返回给客户端浏览器。
4.客户端浏览器根据IP向服务器发起TCP三次握手,建立TCP连接。
5.客户端浏览器向服务器发送HTTP请求,请求百度首页。
6.服务器通过HTTP响应向客户端浏览器返回百度首页文件。
7.释放TCP连接。
8.客户端浏览器解析HTML文件,根据文件内容获取CSS、JS等资源文件,将页面渲染展示给用户。
TCP/IP五层模型中网络层及以上用到的协议:
1.应用层:HTTP、DNS、HTTPS
2.传输层:TCP、UDP
3.网络层:IP、ARP
8、编写strcat函数
2.strcat函数原型char *my_strcat(char *dest,const char *src) //将源字符串加const,表明其为输入参数
{char *strDest=dest;assert(dest!=NULL && src!=NULL); //对源地址和目的地址加非0断言//若使用while(*Dest++),则会出错,指向'\0'之后,会出现dest++,则指向了个'\0'的下一个位置,while(*dest !='\0'){dest++; //循环体内的++可以使指向字符串结束标志'\0'}while((*dest++=*src++)!='\0');return strDest;
}
之后会问为什么要char *的返回值:
主要是为了实现链式表达式。直接返回char *的手段而不是返回void就是为了后来函数调用者方便而设计的,不用你这么麻烦用上述方法去使用了,而直接可以使用拷贝后的dest字符串了,这种方便的实现方法,看起来就像链子链在一起的,所以称为链式表达式。
如 strcpy(buf, strcat(dest, src) )
9、使用C语言中的#define来定义一个常量来表示一年有多少秒?
#define SECONDS_PER_YEAR (unsigned long)(365 * 24 * 60 * 60)
一定要加括号,宏定义只是替换,不加括号会出错。。。。
10、实现把字符串转化成整数
int my_atoi(char *str)
{int sum=0,status=1;if(str == NULL){return 0;}if(*str == '-'){status = -1;str++;}while((*str)!='\0'){sum=sum*10+ ((*str)-'0');str++;}return sum*status;}
11、写一个程序验证系统的大小端存储格式
/*方法1*/
typedef union {int i;char c;
}my_union;int checkSystem1(void)
{my_union u;u.i = 1;return (u.i == u.c);
}
/*方法2*/
int checkSystem2(void)
{int i = 0x12345678;char *c = &i;//字符串地址给到指针,指针按照地址大小顺序遍历输出return ((c[0] == 0x78) && (c[1] == 0x56) && (c[2] == 0x34) && (c[3] == 0x12));//指针可以写成数组的形式
}int main(void)
{ if(checkSystem2()) printf("little endian\n"); else printf("big endian\n"); return 0;
12、如何判断一个byte数据中有多少bit为1?
int select(unsigned char data)
{int count=0,i=1;while(data!=0){count += (data & i);data >>= 1;}return count;
}
13、 C语言中关键字volatile的含义
volatile 的意思是“易失的,易改变的”。这个限定词的含义是向编译器指明变量的内容可能会由于其他程序的修改而变化。
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份
一般说来,volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
一个参数既可以是const还可以是volatile吗?解释为什么。
是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
14、进程间通信的方式有哪些?
1.无名管道( pipe ): 管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
2.有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
3.消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
4.信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
5.信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
6.共享内存( shared memory ) : 共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
7.套接字( socket ) : 套解字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
15、堆和栈的区别
①、申请方式不同:栈由系统自动分配,堆是由人为自行开辟(malloc,new)
② 、申请的大小不同:栈是从高地址像低地址分配的,分配空间较小,堆是由地址向高地分配的,空间较大
③ 、申请效率不同:栈由系统分配,分配速度较快,堆一般较慢
④ 、栈是连续的地址空间,堆不是连续的地址空间,很容易产生内存碎片,浪费内存。
16、分别给出bool,int,float,指针变量 与“零值”比较的 if 语句(假设变量名为var)
Bool: if(!var)Int : if(var==0)Float: const float val=0.00000001If((var >= -val) && (var <= val))
17、 进程和线程的区别
进程和线程的根本区别是进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位。另外区别还有资源开销、包含关系、内存分配、影响关系、执行过程等。
-
资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
-
包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
-
内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的。
-
影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
-
执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行。
进程和线程的根本区别是进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位
18.写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。
#define MIN(A, B) ((A) <= (B)? (A) : (B))
这个测试是为下面的目的而设的:
(1)标识#define在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline)操作符 变为标准C的一部分,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为 了能达到要求的性能,嵌入代码经常是必须的方法。
(2)三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if -then-else更优化的代码,了解这个用法是很重要的。
(3)懂得在宏中小心地把参数用括号括起来。
(4)我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?
least = MIN(*p++, b);
宏定义#define MIN(A, B) ((A) <= (B) ? (A) : (B))对MIN(*p++, b)的作用结果是:
((*p++) <= (b) ? (*p++) : (b))这个表达式会产生副作用,指针p会作两次++自增操作。
19.预处理器标识#error的目的是什么?
这问题对区分一个正常的伙计和一个书呆子是很有用的。只有书呆子才会读C语言课本的附录去找出象这种问题的答案。当然如果你不是在找一个书呆子,那么应试者最好希望自己不要知道答案。
编译程序时,只要遇到 #error 就会跳出一个编译错误当程序比较大时,往往有些宏定义是在外部指定的(如makefile),或是在系统头文件中指定的,当你不太确定当前是否定义了 XXX 时,就可以改成如下这样进行编译:
#ifdef XXX
#error “XXX has been defined”
#else
…
#endif
这样,如果编译时出现错误,输出了XXX has been defined,表明宏XXX已经被定义了。
20. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?
这个问题用几个解决方案。我首选的方案是:
while(1)
{
}
一些程序员更喜欢如下方案:
for(;;)
{
}
这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的基本原理。如果他们的基本答案是:“我被教着这样做,但从没有想到过为什么。”这会给我留下一个坏印象。
第三个方案是用 goto
Loop:
...
goto Loop;
应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。
21. 用变量a给出下面的定义
(1)一个整型数(An integer): int a;
(2)一个指向整型数的指针(A pointer to an integer): int *a;
(3)一个指向指针的的指针,它指向的指针是指向一个整型数(A pointer to a pointer to an
integer): int **a;
(4)一个有10个整型数的数组(An array of 10 integers) :int a[10];
(5)一个有10个指针的数组,该指针是指向一个整型数的(An array of 10 pointers to
integers): int *a[10];
(6)一个指向有10个整型数数组的指针(数组指针):int (*a)[10];int *p[n](指针数组)
(7)指向函数的指针,该函数有一个整型参数并返回一个整型数(函数指针):int (*a)(int);
(8)一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整
型数( An array of ten pointers to functions that take an integer argument and return an
integer ): int (*a[10])(int)。
人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。 但是当我被面试的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这段时间里,我确定我知道这个问题的答案。应试者如果不知道所有的答案(或至少大部分答案),那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么他又能为什么做准备呢?
22. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码 ,第一个设置a的bit 3,第二个清除a的bit 3。在以上两个操作中,要保持其它位不变。
对这个问题有三种基本的反应
(1)不知道如何下手。该被面者从没做过任何嵌入式系统的工作。
(2)用bit fields。bit fields是被扔到C语言死角的东西,它保证你的代码在不同编译器之间
是不可移植的,同时也保证了的你的代码是不可重用的。我最近不幸看到Infineon为其较复杂的通信芯片写的驱动程序,它用到了bit fields因此完全对我无用,因为我的编译器用其它的方式来实现bit fields的。从道德讲:永远不要让一个非嵌入式的家伙沾实际硬件的边。
(3)用#define和bit masks操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方案如下:
#define BIT3 (0x1 < <3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3;
}
一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的我希望看到几个要点:说明常数、|=和&=~操作。
23. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa55。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。
这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
一个较晦涩的方法是:
*(int * const)(0x67a9) = 0xaa55;
即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。
24. C语言同意一些令人震惊的结构,下面的结构是合法的吗,如果是它做些什么?
int a = 5, b = 7, c;
c = a+++b;
这个问题将做为这个测验的一个愉快的结尾。不管你相不相信,上面的例子是完全合乎语法的。问题是编译器如何处理它?水平不高的编译作者实际上会争论这个问题,根据最处理原则,编译器应当能处理尽可能所有合法的用法。因此,上面的代码被处理成:
c = a++ + b;
因此, 这段代码持行后a = 6, b = 7, c = 12。
如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。我发现这个问题的最大好处是:这是一个关于代码编写风格,代码的可读性,代码的可修改性。
25. 整型数组作为参数传递时,无法在子函数中获得其长度!只有字符串可以,因为它有一个尾巴标识(‘\0’)!所以,整型的数组长度,必须与数组名一同传递到子函数才可以!
26.unsigned int compzero = ~0;与unsigned int compzero = 0xFFFF; 的区别
unsigned int zero = 0; unsigned int compzero = 0xFFFF;
对于一个int型不是16位的处理器为说,上面的代码是不正确的。应编写如下:
unsigned int compzero = ~0;
unsigned int compzero = 0xFFFF; 只写了2个字节,16位的才符合 。
32位的可以写:
unsigned int compzero = 0xFFFFFFFF;
但unsigned int compzero = ~0;更安全,不管有多少位,直接取反,把所有的0都变成1了。
27.使用#define和typedef 来声明一个已经存在的数据类型的同义字的区别
Typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。例如,思考一下下面的例子:
#define dPS struct s *typedef struct s * tPS;
以上两种情况的意图都是要定义dPS 和 tPS 作为一个指向结构s指针。哪种方法更好呢?(如果有的话)为什么?
这是一个非常微妙的问题,任何人答对这个问题(正当的原因)是应当被恭喜的。答案是:typedef更好。思考下面的例子:
dPS p1,p2;
tPS p3,p4;
第一个扩展为:struct s * p1, p2;
由于是宏替换,上面的代码定义p1为一个指向结构体的指针,p2为一个实际的结构,这也许不是你想要的。
第二个例子正确地定义了p3 和p4 两个指针。
struct s *p3,*p4;
28.linux中断处理的上半部和下半部
设备的中断会打断内核中进程的正常调度和运行,系统对更高吞吐率的追求势必要求中断服务程序尽可能地短小精悍。但是,这个良好的愿望往往与现实并不吻合。在大多数真实的系统中,当中断到来时,要完成的工作往往并不会是短小的,它可能要进行较大量的耗时处理。
为了在中断执行时间尽可能短和中断处理需完成大量工作之间找到一个平衡点,Linux 将中断处理程序分解为两个半部:顶半部(top half)和底半部(bottom half)。
顶半部完成尽可能少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态并清除中断标志后就进行“登记中断”的工作。“登记中断”意味着将底半部处理程序挂到该设备的底半部执行队列中去。这样,顶半部执行的速度就会很快,可以服务更多的中断请求。
现在,中断处理工作的重心就落在了底半部的头上,它来完成中断事件的绝大多数任务。底半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断,这也是底半部和顶半部的最大不同,因为顶半部往往被设计成不可中断。底半部则相对来说并不是非常紧急的,而且相对比较耗时,不在硬件中断服务程序中执行。
尽管顶半部、底半部的结合能够改善系统的响应能力,但是,僵化地认为 Linux设备驱动中的中断处理一定要分两个半部则是不对的。如果中断要处理的工作本身很少,则完全可以直接在顶半部全部完成。
其他操作系统中对中断的处理也采用了类似于Linux系统的方法,真正的硬件中断服务程序都应该尽可能短。
因此,许多操作系统都提供了中断上下文和非中断上下文相结合的机制,将中断的耗时工作保留到非中断上下文去执行。
Linux 系统实现底半部的机制主要有tasklet,工作队列和软中断。Linux 的中断处理分为两个半部,顶半部处理紧急的硬件操作,底半部处理不紧急的耗时操作。tasklet 和工作队列都是调度中断底半部的良好机制,tasklet 基于软中断实现。内核定时器也依靠软中断实现。内核中的延时是忙等待或者睡眠等待,为了充分利用CPU资源,使系统有更好的吞吐性能,在对延迟时间的要求并不是很精确的情况下,睡眠等待通常是值得推荐的。
29.嵌入式设备,为加快启动速度,可以做哪些方面的优化?
答:linux默认的安装内核相当庞大,为了保证系统的兼容性和灵活性,支持热插拔操作,内核启动时要进行大量的硬件检测和初始化工作,而嵌入式的硬件都是固定的,只需要选择需要的硬件驱动就可以,不需要全部的硬件驱动都检测;因此可以进行适当的裁剪内核达到缩小启动linux系统的目的;同时可以统计驱动模块的耗时时间,对耗时较长的模块驱动加以分析,优化。
30.PSRAM、SDRAM、DDR、DDR2的时序特性?
答:PSRAM,全称Pseudo static random access memory。指的是伪静态随机存储器。
SDRAM:Synchronous Dynamic Random Access Memory,同步动态随机存储器,同步是指 Memory工作需要同步时钟,内部的命令的发送与数据的传输都以它为基准;动态是指存储阵列需要不断的刷新来保证数据不丢失;随机是指数据不是线性依次存储,而是自由指定地址进行数据读写。
DDR=Double Data Rate双倍速率同步动态随机存储器。DDR SDRAM是Double Data Rate SDRAM的缩写,是双倍速率同步动态随机存储器的意思。
在同等核心频率下,DDR2的实际工作频率是DDR的两倍。这得益于DDR2内存拥有两倍于标准DDR内存的4BIT预读取能力。换句话说,虽然DDR2和DDR一样,都采用DDR2内存的频率了在时钟的上升延和下降延同时进行数据传输的基本方式,但DDR2拥有两倍于DDR的预读取系统命令数据的能力。也就是说,在同样100MHz的工作频率下,DDR的实际频率为200MHz,而DDR2则可以达到400MHz。
31.驱动中操作物理绝对地址为什么要先ioremap?
答:因为内核没有办法直接访问物理内存地址,必须先通过ioremap获得对应的虚拟地址。
ioremap将一个IO地址空间映射到内核的虚拟地址空间上去,便于访问。