一区二区三区三上|欧美在线视频五区|国产午夜无码在线观看视频|亚洲国产裸体网站|无码成年人影视|亚洲AV亚洲AV|成人开心激情五月|欧美性爱内射视频|超碰人人干人人上|一区二区无码三区亚洲人区久久精品

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線(xiàn)課程
  • 觀(guān)看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

深入分析同步阻塞網(wǎng)絡(luò)IO的內(nèi)部實(shí)現(xiàn)詳解

Linux愛(ài)好者 ? 來(lái)源:CSDN技術(shù)社區(qū) ? 作者:zhangyanfei01 ? 2021-04-03 14:10 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

在網(wǎng)絡(luò)開(kāi)發(fā)模型中,有一種非常易于開(kāi)發(fā)同學(xué)使用的方式,那就是同步阻塞的網(wǎng)絡(luò) IO(在 Java 中習(xí)慣叫 BIO)。

例如我們想請(qǐng)求服務(wù)器上的一段數(shù)據(jù),那么 C 語(yǔ)言的一段代碼 demo 大概是下面這樣:

int main()

{

int sk = socket(AF_INET, SOCK_STREAM, 0);

connect(sk, 。..)

recv(sk, 。..)

}

但是在高并發(fā)的服務(wù)器開(kāi)發(fā)中,這種網(wǎng)絡(luò) IO 的性能奇差。因?yàn)?/p>

1.進(jìn)程在 recv 的時(shí)候大概率會(huì)被阻塞掉,導(dǎo)致一次進(jìn)程切換

2.當(dāng)連接上數(shù)據(jù)就緒的時(shí)候進(jìn)程又會(huì)被喚醒,又是一次進(jìn)程切換

3.一個(gè)進(jìn)程同時(shí)只能等待一條連接,如果有很多并發(fā),則需要很多進(jìn)程

如果用一句話(huà)來(lái)概括,那就是:同步阻塞網(wǎng)絡(luò) IO 是高性能網(wǎng)絡(luò)開(kāi)發(fā)路上的絆腳石! 俗話(huà)說(shuō)得好,知己知彼方能百戰(zhàn)百勝。所以我們今天先不講優(yōu)化,只深入分析同步阻塞網(wǎng)絡(luò) IO 的內(nèi)部實(shí)現(xiàn)。

在上面的 demo 中雖然只是簡(jiǎn)單的兩三行代碼,但實(shí)際上用戶(hù)進(jìn)程和內(nèi)核配合做了非常多的工作。先是用戶(hù)進(jìn)程發(fā)起創(chuàng)建 socket 的指令,然后切換到內(nèi)核態(tài)完成了內(nèi)核對(duì)象的初始化。接下來(lái) Linux 在數(shù)據(jù)包的接收上,是硬中斷和 ksoftirqd 進(jìn)程在進(jìn)行處理。當(dāng) ksoftirqd 進(jìn)程處理完以后,再通知到相關(guān)的用戶(hù)進(jìn)程。

從用戶(hù)進(jìn)程創(chuàng)建 socket,到一個(gè)網(wǎng)絡(luò)包抵達(dá)網(wǎng)卡到被用戶(hù)進(jìn)程接收到,總體上的流程圖如下:

128256d8-8d80-11eb-8b86-12bb97331649.png

我們今天用圖解加源碼分析的方式來(lái)詳細(xì)拆解一下上面的每一個(gè)步驟,來(lái)看一下在內(nèi)核里是它們是怎么實(shí)現(xiàn)的。閱讀完本文,你將深刻地理解在同步阻塞的網(wǎng)絡(luò) IO 性能低下的原因!

一、創(chuàng)建一個(gè) socket

開(kāi)篇源碼中的 socket 函數(shù)調(diào)用執(zhí)行完以后,內(nèi)核在內(nèi)部創(chuàng)建了一系列的 socket 相關(guān)的內(nèi)核對(duì)象(是的,不是只有一個(gè))。它們互相之間的關(guān)系如圖。當(dāng)然了,這個(gè)對(duì)象比圖示的還要更復(fù)雜。我只在圖中把和今天的主題相關(guān)的內(nèi)容展現(xiàn)了出來(lái)。

