1. 程式人生 > 實用技巧 >十、Kernel_3.0.35版本和Kernel_4.1.15版本在SPI驅動實現機制的差異

十、Kernel_3.0.35版本和Kernel_4.1.15版本在SPI驅動實現機制的差異

Kernel_3.0.35版本和Kernel_4.1.15版本核心在SPI驅動實現機制的差異
一、Kernel_4.1.15版本
1.SPI控制器驅動(基於NXP處理器平臺分析)
入口函式
static int spi_imx_probe(struct platform_device *pdev)
{
    //1.控制器分配
    master = spi_alloc_master(&pdev->dev, sizeof(struct spi_imx_data) + sizeof(int) * num_cs);
    //2.SPI控制器初始化(晶片廠商負責實現)
    spi_imx->bitbang.chipselect = spi_imx_chipselect;//
片選訊號 spi_imx->bitbang.setup_transfer = spi_imx_setupxfer;//初始化SPI傳送函式(包含8/16/32位傳送) /* Initialize the functions for transfer */ if (config.bpw <= 8) { spi_imx
->rx = spi_imx_buf_rx_u8;//實際上,這是一個巨集,下同,在spi_imx.c檔案開頭 spi_imx->tx = spi_imx_buf_tx_u8; spi_imx->tx_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; spi_imx->rx_config.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; }
else if (config.bpw <= 16) { spi_imx->rx = spi_imx_buf_rx_u16; spi_imx->tx = spi_imx_buf_tx_u16; spi_imx->tx_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; spi_imx->rx_config.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; } else { spi_imx->rx = spi_imx_buf_rx_u32; spi_imx->tx = spi_imx_buf_tx_u32; spi_imx->tx_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; spi_imx->rx_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; } spi_imx->bitbang.txrx_bufs = spi_imx_transfer; //如果使用DMA進行傳送(實際上在3.0.35版本的核心不支援使用DMA進行資料傳送) if (spi_imx->bitbang.master->can_dma && spi_imx_can_dma(spi_imx->bitbang.master, spi, transfer)) { spi_imx->usedma = true; ret = spi_imx_dma_transfer(spi_imx, transfer); spi_imx->usedma = false; /* clear the dma flag */ if (ret != -EAGAIN) return ret; } //因為不支援DMA,所以最終走的邏輯是這裡 return spi_imx_pio_transfer(spi, transfer);//裡面呼叫資料傳送函式 ...... ...... spi_imx_push(spi_imx);//裡面呼叫spi_imx->tx進行資料傳送 spi_imx->tx(spi_imx);//呼叫傳送函式,關鍵:spi_imx->tx的賦值,在前面函式spi_imx_setupxfer中完成 ...... spi_imx->bitbang.master->setup = spi_imx_setup; spi_imx->bitbang.master->cleanup = spi_imx_cleanup; spi_imx->bitbang.master->prepare_message = spi_imx_prepare_message; spi_imx->bitbang.master->unprepare_message = spi_imx_unprepare_message; spi_imx->bitbang.master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;//設定SPI模式 ...... ...... //3.獲取中斷資源 irq = platform_get_irq(pdev, 0); ...... //4.申請中斷處理函式 ret = devm_request_irq(&pdev->dev, irq, spi_imx_isr, 0, dev_name(&pdev->dev), spi_imx); ...... //5.註冊SPI控制器--->跳轉分析 ret = spi_bitbang_start(&spi_imx->bitbang); ...... if (master->transfer || master->transfer_one_message)//前面沒有設定,繼續往下走 ...... ...... master->prepare_transfer_hardware = spi_bitbang_prepare_hardware; master->unprepare_transfer_hardware = spi_bitbang_unprepare_hardware; master->transfer_one_message = spi_bitbang_transfer_one;//傳送一個spi資料 //前面已經設定,因此不會走這個if邏輯 if (!bitbang->txrx_bufs) { bitbang->use_dma = 0; bitbang->txrx_bufs = spi_bitbang_bufs; if (!master->setup) { if (!bitbang->setup_transfer) bitbang->setup_transfer = spi_bitbang_setup_transfer; master->setup = spi_bitbang_setup; master->cleanup = spi_bitbang_cleanup; } } ret = spi_register_master(spi_master_get(master));//註冊SPI控制器 status = of_spi_register_master(master);//註冊SPI控制器 ...... INIT_LIST_HEAD(&master->queue);//初始佇列頭(SPI就是通過佇列來處理資料) ...... ...... status = device_add(&master->dev);//向核心中新增裝置 ..... ..... /* If we're using a queued driver, start the queue */ if (master->transfer)//這個msster->transfer在前面並沒有初始化,所以走else分支 dev_info(dev, "master is unqueued, this is deprecated\n"); else { status = spi_master_initialize_queue(master);//初始化一個佇列 master->transfer = spi_queued_transfer;//由於前面沒有初始化master->transfer函式,因此在這裡進行初始化了 if (!master->transfer_one_message)//這個函式在前面spi_bitbang_start函式開頭已經設定了,因此跳過這裡的if邏輯 master->transfer_one_message = spi_transfer_one_message; /* Initialize and start queue */ ret = spi_init_queue(master);//初始化一個佇列 //使用核心執行緒的方式來實現SPI資料的處理() init_kthread_worker(&master->kworker); master->kworker_task = kthread_run(kthread_worker_fn, &master->kworker, "%s", dev_name(&master->dev)); if (IS_ERR(master->kworker_task)) { dev_err(&master->dev, "failed to create message pump task\n"); return PTR_ERR(master->kworker_task); } init_kthread_work(&master->pump_messages, spi_pump_messages); } .... } //以上,SPI控制器的初始化就完成了 //接下來分析資料傳送的函式呼叫流程 //前面初始化傳送函式是spi_imx->tx(spi_imx);那麼實際驅動中是如何呼叫呢? //傳送函式(有同步和非同步兩種方式,在這裡只分析同步方式) int spi_sync(struct spi_device *spi, struct spi_message *message); __spi_sync(spi, message, 0);//同步傳送函式(函式會阻塞,可能引起睡眠,因此不能在中斷上下文及tasklet中呼叫,只能在workqueue中使用) ...... ...... status = __spi_queued_transfer(spi, message, false);//傳送一個SPI資料(spi資料是一message資料塊的形式進行傳送) ...... ...... queue_kthread_work(&master->kworker, &master->pump_messages);//排程核心執行緒 insert_kthread_work(worker, work, &worker->work_list);//到這裡結束,剩下的轉去核心執行緒的執行函式去分析spi_pump_messages //核心執行緒的執行函式 static void spi_pump_messages(struct kthread_work *work) ....... ....... ret = master->transfer_one_message(master, master->cur_msg);//呼叫前面在spi_bitbang_start函式中初始化的master->transfer_one_message = spi_bitbang_transfer_one;//傳送一個spi資料 //轉去分析 static int spi_bitbang_transfer_one(struct spi_master *master, struct spi_message *m) /* transfer data. the lower level code handles any * new dma mappings it needs. our caller always gave * us dma-safe buffers. */ if (t->len) { /* REVISIT dma API still needs a designated * DMA_ADDR_INVALID; ~0 might be better. */ if (!m->is_dma_mapped) t->rx_dma = t->tx_dma = 0; status = bitbang->txrx_bufs(spi, t);//因為前面沒有使用DMA,所以正式傳送資料,走的是這個邏輯,跳轉去分析bitbang->txrx_bufs(spi, t); } //函式指標bitbang->txrx_bufs(spi, t);在spi_imx_probe函式中進行初始化,賦值為spi_imx_transfer static int spi_imx_transfer(struct spi_device *spi, struct spi_transfer *transfer) ...... spi_imx_pio_transfer(spi, transfer); ...... spi_imx_push(spi_imx);//裡面呼叫spi_imx->tx進行資料傳送 spi_imx->tx(spi_imx);//呼叫傳送函式,關鍵:spi_imx->tx的賦值,在前面函式spi_imx_setupxfer中完成(分析到這裡,已經和前面的初始化傳送函式完全對應上了) 二、Kernel_3.0.35版本 NXP官方在3.0.35版本的核心,控制器硬體平臺的初始化和4.1.15版本的核心是差不多的,不同的是在兩個核心版本之間,對於SPI的資料處理機制不一樣 所以省去一部分相同的初始流程,直接分析差異。 static int __devinit spi_imx_probe(struct platform_device *pdev) ...... ...... ret = spi_bitbang_start(&spi_imx->bitbang); ...... INIT_WORK(&bitbang->work, bitbang_work);//注意這裡初始化了一個工作佇列,這是在4.1.15版本的核心所沒有的 //在排程這個工作佇列的時候會執行bitbang_work函式 INIT_LIST_HEAD(&bitbang->queue);//初始化佇列頭 ...... //前面沒有初始化,所以這個if邏輯是成立的 if (!bitbang->master->transfer) bitbang->master->transfer = spi_bitbang_transfer;//傳送資料 if (!bitbang->txrx_bufs)//前面已經初始化了,所以這個邏輯不成立 { bitbang->use_dma = 0; bitbang->txrx_bufs = spi_bitbang_bufs; if (!bitbang->master->setup) { if (!bitbang->setup_transfer) bitbang->setup_transfer = spi_bitbang_setup_transfer; bitbang->master->setup = spi_bitbang_setup; bitbang->master->cleanup = spi_bitbang_cleanup; } } else if (!bitbang->master->setup)//前面probe函式中也初始化了,所以這個邏輯也不成立 { return -EINVAL; } if (bitbang->master->transfer == spi_bitbang_transfer && !bitbang->setup_transfer)//前面probe函式中也初始化了,所以這個邏輯也不成立 { return -EINVAL; } ...... //建立工作佇列,但是這裡有個疑問,呼叫create_singlethread_workqueue建立的工作佇列,只能在CPU0上工作 //對於多CPU的情況,是不是沒有發揮多CPU的效率優勢呢?還是說核心在SPI的實現機制上有bug,避免競爭只能這麼做? //這種使用工作佇列來實現SPI資料處理的方式和4.1.15版本有所不同(其實在3.1.14版本就已經不一樣了) bitbang->workqueue = create_singlethread_workqueue(dev_name(bitbang->master->dev.parent)); ...... status = spi_register_master(bitbang->master);//向核心註冊SPI裝置 //前面已經呼叫INIT_WORK(&bitbang->work, bitbang_work); //在排程這個工作佇列的時候會執行bitbang_work函式,所以這裡插入分析bitbang_work函式 static void bitbang_work(struct work_struct *work) ...... /* transfer data. the lower level code handles any * new dma mappings it needs. our caller always gave * us dma-safe buffers. */ if (t->len) //這部分是和4.1.15版本的核心一樣 { /* REVISIT dma API still needs a designated * DMA_ADDR_INVALID; ~0 might be better. */ if (!m->is_dma_mapped) t->rx_dma = t->tx_dma = 0; status = bitbang->txrx_bufs(spi, t);//呼叫傳送函式bitbang->txrx_bufs(spi, t);在probe中被賦值 spi_imx_push(spi_imx); spi_imx->tx(spi_imx); } 3.0.15版本SPI同步資料的呼叫流程 static int __spi_sync(struct spi_device *spi, struct spi_message *message, int bus_locked) int spi_async_locked(struct spi_device *spi, struct spi_message *message) ret = __spi_async(spi, message); master->transfer(spi, message);//找到這個函式master->transfer(spi, message);的賦值,在int spi_bitbang_start(struct spi_bitbang *bitbang)中賦值的 bitbang->master->transfer = spi_bitbang_transfer;//傳送資料 queue_work(bitbang->workqueue, &bitbang->work);//排程工作佇列,執行工作佇列函式 static void bitbang_work(struct work_struct *work) spi_imx_push(spi_imx); spi_imx->tx(spi_imx); //到這裡,初始化和傳送的流程已經前後對應起來 //兩個核心版本在SPI控制器的初始化流程都差不多,唯一不同的是核心在實現SPI最核心的資料處理時使用了兩種不同的實現機制。 //搜尋整個核心發現,使用核心執行緒來實現的模組很少,4.1.15版本放棄了原來的工作佇列方式而使用核心執行緒的方式顯然可能是原來工作佇列的方式滿足不了傳輸效率吧 //由於使用了核心執行緒的實現方式,所以我們可以很容易的改變排程的策略賦予不同的優先順序。在某些對傳輸效率及時間有較高要求的場景4.1.15版本的實現 //方式明顯是要優於3.0.35版本的。

Kernel_3.0.35版本和Kernel_4.1.15版本核心在SPI驅動實現機制的差異一、Kernel_4.1.15版本1.SPI控制器驅動(基於NXP處理器平臺分析)入口函式static int spi_imx_probe(struct platform_device *pdev){//1.控制器分配master = spi_alloc_master(&pdev->dev, sizeof(struct spi_imx_data) + sizeof(int) * num_cs);//2.SPI控制器初始化(晶片廠商負責實現)spi_imx->bitbang.chipselect = spi_imx_chipselect;//片選訊號spi_imx->bitbang.setup_transfer = spi_imx_setupxfer;//初始化SPI傳送函式(包含8/16/32位傳送)/* Initialize the functions for transfer */if (config.bpw <= 8){spi_imx->rx = spi_imx_buf_rx_u8;//實際上,這是一個巨集,下同,在spi_imx.c檔案開頭spi_imx->tx = spi_imx_buf_tx_u8;spi_imx->tx_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;spi_imx->rx_config.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;}else if (config.bpw <= 16) {spi_imx->rx = spi_imx_buf_rx_u16;spi_imx->tx = spi_imx_buf_tx_u16;spi_imx->tx_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;spi_imx->rx_config.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;}else{spi_imx->rx = spi_imx_buf_rx_u32;spi_imx->tx = spi_imx_buf_tx_u32;spi_imx->tx_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;spi_imx->rx_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;}
spi_imx->bitbang.txrx_bufs = spi_imx_transfer;//如果使用DMA進行傳送(實際上在3.0.35版本的核心不支援使用DMA進行資料傳送)if (spi_imx->bitbang.master->can_dma && spi_imx_can_dma(spi_imx->bitbang.master, spi, transfer)) {spi_imx->usedma = true;ret = spi_imx_dma_transfer(spi_imx, transfer);spi_imx->usedma = false; /* clear the dma flag */if (ret != -EAGAIN)return ret;}//因為不支援DMA,所以最終走的邏輯是這裡return spi_imx_pio_transfer(spi, transfer);//裡面呼叫資料傳送函式............spi_imx_push(spi_imx);//裡面呼叫spi_imx->tx進行資料傳送spi_imx->tx(spi_imx);//呼叫傳送函式,關鍵:spi_imx->tx的賦值,在前面函式spi_imx_setupxfer中完成......spi_imx->bitbang.master->setup = spi_imx_setup;spi_imx->bitbang.master->cleanup = spi_imx_cleanup;spi_imx->bitbang.master->prepare_message = spi_imx_prepare_message;spi_imx->bitbang.master->unprepare_message = spi_imx_unprepare_message;spi_imx->bitbang.master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;//設定SPI模式............//3.獲取中斷資源irq = platform_get_irq(pdev, 0);......//4.申請中斷處理函式ret = devm_request_irq(&pdev->dev, irq, spi_imx_isr, 0, dev_name(&pdev->dev), spi_imx);......//5.註冊SPI控制器--->跳轉分析ret = spi_bitbang_start(&spi_imx->bitbang);......if (master->transfer || master->transfer_one_message)//前面沒有設定,繼續往下走............master->prepare_transfer_hardware = spi_bitbang_prepare_hardware;master->unprepare_transfer_hardware = spi_bitbang_unprepare_hardware;master->transfer_one_message = spi_bitbang_transfer_one;//傳送一個spi資料
//前面已經設定,因此不會走這個if邏輯if (!bitbang->txrx_bufs){bitbang->use_dma = 0;bitbang->txrx_bufs = spi_bitbang_bufs;if (!master->setup){if (!bitbang->setup_transfer)bitbang->setup_transfer = spi_bitbang_setup_transfer;master->setup = spi_bitbang_setup;master->cleanup = spi_bitbang_cleanup;}}ret = spi_register_master(spi_master_get(master));//註冊SPI控制器status = of_spi_register_master(master);//註冊SPI控制器......INIT_LIST_HEAD(&master->queue);//初始佇列頭(SPI就是通過佇列來處理資料)............status = device_add(&master->dev);//向核心中新增裝置........../* If we're using a queued driver, start the queue */if (master->transfer)//這個msster->transfer在前面並沒有初始化,所以走else分支dev_info(dev, "master is unqueued, this is deprecated\n");else{status = spi_master_initialize_queue(master);//初始化一個佇列master->transfer = spi_queued_transfer;//由於前面沒有初始化master->transfer函式,因此在這裡進行初始化了if (!master->transfer_one_message)//這個函式在前面spi_bitbang_start函式開頭已經設定了,因此跳過這裡的if邏輯master->transfer_one_message = spi_transfer_one_message;/* Initialize and start queue */ret = spi_init_queue(master);//初始化一個佇列//使用核心執行緒的方式來實現SPI資料的處理()init_kthread_worker(&master->kworker);master->kworker_task = kthread_run(kthread_worker_fn, &master->kworker, "%s", dev_name(&master->dev));if (IS_ERR(master->kworker_task)){dev_err(&master->dev, "failed to create message pump task\n");return PTR_ERR(master->kworker_task);}init_kthread_work(&master->pump_messages, spi_pump_messages);}....}//以上,SPI控制器的初始化就完成了//接下來分析資料傳送的函式呼叫流程//前面初始化傳送函式是spi_imx->tx(spi_imx);那麼實際驅動中是如何呼叫呢?//傳送函式(有同步和非同步兩種方式,在這裡只分析同步方式)int spi_sync(struct spi_device *spi, struct spi_message *message);__spi_sync(spi, message, 0);//同步傳送函式(函式會阻塞,可能引起睡眠,因此不能在中斷上下文及tasklet中呼叫,只能在workqueue中使用)............status = __spi_queued_transfer(spi, message, false);//傳送一個SPI資料(spi資料是一message資料塊的形式進行傳送)............queue_kthread_work(&master->kworker, &master->pump_messages);//排程核心執行緒insert_kthread_work(worker, work, &worker->work_list);//到這裡結束,剩下的轉去核心執行緒的執行函式去分析spi_pump_messages//核心執行緒的執行函式static void spi_pump_messages(struct kthread_work *work)..............ret = master->transfer_one_message(master, master->cur_msg);//呼叫前面在spi_bitbang_start函式中初始化的master->transfer_one_message = spi_bitbang_transfer_one;//傳送一個spi資料//轉去分析static int spi_bitbang_transfer_one(struct spi_master *master, struct spi_message *m)/* transfer data. the lower level code handles any * new dma mappings it needs. our caller always gave * us dma-safe buffers.*/if (t->len){/* REVISIT dma API still needs a designated* DMA_ADDR_INVALID; ~0 might be better.*/if (!m->is_dma_mapped)t->rx_dma = t->tx_dma = 0;status = bitbang->txrx_bufs(spi, t);//因為前面沒有使用DMA,所以正式傳送資料,走的是這個邏輯,跳轉去分析bitbang->txrx_bufs(spi, t);}//函式指標bitbang->txrx_bufs(spi, t);在spi_imx_probe函式中進行初始化,賦值為spi_imx_transferstatic int spi_imx_transfer(struct spi_device *spi, struct spi_transfer *transfer)......spi_imx_pio_transfer(spi, transfer);......spi_imx_push(spi_imx);//裡面呼叫spi_imx->tx進行資料傳送spi_imx->tx(spi_imx);//呼叫傳送函式,關鍵:spi_imx->tx的賦值,在前面函式spi_imx_setupxfer中完成(分析到這裡,已經和前面的初始化傳送函式完全對應上了)

二、Kernel_3.0.35版本NXP官方在3.0.35版本的核心,控制器硬體平臺的初始化和4.1.15版本的核心是差不多的,不同的是在兩個核心版本之間,對於SPI的資料處理機制不一樣所以省去一部分相同的初始流程,直接分析差異。static int __devinit spi_imx_probe(struct platform_device *pdev)............ret = spi_bitbang_start(&spi_imx->bitbang);......INIT_WORK(&bitbang->work, bitbang_work);//注意這裡初始化了一個工作佇列,這是在4.1.15版本的核心所沒有的//在排程這個工作佇列的時候會執行bitbang_work函式INIT_LIST_HEAD(&bitbang->queue);//初始化佇列頭......//前面沒有初始化,所以這個if邏輯是成立的if (!bitbang->master->transfer)bitbang->master->transfer = spi_bitbang_transfer;//傳送資料if (!bitbang->txrx_bufs)//前面已經初始化了,所以這個邏輯不成立{bitbang->use_dma = 0;bitbang->txrx_bufs = spi_bitbang_bufs;if (!bitbang->master->setup) {if (!bitbang->setup_transfer)bitbang->setup_transfer = spi_bitbang_setup_transfer;bitbang->master->setup = spi_bitbang_setup;bitbang->master->cleanup = spi_bitbang_cleanup;}}else if (!bitbang->master->setup)//前面probe函式中也初始化了,所以這個邏輯也不成立{return -EINVAL;}if (bitbang->master->transfer == spi_bitbang_transfer && !bitbang->setup_transfer)//前面probe函式中也初始化了,所以這個邏輯也不成立{return -EINVAL;}......//建立工作佇列,但是這裡有個疑問,呼叫create_singlethread_workqueue建立的工作佇列,只能在CPU0上工作//對於多CPU的情況,是不是沒有發揮多CPU的效率優勢呢?還是說核心在SPI的實現機制上有bug,避免競爭只能這麼做?//這種使用工作佇列來實現SPI資料處理的方式和4.1.15版本有所不同(其實在3.1.14版本就已經不一樣了)bitbang->workqueue = create_singlethread_workqueue(dev_name(bitbang->master->dev.parent));......status = spi_register_master(bitbang->master);//向核心註冊SPI裝置//前面已經呼叫INIT_WORK(&bitbang->work, bitbang_work);//在排程這個工作佇列的時候會執行bitbang_work函式,所以這裡插入分析bitbang_work函式static void bitbang_work(struct work_struct *work)....../* transfer data. the lower level code handles any * new dma mappings it needs. our caller always gave * us dma-safe buffers.*/if (t->len) //這部分是和4.1.15版本的核心一樣{/* REVISIT dma API still needs a designated * DMA_ADDR_INVALID; ~0 might be better. */if (!m->is_dma_mapped)t->rx_dma = t->tx_dma = 0;status = bitbang->txrx_bufs(spi, t);//呼叫傳送函式bitbang->txrx_bufs(spi, t);在probe中被賦值spi_imx_push(spi_imx);spi_imx->tx(spi_imx);}3.0.15版本SPI同步資料的呼叫流程static int __spi_sync(struct spi_device *spi, struct spi_message *message, int bus_locked)int spi_async_locked(struct spi_device *spi, struct spi_message *message)ret = __spi_async(spi, message);master->transfer(spi, message);//找到這個函式master->transfer(spi, message);的賦值,在int spi_bitbang_start(struct spi_bitbang *bitbang)中賦值的bitbang->master->transfer = spi_bitbang_transfer;//傳送資料queue_work(bitbang->workqueue, &bitbang->work);//排程工作佇列,執行工作佇列函式static void bitbang_work(struct work_struct *work)spi_imx_push(spi_imx);spi_imx->tx(spi_imx);//到這裡,初始化和傳送的流程已經前後對應起來//兩個核心版本在SPI控制器的初始化流程都差不多,唯一不同的是核心在實現SPI最核心的資料處理時使用了兩種不同的實現機制。//搜尋整個核心發現,使用核心執行緒來實現的模組很少,4.1.15版本放棄了原來的工作佇列方式而使用核心執行緒的方式顯然可能是原來工作佇列的方式滿足不了傳輸效率吧//由於使用了核心執行緒的實現方式,所以我們可以很容易的改變排程的策略賦予不同的優先順序。在某些對傳輸效率及時間有較高要求的場景4.1.15版本的實現//方式明顯是要優於3.0.35版本的。