色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
电子发烧友
开通电子发烧友VIP会员 尊享10大特权
海量资料免费下载
精品直播免费看
优质内容免费畅学
课程9折专享价
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

SPDK Thread模型設計與實現 NVMe-oF的使用案例

FPGA之家 ? 來源:CSDN技術社區 ? 作者:CSDN技術社區 ? 2022-07-03 16:20 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

SPDK Thread 模型是SPDK誕生以來十分重要的模塊,它的設計確保了spdk應用的無鎖化編程模型,本文基于spdk最新的release 19.07版本介紹了整體thread模型的設計與實現,并詳細分析了NVMe-oF的使用案例。

SPDK Thread 模型設計與實現

Reactor – 單個CPU Core抽象,主要包含了:

  • Lcore對應的CPU Core id

  • Threads在該核心下的線程

  • Events 這是一個spdk ring,用于事件傳遞接收

Thread – 線程,但它是spdk抽象出來的線程,主要包含了:

  • io_channels資源的抽象,可以是bdev,也可以是具體的tgt

  • tailq 線程隊列,用于連接下一個線程

  • name 線程的名稱

  • Stats 用于計時統計閑置和忙時時間的

  • active_pollers 輪詢使用的poller,非定時

  • timer_pollers 定時的poller

  • messages 這是一個spdk ring,用于消息傳遞接收

  • msg_cache 事件的緩存

1.1 Reactor

對象g_reactor_state有五個狀態對應了應用中reactors運行運行狀態,

enum spdk_reactor_state {

SPDK_REACTOR_STATE_INVALID = 0,

SPDK_REACTOR_STATE_INITIALIZED = 1,

SPDK_REACTOR_STATE_RUNNING = 2,

SPDK_REACTOR_STATE_EXITING = 3,

SPDK_REACTOR_STATE_SHUTDOWN = 4,

};

初始情況下是:

SPDK_REACTOR_STATE_INVALID狀態,在spdk app(任意一個target,比如nvmf_tgt)啟動時,即調用了spdk_app_start方法,會調用spdk_reactors_init,在這個方法中將會初始化所有需要被初始化的reactors(可以在配置文件中指定需要使用的Core,CPU Core 和reactor是一對一的)。并且會將g_reactor_state設置為SPDK_REACTOR_STATE_INITIALIZED。具體代碼如下:

Int spdk_reactors_init(void)

{

// 初始化所有的event mempool

g_spdk_event_mempool = spdk_mempool_create(…);

// 為g_reactors分配內存,g_reactors是一個數組,管理了所有的reactors

posix_memalign((void **)&g_reactors, 64, (last_core + 1) * sizeof(struct spdk_reactor));

// 這里設置了reactor創建線程的方法,之后需要初始化線程的時候將會調用該方法

spdk_thread_lib_init(spdk_reactor_schedule_thread, sizeof(struct spdk_lw_thread));

// 對于每一個啟動的reactor,將會初始化它們

// 初始化reactor過程,即為綁定lcore,初始化spdk ring、threads,對rusage無操作

SPDK_ENV_FOREACH_CORE(i) {

reactor = spdk_reactor_get(i);

spdk_reactor_construct(reactor, i);

}

// 設置好狀態返回

g_reactor_state = SPDK_REACTOR_STATE_INITIALIZED;

return 0;

}

在進入SPDK_REACTOR_STATE_INITIALIZED狀態且spdk_app_start在創建了自己的線程并綁定到了reactors后,會調用spdk_reactors_start方法并將g_reactor_state設置為SPDK_REACTOR_STATE_RUNNING狀態并會創建所有reactor的線程且輪詢。

Void spdk_reactors_start(void) {

SPDK_ENV_FOREACH_CORE(i) {

if (i != current_core) { // 在非master reactor中

reactor = spdk_reactor_get(i); // 得到相應的reactor

// 設置好線程創建后的一個消息,該消息為輪詢函數

rc = spdk_env_thread_launch_pinned(reactor->lcore, _spdk_reactor_run, reactor);

// reactor創建好線程并且會自動執行第一個消息

spdk_thread_create(thread_name, tmp_cpumask);

}

}

// 當前CPU core得到reactor,并且開始輪詢

reactor = spdk_reactor_get(current_core);

_spdk_reactor_run(reactor);

}

