国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

深入理解Linux內(nèi)核網(wǎng)絡(luò)——內(nèi)核與用戶進(jìn)程協(xié)作之同步阻塞方案(BIO)

這篇具有很好參考價(jià)值的文章主要介紹了深入理解Linux內(nèi)核網(wǎng)絡(luò)——內(nèi)核與用戶進(jìn)程協(xié)作之同步阻塞方案(BIO)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

系列文章:

  1. 深入理解Linux網(wǎng)絡(luò)——內(nèi)核是如何接收到網(wǎng)絡(luò)包的
  2. 深入理解Linux網(wǎng)絡(luò)——內(nèi)核與用戶進(jìn)程協(xié)作之同步阻塞方案(BIO)
  3. 深入理解Linux網(wǎng)絡(luò)——內(nèi)核與用戶進(jìn)程協(xié)作之多路復(fù)用方案(epoll)
  4. 深入理解Linux網(wǎng)絡(luò)——內(nèi)核是如何發(fā)送網(wǎng)絡(luò)包的
  5. 深入理解Linux網(wǎng)絡(luò)——本機(jī)網(wǎng)絡(luò)IO
  6. 深入理解Linux網(wǎng)絡(luò)——TCP連接建立過(guò)程(三次握手源碼詳解)
  7. 深入理解Linux網(wǎng)絡(luò)——TCP連接的開(kāi)銷

在上一部分中講述了網(wǎng)絡(luò)包是如何從網(wǎng)卡送到協(xié)議棧的(詳見(jiàn)深入理解Linux網(wǎng)絡(luò)——內(nèi)核是如何接收到網(wǎng)絡(luò)包的),接下來(lái)內(nèi)核還有一項(xiàng)重要的工作,就是在協(xié)議棧接收處理完輸入包后要通知到用戶進(jìn)程,如何用戶進(jìn)程接收到并處理這些數(shù)據(jù)。

進(jìn)程與內(nèi)核配合有多種方案,這里我們這分析兩種典型的:

  1. 同步阻塞方案(Java中習(xí)慣叫BIO)

  2. 多路IO復(fù)用方案(Java中對(duì)應(yīng)NIO)

    • Linux多路復(fù)用有select、poll、epoll,這里只講性能最優(yōu)秀的epoll

本文主要講的是同步阻塞模式的實(shí)現(xiàn)方案,多路IO復(fù)用方案及問(wèn)題解答見(jiàn)文章深入理解Linux內(nèi)核網(wǎng)絡(luò)——內(nèi)核與用戶進(jìn)程協(xié)作之多路復(fù)用方案(epoll)

一、相關(guān)實(shí)際問(wèn)題

  1. 阻塞到底是怎么一回事
  2. 同步阻塞IO都需要哪些開(kāi)銷
  3. 多路復(fù)用epoll為什么就能提高網(wǎng)絡(luò)性能
  4. epoll也是阻塞的嗎
  5. redis為什么網(wǎng)絡(luò)性能突出

二、socket的直接創(chuàng)建

以開(kāi)發(fā)者的角度來(lái)看,調(diào)用socket函數(shù)可以創(chuàng)建一個(gè)socket

int main()
{
    int sk = socket(AF_INET, SOCK_STREAM, 0);
    ......
}

等這個(gè)socket函數(shù)調(diào)用執(zhí)行完以后,用戶層面看到返回的是一個(gè)整數(shù)型的句柄,但其實(shí)內(nèi)核在內(nèi)部創(chuàng)建了一系列的socket相關(guān)的內(nèi)核對(duì)象(不止一個(gè))。它們之間相互的關(guān)系如下:

深入理解Linux內(nèi)核網(wǎng)絡(luò)——內(nèi)核與用戶進(jìn)程協(xié)作之同步阻塞方案(BIO),網(wǎng)絡(luò),linux,網(wǎng)絡(luò),socket,io

socket在內(nèi)核中的定義如下:

struct socket {  
    socket_state            state;  
    unsigned long           flags;  
    const struct proto_ops *ops;  
    struct fasync_struct    *fasync_list;  
    struct file             *file;  
    struct sock             *sk;  
    wait_queue_head_t       wait;  
    short                   type;  
};

typedef enum {  
    SS_FREE = 0,            //該socket還未分配  
    SS_UNCONNECTED,         //未連向任何socket  
    SS_CONNECTING,          //正在連接過(guò)程中  
    SS_CONNECTED,           //已連向一個(gè)socket  
    SS_DISCONNECTING        //正在斷開(kāi)連接的過(guò)程中  
}socket_state;

socket是內(nèi)核抽象出的一個(gè)通用結(jié)構(gòu)體,主要是設(shè)置了一些跟fs相關(guān)的字段,而真正跟網(wǎng)絡(luò)通信相關(guān)的字段結(jié)構(gòu)體是struct sock。

