腾讯后台开发面试题

简单概括:fd只是一个整数,在open时产生。起到一个索引的做用,进程经过PCB中的文件描述符表找到该fd所指向的文件指针filp。html

文件描述符的操做(如: open)返回的是一个文件描述符,内核会在每一个进程空间中维护一个文件描述符表, 全部打开的文件都将经过此表中的文件描述符来引用; 
而流(如: fopen)返回的是一个FILE结构指针, FILE结构是包含有文件描述符的,FILE结构函数能够看做是对fd直接操做的系统调用的封装, 它的优势是带有I/O缓存mysql

每一个进程在PCB(Process Control Block)即进程控制块中都保存着一份文件描述符表,文件描述符就是这个表的索引,文件描述表中每一个表项都有一个指向已打开文件的指针,如今咱们明确一下:已打开的文件在内核中用file结构体表示,文件描述符表中的指针指向file结构体。linux

linux和os:nginx

netstat :显示网络状态c++

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

从总体上看,netstat的输出结果能够分为两个部分:

一个是Active Internet connections,称为有源TCP链接,其中"Recv-Q"和"Send-Q"指%0A的是接收队列和发送队列。这些数字通常都应该是0。若是不是则表示软件包正在队列中堆积。这种状况只能在很是少的状况见到。web

另外一个是Active UNIX domain sockets,称为有源Unix域套接口(和网络套接字同样,可是只能用于本机通讯,性能能够提升一倍)。
Proto显示链接使用的协议,RefCnt表示链接到本套接口上的进程号,Types显示套接口的类型,State显示套接口当前的状态,Path表示链接到套接口的其它进程使用的路径名。面试

 

tcpdump:主要是截获经过本机网络接口的数据,用以分析。可以截获当前全部经过本机网卡的数据包。它拥有灵活的过滤机制,能够确保获得想要的数据。算法

用简单的话来定义tcpdump,就是:dump the traffic on a network,根据使用者的定义对网络上的数据包进行截获的包分析工具。 tcpdump能够将网络中传送的数据包的“头”彻底截获下来提供分析。它支持针对网络层、协议、主机、网络或端口的过滤,并提供and、or、not等逻辑语句来帮助你去掉无用的信息。sql

 

ipcs:检查系统上共享内存的分配

用途

报告进程间通讯设施状态。

ipcs 命令往标准输出写入一些关于活动进程间通讯设施的信息。若是没有指定任何标志,ipcs 命令用简短格式写入一些关于当前活动消息队列共享内存段、信号量、远程队列和本地队列标题。

 

 

 

Linux下ipcs指令的用法详解。ipcs是Linux下显示进程间通讯设施状态的工具。能够显示消息队列、共享内存和信号量的信息。对于程序员可能更有用些,普通的系统管理员通常用不到此指令。

ipcrm:手动解除系统上共享内存的分配

用途
  删除消息队列、信号集、或者共享内存标识。

 

 

 

(若是这四个命令没据说过或者不能熟练使用,基本上能够回家,经过的几率较小 ^_^ ,这四个命令的熟练掌握程度基本上能体现面试者实际开发和调试程序的经验)

 

查看cpu信息

        #cat /proc/cpuinfo

   # cat /proc/meminfo

查看硬盘信息

        # df -lh

更详细的信息

       # cat /proc/scsi/scsi

查看网卡信息

        # dmesg | grep eth

更经常使用的命令(显示系统核心版本号、名称、机器类型等)

       # uname -a

 

cpu 内存硬盘等等与系统性能调试相关的命令必须熟练掌握,设置修改权限 tcp网络状态查看各进程状态抓包相关等相关命令必须熟练掌握

awk sed需掌握

共享内存的使用实现原理(必考必问,而后共享内存段被映射进进程空间以后,存在于进程空间的什么位置?共享内存段最大限制是多少?)

$sysctl kern.ipc.shmmax
kern.ipc.shmmax: 33554432

Linux的2.2.x之后的内核版本支持多种共享内存方式,好比:内存映射mmap、POSIX共享内存、System V共享内存;

 

共享内存定义:共享内存是最快的可用IPC(进程间通讯)形式。它容许多个不相关的进程去访问同一部分逻辑内存。共享内存是由IPC为一个进程建立的一个特殊的地址范围,它将出如今进程的地址空间中。其余进程能够把同一段共享内存段“链接到”它们本身的地址空间里去。全部进程均可以访问共享内存中的地址。若是一个进程向这段共享内存写了数据,所作的改动会马上被有访问同一段共享内存的其余进程看到。所以共享内存对于数据的传输是很是高效的。

共享内存的原理:共享内存是最有用的进程间通讯方式之一,也是最快的IPC形式。两个不一样进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A能够即时看到进程B对共享内存中数据的更新,反之亦然。

c++进程内存空间分布(注意各部分的内存地址谁高谁低,注意栈从高到低分配,堆从低到高分配)

ELF是什么?其大小与程序中全局变量的是否初始化有什么关系(注意未初始化的数据放在bss段)

Linux ELF  ELF = Executable and Linkable Format,可执行链接格式,是UNIX系统实验室(USL)做为应用程序二进制接口(Application Binary Interface,ABI)而开发和发布的。扩展名为elf。工具接口标准委员会(TIS)选择了正在发展中的ELF标准做为工做在32位INTEL体系上不一样操做系统之间可移植的二进制文件格式。假定开发者定义了一个二进制接口集合,ELF标准用它来支持流线型的软件发展。应该减小不一样执行接口的数量。所以能够减小从新编程从新编译的代码。

 

BSS段

可执行程序包括BSS段、数据段代码段(也称文本段)。

BSS(Block Started by Symbol)一般是指用来存放程序中未初始化的全局变量静态变量的一块内存区域。特色是:可读写的,在程序执行以前BSS段会自动清0。因此,未初始的全局变量在程序执行以前已经成0了。

注意和数据段的区别,BSS存放的是未初始化的全局变量静态变量,数据段存放的是初始化后的全局变量和静态变量。

UNIX下可以使用size命令查看可执行文件的段大小信息。如size a.out。

 

可执行文件:包含了代码和数据。具备可执行的程序。

可重定位文件:包含了代码和数据(这些数据是和其余重定位文件和共享的 
object文件一块儿链接时使用的)

共享object文件(又可叫作共享库):包含了代码和数据(这些数据是在链接
时候被链接器ld和运行时动态链接器使用的)。

使建立共享库容易,使动态装载和共享库的结合更加容易。在ELF下,在C++ 
中,全局的构造函数和析构函数在共享库和静态库中用一样方法处理。

使用过哪些进程间通信机制,并详细说明(重点)

makefile编写,虽然比较基础,可是会被问到

mkdir mf

cd mf

vim makefile

hello.o:hello.c hello.h

       gcc –c hello.o -Lm

make

./hello

gdb调试相关的经验,会被问到

GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。或许,各位比较喜欢那种图形界面方式的,像VC、BCB等IDE的调试,但若是你是在 UNIX平台下作软件,你会发现GDB这个调试工具备比VC、BCB的图形化调试器更强大的功能。所谓“寸有所长,尺有所短”就是这个道理。

通常来讲,GDB主要帮助你完成下面四个方面的功能:

一、启动你的程序,能够按照你的自定义的要求为所欲为的运行程序。

二、可以让被调试的程序在你所指定的调置的断点处停住。(断点能够是条件表达式

三、当程序被停住时,能够检查此时你的程序中所发生的事。

四、动态的改变你程序的执行环境。

《GDB使用手册》

gcc -g -o hello hello.c 。

GDB经常使用命令简介
  GDB的命令不少,本文不会所有介绍,仅会介绍一些最经常使用的。在介绍以前,先介绍GDB中的一个很是有用的功能:补齐功能。它就如同Linux下SHELL中的命令补齐同样。当你输入一个命令的前几个字符,而后输入TAB键,若是没有其它命令的前几个字符与此相同,SHELL将补齐此命令。若是有其它命令的前几个字符与此相同,你会听到一声警告声,再输入TAB键,SHELL将全部前几个字符与此相同的命令所有列出。而GDB中的补齐功能不只能补齐GDB命令,并且能补齐参数。
  本文将先介绍经常使用的命令,而后结合一个具体的例子来演示如何实际使用这些命令。下面的全部命令除了第一条启动GDB命令是在SHELL下输入的,其他都是GDB内的命令。大部分GDB内的命令均可以仅输入前几个字符,只要不与其它指令冲突。如quit能够简写为q,由于以q打头的命令只有quit。List能够简写为l,等等
3.1 启动GDB
  你能够输入GDB来启动GDB程序。GDB程序有许多参数,在此没有必要详细介绍,但一个最为经常使用的仍是要介绍的:若是你已经编译好一个程序,咱们假设文件名为hello,你想用GDB调试它,能够输入gdb hello来启动GDB并载入你的程序。若是你仅仅启动了GDB,你必须在启动后,在GDB中再载入你的程序。
3.2 载入程序 === file
  在GDB内,载入程序很简单,使用file命令。如file hello。固然,程序的路径名要正确。
  退出GDB === quit
  在GDB的命令方式下,输入quit,你就能够退出GDB。你也能够输入'C-d'来退出GDB。
3.3 运行程序 === run
  当你在GDB中已将要调试的程序载入后,你能够用run命令来执行。若是你的程序须要参数,你能够在run指令后接着输入参数,就象你在SHELL下执行一个须要参数的命令同样。
3.4 查看程序信息 === info
  info指令用来查看程序的信息,当你用help info查看帮助的话,info指令的参数足足占了两个屏幕,它的参数很是多,但大部分不经常使用。我用info指令最多的是用它来查看断点信息。
3.4.1 查看断点信息
info br
br是断点break的缩写,记得GDB的补齐功能吧。用这条指令,你能够获得你所设置的全部断点的详细信息。包括断点号,类型,状态,内存地址,断点在源程序中的位置等。
3.4.2 查看当前源程序
info source
3.4.3 查看堆栈信息
info stack
用这条指令你能够看清楚程序的调用层次关系。
3.4.4 查看当前的参数
info args
3.5 列出源一段源程序 === list
3.5.1 列出某个函数
list FUNCTION
3.5.2 以当前源文件的某行为中间显示一段源程序
list LINENUM
3.5.3 接着前一次继续显示
list
3.5.4 显示前一次以前的源程序
list -
3.5.5 显示另外一个文件的一段程序
list FILENAME:FUNCTION 或 listFILENAME:LINENUM
3.6 设置断点 === break
  如今咱们将要介绍的也许是最经常使用和最重要的命令:设置断点。不管什么时候,只要你的程序已被载入,而且当前没有正在运行,你就能设置,修改,删除断点。设置断点的命令是break。有许多种设置断点的方法。以下:
3.6.1 在函数入口设置断点

如何定位内存泄露?

内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的、大小任意的(内存块的大小能够在程序运行期决定)、使用完后必须显示释放的内存。应用程序通常使用malloc、realloc、new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块。不然,这块内存就不能被再次使用,咱们就说这块内存泄漏了。

C++程序缺少相应的手段来检测内存信息,只能使用top指令观察进程的动态内存总额。并且程序退出时,咱们没法获知任何内存泄漏信息

使用Linux命令回收内存,能够使用ps、kill两个命令检测内存使用状况和进行回收。在使用超级用户权限时使用命令“ps”,它会列出全部正在运行的程序名称和对应的进程号(PID)。kill命令的工做原理是向Linux操做系统的内核送出一个系统操做信号和程序的进程号(PID)

动态连接和静态连接的区别

动态连接是指在生成可执行文件时不将全部程序用到的函数连接到一个文件,由于有许多函数在操做系统带的dll文件中,当程序运行时直接从操做系统中找。 而静态连接就是把全部用到的函数所有连接到exe文件中。
动态连接是只创建一个引用的接口,而真正的代码和数据存放在另外的可执行模块中,在运行时再装入;而静态连接是把全部的代码和数据都复制到本模块中,运行时就再也不须要库了。

32位系统一个进程最多有多少堆内存

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

咱们按照多个不一样的维度,来看看多进程和多线程的对比(注:都是相对的,不是说一个好得不得了,另外一个差的没法忍受)

维度

多进程

多线程

总结

数据共享、同步

数据是分开的:共享复杂,须要用IPC;同步简单

多线程共享进程数据:共享简单;同步复杂

各有优点

内存、CPU

占用内存多,切换复杂,CPU利用率低

占用内存少,切换简单,CPU利用率高

线程占优

建立销毁、切换

建立销毁、切换复杂,速度慢 

建立销毁、切换简单,速度快 

线程占优 

编程调试

编程简单,调试简单

编程复杂,调试复杂

进程占优 

可靠性

进程间不会相互影响 

一个线程挂掉将致使整个进程挂掉

进程占优

分布式 

适应于多核、多机分布;若是一台机器不够,扩展到多台机器比较简单

适应于多核分布

进程占优

而后咱们来看下线程和进程间的比较

 

子进程继承父进程的属性:

子线程继承主线程的属性:

实际用户ID,实际组ID,有效用户ID,有效组ID;

附加组ID;

进程组ID;

会话ID;

控制终端;

设置用户ID标志和设置组ID标志;

当前工做目录;

根目录;

文件模式建立屏蔽字(umask);

信号屏蔽和安排;

针对任一打开文件描述符的在执行时关闭(close-on-exec)标志;

环境;

链接的共享存储段;

存储映射;

资源限制;

进程中的全部信息对该进程的全部线程都是共享的;

可执行的程序文本;

程序的全局内存;

堆内存;

栈;

文件描述符;

信号的处理是进程中全部线程共享的(注意:若是信号的默认处理是终止该进程那么便是把信号传给某个线程也同样会将进程杀掉);

 

父子进程之间的区别:

子线程特有的:

fork的返回值(=0子进程);

进程ID不一样;

两个进程具备不一样的父进程ID;

子进程的tms_utime,tms_stime,tms_cutime以及tms_ustime均被设置为0;

不继承父进程设置的文件锁;

子进程的未处理闹钟被清除;

子进程的未处理信号集设置为空集;

线程ID;

一组寄存器值;

栈;

调度优先级和策略;

信号屏蔽字;

errno变量;

线程私有数据;

 


1)须要频繁建立销毁的优先用线程。
实例:web服务器。来一个创建一个线程,断了就销毁线程。要是用进程,建立和销毁的代价是很难承受的。
2)须要进行大量计算的优先使用线程。
所谓大量计算,固然就是要消耗不少cpu,切换频繁了,这种状况先线程是最合适的。
实例:图像处理、算法处理
3)强相关的处理用线程,若相关的处理用进程。
什么叫强相关、弱相关?理论上很难定义,给个简单的例子就明白了。
通常的server须要完成以下任务:消息收发和消息处理。消息收发和消息处理就是弱相关的任务,而消息处理里面可能又分为消息解码、业务处理,这两个任务相对来讲相关性就要强多了。所以消息收发和消息处理能够分进程设计,消息解码和业务处理能够分线程设计。
4)可能扩展到多机分布的用进程,多核分布的用线程。
5)都知足需求的状况下,用你最熟悉、最拿手的方式。

至于”数据共享、同步“、“编程、调试”、“可靠性”这几个维度的所谓的“复杂、简单”应该怎么取舍,只能说:没有明确的选择方法。通常有一个选择原则:若是多进程和多线程都可以知足要求,那么选择你最熟悉、最拿手的那个。

 

通常运行一个程序称为一个进程。

进程能够建立线程,也能够建立进程。

线程是由进程管理的,线程之间、线程和父进程(建立线程的进程)之间能够共享内存变量(须要使用策略的)。

进程之间通常不能够直接共享内存变量,须要使用一些进程间的控制共享内存变量。

若是你使用并行计算,建议使用线程。

 

 

 

进程是个容器或者说资源管理者,有独立的内存地址空间。
线程依赖于它所在的进程,共享进程的资源和内存地址空间。

unix特别是linux里面,线程与进程接近;windows的进程彻底是个容器,线程更轻量级。具体能够了解linux下的fork以及clone,windows的createprocess、createthread等

 

什么是进程。最直观的就是一个个pid,官方的说法就:进程是程序在计算机上的一次执行活动。

TCP和UDP分析

主要参看2篇博文:

http://blog.csdn.net/dog250/article/details/6612496

http://blog.csdn.net/dog250/article/details/6896949

还有就是谢老师写的《计算机网络》第五版,.TCP/IP详解(卷一,卷二)以及《Unix网络编程》以及Linux源代码以外,RFC

1、概念:

key:TCP是一种面向链接的、可靠的、字节流服务

  

1.面向连接:TCP面向连接,面向链接意味着两个使用TCP的应用(一般是一个客户和一个服务器)在彼此交换数据以前必须经过三次握手先创建一个TCP链接。在一个TCP中仅有两方彼此通讯,多播和广播不能用于TCP。UDP是不可靠的传输,传输前不须要创建连接,能够应用多播和广播实现一对多的通讯。

 
2.可靠性:TCP提供端到端的流量控制,对收到的数据进行确认,采用超时重发,对失序的数据进行从新排序等机制保证数据通讯的可靠性。而UDP是一种不可靠的服务,接收方可能不能收到发送方的数据报。

 
3.TCP是一种流模式的协议,UDP是一种数据报模式的协议。进程的每一个输出操做都正好产生一个UDP数据报,并组装成一份待发送的IP数据报。TCP应用程序产生的全体数据与真正发送的单个IP数据报可能没有什么联系。TCP会有粘包和半包的现象。

 
4.效率上:速度上,通常TCP速度慢,传输过程当中须要对数据进行确认,超时重发,还要对数据进行排序。UDP没有这些机制因此速度快。数据比例,TCP头至少20个字节,UDP头8个字节,相对效率高。组装效率上:TCP头至少20个字节,UDP头8个字节,系统组装上TCP相对慢。

 
5.用途上:用于TCP可靠性,http,ftp使用。而因为UDP速度快,视频,在线游戏多用UDP,保证明时性

 
对于第三点的理解。TCP可能发送100个“包”,而接收到50个“包”,不是丢“包”了,而是每次接受的“包”都比发送的多,其实TCP并无包的概念。例如,每次发10个字节,可能读得时候一次读了20个字节。TCP是一种流模式的协议,在接收到的缓存中按照发送的包得顺序自动按照顺序拼接好,由于数据基原本自同一个主机,并且是按照顺序发送过来的,TCP的缓存中存放的就是,连续的数据。感受好像是多封装了一步比UDP。而UDP由于可能两个不一样的主机,给同一个主机发送,(一个端口可能收到多个应用程序的数据),或者按照TCP那样合并数据,必然会形成数据错误。我以为关键的缘由仍是,TCP是面向链接,而UDP是无链接的,这就致使,TCP接收的数据为一个主机发来且有序无误的,而UDP多是多个主机发来的无序,可能错误的。

 

写一个c程序辨别系统是16位or32位

法一:int k=~0;

if((unsigned int)k >63356) cout<<"at least 32bits"<<endl;

else cout<<"16 bits"<<endl;

法二://32为系统

int i=65536;

cout<<i<<endl;

int j=65535;

cout<<j<<endl;

写一个c程序辨别系统是大端or小端字节序

用联合体:如char类型的,能够看他输出的是int的高字节仍是低字节

 

信号:列出常见的信号,信号怎么处理?

信号是Linux编程中很是重要的部分,本文将详细介绍信号机制的基本概念、Linux对信号机制的大体实现方法、如何使用信号,以及有关信号的几个系统调用。 

信号机制是进程之间相互传递消息的一种方法,信号全称为软中断信号,也有人称做软中断。从它的命名能够看出,它的实质和使用很象中断。因此,信号能够说是进程控制的一部分。 

1、信号的基本概念 

本节先介绍信号的一些基本概念,而后给出一些基本的信号类型和信号对应的事件。基本概念对于理解和使用信号,对于理解信号机制都特别重要。下面就来看看什么是信号。 

一、基本概念 

软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。进程之间能够互相经过系统调用kill发送软中断信号。内核也能够由于内部事件而给进程发送信号,通知进程发生了某个事件。注意,信号只是用来通知某进程发生了什么事件,并不给该进程传递任何数据。 

收 到信号的进程对各类信号有不一样的处理方法。处理方法能够分为三类:第一种是相似中断的处理程序,对于须要处理的信号,进程能够指定处理函数,由该函数来处 理。第二种方法是,忽略某个信号,对该信号不作任何处理,就象未发生过同样。第三种方法是,对该信号的处理保留系统的默认值,这种缺省操做,对大部分的信 号的缺省操做是使得进程终止。进程经过系统调用signal来指定进程对某个信号的处理行为。 

在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号,当有信号发送给进程时,对应位置位。由此能够看出,进程对不一样的信号能够同时保留,但对于同一个信号,进程并不知道在处理以前来过多少个。 

二、信号的类型 

发出信号的缘由不少,这里按发出信号的缘由简单分类,以了解各类信号: 

(1) 与进程终止相关的信号。当进程退出,或者子进程终止时,发出这类信号。 
(2) 与进程例外事件相关的信号。如进程越界,或企图写一个只读的内存区域(如程序正文区),或执行一个特权指令及其余各类硬件错误。 
(3) 与在系统调用期间遇到不可恢复条件相关的信号。如执行系统调用exec时,原有资源已经释放,而目前系统资源又已经耗尽。 
(4) 与执行系统调用时遇到非预测错误条件相关的信号。如执行一个并不存在的系统调用。 
(5) 在用户态下的进程发出的信号。如进程调用系统调用kill向其余进程发送信号。 
(6) 与终端交互相关的信号。如用户关闭一个终端,或按下break键等状况。 
(7) 跟踪进程执行的信号。 

Linux支持的信号列表以下。不少信号是与机器的体系结构相关的,首先列出的是POSIX.1中列出的信号: 

信号 值 处理动做 发出信号的缘由 
---------------------------------------------------------------------- 
SIGHUP 1 A 终端挂起或者控制进程终止 
SIGINT 2 A 键盘中断(如break键被按下) 
SIGQUIT 3 C 键盘的退出键被按下 
SIGILL 4 C 非法指令 
SIGABRT 6 C 由abort(3)发出的退出指令 
SIGFPE 8 C 浮点异常 
SIGKILL 9 AEF Kill信号 
SIGSEGV 11 C 无效的内存引用 
SIGPIPE 13 A 管道破裂: 写一个没有读端口的管道 
SIGALRM 14 A 由alarm(2)发出的信号 
SIGTERM 15 A 终止信号 
SIGUSR1 30,10,16 A 用户自定义信号1 
SIGUSR2 31,12,17 A 用户自定义信号2 
SIGCHLD 20,17,18 B 子进程结束信号 
SIGCONT 19,18,25 进程继续(曾被中止的进程) 
SIGSTOP 17,19,23 DEF 终止进程 
SIGTSTP 18,20,24 D 控制终端(tty)上按下中止键 
SIGTTIN 21,21,26 D 后台进程企图从控制终端读 
SIGTTOU 22,22,27 D 后台进程企图从控制终端写 

下面的信号没在POSIX.1中列出,而在SUSv2列出 

信号 值 处理动做 发出信号的缘由 
-------------------------------------------------------------------- 
SIGBUS 10,7,10 C 总线错误(错误的内存访问) 
SIGPOLL A Sys V定义的Pollable事件,与SIGIO同义 
SIGPROF 27,27,29 A Profiling定时器到 
SIGSYS 12,-,12 C 无效的系统调用 (SVID) 
SIGTRAP 5 C 跟踪/断点捕获 
SIGURG 16,23,21 B Socket出现紧急条件(4.2 BSD) 
SIGVTALRM 26,26,28 A 实际时间报警时钟信号(4.2 BSD) 
SIGXCPU 24,24,30 C 超出设定的CPU时间限制(4.2 BSD) 
SIGXFSZ 25,25,31 C 超出设定的文件大小限制(4.2 BSD) 

(对于SIGSYS,SIGXCPU,SIGXFSZ,以及某些机器体系结构下的SIGBUS,Linux缺省的动做是A (terminate),SUSv2 是C (terminate and dump core))。 

下面是其它的一些信号 

信号 值 处理动做 发出信号的缘由 
---------------------------------------------------------------------- 
SIGIOT 6 C IO捕获指令,与SIGABRT同义 
SIGEMT 7,-,7 
SIGSTKFLT -,16,- A 协处理器堆栈错误 
SIGIO 23,29,22 A 某I/O操做如今能够进行了(4.2 BSD) 
SIGCLD -,-,18 A 与SIGCHLD同义 
SIGPWR 29,30,19 A 电源故障(System V) 
SIGINFO 29,-,- A 与SIGPWR同义 
SIGLOST -,-,- A 文件锁丢失 
SIGWINCH 28,28,20 B 窗口大小改变(4.3 BSD, Sun) 
SIGUNUSED -,31,- A 未使用的信号(will be SIGSYS) 

(在这里,- 表示信号没有实现;有三个值给出的含义为,第一个值一般在Alpha和Sparc上有效,中间的值对应i386和ppc以及sh,最后一个值对应mips。信号29在Alpha上为SIGINFO / SIGPWR ,在Sparc上为SIGLOST。) 

处理动做一项中的字母含义以下 
A 缺省的动做是终止进程 
B 缺省的动做是忽略此信号 
C 缺省的动做是终止进程并进行内核映像转储(dump core) 
D 缺省的动做是中止进程 
E 信号不能被捕获 
F 信号不能被忽略 

上 面介绍的信号是常见系统所支持的。以表格的形式介绍了各类信号的名称、做用及其在默认状况下的处理动做。各类默认处理动做的含义是:终止程序是指进程退 出;忽略该信号是将该信号丢弃,不作处理;中止程序是指程序挂起,进入中止情况之后还能从新进行下去,通常是在调试的过程当中(例如ptrace系统调 用);内核映像转储是指将进程数据在内存的映像和进程在内核结构中存储的部份内容以必定格式转储到文件系统,而且进程退出执行,这样作的好处是为程序员提 供了方便,使得他们能够获得进程当时执行时的数据值,容许他们肯定转储的缘由,而且能够调试他们的程序。 

注意 信号SIGKILL和SIGSTOP既不能被捕捉,也不能被忽略。信号SIGIOT与SIGABRT是一个信号。能够看出,同一个信号在不一样的系统中值可能不同,因此建议最好使用为信号定义的名字,而不要直接使用信号的值。 

2、信 号 机 制 

上 一节中介绍了信号的基本概念,在这一节中,咱们将介绍内核如何实现信号机制。即内核如何向一个进程发送信号、进程如何接收一个信号、进程怎样控制本身对信 号的反应、内核在什么时机处理和怎样处理进程收到的信号。还要介绍一下setjmp和longjmp在信号中起到的做用。 

一、内核对信号的基本处理方法 

内 核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的位。这里要补充的是,若是信号发送给一个正在睡眠的进程,那么要看 该进程进入睡眠的优先级,若是进程睡眠在可被中断的优先级上,则唤醒进程;不然仅设置进程表中信号域相应的位,而不唤醒进程。这一点比较重要,由于进程检 查是否收到信号的时机是:一个进程在即将从内核态返回到用户态时;或者,在一个进程要进入或离开一个适当的低调度优先级睡眠状态时。 

内核处理一个进程收到的信号的时机是在一个进程从内核态返回用户态时。因此,当一个进程在内核态下运行时,软中断信号并不当即起做用,要等到将返回用户态时才处理。进程只有处理完信号才会返回用户态,进程在用户态下不会有未处理完的信号。 

内 核处理一个进程收到的软中断信号是在该进程的上下文中,所以,进程必须处于运行状态。前面介绍概念的时候讲过,处理信号有三种类型:进程接收到信号后退 出;进程忽略该信号;进程收到信号后执行用户设定用系统调用signal的函数。当进程接收到一个它忽略的信号时,进程丢弃该信号,就象没有收到该信号似 的继续运行。若是进程收到一个要捕捉的信号,那么进程从内核态返回用户态时执行用户定义的函数。并且执行用户定义的函数的方法很巧妙,内核是在用户栈上创 建一个新的层,该层中将返回地址的值设置成用户定义的处理函数的地址,这样进程从内核返回弹出栈顶时就返回到用户定义的函数处,从函数返回再弹出栈顶时, 才返回原先进入内核的地方。这样作的缘由是用户定义的处理函数不能且不容许在内核态下执行(若是用户定义的函数在内核态下运行的话,用户就能够得到任何权 限)。 

在信号的处理方法中有几点特别要引发注意。第一,在一些系统中,当一个进程处理完中断信号返回用户态以前,内核清除用户区中设 定的对该信号的处理例程的地址,即下一次进程对该信号的处理方法又改成默认值,除非在下一次信号到来以前再次使用signal系统调用。这可能会使得进程 在调用signal以前又获得该信号而致使退出。在BSD中,内核再也不清除该地址。但不清除该地址可能使得进程由于过多过快的获得某个信号而致使堆栈溢 出。为了不出现上述状况。在BSD系统中,内核模拟了对硬件中断的处理方法,即在处理某个中断时,阻止接收新的该类中断。 

第二个要 引发注意的是,若是要捕捉的信号发生于进程正在一个系统调用中时,而且该进程睡眠在可中断的优先级上,这时该信号引发进程做一次longjmp,跳出睡眠 状态,返回用户态并执行信号处理例程。当从信号处理例程返回时,进程就象从系统调用返回同样,但返回了一个错误代码,指出该次系统调用曾经被中断。这要注 意的是,BSD系统中内核能够自动地从新开始系统调用。 

第三个要注意的地方:若进程睡眠在可中断的优先级上,则当它收到一个要忽略的信号时,该进程被唤醒,但不作longjmp,通常是继续睡眠。但用户感受不到进程曾经被唤醒,而是象没有发生过该信号同样。 

第 四个要注意的地方:内核对子进程终止(SIGCLD)信号的处理方法与其余信号有所区别。当进程检查出收到了一个子进程终止的信号时,缺省状况下,该进程 就象没有收到该信号似的,若是父进程执行了系统调用wait,进程将从系统调用wait中醒来并返回wait调用,执行一系列wait调用的后续操做(找 出僵死的子进程,释放子进程的进程表项),而后从wait中返回。SIGCLD信号的做用是唤醒一个睡眠在可被中断优先级上的进程。若是该进程捕捉了这个 信号,就象普通讯号处理同样转处处理例程。若是进程忽略该信号,那么系统调用wait的动做就有所不一样,由于SIGCLD的做用仅仅是唤醒一个睡眠在可被 中断优先级上的进程,那么执行wait调用的父进程被唤醒继续执行wait调用的后续操做,而后等待其余的子进程。 

若是一个进程调用signal系统调用,并设置了SIGCLD的处理方法,而且该进程有子进程处于僵死状态,则内核将向该进程发一个SIGCLD信号。 

二、setjmp和longjmp的做用 

前面在介绍信号处理机制时,屡次提到了setjmp和longjmp,但没有仔细说明它们的做用和实现方法。这里就此做一个简单的介绍。 

在 介绍信号的时候,咱们看到多个地方要求进程在检查收到信号后,从原来的系统调用中直接返回,而不是等到该调用完成。这种进程忽然改变其上下文的状况,就是 使用setjmp和longjmp的结果。setjmp将保存的上下文存入用户区,并继续在旧的上下文中执行。这就是说,进程执行一个系统调用,当由于资 源或其余缘由要去睡眠时,内核为进程做了一次setjmp,若是在睡眠中被信号唤醒,进程不能再进入睡眠时,内核为进程调用longjmp,该操做是内核 为进程将原先setjmp调用保存在进程用户区的上下文恢复成如今的上下文,这样就使得进程能够恢复等待资源前的状态,并且内核为setjmp返回1,使 得进程知道该次系统调用失败。这就是它们的做用。 

3、有关信号的系统调用 

前面两节已经介绍了有关信号的大部分知 识。这一节咱们来了解一下这些系统调用。其中,系统调用signal是进程用来设定某个信号的处理方法,系统调用kill是用来发送信号给指定进程的。这 两个调用能够造成信号的基本操做。后两个调用pause和alarm是经过信号实现的进程暂停和定时器,调用alarm是经过信号通知进程定时器到时。所 以在这里,咱们还要介绍这两个调用。 

一、signal 系统调用 

系统调用signal用来设定某个信号的处理方法。该调用声明的格式以下: 
void (*signal(int signum, void (*handler)(int)))(int); 
在使用该调用的进程中加入如下头文件: 
#include <signal.h> 

上述声明格式比较复杂,若是不清楚如何使用,也能够经过下面这种类型定义的格式来使用(POSIX的定义): 
typedef void (*sighandler_t)(int); 
sighandler_t signal(int signum, sighandler_t handler); 
但这种格式在不一样的系统中有不一样的类型定义,因此要使用这种格式,最好仍是参考一下联机手册。 

在调用中,参数signum指出要设置处理方法的信号。第二个参数handler是一个处理函数,或者是 
SIG_IGN:忽略参数signum所指的信号。 
SIG_DFL:恢复参数signum所指信号的处理方法为默认值。 

传递给信号处理例程的整数参数是信号值,这样能够使得一个信号处理例程处理多个信号。系统调用signal返回值是指定信号signum前一次的处理例程或者错误时返回错误代码SIG_ERR。下面来看一个简单的例子: 

#include <signal.h> 
#include <unistd.h> 
#include <stdio.h> 
void sigroutine(int dunno) { /* 信号处理例程,其中dunno将会获得信号的值 */ 
switch (dunno) { 
case 1: 
printf("Get a signal -- SIGHUP "); 
break; 
case 2: 
printf("Get a signal -- SIGINT "); 
break; 
case 3: 
printf("Get a signal -- SIGQUIT "); 
break; 

return; 


int main() { 
printf("process id is %d ",getpid()); 
signal(SIGHUP, sigroutine); //* 下面设置三个信号的处理方法 
signal(SIGINT, sigroutine); 
signal(SIGQUIT, sigroutine); 
for (;;) ; 


其中信号SIGINT由按下Ctrl-C发出,信号SIGQUIT由按下Ctrl-发出。该程序执行的结果以下: 

localhost:~$ ./sig_test 
process id is 463 
Get a signal -SIGINT //按下Ctrl-C获得的结果 
Get a signal -SIGQUIT //按下Ctrl-获得的结果 
//按下Ctrl-z将进程置于后台 
[1]+ Stopped ./sig_test 
localhost:~$ bg 
[1]+ ./sig_test & 
localhost:~$ kill -HUP 463 //向进程发送SIGHUP信号 
localhost:~$ Get a signal – SIGHUP 
kill -9 463 //向进程发送SIGKILL信号,终止进程 
localhost:~$ 

二、kill 系统调用 

系统调用kill用来向进程发送一个信号。该调用声明的格式以下: 
int kill(pid_t pid, int sig); 
在使用该调用的进程中加入如下头文件: 
#include <sys/types.h> 
#include <signal.h> 

该 系统调用能够用来向任何进程或进程组发送任何信号。若是参数pid是正数,那么该调用将信号sig发送到进程号为pid的进程。若是pid等于0,那么信 号sig将发送给当前进程所属进程组里的全部进程。若是参数pid等于-1,信号sig将发送给除了进程1和自身之外的全部进程。若是参数pid小于- 1,信号sig将发送给属于进程组-pid的全部进程。若是参数sig为0,将不发送信号。该调用执行成功时,返回值为0;错误时,返回-1,并设置相应 的错误代码errno。下面是一些可能返回的错误代码: 
EINVAL:指定的信号sig无效。 
ESRCH:参数pid指定的进程或进程组不存在。注意,在进程表项中存在的进程,多是一个尚未被wait收回,但已经终止执行的僵死进程。 
EPERM: 进程没有权力将这个信号发送到指定接收信号的进程。由于,一个进程被容许将信号发送到进程pid时,必须拥有root权力,或者是发出调用的进程的UID 或EUID与指定接收的进程的UID或保存用户ID(savedset-user-ID)相同。若是参数pid小于-1,即该信号发送给一个组,则该错误 表示组中有成员进程不能接收该信号。 

三、pause系统调用 

系统调用pause的做用是等待一个信号。该调用的声明格式以下: 
int pause(void); 
在使用该调用的进程中加入如下头文件: 
#include <unistd.h> 

该调用使得发出调用的进程进入睡眠,直到接收到一个信号为止。该调用老是返回-1,并设置错误代码为EINTR(接收到一个信号)。下面是一个简单的范例: 

#include <unistd.h> 
#include <stdio.h> 
#include <signal.h> 
void sigroutine(int unused) { 
printf("Catch a signal SIGINT "); 


int main() { 
signal(SIGINT, sigroutine); 
pause(); 
printf("receive a signal "); 


在这个例子中,程序开始执行,就象进入了死循环同样,这是由于进程正在等待信号,当咱们按下Ctrl-C时,信号被捕捉,而且使得pause退出等待状态。 

四、alarm和 setitimer系统调用 

系统调用alarm的功能是设置一个定时器,当定时器计时到达时,将发出一个信号给进程。该调用的声明格式以下: 
unsigned int alarm(unsigned int seconds); 
在使用该调用的进程中加入如下头文件: 
#include <unistd.h> 

系 统调用alarm安排内核为调用进程在指定的seconds秒后发出一个SIGALRM的信号。若是指定的参数seconds为0,则再也不发送 SIGALRM信号。后一次设定将取消前一次的设定。该调用返回值为上次定时调用到发送之间剩余的时间,或者由于没有前一次定时调用而返回0。 

注意,在使用时,alarm只设定为发送一次信号,若是要屡次发送,就要屡次使用alarm调用。 

对于alarm,这里再也不举例。如今的系统中不少程序再也不使用alarm调用,而是使用setitimer调用来设置定时器,用getitimer来获得定时器的状态,这两个调用的声明格式以下: 
int getitimer(int which, struct itimerval *value); 
int setitimer(int which, const struct itimerval *value, struct itimerval*ovalue); 
在使用这两个调用的进程中加入如下头文件: 
#include <sys/time.h> 

该系统调用给进程提供了三个定时器,它们各自有其独有的计时域,当其中任何一个到达,就发送一个相应的信号给进程,并使得计时器从新开始。三个计时器由参数which指定,以下所示: 
TIMER_REAL:按实际时间计时,计时到达将给进程发送SIGALRM信号。 
ITIMER_VIRTUAL:仅当进程执行时才进行计时。计时到达将发送SIGVTALRM信号给进程。 
ITIMER_PROF:当进程执行时和系统为该进程执行动做时都计时。与ITIMER_VIR-TUAL是一对,该定时器常常用来统计进程在用户态和内核态花费的时间。计时到达将发送SIGPROF信号给进程。 

定时器中的参数value用来指明定时器的时间,其结构以下: 
struct itimerval { 
struct timeval it_interval; /* 下一次的取值 */ 
struct timeval it_value; /* 本次的设定值 */ 
}; 

该结构中timeval结构定义以下: 
struct timeval { 
long tv_sec; /* 秒 */ 
long tv_usec; /* 微秒,1秒 = 1000000 微秒*/ 
}; 

在setitimer 调用中,参数ovalue若是不为空,则其中保留的是上次调用设定的值。定时器将it_value递减到0时,产生一个信号,并将it_value的值设 定为it_interval的值,而后从新开始计时,如此往复。当it_value设定为0时,计时器中止,或者当它计时到期,而it_interval 为0时中止。调用成功时,返回0;错误时,返回-1,并设置相应的错误代码errno: 
EFAULT:参数value或ovalue是无效的指针。 
EINVAL:参数which不是ITIMER_REAL、ITIMER_VIRT或ITIMER_PROF中的一个。 

下面是关于setitimer调用的一个简单示范,在该例子中,每隔一秒发出一个SIGALRM,每隔0.5秒发出一个SIGVTALRM信号: 

#include <signal.h> 
#include <unistd.h> 
#include <stdio.h> 
#include <sys/time.h> 
int sec; 

void sigroutine(int signo) { 
switch (signo) { 
case SIGALRM: 
printf("Catch a signal -- SIGALRM "); 
break; 
case SIGVTALRM: 
printf("Catch a signal -- SIGVTALRM "); 
break; 

return; 


int main() { 
struct itimerval value,ovalue,value2; 
sec = 5; 

printf("process id is %d ",getpid()); 
signal(SIGALRM, sigroutine); 
signal(SIGVTALRM, sigroutine); 

value.it_value.tv_sec = 1; 
value.it_value.tv_usec = 0; 
value.it_interval.tv_sec = 1; 
value.it_interval.tv_usec = 0; 
setitimer(ITIMER_REAL, &value, &ovalue); 

value2.it_value.tv_sec = 0; 
value2.it_value.tv_usec = 500000; 
value2.it_interval.tv_sec = 0; 
value2.it_interval.tv_usec = 500000; 
setitimer(ITIMER_VIRTUAL, &value2, &ovalue); 

for (;;) ; 


该例子的屏幕拷贝以下: 

localhost:~$ ./timer_test 
process id is 579 
Catch a signal – SIGVTALRM 
Catch a signal – SIGALRM 
Catch a signal – SIGVTALRM 
Catch a signal – SIGVTALRM 
Catch a signal – SIGALRM 
Catch a signal –GVTALRM 

本文简单介绍了Linux下的信号,若是但愿了解其余调用,请参考联机手册或其余文档。

 

 

i++是否原子操做?并解释为何?

说出你所知道的linux系统的各种同步机制(重点),什么是死锁?如何避免死锁(每一个技术面试官必问)

死锁的条件。(互斥条件(Mutualexclusion):一、资源不能被共享,只能由一个进程使用。二、请求与保持条件(Hold andwait):已经获得资源的进程能够再次申请新的资源。三、非剥夺条件(Nopre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。四、循环等待条件(Circularwait):系统中若干进程组成环路,该环路中每一个进程都在等待相邻进程正占用的资源。处理死锁的策略:1.忽略该问题。例如鸵鸟算法,该算法能够应用在极少发生死锁的的状况下。为何叫鸵鸟算法呢,由于传说中鸵鸟看到危险就把头埋在地底下,可能鸵鸟以为看不到危险也就没危险了吧。跟掩耳盗铃有点像。2.检测死锁而且恢复。3.仔细地对资源进行动态分配,以免死锁。4.经过破除死锁四个必要条件之一,来防止死锁产生。)

列举说明linux系统的各种异步机制

exit()与_exit()的区别?

_exit终止调用进程,但不关闭文件,不清除输出缓存,也不调用出口函数。exit函数将终止调用进程。在退出程序以前,全部文件关闭,缓冲输出内容将刷新定义,并调用全部已刷新的“出口函数”(由atexit定义)。

‘exit()’与‘_exit()’有很多区别在使用‘fork()’,特别是‘vfork()’时变得很突出。

 ‘exit()’与‘_exit()’的基本区别在于前一个调用实施与调用库里用户状态结构(user-mode constructs)有关的清除工做(clean-up),并且调用用户自定义的清除程序

如何实现守护进程?

守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端而且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种颇有用的进程。 Linux的大多数服务器就是用守护进程实现的。好比,Internet服务器inetd,Web服务器httpd等。同时,守护进程完成许多系统任务。好比,做业规划进程crond,打印进程lpd等。

守护进程的编程自己并不复杂,复杂的是各类版本的Unix的实现机制不尽相同,形成不一样 Unix环境下守护进程的编程规则并不一致。须要注意,照搬某些书上的规则(特别是BSD4.3和低版本的System V)到Linux会出现错误的。下面将给出Linux下守护进程的编程要点和详细实例。

一. 守护进程及其特性

守护进程最重要的特性是后台运行。在这一点上DOS下的常驻内存程序TSR与之类似。其次,守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工做目录以及文件建立掩模等。这些环境一般是守护进程从执行它的父进程(特别是shell)中继承下来的。最后,守护进程的启动方式有其特殊之处。它能够在Linux系统启动时从启动脚本/etc/rc.d中启动,能够由做业规划进程crond启动,还能够由用户终端(shell)执行。

总之,除开这些特殊性之外,守护进程与普通进程基本上没有什么区别。所以,编写守护进程其实是把一个普通进程按照上述的守护进程的特性改形成为守护进程。若是对进程有比较深刻的认识就更容易理解和编程了。

二. 守护进程的编程要点

前面讲过,不一样Unix环境下守护进程的编程规则并不一致。所幸的是守护进程的编程原则其实都同样,区别在于具体的实现细节不一样。这个原则就是要知足守护进程的特性。同时,Linux是基于Syetem V的SVR4并遵循Posix标准,实现起来与BSD4相比更方便。编程要点以下;

1. 在后台运行。

为避免挂起控制终端将Daemon放入后台执行。方法是在进程中调用fork使父进程终止,让Daemon在子进程中后台执行。

if(pid=fork())
exit(0); //是父进程,结束父进程,子进程继续
2. 脱离控制终端,登陆会话和进程组

有必要先介绍一下Linux中的进程与控制终端,登陆会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登陆会话能够包含多个进程组。这些进程组共享一个控制终端。这个控制终端一般是建立进程的登陆终端。控制终端,登陆会话和进程组一般是从父进程继承下来的。咱们的目的就是要摆脱它们,使之不受它们的影响。方法是在第1点的基础上,调用setsid()使进程成为会话组长:

setsid();

说明:当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登陆会话和进程组脱离。因为会话过程对控制终端的独占性,进程同时与控制终端脱离。

3. 禁止进程从新打开控制终端

如今,进程已经成为无终端的会话组长。但它能够从新申请打开一个控制终端。能够经过使进程再也不成为会话组长来禁止进程从新打开控制终端:

if(pid=fork()) exit(0); //结束第一子进程,第二子进程继续(第二子进程再也不是会话组长)

4. 关闭打开的文件描述符

进程从建立它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,形成进程所在的文件系统没法卸下以及引发没法预料的错误。按以下方法关闭它们:

for(i=0;i 关闭打开的文件描述符close(i);>

5. 改变当前工做目录

进程活动时,其工做目录所在的文件系统不能卸下。通常须要将工做目录改变到根目录。对于须要转储核心,写运行日志的进程将工做目录改变到特定目录如 /tmpchdir("/")

6. 重设文件建立掩模

进程从建立它的父进程那里继承了文件建立掩模。它可能修改守护进程所建立的文件的存取位。为防止这一点,将文件建立掩模清除:umask(0);

7. 处理SIGCHLD信号

处理SIGCHLD信号并非必须的。但对于某些进程,特别是服务器进程每每在请求到来时生成子进程处理请求。若是父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。若是父进程等待子进程结束,将增长父进程的负担,影响服务器进程的并发性能。在Linux下能够简单地将 SIGCHLD信号的操做设为SIG_IGN。

signal(SIGCHLD,SIG_IGN);

这样,内核在子进程结束时不会产生僵尸进程。这一点与BSD4不一样,BSD4下必须显式等待子进程结束才能释放僵尸进程。

三. 守护进程实例

守护进程实例包括两部分:主程序test.c和初始化程序init.c。主程序每隔一分钟向/tmp目录中的日志test.log报告运行状态。初始化程序中的init_daemon函数负责生成守护进程。读者能够利用init_daemon函数生成本身的守护进程。

 

linux的内存管理机制是什么?

Linux虚拟内存的实现须要6种机制的支持:地址映射机制、内存分配回收机制、缓存和刷新机制、请求页机制、交换机制和内存共享机制

内存管理程序经过映射机制把用户程序的逻辑地址映射到物理地址。当用户程序运行时,若是发现程序中要用的虚地址没有对应的物理内存,就发出了请求页要求。若是有空闲的内存可供分配,就请求分配内存(因而用到了内存的分配和回收),并把正在使用的物理页记录在缓存中(使用了缓存机制)。若是没有足够的内存可供分配,那么就调用交换机制;腾出一部份内存。另外,在地址映射中要经过TLB(翻译后援存储器)来寻找物理页;交换机制中也要用到交换缓存,而且把物理页内容交换到交换文件中,也要修改页表来映射文件地址。

linux的任务调度机制是什么?

在每一个进程的task_struct结构中有如下四项:policy、priority、counter、rt_priority。这四项是选择进程的依据。其中,policy是进程的调度策略,用来区分实时进程和普通进程,实时进程优先于普通进程运行;priority是进程(包括实时和普通)的静态优先级;counter是进程剩余的时间片,它的起始值就是priority的值;因为counter在后面计算一个处于可运行状态的进程值得运行的程度goodness时起重要做用,所以,counter 也能够看做是进程的动态优先级。rt_priority是实时进程特有的,用于实时进程间的选择。 
Linux用函数goodness()来衡量一个处于可运行状态的进程值得运行的程度。该函数综合了以上提到的四项,还结合了一些其余的因素,给每一个处于可运行状态的进程赋予一个权值(weight),调度程序以这个权值做为选择进程的惟一依据。关于goodness()的状况在后面将会详细分析。

五种I/O 模式——

阻塞(默认IO模式),

非阻塞(经常使用于管道),

I/O多路复用(IO多路复用的应用场景),

信号I/O,

异步I/O  

五种I/O 模式:
【1】       阻塞I/O           (Linux下的I/O操做默认是阻塞I/O,即open和socket建立的I/O都是阻塞I/O)
【2】       非阻塞 I/O        (能够经过fcntl或者open时使用O_NONBLOCK参数,将fd设置为非阻塞的I/O)
【3】       I/O 多路复用     (I/O多路复用,一般须要非阻塞I/O配合使用)
【4】       信号驱动 I/O    (SIGIO)
【5】        异步 I/O

 

通常来讲,程序进行输入操做有两步:
1.等待有数据能够读
2.将数据从系统内核中拷贝到程序的数据区。

对于sock编程来讲:

         第一步:   通常来讲是等待数据从网络上传到本地。当数据包到达的时候,数据将会从网络层拷贝到内核的缓存中;

         第二步:   是从内核中把数据拷贝到程序的数据区中。

 

阻塞I/O模式                           //进程处于阻塞模式时,让出CPU,进入休眠状态
        阻塞 I/O 模式是最广泛使用的 I/O 模式。是Linux系统下缺省的IO模式。

       大部分程序使用的都是阻塞模式的 I/O 。

       一个套接字创建后所处于的模式就是阻塞 I/O 模式。(由于Linux系统默认的IO模式是阻塞模式)


对于一个UDP 套接字来讲,数据就绪的标志比较简单:
(1)已经收到了一整个数据报
(2)没有收到。
而 TCP 这个概念就比较复杂,须要附加一些其余的变量。

       一个进程调用 recvfrom  ,而后系统调用并不返回知道有数据报到达本地系统,而后系统将数据拷贝到进程的缓存中。(若是系统调用收到一个中断信号,则它的调用会被中断)

   咱们称这个进程在调用recvfrom一直到从recvfrom返回这段时间是阻塞的。当recvfrom正常返回时,咱们的进程继续它的操做。

 

 

 

 

 

非阻塞模式I/O                          //非阻塞模式的使用并不广泛,由于非阻塞模式会浪费大量的CPU资源。
       当咱们将一个套接字设置为非阻塞模式,咱们至关于告诉了系统内核: “当我请求的I/O 操做不可以立刻完成,你想让个人进程进行休眠等待的时候,不要这么作,请立刻返回一个错误给我。”
      咱们开始对 recvfrom 的三次调用,由于系统尚未接收到网络数据,因此内核立刻返回一个EWOULDBLOCK的错误。

      第四次咱们调用 recvfrom 函数,一个数据报已经到达了,内核将它拷贝到咱们的应用程序的缓冲区中,而后recvfrom 正常返回,咱们就能够对接收到的数据进行处理了。
      当一个应用程序使用了非阻塞模式的套接字,它须要使用一个循环来不听的测试是否一个文件描述符有数据可读(称作 polling(轮询))。应用程序不停的 polling 内核来检查是否 I/O操做已经就绪。这将是一个极浪费 CPU资源的操做。这种模式使用中不是很广泛。

 

 例如:

         对管道的操做,最好使用非阻塞方式!

 

 

 

 

I/O多路复用                            //针对批量IP操做时,使用I/O多路复用,很是有好。

       在使用 I/O 多路技术的时候,咱们调用select()函数和 poll()函数或epoll函数(2.6内核开始支持),在调用它们的时候阻塞,而不是咱们来调用 recvfrom(或recv)的时候阻塞。
       当咱们调用 select函数阻塞的时候,select 函数等待数据报套接字进入读就绪状态。当select函数返回的时候,也就是套接字能够读取数据的时候。这时候咱们就能够调用 recvfrom函数来将数据拷贝到咱们的程序缓冲区中。
        对于单个I/O操做,和阻塞模式相比较,select()和poll()或epoll并无什么高级的地方。

       并且,在阻塞模式下只须要调用一个函数:

                            读取或发送函数。

                  在使用了多路复用技术后,咱们须要调用两个函数了:

                             先调用 select()函数或poll()函数,而后才能进行真正的读写。

       多路复用的高级之处在于::

             它能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就能够返回。

 

IO 多路技术通常在下面这些状况中被使用:
一、当一个客户端须要同时处理多个文件描述符的输入输出操做的时候(通常来讲是标准的输入输出和网络套接字),I/O多路复用技术将会有机会获得使用。
二、当程序须要同时进行多个套接字的操做的时候。
三、若是一个 TCP 服务器程序同时处理正在侦听网络链接的套接字和已经链接好的套接字。
四、若是一个服务器程序同时使用 TCP 和 UDP 协议。
五、若是一个服务器同时使用多种服务而且每种服务可能使用不一样的协议(好比 inetd就是这样的)。

 

 

 

异步IO模式有::

      一、信号驱动I/O模式

       二、异步I/O模式

信号驱动I/O模式                                                  //本身没有用过。

       咱们能够使用信号,让内核在文件描述符就绪的时候使用 SIGIO 信号来通知咱们。咱们将这种模式称为信号驱动I/O 模式。

为了在一个套接字上使用信号驱动 I/O 操做,下面这三步是所必须的。
(1)一个和 SIGIO信号的处理函数必须设定。
(2)套接字的拥有者必须被设定。通常来讲是使用 fcntl 函数的 F_SETOWN 参数来
进行设定拥有者。
(3)套接字必须被容许使用异步 I/O。通常是经过调用 fcntl 函数的 F_SETFL 命令,O_ASYNC为参数来实现。

       虽然设定套接字为异步 I/O 很是简单,可是使用起来困难的部分是怎样在程序中判定产生 SIGIO信号发送给套接字属主的时候,程序处在什么状态。

1.UDP 套接字的 SIGIO 信号                   (比较简单)
在 UDP 协议上使用异步 I/O 很是简单.这个信号将会在这个时候产生:

一、套接字收到了一个数据报的数据包。
二、套接字发生了异步错误。
        当咱们在使用 UDP 套接字异步 I/O 的时候,咱们使用 recvfrom()函数来读取数据报数据或是异步 I/O 错误信息。
2.TCP 套接字的 SIGIO 信号                  (不会使用)
          不幸的是,异步 I/O 几乎对 TCP 套接字而言没有什么做用。由于对于一个 TCP 套接字来讲,SIGIO 信号发生的概率过高了,因此 SIGIO 信号并不能告诉咱们究竟发生了什么事情。

在 TCP 链接中, SIGIO 信号将会在这个时候产生:
l  在一个监听某个端口的套接字上成功的创建了一个新链接。
l  一个断线的请求被成功的初始化。
l  一个断线的请求成功的结束。
l  套接字的某一个通道(发送通道或是接收通道)被关闭。
l  套接字接收到新数据。
l  套接字将数据发送出去。

l  发生了一个异步 I/O 的错误。

一个对信号驱动 I/O 比较实用的方面是NTP(网络时间协议 Network TimeProtocol)服务器,它使用 UDP。这个服务器的主循环用来接收从客户端发送过来的数据报数据包,而后再发送请求。对于这个服务器来讲,记录下收到每个数据包的具体时间是很重要的。

由于那将是返回给客户端的值,客户端要使用这个数据来计算数据报在网络上来回所花费的时间。图 6-8 表示了怎样创建这样的一个 UDP 服务器。

 

 

异步I/O模式             //好比写操做,只需用写,不必定写入磁盘(这就是异步I/O)的好处。异步IO的好处效率高。
      当咱们运行在异步 I/O 模式下时,咱们若是想进行 I/O 操做,只须要告诉内核咱们要进行 I/O 操做,而后内核会立刻返回。具体的 I/O 和数据的拷贝所有由内核来完成,咱们的程序能够继续向下执行。当内核完成全部的 I/O 操做和数据拷贝后,内核将通知咱们的程序。
异步 I/O 和  信号驱动I/O的区别是:
        一、信号驱动 I/O 模式下,内核在操做能够被操做的时候通知给咱们的应用程序发送SIGIO 消息。

        二、异步 I/O 模式下,内核在全部的操做都已经被内核操做结束以后才会通知咱们的应用程序。

select,poll,epoll

. Epoll 是何方神圣?

Epoll 但是当前在 Linux 下开发大规模并发网络程序的热门人选, Epoll 在 Linux2.6 内核中正式引入,和 select 类似,其实都 I/O 多路复用技术而已,并无什么神秘的。

其实在Linux 下设计并发网络程序,向来不缺乏方法,好比典型的 Apache 模型( Process Per Connection ,简称PPC), TPC ( ThreadPer Connection )模型,以及 select 模型和 poll 模型,那为什么还要再引入 Epoll 这个东东呢?那仍是有得说说的 …

2. 经常使用模型的缺点

若是不摆出来其余模型的缺点,怎么能对比出 Epoll 的优势呢。

2.1 PPC/TPC 模型

这两种模型思想相似,就是让每个到来的链接一边本身作事去,别再来烦我。只是 PPC 是为它开了一个进程,而TPC 开了一个线程。但是别烦我是有代价的,它要时间和空间啊,链接多了以后,那么多的进程 / 线程切换,这开销就上来了;所以这类模型能接受的最大链接数都不会高,通常在几百个左右。

2.2 select 模型

1. 最大并发数限制,由于一个进程所打开的 FD (文件描述符)是有限制的,www.linuxidc.com 由FD_SETSIZE 设置,默认值是 1024/2048 ,所以 Select 模型的最大并发数就被相应限制了。本身改改这个 FD_SETSIZE ?想法虽好,但是先看看下面吧 …

2. 效率问题, select 每次调用都会线性扫描所有的 FD 集合,这样效率就会呈现线性降低,把 FD_SETSIZE 改大的后果就是,你们都慢慢来,什么?都超时了??!!

3. 内核 / 用户空间内存拷贝问题,如何让内核把 FD 消息通知给用户空间呢?在这个问题上 select 采起了内存拷贝方法。

2.3 poll 模型

基本上效率和select 是相同的,select 缺点的 2 和 3 它都没有改掉。

3. Epoll 的提高

把其余模型逐个批判了一下,再来看看 Epoll 的改进之处吧,其实把 select 的缺点反过来那就是 Epoll 的优势了。

3.1. Epoll 没有最大并发链接的限制,上限是最大能够打开文件的数目,这个数字通常远大于 2048, 通常来讲这个数目和系统内存关系很大,具体数目能够 cat /proc/sys/fs/file-max 察看。

3.2. 效率提高, Epoll 最大的优势就在于它只管你“活跃”的链接,而跟链接总数无关,所以在实际的网络环境中, Epoll的效率就会远远高于 select 和 poll 。

3.3. 内存拷贝, Epoll 在这点上使用了“共享内存 ”,这个内存拷贝也省略了。


4. Epoll 为何高效

Epoll 的高效和其数据结构的设计是密不可分的,这个下面就会提到。

首先回忆一下select 模型,当有I/O 事件到来时,select 通知应用程序有事件到了快去处理,而应用程序必须轮询全部的FD 集合,测试每一个 FD 是否有事件发生,并处理事件;代码像下面这样:

int res = select(maxfd+1, &readfds,NULL, NULL, 120);

if (res > 0)

{

    for (int i = 0; i <MAX_CONNECTION; i++)

    {

       if (FD_ISSET(allConnection[i], &readfds))

       {

           handleEvent(allConnection[i]);

       }

    }

}

// if(res == 0) handle timeout, res < 0handle error


Epoll 不只会告诉应用程序有I/0事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,所以根据这些信息应用程序就能直接定位到事件,而没必要遍历整个FD 集合。

int res = epoll_wait(epfd, events, 20,120);

for (int i = 0; i < res;i++)

{

    handleEvent(events[n]);

}

5. Epoll 关键数据结构

前面提到Epoll 速度快和其数据结构密不可分,其关键数据结构就是:

struct epoll_event {

    __uint32_tevents;      // Epoll events

    epoll_data_tdata;      // User data variable

};

typedef union epoll_data {

    void *ptr;

    int fd;

    __uint32_t u32;

    __uint64_t u64;

} epoll_data_t;

可见epoll_data 是一个 union 结构体 , 借助于它应用程序能够保存不少类型的信息 :fd 、指针等等。有了它,应用程序就能够直接定位目标了。

 

标准库函数和系统调用的区别?

一、系统调用和库函数的关系

系统调用经过软中断int 0x80从用户态进入内核态。

函数库中的某些函数调用了系统调用。

函数库中的函数能够没有调用系统调用,也能够调用多个系统调用。

编程人员能够经过函数库调用系统调用。

高级编程也能够直接采用int 0x80进入系统调用,而没必要经过函数库做为中介。

若是是在核心编程,也能够经过int 0x80进入系统调用,此时不能使用函数库。由于函数库中的函数是内核访问不到的。

二、从用户调用库函数到系统调用执行的流程。

1) 假设用户调用ssize_t write (int fields, cont void *buff, size_tnbytes);库函数。

2) 库函数会执行int 0x80中断。由于中断使得进程从用户态进入内核态,因此参数经过寄存器传送。

3) 0x80中断对应的中断例程被称为system call handler。其工做是:

 i.  存储大多数寄存器到内核堆栈中。这是汇编代码写的。

 ii.  执行真正的系统调用函数――system callservice routine。这是C代码。

 iii. 经过ret_from_sys_call()返回,回到用户态的库函数。这是汇编代码。

一、系统调用

系统调用提供的函数如open, close, read, write, ioctl等,需包含头文件unistd.h。以write为例:其函数原型为 size_t write(int fd, const void *buf, size_t nbytes),其操做对象为文件描述符或文件句柄fd(file descriptor),要想写一个文件,必须先以可写权限用open系统调用打开一个文件,得到所打开文件的fd,例如fd=open(/"/dev/video/", O_RDWR)。fd是一个整型值,每新打开一个文件,所得到的fd为当前最大fd加1。Linux系统默认分配了3个文件描述符值:0-standard input,1-standard output,2-standard error。

系统调用一般用于底层文件访问(low-level file access),例如在驱动程序中对设备文件的直接访问。

系统调用是操做系统相关的,所以通常没有跨操做系统的可移植性。

系统调用发生在内核空间,所以若是在用户空间的通常应用程序中使用系统调用来进行文件操做,会有用户空间到内核空间切换的开销。事实上,即便在用户空间使用库函数来对文件进行操做,由于文件老是存在于存储介质上,所以无论是读写操做,都是对硬件(存储器)的操做,都必然会引发系统调用。也就是说,库函数对文件的操做其实是经过系统调用来实现的。例如C库函数fwrite()就是经过write()系统调用来实现的。

这样的话,使用库函数也有系统调用的开销,为何不直接使用系统调用呢?这是由于,读写文件一般是大量的数据(这种大量是相对于底层驱动的系统调用所实现的数据操做单位而言),这时,使用库函数就能够大大减小系统调用的次数。这一结果又缘于缓冲区技术。在用户空间和内核空间,对文件操做都使用了缓冲区,例如用fwrite写文件,都是先将内容写到用户空间缓冲区,当用户空间缓冲区满或者写操做结束时,才将用户缓冲区的内容写到内核缓冲区,一样的道理,当内核缓冲区满或写结束时才将内核缓冲区内容写到文件对应的硬件媒介。

二、库函数调用

标准C库函数提供的文件操做函数如fopen, fread, fwrite, fclose,fflush, fseek等,需包含头文件stdio.h。以fwrite为例,其函数原型为size_t fwrite(const void *buffer,size_t size, size_t item_num, FILE *pf),其操做对象为文件指针FILE *pf,要想写一个文件,必须先以可写权限用fopen函数打开一个文件,得到所打开文件的FILE结构指针pf,例如pf=fopen(/"~/proj/filename/",/"w/")。实际上,因为库函数对文件的操做最终是经过系统调用实现的,所以,每打开一个文件所得到的FILE结构指针都有一个内核空间的文件描述符fd与之对应。一样有相应的预约义的FILE指针:stdin-standard input,stdout-standard output,stderr-standard error。

库函数调用一般用于应用程序中对通常文件的访问。

库函数调用是系统无关的,所以可移植性好。

因为库函数调用是基于C库的,所以也就不可能用于内核空间的驱动程序中对设备的操做

ping命令所利用的原理是这样的:网络上的机器都有惟一肯定的IP地址,咱们给目标IP地址发送一个数据包,对方就要返回一个一样大小的数据包,根据返回的数据包咱们能够肯定目标主机的存在,能够初步判断目标主机的操做系统等。

补充一个坑爹坑爹坑爹坑爹的问题:系统如何将一个信号通知到进程?

信号机制是异步的;当一个进程接收到一个信号时,它会马上处理这个信号,而不会等待当前函数甚至当前一行代码结束运行。信号有几十种,分别表明着不一样的意义。信号之间依靠它们的值来区分,可是一般在程序中使用信号的名字来表示一个信号。在Linux系统中,这些信号和以它们的名称命名的常量均定义在/usr/include/bits/signum.h文件中。(一般程序中不须要直接包含这个头文件,而应该包含<signal.h>。)

信号事件的发生有两个来源:硬件来源(好比咱们按下了键盘或者其它硬件故障);软件来源,最经常使用发送信号的系统函数是kill, raise, alarm和setitimer以及sigqueue函数,软件来源还包括一些非法运算等操做。

发送信号的主要函数有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。

 

进程能够经过三种方式来响应一个信号:(1)忽略信号,即对信号不作任何处理,其中,有两个信号不能忽略:SIGKILL及SIGSTOP;(2)捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数;(3)执行缺省操做,

 c语言:

宏定义和展开(必须精通)

#define是C语言中提供的宏定义命令,其主要目的是为程序员在编程时提供必定的方便,并能在必定程度上提升程序的运行效率,但学生在学习时每每不能理解该命令的本质,老是在此处产生一些困惑,在编程时误用该命令,使得程序的运行与预期的目的不一致,或者在读别人写的程序时,把运行结果理解错误,这对C语言的学习很不利。
1#define命令剖析 
1.1   #define的概念
#define命令是C语言中的一个宏定义命令,它用来将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本。
该命令有两种格式:一种是简单的宏定义,另外一种是带参数的宏定义。
(1)   简单的宏定义:
#define   <宏名><字符串> 
   例:  #define PI 3.1415926 
(2) 带参数的宏定义
   #define   <宏名> (<参数表>)  <宏体>
   例:#define   A(x) x 
一个标识符被宏定义后,该标识符即是一个宏名。这时,在程序中出现的是宏名,在该程序被编译前,先将宏名用被定义的字符串替换,这称为宏替换,替换后才进行编译,宏替换是简单的替换。
1.2 宏替换发生的时机
为了可以真正理解#define的做用,让咱们来了解一下对C语言源程序的处理过程。当咱们在一个集成的开发环境如TurboC中将编写好的源程序进行编译时,实际通过了预处理、编译、汇编和链接几个过程

 

 

这里要说一下宏的展开次序,若是宏有参数,如TO_STRING_MACRO(x)中的x,咱们称之为形参,而宏实际的参数咱们称之为实参,如TO_STRING_MACRO(a)中的a。

宏的展开是个很复杂的过程,但能够用如下三步来简单描述,

首先用实参替换形参,将实参代入宏文本中;

而后若是实参也是宏,则展开实参;

最后再继续处理宏替换后的宏文本,若宏文本也包含宏则继续展开,不然完成展开。

可是有个例外,那就是第一步后,将实参代入宏文本后,实参以前若是遇到字符“#”或“##”,即便实参是宏,也再也不展开实参,而是看成文本处理。

 

位操做(必须精通)

在计算机程序中,数据的位是能够操做的最小数据单位,理论上能够用“位运算”来完成全部的运算和操做。通常的位操做是用来控制硬件的,或者作数据变换使用,可是,灵活的位操做能够有效地提升程序运行的效率。C语言提供了位运算的功能,这使得C语言也能像汇编语言同样用来编写系统程序。

  位运算符C语言提供了六种位运算符:

& 按位与

| 按位或

^ 按位异或

~ 取反

<< 左移

>> 右移

 

指针操做和计算(必须精通)

&是地址操做符,用来引用一个内存地址。经过在变量名字前使用&操做符,咱们能够获得该变量的内存地址。

1

2

3

4

5

6

7

8

9

// 声明一个int指针

int*ptr;

// 声明一个int值

intval = 1;

// 为指针分配一个int值的引用

ptr = &val;

// 对指针进行取值,打印存储在指针地址中的内容

intderef = *ptr;

printf("%d\n", deref);

指针是一个存储计算机内存地址的变量。在这份教程里“引用”表示计算机内存地址。从指针指向的内存读取数据称做指针的取值。指针能够指向某些具体类型的变量地址,例如 int、long 和 double。指针也能够是 void 类型、NULL 指针和未初始化指针。本文会对上述全部指针类型进行探讨。

  根据出现的位置不一样,操做符 * 既能够用来声明一个指针变量,也能够用做指针的取值。当用在声明一个变量时,*表示这里声明了一个指针。其它状况用到*表示指针的取值。

  &是地址操做符,用来引用一个内存地址。经过在变量名字前使用&操做符,咱们能够获得该变量的内存地址。

语言中指针的15个问题 
aqiaoboy 

1 指针的四要素 
  1指针变量,表示一个内存地址,一般为逻辑地址,与实际的物理地址还有一个映射关系. 
2指针变量的长度,在WIN32下为四个字节, 
  3指针指向的变量 
   该内存地址空间下存放的变量,具体内容多是各类类型的变量. 
  4 指针指向的变量的长度,以该内存地址空间开始的内存空间大小. 
2 Const,volatile修饰指针的含义 
const    char *cpch=”hello’; 
表示指针指向的变量不可改变,但指针自己是能够改变的 
char * const    pchc; 
指针指向的变量能够改变,但指针自己不可改变. 

Const  char * const pchc; 
二者都不可变. 
3 堆和栈上的指针 
指针所指向的这块内存是在哪里分配的,在堆上称为堆上的指针,在栈上为栈上的指针. 
在堆上的指针,能够保存在全局数据结构中,供不一样函数使用访问同一块内存. 
在栈上的指针,在函数退出后,该内存即不可访问. 

4 什么是指针的释放? 
具体来讲包括两个概念. 
1 释放该指针指向的内存,只有堆上的内存才须要咱们手工释放,栈上不须要. 
2 将该指针重定向为NULL. 

5 near,far型指针的区别? 
老式的IBM PC兼容机才有这种区别,由于老式机不能彻底支持32位指针, 
因此才分为16位指针,(near),和32位指针(far) 
从386开始没有这种区别,都是32位指针. 

6 数据结构中的指针? 
其实就是指向一块内存的地址,经过指针传递,可实现复杂的内存访问. 
7 函数指针? 
指向一块函数的入口地址. 

8 指针做为函数的参数? 
好比指向一个复杂数据结构的指针做为函数变量 
这种方法避免整个复杂数据类型内存的压栈出栈操做,提升效率. 
注意:指针自己不可变,但指针指向的数据结构能够改变. 

9 指向指针的指针? 
指针指向的变量是一个指针,即具体内容为一个指针的值,是一个地址. 
此时指针指向的变量长度也是4位. 

10 指针与地址的区别? 
区别: 
1指针意味着已经有一个指针变量存在,他的值是一个地址,指针变量自己也存放在一个长度为四个字节的地址当中,而地址概念自己并不表明有任何变量存在. 
2 指针的值,若是没有限制,一般是能够变化的,也能够指向另一个地址. 
   地址表示内存空间的一个位置点,他是用来赋给指针的,地址自己是没有大小概念,指针指向变量的大小,取决于地址后面存放的变量类型. 

11 指针与数组名的关系? 
  其值都是一个地址,但前者是能够移动的,后者是不可变的. 

12 怎样防止指针的越界使用问题? 
  必须让指针指向一个有效的内存地址, 
1 防止数组越界 
2 防止向一块内存中拷贝过多的内容 
3 防止使用空指针 
4 防止改变const修改的指针 
5 防止改变指向静态存储区的内容 
6 防止两次释放一个指针 
7 防止使用野指针. 


13 指针的类型转换? 
指针转换一般是指针类型和void * 类型以前进行强制转换,从而与指望或返回void指针的函数进行正确的交接. 


14 什么是指针退化? 
若是用一个数组做为函数入参 
好比 
void fun(char a[100]) 

cout<<SIZEOF(A)<


15 指针的移动问题? 

指针P ++具体移动的字节数等于指针指向的变量类型大小. 



写得还比较全的 
函数参数我都用指针没有用数组 

14 什么是指针退化? 
若是用一个数组做为函数入参 
好比 
void fun(char a[100]) 

cout<<SIZEOF(A)<


像这样定义也不能保证检查传进来的参数对不对 
[code] 
int a(char s[100]) 

    s[100]=1; 
    return 0; 


main() 

   char s[20]="hello"; 
   a(s); 

[/code]

内存分配(必须精通)

sizeof必考

内存分配方式有三种:

  (1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。

  (2)在栈上建立。在执行函数时,函数内局部变量的存储单元均可以在栈上建立,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,可是分配的内存容量有限。

  (3)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员本身负责在什么时候用free或delete释放内存。动态内存的生存期由咱们决定,使用很是灵活,但问题也最多。

各种库函数必须很是熟练的实现

 

哪些库函数属于高危函数,为何?(strcpy等等)

c++:

一个String类的完整实现必须很快速写出来(注意:赋值构造,operator=是关键)

虚函数的做用和实现原理(必问必考,实现原理必须很熟)

有虚函数的类内部有一个称为“虚表”的指针(有多少个虚函数就有多少个指针),这个就是用来指向这个类虚函数。也就是用它来肯定调用该那个函数。

实际上在编译的时候,编译器会自动加入“虚表”。虚表的使用方法是这样的:若是派生类在本身的定义中没有修改基类的虚函数,就指向基类的虚函数;若是派生类改写了基类的虚函数(就是本身从新定义),这时虚表则将原来指向基类的虚函数的地址替换为指向自身虚函数的指针。那些被virtual关键字修饰的成员函数,就是虚函数。虚函数的做用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离;用形象的语言来解释就是实现以共同的方法,但因个体差别而采用不一样的策略。

每一个类都有本身的vtbl,vtbl的做用就是保存本身类中虚函数的地址,咱们能够把vtbl形象地当作一个数组,这个数组的每一个元素存放的就是虚函数的地址,

虚函数的效率低,其缘由就是,在调用虚函数以前,还调用了得到虚函数地址的代码。

 

sizeof一个类求大小(注意成员变量,函数,虚函数,继承等等对大小的影响)

指针和引用的区别(通常都会问到)

相同点:1. 都是地址的概念;
指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。

区别:1. 指针是一个实体,而引用仅是个别名;

2. 引用使用时无需解引用(*),指针须要解引用;

3. 引用只能在定义时被初始化一次,以后不可变;指针可变;

4. 引用没有 const,指针有 const;

5. 引用不能为空,指针能够为空;

6. “sizeof 引用”获得的是所指向的变量(对象)的大小,而“sizeof 指针”获得的是指针自己(所指向的变量或对象的地址)的大小;

7. 指针和引用的自增(++)运算意义不同;

8.从内存分配上看:程序为指针变量分配内存区域,而引用不须要分配内存区域。

多重类构造和析构的顺序

先调用基类的构造函数,在调用派生类的构造函数

先构造的后析构,后构造的先析构

stl各容器的实现原理(必考)

STL = Standard Template Library,标准模板库

STL共有六大组件
 一、容器。二、算法。三、迭代器。四、仿函数。六、适配器。

序列式容器:
vector-数组,元素不够时再从新分配内存,拷贝原来数组的元素到新分配的数组中。
list-单链表。
deque-分配中央控制器map(并不是map容器),map记录着一系列的固定长度的数组的地址.记住这个map仅仅保存的是数组的地址,真正的数据在数组中存放着.deque先从map中央的位置(由于双向队列,先后均可以插入元素)找到一个数组地址,向该数组中放入数据,数组不够时继续在map中找空闲的数组来存数据。当map也不够时从新分配内存看成新的map,把原来map中的内容copy的新map中。因此使用deque的复杂度要大于vector,尽可能使用vector。

stack-基于deque。
queue-基于deque。
heap-彻底二叉树,使用最大堆排序,以数组(vector)的形式存放。
priority_queue-基于heap。
slist-双向链表。

关联式容器:
set,map,multiset,multimap-基于红黑树(RB-tree),一种加上了额外平衡条件的二叉搜索树。

hash table-散列表。将待存数据的key通过映射函数变成一个数组(通常是vector)的索引,例如:数据的key%数组的大小=数组的索引(通常文本经过算法也能够转换为数字),而后将数据看成此索引的数组元素。有些数据的key通过算法的转换多是同一个数组的索引值(碰撞问题,能够用线性探测,二次探测来解决),STL是用开链的方法来解决的,每个数组的元素维护一个list,他把相同索引值的数据存入一个list,这样当list比较短时执行删除,插入,搜索等算法比较快。

hash_map,hash_set,hash_multiset,hash_multimap-基于hashtable。

extern c 是干啥的,(必须将编译器的函数名修饰的机制解答的很透彻)

volatile是干啥用的,(必须将cpu的寄存器缓存机制回答的很透彻)

volatile的本意是“易变的” 由于访问寄存器要比访问内存单元快的多,因此编译器通常都会做减小存取内存的优化,但有可能会读脏数据。当要求使用volatile声明变量值的时候,系统老是从新从它所在的内存读取数据,即便它前面的指令刚刚从该处读取过数据。精确地说就是,遇到这个关键字声明的变量,编译器对访问该变量的代码就再也不进行优化,从而能够提供对特殊地址的稳定访问;若是不使用volatile,则编译器将对所声明的语句进行优化。(简洁的说就是:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以避免出错)

5.volatile的本质:

1> 编译器的优化

在本次线程内, 当读取一个变量时,为提升存取速度,编译器优化时有时会先把变量读取到一个寄存器中;之后,再取变量值时,就直接从寄存器中取值;当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致。

当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而形成应用程序读取的值和实际的变量值不一致。

当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而形成应用程序读取的值和实际的变量值不一致。

2>volatile应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人。

 

static const等等的用法,(能说出越多越好)

数据结构或者算法:

《离散数学》范围内的一切问题皆由可能被深刻问到(这个最坑爹,最重要,最体现功底,最能加分,特别是各种树结构的实现和应用)

各种排序:大根堆的实现,快排(如何避免最糟糕的状态?),bitmap的运用等等

hash,任何一个技术面试官必问(例如为何通常hashtable的桶数会取一个素数?如何有效避免hash结果值的碰撞)

网络编程:

tcp与udp的区别(必问)

1.基于链接与无链接 
2.对系统资源的要求(TCP较多,UDP少) 
3.UDP程序结构较简单 
4.流模式与数据报模式
5.TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证

TCP---传输控制协议,提供的是面向链接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间创建一个TCP链接,以后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另外一端。
UDP---用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,可是并不能保证它们能到达目的地。因为UDP在传输数据报前不用在客户和服务器之间创建一个链接,且没有超时重发等机制,故而传输速度很快

 

udp调用connect有什么做用?

1:UDP中能够使用connect系统调用2:UDP中connect操做与TCP中connect操做有着本质区别.TCP中调用connect会引发三次握手,client与server创建连结.UDP中调用connect内核仅仅把对端ip&port记录下来.3:UDP中能够屡次调用connect,TCP只能调用一次connect.UDP屡次调用connect有两种用途:1,指定一个新的ip&port连结.2,断开和以前的ip&port的连结.指定新连结,直接设置connect第二个参数便可.断开连结,须要将connect第二个参数中的sin_family设置成 AF_UNSPEC便可. 4:UDP中使用connect能够提升效率.缘由以下:普通的UDP发送两个报文内核作了以下:#1:创建连结#2:发送报文#3:断开连结#4:创建连结#5:发送报文#6:断开连结采用connect方式的UDP发送两个报文内核以下处理:#1:创建连结#2:发送报文#3:发送报文另一点,每次发送报文内核都由可能要作路由查询.5:采用connect的UDP发送接受报文能够调用send,write和recv,read操做.固然也能够调用sendto,recvfrom.调用sendto的时候第五个参数必须是NULL,第六个参数是0.调用recvfrom,recv,read系统调用只能获取到先前connect的ip&port发送的报文. 
UDP中使用connect的好处:1:会提高效率.前面已经描述了.2:高并发服务中会增长系统稳定性.缘由:假设client A 经过非connect的UDP与serverB,C通讯.B,C提供相同服务.为了负载均衡,咱们让A与B,C交替通讯.A 与 B通讯IPa:PORTa<----> IPb:PORTbA 与 C通讯IPa:PORTa'<---->IPc:PORTc 
假设PORTa 与 PORTa'相同了(在大并发状况下会发生这种状况),那么就有可能出现A等待B的报文,却收到了C的报文.致使收报错误.解决方法内就是采用connect的UDP通讯方式.在A中建立两个udp,而后分别connect到B,C.

tcp链接中时序图,状态图,必须很是很是熟练

 

 

socket服务端的实现,select和epoll的区别(必问)

select的本质是采用32个整数的32位,即32*32= 1024来标识,fd值为1-1024。当fd的值超过1024限制时,就必须修改FD_SETSIZE的大小。这个时候就能够标识32*max值范围的fd。

对于单进程多线程,每一个线程处理多个fd的状况,select是不适合的。

1.全部的线程均是从1-32*max进行扫描,每一个线程处理的均是一段fd值,这样作有点浪费

2.1024上限问题,一个处理多个用户的进程,fd值远远大于1024

因此这个时候应该采用poll,

poll传递的是数组头指针和该数组的长度,只要数组的长度不是很长,性能仍是很不错的,由于poll一次在内核中申请4K(一个页的大小来存放fd),尽可能控制在4K之内

epoll仍是poll的一种优化,返回后不须要对全部的fd进行遍历,在内核中维持了fd的列表。select和poll是将这个内核列表维持在用户态,而后传递到内核中。可是只有在2.6的内核才支持。

epoll更适合于处理大量的fd ,且活跃fd不是不少的状况,毕竟fd较多仍是一个串行的操做

 

epoll哪些触发模式,有啥区别?(必须很是详尽的解释水平触发和边缘触发的区别,以及边缘触发在编程中要作哪些更多的确认)

epoll能够同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,若是咱们没有采起行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,可是代码实现至关复杂。

epoll一样只告知那些就绪的文件描述符,并且当咱们调用epoll_wait()得到就绪文件描述符时,返回的不是实际的描述符,而是一个表明就绪描述符数量的值,你只须要去epoll指定的一个数组中依次取得相应数量的文件描述符便可,这里也使用了内存映射(mmap)技术,这样便完全省掉了这些文件描述符在系统调用时复制的开销。

另外一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用必定的方法后,内核才对全部监视的文件描述符进行扫描,而epoll事先经过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用相似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便获得通知。

 

大规模链接上来,并发模型怎么设计

tcp结束链接怎么握手,time_wait状态是什么,为何会有time_wait状态?哪一方会有time_wait状态,如何避免time_wait状态占用资源(必须回答的详细)

tcp头多少字节?哪些字段?(必问)

头20字节,选项12字节

什么是滑动窗口(必问)

滑动窗口(Sliding window )是一种流量控制技术。滑动窗口协议是用来改善吞吐量的一种技术,即允许发送方在接收任何应答以前传送附加的包。接收方告诉发送方在某一时刻能送多少包(称窗口尺寸)。TCP中采用滑动窗口来进行传输控制,滑动窗口的大小意味着接收方还有多大的缓冲区能够用于接收数据。发送方能够经过滑动窗口的大小来肯定应该发送多少字节的数据。当滑动窗口为0时,发送方通常不能再发送数据报,但有两种状况除外,一种状况是能够发送紧急数据,例如,容许用户终止在远端机上的运行进程。另外一种状况是发送方能够发送一个1字节的数据报来通知接收方从新声明它但愿接收的下一字节及发送方的滑动窗口大小。滑动窗口协议的基本原理就是在任意时刻,发送方都维持了一个连续的容许发送的帧的序号,称为发送窗口;同时,接收方也维持了一个连续的容许接收的帧的序号,称为接收窗口。发送窗口和接收窗口的序号的上下界不必定要同样,甚至大小也能够不一样。不一样的滑动窗口协议窗口大小通常不一样。发送方窗口内的序列号表明了那些已经被发送,可是尚未被确认的帧,或者是那些能够被发送的帧。

connect会阻塞,怎么解决?(必考必问)

最一般的方法最有效的是加定时器;也能够采用非阻塞模式。

设置非阻塞,返回以后用select检测状态)

若是select返回可读,结果只读到0字节,什么状况?

某个套接字集合中没有准备好,可能会select内存用FD_CLR清该位为0;

keepalive是什么东东?如何使用?

设置Keepalive参数,检测已中断的客户链接

·        Determine how long to wait beforeprobing the connection. On most platforms the default is 2 hours.

·       Determine how long to wait beforeretrying the probe.

·       Determine how many times to probe theconnection.

 

列举你所知道的tcp选项,并说明其做用。

1.窗口扩大因子TCP Window Scale Option (WSopt)

TCP窗口缩放选项是用来增长TCP接收窗口的大小而超过65536字节。

2.SACK选择确认选项

最大报文段长度(M S S)表示T C P传往另外一端的最大块数据的长度。当创建一个链接时,每一方都有用于通告它指望接收的 M S S选项(M S S选项只能出如今S Y N报文段中)。经过MSS,应用数据被分割成TCP认为最适合发送的数据块,由TCP传递给IP的信息单位称为报文段或段(segment)。

TCP通讯时,若是发送序列中间某个数据包丢失,TCP会经过重传最后确认的包开始的后续包,这样原先已经正确传输的包也可能重复发送,急剧下降了TCP性能。为改善这种状况,发展出SACK(SelectiveAcknowledgment, 选择性确认)技术,使TCP只从新发送丢失的包,不用发送后续全部的包,并且提供相应机制使接收方能告诉发送方哪些数据丢失,哪些数据重发了,哪些数据已经提早收到等。

3.MSS:Maxitum Segment Size 最大分段大小

socket什么状况下可读?

a.The number of bytes of data in the socket receive buffer is greater than or 
     equal to the current size of the low-water mark forthe socket receive buffer.
     A read operation on the socket will not block and willreturn a value greater than 0
b.  The read half of the connections is closed (i.e., A TCPconnection that has received a FIN).
     A read operation on the socket will not block and willreturn 0 (i.e., EOF)
c. The socket is a listening socket and the number of completed connection isnonzero.
    An accept on the listening socket will normally not block,although we will describe a   
d. A socket error is pending. A read operation on the socket will not block andwill return
    an error (-1) with errno set to the specific error condition

db:
mysql,会考sql语言,服务器数据库大规模数据怎么设计,db各类性能指标

最后:补充一个最最重要,最最坑爹,最最有难度的一个题目:一个每秒百万级访问量的互联网服务器,每一个访问都有数据计算和I/O操做,若是让你设计,你怎么设计?

1)tcp三次握手的过程,accept发生在三次握手哪一个阶段?

三次握手以后
2)Tcp流, udp的数据报,之间有什么区别,为何TCP要叫作数据流?
流无边界,数据报有边界.TCP是先进先出的,而且可靠.

3)const的含义及实现机制,好比:const int i,是怎么作到i只可读的?
编译器相关,优化可能让其直接转为一常量代入. const用来讲明所定义的变量是只读的。

  这些在编译期间完成,编译器可能使用常数直接替换掉对此变量的引用。



4) valitale的含义。

volatile吧,告诉编译器此处必须得从地址去取,不得做相关优化。千万注意,这里与硬件cache可不是一回事。
5)OFFSETOF(s, m)的宏定义,s是结构类型,m是s的成员,求m在s中的偏移量。

#define OFFSETOF(s, m) ({s s1;(void*)(&s1)-(void*)(&s1->m);})/*gcc*/


6)100亿个数,求最大的1万个数,并说出算法的时间复杂度。
建一个堆,先把最开始的1万个数放进去。之后每进一个,都把最小的赶出来。

7)设计一个洗牌的算法,并说出算法的时间复杂度。

