1,volatile (防止编译器优化)
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份
应用实例1,并行设备的硬件寄存器
2,中断服务程序中访问非自动变量
3,多线程中同时被几个任务共享的变量;
2,中断服务程序
中断服务程序中 不能有参数,不能有返回值
中断服务程序中不能做浮点运算;
3,位的清零和置一
a |= (0x1<<n); 第n位写 1
a &= ~(0x1<<n) 第n位清零
4,当表达式中有 有符号和无符号的数运算时 ,编译器强制转换所有的数据为
无符号运算; 则负数转换成无符号之后,会变得很大;
5,#define只是简单的替换
typedef 是定义了一种新的数据类型;
#define sTR struct s *
typedef struct s * sTR
6,extern 是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字
extern int a; 变量的声明
变量的申明和定义的区别是 声明不需要为其申请内存,定义会为其申请内存空间
变量只能被定义一次,但 可以被申明多次
***:通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern 声明
7,C++调用C的函数时,在使用前加c的头文件,添加方式
extern "C"
{
#include "cexample.h"
}
8,以空间换时间 (空间指内存空间)
例如:字符串的赋值。
方法A,通常的办法:
#define LEN 32
char string1 [LEN];
memset (string1,0,LEN);
strcpy (string1,"This is an example!!"
方法B:
const char string2[LEN]="This is an example!"
char*cp;
cp=string2;
(使用的时候可以直接用指针来操作。)
从上面的例子可以看出,A 和B 的效率是不能比的。在同样的存储空间下,B 直接使用指针就可以操作了,而A 需要调用
两个字符函数才能完成。B 的缺点在于灵活性没有A 好。在需要频繁更改一个字符串内容的时候,A 具有更好的灵活性;
如果采用方法B,则需要预存许多字符串,虽然占用了 大量的内存,但是获得了程序执行的高效率。
函数和宏函数的区别就在于,宏函数占用了大量的空间,而函数占用了时间。大家要知道的是,
函数调用是要使用系统的栈来保存数据的,如果编译器里有栈检查选项,一般在函数的头会嵌
入一些汇编语句对当前栈进行检查;同时,CPU 也要在函数调用时保存和恢复当前的现场,
进行压栈和弹栈操作,所以,函数调用需要一些CPU 时间。而宏函数不存在这个问题。
宏函数仅仅作为预先写好的代码嵌入到当前程序,不会产生函数调用,所以仅仅是占用了空间,
在频繁调用同一个宏函数的时候,该现象尤其突出。
9,数学运算的魅力
1+2+...+100 = 100*(1+100)/2
345%32 = 345 - (345>>4<<4); 后者运算更快一些
10,#error
停止编译,并报错XXX;
perror() 输出错误信息。
11,int *p[10]; 不加括号,表示指针数组
int (*p)[10]; 加括号,表示数组指针
12,const意味着只读的意思
const int *a;
表示指向常整形数的指针 指针可以修改,指针指向的数据不能被修改
int * const a;
表示指向整形变量的一个常量指针,指针不可以被修改,指针指向的变量可以尅修改;
13,多任务OS 的核心是系统调度器,它使用任务控制块(TCB)来管理任务调度功能;
。TCB 包括任务的当前状态、优先级、要等待的事件或资源、任务程序码的起始地址、初始堆栈指针;
14,逆序打印字符串 用递归的方法
void print(char* p){
if(*p != 0){
p++ ;
print(p);
p--;
}
if(*p != 0)
printf("%c",*p);
}
void main(void){
char *p = "nihaoya";
print(p);
printf("\n");
return ;
}
15,*p++ 先执行*p 再执行p++
*++p 先执行++p 在执行*p
while(1){
if(i++ == 3)break; //直接跳出while循环;
}
while(1){
if(i++ == 3) continue; //跳过本次循环,进行下一次循环;
}
sizeof(字符串) 得到字符串的长度,包括‘\0’
strlen(字符串指针)
16,回调函数:
钩子函数:
可重入函数:函数体内不容许调用函数体外的变量(也就是不容许调用全局变量);
17,共享库的创建:
创建 xxx.c xxx.h文件,先编译成xxx.o 文件 $:gcc xxx.c -c
之后编译成 .so文件 $:gcc -shared xxx.o -o libxxx.so
使用共享库
在其他目录的一个文件中使用xxx.h里面的函数,需要加载刚才编译好的共享库路径 和头文件路径
***: $:main.c -l xxx(动态库的名字,没有lib) -L 指定路径(指定动态库的路径) -I 指定头文件路径
此时会生成 a.out文件
运行a.out出现找不到动态库路径,此时需要加载动态库到内存中,
使用$:export LD_LIBRARY_PATH=动态库的绝对路径
18,给一个绝对地址跳转到此地址执行;
typedef void (*P)(int,int);
P p;
p = (P)0x100000;
p(1,2);
或者{
指针: (void (*)(int,int,int))0x20008000
指针取值: *((void (*)(int,int,int))0x20008000)
到此地址去执行 (*((void (*)(int,int,int))0x20008000))(1,2,3);
}
给一个绝对地址,对此地址进行赋值
*(unsigned int*)0x100000 = 100;
19,将字符串"12345" 转换成 数字 12345 函数
atoi(const char* ptr)
20,#include <filename.h> 和 #include “filename.h” 有什么区别?
#include <filename.h> 编译器从标准库路径开始搜索 filename.h
#include “filename.h” 编译器从用户的工作路径开始搜索 filename.h
21,子函数申请的内存用一级指针无法传到main函数,必须用二级指针
Void GetMemory2(char **p, int num)
{
*p = (char *)malloc(num);
}
野指针是指定义之后使用,没有使用malloc申请;或者释放之后
22,assert 函数用来判断字符串是否为NULL?
23, bool,float,指针变量与“零值”比较的 if 语句。
Bool:
if(flag)
if(!flag)
//if(flag==TRUE),if(flag==1),if(flag==FALSE),if(flag==0), 这些属于不良风格,
不得分。
Float:
if((x>=-0.000001)&&(x<=0.000001))
//不可以将浮点变量用”==” , ”!=”与数字比较,应该设法转化成”<=” , ”>=”此
类形式,例如 if(x==0.0) if(x!=0.0),是错误的。
指针:
if(p==NULL)
If(p!=NULL)
//if(p==0),if(p!=0),if(p),if(!).不良风格
24, i+++j 等价于(i++)+j)
25, 头文件中的 ifndef/define/endif 干什么用的?
防止头文件被重复引用
26,交换两个参数 的宏
#define SWAP(a,b) (a)=(a)+(b);(b)=(a)-(b);(a)=(a)-(b);
27,strcpy 能把 strSrc 的内容复制到 strDest,为什么还要 char * 类型的返回值?
为了实现链式表达式。
28,const 是什么含意?
const 意味着“只读” 。
const int a; a 是一个常整型数;
int const a; a 是一个常整型数;
const int *a; a 是一个指向常整型数的指针(整型数是不可修改的,但指针可
以);
int * const a; a 是一个指向整型数的常指针(指针指向的整型数是可以修改
的,但指针是不可修改的);
int const * a const; a 是一个指向常整型数的常指针(指针指向的整型数是不
可修改的,同时指针也是不可修改的);
1). 关键字 const 的作用是为给读你代码的人传达非常有用的信息,实际上,
声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很
多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当
然,懂得用 const 的程序员很少会留下的垃圾让别人来清理的。)
2). 通过给优化器一些附加的信息,使用关键字 const 也许能产生更紧凑的代
码。
3). 合理地使用关键字 const 可以使编译器很自然地保护那些不希望被改变
的参数,防止其被无意的代码修改。简而言之,这样可以减少 bug 的出现。
29,对于一个非0的数据的定义
unsigned int a = 0xffff; 不好的习惯
unsigned int a = ~0; 良好的习惯,避免了编译器对处理器是十六位还是三十二位的判断;
30,main() { int a[5]={1,2,3,4,5};
int *ptr=(int *)(&a+1);
printf("%d,%d",*(a+1),*(ptr-1));
}
答案:2。5
*(a+1)就是a[1],*(ptr-1)就是a[4],执行结果是2,5 &a+1不是首地址+1,系统会认为加一个a数组的偏移,
是偏移了一个数组的大小(本例是5个int) int *ptr=(int *)(&a+1); 则ptr实际是&(a[5]),也就是a+5 原因如下: &a
是数组指针,其类型为 int (*)[5]; 而指针加1要根据指针类型加上一定的值,不同类型的指针+1之后增加的大小不同
a是长度为5的int数组指针,所以要加 5*sizeof(int) 所以ptr实际是a[5] 但是prt与(&a+1)类型是不一样的(这点很重
要) 所以prt-1只会减去sizeof(int*) a,&a的地址是一样的,但意思不一样,a是数组首地址,也就是a[0]的地址,&a是
对象(数组)首地址,a+1是数组下一元素的地址,即a[1],&a+1是下一个对象的地址,即a[5].
31,c和c++中的struct有什么不同?答案:c和c++中struct的主要区别是c中的struct不可以含有成员函数,而c++中的
struct可以。c++中struct和class的主要区别在于默认的存取权限不同,struct默认为public,而class默认为private
32, nor flash可以随机访问地址数据,nand flash 不可以随机访问数据;
nandflash 读写的基本单位是页,多个页组成一块,修改一页时必须将此页对应的块复制到缓冲区在修改,然后擦除块,
修改完将缓冲区中的块写入nandflash
norflash以字节为单位访问 nandflash以页为单位访问;
nandflash 有坏块现象;在初始化nandflash时必须对坏块进行检测,并标记;
nandflash的SLC和MLC的区别、
SLC: 每个存储单元只有1bit数据 存储速度比较快 价格贵 存储密度低
MLC: 每个存储单元可以有2bit数据 存储速度比较慢 价格便宜 存储密度高
norflash的读取速度比nand的快
nandflsh的写入速度比nor的快
位交换:所有flash都受位交换的困扰,nand更严重一些,出现位交换现象:一个bit位发生了反转或者被报告反转了
出现位反转就需要进行 错误探测/错误(EDC/ECC)校验算法;
注意:位反转和坏块是两码事,坏块是根据初始化的驱动程序将块标记,位反转是需要进行ECC校验算法来避免的;
33,iic总线 :两线式串行总线;
SCL时钟线 SDL数据线
起始信号 8bit的地址信号 等待应答信号 8bit的数据 等待应答信号 ... 结束信号
34,uart有两种模式:
轮训模式 中断模式
35,usb的四种传输方式
控制传输 批量传输 同步传输 中断传输
36, IOS的七层网络结构图
应用层 [表示层 会话层] 传输层(TCP/UDP) 网络层(IP) 数据链路层 物理层
TCP---传输控制协议,提供的是面向连接、可靠的字节流服务;
当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。
TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
UDP---用户数据报协议,是一个简单的面向数据报的运输层协议。
UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。
由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。
37,可重入函数、不可重入函数、回调函数
在实时系统的设计中,经常会出现多个任务调用同一个函数的情况。如果这个函数不幸被设计成为不可重入的函数的话,
那么不同任务调用这个函数时可能修改其他任务调用这个函数的数据,从而导致不可预料的后果。
那么什么是可重入函数呢?所谓可重入函数是指一个可以被多个任务调用的过程,
任务在调用时不必担心数据是否会出错。不可重入函数在实时系统设计中被视为不安全函数。
满足下列条件的函数多数是不可重入的:
1) 函数体内使用了静态的数据结构;
2) 函数体内调用了malloc()或者free()函数;
3) 函数体内调用了标准I/O函数。
要想函数不可重入,需注意一下几点:
1)不要使用全局变量;
2)不要使用静态变量;
3)在和硬件发生交互的时候,注意关闭中断,交互完毕之后打开中断;
4)不能调用其他任何不可重入函数;
38,linux中断操作:
linux内核中断要求尽可能快一点完成中断任务,所以把紧急的任务交给了中断顶半部,
不是紧急的任务交给中断底半部
中断底半部的任务实现主要有
软中断
tasklet
工作队列
在中断的顶半部注册底半部的处理函数,在中断的底半部就会实现;
39, Uboot启动过程:bootloader kernel正确挂接根文件系统以后才结束
1》硬件初始化:前4K是在cpu内部内存中运行的,即SRAM
1)主要作用设置异常向量表,进入管理模式,屏蔽IRQ,FIQ中断,
关闭看门狗,关闭中断,关MMU,cache,时钟,内存;
2)加载U-Boot第二阶段代码到RAM空间 //选择从哪里启动,是nor flash还是nand flash
3)软件初始化:
设置堆栈
清空bss
4)跳转到第二阶段代码入口
(搬运的第二段代码的作用)
初始化本阶段使用的硬件设备
检测系统内存映射
将内核从Flash读取到RAM中
为内核设置启动参数
调用内核
2》扩展内容:
LOGO显示
组合按键:实现一些特定的功能,如刷机
3》引导系统
加载操作系统内核,并且给内核传递参数,并且正确挂接完根文件系统以后,uboot才算结束。
40,进程与线程的区别
在一个多任务环境中下面的概念可以帮助我们理解两者间的差别:
进程间是独立的,这表现在内存空间,上下文环境;线程运行在进程空间内。 一般来讲(不使用特殊技术)
进程是无法突破进程边界存取其他进程内的存储空间;而线程由于处于进程空间内,所以同一进程所产生的
线程共享同一内存空间。 同一进程中的两段代码不能够同时执行,除非引入线程。线程是属于进程的,当进
程退出时该进程所产生的线程都会被强制退出并清除。线程占用的资源要少于进程所占用的资源。 进程和线
程都可以有优先级。在线程系统中进程也是一个线程。可以将进程理解为一个程序的第一个线程。
线程是指进程内的一个执行单元,也是进程内的可调度实体.与进程的区别:(1)地址空间:进程内的一个执行单元;
进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间;(2)进程是资源分配和拥有的单
位,同一个进程内的线程共享进程的资源(3)线程是处理器调度的基本单位,但进程不是.(4)二者均可并发执行
41,关于extern 在C++中调用C的函数
在C++ 程序中调用被 C编译器编译后 的函数,为什么要加 extern “C”?
答:C++语言支持函数重载,C语言不支持函 数重载。函数被C++编译后在库中的名字与C语言的不同。 假设某个函数的原型 为: void foo(int x, int y);该函数被C编译器编译后 在库中的名字为_foo,而C++编译器则会产生 像_foo_int_int之类的名字。
C++提供了C连接交换指定符号extern“C”来解决名字匹 配问题。
42,关于sprintf和sscanf函数的用法
sprintf是吧数字转换成字符串,例如
char buf[100];
sprintf(buf,"%d",12345); //sprintf(buf,"ttysac%d",i); 循环生成设备节点
printf("buf = %s\n",buf);
scanf的使用
char buf[100]="12345";
int num;
scanf(buf,"%d",&num);
printf("num = %d\n",num);
43,在ARM中如何判断一个立即数是否合法
- 1,判断一个数是否符合8位位图的原则, 首先看这个数的二进制表示中1的个数是否不超过8个. 如果不超过8个,
- 再看这n个1(n<=8)是否能同时放到8个二进制位中, 如果可以放进去, 再看这八个二进制位是否可以循环右移
- 偶数位得到我们欲使用的数. 如果可以, 则此数符合8位位图原理, 是合法的立即数. 否则, 不符合.
- 2,无法表示的32位数, 只有通过逻辑或算术运算等其它途径获得了. 比如0xffffff00, 可以通过0x000000ff按位取反得到.
因此以后的编程中, 时刻检查用到的第二操作数是否符合8位位图是一件千万不能疏忽的事.
44, 应聘驱动工程师会遇到的问题:(注:此为自己记录,答案没有找全)
1. 简述一下驱动的架构
2. HAL层是如何与底层进行通信的
3. Android中sensor是如何上报数据的
4. 设备总线的理解
5. 屏的种类
44,request_threaded_irq()
为什么要提出中断线程化?
在 Linux 中,中断具有最高的优先级。不论在任何时刻,只要产生中断事件,内核将立即执行相应的中断
处理程序,等到所有挂起的中断和软中断处理完毕后才能执行正常的任务,因此有可能造成实时任务得不
到及时的处理。中断线程化之后,中断将作为内核线程运行而且被赋予不同的实时优先级,实时任务可以
有比中断线程更高的优先级。这样,具有最高优先级的实时任务就能得到优先处理,即使在严重负载下仍
有实时性保证。but,并不是所有的中断都可以被线程化,比如时钟中断,主要用来维护系统时间以及定时器
等,其中定时器是操作系统的脉搏,一旦被线程化,就有可能被挂起,这样后果将不堪设想,所以不应当
被线程化。
45,指针在形参的参数实例
#include <stdio.h>int func(void* arg){
int argc = *((int *) ((void **)arg)[0] ) ;
char **argv = *((char ***) ( (void **) arg)[1] ) ;
printf("argc = %d \n",argc);
return 0;
}
int main(int argc, char** argv){
// printf("argc = %d , argv[0] = %p argv[1] = %p argv[2] = %p\n",argc, argv[0], argv[1], argv[2]);
void * tx_arg[2];
tx_arg[0] = &argc;
tx_arg[1] = &argv;
printf("argv[0] = %s, argv[1] = %s\n",argv[1],argv[2]);
func(tx_arg);
return 0;
}