UbuntuではAndroidシステムにLinuxカーネルドライバの実現方法を編纂しています。


スマートフォンの時代には、各ブランドの携帯は個性的な特徴があります。このような一風変わった個性によってユーザーを引きつけ、ブランドの凝集力とユーザーの忠城度を作り上げるのです。典型的な代表は非iPhoneです。統計によると、2011年5月現在のAppStoreのアプリケーション数は381062件となり、1位はAndroid Marketのアプリケーション数は294738に達し、AppStoreの後を追い、8月にAppStoreを越える見込みです。Androidシステムが市場占有率を徐々に拡大するにつれて、端末設備の多様性は多くのモバイル開発者の参加を急ぐ必要がある。業界の統計によると、Androidの研究開発人材は少なくとも30万人不足しています。現在、Android人材に対する需要の1つは、ハードウェア駆動のAndroid人材の需要に偏っており、1つはソフトウェア応用のAndroid人材の需要に偏っている。全体としては、Androidのハードウェア駆動を志す開発エンジニアにとって、今は大きなチャンスです。じゃ、Androidシステムのカーネルドライバの作成方法を見てみましょう。
        ここでは、実際のハードウェア装置のためのカーネルドライバを作成しません。Androidシステムのカーネルドライバを作成するためのプロセスを説明するために、仮想ハードウェアデバイスを使用して、このデバイスは4バイトのレジスタしかありません。私たちが初めてプログラム言語を勉強した時、「ハロー、World」を例にとって好きでした。ここではこの仮想デバイスを「ハロー」と名づけました。このカーネルドライバもハロードライバと名づけました。実際には、Androidカーネルドライバは、一般的なLinuxカーネルドライバと同じ方法で、Linuxモジュールとして実装されています。具体的には、前のAndroid学習の開始文のLinux Device Driversを参照してください。しかし、ここではAndroidシステムの観点からAndroidカーネルドライバの作成とコンパイルの過程を説明します。
       一.前の2つの記事を参照してください。AndroidのソースはUbuntuでダウンロードして、コンパイルとインストールします。と、Androidカーネルドライバの開発環境を準備してください。
       二.ケネル/common/driversディレクトリに入り、helloディレクトリを新規作成する:
             USER-NAME@MACHINE-NAME:~/Android$cdケネル/common/drivers
             USER-NAME@MACHINE-NAME://Android/ケネル/common/drivers$mkdir hello
      三.ハローディレクトリにハロー.hファイルを追加する:

#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
        このヘッダファイルは、いくつかの文字列定数マクロを定義しています。あとで使用します。また、ハローハローキテクチャーも定義しています。android_dev、これは私たちの仮想ハードウェアデバイスです。valメンバー変数はデバイス内のレジスタを表しています。そのタイプはintで、semメンバー変数は信号量です。同期でレジスタvalにアクセスします。devメンバー変数は埋め込まれた文字デバイスです。このLinuxドライバはキャラクターデバイス構造体をカスタマイズする標準方法です。
        四.helloディレクトリにhello.cファイルを追加します。これはドライバの実現部分です。
        ドライバの機能は、主にデバイスにアクセスするレジスタの値を上位層に提供し、読み書きを含む。ここでは、3つのアクセスデバイスレジスタの方法が提供され、1つは、プロファイルシステムによってアクセスされること、2つは、従来のデバイスファイルの方法によってアクセスされること、3つは、devfsファイルシステムによってアクセスされることである。このドライバの実装については以下のセグメントで説明します。
         まず必要なヘッダファイルと3つのアクセスデバイスを定義する方法を含む。

#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);
 
   伝統的な設備ファイルのアクセス方法を定義します。主にハローを定義します。open、ハローrelease、ハローreadとhellowriteの4つのデバイスファイルを開く、リリースする、読み書きする方法:

/*      */
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;
}

/*        val  */
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;
	} 

	/*    val             */
	if(copy_to_user(buf, &(dev->val), sizeof(dev->val))) {
		err = -EFAULT;
		goto out;
	}

	err = sizeof(dev->val);

out:
	up(&(dev->sem));
	return err;
}

/*        val*/
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をデバイスの一つの属性と見なし、この属性を読み書きすることによってデバイスにアクセスし、主にハローキティを実現する。val_ショーとハローval_store 2つの方法は、2つの内部で使用されるval値へのアクセス方法を同時に定義しています。ハローゲットするvalと_uハローsetval:

/*     val      buf ,    */
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
", val); } /* buf 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; } /* val*/ 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); } /* val*/ 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ファイルシステムを通じたアクセス方法を定義し、主にハローワークを実現しました。procreadとhelloprocwriteの二つの方法は、プロファイルシステムでファイルを作成し削除する方法を同時に定義しました。クリアードprocとhelloレモーブproc:

/*       val  ,   page    */
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);
}

/*      buff        val  */
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.
", len); return -EFAULT; } page = (char*)__get_free_page(GFP_KERNEL); if(!page) { printk(KERN_ALERT"Failed to alloc page.
"); return -ENOMEM; } /* */ if(copy_from_user(page, buff, len)) { printk(KERN_ALERT"Failed to copy buff from user.
"); err = -EFAULT; goto out; } err = __hello_set_val(hello_dev, page, len); out: free_page((unsigned long)page); return err; } /* /proc/hello */ 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; } } /* /proc/hello */ static void hello_remove_proc(void) { remove_proc_entry(HELLO_DEVICE_PROC_NAME, NULL); }
 最後に、モジュールロードとアンインストール方法を定義します。ここでは、デバイス登録と初期化操作を実行する限り、

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;
	} 

	/*          val  */
	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.
