技术面知识点总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_15437629/article/details/52388685

计算机网络

TCP/IP

模型

这里写图片描述

TCP/IP协议集的分层实施:
这里写图片描述

为什么要给网络划分层次?

(1)各层之间相对独立,每层都可以用最合适的技术实现;
(2)各层只需知道通过层间接口提供的服务,各层因技术进步而做的改动不会影响到其它层,从而保持体系结构的稳定性;
(3)将整个系统划分为若干子系统,易于实现和维护;
(4)促进标准化工作

TCP、UDP

比较

TCP 是面向连接的,是可靠的,需要进行确认和排序,支持流量和错误控制,
而 UDP是无连接的,不进行确认和排序,不提供错误和流量控制功能。
UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制,优势是它在传递数据时非常快,传输开销小
这里写图片描述

TCP报头中的字段包括源端口、目标端口、序列号、确认号、报头长度、保留字段(保留供以后使用)、编码位窗口大小、校验和、紧急指针、选项和数据字段。

源端口 :发送主机的应用程序的端口号(端口 将在本节后面解释)。
目标端口: 目标主机的应用程序的端口号。
序列号 :一个编号, TCP 用来将数据按正确的顺序重新排列(称为排序) 重传丢失或受损的数据。
确认号: TCP 期待接下来收到的数据段。
报头长度 :TCP 报头的长度,以 32 位字为单位。它指出了数据的开始位置,TCP 报头的长度为32 位的整数倍,即使包含选项时亦如此。
保留:总是设置为零。
编码位/标志:用于建立和终止会话的控制功能。
窗口大小:发送方愿意接受的窗口大小,单位为字节
校验和 CRC (Cyclic Redundancy Check,循环冗余校验):由于 TCP 不信任低层,因此检查所有数据。 CRC 检查报头和数据字段。
紧急: 仅当设置了编码位中的紧急指针字段时,该字段才有效。如果设置了紧急指针,该字段表示非紧急数据的开头位置相对于当前序列号的偏移量,单位为字节。
选项 :长度为 32 位的整数倍。也就是说,没有选项时,长度为 。然而,如果包含选项时导致该字段的长度不是 32位的整数倍,必须填充零,以确保该字段的长度为 32 位的整数倍。
数据:传递给传输层的 TCP 协议的信息,包括上层报头。

这里写图片描述
UDP 报头只包含字段源端口、目标端口、长度、校验和和数据。相对于TCP报头,其字段更少了,但代价是不提供 TCP 的高级功能。

源端口号 : 发送主机的应用程序的端口号。
目标端口号 : 目标主机上被请求的应用程序的端口号
长度 : UDP 报头和 UDP 数据的总长度
校验和 : UDP 报头和 UDP 数据的校验和。
数据 : 上层数据。

这里写图片描述

TCP、UDP 必须使用端口号与上层通信,其值不小于1024。在数据段中, TCP,UDP 将它们用 作源端口和目标端口。
小于 1024 的端口号为知名端口号。
下表列出了 TCP/IP 协议簇常用的应用程序、它们的知名端口号以及它们使用的传输层协议: (DHCP->UDP)
这里写图片描述

可靠性

TCP通过下列方式来提供可靠性:

(1)需要进行确认和排序:对于收到的请求,给出确认响应;对失序数据进行重新排序,然后才交给应用层。对于重复数据,进行丢弃。

(2) TCP将保持它首部和数据的检验和。如果收到段的检验和有差错,TCP将丢弃报文段,不给出响应,超时会重发数据。

(3)超时重发:当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。

(4)TCP将数据截断为合理的长度进行发送。而UDP程序产生的数据报长度保持不变。

(5)TCP还能提供流量控制。TCP连接的每一方都有固定大小的缓冲空间,防止发送速率过快导致接收端的缓冲区溢出。TCP使用的流量控制协议是可变大小的滑动窗口协议。

滑动窗口技术:

滑动窗口(Sliding window)是一种流量控制技术。如果发送端不考虑网络情况直接发送数据包,可能会超过接收端的接收能力而产生丢包的情况。
滑动窗口的大小意味着接收方还有多大的缓冲区可以用于接收数据。发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据,防止发送速率过快导致接收端的缓冲区溢出。

滑动窗口处理过程:

(1)TCP连接阶段,双方协商窗口尺寸,同时接收方预留数据缓冲区用于接收数据。
(2)发送方根据协商结果发送符合窗口尺寸的数据字节流,并等待对方确认信息。
(3)然后根据确认信息,对窗口尺寸进行调整,增加或减少发送未得到确认的字节流中的字节数。(例如:出现拥塞时,将发送窗口减小为原来的一半,同时将超时重传的时间间隔扩大一倍)

TCP的拥塞控制由4个核心算法组成:

(1)“慢启动”(Slow Start): 发送方维持一个叫做拥塞窗口cwnd(congestion window)的状态变量。发送方让自己的发送窗口等于拥塞窗口。慢启动算法的思路就是,不要一开始就发送大量的数据,先探测一下网络的拥塞程度,由小到大逐渐增加拥塞窗口的大小。一次传输轮次之后拥塞窗口就加倍。这就是乘法增长。
(2)“拥塞避免”(Congestion voidance):拥塞避免算法让拥塞窗口缓慢增长,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍。这样拥塞窗口按线性规律缓慢增长。
(3)“快速重传 ”(Fast Retransmit):快重传要求接收方在收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等到自己发送数据时捎带确认。快重传算法规定,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期。
(4)“快速恢复”(Fast Recovery):①当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把慢开始门限ssthresh门限减半。但是接下去并不执行慢开始算法。
②因为考虑到如果网络出现拥塞的话就不会收到好几个重复的确认,所以发送方现在认为网络可能没有出现拥塞。所以此时不执行慢开始算法,而是将cwnd设置为ssthresh的大小,然后执行拥塞避免算法。如下图:

三次握手

这里写图片描述
首先服务器创建socket,绑定自身端口号进行监听。
(1)第一次握手:客户端向服务器发送SYN包,假设序列号为x,进入SYN_SEND状态。
(2)第二次握手:服务器收到SYN包,进行确认,发送ACK报文,序列号为x+1,同时自己也发送一个SYN包(假设序列号为y),此时服务器进入SYN_RECV状态。
(3)第三次握手:客户端收到服务器B的SYN+ACK包,向服务器B发送确认包ACK(确认号为y+1),客户端和服务器进入ESTABLISHED状态,完成三次握手。

四次挥手

这里写图片描述
(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。
(2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。
(3)然后服务器B也会发送一个FIN给客户端A。
(4)客户端A发回ACK报文确认,确认序号为收到序号加1。

TIME_WAIT状态

主动关闭连接的一方会进入TIME_WAIT状态。这样设计主要有两个原因:
(1)可靠地实现TCP连接的终止。四次挥手过程中,如果客户端最终发送的ACK丢失,服务器会重发FIN,通过保持TIME_WAIT状态可以允许它重发ACK,保证TCP连接正常地终止。
(2)让旧连接的重复分组在网络中消失。因为如果复用旧连接的端口马上开启一个新的连接,原来旧连接中的数据包可能会被这个新连接收到。处于TIME_WAIT状态的连接就可以避免这种情况。它会持续2个最大分解生命期(MSL),也就是一个IP数据包能在网络中生存的最长时间,保证新连接建立的时候,旧连接的数据包已经在网络中消失了。
这里写图片描述

1

TCP/IP、Http、Socket的区别

TCP协议对应于传输层,主要解决数据如何在网络中传输,
而HTTP协议对应于应用层,主要解决如何包装数据。
socket则是对TCP/IP协议的封装和应用。是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。

ARP协议

ARP (Address Resolution Protocol):地址解析协议: 负责将某个IP地址解析成对应的MAC地址。

原理:

主机会将包含目标IP地址的ARP请求广播到网络上的所有主机,目标主机收到ARP报文进行响应,这样该主机就得到目标主机的物理地址;收到返回消息后将该IP地址和物理地址存入本机ARP缓存中并保留一定时间,下次请求时直接查询ARP缓存以节约资源。

问题:

Arp是建立在网络中各个主机互相信任的基础上的,主机收到应答报文时不会检测该报文的真实性就会将其记入本机ARP缓存;因此攻击者就可以通过伪造IP地址和MAC地址实现ARP欺骗,使主机发送的信息无法到达预期的主机或到达错误的主机,造成网络阻塞或中断或中断。

解决:

杀毒软件,安装ARP防火墙,
将IP和MAC地址进行绑定
对于不经常变动的网络环境,可以通过静态ARP的方式,让ARP映射不被新的ARP数据刷新

icmp协议

ICMP是(Internet Control Message Protocol)因特网控制报文协议。可向主机提供有关网络故障的消息,比如网络通不通、主机是否可达、路由是否可用等。比如我们经常使用的Ping命令就是基于icmp协议的。

ping的原理

Ping主要有两种情况,一种是同一网段,一种是跨网段的。

如果在同一网段,主机A ping主机B,主机A会先检查自己缓存中是否有主机B的MAC地址,如果没有B的MAC地址,就会向外发送一个ARP广播包。交换机有学习MAC地址的能力,它会检索自己有没有保存主机B的MAC地址,如果有,就直接返回给A主机,如果没有,就会向所有端口发送ARP广播,直到主机B收到了报文进行响应。这时候主机A 学到了主机B的MAC地址,就把这个MAC封装到ICMP报文中向主机B发送;当主机B收到了这个报文后,就按同样的格式,返回一个值给主机A,完成了同一网段内的Ping过程。

如果主机A发现主机B不在同一个网段,同样通过发送ARP广播,先学到网关的MAC地址,发送ICMP报文,路由器查找路由表,找到去主机B的端口,将原来主机A的MAC地址改为自己的MAC地址向主机B转发。这样一来主机B也学到路由器端口MAC,通过路由器就可以完成了跨网段的Ping过程。

Post和Get

区别:
1、Get是从服务器端获取数据,Post则是向服务器端发送数据。
2、在客户端,Get方式通过URL提交数据,在URL地址栏可以看到请求消息,该消息被编码过;Post数据则是放在Html header内提交。
3、Get方式提交的参数及参数值会在地址栏显示,不安全,而Post不会,比较安全。
4、对于Get方式,服务器端用Request.QueryString获取变量的值;对用Post方式,服务器端用Request.Form获取提交的数据值。
5、Get方式提交的数据最多1024字节,而Post则没有限制。

Cookie和Session

比较 Cookie Session
储存位置 客户端 服务器端
目的 跟踪会话,也可以保存用户偏好设置或者保存用户名密码等 跟踪会话
安全性 不安全 安全

session技术是要使用到cookie的,之所以出现session技术,主要是为了安全。

HTTP和HTTPS

基础知识:Http的请求格式如下。

<request line>主要包含三个信息:
1、请求的类型(GET或POST),
2、要访问的资源(如\res\img\a.jif),
3、Http版本(http/1.1)

<header>                用来说明服务器要使用的附加信息
<blank line>            这是Http的规定,必须空一行
[<request-body>]        请求的内容数据

HTTPS和HTTP的区别

 一、https协议需要到ca申请证书,一般免费证书很少,需要交费。
 二、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
 三、http是超文本传输协议,信息是明文传输,是无状态的;https 则是具有安全性的ssl加密传输协议。是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

HTTP状态码:
消息(1字头)
成功(2字头)
重定向(3字头)
请求错误(4字头)
服务器错误(5、6字头)

HTTP1.0和HTTP1.1

推荐: http://blog.csdn.net/elifefly/article/details/3964766

HTTP 1.0规定浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个TCP连接,服务器完成请求处理后立即断开TCP连接,服务器不跟踪每个客户也不记录过去的请求。

HTTP1.1概括如下:
1. 长链接
2. 文件断点续传
3. 请求头Host字段,一个服务器多个网站
4. 提供了身份认证,状态管理,Cache缓存等机制

HTTP 1.1支持持久连接,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟。

HTTP 1.1还允许客户端不用等待上一次请求结果返回,就可以发出下一次请求,但服务器端必须按照接收到客户端请求的先后顺序依次回送响应结果,以保证客户端能够区分出每次请求的响应内容,这样也显著地减少了整个下载过程所需要的时间。

HTTP 1.0不支持Host请求头字段,HTTP 1.1中增加Host请求头字段后,WEB浏览器可以使用主机头名来明确表示要访问服务器上的哪个WEB站点,这才实现了在一台WEB服务器上可以在同一个IP地址和端口号上使用不同的主机名来创建多个虚拟WEB站点。

从输入url到显示网页

后台发生了什么?

  1. 输入url地址
  2. 浏览器通过域名系统(DNS)查找域名的 IP 地址,包括:浏览器缓存->系统缓存->路由器缓存->ISP DNS -> 递归搜索 你的ISP的DNS服务器从跟域名服务器开始进行递归搜索,
  3. 浏览器向 web 服务器发送一个 HTTP 请求
  4. 服务器的重定向响应
  5. 浏览器跟踪重定向地址
  6. 服务器处理请求
  7. 服务器返回一个 HTTP 响应
  8. 浏览器显示 HTML
  9. 浏览器发送请求获取嵌入在 HTML 中的资源(如图片、音频、视频、CSS、JS等等)
  10. 浏览器发送异步请求

操作系统

进程与线程的区别

(调度、并发性、拥有资源、开销)
进程:是程序的一次执行,负责一个程序运行时的内存分配空间,是资源分配的最小单位;
线程:是进程的一个执行单元,是cpu调度的最小单位。一个进程可以有多个线程。
不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行。线程的划分尺度小于进程,使得多线程程序的并发性高。
每个进程都有独立的内存地址空间;而同一进程中的所有线程共享同一块内存和系统资源,有利于提高了程序的运行效率。
概括地说:
进程速度慢,内存占用、开销大,但可靠性高,编译调试简单;
线程速度快,内存占用、开销小,但可靠性较差,编译调试复杂。
故需要频繁创建销毁’或需要大量计算的优先用线程。

注:多线程和多进程的区别
必须从cpu调度,上下文切换,数据共享,多核cup利用率,资源占用,等等各方面回答,然后有一个问题必须会被问到:哪些东西是一个线程私有的?答案中必须包含寄存器。

进程间通信方式:

进程间通信主要包括管道, 系统 IPC(包括消息队列,信号量,共享内存), 信号,SOCKET.
(1) 管道包括三种:
①普通管道 pipe, 通常有限制,一是半双工,只能单向传输;二是只能在父子进程间使用.
②流管道 s_pipe: 去除了第一种限制,可以双向传输.
③命名管道:name_pipe, 去除了第二种限制,可以在许多并不相关的进程之间进行通讯.
(2) 消息队列( message queue ) : 消息队列是消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
(3) 信号量( semophore ) :信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程
之间的同步手段。
(4) 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建, 但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计
的。它往 往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
(5) 信号 ( signal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
(6) 套接字( socket ):套解字也是一种进程间通信机制,与其他通信机制不同的是它可用于不同机器间的进程通信。

线程间的通信方式:

● 使用全局变量:由于多个线程可能更改全局变量,因此全局变量最好声明为violate
● 使用消息实现通信:在Windows程序设计中,每一个线程都可以拥有自己的消息队列(UI线程默认自带消息队列和消息循环,工作线程需要手动实现消息循环),因此可以采用消息进行线程间通信sendMessage,postMessage。
● 使用事件CEvent类实现线程间通信:Event对象有两种状态:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。

注:Linux进程通信与线程通信的区别:

▶ linux中的进程,是由fork()系统调用创建的;进程间都有独立的地址空间,它们之间不能之间通信,必须通过进程间通信机制(IPC)来完成。常见得有PIPE、命名管道、消息队列、信号量、共享内存、信号、socket等。
▶ linux中的线程,是由clone()系统调用创建的,同一进程中的所有线程共享同一块内存和系统资源的,故线程间可以直接访问,但要注意并发情况,实现线程同步

同一进程中的所有线程共享同一块内存和系统资源的,故线程间可以直接访问,线程间通信的主要目的用于线程同步。

线程间的同步方式:

线程间的同步方法大体可分为两类:用户模式和内核模式。
内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态;
用户模式就是不需要切换到内核态,只在用户态完成操作。
用户模式下的方法有:原子操作(例如一个单一的全局变量), 临界区。
内核模式下的方法有: 事件,信号量,互斥量。

(1)、临界区( CCriticalSection)
当多个线程访问一个独占性共享资源时,可以使用临界区对象。拥有临界区的线程可以访问被保护起来的资源或代码
段,其他线程若想访问,则被挂起,直到拥有临界区的线程放弃临界区为止。
具体应用方式:
1)、 定义临界区对象 CcriticalSection g_CriticalSection;
2)、 在访问共享资源(代码或变量)之前,先获得临界区对象, g_CriticalSection.Lock();
3)、 访问共享资源后,则放弃临界区对象, g_CriticalSection.Unlock();

(2)、事件( CEvent)
事件机制,则允许一个线程在处理完一个任务后,主动唤醒另外一个线程执行任务。
比如在某些网络应用程序中,一个线程如 A 负责侦听通信端口,另外一个线程 B 负责更新用户数据,利用事件机制,则线程 A 可以通知线程 B 何时更新用户数据。每个 Cevent 对象可以有两种状态:有信号状态和无信号状态。 Cevent 类对象有两种类型:人工事件和自动事件。
●自动事件对象,在被至少一个线程释放后自动返回到无信号状态;
●人工事件对象,获得信号后,释放可利用线程,但直到调用成员函数 ReSet()才将其设置为无信号状态。在创建 Cevent
对象时,默认创建的是自动事件。

(3)、互斥量( CMutex)
互斥对象和临界区对象非常相似,只是其允许在进程间使用,而临界区只限制与同一进程的各个线程之间使用,但是更节省资源,更有效率。

(4)、信号量( CSemphore)
使用一个计数器来限制可以使用某共享资源的线程数目。
CSemaphore 类对象保存了对当前访问某一个指定资源的线程的计数值,该计数值是当前还可以使用该资源的线程数目。如果这个计数达到了零,则所有对这个 CSemaphore 类对象所控制的资源的访问尝试都被放入到一个队列中等待,直到超时或计数值不为零为止。

用户线程和内核线程

●用户线程(ULT)指不需要内核支持而在用户程序中实现的线程,不依赖于操作系统核心,应用进程利用线程库提供创建、同步、调度和管理线程的函数来控制用户线程。不需要用户态/核心态切换,速度快,操作系统内核不知道多线程的存在,因此一个线程阻塞将使得整个进程(包括它的所有线程)阻塞。由于这里的处理器时间片分配是以进程为基本单位,所以每个线程执行的时间相对减少。
●内核线程(KST,也叫守护线程):由操作系统内核创建和撤销。内核维护进程及线程的上下文信息以及线程切换。一个内核线程由于I/O操作而阻塞,不会影响其它线程的运行。Windows NT和2000/XP支持内核线程。

内核线程能很好的利用多核cpu,更有利于并发使用多处理器的资源,一个线程阻塞其他线程仍然可以正常运行;当线程进行切换的时候,需要由用户态转化为内核态,上下文切换开销大。
用户线程上下文切换开销小,但是如果一个线程阻塞整个进程就阻塞了,不能很好的利用多核cpu。

注: 通过处理器实现进程交错执行的机制称为上下文切换。

如何创建守护进程

  1. fork()创建子进程,父进程退出;
  2. setsid()在子进程中创建新会话
  3. Chdir(“/”)改变当前目录为根目录;
  4. umask()重设文件权限掩码;
  5. 关闭文件描述符;

内核态和用户态的区别

•当一个进程执行系统调用而陷入内核代码中执行时,我们就称进程处于内核态。此时处理器处于特权级最高的0级。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。
•当进程在执行用户自己的代码时,则称其处于用户态。即此时处理器在特权级最低的3级。当正在执行用户程序而突然被中断程序中断时,此时用户程序也可以象征性地称为处于进程的内核态。因为中断处理程序将使用当前进程的内核栈。

为什么需要

内核态?

当进程处于内核态时,特权级最高,才能使用某些重要指令。在CPU的所有指令中,有一些指令是非常危险的,如果错用,将导致整个系统崩溃。比如:清内存、设置时钟等。普通的应用程序只能使用那些不会造成灾难的指令。

什么时候进入内核态?

当一个进程执行系统调用而陷入内核代码中执行时,我们就称进程处于内核态;当正在执行用户程序而突然被中断程序中断时,此时用户程序也可以象征性地称为处于进程的内核态。因为中断处理程序将使用当前进程的内核栈。

并行与并发

• 并行是指多个CPU可以同时单独处理多个进程,是真正的同时进行。
• 并发是指多个进程的指令交错执行,宏观上看起来,多个任务就像是同时在进行。
系统一次性预存很多批处理任务进入内存,然后通过一定的调度算法来让这些批处理任务划分CPU的时间,这样看起来,多个任务就像是同时在进行。这个模式,就叫做并发。

常见的调度算法

  1. 先来先服务(FCFS, First Come First Serve):就是按照各个作业进入系统的自然次序来调度作业。这种调度算法的优点是实现简单,公平。其缺点是不利于短作业,因为短作业等待处理的时间可能比实际运行时间长得多;
  2. 短作业优先(SJF, Shortest Job First):: 就是优先调度并处理短作业,所谓短是指作业的运行时间短。而在作业未投入运行时,并不能知道它实际的运行时间的长短,因此需要用户在提交作业时同时提交作业运行时间的估计值。
  3. 最高优先级调度(Priority Scheduling):每一个作业规定一个表示该作业优先级别的整数,当需要将新的作业由输入井调入内存处理时,优先选择优先数最高的作业。
    4.最高响应比优先算法(HRN):既利于短作业又有利于长作业,选择响应比最高的作业运行。响应比=1+作业等待时间/作业处理时间。
  4. 时间片轮转(RR, Round Robin)
  5. 多级反馈队列调度(multilevel feedback queue scheduling)

实时调度算法:

  1. 最早截至时间优先 EDF
  2. 最低松弛度优先 LLF

协程

• 调度算法对于程序来说都是“被动”式的。比如调度算法会把CPU的时间分配给还在IO等待中的程序,虽然可以立即检查并交出CPU,但是这里还是有一个切换的过程。
• 协程可以说是“主动式”的,由程序来告诉计算机,我要进行等待IO行为了,可以把CPU的控制权交给别人,这个实现的基础是用户态的。所以,协程就是在用户态线程中,两个程序协商好了,通过某种方式协作运营,共享CPU控制权的方法。一般来说,这个协商的方法通用的关键字就是yield。

协程与进程线程比较有什么优势?

协程避免了无意义的调度,由此可以提高性能,但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力

孤儿进程

一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

僵尸进程

如果子进程已经退出, 但是父进程又没有调用wait/waitpid获取子进程的状态信息,释放它占用的资源,该子进程将成为僵尸进程。

僵尸进程怎么解除?怎么避免?

需要找到其父进程:ps -ef | grep defunct_process_pid并kill掉。
如何避免:在fork子进程之后我们都要wait它们;同时,当子进程退出的时候,内核都会给父进程一个SIGCHLD信号,我们可以建立一个捕获SIGCHLD信号的处理函数,在函数体中调用wait(或waitpid),就可以清理退出的子进程以达到防止僵尸进程的目的。

物理内存与虚拟内存

物理内存就是系统硬件提供的内存大小,是真正的内存;
虚拟内存是为了满足物理内存的不足而提出的策略,它是利用磁盘空间虚拟出的一块逻辑内存,用作虚拟内存的磁盘空间被称为交换空间(SwapSpace)。

linux内存管理机制

linux的内存管理采取的是分页存取机制,为了保证物理内存能得到充分的利用,内核会在适当的时候将物理内存中不经常使用的数据页自动交换到虚拟内存的交换空间中,而将经常使用的信息保留到物理内存。

Linux内存管理相比于windows的一个优秀特性,就是它的缓存机制,它会从内存中划分出一部分空间作为cache、buffers。这样当操作系统需要读取某些文件时,会首先在buffers和cached内存区查找,如果没有找到需要的数据才从磁盘读取。因为内存的访问速度必磁盘快很多,而且数据一旦被访问就很有可能在短期内被再次访问。所以提高了Linux系统的数据访问性能。

注:buffers主要用来存放目录、文件属性及权限等等;而cached直接用来缓存我们常用到的文件和数据。

要深入了解linux内存运行机制,需要知道下面提到的几个方面:

•Linux系统会根据系统配置不时地进行页面交换操作,以保持一定量的空闲物理内存,有些配置下即使并没有什么事情需要内存,Linux也会交换出暂时不用的内存页面。这可以避免等待交换所需的时间。
•Linux 进行页面交换是有条件的,不是所有页面在不用时都交换到虚拟内存,linux内核根据“最近最经常使用”算法,仅仅将一些不经常使用的页面文件交换到虚拟内存
•交换空间的页面在使用时会首先被交换到物理内存,如果此时没有足够的物理内存来容纳这些页面,它们又会被马上交换出去,如此以来,虚拟内存中可能没有足够空间来存储这些交换页面,最终会导致linux出现假死机、服务异常等问题,linux虽然可以在一段时间内自行恢复,但是恢复后的系统已经基本不可用了。所以,需要合理规划和设计Linux内存的使用

注:
内存监控常用指令:top、free;
linux可用内存=free+cached+buffers=total-used

内存泄漏

指某块内存块使用完后没有及时释放,不能被再次使用,我们就说这块内存泄漏了。

如何避免内存泄漏?

1.减少不必要的全局变量或者生命周期较长的对象,像HashMap等的使用很容易出现内存泄露。
2.注意程序逻辑,避免“死循环”之类的
3.避免创建过多的对象和临时变量 。
4,成对使用new与delete,malloc和free;用类指针对象代替原始指针,利用析构函数自动释放内存资源
5,对于循环引用导致的内存泄漏可以使用弱引用的智能指针来解决
6,监听器在释放对象时及时删除,数据库连接及时关闭。

注:智能指针是一种资源管理类,通过对原始指针进行封装,在资源管理对象进行析构时对指针指向的内存进行释放,通常使用引用计数方式进行管理。

如何定位内存泄漏?

1,如何发现内存泄漏?

实际上不同的系统都带有内存监视工具,我们可以从监视工具收集一段时间内的堆栈内存信息,观测增长趋势,来确定是否有内存泄漏。在 Linux 平台可以用 ps 命令,来监视内存的使用,比如 ps -aux , (观测指定进程的VSZ值)。

我们可以使用top指令观察进程的动态内存总额。
程序退出时,可以使用ps、kill两个命令检测内存使用情况和进行回收。

定位方法(包括静态分析,动态实时检测)

2, 静态分析

包括手动检测和静态工具分析,这是代价最小的调试方法。

2.1 手动检测:通过少量的实践和适当的文本搜索,您能够快速验证平衡的 malloc() 和 free() 或者 new 和 delete 的源主体。人工检测
2.2 静态代码分析工具
比如 splint, PC-LINT, BEAM 等。
BEAM 可以检测四类问题: 没有初始化的变量;废弃的空指针;内存泄漏;冗余计算。而且支持的平台比较多

3,动态运行检测

实时检测工具主要有 valgrind, Rational purify 等。
  1、valgrind。valgrind检测的内存泄漏是非常准的,可以精确定位到代码行甚至是变量。valgrind基于valginrd core框架,这是个非常有强大的框架,他的作用不仅仅在于检测内存泄漏的,强烈建议测试新手通读下全部的文档。valgind自己也会有误报和漏报,所有具体场景需要具体分析。报告中一旦出现definitely lost的标记,就表示一定会有内存泄漏,泄漏的字节数也会报告出来,可以根据泄漏的内存大小和请求次数计算出到底是那个变量没有释放。
  2、利用pmap+gdb,pmap可以列出特定进程的所有内存分配的地址和大小,通过gdb就可以直接看这些地址属于什么变量,通过统计这些内存地址的大小,就可以很容易的发现问题。利用自动化的gdb调试工具也可以很方便的定位。
  3、其他的还包括memprof、商业工具Purify IBM出品,官方宣传说的不错,但是这种不开放的技术,在业界得不到认可,国内大公司一般那都不用,只有人傻钱多的公司在用。
  4、利用一些trace工具,比如ptrace,strace之类的工具,这些trace工具会追踪特定的api,只需要统计malloc和free的调用次数就可以简单的发现是否有泄漏,但是无法定位代码行。另外还有一个更高深的工具,SystemTap,这个在国内应用还不多,但是非常厉害,可以方便hook程序的关键逻辑并插入探针。从而可以方便的检测内存泄漏。Systemtap目前还不通用,而且安装复杂,暂时不推荐使用,可以关注下,过几年可能会大规模应用。
  

linux系统中找出并解决程序错误方法:

● 添加’print’ 打印语句
● 查询 (/proc, /sys 等)
跟踪 命令(strace/ltrace)
Valgrind 工具
GDB单步调试

死锁

死锁:是指多个进程因竞争共享资源而造成的一种僵局,若无外力作用,这些进程都在等待对方执行完毕才能继续往下执行,都陷入了无限的等待中。

原因:

  1. 系统资源不足后分配不当
  2. 程序推进顺序不当

4个必要条件:

互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。

解决死锁的基本方法:

(1)预防死锁:打破产生死锁的4个必要条件之一
资源一次性分配:(破坏请求和保持条件)
可剥夺资源:即当某进程新的资源未满足时,释放已占有的资源(破坏不可剥夺条件)
资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)

(2)避免死锁(银行家算法):
系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全状态,则将资源分配给进程;否则,进程等待。

(3)检测死锁:
系统检测出死锁发生的位置和原因,并通过外力把它从死锁状态中解脱出来,常采用的方法有:
1)从其他进程剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
2)撤消死锁进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。

(4). 解除死锁: 1. 剥夺资源 2. 撤销进程

自旋锁和互斥锁

1,自旋锁:线程一直是running(加锁——>解锁),它是死循环检测的,加锁全程消耗cpu,随着持锁时间的增加,开销线性增长。主要用在临界区持锁时间非常短且CPU资源不紧张的情况下。

2,互斥锁:线程会从sleep(加锁)——>running(解锁),过程中有上下文的切换,cpu的抢占,信号的发送等开销。但是临界区持锁时间的大小并不会对互斥锁的开销造成影响。所以虽然互斥锁的起始开销可能高于自旋锁,却更适合用于临界区持锁时间比较长的操作,比如:
1 临界区IO操作
2 临界区代码复杂或者循环量大
3 临界区竞争非常激烈
4 单核处理器

分页和分段

分页: 用户程序的地址空间被划分成若干固定大小的区域,称为“页”,相应地,内存空间分成若干个物理块,页和块的大小相等。可将用户程序的任一页放在内存的任一块中,实现了离散分配。会产生内零头。

分段: 将用户程序地址空间分成若干个大小不等的段,每段可以定义一组相对完整的逻辑信息。存储分配时,以段为单位,段与段在内存中可以不相邻接,也实现了离散分配。会产生外零头。

分页与分段的主要区别

  1. 页是信息的物理单位,分页是为了实现非连续分配,以便解决内存碎片问题,是由于系统管理的需要.段是信息的逻辑单位,它含有一组意义相对完整的信息,分段的目的是为了更好地实现共享,满足用户的需要.
  2. 页的大小固定,由系统确定,将逻辑地址划分为页号和页内地址是由机器硬件实现的.而段的长度却不固定,决定于用户所编写的程序,通常由编译程序在对源程序进行编译时根据信息的性质来划分.
  3. 分页的作业地址空间是一维的.分段的地址空间是二维的.
    在编程的时候,如果是分页存储,你只需要给定一个虚拟地址,然后操作系统会自己去把虚拟地址划分成虚页号和页内偏移,所以是一维的。而如果是段式存储的话,你需要给定的虚拟地址必须包括虚段号和段内偏移量,因为分段式从程序员的角度来分的,操作系统并不知道,所以段式存储是二维的。