struct sock是網(wǎng)絡(luò)層對(duì)于struct socket的表示,其中成員非常多,這里只介紹其中一部分。

  1. sk_prot和sk_prot_creator,這兩個(gè)成員指向特定的協(xié)議處理函數(shù)集,其類型是結(jié)構(gòu)體struct proto,該結(jié)構(gòu)體也是跟struct proto_ops相似的一組協(xié)議操作函數(shù)集。這兩者之間的概念似乎有些混淆,可以這么理解,struct proto_ops的成員操作struct socket層次上的數(shù)據(jù),處理完了,再由它們調(diào)用成員sk->sk_prot的函數(shù),操作struct sock層次上的數(shù)據(jù)。即它們之間存在著層次上的差異。struct proto類型的變量在協(xié)議棧中總共也有三個(gè),分別是mytcp_prot,myudp_prot,myraw_prot,對(duì)應(yīng)TCP, UDP和RAW協(xié)議。

  2. sk_state表示socket當(dāng)前的連接狀態(tài),是一個(gè)比struct socket的state更為精細(xì)的狀態(tài),其可能的取值如下:

    • enum {  
         TCP_ESTABLISHED = 1,  
         TCP_SYN_SENT,  
         TCP_SYN_RECV,  
         TCP_FIN_WAIT1,  
         TCP_FIN_WAIT2,  
         TCP_TIME_WAIT,  
         TCP_CLOSE,  
         TCP_CLOSE_WAIT,  
         TCP_LAST_ACK,  
         TCP_LISTEN,  
         TCP_CLOSING, 
        
         TCP_MAX_STATES; 
      }
      
    • 這些取值從名字上看,似乎只使用于TCP協(xié)議,但事實(shí)上,UDP和RAW也借用了其中一些值,在一個(gè)socket創(chuàng)建之初,其取值都是TCP_CLOSE,一個(gè)UDP socket connect完成后,將這個(gè)值改為T(mén)CP_ESTABLISHED,最后,關(guān)閉sockt前置回TCP_CLOSE,RAW也一樣。
  3. sk_rcvbuf和sk_sndbuf:表示接收和發(fā)送緩沖區(qū)的大小。這兩個(gè)值是動(dòng)態(tài)的,應(yīng)用程序可以通過(guò)setsockopt系統(tǒng)調(diào)用來(lái)改變它們的值。但是,這些值也受到了一些全局內(nèi)核參數(shù)的限制(通常由/proc/sys/net/core/rmem_default(對(duì)于接收緩沖區(qū))和/proc/sys/net/core/wmem_default(對(duì)于發(fā)送緩沖區(qū))這兩個(gè)內(nèi)核參數(shù)來(lái)決定)。

  4. sk_receive_queue和sk_write_queue:接收緩沖隊(duì)列和發(fā)送緩沖隊(duì)列,隊(duì)列里排列的是套接字緩沖區(qū)struct sk_buff,隊(duì)列中的struct sk_buff的字節(jié)數(shù)總和不能超過(guò)緩沖區(qū)大小的設(shè)定。在sock實(shí)例創(chuàng)建的時(shí)候初始化的,最開(kāi)始為空的隊(duì)列(雙向鏈表)。

  5. struct inet_sock:這是INET域?qū)S玫囊粋€(gè)socket表示,它是在struct sock的基礎(chǔ)上進(jìn)行的擴(kuò)展,在基本socket的屬性已具備的基礎(chǔ)上,struct inet_sock提供了INET域?qū)S械囊恍傩?,比如TTL,組播列表,IP地址,端口等,完整定義如下:

    • struct inet_sock {  
                  struct sock     sk;  
      #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)  
                  struct ipv6_pinfo   *pinet6;  
      #endif  
                  __u32           daddr;          //IPv4的目的地址。  
                  __u32           rcv_saddr;      //IPv4的本地接收地址。  
                  __u16           dport;          //目的端口。  
                  __u16           num;            //本地端口(主機(jī)字節(jié)序)。  
                  __u32           saddr;          //發(fā)送地址。  
                  __s16           uc_ttl;         //單播的ttl。  
                  __u16           cmsg_flags;  
                  struct ip_options   *opt;  
                  __u16           sport;          //源端口。  
                  __u16           id;             //單調(diào)遞增的一個(gè)值,用于賦給iphdr的id域。  
                  __u8            tos;            //服務(wù)類型。  
                  __u8            mc_ttl;         //組播的ttl  
                  __u8            pmtudisc;  
                  __u8            recverr:1,  
                                  is_icsk:1,  
                                  freebind:1,  
                                  hdrincl:1,      //是否自己構(gòu)建ip首部(用于raw協(xié)議)  
                                  mc_loop:1;      //組播是否發(fā)向回路。  
                  int             mc_index;       //組播使用的本地設(shè)備接口的索引。  
                  __u32           mc_addr;        //組播源地址。  
                  struct ip_mc_socklist   *mc_list;   //組播組列表。  
                  struct {  
                      unsigned int        flags;  
                      unsigned int        fragsize;  
                      struct ip_options   *opt;  
                      struct rtable       *rt;  
                      int                 length;  
                      u32                 addr;  
                      struct flowi        fl;  
                  } cork;  
              };
      