12cd7ed8-8d80-11eb-8b86-12bb97331649.png

我們來(lái)翻翻源碼,看下上面的結(jié)構(gòu)是如何被創(chuàng)造出來(lái)的。

//file:net/socket.c

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)

{

。..。..

retval = sock_create(family, type, protocol, &sock);

}

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

//file:net/socket.c

int __sock_create(struct net *net, int family, int type, int protocol,

struct socket **res, int kern)

{

struct socket *sock;

const struct net_proto_family *pf;

。..。..

//分配 socket 對(duì)象

sock = sock_alloc();

//獲得每個(gè)協(xié)議族的操作表

pf = rcu_dereference(net_families[family]);

//調(diào)用每個(gè)協(xié)議族的創(chuàng)建函數(shù), 對(duì)于 AF_INET 對(duì)應(yīng)的是

err = pf-》create(net, sock, protocol, kern);

}

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

//file:net/ipv4/af_inet.c

tatic int inet_create(struct net *net, struct socket *sock, int protocol,

int kern)

{

struct sock *sk;

//查找對(duì)應(yīng)的協(xié)議,對(duì)于TCP SOCK_STREAM 就是獲取到了

//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,

// },

//}

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 賦到 sock-》sk_prot 上

sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);

//對(duì) sock 對(duì)象進(jìn)行初始化

sock_init_data(sock, sk);

}

在 inet_create 中,根據(jù)類(lèi)型 SOCK_STREAM 查找到對(duì)于 tcp 定義的操作方法實(shí)現(xiàn)集合 inet_stream_ops 和 tcp_prot。并把它們分別設(shè)置到 socket-》ops 和 sock-》sk_prot 上。

12efa35a-8d80-11eb-8b86-12bb97331649.png

我們?cè)偻驴吹搅?sock_init_data。在這個(gè)方法中將 sock 中的 sk_data_ready 函數(shù)指針進(jìn)行了初始化,設(shè)置為默認(rèn) sock_def_readable()。

130b41e6-8d80-11eb-8b86-12bb97331649.png

//file: net/core/sock.c

void sock_init_data(struct socket *sock, struct sock *sk)

{

sk-》sk_data_ready = sock_def_readable;

sk-》sk_write_space = sock_def_write_space;

sk-》sk_error_report = sock_def_error_report;

}

當(dāng)軟中斷上收到數(shù)據(jù)包時(shí)會(huì)通過(guò)調(diào)用 sk_data_ready 函數(shù)指針(實(shí)際被設(shè)置成了 sock_def_readable()) 來(lái)喚醒在 sock 上等待的進(jìn)程。這個(gè)咱們后面介紹軟中斷的時(shí)候再說(shuō),這里記住這個(gè)就行了。

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

二、等待接收消息

接著我們來(lái)看 recv 函數(shù)依賴(lài)的底層實(shí)現(xiàn)。首先通過(guò) strace 命令跟蹤,可以看到 clib 庫(kù)函數(shù) recv 會(huì)執(zhí)行到 recvfrom 系統(tǒng)調(diào)用。

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

1339a86a-8d80-11eb-8b86-12bb97331649.png

看完了整個(gè)流程圖,接下來(lái)讓我們根據(jù)源碼來(lái)看更詳細(xì)的細(xì)節(jié)。其中我們今天要關(guān)注的重點(diǎn)是 recvfrom 最后是怎么把自己的進(jìn)程給阻塞掉的(假如我們沒(méi)有使用 O_NONBLOCK 標(biāo)記)。

//file: net/socket.c

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ù)用戶(hù)傳入的 fd 找到 socket 對(duì)象

sock = sockfd_lookup_light(fd, &err, &fput_needed);

。..。..

err = sock_recvmsg(sock, &msg, size, flags);

。..。..

}

sock_recvmsg ==》 __sock_recvmsg =》 __sock_recvmsg_nosec

static inline int __sock_recvmsg_nosec(struct kiocb *iocb, struct socket *sock,

struct msghdr *msg, size_t size, int flags)