• 什么是缺页中断?
(缺页中断就是要访问的页不在主存,需要操作系统将其调入主存后再进行访问。)

页面置换算法

  1. 先进先出FIFO
  2. 最近最久未使用算法LRU:最近一段时间里最久没有使用过的页面予以置换.
  3. clock算法
  4. 最佳置换算法OPT:不可能实现

中断和轮询的特点

1,中断是指在计算机执行期间,系统内发生急需处理事件,使得CPU暂时中断当前正在执行的程序而转去执行相应的事件处理程序。待处理完毕后又返回原来被中断处继续执行或调度新的进程执行的过程。
2,轮询是指定时地对各种设备轮流询问一遍有无处理要求。有要求的则加以处理。在处理I/O设备的要求之后,处理机返回继续工作。尽管轮询需要时间,但轮询要比I/O设备的速度要快得多,所以一般不会发生不能及时处理的问题。

当然,再快的处理机,能处理的输入输出设备的数量也是有一定限度的。而且,程序轮询毕竟占据了CPU相当一部分处理时间,因此,程序轮询是一种效率较低的方式,在现代计算机系统中已很少应用。
轮询——效率低,等待时间很长,CPU利用率不高。
中断——容易遗漏一些问题,CPU利用率高。

中断程序的分类

1, 硬中断就是由硬件引起的中断。如键盘、定时器,以及一些硬件故障等。
硬中断又可分为可屏蔽中断(如键盘产生的);非屏蔽中断(如由微处理器产生的);
2, 软中断是由中断指令(INT)引起的中断。

select,poll和epoll

 select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

(但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间)

基本上select有3个缺点(poll改善了第一个缺点,epoll改了三个缺点.):
1. 连接数受限
2. 查找配对速度慢
3. 数据由内核拷贝到用户态

• select缺点:
1,最大并发数限制:默认是1024个fd;
2,效率低:采用轮询处理,每次都会线性扫描整个fd_set,集合越大速度越慢;
3,内核/用户空间内存拷贝问题;

• epoll的提升:
1,本身没有最大并发连接的限制,仅受系统中进程能打开的最大文件数目限制;
2,效率提升:epoll是维护一个队列,直接看队列是不是空就可以了,epoll只会对”活跃”的socket进行操作,只有活跃的socket才会主动的去调用fd的callback函数,
3,省去不必要的内存拷贝:epoll通过内核与用户空间mmap同一块内存实现。

• epoll 实现:
epoll是一种IO多路复用技术,可以非常高效的处理数以百万计的socket句柄,非常适合监视大量在线用户,也就是长连接,少量活跃用户的情况。

首先要调用epoll_create建立一个epoll对象。然后通过epoll_ctl可以操作建立的epoll,例如,将刚建立的socket加入到epoll中让其监控,或者把epoll正在监控的某个socket句柄移出epoll,不再监控它等等。epoll_wait在调用时,在给定的timeout时间内,当在监控的句柄中有事件发生时,就返回用户态的进程。

epoll比select的优越之处:select每次调用时都要将用户态的socket列表copy到内核态,非常低效;而我们调用epoll_wait时就不用传递socket句柄给内核,因为内核已经在epoll_ctl中拿到了要监控的socket句柄列表。

相关问题:
• connect会阻塞,怎么解决?(提示:1,使用计时器;2,设置非阻塞,返回之后用select检测状态)
• 如果select返回可读,结果只读到0字节,什么情况?(某个套接字集合没有准备好,可能select内存用FD_CLR将该位清为0).

selec,poll和epoll区别总结:http://www.cnblogs.com/Anker/p/3265058.html

边沿触发和水平触发

边缘触发是指每当状态变化时发生一个 io 事件,条件触发是只要满足条件就发生一个 io 事件

  1. 边缘触发(ET):使用此种模式,只能获取一次就绪通知,如果没有处理完全部数据,并且再次调用epoll_wait()的时候,它将会阻塞,因为就绪事件已经释放出来了。ET的效能更高,但是要求也更高。在ET模式下,必须一次性处理完所有事件。ET只支持非阻塞socket)

  2. 水平触发(LT,也叫条件触发):使用此种模式,当数据可读的时候,epoll_wait()将会一直返回就绪事件。如果你没有处理完全部数据,并且再次在该 epoll实例上调用epoll_wait()才监听描述符的时候,它将会再次返回就绪事件,因为有数据可读。

概念:

同步、异步、阻塞、非阻塞

同步和异步关注的是消息通信机制。
同步是A发出一个调用后等待B完成返回结果再继续向下执行;
异步是A不需等待返回结果,继续处理其他事情,等B完成后再取回结果。

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.
阻塞是指调用结果返回之前,当前线程会被挂起,线程只有在得到结果之后才会返回。
非阻塞指没有得到返回结果之前,该调用不会阻塞当前线程。

计算机从电源加载开始的启动过程

启动引导程序BIOS-开机自检-加载操作系统(从磁盘读到RAM中)-读取配置文件)

C/C++

关键字

A.基本数据类型(5个)

  void :声明函数无返回值或无参数,声明无类型指针,显式丢弃运算结果

  char :字符型类型数据,属于整型数据的一种

  int :整型数据,通常为编译器指定的机器字长

  float :单精度浮点型数据,属于浮点数据的一种

  double :双精度浮点型数据,属于浮点数据的一种

 B .类型修饰关键字(4个)

  short :修饰int,短整型数据,可省略被修饰的int。

  long :修饰int,长整形数据,可省略被修饰的int。

  signed :修饰整型数据,有符号数据类型

  unsigned :修饰整型数据,无符号数据类型

 C .复杂类型关键字(5个)

  struct :结构体声明

  union :共用体声明

  enum :枚举声明

  typedef :声明类型别名

  sizeof :得到特定类型或特定类型变量的大小

 D .存储级别关键字(6个)

  auto :指定为自动变量,由编译器自动分配及释放。通常在栈上分配

  static :指定为静态变量,分配在静态变量区,修饰函数时,指定函数作用域为文件内部

  register :指定为寄存器变量,建议编译器将变量存储到寄存器中使用,也可以修饰函数形参,建议编译器通过寄存器而不是堆栈传递参数

  extern :指定对应变量为外部变量,即标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。

  const :与volatile合称“cv特性”,指定变量不可被当前线程/进程改变(但有可能被系统或其他线程/进程改变)

  volatile :与const合称“cv特性”,指定变量的值有可能会被系统或其他进程/线程改变,强制编译器每次从内存中取得该变量的值

C/C++基本数据类型所占字节数:

32位编译器:

char :1个字节
char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)
short int : 2个字节
int: 4个字节
unsigned int : 4个字节
float: 4个字节
long: 4个字节
long long: 8个字节
unsigned long: 4个字节
double: 8个字节

64位编译器:

char :1个字节
char*(即指针变量): 8个字节
short int : 2个字节
int: 4个字节
unsigned int : 4个字节
float: 4个字节
long: 8个字节
long long: 8个字节
double: 8个字节

关键字static的作用

在c语言中,static关键字的作用:

  • 函数体内static变量的作用范围为该函数体,该变量的内存只被分配一次,因此其值在下次调用时人维持上次的值。
  • 在模块内的static全局变量可以被模块内的所有函数访问,但不能被模块外的其他函数访问。
  • 在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在申明它的模块内
  • 在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝
  • 在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量

C++中:

  • 隐藏函数,所有未加static的全局变量和函数都具有全局可见性。
  • 保持变量内容的持久性。共有两种变量存储在静态存储区:全局变量和static变量。存储在静态数据区的变量会在程序刚开始时完成唯一的一次初始化。static可以控制变量的课件范围(隐藏)
  • 静态数据成员是静态存储的,所以必须对它进行初始化。static变量默认默认初始化为0.

注:C++成员函数不能同时又static和const进行修饰。因为static表示该函数是静态成员函数,为类所有,而const是用于修饰成员函数的,两者矛盾。

关键字const的作用

c语言中,const关键字的作用主要有以下几点:

(1)可以定义const常量,具有不可变性。 例如: const int Max=100; int Array[Max];
(2)便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患。例如: void f(const int i) { ………} 编译器就会知道i是一个常量,不允许修改;
(3)可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。
(4)可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。 还是上面的例子,如果在函数体内修改了i,编译器就会报错; 例如:
void f(const int i) { i=10;//error! }
(5) 为函数重载提供了一个参考。
class A { ……
void f(int i) {……} //一个函数
void f(int i) const {……} //上一个函数的重载 ……
};
(6) 可以节省空间,避免不必要的内存分配。 例如:

#define PI 3.14159 //常量宏 
const doulbe Pi=3.14159; //此时并未将Pi放入ROM中 ...... 
double i=Pi; //此时为Pi分配内存,以后不再分配! 
double I=PI; //编译期间进行宏替换,分配内存 
double j=Pi; //没有内存分配 
double J=PI; //再进行宏替换,又一次分配内存! 

const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。
(7) 提高了效率。 编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。

const define inline的区别

本质:define只是字符串替换,const参与编译运行,inline由编译器控制。

(1)define:仅仅是宏替换,不做类型检查;不占用内存
(2)const:拥有类型,会执行相应的类型;占用内存;(const内存效率更高,编译器通常将const变量保存在符号表中,使它成为一个编译期间的常量,没有存储和读取的操作)
(3)inline:会真正地编译到代码中;inline函数是否展开由编译器决定,有时候函数太大时编译器可能会选择不展开相应的函数。

如何

用 gdb 调试

使用编译器(cc/gcc/g++)的 -g 参数把调试信息加到可执行文件中。
使用gdb 指令启动GDB。
gdb的使用

如何调试运行中的程序

用 gdb调试运行状态下的程序,核心的是gdb内部的attach命令,例如 attach pid

运行了几天的程序崩掉,如何分析错误

首先在调试环境的console或log文件中,根据错误信息的内容初步判断错误类型与位置,比如实际为null的内存被使用,当然也可能是其他错误,在这个位置加入断点,一旦执行该行,程序即崩溃(无法调试的环境下则加入充分的debug信息),再次运行程序,在这一点上监视各个变量,找到导致崩溃的变量值,向上追溯,找到赋值操作的根源时,错误的原因也就找到了,接下来就是讨论对策与解决方案

程序编译与链接

推荐: http://www.ruanyifeng.com/blog/2014/11/compiler.html

Bulid过程可以分解为4个步骤:预处理(Prepressing), 编译(Compilation)、汇编(Assembly)、链接(Linking)

以c语言为例:

1 预处理

预编译过程主要处理那些源文件中的以“#”开始的预编译指令,主要处理规则有:

  1. 将所有的“#define”删除,并展开所用的宏定义
  2. 处理所有条件预编译指令,比如“#if”、“#ifdef”、 “#elif”、“#endif”
  3. 处理“#include”预编译指令,将被包含的文件插入到该编译指令的位置,注:此过程是递归进行的
  4. 删除所有注释
  5. 添加行号和文件名标识,以便于编译时编译器产生调试用的行号信息以及用于编译时产生编译错误或警告时可显示行号
  6. 保留所有的#pragma编译器指令。

2 编译

编译过程就是把预处理完的文件进行一系列的词法分析、语法分析、语义分析及优化后生成相应的汇编代码文件。这个过程是整个程序构建的核心部分。

3 汇编

汇编器是将汇编代码转化成机器可以执行的指令,每一条汇编语句几乎都是一条机器指令。经过编译、链接、汇编输出的文件成为目标文件(Object File)

4 链接

链接的主要内容就是把各个模块之间相互引用的部分处理好,使各个模块可以正确的拼接。
链接的主要过程包块 地址和空间的分配(Address and Storage Allocation)、符号决议(Symbol Resolution)和重定位(Relocation)等步骤。

静态链接

静态链接就是在编译链接时直接将需要的执行代码拷贝到调用处,优点就是在程序发布的时候就不需要的依赖库,也就是不再需要带着库一块发布,程序可以独立执行,但是体积可能会相对大一些。
静态库的链接可以使用静态链接,动态链接库也可以使用这种方法链接导入库

动态链接

动态链接就是在编译的时候不直接拷贝可执行代码,而是通过记录一系列符号和参数,在程序运行时将需要的动态库加载到内存中,当程序运行到指定的代码时,去执行已加载的动态库可执行代码,最终达到运行时连接的目的。优点是多个程序可以共享同一段代码,而不需要在磁盘上存储多个拷贝,程序初始化时间较短,但运行期间的性能比不上静态链接的程序。

什么是库

所谓库就是一些功能代码经过编译连接后的可执行形式:
静态库:在程序编译时会被连接到目标代码中,程序运行时将不再依赖静态库。
动态库:在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在

• 标准库函数和系统调用的区别?
库是可重用的模块,处于用户态;系统调用是os提供的服务,处于内核态,是指最底层的一个调用.库函数中有很大部分是对系统调用的封装

如何生成静态库?如何生成动态库?

生成静态库:首先由源文件编译生成.o文件,使用ar命令将.o转换成.a文件,生成静态库(以lib开头,以.a结尾);
动态库的后缀是.so,编译生成动态库的命令为:gcc (-fpic) -shared -o libmyfunction.so myfunction.c (-fpic 使输出的对象模块是按照可重定位地址方式生成的。 -shared指定把对应的源文件生成对应的动态链接库文件)

•引用动态库时 为什么编译能通过,而运行不能通过:编译是按你指定的路径去编译,而运行时是通过环境变量去找,所以这时候就要指定路径,把路径加到环境变量中就OK了。

• 如何查看链接的动态库(ldd a 会列出a所依赖的动态库)

makefile

文件的作用是什么?

makefile带来的好处就是“自动化编译”。
makefile定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。一旦写好,只需要一个make命令,整个工程完全自动编译,极大地提高了软件开发的效率。

如何编写 makefile

makefile的语法:
target(目标) : prerequisites.(依赖文件)
command(shell命令)


vim makefile
hello.o:hello.c hello.h
gcc –c hello.o -Lm
make
./hello

Makefile的规则:

  目标 : 需要的条件 (注意冒号两边有空格)
  命令  (注意前面用tab键开头)

  解释一下:
  1 目标可以是一个或多个,可以是Object File,也可以是执行文件,甚至可以是一个标签。
  2 需要的条件就是生成目标所需要的文件或目标
  3 命令就是生成目标所需要执行的脚本

i++

是否是原子操作?

不是。.i++分为三个阶段:内存到寄存器;寄存器自增;写回内存。这三个阶段中间都可以被中断分离开.而原子操作是不可分割的,不加锁互斥是不行的

算法与数据结构

数组和链表

从逻辑结构来看:
1. 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费;数组根据下标查找方便,访问效率高。
2. 链表动态地进行内存分配,需要时可以用new分配内存空间,不需要时用delete将已分配的空间释放,不会造成内存空间的浪费。且可以方便地插入、删除操作项。(数组中插入、删除数据项时,需要移动其它数据项,非常繁琐;链表根据next指针找到下一个元素)

从内存存储来看:
1. (静态)数组从栈中分配空间, 对于程序员方便快速,但是自由度小
2. 链表从堆中分配空间, 自由度大但是申请管理比较麻烦

综上,如果需要快速访问数据,很少或不插入和删除元素,就应该用数组;相反, 如果需要经常插入和删除元素就需要用链表数据结构了。

数组和集合

数组:长度固定可以存储基本数据类型和引用数据类型(存储的元素必须是同一数据类型);
集合:长度可变,只能存储引用数据类型(数组,类,接口)(存储的元素可以是不同数据类型);

二叉树

●二叉搜索/查找树:左子树的所有节点值均小于根节点值,右子树的所有节点值均不小于根节点值;或是一颗空树
●完全二叉树:只有最下面一层的节点度可以小于2,且都集中在最左边的若干位置
●平衡二叉树(AVL):左右子树的深度差不超过1,;或为空树。(windows对进程地址空间的管理用到了AVL树)
●红黑树:是一种自平衡的二叉查找树,追求局部平衡,通过对任何一条从根节点到叶子节点的简单路径上的各个节点的颜色进行约束,确保一个节点的两颗子树高度差不会相差两倍,保证每次插入最多只需3次旋转就能达到平衡。典型的应用是关联数组。
性质:
1)结点或者是红的或者是黑的;
2)根结点是黑的;
3)每个叶结点(即空节点NIL)是黑的;
4)如果一个结点是红的,则它的两个孩子都是黑的;
5)对每个结点,从该结点到其他子结点的所有路径上包含相同数目的黑结点。

●B树:是一种多路查找树,一般用于数据库系统中,分支多层数少,降低I/O耗时
●B+树:是B树的变种树,数据只保存在叶子节点中,其他节点中含有关键字,每个关键字不保存数据只用来索引,为文件系统而生。
●Trie树:又名单词查找树,主要处理字符串,将字符串的相同前缀保存在相同节点中,经常被搜索引擎系统用于文本词频统计。
●哈夫曼树(最优二叉树):给定n个权值的叶子结点,构造一棵二叉树,若树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

结点的带权路径长度:结点到树根之间的路径长度与该结点上权的乘积.

下面演示了用Huffman算法构造一棵Huffman树的过程:
这里写图片描述
用一个类似于树杈的“树枝”连接两个最小权值。在顶点处计算出这两个数字的和并写在上面。然后再比较剩下的数字和这个和的大小,如果该和正好是下一步的两个最小数的其中一个那么这个树直接往上生长就可以了。如果该数不是下一步的两个最小数的其中一个那么,就并列生长。

通过哈夫曼树来构造的编码称为哈弗曼编码:
霍夫曼编码是一种无前缀的不定长的编码。解码时不会混淆。其主要应用在数据压缩,加密解密等场合。
使用频度较高的字符分配较短的编码,使用频度较低的字符分配较长的编码。但当信息源各符号出现的概率很不平均的时候,哈夫曼编码的效果才明显。

假如我有A,B,C,D,E五个字符,出现的频率(即权值)分别为5,4,3,2,1,取两个最小权值作为左右子树依次建立哈夫曼树,如下图:
这里写图片描述
其中各个权值替换对应的字符即为下图:
这里写图片描述
所以各字符对应的编码为:A->11,B->10,C->00,D->011,E->010

排序算法与字符串匹配算法

交换排序:冒泡排序、快速排序
选择排序:简单选择排序、堆排序
插入排序:直接插入排序、二分法插入排序、希尔排序
归并排序、基数排序

详见:http://blog.csdn.net/qq_15437629/article/details/52433062

最短路径算法

1,Dijkstra(迪杰斯特拉)算法

Dijkstra算法(迪杰斯特拉)是典型的最短路径路由算法,用于计算一个节点到其他所有节点的最短路径。其采用的是贪心算法的策略,主要特点是以起始点为中心向外层层扩展,找出距起始点最近的点,直到扩展到终点为止。
Dijkstra算法能得出最短路径的最优解,但由于它遍历计算的节点很多,所以效率低。可以用堆优化。

大概过程:
创建两个表,OPEN, CLOSE。
OPEN表保存所有已生成而未考察的节点,CLOSED表中记录已访问过的节点。
1. 访问路网中距离起始点最近且没有被检查过的点,把这个点放入OPEN组中等待检查。
2. 从OPEN表中找出距起始点最近的点,找出这个点的所有子节点,把这个点放到CLOSE表中。
3. 遍历考察这个点的子节点。求出这些子节点距起始点的距离值,放子节点到OPEN表中。
4. 重复第2和第3步,直到OPEN表为空,或找到目标点。

2,Floyd算法\Floyd-Warshall算法

Floyd算法又称为插点法,是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,与Dijkstra算法类似。

优点:容易理解,可以算出任意两个节点之间的最短距离,代码编写简单。
缺点:时间复杂度比较高,不适合计算大量数据。时间复杂度:O(n^3);空间复杂度:O(n^2)

Floyd算法适用于APSP(All Pairs Shortest Paths,多源最短路径),是一种动态规划算法,稠密图效果最佳,边权可正可负。此算法简单有效,由于三重循环结构紧凑,对于稠密图,效率要高于执行|V|次Dijkstra算法,也要高于执行V次SPFA算法。

算法过程:
  1,从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。
  2,对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比已知的路径更短。如果是更新它。
  把图用邻接矩阵G表示出来,如果从Vi到Vj有路可达,则G[i,j]=d,d表示该路的长度;否则G[i,j]=无穷大。定义一个矩阵D用来记录所插入点的信息,D[i,j]表示从Vi到Vj需要经过的点,初始化D[i,j]=j。把各个顶点插入图中,比较插点后的距离与原来的距离,G[i,j] = min( G[i,j], G[i,k]+G[k,j] ),如果G[i,j]的值变小,则D[i,j]=k。在G中包含有两点之间最短道路的信息,而在D中则包含了最短通路径的信息。
  比如,要寻找从V5到V1的路径。根据D,假如D(5,1)=3则说明从V5到V1经过V3,路径为{V5,V3,V1},如果D(5,3)=3,说明V5与V3直接相连,如果D(3,1)=1,说明V3与V1直接相连。

3,SPFA算法\Bellman-Ford算法