sock_create是創(chuàng)建socket的主要位置,其中sock_create又調(diào)用了__sock_create

int __sock_create(struct net *net, int family, ...)
{
    struct socket *sock;
    const struct net_proto_family *pf;
    ......

    // 分配socket對(duì)象
    sock = sock_alloc();
    // 獲得每個(gè)協(xié)議族的操作表
    pf = rcu_dereference(net_families[family]
    // 調(diào)用指定協(xié)議族的創(chuàng)建函數(shù),對(duì)于AF_INET對(duì)應(yīng)的就是inet_creat
    err = pf->create(net, sock, protocol, kern);
}

在__sock_create里,首先調(diào)用sock_alloc來(lái)分配一個(gè)struct socket的內(nèi)核對(duì)象,接著獲取協(xié)議族的操作函數(shù)表調(diào)用其create方法,對(duì)于AF_INET協(xié)議族來(lái)說(shuō),執(zhí)行的是inet_create方法。

static int inet_create(struct net *net, struct socket *sock, int protocol, int kern)
{
    struct sock *sk;

    list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {
	// 將inet_stream_ops賦值到socket->ops上
	sock->ops = answer->ops;
	// 獲得tcp_prot
	answer_prot = answer->prot;
	// 分配sock對(duì)象,并把tcp_prot賦值到sk->prot上
	sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);
	// 對(duì)sock對(duì)象進(jìn)行初始化
	sock_init_data(sock, sk);
    }
}

static struct inet_protosw inetsw_array[] = 
{
    {
    .type = SOCK_STREAM;
    .protocol = IPPROTO_TCP,
    .prot = &tcp_prot,
    .ops = &inet_stream_ops,
    .no_check = 0,
    .flags = INET_PROTOSW_PERMANENT | INET_PROTOSW_ICSK,
    },
}

在inet_create中,根據(jù)類型SOCK_STREAM查找到對(duì)于TCP定義的操作方法實(shí)現(xiàn)集合inet_stream_ops和tcp_prot,并把它們發(fā)別設(shè)置到socket->ops和sk->prot上。

最后的sock_init_data將sk中的sk_data_ready函數(shù)指針進(jìn)行了初始化(也包括設(shè)置其他函數(shù)指針),設(shè)置為默認(rèn)的sock_def_readable,同時(shí)也會(huì)初始化sk_receive_queue和sk_write_queue為空隊(duì)列

inetsw_array存儲(chǔ)了AF_INET類型套接字的的所有網(wǎng)絡(luò)協(xié)議

深入理解Linux內(nèi)核網(wǎng)絡(luò)——內(nèi)核與用戶進(jìn)程協(xié)作之同步阻塞方案(BIO),網(wǎng)絡(luò),linux,網(wǎng)絡(luò),socket,io

當(dāng)軟中斷上收到數(shù)據(jù)包時(shí)會(huì)通過(guò)調(diào)用sk_data_ready函數(shù)指針(實(shí)際上被設(shè)置成了sock_def_readable)來(lái)喚醒sock上等待的進(jìn)程

至此一個(gè)tcp對(duì)象,確切的說(shuō)是AF_INET協(xié)議族下的SOCK_STREAM對(duì)象就算創(chuàng)建完成了,這里花費(fèi)了一次socket系統(tǒng)調(diào)用的開(kāi)銷。

三、內(nèi)核和用戶進(jìn)程協(xié)作之阻塞方式

同步阻塞IO總體流程如下

深入理解Linux內(nèi)核網(wǎng)絡(luò)——內(nèi)核與用戶進(jìn)程協(xié)作之同步阻塞方案(BIO),網(wǎng)絡(luò),linux,網(wǎng)絡(luò),socket,io

1)等待接收消息

查看recv函數(shù)的底層實(shí)現(xiàn)。首先通過(guò)strace命令追蹤,可以看到clib庫(kù)函數(shù)recv會(huì)執(zhí)行recvfrom系統(tǒng)調(diào)用。

進(jìn)入系統(tǒng)調(diào)用后,用戶進(jìn)程就進(jìn)入了內(nèi)核態(tài),執(zhí)行一系列的內(nèi)核協(xié)議層函數(shù),然后到socket對(duì)象的接收隊(duì)列中查看是否有數(shù)據(jù),沒(méi)有的話就把自己添加到socket對(duì)應(yīng)的等待隊(duì)列里然后讓出CPU,操作系統(tǒng)選擇下一個(gè)就緒狀態(tài)的進(jìn)程來(lái)執(zhí)行。

SYSCALL_DEFINE6(recvfrom, int, fd, void __user *, ubuf, size_t, size,
		unsigned int, flags, struct sockaddr __user *, addr,
		int __user *, addr_len)
{
    struct socket *sock;
    // 根據(jù)傳入的fd找到socket對(duì)象
    sock = sock_lookup_light(fd, &err, &fput_needed);
    ......
    err = sock_recvmsg(sock, &msg, size, flags);
    ......
}

后續(xù)的調(diào)用順序?yàn)椋?/p>

sock_recvmsg => __sock_recvmsg => __sock_recvmsg_nosec

在__sock_recvmsg_nosec中會(huì)去調(diào)用socket對(duì)象proto_ops里的recvmsg,在AF_INET中其指向的是inet_recvmsg方法。

而在inet_recvmg中,會(huì)去調(diào)用socket中的sock對(duì)象的sk->sk_prot->recvmsg,在SOCK_STREAM中它的實(shí)現(xiàn)是tcp_recvmsg方法。

int tcp_recvmsg(struct kiocb *iocb, strcut sock * sock, struct msghdr *msg, 
size_t len, int nonblock, int flags, int *addr_len)
{
    int copied = 0;
    ......
    
    // 如果設(shè)置了MSG_WAITALL,則target==len,即recv函數(shù)中的參數(shù)len
    // 如果沒(méi)設(shè)置MSG_WAITALL,則target==1
    target = sock_rcvlowat(sk, flags & MSG_WAITALL, len);
    
    do {
 	// 遍歷接收隊(duì)列接收數(shù)據(jù)
	skb_queue_walk(&sk->sk_receive_queue, skb) {
	    ......
	}
	......
    }
    if(copied >= target) {
 	release_sock(sk);
	lock_sock(sk);
    } else // 如果沒(méi)有收到足夠數(shù)據(jù),啟用sk_wait_data阻塞當(dāng)前進(jìn)程
	sk_wait_data(sk, &timeo);
}

可以看到這里會(huì)去遍歷socket的接收隊(duì)列,如果接收到的數(shù)據(jù)不滿足目標(biāo)數(shù)量則會(huì)阻塞當(dāng)前進(jìn)程,具體阻塞方法的實(shí)現(xiàn)邏輯如下

int sk_wait_data(struct sock *sk, long *timeo)
{
    // 當(dāng)前進(jìn)程(current)關(guān)聯(lián)到所定義的等待隊(duì)列項(xiàng)上
    DEFINE_WAIT(wait);
    // 調(diào)用sk_sleep獲取sock對(duì)象下的wait并準(zhǔn)備掛起,將進(jìn)程狀態(tài)設(shè)置為可打斷
    prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
    set_bit(SOCK_ASYNC_WAITDATA, &sk->sk_socket->flags);
    // 通過(guò)調(diào)用schedule_timeout讓出CPU,如何進(jìn)行睡眠
    rc = sk_wait_event(sk, timeo, !skb_queue_empty(&sk->sk_receive_queue);
    ......
}

#define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)
#define DEFINE_WAIT_FUNC(name, function) wait_queue_t name = {           \
						.private = current	 \
						.func = function	 \
						.task_list = LIST_HEAD_INIT((name).task_list) }

首先在DEFINE_WAIT宏下**,定義了一個(gè)等待隊(duì)列項(xiàng)wait**,在這個(gè)新的等待隊(duì)列項(xiàng)上注冊(cè)了回調(diào)函數(shù)autoremove_wake_function,并把當(dāng)前進(jìn)程描述符current關(guān)聯(lián)到其.private成員上。

task_list = LIST_HEAD_INIT((name).task_list)將wait_queue_t的task_list成員初始化為一個(gè)空的鏈表頭。LIST_HEAD_INIT是一個(gè)宏,它接受一個(gè)list_head類型的變量,并將它初始化為一個(gè)空的鏈表頭。在這個(gè)宏定義中,(name).task_list實(shí)際上就是新定義的wait_queue_t變量的task_list成員。
所以,這行代碼的意思就是將新定義的wait_queue_t變量的task_list成員初始化為一個(gè)空的鏈表頭。這是必要的步驟,因?yàn)樵趙ait_queue_t被添加到等待隊(duì)列之前,它的task_list必須被初始化為一個(gè)有效的鏈表節(jié)點(diǎn)。

prepare_to_wait()中會(huì)將wait變量的task_list成員添加到wait_queue_head_t類型的等待隊(duì)列中。也就是說(shuō),task_list成員會(huì)被鏈接到sk_sleep()返回的等待隊(duì)列中。

typedef struct __wait_queue_head wait_queue_head_t;

struct __wait_queue_head {
   spinlock_t lock;
   struct list_head task_list;
};

緊接著調(diào)用sk_sleep獲取sock對(duì)象下的等待隊(duì)列列表頭wait_queue_head_t。