{

。..。..

return sock-》ops-》recvmsg(iocb, sock, msg, size, flags);

}

調(diào)用 socket 對(duì)象 ops 里的 recvmsg, 回憶我們上面的 socket 對(duì)象圖,從圖中可以看到 recvmsg 指向的是 inet_recvmsg 方法。

13682686-8d80-11eb-8b86-12bb97331649.png

//file: net/ipv4/af_inet.c

int inet_recvmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,

size_t size, int flags)

{

。..

err = sk-》sk_prot-》recvmsg(iocb, sk, msg, size, flags & MSG_DONTWAIT,

flags & ~MSG_DONTWAIT, &addr_len);

這里又遇到一個(gè)函數(shù)指針,這次調(diào)用的是 socket 對(duì)象里的 sk_prot 下面的 recvmsg方法。同上,得出這個(gè) recvmsg 方法對(duì)應(yīng)的是 tcp_recvmsg 方法。

//file: net/ipv4/tcp.c

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 {

//遍歷接收隊(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);

}

終于看到了我們想要看的東西,skb_queue_walk 是在訪(fǎng)問(wèn) sock 對(duì)象下面的接收隊(duì)列了。

139df7c0-8d80-11eb-8b86-12bb97331649.png

如果沒(méi)有收到數(shù)據(jù),或者收到不足夠多,則調(diào)用 sk_wait_data 把當(dāng)前進(jìn)程阻塞掉。

//file: net/core/sock.c

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è)置為可打斷 INTERRUPTIBLE

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));

。..

我們?cè)賮?lái)詳細(xì)看下 sk_wait_data 是怎么把當(dāng)前進(jìn)程給阻塞掉的。

13c95528-8d80-11eb-8b86-12bb97331649.png

首先在 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成員上。

//file: include/linux/wait.h

#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),

}

緊接著在 sk_wait_data 中 調(diào)用 sk_sleep 獲取 sock 對(duì)象下的等待隊(duì)列列表頭 wait_queue_head_t。sk_sleep 源代碼如下:

//file: include/net/sock.h

static inline wait_queue_head_t *sk_sleep(struct sock *sk)

{

BUILD_BUG_ON(offsetof(struct socket_wq, wait) != 0);

return &rcu_dereference_raw(sk-》sk_wq)-》wait;

}

接著調(diào)用 prepare_to_wait 來(lái)把新定義的等待隊(duì)列項(xiàng) wait 插入到 sock 對(duì)象的等待隊(duì)列下。

//file: kernel/wait.c

void

prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)

{

unsigned long flags;

wait-》flags &= ~WQ_FLAG_EXCLUSIVE;

spin_lock_irqsave(&q-》lock, flags);

if (list_empty(&wait-》task_list))

__add_wait_queue(q, wait);

set_current_state(state);

spin_unlock_irqrestore(&q-》lock, flags);

}

這樣后面當(dāng)內(nèi)核收完數(shù)據(jù)產(chǎn)生就緒時(shí)間的時(shí)候,就可以查找 socket 等待隊(duì)列上的等待項(xiàng),進(jìn)而就可以找到回調(diào)函數(shù)和在等待該 socket 就緒事件的進(jìn)程了。

最后再調(diào)用 sk_wait_event 讓出 CPU,進(jìn)程將進(jìn)入睡眠狀態(tài),這會(huì)導(dǎo)致一次進(jìn)程上下文的開(kāi)銷(xiāo)。

接下來(lái)的小節(jié)里我們將能看到進(jìn)程是如何被喚醒的了。

三、軟中斷模塊

接著我們?cè)俎D(zhuǎn)換一下視角,來(lái)看負(fù)責(zé)接收和處理數(shù)據(jù)包的軟中斷這邊。關(guān)于網(wǎng)絡(luò)包到網(wǎng)卡后是怎么被網(wǎng)卡接收,最后在交由軟中斷處理的,這里就不多贅述了。感興趣的請(qǐng)看之前的文章《圖解Linux網(wǎng)絡(luò)包接收過(guò)程》。我們今天直接從 tcp 協(xié)議的接收函數(shù) tcp_v4_rcv 看起。

13efd5ea-8d80-11eb-8b86-12bb97331649.png

軟中斷(也就是 Linux 里的 ksoftirqd 進(jìn)程)里收到數(shù)據(jù)包以后,發(fā)現(xiàn)是 tcp 的包的話(huà)就會(huì)執(zhí)行到 tcp_v4_rcv 函數(shù)。接著走,如果是 ESTABLISH 狀態(tài)下的數(shù)據(jù)包,則最終會(huì)把數(shù)據(jù)拆出來(lái)放到對(duì)應(yīng) socket 的接收隊(duì)列中。然后調(diào)用 sk_data_ready 來(lái)喚醒用戶(hù)進(jìn)程。

我們看更詳細(xì)一點(diǎn)的代碼:

// file: net/ipv4/tcp_ipv4.c

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 未被用戶(hù)鎖定

if (!sock_owned_by_user(sk)) {

{

if (!tcp_prequeue(sk, skb))

ret = tcp_v4_do_rcv(sk, skb);

}

}

}

在 tcp_v4_rcv 中首先根據(jù)收到的網(wǎng)絡(luò)包的 header 里的 source 和 dest 信息來(lái)在本機(jī)上查詢(xún)對(duì)應(yīng)的 socket。找到以后,我們直接進(jìn)入接收的主體函數(shù) tcp_v4_do_rcv 來(lái)看。

//file: net/ipv4/tcp_ipv4.c

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;

}

//其它非 ESTABLISH 狀態(tài)的數(shù)據(jù)包處理

。..。..

}

我們假設(shè)處理的是 ESTABLISH 狀態(tài)下的包,這樣就又進(jìn)入 tcp_rcv_established 函數(shù)中進(jìn)行處理。

//file: net/ipv4/tcp_input.c

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ù) ready,喚醒 socket 上阻塞掉的進(jìn)程

sk-》sk_data_ready(sk, 0);

在 tcp_rcv_established 中通過(guò)調(diào)用 tcp_queue_rcv 函數(shù)中完成了將接收數(shù)據(jù)放到 socket 的接收隊(duì)列上。

1438e4a6-8d80-11eb-8b86-12bb97331649.png

如下源碼所示

//file: net/ipv4/tcp_input.c

static int __must_check tcp_queue_rcv(struct sock *sk, struct sk_buff *skb, int hdrlen,

bool *fragstolen)

{

//把接收到的數(shù)據(jù)放到 socket 的接收隊(duì)列的尾部

if (!eaten) {

__skb_queue_tail(&sk-》sk_receive_queue, skb);

skb_set_owner_r(skb, sk);

}

return eaten;

}

調(diào)用 tcp_queue_rcv 接收完成之后,接著再調(diào)用 sk_data_ready 來(lái)喚醒在socket上等待的用戶(hù)進(jìn)程。 這又是一個(gè)函數(shù)指針?;叵肷厦嫖覀?cè)?創(chuàng)建 socket 流程里執(zhí)行到的 sock_init_data 函數(shù),在這個(gè)函數(shù)里已經(jīng)把 sk_data_ready 設(shè)置成 sock_def_readable 函數(shù)了(可以ctrl + f 搜索前文)。它是默認(rèn)的數(shù)據(jù)就緒處理函數(shù)。

//file: net/core/sock.c

static void sock_def_readable(struct sock *sk, int len)

{

struct socket_wq *wq;

rcu_read_lock();

wq = rcu_dereference(sk-》sk_wq);

//有進(jìn)程在此 socket 的等待隊(duì)列

if (wq_has_sleeper(wq))

//喚醒等待隊(duì)列上的進(jìn)程

wake_up_interruptible_sync_poll(&wq-》wait, POLLIN | POLLPRI |

POLLRDNORM | POLLRDBAND);

sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_IN);

rcu_read_unlock();

}

在 sock_def_readable 中再一次訪(fǎng)問(wèn)到了 sock-》sk_wq 下的wait。回憶下我們前面調(diào)用 recvfrom 執(zhí)行的最后,通過(guò) DEFINE_WAIT(wait) 將當(dāng)前進(jìn)程關(guān)聯(lián)的等待隊(duì)列添加到 sock-》sk_wq 下的 wait 里了。