之前提到spdk_reactors_init方法中調用了spdk_thread_lib_init方法傳入了創建thread的spdk_reactor_schedule_thread方法,在調用spdk_thread_create會回調該方法。這個方法它主要的功能就是告訴這個新創建的線程綁定創建該線程的reactor。

spdk_reactor_schedule_thread(struct spdk_thread *thread)

{

// 得到該線程設置的cpu mask

cpumask = spdk_thread_get_cpumask(thread);

for (i = 0; i < spdk_env_get_core_count(); i++) {

…. // 遍歷cpu core

// 通過cpu mask找到對應的核心,并產生event

if (spdk_cpuset_get_cpu(cpumask, core)) {

evt = spdk_event_allocate(core, _schedule_thread, lw_thread, NULL);

break;

}

}

// 傳遞該event,即對應的reatcor會調用_schedule_thread方法,

spdk_event_call(evt);

}

_schedule_thread(void *arg1, void *arg2)

{

struct spdk_lw_thread *lw_thread = arg1;

struct spdk_reactor *reactor;

// 消息傳遞到對應的reactor后將該thread加入到reactor中

reactor = spdk_reactor_get(spdk_env_get_current_core());

TAILQ_INSERT_TAIL(&reactor->threads, lw_thread, link);

}

在SPDK_REACTOR_STATE_RUNNING后,此時所有reactor就進入了輪詢狀態。_spdk_reactor_run函數為線程提供了輪詢方法:

static int _spdk_reactor_run(void *arg) {

while (1) {

// 處理reactor上的event消息,消息會在之后講到

_spdk_event_queue_run_batch(reactor);

// 每一個reactor上注冊的thread進行遍歷并且處理poller事件

TAILQ_FOREACH_SAFE(lw_thread, &reactor->threads, link, tmp) {

rc = spdk_thread_poll(thread, 0, now);

}

// 檢查reactor的狀態

if (g_reactor_state != SPDK_REACTOR_STATE_RUNNING) {

break;

}

}

}

而當spdk app被調用spdk_app_stop方法后將會相應的通知每一個reactor調用spdk_reactors_stop方法,將g_reactor_state賦值為SPDK_REACTOR_STATE_EXITING,即開始退出了。回到_spdk_reactor_run函數中,輪詢將會被跳出,并且執行銷毀線程的代碼。

static int _spdk_reactor_run(void *arg) {

….. // 輪詢

TAILQ_FOREACH_SAFE(lw_thread, &reactor->threads, link, tmp) {

thread = spdk_thread_get_from_ctx(lw_thread);

TAILQ_REMOVE(&reactor->threads, lw_thread, link);

spdk_set_thread(thread);

spdk_thread_exit(thread);

spdk_thread_destroy(thread);

}

}

在這之后,主線程的_spdk_reactor_run會返回到spdk_reactors_start中,并將g_reactor_state賦值為SPDK_REACTOR_STATE_SHUTDOWN,返回到spdk_app_start中等待應用退出。

最后,總結一下reactors和CPU core以及spdk thread關系應該如圖1所示

1035d500-e91c-11ec-ba43-dac502259ad0.png

圖1 CPU cores、reactors和thread關系圖

Reactor生命周期流程圖則如圖2所示

10691eec-e91c-11ec-ba43-dac502259ad0.png

圖2 reactor生命周期流程圖

1.2 thread

當Reactors進行輪詢時,除了處理自己的事件消息之外,還會調用注冊在該reactor下面的每一個線程進行輪詢。不過通常一個reactor只有一個thread,在spdk應用中,更多的是注冊多個poller而不是注冊多個thread。具體的輪詢方法為:

Int spdk_thread_poll(struct spdk_thread *thread, uint32_t max_msgs, uint64_t now) {

// 首先先處理ring傳遞過來的消息

msg_count = _spdk_msg_queue_run_batch(thread, max_msgs);

// 調用非定時poller中的方法

TAILQ_FOREACH_REVERSE_SAFE(poller, &thread->active_pollers,

active_pollers_head, tailq, tmp) {

// 調用poller注冊的方法之前,會對poller狀態檢測且轉換

if (poller->state == SPDK_POLLER_STATE_UNREGISTERED) {

TAILQ_REMOVE(&thread->active_pollers, poller, tailq);

free(poller);

continue;

}

poller->state = SPDK_POLLER_STATE_RUNNING;

// 調用poller注冊的方法

poller_rc = poller->fn(poller->arg);

// poller轉換狀態

poller->state = SPDK_POLLER_STATE_WAITING;

}

// 調用定時poller中的方法

TAILQ_FOREACH_SAFE(poller, &thread->timer_pollers, tailq, tmp) {

// 類似非定時poller過程,不過會檢查是否到了預定的時間

if (now < poller->next_run_tick) break;

}

// 最后統計時間

}

Io_device 和 io_channel在thread中也是非常重要的概念。它們的實現都在thread.c中,io_device是設備的抽象,io_channel是對該設備通道的抽象。一個線程可以創建多個io_channel . io_channel只能和一個io_device綁定,并且這個io_channel是別的線程使用不了的。

10a42758-e91c-11ec-ba43-dac502259ad0.png

圖 3 io_device、io_channel和線程關系圖

Io_device結構

struct io_device {

void *io_device; // 抽象的device指針

char name[SPDK_MAX_DEVICE_NAME_LEN + 1]; // 名字

spdk_io_channel_create_cb create_cb; // io_channel創建的回調函數

spdk_io_channel_destroy_cb destroy_cb; // io_channel銷毀的回調函數

spdk_io_device_unregister_cb unregister_cb; // io_device解綁的回調函數

struct spdk_thread *unregister_thread; // 不使用該device線程

uint32_t ctx_size; // ctx的大小,將會傳給io_channel處理

uint32_t for_each_count; // io_channel的數量

TAILQ_ENTRY(io_device) tailq; // device隊列頭

uint32_t refcnt; // 計數器

bool unregistered; // 是否該device被注冊

};

可以看到,io_device實際上只提供了一些自身io_device的操作和io_channel相關的方法,具體的io_device實體其實是那個名字叫io_device的void指針。因為thread中的io_device只提供了thread這一層接口,具體的io操作每一個設備很難被抽象出來,所以這一層的接口只負責管理io_channel的創建、銷毀和綁定等。

Io_channel的結構

struct spdk_io_channel {

struct spdk_thread *thread; // 綁定的線程

struct io_device *dev; // 綁定的io_device

uint32_t ref; // io_channel引用計數

uint32_t destroy_ref; // destroy前被引用的次數

TAILQ_ENTRY(spdk_io_channel) tailq; // io_channel 隊列頭

spdk_io_channel_destroy_cb destroy_cb; // io_channel銷毀的回調函數

};

雖然io_channel看起來是很簡單的結構體,實際上在創建一個io_device的時候,會要求使用者傳入一個io_channel_ctx的大小作為調用的參數,而在給io_channel分配內存的時候,除了分配本身io_channel結構體的大小外,還會額外分配一個io_channel_ctx的大小,這個context可以理解成一個void指針,當用戶在使用io_channel的時候,實際上還是通過context的部分去訪問io_device。

NVMe-oF實例

nvmf_tgt 是spdk中一個重要的模塊,這里詳細的寫一下它作為一個target實例是如何使用thread、io_device以及io_channel的。

在spdk應用剛啟動的時候,reactor模塊就會自動加載起來,然后在加載nvmf subsystem的時候,會調用spdk_nvmf_subsystem_init(lib/event/subsystems/nvmf/nvmf_tgt.c)方法,nvmf_tgt其實也是有生命周期,并且有一個狀態機去管理它的生命周期。

enum nvmf_tgt_state {

NVMF_TGT_INIT_NONE = 0, // 最初的狀態

NVMF_TGT_INIT_PARSE_CONFIG, // 解析配置文件

NVMF_TGT_INIT_CREATE_POLL_GROUPS, // 創建poll groups

NVMF_TGT_INIT_START_SUBSYSTEMS, // 啟動subsystem

NVMF_TGT_INIT_START_ACCEPTOR, // 開始接收

NVMF_TGT_RUNNING, // running

NVMF_TGT_FINI_STOP_SUBSYSTEMS,

NVMF_TGT_FINI_DESTROY_POLL_GROUPS,

NVMF_TGT_FINI_STOP_ACCEPTOR,

NVMF_TGT_FINI_FREE_RESOURCES,

NVMF_TGT_STOPPED,

NVMF_TGT_ERROR,

};

