ddk驱动简单示例
#簡單的驅(qū)動程序
本文檔是[Driver Development Kit教程](ddk-tutorial.md)文檔的一部分。
##概述
在本章中,我們將了解驅(qū)動程序的基礎(chǔ)知識。
我們將從簡單到稍微復(fù)雜,每個驅(qū)動程序說明一組具體的概念如下:
`dev/misc/demo-null` 和 `dev/misc/demo-zero`:
*小的,“no-state”的接收器/源驅(qū)動程序,用于解釋基本知識,比如,
如何處理client端的** read()**和** write()**請求。
`dev/misc/demo-number`:
*一個返回ASCII碼的驅(qū)動程序,說明了每個設(shè)備的上下文,一次性**讀取()**
????operation,并介紹基于FIDL的控制operation。
`dev/misc/demo-multi`:
*具有多個子設(shè)備的驅(qū)動程序。
`dev/misc/demo-fifo`:
*顯示更復(fù)雜的設(shè)備狀態(tài),檢查部分** read()**和** write()**operation,以及
????引入狀態(tài)信令以啟用阻塞I/O.
作為參考,所有這些驅(qū)動程序的源代碼都在`//zircon/system/dev/sample`目錄。
##注冊
一個叫設(shè)備管理器的系統(tǒng)進程(后文稱`devmgr`),負責設(shè)備驅(qū)動程序。
在初始化期間,它會在`/boot/driver`和`/system/driver`目錄中搜索驅(qū)動程序。
<! - ?@@@ TODO Brian說,當我們切換到基于包管理的世界時,/system將要消失
。此時這些驅(qū)動程序?qū)⒂蒰arnet中的包管理器提供 - >
這些驅(qū)動程序?qū)崿F(xiàn)為動態(tài)共享對象(** DSO **),并提供兩個有意思的Items:
*一組用于評估驅(qū)動程序綁定的`devmgr`指令
*一組綁定函數(shù)。
讓我們看一下`dev/sample/null`目錄中`demo-null.c`的底部:
```C
static zx_driver_ops_t demo_null_driver_ops = {
????.version = DRIVER_OPS_VERSION,
????.bind = null_bind,
};
ZIRCON_DRIVER_BEGIN(demo_null_driver,demo_null_driver_ops,“zircon”,“0.1”,1)
????BI_MATCH_IF(EQ,BIND_PROTOCOL,ZX_PROTOCOL_MISC_PARENT),
ZIRCON_DRIVER_END(demo_null_driver)
```
<! - ?@@@ alainv sez這些宏將被棄用,轉(zhuǎn)而使用驅(qū)動綁定語言(Driver Binding Language) ?- >
C預(yù)處理器宏`ZIRCON_DRIVER_BEGIN`和`ZIRCON_DRIVER_END`限定在DSO中創(chuàng)建的ELF note section。
本section包含一個或多個由`devmgr`評估(evaluated)的語句。
在上面,如果設(shè)備的“BIND_PROTOCOL”等于`ZX_PROTOCOL_MISC_PARENT`,宏`BI_MATCH_IF`是一個評估為'true`的條件。
`true`評估會導(dǎo)致`devmgr`隨后使用在`ZIRCON_DRIVER_BEGIN`宏中提供的綁定operation綁定驅(qū)動程序。
我們現(xiàn)在可以忽略這個“glue”,只需注意這部分代碼:
*告訴`devmgr`,這個驅(qū)動程序可以綁定到需要`ZX_PROTOCOL_MISC_PARENT`protocol的設(shè)備,同時它包含一個指向`zx_drivers_ops_t`表的指針,該表列出了此DSO提供的功能。
要初始化設(shè)備,`devmgr`通過`.bind`成員(也在`demo-null.c`中),調(diào)用綁定函數(shù)** null_bind()**:
```C
static zx_protocol_device_t null_device_ops = {
????.version = DEVICE_OPS_VERSION,
????.read = null_read,
????.write = null_write,
};
zx_status_t null_bind(void * ctx,zx_device_t * parent){
????device_add_args_t args = {
????????.version = DEVICE_ADD_ARGS_VERSION,
????????.name =“demo-null”,
????????.ops =&null_device_ops,
????};
????return device_add(parent,&args,NULL);
}
```
綁定功能負責通過調(diào)用** device_add()**(入?yún)?#xff1a;指向父設(shè)備的指針、參數(shù)結(jié)構(gòu)體)來“publishing”設(shè)備。
新設(shè)備被綁定到父路徑名&mdash;
注意我們?nèi)绾蝹鬟f上面`.name`中的`demo-null`成員。
`.ops`成員是`zx_protocol_device_t`結(jié)構(gòu)的指針,它列出了適用于該設(shè)備operation。
我們將在下面看到這些函數(shù),** null_read()**和** null_write()**。
在調(diào)用** device_add()**后,設(shè)備名稱已注冊,并且參數(shù)傳遞的`.ops`成員方法也已經(jīng)綁定到設(shè)備。
從** null_bind()**成功返回,告訴'devmgr'驅(qū)動程序現(xiàn)在已經(jīng)與設(shè)備關(guān)聯(lián)起來了。
此時,我們的`/dev/misc/demo-null`設(shè)備已準備好處理client端請求,
這意味著它必須:
*支持** open()**和** close()**
*提供一個** read()**處理程序,它立即返回文件結(jié)尾(** EOF **)
*提供一個** write()**處理程序,丟棄發(fā)送給它的所有數(shù)據(jù)
無需其他功能。
##從設(shè)備讀取數(shù)據(jù)
在`zx_protocol_device_t`結(jié)構(gòu)的`null_device_ops`成員中,提供了我們支持的operation:** null_read()**和** null_write()**。
** null_read()**函數(shù)提供讀功能:
```C
static zx_status_t
null_read(void * ctx,void * buf,size_t count,zx_off_t off,size_t * actual){
????* actual = 0;
????return ZX_OK;
}
```
請注意,有兩個與大小相關(guān)的參數(shù)傳遞給處理程序:
參數(shù) ? ? ? ? | 含義
------------ | ------------------------------------- -------------------
`count` ? ? ?| client端可以接受的最大字節(jié)數(shù)
`actual` ? ? | 發(fā)送到client端的實際字節(jié)數(shù)
下圖說明了這種關(guān)系:
圖1 TODO
也就是說,client端緩沖區(qū)的可用大小(此處為`sizeof(buf)`)作為`count`參數(shù)傳遞給** null_read()**。
類似地,當調(diào)用** null_read()**函數(shù),表示它讀取的字節(jié)數(shù)(在我們的例子中為0)時,這個
顯示為client端** read()**函數(shù)的返回值。
> **注意:
>處理程序應(yīng)始終立即返回。
>按照慣例,在'* actual`中指示零字節(jié)表示EOF **
當然,有些情況下設(shè)備沒有立即可用的數(shù)據(jù),這時它不是EOF情況。
例如,串行端口可能正在等待從遠程端到達更多字符。
這是由特殊通知處理的,我們將在下面的`/dev/misc/demo-fifo`設(shè)備中看到。
###將數(shù)據(jù)寫入設(shè)備
將數(shù)據(jù)從client端寫入設(shè)備幾乎是相同的,并提供 ** null_write()**函數(shù):
```C
static zx_status_t
null_write(void * ctx,const void * buf,size_t count,zx_off_t off,size_t * actual){
????* actual = count;
????return ZX_OK;
}
```
與** read()**一樣,** null_write()**由client端調(diào)用** write()**觸發(fā):
圖2 TODO
client端在其** write()**函數(shù)中指定了他們希望傳輸?shù)淖止?jié)數(shù),這將在設(shè)備的** null_write()**函數(shù)中顯示為`count`參數(shù)。
該設(shè)備可能已滿(不是我們的`/dev/misc/demo-null`的情況,盡管—它永遠不會填滿),因此設(shè)備需要告訴client端它實際上寫了多少字節(jié)。
這是通過`actual`參數(shù)完成的,該參數(shù)顯示為client端** write()**函數(shù)的返回值。
請注意,我們的** null_write()**函數(shù)包含以下代碼:
```C
* actual = count;
```
這告訴client端他們的所有數(shù)據(jù)都已寫入。
當然,因為這是`/dev/misc/demo-null`設(shè)備,數(shù)據(jù)實際上并沒有*去*任何地方。
> **注意:
>就像** ** null_read()** **情況一樣,處理程序不能阻塞。**
##如何** open()**和** close()**?
我們沒有提供** open()**或** close()**處理程序,但我們的設(shè)備支持這些operation。
這是可能的,因為任何未提供的operation回調(diào)函數(shù)都具有默認值。
大多數(shù)默認值只返回“不支持”,但在** open()**和** close()**的情況下
默認設(shè)置為簡單設(shè)備提供足夠的支持。
##`/dev/misc/demo-zero`
正如您可能想象的那樣,`/dev/misc/demo-zero`設(shè)備的源代碼幾乎與`/dev/misc/demo-null`完全相同。
從operation的角度來看,`/dev/misc/demo-zero`應(yīng)該會返回無窮無盡個零 —只要client去read。
我們不支持write。
考慮`/dev/misc/demo-zero`的** zero_read()**函數(shù):
```C
static zx_status_t
zero_read(void * ctx,void * buf,size_t count,zx_off_t off,size_t * actual){
????memset(buf,0,count);
????* actual = count;
????return ZX_OK;
}
```
代碼將整個緩沖區(qū)`buf`設(shè)置為零(長度由client端給出的`count`參數(shù)確定),并告訴client端有多少字節(jié)可用(通過按client要求,將`* actual`設(shè)置為相同的數(shù)字)。
##`/dev/misc/demo-number`
讓我們根據(jù)上面學到的概念構(gòu)建一個更復(fù)雜的設(shè)備。
我們將其稱為`/dev/misc/demo-number`,其作用是返回以ASCII字符串形式顯示的下一個數(shù)字。
例如,以下可能是使用該設(shè)備的典型命令行會話:
```shell
$ cat /dev/misc/demo-number
0
$ cat /dev/misc/demo-number
1
$ cat /dev/misc/demo-number
2
```
`/dev/misc/demo-null`立即返回EOF,`/dev/misc/demo-zero`返回永無止境的零,而`/dev/misc/demo-number`是中間的一種:它需要返回一個短數(shù)據(jù)序列,然后返回EOF。
在現(xiàn)實世界中,client端可以一次讀取一個字節(jié),或者它可以請求大緩沖區(qū)來存下足夠的數(shù)據(jù)。
對于我們的初始版本,我們假設(shè)client端要求一個“大”的緩沖區(qū),足以立即獲取所有數(shù)據(jù)。
這意味著我們可以采取捷徑。
有一個偏移參數(shù)(`zx_off_t off`)作為第4個參數(shù)傳遞給** read()**處理函數(shù):
```C
static zx_status_t
number_read(void * ctx,void * buf,size_t count,zx_off_t off,size_t * actual)
```
這表明client希望開始(或繼續(xù))read的位置。
我們在這里進行的簡化是,如果client端的偏移量為零,則意味著它從頭開始讀,所以我們返回的數(shù)據(jù)與client端可以處理的數(shù)據(jù)一樣多。
但是,如果偏移量不為零,則返回“EOF”。
讓我們討論代碼(請注意,我們最初提供的版本比在源目錄中版本稍微簡單一些):
```C
static int global_counter; //好與壞,見下文
static zx_status_t
number_read(void * ctx,void * buf,size_t count,zx_off_t off,size_t * actual){
????//(1)我們?yōu)槭裁丛谶@里?
????if(off == 0){
????????//(2)第一次read;盡可能多地返回數(shù)據(jù)
????????int n = atomic_add(&global_counter);
????????char tmp [22]; // 2^64是20位+ \ n + nul = 22個字節(jié)
????????* actual = snprintf(tmp,sizeof(tmp),“%d \ n”,n);
????????if(* actual> count){
????????????* actual = count;
????????}
????????memcpy(buf,tmp,* actual);
????} else {
????????//(3)不是第一次 - 返回EOF
????????* actual = 0;
????}
????return ZX_OK;
}
```
我們做出的第一個決定是在步驟(1)中,我們確定client是否是第一次read字符串。
如果偏移量為零,那么這是第一次。
在這種情況下,在步驟(2)中,我們從`global_counter`中獲取一個值,將其放入一個字符串中,并告訴client端我們返回了一些字節(jié)數(shù)。
我們返回的字節(jié)數(shù)限制為以下判斷的較小值:
*client端緩沖區(qū)的大小(由`count`給出)
*生成的字符串的大小(從** snprintf()**返回的)。
但是,如果偏移量不為零,則意味著它不是client端的第一次從該設(shè)備讀取數(shù)據(jù)。
在這種情況下,在步驟(3)中,我們只需設(shè)置我們返回的字節(jié)數(shù)('* actual`)為零,這具有向client端指示“EOF”的效果(就像它在上面的`null`驅(qū)動程序中完成了一樣。
### Globals(全局變量)很糟糕
我們使用的`global_counter`對驅(qū)動程序來說是全局的。
這意味著最終調(diào)用** number_read()**的每個會話都以增加這個數(shù)字結(jié)束。
這是預(yù)期的&mdash;畢竟,`/dev/misc/demo-number`的工作是“分發(fā)增長的數(shù)字給client”。
可能沒有預(yù)期的是,如果驅(qū)動程序被多次實例化(例如,使用真實的硬件驅(qū)動程序,就可能會發(fā)生),然后這個全局變量就會在不同的實例中共享。
通常,這不是您想要的真正的硬件驅(qū)動程序(因為每個驅(qū)動程序?qū)嵗仟毩⒌?#xff09;。
解決方案是創(chuàng)建“per-device”上下文塊;這個上下文塊將包含每個設(shè)備唯一的數(shù)據(jù)。
為了創(chuàng)建每個設(shè)備的上下文塊,我們需要調(diào)整我們的綁定例程。
回想一下,綁定例程是在設(shè)備和其protocol ops之間建立關(guān)聯(lián)的地方。
如果我們要在綁定例程中創(chuàng)建上下文塊,那么我們就可以了在我們稍后的讀處理程序中使用它:
```C
typedef struct {
????zx_device_t * zxdev;
????uint64_t counter;
} number_device_t;
zx_status_t
number_bind(void * ctx,zx_device_t * parent){
????//分配和初始化每個設(shè)備的上下文塊
????number_device_t * device = calloc(1,sizeof(* device));
????if(!device){
????????return ZX_ERR_NO_MEMORY;
????}
????device_add_args_t args = {
????????.version = DEVICE_ADD_ARGS_VERSION,
????????.name =“demo-number”,
????????.ops =&number_device_ops,
????????.ctx =device,
????};
????zx_status_t rc = device_add(parent,&args,&device-> zxdev);
????if(rc!= ZX_OK){
????????free(device);
????}
????return rc;
}
```
這里我們已經(jīng)分配了一個上下文塊并將其存儲在`device_add_args_t`的`ctx`成員中,`args`我們傳遞給** device_add()**。
現(xiàn)在,在綁定時創(chuàng)建的唯一的上下文塊實例與每個綁定的設(shè)備實例相關(guān)聯(lián),并可使用所有的通過** number_bind()**綁定的protocol函數(shù)。
請注意,雖然我們不使用上下文塊中的`zxdev`設(shè)備,但這是一個很好的做法,如果我們以后需要它用于任何其他設(shè)備相關(guān)operation,請堅持下去。
圖 TODO
上下文塊可以在`number_device_ops`定義的所有protocol函數(shù)中使用,例如
我們的** number_read()**函數(shù):
```C
static zx_status_t
number_read(void * ctx,void * buf,size_t count,zx_off_t off,size_t * actual){
????if(off == 0){
????????number_device_t * device = ctx;
????????int n = atomic_fetch_add(&device-> counter,1);
????????// ------------------------------------------------
????????//其他一切與以前的版本相同
????????// ------------------------------------------------
????????char tmp [22]; // 2^64是20位+ \ n + \ 0
????????* actual = snprintf(tmp,sizeof(tmp),“%d \ n”,n);
????????if(* actual> count){
????????????* actual = count;
????????}
????????memcpy(buf,tmp,* actual);
????} else {
????????* actual = 0;
????}
????return ZX_OK;
}
```
請注意我們?nèi)绾问褂蒙舷挛膲K中的值替換原始版本的`global_counter`。
使用上下文塊,每個設(shè)備都有自己獨立的計數(shù)器。
###清理上下文
當然,每次我們** calloc()**的時候,我們都要在某處** free()**。
這是在我們的** number_release()**處理程序中完成的,我們將它存儲在`zx_protocol_device_t中
number_device_ops`結(jié)構(gòu):
```C
static zx_protocol_device_t
number_device_ops = {
????//其他初始化...
????.release = number_release,
};
```
** number_release()**函數(shù)很簡單:
```C
static void
number_release(void* ctx) {
? ? free(ctx);
}
```
在卸載驅(qū)動程序之前調(diào)用** number_release()**函數(shù)。
###控制您的設(shè)備
有時,需要向設(shè)備發(fā)送控制消息。
這是不通過** read()** / ** write()**接口傳播的數(shù)據(jù)。
例如,在`/dev/misc/demo-number`中,我們可能想要一種將計數(shù)預(yù)設(shè)為給定數(shù)字的方法。
在傳統(tǒng)的POSIX環(huán)境中,這是通過client端上的** ioctl()**調(diào)用完成的
在驅(qū)動程序端,以及適當?shù)?* ioctl()**處理程序。
在Fuchsia下,通過FIDL語言來編組數(shù)據(jù)
([** FIDL **](https://fuchsia.googlesource.com/fuchsia/+/master/docs/development/languages/fidl/README.md))。
有關(guān)FIDL本身的更多詳細信息,請參閱上面的參考。
對于我們這里的目的,FIDL:
*由類似C語言描述,
*用于定義控件功能的輸入和輸出參數(shù),
*為client端和驅(qū)動程序端生成代碼。
>如果您已經(jīng)熟悉Google的“Protocol Buffers”,那么您使用FIDL會非常舒服。
FIDL有許多優(yōu)點。
因為輸入和輸出參數(shù)是明確定義的,結(jié)果是在client端和驅(qū)動程序上生成具有嚴格類型安全性和檢查的代碼。
通過從其實現(xiàn)中抽象出消息的定義,FIDL代碼生成器可以生成多種不同語言的代碼,無需額外的工作。
這特別有用,例如,當client需要用您不一定熟悉的語言的API時。
####使用FIDL
在大多數(shù)情況下,您將使用設(shè)備已提供的FIDL API,并且很少需要創(chuàng)建自己的。
但是,了解機制是一個好主意,端到端。
使用FIDL進行設(shè)備控制很簡單:
*在“.fidl”文件中定義輸入,輸出和接口,
*編譯FIDL代碼并生成client端函數(shù)
*將消息處理程序添加到驅(qū)動程序以接收控制消息。
我們將通過為我們的`/dev/misc/demo-number`驅(qū)動程序?qū)崿F(xiàn)“預(yù)設(shè)值計數(shù)器”控制功能,來查看這些步驟。
####定義FIDL接口
我們需要做的第一件事是定義interface。
因為我們要做的就是將計數(shù)預(yù)設(shè)為用戶指定的值,我們的interface非常簡單。
這就是“`.fidl`”文件的樣子:
```FIDL
library zircon.sample.number;
[Layout="Simple"]
interface Number {
? ? // set the number to a given value
? ? SetNumber(uint32 value) -> (uint32 previous);
};
```
第一行,`library zircon.sample.number;`為生成的庫提供了一個名稱。
接下來,`[Layout =“Simple”]`生成[簡單的C綁定](https://fuchsia.googlesource.com/fuchsia/+/master/docs/development/languages/fidl/languages/c.md#simple-bindings).
最后,`interface`部分定義了所有可用的接口。
每個接口都有編號,有名稱,并指定輸入和輸出。
在這里,我們有一個名為** SetNumber()**的接口函數(shù),它接受一個`uint32`(即FIDL等效于C標準整數(shù)`uint32_t`類型)作為輸入,并返回一個`uint32`結(jié)果(計數(shù)器更改的前一個值)。
我們將在下面看到更多高級示例。
####編譯FIDL代碼
FIDL代碼由構(gòu)建系統(tǒng)自動編譯;你只需要添加一個依賴項
進入`rules.mk` makefile。
假設(shè)調(diào)用“.fidl`”文件,這就是獨立的`rules.mk`的樣子
`demo_number.fidl`:
```makefile文件
LOCAL_DIR:= $(GET_LOCAL_DIR)
MODULE:= $(LOCAL_DIR)
MODULE_TYPE:= fidl
MODULE_PACKAGE:= fidl
MODULE_FIDL_LIBRARY:= zircon.sample.number
MODULE_SRCS + = $(LOCAL_DIR)/demo_number.fidl
include make / module.mk
```
編譯完成后,接口文件將顯示在構(gòu)建輸出目錄中。
確切的路徑取決于構(gòu)建目標(例如,...`/zircon/build-x64/`... x86 64位版本),以及包含F(xiàn)IDL文件的源目錄。
對于此示例,我們將使用以下路徑:
<dl>
<dt>...`/zircon/system/dev/sample/number/demo-number.c`
<dd>source file for `/dev/misc/demo-number` driver
<dt>...`/zircon/system/fidl/zircon-sample/demo_number.fidl`
<dd>source file for FIDL interface definition
<dt>...`/zircon/build-x64/system/fidl/zircon-sample/gen/include/zircon/sample/number/c/fidl.h`
<dd>generated interface definition header include file
</dl>
查看由FIDL編譯器生成的接口定義頭文件是有益的。
在這里,它會進行注釋和編輯,以顯示亮點:
```C
//(1)前向聲明
#define zircon_sample_number_NumberSetNumberOrdinal((uint32_t)0x1)
//(2)外部聲明
extern const fidl_type_t zircon_sample_number_NumberSetNumberRequestTable;
extern const fidl_type_t zircon_sample_number_NumberSetNumberResponseTable;
//(3)聲明
struct zircon_sample_number_NumberSetNumberRequest {
????fidl_message_header_t hdr;
????uint32_t value;
};
struct zircon_sample_number_NumberSetNumberResponse {
????fidl_message_header_t hdr;
????uint32_t result;
};
//(4)client端綁定原型
zx_status_t
zircon_sample_number_NumberSetNumber(zx_handle_t _channel,
?????????????????????????????????????uint32_t value,
?????????????????????????????????????uint32_t * out_result);
//(5)FIDL消息operation結(jié)構(gòu)
typedef struct zircon_sample_number_Number_ops {
????zx_status_t(* SetNumber)(void * ctx,uint32_t value,fidl_txn_t * txn);
} zircon_sample_number_Number_ops_t;
//(6)調(diào)度原型
zx_status_t
zircon_sample_number_Number_dispatch(void * ctx,fidl_txn_t * txn,fidl_msg_t * msg,
?????????????????????????????????????const zircon_sample_number_Number_ops_t * ops);
zx_status_t
zircon_sample_number_Number_try_dispatch(void * ctx,fidl_txn_t * txn,fidl_msg_t * msg,
?????????????????????????????????????????const zircon_sample_number_Number_ops_t * ops);
//(7)回復(fù)原型
zx_status_t
zircon_sample_number_NumberSetNumber_reply(fidl_txn_t * _txn,uint32_t result);
```
>請注意,此生成的文件包含與client端*和*驅(qū)動程序相關(guān)的代碼。
簡而言之,生成的代碼表示:
1.命令編號的定義(“NumberOrdinal`”,回想一下我們使用命令
????** SetNumber()**)的數(shù)字`1`,
2.表的外部定義(我們不使用這些),
3.請求和響應(yīng)消息格式的聲明;這些包括FIDL開銷標題和我們指定的數(shù)據(jù),
4.client端綁定原型&mdash;我們將看到client端如何使用以下內(nèi)容,
5. FIDL消息operation結(jié)構(gòu);這是您在驅(qū)動程序中提供的功能列表
????處理“.fidl`”文件中定義的每個FIDL接口,
6.顯示原型&mdash;這是由我們的FIDL消息處理程序調(diào)用的,
7.回復(fù)原型&mdash;當我們想要回復(fù)client端時,我們在驅(qū)動程序中調(diào)用它。
####client端
讓我們從一個基于命令行的小型client端開始,稱為`set_number`,使用上面的FIDL接口。
它假定我們控制的設(shè)備稱為`/dev/misc/demo-number`。
該程序只需要一個參數(shù)&mdash;設(shè)置當前計數(shù)器的數(shù)字。
這是程序operation的示例:
```bash
$ cat /dev/misc/demo-number
0
$ cat /dev/misc/demo-number
1
$ cat /dev/misc/demo-number
2
$ set_number 77
Original value was 3
$ cat /dev/misc/demo-number
77
$ cat /dev/misc/demo-number
78
```
完整的程序如下:
```C
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <zircon/syscalls.h>
#include <lib/fdio/util.h>
// (1) include the generated definition file
#include <zircon/sample/number/c/fidl.h>
int main(int argc, const char** argv)
{
? ? static const char* dev = "/dev/misc/demo-number";
? ? // (2) get number from command line
? ? if (argc != 2) {
? ? ? ? fprintf(stderr, "set_number: ?needs exactly one numeric argument,"
? ? ? ? ? ? ? ? " the value to set %s to\n", dev);
? ? ? ? exit(EXIT_FAILURE);
? ? }
? ? uint32_t n = atoi(argv[1]);
? ? // (3) establish file descriptor to device
? ? int fd = open(dev, O_RDWR);
? ? if (fd == -1) {
? ? ? ? fprintf(stderr, "set_number: can't open %s for O_RDWR, errno %d (%s)\n",
? ? ? ? ? ? ? ? dev, errno, strerror(errno));
? ? ? ? exit(EXIT_FAILURE);
? ? }
? ? // (4) establish handle to FDIO service on device
? ? zx_handle_t num;
? ? zx_status_t rc;
? ? if ((rc = fdio_get_service_handle(fd, &num)) != ZX_OK) {
? ? ? ? fprintf(stderr, "set_number: can't get fdio service handle, error %d\n", rc);
? ? ? ? exit(EXIT_FAILURE);
? ? }
? ? // (5) send FDIO command, get response
? ? uint32_t orig;
? ? if ((rc = zircon_sample_number_NumberSetNumber(num, n, &orig)) != ZX_OK) {
? ? ? ? fprintf(stderr, "set_number: can't execute FIDL command to set number, error %d\n", rc);
? ? ? ? exit(EXIT_FAILURE);
? ? }
? ? printf("Original value was %d\n", orig);
? ? exit(EXIT_SUCCESS);
}
```
這與使用POSIX ** ioctl()**的方法非常相似,不同之處在于:
*我們建立了FDIO服務(wù)的句柄(步驟4),和
* API對特定類型operation是type-safe 和 prototyped(步驟5)。
請注意,FDIO命令有一個很長的名稱:** zircon_sample_number_NumberSetNumber()**
(其中包括大量重復(fù))。
這是FIDL編譯器代碼生成過程的一個反應(yīng)&mdash;該
“`zircon_sample_number`”部分來自“`library zircon.sample.number`”
聲明,第一個“`Number`”來自“`interface Number`”語句和最后一個
“`SetNumber`”是接口定義語句中接口的名稱。
####向驅(qū)動程序添加消息處理程序
在驅(qū)動方面,我們需要:
*處理FIDL消息
*解復(fù)用消息(找出它是哪個控制消息)
*生成回復(fù)
結(jié)合上面的原型,來處理我們的FIDL控制消息
我們需要綁定一個消息處理函數(shù)(就像我們按順序執(zhí)行的那樣)
處理** read()**,例如):
```C
static zx_protocol_device_t number_device_ops = {
????.version = DEVICE_OPS_VERSION,
????.read = number_read,
????.release = number_release,
????.message = number_message,//處理FIDL消息
};
```
在這種情況下,** number_message()**函數(shù)是微不足道的;它只是包裝發(fā)貨功能:
```C
static zircon_sample_number_Number_ops_t number_fidl_ops = {
????.SetNumber = fidl_SetNumber,
};
static zx_status_t number_message(void * ctx,fidl_msg_t * msg,fidl_txn_t * txn){
????zx_status_t status = zircon_sample_number_Number_dispatch(ctx,txn,msg,&number_fidl_ops);
????return status;
}
```
生成的** zircon_sample_number_Number_dispatch()**函數(shù)接收傳入消息
并根據(jù)提供的函數(shù)表調(diào)用適當?shù)奶幚砗瘮?shù)`number_fidl_ops`。
當然,在我們這個簡單的例子中,只有一個函數(shù)`SetNumber`:
```c
static zx_status_t fidl_SetNumber(void* ctx, uint32_t value, fidl_txn_t* txn)
{
? ? number_device_t* device = ctx;
? ? int saved = device->counter;
? ? device->counter = value;
? ? return zircon_sample_number_NumberSetNumber_reply (txn, saved);
}
```
** fidl_SetNumber()**處理程序:
*建立指向設(shè)備上下文的指針,
*保存當前計數(shù)值(以便以后可以返回),
*將新值設(shè)置到設(shè)備上下文中,并且
*調(diào)用“回復(fù)”函數(shù)將值返回給客戶端。
請注意,** fidl_SetNumber()**函數(shù)具有與FIDL匹配的原型規(guī)格,確保類型安全。同樣,回復(fù)功能,
** zircon_sample_number_NumberSetNumber_reply()**也符合FIDL規(guī)范的接口定義結(jié)果部分的原型。
####高級用途
FIDL表達式當然可以比我們上面顯示的更復(fù)雜。
例如,可以使用嵌套結(jié)構(gòu),而不是簡單的`uint32`。
輸入和輸出都允許多個參數(shù)。見
[FIDL參考](https://fuchsia.googlesource.com/fuchsia/+/master/docs/development/languages/fidl/README.md)。
##使用`/dev/misc/demo-multi`注冊多個設(shè)備
到目前為止,討論的設(shè)備是“單身人士”&mdash;也就是說,一個注冊名稱做了一件事
(`null`表示空設(shè)備,`number`表示數(shù)字設(shè)備,依此類推)。
如果您擁有一組所有執(zhí)行類似功能的設(shè)備,該怎么辦?
例如,您可能有一個具有16個通道的某種多通道控制器。
處理這個問題的正確方法是:
1.創(chuàng)建一個驅(qū)動程序?qū)嵗?#xff0c;
2.創(chuàng)建基本設(shè)備節(jié)點,和
3.在該基本設(shè)備下顯示您的子設(shè)備。
如上所述,創(chuàng)建驅(qū)動程序?qū)嵗且环N很好的做法,在“Globals is bad”中(我們稍后在這個特定的背景下再討論一下)。
在這個例子中,我們將創(chuàng)建一個基本設(shè)備`/dev/misc/demo-multi`,然后我們將在名為“0”到“15”的情況下創(chuàng)建16個子設(shè)備(例如,`/dev/misc/demo-multi/7`)。
```c
static zx_protocol_device_t multi_device_ops = {
? ? .version = DEVICE_OPS_VERSION,
? ? .read = multi_read,
? ? .release = multi_release,
};
static zx_protocol_device_t multi_base_device_ops = {
? ? .version = DEVICE_OPS_VERSION,
? ? .read = multi_base_read,
? ? .release = multi_release,
};
zx_status_t multi_bind(void* ctx, zx_device_t* parent) {
? ? // (1) allocate & initialize per-device context block
? ? multi_root_device_t* device = calloc(1, sizeof(*device));
? ? if (!device) {
? ? ? ? return ZX_ERR_NO_MEMORY;
? ? }
? ? device->parent = parent;
? ? // (2) set up base device args structure
? ? device_add_args_t args = {
? ? ? ? .version = DEVICE_ADD_ARGS_VERSION,
? ? ? ? .ops = &multi_base_device_ops, ? ? ? ? ?// use base ops initially
? ? ? ? .name = "demo-multi",
? ? ? ? .ctx = &device->base_device,
? ? };
? ? // (3) bind base device
? ? zx_status_t rc = device_add(parent, &args, &device->base_device.zxdev);
? ? if (rc != ZX_OK) {
? ? ? ? return rc;
? ? }
? ? // (4) allocate and bind sub-devices
? ? args.ops = &multi_device_ops; ? ? ? ? ? ? ? // switch to sub-device ops
? ? for (int i = 0; i < NDEVICES; i++) {
? ? ? ? char name[ZX_DEVICE_NAME_MAX + 1];
? ? ? ? sprintf(name, "%d", i);
? ? ? ? args.name = name; ? ? ? ? ? ? ? ? ? ? ? // change name for each sub-device
? ? ? ? device->devices[i] = calloc(1, sizeof(*device->devices[i]));
? ? ? ? if (device->devices[i]) {
? ? ? ? ? ? args.ctx = &device->devices[i]; ? ? // store device pointer in context
? ? ? ? ? ? device->devices[i]->devno = i; ? ? ?// store number as part of context
? ? ? ? ? ? rc = device_add(device->base_device.zxdev, &args, &device->devices[i]->zxdev);
? ? ? ? ? ? if (rc != ZX_OK) {
? ? ? ? ? ? ? ? free(device->devices[i]); ? ? ? // device "i" failed; free its memory
? ? ? ? ? ? }
? ? ? ? } else {
? ? ? ? ? ? rc = ZX_ERR_NO_MEMORY;
? ? ? ? }
? ? ? ? // (5) failure backout
? ? ? ? if (rc != ZX_OK) {
? ? ? ? ? ? for (int j = 0; j < i; j++) {
? ? ? ? ? ? ? ? device_remove(device->devices[j].zxdev);
? ? ? ? ? ? ? ? free(device->devices[j]);
? ? ? ? ? ? }
? ? ? ? ? ? device_remove(device->base_device.zxdev);
? ? ? ? ? ? free(device);
? ? ? ? ? ? return rc;
? ? ? ? }
? ? }
? ? return rc;
}
// (6) release the per-device context block
static void multi_release(void* ctx) {
? ? free(ctx);
}
```
步驟是:
1.建立設(shè)備上下文指針,以防多次加載此驅(qū)動程序。
2.創(chuàng)建并初始化我們將傳遞給** device_add()**的`args`結(jié)構(gòu).
????該結(jié)構(gòu)具有基本設(shè)備名稱,“`demo-multi`”和上下文指針到基本設(shè)備上下文塊`base_device`。
3.調(diào)用** device_add()**添加基本設(shè)備。
????現(xiàn)在已經(jīng)創(chuàng)建了`/dev/misc/demo-multi`。
????請注意,我們將新創(chuàng)建的設(shè)備存儲到`base_device.zxdev`中。這稍后將作為子設(shè)備的“父”設(shè)備。
4.現(xiàn)在創(chuàng)建16個子設(shè)備作為基礎(chǔ)(“父”)設(shè)備的子設(shè)備。
????請注意,我們將`ops`成員更改為指向子設(shè)備協(xié)議操作`multi_device_ops`而不是基本版本。
????每個子設(shè)備的名稱只是設(shè)備編號的ASCII表示。
????請注意,我們將設(shè)備編號索引`i`(0 .. 15)存儲在`devno`中作為上下文
????(我們有一個稱為`multi_devices`的上下文數(shù)組,我們很快就會看到)。
????我們還說明了動態(tài)分配每個子設(shè)備,而不是在父結(jié)構(gòu)中分配其空間。
????對于“熱插拔”設(shè)備來說,這是一個更現(xiàn)實的用例&mdash;你沒有必要分配一個大的上下文結(jié)構(gòu),或者執(zhí)行初始化工作,對于尚未存在的設(shè)備。
5.如果發(fā)生故障,我們需要刪除和釋放我們已添加的設(shè)備,包括基本設(shè)備和每設(shè)備上下文塊。
????請注意,我們釋放了但不包括失敗的設(shè)備索引。這就是** device_add()**失敗,我們在步驟4中對子設(shè)備結(jié)構(gòu)調(diào)用** free()**的原因。
6.我們在發(fā)布處理程序中發(fā)布了每設(shè)備上下文塊。
###哪個設(shè)備是哪個?
我們有兩個** read()**函數(shù),** multi_read()**和** multi_base_read()**。
這使我們可以使用不同的行為來讀取基本設(shè)備.
read 16個子設(shè)備中的一個。
讀取的基本設(shè)備幾乎與我們在`/dev/misc/demo-number`中看到的相同:
```C
static zx_status_t
multi_base_read(void * ctx,void * buf,size_t count,zx_off_t off,size_t * actual){
????const char * base_name =“base device \ n”;
????if(off == 0){
????????* actual = strlen(base_name);
????????if(* actual> count){
????????????* actual = count;
????????}
????????memcpy(buf,base_name,* actual);
????} else {
????????* actual = 0;
????}
????return ZX_OK;
}
```
這只是為讀取返回字符串“`base device \ n`”,直到client端允許的字節(jié)數(shù)的最大值。
但是子設(shè)備的讀取需要知道哪個設(shè)備被調(diào)用。
我們在單個子設(shè)備上下文塊中保留一個名為`devno`的設(shè)備索引:
```C
typedef struct {
????zx_device_t * zxdev;
????int devno; //設(shè)備號(索引)
} multidev_t;
```
存儲16個子設(shè)備以及基本設(shè)備的上下文塊在上面的綁定函數(shù)的步驟(1)中創(chuàng)建的每設(shè)備上下文塊。
```C
//這包含我們的每個設(shè)備實例
#define NDEVICES 16
typedef struct {
????zx_device_t * parent;
????multidev_t * devices [NDEVICES]; //指向我們的16個子設(shè)備的指針
????multidev_t base_device; //我們的基礎(chǔ)設(shè)備
} multi_root_device_t;
```
請注意,每個設(shè)備的`multi_root_device_t`上下文結(jié)構(gòu)包含1個`ultidev_t`
上下文塊(用于基本設(shè)備)和16個指向動態(tài)分配上下文的子設(shè)備塊的指針。
這些上下文塊的初始化發(fā)生在步驟(3)中(對于基本設(shè)備)和(4)(在每個子設(shè)備的`for`循環(huán)中完成)。
圖 TODO
上圖說明了每設(shè)備和和個別設(shè)備上下文塊之間的關(guān)系。
子設(shè)備7代表所有子設(shè)備。
這就是我們的** multi_read()**函數(shù)的樣子:
```C
static const char* devnames[NDEVICES] = {
? ? "zero", "one", "two", "three",
? ? "four", "five", "six", "seven",
? ? "eight", "nine", "ten", "eleven",
? ? "twelve", "thirteen", "fourteen", "fifteen",
};
static zx_status_t
multi_read(void* ctx, void* buf, size_t count, zx_off_t off, size_t* actual) {
? ? multidev_t* device = ctx;
? ? if (off == 0) {
? ? ? ? char tmp[16];
? ? ? ? *actual = snprintf(tmp, sizeof(tmp), "%s\n", devnames[device->devno]);
? ? ? ? if (*actual > count) {
? ? ? ? ? ? *actual = count;
? ? ? ? }
? ? ? ? memcpy(buf, tmp, *actual);
? ? } else {
? ? ? ? *actual = 0;
? ? }
? ? return ZX_OK;
}
```
從命令行執(zhí)行我們的設(shè)備會產(chǎn)生如下結(jié)果:
```shell
$ cat /dev/misc/demo-multi
base device
$ cat /dev/misc/demo-multi/7
seven
$ cat /dev/misc/demo-multi/13
thirteen
```
###多個multiple設(shè)備
為支持多個設(shè)備的控制器創(chuàng)建“每個設(shè)備”上下文塊可能看起來很奇怪,但它與任何其他控制器沒有什么不同。
如果這是一個真正的硬件設(shè)備(比如16通道數(shù)據(jù)采集系統(tǒng)),您當然可以將兩個或更多這些插入您的系統(tǒng)。
每個驅(qū)動程序都將獲得一個唯一的基本設(shè)備名稱(例如`/dev/daq-0`,`/dev/daq-1`,依此類推),然后以該名稱manifest其channel(例如,對于第二數(shù)據(jù)采集系統(tǒng)上的第8個通道,`/dev/daq-1/7`)。
理想情況下,應(yīng)根據(jù)硬件提供了獨特的密鑰來為某種類型分配唯一的基本設(shè)備名稱。
這具有可重復(fù)性/可預(yù)測性的優(yōu)點,特別是對于熱插拔設(shè)備。
例如,在數(shù)據(jù)采集的情況下,將連接不同的設(shè)備到每個控制器通道。
在重新啟動或熱插拔/重新插入事件之后,希望能夠?qū)⒚總€控制器與已知的基本設(shè)備名稱相關(guān)聯(lián);它沒有讓插件/拔出事件之間的設(shè)備名稱隨機更改。
##阻塞讀寫:`/dev/misc/demo-fifo`
到目前為止,我們檢查過的所有設(shè)備都會立即返回數(shù)據(jù)(對于** read()**operation),或(在`/dev/misc/demo-null`的情況下),接受數(shù)據(jù)而不阻塞(對于** write()**operation)。
我們將討論的下一個設(shè)備`/dev/misc/demo-fifo`如果有可用的數(shù)據(jù),將立即返回數(shù)據(jù),否則它將阻塞client端,直到數(shù)據(jù)可用。
同樣,對于寫入,如果有空間,它將立即接受數(shù)據(jù),否則它將阻塞client端,直到空間可用。
read和write的私有處理者必須立即返回(無論是否有數(shù)據(jù)或空間是否可用)。
但是,他們不必立即返回或接受*數(shù)據(jù)*;他們可以改為向client表明它應(yīng)該等待。
我們的FIFO設(shè)備通過維護單個32kbyte FIFO來運行。
client端可以讀取和寫入FIFO,并將展示所討論的在滿條件和空條件下的阻塞行為。
###上下文結(jié)構(gòu)
首先要看的是上下文結(jié)構(gòu):
```C
#define FIFOSIZE 32768
typedef struct {
? ? zx_device_t* ? ?zxdev;
? ? mtx_t ? ? ? ? ? lock;
? ? uint32_t ? ? ? ?head;
? ? uint32_t ? ? ? ?tail;
? ? char ? ? ? ? ? ?data[FIFOSIZE];
} fifodev_t;
```
這是一個基本的循環(huán)緩沖區(qū);數(shù)據(jù)被寫入`head`表示的位置,并從`tail`指示的位置讀取。
如果`head == tail`那么FIFO是空的,如果`head`就在`tail`之前(使用環(huán)繞數(shù)學)表示FIFO已滿,否則它有一些數(shù)據(jù)和一些空間可用。
從更高的層面看,** fifo_read()**和** fifo_write()**函數(shù)幾乎相同,
所以讓我們從** fifo_write()**開始:
```C
static zx_status_t
fifo_write(void* ctx, const void* buf, size_t len,
? ? ? ? ? ?zx_off_t off, size_t* actual) {
? ? // (1) establish context pointer
? ? fifodev_t* fifo = ctx;
? ? // (2) lock mutex
? ? mtx_lock(&fifo->lock);
? ? // (3) write as much data as possible
? ? size_t n = 0;
? ? size_t count;
? ? while ((count = fifo_put(fifo, buf, len)) > 0) {
? ? ? ? len -= count;
? ? ? ? buf += count;
? ? ? ? n += count;
? ? }
? ? if (n) {
? ? ? ? // (4) wrote something, device is readable
? ? ? ? device_state_set(fifo->zxdev, DEV_STATE_READABLE);
? ? }
? ? if (len) {
? ? ? ? // (5) didn't write everything, device is full
? ? ? ? device_state_clr(fifo->zxdev, DEV_STATE_WRITABLE);
? ? }
? ? // (6) release mutex
? ? mtx_unlock(&fifo->lock);
? ? // (7) inform client of results, possibly blocking it
? ? *actual = n;
? ? return (n == 0) ? ZX_ERR_SHOULD_WAIT : ZX_OK;
}
```
在步驟(1)中,我們建立一個指向該設(shè)備實例的上下文塊的上下文指針。
接下來,我們在步驟(2)中鎖定互斥鎖。
這樣做是因為我們的驅(qū)動程序中可能有多個線程,而我們不希望他們互相干涉。
緩沖管理在步驟(3)中執(zhí)行&mdash;我們稍后會檢查實現(xiàn)。
了解在步驟(3)之后需要采取的措施非常重要:
*如果我們寫了一個或多個字節(jié)(由'n`表示非零),我們需要
????將設(shè)備標記為“可讀”(通過** device_state_set()**
????和'DEV_STATE_READABLE`),
????這在步驟(4)中完成。我們這樣做是因為數(shù)據(jù)現(xiàn)在可用。
*如果我們還有剩余的字節(jié)要寫(由'len`表示非零),我們
????需要將設(shè)備標記為“不可寫”(通過** device_state_clr()**和
????`DEV_STATE_WRITABLE`),在步驟(5)完成。我們知道FIFO已滿,因為
????我們無法寫出所有數(shù)據(jù)。
我們可能依賴于執(zhí)行步驟(4)和(5)中的一個或兩個關(guān)于write期間發(fā)生的事情。
我們將始終至少執(zhí)行其中一個,因為`n`和`len`不能同時執(zhí)行為零。
這意味著一個不可能的條件:我們既沒有寫任何數(shù)據(jù)(`n`,傳輸?shù)目傋止?jié)數(shù),為零),但同時又寫入了所有數(shù)據(jù)(`len`,要傳輸?shù)氖S嘧止?jié)數(shù)也為零)。
在步驟(7)中,做出關(guān)于阻塞client端的決定。
如果`n`為零,則意味著我們無法寫入任何數(shù)據(jù)。
在這種情況下,我們返回`ZX_ERR_SHOULD_WAIT`。
此返回值會阻塞client端。
當** device_state_set()**時,client端被解鎖,函數(shù)在步驟(2)中從** fifo_read()**處理程序調(diào)用:
```c
static zx_status_t
fifo_read(void* ctx, void* buf, size_t len,
? ? ? ? ? zx_off_t off, size_t* actual) {
? ? fifodev_t* fifo = ctx;
? ? mtx_lock(&fifo->lock);
? ? size_t n = 0;
? ? size_t count;
? ? while ((count = fifo_get(fifo, buf, len)) > 0) {
? ? ? ? len -= count;
? ? ? ? buf += count;
? ? ? ? n += count;
? ? }
? ? // (1) same up to here; except read as much as possible
? ? if (n) {
? ? ? ? // (2) read something, device is writable
? ? ? ? device_state_set(fifo->zxdev, DEV_STATE_WRITABLE);
? ? }
? ? if (len) {
? ? ? ? // (3) didn't read everything, device is empty
? ? ? ? device_state_clr(fifo->zxdev, DEV_STATE_READABLE);
? ? }
? ? mtx_unlock(&fifo->lock);
? ? *actual = n;
? ? return (n == 0) ? ZX_ERR_SHOULD_WAIT : ZX_OK;
}
```
算法的形狀與書寫案例相同,但有兩點不同:
1.我們正在讀數(shù)據(jù),所以調(diào)用** fifo_get()**而不是** fifo_put()**
2.“DEV_STATE”邏輯是互補的:在write情況下,我們設(shè)置可讀
????并且清除可寫,在read案例中我們設(shè)置了可寫和清除可讀。
與write案例類似,在`while`循環(huán)之后,我們將執(zhí)行其中一個或兩個以下行動:
*如果我們讀取一個或多個字節(jié)(由'n`表示為非零),我們需要標記設(shè)備現(xiàn)在可寫(我們消耗數(shù)據(jù),因此現(xiàn)在有一些空間可用)。
*如果我們?nèi)匀挥凶止?jié)要讀(如'len`表示非零),我們標記設(shè)備為空(我們沒有獲得所有數(shù)據(jù),所以這一定是因為我們耗盡了裝置)。
如在書面案例中,將執(zhí)行上述動作中的至少一個。
為了使它們都不執(zhí)行,都是'n`(讀取的字節(jié)數(shù))和`len`(要讀取的字節(jié)數(shù))必須為零,這意味著不可能,幾乎是形而上學的條件,即同時read任何內(nèi)容和所有內(nèi)容。
>此處還有一個額外的微妙之處。
>當`n`為零時,我們*必須*返回`ZX_ERR_SHOULD_WAIT`&mdash;我們不能返回`ZX_OK`。
>將`* actual`設(shè)置為零返回`ZX_OK`表示EOF,這絕對不是這里的情況。
###讀寫交互
正如您所看到的,讀取處理程序允許阻塞寫入client端來取消阻塞,并且
write handler是允許阻塞讀取client端來解除阻塞的原因。
當client端被阻塞時(通過`ZX_ERR_SHOULD_WAIT`返回代碼),它被對應(yīng)的** device_state_set()**喚醒。此喚醒動作使client端再次嘗試其讀取或?qū)懭氩僮鳌?br /> 請注意,client端被喚醒后無法保證成功。我們可以有多個讀者,例如,等待數(shù)據(jù)。
假設(shè)所有這些都被阻塞,因為FIFO是空的。另一個client端出現(xiàn)并寫入FIFO。這會導(dǎo)致** device_state_set()**使用`DEV_STATE_READABLE`調(diào)用函數(shù)。其中一個client端可能會消耗所有可用數(shù)據(jù);該其他client端將嘗試讀取,但將獲得`ZX_ERR_SHOULD_WAIT`并將阻塞。
###緩沖管理
正如所承諾的那樣,為了完整起見,這里是對緩沖管理的快速檢查。這兩個例程都很常見。
我們將查看讀取路徑(寫入路徑幾乎相同)。
在read函數(shù)的核心,我們看到:
```c
? ? size_t n = 0;
? ? size_t count;
? ? while ((count = fifo_get(fifo, buf, len)) > 0) {
? ? ? ? len -= count;
? ? ? ? buf += count;
? ? ? ? n += count;
? ? }
```
三個變量`n`,`count`和`len`是相互關(guān)聯(lián)的。
傳輸?shù)目傋止?jié)數(shù)存儲在`n`中。
在每次迭代期間,`count`獲取傳輸?shù)淖止?jié)數(shù),并將其用作控制`while`循環(huán)的基礎(chǔ)。
變量`len`表示要傳輸?shù)氖S嘧止?jié)數(shù)。
每次循環(huán)時,`len`減少了傳輸?shù)淖止?jié)數(shù),`n`相應(yīng)的增加。
因為FIFO是作為循環(huán)緩沖區(qū)實現(xiàn)的,所以它意味著一個完整的數(shù)據(jù)集可能在FIFO中連續(xù)定位,也可能是環(huán)繞的FIFO的結(jié)束回到開頭。
底層的** fifo_get()**函數(shù)可以獲得盡可能多的數(shù)據(jù),而無需包裝。
這就是`while`循環(huán)要“retry” operation的原因;看它能否得到更多的數(shù)據(jù)可能是由于`tail`回繞到緩沖區(qū)的開頭。
我們會在調(diào)用** fifo_get()**一到三次。
1.如果FIFO為空,我們只需調(diào)用一次。
????它將返回零,表示沒有可用的數(shù)據(jù)。
2.如果數(shù)據(jù)連續(xù)位于底層FIFO緩沖區(qū)中,我們稱之為第二次;
????第一次獲取數(shù)據(jù),第二次返回零,表明
????沒有更多數(shù)據(jù)可用。
3.如果數(shù)據(jù)在緩沖區(qū)中被循環(huán)覆蓋,我們將調(diào)用它第三次。
????一次獲得第一部分,第二次獲得環(huán)繞部分,第三次將返回零,表示沒有更多數(shù)據(jù)可用。
?
總結(jié)
- 上一篇: 《高等代数学》(姚慕生),习题1.1:二
- 下一篇: 英文参考文献的正确引用格式详解