那接下來(lái)就是調(diào)用 wake_up_interruptible_sync_poll 來(lái)喚醒在 socket 上因?yàn)榈却龜?shù)據(jù)而被阻塞掉的進(jìn)程了。

14726a5a-8d80-11eb-8b86-12bb97331649.png

//file: include/linux/wait.h

#define wake_up_interruptible_sync_poll(x, m)

__wake_up_sync_key((x), TASK_INTERRUPTIBLE, 1, (void *) (m))

//file: kernel/sched/core.c

void __wake_up_sync_key(wait_queue_head_t *q, unsigned int mode,

int nr_exclusive, void *key)

{

unsigned long flags;

int wake_flags = WF_SYNC;

if (unlikely(!q))

return;

if (unlikely(!nr_exclusive))

wake_flags = 0;

spin_lock_irqsave(&q-》lock, flags);

__wake_up_common(q, mode, nr_exclusive, wake_flags, key);

spin_unlock_irqrestore(&q-》lock, flags);

}

__wake_up_common 實(shí)現(xiàn)喚醒。這里注意下, 該函數(shù)調(diào)用是參數(shù) nr_exclusive 傳入的是 1,這里指的是即使是有多個(gè)進(jìn)程都阻塞在同一個(gè) socket 上,也只喚醒 1 個(gè)進(jìn)程。其作用是為了避免驚群。

//file: kernel/sched/core.c

static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,

int nr_exclusive, int wake_flags, void *key)

{

wait_queue_t *curr, *next;

list_for_each_entry_safe(curr, next, &q-》task_list, task_list) {

unsigned flags = curr-》flags;

if (curr-》func(curr, mode, wake_flags, key) &&

(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)

break;

}

}

在 __wake_up_common 中找出一個(gè)等待隊(duì)列項(xiàng) curr,然后調(diào)用其 curr-》func?;貞浳覀兦懊嬖?recv 函數(shù)執(zhí)行的時(shí)候,使用 DEFINE_WAIT() 定義等待隊(duì)列項(xiàng)的細(xì)節(jié),內(nèi)核把 curr-》func 設(shè)置成了 autoremove_wake_function。

//file: include/linux/wait.h

#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),

}

在 autoremove_wake_function 中,調(diào)用了 default_wake_function。

//file: 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);

}

調(diào)用 try_to_wake_up 時(shí)傳入的 task_struct 是 curr-》private。這個(gè)就是當(dāng)時(shí)因?yàn)榈却蛔枞倪M(jìn)程項(xiàng)。當(dāng)這個(gè)函數(shù)執(zhí)行完的時(shí)候,在 socket 上等待而被阻塞的進(jìn)程就被推入到可運(yùn)行隊(duì)列里了,這又將是一次進(jìn)程上下文切換的開(kāi)銷(xiāo)。

小結(jié)

好了,我們把上面的流程總結(jié)一下。內(nèi)核在通知網(wǎng)絡(luò)包的運(yùn)行環(huán)境分兩部分:

第一部分是我們自己代碼所在的進(jìn)程,我們調(diào)用的 socket() 函數(shù)會(huì)進(jìn)入內(nèi)核態(tài)創(chuàng)建必要內(nèi)核對(duì)象。recv() 函數(shù)在進(jìn)入內(nèi)核態(tài)以后負(fù)責(zé)查看接收隊(duì)列,以及在沒(méi)有數(shù)據(jù)可處理的時(shí)候把當(dāng)前進(jìn)程阻塞掉,讓出 CPU。

第二部分是硬中斷、軟中斷上下文(系統(tǒng)進(jìn)程 ksoftirqd)。在這些組件中,將包處理完后會(huì)放到 socket 的接收隊(duì)列中。然后再根據(jù) socket 內(nèi)核對(duì)象找到其等待隊(duì)列中正在因?yàn)榈却蛔枞舻倪M(jìn)程,然后把它喚醒。

1493d35c-8d80-11eb-8b86-12bb97331649.png

