1、C/C++编程语言
2、算法思想和数据结构
3、UNIX/Linux 操作系统上的软件开发环境及常用工具 GNU 开发套件,熟悉各种 Linux 的调试工具和方法,如 GDB、G++ 、coredump
4、常见嵌入式处理器 有 ARM7、ARM9、ARM11 等嵌入式处理器编程,包括 Ethernet、UART、USB、DMA、I2C、SPI 等等
5、网络开发及相关网络协议 如 TCP/IP,SERIA,SNMP,TCP、UDP、HTTP、FTP 等网络协议
6、linux 系统的 Socket 网络编程
7、linux 系统的多线程编程
8、linux 系统的常用脚本语言 Shell、PHP
9、Linux 模块的底层驱动编程
10、应用层得 RTP 工作原理
11、Linux 内核的修改、配置、编译等
什么是预编译,何时需要预编译
预编译又称预处理,是做一些代码文本的替换工作,处理#开头的指令,比如拷贝#include
包含的文件代码,#define
宏定义的替换,条件编译等就是为编译做的预备工作的阶段。预编译指令指示了在程序正式编译前就由编译器进行了操作,可以放在程序的任何位置。
何时使用:当大部分文件代码基本上不会更改时,比如 MFC 的一些头文件以及一些必要的 API 使用代码,也可以把你自己的一部分代码封装起来到一个 C 或 C++文件中,(比如在其中包含一些头文件或必要的代码什么的,然后在 VC-C/C++—PreCompiled Headers 里选择第三项 Create compiled Header file)来指定为预编译头文件,这样就在以后的程序修改中编译时不会反复编译这部分。注意:过多的使用预编译头文件会大大降低编译的速度。
深入理解 char * const p,char const *p,const char *p 的区别
1、关键看 const 修饰的是谁 2、由于没有 const _的运算,若出现 const _ 的形式,则 const 实际上是修饰前面的。
-
char *const p
(const 修饰的是 p):只能对“某个固定的位置”进行读写操作,并且在定义 p 时就必须初始化(因为在后面不能执行“p=..”的操作,因此就不能在后面初始化,因此只能在定义时初始化)。(“某个固定的位置”是相对于 char *p 来说所限定的内容) -
char const*p
,由于没有 const运算,则 const 实际上是修饰前面的 char,因此char const*p
等价于const char*p
。const char*p
或者char const *p
(因为没有 constp 运算,因此 const 修饰的还是前面的 char):可以对任意位置(非系统敏感区域)进行“只读”操作。(“只读”是相对于 char *p 来说所限定的内容)
一个 32 位的机器,该机器的指针是多少位答案
指针是多少位只要看地址总线的位数就行了。80386 以后的机子都是 32 的数据总线。所以指针的位数就是 4 个字节了。
关键字 static 的作用是什么?
定义静态变量
关键字 const 有什么含意?
表示常量不可以修改的变量。
关键字 volatile 有什么含意?并举出三个不同的例子?
一个定义为 volatile 的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是 volatile 变量的几个例子
- 并行设备的硬件寄存器(如:状态寄存器)
- 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
- 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我认为这是区分 C 程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS 等等打交道,所用这些都要求 volatile 变量。不懂得 volatile 内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑这否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得 volatile 完全的重要性。
- 一个参数既可以是 const 还可以是 volatile 吗?解释为什么。
- 一个指针可以是 volatile 吗?解释为什么。
- 下面的函数有什么错误:
int square(volatile int *ptr){return *ptr * *ptr;}
下面是答案:
- 是的。一个例子是只读的状态寄存器。它是 volatile 因为它可能被意想不到地改变。它是 const 因为程序不应该试图去修改它。
- 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个 buffer 的指针时。
- 这段代码的有个恶作剧。这段代码的目的是用来返指针ptr 指向值的平方,但是,由于ptr 指向一个 volatile 型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr){int a,b;a = *ptr;b = *ptr;return a * b;}
由于*ptr 的值可能被意想不到地该变,因此 a 和 b 可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr){int a;a = *ptr;return a * a;}
列举几种进程的同步机制,并比较其优缺点。
原子操作、信号量机制、自旋锁、管程,会合,分布式系统
进程之间通信的途径
目的:
- 数据传输:一个进程向另一个进程传输数据,一般在一个字节和几 M 字节之间。
- 共享数据
- 资源共享:多个进程之间共享同样的资源,为了做到这一点需要内核提供锁和同步机制。
- 通知事件:一个进程向另一个进程或一组进程发送消息,通知他梦发生了某种事件(如进程终止样通知付进程)
- 进程控制:有些进程希望完全控制另一个进程(Debug 进程),此时控制进程希望能够拦截另一个进程的所有的陷入和异常,并能够及时知道他的状态改变。
-
管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
创建一个简单的管道,可以使用系统调用 pipe()。它接受一个参数,也就是一个包括两个整数的数组。如果系统调用成功,此数组将包括管道使用的两个文件描述符。创建一个管道之后,一般情况下进程将产生一个新的进程。
系统调用:pipe();
原型:
int pipe(int fd[2]);
返回值:如果系统调用成功,返回 0。如果系统调用失败返回-1: errno=EMFILE(没有空亲的文件描述符) EMFILE(系统文件表已满) EFAULT(fd 数组无效)
注意:fd[0]用于读取管道,fd[1]用于写入管道。
管道的创建
#include<unistd.h>#include<errno.h>#include<stdio.h>#include<stdlib.h>int main(){int pipe_fd[2];if(pipe(pipe_fd)<0){printf("pipe create error\n");return -1;}elseprintf("pipe create success\n");close(pipe_fd[0]);close(pipe_fd[1]);}管道的读写 管道主要用于不同进程间通信。实际上,通常先创建一个管道,再通过 fork 函数创建一个子进程。图见附件。
子进程写入和父进程读的命名管道:图见附件
管道读写注意事项: 可以通过打开两个管道来创建一个双向的管道。但需要在子理程中正确地设置文件描述符。必须在系统调用 fork()中调用 pipe(),否则子进程将不会继承文件描述符。当使用半双工管道时,任何关联的进程都必须共享一个相关的祖先进程。因为管道存在于系统内核之中,所以任何不在创建管道的进程的祖先进程之中的进程都将无法寻址它。而在命名管道中却不是这样。管道实例见:pipe_rw.c
#include<unistd.h>#include<memory.h>#include<errno.h>#include<stdio.h>#include<stdlib.h>int main(){int pipe_fd[2];pid_t pid;char buf_r[100];char* p_wbuf;int r_num;memset(buf_r,0,sizeof(buf_r));//数组中的数据清0;if(pipe(pipe_fd)<0){printf("pipe create error\n");return -1;}if((pid=fork())==0){printf("\n");close(pipe_fd[1]);sleep(2);if((r_num=read(pipe_fd[0],buf_r,100))>0){printf("%d numbers read from be pipe is %s\n",r_num,buf_r);}close(pipe_fd[0]);exit(0);}else if(pid>0){close(pipe_fd[0]);if(write(pipe_fd[1],"Hello",5)!=-1)printf("parent write success!\n");if(write(pipe_fd[1]," Pipe",5)!=-1)printf("parent wirte2 succes!\n");close(pipe_fd[1]);sleep(3);waitpid(pid,NULL,0);exit(0);}} -
有名管道 (named pipe): 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
-
信号量( semophore ): 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
-
消息队列( message queue ): 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
-
信号 ( sinal ): 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。信号是软件中断。信号(signal)机制是 Unix 系统中最为古老的进程之间的能信机制。它用于在一个或多个进程之间传递异步信号。很多条件可以产生一个信号。
-
共享内存( shared memory ):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
-
套接字( socket ): 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
死锁产生的原因及四个必要条件
产生死锁的原因主要是: (1) 因为系统资源不足。 (2) 进程运行推进的顺序不合适。 (3) 资源分配不当等。
如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。
产生死锁的四个必要条件: (1) 互斥条件:一个资源每次只能被一个进程使用。 (2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 (3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。 (4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
死锁的解除与预防: 理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和解除死锁。所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态的情况下占用资源。因此,对资源的分配要给予合理的规划。
操作系统中进程调度策略有哪几种?
FCFS(先来先服务),优先级,时间片轮转,多级反馈
类的静态成员和非静态成员有何区别?
答案:类的静态成员每个类只有一个,非静态成员每个对象一个
纯虚函数如何定义?使用时应注意什么?
答案:virtual void f()=0;
是接口,子类必须要实现
数组和链表的区别
答案:数组:数据顺序存储,固定大小;链表:数据可以随机存储,大小可动态改变
ISO 的七层模型是什么?tcp/udp 是属于哪一层?tcp/udp 有何优缺点?
OSI(Open System Interconnection)参考模型是国际标准化组织(ISO)制定的一个用于计算机或通信系统间互联的标准体系,一般称为 OSI 参考模型或七层模型。它是一个七层的、抽象的模型,不仅包括一系列抽象的术语或概念,也包括具体的协议
- 应用层
- 表示层
- 会话层
- 运输层
- 网络层
- 数据链路层
- 物理层
tcp /udp 属于运输层。TCP 服务提供了数据流传输、可靠性、有效流控制、全双工操作和多路复用技术等。与 TCP 不同, UDP 并不提供对 IP 协议的可靠机制、流控制以及错误恢复功能等。由于 UDP 比较简单, UDP 头包含很少的字节,比 TCP 负载消耗少。
- tcp: 提供稳定的传输服务,有流量控制,缺点是包头大,冗余性不好,开销大,实时性较差。
- udp: 包头小,开销小 ,占用资源少,实时性较好,缺点是不可靠。
tcp 是面向连接的可靠字节流;udp 是无连接的不可靠报文传递。
TCP 是传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须在双方之间建立一个 TCP 连接,之后才能传输数据,TCP 提供超时重发、丢弃重复数据、检验数据、流控制等功能,保证数据能从一端传输到另一端。
UDP 是用户数据报协议,是一个简单的面向数据报的运输层的协议。UDP 不提供可靠性,他只是把应用程序传给 IP 层的数据报发送出去。但是并不保证它们到达目的地,由于 UDP 在传输前不需要在客户端与服务器端之间建立一个连接,且没有超时重发机制,故而传输速度很快。
已知一个数组 table,用一个宏定义,求出数据的元素个数
#define NIBL (sizeof(table)/sizeof(table[0]))
对于一个频繁使用的短小函数,在 C 语言中应用什么实现,在 C++中应用什么实现?
答案:c 用宏定义,c++用 inline
软件测试都有那些种类?
- 黑盒测试:黑盒测试,指的是把被测的软件看作是一个黑盒子,我们不去关心盒子里面的结构是什么样子的,只关心软件的输入数据和输出结果。它只检查程序功能是否按照需求规格说明书的规定正常使用,程序是否能适当地接收输入数据而产生正确的输出信息。黑盒测试着眼于程序外部结构,不考虑内部逻辑结构,主要针对软件界面和软件功能进行测试。
- 白盒测试:白盒测试,指的是把盒子盖子打开,去研究里面的源代码和程序结果。它是按照程序内部的结构测试程序,通过测试来检测产品内部动作是否按照设计规格说明书的规定正常进行,检验程序中的每条通路是否都能按预定要求正确工作。
- 灰盒测试:灰盒测试介于黑盒测试与白盒测试之间。可以这样理解,灰盒测试关注输出对于输入的正确性,同时也关注内部表现,但这种关注不象白盒那样详细、完整,只是通过一些表征性的现象、事件、标志来判断内部的运行状态,有时候输出是正确的,但内部其实已经错误了,这种情况非常多,如果每次都通过白盒测试来操作,效率会很低,因此需要采取这样的一种灰盒的方法。
Enumstring { x1, x2, x3=10, x4, x5, }x; 问 x;
枚举元素的值不可以改变,但是你可以在定义时对其进行初始化。若都不赋值依次为 0、1、2、3、4、5…若赋了一个值则后面的值为前面的一次加一。
对其进行赋值如:
enum string{x1=1,x2,x3=10,x4=12,x5,}x;
那么现在元素的值依次为 1、2、10、12、13
指针运算
unsigned char *p1;unsigned long *p2;p1=(unsigned char *)0x801000;p2=(unsigned long *)0x810000;
请问 p1+5= ; p2+5= ;
答案:801005; 810014。不要忘记了这个是 16 进制的数字,p2 要加 20 变为 16 进制就是 14
TCP/IP 通信建立的过程怎样,端口有什么作用?
答案:三次握手,确定是哪个应用程序使用该协议
局部变量能否和全局变量重名?
答案:能,局部会屏蔽全局。要用全局变量,需要使用”::” 局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量,比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内
如何引用一个已经定义过的全局变量?
答案:extern 可以用引用头文件的方式,也可以用 extern 关键字,如果用引用头文件方式来引用某个在头文件中声明的全局变理,假定你将那个变写错了,那么在编译期间会报错,如果你用 extern 方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连接期间报错
全局变量可不可以定义在可被多个.C 文件包含的头文件中?为什么?
答案:可以,在不同的 C 文件中以 static 形式来声明同名全局变量。可以在不同的 C 文件中声明同名的全局变量,前提是其中只能有一个 C 文件中对此变量赋初值,此时连接不会出错
语句 for( ;1 ;)有什么问题?它是什么意思?
答案:和 while(1)相同。
队列和栈有什么区别?
答案:队列先进先出,栈后进先出
什么是可重入
可重入函数主要用于多任务的环境中,一个可重入函数简单的来说就是可以被中断的函数,也就是说,在改函数任何的执行的时候都能中断他,转入OS调度下去执行另一断代码,而返回控制时不会出现错误;而不可重入的函数使用了一些系统资源,比如全局变量区,中断向量表等,所以他如果被中断的话,可能出现问题,这类函数是不能运行在多任务环境下的。
TCP/IP 的三次握手
第一次握手:客户端发送 syn 包(syn=j)到服务器,并进入 SYN_SEND 状态,等待服务器确认; 第二次握手:服务器收到 syn 包,必须确认客户的 SYN(ack=j+1),同时自己也发送一个 SYN 包(syn=k),即 SYN+ACK 包,此时服务器进入 SYN_RECV 状态; 第三次握手:客户端收到服务器的 SYN+ACK 包,向服务器发送确认包 ACK(ack=k+1),此包发送完毕,客户端和服务器进入 ESTABLISHED 状态,完成三次握手。
握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。 理想状态下,TCP 连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。 断开连接时服务器和客户端均可以主动发起断开 TCP 连接的请求,断开过程需要经过“四次握手”(过程就不细写了,就是服务器和客户端交互,最终确定断开)
TCP/UDP 区别?
TCP-传输控制协议,提供的是面向连接、可靠的字节流服务。但客户端和服务器端彼此交换数据前,必须现在双方建立 TCP 连接,然后才能传输数据。TCP 提供方超时重发,丢弃重复的数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
UDP:用户数据报协议,是一个简单的面向数据报的运输层协议,UDP 不提供平可靠性,他只是负者吧应用程序传给 IP 层的数据报发送出去,但是并不能保证它们能够到达目的地,由于 UDP 在传输数据报前不用再客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度快。
内存分配应该注意什么问题?
- 检查内存是否分配成功
- 内存生命周期,程序结束时记得 free,避免内存的泄露
- 使用过程中,避免指针的越界访问,会导致不必要的错误。
OSI 模型分哪几层?
物理层、数据链路层、网络层、传输层、应用层、表示层、会话层
- 集线器 hub 工作在物理层
- 网卡工作在物理层
- 路由器 router 工作在网络层
- 交换机 switch 工作在数据链路层
常见的网络层协议:IP 协议、ICMP、IGMP、MPLS、ARP
常见的传输层协议:TCP、UDP
常见的应用层协议:TELNET、SMTP、HTTP、FTP、DHCP、SNMP
线程和进程的区别联系:
- 进程:子进程是父进程的复制品。子进程获得父进程数据空间、堆和栈的复制品。
- 线程:相对与进程而言,线程是一个更加接近与执行体的概念,它可以与同进程的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
两者都可以提高程序的并发度,提高程序运行效率和响应时间。 线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源管理和保护;而进程正相反。同时,线程适合于在 SMP 机器上运行,而进程则可以跨机器迁移。
根本区别就一点:用多进程每个进程有自己的地址空间(address space),线程则共享地址空间。所有其它区别都是由此而来的:
- 速度:线程产生的速度快,线程间的通讯快、切换快等,因为他们在同一个地址空间内。
- 资源利用率:线程的资源利用率比较好也是因为他们在同一个地址空间内。
- 同步问题:线程使用公共变量/内存时需要使用同步机制还是因为他们在同一个地址空间内
实时操作系统
所谓实时操作系统,实际上是指操作系统工作时,其资源可以根据需要随时进行动态分配,由于各种资源可以进行动态分配,因此其处理事务的能力较强、速度较快。其主要的特点是提供及时的响应和高可靠性。
堆栈溢出
的主要原因是没有回收垃圾资源。
constructor 函数不能申明为虚函数
冒泡排序算法的时间复杂度 o(n^2)
internet 物理地址和 IP 地址转换采用 ARP(地址解析协议)
写出 float x 与“零值”比较的 if 语句。
if(x>0.000001 && x < -0.000001)
在 Windows 中,对于一般的 malloc,没有 free,系统会不会自动的回收?
在 Windows 中,对于一般没有 free 的 malloc,在进程正常结束时时可以回收的,不管用户怎么 malloc,在进程结束的时候,其虚拟地址空间就回被直接的销毁,操作系统只需要在进程结束的时候调用内存管理模块。把分页文件中与此进程相关的记录全部删除,标记为“可用空间”,就可以使所有申请的内存都一次性的回收。但是,只有在进程退出时,内核会调用内存管理模块进行执行清理,如果在一个服务器上长期运行的程序,不可能动不动就退出来清理一次。这个时候,不合理的内存分配导致的泄露会使程序运行出现问题。
请写出你所知道的 C 语言关键字。
C 语言关键字有 32 个:
关键字 | 意义 |
---|---|
auto | 声明自动变量,缺省时编译器一般默认为 auto |
int | 声明整型变量 |
double | 声明双精度变量 |
long | 声明长整型变量 |
char | 声明字符型变量 |
float | 声明浮点型变量 |
short | 声明短整型变量 |
signed | 声明有符号类型变量 |
unsigned | 声明无符号类型变量 |
struct | 声明结构体变量 |
union | 声明联合数据类型 |
enum | 声明枚举类型 |
static | 声明静态变量 |
switch | 用于开关语句 |
case | 开关语句分支 |
default | 开关语句中的“其他”分支 |
break | 跳出当前循环 |
register | 声明寄存器变量 |
const | 声明只读变量 |
volatile | 说明变量在程序执行中可被隐含地改变 |
typedef | 用以给数据类型取别名(当然还有其他作用 |
extern | 声明变量是在其他文件正声明(也可以看 |
return | 子程序返回语句(可以带参数,也可不带 |
void | 声明函数无返回值或无参数,声明空类 |
continue | 结束当前循环,开始下一轮循环 |
do | 循环语句的循环体 |
while | 循环语句的循环条件 |
if | 条件语句 |
else | 条件语句否定分支(与 if 连用) |
for | 一种循环语句(可意会不可言传) |
goto | 无条件跳转语句 |
sizeof | 计算对象所占内存空间大小 |
定义和声明的区别:
定义:编译器在创建一个对象时,为该对象申请开辟的内存空间,这个空间的的名字就是变量名或者对象名。同一个变量名在摸个区域内只能定义一次,重复定义会出现错误的。
声明:有两种作用,
- 告诉编译器,这个变量或者函数,我已经定义了(开辟空间了),但是在别的地方,我先说明一下,免得编译器报错。当然,声明可以多次出现。
- 告诉编译器,这个变量名或者对象名,我先预定了,其他地方不可以用了。和在饭馆吃饭(人多)要提前预约一样的道理。
定义和声明最本质的区别在于,声明没开辟空间,而定义则创建对象(变量)并开辟了空间。这是最重要的一点。
结构体大小计算
struct Test{ int Num; char*Pc; short sDtate; char ch[2]; short S[4];}*p;
这个结构体的大小多少呢?
int:占 4 个字节(32 系统)。 charpc:4 个:指针相当于地址,地址就是你当前操作系统的位数。如果是指针数组即存放指针的数组,占用的空间是 4数组的个数。如果是数组指针即指向数组的指针,指针指向的是数组的地址,占用 4 个字节。 short sDtate:2 个 char ch[2]:2 个 short S[4]:2*4=8 short 型数组
所以一起:4+4+2+2+8=20 字节。
IPv4 地址分类
IPv4 把所有的 IP 地址分为 A、B、C、D、E 五类。
- B 类 IP 地址地址范围:128.0.0.0-191.255.255.255,子网掩码为 255.255.0.0
- C 类 IP 地址范围:192.0.0.0-223.255.255.255,子网掩码为 255.255.255.0
- D 类地址的用途:IP 采用 D 类地址来支持多播。每个 D 类地址代表一组主机。共有 28 位可用来标识小组。所以可以同时有多达 25 亿个小组。当一个进程向一个 D 类地址发送分组时,会尽最大的努力将它送给小组的所有成员,但不能保证全部送到。有些成员可能收不到这个分组。举个例子来说,假定五个节点都想通过 I P 多播,实现彼此间的通信,它们便可加入同一个组地址。全部加入之后,由一个节点发出的任何数据均会一模一样地复制一份,发给组内的每个成员,甚至包括始发数据的那个节点。
写一个“标准”宏 MIN,这个宏输入两个参数并返回较小的一个?
#define MIN(a,b) ((a<=b)? a:b)
什么是 MMU,MMU 的作用?
MMU (memory Management Unit ,内存管理单元) 操作系统通过处理器的 MMU 功能,能实现以下功能:
- 虚拟内存,有了虚拟内存,可以在处理器上运行比实际内存大的应用程序。为了实现虚拟内存,操作系统通常要设置一个交换分区(同常是硬盘),通过将不活跃的内存中的数据放在交换分区,操作系统可以腾出其空间为其他的程序服务。虚拟内存是通过虚拟地址来实现的。
- 内存保护,根据需要对特定的内存区块的访问进行保护,通过这一功能,可以将特定的内存块设置成只读。只写或可读可写。
在嵌入式系统中,通常不会使用虚拟地址这一功能,因为它会使得任务的调度时间不具确定性。还有另一个原因就是,嵌入式系统的存储空间通常很小,有的只采用 FLASH 作为存储介质,并没有特定的空间用做交换分区。在嵌入式系统中 MMU 主要用来做内存保护作用的
关键字 volatile 有什么含义,并给出几个应用场合。
一个定义的 volatitl 的变量可能会被意想不到的改变,这样,编译器就不会去假设这个变量的值了。精确的说就是,优化器在用到这个变量时必须每次都小心的重新读取这个变量的值,而不是使用保存在寄存器里的备份。
-
并行设备的硬件寄存器(如:状态寄存器)
-
一个中断服务子程序中会访问到的非自动变量。
-
多线程应用中被几个任务共享的变量。
-
一个参数既可以是 const 还可以是 volatile 吗?解释为什么。 是的。一个例子是只读的状态寄存器。它是 volatile 因为它可能被意想不到地改变。它是 const 因为程序不应该试图去修改它。
-
一个指针可以是 volatile 吗?解释为什么。 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个 buffer 的指针时。
链表节点插入函数
struct node{ struct node *next; struct node *previous;}NODE;
/*链表定义*/typedef struct{ NODE node; int count;} LIST;
#define HEAD node.next#define TALL node.previous
/*初始化一个链表*/void lstInit (LIST *pList){ pPlist->HEAD=NULL; pPlist->TALL=NULL; pPlist->count=0;}
请编写函数把一个节点(pNode)插入链表 pList 中某个节点(pPrev)之后。
嵌入式常见的数据结构
- 链表:可以动态的进行存储分配,根据需要开辟内存单元。由两部分组成:数据域和指针域。
struct STU{ char name[20]; char stuno[10]; int age; int score;}stu[50];
typedef struct STU ElemType;
struct LNODE{ ElemType data; struct LNODE *next;};
typedef struct LNODE LNode;typedef struct LNODE *LinkList;
// 结点的初始化int init(LinkList *L){ L=(LNode *)malloc(sizeof(LNode)); if(!L){ exit(1); }}
如何在 C++中引用 C?
C++和 C 是两种完全不同的编译连接处理方式,如果直接在 C++里面调用 C 函数,会找不到函数体,报连接错误,就要在 C++文件中面申明哪些函数是 C 写的,要用 C 的处理方式处理。
- 引用头文件前需要加上 extern “C”如:
extern “C”{#include”S.h”}
- C++调用 C 函数的方法,将用到的函数全部重新声明一遍,如
Extern “C”{Externa void A_app(int);}
C++语言支持函数的重载,C 语言不支持函数的重载。函数被 C++编译后在库中的名字与 C 语言的不同,所以 C++程序不能直接调用 C 函数,C++提供一个 C 连接交换指定符号 extern”C”来解决这个问题。
C 语言中嵌入汇编?
为什么:C 语言是一种中级语言,可以实现高级语言的模块化编程,又可以实现对底层的操作。但是,与汇编相比,C 语言的效率还是比较低的。因此,在对效率与硬件操作要求比较高的地方,可以采用将部分汇编语句嵌入到 C 语言中的方式。
在 gcc 中,可以使用asm表示后面代码为内嵌汇编代码,volatile表示编译器不要优化代码。语法 _asm_ _volatile_(汇编语句模板:输出部分:输入部分:破坏描述部分)
汇编语句模板是汇编命令的字符串,输出部分是需要输出到 C 变量参数列表,输入部分是需要从 C 变量输入到 ASM 汇编的参数列表,破坏描述部分是执行汇编指令会破坏的寄存器描述。
#error 预处理指令的作用是
编译程序时,只要遇到#error 就会生成一个编译错误提示消息,并停止编译。其语法格式为:#error error-message
Makefile 基本结构:
Make 工程管理器。读入 Makefile 文件的内容,能够根据文件时间戳自动发现更新过的文件而减少编译的时间。
内容包括:目标体,依赖文件,创建每个目标体所需要运行的命令。
解释操作系统原理中的作业、进程、线程、管程各自定义?
- 作业:用户在一次解题或一个事物处理过程中要求计算机系统所做工作的集合。他包括用户程序、所需要的数据以及控制命令等。作业是一系列有序的步骤组成的。
- 进程:一个程序在一个数据集合上的一次运行过程。所以一个程序在不同数据集合上运行,乃至一个程序在同样数据集合上的多次运行都是不同的进程。
- 线程:线程是进程中的一个实体,是被系统独立调度和执行的基本单位。
- 管程:管程实际上定义了一个数据结构和在该数据结构上能为并发进程所执行的一组操作,这组操作能同步进程和改变管程中的数据。
在 Windows 编程中互斥量(mutex)的作用和临界区(critical section)类似,区别是?
-
临界区只能用于对象在同一进程里线程间的互斥访问;互斥体可以用于对象进程间或线程间的互斥访问。
-
临界区是非内核对象,只在用户态进行锁操作,速度快;互斥体是内核对象,在核心态进行锁操作,速度慢。
-
临界区和互斥体在 Windows 平台都下可用;Linux 下只有互斥体可用。
-
临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
-
互斥量:为协调共同对一个共享资源的单独访问而设计的。
可剥夺处理机的调度方法
段作业优先。短作业优先=最短剩余时间作业优先
进程调度。
无论是在批处理系统还是分时系统中,用户进程数一般都多于处理机数、这将导致它们互相争夺处理机。另外,系统进程也同样需要使用处理机。这就要求进程调度程序按一定的策略,动态地把处理机分配给处于就绪队列中的某一个进程,以使之执行。
调度的方式:非剥夺式方式和剥夺式方式
- 非剥夺式:分派程序一旦吧处理机分配给某个进程后变一直运行下去,直到进程完成或发生某事件而阻塞时,才把处理机分配给另一个进程。
- 剥夺方式:的那个一个进程正在运行时,系统可以基于某种原则,剥夺以分配给他的处理机,分配给其他的进程。原则有优先原则、短进程优先原则、时间片原则。
算法:
- 先进先出算法(FIFO):算法总是把处理机分配给最先进入就绪队列的进程,一个进程一旦分配到了处理机,便一直进行下去,直到进程阻塞会完成才释放处理机。因此它的服务质量不佳,速度慢。
- 短进程优先:最短 CPU 运行期优先调度算法(SCBF—Shortest CPU Burst First),该算法从就绪队列中选出下一个“CPU 执行期最短”的进程,为之分配处理机。
- 轮转法:前几种算法主要用于批处理系统中,不能作为分时系统中的主调度算法,在分时系统中,都采用时间片轮转法。简单轮转法:系统将所有就绪进程按 FIFO 规则排队,按一定的时间间隔把处理机分配给队列中的进程。这样,就绪队列中所有进程均可获得一个时间片的处理机而运行。
- 多级反馈队列:多级反馈队列方式是在系统中设置多个就绪队列,并赋予各队列以不同的优先权。
分时操作系统与实时操作系统
所谓死锁:
是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
虽然进程在运行过程中,可能发生死锁,但死锁的发生也必须具备一定的条件,死锁的发生必须具备以下四个必要条件。
- 互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
- 请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
- 不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
- 环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的 P0 正在等待一个 P1 占用的资源;P1 正在等待 P2 占用的资源,……,Pn 正在等待已被 P0 占用的资源。
处理方法:
在系统中已经出现死锁后,应该及时检测到死锁的发生,并采取适当的措施来解除死锁。
-
预防死锁: 这是一种较简单和直观的事先预防的方法。方法是通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或者几个,来预防发生死锁。预防死锁是一种较易实现的方法,已被广泛使用。但是由于所施加的限制条件往往太严格,可能会导致系统资源利用率和系统吞吐量降低。
-
避免死锁: 该方法同样是属于事先预防的策略,但它并不须事先采取各种限制措施去破坏产生死锁的的四个必要条件,而是在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免发生死锁。
-
检测和解除死锁:
- 检测:这种方法并不须事先采取任何限制性措施,也不必检查系统是否已经进入不安全区,此方法允许系统在运行过程中发生死锁。但可通过系统所设置的检测机构,及时地检测出死锁的发生,并精确地确定与死锁有关的进程和资源。检测方法包括定时检测、效率低时检测、进程等待时检测等。
- 解除死锁:采取适当措施,从系统中将已发生的死锁清除掉。这是与检测死锁相配套的一种措施。当检测到系统中已发生死锁时,须将进程从死锁状态中解脱出来。常用的实施方法是撤销或挂起一些进程,以便回收一些资源,再将这些资源分配给已处于阻塞状态的进程,使之转为就绪状态,以继续运行。死锁的检测和解除措施,有可能使系统获得较好的资源利用率和吞吐量,但在实现上难度也最大。
产生死锁的 4 个必要条件,只要使其中之一不能成立,死锁就不会出现。
避免死锁的算法:
- 有序资源分配法:给资源统一编号,以上升的次序,这样就破坏了环路条件,避免了死锁。
- 银行家算法:该算法需要检查申请者对资源的最大需求量,如果系统现存的各类资源可以满足申请者的需求,那满足需求者的需求。这样申请者就可以很快的完成计算,然后释放其占用的资源,从而保证了系统中所有的进程都能完成,所以避免死锁的发生。
进程:
Linux 系统中的进程:
- 交互进程:该进程是由 shell 控制和运行的,它既可以在前台运行,也可以在后台运行。
- 批处理进程:该进程不属于某个终端,它被提交到一个队列中以便顺序执行。
- 守护进程:该进程只有在有需要的时候才被唤起在后台运行,一般在 Linux 启动的时候开始执行。
当正在运行的程序等待其他的系统资源时,Linux 内核将取得系统的 CPU 控制权,并将 CPU 分配给其他正在等待的进程。内核的调度算法将决定 CPU 分配给哪个进程。
进程的几种状态:运行、可中断、不可中断、僵尸(进程运行结束,等待父进程销毁它)、停止。
进程的创建与终止: 在新的地址空间里创建进程、读入可执行文件、最后再开始执行。(fork()复制当前进程创建子进程,采用的是写时复制页技术,即内核在创建的时候没有把资源复制过来,资源的复制只是在需要写入数据的时候发生,之前一直是只读的方式共享数据,该技术能使 Linux 具有快速执行能力;exec()函数,读取可执行文件并载入地址空间开始运行)。进程的终止,Linux 首先把进程设置为僵死状态,申请死亡。父进程得到消息,开始调用 wait(),最终终止子进程,子进程占用的资源被回收。
进程的三种状态:
- 就绪状态:进程分配到了除 CPU 以外的必要资源,只要获得处理机就可以执行。
- 执行状态:进程获得处理机,正在执行。
- 阻塞状态:正在执行,由于等待某个事件发生而无法执行,便放弃处理机而处于阻塞状态。引起进程阻塞的原因:等待 IO 完成,申请缓冲区不能完成,等待信件(信号)等。
Linux 的内存管理:
早期的计算机中,程序直接运行在物理内存上。但是不适合现在的系统,这些系统支持多任务、多进程。这时我们就要考虑将系统的有限物理内存如何及时有效的分配给多个程序,这个事情本身我们称之为内存管理。
内存管理想要解决的问题:
- 进程地址空间不能分割,由于程序直接访问的是物理内存,此时程序所使用的内存空间不是隔离的。
- 内存使用的效率低。
- 程序运行的地址不能确定,程序每次运行的时候,都要在内存中开辟一块足够大的空闲区域,而这个空闲区域位置是不确定的,这会带来重定位的问题。
内存管理无非就是想办法解决上面三个问题,如何使进程的地址空间隔离,如何提高内存的使用效率,如何解决程序运行时的重定位问题?
这里引用计算机界一句无从考证的名言:“计算机系统里的任何问题都可以靠引入一个中间层来解决。”
内存管理的几种方式:
- 页式管理。
- 段式管理。
- 段页式管理。
X86 与 X64
Intel 曾用 8086、80286、80386 等作为其 PC 用 CPU 的型号表示法,X86 是 Intel 制造的普通 CPU,X64 是 X86_64 的缩写,是 X86 的改进版,加入了 64 位地址扩展等性能。
linux 系统的 Socket 网络编程
socket 相当于进行网络通信两端的插座,只要对方的 socket 和自己的 socket 有通信连接,双方就可以通信。
服务器端的程序编写:
- 调用 ServerSocket(int port)创建一个服务器端的套接字,并绑定到指定的端口号。
- 调用 accept(),监听连接请求,接收连接,返回通信套接字。
- 调用 Socket 类的 getOutStream()和 getinputStream()获取输出流和输入流,开始网络的输出与输入。
- 关闭通信套接字 Socket.close()
客户端的程序编写:
- 调用 Socket 创建一个流套接字,并连接到服务器。
- 调用 Socket 类的 getoutputstream()和 fetInputStream 获取输出流和输入流,开始网络的数据的发送和接收。
- 关闭通信套接字 socket.close()。
网络中常见的 ping 命令是什么协议?
Ping 的原理是,向指定的 ip 地址发送一定长度的数据包,按照约定,若指定的 IP 地址存在,会返回同样大小的数据包。在特定的时间没有返回,就是超时,一般认为指定的 IP 地址不存在。Ping 使用的是 ICMP(Internet Control Message Protocol,互联网控制消息协议)。有些防火墙会屏蔽 ICMP 协议,所以有时候 ping 的结果只能做参考,ping 不通并不表示对方的 IP 不存在。它是一把双刃剑,别人使用 ping 命令可能会探测到你的计算机上的许多消息。可以安装防火墙,或创建一个禁止所有计算机 ping 本机地址的安全策略。