1. 程式人生 > >linux裝置驅動—高階字元驅動(上)

linux裝置驅動—高階字元驅動(上)

在之前, 我們介紹了簡單的字元裝置驅動,實現了簡單的open, close, read, write等驅動提供的基本功能。但是大部分驅動程式還會提供更多的功能。本章我們會介紹ioctrl 系統呼叫,和使用者空間保持同步的幾種途徑。
一. Ioctl
1. 函式原型 呼叫
通過裝置驅動程式執行各中型別的硬體控制。

使用者空間,ioctl 系統呼叫

int ioctl(int fd, unsigned long request, ...);

核心空間

int (*ioctl) ( struct file *filp, unsigned int cmd, unsigned
long arg);

2.Ioctl cmd的選擇
Linux中把ioctl cmd劃分成幾個位段來幫助建立唯一的cmd。這幾個位段一般是:type(模數),序號,傳輸方向和引數大小。在定義的時候可以參考include/asm/ioctl.h 和 Documentation/ioctl-number.txt兩個檔案,標頭檔案定義了構建cmd命令的巨集,而ioctl-number.txt列舉了核心中已經使用的tpye,為了唯一性,儘量不要和這裡的type重疊。
• Type: 幻數。選擇一個號碼(j記住先仔細閱讀ioctl-number.txt),並在驅動中使用這個號碼。這個欄位有8位寬。
• number:序(順序)號. 它是 8 位(_IOC_NRBITS)寬.
• Direction: 資料傳送的方向,如果這個特殊的命令涉及資料傳送. 可能的值是 _IOC_NONE(沒有資料傳輸), _IOC_READ, _IOC_WRITE, 和 _IOC_READ|_IOC_WRITE (資料在2個方向被傳送). 資料傳送是從應用程式的觀點來看待的; _IOC_READ 意思是從裝置讀, 因此裝置必須寫到使用者空間. 注意這個成員是一個位掩碼, 因此 _IOC_READ 和 _IOC_WRITE 可使用一個邏輯 AND 操作來抽取.
• size:涉及到的使用者資料的大小. 這個成員的寬度是依賴體系的, 但是常常是 13 或者 14 位. 你可為你的特定體系在巨集 _IOC_SIZEBITS 中找到它的值. 你使用這個 size 成員不是強制的 - 核心不檢查它 – 但是它是一個好主意. 正確使用這個成員可幫助檢測使用者空間程式的錯誤並使你實現向後相容, 如果你曾需要改變相關資料項的大小. 如果你需要更大的資料結構, 但是, 你可忽略這個 size 成員. 我們很快見到如何使用這個成員.
下面是一個定義ioctl命令的展示

/*
 * Ioctl definitions
 */

/* Use 'k' as magic number */
#define SCULL_IOC_MAGIC  'k'
/* Please use a different 8-bit number in your code */

#define SCULL_IOCRESET    _IO(SCULL_IOC_MAGIC, 0)

/*
 * S means "Set" through a ptr,
 * T means "Tell" directly with the argument value
 * G means "Get": reply by
setting through a pointer * Q means "Query": response is on the return value * X means "eXchange": switch G and S atomically * H means "sHift": switch T and Q atomically */ #define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int) #define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int) #define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3)[] #define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4) #define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int) #define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int) #define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7) #define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8) #define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int) #define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10, int) #define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11) #define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12) /*

3.Ioctl cmd的選擇
我們知道copy_from_user和copy_to_user函式可以安全的與使用者空間交換資料,但是因為ioctl通常涉及到較小的資料項,因此可以用其他方法更有效的操作。
• access_ok驗證地址合理性
api:int access_ok(int type, const void *addr, unsigned long size)
程式碼例項:

/*
 * 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 後,就可以安全的進行資料傳輸。

put_user(datum, ptr)   //當要傳遞單個數據時,應該用這些巨集, 而不是會用copy_to_user
get_user(local, ptr);

4.ioctl命令的實現
程式碼示例
………見csdn

5.ioctl同read和write的區別:
ioctl被譽為Unix系統的”瑞士軍刀”,他被當作擴充Linux系統功能一個通用的方法,在Linux系統中被廣泛使用。
ioctl一般用來使用者空間程式和驅動程式模組之間傳遞控制資料,ioctl同read和write的區別是:
• ioctl一般是用來傳遞控制引數的,比如:串列埠的波特率、串列埠的流控方法(xon/xoff、DTR/DSR、RTS/CTS)等等,一般不用來傳遞“主要的”資料(我不到合適的詞來說明:)。
• ioctl的語義一般是非阻塞的,read和write卻省是阻塞的。
ioctl的介面是萬能的,ioctl(fd, cmd, arg)第三個引數可以是一個整形變數,也可以是一個指向某種資料結構的指標。

二. 阻塞型I/o
1. 休眠概念
當一個程序被置入休眠,他會標記為一種特殊狀態並從排程器的執行佇列中移走。知道某些情況改變了這個狀態,程序才會在任意cpu上排程。為了安全的進入休眠,記住兩個原則。
• 永遠不要再原子上下文中進入休眠
• 確保能喚醒程序的條件存在
2. 函式原型

等待
wait_event(queue, condition)    //非中斷休眠
wait_event_interruptible(queue, condition)  //可以被訊號中斷
wait_event_timeout(queue, condition, timeout)  //只會等待給定的時間
wait_event_interruptible_timeout(queue, condition, timeout)
喚醒
void wait_up(wait_queue_head_t *queue);
void wait_up_interruptible(wait_queue_head_t *queue); 

程式碼例項:

ssize_t sleepy_read (struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
    printk(KERN_DEBUG "process %i (%s) going to sleep\n",
            current->pid, current->comm);
    wait_event_interruptible(wq, flag != 0);
    flag = 0;
    printk(KERN_DEBUG "awoken %i (%s)\n", current->pid, current->comm);
    return 0; /* EOF */
}

ssize_t sleepy_write (struct file *filp, const char __user *buf, size_t count,
        loff_t *pos)
{
    printk(KERN_DEBUG "process %i (%s) awakening the readers...\n",
            current->pid, current->comm);
    flag = 1;
    wake_up_interruptible(&wq);
    return count; /* succeed, to avoid retrial */
}
//測試    
輸入讀命令:
                         cat /dev/sleepy                  // 終端阻塞
檢視dmesg:
[ 3087.029304] process 17112 (cat) going to sleep

輸入寫命令:
                          ls -l > /dev/sleepy             //讀程序喚醒
檢視dmesg:
process 17120 (ls) awakening the readers…   

下一章節 介紹 poll 和 select 的用法