首先在NVMF_TGT_INIT_PARSE_CONFIG狀態中,nvmf_tgt會去解析啟動時傳入的配置文件,當解析了[nvmf]這個label后,會調用spdk_nvmf_tgt_create這個方法,這個方法將初始化了全局的g_nvmf_tgt變量,同時也將tgt注冊成了一個io_device。

spdk_io_device_register(tgt,

spdk_nvmf_tgt_create_poll_group,

spdk_nvmf_tgt_destroy_poll_group,

sizeof(struct spdk_nvmf_poll_group),

"nvmf_tgt");

spdk_nvmf_tgt_create_poll_group和spdk_nvmf_tgt_destroy_poll_group是io_channel創建和銷毀的回調方法。第三個參數是io_channel_ctx的size,既然這里傳入了spdk_nvmf_poll_group的大小,那么很明顯說明在nvmf中io_channel_ctx對象就是spdk_nvmf_poll_group。

當config文件解析完了之后,nvmf_tgt狀態到了NVMF_TGT_INIT_CREATE_POLL_GROUPS,這個狀態下會為每一個線程都創建相應的poll group。

spdk_for_each_thread(nvmf_tgt_create_poll_group,

NULL,

nvmf_tgt_create_poll_group_done);

static void nvmf_tgt_create_poll_group(void *ctx)

{

struct nvmf_tgt_poll_group *pg;

….

pg->thread = spdk_get_thread();

pg->group = spdk_nvmf_poll_group_create(g_spdk_nvmf_tgt);

….

}

再看spdk_nvmf_poll_group_create中,

struct spdk_nvmf_poll_group * spdk_nvmf_poll_group_create(struct spdk_nvmf_tgt *tgt)

{

struct spdk_io_channel *ch;

ch = spdk_get_io_channel(tgt);

….

return spdk_io_channel_get_ctx(ch);

}

在spdk_get_io_channel中,會先去檢查傳入的io_device是不是已經注冊好了的,如果已經注冊了,將會創建一個新的io_channel返回,創建的過程會回調在注冊io_device時注冊的io_channel創建方法(即方法spdk_nvmf_tgt_create_poll_group)。

static int spdk_nvmf_tgt_create_poll_group(void *io_device, void *ctx_buf)

{

….. // 初始化transport 、nvmf subsystem等

// 注冊一個poller

group->poller = spdk_poller_register(spdk_nvmf_poll_group_poll, group, 0);

group->thread = spdk_get_thread();

return 0;

}

在spdk_nvmf_poll_group_poll中,因為spdk_nvmf_poll_group對象中有transport的poll group,所以它會調用對應的transport的poll_group_poll方法,比如rdma的poll_group_poll就會輪詢rdma注冊的poller處理每個在相應的qpair來的請求,進入rdma的狀態機將請求處理好。

然后這個狀態就結束了,之后再初始化好了nvmf subsystem相關的東西之后,到了狀態NVMF_TGT_INIT_START_ACCEPTOR。在這個狀態中,只注冊了一個poller。

g_acceptor_poller = spdk_poller_register(acceptor_poll, g_spdk_nvmf_tgt,

g_spdk_nvmf_tgt_conf->acceptor_poll_rate);

這個poller調用的transport的方法,不斷的監聽是不是有新的fd連接進來,如果有就調用new_qpair的回調。

總結

spdk thread 模型是spdk無鎖化的基礎,在一個線程中,當分配一個任務后,一直會運行到任務結束為止,這確保了不需要進行線程之間的切換而帶來額外的損耗。同時,高效的spdk ring提供了不同線程之間的消息傳遞,這就使得任務結束的結果可以高效的傳遞給別的處理線程。而io_device和io_channel的設計保證了資源的抽象訪問以及獨立的路徑不去爭搶資源池,并且塊設備由于是對塊進行操作的所以也十分適合抽象成io_device。正是因為以上幾點才讓spdk線程模型能夠達到無鎖化且為多個target提供了基礎線程框架的支持。

原文標題:SPDK線程模型解析

文章出處:【微信公眾號:FPGA之家】歡迎添加關注!文章轉載請注明出處。

審核編輯:湯梓紅
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • cpu
    cpu
    +關注

    關注

    68

    文章

    11070

    瀏覽量

    216816
  • 模型
    +關注

    關注

    1

    文章

    3516

    瀏覽量

    50343
  • Thread
    +關注

    關注

    2

    文章

    89

    瀏覽量

    26542
  • nvme
    +關注

    關注

    0

    文章

    251

    瀏覽量

    23211

原文標題:SPDK線程模型解析

文章出處:【微信號:zhuyandz,微信公眾號:FPGA之家】歡迎添加關注!文章轉載請注明出處。

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

掃碼添加小助手

加入工程師交流群

    評論

    相關推薦
    熱點推薦

    NVMe協議研究掃盲

    內部的并行性實現可擴展性、高吞吐量和低延遲的目標。相較于SATA協議,NVMe協議具有以下幾點優勢:管理更高效、功能性更強、I/O效率更高、讀寫延遲和功耗更低。由于NVMe SSD與HDD和SATA
    發表于 06-02 23:28

    TH58NVG4S0HTA20閃存芯片TH58NVG4S0HTAK0

    TH58NVG4S0HTA20閃存芯片TH58NVG4S0HTAK0鎧俠宣布其 EM6 系列企業級 NVMe-oF 固態硬盤 (SSD) 可用于以太網束閃存 (EBOF) 系統,采用Marvell
    發表于 01-25 09:00

    IBM大波存儲陣列新增對NVMe-oF的支持

    IBM近日進行了大量的產品發布,其中閃存陣列增加了容量,一系列使用FC加速數據訪問的陣列新增了對NVMe-oF的支持。
    的頭像 發表于 11-05 08:53 ?4249次閱讀

    業界首款NVMe-oF SSD轉換器控制器能夠降低總體擁有成本

    Marvell 88SN2400能夠將NVMe SSD轉換為NVMe-oF SSD,實現最佳的高性能分類式存儲,從而降低云和企業數據中心應用的總體擁有成本
    發表于 11-23 17:10 ?1214次閱讀

    超全的SPDK性能評估指南

    SPDK采用異步I/O(Asynchronous I/O)加輪詢(Polling)的工作模式,通常與Kernel的異步I/O作為對比。在此,主要介紹通過使用fio評估Kernel異步I/O,以及spdk fio_plugin的兩種模式。
    的頭像 發表于 11-26 09:58 ?9515次閱讀

    Marvell推出NVMe-oF以太網SSD技術和新一代SSD控制器解決方案

    Marvell(NASDAQ:MRVL)今日宣布擴展其革命性的NVMe over Fabrics?(NVMe-oF?)產品組合。這些突破性的解決方案,包括由Marvell NVMe-oF SSD轉換
    的頭像 發表于 08-19 01:19 ?5261次閱讀

    西部數據新款數據存儲解決方案助力企業實現NVMe的過渡

    西部數據公司擁有完整的企業級數據存儲解決方案,6月29日,西部數據最新推出了基于Ultrastar NVMe SSD設計的新款數據存儲解決方案,并由NVMe-oF提供強化支持,為下一代數據基礎架構奠定根基。
    的頭像 發表于 07-07 16:33 ?3141次閱讀

    NVMe-oF的優勢及未來

    (NVIDIA)等關鍵技術合作伙伴的合作,共同解鎖NVMe-oF技術可以帶來的優勢。我們還在開發創新技術,如異構存儲器存儲引擎(HSE),這將有助于使用NVMe-oF優化對閃存的大規模訪問。 NVMe-oF
    的頭像 發表于 01-05 17:42 ?1w次閱讀
    <b class='flag-5'>NVMe-oF</b>的優勢及未來

    整體thread模型的設計與實現

    ,并詳細分析了NVMe-oF的使用案例。 P1 SPDK Thread 模型設計與實現 Reactor – 單個CPU Core抽象,主要包
    的頭像 發表于 03-29 14:39 ?2365次閱讀
    整體<b class='flag-5'>thread</b><b class='flag-5'>模型</b>的設計與<b class='flag-5'>實現</b>

    RT-Thread AI kit開源:輕松實現一鍵部署AI模型至 RT-Thread

    RT-AK 是 RT-Thread 團隊為 RT-Thread 實時操作系統所開發的 AI 套件,能夠一鍵將 AI 模型部署到 RT-Thread 項目中,讓用戶可以 在統一...
    發表于 01-25 18:18 ?3次下載
    RT-<b class='flag-5'>Thread</b> AI kit開源:輕松<b class='flag-5'>實現</b>一鍵部署AI<b class='flag-5'>模型</b>至 RT-<b class='flag-5'>Thread</b>

    為什么NVMe/TCP是數據中心的更優選擇

    與SCSI、ISCSI、SAS或SATA 接口不同,NVMe實現了針對多核服務器 CPU 優化的簡化命令模式和多隊列體系結構。NVMe-oF規范擴展了NVMe
    的頭像 發表于 04-18 10:22 ?1844次閱讀
    為什么<b class='flag-5'>NVMe</b>/TCP是數據中心的更優選擇

    RT-Thread設備模型框架及創建注冊設備的實現

    RT-Thread設備模型框架及創建注冊設備的實現方式介紹如下:
    的頭像 發表于 05-28 10:38 ?2413次閱讀
    RT-<b class='flag-5'>Thread</b>設備<b class='flag-5'>模型</b>框架及創建注冊設備的<b class='flag-5'>實現</b>

    DPU應用場景系列(二) 存儲功能卸載

    據中心的計算和存儲進行分解。NVMe-oF協議定義了使用各種通用的傳輸協議來實現NVMe功能的方式。在NVMe-oF誕生之前,數據存儲協議可以分為三種:(1)iSCSI
    的頭像 發表于 05-19 14:34 ?3044次閱讀
    DPU應用場景系列(二) 存儲功能卸載

    vSphere 7.0 U1中的NVMe-oF的性能表征

    電子發燒友網站提供《vSphere 7.0 U1中的NVMe-oF的性能表征.pdf》資料免費下載
    發表于 08-04 09:15 ?0次下載
    vSphere 7.0 U1中的<b class='flag-5'>NVMe-oF</b>的性能表征

    SPDK在虛擬化場景下的使用方法

    ? SPDK(全稱Storage Performance Development Kit),提供了一整套工具和庫,以實現高性能、擴展性強、全用戶態的存儲應用程序。它是繼DPDK之后,intel在存儲領域
    的頭像 發表于 11-10 10:12 ?2375次閱讀
    <b class='flag-5'>SPDK</b>在虛擬化場景下的使用方法
    主站蜘蛛池模板: www.伊人网| 2019夜夜| 三级视频黄色 | 久久国产高清字幕中文 | 亚洲精品美女久久久久99 | 出差无套内射小秘书 | 青青草原在线新免费 | 中国女人内谢69xxxxxx直播 | 色橹橹欧美在线观看视频高清 | 午夜在线观看免费完整直播网 | 牛牛自拍国产免费视频 | 99九九免费热在线精品 | 69国产精品成人无码视频 | 噜妇插内射精品 | 色cccwww | 99精品网站 | 国产成人精品一区二区三区视频 | 青青青手机视频 | 亚洲国产系列一区二区三区 | 最近中文字幕在线中文高清版 | 无限资源在线看影院免费观看 | 风月宝鉴之淫乱英雄传 电影 | h版动漫在线播放的网站 | 秋霞成人午夜鲁丝一区二区三区 | 一区三区在线专区在线 | 亚洲欧美综合视频 | 2018三级网站免费观看 | 佐山爱巨大肥臀在线 | 亚洲精品永久免费 | 亲嘴扒胸摸屁股视频免费网站 | 色播播电影 | 午夜国产理论 | 郭德纲于谦2012最新相声 | 最新日本免费一区 | 丝袜美腿美女被狂躁在线观看 | 99国产精品欲AV蜜桃臀麻豆 | 久99视频精品免费观看福利 | 手机看片国产免费 | 红尘影院手机在线观看 | 在线看无码的免费网站 | 久久中文字幕无线观看 |

    電子發燒友

    中國電子工程師最喜歡的網站

    • 2931785位工程師會員交流學習
    • 獲取您個性化的科技前沿技術信息
    • 參加活動獲取豐厚的禮品