一文講解字符設備驅動之常用內(nèi)核函數(shù)
字符設備驅動含有open、read、write、ioctl等函數(shù),用于用戶層和內(nèi)核之間的通信,所以當用戶要獲得內(nèi)核驅動的一些數(shù)據(jù)或者發(fā)送一些控制命令,就需要使用設備驅動了。對于一些中斷類型的驅動,比如輸入子系統(tǒng),用戶層不需要對其進行操作,可以不使用設備驅動。
1.register_chrdev
說起字符設備驅動程序,肯定少不了register_chrdev函數(shù),它在內(nèi)存中分配了一塊區(qū)域用于字符設備驅動的熱拔插。這個區(qū)域里面的驅動擁有相同的主設備號,不同的次設備號,但它們都指向同一個file_operations結構體。就好比USB接口,一臺電腦上有好幾個USB接口,它們的屬性都是一樣的,鼠標接上去后左鍵右鍵功能都是相同的,這是因為它們有相同的usb_device結構體。
對于linux2.x的內(nèi)核一般都是采用這種方式注冊字符設備驅動,但這種方式的缺點是同一結構體下分配了過多的字符設備驅動的接口。所以linux3.x的內(nèi)核對其進行了改進,register_chrdev函數(shù)可以拆分成3給獨立的函數(shù)來對字符設備驅動進行注冊。register_chrdev = register_chrdev_region/alloc_chrdev_region + cdev_init + cdev_add。
如果主設備號已經(jīng)指定了,就是定義了major = ?,就使用register_chrdev_region來開辟驅動的接口;如果讓內(nèi)核自動分配major的值,則使用alloc_chrdev_region。具體的函數(shù)形式如下:
這里要說一下cdev_alloc()這個函數(shù),如果是static struct cdev *cdev這么定義的,則使用到cdev_alloc,如果定義的是結構體而不是指針(static struct cdev cdev),就不需要使用cdev_alloc這個函數(shù)了。宏定義MAXPIN表示對該file_operations結構體可以支持的設備驅動個數(shù)。
2. class_create
【文章福利】小編推薦自己的Linux內(nèi)核技術交流群:【891587639】整理了一些個人覺得比較好的學習書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)? ??


3. request_irq
具體形式如下:
error = request_irq(IRQ_EINT0,key_handler,IRQ_TYPE_EDGE_BOTH,"key1",NULL);
這里要注意的是第一個參數(shù)是中斷的類型,像上面直接寫的IRQ_EINT0,這樣就啟動了外部中斷0,在Linux內(nèi)核里面就不需要再像之前的裸板程序一樣配置相關引腳為中斷模式了,這里內(nèi)核已經(jīng)幫我們做好了;然后第二個參數(shù)是中斷服務函數(shù),發(fā)生中斷之后進入這個函數(shù)。第三給參數(shù)很重要,它是中斷的觸發(fā)類型,這里我用的是雙邊沿觸發(fā)。如果只有一個中斷,最后一個參數(shù)寫NULL就可以了,如果有多個中斷,最好是分配一個結構體,里面記錄每個中斷的信息,這些中斷可以使用相同的中斷服務函數(shù),根據(jù)中斷信息就可以知道是哪個中斷發(fā)生了。
而發(fā)生中斷后,進入static irqreturn_t key_irq(int irq, void *dev_id),它的dev_id正是request_irq的最后一個參數(shù)&key[i],通過(&key[i])->pin我們就能知道到底是GPF0,還是GPF2或者是GPG3發(fā)生了中斷,也就是外部中斷0,2或者11處發(fā)生了中斷。
4 .時間函數(shù)
首先定義一個timer_list結構體static struct timer_list key_time。
再初始化函數(shù)里加上 init_timer(&key_time); key_time.function = key_time_function; add_timer(&key_time);
然后中斷服務函數(shù)里面加上 mod_timer(&key_time,jiffies + HZ/100); //延遲10ms,延遲過程中斷仍然可以被觸發(fā) 原先在中斷服務函數(shù)里面實現(xiàn)的代碼放到時間函數(shù)key_time_function里面。
對于 mod_timer,+HZ表示延時1秒,/100就是10ms了。
時間函數(shù)可以用于中斷,去按鍵抖動。中斷里啟動時間函數(shù)后中斷函數(shù)就結束了,它不管時間函數(shù)是否執(zhí)行完成。這樣在延時10ms的過程中中斷又可以被觸發(fā),這就去了按鍵抖動,這是它與傳統(tǒng)的delay函數(shù)的區(qū)別。它只要告訴內(nèi)核多長時間后啟動時間函數(shù),mod_timer這個函數(shù)就執(zhí)行完了,中斷服務函數(shù)就執(zhí)行完了,剩下的就等過完這段時間后內(nèi)核啟動時間函數(shù)key_time_function,然后執(zhí)行里面的內(nèi)容就可以了。
5. 睡眠喚醒
5.1 聲明這個待喚醒事件
static DECLARE_WAIT_QUEUE_HEAD(dma_waitq);
5.2 休眠
wait_event_interruptible(dma_waitq, ev_dma);
5.3 喚醒
wake_up_interruptible(&dma_waitq);
5.4 過程分析
從if條件語句可以看出,要進入休眠首先condition要等于0,其次再看宏__wait_event_interruptible:
do...while(0)實際上也只是執(zhí)行了一次循環(huán),但卻有重要意義:它使得__wait_event_interruptible可以像函數(shù)一樣使用(實際上是宏),在程序中__wait_event_interruptible();就可以使用它。那為什么不直接將它定義為函數(shù)呢?因為它位于wait.h頭文件,定義為函數(shù),被不同的程序引用會造成重復定義。當然也可以像上面一樣打一個()。
然后進入for(;;)循環(huán),當condition == 1時break,然后執(zhí)行finish_wait(&wq, &__wait),接著
而wake_up_interruptible(&dma_waitq)做的事情是:
正好與上面的finish_wait(&wq, &__wait)對應,至于里面自旋鎖的操作,我也不懂,我明白的是:
要休眠就得讓condition = 0,然后wait_event_interruptible(dma_waitq, ev_dma);
要喚醒進程,就得先讓condition = 1,然后wake_up_interruptible(&dma_waitq)
6 ioctl函數(shù)
ioctl像read、write一樣讀寫:
