BAT后台开发面试题库

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

第一次握手:客户端发送syn包(syn=j)到服务器。

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个ASK包(ask=k)。

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1)。

三次握手完成后,客户端和服务器就建立了tcp连接。这时可以调用accept函数获得此连接。client 的 connect  引起3次握手,server 在socket, bind, listen后,阻塞在accept,三次握手完成后,accept返回一个fd,

因此accept发生在三次握手之后。

 

2.Tcp流,udp的数据报,之间有什么区别,为什么TCP要叫做数据流?

TCP本身是面向连接的协议,S和C之间要使用TCP,必须先建立连接,数据就在该连接上流动,可以是双向的,没有边界。所以叫数据流,占系统资源多UDP不是面向连接的,不存在建立连接,释放连接,每个数据包都是独立的包,有边界,一般不会合并。TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证

 

3.const的含义及实现机制,比如:const int i,是怎么做到i只可读的?

const指示对象为常量,只读。实现机制:这些在编译期间完成,对于内置类型,如int,编译器可能使用常数直接替换掉对此变量的引用。而对于结构体不一定。

 

4. valitale的含义。

valitale直接从内存读取数据,防止cache造成数据不一致。volatile吧,告诉编译器此处必须得从地址去取,不得作相关优化。千万注意,这里与硬件cache可不是一回事。

 

5.OFFSETOF(s,m)的宏定义,s是结构类型,m是s的成员,求m在s中的偏移量。

#define OFFSETOF(s, m) size_t(&((s*)0)->m)

 

6.100亿个数,求最大的1万个数,并说出算法的时间复杂度。

如果把100亿个数全部读入内存,需要100 0000 0000 * 4B 大约40G的内存,这显然是不现实的。我们可以在内存中维护一个大小为10000的最小堆,每次从文件读一个数,与最小堆的堆顶元素比较,若比堆顶元素大,则替换掉堆顶元素,然后调整堆。最后剩下的堆内元素即为最大的1万个数,算法复杂度为O(NlogN)实现:从文件读数据有讲究,如果每次只读一个数,效率太低,可以维护一个输入缓冲区,一次读取一大块数据到内存,用完了又从文件接着读,这样效率高很多,缓冲区的大小也有讲究,一般要设为4KB的整数倍,因为磁盘的块大小一般就是4KB。

 

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

第一种: for i:=1 to n do swap(a[i], a[random(1,n)]);  // 凑合,但不是真正随机

第二种: for i:=1 to n do swap(a[i], a[random(i,n)]);   // 真正的随机算法

其中,random(a,b)函数用于返回一个从a到b(包括a和b)的随机整数。

至于怎么证明上两个算法,没想好。

算法复杂度是O(n。。。),要研究下random的实现。

 

8.socket在什么情况下可读?

1. 接收缓冲区有数据,一定可读

2. 对方正常关闭socket,也是可读

3. 对于侦听socket,有新连接到达也可读

4.socket有错误发生,且pending~~~

 

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

拥塞控制是把整体看成一个处理对象的,流量控制是对单个的。感知的手段应该不少,比如在TCP协议里,TCP报文的重传本身就可以作为拥塞的依据。依据这样的原理,应该可以设计出很多手段。

 

--------------

1)三次握手之后

2)流无边界,数据报有边界.TCP是先进先出的,并且可靠.

3)编译器相关,优化可能让其直接转为一常量代入.

4)volatile吧,告诉编译器此处必须得从地址去取,不得作相关优化。千万注意,这里与硬件cache可不是一回事。

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

6)建一个堆,先把最开始的1万个数放进去。以后每进一个,都把最小的赶出来。

7)产生2*54+rand()%2次交换,所有序列已经很接近平均分布(只要rand()满足均分),并且比较快。否则会是复杂度比较高的算法。

 

 

linux和os:

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

Linux对共享内存的实现,在2.6采用了内存映射技术。对于内存共享,主要集中在三个内核函数,他们是do_shmat,sys_shmat和sys_shmdt。其中,sys_shmat调用了do_shmat最终实现了共享内存的attach。sys_shmdt实现了共享内存的detach和destroy。下面我主要对这三个函数的源码进行分析。在分析之前,首先介绍共享内存实现原理。

 

原理:

    我们知道,LINUX的进程拥有自己独立的地址空间,这点和vxworks是不同的。系统中多个进程共享同一内存段,可以通过系统提供的共享内存机制进行。当进程向系统提出创建或附着共享内存的申请的时候,内核会为每一个新的共享内存段提供一个shmid_kernel的数据结构来维护共享段和文件系统之间的关系(也可以理解成共享内存段和文件系统之间建立关系的桥梁)。下面就是这个数据结构:

 