SPFA(Shortest Path Faster Algorithm)(队列优化)算法是求单源最短路径的一种算法,它还有一个重要的功能是判负环(在差分约束系统中会得以体现),在Bellman-ford算法的基础上加上一个队列优化,减少了冗余的松弛操作,是一种高效的最短路算法。

对SPFA的一个很直观的理解就是由无权图的BFS转化而来。在无权图中,BFS首先到达的顶点所经历的路径一定是最短路(也就是经过的最少顶点数),所以此时利用数组记录节点访问可以使每个顶点只进队一次,但在带权图中,最先到达的顶点所计算出来的路径不一定是最短路。一个解决方法是放弃数组,此时所需时间自然就是指数级的,所以我们不能放弃数组,而是在处理一个已经在队列中且当前所得的路径比原来更好的顶点时,直接更新最优解。

SPFA算法有两个优化策略SLF和LLL——SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j)< dist(i),则将j插入队首,否则插入队尾; LLL:Large Label Last 策略,设队首元素为i,队列中所有dist值的平均值为x,若dist(i)>x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出队进行松弛操作。 SLF 可使速度提高 15 ~ 20%;SLF + LLL 可提高约 50%。 在实际的应用中SPFA的算法时间效率不是很稳定,为了避免最坏情况的出现,通常使用效率更加稳定的Dijkstra算法。

4,A*算法

A*算法;A*(A-Star)算法是一种静态路网中求解最短路径最有效的直接搜索方法,也是解决许多搜索问题的有效算法。算法中的距离估算值与实际值越接近,最终搜索速度越快。

数据库

范式

第一范式:原子性,字段不可分割
第二范式:就是完全依赖,不能部分依赖;
第三范式:不能存在传递依赖

第一范式(例如:学生信息表):

学生编号    姓名  性别         联系方式 
20080901  张三    男   email:zs@126.com,phone:88886666 
20080902  李四    女   email:ls@126.com,phone:66668888  

以上的表就不符合,第一范式:联系方式字段可以再分,所以变更为正确的是:

学生编号   姓名  性别     电子邮件     电话 
20080901  张三  男   zs@126.com  88886666 
20080902  李四  女   ls@126.com  66668888

第二范式(例如:学生选课表):

学生    课程      教师    教师职称      教材             教室  上课时间 
李四    Spring  张老师   java讲师  《Spring深入浅出》    301   08:00 
张三    Struts  杨老师   java讲师  《Struts in Action》 302   13:30  

这里通过(学生,课程)可以确定教师、教师职称,教材,教室和上课时间,所以可以把(学生,课程)作为主键。但是,教材并不完全依赖于(学生,课程),只拿出课程就可以确定教材,因为一个课程,一定指定了某个教材。这就叫不完全依赖,或者部分依赖。出现这种情况,就不满足第二范式。
修改后:

选课表:
学生     课程    教师    教师职称   教室  上课时间 
李四    Spring  张老师   java讲师  301 08:00 
张三    Struts  杨老师   java讲师  302 13:30 

课程表: 
 课程      教材         
SpringSpring深入浅出》  
StrutsStruts in Action

所以,第二范式可以说是消除部分依赖。可以减少插入异常,删除异常和修改异常。

第三范式:
上例中修改后的选课表中,一个教师能确定一个教师职称。这样,教师依赖于(学生,课程),而教师职称又依赖于教师,这叫传递依赖。第三范式就是要消除传递依赖。
修改后:

选课表:  
学生     课程    教师    教室  上课时间 
李四    Spring  张老师   301 08:00 
张三    Struts  杨老师   302 13:30 

教师表: 
教师    教师职称 
张老师   java讲师 
杨老师   java讲师  

这样,新教师的职称在没被选课的时候也有地方存了,没人选这个教师的课的时候教师的职称也不至于被删除,修改教师职称时只修改教师表就可以了。

主键和外键

主键:能够唯一表示数据表中的每条记录的字段或者字段的组合就称为主键。不能有重复的,不允许为空
比如 :
  学生表(学号,姓名,性别,班级)
其中每个学生的学号是唯一的,学号就是一个主键

  上机记录表(卡号,学号,姓名、序列号)
上机记录表中单一一个属性无法唯一标识一条记录,学号和姓名的组合才可以唯一标识一条记录,所以 学号和姓名的属性组是一个主键

外键:若有两个表A,B,x是A的主键,而B中也有x字段,则x就是表B的外键,外键约束主要用来维护两个表之间数据的一致性。外键可以有重复的, 可以是空值

例如:上机记录表中的学号和学生表中的学号相对应,并且学生表中的学号是学生表的主键,则称上机记录表中的学号是学生表的外键

关系:外键一定是另外某个表的主键。

聚集索引和非聚集索引的区别?
聚集索引一定是唯一索引。但唯一索引不一定是聚集索引。
聚集索引,在索引页里直接存放数据,而非聚集索引在索引页里存放的是索引,这些索引指向专门的数据页的数据。

数据库的基本锁

(1)共享(S)锁:多个事务可封锁一个共享页; 通常是该页被读取完毕,S锁立即被释放。
它是非独占的,允许其他事务同时读取其锁定的资源,但不允许其他事务修改它。
(2)排它(X)锁:仅允许一个事务封锁此页;X锁一直到事务结束才能被释放,其他任何事务必须等到X锁被释放才能对该页进行访问。适用于修改数据的场合。
(3)更新(U)锁:更新锁在的初始化阶段用来锁定可能要被修改的资源,避免使用共享锁造成的死锁现象。
用来预定要对此页施加X锁,它允许其他事务读,但不允许再施加U锁或X锁;当被读取的页将要被更新时,则升级为X锁;U锁一直到事务结束时才能被释放。

数据库带来的并发问题

丢失更新

当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,会发生丢失更新问题。每个事务都不知道其它事务的存在。最后的更新将重写由其它事务所做的更新,这将导致数据丢失。   

例如:事务A和事务B同时修改某行的值,事务A将数值改为1并提交,事务B将数值改为2并提交。这时数据的值为2,事务A所做的更新将会丢失。

如何解决呢?基本两种思路,一种是悲观锁,另外一种是乐观锁;

悲观锁:假定并发冲突总是发生,屏蔽一切可能违反数据完整性的操作
乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。

脏读

当一个事务读取另一个事务尚未提交的修改时,产生脏读。

例如:
1.Mary的原工资为1000, 财务人员将Mary的工资改为了8000(但未提交事务)
2.Mary读取自己的工资 ,发现自己的工资变为了8000,欢天喜地!
3.而财务发现操作有误,回滚了事务,Mary的工资又变为了1000
像这样,Mary记取的工资数8000是一个脏数据。

解决办法:在一个事务提交前,任何其他事务不可读取其修改过的值,则可以避免该问题。

不可重复读

 同一查询在同一事务中多次进行,由于其他提交事务所做的修改或删除,每次返回不同的结果集,此时发生非重复读。

例如:
在事务1中,Mary 读取了自己的工资为1000,操作并没有完成 ,这时财务人员修改了Mary的工资为2000,并提交了事务.在事务1中,Mary 再次读取自己的工资时,工资变为了2000

解决办法:只有在修改事务完全提交之后才可以读取数据,则可以避免该问题。

幻读  

 同一查询在同一事务中多次进行,由于其他提交事务所做的插入操作,每次返回不同的结果集,此时发生幻像读。
 当对某行执行插入或删除操作,而该行属于某个事务正在读取的行的范围时,会发生幻像读问题。事务第一次读的行范围显示出其中一行已不复存在于第二次读或后续读中,因为该行已被其它事务删除。同样,由于其它事务的插入操作,事务的第二次或后续读显示有一行已不存在于原始读中。

例如:目前工资为1000的员工有10人。

事务1,读取所有工资为1000的员工。
这时事务2向employee表插入了一条员工记录,工资也为1000。 事务1再次读取所有工资为1000的员工 共读取到了11条记录。

解决办法:在操作事务完成数据处理之前,任何其他事务都不可以添加新数据,则可避免该问题

sql基本语句

SQL (结构化查询语言)是用于执行查询的语法。但是 SQL 语言也包含用于更新、插入和删除记录的语法。可以把 SQL 分为两个部分:数据操作语言 (DML) 和 数据定义语言 (DDL)。

查询和更新指令构成了 SQL 的 DML 部分:
SELECT - 从数据库表中获取数据
UPDATE - 更新数据库表中的数据
DELETE - 从数据库表中删除数据
INSERT INTO - 向数据库表中插入数据

SQL 的数据定义语言 (DDL) 部分使我们有能力创建或删除表格。我们也可以定义索引(键),规定表之间的链接,以及施加表间的约束。
SQL 中最重要的 DDL 语句:
CREATE DATABASE - 创建新数据库
ALTER DATABASE - 修改数据库
CREATE TABLE - 创建新表
ALTER TABLE - 变更(改变)数据库表
DROP TABLE - 删除表
CREATE INDEX - 创建索引(搜索键)
DROP INDEX - 删除索引


基本语法:

下面的例子是一个名为 “Persons” 的表:
这里写图片描述
注释:SQL 语句对大小写不敏感。SELECT 等效于 select。

1,获取名为 “LastName” 和 “FirstName” 的列的内容(从名为 “Persons” 的数据库表):

SELECT LastName,FirstName FROM Persons

2,如果有多个city相同,仅需列出一次的话(distinct):

SELECT DISTINCT City FROM Persons 

3,显示所有姓为 “Carter” 并且名为 “Thomas” 的人:

SELECT * FROM Persons WHERE FirstName='Thomas' AND LastName='Carter'

4,以字母顺序显示

SELECT * FROM Persons ORDER BY Year
SELECT * FROM Persons ORDER BY Year DESC (逆序)

5,insert;update;delete

INSERT INTO Persons VALUES ('Gates', 'Bill', 'Xuanwumen 10', 'Beijing''1986')
INSERT INTO Persons (LastName, Address) VALUES ('Wilson', 'Champs-Elysees')       #在指定列中插入
UPDATE Persons SET FirstName = 'Fred' WHERE LastName = 'Wilson' #为 lastname 是 "Wilson" 的人添加 firstname
DELETE FROM Person WHERE LastName = 'Wilson' 

高级语法:

1,top:

SELECT TOP 2 * FROM Persons     #从"Persons" 表中选取头两条记录
SELECT TOP 50 PERCENT * FROM Persons   #选出前50%的记录

2,like:

SELECT * FROM Persons WHERE City LIKE 'N%'   #选取居住在以 "N" 开头的城市里的人
SELECT * FROM Persons WHERE City LIKE '%g'    #选取居住在以 "g" 结尾的城市里的人
SELECT * FROM Persons WHERE City LIKE '%lon%'   #选取居住在包含 "lon" 的城市里的人
SELECT * FROM Persons WHERE City NOT LIKE '%lon%'   #选取居住在不包含 "lon" 的城市里的人
SELECT * FROM Persons WHERE City LIKE '[ALN]%'    #选取居住在以 "A、L或N" 开头的城市里的人