产生2*54+rand()%2次交换,全部序列已经很接近平均分布(只要rand()知足均分),而且比较快。不然会是复杂度比较高的算法。我统计过。
 socket在什么状况下可读?
拥塞控制是把总体当作一个处理对象的,流量控制是对单个的。感知的手段应该很多,好比在TCP协议里,TCP报文的重传自己就能够做为拥塞的依据。依据这样的原理,应该能够设计出不少手段。
--------------
1)三次握手以后
2)流无边界,数据报有边界.TCP是先进先出的,而且可靠.
3)编译器相关,优化可能让其直接转为一常量代入.
4)volatile吧,告诉编译器此处必须得从地址去取,不得做相关优化。千万注意,这里与硬件cache可不是一回事。
5)#define OFFSETOF(s, m) ({s s1;(void*)(&s1)-(void*)(&s1->m);})/*gcc*/
6)建一个堆,先把最开始的1万个数放进去。之后每进一个,都把最小的赶出来。
7)产生2*54+rand()%2次交换,全部序列已经很接近平均分布(只要rand()知足均分),而且比较快。不然会是复杂度比较高的算法。我统计过。
不知道想问什么。
9)拥塞控制是把总体当作一个处理对象的,流量控制是对单个的。感知的手段应该很多,好比在TCP协议里,TCP报文的重传自己就能够做为拥塞的依据。依据这样的原理,应该能够设计出不少手段。

9)流量控制与拥塞控制的区别,节点计算机怎样感知网络拥塞了?

 

 

前段时间专心面过腾讯,通过了N轮的技术面,结果仍是挂了,但没挂在技术面,比较欣慰,回来以后写一点总结,以供有梦想进入腾讯作后台服务器开发的同窗参考,本文章为胡成精心总结,胡成原创,copy和转载请通知。ps:()以内的文字由做者点评,非面试题文字。

linux和os:

netstat tcpdump ipcs ipcrm (若是这四个命令没据说过或者不能熟练使用,基本上能够回家,经过的几率较小^_^ ,这四个命令的熟练掌握程度基本上能体现面试者实际开发和调试程序的经验)

cpu 内存硬盘等等与系统性能调试相关的命令必须熟练掌握,设置修改权限 tcp网络状态查看各进程状态抓包相关等相关命令必须熟练掌握

awk sed需掌握

共享内存的使用实现原理(必考必问,而后共享内存段被映射进进程空间以后,存在于进程空间的什么位置?共享内存段最大限制是多少?)

c++进程内存空间分布(注意各部分的内存地址谁高谁低,注意栈从高道低分配,堆从低到高分配)

ELF是什么?其大小与程序中全局变量的是否初始化有什么关系(注意.bss段)

使用过哪些进程间通信机制,并详细说明(重点)

