请描述一下用多线程怎么实现生产者消费者模型 | ||||||||||||||||||||||||
知道nginx的惊群现象吗?怎么解决? 惊群效应概述在Linux系统中,惊群效应常见于使用accept系统调用和epoll等多路复用机制的场景。例如,当一个父进程监听一个端口,并fork出多个子进程,所有子进程都尝试通过accept或epoll_wait等待新连接的到来。当新连接请求到达时,所有子进程可能都会被唤醒,但只有一个能成功处理新连接,其他则重新休眠。 Nginx的解决方案Nginx通过以下策略解决惊群效应: 1. 主进程监听,工作进程处理Nginx采用master-worker模型,其中master进程负责监听端口和分发连接请求,而worker进程负责处理实际的连接请求。master进程监听socket,当有新的连接请求到达时,master进程通过一定的策略(如轮询)将连接请求分配给其中一个空闲的worker进程。这种单一监听者模式避免了多个worker进程同时监听同一个socket的情况,从而减少了惊群效应的发生。 2. 锁机制(accept_mutex)Nginx引入了一个互斥锁(accept_mutex)来控制对新连接的接受。当配置文件中启用了accept_mutex时,只有成功获取到锁的worker进程才能处理新连接请求。具体实现中,Nginx使用原子操作和共享内存来管理锁的状态,确保锁的安全性和高效性。 复制 3. 负载均衡Nginx通过负载均衡策略确保各个worker进程能够均匀分担工作负载。除了使用accept_mutex外,Nginx还通过监控每个worker进程的连接数和负载情况,动态调整新连接的分发策略。当一个worker进程的连接数达到其最大容量的7/8时,Nginx会停止向该进程分发新连接请求,直到其负载减轻。 复制 4. 利用内核特性随着Linux内核的发展,一些内核特性也被用于减少惊群效应。例如,Linux 2.6及之后的版本在accept系统调用中引入了互斥等待变量,避免了不必要的唤醒。此外,Linux 4.5及以后的版本在epoll中增加了EPOLLEXCLUSIVE标志,允许用户设置只有一个进程或线程被唤醒来处理事件。Nginx在较新版本中利用这些内核特性来进一步优化性能。 5. EPOLL和SO_REUSEPORTNginx使用epoll作为其主要的事件驱动机制。每个worker进程都有自己的epoll实例,用于监听和处理事件。在Nginx 1.9.1及以后的版本中,还引入了SO_REUSEPORT选项,允许多个进程监听同一个端口,内核会自动将连接请求分发给其中一个进程,进一步减少了惊群效应。 结论Nginx通过主进程监听、互斥锁、负载均衡、利用内核特性以及EPOLL和SO_REUSEPORT等多种策略有效解决了惊群效应,从而提高了服务性能和系统资源利用率。这些策略不仅减少了不必要的进程唤醒和上下文切换,还确保了各个worker进程能够公平地分担工作负载,为Nginx的高性能表现提供了有力支持。 | ||||||||||||||||||||||||
请说一下epoll的内核实现,都涉及哪些数据结构? epoll_create:创建内核事件表用来存放描述符和事件。它的数据结构为:struct eventpoll。其中包括了两个重要成员,一个是红黑树,也就是内核事件表。另一个重要成员是用于存放就绪事件的队列。 epoll_ctl:红黑树添加节点操作:ep_insert。红黑树移除节点操作:ep_remove。红黑树修改节点操作:ep_modify。每个节点都是一个描述符和事件的结构体。 epoll_wait:负责收集就绪事件。 注意:关于epoll_wait是如何收集就绪事件的,大致是这样一个思路:添加事件和描述符时,注册回调函数。当描述符上有事件就绪时,把描述符和事件添加到队列中 | ||||||||||||||||||||||||
select和epoll的区别? 第一部分:select和epoll的任务 要比较epoll相比较select高效在什么地方,就需要比较二者做相同事情的方法。 要完成对I/O流的复用需要完成如下几个事情: 1.用户态怎么将文件句柄传递到内核态? 2.内核态怎么判断I/O流可读可写? 3.内核怎么通知监控者有I/O流可读可写? 4.监控者如何找到可读可写的I/O流并传递给用户态应用程序? 5.继续循环时监控者怎样重复上述步骤? 搞清楚上述的步骤也就能解开epoll高效的原因了。 select的做法: 步骤1的解法:select创建3个文件描述符集,并将这些文件描述符拷贝到内核中,这里限制了文件句柄的最大的数量为1024(注意是全部传入---第一次拷贝); 步骤2的解法:内核针对读缓冲区和写缓冲区来判断是否可读可写,这个动作和select无关; 步骤3的解法:内核在检测到文件句柄可读/可写时就产生中断通知监控者select,select被内核触发之后,就返回可读可写的文件句柄的总数; 步骤4的解法:select会将之前传递给内核的文件句柄再次从内核传到用户态(第2次拷贝),select返回给用户态的只是可读可写的文件句柄总数,再使用FD_ISSET宏函数来检测哪些文件I/O可读可写(遍历); 步骤5的解法:select对于事件的监控是建立在内核的修改之上的,也就是说经过一次监控之后,内核会修改位,因此再次监控时需要再次从用户态向内核态进行拷贝(第N次拷贝) epoll的做法: 步骤1的解法:首先执行epoll_create在内核专属于epoll的高速cache区,并在该缓冲区建立红黑树和就绪链表,用户态传入的文件句柄将被放到红黑树中(第一次拷贝)。 步骤2的解法:内核针对读缓冲区和写缓冲区来判断是否可读可写,这个动作与epoll无关; 步骤3的解法:epoll_ctl执行add动作时除了将文件句柄放到红黑树上之外,还向内核注册了该文件句柄的回调函数,内核在检测到某句柄可读可写时则调用该回调函数,回调函数将文件句柄放到就绪链表。 步骤4的解法:epoll_wait只监控就绪链表就可以,如果就绪链表有文件句柄,则表示该文件句柄可读可写,并返回到用户态(少量的拷贝); 步骤5的解法:由于内核不修改文件句柄的位,因此只需要在第一次传入就可以重复监控,直到使用epoll_ctl删除,否则不需要重新传入,因此无多次拷贝。 简单说:epoll是继承了select/poll的I/O复用的思想,并在二者的基础上从监控IO流、查找I/O事件等角度来提高效率,具体地说就是内核句柄列表、红黑树、就绪list链表来实现的。 | ||||||||||||||||||||||||
fork()都会做哪些复制? | ||||||||||||||||||||||||
什么是写时拷贝?Fork以后,父进程打开的文件指针位置在子进程里面是否一样? 首先我们先来回忆一下fork()函数: 函数头文件:#include<unistd.h> 函数原型:pid_t fork(void);其中pid_t是一个宏定义:#define pid_t int 返回值: fork函数调用一次返回两次: 在父进程中返回生成子进程的id(因为父进程不止拥有一个子进程) 在子进程返回一个整数0(因为子进程只有一个父进程,可以通过调用getppid()得到父进程的进程ID) 当然,如果创建失败,则返回-1; 复制内容: 传统的fork函数之后会做这样的举动: 例: 现在有一个进程1,在它的虚拟地址空间有:.text .data .bss heap stack 相应,内核会为这几个部分分配各自的物理空间。 我们现在用fork给进程1创建子进程,名为进城2 --->现在子进程开始复制这几个部分,也就是虚拟地址空间: --->为子进程的这几个部分分配物理块: 可以看出:fork()之后会进行数据段,堆区,栈区的复制(.text共享)当然还有打开的文件描述符。 大家可以看到:传统的fork系统调用直接把所有的资源复制给新创建的进程,这种实现过于简单 并且效率低下,因为它拷贝的数据也许并不共享,如果这时子进程执行exec函数系统调用,那么 拷贝也就没有什么意义了。 所以呢:::::接下来就有了一个新的技术叫写时拷贝: fork()之后父进程的虚拟空间拷贝给子进程,在虚拟空间与物理页表建立映射的过程中使用了写时拷贝 使得子进程共享父进程的物理空间,当父子进程其中一个对该区域进行写入时,子进程复制一个新的 物理页表并建立映射,使得父子进程相互独立,同时节省了很多物理内存。子进程和父进程拥有相同 的相互独立的虚拟空间(不同的进程都拥有自己独立的虚拟地址空间), 但是却没有复制物理页表 写时拷贝: 在网上看到这样几句话分享给大家: fork()之后exec之前两个进程用的是相同的物理空间(当然我们上面已经说过了),子进程的代码段,数据段 堆栈都是指向父进程的物理空间(物理页面为只读模式),也就是说,两者的虚拟空间不同,但对应的物理 空间是一样的,如果不是exec,内核会为子进程的数据段,堆,栈分配物理空间,而代码段继续共享父进程 的物理空间,而如果是因为exec,由于两者的代码不同,子进程的代码段也会分配到独立的物理空间。 接下来我们再来看一下vfork函数,看看vfork函数和fork函数有什么区别呢?????? vfork函数的做法更加简单粗暴,内核连子进程的虚拟空间也不创建了,直接共享了父进程的虚拟空间,也就间接 的共享了物理空间,保证子进程先运行,在它调用exec或exit之后父进程才可能被调度。 接下来呢还有一个问题就是父进程打开的文件指针位置在子进程里面是否一样?????? 父进程的打开文件指针存放在PCB中,PCB被复制到子进程中后,子进程对应相应的文件描述符也能对文件 进行操作,该描述符指向同一个文件表项,文件表项引用计数加1; 打开文件的内核数据结构 两个独立进程各自打开同一个文件 注:此图中可以看出,对于同一个文件,打开该文件的每个进程都得到一个文件表项,但对一个给定的文件只有一个v节点表项。
父子进程的每一个相同的打开文件描述符共享一个文件表项 注:文件表项只有在所有引用它的fd(即文件描述符)全部关闭的情况下才会真正关闭,5如果子进程关闭父、子进程共享的文件描述符后父进程仍可以使用对应的文件表项。 | ||||||||||||||||||||||||
iocp是什么IOCP (Input/Output Completion Ports) 是 Windows 操作系统提供的一种高性能、可扩展的异步 I/O 模型。这种模型基于事件驱动,可以大幅提升 I/O 操作的效率和并发量,尤其适合处理大量连接和大量数据流的网络应用。IOCP 主要包含三个组件:I/O 端口、完成端口和工作者线程池。I/O 端口用来描述文件句柄或套接字,完成端口用来存储已完成的 I/O 请求以及通知工作者线程处理请求,工作者线程池则负责执行 I/O 操作和相关的业务逻辑。 iocp基本原理IOCP 的基本原理是通过事件驱动的方式来处理 I/O 请求,以避免传统的阻塞式 I/O 操作带来的性能问题。具体来说,IOCP 通过以下几个步骤完成异步 I/O 操作:
以上过程中,工作者线程负责从完成队列中取出已完成的 I/O 请求,并进行相应的处理。由于这些线程都是从线程池中获取的,因此可以有效地管理和控制线程的数量和使用情况,从而提高系统的效率和稳定性。 应用场景IOCP 的高性能、可扩展特性使其广泛应用于以下几个领域:
总之,IOCP 在需要高性能、高并发、低延迟的应用场景下具有很大的优势,并且可以有效地避免传统阻塞式 I/O 带来的性能问题。 | ||||||||||||||||||||||||
tcp/ip的四层协议,为什么要有传输层和网络层? TCP/IP(Transmission Control Protocol/Internet Protocol)是一组用于互联网通信的协议。它是互联网的基础架构,支持各种应用程序进行数据传输和网络连接。本文将详细介绍TCP/IP的工作原理、协议栈以及网络层的功能。 工作原理TCP/IP是一种面向连接的通信协议,它使用客户端/服务器模型进行通信。在TCP/IP中,数据被划分为小的数据包,并在网络中通过IP地址进行传输。TCP/IP协议栈中的每个协议层都有特定的任务,以确保数据的传输和接收。 协议栈TCP/IP协议栈是一组协议按照特定顺序工作以保证数据的传输。它由四个主要层组成,分别是:
网络层的功能网络层是TCP/IP协议栈中的核心层,它负责处理网络互联和数据传输。以下是网络层的一些主要功能:
| ||||||||||||||||||||||||
tcp/ip三次握手和四次挥手过程以及信令流程,画出来! | ||||||||||||||||||||||||
tcp三次握手哪一个阶段会抛出异常?为什么不能两次握手,说下原因? 三次握手的概念三次握手是TCP协议用于建立连接的一种机制。它涉及到客户端和服务器之间的三个步骤,确保双方都能够正常通信。
为什么是三次握手?为什么不是两次握手或四次握手呢?这涉及到建立连接的可靠性和防止网络中的不确定性。让我通过一个实际的案例来理解为什么三次握手是必要的。 案例分析:网络延迟引发的问题假设我只有两次握手,而不是三次。客户端发送SYN,服务器回应SYN + ACK,看起来连接已经建立。但在这之后,由于网络延迟或其他原因,客户端并没有收到服务器的确认,导致客户端以为连接已建立,而服务器并不知情。 这种情况下,客户端和服务器之间的连接状态将变得不一致,可能导致各种问题,如资源浪费、连接超时等。为了防止这种情况,引入第三次握手可以确保双方都能够确认连接已经建立,避免了不确定性带来的问题。 安全性考虑三次握手在一定程度上提高了连接的安全性。通过要求客户端和服务器都发送和确认连接请求,它减少了未经授权的连接建立的可能性。如果只有两次握手,可能会容易受到一些网络攻击,例如SYN洪泛攻击,因为服务器无法确认客户端是否真的要建立连接。 可靠性和状态同步三次握手的每一步都具有明确定义的状态。第一次握手表示客户端希望建立连接,第二次握手表示服务器接受连接,并准备好接收数据,第三次握手表示客户端也确认连接建立。这种状态同步确保了双方都了解连接的当前状态。 处理网络延迟三次握手的过程中,如果某一步的消息由于网络延迟未能及时到达,发送方会在一段时间后重新发送。这种机制有助于处理因为网络延迟引起的消息丢失,确保了连接的可靠性。 为什么不是两次握手?如果只有两次握手,存在一些潜在的问题。例如,在两次握手中,服务器接收到连接请求后立即回应,这时连接就建立了。但如果这个回应由于网络延迟而在一段时间后才到达,客户端就无法得知连接是否真的建立成功。这可能导致客户端错误地认为连接已经建立,而服务器并不知情。 为什么不是四次握手?四次握手是在连接关闭时使用的,与连接建立时的三次握手不同。在连接关闭时,需要双方确认彼此都已准备好断开连接。而在连接建立时,通过三次握手就能够确保连接的可靠性和安全性。 让我通过更详细的步骤来理解三次握手的建立连接过程。
详细步骤:关闭连接当连接建立后,双方通信完成后可能需要关闭连接。下面是关闭连接的详细步骤:
| ||||||||||||||||||||||||
什么是虚拟进程? | ||||||||||||||||||||||||
Linux下进程都有哪些通信方式?项目中使用全双工和半双工通信的区别? 进程间的7种通信方式如下: | ||||||||||||||||||||||||
进程和线程的区别,那你知道的都说一下! 1、根本区别进程和线程的根本区别是进程是操作系统(OS)资源分配的基本单位,而线程是处理器(CPU)任务调度和执行的基本单位。 2、资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。 3、包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线 4、内存分配:同一进程的线程共享本进分享截屏空间和资源,而进程之间的地址空间和资源是相互独立的。 5、影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。 6、执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行。 ✌️ 补充一、进程、线程、协程的概念进程:是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。 二、进程和线程的区别地址空间:线程共享本进程的地址空间,而进程之间是独立的地址空间。 三、协程和线程的区别协程避免了无意义的调度,由此可以提高性能,但程序员必须自己承担调度的责任。同时,协程也失去了标准线程使用多CPU的能力。 四、何时使用多进程,何时使用多线程?对资源的管理和保护要求高,不限制开销和效率时,使用多进程。要求效率高,频繁切换时,资源的保护管理要求不是很高时,使用多线程。 五、为什么会有线程?每个进程都有自己的地址空间,即进程空间,在网络或多用户换机下,一个服务器通常需要接收大量不确定数量用户的并发请求,为每一个请求都创建一个进程显然行不通(系统开销大响应用户请求效率低),因此操作系统中线程概念被引进。 | ||||||||||||||||||||||||
什么是同步/异步?你项目中写的半同步/半异步是什么意思? 同步和异步强调的是消息通信机制 (synchronous communication/ asynchronous communication)。 所谓同步,就是在发出一个"调用"时,在没有得到结果之前,该“调用”就不返回。 但是一旦调用返回,就得到返回值了。 换句话说,就是由“调用者”主动等待这个“调用”的结果。 而异步则是相反,"调用"在发出之后,这个调用就直接返回了,所以没有返回结果。 同步和阻塞的区别:
| ||||||||||||||||||||||||
epoll的ET/LT模式在实现上有什么区别?内核上是两种模式是怎么实现的? LT模式 在这种模式下,大家会认为读数据会简单一些,因为即使数据没有读完,那么下次调用epoll_wait()时,它还会通知你在上没读完的文件描述符上继续读,也就是人们常说的这种模式不用担心会丢失数据。 而写数据时,因为使用 LT 模式会一直触发 EPOLLOUT 事件,那么如果代码实现依赖于可写事件触发去发送数据,一定要在数据发送完之后移除检测可写事件,避免没有数据发送时无意义的触发。 ET模式 这种模式听起来清爽了很多,只有状态变化时才会通知,通知的次数少了自然也会引发一些问题,比如触发读事件后必须把数据收取干净,因为你不一定有下一次机会再收取数据了,即使不采用一次读取干净的方式,也要把这个激活状态记下来,后续接着处理,否则如果数据残留到下一次消息来到时就会造成延迟现象。 这种模式下写事件触发后,后续就不会再触发了,如果还需要下一次的写事件触发来驱动发送数据,就需要再次注册一次检测可写事件。 数据的读取和发送 而数据的写不太容易理解,因为数据的读是对端发来数据导致的,而数据的写其实是自己的逻辑层触发的,所以在通过网络发数据时通常都不会去注册监可写事件,一般都是调用 send 或者 write 函数直接发送,如果发送过程中, 函数返回 -1,并且错误码是 EWOULDBLOCK 表明发送失败,此时才会注册监听可写事件,并将剩余的服务存入自定义的发送缓冲区中,等可写事件触发后再接着将发送缓冲区中剩余的数据发送出去。 | ||||||||||||||||||||||||
vi的基本命令? | ||||||||||||||||||||||||
Linux上查看系统内存使用情况的命令? 1、free命令 2、 vmstat命令 3、 /proc/meminfo 命令 4、 top命令 5、 htop 命令 | ||||||||||||||||||||||||
Linux上查看系统版本的命令? 系统基本信息:uname 进程状态的命令?系统所启动服务的命令? | ||||||||||||||||||||||||
Linux上查看linuxCPU的命令? | ||||||||||||||||||||||||
进程池和线程池的具体实现写一下! | ||||||||||||||||||||||||
Linux 调试核心转储文件,程序断点是如何实现的(问我会不会汇编)? | ||||||||||||||||||||||||
fwrire和write的区别,sendfile的内部实现? 在介绍write()和fwrite()的区别和联系之前,先介绍什么是系统调用,系统调用执行期间所经历的每个步骤,以及为了提升系统性能而设置的缓冲机制。最后,给出write()和fwrite()的区别于联系。 系统调用系统调用时受控的内核入口,借助于这一机制,进程可以请求内核以自己的名义去执行某些动作。以应用程序编程接口(API)的形式,内核提供一系列服务供程序访问。 在深入系统调用的运作方式之前,务必关注以下几点:
从编程的角度来看,系统调用与C语言函数的调用很相似。但是,在执行系统调用时,幕后会经历诸多步骤,下面以一个具体的硬件平台——x86-32为例,按事件发生的顺序对这些步骤加以分析。
库函数C语言标准库函数由许多库函数组成,其用途多种多样,可以用来执行以下任务:打开文件、将时间转换为可读格式,以及进行字符串比较等等。许多库函数不会使用任何系统调用(如字符串相关操作),在用户态即可完成相关逻辑,但另外一些库函数,在建立在系统调用之上,是系统调用在用户态的封装。之所以设计库函数,是为了提供比直接调用库函数更为灵活方便的接口。当然,有时为了更进一步,希望能够比直接调用系统调用付出更小的开销。 标准C语言函数库的实现随UNIX的实现而异。GNU C语言函数库(glibc)是Linux上最常用的实现。 write()在介绍了系统调用和库函数之后,回到本文的主题:write()函数和fwrite()函数。简单来说,write()函数是系统调用,是文件通用I/O API,fwrite()是glibc库函数,是系统调用write()在用户态的封装。所谓通用I/O模型,是因为在Linux系统中,所有设备都抽象为文件,那么,open()、read()、write()和close()可以对所有类型的文件执行I/O操作。 所有执行I/O操作的系统调用都以文件描述符,一个非负整数(通常是小整数)来指向打开的文件。write()系统调用将数据写入一个已打开的文件中。
buffer参数为要写入文件中的数据的内存地址,count参数为要写入文件的字符数,fd参数为目的文件的描述符。如果调用成功,将返回实际写入文件的字节数,该返回值可能小于count值,这称为“部分写”。对磁盘文件来说,造成“部分写”的原因可能是磁盘已满,或者是因为进程资源对文件大小的限制。 内核缓冲 出于速度和效率考虑,系统I/O调用(即内核)和标准C语言库I/O函数,在操作磁盘文件时,会对数据进行缓冲。 write()系统调用在操作磁盘文件时不会直接发起磁盘访问,而是仅仅在用户空间缓冲区与内核缓冲区高速缓存(kernel buffer cache)之间复制数据。例如,如下调用将3个字节的数据从用户空间内存传递到内核空间的缓冲区中,write()随即返回:
在后续某个时间,内核会将其从缓冲区写入(刷新)到磁盘。(因此,可以说系统调用与磁盘操作并不同步。)如果在此期间,另一进程试图读取该文件的这几个字节,那么内核将自动从缓冲高速缓存中提供这些数据,而不是从文件中(读取过期的内容)。通过缓存,减少了内核必须执行磁盘操作传输数据的次数,提升了性能。 Linux内核对缓冲区高速缓存的大小没有固定上限。内核会分配尽可能多的缓冲区高速缓存页,而仅受限于两个因素:可用的物理内存总量,以及出于其他目的对物理内存的需求(例如,需要将正在运行进程的文本和数据页保留在物理内存中)。若可用内存不足,则内核会将一些修改过的缓冲区高速缓存页内容刷新到磁盘,并释放其供系统使用。
fwrite()正如上文所述,fwrite()是glibc库函数,是系统调用write()的封装。
buffer指向要写入的缓存数组中第一个对象,size为每个对象的大小,count表示写入对象的数量,stream指向输出流。 如果调用成功,函数返回写入的对象的数量;如果有错误发生,函数返回值可能小于count值。如果size或者count为0,函数返回0并且不会有任何操作。 库缓冲 当操作磁盘文件时,库函数将大块数据缓冲在用户空间以减少系统调用,如fprintf()、fscanf()、fgets()、fputc()、fgetc()、fputs()。因此,stdio库可以是编程者免于自行处理对数据的缓冲。 当然,stdio库提供函数允许设置缓冲模式。
参数stream标识将要修改哪个文件流的缓冲。打开流后,必须在调用任何其他stdio函数之前先调用setvbuf()。setvbuf()调用将影响后续在指定流上进行的所有stdio操作。 参数buf和size则针对参数stream要使用的缓冲区,有两种方式:
参数mode指定了缓冲类型,并具有下列值之一: _IONBF 不对IO进行缓冲。每个stdio库函数将立即调用write()或者read(),并且忽略buf和size参数,可以分别指定两个参数为NULL和0。stderr默认属于这一类型,从而保证错误能立即输出。 _IOLBF 采用行缓冲IO。指代终端设备的流默认属于这一类型。对于输出流,在输出一个换行符(除非缓冲区已经填满)前将缓冲数据,对于输入流,每次读取一行数据。 _IOFBF 采用全缓冲IO。单次读、写数据(通过read()或者write()系统调用)的大小与缓冲区相同,指代磁盘的流默认采用此模式。 setbuf()和setbuffer()和setvbuf()类似。 无论当前采用何种缓冲模式,在任何时候,都可以使用fflush()库函数强制将stdio输出流中的数据(即通过write())刷新到内核缓冲区中。此函数会刷新指定stream的输出缓冲区。 总结write()和fwrite()到底有什么区别和联系呢?
| ||||||||||||||||||||||||
libevent、Rector模式、服务器网络模型 | ||||||||||||||||||||||||
服务器瓶颈的定位?怎么测试定位?如何设计解决瓶颈问题? | ||||||||||||||||||||||||
IO瓶颈的解决方案都有哪些? 压测过程中TPS上不去?请求响应时间过长? 硬件资源不足:服务器的CPU、内存、磁盘等硬件资源不足,无法支撑高并发的请求处理。可以通过增加硬件资源或者优化服务器配置来提升TPS。 网络带宽限制:网络带宽不足,导致服务器无法及时响应请求。可以通过增加网络带宽或者优化网络传输方式来提升TPS。 数据库性能问题:数据库的读写性能不足,无法满足高并发的请求。可以通过优化数据库结构、索引、查询语句等方式来提升数据库性能。 代码逻辑问题:应用程序中存在性能瓶颈或者死循环等问题,导致请求处理速度变慢。可以通过代码优化来提升性能。也可能存在死锁或其他同步问题导致线程或进程阻塞,从而影响TPS。 压测/肉机配置问题:压测工具的配置(如压测肉机数量、压测脚本、场景设计等)可能影响TPS的表现。如压力机可能无法虚拟出预期的用户数,或者压测脚本和场景设计不合理,都会导致TPS无法提升。 负载均衡:负载均衡器配置不当可能导致请求分配不均,从而影响性能。检查负载均衡策略,优化配置。 依赖服务问题:应用程序依赖的第三方服务性能不稳定或者响应速度较慢,导致整体性能受限。可以通过替换或者优化第三方服务来提升性能。 服务配置问题:系统的配置参数不合理,如线程池大小、连接池配置等,也会导致响应时间增。 1、应用cpu瓶颈如何定位分析 定位: 分析: 如果配置好重新压测,cpu使用率依旧还是高,可以看看io是否很高(io操作:读取和写入磁盘上的文件或数据),如果io高,那么有可能出现堵塞; 如果火焰图代码部分,有耗时比较大并且有慢查询的,可以进行sql语句优化; 2、应用内存瓶颈如何定位分析 定位: 分析: 3、应用磁盘瓶颈如何定位分析 定位: 分析: 读写的数据太大导致; sql语句写的不规范导致,或者数据库连接数比较少导致,也有可能是mysql配置参数问题,如:数据库缓存配置小,导致不够用,才一直进行磁盘的读写操作。 如果代码都没问题,连接数都没问题,myslq配置也正确,但io还是很高的,可以增加资源来解决后继续进行压测。 4、数据库内存瓶颈如何定位分析 定位: 分析数据库缓冲池:查看数据库的缓冲池配置,了解缓冲池的大小、命中率等;如果缓冲池命中率低或者缓冲池过小,可能会导致频繁的磁盘读取,从而影响性能。 检查数据库索引:检查数据库表的索引情况,确定是否存在缺失或者无效的索引;缺失或者无效的索引可能导致数据库执行大量的全表扫描,增加了磁盘IO的负担。 分析数据库查询语句:分析服务对数据库的查询语句,了解查询的复杂度和效率;复杂且低效的查询语句可能会导致数据库内存的过度消耗,从而影响性能。 查看数据库日志:查看数据库的错误日志和慢查询日志,了解是否存在异常和性能较差的查询;通过分析日志可以找出潜在的性能问题和优化的方向。 检查数据库配置:检查数据库的配置参数,例如内存限制、并发连接数等;如果配置参数不合理,可能会导致内存不足或者并发连接过多,从而影响性能。 分析: 根据分析结果,优化数据库查询语句和索引,例如调整查询条件、添加合适的索引等;同时,也可以考虑调整数据库的配置参数,如增加内存限制、调整并发连接数等。 5、网络io瓶颈如何定位分析 定位: 检查网络设备,如路由器、交换机、防火墙等,查看其配置和性能。确保设备的带宽和性能能够满足需求,如果设备配置不合适或者存在故障,可能会引起网络I/O瓶颈。 分析: 6、带宽瓶颈如何定位分析 定位: 分析: 7、redis内存瓶颈如何定位分析 定位: 查看Redis的日志文件,观察是否有内存相关的警告或错误信息,如内存使用过高的问题。 分析: 以上方法无法解决内存瓶颈问题,可以考虑扩容Redis集群,增加节点的数量和内存容量,以提高整个系统的内存处理能力。 | ||||||||||||||||||||||||
Linux怎么调试内存? | ||||||||||||||||||||||||
描述符对于服务器有什么用,感觉是TCP底层(不太会) 文件描述符(file descriptor)就是内核为了高效管理这些已经被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符来实现。 同时还规定系统刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。 文件又可分为:普通文件、目录文件、链接文件和设备文件。在操作这些所谓的文件的时候,我们每操作一次就找一次名字,这会耗费大量的时间和效率。所以Linux中规定每一个文件对应一个索引,这样要操作文件的时候,我们直接找到索引就可以对其进行操作了。 文件描述符(file descriptor)就是内核为了高效管理这些已经被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符来实现。同时还规定系统刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。这意味着如果此时去打开一个新的文件,它的文件描述符会是3,再打开一个文件文件描述符就是4...... Linux内核对所有打开的文件有一个文件描述符表格,里面存储了每个文件描述符作为索引与一个打开文件相对应的关系,简单理解就是下图这样一个数组,文件描述符(索引)就是文件描述符表这个数组的下标,数组的内容就是指向一个个打开的文件的指针。 上面只是简单理解,实际上关于文件描述符,Linux内核维护了3个数据结构: 进程级的文件描述符表 系统级的打开文件描述符表: 当前文件偏移量(调用read()和write()时更新,或使用lseek()直接修改) 文件类型(例如:常规文件、套接字或FIFO)和访问权限 在进程 A 中,文件描述符 1 和 20 都指向了同一个打开文件表项,标号为 23(指向了打开文件表中下标为 23 的数组元素),这可能是通过调用 dup()、dup2()、fcntl() 或者对同一个文件多次调用了 open() 函数形成的。 | ||||||||||||||||||||||||
epoll的触发模式 | ||||||||||||||||||||||||
页缓存他说的pagecache?Linux内核物理页面的页面缓存机制 | ||||||||||||||||||||||||
Linux进程虚拟地址空间的分布 1、参数说明程序段(Text):程序代码在内存中的映射,存放函数体的二进制代码。 初始化过的数据(Data):在程序运行初已经对变量进行初始化的数据。 未初始化过的数据(BSS):在程序运行初未对变量进行初始化的数据。 栈(Stack):存储局部、临时变量,函数调用时,存储函数的返回指针,用于控制函数的调用和返回。在程序块开始时自动分配内存,结束时自动释放内存,其操作方式类似于数据结构中的栈。 堆 (Heap):存储动态内存分配,需要程序员手工分配,手工释放.注意它与数据结构中的堆是两回事,分配方式类似于链表。 2、注意事项
| ||||||||||||||||||||||||
epoll触发模式(二面又问了,问了2遍) | ||||||||||||||||||||||||
Linux进程核心转储文件的调试coredump | ||||||||||||||||||||||||
进程通信 | ||||||||||||||||||||||||
Libevent的实现机制 | ||||||||||||||||||||||||
Linu查看进程堆栈命令 | ||||||||||||||||||||||||
LRU的实现策略 | ||||||||||||||||||||||||
malloc底层的实现是什么? | ||||||||||||||||||||||||
半同步/半异步模式和work-master模式是什么? | ||||||||||||||||||||||||
进程间通信共享内存的底层原理是什么? | ||||||||||||||||||||||||
说说gdb常用的调试命令都有哪些? | ||||||||||||||||||||||||
Linux的内存分布(4G空间)? | ||||||||||||||||||||||||
tcp三次握手,四次挥手,为什么是三次?为什么是四次?time_wait出现在什么时候,它的作用是什么?画出tcp报头?tcp的滑动窗口满,返回什么? | ||||||||||||||||||||||||
进程间通信有几种方式?你都在什么情况用到? 进程间通信的几种方式包括:
| ||||||||||||||||||||||||
页面置换算法有哪些 页面置换算法是操作系统中用于管理内存的一种技术。当系统的物理内存不足时,页面置换算法决定哪些内存页面将被移出,以便为新的页面腾出空间。这些算法的目标是最小化页面置换的频率,从而提高系统的性能。 常见的页面置换算法 最优页面置换算法(OPT) 最优页面置换算法是一种理想化的算法,它选择将来最长时间不会被访问的页面进行置换。虽然在实际中无法实现,因为无法预知未来的页面访问情况,但OPT算法提供了一个性能评估的标准1。 先进先出页面置换算法(FIFO) 先进先出算法是一种简单的页面置换策略,它淘汰最早进入内存的页面。FIFO算法容易实现,但可能会导致Belady异常,即系统给进程分配的物理内存增加时,缺页率反而上升2。 最近最少使用页面置换算法(LRU) 最近最少使用算法基于“最近未被使用的页面在将来也不太可能被使用”的假设,选择最近最少被访问的页面进行置换。LRU算法的性能接近最优算法,但实现起来较为复杂3。 第二次机会页面置换算法(SC) 第二次机会算法是对FIFO算法的改进,它给每个页面一个机会证明自己最近是否被访问过。如果页面最近被访问过,则给予第二次机会,否则进行置换2。 时钟页面置换算法(CLOCK) 时钟算法是第二次机会算法的变种,它通过维护一个循环列表来实现,效率更高。时钟算法检查指针指向的页面,如果该页面最近被访问过,则指针前移,否则置换该页面2。 最不常用页面置换算法(NFU) 最不常用算法通过为每个页面维护一个计数器来跟踪页面的访问频率,每次置换时选择计数器值最小的页面2。 老化算法 老化算法是NFU算法的改进版,它通过对计数器进行右移和加权来模拟页面的老化过程,从而更准确地选择置换页面2。 工作集页面置换算法 工作集算法基于一个进程当前正在使用的页面集合,即工作集。它试图保持进程的工作集在内存中,以减少缺页中断2。 工作集时钟页面置换算法(WSClock) 工作集时钟算法结合了工作集和时钟算法的特点,通过检查页面的工作集状态和访问位来决定页面置换,这种算法在实际中得到了广泛应用2。 总结 页面置换算法是操作系统内存管理的重要组成部分,它们通过不同的策略来优化内存的使用效率。虽然没有一种算法能够适用于所有情况,但理解这些算法的原理和适用场景对于设计和优化操作系统至关重要。 | ||||||||||||||||||||||||
负载均衡常用算法 常见的负载均衡算法包括:
| ||||||||||||||||||||||||
心跳包机制 | ||||||||||||||||||||||||
send的返回值是什么?你刨析过什么源码? 不管是send还是recv方法,都是数据的缓冲区和发送的缓冲区的拷贝操作过程,真正发送数据的是协议功能,注意三种返回值的可能性,>0表示成功,返回实际发送或接受的字节数,=0表示超时,对方主动关闭了连接过程,<0出错,此种情况可能出现过重情况,如上所示。其中errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN这三种是特殊情况,实际使用中表示继续正常接受数据即可。 | ||||||||||||||||||||||||
线程同步的机制(四种锁,信号量,屏障,条件变量) 1.互斥锁(mutex):互斥锁是最常见的一种锁,用来保护共享资源的互斥访问。一次只有一个线程可以获得互斥锁。如果其他线程试图获得已经被锁定的互斥锁,他们将被阻塞,直到锁被释放 线程同步的方式1、互斥锁 当有多个线程需要访问同一个资源时,如果不做处理,有时候就会出现问题,比如有两个线程需要使用打印机,进程A正在使用,而进程B也要使用打印机,此时打印出来的东西就是错乱的。互斥锁就是控制对共享资源的使用。互斥锁只有两种状态:加锁、解锁。互斥锁的特点:原子性:把互斥量锁定位一个原子操作,这就保证了如果同一时间只会有一个线程锁定共享资源唯一性:如果一个线程锁定了某个互斥量,那么只有该线程可以使用这个被锁定的互斥量非繁忙等待:如果一个线程锁定了某互斥量,另一个线程又来访问该互斥量,则第二个线程会被挂起,当第一个线程解锁该互斥量后唤醒第二个线程对该互斥量进行访问。2、条件变量: 与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来锁定一个线程,直到某个特殊的条件发生为止。通常条件变量和互斥锁同时发生。条件变量可以是我们睡眠等待某种情况的发生。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待“条件变量的条件成立”而挂起另一个线程使条件成立 条件的检测是在互斥锁的保护下进行的。线程在改变条件变量的状态之前必须先锁定互斥量。如果一个条件为假,则线程自动阻塞,并释放等待状态改变的互斥锁。如果另外一个线程改变了条件,它发信号给相关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。条件变量的操作流程如下:初始化条件变量等待条件成立激活条件变量清除条件变量3、信号量 使用信号量首先我们要清楚临界资源和临界区的概念,临界资源就是同一时刻只允许一个线程(或进程)访问的资源,临界区就是访问临界资源的代码段。 信号量是一种特殊的变量,用来控制对临界资源的使用,在多个进程或线程都要访问临界资源的时候,就需要控制多个进行或线程对临界资源的使用。信号量机制通过p、v操作实现。p操作:原子减1,申请资源,当信号量为0时,p操作阻塞;v操作:原子加1,释放资源。信号量的使用及实例:信号量4、读写锁 读写锁和互斥锁类似,不过读写锁允许更改的并行性。互斥锁要么是加锁状态,要么是不加锁状态。而读写锁可以有三种状态:读模式下的加锁、写模式下的加锁、不加锁 状态。一次只有一个线程可以占有写模式下的读写锁,但是可以有多个线程占有读模式下的读写锁。读写锁的特点:如果有线程读数据,则允许其他线程读数据,但不允许写如果有线程写数据,则不允许其他线程进行读和写5、自旋锁 自旋锁和互斥锁的功能一样,但是互斥锁在线程阻塞时会让出cpu,而自旋锁则不会让出cpu,一直等待,直到得到锁。 | ||||||||||||||||||||||||
自旋锁的存在的问题以及自旋锁的底层实现 互斥锁(Mutex Lock): 依赖于操作系统提供的原语或者系统调用,如信号量、互斥量等。 当一个线程尝试获取锁时,它会使用原语或系统调用尝试获取锁。 使用操作系统提供的原语或者系统调用来实现。 依赖于硬件提供的原子操作,通常使用CAS指令。 当一个线程尝试获取锁时,它会循环检查锁的状态。 使用硬件提供的原子操作来实现,通常使用CAS指令。 | ||||||||||||||||||||||||
读写锁的特点,底层实现 读写锁允许多个线程同时读取共享资源,但在写入共享资源时只允许一个线程进行 。 它把锁分为两部分:读锁和写锁,其中读锁允许多个线程同时获得,因为读操作本身是线程安全的,而写锁则是互斥锁,不允许多个线程同时获得写锁,并且写操作和读操作也是互斥的。 | ||||||||||||||||||||||||
一堆数据,需要线程同步,如何实现,比较方法的优劣 | ||||||||||||||||||||||||
自己对虚拟内存的理解,把你知道的都说出来! | ||||||||||||||||||||||||
tcp和udp的区别,要实现一个简单的聊天程序,选那个? | ||||||||||||||||||||||||
epoll的两种模式的特点 | ||||||||||||||||||||||||
进程和线程的区别(一直问还有没有补充的二) | ||||||||||||||||||||||||
select和epoll的区别?(epoll内核源码看过,从内核实现角度回答,所以回答的不错) | ||||||||||||||||||||||||
Linux相关CPU,内存,网络方面相关指令 | ||||||||||||||||||||||||
父子进程fork时,打开的文件的偏移量是否是相同的? | ||||||||||||||||||||||||
详细说明Linux虚拟地址空间 | ||||||||||||||||||||||||
time_wait的危害,三次握手,四次断开 | ||||||||||||||||||||||||
epoll的ET模式时,如果数据只读了一半,也就是缓冲区的数据 | ||||||||||||||||||||||||
Linux内核解决惊群问题 | ||||||||||||||||||||||||
管道为什么是半双工的? | ||||||||||||||||||||||||
Linux下文件的组织形式? | ||||||||||||||||||||||||
Linux下有哪些锁机制?信号量的原理和进程间的通信? | ||||||||||||||||||||||||
三次握手和四次挥手的状态转换,问的很细,timewait,clostwait的特点 | ||||||||||||||||||||||||
tcp/udp协议的区别? | ||||||||||||||||||||||||
线程和进程的区别 | ||||||||||||||||||||||||
tcp/ip协议的拥塞控制是怎样的 |
linux操作系统常用面试题目
于 2019-05-05 05:26:55 首次发布