阻塞IO和非阻塞IO
- 阻塞的文件描述符为阻塞IO
- 非阻塞的文件描述符为非阻塞IO
同步IO和异步IO
- 同步IO向应用程序通知的是IO就绪事件。要求用户代码自行执行读写操作,将数据从内核缓冲区读入用户缓冲区。
- 异步IO向应用程序通知的是IO完成事件 。由内核来执行IO读写操作。在linux环境下,aio.h头文件定义的函数提供了对异步IO的支持。
事件处理模式
- reactor 同步IO模型通常用于实现reactor模式。要求主线程只负责监听文件描述符是否有事件发生,有的话就立即将该事件通知工作线程。
- proactor 异步IO模型通常用于实现proactor模式。也可以用同步IO模拟出proactor模式。proactor将所有IO操作都交给主线程和内核来处理,工作线程仅仅负责业务逻辑。
Reactor模式的工作流程
- 主线程往epoll内核事件表中注册socket上的就绪事件。
- 主线程调用epoll_wait等待socket上有数据可读。
- 当socket上有数据可读时,epoll_wait通知主线程。主线程将socket可读事件放入请求队列。
- 睡眠在请求队列上的某个工作线程被唤醒,它从socket读取数据,并处理客户请求,然后往epoll内核事件表中注册该socket上的写就绪事件。
- 主线程调用epoll_wait等待socket可写。
- 当socket可写时,epoll_wait通知主线程。主线程将socket可写事件放入请求队列。
- 睡眠在请求队列上的某个工作线程被唤醒,它往socket上写入服务器处理客户请求的结果。
并发模式
半同步半异步模式:同步线程用于处理客户逻辑,异步线程用于处理IO事件。异步线程监听到客户请求后,就将其封装成请求对象并插入到请求队列中,请求队列将通知某个工作在同步模式下的工作线程来读取并处理该请求对象。半同步半反应堆模式采用的事件处理模式是reactor模式:它要求工作线程自己从socket上读取客户请求和往socket写入服务器应答。半同步半反应堆也可以模拟proactor模式,即由主线程来完成数据的读写。在这种情况下,主线程会将应用程序数据,任务类型等信息封装为一个任务对象,然后将其插入请求队列。工作线程从请求对象取得任务对象以后,可直接处理无需执行读写操作。
问题:主线程和工作线程共享请求队列需要加锁。工作线程较少时可能产生请求任务堆积。
领导者追随者模式
在IO模型中,同步和异步区分的是内核向应用程序通知的是何种事件,是就绪事件还是完成事件,以及该由谁来完成IO读写,是应用程序还是内核。
在并发模式中,同步指的是程序完全按照代码序列的顺序执行。异步指的是程序的执行需要由系统事件来驱动。常见的系统事件包括中断和信号。
虚拟地址空间
**虚拟地址是操作系统管理内存的一种方式。**方便不同进程使用的虚拟地址彼此隔离。方便物理内存中不相邻的内存在虚拟地址上视为连续的来使用。虚拟地址和物理地址的映射是通过MMU页表进行的。虚拟内存对实际内存有保护作用。
什么是进程
进程是系统进行资源分配的基本单位,是程序加载到内存后的执行过程。进程一般由数据段,代码段和进程控制块三部分组成。系统通过进程控制块感知进程的存在并对进程进行控制。由于进程之间空间相互独立,多进程比多线程更安全,一个进程基本上不会影响另外一个进程。
进程三种状态
- 创建:创建PCB
- 就绪
- 运行
- 阻塞
- 终止: 归还PCB
什么是线程
线程是CPU调度的基本单位。一个进程可以包含多个线程,线程自己基本不拥有系统资源,但是它可以和同属于一个进程的其他线程共享进程所拥有的全部资源。多线程之间对内存共享,线程间通信可以直接基于共享内存来实现,比多进程之间通信更轻量。多线程之间切换不需要切换虚拟内存空间、文件描述符等,所以线程的上下文切换也比多进程轻量。
进程fork以后,遵循读时共享写时复制的机制。
父子进程长期共享:文件描述符和mmap建立的映射区。
子进程的进程ID,定时器,未决信号集和父进程不同。
多进程和多线程的应用场景
一般不同任务间需要大量的通信,使用多线程的场景比多进程多。IO密集型。
但是多进程有更高的容错性,一个进程的崩溃不会导致整个系统的崩溃,在任务安全性较高的情况下,采用多进程。CPU密集型。
进程线程的本质区别
- 进程更安全,一个进程完全不会影响另外的进程。
- 进程间通信比线程间通信的性能差很多。
- 线程切换开销更低。
IPC进程间通信55555555555
- 无名管道pipe(血缘关系的进程)
- 有名管道fifo (无血缘关系的进程)
- 共享内存
- 信号(开销小)
- 消息队列
- 信号量
- 套接字
进程间同步
- 文件锁
- 信号量
线程间同步
- 互斥锁
- 读写锁(读时共享,写时互斥)
- 条件变量
- 信号量(互斥锁的升级版)
- 自旋锁(可以避免进程或线程上下文的开销)
线程共享资源
- 文件描述符表(打开的文件)
- 进程用户ID和进程组ID
- 进程的**内存地址空间.**text代码段 .data数据段 .bss heap堆区 全局变量 静态变量
- 每种信号的处理方式
- 进程的当前目录
线程独享资源
- 线程栈
- 寄存器组的值
- 线程ID
- 错误返回码errno变量
- 线程信号屏蔽字
- 线程优先级
进程调度方式
- 抢占式:立马停止。
- 非抢占式:时间片用完或者等待资源时,再调用另一个进程。
进程调度算法
- 先来先服务
- 短作业优先
- 优先级调度
- 时间片轮转
- 高响应比优先
管道
管道是一种伪文件,实质为内核缓冲区 大小为4K 内核借用环形队列实现
管道是半双工的,数据只能单向流动,不可重复读取,只能用于有血缘关系的进程
Linux命令
- find命令,用来查找文件。常用的按照名字查找-name,按照文件类型查找-type,linux常用的文件类型有七种,普通文件,目录文件,管道,套接字,软链接,块设备,字符设备。还可以按照文件大小查询-size。
- grep命令,按照文件内容来查找。使用规则是grep option pattern file
- ps aux
- curl命令 访问一个网页
- df查看磁盘大小
- du查看目录大小
- free -h 查看内存大小和使用情况
- top查看系统的实时负载
- netstat -ta 查看监听的TCP
- stat 获取文件属性
- file 查看文件类型
- sudo iptables -L 查看防火墙状态
- sudo vim etc/sysctl.conf 查看TCP属性
大端字节序和小端字节序
- 大端字节序:网络字节序(高位存低位)
- 小端字节序:主机字节序,现代PC机采用小端字节序(低位存低位,高位存高位)
比如0x1f3f5f7f 地址0x1000 0x1001 0x1002 0x1003
大端法:7f存在0x1003 5f存0x1002 3f存0x1001 1f存0x1000 低存高
小端法:7f存在0x1000 5f存0x1001 3f存0x1002 1f存0x1003 低存低
socket服务器端所用函数
socket 创建socket文件描述符 bind 绑定IP和端口号 listen 监听 accept 接受连接 处理客户端的业务
socket客户端所用函数
socket 创建套接字文件描述符 bind 绑定IP和端口号(也可以隐式绑定) connect 尝试连接服务器 处理服务器端的业务
五种网络IO模型
同步阻塞IO
同步非阻塞IO
IO多路复用
信号驱动IO
异步IO
select与poll、epoll的各自的优缺点和区别
- select是跨平台的,windows、linux、unix系统下都有
- poll在linux和unix下有 epoll是linux特有,epoll的要义就是高效的监视多个socket
- 多路IO监听时没有动静,监听会休眠监听。
讲讲epoll的边沿触发和水平触发
- 水平触发:如果epoll_wait缓冲区有数据则直接返回。
- 边沿触发:如果一次没有读完epoll_wait缓冲区中的数据,则只有当另外有数据再写入时,才返回。 使用边沿触发和非阻塞IO来达到水平触发的效果,减少了epoll_wait的调用次数,提高了效率。 边沿触发可以只读取缓存区中前面的部分信息,进而分析后面的信息是否有用,如果无用则直接丢弃。
Libevent库
是一个开源的库,封装了socket和IO多路转接,用于高并发服务器的开发。跨平台可移植性好。跨平台,线程安全,基于reactor模式实现的高效网络库。
协程
协程是一种用户态的轻量级线程。协程的开销远远小于线程的开销。
协程是一种比线程更加轻量级的存在,一个线程可以拥有多个协程。 无论是进程还是线程,都是由操作系统所管理的。而协程不是被OS所管理,而完全是由程序所控制(也就是在用户态执行)。
信号
信号是一种不精确通信。
常用的信号有SIGKILL 9 无条件终止信号,SIGSEGV 11 无效存储访问 SIGPOLL 8 轮询事件信号。
信号有三种处理方式:忽略,捕获,默认。
kill命令向进程发送信号
什么是死锁
因为资源调度的方式不合理或者资源的稀缺性,导致进程间的相互等待。
死锁的四个必要条件:互斥条件,请求和保持条件,环路等待条件,不可剥夺条件。
死锁的预防只要破坏死锁产生的四个必要条件。通常采用预先静态分配方法,可以破坏请求和保持条件。
死锁的避免:采用银行家算法,只要系统处于安全状态,系统便可避免死锁。
死锁的解决:撤销进程,剥夺资源。
僵尸进程和孤儿进程
- 僵尸进程:子进程死亡,而父进程没有进行回收 waitpid回收指定进程
- 孤儿进程:父进程死亡,而子进程仍然存活,但是系统会让init进程领养孤儿进程。
fork函数
fork函数用来创建子进程 一次调用,两次返回。在父进程中返回子进程的PID,在子进程中返回0
exec族
在程序中调用另一个可执行程序,但是进程ID不改变。
网络编程IO
服务器通常需要处理三类事件:IO事件,信号及定时事件。
事件处理模式:reactor和proactor
同步IO模型通常用于实现reactor模式
异步IO则用于实现proactor模式
什么是reactor模式
它要求主线程只负责监听文件描述符上是否有事件发生,有的话立即将该事件通知工作线程。读写数据,接受新的连接以及处理客户请求均在工作线程中完成。
什么是proactor模式
它将所有IO操作都交给主线程和内核来处理,工作线程仅仅负责业务逻辑。
mmap存储映射
将磁盘空间映射到进程空间,**使进程可以采用指针的方式操作这段内存,**而不用调用read和write函数。提高了读写的效率,同时也可以实现进程间的通信。
异步IO原理
底层将数据准备好后,内核会给进程发送一个异步通知信号SIGIO29通知进程,然后进程调用信号处理函数去读数据,没准备好,数据就忙自己的事情。
select poll epoll
- select单个进程打开的文件描述符有上限,为1024或者2048。select对于有响应的事件需要轮询来查找满足要求的事件。每次调用select都需要把文件描述符集合从用户态拷贝到内核态。
- poll描述fd的集合是链式的,解决了打开文件描述符数量的限制。同样需要轮询满足事件的文件描述符。也需要进行用户态和内核态的文件描述符拷贝。poll是水平触发。
- epoll使用了mmap内存映射技术和红黑树的数据结构。通过三个函数来监听多个文件描述符,同时不随数量的上升效率呈线性的下降。mmap内存读写快于IO读写,及时共享映射内存的改变。