每次一個(gè)進(jìn)程專(zhuān)門(mén)為了等一個(gè) socket 上的數(shù)據(jù)就得被從 CPU 上拿下來(lái)。然后再換上另一個(gè)進(jìn)程。等到數(shù)據(jù) ready 了,睡眠的進(jìn)程又會(huì)被喚醒??偣矁纱芜M(jìn)程上下文切換開(kāi)銷(xiāo),根據(jù)之前的測(cè)試來(lái)看,每一次切換大約是 3-5 us(微秒)左右。如果是網(wǎng)絡(luò) IO 密集型的應(yīng)用的話(huà),CPU 就不停地做進(jìn)程切換這種無(wú)用功。

在服務(wù)端角色上,這種模式完全沒(méi)辦法使用。因?yàn)檫@種簡(jiǎn)單模型里的 socket 和進(jìn)程是一對(duì)一的。我們現(xiàn)在要在單臺(tái)機(jī)器上承載成千上萬(wàn),甚至十幾、上百萬(wàn)的用戶(hù)連接請(qǐng)求。如果用上面的方式,那就得為每個(gè)用戶(hù)請(qǐng)求都創(chuàng)建一個(gè)進(jìn)程。相信你在無(wú)論多原始的服務(wù)器網(wǎng)絡(luò)編程里,都沒(méi)見(jiàn)過(guò)有人這么干吧。

如果讓我給它起一個(gè)名字的話(huà),它就叫單路不復(fù)用(飛哥自創(chuàng)名詞)。那么有沒(méi)有更高效的網(wǎng)絡(luò) IO 模型呢?當(dāng)然有,那就是你所熟知的 select、poll 和 epoll了。下次飛哥再開(kāi)始拆解 epoll 的實(shí)現(xiàn)源碼,敬請(qǐng)期待!

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

注意一下,我說(shuō)的是角色,不是具體的機(jī)器。例如對(duì)于你的 php/java/golang 接口機(jī),你接收用戶(hù)請(qǐng)求的時(shí)候,你是服務(wù)端角色。但當(dāng)你再請(qǐng)求 redis 的時(shí)候,就變?yōu)榭蛻?hù)端角色了。

不過(guò)現(xiàn)在有一些封裝的很好的網(wǎng)絡(luò)框架例如 Sogou Workflow,Golang 的 net 包等在網(wǎng)絡(luò)客戶(hù)端角色上也早已摒棄了這種低效的模式!
編輯:lyn

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀(guān)點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • Socket
    +關(guān)注

    關(guān)注

    1

    文章

    212

    瀏覽量

    35859
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4900

    瀏覽量

    70731
  • BIO
    BIO
    +關(guān)注

    關(guān)注

    0

    文章

    6

    瀏覽量

    9464
  • C 語(yǔ)言
    +關(guān)注

    關(guān)注

    0

    文章

    18

    瀏覽量

    14334
  • 網(wǎng)絡(luò)開(kāi)發(fā)

    關(guān)注

    0

    文章

    13

    瀏覽量

    7917

原文標(biāo)題:圖解:深入理解高性能網(wǎng)絡(luò)開(kāi)發(fā)路上的絆腳石,同步阻塞網(wǎng)絡(luò) IO

