1.进程-线程-协程

1-1.进程

  • 进程:正在运行的程序的实例,系统中的每个程序都运行在某个进程的上下文中;进程是系统资源分配的最小单位
  • 进程出现的目的:更好的利用CPU资源

    • A执行任务(读取数据)的时候,让B执行任务,当A读取完数据后,再“切换”回A任务
    • 切换涉及到状态的保存和恢复,因此通过进程来分配资源,标识任务
    • 分配CPU去执行进程称之为调度,进程的状态保存、恢复、上下文切换
  • 进程内部有哪些信息

    • 进程描述信息:进程标识号/用户标识号/家族关系
    • 进程控制信息:进程当前状态/进程优先级/程序开始地址/各种计时信息/通信信息/资源管理信息等等

1-2.线程

  • 线程:运行在进程上下文中的逻辑流;线程是CPU调度的最小单位
  • 线程出现的目的:共享进程资源,减少上下文切换的损耗

    • AB任务拥有共同需要的系统资源

1-3.协程

  • 协程:程序之间的切换由用户自行处理,可以是大型程序中的某段代码;协程是用户级别的CPU调度
  • 线程出现的目的:避免陷入内核级别的上下文切换造成性能上的损失

    • 高并发场景,存在大量线程处于等待状态的情况
    • 进程/线程都是操作系统自带的;协程由程序原生支持
    • 核心:线程是内核态调度,协程是用户态调度

1-4.上下文切换

  • 定义:将CPU资源从一个进程分配给另一个进程的机制,因为同一时刻只能执行一条CPU指令
  • 切换过程:在切换过程中:首先要存储当前进程状态,读入下一个进程的状态,然后执行次进程
  • 为什么进程上下文切换比线程上下文切换代价高?

    • 进程切换分为两个步骤:对于线程来说只需要执行第2步,对于进程来说要执行12步

      1. 切换页目录以使用新的地址空间
      2. 切换内核栈和硬件上下文
  • 其他关于进程和线程的描述:

    • 进程和线程都是CPU工作时间段的描述,只不过是颗粒度不同;因为线程共享进程的上下文,所以是更细的CPU时间段描述
    • 进程就是包换上下文切换的程序执行时间总和 = CPU加载上下文+CPU执行+CPU保存上下文

2.进程类型

2-1.守护进程

  • 守护进程:运行在后台的一种特殊进程,独立于控制终端并周期性地执行某些任务

2-2.孤儿进程

  • 孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,这些子进程称为孤儿进程
  • 当父进程退出后:孤儿进程将由 init 进程收养并对它们完成状态收集工作

2-3.僵尸进程

  • 僵尸进程:子进程exit()退出后,父进程没有对其进行回收(调用wait()或waitpid()函数),导致子进程占用的资源没有释放从而成为僵尸进程
  • 解决办法:

    1. 使用top命令的时候查看zombie信息
    2. ps -elf | grep Z 找到对应的僵尸进程
    3. 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的抢占版本
  • 优先权调度:优先级最高的任务最先调度
  • 轮询调度
  • 多级队列调度
  • 多级反馈队列调度

    1. 进程在进入待调度的队列等待时,首先进入优先级最高的Q1等待
    2. 首先调度优先级高的队列中的进程。若高优先级中队列中已没有调度的进程,则调度次优先级队列中的进程。
    3. 对于同一个队列中的各个进程,按照时间片轮转法调度
    4. 在低优先级的队列中的进程在运行时,又有新到达的作业,那么在运行完这个时间片后,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

    1. 临时修改:ulimit -n [numbers]
    2. 重启后失效:

      # 配置文件:/etc/security/limits.conf
      soft nofile 1000000
      hard nofile 1000000
    3. 永久修改:在/etc/rc.local文件中: ulimit -SHna 1000000
    4. 全局限制:在/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)
    • 半连接队列长度由内核参数 tcp_max_syn_backlog决定

      • 半连接队列长度 = min(backlog, 内核参数:net.core.somaxconn,内核参数:tcp_max_syn_backlog)
    • 总结:以上关于全/半连接队列长度取值意味着:当全连接队列满后,就不在接受连接放入到半连接队列中
  • 关于设置backlog参数后,能够建立全连接的数量:backlog + 1 问题

    • 源码中: 关于判断是否加入全连接队列的条件是:if 全连接数 > backlog: 进队列

参考


英格拉姆浩
40 声望12 粉丝

面对焦虑,认识自我,提升技术


« 上一篇
网络面试题
下一篇 »
MySQL面试复习1