日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
一文讀懂零拷貝技術(shù)

splice 原理重溫

我們先來回顧一下 splice 的原理:

創(chuàng)新互聯(lián)專注于尚志網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠為您提供尚志營銷型網(wǎng)站建設(shè),尚志網(wǎng)站制作、尚志網(wǎng)頁設(shè)計(jì)、尚志網(wǎng)站官網(wǎng)定制、重慶小程序開發(fā)服務(wù),打造尚志網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供尚志網(wǎng)站排名全網(wǎng)營銷落地服務(wù)。

如上圖所示,使用 splice? 拷貝數(shù)據(jù)時(shí),需要通過管道作為中轉(zhuǎn)。splice? 首先將 頁緩存? 綁定到 管道? 的寫端,然后通過 管道? 的讀端讀取到 頁緩存? 的數(shù)據(jù),并且拷貝到 socket 緩沖區(qū)中。

管道有個(gè) 環(huán)形緩沖區(qū)?,這個(gè) 環(huán)形緩沖區(qū)? 需要綁定真實(shí)的物理內(nèi)存頁。而 splice 就是將管道的 環(huán)形緩沖區(qū)? 綁定到文件的 頁緩存,如下圖所示:

通過將文件頁緩存綁定到管道的環(huán)形緩沖區(qū)后,就可以通過管道的讀端讀取文件頁緩存的數(shù)據(jù)。

splice 代碼實(shí)現(xiàn)

splice 的使用過程,要將文件內(nèi)容發(fā)送到客戶端連接的步驟如下:

首先,使用splice() 系統(tǒng)調(diào)用將文件的內(nèi)容與管道綁定。

然后,使用splice() 系統(tǒng)調(diào)用將管道的數(shù)據(jù)拷貝到客戶端連接 socket。

我們先來看看 splice() 系統(tǒng)調(diào)用的實(shí)現(xiàn),代碼如下:

asmlinkage 
( fd_in, *off_in,
fd_out, *off_out,
len, flags)
{
error;
*, *;
fput_in, fput_out;
...

error = -EBADF;
in = fget_light(fd_in, &fput_in); // 1. 獲取數(shù)據(jù)輸入方文件對象
(in) {
(in->f_mode & FMODE_READ) {
out = fget_light(fd_out, &fput_out); // 2. 獲取數(shù)據(jù)輸出方文件對象
(out) {
(out->f_mode & FMODE_WRITE)
// 3. 調(diào)用 do_splice() 函數(shù)進(jìn)行下一步操作
error = do_splice(in, off_in, out, off_out, len, flags);

fput_light(out, fput_out);
}
}

fput_light(in, fput_in);
}

error;
}

splice()? 系統(tǒng)調(diào)用主要調(diào)用 do_splice()? 函數(shù)進(jìn)行下一步處理,我們來分析一下 do_splice()? 函數(shù)的實(shí)現(xiàn)。do_splice() 函數(shù)主要分兩種情況進(jìn)行處理,代碼如下:

(struct file *in,  *off_in, 
struct file *out, *off_out,
len, flags)
{
*;
offset, *off;
ret;

// 情況1: 如果輸入端是一個(gè)管道?
pipe = pipe_info(in->f_path.dentry->d_inode);
(pipe) {
...
// 調(diào)用 do_splice_from() 函數(shù)管道數(shù)據(jù)拷貝到目標(biāo)文件句柄
ret = do_splice_from(pipe, out, off, len, flags);
...
ret;
}

// 情況2: 如果輸出端是一個(gè)管道?
pipe = pipe_info(out->f_path.dentry->d_inode);
(pipe) {
...
// 調(diào)用 do_splice_to() 函數(shù)將文件內(nèi)容與管道綁定
ret = do_splice_to(in, off, pipe, len, flags);
...
ret;
}

-EINVAL;
}

如上面代碼所示,do_splice() 函數(shù)分兩種情況處理,如下:

如果輸入端是一個(gè)管道,則調(diào)用do_splice_from() 函數(shù)進(jìn)行處理。

如果輸出端是一個(gè)管道,則調(diào)用do_splice_to() 函數(shù)進(jìn)行處理。

下面我們分別來說明這兩種情況的處理過程。

1. 輸入端是一個(gè)管道

如果輸入端是一個(gè)管道(也就是說從管道拷貝數(shù)據(jù)到輸出端句柄),那么將會調(diào)用 do_splice_from()? 函數(shù)進(jìn)行處理,do_splice_from() 函數(shù)的實(shí)現(xiàn)如下:

(struct pipe_inode_info *pipe, struct file *out,
*ppos, len, flags)
{
...
out->f_op->splice_write(pipe, out, ppos, len, flags);
}

如果輸出端是一個(gè)普通文件,那么 out->f_op->splice_write()? 將會指向 generic_file_splice_write()? 函數(shù)。如果輸出端是一個(gè) socket,那么 out->f_op->splice_write()? 將會指向 generic_splice_sendpage() 函數(shù)。

下面將以 generic_file_splice_write()? 函數(shù)作為分析對象,generic_file_splice_write()? 函數(shù)會調(diào)用 __splice_from_pipe() 進(jìn)行下一步處理,如下所示:

(struct pipe_inode_info *pipe, struct file *out,
*ppos, len, flags)
{
...
ret = __splice_from_pipe(pipe, &sd, pipe_to_file);
...
ret;
}

我們接著來分析 __splice_from_pipe() 函數(shù)的實(shí)現(xiàn):

__splice_from_pipe(struct pipe_inode_info *pipe, struct splice_desc *sd,
splice_actor *actor)
{
...
(;;) {
(pipe->nrbufs) {
// 1. 獲取管道環(huán)形緩沖區(qū)
* = -> + ->;
* = ->;
...

// 2. 把管道環(huán)形緩沖區(qū)的數(shù)據(jù)拷貝到輸出端文件。
// 其中 actor 指針指向 pipe_to_file() 函數(shù),由 generic_file_splice_write() 函數(shù)傳入
err = actor(pipe, buf, sd);
(err <= 0) {
(!ret && err != -ENODATA)
ret = err;
;
}
...
}
...
}
...
ret;
}

對 __splice_from_pipe() 函數(shù)進(jìn)行簡化后,邏輯就很簡單。主要過程如下:

獲取管道環(huán)形緩沖區(qū)(管道的實(shí)現(xiàn)可以參考《圖解 | Linux進(jìn)程通信 - 管道實(shí)現(xiàn)》一文)。

調(diào)用pipe_to_file() 函數(shù)把管道環(huán)形緩沖區(qū)的數(shù)據(jù)拷貝到輸出端的文件中。

所以,輸入端是一個(gè)管道的調(diào)用鏈如下:

sys_splice()
└→ do_splice()
└→ do_splice_from()
└→ generic_file_splice_write()
└→ __splice_from_pipe()
└→ pipe_to_file()

2. 輸出端是一個(gè)管道

如果輸出端是一個(gè)管道(也就是說將輸入端與管道綁定),那么將會調(diào)用 do_splice_to()? 函數(shù)進(jìn)行處理,do_splice_to() 函數(shù)的實(shí)現(xiàn)如下:

static long
do_splice_to(struct file *in, loff_t *ppos, struct pipe_inode_info *pipe,
size_t len, unsigned int flags)
{
...
return in->f_op->splice_read(in, ppos, pipe, len, flags);
}

如果輸入端是一個(gè)普通文件,那么 in->f_op->splice_read()? 將會指向 generic_file_splice_read()? 函數(shù)。如果輸出端是一個(gè) socket,那么 in->f_op->splice_read()? 將會指向 sock_splice_read() 函數(shù)。

下面將以 generic_file_splice_read()? 函數(shù)作為分析對象,generic_file_splice_read()? 函數(shù)會調(diào)用 __generic_file_splice_read() 進(jìn)行下一步處理,如下所示:

static int
__generic_file_splice_read(struct file *in, loff_t *ppos,
struct pipe_inode_info *pipe,
size_t len, unsigned int flags)
{
...
struct page *pages[PIPE_BUFFERS];
struct splice_pipe_desc spd = {
.pages = pages,
...
};
...

// 1. 查找已經(jīng)存在頁緩存的頁面
spd.nr_pages = find_get_pages_contig(mapping, index, nr_pages, pages);
index += spd.nr_pages;

...
// 2. 如果有些頁緩存還不存在,那么申請新的頁緩存
while (spd.nr_pages < nr_pages) {
page = find_get_page(mapping, index);
...
pages[spd.nr_pages++] = page;
index++;
}

// 3. 如果頁緩存與硬盤的數(shù)據(jù)不一致,那么先從硬盤同步到頁緩存
for (page_nr = 0; page_nr < nr_pages; page_nr++) {
...
page = pages[page_nr];
...
if (!PageUptodate(page)) {
...
error = mapping->a_ops->readpage(in, page); // 從硬盤讀取數(shù)據(jù)
...
}
...
spd.nr_pages++;
index++;
}

...
// 4. 將頁緩存與管道綁定
if (spd.nr_pages)
return splice_to_pipe(pipe, &spd);

return error;
}

__generic_file_splice_read()? 函數(shù)的代碼比較長,為了更易于分析,所以對其進(jìn)行了精簡。從精簡后的代碼可以看出,__generic_file_splice_read() 函數(shù)主要完成 4 個(gè)步驟:

查找要綁定的頁緩存是否已經(jīng)存在(已經(jīng)從硬盤同步到頁緩存)。

如果還有沒有同步到內(nèi)核的頁緩存,那么申請新的頁緩存。

如果頁緩存與硬盤的數(shù)據(jù)不一致,那么先從硬盤同步到頁緩存。

調(diào)用splice_to_pipe() 函數(shù)將頁緩存與管道綁定。

所以最終會調(diào)用 splice_to_pipe()? 函數(shù)將頁緩存與管道綁定,我們來看看 splice_to_pipe() 函數(shù)的實(shí)現(xiàn):

ssize_t
splice_to_pipe(struct pipe_inode_info *pipe, struct splice_pipe_desc *spd)
{
unsigned int spd_pages = spd->nr_pages;
int ret, do_wakeup, page_nr;
...
for (;;) {
...
if (pipe->nrbufs < PIPE_BUFFERS) {
// 指向管道環(huán)形緩沖區(qū)當(dāng)前位置
int newbuf = (pipe->curbuf + pipe->nrbufs) & (PIPE_BUFFERS - 1);
struct pipe_buffer *buf = pipe->bufs + newbuf;

// 將環(huán)形緩沖區(qū)與頁緩存綁定
buf->page = spd->pages[page_nr];
buf->offset = spd->partial[page_nr].offset;
buf->len = spd->partial[page_nr].len;
buf->private = spd->partial[page_nr].private;
buf->ops = spd->ops;
if (spd->flags & SPLICE_F_GIFT)
buf->flags |= PIPE_BUF_FLAG_GIFT;

pipe->nrbufs++;
page_nr++;
ret += buf->len;

...
if (pipe->nrbufs < PIPE_BUFFERS)
continue;
break;
}

...
}

...
return ret;
}

splice_to_pipe() 函數(shù)代碼雖然比較長,但是邏輯很簡單,就是將管道的環(huán)形緩沖區(qū)與文件的頁緩存進(jìn)行綁定,這樣就能過通過管道的讀端來讀取頁緩存的數(shù)據(jù)。

所以,輸出端是一個(gè)管道的調(diào)用鏈如下:

sys_splice()
└→ do_splice()
└→ do_splice_to()
└→ generic_file_splice_read()
└→ __generic_file_splice_read()
└→ splice_to_pipe()

總結(jié)

本文主要介紹了 splice? 的原理與實(shí)現(xiàn),splice? 是 零拷貝技術(shù)? 的一種實(shí)現(xiàn)。希望通過本文,能夠讓讀者對 零拷貝技術(shù) 有更深入的理解。


網(wǎng)站題目:一文讀懂零拷貝技術(shù)
文章位置:http://m.5511xx.com/article/dpdeeci.html