接著調(diào)用prepare_to_wait來(lái)把新定義的等待隊(duì)列項(xiàng)wait插入sock對(duì)象的等待隊(duì)列,這樣后面當(dāng)內(nèi)核收完數(shù)據(jù)產(chǎn)生就緒事件的時(shí)候,就可以查找socket等待隊(duì)列上的等待項(xiàng),進(jìn)而可以找到回調(diào)函數(shù)和等待該socket就緒時(shí)間的進(jìn)程了。

最后調(diào)用sk_wait_event讓出CPU,進(jìn)程將進(jìn)入睡眠狀態(tài),這會(huì)導(dǎo)致一次進(jìn)程上下文的開(kāi)銷,而這個(gè)開(kāi)銷是昂貴的,大約需要花費(fèi)幾個(gè)微秒的CPU時(shí)間

2)軟中斷模塊

上篇文章中我們講到了網(wǎng)絡(luò)包到網(wǎng)卡之后是怎么被網(wǎng)卡接收最后再交給軟中斷處理的,最后講到了ip_rcv根據(jù)inet_protos和數(shù)據(jù)包的協(xié)議將包交給上層協(xié)議棧的處理函數(shù)。軟中斷(也就是ksoftirqd線程)收到數(shù)據(jù)包以后,發(fā)現(xiàn)是TCP包就會(huì)執(zhí)行tcp_v4_rcv函數(shù),這里直接從TCP協(xié)議的接收函數(shù)tcp_v4_rcv開(kāi)始。

int tcp_v4_rcv(struct sk_buff *skb)
{
    ......
    th = tcp_hdr(skb); // 獲取tcp header
    iph = ip_hdr(skb); // 獲取ip header
    // 根據(jù)數(shù)據(jù)包header中的IP、端口信息查找對(duì)應(yīng)的socket
    sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest);
    ......
    // socket未被用戶鎖定
    if(!sock_owned_by_user(sk)) {
	{
	    if(!tcp_prequeue(sk, skb))
		ret = tcp_v4_do_rcv(sk, skb);
	}
    } else
	// 如果數(shù)據(jù)包被用戶進(jìn)程鎖定,則數(shù)據(jù)包進(jìn)入后備處理隊(duì)列,并且該進(jìn)程進(jìn)入
	// 套接字的后備處理等待隊(duì)列sk->lock.wq
	sk_add_backlog(sk, skb);
}

首先根據(jù)收到的網(wǎng)絡(luò)包的header里的source和dest信息在本機(jī)上查詢對(duì)應(yīng)的socket。

tcp_hashinfo是一個(gè)散列表,用于存儲(chǔ)所有活動(dòng)的TCP套接字,從中查找與這個(gè)數(shù)據(jù)包對(duì)應(yīng)的sock(即struct sock實(shí)例)。如果找到了匹配的套接字,就說(shuō)明有一個(gè)連接正在接收這個(gè)數(shù)據(jù)包的源IP和端口發(fā)送的數(shù)據(jù)。

找到以后,首先要判斷socket是否已經(jīng)被用戶鎖定。

在Linux中,如果一個(gè) sock 對(duì)象被用戶進(jìn)程鎖定(例如,用戶進(jìn)程正在調(diào)用 recv 等系統(tǒng)調(diào)用讀取數(shù)據(jù)),那么內(nèi)核就不應(yīng)該直接修改 sock 的狀態(tài),而應(yīng)該將接收到的數(shù)據(jù)包放入后備處理隊(duì)列,稍后再處理(當(dāng)數(shù)據(jù)包被添加到后備處理隊(duì)列后,這個(gè)數(shù)據(jù)包的處理就結(jié)束了,只有當(dāng) socket 變?yōu)榭臻e狀態(tài),那些在后備處理隊(duì)列中的數(shù)據(jù)包才會(huì)被處理,通常這個(gè)處理過(guò)程就包括將數(shù)據(jù)包添加到接收隊(duì)列中)。

如果socket沒(méi)有被鎖定,則調(diào)用tcp_prequeue嘗試將數(shù)據(jù)包添加到sock的預(yù)處理隊(duì)列中。如果添加成功則返回1,否則返回0。

在這個(gè)函數(shù)里面,會(huì)對(duì) sysctl_tcp_low_latency 進(jìn)行判斷,也即是不是要低時(shí)延地處理網(wǎng)絡(luò)包。如果把 sysctl_tcp_low_latency 設(shè)置為 0,那就要放在 prequeue 隊(duì)列中暫存,這樣不用等待網(wǎng)絡(luò)包處理完畢,就可以離開(kāi)軟中斷的處理過(guò)程,但是會(huì)造成比較長(zhǎng)的時(shí)延(因?yàn)閿?shù)據(jù)包的處理延遲到了進(jìn)程被調(diào)度的時(shí)候。對(duì)于接收數(shù)據(jù)包而言沒(méi)有區(qū)別,因?yàn)槎际切枰竭M(jìn)程被調(diào)度時(shí)才拷貝到用戶空間。但是由于TCP協(xié)議處理被延遲,導(dǎo)致ACK的發(fā)送延遲,從而使數(shù)據(jù)發(fā)送端的數(shù)據(jù)發(fā)送延遲)。如果把 sysctl_tcp_low_latency 設(shè)置為 1,則調(diào)用 tcp_v4_do_rcv()立即處理。