makefile编写,虽然比较基础,可是会被问到

gdb调试相关的经验,会被问到

如何定位内存泄露?

动态连接和静态连接的区别

32位系统一个进程最多多少堆内存

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

写一个c程序辨别系统是64位 or32位

写一个c程序辨别系统是大端or小端字节序

信号:列出常见的信号,信号怎么处理?

i++是否原子操做?并解释为何???????

说出你所知道的各种linux系统的各种同步机制(重点),什么是死锁?如何避免死锁(每一个技术面试官必问)

列举说明linux系统的各种异步机制

exit() _exit()的区别?

如何实现守护进程?

linux的内存管理机制是什么?

linux的任务调度机制是什么?

标准库函数和系统调用的区别?

补充一个坑爹坑爹坑爹坑爹的问题:系统如何将一个信号通知到进程?(这一题哥没有答出来)

c语言:

宏定义和展开(必须精通)

位操做(必须精通)

指针操做和计算(必须精通)

内存分配(必须精通)

sizeof必考

各种库函数必须很是熟练的实现

哪些库函数属于高危函数,为何?(strcpy等等)

c++:

一个String类的完整实现必须很快速写出来(注意:赋值构造,operator=是关键)

虚函数的做用和实现原理(必问必考,实现原理必须很熟)

sizeof一个类求大小(注意成员变量,函数,虚函数,继承等等对大小的影响)

指针和引用的区别(通常都会问到)

多重类构造和析构的顺序

stl各容器的实现原理(必考)

extern c 是干啥的,(必须将编译器的函数名修饰的机制解答的很透彻)

volatile是干啥用的,(必须将cpu的寄存器缓存机制回答的很透彻)

static const等等的用法,(能说出越多越好)

数据结构或者算法:

《离散数学》范围内的一切问题皆由可能被深刻问到(这个最坑爹,最重要,最体现功底,最能加分,特别是各种树结构的实现和应用)

各种排序:大根堆的实现,快排(如何避免最糟糕的状态?),bitmap的运用等等

hash,任何一个技术面试官必问(例如为何通常hashtable的桶数会取一个素数?如何有效避免hash结果值的碰撞)

网络编程:

tcp与udp的区别(必问)

udp调用connect有什么做用?

tcp链接中时序图,状态图,必须很是很是熟练

socket服务端的实现,select和epoll的区别(必问)

epoll哪些触发模式,有啥区别?(必须很是详尽的解释水平触发和边缘触发的区别,以及边缘触发在编程中要作哪些更多的确认)

大规模链接上来,并发模型怎么设计

 

tcp结束链接怎么握手,time_wait状态是什么,为何会有time_wait状态?哪一方会有time_wait状态,如何避免time_wait状态占用资源(必须回答的详细)

