一、 进程与线程的区别?
- 进程是资源分配的单位,线程是系统调度的单位。
- 同一个进程可以拥有多个线程,一个线程只能属于一个进程。
- 每个进程拥有独立的地址空间,而同一进程下的所有线程共享该进程的资源。
- 创建进程的开销大,包括创建虚拟地址空间等需要大量的系统资源;创建线程的开销小,基本上只有一个内核对象和一个堆栈。
- 进程间切换和通信开销大,而线程是轻量级的进程,切换和通信开销小;
- 进程结束后,该进程下的所有线程将被销毁,而一个线程的结束不会影响到同一进程下的其它线程。
- 进程占用内存多,CPU 利用率低;线程占用内存少,CPU 利用率高。
二、线程共享资源与非共享资源
三、多线程锁的种类?
- 互斥量(mutex,当锁被其他线程占用时,其他线程是睡眠状态)
- 读写锁
- 递归锁(一般用于递归函数,拥有该锁的线程可以多次获取该锁,而且不会造成死锁)
- 自旋锁(不断获取该锁,当锁被其他线程占用时,其他线程并不是睡眠状态,而是不停的消耗CPU,获取锁)
四、进程间通信方式
- 管道
- 信号
- 信号量
- 共享内存
- 消息队列
- 套接字
五、如何设置线程数?多线程程序架构,线程数量应该如何设置?
最佳线程数目 = ( (线程等待时间 + 线程 CPU 时间) / 线程 CPU 时间 ) * CPU 数目
应尽量和CPU核数相等或者为CPU核数+1的个数。
六、网络编程设计模式,reactor/proactor/半同步半异步模式?
reactor 模式
:同步阻塞 I/O 模式,注册对应读写事件处理器,等待事件发生从而调用事件处理器处理事件。proactor 模式
:异步 I/O 模式。两种模式的主要区别就是真正的读取和写入操作是由谁来完成的。Reactor 中需要应用程序自己读取或者写入数据,Proactor 模式中,应用程序不需要进行实际读写过程。
Reactor
:
主线程往 epoll 内核上注册 socket 读事件,主线程调用 epoll_wait 等待 socket 上有数据可读,当 socket 上有数据可读的时候,主线程把 socket 可读事件放进请求队列,睡眠在请求队列上的某个工作线程被唤醒,处理客户请求,然后往 epoll 内核上注册 socket 写请求事件。主线程调用 epoll_wait 等待写请求事件,当有事件可写的时候,主线程把 socket 可写事件放入请求队列,,睡眠在请求队列上的某个工作线程被唤醒,处理客户请求。
Proactor
:
主线程调用 aio_read 函数向内核注册 socket 上的读完成事件,并告诉内核用户缓冲区的位置,以及读完成后如何通知应用程序,主线程继续处理其他逻辑,当 socket 上的数据被读入到用户缓冲区后,通过信号告知应用程序数据已经可用。应用程序预先定义号的信号处理函数选择一个工作线程来处理请求。工作线程处理完客户请求之后调用 aio_write 函数向内核注册 socket 写完成事件,并告诉内核写缓冲区的位置,以及写完成时如何通知应用程序。主线程处理其他逻辑。当用户缓冲区的数据被写入 socket 之后内核向应用程序发送一个信号,以通知应用程序数据已经发送完毕。应用程序预先定义的数据处理函数就会完成工作。
半同步半异步
:
上层的任务(如:数据库查询,文件传输)使用同步 I/O 模型,简化了编写并行程序的难度。
底层的任务(如网络控制器的中断处理)使用异步 I/O 模型,提供了执行效率。
【举个栗子】
家里有两个佣人, 一个叫reactor, 另一个叫proactor。
1.每天早午晚的饭点,reactor都会提醒你该去做饭了。
2.你只要告诉proactor,家里的饭桌在哪里,proactor就会在会在每天的饭点做好饭,端到你指定的饭桌上,然后通知你,可以吃饭了。
七、connect可能会长时间阻塞,怎么解决?
- 设置一个定时器。
- 采用非阻塞模式:设置非阻塞,返回之后用 select 检查状态。
八、如果 select 返回可读,结果只读到 0 字节,什么情况?
某个套接字集合中没有准备好,可能可能会 select 内存用 FD_CLR 清为 0。
九、keepalive 是什么东西?如何使用?
keepalive
:是在 TCP 中一个检测死连接的机制。
- 如果主机可达,对方就会响应 ACK 应答,就认为是存活的。
- 如果可达,但应用程序退出,对方就发 RST 应答,默认是存活的。
- 如果可达,但应用程序崩溃,对方就发送 FIN 消息。
- 如果对方主机不响应 ACK,RST,继续发送直到超时,就撤销连接。默认两个小时。
十、socket 什么情况下可读?什么情况下可写?
可读
- socket 接收缓冲区已经接收的数据的字节数大于等于 socket 接收缓冲区低潮限度的当前值;对这样的 socket 的读操作不会阻塞,并返回一个大于 0 的值(准备好读入的数据的字节数)。
- 连接的读一半关闭(即:接收到对方发过来的 FIN 的 TCP 连接),并且返回0。
- socket 是一个用于监听的 socket,并且已经完成的连接数为非 0。这样的 socket 处于可读状态。
- 有一个 socket 有异常错误条件待处理。对这样的 socket 的读操作将不会阻塞,并且返回一个错误(-1)。
可写
- socket 发送缓冲区中的可用空间字节数大于等于发送缓冲区的低潮限度的当前值。这意味着,我们如果将这样的 socket 设置为非阻塞模式,写操作将不会阻塞,并且返回一个正值。
- 连接的写这一半关闭,对于这样的 socket 的写操作将产生信号 SIGPIPE。
- 有一个 socket 有异常错误条件待处理。对这样的 socket 的写操作将不会阻塞,并且返回一个错误(-1)。
十一、UDP 调用 connect 有什么作用?
- UDP 是支持一对一、一对多、多对一和多对多的通信,所以每次调用 sendto() / recvrom() 时都必须指定目标 IP 和端口号。通过调用 connect() 建立一个端到端的连接,就可以和 TCP 一样使用 send() / recv() 传递数据,而不需要每次都要指定一个 IP 和 端口号。但是它和 TCP 不同的是没有三次握手过程。
- 可以通过在已建立连接的 UDP 套接字上,调用 connect() 实现指定新的 IP 地址和端口号以及断开连接。
十二、socket编程,如果client断电了,服务器如何快速知道?
- 使用定时器(适合有数据流动的情况);
- 使用 socket 选项的 SO_KEEPALIVE(适合没有数据流动 的情况);
(1)
应用层自己实现心跳包
。由应用程序自己发送心跳包来检测连接是否正常。服务器端在一个定时事件中定时向客户端发送一个短小的数据包,然后启动一个线程,在该线程中不断检测客户端的 ACK 应答包,如果在定时事件内收到 ACK 包,那么该连接仍然时可用的。但是,如果定时器已经超时,仍然没有收到客户端的 ACK 应答包,即可以认为客户端已经断开。同样道理,如果客户端在一定时间内没有收到服务器的心跳包,则也会认为改TCP连接不可用了。
(2)使用 TCP 的 keepalive 机制
。利用TCP/IP协议层的内置的KeepAlive功能来实现心跳功能则简单得多。不论是服务器端还是客户端,只要一端开启KeepAlive功能后,就会自动的在规定时间内向对端发送心跳包, 而另一端在收到心跳包后就会自动回复,以告诉对端主机我仍然在线。因为开启KeepAlive功能需要消耗额外的宽带和流量,所以TCP协议层默认是不开启KeepAlive功能的。
Linux 操作系统
一、熟练netstat tcpdump ipcs ipcrm
- netstat:检查网络状态
- tcpdump:截获数据包
- ipcs:检查共享内存
- ipcrm:解除共享内存
二、共享内存段被映射进进程空间之后,存在于进程空间的什么位置?共享内存段最大限制是多少?
将一块内存映射到两个或者多个进程地址空间。通过指针访问该共享内存区。一般通过 mmap 将文件映射到进程地址共享区。存在于
进程数据段
,最大限制是 0x2000000 Byte。
三、进程内存空间分布
四、LF是什么?其大小与程序中全局变量的是否初始化有什么关系(注意.bss段)?
ELF
是一种对象文件的格式,用于定义不同类型的对象文件中都放了什么东西,以及都以什么样的格式去放这些东西。可以减少重新编程重新编译的代码。ELF 有三种类型
- 可重定位的对象文件:由汇编编译器生成的 .o 文件
- 可执行的对象文件:可执行应用程序
- 可被共享的对象文件:动态库文件,也即 .so 文件。
五、静态链接和动态链接的区别?
动态链接是之建立一个引用的接口,而真正的代码和数据库存放在另外的可执行模块中,在可执行文件运行时再装入;而静态链接是把所有的代码和数据都复制到本模块中,运行时就不再需要库了。
六、写一个c程序辨别系统是64位 or 32位
void* number = 0;
printf("%d\n",sizeof(&number)); // 4: 32位 ; 8:64位
gcc:
int main()
{
#ifdef __X86_64__
printf("__X86_64__");
#elif __i386__
printf("__i386__");
#endif
}
七、判断系统是大端还是小端
使用 union:默认使用小端
typedef union {
int a;
char c;
}ut;
int main()
{
ut u;
u.a = 1;
if(u.a == u.c)
cout << "little";
else
cout << "big";
return 0;
}
八、信号:列出常见的信号,信号怎么产生,怎么处理?
- 常见的信号:
(2) -- SIGINT (终止/中断)
(3) -- SIGQUIT (退出)
(7) -- SIGBUS (总线错误)
(8) -- SIGFPE (除0操作)
(9) -- SIGKILL
(10) -- SIGUSR1
(11) -- SIGSECG (段错误非法访问内存)
(12) -- SIGUSR2
(14) -- SIGALRM
(17) -- SIGCHLD
(20) -- SIGTSTP (暂停/停止)
- 信号的产生
- 终端按键产生
- 硬件异常产生(段错误、除0)
- kill 函数
- raise/abort 函数
- 软件产生(alarm 函数)
- 信号的处理
- 执行默认的动作
- 忽略
- 捕捉(信号处理函数)
九、i++ 是否原子操作?并解释为什么?
不是原子操作。i++ 主要看三个步骤:首先把数据从内存放到寄存器中,在寄存器中进行自增处理,放回到内存中。
十、说出你所知道的各类linux系统的各类同步机制(重点),什么是死锁?如何避免死锁(每个技术面试官必问)
同步机制
:
- 原子操作:不会被任何事务打断,通常用于资源计数,引用计数。
- 信号量,绝大部分作为互斥锁使用。
- 读写信号量,可以允许多个读,一个写。一旦有人在写,就大家都不能读。但是如果没人在写,就可以允许很多一起读。
- 自旋锁:自旋锁与互斥锁的区别在于不会导致睡眠
- 内核锁
- 顺序锁:读者可以在写的时候读,写着也可以在读的时候写,但是写与写之间还是互斥的。利用了一个序号,写的时候要对序号加 1,这样读的人就可以知道读的期间有没有人在写。
死锁
:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,如果没有外力的作用,它们都将无法向前推进,此时称系统出现了死锁。
如何避免死锁
- 资源的互斥是客观的。
- 申请资源得不到满足时释放已经拥有的资源
- 只有在全部资源都可以得到的情况下才一次性分配
- 资源有序分配。给所有资源编号,进程对资源的请求必须是严格递增的序列。
十一、如何创建守护进程
- 创建子进程,父进程退出
- 在子进程中创建新会话:使子进程完全独立出来,脱离控制
- 改变当前目录位根目录
- 重设文件权限掩码
- 关闭文件描述符(dup2 – 0/1/2 重定向到 /dev/null)
- 开始执行守护进程核心工作
- 守护进程退出处理
当用户需要外部停止守护进程运行时,往往会使用 kill 命令停止该守护进程。所以,守护进程中需要编码来实现 kill 发出的 signal 信号处理,达到进程的正常退出。
十二、linux的任务调度机制是什么?
Linux 分 实时进程和普通进程,实时进程应该优于普通进程而运行。
实时进程:
(1)FIFO(先来先服务)
(2)RR(时间片轮转调度)
每个进程有两个优先级(动态优先级和实时优先级),实时优先级是用来衡量实时进程是否值得运行的。非实时进程有两种优先级(动态优先级和静态优先级)。实时进程又增加了第三种优先级,实时优先级。优先级越高,得到的 CPU 时间的机会也就越大。
十三、标准库函数和系统调用的区别?
系统调用
:是操作系统提供给用户调用的一组接口,是方便使用操作系统的接口。库函数
:把一些常用的函数编写完放在一个 lib 文件中,供别人用。是为了人们编程的方便
十四、系统如何将一个信号通知到进程?
内核给进程发送消息,是在进程所在的进程表项的信号域设置对应的位。进程处理信号的时机就是从内核态即将返回用户态的时候。执行用户自定义信号处理函数的方法很巧妙。把该函数的地址放在用户栈栈顶,进程从内核返回到用户态的时候,先弹出信号处理函数地址,于是就去执行信号处理函数了,然后再弹出,才是返回进入内核时的状态。
十五、fork()一子进程程后父进程的全局变量能不能使用?
fork 后子进程将会拥有父进程的几乎一切资源,父子进程各自都有自己的全局变量。不能通用,不同与线程。对于线程,各个线程共享全局变量。
十六、Linux 内核的主要功能
- 进程管理:进程是系统资源分配的最小单元。内核负责创建和销毁进程,而且由调度程序采取合适的调度策略,实现进程间的合理且实时的处理器资源的共享。内核还负责实现不同进程间、进程和其它部件之间的通信。
- 内存管理:内核为每个进程建立一个虚拟地址空间。内存管理部分代码可分为硬件无关部分和硬件有关部分。硬件无关部分实现进程和内存之间的地址映射等功能。硬件有关部分实现不同体系结构上的内存管理相关功能并为内存管理提供与硬件无关的虚拟接口。
- 文件管理:在 Linux 系统中任何一个概念都可以看做一个文件。内核在非结构化的硬件上建立了一个结构化的虚拟文件系统,隐藏了各种硬件的具体细节。
- 设备管理:任何一种设备控制操作都由设备特定的驱动代码来进行。内核中必须提供系统中可能要操作的每一种外设的驱动。
- 网络管理:内核支持各种网络标准协议和网络设备。
十七、Linux 内核态与用户态的区别?两种状态之间切换的时机?
- 内核态运行操作系统程序,操作系统在内核态运行;用户态运行用户程序,而且应用程序只能运行在用户态。
- 指令划分:在内核态运行的指令为特权指令,特权指令只能由操作系统使用,对内存空间的访问范围基本不受限制。在用户态运行的指令为非特权指令,一般应用程序所使用的都是非特权指令,只能完成一般性的操作和任务,不能对系统中的硬件个软件直接访问,其对内存的访问范围也局限于用户空间。
- 特权级别:Linux 特权分为4 级:R0,R1,R2 和 R3。内核态为 R0,用户态为 R3。
区别
:
- 内核态和用户态是操作系统的两种运行级别,当程序运行在 3 级特权上时,就可以称之为运行在用户态。这是最低特权级,大部分用户直接面对的程序都是运行在用户态;当程序运行在 0 级特权上时,就可以称之为运行在内核态。
- 运行在用户态的程序不能直接访问操作系统内核数据结构和程序。当我们在系统中执行一个程序时,大部分运行在用户态下的,在其需要操作系统帮助完成某些它没有权利和能力完成的工作时就会切换到内核态(比如操作硬件)。
- 这两种状态的主要差别是:处于用户态执行时,进程所能访问的内存空间和对象受到限制,其所处于占有的处理器是可被抢占的;处于内核态执行时,则能访问所有的内存空间和对象,且所占有的处理器是不允许被抢占的。
切换时机
- 用户态->内核态:系统调用/异常/外围设备的中断
- 内核态->用户态:设置程序状态字
网络编程
- 请画出socket通信连接过程
- TCP头大小,包含字段?
TCP头部,20字节。
UDP 头部:8字节
- 使用 UDP 和 TCP 进程进行网络传输,为什么TCP 能保证包的发送顺序,而 UDP 无法保证?
因为 TCP 发送的数据包是按序号发送的,有确认和重传机制。而 UDP 是不可靠的发送机制,发送对应端口的数据包不是按顺序发送的。
- epoll哪些触发模式,有啥区别?
水平触发
(LT):当描述符上有数据可读的时候,在提醒用户之后,如果用户没有将该描述符上的数据处理完,在下一次 epoll_wait 返回的时候,它还会再提醒用户该描述符可读,直到用户将该描述符上的数据读完。这样的工作模式可靠但是效率低边沿触发
(ET):当描述符上有数据就绪时,在提醒用户后,如果用户没有将该描述符上的数据读完,那么,在下次 epoll_wait 返回的时候,它将不再提醒用户。除非该描述符上有新的数据到达。这样的工作模式高效但不可靠。所以在使用边沿触发的时候,需要用户一次性将数据读取完。
也就是说在 LT 模式下一定要确认收发的数据包的 buffer 是不是足够大。如果发送数据包的大小大于 buffer 的时候就可能会出现数据丢失的情况。
- TCP 和 UDP 的区别?为什么 TCP 要叫做数据流?
- TCP 是面向连接、可靠的、基于字节流的 传输层协议;UDP 是无连接的、不可靠的、面向报文的传输层协议。
- TCP 是面向连接的通信,需要三次握手等连接过程,会有延时、实时性较差,同时过程复杂,也使其易于攻击;UDP 没有建立连接的过程,因而实时性较强,也比较安全。
- 在传输相同数据大小的时候,TCP 首部开销 20 字节,UDP 首部开销 8 字节。TCP 首部比 UDP 复杂,故实际包含的用户数据较少。
- TCP 在 IP 协议的基础上添加了序号机制、确认机制、超时重传机制等,保证了传输的可靠性,不会出现丢包、重复、乱序,而 UDP 有丢包。所以 TCP 的开销大,UDP的开销小。
- TCP 连接只能是点到点的,而 UDP 支持 一对一、一对多、多对一和多对多。
- 流量控制和拥塞控制的实现机制
流量控制
:如果发送方把数据发送得很快,接收方就有可能来不及接收,这就会造成数据的丢失。TCP 的流量控制是利用滑动窗口机制实现的,接受方在返回的数据中会包含自己的接收窗口的大小,以控制发送方的数据发送。拥塞控制
:拥塞控制就是防止过多的数据注入到网络中,这样可以使网络中的路由器或者链路不致过载。两者区别
:流量控制是为了预防阻塞。流量控制是点对点通信量的控制,而拥塞控制是全局性的,涉及到所有的主机和降低网络性能的因素。拥塞控制的两种方法
:慢开始和拥塞避免;快重传和快恢复。
- 滑动窗口的实现机制
滑动窗口协议是 TCP 流量控制的一种方法。该机制允许发送方在停止并等待接收确认报文段前可以连续发送多个分组。由于发送方不必发一个分组就停下来等待确认,因此该协议 可以加速数据的传输。只有在接收窗口向前滑动时(与此同时也发送了确认),发送窗口才有可能向前滑动。收发两端的窗口按照以上规律不断地向前滑动,因此这种协议又称为滑动窗口协议。
- epoll 和 select 的区别?
select
:采用轮询方式检测就绪事件,select 返回之后还要遍历所有的 fd 才能知道哪些文件描述符就绪。用户通过 3 个参数分别传入感兴趣的可读、可写及异常等事件。内核通过对这些参数的在线修改来反馈其中的就绪事件。这使得用户每次调用 select 都要重置这三个参数。而且 select 最大支持的文件描述符为 1024。epoll
:采用回调方式检测就绪事件,epoll 返回的只有就绪的文件描述符。内核通过一个事件表直接管理用户感兴趣的所有事件。因此每次调用的时 epoll_wait 时,无须重复传入用户感兴趣的事件。epoll_wait 系统调用中的参数 events 仅用来反馈就绪的事件。epoll 最大支持的文件描述符可达到 64435。
- 网络中,如果客户端突然掉线或者重启,服务器端怎么样才能立刻知道?
若客户端掉线或者重新启动,服务器端会收到复位信号,每一种 TCP/IP 的实现都不一样,控制机制也不一样。
- TTL是什么?有什么用处,通常那些工具会用到它?ping? traceroute? ifconfig? netstat?
TTL 是 Time To Live(生存时间值),每经过一个路由就会被减去1,如果它编变成 0,包就会被丢掉。它的主要目的是防止包在有回路的网络上死转,浪费网络资源。ping 和 traceroute 会用到。
- Linux 的五种 I/O 模式?
- 阻塞 I/O
- 非阻塞 I/O
- I/O 复用
- 信号驱动 I/O
- 异步 I/O
- 请说出http协议的优缺点。
-
优点
- 支持 客户/服务器 模型
- 简单快速:客户向服务器请求服务的时候,只需要传送请求方法和路径,通信速度很快。
- 灵活:HTTP 允许传输任意类型的数据对象。
- 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答之后,即断开连接。采用这种方式可以节省传输时间。
- 无状态:HTTP 协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,导致每次连接传送的数据量增长。
缺点
不够安全,可以使用 https 完成使用。
- 大规模连接上来,并发模型怎么设计?
Epoll + 线程池(epoll 可以采用 libevent 处理)
- 流量控制与拥塞控制的区别,节点计算机怎样感知网络拥塞了?
感知的手段应该不少,比如在 TCP 协议里面,TCP 报文的重传本身就可以作为拥塞的依据。
数据结构
- 描述一种 hash table 的实现方法。
- 除留余数法:h(k) = k mod p,p 选取的是比较大的素数比较好。
- 平方取中法:取其平方值的中间几位
- 数字分析法:关键字中选出分布较均匀的若干位
- 分段叠加法
处理冲突的方法:
- 开放地址法(再散列法)
- 再哈希法(构造多个哈希函数)
- 链地址法
- 建立公共溢出区