这个数据结构定义在shm.h头文件中

struct shmid_kernel /* private to thekernel */

   struct kern_ipc_perm    shm_perm;// 访问权限的信息

   struct file *       shm_file; // 指向虚拟文件系统的指针

 

   unsigned long       shm_nattch; //有多少个进程attach上了这个共享内存段

   unsigned long       shm_segsz; // 共享内存段大小

   // 以下是一些访问时间的相关信息

   time_t          shm_atim;

   time_t          shm_dtim;

   time_t          shm_ctim;

   pid_t           shm_cprid;

   pid_t           shm_lprid;

   struct user_struct  *mlock_user;

};

 

该数据结构中最重要的部分就是shm_file这个字段。它指向了共享内存对应的文件。在该结构中有一个字段,f_mapping,它指向了该内存段使用的页面(物理内存)。同时,结构中,也包含一个字段,f_path,用于指向文件系统中的文件(dentry->inode),这样就建立了物理内存和文件系统的桥梁。当进程需要创建或者attach共享内存的时候,在用户态,会先向虚拟内存系统申请各自的vma_struct,并将其插入到各自任务的红黑树中,该结构中有一个成员vm_file,它指向的就是struct file(shm_file)。这样虚拟内存、共享内存(文件系统)和物理内存就建立了连接。

 

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

C程序一般分为

 

1.程序段(text):  程序段为程序代码在内存中的映射.一个程序可以在内存中多有个副本.

2.文字常量区: 常量字符串就是放在这里的。

3.初始化过的数据(data):  在程序运行之初已经对变量进行初始化的.全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。

4.未初始化过的数据(bss): 在程序运行初未对变量进行初始化的数据。

6.堆栈(stack): 存储局部,临时变量,在程序块开始时自动分配内存,结束时自动释放内存.存储函数的返回指针. 当函数被调用时,它们被压入栈;当函数返回时,它们就要被弹出堆栈。

7.堆(heap): 存储动态内存分配,需要程序员手工分配(c中malloc函数,c++中new函数),手工释放(free 和 delete 函数).

3和4称为静态存储区,6和7称为动态存储区。

 

windows进程中的内存结构

#include <stdio.h>

#include <string.h>

int a = 0; //全局初始化区

char *p1; //全局未初始化区

main()

{

int b; //栈

char s[] = "abc"; //栈

char *p2; //栈

char *p3 = "123456";// 123456\0在常量区,p3在栈上。

static int c =0;// 全局(静态)初始化区

static int uc,uc1,uc2;// 全局(静态)未初始化区

p1 = (char *)malloc(10);

p2 = (char *)malloc(20);

//分配得来得10和20字节的区域就在堆区。

strcpy(p1, "123456");// 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。

 

printf("堆p1\t\t\t0x%08x\n",p1);

printf("堆p2\t\t\t0x%08x\n",p2);

printf("栈&p3\t\t\t0x%08x\n",&p3);

printf("栈&p2\t\t\t0x%08x\n",&p2);

printf("栈s\t\t\t0x%08x\n",s);

printf("栈&s[1]\t\t0x%08x\n",&s[1]);

printf("栈&b\t\t\t0x%08x\n",&b);

printf("main地址\t\t0x%08x\n",main);

printf("文本常量区\t\t0x%08x\n",p3);

printf("全局初始化区\t\t0x%08x\n",&a);

printf("(静态)初始化区\t0x%08x\n",&c);

printf("全局未初始化区\t\t0x%08x\n",&p1);

printf("(静态)未初始化区\t0x%08x\n",&uc);

printf("(静态)未初始化区\t0x%08x\n",&uc1);

printf("(静态)未初始化区\t0x%08x\n",&uc2);

}

 

你可以看到这些变量在内存是连续分布的,但是本地变量和全局变量分配的内存地址差了十万八千里,而全局变量和静态变量分配的内存是连续的。这是因为本地变量和全局/静态变量是分配在不同类型的内存区域中的结果。对于一个进程的内存空间而言,可以在逻辑上分成3个部份:代码区,静态数据区和动态数据区。动态数据区一般就是“堆栈”。“栈(stack)”和“堆(heap)”是两种不同的动态数据区,栈是一种线性结构,堆是一种链式结构。进程的每个线程都有私有的“栈”,所以每个线程虽然代码一样,但本地变量的数据都是互不干扰。一个堆栈可以通过“基地址”和“栈顶”地址来描述。全局变量和静态变量分配在静态数据区,本地变量分配在动态数据区,即堆栈中。程序通过堆栈的基地址和偏移量来访问本地变量。

 