通配符说明:
这里写图片描述

3,in:从表中选取姓氏为 Adams 和 Carter 的人:

SELECT * FROM Persons WHERE LastName IN ('Adams','Carter')

4,join:用于根据两个或多个表中的列之间的关系,从这些表中查询数据。

“Persons” 表:
这里写图片描述

“Orders” 表:
这里写图片描述

“Id_O” 列是 Orders 表中的的主键,同时,”Orders” 表中的 “Id_P” 列用于引用 “Persons” 表中的人,而无需使用他们的确切姓名。请留意,”Id_P” 列把上面的两个表联系了起来。

我们可以从两个表中获取数据:谁订购了产品,和订购了什么产品

SELECT Persons.LastName, Persons.FirstName, Orders.OrderNo FROM Persons, Orders WHERE Persons.Id_P = Orders.Id_P 

除了上面的方法,我们也可以使用关键词 JOIN 来从两个表中获取数据:

SELECT Persons.LastName, Persons.FirstName, Orders.OrderNo FROM Persons INNER JOIN Orders ON Persons.Id_P = Orders.Id_P ORDER BY Persons.LastName

补充:INNER JOIN;LEFT JOIN;RIGHT JOIN:
(1)INNER JOIN 返回两表符合匹配条件的行
(2)LEFT JOIN 关键字会从左表 (table_name1) 那里返回所有的行,即使在右表 (table_name2) 中没有匹配的行。
(3)RIGHT JOIN 关键字会右表 (table_name2) 那里返回所有的行,即使在左表 (table_name1) 中没有匹配的行。

1 事务

数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。

2 数据库索引

推荐: http://tech.meituan.com/mysql-index.html

MySQL索引背后的数据结构及算法原理

聚集索引,非聚集索引,B-Tree,B+Tree,最左前缀原理

高并发

如何在j2ee项目中处理高并发量访问?

1、HTML静态化。因为纯静态化的html页面是效率最高、消耗最小的。
2、图片服务器分离。图片是最消耗资源的,我们有必要将图片与页面进行分离,基本上大型网站都会有独立的、甚至很多台的图片服务器。
3、在面对大量访问的时候,数据库的瓶颈很快就能显现出来,我们可以使用数据库集群或者库表散列。
(数据库集群在架构、成本、扩张性方面都会受到所采用数据库类型的限制,库表散列是从应用程序的角度来考虑改善系统架构,通过在应用程序中安装功能模块将数据库进行分离,不同的模块对应不同的数据库或者表,再按照一定的策略对某个页面或者功能进行更小的数据库散列,比如一个论坛就可以对帖子、用户按照板块和ID进行散列数据库和表,这样就能够低成本的提升系统的性能并且有很好的扩展性。)
4、缓存。Linux上提供的Memory Cache是常用的缓存接口,比如用Java开发的时候就可以调用MemoryCache对一些数据进行缓存和通讯共享。
5、搭建镜像站点,数据进行定时更新或者实时更新
6、CDN加速技术。通过在现有的网络中增加一层新的网络架构,将网站的内容发布到最接近用户的网络“边缘”,使用户可以就近取得所需的内容,提高用户访问网站的响应速度。
7、负载均衡技术,将整个区间段的业务流分配到合适的应用服务器进行处理:

负载均衡方式

(1)、DNS负载均衡,在DNS中为多个地址配置同一个名字,查询这个名字的客户机将得到其中一个地址,使得不同的客户访问不同的服务器,达到负载均衡的目的(DNS负载均衡是一种简单而有效的方法,但是它不能区分服务器的差异,也不能反映服务器的当前运行状态)。
(2)、使用代理服务器,将用户请求转发给多台服务器,从而达到负载均衡的目的,提升网页的访问速度。
(3)、NAT负载均衡,通过网络地址转换的网关,将一个外部IP地址映射为多个内部IP地址,对每次连接请求动态使用其中一个内部地址,达到负载均衡的目的。
(4)、协议内部支持负载均衡 ,比如HTTP协议中的重定向能力等,HTTP运行于TCP连接的最高层。
(5)、对于大型网络,可以采用混合型负载均衡 ,由于多个服务器群内硬件设备、各自的规模、提供的服务等的差异,我们可以考虑给每个服务器群采用最合适的负载均衡方式,然后又在这多个服务器群间再一次负载均衡向外界提供服务,从而达到最佳的性能

如何设计一个高并发的系统

① 数据库的优化,包括合理的事务隔离级别、SQL语句优化、索引的优化
② 使用缓存,尽量减少数据库 IO
③ 分布式数据库、分布式缓存
④ 服务器的负载均衡

海量数据处理问题

2.5亿个整数中找出不重复的整数的个数,内存空间不足以容纳这2.5亿个整数

有点像鸽巢原理,整数个数为2^32,也就是,我们可以将这2^32个数,划分为2^8个区域(比如用单个文件代表一个区域),然后将数据分离到不同的区域,然后不同的区域在利用bitmap就可以直接解决了。也就是说只要有足够的磁盘空间,就可以很方便

二进制文件中有2.5亿个数字,其中只有10个数字重复。
解决思路如下:
采用2-Bitmap(每个数分配2bit,00表示不存在,01表示出现一次,10表示多次,11无意义)进行。然后扫描这2.5亿个整数,查看Bitmap中相对应位,如果是00变01,01变10,10保持不变。所描完事后,查看bitmap,把对应位是01的整数输出即可。
映射关系如下:

|00 00 00 00| //映射|3 2 1 0|
|00 00 00 00| //映射|7 6 5 4|
……
|00 00 00 00| 

因此,每个char字符可表示4个数字。
代码如下:

#include<stdio.h>
#include<memory.h>
int n=250000000; //数据个数
unsigned char flags[250000000/4];//需要n/4个字符

unsigned get_val(int idx) //获取数字出现次数

{
  int i=idx/4;
  int j=idx%4;
  unsigned ret=(flags[i]&(0x3<<(2*j)))>>(2*j); //位运算
  return ret;
}

unsigned set_val(int idx,unsigned int val) //在8bit中设置数字出现次数
{
  int i=idx/4; //确定数字在字符数组中的位置
  int j=idx%4; //确定数字在8bit中的位置
  unsigned tmp=(flags[i]&~((0x3<<(2*j)))) | (((val%4)<<(2*j))); //位运算
  flags[i]=tmp;
  return 0;
}

unsigned add_one(int idx) //数字出现次数+1,如果大于2,不变
{
  int i=get_val(idx);
  if (i>=2)
    return 1;
  else
  {
    set_val(idx,i+1);
    return 0;
  }
}

int main()
{
  FILE *fp;
  fp=fopen("number.bin","rb"); //读取二进制数据文件
  int i;
  int s=sizeof(int);
  memset(flags,0,sizeof(flags));
  while(!feof(fp))
  {
    fread(&i,s,1,fp);
    add_one(i);
  }
  int all=0;
  for(i=0;i<n;i++)
  {
    if(get_val(i)==1) //出现次数等于1,统计输出
      all++;
  }
  printf("%d\n",all);
  fclose(fp);
  return 0;
}

运行结果如下:

249999980

real    0m35.566s
user    0m31.694s
sys    0m1.300s

可见,利用位运算、二进制数据文件,可以高效地解决这个问题。

linux常用命令

lsof

lsof(list open files):列出当前系统打开文件(普通文件、目录、管道、socket、网络文件)。因为打开文件的描述符列表提供了大量关于这个应用程序本身的信息,因此通过lsof工具能够查看这个列表对系统监测以及排错将是很有帮助的。

在linux环境下,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件。所以如传输控制协议 (TCP) 和用户数据报协议 (UDP) 套接字等,系统在后台都为该应用程序分配了一个文件描述符,无论这个文件的本质如何,该文件描述符为应用程序与基础操作系统之间的交互提供了通用接口。因为应用程序打开文件的描述符列表提供了大量关于这个应用程序本身的信息,因此通过lsof工具能够查看这个列表对系统监测以及排错将是很有帮助的。

netstat

Netstat 命令用于显示各种网络相关信息,如网络连接,路由表,接口状态 (Interface Statistics),masquerade 连接,多播成员 (Multicast Memberships) 等等。

一些常用的netstat选项包括:

-a : 显示所有socke信息
-r : 显示路由信息
-i : 显示网卡借口统计
-s : 显示网络协议统计

实用命令实例:

  1. 列出所有端口 (包括监听和未监听的)
    列出所有端口 netstat -a
    列出所有 tcp 端口 netstat -at
    列出所有 tcp 端口 netstat -au

  2. 列出所有处于监听状态的 Sockets
    只显示监听端口 netstat -l
    只列出所有监听 tcp 端口 netstat -lt
    只列出所有监听 udp 端口 netstat -lu
    只列出所有监听 UNIX 端口 netstat -lx

  3. 显示每个协议的统计信息
    显示所有端口的统计信息 netstat -s
    显示 TCP 或 UDP 端口的统计信息 netstat -st 或 -su

  4. 在 netstat 输出中显示 PID 和进程名称 netstat -p

strace

strace可以跟踪到一个进程产生的系统调用,包括参数,返回值,执行消耗的时间。

在Linux中,进程不能直接访问硬件设备,当进程需要访问硬件设备(比如读取磁盘文件,接收网络数据等等)时,必须由用户态模式切换至内核态模式,通过系统调用访问硬件设备。strace可以跟踪到进程执行时的系统调用和所接收的信号。

ptrace系统调用

1, 功能描述:
ptrace 提供了一种机制使得父进程可以观察和控制子进程的执行过程,同时提供查询和修改子进程的镜像和寄存器的能力。主要用于执行断点调试和系统调用跟踪。

2,ptrace 的使用流程:
父进程 fork() 出子进程,子进程中执行我们所想要 trace 的程序,子进程需要先调用一次 ptrace,以 PTRACE_TRACEME 为参数,来告诉内核当前进程已经正在被 traced,最后使用 exec 等操作来初始化一个进程跟踪。

当子进程执行 execve() 之后,子进程会进入暂停状态,把控制权转给它的父进程(SIG_CHLD信号), 而父进程在fork()之后,就调用 wait() 等子进程停下来,当 wait() 返回后,父进程就可以去查看子进程的寄存器或者对子进程做其它的事情了。

3,用法:

  #include <sys/ptrace.h>
  long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

参数说明:
request:请求执行的行为,
pid:目标进程标识。
addr:执行 peek 和 poke 操作的目标地址。
data:对于 poke 操作,存放数据的地方。对于 peek 操作,获取数据的地方。