實(shí)際上代碼中較新的版本已經(jīng)沒(méi)有了tcp_prequeue()函數(shù)。之所以取消prequeue,是因?yàn)樵诖蠖嗍褂檬录?qū)動(dòng)(epoll)的當(dāng)下,已經(jīng)很少有阻塞在recvfrom()或者read()的服務(wù)端代碼了。

如果數(shù)據(jù)包沒(méi)有加入預(yù)處理隊(duì)列則進(jìn)入接收的主體函數(shù)tcp_v4_do_rcv。

int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
    if(sk->sk_state == TCP_ESTABLISHED) {
	// 執(zhí)行鏈接狀態(tài)下的數(shù)據(jù)處理
	if(tcp_rcv_established(sk, skb, tcp_hdr(skb), skb->len)) {
	    rsk = sk;
	    goto reset;
	}
	return 0;
    }

    // 其他非ESTABLISHED狀態(tài)的數(shù)據(jù)包處理
    ......
}

int tcp_rcv_established(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th, unsigned int len)
{
    ......
    // 接收數(shù)據(jù)放到隊(duì)列中
    eaten = tcp_queue_rcv(sk, skb, tcp_header_len, &fragstolen);
    // 數(shù)據(jù)準(zhǔn)備好,喚醒socket上組色調(diào)的進(jìn)程
    sk->sk_data_ready(sk, 0);
}

假設(shè)處理的是ESTABLISHED狀態(tài)下的包(即已經(jīng)完成握手,建立連接),這樣就又進(jìn)入了tcp_rcv_established函數(shù)進(jìn)行處理。

在tcp_rcv_established中完成了將接收到的數(shù)據(jù)放到socket的接收隊(duì)列尾部,并調(diào)用sk_data_ready來(lái)喚醒在socket上等待的用戶進(jìn)程(創(chuàng)建socket時(shí)在sock_init_data函數(shù)里將該指針設(shè)置成了sock_def_readable)。

**喚醒進(jìn)程時(shí),即使等待隊(duì)列里有多個(gè)進(jìn)程阻塞著,也只喚醒一個(gè)進(jìn)程,避免驚群。**會(huì)從頭部開(kāi)始依次檢查每一個(gè)進(jìn)程,看看是否滿足喚醒的條件。如果滿足條件,就將該進(jìn)程喚醒。

在等待隊(duì)列中,進(jìn)程是按照它們進(jìn)入隊(duì)列的順序排列的,即先進(jìn)入隊(duì)列的進(jìn)程在隊(duì)列的前面,后進(jìn)入隊(duì)列的進(jìn)程在隊(duì)列的后面

在前面調(diào)用recvfrom時(shí),當(dāng)數(shù)據(jù)不夠后調(diào)用的sk_wait_data函數(shù)中使用DEFINE_WAIT定義了等待隊(duì)列項(xiàng)的細(xì)節(jié),并且把curr->func設(shè)置成了autoremove_wake_function,那么在喚醒進(jìn)程時(shí)會(huì)去調(diào)用這個(gè)函數(shù),它會(huì)去調(diào)用default_wake_function將因?yàn)榈却蛔枞倪M(jìn)程喚醒。這個(gè)函數(shù)執(zhí)行完之后,這個(gè)進(jìn)程就可以就可以被推入可運(yùn)行隊(duì)列里,在這里又將產(chǎn)生一次進(jìn)程上下文切換的開(kāi)銷。

3)同步隊(duì)列阻塞總結(jié)

同步阻塞方式接收網(wǎng)絡(luò)包的整個(gè)過(guò)程分為兩個(gè)部分:

  1. 我們自己的代碼所在的進(jìn)程:我們調(diào)用的socket()函數(shù)會(huì)進(jìn)入內(nèi)核態(tài)創(chuàng)建必要的內(nèi)核對(duì)象。recv()函數(shù)會(huì)在進(jìn)入內(nèi)核態(tài)以后負(fù)責(zé)查看接收隊(duì)列,以及在沒(méi)有數(shù)據(jù)可以處理的時(shí)候把當(dāng)前進(jìn)程組色調(diào),讓出CPU。
  2. 硬中斷、軟中斷上下文:在這些組件中,將包處理完后會(huì)放到socket的接收隊(duì)列中,然后根據(jù)socket內(nèi)核對(duì)象找到其等待隊(duì)列中正在因?yàn)榈却蛔枞舻倪M(jìn)程,將它喚醒。