├———————┤低端内存区域

│……│

├———————┤

│动态数据区│

├———————┤

│……│

├———————┤

│代码区│

├———————┤

│静态数据区│

├———————┤

│……│

├———————┤高端内存区域

堆栈是一个先进后出的数据结构,栈顶地址总是小于等于栈的基地址。

 

linux c程序存储空间布局

进程在内存中的影像.

我们假设现在有一个程序, 它的函数调用顺序如下.

main(...) ->; func_1(...) ->; func_2(...)->; func_3(...)

即: 主函数main调用函数func_1; 函数func_1调用函数func_2; 函数func_2调用函数func_3

当程序被操作系统调入内存运行, 其相对应的进程在内存中的影像如下图所示.

 (内存高址)

       +--------------------------------------+

        |省略了一些我们不需要关心的区

       +--------------------------------------+

       |  env strings (环境变量字串) |

       +--------------------------------------+

       |  argv strings (命令行字串) |

       +--------------------------------------+

       |  env pointers (环境变量指针) |   

SHELL的环境变量和命令行参数保存区

       +--------------------------------------+ 

       |  argv pointers (命令行参数指针)|

       +--------------------------------------+

       |  argc (命令行参数个数)   |

       +--------------------------------------+

       |            main 函数的栈帧 |

       +--------------------------------------+ 

       |            func_1 函数的栈帧 |

       +--------------------------------------+   

       |            func_2 函数的栈帧 |

       +--------------------------------------+

       |            func_3 函数的栈帧| Stack(栈)

       +......................................+

       +......................................+ 

       |            Heap (堆)  |

       +--------------------------------------+

       |        Uninitialised (BSS) data |  非初始化数据(BSS)区

       +--------------------------------------+

       |        Initialised data    |  初始化数据区

       +--------------------------------------+

       |        Text             |  文本区

       +--------------------------------------+

   (内存低址)

   这里需要说明的是:

i)随着函数调用层数的增加, 函数栈帧是一块块地向内存低地址方向延伸的.              随着进程中函数调用层数的减少, 即各函数调用的返回, 栈帧会一块块地被遗弃而向内存的高址方向回缩. 各函数的栈帧大小随着函数的性质的不同而不等, 由函数的局部变量的数目决定.

ii)  进程对内存的动态申请是发生在Heap(堆)里的. 也就是说, 随着系统动态分配给进程的内存数量的增加, Heap(堆)有可能向高址或低址延伸, 依赖于不同CPU的实现. 但一般来说是向内存的高地址方向增长的. 堆和栈相向而生,堆和栈之间有个临界点,称为stkbrk.

iii) 在BSS数据或者Stack(栈)的增长耗尽了系统分配给进程的自由内存的情况下, 进程将会被阻塞, 重新被操作系统用更大的内存模块来调度运行 (虽然和exploit没有关系, 但是知道一下还是有好处的)

iv)  函数的栈帧里包含了函数的参数(至于被调用函数的参数是放在调用函数的栈帧还是被调用函数栈帧, 则依赖于不同系统的实现),它的局部变量以及恢复调用该函数的函数的栈帧(也就是前一个栈帧)所需要的数据, 其中包含了调用函数的下一条执行指令的地址.

v)   非初始化数据(BSS)区用于存放程序的静态变量,这部分内存都是被初始化为零的. 初始化数据区用于存放可执行文件里的初始化数据. 这两个区统称为数据区.

vi)  Text(文本区)是个只读区, 任何尝试对该区的写操作会导致段违法出错. 文本区是被多个运行该可执行文件的进程所共享的. 文本区存放了程序的代码.

 

2) 函数的栈帧.

函数调用时所建立的栈帧包含了下面的信息:

i) 函数的返回地址. 返回地址是存放在调用函数的栈帧还是被调用函数的栈帧里, 取决于不同系统的实现.

 

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

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

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

多线程和多进程的区别(重点面试官最最关心的一个问题,必须从cpu调度,上下文切换,

数据共享,多核cup利用率,资源占用,等等各方面回答,然后有一个问题必须会被问到:

 

哪些东西是一个线程私有的?答案中必须包含寄存器,否则悲催)

 

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

 

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

 

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

 

i++是否原子操作?并解释为什么???????

 

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

 

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

 

如何实现守护进程?

 

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状态占用资源(必须回答的详细)

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

什么是滑动窗口(必问)

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

如果select返回可读,结果只读到0字节,什么情况?

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

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

socket什么情况下可读?

db:

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

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

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值