linux内核视角看阻塞式IO

一、概述

网络包从网卡送到协议栈后,内核还有一项重要的工作,就是要能通知用户进程,让用户进程能够收到并处理这些数据。用户进程和内核的交互一般有两种典型的方案,一种是同步阻塞,另一种是多路IO复用。

二、内核socket对象

linux内核视角看阻塞式IO_第1张图片

三、同步阻塞IO

同步阻塞IO在java中叫BIO,典型的用户进程代码:

//创建socket
//建立connect
//接收数据recv

虽然用户进程代码只有两三行代码,但实际上用户进程和内核配合做了很多的工作。首先是用户进程发起创建socket的指令,然后用户进程切换到内核态完成socket内核对象的初始化,接下来,Linux在数据包的接收上是通过cpu硬中断和内核ksoftirqd线程进行处理,ksoftirqd线程处理完后,再通知用户进程。
同步阻塞IO总体流程如下:

linux内核视角看阻塞式IO_第2张图片

3.1 用户进程等待接收数据

用户进程进行recv系统调用后,用户进程就进入了内核态,到socket对象的接收队列中查看是否有数据,没有的话就把自己添加到socket对应的等待队列里,最后让出CPU,所以这里就能看到用户进程阻塞了。
linux内核视角看阻塞式IO_第3张图片

看一下,socket对象上注册的tcp_recvmsg函数,阻塞了当前进程:

int tcp_recvmsg(struct kiocb *iocb,struct sock *sk,struct msghdr *msg,size_t len,int nonblock,int flags,int *addr_len){
    int copied = 0;
    ......
    do{
        //遍历接收队列接收数据
        skb_queue_walk(&sk->sk_receive_queue,skb){
        ......
        }
        ......
    }
    if(copied >= target){
      ......
    }else{
      //没有收到足够的数据,调用sk_wait_data阻塞当前进程
      sk_wait_data(sk,&timeo); 
    }
}

3.2 软中断

网卡接收到数据后,发出硬中断通知CPU,最后交由软中断处理,也就是内核ksoftirqd线程去处理。ksoftiqrd线程主要做两件事,一是从RingBuffer取出数据放到socket对象的接收队列,二是唤醒阻塞在socket等待队列上的用户进程。
linux内核视角看阻塞式IO_第4张图片
内核函数sock_def_readable最终调用了default_wake_function,来唤醒用户进程:

// kernel/sched/core.c
int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags, void *key){
    return try_to_wake_up(curr->private, mode, wake_flags);
}

curr->private,这个就是阻塞的用户进程描述符。在多路IO复用技术中也能看到这一步。

四、综述

同步阻塞IO接收网络包整体分为两个部分,每次用户进程为了等socket上的数据而主动陷入阻塞,让出CPU,操作系统调度另外的进程在CPU上运行,等到数据准备好后,阻塞的用户进程又会被唤醒,操作系统重新调度用户进程在CPU上运行,整个过程就产生了两次进程间的上下文切换的开销,并且用户进程在等待数据的时候是陷入阻塞的,所以同步阻塞IO效率很低。

你可能感兴趣的:(linux网络,linux,服务器,网络)