在看 scull 驅動的 ioctl 代碼之前, 我們需要涉及的另一點是如何使用這個額外的參數. 如果它是一個整數, 就容易: 它可以直接使用. 如果它是一個指針, 但是, 必須小心些.
當用一個指針引用用戶空間, 我們必須確保用戶地址是有效的. 試圖存取一個沒驗證過的 用戶提供的指針可能導致不正確的行為, 一個內核 oops, 系統崩潰, 或者安全問題. 它 是驅動的責任來對每個它使用的用戶空間地址進行正確的檢查, 並且返回一個錯誤如果它 是無效的.
在第 3 章, 我們看了 copy_from_user 和 copy_to_user 函數, 它們可用來安全地移動 數據到和從用戶空間. 這些函數也可用在 ioctl 方法中, 但是 ioctl 調用常常包含小數 據項, 可通過其他方法更有效地操作. 開始, 地址校驗(不傳送數據)由函數 access_ok 實現, 它定義在 <asm/uaccess.h>:
int access_ok(int type, const void *addr, unsigned long size);
第一個參數應當是 VERIFY_READ 或者 VERIFY_WRITE, 依據這個要進行的動作是否是讀用 戶空間內存區或者寫它. addr 參數持有一個用戶空間地址, size 是一個字節量. 例如, 如果 ioctl 需要從用戶空間讀一個整數, size 是 sizeof(int). 如果你需要讀和寫給定 地址, 使用 VERIFY_WRITE, 因為它是 VERIRY_READ 的超集.
不象大部分的內核函數, access_ok 返回一個布爾值: 1 是成功(存取沒問題)和 0 是失 敗(存取有問題). 如果它返回假, 驅動應當返回 -EFAULT 給調用者.
關於 access_ok 有多個有趣的東西要注意. 首先, 它不做校驗內存存取的完整工作; 它只 檢查看這個內存引用是在這個進程有合理權限的內存范圍中. 特別地, access_ok 確保這 個地址不指向內核空間內存. 第 2, 大部分驅動代碼不需要真正調用 access_ok. 后面描 述的內存存取函數為你負責這個. 但是, 我們來演示它的使用, 以便你可見到它如何完成.
scull 源碼利用了 ioclt 號中的位段來檢查參數, 在 switch 之前: int err = 0, tmp;
int retval = 0;
/*
* extract the type and number bitfields, and don't decode
* wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
*/
if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC)
return -ENOTTY;
if (_IOC_NR(cmd) > SCULL_IOC_MAXNR)
return -ENOTTY;
/*
* the direction is a bitmask, and VERIFY_WRITE catches R/W
* transfers. `Type' is user-oriented, while
* access_ok is kernel-oriented, so the concept of "read" and
* "write" is reversed
*/
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void user *)arg, _IOC_SIZE(cmd)); else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void user *)arg, _IOC_SIZE(cmd));
if (err)
return -EFAULT;
在調用 access_ok 之后, 驅動可安全地進行真正的傳輸. 加上 copy_from_user 和 copy_to_user_ 函數, 程序員可利用一組為被最多使用的數據大小(1, 2, 4, 和 8 字節) 而優化過的函數. 這些函數在下面列表中描述, 它們定義在 <asm/uaccess.h>:
put_user(datum, ptr)
put_user(datum, ptr)
這些宏定義寫 datum 到用戶空間; 它們相對快, 並且應當被調用來代替 copy_to_user 無論何時要傳送單個值時. 這些宏已被編寫來允許傳遞任何類型的 指針到 put_user, 只要它是一個用戶空間地址. 傳送的數據大小依賴 prt 參數的 類型, 並且在編譯時使用 sizeof 和 typeof 等編譯器內建宏確定. 結果是, 如果 prt 是一個 char 指針, 傳送一個字節, 以及對於 2, 4, 和 可能的 8 字節.
put_user 檢查來確保這個進程能夠寫入給定的內存地址. 它在成功時返回 0, 並 且在錯誤時返回 -EFAULT. put_user 進行更少的檢查(它不調用 access_ok), 但是仍然能夠失敗如果被指向的內存對用戶是不可寫的. 因此, put_user 應當 只用在內存區已經用 access_ok 檢查過的時候.
作為一個通用的規則, 當你實現一個 read 方法時, 調用 put_user 來節省幾個 周期, 或者當你拷貝幾個項時, 因此, 在第一次數據傳送之前調用 access_ok 一 次, 如同上面 ioctl 所示.
get_user(local, ptr)
get_user(local, ptr)
這些宏定義用來從用戶空間接收單個數據. 它們象 put_user 和 put_user, 但 是在相反方向傳遞數據. 獲取的值存儲於本地變量 local; 返回值指出這個操作是 否成功. 再次, get_user 應當只用在已經使用 access_ok 校驗過的地址.
如果做一個嘗試來使用一個列出的函數來傳送一個不適合特定大小的值, 結果常常是一個 來自編譯器的奇怪消息, 例如"coversion to non-scalar type requested". 在這些情況 中, 必須使用 copy_to_user 或者 copy_from_user.