文章出處:【微信號(hào):LinuxHub,微信公眾號(hào):Linux愛(ài)好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    深入分析小智AI現(xiàn)象級(jí)項(xiàng)目背后的成功密碼

    創(chuàng)新技術(shù),實(shí)現(xiàn)了從"冰冷機(jī)器"到"情感伙伴"的跨越。更重要的是,其開(kāi)源戰(zhàn)略不僅降低了AI硬件的開(kāi)發(fā)門(mén)檻,還構(gòu)建了一個(gè)活躍的全球開(kāi)發(fā)者生態(tài),催生出超過(guò)30萬(wàn)臺(tái)硬件設(shè)備接入,日對(duì)話(huà)量突破90萬(wàn)條的驚人成績(jī)。本文將深入分析小智AI的技術(shù)
    的頭像 發(fā)表于 07-02 10:54 ?1548次閱讀

    分布式IO選型指南:2025年分布式無(wú)線(xiàn)遠(yuǎn)程IO品牌及采集控制方案詳解

    近年來(lái),隨著工業(yè)物聯(lián)網(wǎng)(IIoT)、智能制造和工業(yè)4.0的深入發(fā)展,分布式無(wú)線(xiàn)遠(yuǎn)程IO模塊在工業(yè)控制領(lǐng)域的應(yīng)用愈發(fā)廣泛。這種模塊通過(guò)無(wú)線(xiàn)方式實(shí)現(xiàn)遠(yuǎn)程數(shù)據(jù)采集與控制,極大地提高了工業(yè)設(shè)施的靈活性和效率
    的頭像 發(fā)表于 06-23 09:48 ?218次閱讀

    2025全球分布式無(wú)線(xiàn)IO模塊品牌盤(pán)點(diǎn):十大領(lǐng)先品牌市場(chǎng)分析

    隨著工業(yè)自動(dòng)化、物聯(lián)網(wǎng)和智能制造的深入發(fā)展,分布式無(wú)線(xiàn)IO模塊以其靈活性和高效性成為工業(yè)控制系統(tǒng)的重要組成部分。這種模塊通過(guò)無(wú)線(xiàn)方式實(shí)現(xiàn)數(shù)據(jù)采集、傳輸和控制,廣泛應(yīng)用于工業(yè)自動(dòng)化、智慧城市、能源管理
    的頭像 發(fā)表于 06-20 10:17 ?372次閱讀

    安徽京準(zhǔn):北斗衛(wèi)星同步時(shí)鐘的安裝與調(diào)試詳解

    安徽京準(zhǔn):北斗衛(wèi)星同步時(shí)鐘的安裝與調(diào)試詳解
    的頭像 發(fā)表于 06-05 10:08 ?429次閱讀
    安徽京準(zhǔn):北斗衛(wèi)星<b class='flag-5'>同步</b>時(shí)鐘的安裝與調(diào)試<b class='flag-5'>詳解</b>

    VirtualLab應(yīng)用:亞波長(zhǎng)結(jié)構(gòu)偏振光柵的深入分析

    線(xiàn)柵偏振器 組件內(nèi)部光場(chǎng)分析儀: FMM 演示了一種分析器,它允許計(jì)算通過(guò)光柵組件傳播的光場(chǎng)。為此目的,F(xiàn)MM是要采用不同形狀的周期結(jié)構(gòu)。 利用傅里葉模態(tài)法(FMM,也稱(chēng)為RCWA)分析
    發(fā)表于 05-26 08:45

    電機(jī)學(xué)教程

    電機(jī)學(xué)的學(xué)習(xí)對(duì)象以四大電機(jī),即變壓器、直流電機(jī)、異步電機(jī)和同步電機(jī)為主,其總的學(xué)習(xí)方法和步驟是一致的,其可概括如下: (1)了解或熟悉各類(lèi)電機(jī)的基本結(jié)構(gòu)及其他總體性的概貌情況: (2)深入分析內(nèi)部
    發(fā)表于 05-12 14:50

    VirtualLab Fusion應(yīng)用:亞波長(zhǎng)結(jié)構(gòu)偏振光柵的深入分析

    的極化行為:tm偏振部分幾乎不受影響,而te偏振部分幾乎完全反射。這個(gè)分析是由VirtualLab Fusion的組件內(nèi)光場(chǎng)分析儀: FMM。 超稀疏介質(zhì)納米線(xiàn)柵偏振器 組件內(nèi)部光場(chǎng)分析
    發(fā)表于 04-28 10:09

    在testbench中如何使用阻塞賦值和非阻塞賦值

    本文詳細(xì)闡述了在一個(gè)testbench中,應(yīng)該如何使用阻塞賦值與非阻塞賦值。首先說(shuō)結(jié)論,建議在testbench中,對(duì)時(shí)鐘信號(hào)(包括分頻時(shí)鐘)使用阻塞賦值,對(duì)其他同步信號(hào)使用非
    的頭像 發(fā)表于 04-15 09:34 ?654次閱讀
    在testbench中如何使用<b class='flag-5'>阻塞</b>賦值和非<b class='flag-5'>阻塞</b>賦值

    VirtualLab Fusion應(yīng)用:亞波長(zhǎng)結(jié)構(gòu)偏振光柵的深入分析

    線(xiàn)柵偏振器 組件內(nèi)部光場(chǎng)分析儀: FMM 演示了一種分析器,它允許計(jì)算通過(guò)光柵組件傳播的光場(chǎng)。為此目的,F(xiàn)MM是要采用不同形狀的周期結(jié)構(gòu)。 利用傅里葉模態(tài)法(FMM,也稱(chēng)為RCWA)分析
    發(fā)表于 03-28 08:55

    時(shí)域網(wǎng)絡(luò)分析儀的原理和應(yīng)用場(chǎng)景

    進(jìn)行計(jì)算。 頻域/時(shí)域轉(zhuǎn)換:網(wǎng)絡(luò)分析儀通過(guò)FFT(快速傅里葉變換)和CZT(線(xiàn)性調(diào)頻Z變換)實(shí)現(xiàn)時(shí)域到頻域的轉(zhuǎn)換,從而能夠獲取被測(cè)器件在時(shí)域上的響應(yīng)特性。 應(yīng)用場(chǎng)景 軍用電子裝備:在相控陣?yán)走_(dá)、精確
    發(fā)表于 01-13 16:03

    電流倒灌揭秘:IO口損壞與系統(tǒng)故障的真相

    不開(kāi)機(jī)或休眠及喚醒異常。這些問(wèn)題雖然聽(tīng)起來(lái)頗為技術(shù)性,但它們對(duì)嵌入式系統(tǒng)的正常運(yùn)行至關(guān)重要。本期,我們將深入分析這些現(xiàn)象背后的原因。IO口損壞IO端口可分為高阻、三
    的頭像 發(fā)表于 12-11 11:38 ?1105次閱讀
    電流倒灌揭秘:<b class='flag-5'>IO</b>口損壞與系統(tǒng)故障的真相

    一文解讀Linux 5種IO模型

    Linux里有五種IO模型:阻塞IO、非阻塞IO、多路復(fù)用IO、信號(hào)驅(qū)動(dòng)式
    的頭像 發(fā)表于 11-09 11:12 ?857次閱讀
    一文解讀Linux 5種<b class='flag-5'>IO</b>模型

    socket編程中的阻塞與非阻塞

    網(wǎng)絡(luò)編程中, socket 是一個(gè)非常重要的概念,它提供了一個(gè)抽象層,使得開(kāi)發(fā)者可以不必關(guān)心底層的網(wǎng)絡(luò)通信細(xì)節(jié)。 socket 編程中的阻塞與非阻塞模式是兩種不同的操作方式,它們對(duì)程
    的頭像 發(fā)表于 11-01 16:13 ?749次閱讀

    SystemView上下文統(tǒng)計(jì)窗口識(shí)別阻塞原因

    SystemView工具可以記錄嵌入式系統(tǒng)的運(yùn)行時(shí)行為,實(shí)現(xiàn)可視化的深入分析。在新發(fā)布的v3.54版本中,增加了一項(xiàng)新功能:上下文統(tǒng)計(jì)窗口,提供了對(duì)任務(wù)運(yùn)行時(shí)統(tǒng)計(jì)信息的深入分析,使用戶(hù)能夠徹底檢查每個(gè)任務(wù),幫助開(kāi)發(fā)人員識(shí)別
    的頭像 發(fā)表于 08-20 11:31 ?701次閱讀

    socket阻塞和非阻塞的區(qū)別是什么

    在計(jì)算機(jī)編程中,socket 是一種通信端點(diǎn),用于在網(wǎng)絡(luò)中進(jìn)行數(shù)據(jù)傳輸。Socket 可以是阻塞的或非阻塞的,這兩種模式在處理數(shù)據(jù)傳輸時(shí)有不同的行為。 阻塞模式(Blocking Mo
    的頭像 發(fā)表于 08-16 11:13 ?1233次閱讀