在智能手機時代,每個品牌的手機都有自己的個性特點。正是依靠這種與眾不同的個性來吸引用戶,營造品牌凝聚力和用戶忠城度,典型的代表非iphone莫屬了。
據統計,截止2011年5月,AppStore的應用軟件數量達381062個,位居第一,而Android Market的應用軟件數量達294738,緊隨AppStore后面,并有望在8月份越過AppStore。隨著Android系統逐步擴大市場占有率,終端設備的多樣性亟需更多的移動開發人員的參與。
據業內統計,Android研發人才缺口至少30萬。目前,對Android人才需求一類是偏向硬件驅動的Android人才需求,一類是偏向軟件應用的Android人才需求。總的來說,對有志于從事Android硬件驅動的開發工程師來說,現在是一個大展拳腳的機會。那么,就讓我們一起來看看如何為Android系統編寫內核驅動程序吧。
?? ? ? ?這里,我們不會為真實的硬件設備編寫內核驅動程序。為了方便描述為Android系統編寫內核驅動程序的過程,我們使用一個虛擬的硬件設備,這個設備只有一個4字節的寄存器,它可讀可寫。想起我們第一次學習程序語言時,都喜歡用“Hello, World”作為例子,這里,我們就把這個虛擬的設備命名為“hello”,而這個內核驅動程序也命名為hello驅動程序。其實,Android內核驅動程序和一般Linux內核驅動程序的編寫方法是一樣的,都是以Linux模塊的形式實現的,具體可參考前面Android學習啟動篇一文中提到的Linux Device Drivers一書。不過,這里我們還是從Android系統的角度來描述Android內核驅動程序的編寫和編譯過程。
?? ? ? 一. 參照前面兩篇文章在Ubuntu上下載、編譯和安裝Android最新源代碼和在Ubuntu上下載、編譯和安裝Android最新內核源代碼(Linux Kernel)準備好Android內核驅動程序開發環境。
?? ? ? 二. 進入到kernel/common/drivers目錄,新建hello目錄:
?? ? ??USER-NAME@MACHINE-NAME:~/Android$ cd kernel/common/drivers
?? ? ? USER-NAME@MACHINE-NAME:~/Android/kernel/common/drivers$ mkdir hello
?? ? ? 三. 在hello目錄中增加hello.h文件:
[cpp] view plaincopy
#ifndef?_HELLO_ANDROID_H_??#define?_HELLO_ANDROID_H_????#include?<linux/cdev.h>??#include?<linux/semaphore.h>????#define?HELLO_DEVICE_NODE_NAME??"hello"??#define?HELLO_DEVICE_FILE_NAME??"hello"??#define?HELLO_DEVICE_PROC_NAME??"hello"??#define?HELLO_DEVICE_CLASS_NAME?"hello"????struct?hello_android_dev?{??????int?val;??????struct?semaphore?sem;??????struct?cdev?dev;??};????#endif??
???這個頭文件定義了一些字符串常量宏,在后面我們要用到。此外,還定義了一個字符設備結構體hello_android_dev,這個就是我們虛擬的硬件設備了,val成員變量就代表設備里面的寄存器,它的類型為int,sem成員變量是一個信號量,是用同步訪問寄存器val的,dev成員變量是一個內嵌的字符設備,這個Linux驅動程序自定義字符設備結構體的標準方法。
?? 四.在hello目錄中增加hello.c文件,這是驅動程序的實現部分。驅動程序的功能主要是向上層提供訪問設備的寄存器的值,包括讀和寫。這里,提供了三種訪問設備寄存器的方法,一是通過proc文件系統來訪問,二是通過傳統的設備文件的方法來訪問,三是通過devfs文件系統來訪問。下面分段描述該驅動程序的實現。
?? 首先是包含必要的頭文件和定義三種訪問設備的方法:
[cpp] view plaincopy
#include?<linux/init.h>??#include?<linux/module.h>??#include?<linux/types.h>??#include?<linux/fs.h>??#include?<linux/proc_fs.h>??#include?<linux/device.h>??#include?<asm/uaccess.h>????#include?"hello.h"??????static?int?hello_major?=?0;??static?int?hello_minor?=?0;??????static?struct?class*?hello_class?=?NULL;??static?struct?hello_android_dev*?hello_dev?=?NULL;??????static?int?hello_open(struct?inode*?inode,?struct?file*?filp);??static?int?hello_release(struct?inode*?inode,?struct?file*?filp);??static?ssize_t?hello_read(struct?file*?filp,?char?__user?*buf,?size_t?count,?loff_t*?f_pos);??static?ssize_t?hello_write(struct?file*?filp,?const?char?__user?*buf,?size_t?count,?loff_t*?f_pos);??????static?struct?file_operations?hello_fops?=?{??????.owner?=?THIS_MODULE,??????.open?=?hello_open,??????.release?=?hello_release,??????.read?=?hello_read,??????.write?=?hello_write,???};??????static?ssize_t?hello_val_show(struct?device*?dev,?struct?device_attribute*?attr,??char*?buf);??static?ssize_t?hello_val_store(struct?device*?dev,?struct?device_attribute*?attr,?const?char*?buf,?size_t?count);??????static?DEVICE_ATTR(val,?S_IRUGO?|?S_IWUSR,?hello_val_show,?hello_val_store);??
?? ? ? ?定義傳統的設備文件訪問方法,主要是定義hello_open、hello_release、hello_read和hello_write這四個打開、釋放、讀和寫設備文件的方法:
[cpp] view plaincopy
??static?int?hello_open(struct?inode*?inode,?struct?file*?filp)?{??????struct?hello_android_dev*?dev;??????????????????????????dev?=?container_of(inode->i_cdev,?struct?hello_android_dev,?dev);??????filp->private_data?=?dev;????????????return?0;??}??????static?int?hello_release(struct?inode*?inode,?struct?file*?filp)?{??????return?0;??}??????static?ssize_t?hello_read(struct?file*?filp,?char?__user?*buf,?size_t?count,?loff_t*?f_pos)?{??????ssize_t?err?=?0;??????struct?hello_android_dev*?dev?=?filp->private_data;??????????????????????if(down_interruptible(&(dev->sem)))?{??????????return?-ERESTARTSYS;??????}????????if(count?<?sizeof(dev->val))?{??????????goto?out;??????}??????????????????????if(copy_to_user(buf,?&(dev->val),?sizeof(dev->val)))?{??????????err?=?-EFAULT;??????????goto?out;??????}????????err?=?sizeof(dev->val);????out:??????up(&(dev->sem));??????return?err;??}??????static?ssize_t?hello_write(struct?file*?filp,?const?char?__user?*buf,?size_t?count,?loff_t*?f_pos)?{??????struct?hello_android_dev*?dev?=?filp->private_data;??????ssize_t?err?=?0;??????????????????????if(down_interruptible(&(dev->sem)))?{??????????return?-ERESTARTSYS;??????????????}????????????????if(count?!=?sizeof(dev->val))?{??????????goto?out;??????????????}??????????????????????if(copy_from_user(&(dev->val),?buf,?count))?{??????????err?=?-EFAULT;??????????goto?out;??????}????????err?=?sizeof(dev->val);????out:??????up(&(dev->sem));??????return?err;??}??
?? ? ? ?定義通過devfs文件系統訪問方法,這里把設備的寄存器val看成是設備的一個屬性,通過讀寫這個屬性來對設備進行訪問,主要是實現hello_val_show和hello_val_store兩個方法,同時定義了兩個內部使用的訪問val值的方法__hello_get_val和__hello_set_val:
[cpp] view plaincopy
??static?ssize_t?__hello_get_val(struct?hello_android_dev*?dev,?char*?buf)?{??????int?val?=?0;??????????????????????if(down_interruptible(&(dev->sem)))?{??????????????????????????return?-ERESTARTSYS;??????????????}????????????????val?=?dev->val;??????????????up(&(dev->sem));????????????????return?snprintf(buf,?PAGE_SIZE,?"%d\n",?val);??}??????static?ssize_t?__hello_set_val(struct?hello_android_dev*?dev,?const?char*?buf,?size_t?count)?{??????int?val?=?0;??????????????????????????????val?=?simple_strtol(buf,?NULL,?10);??????????????????????????????if(down_interruptible(&(dev->sem)))?{??????????????????????????return?-ERESTARTSYS;??????????????}????????????????dev->val?=?val;??????????????up(&(dev->sem));????????return?count;??}??????static?ssize_t?hello_val_show(struct?device*?dev,?struct?device_attribute*?attr,?char*?buf)?{??????struct?hello_android_dev*?hdev?=?(struct?hello_android_dev*)dev_get_drvdata(dev);????????????????return?__hello_get_val(hdev,?buf);??}??????static?ssize_t?hello_val_store(struct?device*?dev,?struct?device_attribute*?attr,?const?char*?buf,?size_t?count)?{???????struct?hello_android_dev*?hdev?=?(struct?hello_android_dev*)dev_get_drvdata(dev);??????????????return?__hello_set_val(hdev,?buf,?count);??}??
?? ? ? ?定義通過proc文件系統訪問方法,主要實現了hello_proc_read和hello_proc_write兩個方法,同時定義了在proc文件系統創建和刪除文件的方法hello_create_proc和hello_remove_proc:
[cpp] view plaincopy
??static?ssize_t?hello_proc_read(char*?page,?char**?start,?off_t?off,?int?count,?int*?eof,?void*?data)?{??????if(off?>?0)?{??????????*eof?=?1;??????????return?0;??????}????????return?__hello_get_val(hello_dev,?page);??}??????static?ssize_t?hello_proc_write(struct?file*?filp,?const?char?__user?*buff,?unsigned?long?len,?void*?data)?{??????int?err?=?0;??????char*?page?=?NULL;????????if(len?>?PAGE_SIZE)?{??????????printk(KERN_ALERT"The?buff?is?too?large:?%lu.\n",?len);??????????return?-EFAULT;??????}????????page?=?(char*)__get_free_page(GFP_KERNEL);??????if(!page)?{??????????????????????????printk(KERN_ALERT"Failed?to?alloc?page.\n");??????????return?-ENOMEM;??????}??????????????????????if(copy_from_user(page,?buff,?len))?{??????????printk(KERN_ALERT"Failed?to?copy?buff?from?user.\n");??????????????????????????err?=?-EFAULT;??????????goto?out;??????}????????err?=?__hello_set_val(hello_dev,?page,?len);????out:??????free_page((unsigned?long)page);??????return?err;??}??????static?void?hello_create_proc(void)?{??????struct?proc_dir_entry*?entry;????????????entry?=?create_proc_entry(HELLO_DEVICE_PROC_NAME,?0,?NULL);??????if(entry)?{??????????entry->owner?=?THIS_MODULE;??????????entry->read_proc?=?hello_proc_read;??????????entry->write_proc?=?hello_proc_write;??????}??}??????static?void?hello_remove_proc(void)?{??????remove_proc_entry(HELLO_DEVICE_PROC_NAME,?NULL);??}??
?? 最后,定義模塊加載和卸載方法,這里只要是執行設備注冊和初始化操作:
[cpp] view plaincopy
??static?int??__hello_setup_dev(struct?hello_android_dev*?dev)?{??????int?err;??????dev_t?devno?=?MKDEV(hello_major,?hello_minor);????????memset(dev,?0,?sizeof(struct?hello_android_dev));????????cdev_init(&(dev->dev),?&hello_fops);??????dev->dev.owner?=?THIS_MODULE;??????dev->dev.ops?=?&hello_fops;??????????????????????err?=?cdev_add(&(dev->dev),devno,?1);??????if(err)?{??????????return?err;??????}??????????????????????init_MUTEX(&(dev->sem));??????dev->val?=?0;????????return?0;??}??????static?int?__init?hello_init(void){???????int?err?=?-1;??????dev_t?dev?=?0;??????struct?device*?temp?=?NULL;????????printk(KERN_ALERT"Initializing?hello?device.\n");??????????????????????err?=?alloc_chrdev_region(&dev,?0,?1,?HELLO_DEVICE_NODE_NAME);??????if(err?<?0)?{??????????printk(KERN_ALERT"Failed?to?alloc?char?dev?region.\n");??????????goto?fail;??????}????????hello_major?=?MAJOR(dev);??????hello_minor?=?MINOR(dev);??????????????????????hello_dev?=?kmalloc(sizeof(struct?hello_android_dev),?GFP_KERNEL);??????if(!hello_dev)?{??????????err?=?-ENOMEM;??????????printk(KERN_ALERT"Failed?to?alloc?hello_dev.\n");??????????goto?unregister;??????}??????????????????????err?=?__hello_setup_dev(hello_dev);??????if(err)?{??????????printk(KERN_ALERT"Failed?to?setup?dev:?%d.\n",?err);??????????goto?cleanup;??????}??????????????????????hello_class?=?class_create(THIS_MODULE,?HELLO_DEVICE_CLASS_NAME);??????if(IS_ERR(hello_class))?{??????????err?=?PTR_ERR(hello_class);??????????printk(KERN_ALERT"Failed?to?create?hello?class.\n");??????????goto?destroy_cdev;??????}??????????????????????temp?=?device_create(hello_class,?NULL,?dev,?"%s",?HELLO_DEVICE_FILE_NAME);??????if(IS_ERR(temp))?{??????????err?=?PTR_ERR(temp);??????????printk(KERN_ALERT"Failed?to?create?hello?device.");??????????goto?destroy_class;??????}??????????????????????err?=?device_create_file(temp,?&dev_attr_val);??????if(err?<?0)?{??????????printk(KERN_ALERT"Failed?to?create?attribute?val.");??????????????????????????goto?destroy_device;??????}????????dev_set_drvdata(temp,?hello_dev);??????????????????????hello_create_proc();????????printk(KERN_ALERT"Succedded?to?initialize?hello?device.\n");??????return?0;????destroy_device:??????device_destroy(hello_class,?dev);????destroy_class:??????class_destroy(hello_class);????destroy_cdev:??????cdev_del(&(hello_dev->dev));????cleanup:??????kfree(hello_dev);????unregister:??????unregister_chrdev_region(MKDEV(hello_major,?hello_minor),?1);????fail:??????return?err;??}??????static?void?__exit?hello_exit(void)?{??????dev_t?devno?=?MKDEV(hello_major,?hello_minor);????????printk(KERN_ALERT"Destroy?hello?device.\n");??????????????????????hello_remove_proc();??????????????????????if(hello_class)?{??????????device_destroy(hello_class,?MKDEV(hello_major,?hello_minor));??????????class_destroy(hello_class);??????}??????????????????????if(hello_dev)?{??????????cdev_del(&(hello_dev->dev));??????????kfree(hello_dev);??????}??????????????????????unregister_chrdev_region(devno,?1);??}????MODULE_LICENSE("GPL");??MODULE_DESCRIPTION("First?Android?Driver");????module_init(hello_init);??module_exit(hello_exit);??
??? 五.在hello目錄中新增Kconfig和Makefile兩個文件,其中Kconfig是在編譯前執行配置命令make menuconfig時用到的,而Makefile是執行編譯命令make是用到的:
?? ? ? Kconfig文件的內容
?? ? ? config HELLO
?? ? ? ? ? tristate "First Android Driver"
?? ? ? ? ? default n
?? ? ? ? ? help
?? ? ? ? ? This is the first android driver.
?? ? ?Makefile文件的內容
?? ? ?obj-$(CONFIG_HELLO) += hello.o
?? ? ?在Kconfig文件中,tristate表示編譯選項HELLO支持在編譯內核時,hello模塊支持以模塊、內建和不編譯三種編譯方法,默認是不編譯,因此,在編譯內核前,我們還需要執行make menuconfig命令來配置編譯選項,使得hello可以以模塊或者內建的方法進行編譯。
?? ? ?在Makefile文件中,根據選項HELLO的值,執行不同的編譯方法。
?? ? ?六. 修改arch/arm/Kconfig和drivers/kconfig兩個文件,在menu "Device Drivers"和endmenu之間添加一行:
?? ? ?source "drivers/hello/Kconfig"
?? ? ? ?這樣,執行make menuconfig時,就可以配置hello模塊的編譯選項了。.?
七.?修改drivers/Makefile文件,添加一行:
obj-$(CONFIG_HELLO) += hello/
?? ? ? ?八. 配置編譯選項:
USER-NAME@MACHINE-NAME:~/Android/kernel/common$?make menuconfig
找到"Device Drivers" => "First Android Drivers"選項,設置為y。
注意,如果內核不支持動態加載模塊,這里不能選擇m,雖然我們在Kconfig文件中配置了HELLO選項為tristate。要支持動態加載模塊選項,必須要在配置菜單中選擇Enable loadable module support選項;在支持動態卸載模塊選項,必須要在Enable loadable module support菜單項中,選擇Module unloading選項。
九. 編譯:
USER-NAME@MACHINE-NAME:~/Android/kernel/common$?make
?? ? ? ?編譯成功后,就可以在hello目錄下看到hello.o文件了,這時候編譯出來的zImage已經包含了hello驅動。
十. 參照在Ubuntu上下載、編譯和安裝Android最新內核源代碼(Linux Kernel)一文所示,運行新編譯的內核文件,驗證hello驅動程序是否已經正常安裝:
USER-NAME@MACHINE-NAME:~/Android$?emulator -kernel ./kernel/common/arch/arm/boot/zImage &
?? ? ? ?USER-NAME@MACHINE-NAME:~/Android$ adb shell
進入到dev目錄,可以看到hello設備文件: root@android:/ # cd dev root@android:/dev # ls
?? ?進入到proc目錄,可以看到hello文件: root@android:/ # cd proc root@android:/proc # ls
訪問hello文件的值: root@android:/proc # cat hello 0
root@android:/proc # echo '5' > hello
root@android:/proc # cat hello 5
進入到sys/class目錄,可以看到hello目錄: root@android:/ # cd sys/class root@android:/sys/class # ls
進入到hello目錄,可以看到hello目錄: ?? ? ? ?root@android:/sys/class # cd hello ?? ? ? ?root@android:/sys/class/hello # ls 進入到下一層hello目錄,可以看到val文件:
root@android:/sys/class/hello # cd hello
root@android:/sys/class/hello/hello # ls 訪問屬性文件val的值:
root@android:/sys/class/hello/hello # cat val
5
root@android:/sys/class/hello/hello # echo '0' ?> val
root@android:/sys/class/hello/hello # cat val
0
至此,我們的hello內核驅動程序就完成了,并且驗證一切正常。這里我們采用的是系統提供的方法和驅動程序進行交互,也就是通過proc文件系統和devfs文件系統的方法,下一篇文章中,我們將通過自己編譯的C語言程序來訪問/dev/hello文件來和hello驅動程序交互,敬請期待。
總結
以上是生活随笔為你收集整理的在Ubuntu上为Android系统编写Linux内核驱动程序的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。