每次一個(gè)進(jìn)程專門(mén)為了等待一個(gè)socket上的數(shù)據(jù)就被從CPU上拿出來(lái),然后換上另一個(gè)進(jìn)程。等到數(shù)據(jù)準(zhǔn)備好,睡眠的進(jìn)程又會(huì)被喚醒,總共產(chǎn)生兩次進(jìn)程上下文切換開(kāi)銷。根據(jù)業(yè)界的測(cè)試,每一次切換大約花費(fèi)3-5微妙。

然而從開(kāi)發(fā)者的角度而言,進(jìn)程上下文切換其實(shí)沒(méi)有做有意義的工作。如果是網(wǎng)絡(luò)IO密集型的應(yīng)用,CPU就會(huì)被迫不停地做進(jìn)程切換這種無(wú)用功。

這種模式在客戶端角色上現(xiàn)在還存在使用的情形,因?yàn)槟愕倪M(jìn)程可能確實(shí)需要等MySQL的數(shù)據(jù)返回成功之后才能渲染頁(yè)面返回給用戶,否則什么也干不了。

而在服務(wù)端角色上,這種模式完全無(wú)法使用。因?yàn)檫@種模型里的socket和進(jìn)程是一對(duì)一的,現(xiàn)在的單臺(tái)機(jī)器要承載成千上萬(wàn)甚至更多的用戶連接請(qǐng)求,如果用上面的方式,就得為每個(gè)用戶請(qǐng)求都創(chuàng)建一個(gè)進(jìn)程,否則無(wú)法同時(shí)處理多個(gè)用戶的請(qǐng)求,然而這肯定是不現(xiàn)實(shí)的。

所以我們需要更高效的網(wǎng)絡(luò)IO模型!可前往深入理解Linux內(nèi)核網(wǎng)絡(luò)——內(nèi)核與用戶進(jìn)程協(xié)作之多路復(fù)用方案(epoll)繼續(xù)學(xué)習(xí)多路IO復(fù)用解決方案~

參考資料

《深入理解Linux網(wǎng)絡(luò)》—— 張彥飛文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-539691.html