"); /* */ err = alloc_chrdev_region(&dev, 0, 1, HELLO_DEVICE_NODE_NAME); if(err < 0) { printk(KERN_ALERT"Failed to alloc char dev region.
"); goto fail; } hello_major = MAJOR(dev); hello_minor = MINOR(dev); /* helo */ hello_dev = kmalloc(sizeof(struct hello_android_dev), GFP_KERNEL); if(!hello_dev) { err = -ENOMEM; printk(KERN_ALERT"Failed to alloc hello_dev.
"); goto unregister; } /* */ err = __hello_setup_dev(hello_dev); if(err) { printk(KERN_ALERT"Failed to setup dev: %d.
", err); goto cleanup; } /* /sys/class/ hello*/ 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.
"); goto destroy_cdev; } /* /dev/ /sys/class/hello hello*/ 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; } /* /sys/class/hello/hello val*/ 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); /* /proc/hello */ hello_create_proc(); printk(KERN_ALERT"Succedded to initialize hello device.
"); 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.
"); /* /proc/hello */ 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);
       五.ハローディレクトリにKconfigとMakefileの二つのファイルを追加します。ここでKconfigはコンパイル前にプロファイルコマンドMake menuconfigを実行する時に使います。Makefileはコンパイル命令makeを実行する時に使います。
       Kconfigファイルの内容
           config HELLO
           tristate「First Android Driver」
           default n
           help
           This the first android driver.
       Makefileファイルの内容     
       obj-$HELLO)+=ハロー.o
      Kconfigファイルでは、tristateはコンパイルオプションHELLOがカーネルのコンパイルをサポートしています。helloモジュールはモジュール、ビルド、コンパイルしないという三つのコンパイル方法をサポートしています。デフォルトはコンパイルされていません。したがって、カーネルのコンパイルを行う前に、私たちはまだmake menuconfigコマンドを実行してコンパイルオプションを配置する必要があります。
      Makefileファイルでは、オプションHELLOの値に応じて、異なるコンパイル方法が実行されます。
      六.arch/arm/Kconfigとdrivers/kconfigの二つのファイルを修正し、menu「Device Drivers」とendmenuの間に一行を追加します。 
           source「drivers/hello/Kconfig」   
       このように、make menuconfigを実行すると、ハローモジュールのコンパイルオプションが設定されます。
      七.DRivers/Makefileファイルを修正し、一行を追加する:    
           obj-$HELLO)+=hello/
       八.コンフィギュレーションのオプション:
        USER-NAME@MACHINE-NAME://Android/ケネル/common$make menuconfig     
        「Device Drivers」=>「First Android Drivers」のオプションを見つけてyに設定します。       
注意してください。カーネルがダイナミックロードモジュールをサポートしていないと、ここでmを選択できません。ただし、KconfigファイルにはHELLOオプションがtristateとして設定されています。動的ローディングモジュールオプションをサポートするには、設定メニューからEnbale loadable module supportオプションを選択しなければなりません。動的アンインストールモジュールオプションをサポートするには、必ずEnbale loadable module supportメニュー項目の中から、Module unloadingオプションを選択してください。
       九.コンパイル:     
      USER-NAME@MACHINE-NAME:~/Android/ケネル/common$make       
      コンパイルが成功したら、ハローディレクトリの下にハロー.oファイルが見えます。この時にコンパイルされたzImageにはハロードライバが含まれています。    
      10.UbuntuでAndroidの最新のカーネルソースコードをダウンロードしてコンパイルしてインストールすることを参照してください。新しいコンパイルのカーネルファイルを実行して、ハロードライバが正常にインストールされているかどうかを検証します。      
        USER-NAME@MACHINE-NAME://Android$emulator-kenel./ケネル/common/arch/boot/zImage&
        USER-NAME@MACHINE-NAME:~/Android$adb shell      
        devディレクトリに入ると、ハローデバイスファイルが見られます。      
        root@android:/菗cd dev
        root@android:/dev菵ls      
        procディレクトリに入ると、ハローファイルが見られます。   
        root@android:/菗cd proc
        root@android:/proc菗ls    
        ハローファイルの値にアクセス:    
        root@android:/proc菗cat hello
        0
        root@android:/proc玩echo'5'hello
        root@android:/proc菗cat hello
        5
        sys/classディレクトリに入ると、ハローディレクトリが見られます。
        root@android:/菗cd sys/class
        root@android:/sys/class〓〓ls
        ハローディレクトリに入ると、ハローディレクトリが見えます。
        root@android:/sys/class菗cd hello
        root@android:/sys/class/hello〓ls
        次の階のハローディレクトリに入ると、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    
  これで、私たちのハローカーネルドライバは完成しました。すべての正常性を検証します。ここではシステムが提供する方法とドライバを交互に使う方法を採用しています。つまり、プロファイルシステムとdevfsファイルシステムを通じて、次の文章では自分でコンパイルしたC言語プログラムを通じて/dev/helloファイルにアクセスして、helloドライバと交流します。ご期待ください。
 続いて関連した文章の資料を整理して、Androidのソースコードを研究する友達を助けることができることを望んで、みんなの支持に感謝します。