1.进程与线程的区别
地址空间
:同一进程内的线程
共享本进程的地址空间
,而
进程之间则是独立的地址空间
。
资源拥有
:同一进程内的线程
共享本进程的资源如内存、I/O、cpu等
,但是
进程之间的资源是独立的
。一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以
多进程要比多线程健壮
进程切换时,消耗的
资源大,效率低
。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
线程是处理器调度的基本单位,但是进程不是。
两者均可并发执行。
优缺点:
线程执行开销小,但是不利于资源的管理和保护。
进程执行开销大,但是能够很好的进行资源管理和保护。
何时使用多进程,何时使用多线程?
对资源的管理和保护要求高,不限制开销和效率时,使用多进程。
要求效率高,频繁切换时,资源的保护管理要求不是很高时,使用多线程。
2.子进程继承了什么
- 子进程继承父进程
- 用户号UIDs和用户组号GIDs
- 环境Environment
- 堆栈
- 共享内存
- 打开文件的描述符
- 执行时关闭(Close-on-exec)标志
- 信号(Signal)控制设定
- 进程组号
- 当前工作目录
- 根目录
- 文件方式创建屏蔽字
- 资源限制
- 控制终端
子进程独有
- 进程号PID
- 不同的父进程号
- 自己的文件描述符和目录流的拷贝
- 子进程不继承父进程的进程正文(text),数据和其他锁定内存(memory locks)
- 不继承异步输入和输出
3.进程间通信
- 无名管道和有名管道
- 信号量、消息队列、共享内存
- Socket域套接字
- 信号
4.智能指针
- 指针生命周期结束时
主动释放堆空间
- 一片堆空间
最多只能由一个指针标识
杜绝
指针运算和指针比较
5.C++为什么要继承
- 继承是c++中
代码复用
的重要手段。通过
继承
可以
获得父类的所有功能
,并且可以在子类中
重写已有功能
,或者
添加新功能
6.什么是多态
- 根据实际的
对象类型决定函数调用
的具体目标- 同样的
调用语句
在实际运行时有
多种不同的表现形态
7.反转单链表
//递归实现 Node* reverse(Node* list) { if(list == NULL || list->next == NULL) { return list; } else { Node* guard = list->next; Node* ret = reverse(list->next); guard->next = list; list->next = NULL; return ret; } }
5.C++写时复制
拷贝控制
C++提供两个拷贝控制函数
- 拷贝构造函数
- 赋值运算符重载
例如:
String
类class String{ public: String(const char* str = NULL); String(const String& str); String& operator=(const String& str); ~String(); size_t size()const; friend ostream& operator<<(ostream& os,String const& str); private: char* data; };
深拷贝与浅拷贝
概念
浅拷贝:只拷贝指针地址。
通常默认拷贝构造函数与赋值运算符重载都是浅拷贝。深拷贝:重现分配堆内存,拷贝指针指向内容。
例如:String类String::String(const char* str){ if(NULL == str){ data = new char[1]; data[0] = '\0'; }else{ data = new char[strlen(str)+1]; strcpy(data,str); } } String::~String(){ delete [] data; data = NULL; } String::String(const String& str){ data = new char[str.size()+1]; strcpy(data,str.data); } String& String::operator=(const String& str){ if(this != &str){ delete [] data; data = new char[str.size()+1]; strcpy(data,str.data); } return *this; } inline size_t String::size()const{ return strlen(data); } ostream& operator<<(ostream& os,const String& str){ return os << static_cast<const void *>(str.data) << ':' << str.data; }
int main(){ String s1("Hello World"); String s2 = s1; String s3; s3 = s1; cout << s1 << endl; cout << s2 << endl; cout << s3 << endl; }
比较
优点 缺点
浅拷贝 只有一份数据,节省空间。 因为多个指针指向同一个空间,容易引发同一内存多次释放的问题。 深拷贝 每个指针指向不同地址,没有同一内存多次释放的问题。 存在多份相同数据,浪费空间。
浅拷贝与深拷贝的优缺点分别互为彼此的优缺点
。有什么办法可以兼有二者的优点?
主要解决问题:
数据相同时只有一份内存。
不会出现多次释放问题。
计数器技术:
数据相同共享一份内存
计数器技术就是兼有浅拷贝与深拷贝优点的一种技术。
在类声明中添加了计数器
s_count
。class String{ public: String(const char* str = NULL); String(const String& str); String& operator=(const String& str); ~String(); size_t size()const; friend ostream& operator<<(ostream& os,String const& str); private: char* data; static int s_count; };
实现中增加计数处理
int String::s_count = 0; String::String(const char* str){ if(NULL == str){ data = new char[1]; data[0] = '\0'; }else{ data = new char[strlen(str)+1]; strcpy(data,str); } ++s_count; } String::~String(){ if(--s_count == 0){ delete [] data; data = NULL; } } String::String(const String& str):data(str.data){ ++s_count; } String& String::operator=(const String& str){ if(this != &str){ data = str.data; ++s_count; } return *this; }
问题:构造
新对象
的计数是几?计数器技术会导致构造新对象计数错误。
解决:每个空间应该具有自己的引用计数,而不能所有空间共享一个引用计数
。
1.接口和抽象类的区别
面向对象中的抽象类
-可用于表示现实世界中的
抽象概念
– 是一种只能定义类型,而
不能产生对象
的类-
只能被继承并重写相关函数
-直接特征
是相关函数没有完整的实现
抽象类与纯虚函数
C++语言中没有抽象类的概念
C++中通过
纯虚函数
实现抽象类纯虚函数是指
只定义原型的成员函数
—个C++类中存在纯虚函数就成为了抽象类
纯虚函数的语法规则
class Shape { public: virtual double area() = 0; };
C++中的接口
满足下面条件的C++类则称为接口
-类中
没有定义任何的成员变量
-所有的成员函数都是
公有的
-所有的成员函数都是
纯虚函数
-接口是—种特殊的抽象类 (仅C++)
class Channel { public: virtual bool open() = 0; virtual void close() = 0; virtual bool send(char* buf, int len) = 0; virtual int receive(char* buf, int len) = 0; };
7、如何判断循环队列为满
队列是一种特殊的线性表
队列仅能在线性表的两端进行操作
-队头(Front) : 取出数据元素的一端
-队尾(Rear) : 插入数据元素的一端
队列的特性
-先进先出(First In First Out)
基于顺序存储结构的队列
StaticQueue设计要点
-使用原生数组作为队列的存储空间
-使用模板参数决定队列的最大容量
StaticQueue实现要点(循环计数法)
-关键操作
m_rear
队尾标识
m_front
队头标识
m_length
当前队列长度进队列: m_space[m_rear] = e; m_rear = (m_rear + 1) % N;
出队列: m_front = (m_front + 1) % N;
-队列的状态
队空: (m_length == 0) && (m_front == m_rear)
队满: (m_length == N) && (m_front == m_rear)
8、函数指针与指针函数
函数指针
函数具有可赋值给指针的物理内存地址,一个函数的函数名称就是一个指针,它指向函数的代码。函数的地址是该函数的入口,也是调用函数的地址。函数的调用可以通过函数名,也可以通过指向函数的指针来调用,函数指针还允许将函数作为变元传递给其他函数。数组名代表数组首元素的地址,同样,函数名代表函数的地址。
函数指针原型:
类型 (*指针变量名)(参数列表);
int (*fun)(int a, int b);
函数
指针,首先它是一个指针,它指向函数的地址
#include <stdio.h> int Max(int i,int j) { return i>j?i:j; } int Min(int i,int j) { return i>j?j:i; } //声明函数指针 int (*fun)(int, int); int main() { int a = 10, b = 20; int ret = 0; fun = &Max; //函数指针赋值 printf("The max value = %d\n",fun(a,b)); fun = &Min; //函数指针赋值 printf("The min value = %d\n",fun(a,b)); return 0; } /* The max value = 20 The min value = 10 */
#include <stdio.h> int Max(int i,int j) { return i>j?i:j; } //声明函数指针 int (*fun)(int, int); int getMax(int a, int b, int (*fun)(int, int)) { return fun(a, b); } int main() { int a = 10, b = 20; int ret = 0; printf("The max value = %d\n",getMax(a,b, Max)); return 0; } /* The max value = 20 */
函数指针数组
函数指针数组:
首先它是一个数组,数组元素是函数的地址。即把函数的地址保存在数组中
。#include <stdio.h> int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } int mul(int a, int b) { return a * b; } int div(int a, int b) { return a / b; } int main() { int x, y; int input = 0; int ret = 0; int (*p[5])(int x, int y) = {0, add, sub, mul, div}; printf("input(0~4): >"); scanf("%d", &input); printf("input two num:>"); scanf("%d %d", &x,&y); ret = (*p[input])(x, y); printf("ret = %d\n", ret); return 0; }
typedef函数指针
#include <stdio.h> typedef int (*pFun)(int, int);//定义函数指针别名 int Max(int i, int j) { return i > j ? i : j; } int main() { pFun fun;//定义函数指针变量 fun = Max;//函数指针变量赋值 printf("Max value:%d\n",(*fun)(10,20)); return 0; } /* Max value:20 */
指针函数
指针
函数
:首先它是一个函数,其返回值为指针,即返回指针的函数,原型:类型名* 函数名(函数参数列表);
如:int * pFun(int, int);
因为
* 的优先级低于()
,因此pFun首先与后面的()结合,即它是一个函数* 与前面的int结合,返回值类型为指向int的指针。
PS:
函数指针调用时,有两种方式:fun(参数列表);
(*fun)(参数列表);
9、struct 和 class的区别
- 在用struct定义类时,所有成员的默认访问级别为
public
- 在用class定义类时,所有成员的默认访问级别为
private
- 早期的c++直接复用class关键字来定义模板
10、struct计算大小
结构体是一种复合数据类型,通常编译器会自动的进行其成员变量的对齐,已提高数据存取的效率。在
默认情况
下,编译器为结构体的成员按照
自然对齐
(natural alignment)方式分配存储空间,各个成员按照其声明顺序在存储器中顺序存储。自然对齐是指按照结构体中成员size最大的对齐,在cl编译器下可以使用来指定结构体的对齐方式。#pragma pack(n)
默认对齐方式
在默认对齐方式下,结构体成员的内存分配满足下面三个条件
- 结构体第一个成员的地址和结构体的首地址相同
结构体每个成员地址
相对于
结构体首地址
的
偏移量
(offset)是该成员大小的整数倍,如果不是则编译器会在成员之间添加填充字节(internal adding)。- 结构体总的大小要是其成员中最大size的整数倍,如果不是编译器会在其末尾添加填充字节(trailing padding)。
struct s1{ char ch; int a; double b; char c1; }; struct s2{ char ch; int a; double b; }; int main() { cout << "s1的大小: " << sizeof(struct s1) << endl; cout << "ch的地址偏移是 " << offsetof(s1, ch) << endl; cout << "a 的地址偏移是 " << offsetof(s1, a) << endl; cout << "b 的地址偏移是 " << offsetof(s1, b) << endl; cout << "c1的地址偏移是 " << offsetof(s1, c1) << endl; cout << "=====================================" << endl; cout << "s2的大小: " << sizeof(struct s2) << endl; cout << "ch的地址偏移是 " << offsetof(s2, ch) << endl; cout << "a 的地址偏移是 " << offsetof(s2, a) << endl; cout << "b 的地址偏移是 " << offsetof(s2, b) << endl; getchar(); return 0; }
代码中
offsetof
函数可以得到结构体成员相对于该结构体首地址的偏移量。
其运行结果如下图
对于结构体s1来说,
- ch是其第一个成员故其地址和结构体的地址是相同的也就是说偏移量为0;
- a是int型其大小为4个字节,按照
条件(2) 结构体每个成员地址相对于结构首地址的偏移量(offset)是该成员大小的整数倍,如果不是则编译器会在成员之间添加填充字节
,所以其地址偏移应该是4,也就说编译器在第一个成员ch后面填充了3个字节。- b是double型占8个字节,其地址偏移应该是8的整数倍,由于a的地址偏移是4其大小为4个字节,正好b的偏移地址是8,不需要填充字节。
- c1是char型占1个字节,偏移地址是16(b的偏移地址是8大小也是8,中间也没有填充字节)。
- 这时成员ch占1个字节后面有3个字节的填充,a占4个字节后面无填充,b占8个字节后面无填充,c1占1个字节,s1总的大小是1+3+4+8+1=17。按照
条件(3)结构体总的大小需是其最大成员所占空间的整数倍
,其最大的成员b占有8字节,17显然是不符合条件的,所以需要在结构体的末尾填充7个字节,最后结构体总的大小是
24字节
。
结构体s2和s1的成员是非常相似的,唯一的区别是其末尾没有最后7个字节的填充,所以其大小是16个字节,这里用于和s1做对比说明s1末尾的填充字节指定对齐方式
可以使用
#pragma pack(N)
来指定结构体成员的对齐方式
对于指定的对齐方式,其成员的地址偏移以及结构的总的大小也有下面三个约束条件
- 结构体第一个成员的地址和结构体的首地址相同
- 结构体每个成员的地址偏移需要满足:N 大于等于 该成员的大小,那么该成员的地址偏移需满足默认对齐方式(地址偏移是其成员大小的整数倍);N 小于 该成员的大小,那么该成员的地址偏移是
N的整数倍
。- 结构体总的大小需要时N的整数倍,如果不是需要在结构体的末尾进行填充。
如果N大于结构体成员中最大成员的大小,则N不起作用,仍然按照默认方式对齐。
示例仍然是上面的s1和s2,不过使用
#pragma pack(4)
结果分析
- ch是其第一个成员故其地址和结构体的地址是相同的也就是说偏移量为0;
- a占4个字节,和设定的对齐方式相等,所以其地址偏移是其大小的整数倍为4。
- b占8个字节,大于设定的对齐方式4,所以其地址偏移是N的整数倍为8。
- c1占1个字节,小于设定的对齐方式,所以其地址偏移是其大小的整数倍为16。
- 总的大小17个字节,不是N(4)的整数倍,所以在结构体的末尾填充3个字节,总的大小为20个字节。
说明:
在使用#pragma pack设定对齐方式一定要是2的整数幂,也就是(1,2,4,8,16,…),不然不起作用的,仍然按照默认方式对齐。
- 当结构体中有
其他的结构体
作为成员时,计算最大成员是不能把结构体成员作为一个整体来计算,
要看其每个成员的大小
1.arm9有多少个寄存器 2.P15 、P14、P13寄存器是什么寄存器
ARM9 处理器的内部总共有
37
个32位的寄存器,其中
31个用作通用寄存器
,
6个用作状态寄存器
。
R13寄存器的作用通常是堆栈指针
,又称为SP。
R14寄存器可用作子程序链接寄存器LR
(Link Register)。
R15寄存器的功能是程序计数器
,又称为PC。
4.主设备号和次设备号的区别
- 主设备号为了区分不同的种类的设备
- 次设备号为了区分同一类型的多个设备
5、驱动设备有哪几类
字符设备
,准确的说应该叫“字节设备”,软件操作设备时是以字节为单位进行的。典型的如LCD、串口、LED、蜂鸣器、触摸屏······
块设备
,块设备是相对于字符设备定义的,块设备被软件操作时是以块(多个字节构成的一个单位)为单位的。设备的块大小是设备本身设计时定义好的,软件是不能去更改的,不同设备的块大小可以不一样。常见的块设备都是存储类设备,如:硬盘、NandFlash、iNand、SD····
网络设备
,网络设备是专为网卡设计的驱动模型,linux中网络设备驱动主要目的是为了支持API中socket相关的那些函数工作。
3.如何将驱动或模块编译进内核
- 动态方式:采用insmod命令来给运行中的linux加载模块。
- 静态方式:修改linux的配置菜单,添加模块相关文件到源码对应目录,然后把模块直接编译进内核。
6、平台驱动是什么
平台总线(platform_bus)的需求来源?
随着soc的升级,S3C2440->S3C6410->S5PV210->4412,以前的程序就得重新写一遍,做着大量的重复工作,人们为了提高效率,发现
控制器的操作逻辑(方法)是一样的
,只有
寄存器地址不一样
,如果将与硬件有关的代码(platform_device)和驱动代码(platform_driver)分开,升级soc后,因为驱动方式一样,只需要修改与硬件有关的代码就可以,实现一个驱动控制多个设备。
平台(platform)总线是一种虚拟的总线
平台总线三要素:平台总线、平台设备、平台驱动
平台总线原则:先分离,后合并
分离:
将设备信息封装成 platform_device,将驱动信息封装成 platform_driver,并为各自起名称,然后将 platform_device 中的 struct device 和 platform_driver 中的 struct device_driver 分别注册到设备链表和驱动链表中。
合并:
在系统每注册一个设备(驱动)时,平台总线会找与之匹配的驱动(设备),匹配原则是名称相同。装载(insmod)时设备和驱动没有顺序,卸载(rmmod)时必须先卸载设备文件,因为卸载设备会调用驱动中的 remove 函数
7、probe函数作用
probe函数在设备驱动注册最后收尾工作,当设备的device 和其对应的driver 在总线上完成配对之后,系统就调用
platform
设备的probe函数完成驱动注册最后工作。资源、
中断
调用函数以及其他相关工作。
10.*[P + 1] 换成 *[1 + P]
a[n] <==> *(a+n) <==> *(n+a) <==> n[a]
11.有序链表插入节点依然有序
linklist* insertList(int n, linklist* head)
{
linklist *p = NULL;
linklist *s = NULL;
linklist *q = NULL;
//待插入结点
s = (linklist*)malloc(sizeof(linklist));
s->data = n;
s->next=NULL;
if(head == NULL)
return p;
//p,q同时指向头结点
p = q = head;
//使用q作为判断条件,查找到小于n的位置
while(q != NULL && q->data < n)
{
p = q;//此时p指向q的前一个位置
q = q->next;
}
if(p == head)
{
s->next = p;
head = s;//在头部插入
}
else
{
s->next = q;
p->next = s;
}
return head;
}
8.打印内核信息
1.printk()是一个内核的一个记录日志的机制,经常用来记录信息或者警告。
2.查看当前控制台的打印级别
1 cat /proc/sys/kernel/printk
4 4 1 7
其中第一个“4”表示内核打印函数printk的打印级别,只有级别比他高的信息才能在控制台上打印出来,既 0-3级别的信息3.修改打印
echo “新的打印级别 4 1 7” >/proc/sys/kernel/printk4.不够打印级别的信息会被写到日志中可通过dmesg 命令来查看
5.printk函数的使用
printk(打印级别 “要打印的信息”)打印级别 既上面定义的几个宏