到了這里,關(guān)于深入理解Linux內(nèi)核網(wǎng)絡(luò)——內(nèi)核與用戶進(jìn)程協(xié)作之同步阻塞方案(BIO)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 深入理解 Linux 內(nèi)核

    深入理解 Linux 內(nèi)核

    Linux 內(nèi)核設(shè)計(jì)與實(shí)現(xiàn) 深入理解 Linux 內(nèi)核 深入理解 Linux 內(nèi)核(二) Linux 設(shè)備驅(qū)動(dòng)程序 Linux設(shè)備驅(qū)動(dòng)開(kāi)發(fā)詳解 ??本文主要用來(lái)摘錄《深入理解 Linux 內(nèi)核》一書(shū)中學(xué)習(xí)知識(shí)點(diǎn),本書(shū)基于 Linux 2.6.11 版本,源代碼摘錄基于 Linux 2.6.34 ,兩者之間可能有些出入。 ??可參考 ? 1、

    2023年04月27日
    瀏覽(28)
  • 深入理解Linux內(nèi)核--內(nèi)存尋址

    使用80x86微處理器時(shí),需區(qū)分三種不同地址。 1.邏輯地址,每一個(gè)邏輯地址都由一個(gè)段和偏移量組成。 2.線性地址(虛擬地址),如果是32位系統(tǒng),則位一個(gè)32位無(wú)符號(hào)整數(shù)??杀磉_(dá)高達(dá)4GB地址。 3.物理地址,用于內(nèi)存芯片級(jí)內(nèi)存單元尋址。與從微處理器的地址引腳發(fā)到內(nèi)存總

    2024年02月13日
    瀏覽(26)
  • 深入理解Linux 內(nèi)核追蹤機(jī)制

    深入理解Linux 內(nèi)核追蹤機(jī)制

    Linux 存在眾多 tracing tools,比如 ftrace、perf,他們可用于內(nèi)核的調(diào)試、提高內(nèi)核的可觀測(cè)性。眾多的工具也意味著繁雜的概念,諸如 tracepoint、trace events、kprobe、eBPF 等,甚至讓人搞不清楚他們到底是干什么的。本文嘗試?yán)砬暹@些概念。 ? Probe Handler 如果我們想要追蹤內(nèi)核的一

    2024年02月15日
    瀏覽(29)
  • 深入理解Linux內(nèi)核——內(nèi)存管理(2)

    深入理解Linux內(nèi)核——內(nèi)存管理(2)

    提要:本系列文章主要參考 MIT 6.828課程 以及兩本書(shū)籍 《深入理解Linux內(nèi)核》 《深入Linux內(nèi)核架構(gòu)》 對(duì)Linux內(nèi)核內(nèi)容進(jìn)行總結(jié)。 內(nèi)存管理的實(shí)現(xiàn)覆蓋了多個(gè)領(lǐng)域: 內(nèi)存中的物理內(nèi)存頁(yè)的管理 分配大塊內(nèi)存的伙伴系統(tǒng) 分配較小內(nèi)存的slab、slub、slob分配器 分配非連續(xù)內(nèi)存塊的

    2024年02月13日
    瀏覽(27)
  • 深入理解Linux內(nèi)核——內(nèi)存管理(1)

    深入理解Linux內(nèi)核——內(nèi)存管理(1)

    提要:本系列文章主要參考 MIT 6.828課程 以及兩本書(shū)籍 《深入理解Linux內(nèi)核》 《深入Linux內(nèi)核架構(gòu)》 對(duì)Linux內(nèi)核內(nèi)容進(jìn)行總結(jié)。 內(nèi)存管理的實(shí)現(xiàn)覆蓋了多個(gè)領(lǐng)域: 內(nèi)存中的物理內(nèi)存頁(yè)的管理 分配大塊內(nèi)存的伙伴系統(tǒng) 分配較小內(nèi)存的slab、slub、slob分配器 分配非連續(xù)內(nèi)存塊的

    2024年02月13日
    瀏覽(26)
  • 深入理解Linux內(nèi)核——內(nèi)存管理(3)

    深入理解Linux內(nèi)核——內(nèi)存管理(3)

    提要:本系列文章主要參考 MIT 6.828課程 以及兩本書(shū)籍 《深入理解Linux內(nèi)核》 《深入Linux內(nèi)核架構(gòu)》 對(duì)Linux內(nèi)核內(nèi)容進(jìn)行總結(jié)。 內(nèi)存管理的實(shí)現(xiàn)覆蓋了多個(gè)領(lǐng)域: 內(nèi)存中的物理內(nèi)存頁(yè)的管理 分配大塊內(nèi)存的伙伴系統(tǒng) 分配較小內(nèi)存的slab、slub、slob分配器 分配非連續(xù)內(nèi)存塊的

    2024年02月13日
    瀏覽(26)
  • 《深入Linux內(nèi)核架構(gòu)》第2章 進(jìn)程管理和調(diào)度 (2)

    《深入Linux內(nèi)核架構(gòu)》第2章 進(jìn)程管理和調(diào)度 (2)

    目錄 2.4 進(jìn)程管理相關(guān)的系統(tǒng)調(diào)用 2.4.1 進(jìn)程復(fù)制 2.4.2 內(nèi)核線程 2.4.3 啟動(dòng)新程序 2.4.4 退出進(jìn)程 本專欄文章將有70篇左右,歡迎+關(guān)注,訂閱后續(xù)文章。 1. _do_fork函數(shù) ????????fork vfork clone都最終調(diào)用_do_fork ????????????????clone:通過(guò)CLONE_XX標(biāo)志精確控制父子進(jìn)程共享哪

    2024年04月11日
    瀏覽(22)
  • 【深入理解Linux內(nèi)核鎖】三、原子操作

    系列文章 : 我的圈子:高級(jí)工程師聚集地 【深入理解Linux鎖機(jī)制】一、內(nèi)核鎖的由來(lái) 【深入理解Linux鎖機(jī)制】二、中斷屏蔽 【深入理解Linux鎖機(jī)制】三、原子操作 【深入理解Linux鎖機(jī)制】四、自旋鎖 【深入理解Linux鎖機(jī)制】五、衍生自旋鎖 【深入理解Linux鎖機(jī)制】六、信號(hào)

    2024年02月12日
    瀏覽(28)
  • 深入理解Linux內(nèi)核——內(nèi)存管理(4)——伙伴系統(tǒng)(1)

    深入理解Linux內(nèi)核——內(nèi)存管理(4)——伙伴系統(tǒng)(1)

    提要:本系列文章主要參考 MIT 6.828課程 以及兩本書(shū)籍 《深入理解Linux內(nèi)核》 《深入Linux內(nèi)核架構(gòu)》 對(duì)Linux內(nèi)核內(nèi)容進(jìn)行總結(jié)。 內(nèi)存管理的實(shí)現(xiàn)覆蓋了多個(gè)領(lǐng)域: 內(nèi)存中的物理內(nèi)存頁(yè)的管理 分配大塊內(nèi)存的伙伴系統(tǒng) 分配較小內(nèi)存的slab、slub、slob分配器 分配非連續(xù)內(nèi)存塊的

    2024年02月10日
    瀏覽(22)
  • 【Linux】深入理解進(jìn)程概念

    【Linux】深入理解進(jìn)程概念

    個(gè)人主頁(yè):??在肯德基吃麻辣燙 我的gitee:Linux倉(cāng)庫(kù) 個(gè)人專欄:Linux專欄 分享一句喜歡的話:熱烈的火焰,冰封在最沉默的火山深處 本篇文章繼上一篇的馮諾依曼體系結(jié)構(gòu)和操作系統(tǒng)這兩個(gè)軟硬件的概念之后,開(kāi)始講解Linux下 三座大山 之一的 進(jìn)程 。 因?yàn)檫M(jìn)程概念雖然不是

    2024年02月14日
    瀏覽(18)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包