·       TIME_WAIT:表示收到了对方的FIN报文,并发送出了ACK报文。 TIME_WAIT状态下的TCP链接会等待2*MSL(Max Segment Lifetime,最大分段生存期,指一个TCP报文在Internet上的最长生存时间。每一个具体的TCP协议实现都必须选择一个肯定的MSL值,RFC 1122建议是2分钟,但BSD传统实现采用了30秒,Linux能够cat /proc/sys/net/ipv4/tcp_fin_timeout看到本机的这个值),而后便可回到CLOSED 可用状态了。若是FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,能够直接进入到TIME_WAIT状态,而无须通过FIN_WAIT_2状态。

  • 若是使用了nginx代理,那么系统TIME_WAIT的数量会变得比较多,这是因为nginx代理使用了短连接的方式和后端交互的缘由,使得nginx和后端的ESTABLISHED变得不多而TIME_WAIT不少。这不但发生在安装nginx的代理服务器上,并且也会使后端的app服务器上有大量的TIME_WAIT。查阅TIME_WAIT资料,发现这个状态不少也没什么大问题,但可能由于它占用了系统过多的端口,致使后续的请求没法获取端口而形成障碍。

    虽然TIME_WAIT会形成一些问题,可是要彻底枪毙掉它也是不正当的,虽然看起来这么作没什么错。具体可看这篇文档:

    http://hi.baidu.com/tim_bi/blog/item/35b005d784ca91d5a044df1d.html

    因此目前看来最好的办法是让每一个TIME_WAIT早点过时。

    在linux上能够这么配置:

    #让TIME_WAIT状态能够重用,这样即便TIME_WAIT占满了全部端口,也不会拒绝新的请求形成障碍
    echo "1" > /proc/sys/net/ipv4/tcp_tw_reuse
    #让TIME_WAIT尽快回收,我也不知是多久,观察大概是一秒钟
    echo "1" > /proc/sys/net/ipv4/tcp_tw_recycle

    不少文档都会建议两个参数都配置上,可是我发现只用修改tcp_tw_recycle就能够解决问题的了,TIME_WAIT重用TCP协议自己就是不建议打开的。

    不能重用端口可能会形成系统的某些服务没法启动,好比要重启一个系统监控的软件,它用了40000端口,而这个端口在软件重启过程当中恰好被使用了,就可能会重启失败的。linux默认考虑到了这个问题,有这么个设定:

    #查看系统本地可用端口极限值
    cat /proc/sys/net/ipv4/ip_local_port_range

    用这条命令会返回两个数字,默认是:32768 61000,说明这台机器本地能向外链接61000-32768=28232个链接,注意是本地向外链接,不是这台机器的全部链接,不会影响这台机器的80端口的对外链接数。但这个数字会影响到代理服务器(nginx)对app服务器的最大链接数,由于nginx对app是用的异步传输,因此这个环节的链接速度很快,因此堆积的链接就不多。假如nginx对app服务器之间的带宽出了问题或是app服务器有问题,那么可能使链接堆积起来,这时能够经过设定nginx的代理超时时间,来使链接尽快释放掉,通常来讲极少能用到28232个链接。

    由于有软件使用了40000端口监听,经常出错的话,能够经过设定ip_local_port_range的最小值来解决:

    echo "40001 61000" > /proc/sys/net/ipv4/ip_local_port_range

    可是这么作很显然把系统可用端口数减小了,这时能够把ip_local_port_range的最大值往上调,可是好习惯是使用不超过32768的端口来侦听服务,另外也没必要要去修改ip_local_port_range数值成1024 65535之类的,意义不大。

    由于使用了nginx代理,在windows下也会形成大量TIME_WAIT,固然windows也能够调整:

    在注册表(regedit)的HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters上添加一个DWORD类型的值TcpTimedWaitDelay,值就是秒数,便可。

    windows默认是重用TIME_WAIT,我如今还不知道怎么改为不重用的,本地端口也没查到是什么值,但这些都关系不大,均可以按系统默认运做。
  • ------------------------------------------------------------------------------------------------------------------------
  • TIME_WAIT状态
  • 根据TCP协议,主动发起关闭的一方,会进入TIME_WAIT状态,持续2*MSL(Max Segment Lifetime),缺省为240秒,在这个post中简洁的介绍了为何须要这个状态。
  • 值得一说的是,对于基于TCP的HTTP协议,关闭TCP链接的是Server端,这样,Server端会进入TIME_WAIT状态,可想而知,对于访问量大的Web Server,会存在大量的TIME_WAIT状态,假如server一秒钟接收1000个请求,那么就会积压240*1000=240,000个TIME_WAIT的记录,维护这些状态给Server带来负担。固然现代操做系统都会用快速的查找算法来管理这些TIME_WAIT,因此对于新的TCP链接请求,判断是否hit中一个TIME_WAIT不会太费时间,可是有这么多状态要维护老是很差。
  • HTTP协议1.1版规定default行为是Keep-Alive,也就是会重用TCP链接传输多个request/response,一个主要缘由就是发现了这个问题。还有一个方法减缓TIME_WAIT压力就是把系统的2*MSL时间减小,由于240秒的时间实在是忒长了点,对于Windows,修改注册表,在HKEY_LOCAL_MACHINE\ SYSTEM\CurrentControlSet\Services\ Tcpip\Parameters上添加一个DWORD类型的值TcpTimedWaitDelay,通常认为不要少于60,否则可能会有麻烦。
  • 对于大型的服务,一台server搞不定,须要一个LB(Load Balancer)把流量分配到若干后端服务器上,若是这个LB是以NAT方式工做的话,可能会带来问题。假如全部从LB到后端Server的IP包的source address都是同样的(LB的对内地址),那么LB到后端Server的TCP链接会受限制,由于频繁的TCP链接创建和关闭,会在server上留下TIME_WAIT状态,并且这些状态对应的remote address都是LB的,LB的source port撑死也就60000多个(2^16=65536,1~1023是保留端口,还有一些其余端口缺省也不会用),每一个LB上的端口一旦进入Server的TIME_WAIT黑名单,就有240秒不能再用来创建和Server的链接,这样LB和Server最多也就能支持300个左右的链接。若是没有LB,不会有这个问题,由于这样server看到的remote address是internet上广阔无垠的集合,对每一个address,60000多个port实在是够用了。
  • 一开始我以为用上LB会很大程度上限制TCP的链接数,可是实验代表没这回事,LB后面的一台Windows Server 2003每秒处理请求数照样达到了600个,难道TIME_WAIT状态没起做用?用Net Monitor和netstat观察后发现,Server和LB的XXXX端口之间的链接进入TIME_WAIT状态后,再来一个LB的XXXX端口的SYN包,Server照样接收处理了,而是想像的那样被drop掉了。翻书,从书堆里面找出覆满尘土的大学时代买的《UNIX Network Programming, Volume 1, Second Edition: Networking APIs: Sockets and XTI》,中间提到一句,对于BSD-derived实现,只要SYN的sequence number比上一次关闭时的最大sequence number还要大,那么TIME_WAIT状态同样接受这个SYN,难不成Windows也算BSD-derived?有了这点线索和关键字(BSD),找到这个post,在NT4.0的时候,仍是和BSD-derived不同的,不过Windows Server 2003已是NT5.2了,也许有点差异了。
  • 作个试验,用Socket API编一个Client端,每次都Bind到本地一个端口好比2345,重复的创建TCP链接往一个Server发送Keep-Alive=false的HTTP请求,Windows的实现让sequence number不断的增加,因此虽然Server对于Client的2345端口链接保持TIME_WAIT状态,可是老是可以接受新的请求,不会拒绝。那若是SYN的Sequence Number变小会怎么样呢?一样用Socket API,不过此次用Raw IP,发送一个小sequence number的SYN包过去,Net Monitor里面看到,这个SYN被Server接收后如泥牛如海,一点反应没有,被drop掉了。
  • 按照书上的说法,BSD-derived和Windows Server 2003的作法有安全隐患,不过至少这样至少不会出现TIME_WAIT阻止TCP请求的问题,固然,客户端要配合,保证不一样TCP链接的sequence number要上涨不要降低。
  • ----------------------------------------------------------------------------------------------------------------------------
  • Socket中的TIME_WAIT状态
  • 在高并发短链接的server端,当server处理完client的请求后马上closesocket此时会出现time_wait状态而后若是client再并发2000个链接,此时部分链接就链接不上了,用linger强制关闭能够解决此问题,可是linger会致使数据丢失,linger值为0时是强制关闭,不管并发多少多能正常链接上,若是非0会发生部分链接不上的状况!(可调用setsockopt设置套接字的linger延时标志,同时将延时时间设置为0。)
  • TCP/IP的RFC文档。TIME_WAIT是TCP链接断开时一定会出现的状态。
    是没法避免掉的,这是TCP协议实现的一部分。
    在WINDOWS下,能够修改注册表让这个时间变短一些

  • time_wait的时间为2msl,默认为4min.
    你能够经过改变这个变量:
    TcpTimedWaitDelay 
    把它缩短到30s
  • TCP要保证在全部可能的状况下使得全部的数据都可以被投递。当你关闭一个socket时,主动关闭一端的socket将进入TIME_WAIT状态,而被动关闭一方则转入CLOSED状态,这的确可以保证全部的数据都被传输。当一个socket关闭的时候,是经过两端互发信息的四次握手过程完成的,当一端调用close()时,就说明本端没有数据再要发送了。这好似看来在握手完成之后,socket就都应该处于关闭CLOSED状态了。但这有两个问题,首先,咱们没有任何机制保证最后的一个ACK可以正常传输,第二,网络上仍然有可能有残余的数据包(wandering duplicates),咱们也必须可以正常处理。
    经过正确的状态机,咱们知道双方的关闭过程以下

  • 假设最后一个ACK丢失了,服务器会重发它发送的最后一个FIN,因此客户端必须维持一个状态信息,以便可以重发ACK;若是不维持这种状态,客户端在接收到FIN后将会响应一个RST,服务器端接收到RST后会认为这是一个错误。若是TCP协议可以正常完成必要的操做而终止双方的数据流传输,就必须彻底正确的传输四次握手的四个节,不能有任何的丢失。这就是为何socket在关闭后,仍然处于 TIME_WAIT状态,由于他要等待以便重发ACK。
  • 若是目前链接的通讯双方都已经调用了close(),假定双方都到达CLOSED状态,而没有TIME_WAIT状态时,就会出现以下的状况。如今有一个新的链接被创建起来,使用的IP地址与端口与先前的彻底相同,后创建的链接又称做是原先链接的一个化身。还假定原先的链接中有数据报残存于网络之中,这样新的链接收到的数据报中有多是先前链接的数据报。为了防止这一点,TCP不容许从处于TIME_WAIT状态的socket创建一个链接。处于TIME_WAIT状态的socket在等待两倍的MSL时间之后(之因此是两倍的MSL,是因为MSL是一个数据报在网络中单向发出到认定丢失的时间,一个数据报有可能在发送图中或是其响应过程当中成为残余数据报,确认一个数据报及其响应的丢弃的须要两倍的MSL),将会转变为CLOSED状态。这就意味着,一个成功创建的链接,必然使得先前网络中残余的数据报都丢失了。
  • 因为TIME_WAIT状态所带来的相关问题,咱们能够经过设置SO_LINGER标志来避免socket进入TIME_WAIT状态,这能够经过发送RST而取代正常的TCP四次握手的终止方式。但这并非一个很好的主意,TIME_WAIT对于咱们来讲每每是有利的。
  • 客户端与服务器端创建TCP/IP链接后关闭SOCKET后,服务器端链接的端口
    状态为TIME_WAIT
  • 是否是全部执行主动关闭的socket都会进入TIME_WAIT状态呢?
    有没有什么状况使主动关闭的socket直接进入CLOSED状态呢?
  • 主动关闭的一方在发送最后一个 ack 后
    就会进入 TIME_WAIT 状态 停留2MSL(max segment lifetime)时间
    这个是TCP/IP必不可少的,也就是“解决”不了的。

    也就是TCP/IP设计者原本是这么设计的
    主要有两个缘由
    1。防止上一次链接中的包,迷路后从新出现,影响新链接
       (通过2MSL,上一次链接中全部的重复包都会消失)
    2。可靠的关闭TCP链接
       在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会从新发
       fin, 若是这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。因此
       主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。

    TIME_WAIT 并不会占用很大资源的,除非受到攻击。

    还有,若是一方 send 或 recv 超时,就会直接进入 CLOSED 状态
  • socket-faq中的这一段讲的也很好,摘录以下:
    2.7. Please explain the TIME_WAIT state.

 

tcp头多少字节?哪些字段?(必问)

什么是滑动窗口(必问)

connect会阻塞,怎么解决?(必考必问,提示:设置非阻塞,返回以后用select检测状态)

若是select返回可读,结果只读到0字节,什么状况?

keepalive 是什么东东?如何使用?

 

keepalive

在TCP中有一个Keep-alive的机制能够检测死链接,原理很简单,TCP会在空闲了必定时间后发送数据给对方:

1.若是主机可达,对方就会响应ACK应答,就认为是存活的。

2.若是可达,但应用程序退出,对方就发RST应答,发送TCP撤消链接。

3.若是可达,但应用程序崩溃,对方就发FIN消息。

4.若是对方主机不响应ack, rst,继续发送直到超时,就撤消链接。这个时间就是默认

的二个小时。

 

列举你所知道的tcp选项,并说明其做用。

 

socket什么状况下可读?

 

每次读操做返回前都要检查是否还有剩余数据没读完,若是是的话保持数据有效标志,不这样设计的话会出现明显的不一致,那就是数据在读缓冲但没有读有效标志。

固然也能够设计成让程序必须一次性取走全部数据,但这样设计的接口不友好,win32中也确实存在这种类型的API

db:
mysql,会考sql语言,服务器数据库大规模数据怎么设计,db各类性能指标

最后:补充一个最最重要,最最坑爹,最最有难度的一个题目:一个每秒百万级访问量的互联网服务器,每一个访问都有数据计算和I/O操做,若是让你设计,你怎么设计?

 

 

shell下输入“man 2read ” 你先看看。
ssize_t read(int fd, void *buf, size_t count);
意义:从文件描述符fd所指向的文件中读取count个字节的数据到buf所指向的缓存中。
文件描述符是由无符号整数表示的句柄,进程使用它来标识打开的文件。
文件描述符0表明标准文件。
fd是这么来的。
fd=(open或creat成功调用时的返回值)

 

 

于一个完整的程序,在内存中分布状况以下图:

 

原文连接:http://blog.csdn.net/ibmfahsion/article/details/11992403?utm_source=tuicool

相关文章
相关标签/搜索