1.进程-线程-协程
1-1.进程
- 进程:正在运行的程序的实例,系统中的每个程序都运行在某个进程的上下文中;进程是系统资源分配的最小单位
-
进程出现的目的:更好的利用CPU资源
- A执行任务(读取数据)的时候,让B执行任务,当A读取完数据后,再“切换”回A任务
- 切换涉及到状态的保存和恢复,因此通过进程来分配资源,标识任务
- 分配CPU去执行进程称之为调度,进程的状态保存、恢复、上下文切换
-
进程内部有哪些信息
- 进程描述信息:进程标识号/用户标识号/家族关系
- 进程控制信息:进程当前状态/进程优先级/程序开始地址/各种计时信息/通信信息/资源管理信息等等
1-2.线程
- 线程:运行在进程上下文中的逻辑流;线程是CPU调度的最小单位
-
线程出现的目的:共享进程资源,减少上下文切换的损耗
- AB任务拥有共同需要的系统资源
1-3.协程
- 协程:程序之间的切换由用户自行处理,可以是大型程序中的某段代码;协程是用户级别的CPU调度
-
线程出现的目的:避免陷入内核级别的上下文切换造成性能上的损失
- 高并发场景,存在大量线程处于等待状态的情况
- 进程/线程都是操作系统自带的;协程由程序原生支持
- 核心:线程是内核态调度,协程是用户态调度
1-4.上下文切换
- 定义:将CPU资源从一个进程分配给另一个进程的机制,因为同一时刻只能执行一条CPU指令
- 切换过程:在切换过程中:首先要存储当前进程状态,读入下一个进程的状态,然后执行次进程
-
为什么进程上下文切换比线程上下文切换代价高?
-
进程切换分为两个步骤:对于线程来说只需要执行第2步,对于进程来说要执行12步
- 切换页目录以使用新的地址空间
- 切换内核栈和硬件上下文
-
-
其他关于进程和线程的描述:
- 进程和线程都是CPU工作时间段的描述,只不过是颗粒度不同;因为线程共享进程的上下文,所以是更细的CPU时间段描述
- 进程就是包换上下文切换的程序执行时间总和 = CPU加载上下文+CPU执行+CPU保存上下文
2.进程类型
2-1.守护进程
- 守护进程:运行在后台的一种特殊进程,独立于控制终端并周期性地执行某些任务
2-2.孤儿进程
- 孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,这些子进程称为孤儿进程
- 当父进程退出后:孤儿进程将由 init 进程收养并对它们完成状态收集工作
2-3.僵尸进程
- 僵尸进程:子进程exit()退出后,父进程没有对其进行回收(调用wait()或waitpid()函数),导致子进程占用的资源没有释放从而成为僵尸进程
-
解决办法:
- 使用top命令的时候查看zombie信息
- ps -elf | grep Z 找到对应的僵尸进程
- kill -9 P_pid 杀死僵尸进程的父进程;让子进程由Init进程回收
-
如何避免:
- 父进程wait()或waitpid()等待子进程结束
- Singal信号:当子进程exit()后,发送信号给父进程,父进程调用wait()
- Singal信号:将信号直接发往内核,由内核直接回收
- fork()两次:子进程exit()后,孙进程由Init进程回收
3.进程/线程通信方式
3-1.进程间通信
-
进程通信方式:管道/消息队列/信号量/共享存储/Socket
- 信号量:是一个计数器,可以用来控制多个进程对共享资源的访问,交换的信息量较少也称之为低级进程通信
- 管道:单向,先进先出,无格式的,固定大小的字节流,它把一个进程的标准输出和另一个进程的标准输入连接在一起(场景:父子进程中)
- 消息队列:是一个在系统内核中用来保存消息的队列,它在系统内核中是以消息链表的形式出现的;消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点
- 共享存储:允许多个进程访问同一个逻辑内存,这个逻辑内存可以被多个进程映射到自身的内存地址空间中,是最快的IPC通信方式;专门针对其他底层进程通信方式来设计的,通常配合信号量完成进程的通信和同步
- 套接字:可用于不同主机上进程之间的通信
3-2.线程间通信
-
线程通信方式:同一进程下的线程共享数据(比如全局变量,静态变量),通过这些数据来通信不仅快捷而且方便,所以主要作用是线程同步问题
- ?机制:互斥锁/条件变量/读写锁
- 信号机制
- 信号量机制
4.进程调度
4-1.调度种类
- 作业调度:决定把后背作业调入内存中运行
- 进程调度:决定把就绪队列的某个进程获得CPU
- 虚拟存储中引入调度:在内外对换区进行进程对换
4-2.调度方式
- 非抢占调度:一旦分配CPU给进程便让其一直运行,直到进程完成或发生进程调度某个事件而堵塞时,才会把CPU分配给其他进程
- 抢占调度:操作系统将正在运行的进程暂停,由调度程序将CPU分配给其他就绪进程的调度方式
4-3.调度算法
- FIFO:先来先调度
- SJP:最短的作业(CPU区间长度最小)最先调度
- SRJP:SJP的抢占版本
- 优先权调度:优先级最高的任务最先调度
- 轮询调度
- 多级队列调度
-
多级反馈队列调度:
- 进程在进入待调度的队列等待时,首先进入优先级最高的Q1等待
- 首先调度优先级高的队列中的进程。若高优先级中队列中已没有调度的进程,则调度次优先级队列中的进程。
- 对于同一个队列中的各个进程,按照时间片轮转法调度
- 在低优先级的队列中的进程在运行时,又有新到达的作业,那么在运行完这个时间片后,CPU马上分配给新到达的作业(抢占式)
5.线程共享资源和独占资源分配问题
-
堆:是大家共有的空间,分全局堆和局部堆
- 全局堆就是所有没有分配的空间
- 局部堆就是用户分配的空间
-
栈:是个线程独有的,保存其运行状态和局部自动变量的
- 栈在线程开始的时候初始化,每个线程的栈互相独立,因此栈是thread safe的
- 操作系统在切换线程的时候会自动的切换栈,就是切换SS/ESP寄存器
6.Linux网络IO模型
6-1.同步/异步/阻塞/非阻塞
- 同步:执行一个操作后,等待结果,然后才执行后续操作
- 异步:执行一个操作后,可以继续执行其他操作,等待通知再来执行刚才没执行完的操作
- 阻塞:进程给CPU传达任务后,一直等待CPU处理完后,然后执行后续操作
- 非阻塞:进程给CPU传达任务后,继续执行后续操作,隔断时间再来询问之前的操作是否完成
6-2.select/poll/epoll
- select:具有O(N)的无差别轮询复杂度,使用元组进行存储fd
- poll:跟select类似,但使用链表存储这样就没有最大连接数的限制
- epoll:事件驱动(每个事件关联fd),会把每个IO流的事件信息通知我们,时间复杂度为O(1)
-
epoll的两种模式:
- LT模式:只要fd中还有数据可读,每次epoll_wait()都会返回它的事件信息,通知用户程序去操作
- ET模式:它只会提示一次,直到下次再有数据流入之前都不会再提示了,无论fd中是否还有数据可读,所以这个模式下一定要读完buffer
- 为什么要有ET模式:在LT模式下,如果有大量不需要读写的就绪fd,每次调用epoll_wait都会返回,这样影响用户程序检索自己所关心的fd的效率; 这种模式效率更高,系统中不会充斥着大量不需要的就绪fd
-
epoll优点:
- 没有最大连接数限制
- 效率提升:不是轮询方式,而是“活跃可用”的fd进行callback
-
总结:
- select和poll在“醒着”状态时需要不断轮询fd集合,而epoll只需要判断“就绪链表”是否为空值,因为就绪设备会进行callback,将就绪fd放入到就绪链表中,所以节省大量CPU时间
- select和poll每次轮询都需要将fd集合从内核态拷贝到用户态,而epoll只需要拷贝一次,这样能节省开销
7.copy-on-write技术
- COW技术:多个调用者调用相同资源时,它们会获得相同的指针指向的资源,当有调用者修改资源内容时,系统才会真正复制一份副本,而其他调用者的资源内容保持不变
- 优点:如果没有调用者修改资源,就不会有副本的创建,节省资源和系统开销
- COW详述:系统会为子进程创建虚拟空间结构(来自于父进程),但是不会创建物理空间;虚拟空间是指向物理空间的
8.TCP最大连接数问题
- TCP最大连接数的影响因素:内存和文件描述符
- 文件描述符:每个tcp连接都会占用一个文件描述符,操作系统对最大文件数有限制
-
如何修改TCP最大连接数:单个进程能够打开的文件数量默认上限为1024(命令:
ulimit -n
)- 临时修改:ulimit -n [numbers]
-
重启后失效:
# 配置文件:/etc/security/limits.conf soft nofile 1000000 hard nofile 1000000
- 永久修改:在/etc/rc.local文件中:
ulimit -SHna 1000000
-
全局限制:在/etc/sysctl.conf文件中:
[root@test ~]# cat /proc/sys/fs/file-nr # 已经分配的文件句柄数 已经分配但没有使用的句柄数 最大文件句柄数 992 0 95146 # 调整相关值:/etc/sysctl.conf文件中 fs.file-max = 1000000 # 最大文件句柄数 net.ipv4.ip_conntrack_max = 1000000 net.ipv4.netfilter.ip_conntrack_max = 1000000
-
高并发Web服务内核参数调优:
net.ipv4.tcp_syncookies=1 # 预防少量SYN攻击 net.ipv4.tcp_tw_reuse=1 # 开启重用(timewait) net.ipv4.tcp_tw_recycle=1 # 开启tcp快速回收(timewait) net.ipv4.tcp_fin_timeout=30 # 处于fin_wait_2状态的时间 net.ipv4.tcp_max_syn_backlog=65536 # 半连接队列长度 net.core.somaxconn=32768 # 全连接队列长度 net.core.netdev_max_backlog=65536 # 每个网络接口能够接收的数量 sysctl -w net.ipv4.ip_conntrack_max=65536
9.backlog参数
- backlog是系统调用listen()参数,listen只是posix标准,不是TCP的标准,就意味着不同的内核可以有自己独立的实现
-
Unix中定义:backlog参数只限制全连接队列长度
- 全连接队列-SYN队列:进入ESTABLISHED状态
- 半连接队列-ACCEPT队列:进入SYN_RECV状态
-
关于somaxconn和max_backlog分析:
- 当从SYN_RECV状态变化为EASTABLISHED状态后,它会从SYN队列转移ACCEPT队列中
-
backlog参数描述的是服务器端ESTABELLISHED状态对应的全连接队列长度
- 全连接队列长度 = min(
backlog
, 内核参数:net.core.somaxconn
)
- 全连接队列长度 = min(
-
半连接队列长度由内核参数 tcp_max_syn_backlog决定
- 半连接队列长度 = min(
backlog
, 内核参数:net.core.somaxconn
,内核参数:tcp_max_syn_backlog
)
- 半连接队列长度 = min(
- 总结:以上关于全/半连接队列长度取值意味着:当全连接队列满后,就不在接受连接放入到半连接队列中
-
关于设置backlog参数后,能够建立全连接的数量:
backlog + 1
问题- 源码中: 关于判断是否加入全连接队列的条件是:
if 全连接数 > backlog: 进队列
- 源码中: 关于判断是否加入全连接队列的条件是:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。