request 可能的选择有:  
  PTRACE_TRACEME    //指示父进程跟踪某个子进程的执行。任何传给子进程的信号将导致其停止执行,同时父进程调用 wait() 时会得到通告。之后,子进程调用 exec() 时,核心会给它传送 SIGTRAP 信号,在新程序开始执行前,给予父进程控制的机会。pid, addr, 和 data参数被忽略。

    以上是唯一由子进程使用的请求,剩下部分将由父进程使用的请求。
PTRACE_PEEKTEXT, PTRACE_PEEKDATA    //从子进程内存空间 addr 指向的位置读取一个字,并作为调用的结果返回。Linux 内部对文本段和数据段不加区分,所以目前这两个请求相等。data 参数被忽略。

    PTRACE_PEEKUSR    //从子进程的用户区 addr 指向的位置读取一个字,并作为调用的结果返回。

    PTRACE_POKETEXT, PTRACE_POKEDATA    //将 data 指向的字拷贝到子进程内存空间由 addr 指向的位置。

    PTRACE_POKEUSR    //将 data 指向的字拷贝到子进程用户区由 addr 指向的位置。

    PTRACE_GETREGS, PTRACE_GETFPREGS    //将子进程通用和浮点寄存器的值拷贝到父进程内由 data 指向的位置。addr 参数被忽略。

    PTRACE_GETSIGINFO    //获取导致子进程停止执行的信号信息,并将其存放在父进程内由 data 指向的位置。addr 参数被忽略。

    PTRACE_SETREGS, PTRACE_SETFPREGS    //从父进程内将 data 指向的数据拷贝到子进程的通用和浮点寄存器。addr 参数被忽略。

    PTRACE_SETSIGINFO    //将父进程内由 data 指向的数据作为 siginfo_t 结构体拷贝到子进程。addr 参数被忽略。

    PTRACE_SETOPTIONS    //将父进程内由 data 指向的值设定为 ptrace 选项,data 作为位掩码来解释,由下面的标志指定。

    PTRACE_O_TRACESYSGOOD    //当转发 syscall 陷阱 (traps) 时,在信号编码中设置位7,即第一个字节的最高位。例如:SIGTRAP | 0x80。这有利于追踪者识别一般的陷阱和那些由 syscall 引起的陷阱。

    PTRACE_O_TRACEFORK    //通过 (SIGTRAP | PTRACE_EVENT_FORK << 8) 使子进程下次调用 fork() 时停止其执行,并自动跟踪开始执行时就已设置 SIGSTOP 信号的新进程。新进程的 PID 可以通过 PTRACE_GETEVENTMSG 获取。

    PTRACE_O_TRACEVFORK    //通过 (SIGTRAP | PTRACE_EVENT_VFORK << 8) 使子进程下次调用 vfork() 时停止其执行,并自动跟踪开始执行时就已设置 SIGSTOP 信号的新进程。新进程的 PID 可以通过 PTRACE_GETEVENTMSG 获取。

    PTRACE_O_TRACECLONE    //通过 (SIGTRAP | PTRACE_EVENT_CLONE << 8) 使子进程下次调用 clone() 时停止其执行,并自动跟踪开始执行时就已设置 SIGSTOP 信号的新进程。新进程的 PID 可以通过 PTRACE_GETEVENTMSG 获取。

    PTRACE_O_TRACEEXEC    //通过 (IGTRAP | PTRACE_EVENT_EXEC << 8) 使子进程下次调用 exec() 时停止其执行。

    PTRACE_O_TRACEVFORKDONE //通过 (SIGTRAP | PTRACE_EVENT_VFORK_DONE << 8) 使子进程下次调用 exec() 并完成时停止其执行。
PTRACE_O_TRACEEXIT    //通过 (SIGTRAP | PTRACE_EVENT_EXIT << 8) 使子进程退出时停止其执行。子进程的退出状态可通过 PTRACE_GETEVENTMSG 。

    PTRACE_GETEVENTMSG    //获取刚发生的 ptrace 事件消息,并存放在父进程内由 data 指向的位置。addr参数被忽略。

    PTRACE_CONT    //重启动已停止的进程。如果 data 指向的数据并非0,同时也不是 SIGSTOP 信号,将会作为传递给子进程的信号来解释。那样,父进程可以控制是否将一个信号发送给子进程。addr 参数被忽略。

    PTRACE_SYSCALL, PTRACE_SINGLESTEP    //如同 PTRACE_CONT 一样重启子进程的执行,但指定子进程在下个入口或从系统调用退出时,或者执行单个指令后停止执行,这可用于实现单步调试。addr 参数被忽略。

    PTRACE_SYSEMU, PTRACE_SYSEMU_SINGLESTEP    //用于用户模式的程序仿真子进程的所有系统调用。

    PTRACE_KILL    //给子进程发送 SIGKILL 信号,从而终止其执行。data,addr 参数被忽略。

    PTRACE_ATTACH    //衔接到pid指定的进程,从而使其成为当前进程的追踪目标。

    PTRACE_DETACH    // PTRACE_ATTACH 的反向操作。

gdb的使用

1、首先获得程序的PID : ps -ef | grep xxxxx
2、进入调试程序 : gdb attach PID
3、gcore命令生成CORE文件
4、进程信息可以用info proc显示
5、 寄存器信息可以用info reg显示

GDB命令行最基本操作:

● 程序启动方式:

A.冷启动:
gdb program (program是执行文件)
gdb –p pid

B.热启动:
(gdb) attach pid (调试运行中的程序)

C.传入命令行参数:
GDB启动时,可以加上一些GDB的启动参数:
-symbols/-s : 从指定文件中读取符号表。
-se file : 从指定文件中读取符号表信息,并把他用在可执行文件中。
-core/-c : 调试时core dump的core文件。
-directory/-d : 加入一个源文件的搜索路径。默认搜索路径是环境变量中PATH所定义的路径。

● 设置观察点:
○ w Expression,当Expression是一个变量名时,这个变量变化时会停止执行;你也可以使用条件来限定,比如w (z>28),当z大于28时,程序停止。注意观察点一般使用在更大范围上的变量,而不是本地变量,因为在局部变量上设置的观察点在局部结束时(比 如该变量所在的函数执行结束时)就被取消了。
○ 当然这并不包含main的情况,因为main函数执行结束后程序就结束了。
● 查看栈帧:
○ 栈帧指的是在一个函数调用时,该函数调用的运行信息(包含本地变量、参数以及函数被调用的位置)存储的地方。每当一个函数被调用时,一个新的帧就被系统压入一个由系统维护的帧,在这个栈的顶端是现在正在运行的函数信息,当该函数调用结束时被弹出并析构。
○ 在GDB中,frame 0为当前帧,frame 1为当前帧的父帧,frame 2为父帧的父帧,等等,用down命令则是反向的。这是一个很有用的信息,因为在早期的一些帧中的信息可能会给你一些提示。
○ backtrace(bt/ where)查看整个帧栈
○ 注意:在帧中来回并不影响程序的执行。

这里写图片描述

一,多线程调试可能是问得最多的。其实,重要就是下面几个命令:
● info thread 查看当前进程的线程。
● thread ID 切换调试的线程为指定ID的线程。
● break file.c : 100 thread all 在file.c文件第100行处为所有经过这里的线程设置断点。
set scheduler-locking off|on|step,这个是问得最多的。在使用step或者continue命令调试当前被调试线程的时候,其他线程也是同时执行的,怎么只让被调试程序执行呢?通过这个命令就可以实现这个需求。
○ off 不锁定任何线程,也就是所有线程都执行,这是默认值。
○ on 只有当前被调试程序会执行。
○ step 在单步的时候,除了next过一个函数的情况(熟悉情况的人可能知道,这其实是一个设置断点然后continue的行为)以外,只有当前线程会执行。

二,调试宏:
这个问题超多。在GDB下,我们无法print宏定义,因为宏是预编译的。但是我们还是有办法来调试宏,这个需要GCC的配合。
●在GCC编译程序的时候,加上-ggdb3参数,这样,就可以调试宏了。
另外,你可以使用下述的GDB的宏调试命令 来查看相关的宏。
● info macro – 可以查看这个宏在哪些文件里被引用了,以及宏定义是什么样的。
● macro – 可以查看宏展开的样子。

性能监控和优化命令

内存:top,free,sar,pmap,vmstat,mpstat,iostat,
cpu:top,vmstat,mpstat,iostat,sar
I/O:vmstat,mpstat,iostat,sar
进程:ps,top,ipcs,ipcrm
系统运行负载:uptime,w

●ps:列出系统中正在运行的进程.ps告诉我们每个进程使用的内存量以及所消耗的CPU时间。
●top:动态显示进程信息,对进程实时监控

●free:显示系统使用和空闲的内存情况
●pmap:可以显示进程占用的内存量。可以通过pmap找到占用内存量最多的进程。

●sar:多方面对系统活动进行报告,包括磁盘IO,cpu效率,内存使用情况,系统调用情况,文件读写情况。
●ipcs:提供进程间通信方式的信息,包括消息队列、共享内存、信号等
●ipcrm:删除一个消息对象(消息队列、信号集或者共享内存标识)

●vmstat:是Virtual Meomory Statistics(虚拟内存统计)的缩写,可对操作系统的虚拟内存、进程、CPU活动进行监控。它是对系统的整体情况进行统计,不足之处是无法对某个进程进行深入分析。

●iostat:既可以显示CPU使用情况,也可以看到每个磁盘的IO情况.

iostat是I/O statistics(输入/输出统计)的缩写,iostat工具将对系统的磁盘操作活动进行监视。它的特点是汇报磁盘活动统计情况,同时也会汇报出CPU使用情况。同vmstat一样,iostat也有一个弱点,就是它不能对某个进程进行深入分析,仅对系统的整体情况进行分析

●mpstat:mpstat用在多处理器的服务器上,用来显示每一个CPU的状态。另外,mpstat也会显示所有处理器的平均状况。

mpstat是MultiProcessor Statistics的缩写,是实时系统监控工具。其报告与CPU的一些统计信息,这些信息存放在/proc/stat文件中。在多CPU系统里,其不但能查看所有CPU的平均状况信息,而且能够查看特定CPU的信息。

●uptime:显示系统已经运行了多长时间,它依次显示下列信息:当前时间、系统已经运行了多长时间、目前有多少登陆用户、系统在过去的1分钟、5分钟和15分钟内的平均负载。

●w:查询登录当前系统的用户信息,以及这些用户目前正在做什么操作,另外load average后面的三个数字则显示了系统最近1分钟、5分钟、15分钟的系统平均负载情况。

其它

获取文件夹的大小:
ls -lh
du -sh
展开阅读全文

没有更多推荐了,返回首页